Javascript performance - Events on removed nodes - javascript

My question is simple: Do we have to remove Javascript events listeners from elements that we are removing dynamically from the DOM?
Let's take this example (fiddle with full code) using jQuery: we have a user action that changes the DOM content using jQuery html function:
$('wrapper').html(markup);
The markup variable has got a lot of elements with the class eventDiv, and on those elements we add some event listeners:
$(".eventDiv").mouseenter(function() {
$(this).css('background-color', 'red');
});
$(".eventDiv").mouseleave(function() {
$(this).css('background-color', 'white');
});
Does this way of changing html content lead to a memory leak? Do we have to unbind all the event listener? Or is the removing of the nodes from the DOM enough?
And second, there is an easy way to remove all the listener on child element of a container div? In my case something like
$('wrapper').unbindAllChildEventsListeners();
PS: this is a example, the real case scenario has got a lot of different events on the new markup that is loaded by the user action, and also a DOM tree much more articulated, the "markup" variable is coming from a dustjs template.

Consider this example:
http://jsbin.com/bunoyedabo/edit?html,js,console,output
First of all, use event delegation. $().on('eventName', 'targetSelector', cb);
Second is that you can unbind events using .off() or .unbind().

Related

Newly added elements $.each and events

I’ve read many posts already on the $.each and newly added elements + event attachment. Many of the current Questions regarding this topic on StackOverflow don’t seem to work for me. $.on() is normally recommended since it allows us to append new elements and still maintain a single event listener + handler.
In my current code:
1.$(‘input[type="checkbox"]’).on(“change”, function(e){});
//I do a logical if-statement, if(this.checked) else
//With-in the if-statement I run $.each, however, once I have appended new element in this context a new li to the ul, it stops working.
Out of the curiosity has anyone encountered something like this before, and if YES, how have you folks solved this?
Some StackOverflow posts I have already seen:
jQuery $(element).each function doesn't work on newly added elements
jquery: dynamically appending li items to ul then adding click, but click runs through each li
Event binding on dynamically created elements?
Currently what you are using is called a "direct" binding which will only attach to element that exist on the page at the time your code makes the event binding call.
Delegated events have the advantage that they can process events from descendant elements that are added to the document at a later time.
As you are creating elements.
You need to use Event Delegation. You have to use .on() using delegated-events approach.
General Syntax
$(document).on(event, selector, eventHandler);
Ideally you should replace document with closest static container.
Example
$(document).on('change', 'input[type="checkbox"]', function(){
//Your code
});

Is using a universal document.addEventListener("click",f) listener slower or weaker form than a specific use of it?

