Plug-in: Ensure Single Event Handler in jQuery

Share on facebook
Facebook
Share on twitter
Twitter
Share on linkedin
LinkedIn

I recently had a performance issue when an AJAX control updated the DOM and the script added event bindings to the elements. However, some elements were unchanged. The events were still added again, causing multiple events and performance degradation within the browser. The existing fix was:

				
					
if (!$("#open-btn").hasClass("init")) {
	$("#open-btn").click(open);
  $("#open-btn").addClass("init");
}
				
			

Sure, you can do that. An easier way is to ensure that all current events are removed on the element and then adding it again. This can be done my easier by using ‘off’ as:

				
					$("#open-btn").off().click(open);
				
			

Here is a JS fiddle sample of how this works

This works with “element.click” and “element.on(‘click’)” style event handlers, and you can either specify “off” to destroy all events or “off(‘click’)” for a specific event.

Note that you obviously might end up destroying click events added by other code/team members etc, so needs testing and be sure to investigate the events via developer tools or web inspector.

As an update, I wrote a plug-in to handle this as well. The point is, you could use off and on -but what if there are several handlers? You will risk overwriting other events on the item.

For example:

				
					var element = $("#open-btn");

// Adding event to open a UI element
element.click(open);

// Later, adding event to disable button until window closed
element.off("click”).click(disable);

// Now, the window won’t open
				
			

This could cause issues when several events are attached to the same object. So instead, we use this:

				
					
var element = $("#open-btn");
element.attach("click", open);
element.attach("click", disable);
				
			

The “attach” method is my custom plugin. It will search for the event handler and only add it if not already attached. It works like this:

				
					/**
 * Attaches an event handler only if not already attached.
 * @param {any} type The event type, such as 'click' or 'keyup'.
 * @param {any} handler The function handler.
 */
$.fn.attach = function (type, handler) {
    // Only attach if not already on element
    if (!$(this).isAttached(type, handler)) {
        $(this).on(type, handler);
    }
};
				
			

The point is to interrogate each event of all properties, which is done by iteration as:

				
					
this.each(function () {
    var events = $._data($(this)[0], "events");
    for (var prop in events) {
        if (Object.prototype.hasOwnProperty.call(events, prop)) {
            var event = events[prop][0];
				
			

We now have event which has info on what type of event it is and what handler is on it. We can interrogate one or both. The handler will be textually the same but can change during browser attachments, so I compare them as string literals, as:

				
					if (event.type === type && typeof (handler) === "undefined") {
    // The event type, such as 'keyup' is attached but we are not
    // comparing the function. The event is attached.
    found = true;
}

if (event.type === type && typeof (handler) !== "undefined") {

    // Compare the function
    var comparer = event.handler.toString().replace(/\s/g, "") === handler.toString().replace(/\s/g, "");

    // We compare on function level as well
    // and they are the same. The event is attached.
    if (comparer) {
        found = true;
    }
}
				
			

Leave a Reply

Your email address will not be published.