jQuery clone() not cloning event bindings, even with on() - javascript

I have created a series of custom jQuery events for use in mobile web applications. They work great and have been tested. However, I have run into a small problem which I am having trouble understanding.
I am using .clone() on a few elements within the DOM, which contain a button. The button has some of the custom events bound to it (the events are bound using .on()), but. Unfortunately, when I use jQuery's .clone(), the bindings are not preserved, and I have to add them again.
Has anyone encountered this before, does someone know of a potential work around? I thought that using .on() was supposed to preserve the binding for elements that exist now, or in the future?

I think you should use this overload of the .clone() method:
$element.clone(true, true);
clone( [withDataAndEvents] [, deepWithDataAndEvents] )
withDataAndEvents: A Boolean indicating whether event handlers and data should be copied along with the elements. The default value is false.
deepWithDataAndEvents: A Boolean indicating whether event handlers and data for all children of the cloned element should be copied. By default its value matches the first argument's value (which defaults to false).
Beware that .on() does not actually bind the events to the targets but to the element you are delegating to. So if you have:
$('#container').on('click', '.button', ...);
The events are actually binded to #container. When a click on a .button element occurs, it bubbles up to the #container element The element which triggered the event is evaluated upon the selector parameter of .on() and if it matches, the event handler is executed. This is how event delegation works.
If you clone the element #container, you have to deep clone with events and data for the bindings made with .on() to be preserved.
This would not be necessary if you were using .on() on a parent of #container.

Related

If I bind a JavaScript event to an element, then delete the element, what happens to the event?

Let's say I have an element:
<section id="container">
<div id="curious">hey, there</div>
</section>
Then, after the DOM loads, I bind an event to the element, like so:
$('#curious').click(function (){
alert('Are you curious?');
});
Later on, the element gets deleted:
$('#container').html('');
What happens to the bound event? Is it deleted too? Does it linger around? Is it a good practice to clean it up?
According to the jQuery documentation for the .html() method, the event handlers are removed.
This is done to prevent memory leaks.
When .html() is used to set an element's content, any content that was in that element is completely replaced by the new content. Additionally, jQuery removes other constructs such as data and event handlers from child elements before replacing those elements with the new content.
Similarly, the same applies when using the .empty()/.remove() methods as well:
all bound events and jQuery data associated with the elements are removed.
If you want to retain the data and event listeners, use the .detach() method instead. The .detach() method is essentially the same as the .remove() method except for the fact that it keeps all jQuery data associated with the removed elements (which means that you can append the same element after detaching it, and the events would still be bound).

jQuery :last-child not updated as additional children are added?

