Avoiding memory leaks winjs EventListeners - javascript

I want to know if I add an event listener to a button, do I have to remove it on unload? Would pressing the 'back' button automatic removes everything current page elements in which I don't need to worry about memory leaks?
(function () {
"use strict";
ui.Pages.define("/pages/registraton/registraton.html",{
ready: function (element, options) {
document.getElementById("submitRegister").addEventListener(
"click", postRegistration , false);
},
unload: function () {
document.getElementById("submitRegister").removeEventListener(
"click", postRegistration, false);
}
});...
Thanks in advance.

You need to worry about memory leaks in the single-page navigation model that the WinJS.Navigation namespace promotes.
The model you've set up -- where by you implement unload -- is definitely the right approach. How complex & deep you want to get depends on the complexity of your application. Specifically, if you have multiple controls, with multiple manual event handlers you may want to create a set of helpers to enable you to clean up those handlers in one swoop. This may be as simple as pushing element, event name, and the handler instance into an array when when leaving that page and destroying/removing it from the DOM, you can just burn through the array removing the items that need to be cleaned up.
Note that you need to only need to explicitly clean up the case where the handler, and the DOM object have different life times. If they go away together -- e.g. a control attached to a DOM element in the page then you don't have to clean up the everything explicitly. The Garbage Collector will eventually clean it up. If you are a particularly memory heavy application, you may get some wins here by removing the listeners more aggressively.
There are some other things to remember:
This also applies to pure javascript objects that implement the addEventListener contract i.e. the list view
Don't use attachEvent -- it's going to cause unbreakable cycles due to it's old implementation under the covers. It is actually a deprecated API, so shouldn't be used anyway
Be wary when you supply event handlers where you've bound the this pointer, when your trying to unbind them. E.g.
Example:
var element = getInterestingElement();
element.addEventListener("click", this.handleClick.bind(this));
If you try to detach the event, you're lost -- the return valud from the .bind() is lost in the wind, and you'll never be able to unhook it:
var element = getInterestingElement();
element.removeEventListener("click", this.handleClick); // Won't remove the listener
element.removeEventListener("click", this.handleClick.bind(this)); // Won't remove, since it's a different function object
The best solution here is to either monkey patch handleClick before attaching it:
this.handleClick = this.handleClick.bind(this);
Or store it away for later use:
this.handlerClickToCleanup = this.handleClick.bind(this);
element.addEventListener("click", this.handleClickToCleanup);

Related

How to Get Event Listener of an Element

Is there any way to get the list of all event listeners of an element on the HTML page using JavaScript on that page.
Note: I know we can see them with Chrome dev tools event listeners but I want to log/access see list using the JavaScript of the page.
Also, I know we can get them through jQuery but for that, we also have to apply the events using jQuery, but I want something that would be generic so I could also access the event listeners applied to other elements such as web components or react components.
If you really had to, a general way to do this would be to patch EventTarget.prototype.addEventListener:
const listeners = [];
const orig = EventTarget.prototype.addEventListener;
EventTarget.prototype.addEventListener = function(...args) {
if (this instanceof HTMLElement) {
listeners.push({
type: args[0],
fn: args[1],
target: this,
});
}
return orig.apply(this, args);
};
document.body.addEventListener('click', () => console.log('body clicked'));
console.log(listeners[0].fn);
click this body
To find listeners attached to an element, iterate through the listeners array and look for targets which match the element you're looking for.
To be complete, also patch removeEventListener so that items can be removed from the array when removed.
If you need to watch for listeners attached via on, then you'll have to do something similar to the above to patch the HTMLElement.prototype.onclick getter/setter, and for each listener you want to be able to detect.
That said, although you said you want a generic solution, rather than patching built-in prototypes, it'd be better to add the listeners through jQuery or through your own function.
What I did when I had a similar problem is add a data attribute when the listener was set, so I could identify it later.
At the end of the function that adds the listener:
elm.setAttribute('data-has_mask', true);
At the beginning of that same function:
if("true" == elm.getAttribute('data-has_mask')) {
return;
}
Maybe not exactly what the OP is looking for, but I was having a lot of trouble with this, and this is an obvious solution for a particular use case, and I guess it might help someone out.

Calling a service using EventListeners

Disclaimer: Title is not super exact.
I have a service with a public method openCamera that calls a library in the global object and attaches eventlisteners to its HTML elements. When I call this service method from the HTML elements events, it works fine, but not when I call it via events attached to the window element. An example below:
class ImageService {
public static AttachEventOnce = false;
public openCamera() {
let camera = new JpegCamera('myContainer');
// captureBtn and videoBox are HTML elements generated from the library once instantiated
camera.captureBtn.addEventListener('click', () => this.openCamera()); // this works fine
camera.videoBox.addEventListener('resize', () => this.openCamera()); // doesn't enter here ie not calling it
if (!ImageService.AttachEventOnce) {
var that = this;
window.addEventListener('resize', () => that.openCamera()); // the buttons stop working
ImageService.AttachEventOnce = true;
}
};
}
The logic have been somewhat minified but more or less the same. I just want to call the service method again and again when window is resized. I don't care where I attach the listener (HTML element generated from the library or window).
My take: The window seem to retain the older object reference too as well as other listeners for other buttons which I think is causing the issue.
The window seem to retain the older object reference too as well as other listerers for other buttons which I think is causing the issue.
Correct — not because it's on window, but because you're using an arrow function and only hooking the event once, on the first instance where openCamera is called. So it doesn't matter whether that instance is discarded by everything else, it's the only instance that will receive that resize event. (There's also no reason for the var that = this; thing and then using that inside the function; arrow functions close over this like they do variables, so it does exactly what just using this within the function would do.)
It's not clear why you're doing that as opposed to hooking the event in an instance-specific way like you are the other events. If you remove the logic hooking it only once, you'll get the per-instance behavior.
Separately: It's odd to be attaching new event handlers every time the event occurs. You'll very quickly have them stacking up. The first time (say) you receive a click, you'll add a second click handler; the next time you receive a click, you'll receive two of them (one for each handler), and add two more handlers; and so on, doubling every time. This is bad enough with clicks, but disasterous with resize as there are a lot of resize events triggered when the window is being resized.

Utilizing Firefox's default/built-in Event Listeners

I have a context menuitem which is activated if an image is right-clicked, the exact same way that 'context-copyimage' is activated.
Is it possible to tie/pair that menuitem to the 'context-copyimage' therefore eliminating the need to add extra (duplicate) event-listeners and show/hide handlers??!!
(Adding an observer to 'context-copyimage' defeats the purpose)
If not, is it possible to use the event-listener that 'context-copyimage' uses?
Update:
I am trying to reduce listeners. At the moment, script has a popupshowing listeners. On popupshowing, it checks for gContextMenu.onImag and if true, it shows the menuitem. Firefox's context-copyimage does the exact same thing. I was wondering if it was possible to tie these 2 in order to remove/reduce the in-script event listeners.
I was also chatting with Dagger and he said that:
... the state of built-in items isn't set from an event handler, it's
set from the constructor for nsContextMenu, and there are no
mechanisms to hook into it
So it seems, that is not possible
No, there is no sane way of avoiding the event listener that would perform better than another event listener and is compatible with unloading the add-on in session.
Hooking nsContextMenu
As you have been already told, the state is initialized via gContextMenu = new nsContextMenu(...). So you'd need to hook the stuff, which is actually quite easy.
var newProto = Object.create(nsContextMenu.prototype);
newProto.initMenuOriginal = nsContextMenu.prototype.initMenu;
newProto.initMenu = function() {
let rv = this.initMenuOriginal.apply(this, arguments);
console.log("ctx", this.onImage, this); // Or whatever code you'd like to run.
return rv;
};
nsContextMenu.prototype = newProto;
Now, the first question is: Does it actually perform better? After all this just introduced another link in the prototype-chain. Of course, one could avoid Object.create and just override nsContextMenu.prototype.initMenu directly.
But the real question is: How would one remove the hook again? Answer: you really cannot, as other add-ons might have hooked the same thing after you and unhooking would also unhook the other add-ons. But you need to get rid of the reference, or else the add-on will leak memory when disabled/uninstalled. Well, you could fight with Components.utils.makeObjectPropsNormal, but that doesn't really help with closed-over variables. So lets avoid closures... Hmm... You'd need some kind of messaging, e.g. event listeners or observers... and we're back to square one.
Also I wouldn't call this sane compared to
document.getElementById("contentAreaContextMenu").addEventListener(...)
I'd call it "overkill for no measurable benefit".
Overriding onpopupshowing=
One could override the <menupopup onpopupshowing=. Yeah, that might fly... Except that other add-ons might have the same idea, so welcome to compatibility hell. Also this again involves pushing stuff into the window, which causes cross-compartment wrappers, which makes things error-prone again.
Is this a solution? Maybe, but not a sane one.
What else?
Not much, really.
Yes this is absolutely possible.
Morat from mozillazine gave a great solution here: http://forums.mozillazine.org/viewtopic.php?p=13307339&sid=0700480c573017c00f6e99b74854b0b2#p13307339
function handleClick(event) {
window.removeEventListener("click", handleClick, true);
event.preventDefault();
event.stopPropagation();
var node = document.popupNode;
document.popupNode = event.originalTarget;
var menuPopup = document.getElementById("contentAreaContextMenu");
var shiftKey = false;
gContextMenu = new nsContextMenu(menuPopup, shiftKey);
if (gContextMenu.onImage) {
var imgurl = gContextMenu.mediaURL || gContextMenu.imageURL;
}
else if (gContextMenu.hasBGImage && !gContextMenu.isTextSelected) {
var imgurl = gContextMenu.bgImageURL;
}
console.log('imgurl = ', imgurl)
document.popupNode = node;
gContextMenu = null;
}
window.addEventListener("click", handleClick, true);
this gives you access to gContextMenu which has all kinds of properties like if you are over a link, or if you right click on an image, and if you did than gContextMenu.imageURL holds its value. cool stuff
This code here console logs imgurl, if you are not over an image it will log undefined

Moving from classic event management to event delegation in JavaScript OOP

The old event management in which each handler for specific actions was directly attached to the target element is becoming outdated, since considerations about performance and memory saving started spreading in the developers community.
Event delegation implementations had an acceleration since jQuery updated the old fashioned .bind() and .live() methods with the new .on() method to allow delegation.
This determines a change in some seasoned approaches, where to use event delegation a rework is necessary.
I am trying to work out some best practice while keeping the coding style of my library, and looked for similar situations faced from other developers to find an answer.
Using OOP with functions as constructors, I usually have interfaces for objects creation like this:
var widget = new Widget({
timeout: 800,
expander: '.expanders'
});
with object literals given as argument, providing a clean map of names and values of the input passed. The class underlying this code could be something like the following:
var Widget = function(options) {
// some private members
var _timeout;
var _$widget;
var _$expander;
// methods
this.init = function() {
_timeout = options.timeout || 500;
_$expander = $(options.expander);
_$widget = _$expander.next();
_$expander.on('click', _toggle);
};
var _toggle = function(e) {
if (_$widget.is(':visible')) {
_$widget.hide(_timeout);
} else {
_$widget.show(_timeout);
}
};
this.init();
};
Using "private" methods gave me some benefits in terms of code readability and cleanness (only useful methods are publicly exposed to the user), beyond the small gain in performance (each scope resolution takes more time than a local variable). But when speaking about event handlers, it clashes with the event delegation paradigm.
I thought to make public the methods that I used to associate internally to the listeners in the class:
this.toggle = function(e) {
if (_$widget.is(':visible')) {
_$widget.hide(_timeout);
} else {
_$widget.show(_timeout);
}
};
then driving externally, or in another proxy class, the proper delegation with something like this:
var widget = new Widget({
expander: '.expanders'
});
$(delegationContext).on('click', '.expanders', widget.toggle);
but it did not seem to me the best approach, failing in the exposure of a non-useful method in the interface, so I tried a way to let the main class know directly all the information to delegate the event autonomously, through the interface:
var widget = new Widget({
timeout: 800,
expander: {
delegationContext: '.widgetContainer',
selector: '.expanders'
}
});
which would allow to keep on using private methods internally in the class:
var $context = $(options.expander.delegationContext);
$context.on('click', options.expander.selector, _toggle);
What are your practice and suggestions about it?
And what are the main trends of other developers you heard about as far as today?
it clashes with the event delegation paradigm.
Forget that paradigm! Don't choose it because it's "cool" and directly attaching events "is becoming outdated", choose it only when it is necessary. Event delegation is not necessary for you, nor does it speed up anything. It is not a solution you can apply - you have no problem!
You have one element and a single element-specific event handler function (every widget expander has its own function). You can already infer from your problems applying event delegation that you cannot and should not use it here. Attach the handlers directly.
Event delegation is only useful when you have a vast amount of similar elements, located consistently in the DOM with one common ancestor, that would all going to be attached the same event handler function. This is not the case with your Widget constructor that does take instance-specific options such as the expander selector.
In your current Widget class, using event delegation would clash with the single responsibility principle. In the event delegation paradigm, you would need to see the elements in the delegation context as a whole, being as homogeneous as possible. The more element-specific data and state you add, the more delegation advantages you are loosing.
If you really want to use event delegation here, I would suggest something like
var Widgets = {
init: function(delegationContext) {
$(delegationContext).on("click", ".widget-expander", function(e) {
$this = $(this);
$this.next().toggle($this.data("widget-expander-timeout") || 500);
});
},
activate: function(options) {
$(options.expander)
.addClass("widget-expander")
.data("widget-expander-timeout", options.timeout);
}
};
Here no constructors are used. You just initialise the event delegation context, and then you can add single elements to be captured by the delegation mechanism. All data is stored on the element, to be accessible from the event handler. Example:
Widgets.init('.widgetContainer');
Widgets.activate({expander: '.expanders', timeout: 800});
And what are the main trends of other developers you heard about as far as today?
Apart from that question being off-topic on StackOverflow, I can only advise you not to follow every trend someone heard about. Learn about new (or advertised or fancy) technologies, yes, but do not forget to learn about when to use them.

Trigger code when custom event is being bound in jQuery

JQuery has great support for custom events - .bind("foo", function(e).... However what if the mechanic of triggering the event is not ready yet and has to be constructed only on those elements that have the event bound on?
For example I want a scrollin event that gets fired when an element is scrolled into a viewport. To do this, I would onscroll have to check all the elements and trigger scrollin on those that were outside the viewport and now are inside. This is not acceptable.
There are some tricks to speed it up. For example one of the plugins for this checks all the elements in "private" $.cache and does the checking only on those that have scrollin event bound.
But that's also ugly. What I need is an additional callback for the binding of the event (additional to the callback for handling) that would take care of the scroll management, that is to put the element(s) into some elementsCheckOnScrol cache array.
I'm looking for something like:
$.delegateBind("scrollin", function(jqSelection) { ... });
element.bind("scrollin", function(e) {..}); //Calls ^ after internal bind management
Edit: This would be nice api!
$.bind("bind", function(onWhat) { ... })
:-)
If I'm not misunderstanding you, you could patch the bind method like this:
(function($) {
var oldBind = $.fn.bind;
$.fn.bind = function(name) {
if(name === "scrollin") {
delegateFunction(this);
}
oldBind.apply(this, arguments);
};
})(jQuery);
What it does is checking whether a scrollin is being bound, and if so, calls your delegate function. After that it simply calls the original bind function which does all jQuery things like it does regularly.
After having added this code, you could use it like this: http://jsfiddle.net/pimvdb/g4k2G/.
function delegateFunction(selection) {
alert(selection.length);
}
$('a').bind('scrollin', function() {});
Note that this does not support object literals being passed to .bind (only (name, func)), but you could implement that as well.
I found an $.event.special API, but I don't know "how much" public it is. It is not in the docs and has been changed at least once before. http://benalman.com/news/2010/03/jquery-special-events/

Categories