To improve maintenability I'm trying to replace HTML that's being dynamically generated in script with AJAX query results. But there's rather uncommon problem to bind event handlers to dynamic content, although there's a lot of solution much of them work just partially. I mean that if I write
$("#m" + event.PeerId).live("hover", function (args) {
debugger;
}
it works but
$("#m" + event.PeerId + " sendMessage").live("click", function (args) {
debugger;
}
doesn't work, the event doesn't occurs if I click. Also I can trace hover event of the root downloaded element but I can't trace load, ready, etc. - they also never occur.
Tell me please how can I handle button click that is some subelement of the loaded content.
If you are using any recent version of jQuery you should use a delegated event handler:
$(document).on("click", "#m" + event.PeerId + " sendMessage", function (args) {
debugger;
});
If your ids all start with m, but the peerid is not know yet, use this:
$(document).on("click", "[id^=m] sendMessage", function (args) {
debugger;
});
Delegated event handlers should be attached to a non-changing ancestor (document being the default if nothing closer to the changing elements is handy).
They work by listening for the event bubbling up the ancestors, to the one the handler is attached to, then applies the jQuery filter, the calls the function for each matching element that caused the event.
Note: Never use 'body' for delegated event handlers as it has bugs relating to styling (which can cause events to not occur). Always use document if you do not have a closer element that does not change.
Related
I have a jQuery plugin, and inside of it I have an init function. Inside of this init-function I attach some events:
(function ( $ ) {
$.fn.gallery = function(options) {
var init = function(self) {
var main += '<input id="gallery-search">';
//click event for the filter checkboxes
$("body").on('click', self.selector+" .gallery-filter-checkbox",function(event) {
self.data(filter( self ));
});
//capture input in the search box
$('#gallery-search').keyup(function(){
console.log('test');
});
self.html(output);
}
}( jQuery ));
}
The first one works just fine, but the second one doesn't work at all. I have tested it outside of the plugin scope and it works just fine so there is no syntax error, but probably an error in the way I try and attach the event?
Since #gallery-search is created dynamically, you can use delegated event handler:
$(document).on('keyup', '#gallery-search', function() { ... });
If self represents static HTML element at page, you can use a little better (for performance) version:
self.on('keyup', '#gallery-search', function() { ... });
Or you can place event handler in code after element's insertion, if HTML will not be modified later:
self.html(output);
$('#gallery-search').keyup(function()
{
console.log('test');
});
keyup() is a shortcut for bind('keyup',callback) which will register some event handler on the elements that are already present in the DOM (not necessarily rendered). It won't work if the element is not present in DOM when it's defined. To do that you need to use delegate() which will attach some handler on an element which is currently available or in might be available in future.
From jQuery 1.7 onwards, it's recommended to use on() as it combines the functionality of bind / delegate / live.
on() can be used in two ways
$(selector).on('event',callback);
$(parentSelector).on('event','someChildSelector', callback);
First one is direct handler and second one is called delegated handler.
If you're using first one to register event handlers, you've to make sure that element is present in the DOM at the time of registering just like bind(). So if you're adding new elements, you have to attach that handler again.
If you're using the second way, you don't have to worry about registering the handler again
$(document).on('click','.row',callback);
As document is always available, you callback will be registered as click handler for all the existing rows and any new row that you might add in the future.
I strongly recommend you read the Direct and delegated events section here. They even explain about the performance benefits.
Now that you know how it works, you can fix it using on() either as a direct handler or as a delegated handler.
EDIT : It's better to use closest static parent than document/body when using on() to register delegated handlers. Thanks to Regent for suggesting that :)
$('closestStaticParent').on('keyup','#gallery-search',function(){
console.log('test');
});
I am building a Windows 8.1 Store application with WinJS. When the user queries some search results show up in a <p class="searchresults">content</p> tag.
I'd like to add an event handler to the .searchresults class. I've done the following:
$('.searchresults').on('click', function() {
console.log("clicked");
});
I've tried even without .on()
$('.searchresults').click(function() {
console.log("clicked");
});
However the event never gets fired. I've set up a breakpoint, so I can see when it fires - but that never happens
I've tried to add an event handler via the WinJS way:
Microsoft.Maps.Events.addHandler(document.getElementsByClassName("searchresults"), 'click', myfunc);
Without success.
Any ideas why this is happening?
I will guess that you are creating the <p class="searchresults">content</p> object AFTER you try to install the event handler (a common problem with dynamic content). That will not work with normal event handling because the DOM object does not exist when you try to add the event handler to it.
If this is the case, then you need to use delegated event handling like this:
$(document.body).on('click', '.searchresults', function() {
console.log("clicked");
});
This will allow you to dynamically create the searchresults content at any time and the event handler will still fire via event delegation (events propagate up to their parents).
You haven't shown the HTML around the search results content, but the most optimal way to do this is to select the closest static parent to the search results (a parent that is not dynamically created and already exists at the time you attach the event handler) and attach the event to that:
$(closest static parent selector).on('click', '.searchresults', function() {
console.log("clicked");
});
Below two scenario give me the same behavior. But What is the difference technically? (I put the below code in the last section of script tags in the body.)
$(document).ready(function() {
$('.collapse').collapse({toggle: false});
$(document).on('click', '#expandAllLessons', function() {
$('div.accordion-body').collapse('show');
});
$(document).on('click', '#collapseAllLessons', function() {
$('div.accordion-body.collapse').collapse('hide');
});
});
or
$(document).ready(function() {
$('.collapse').collapse({toggle: false});
});
$(document).on('click', '#expandAllLessons', function() {
$('div.accordion-body').collapse('show');
});
$(document).on('click', '#collapseAllLessons', function() {
$('div.accordion-body.collapse').collapse('hide');
});
Thanks.
More or less, it's doing the same thing.
With the use of .on() with a child selector, you're using event delegation to bind any future events to any elements that match that selector. document is the very tippy top of the DOM tree (and available upon script execution), so your event delegation works.
.ready() waits until the DOM has assembled, so you can, more reliably, directly bind events using methods like .click(), .hover(), etc.
So your first example is just waiting for the DOM to assemble, then delegating the event. The second example is just delegating the event immediately upon script execution.
From jQuery's documentation regarding .on():
Direct and delegated events
The majority of browser events bubble, or
propagate, from the deepest, innermost element (the event target) in
the document where they occur all the way up to the body and the
document element. In Internet Explorer 8 and lower, a few events such
as change and submit do not natively bubble but jQuery patches these
to bubble and create consistent cross-browser behavior.
If selector is omitted or is null, the event handler is referred to as
direct or directly-bound. The handler is called every time an event
occurs on the selected elements, whether it occurs directly on the
element or bubbles from a descendant (inner) element.
When a selector is provided, the event handler is referred to as
delegated. The handler is not called when the event occurs directly on
the bound element, but only for descendants (inner elements) that
match the selector. jQuery bubbles the event from the event target up
to the element where the handler is attached (i.e., innermost to
outermost element) and runs the handler for any elements along that
path matching the selector.
Whenever you do a function, regardless of whether it's $(document).ready(function(){}); or something else, all the contents inside that function can only read stuff that's at its level or above it (unless you're using return functions).
The top paragraph means that all your code won't be executed until it's loaded, but it also means that it's nested code. Nested code means certain variables and functions won't be readable from outside. Example:
function bob(){
function sandy(){
function joe(){
alert("I can access anything written by sandy, bob or ray!");
}
}
}
function ray(){
alert("I can't see anything from anybody but bob!");
}
Look at the comments first. After jQuery 1.7 on can delegate events as well:
"The .on() method attaches event handlers to the currently selected
set of elements in the jQuery object. As of jQuery 1.7, the .on()
method provides all functionality required for attaching event
handlers. For help in converting from older jQuery event methods, see
.bind(), .delegate(), and .live()."
So before jQuery 1.7 this is the correct answer:
First is better because, document ready event is triggered when HTML document is fully loaded to DOM. And then you're sure you have all elements in place, and you can bind events to them.
But if you bind event before loading '#expandAllLessons' element to DOM, then it will simply not work, as jQuery selector will not find any elements, and will not bind this event anywhere.
After 1.7 both will work almost in the same way. Almost, because in first case, when you trigger event before document will be ready, it will not be executed. In second example it will be executed, because it was attached when script was loaded.
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>
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