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);
}
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.
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).
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);
}
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.
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
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.
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.
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.
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);
}
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);
}
I am trying to run a simple function each time there is a change in the value of a custom data attribute of a DOM element.
Here is an example below
<div id="myDiv" data-type="type-1">
<!-- Some Content -->
</div>
In the HTML code above, i have a div with a custom data attribute of data-type with a value which i change using javascript. I would like to fire up a another function when ever the value of the attribute is changed depending on the value it holds.
For instance Using an if-statement(which doesn't work! 😒)
var myDiv = document.getElementById("myDiv");
var myDivAttr = myDiv.getAttribute("data-type");
if(myDivAttr == "type-1"){
typeOneFunction();
}
else if(myDivAttr == "type-2"){
typeTwoFunction();
}
// and so on...
I hope my question is clear enough😇😊
You can achieve this using Mutation Observers
// Select the node that will be observed for mutations
const targetNode = document.getElementById('myDiv');
// Options for the observer (which mutations to observe)
const config = { attributes: true };
// Callback function to execute when mutations are observed
const callback = function(mutationsList, observer) {
// Use traditional 'for loops' for IE 11
for(let mutation of mutationsList) {
if (mutation.type === 'attributes') {
if(myDivAttr == "type-1"){
typeOneFunction();
}
else if(myDivAttr == "type-2"){
typeTwoFunction();
}
}
}
};
// Create an observer instance linked to the callback function
const observer = new MutationObserver(callback);
// Start observing the target node for configured mutations
observer.observe(targetNode, config);
// Later, you can stop observing
observer.disconnect();
https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver
I didn't test that code*
I've seen from this post about Chromium DevTools that there exists the possibility to add DOM breakpoints. Given that I've a full range of elements to monitor I was trying to find a way to programmatically add such breakpoints. I also read this question about DOM breakpoints but it doesn't seem to give me any useful hint.
To achieve a similar result I've used to instrument the setAttribute() function of such DOM elements replacing it with a wrapper that uses the debugger; instruction to trigger the debugger. Anyway this approach fails when dealing with innerHTML or innerText assignments given that there is no way of achieving operator overloading in js.
Can someone suggest me a practical solution?
You may want to use MutationObserver, to observe for any change to a DOM at given root element. Also you can put debugger there and if devTools is open it should break.
const targetNode = document.getElementById('observed-element');
const config = { attributes: true, childList: true, subtree: true };
// Callback function to execute when mutations are observed
const callback = (mutationList, observer) => {
for (const mutation of mutationList) {
console.log(mutation.type);
console.log(mutation.target);
debugger;
}
};
// Create an observer instance linked to the callback function
const observer = new MutationObserver(callback);
// Start observing the target node for configured mutations
observer.observe(targetNode, config);
// change class
setTimeout(()=>{
targetNode.setAttribute('class', 'some-class')
}, 0);
// change innerText
setTimeout(()=>{
targetNode.innerText = 'some text';
}, 0);
<div id="observed-element">
</div>
You need to open Devtools-Over-Devtools and get references to instances of DOMModel and DOMDebuggerModel
// open Devtools (Ctrl+Shift+I)
// open DevtoolsOverDevtools (Ctrl+Shift+I in Devtools)
// open sdk.js from Ctrl+P pane
// set breakpoint in function setDOMBreakpoint(e, t)
// set HTML breakpoint in Devtools to pause on the created breakpoint
// inside setDOMBreakpoint(e, t)
window.domModel = e.domModel()
window.domDebuggerModel = this
// resume execution, disable breakpoint
_k = [...domModel.idToDOMNode.keys()][0]
_a = await domModel.querySelectorAll(_k, 'div')
_b = _a.map(e => domModel.idToDOMNode.get(e)).filter(Boolean)
_b.map(e => domDebuggerModel.setDOMBreakpoint(e, 'node-removed'))
// 'subtree-modified' | 'attribute-modified' | 'node-removed'
// now all elements are breakpointed
window.DEBUG = true; // toggles programmatic debugging
flag with a global check debug function, like so:
window.CHECK_DEBUG = function() {
if (window.DEBUG) { debugger; }
}
And then insert the following in any function you’re concerned about debugging:
function foo() {
CHECK_DEBUG();
// foo's usual procedure ...
}
To take this a step further (and to take a page out of Firebug's debug() and undebug() wrapper functions) you can decorate the native JavaScript Function object like so:
Function.prototype.debug = function(){
var fn = this;
return function(){
if (window.DEBUG) { debugger; }
return fn.apply(this, arguments);
};
};
Then you can dynamically debug any function by calling:
foo = foo.debug();
Is there any way to catch the document.createElement() event?
For example, somewhere, inside the <body> section I have
<script>
var div = document.createElement("div");
<script>
Is it possible to track that event from the <head> section (using some addEventListener, mutation observer, or any other way)?
Note: I need to track the creation of the element, not the insertion
Warning This code won't work in every browser. All bets are off when it comes to IE.
(function() {
// Step1: Save a reference to old createElement so we can call it later.
var oldCreate = document.createElement;
// Step 2: Create a new function that intercepts the createElement call
// and logs it. You can do whatever else you need to do.
var create = function(type) {
console.log("Creating: " + type);
return oldCreate.call(document, type);
}
// Step 3: Replace document.createElement with our custom call.
document.createElement = create;
}());
This is, similarly to other answers, an imperfect and incomplete solution (and is explicitly tested in only Chrome 34 on Windows 8.1):
// creating a function to act as a wrapper to document.createElement:
document.create = function(elType){
// creating the new element:
var elem = document.createElement(elType),
// creating a custom event (called 'elementCreated'):
evt = new CustomEvent('elementCreated', {
// details of the custom event:
'detail' : {
// what was created:
'elementType' : elem.tagName,
// a reference to the created node:
'elementNode' : elem
}
});
// dispatching the event:
this.dispatchEvent(evt);
// returning the created element:
return elem;
};
// assigning an event-handler to listen for the 'elementCreated' event:
document.addEventListener('elementCreated', function(e){
// react as you like to the creation of a new element (using 'document.create()'):
console.log(e);
});
// creating a new element using the above function:
var newDiv = document.create('div');
JS Fiddle demo.
References:
Creating and triggering events (MDN).
EventTarget.addEventListener().
EventTarget.dispatchEvent().
It's possible to create custom Events in javascript. And it's supported by all browsers too.
Check it out: http://jsfiddle.net/JZwB4/1/
document.createElement = (function(){
var orig = document.createElement;
var event = new CustomEvent("elemCreated");
return function() {
document.body.dispatchEvent(event);
orig.call(document,x);
};
})();
document.body.addEventListener('elemCreated', function(){
console.log('created');
},false);
var x= document.createElement('p'); //"created" in console
I want to instantiate a new MediaElementPlayer object. When it's successfully created, I want to pass the whole object on to another function (my_object.attachEvents). My code is as follows:
var options = {
success: function () {
//point 2
console.log("passing player object", local_player_instance);
my_main_object.attachEvents(local_player_instance);
}
}
//point 1
console.log('player inited', local_player_instance);
local_player_instance.video = new MediaElementPlayer('#video_player', options);
my_main_object.attachEvents = function(local_player_instance) {
local_player_instance.video.play()
}
In Firefox, the assignment at point one is executed before the line at point 2 calls the attach events method.
Im Chrome, point 2 is evaluate first, and as a result when the play method in the attach events function is called it doesn't exist.
My question is, how do I pass successfully pass the MediaElementPlayer to another function when it is created?
The best way to handle this in a cross browser way is
// here's where you'll store a global reference to the player
var globalMediaElement = null;
var options = {
success: function (domNode, mediaElement) {
globalMediaElement = mediaElement;
doStuff();
// you can also get the the player via jQuery here
$('#video_player').player
}
}
// create MediaElement
new MediaElementPlayer('#video_player', options);
function doStuff() {
globalMediaElement.play();
}