Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

The value of "this" within the handler using addEventListener

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);     } 
like image 572
Darthg8r Avatar asked Aug 27 '09 02:08

Darthg8r


People also ask

What is this inside addEventListener?

Every scope in JavaScript has a this object that represents the calling object for the function. That's the reason why this inside addEventListener callback is invoked on the context of the current element instead of the global object. Refer below code for more clear understanding: function sayNameForAll() { console.

Does addEventListener bind this?

addEventListener cannot bind this again. You can call bind multiple times. Even if you call it on a bound function, you can perform partial application.

Can addEventListener return a value?

You can't. JavaScript isn't capable of time travel. The event handler function won't run until the event happens. By that time, the function that called addEventHandler will have finished running and returned.

Is addEventListener an event handler?

The addEventListener() method attaches an event handler to the specified element. The addEventListener() method attaches an event handler to an element without overwriting existing event handlers. You can add many event handlers to one element.


2 Answers

You can use bind which lets you specify the value that should be used as this for all calls to a given function.

   var Something = function(element) {       this.name = 'Something Good';       this.onclick1 = function(event) {         console.log(this.name); // undefined, as this is the element       };       this.onclick2 = function(event) {         console.log(this.name); // 'Something Good', as this is the binded Something object       };       element.addEventListener('click', this.onclick1, false);       element.addEventListener('click', this.onclick2.bind(this), false); // Trick     } 

A problem in the example above is that you cannot remove the listener with bind. Another solution is using a special function called handleEvent to catch any events:

var Something = function(element) {   this.name = 'Something Good';   this.handleEvent = function(event) {     console.log(this.name); // 'Something Good', as this is the Something object     switch(event.type) {       case 'click':         // some code here...         break;       case 'dblclick':         // some code here...         break;     }   };    // Note that the listeners in this case are this, not this.handleEvent   element.addEventListener('click', this, false);   element.addEventListener('dblclick', this, false);    // You can properly remove the listners   element.removeEventListener('click', this, false);   element.removeEventListener('dblclick', this, false); } 

Like always mdn is the best :). I just copy pasted the part than answer this question.

like image 86
gagarine Avatar answered Sep 23 '22 09:09

gagarine


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).

like image 22
kangax Avatar answered Sep 23 '22 09:09

kangax