Problem with object references when using bound event handlers

Discussion in 'Javascript' started by Murray Hopkins, Feb 27, 2007.

  1. 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_'+gridnumb) // a reference to the table that is
    the grid

    this.onclickHandler = null
    }

    // ASSIGN THE ONCLICK FUNCTION PASSED IN
    Grid.prototype.assignOnclickHandler = function(handler) {
    this.onclickHandler = 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.rows[r].cells[c].onclick = this.onclickHandler
    }
    }
    }

    And if I do this on a test page:

    var grid = new Array()
    grid[0] = new Grid(0)
    grid[0].assignOnclickHandler(handleClick)

    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_'+gridnumb) // a reference to the table that is
    the grid

    // this.onclickHandler = null <-- removed this
    }

    /* removed this
    // ASSIGN THE ONCLICK FUNCTION PASSED IN
    Grid.prototype.assignOnclickHandler = function(handler) {
    this.onclickHandler = 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.rows[r].cells[c].onclick = this.onclickHandler
    }
    }
    }


    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
    Murray Hopkins, Feb 27, 2007
    #1
    1. Advertising

  2. Murray Hopkins

    RobG Guest

    On Feb 28, 9:45 am, Murray Hopkins <> wrote:
    > 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_'+gridnumb) // 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.getElementById.


    [...]
    >
    > // 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.rows[r].cells[c].onclick = this.onclickHandler


    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.onclickHandler.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
    RobG, Feb 28, 2007
    #2
    1. Advertising

  3. 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.onclickHandler
    }

    Grid.prototype.onclickHandler = function(ev) {
    alert(this) // this = the table element object
    var elem = eventTarget(ev) // cross browser function to get the
    target alert("inhandler:"+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,grid);}
    }

    Grid.prototype.onclickHandler = function(gridObj) {
    alert(this) // this = the table element object
    alert("inhandler:"+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.srcElement 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
    Murray Hopkins, Feb 28, 2007
    #3
  4. Murray Hopkins

    RobG Guest

    On Feb 28, 2:34 pm, Murray Hopkins <> wrote:
    > 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.onclickHandler
    >
    > }
    >
    > Grid.prototype.onclickHandler = function(ev) {
    > alert(this) // this = the table element object
    > var elem = eventTarget(ev) // cross browser function to get the
    > target alert("inhandler:"+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,grid);}
    >
    > }
    >
    > Grid.prototype.onclickHandler = function(gridObj) {
    > alert(this) // this = the table element object
    > alert("inhandler:"+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.srcElement 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.getElementById(el) : el;
    }

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

    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.onclickHandler.call(table);
    }
    }
    }
    }

    // New function
    Grid.prototype.addHandler = function()
    {
    var grid = this;
    grid.gridObj.onclick = 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.nodeType != 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
    RobG, Feb 28, 2007
    #4
  5. 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,idName) {
    this.rows = 1;
    this.cols = 1;
    this.gridNumb = gridNumb;
    this.containerObj = document.getElementById(idName);
    this.gridhtml = '';
    this.onclickHandlerExt = 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.containerObj.innerHTML = txt
    // Save the object reference to this table
    this.tableObj = document.getElementById("_grid_" + this.gridNumb)
    }

    // Allow the calling page to specify a function that will
    // execute when a cell is clicked
    Grid.prototype.assignOnClickHandlerExternal = function(handler) {
    this.onclickHandlerExternal = 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.tableObj;
    table.onclick =
    function(ev){thisgrid.onclickHandler.call(table,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.onclickHandler)
    sets the scope to this instance of the grid object
    3. Using call (thisgrid.onclickHandler.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,thisGrid) {

    // 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.elem
    var evnt = eventDetails.ev
    var col = td.cellIndex
    var row = td.parentNode.rowIndex
    var cellInfoObj = {tableObj:thisGrid, row:row, col:col, cell:td}

    // Return the extracted data to the external event hanlder function
    // plugged in when the object was created
    thisGrid.onclickHandlerExternal(evnt,cellInfoObj)
    }

    // 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.setDimensions(6,8);
    // Assign the onclick event handler (see below)
    grid.assignOnClickHandlerExternal(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(eventObj,cellInfo) {
    var txt = ''
    txt += 'this = a reference to the grid object iteself' + '<br>'
    txt += 'gridNumb = ' + cellInfo.tableObj.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.getElementById("displayresults").innerHTML = txt
    }


    </script>

    </head>

    <body onload="processLoad()">
    Grid example. Click a cell
    <div id="_grid_0_container"></div>
    Results:
    <div id="displayresults" style="border:1px solid silver;
    width:300px;"></div>
    </body>
    </html>
    Murray Hopkins, Feb 28, 2007
    #5
  6. See the Solution I posted.
    Murray Hopkins, Feb 28, 2007
    #6
  7. 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
    Murray Hopkins, Feb 28, 2007
    #7
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. sunil panda

    Lower bound & Upper bound

    sunil panda, Dec 25, 2003, in forum: Java
    Replies:
    9
    Views:
    7,206
    thushara wijeratna
    Oct 7, 2008
  2. Matthias Kaeppler

    Object lifetimes when bound to references

    Matthias Kaeppler, Apr 2, 2005, in forum: C++
    Replies:
    6
    Views:
    339
    Victor Bazarov
    Apr 4, 2005
  3. Phillip Vong
    Replies:
    0
    Views:
    408
    Phillip Vong
    Jul 27, 2006
  4. Freyr
    Replies:
    1
    Views:
    225
    bobicanprogram
    Jul 16, 2009
  5. D. Patterson

    Problem using object methods as event handlers

    D. Patterson, Nov 4, 2005, in forum: Javascript
    Replies:
    2
    Views:
    98
    D. Patterson
    Nov 4, 2005
Loading...

Share This Page