The value of "this" within the handler using addEventListener

JavascriptOopDom Events

Javascript Problem Overview


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);
    }

Javascript Solutions


Solution 1 - Javascript

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.

Solution 2 - Javascript

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

Solution 3 - Javascript

Also, one more way is to use the EventListener Interface (from DOM2 !! Wondering why no one mentioned it, considering it is the neatest way and meant for just such a situation.)

I.e, instead of a passing a callback function, You pass an object which implements EventListener Interface. Simply put, it just means you should have a property in the object called "handleEvent" , which points to the event handler function. The main difference here is, inside the function, this will refer to the object passed to the addEventListener. That is, this.theTicketTable will be the object instance in the belowCode. To understand what I mean, look at the modified code carefully:

ticketTable.prototype.render = function(element) {
...
var self = this;

/*
 * Notice that Instead of a function, we pass an object. 
 * It has "handleEvent" property/key. You can add other
 * objects inside the object. The whole object will become
 * "this" when the function gets called. 
 */

cell1.addEventListener('click', {
                                 handleEvent:this.handleCellClick,                  
                                 theTicketTable:this
                                 }, false);
...
};

// note the "event" parameter added.
ticketTable.prototype.handleCellClick = function(event)
{ 
   
    /*
     * "this" does not always refer to the event target element. 
     * It is a bad practice to use 'this' to refer to event targets 
     * inside event handlers. Always use event.target or some property
     * from 'event' object passed as parameter by the DOM engine.
     */
    alert(event.target.innerHTML);

    // "this" now points to the object we passed to addEventListener. So:

    alert(this.theTicketTable.tickets.length);
}

Solution 4 - Javascript

This arrow syntax works for me:

document.addEventListener('click', (event) => {
  // do stuff with event
  // do stuff with this 
});

this will be the parent context and not the document context.

Solution 5 - Javascript

With ES6, you can use an arrow function as that will use lexical scoping[0] which allows you to avoid having to use bind or self = this:

var something = function(element) {
  this.name = 'Something Good';
  this.onclick1 = function(event) {
    console.log(this.name); // 'Something Good'
  };
  element.addEventListener('click', () => this.onclick1());
}

[0] https://medium.freecodecamp.org/learn-es6-the-dope-way-part-ii-arrow-functions-and-the-this-keyword-381ac7a32881

Solution 6 - Javascript

I know this is an older post, but you can also simply assign the context to a variable self, throw your function in an anonymous function that invokes your function with .call(self) and passes in the context.

ticketTable.prototype.render = function(element) {
...
    var self = this;
    cell1.addEventListener('click', function(evt) { self.handleCellClick.call(self, evt) }, false);
...
};

This works better than the "accepted answer" because the context doesn't need to be assigned a variable for the entire class or global, rather it's neatly tucked away within the same method that listens for the event.

Solution 7 - Javascript

According to https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener ,

my_element.addEventListener('click', (e) => {
  console.log(this.className)           // WARNING: `this` is not `my_element`
  console.log(e.currentTarget === this) // logs `false`
})

so if you use the arrow functions you can go safe beacause they do not have their own this context.

Solution 8 - Javascript

What about

...
    cell1.addEventListener("click", this.handleCellClick.bind(this));
...

ticketTable.prototype.handleCellClick = function(e)
    {
        alert(e.currentTarget.innerHTML);
        alert(this.tickets.length);
    }

e.currentTarget points to the target which is bound to the "click event" (to the element that raised the event) while

bind(this) preserves the outerscope value of this inside the click event function.

If you want to get an exact target clicked, use e.target instead.

Solution 9 - Javascript

Heavily influenced by kamathln and gagarine's answer I thought I might tackle this.

I was thinking you could probably gain a bit more freedom if you put handeCellClick in a callback list and use an object using the EventListener interface on the event to trigger the callback list methods with the correct this.

function ticketTable(ticks)
    {
        // tickets is an array
        this.tickets = ticks;
        // the callback array of methods to be run when
        // event is triggered
        this._callbacks = {handleCellClick:[this._handleCellClick]};
        // assigned eventListenerInterface to one of this
        // objects properties
        this.handleCellClick = new eventListenerInterface(this,'handleCellClick');
    } 

//set when eventListenerInterface is instantiated
function eventListenerInterface(parent, callback_type) 
    {
        this.parent = parent;
        this.callback_type = callback_type;
    }

//run when event is triggered
eventListenerInterface.prototype.handleEvent(evt)
    {
        for ( var i = 0; i < this.parent._callbacks[this.callback_type].length; i++ ) {
            //run the callback method here, with this.parent as
            //this and evt as the first argument to the method
            this.parent._callbacks[this.callback_type][i].call(this.parent, evt);
        }
    }

ticketTable.prototype.render = function(element)
    {
       /* your code*/ 
        {
            /* your code*/

            //the way the event is attached looks the same
            cell1.addEventListener("click", this.handleCellClick, false);

            /* your code*/     
        }
        /* your code*/  
    }

//handleCellClick renamed to _handleCellClick
//and added evt attribute
ticketTable.prototype._handleCellClick = function(evt)
    {
        // this shouldn't work
        alert(this.innerHTML);
        // this however might work
        alert(evt.target.innerHTML);

        // this should work
        alert(this.tickets.length);
    }

Solution 10 - Javascript

The MDN explanation gives what to me is a neater solution further down.

In this example you store the result of the bind() call, which you can then use to unregister the handler later.

const Something = function(element) {
  // |this| is a newly created object
  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 bound to newly created object
  };

  // bind causes a fixed `this` context to be assigned to onclick2
  this.onclick2 = this.onclick2.bind(this);

  element.addEventListener('click', this.onclick1, false);
  element.addEventListener('click', this.onclick2, false); // Trick
}
const s = new Something(document.body);

In the posters example you would want to bind the handler function in the constructor:

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

    this.handleCellClick = this.handleCellClick.bind(this); // Note, this means that our handleCellClick is specific to our instance, we aren't directly referencing the prototype any more.
} 

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
            this.handleCellClick = this.handleCellClick.bind(this); // Note, this means that our handleCellClick is specific to our instance, we aren't directly referencing the prototype any more.
            cell1.addEventListener("click", this.handleCellClick, false);

            // We could now unregister ourselves at some point in the future with:
            cell1.removeEventListener("click", this.handleCellClick);

            // 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);

    }

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionDarthg8rView Question on Stackoverflow
Solution 1 - JavascriptgagarineView Answer on Stackoverflow
Solution 2 - JavascriptkangaxView Answer on Stackoverflow
Solution 3 - JavascriptkamathlnView Answer on Stackoverflow
Solution 4 - JavascriptMarkView Answer on Stackoverflow
Solution 5 - JavascriptChrisView Answer on Stackoverflow
Solution 6 - JavascriptTom DoeView Answer on Stackoverflow
Solution 7 - JavascriptJacopo StefanutoView Answer on Stackoverflow
Solution 8 - JavascriptDevWLView Answer on Stackoverflow
Solution 9 - Javascriptniall.campbellView Answer on Stackoverflow
Solution 10 - JavascriptWilcoView Answer on Stackoverflow