473,748 Members | 8,367 Online
Bytes | Software Development & Data Engineering Community
+ Post

Home Posts Topics Members FAQ

Problem with object references when using bound event handlers

Hi.

THE QUESTION: How do I get a reference to my Object when processing an
event handler bound to an html element ?

CONTEXT:
Sorry if it is a bit long.

I am developing a JS calendar tool. One of the requirements is that the
calendar will need to display a varying number of months (1..3)
depending on the calling page. Imagine 1, 2 or 3 calendar pages side by
side as required.

I have built a grid object that will contain one month's dates with the
day names at the top. The calendar object inherits the grid object as an
array of "calendar pages" - one grid per month and the calendar provides
the content for each grid. I will use the grid object for another
completely different object later and so I want to use good OOP
encapsulation. The grid is a table generated on the fly and is "dumb" as
far as what it is used for.

I have attached an onlick event to each cell of the grid. Using OOP
priciples I want the calling program (the calendar object in this case)
to provide a function to handle the click and the grid object will
provide to the calendar the row and column of that cell as well as the
grid number (so the calendar can work out which date was clicked since
it knows what the data means and the grid doesnt).

The following technique works:

// INITIALISE THE GRID
function Grid(gridNumb) {
this.gridNumb = gridNumb;
this.rows = 6;
this.cols = 7;
this.gridobj = $('grid_'+gridn umb) // a reference to the table that is
the grid

this.onclickHan dler = null
}

// ASSIGN THE ONCLICK FUNCTION PASSED IN
Grid.prototype. assignOnclickHa ndler = function(handle r) {
this.onclickHan dler = handler;
}

// ADD THAT HANDLER TO EACH CELL
Grid.prototype. addHandlers = function() {
for (r=0; r < this.rows; r++) {
for (c=0; c < this.cols; c++) {
this.gridObj.ro ws[r].cells[c].onclick = this.onclickHan dler
}
}
}

And if I do this on a test page:

var grid = new Array()
grid[0] = new Grid(0)
grid[0].assignOnclickH andler(handleCl ick)

function handleClick() {
alert(this) // this is a reference to the table cell that was click on
col = this.cellIndex
etc..
}
the handleClick function works and returns the reference to the table
cell that was clicked. All good.

BUT...

what I actually want to do is have the grid object return the row,
column and gridID number to the calling program instead of just a
reference to the table cell that was clicked.

So, I modified the above so that I am using an internal onclick handler
function that will do the necessary work to return the row, column and
gridID to the calling object.

ie

// INITIALISE THE GRID
function Grid(gridNumb) {
this.gridNumb = gridNumb;
this.rows = 6;
this.cols = 7;
this.gridobj = $('grid_'+gridn umb) // a reference to the table that is
the grid

// this.onclickHan dler = null <-- removed this
}

/* removed this
// ASSIGN THE ONCLICK FUNCTION PASSED IN
Grid.prototype. assignOnclickHa ndler = function(handle r) {
this.onclickHan dler = handler;
}
*/

// ADDED THIS INTERNAL HANDLER:
Grid.prototype. onclickHandler = function() {
alert(this.rows )
// 1. calculate the row, col and gridNumb ...

// 2. return those values ...

}
// ADD THAT HANDLER TO EACH CELL - SAME AS BEFORE
Grid.prototype. addHandlers = function() {
for (r=0; r < this.rows; r++) {
for (c=0; c < this.cols; c++) {
this.gridObj.ro ws[r].cells[c].onclick = this.onclickHan dler
}
}
}
Now, when a cell on the grid is clicked, the new internal onclick
function fires - which is correct.

THE PROBLEM:
The alert(this.rows ) in the internal onclick function shows "undefined"
because the "this" refers to the table cell element, not the grid object!

How do I get a reference to the grid object from that point ???

The obvious work-around is to use the external grid var directly but
apart from breaking the encapsulation when I have multiple grids I dont
know which one has been clicked since I cant reference anything about
the grid object itself.

The other solution is to set the id of each td element to contain the
grid number and use getElementById to get the reference, but I was
hoping to find an OOP way of doing it.
Any ideas ?

Thanks,
Murray
Feb 27 '07 #1
6 1825
On Feb 28, 9:45 am, Murray Hopkins <mur...@optusne t.com.auwrote:
Hi.