I have a set of inputs where I would like to add another input when the last input currently displayed receives focus. The form starts out with 2 inputs.
$('.answer_fields input:last-child').focus(function(event) {
var nextfield = $('.answer_fields input').length + 1;
$('.answer_fields').append('<input class="form-control answer_field" placeholder="Answer option #' + nextfield +'" id="answer' + nextfield +'">');
});
As it currently stands, additional fields are only appended when the 2nd input (the original "last-child") receives focus.
Checking the source seems to show that the inputs are getting added to the DOM as expected. What am I missing?
The jQuery selector is evaluated ONCE at the time you run the code to install the .focus() event handler. It isn't adjusted live as things change. You can switch to event delegation if you want that type of behavior.
A similar event handler using event delegation that will be evaluated live would look like this:
$(some common parent selector).on("focus", ".answer_fields input:last-child", function() {
// code here
});
Working demo: http://jsfiddle.net/jfriend00/ZZLPJ/
First off, your original code did not work because you were attaching event handlers to specific elements that existed at the time you ran your first code. Which events you had event handlers attached to did not change as your structure changed in the future so new elements would not have event handlers on them and the :last-child portion of your selector would not be re-evaluated.
Event delegation works because certain events that occur on a given element will bubble up the DOM hierarchy through each parent. You can attach a single event handler to a common parent and when that parent is notified that a given event occurred, it can then dynamically evaluate the selector you specified vs. the child item that originated the event and if it's a match, fire your event handler. This gives you the "real-time" selector evaluation that you want in your case.
To give you an idea, these are what I call "static" event handlers:
$(".test:last-child").focus(fn);
$(".test:last-child").on("focus", fn);
This a delegated (and thus dynamic) event handlers:
$("#parent").on("focus", ".test:last-child", fn);
For more info on this topic, see these other answers:
jQuery .live() vs .on() method for adding a click event after loading dynamic html
Does jQuery.on() work for elements that are added after the event handler is created?
Should all jquery events be bound to $(document)?
Just checked to be sure and the focus event does bubble event delegation will work with it.
This is a common question for people starting to use jQuery. It turns out that the named eventListeners (i.e. $.fn.click, $.fn.focus, etc.) attach an event listener to each matching DOM element when the page is first loaded. These listeners are defined on the matching element themselves, meaning that each one is unique to the element it is attached to.
You will hear this method compared to what is commonly known as event delegation. Event delegation involves attaching an even listener to a shared parent element that will check the event.target attribute to see if it matches your criterion (in your case, .answer_fields input:last-child). This differs from attaching a unique listener to each DOM element you wish to target because it allows you to add new nodes to the DOM dynamically, which will then be handled by the parent node's listener like any other node.
To accomplish event delegation using jQuery, use the .on() function on a common ancestor (I'll use document as an example) and listen for all matching elements as the second argument of the function call:
$(document).on('focus', '.answer_fields input:last-child', function(event) {
// your code here
});
Not only will this solve your problem with dynamically created DOM elements, but event delegation will greatly improve the performance of your page by reducing the total number of event listeners attached to elements in the DOM.
For more information about event delegation, I'd encourage you to check out this tutorial and this SO question

Remove all event listeners on a DOM object

Is it possible to remove all event listeners on an object even those declared externally, I have the following code at http://jsfiddle.net/E5n7g.
I use the built-in
addEventListener()
to add an event on the "a tag", I then try and remove this using jQuery, however it seems jQuery can only unbind events its bound itself.
The reason I am asking this, is because I am trying to use jQuery on combination with a product which uses Dojo. When I bind the "click" event, its added to the list of event handlers however whenever I click that link its erasing that event handler and only leaving Dojo's intact.
One method I found that I used (and worked rather well) was the cloneNode function.
var new_el = el.cloneNode(true); //true means a deep copy
el.parentNode.replaceChild(new_el,el);
When you clone the element, it does not clone any events.

Direct vs. Delegated - jQuery .on()

I am trying to understand this particular difference between the direct and delegated event handlers using the jQuery .on() method. Specifically, the last sentence in this paragraph:
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.
What does it mean by "runs the handler for any elements"? I made a test page to experiment with the concept. But both following constructs lead to the same behavior:
$("div#target span.green").on("click", function() {
alert($(this).attr("class") + " is clicked");
});
or,
$("div#target").on("click", "span.green", function() {
alert($(this).attr("class") + " is clicked");
});
Maybe someone could refer to a different example to clarify this point? Thanks.
Case 1 (direct):
$("div#target span.green").on("click", function() {...});
== Hey! I want every span.green inside div#target to listen up: when you get clicked on, do X.
Case 2 (delegated):
$("div#target").on("click", "span.green", function() {...});
== Hey, div#target! When any of your child elements which are "span.green" get clicked, do X with them.
In other words...
In case 1, each of those spans has been individually given instructions. If new spans get created, they won't have heard the instruction and won't respond to clicks. Each span is directly responsible for its own events.
In case 2, only the container has been given the instruction; it is responsible for noticing clicks on behalf of its child elements. The work of catching events has been delegated. This also means that the instruction will be carried out for child elements that are created in future.
The first way, $("div#target span.green").on(), binds a click handler directly to the span(s) that match the selector at the moment that code is executed. This means if other spans are added later (or have their class changed to match) they have missed out and will not have a click handler. It also means if you later remove the "green" class from one of the spans its click handler will continue to run - jQuery doesn't keep track of how the handler was assigned and check to see if the selector still matches.
The second way, $("div#target").on(), binds a click handler to the div(s) that match (again, this is against those that match at that moment), but when a click occurs somewhere in the div the handler function will only be run if the click occurred not just in the div but in a child element matching the selector in the second parameter to .on(), "span.green". Done this way it doesn't matter when those child spans were created, clicking upon them will still run the handler.
So for a page that isn't dynamically adding or changing its contents you won't notice a difference between the two methods. If you are dynamically adding extra child elements the second syntax means you don't have to worry about assigning click handlers to them because you've already done it once on the parent.
The explanation of N3dst4 is perfect. Based on this, we can assume that all child elements are inside body, therefore we need use only this:
$('body').on('click', '.element', function(){
alert('It works!')
});
It works with direct or delegate event.
Tangential to the OP, but the concept that helped me unravel confusion with this feature is that the bound elements must be parents of the selected elements.
Bound refers to what is left of the .on.
Selected refers to the 2nd argument of .on().
Delegation does not work like .find(), selecting a subset of the bound elements. The selector only applies to strict child elements.
$("span.green").on("click", ...
is very different from
$("span").on("click", ".green", ...
In particular, to gain the advantages #N3dst4 hints at with "elements that are created in future" the bound element must be a permanent parent. Then the selected children can come and go.
EDIT
Checklist of why delegated .on doesn't work
Tricky reasons why $('.bound').on('event', '.selected', some_function) may not work:
Bound element is not permanent. It was created after calling .on()
Selected element is not a proper child of a bound element. It's the same element.
Selected element prevented bubbling of an event to the bound element by calling .stopPropagation().
(Omitting less tricky reasons, such as a misspelled selector.)
I wro te a post with a comparison of direct events and delegated. I compare pure js but it has the same meaning for jquery which only encapsulate it.
Conclusion is that delegated event handling is for dynamic DOM structure where binded elements can be created while user interact with page ( no need again bindings ), and direct event handling is for static DOM elements, when we know that structure will not change.
For more information and full comparison -
http://maciejsikora.com/standard-events-vs-event-delegation/
Using always delegated handlers, which I see is current very trendy is not right way, many programmers use it because "it should be used", but truth is that direct event handlers are better for some situation and the choice which method use should be supported by knowledge of differences.
Case 3 (delegated):
$("div#target").delegate("span.green", "click", function() {...});

Are defining many jQuery event handlers "expensive?" Internally Implemented at C/C++ or JavaScript level?

I have a long a web form. I'm wondering if I should bind() to each <input> element separately (what I really want) or should I only define one bind() all of the input elements then do an if-then inside the handler to handle the specific element?
jQuery's event handlers are implemented in JavaScript (they have to be).
In general, it's best to use a single handler for the entire form, rather than a large number of individual elements. But jQuery's live feature can help quite a lot, it does a lot of the plumbing for event delegation for you. For example:
$("#formid input").live('focus', function(event) {
var field = $(this);
// `field` now references the field that was focussed
});
...watches all fields in your formid form for focus events. If you're using jQuery 1.4, that works even with the focus event in that example (even though focus doesn't bubble, and so is usually tricky to use with event delegation). Prior to 1.4 that won't work, but bubbling events like click and such do.
I would bind to all the inputs, but you can use classes to narrow it down to just the inputs you want.
Instead of
$("#formid input").click(function() {
if($(this).attr('something') == 'special_field') {
//do A
}
});
add a class to the elements you are interested in <input class='special_field' ... >, and change your selector:
$("#formid input.special_field").click(function() {
//do A
});
That will limit the handler to only the desired elements without any if statement checks.
You can repeat this for anything that needs special handling.
Final note on the .live() method. It can be really handy for a couple reasons:
It doesn't actually create a binding for every matching element, it creates a listener on the body element by default(you can choose another element as the listener) and waits for an even to bubble up to it. When it bubbles, it checks the triggered element against the selector, and if its a match fires the event.
Because the selector is applied when the event fires, it means that new elements added via Javascript or AJAX will fire the code without needing to bind or rebind them.
There is one listener for many elements, meaning for large sets it can be much quicker.

Categories

Resources