jQuery best way to apply eventhandlers - javascript

I'm working on a grid with 20 columns and 100+ rows. Every column has an input field. Now I want to put eventHandlers on the input fields like change and keyup, focus, and much more. So there can be 20*100 events = 2000!
What is the best way to do this? I've read something about eventHandlers and memory problems.
This is how I think I should do it:
$("#grid input").each(function() {
$(this).keyup(function() {
//
});
$(this).change(function() {
//
});
});
Or is this the best way to do it?
$("#grid").keyup(function() {
//
});

You're looking for event delegation.
$("#grid").on("change", "input", function() {
// Handle the event here, `this` refers to the input where it occurred
});
That attaches one handler (on #grid), which jQuery then fires only if the event passed through an element matching the second selector. It calls the handler as though the handler were attached to the actual input. Even events like focus that don't natively bubble are supported through jQuery's mechanations.
More in the on documentation.

I'd suggest using Event Delegation, like so:
$("#grid").on("keyup", "input", function() {
...
});
Rather than adding a listener to each and every input, you're only adding one to #grid.
As per this great answer: What is DOM event delegation?
Another benefit to event delegation is that the total memory footprint used by event listeners goes down (since the number of event bindings go down). It may not make much of a difference to small pages that unload often (i.e. user's navigate to different pages often). But for long-lived applications it can be significant.
There are some really difficult-to-track-down situations when elements removed from the DOM still claim memory (i.e. they leak), and often this leaked memory is tied to an event binding. With event delegation you're free to destroy child elements without risk of forgetting to "unbind" their event listeners (since the listener is on the ancestor). These types of memory leaks can then be contained (if not eliminated, which is freaking hard to do sometimes. IE I'm looking at you).

Related

How to use jQuery this with .on function and .each function

I am using jQuery each function and on click on current i.e $(this) I am executing JS code, It will be more clear if you look on following code
$('.switch-cal').each(function(){
$(this).on( 'click', function( e ){
.... CODE HERE ....
Please tell me correct way of use "this" with ".on" inside ".each" function.
Thank you.
1. Short answer: There is no need for the each:
Do not use click inside of each... you do not need to with jQuery. It handlers collections automatically for most operations (including event handlers):
e.g.
$('.switch-cal').click(function(){
//$(this) is the calendar clicked
.... CODE HERE ....
2. Try a delegated handler:
It is often more convenient to use a single delegated event handler (attached to the nearest non-changing ancestor element, or document if none is handy). The best feature of delegated events is that they can work on elements that do not even exist yet! :)
The run-time overhead is quite low as they only need to apply the selector to the elements in the bubble-chain. Unless you are processing 50,000 mouse operations per second the speed difference is negligible, so for mouse events, don't be put of by ridiculous claims of inefficiency (like the down-voter's ranting below). The benefits usually out-way any extra overhead. Nobody clicks 50,000 times per second!:
e.g.
$(document).on('click', '.switch-cal', function(){
//$(this) is the calendar clicked
.... CODE HERE ....
Delegated events work by listening for events (in this case click) bubbling up the DOM to a non-changing ancestor element. It then applies the selector. It then calls the function on each matching element that caused the event.
The closer the non-changing element is to the elements in question the better, to reduce the number of tests required, but document is your fall-back if nothing else is convenient. Do not use body as it has problems relating to styling that means events may not fire.
#TrueBlueAussie had the correct answer. You should use a single delegated event.
$(document).on('click', '.switch-cal', function(){
//$(this) is the calendar clicked
.... CODE HERE ....
Still if you want to keep your "this" scope, you may
$('.switch-cal').each(function(){
$(this).on( 'click', function( e ){
.... CODE HERE ....
}.bind(this))
});

Capture all the events (javascript)

I want to be able to capture all events that are both created and dispatched and fire some callback when that happens.
Also, I would like to be able to fire a callback anytime an event is paired with an event listener.
Problems include: dynamically added elements, events whose propagation or bubbling is prevented, and custom events that are generated dynamically. I imagine there would need to be a prototyping of dispatchEvent or something, but I am unsure. Is this even possible?
Some event basics:
Events are dispatched "on" a DOM object (usually an element) that is the event target.
Events can firstly propagate down to child elements in a capture phase. This phase is rarely used since it wasn't supported by some widely used browsers until recently.
Events can secondly propagate up to parent elements in a bubbling phase. This phase is commonly used.
Some events don't propagate, they have neither a capture or bubble phase (e.g. focus, blur and submit events). Some events that propagate in some browsers don't propagate in others.
DOM elements that respond to events have an event handler. It can be set to listen for particular events and call a listener function when that event reaches the element, either during capture, bubbling or if the element is an event target.
Listeners can cancel propagation, e.g. a click event on a span inside a link can cancel propagation so the link doesn't get the click
Given the above, it is a practical impossibility to "capture all events" using the Events API. It would require establishing a listener for every event type on every element and be impossible to capture custom events because you have to know about them to set an appropriate listener.
I imagine there would need to be a prototyping of dispatchEvent or something
dispatchEvent is a method of an Event instance, it's not specified to be a constructor (there is no requirement for it to have an internal [[Construct]] method) so not practical to use as such. Browsers aren't required to implement prototype inheritance for host objects (though most do), and the implementation details of host objects and methods are largely hidden, so this is not an option.
You might try extending the Event API, but you really should not mess with host objects.
It seems that you are concerned about dynamically added elements. There is a strategy called "event delegation", where you work out the events you need to listen for, then setup listeners as close to the event targets as you can on an element that doesn't change (e.g. a table element if you are dynamically adding and removing table rows, or a container div for other elements) for the specific event types you need to respond to.
You can also have the functions that are modifying the DOM dispatch custom events to add listeners or whatever.
If you really want to do this, then you can override addEventListener to keep track of events being registered and fired.
var myEventManager = (function() {
var old = EventTarget.prototype.addEventListener,
listeners = [],
events = [];
EventTarget.prototype.addEventListener = function(type, listener) {
function new_listener(listener) {
return function(e) {
events.push(e); // remember event
return listener.call(this, e); // call original listener
};
}
listeners.push([type, listener]); // remember call
return old.call(this, type, new_listener(listener)); // call original
};
return {
get_events: function() { return events; },
get_listeners: function() {return listeners; }
};
}());
However, there are uncountable reasons not to do this, not least the fact that you will quickly run out of memory as you record thousands of events such as mouse moves. This will also not capture event listeners set in ways such as elt.onclick. Nor of course will it catch listeners set up via the old IE attachEvent API. Most importantly, it will not help with you that events that are generated and listened for internally, such as a mouse click on a check box. (A complete solution would also require handling removeEventListener.)
You can also override createEvent and dispatch in similar fashion, but again, that will capture only events that are explicitly created or dispatched in the JS code.
If you really want to do what you seem to be wanting to, I guess you need to fork Chrome.

Javascript Delegation Best Practice [closed]

Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 9 years ago.
Improve this question
I am developing/have developed a few systems now, I have come across a querk in my programming, I seem to delegation all my JQuery functions to the document like so:
$(document).on('click', '.modal-editor', function () {
//code
});
$(document).on('click', '.another-class', function () {
//code
});
$(document).on('click', '#another-id', function () {
//code
});
Is there anything wrong with this?
UPDATE:
Ok so in essence there is nothing wrong with this until:
The applications reaches a scale where delegation needs to be more localised to prevent slowing down UI. "Every event that takes place needs to run every selector against every element between the e.target and the document"
Delegation like this will increase the chances of unexpected behaviour like propagation if nested functions are used.
Referring back to my question (best practice), best practice would be to delegate the event to the closest static element as possible to attempt to avoid the above.
"Is there anything wrong with this?"
Yes. Every event that takes place needs to run every selector against every element between the e.target and the document. That's unnecessarily expensive.
Basically like this simplified pseudo code:
// start on the e.target
var node = e.target;
// For the target and all its ancestors until the bound element...
while (node && node !== this) {
// ...test the current node against every click selector it was given
for (var i = 0; i < click_selectors.length; i++) {
// If a selector matches...
if ($(node).is(selector)) {
// ...run the handler for that selector
}
}
node = node.parentNode;
}
Also, if your code or some other code you loaded binds a handler between the e.target and the document, and calls e.stopPropagation(), the event will never reach the document, and so yours will not fire.
It's much better to keep your delegation as close to the intended element(s) as possible. And use it primarily for dynamically loaded elements because of the extra overhead it adds, especially in the case of events that fire rapidly.
Yes. In itself there isn't, nto really, but delegating all events from the document (root of the DOM) does meant that all click events, including those you're not interested in will be handled at least partially:
$(document).on('click', '$another-id', function(){});
is a particularly bad idea, in that respect: you're after a single element, but if I click anywhere in the body, jQ will do something like:
if (event.target.id == 'another-id')
{
return callback.call(event.target, $(event));//where callback is the anonymous function you passed to on
}
return event;
So all click events result in a function call. This can slow your UI down.
By no means should you stop delegating events, but bind the listeners wisely. If all the DOM elements you want to use are contained within the #container div, for example, then bind your listeners there. If you want to handle navigation events, bind the listener to the node that contains all the navigation elements your after
Add to that that, if you cancel an event, but failed to stopPropagation, the event will still end up invoking all your other listeners that might be queued. Returnign false can also cause trouble, seeing as, in jQ, return false is translated to e.preventDefault(); e.stopPropagation(). So be careful when dealing with nested elements, if you want to handle a click on links in your page, but also on elements like <a class='navigation'>, both handlers might be called, depending on the selectors used:
$(document).on('click', 'a', function(){});//is called for all links
$(document).on('click', 'a.navigation', function(){});//is called for navigation
Which will be invoked first? Which would you want to use in a given situation? There's room for error here, that shouldn't be there:
$('#navContainer').on('click', 'a.navigation', function(){});//is called for all links
At least makes things a tad safer, clearer and lighter, too.
If you want to delegate an event, using an ID selector, and the element already exists in the DOM, don't delegate:
$('#another-id').on('click', function(){});
is shorter, and will likely even be faster anyway
Delegation is great when you want a particular event to apply to multiple elements, without each element having its own event handler. In the instance above this is likely for the first two methods.
Where there will only ever be one element with that matches the criteria in theory it might have a performance implication, depending on the size of your document, due to every event having to be tested against the handler filter to see if it matches.
Also, if every method is added as a delegate, and you are frequently loading and uploading sections of the page those events are going to hang around longer than the elements on the page that they belong to.
You can delegate your handlers to something other than the document element, such as a surrounding div or something instead perhaps in this kind of scenario. Or use event namespacing to make sure events aren't being added multiple times.
There is nothing wrong with your code,
$(document) is not necessary to be place always in event delegation, that selector refers to the closest parent element to which your elements are added dynamically:
Syntax:
$(closest parent selector).on('event','target selector', function(){
});
You can also use document.body
$(document.body).on('event','target selecor',function(){
});
Side Note: For existing DOM elements you can use normal click event.
$('selector').click(function(){
});
OR
$('selector').on('click',function(){
});
For more information refer to .on() API documentation.

Native addEventListener with selector like .on() in jQuery

Does anyone know how jQuery's .on() method can be implemented in native JS? The addEventListener method does not take a child/selector element as a way to filter, and I don't think I have the proper bubbling/capturing knowledge to completely understand what is happening in there. I did consult the source in event.js, where it appears that eventually addEventListener does get used just as it normally does, but I'm not sure I quite grok the source.
If the native method does not provide a mechanism for taking advantage of bubbling and capturing, then does the jQuery .on() function really even have any benefit, or does it just make it look that way? I was under the impression that
.on('parent', '.child', fn(){});
is more efficient than attaching an event to all children individually, but from my interpretation of the source, it's difficult to tell if jQuery is somehow managing this in a way to leads to performance improvement, or if it's just for readability.
Is there a standard methodology for implementing events on a parent that take advantage of bubbling/capture phases for it's child elements, rather than having to attach an event to each individual child?
To perform event delegation natively:
parent.addEventListener('click', function(e) {
if(e.target.classList.contains('myclass')) {
// this code will be executed only when elements with class
// 'myclass' are clicked on
}
});
The efficiency you are referring to has to do with how many event handlers you add. Imagine a table with 100 rows. It is much more efficient to attach a single event handler to the table element then 'delegate' to each row than attach 100 event handlers, 1 to each row.
The reason event delegation works is because a click event actually fires on both the child and the parent (because you're clicking over a region within the parent). The above code snippet fires on the parent's click event, but only executes when the condition returns true for the event target, thus simulating a directly attached event handler.
Bubbling/capturing is a related issue, but you only need to worry about it if the order of multiple event handlers firing matters. I recommend reading further on event order if you are interested in understanding bubbling vs capturing.
The most common benefit of event delegation is that it handles new elements that are added to the DOM after the event handler is attached. Take the above example of a table of 100 rows with click handlers. If we use direct event handler attachment (100 event handlers), then new rows that are added will need event handlers added manually. If we use delegated events, then new rows will automatically 'have' the event handler, because it's technically been added to the parent which will pick up all future events. Read What is DOM Event Delegation, as Felix Kling suggested, for more information.
Adding to the accepted answer: since often the actual event target will be nested within the element you want to bind the listener to it would be better to query the parents (Element.closest() includes the element itself). Also this works with complex CSS selectors instead of a single class only.
<button><span>button with</span><span>multiple click targets</span></button>
function addListener(el, type, callbackFn, selector) {
el.addEventListener(type, e => {
const target = e.target.closest(selector);
if (target) callbackFn.call(target, e);
}, true);
}
addListener(document, "click", e => console.log("clickediclick"), "button");
The answer by #Raine Revere, while concise, does not handle all cases. For example, if .child element contains children of its own, then click on the grandchildren will not trigger the handler. Also, jQuery sets the this execution context to the matched element.
The following snippet handles it correctly.
function on(event, elem, selector, handler) {
elem.addEventListener(event, ev => {
const target = ev.target.closest(selector);
if (target) {
handler.apply(target, arguments)
}
})
}
on('click', document.querySelector('.parent'), '.child', function () { console.log('Clicked ' + this.tagName);});
<div class="parent">
<button class="child">Click Me <i class="icon icon-something">→</i></button>
</div>

how to properly unbind events in Jquery-Mobile using on/off when a page is kept in the DOM?

As Jquery Mobile keeps some pages in the DOM when navigating around, a page may be visited multiple times when going back and forth.
If I'm binding to a page like below and inside this binding perform all my page logic, which includes "nested element bindings":
// listener for the page to show:
$(document).on('pagebeforeshow.register', '#register', function() {
// stuff
// page event bindings:
$(document).on('click.register', '.registerSubmitter', function(e) {
// do something
});
});
Going back and forth causes my nested binding to be attached multiple times.
Right now trying to work around this like so (doesn't work...):
$(document).on('click', '.registrySubmitter', function(e) {
if ( $(this).attr('val') != true ) {
$(this).attr('val') == true;
// do something
}
});
So I'm only allowing the first binding to pass and then I block every other binding attempt that comes along.
While this works, it's far from optimal.
Question:
How and when should event bindings be properly unbound/offed? Is there a general way (kill all) or do I have to do this binding per binding? Maybe more importantly: Is it better performance-wise to do a binding once and keep it or bind/unbind when the user comes to/leaves the page?
Thanks for input!
EDIT:
So I'm namespacing all my events and then listen for pageHide like so:
$(document).on('pagehide.register', '#register', function(){
$(document).off('.registryEvents');
});
While this seems to unbind, it also fires when ever I close a custom dialog/selectmenu on the page, so I'm loosing my bindings before leaving the page. So partial answer, I should use off(), but how to bind to the page really being left vs. opening and closing a select menu?
When you use .on() like that, you are delegating the event handling to the document element, meaning you can setup that delegated event binding anytime you want because the document element is always available.
I've got two suggestions:
Use the pageinit or pagecreate event to only run the page-specific bindings when pages are added to the DOM and initialized. Using this method I would not delegate the event bindings within the pageinit or pagecreate event handlers because when they fire, all the elements on the pseudo-page are in the DOM:
.
$(document).on('pageinit', '#register', function() {
//note that `this` refers to the `#register` element
$(this).find('.registerSubmitter').on('click', function(e) {
// do something
});
});
Bind the delegated event handlers once and don't worry about when pages are actually in the DOM:
.
//this can be run in the global scope
$(document).on('click.register', '.registerSubmitter', function(e) {
// do something
});
Basically when you bind an event using delegation like you are, the actual CPU hit of adding the event handler is less but each time an event is dispatched (of any kind that bubbles) it has to be checked if it matches the delegated event handler's selector.
When you bind directly to elements it generally takes more time to do the actual binding because each individual element has to be bound to rather than binding once to the document element like with event delegation. This however has the benefit that no code runs unless a specific element receives a specific event.
A quick blurb from the documentation:
Triggered on the page being initialized, after initialization occurs.
We recommend binding to this event instead of DOM ready() because this
will work regardless of whether the page is loaded directly or if the
content is pulled into another page as part of the Ajax navigation
system.
Source: http://jquerymobile.com/demos/1.1.0/docs/api/events.html

Categories