I recently ran into an issue on app-UI where child views were having their touch/mouse event listeners removed once another view was pushed onto the stack in the ViewNavigator component.
If you haven’t seen it yet, app-UI is a collection of reusable “application container” components for building native-like mobile experiences for apps built with web technologies. It is still in the very early stages of development, and I’m actively working on it… so please let me know if you run into any issues or suggestions.
I scoured through all of my application and framework code, but just couldn’t seem to figure out why/where event listeners were being removed. What I needed to do was simple, figure out where “removeEventListener” was being invoked. “removeEventListener” is a native function, so you can’t set a breakpoint to see every instance where it is being invoked, right?
Actually, you can, but you have to look at the problem just a bit differently…
You can’t set a breakpoint on a native function, however, since JavaScript uses prototypal inheritance you can change the prototype of an object to change its behavior and override native functions. Since you can modify an object prototype, changes to that object’s prototype will be applied to all instances of that object type. So, you can add some debugging code to override the behavior of HTMLElement’s “removeEventListener” function.
First, make a copy of the original removeEventListener function on the HTMLElement.prototype:
[js]HTMLElement.prototype.originalRemoveEventListener
= HTMLElement.prototype.removeEventListener[/js]
Next, override the original removeEventListener function, add a console.log() statement, and then invoke the original removeEventListener function that you just made a copy of:
[js]HTMLElement.prototype.removeEventListener = function(type, listener, useCapture)
{
console.log(‘remove: ‘ + type);
this.originalRemoveEventListener(type, listener, useCapture);
};[/js]
Now, every time that the HTMLElement’s removeEventListener function is invoked, you will get a console.log() statement. Not only do you get console debugging, but you can now set a breakpoint inside of the new, overridden, removeEventListener function. Using the breakpoint, you can use your developer tools to view the call stack and track down where the removeEventListener function is being invoked. Thus, you can track down the root of your issue. I use the Chrome developer tools, but similar tools are also available in Safari, FireFox, IE, and Opera.
Using the call stack, I was able to track down that I was inadvertently calling jQuery’s remove() function on the view’s DOM element, instead of jQuery’s detach() function. Both remove()
and detach()
will remove your elements from the page’s DOM, however remove()
also gets rid of any event listeners to prevent memory leaks. When I made the one-line code change from remove() to detach(), everything resumed working as expected. I then removed the debugging code.
Here’s the complete code in one snippet for overriding removeEventListener:
[js]HTMLElement.prototype.originalRemoveEventListener
= HTMLElement.prototype.removeEventListener;
HTMLElement.prototype.removeEventListener = function(type, listener, useCapture)
{
console.log(‘remove: ‘ + type);
this.originalRemoveEventListener(type, listener, useCapture);
};[/js]
Prototypal inheritance gives you the ability to change the behavior of objects at runtime, and it can be incredibly powerful in building your HTML/JS applications.