THE QUESTION: How do I get a reference to my Object when processing an
event handler bound to an html element ?
You want to set the hanlder's this keyword to reference an object
other than the element firing the event, you can use the function's
call method to set it - but there is a better strategy.

[...]
BUT...

what I actually want to do is have the grid object return the row,
column and gridID number to the calling program instead of just a
reference to the table cell that was clicked.

So, I modified the above so that I am using an internal onclick handler
function that will do the necessary work to return the row, column and
gridID to the calling object.

ie
e.g. :-)
>
// INITIALISE THE GRID
function Grid(gridNumb) {
this.gridNumb = gridNumb;
this.rows = 6;
this.cols = 7;
this.gridobj = $('grid_'+gridn umb) // a reference to the table that is
the grid
It would be if the capitalisation matched - gridnumb != gridNumb.

I'll guess that you have your own $() function that is a short-cut
wrapper for document.getEle mentById.
[...]
>
// ADDED THIS INTERNAL HANDLER:
Grid.prototype. onclickHandler = function() {
alert(this.rows )
// 1. calculate the row, col and gridNumb ...
// 2. return those values ...
}
// ADD THAT HANDLER TO EACH CELL - SAME AS BEFORE
Grid.prototype. addHandlers = function() {
for (r=0; r < this.rows; r++) {
for (c=0; c < this.cols; c++) {
this.gridObj.ro ws[r].cells[c].onclick = this.onclickHan dler
Another capitalisation error - gridObj != gridobj. It makes life much
easier if you post a "working" example.

Anyhow, here is where you want to set the onclick function's this
keyword, something like:

Grid.prototype. addHandlers = function() {

// For convenience
var grid = this;
var table = this.gridObj;

// Keep variables local, especially counters
for (var r=0; r < this.rows; r++) {
for (var c=0; c < this.cols; c++) {
table.rows[r].cells[c].onclick = function(){
grid.onclickHan dler.call(table );
}
}
}
}

A better strategy would be to add a single handler to the table, then
use the event object (event.target/srcElement) to find the cell that
was clicked on. The above creates a large number of closures and
exercises IE's memory leak by having a circular closure involving a
DOM element. Unless you manually remove the hanlders, you will
eventually have memory problems.

The number of rows and columns need not be set as properties of the
grid object since they can be retrieved from the table. That way if
you modify the table by adding or deleting rows or cells, you don't
have to update the corresponding grid object.
--
Rob

Feb 28 '07 #2
Hi Rob,

Thanks for your reply. Firstly, sorry about the capitalisation errors. I
was abbreviating a much long object for clarity and wasnt careful enough.

I have played around with your suggestion:

ie: table.rows[r].cells[c].onclick =
function(){grid .onclickHandler .call(table);}

and have learned a lot about the .call function - thanks.
A better strategy would be to add a single handler to the table, then
use the event object (event.target/srcElement) to find the cell that
was clicked on. The above creates a large number of closures and
exercises IE's memory leak by having a circular closure involving a
DOM element. Unless you manually remove the hanlders, you will
eventually have memory problems.
I am unsure about this. I understand what you are saying and have tried
it but cant figure out how to get the event object using mozilla when I
am in the handler within the grid object.

eg if I do this:
Grid.prototype. addHandlers = function() {
var table = this.gridObj
table.onclick = this.onclickHan dler
}

Grid.prototype. onclickHandler = function(ev) {
alert(this) // this = the table element object
var elem = eventTarget(ev) // cross browser function to get the
target alert("inhandle r:"+elem) // the target element object ie the td
object
}

it works but I dont have a reference to the grid object itself.

And if I do this:
Grid.prototype. addHandlers = function() {
var grid = this;
var table = this.gridObj
table.onclick = function(){grid .onclickHandler .call(table,gri d);}
}

Grid.prototype. onclickHandler = function(gridOb j) {
alert(this) // this = the table element object
alert("inhandle r:"+gridObj) // ie a reference to the grid object

// but no reference to the event object

}

it works but I dont have an event object to enable me to figure out how
to access the cell that was clicked.

For IE I could use the event.srcElemen t but how can I access the event
object with mozilla since it isnt passed in via the ev parameter ?
>
The number of rows and columns need not be set as properties of the
grid object since they can be retrieved from the table. That way if
you modify the table by adding or deleting rows or cells, you don't
have to update the corresponding grid object.
Indeed. That was part of the abbreviation for the example. The object
that calls the grid sets the dimensions of the grid dynamically (via a
method that I deleted from the example).

Thanks again,
Murray
Feb 28 '07 #3
On Feb 28, 2:34 pm, Murray Hopkins <mur...@optusne t.com.auwrote:
RobG wrote:
A better strategy would be to add a single handler to the table, then
use the event object (event.target/srcElement) to find the cell that
was clicked on. The above creates a large number of closures and
exercises IE's memory leak by having a circular closure involving a
DOM element. Unless you manually remove the hanlders, you will
eventually have memory problems.

I am unsure about this. I understand what you are saying and have tried
it but cant figure out how to get the event object using mozilla when I
am in the handler within the grid object.
Gecko browsers (and others) will pass a reference to the event object
as the first argument to the function called by the event. IE makes
it available as the global event object.

<URL: http://www.quirksmode.org/js/introevents.html >

eg if I do this:
Grid.prototype. addHandlers = function() {
var table = this.gridObj
table.onclick = this.onclickHan dler

}

Grid.prototype. onclickHandler = function(ev) {
alert(this) // this = the table element object
var elem = eventTarget(ev) // cross browser function to get the
target alert("inhandle r:"+elem) // the target element object ie the td
object

}

it works but I dont have a reference to the grid object itself.
Create a closure back to it. Some browsers will let you add a
reference to the object to the DOM element, but not all (or even
enough).
>
And if I do this:
Grid.prototype. addHandlers = function() {
var grid = this;
var table = this.gridObj
table.onclick = function(){grid .onclickHandler .call(table,gri d);}

}

Grid.prototype. onclickHandler = function(gridOb j) {
alert(this) // this = the table element object
alert("inhandle r:"+gridObj) // ie a reference to the grid object

// but no reference to the event object

}

it works but I dont have an event object to enable me to figure out how
to access the cell that was clicked.

For IE I could use the event.srcElemen t but how can I access the event
object with mozilla since it isnt passed in via the ev parameter ?
See code below for addHandler() - note no 's'.

The number of rows and columns need not be set as properties of the
grid object since they can be retrieved from the table. That way if
you modify the table by adding or deleting rows or cells, you don't
have to update the corresponding grid object.

Indeed. That was part of the abbreviation for the example. The object
that calls the grid sets the dimensions of the grid dynamically (via a
method that I deleted from the example).
I wouldn't set it at all, just get it from the table if or when you
need it.
Here's my test example:

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<head><title>Hi </title>
<style type="text/css">
table {
border-collapse: collapse;
border-top: 1px solid blue;
border-left: 1px solid blue;
}
td {
border-bottom: 1px solid blue;
border-right: 1px solid blue;
}
</style>
<script>

function $(el) {
return (typeof el=='string')? document.getEle mentById(el) : el;
}

function Grid(gridNumb) {
this.gridNumb = gridNumb;
this.gridObj = $('grid_'+gridN umb);
}

Grid.prototype. onclickHandler = function() {
alert(this.rows )
}

// Old function
Grid.prototype. addHandlers = function() {
var grid = this;
var table = this.gridObj;
var row;

// Get num rows and cells from table, don't store in object
for (var r=0, len=table.rows. length; r<len; r++) {
row = table.rows[r]
for (var c=0, len2=row.cells. length; c<len2; c++) {
row.cells[c].onclick = function(){
grid.onclickHan dler.call(table );
}
}
}
}

// New function
Grid.prototype. addHandler = function()
{
var grid = this;
grid.gridObj.on click = function(e) {
var e = e || window.event;
var tgt = e.target || e.srcElement;

// Fix to get type 1 if type 3 (text node) returned
while(tgt.nodeT ype != 1) tgt = tgt.parentNode;

// Here's the element
alert( tgt.textContent || tgt.innerText );

// Closure back to the grid object
alert('grid is an ' + typeof grid);
}
}
window.onload = function(){
var x = new Grid('0');
x.addHandler();
}
</script>

</head>
<body>

<table id="grid_0">
<tr><td>cell 0 0<td>cell 0 1<td>cell 0 2
<tr><td>cell 1 0<td>cell 1 1<td>cell 1 2
</table>

</body>
--
Rob
Feb 28 '07 #4
Thanks to Rob for his explanation. Here is a working example of the
solution. Tested on IE 6 and FF 2. I have included comments to explain
what is going on. The layout isnt good here so copy and paste into your
editor.

This is a cut down version of the actual object to highlight the
methodology of attaching an object to a html element so that you can get
a reference to the object when it is clicked. This could be extended to
mousing over etc.

This is especially useful where the object creates the html element that
it is bound to.

Cheers,
Murray

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
<title>Untitled </title>

<script type='text/javascript'>
function Grid(gridNumb,i dName) {
this.rows = 1;
this.cols = 1;
this.gridNumb = gridNumb;
this.containerO bj = document.getEle mentById(idName );
this.gridhtml = '';
this.onclickHan dlerExt = null;
this.tableObj = null;
}

Grid.prototype. setDimensions = function(r,c) {
this.rows = r;
this.cols = c;
}

// Create a table of the specified dimensions then display it
Grid.prototype. create = function() {
var txt = ''
txt += '<table border=1 id="_grid_' + this.gridNumb + '">'
for (r=0; r < this.rows; r++) {
txt += '<tr>';
for (c=0; c < this.cols; c++) {
cellNumb = (r*this.cols) + c
txt += '<td>' + cellNumb + '</td>';
}
txt += '</tr>';
}
txt += '</table>'

this.containerO bj.innerHTML = txt
// Save the object reference to this table
this.tableObj = document.getEle mentById("_grid _" + this.gridNumb)
}

// Allow the calling page to specify a function that will
// execute when a cell is clicked
Grid.prototype. assignOnClickHa ndlerExternal = function(handle r) {
this.onclickHan dlerExternal = handler;
}
// Add an event handler to the table. This is the key function in
this example
Grid.prototype. addHandler = function() {
var thisgrid = this;
var table = thisgrid.tableO bj;
table.onclick =
function(ev){th isgrid.onclickH andler.call(tab le,ev,thisgrid) ;}

/* NOTES:
1. Using function(ev) passes the mozilla event object to the function
onclickHandler when
the table is clicked. Not required by IE.
2. Prefixing the onclickHandler with thisgrid (thisgrid.oncli ckHandler)
sets the scope to this instance of the grid object
3. Using call (thisgrid.oncli ckHandler.call) ensures that the function
onclickHandler is part
of this instance of the grid object
4. The parameters to call:
table: a reference to the table object
ev: the mouse event
thisgrid: a reference to this instance of the grid object
*/

}
// This is the function called by the table onclick event handler
set above
Grid.prototype. onclickHandler = function(ev,thi sGrid) {

// Just to show that the "this" here is a reference to
// the table object (passed as the first parameter in the .call above)
// rather than a reference to the grid object itself
var table = this

// Cross browser function to get the elememt that was clicked (see
below)
var eventDetails = eventTarget(ev)

// Extract the various bits of information that might be used later
var td = eventDetails.el em
var evnt = eventDetails.ev
var col = td.cellIndex
var row = td.parentNode.r owIndex
var cellInfoObj = {tableObj:thisG rid, row:row, col:col, cell:td}

// Return the extracted data to the external event hanlder function
// plugged in when the object was created
thisGrid.onclic kHandlerExterna l(evnt,cellInfo Obj)
}

// Not a method
function eventTarget(e){
// Get the element object that triggered the event
// After Goodman - Dynamic HTML Definitive Reference V2
e = (e) ? e : ((event) ? event : null);
var elem = null
if (e) { elem = (e.target) ? e.target : ((e.srcElement) ?
e.srcElement : null); }
// Return both the element that was clicked and the event object
itself
return {elem:elem, ev:e}
}

</script>
<script type='text/javascript'>
var grid = null
function processLoad() {
// Create the grid object
grid = new Grid(0,'_grid_0 _container');
grid.setDimensi ons(6,8);
// Assign the onclick event handler (see below)
grid.assignOnCl ickHandlerExter nal(handleClick );
// Create the grid table
grid.create();
// Add the event handler we passed in above
grid.addHandler ()
}
// This is the function that receives the data about the cell that
was clicked
// ie the end point of the onclick event
// Just display the results
function handleClick(eve ntObj,cellInfo) {
var txt = ''
txt += 'this = a reference to the grid object iteself' + '<br>'
txt += 'gridNumb = ' + cellInfo.tableO bj.gridNumb + '<br>'
txt += 'eventObj = ' + eventObj + '<br>'
txt += 'row = '+cellInfo.row + '<br>'
txt += 'col = '+cellInfo.col + '<br>'
txt += 'td = ' +cellInfo.cell + '<br>'
txt += 'td content = ' +cellInfo.cell. innerHTML + '<br>'
document.getEle mentById("displ ayresults").inn erHTML = txt
}
</script>

</head>

<body onload="process Load()">
Grid example. Click a cell
<div id="_grid_0_con tainer"></div>
Results:
<div id="displayresu lts" style="border:1 px solid silver;
width:300px;"></div>
</body>
</html>

Feb 28 '07 #5
See the Solution I posted.
Feb 28 '07 #6
A small adjustment to make the variables local.

I said:
for (r=0; r < this.rows; r++) {
txt += '<tr>';
for (c=0; c < this.cols; c++) {
cellNumb = (r*this.cols) + c
txt += '<td>' + cellNumb + '</td>';
}
txt += '</tr>';
}
Should use var :
for (var r=0; r < this.rows; r++) {
txt += '<tr>';
for (var c=0; c < this.cols; c++) {
cellNumb = (r*this.cols) + c
txt += '<td>' + cellNumb + '</td>';
}
txt += '</tr>';
}

Slack.
Murray
Feb 28 '07 #7

This thread has been closed and replies have been disabled. Please start a new discussion.

Similar topics

2
1564
by: D. Patterson | last post by:
Hello all. I've a bit of a problem. I would like to use a method in an object as an event handler. Unfortunately, when the method is called the "this" object references the event trigger rather than my object. For example, with the following code, an alert will be shown when the page loads where the value of my_name is "My Object" and nodeName is 'undefined". Dismissing
38
5222
by: VK | last post by:
Hello, In my object I have getDirectory() method which returns 2-dimentional array (or an imitation of 2-dimentional array using two JavaScript objects with auto-handled length property - please let's us do not go into an "each dot over i" clarification discussion now - however you want to call - you call it ;-) array contains records of all files in the current dir. array contains records of all subs in the current dir
9
1828
by: Peter Krikelis | last post by:
Hi, Finally figured events and delegates whew! Thanks to the people in this community that helped me out. So now I have a class that raises an event. Now if I instantiate an object array of that class, and the event fires, is there any way of getting the array index of the object that raised that event?
5
1440
by: Raj Chudasama | last post by:
I have a client server app. On the client i have buttons (derived from user contol) that let user perform actions when they are connected to the server. so when they connect (using a menuitem) i add event handlers to the buttons and when they disconnect i remove them. Now, everything works fine when they connnect for the first time. All of the msgs are sent propoerly by the client. To figure out what button is click by the client i used...
10
4027
by: Charles Law | last post by:
For some reason, when I click the X to close my MDI parent form, the action appears to be re-directed to one of the MDI child forms, and the parent remains open. I am then unable to close the application. What should happen, is that the main MDI form should close, taking the child forms with it. There is code to loop through the child forms, remove the controls on each of them, and then close the form, but this code should execute only...
16
2898
by: anonymous.user0 | last post by:
The way I understand it, if I have an object Listener that has registered as a listener for some event Event that's produced by an object Emitter, as long as Emitter is still allocated Listener will stay alive. Is this correct? If this is correct, I've got a problem. Let's say I've got an object Customer that has an PurchaseList (Collection) of Purchase objects. Now, these Purchase objects were pulled from a datasource Datasource. The...
2
2525
by: ian | last post by:
Hi, I've got the weirdest garbage collection problem - I'd appreciate any advice you've got. 1. A class 'X' in a system I'm working on contains a reference to an XmlDocument, populated via LoadXML. 2. Objects of type X are reused alot so different XML is loaded numerous times. 3. If I keep this reference, the system looses more and more memory until it
6
2011
by: Joel | last post by:
2 Questions: (1) The documentation says application.run() creates a standard message loop on the current thread and "optionally" shows a form. This is really confusing because I was of the understanding that application.run() creates a message loop for the form and passes all messages to it. If showing the form is optional, and I want to to display 2 forms, which form will application.run() pass the windows messages to?
6
2478
by: Shailen Sukul | last post by:
Observed a weird behaviour with object references. See code listing below: using System; using System.Collections.Generic; using System.Text; namespace PointerExceptionTest { /*
0
8984
marktang
by: marktang | last post by:
ONU (Optical Network Unit) is one of the key components for providing high-speed Internet services. Its primary function is to act as an endpoint device located at the user's premises. However, people are often confused as to whether an ONU can Work As a Router. In this blog post, we’ll explore What is ONU, What Is Router, ONU & Router’s main usage, and What is the difference between ONU and Router. Let’s take a closer look ! Part I. Meaning of...
0
8823
by: Hystou | last post by:
Most computers default to English, but sometimes we require a different language, especially when relocating. Forgot to request a specific language before your computer shipped? No problem! You can effortlessly switch the default language on Windows 10 without reinstalling. I'll walk you through it. First, let's disable language synchronization. With a Microsoft account, language settings sync across devices. To prevent any complications,...
0
9530
Oralloy
by: Oralloy | last post by:
Hello folks, I am unable to find appropriate documentation on the type promotion of bit-fields when using the generalised comparison operator "<=>". The problem is that using the GNU compilers, it seems that the internal comparison operator "<=>" tries to promote arguments from unsigned to signed. This is as boiled down as I can make it. Here is my compilation command: g++-12 -std=c++20 -Wnarrowing bit_field.cpp Here is the code in...
0
9363
jinu1996
by: jinu1996 | last post by:
In today's digital age, having a compelling online presence is paramount for businesses aiming to thrive in a competitive landscape. At the heart of this digital strategy lies an intricately woven tapestry of website design and digital marketing. It's not merely about having a website; it's about crafting an immersive digital experience that captivates audiences and drives business growth. The Art of Business Website Design Your website is...
0
8237
agi2029
by: agi2029 | last post by:
Let's talk about the concept of autonomous AI software engineers and no-code agents. These AIs are designed to manage the entire lifecycle of a software development project—planning, coding, testing, and deployment—without human intervention. Imagine an AI that can take a project description, break it down, write the code, debug it, and then launch it, all on its own.... Now, this would greatly impact the work of software developers. The idea...
1
6793
isladogs
by: isladogs | last post by:
The next Access Europe User Group meeting will be on Wednesday 1 May 2024 starting at 18:00 UK time (6PM UTC+1) and finishing by 19:30 (7.30PM). In this session, we are pleased to welcome a new presenter, Adolph Dupré who will be discussing some powerful techniques for using class modules. He will explain when you may want to use classes instead of User Defined Types (UDT). For example, to manage the data in unbound forms. Adolph will...
0
4593
by: TSSRALBI | last post by:
Hello I'm a network technician in training and I need your help. I am currently learning how to create and manage the different types of VPNs and I have a question about LAN-to-LAN VPNs. The last exercise I practiced was to create a LAN-to-LAN VPN between two Pfsense firewalls, by using IPSEC protocols. I succeeded, with both firewalls in the same network. But I'm wondering if it's possible to do the same thing, with 2 Pfsense firewalls...
0
4864
by: adsilva | last post by:
A Windows Forms form does not have the event Unload, like VB6. What one acts like?
3
2206
bsmnconsultancy
by: bsmnconsultancy | last post by:
In today's digital era, a well-designed website is crucial for businesses looking to succeed. Whether you're a small business owner or a large corporation in Toronto, having a strong online presence can significantly impact your brand's success. BSMN Consultancy, a leader in Website Development in Toronto offers valuable insights into creating effective websites that not only look great but also perform exceptionally well. In this comprehensive...

By using Bytes.com and it's services, you agree to our Privacy Policy and Terms of Use.

To disable or enable advertisements and analytics tracking please visit the manage ads & tracking page.