views:

704

answers:

1

I've created a javascript object via prototyping. I'm trying to render a table dynamically. While the rendering part is simple and works fine, I also need to handle certain client side events for the dynamically rendered table. That, also is easy. Where I'm having issues is with the "this" reference inside of the function that handles the event. Instead of "this" references the object, it's referencing the element that raised the event.

See code. The problematic area is in "ticketTable.prototype.handleCellClick = function()"

function ticketTable(ticks)
{
    // tickets is an array
    this.tickets = ticks;
} 

ticketTable.prototype.render = function(element)
    {
        var tbl = document.createElement("table");
        for ( var i = 0; i < this.tickets.length; i++ )
        {
            // create row and cells
            var row = document.createElement("tr");
            var cell1 = document.createElement("td");
            var cell2 = document.createElement("td");

            // add text to the cells
            cell1.appendChild(document.createTextNode(i));
            cell2.appendChild(document.createTextNode(this.tickets[i]));

            // handle clicks to the first cell.
            // FYI, this only works in FF, need a little more code for IE
            cell1.addEventListener("click", this.handleCellClick, false);

            // add cells to row
            row.appendChild(cell1);
            row.appendChild(cell2);


            // add row to table
            tbl.appendChild(row);            
        }

        // Add table to the page
        element.appendChild(tbl);
    }

    ticketTable.prototype.handleCellClick = function()
    {
        // PROBLEM!!!  in the context of this function, 
        // when used to handle an event, 
        // "this" is the element that triggered the event.

        // this works fine
        alert(this.innerHTML);

        // this does not.  I can't seem to figure out the syntax to access the array in the object.
        alert(this.tickets.length);
    }
+3  A: 

You need to "bind" handler to your instance.

var _this = this;
function onClickBound(e) {
  _this.handleCellClick.call(cell1, e || window.event);
}
if (cell1.addEventListener) {
  cell1.addEventListener("click", onClickBound, false);
}
else if (cell1.attachEvent) {
  cell1.attachEvent("onclick", onClickBound);
}

Note that event handler here normalizes event object (passed as a first argument) and invokes handleCellClick in a proper context (i.e. referring to an element that was attached event listener to).

Also note that context normalization here (i.e. setting proper this in event handler) creates a circular reference between function used as event handler (onClickBound) and an element object (cell1). In some versions of IE (6 and 7) this can, and probably will, result in a memory leak. This leak in essence is browser failing to release memory on page refresh due to circular reference existing between native and host object.

To circumvent it, you would need to either a) drop this normalization; b) employ alternative (and more complex) normalization strategy; c) "clean up" existing event listeners on page unload, i.e. by using removeEventListener, detachEvent and elements nulling (which unfortunately would render browsers' fast history navigation useless).

You could also find a JS library that takes care of this. Most of them (e.g.: jQuery, Prototype.js, YUI, etc.) usually handle cleanups as described in (c).

kangax
Where does var _this = this; in the context of my code go? Do I need to add onClickBound(e) to the prototype?
Darthg8r
In `render`, right before attaching event listener. You can pretty much just replace `addEventListener` line from original example with this snippet.
kangax
It's interesting that you mention clean up. I'll actually be destroying these objects at some point in the process as well. I had originally planned on just doing .innerHTML = ""; My guess is that is bad in this context. How would I destroy this tables and avoid the mentioned leak?
Darthg8r
As I said before, look into `removeEventListener`/`detachEvent` and breaking circular references. Here's a good explanation of a leak - http://www.jibbering.com/faq/faq_notes/closures.html#clMem
kangax