My code is this:
document.addEventListener('click', function(ev){
if(ev.path[0].className == 'linkTogether'){//do something}
if(ev.path[0].id == "createNewPage"){//do something}
});
Which has actually been working well for dynamically created buttons and nodes, but something just feels off about it. So I'm wondering if this is best practice or if there is a better way to add event listeners to dynamically created elements.
Thanks,
Jack
In specific cases where you have huge amount of objects that behave in the same way you can use this technique (adding event listener to their parent) to improve the performance of your script.
In a general page however you just have to many different objects and iterating trough all of them to check which one you've clicked is not faster.
Here is an article for the technique you are referring to - event delegation.
This is the proper pattern for creating event listeners that will work for dynamically-added elements. It's essentially the same approach as used by jQuery's event delegation methods (e.g. .on).
However, it does have performance implications. Every time you click anywhere in the document, the code will run, and have to go through the entire list of event bindings that you need to check. You can improve this by adding your event listener to a more specific element. If the dynamic elements are always added inside a specific DIV, add your listener to that DIV rather than document.
This also avoids another pitfall of event delegation. Event delegation depends on the event bubbling up from an inner element to all its containers. But if there are any handlers along the way that call event.stopPropagation, the event won't make it out to document. If you add the listener to a lower element, you're less likely to have a conflict like that.
And I would like to add, if you create elements dynamically, better you include the listeners inline and filter in the function what you want to do.
<div class="form_ID1" onclick="myfunction(this, event);">
... children bubbling
... Use if statements in caught js myFunction function.
In myfunction function you will capture the child element by event.target and the element that contains the listener by this.className.
There are scenarios however that you need a universal ( document ) click event. e.g You need to close a pop up when you click outside of a pop up !! It is like: e.g.
if (clicked.className != popup.className) popup.remove()
Even in this case there is a workaround by inserting an onblur="myfunction(this); in the parent DIV of the popup.

jQuery on() method - which way to use is better for performance?

Is it better to attach the on() event to the document or a closer parent?
Note: Initially this question had another aspect and a different topic. It became obsolete really quickly (typo in the source code)
The best key for performance using jQuery is to use an id as the initial identifier. For example:
$('#my_id').on('click', 'tag.my_class', function () {
...
});
This allows jQuery to go straight to the container, and then begin trawling from there.
if you bind the "on" event to the closest parent will produce exactly what are you looking for, click function will works fine even if it is appended to document, but in future if you append any elements with class "clickable" will also get binded. so its always good practice to append the "on" event to closest parent rather than whole document.
if you want more specific you can use
$("ul.media-grid").on('click', 'li.clickable', function () {
alert("works")
});
as it will get the ul with the class "media-grid" and appends the event to the li's with class "clickable"

How do I make new elements draggable with jquery?

I'm loading new elements with a form. After the elements are loaded I need to make each one draggable. According to .on doc "Delegated events have the advantage that they can process events from descendant elements that are added to the document at a later time."
I've tried oh-so-many variants of .on, .click, etc but so far no luck. I'm currently working with...
$('#parent').on('change', '.thumb', function(event){
alert('loaded');
$('.thumb').draggable();
});
...but, it doesn't attach to the new .thumb element. How can I accomplish this?
Edit: Here's the html...
<input type="file" id="parent" name="files[]" multiple />
<output> //these spans are created after files are selected from 'file'
<span><img class=".thumb" src="..."></span>
<span><img class=".thumb" src="..."></span>
</output>
When you use a plugin that requires binding it's own events and DOM manipulation from within the plugin, delegation methods like on() are useless.
You need to call the draggable() method when you load new elements such as in success callback of ajax.
If you are using load()
$('#myDv').load( url, function(){
/* new html has been inserted now */
/* in case any other active draggables will only search within $('#myDiv') for new elements that need to be called*/
$(this).find('.dragClass').draggable();
})
There isn't enough detail for me to answer this question specifically, so I will attempt to guess what the problem is.
You are binding this function to the event "change" of an element with an id of "parent." The "change" function will only work in certain DOM elements, namely input, textarea, and select. (http://api.jquery.com/change/) This means that the change event will never fire if the element with id "parent" is anything but those three tags.
If this is the problem, I would suggest moving the .draggable() method to the same place you are adding "elements with a form."
Try this:
$('#parent').live('change', '#child', function(event){
alert('loaded');
$('#child').draggable();
});

jQuery: How to listen for DOM changes?

Is it possible and how can I listen for changes through the entire DOM tree with jQuery?
My specific issue: I have a 'tooltip' function that displays the contents of the title attribute in a stylish way when you do a hover on any html element. When you do a hover, however, by standard the browser renders the title in its own box. I would like to supress that. So what I've thought of is to move the contents of the title attribute to a custom (HTML5) data-title attribute the first time the page is loaded, and then my tooltip function will work with data-title.
The problem is that later on I might add / remove / change the HTML dynamically, so I need to 'rebind' those elements - change those title attrs again. It would be nice if there was an event listener that would listen for such changes for me and rebind the elements automatically.
My best guess is that you want to listen to DOM mutation events.
You can do that by DOM mutation event as any normal javascript event such as a mouse click.
Refer to this : W3 MutationEvent
Example:
$("element-root").bind("DOMSubtreeModified", "CustomHandler");
[edited in reply to research by member Tony]
So, without additional code, this is a bit of a blind shot, but it seems to me there are two things to think about here: 1. the default browser tooltip behaviour; 2. a potentially updated DOM and the ability for your custom tooltips to continue functioning.
Regarding #1: when you bind your custom event to the element, you can use event.preventDefault() so that the tooltips don't appear. This doesn't work properly. So, the workaround to keep using the "title" attribute is to grab the value, push it into the data object (the $.data() function), and then null the title with an empty string (removeAttr is inconsistent). Then on mouseleave, you grab the value out of the data object and push it back into the title. This idea comes from here: How to disable tooltip in the browser with jQuery?
Regarding #2: instead of re-binding on DOM change, you just need to bind once to a listener element that is never expected to be destroyed. Usually this is a container element of some sort, but it can even be document (approximating .live() which is now deprecated) if you really need an all-encompassing container. Here's a sample that uses some fake markup of my own devising:
var container = $('.section');
container.on('mouseenter', 'a', function() {
var $this = $(this);
var theTitle = $this.attr('title');
$this.attr('title', '');
$('#notatooltip').html(theTitle);
$.data(this, 'title', theTitle);
});
container.on('mouseleave', 'a', function() {
$('#notatooltip').html('');
var $this = $(this);
var storedTitle = $.data(this, 'title');
$this.attr('title', storedTitle);
});
My unrealistic markup (just for this example) is here:
<div class="section">
Hover this foo!
<div id="notatooltip"></div>
</div>
And a fiddle is here: http://jsfiddle.net/GVDqn/
Or with some sanity checks: http://jsfiddle.net/GVDqn/1/
There's probably a more optimal way to do this (I honestly didn't research if you could bind two separate functions for two separate events with one selector) but it'll do the trick.
You shouldn't need to re-bind based on DOM change, the delegated listener will automatically handle it. And you should be able to prevent default tooltip functionality just by preventing it.
You need to look at this here: http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-eventgroupings-mutationevents
As noted by Greg Pettit, you should be using the on() function on the element.
What this does is allows you to bind a selector to an event, then jQuery will add this event handler when the objects returned by the selector are available.
If you wanted a function to fire on a mouse over event and you wanted it to fire on all elements with the class of *field_title* you would do this:
$('.field_title').bind('mouseenter', function() { doSomething(); });
This will trigger on the over mouse over event on any objects that have the class of *field_title* and execute the function doSomething().
Hope that makes sense :)

Categories

Resources