Class selector does not work after append (JQuery) - javascript

$(".quantidade").on("change blur", function() {
if ($(this).val().length) {
validateField("quantidade");
} else {
invalidateField("quantidade");
showGeneralMessage("É necessário preencher a quantidade!", "danger");
}
});
It works just fine, until a new item is added with append:
$(".btn-add-produto").click(function() {
...
$("#listaProdutosPedido").append(content);
...
});
This "content" includes input with the same class. After this html piece is added, the class selector does not apply to it. I guess it happens because when the document is ready, that piece of HTML does not exist. How do I "reload" the javascript / document so that the selector works even with future references? I've tried to check with setInterval(), but it does not work as well...

You need to use .on() delegated event syntax. In event delegation, you attach the event to a parent element. Internally, jQuery relies on the events ability to bubble up the DOM tree. When an event of the specified type with the e.target property matching your delegated selector is found, your event is fired.
To accomodate this change, modify your code like so:
$(document).on("change blur", ".quantidade", function() {
if ($(this).val().length) {
validateField("quantidade");
} else {
invalidateField("quantidade");
showGeneralMessage("É necessário preencher a quantidade!", "danger");
}
});
Please note, delegating at this high of a level is extremely inefficient. document should be changed out for the closest parent element that contains the newly appended content. In this case, I used it to demonstrate how the process occurs because there was not HTML DOM tree output specified in your question. When implementing this into your system, I encourage you to update the document selector accordingly.

Related

jQuery and event handlers, why does it matter if I split the selector?

In jQuery I have experienced a difference in how my event handlers work, depending on whether I split the selector.
Selector not split (#myId .someClass):
$('#myId .someClass').on('click', function (e) {
alert('x');
});
Selector "split" (#myId ........ .someClass):
$('#myId').on('click', '.someClass', function (e) {
alert('x');
});
When I use the latter, I will get the same event multiple times from same click, whereas the first only give me the click event once (however I sometimes experience that the first one does not even work).
Can someone explain why there is this difference?
The difference is e.g. that the first version
$('#myId .someClass').on('click', function (e) { ...
binds the click event to all .someClass elements that are descendants of the element with the id #myId and are already in the DOM when the page is loaded, while the second version
$('#myId').on('click', '.someClass', function (e) { ..
will delegate the click event from the #myId element to all descendant elements with the class .someClass, even if they are dynamically added later.
For reference: http://api.jquery.com/on/
As one essential quote from there, section "Direct and delegated events":
Event handlers are bound only to the currently selected elements; they
must exist on the page at the time your code makes the call to .on().

Best practices for where to add event listeners

On my page, the user clicks on an element in order to edit it. To facilitate this, I assign the class editable to all such elements.
How should I listen for clicks on all these elements? Currently, I'm doing this:
document.body.addEventListener("click", (event) => {
if (event.target.classList.contains("editable")) {
// do stuff
}
});
The alternative would be to set a listener on every element, like this:
const editables = document.getElementsByClassName("editable");
for (const editable of editables) {
editable.addEventListener("click", editElement);
}
It seems to me that the first way must be better for performance, since it's only one element being listened on, but is it possible to degrade performance by attaching all such events to the body element? Are there any other considerations (e.g. browser implementations of event handling) that I'm neglecting which would suggest doing it the second way?
Short answer: definitely do it the first way. Event delegation is way more performant, but requires extra conditionals in your code, so it's basically a complexity versus performance tradeoff.
Longer Answer: For a small number of elements, adding individual event handlers works fine. However, as you add more and more event handlers, the browser's performance begins to degrade. The reason is that listening for events is memory intensive.
However, in the DOM, events "bubble up" from the most specific target to the most general triggering any event handlers along the way. Here's an example:
<html>
<body>
<div>
<a>
<img>
</a>
</div>
</body>
</html>
If you clicked on the <img> tag, that click event would fire any event handlers in this order:
img
a
div
body
html
document object
Event delegation is the technique of listening to a parent (say <div>) for a bunch of event handlers instead of the specific element you care about (say <img>). The event object will have a target property which points to the specific dom element from which the event originated (in this case <img>).
Your code for event delegation might look something like this:
$(document).ready(function(){
$('<div>').on('click', function(e) {
// check if e.target is an img tag
// do whatever in response to the image being clicked
});
});
For more information checkout Dave Walsh's blog post on Event Delegation or duckduckgo "event delegation".
NOTE ON CODE SAMPLE IN OP: In the first example, target.hasClass('editable') means that the specific thing clicked on must have the class editable for the if block to execute. As one of the commenters pointed out, that's probably not what you want. You might want to try something along these lines instead:
$(document).on('click', function(e) {
if ($(e.target).parents(".editable").length) {
// Do whatever
}
});
Let's break that down a bit:
$(e.target) - anything that on the page that was clicked converted to jQuery
.parents(".editable") - find all the ancestors of the element clicked, then filter to only include ones with the class "editable"
.length - this should be an integer. If 0, this means there are no parents with "editable" class
Another plus point for the first method
I was using the second (alternative) method that you have mentioned I noticed that when the ajax loaded... the newly created elements were not listening the event. I had to redo the for loop after ajax every time.
With the first method which looks like following in my code also works with ajax.
document.addEventListener('click', function (e) {
if (hasClass(e.target, 'classname')) {
// do stuff
}
}, false);
So first one is better

JQuery selectors missunderstanding

I'm trying to make a simple tree-view script, and start with opening/closing nodes. But I stuck with some problem:
$(function() {
$('#tree li.closedNode').on('click',function(e){
e.stopPropagation();
$(this).removeClass('closedNode').addClass('openedNode').children(':not(a.caption)').show();
})
$('#tree li.openedNode').on('click',function(e){
e.stopPropagation();
$(this).addClass('closedNode').removeClass('openedNode').children(':not(a.caption)').hide();
})
jsfiddle: http://jsfiddle.net/F33dS/14/
So, then you click on 'click here' it's closing, changing class, but it still firing event for 'li.openedNode'. I know, that I missed somthing simple, but what? I really can't find the problem. So, why it's working in this way?
You're binding event to things that don't exist yet.
You need to use .on() such that it targets all matching elements whether they exist now or in the future.
http://jsfiddle.net/F33dS/16/
$('#tree').on('click', 'li.closedNode', function(e){
e.stopPropagation();
$(this).removeClass('closedNode').addClass('openedNode').children(':not(a.caption)').show();
})
$('#tree').on('click', 'li.openedNode', function(e){
e.stopPropagation();
$(this).addClass('closedNode').removeClass('openedNode').children(':not(a.caption)').hide();
})
Your nodes have the class openNode on page load.
The script looks for $('#tree li.openNode') and matches elements.
The script looks for $('#tree li.closedNode') and matches none.
It's only when the user clicks the element that a match would be found for $('#tree li.closedNode').
So we tell its parent which exists to look for the click event for both matching descendants. As soon as one MATCHING descendant pops into existence (when you change the class name), the event triggers.
From http://api.jquery.com/on/
Event handlers are bound only to the currently selected elements; they must exist on the page at the time your code makes the call to .on(). To ensure the elements are present and can be selected, perform event binding inside a document ready handler for elements that are in the HTML markup on the page. If new HTML is being injected into the page, select the elements and attach event handlers after the new HTML is placed into the page. Or, use delegated events to attach an event handler, as described next.
Delegated events have the advantage that they can process events from descendant elements that are added to the document at a later time. By picking an element that is guaranteed to be present at the time the delegated event handler is attached, you can use delegated events to avoid the need to frequently attach and remove event handlers.
In your original code you set up the correct selector, but there were no matches at that time. So, #tree which exists at the time, can store the events made on its descendants.
Just one other thing
Why hide and show the list elements with jQuery? You're adding a class anyway so you could do that with CSS.

Document click not in elements jQuery

Using jQuery how does one detect clicks not on specific elements, and perform an action consequently?
I have the following JavaScript
$('#master').click(function() {
$('#slave').toggle();
});
$(document).not('#master','#slave').click(function() {
$('#slave').hide();
});
and I cannot see where I am going wrong logically. You can see a live example here
Since you're binding to the click event on the document, you can use event.target to get the element that initiated the event:
$(document).click(function(event) {
if (!$(event.target).is("#master, #slave")) {
$("#slave").hide();
}
});
EDIT: As Mattias rightfully points out in his comment, the code above will fail to identify click events coming from descendants of #master and #slave (if there are any). In this situation, you can use closest() to check the event's target:
$(document).click(function(event) {
if (!$(event.target).closest("#master, #slave").length) {
$("#slave").hide();
}
});
Does this code do what you want? (not entirely sure if I understood correctly)
$('body').on('click', '*:not( #master, #slave )', function() {
$('#slave').hide();
});
http://jsfiddle.net/gZ4Hz/8/
Event delegation has long been supported natively by jQuery. The difficulty lies in creating the appropriate selector. Originally, delegate was used, but more recently the delegate form of on should be used.
The purpose of event delegation is to listen to events on child elements and invoke the bound event handlers on those elements as though they had been bound to the child element, instead of the parent. This means that instead of binding handlers to every element in the DOM, you're binding a handler to every element in the initial selection (document is a single element). This also makes for a simple way to use a single selector to bind to an ever changing set of elements, as new elements will propagate their events to document whether or not they existed when the initial event handler was bound:
$(document).on('click', '*:not(#master, #master *, #slave, #slave *)', function (e) {
//this will reference the clicked element
});
Additionally, note that I not only said the elements must not be #master or #slave, they must not be children of #master or #slave either.
Another thought, it may not be working because your browser may not be rendering body at 100% height; Try adjusting your base css to fix height of body and then a couple other thoughts.
e.stopPropagation(): Prevents the event from bubbling up the DOM tree, preventing any parent handlers from being notified of the event.
So if you change the first click code to the following:
$('#master').click(function(e) {
e.stopPropagation();
$('#slave').toggle();
});
Then you could change the call sign of the second too:
$("body, body *").not('#master, #slave').click(function(e) {
$('#slave').hide();
});
And that should cover it. Give it a try! or see this jsFiddle
Fredrik's answers works for elements already present in the document, but it didn't work for elements fetched by ajax calls.
I tried the following and it works for me. Sharing the code for future ajax coders.
$(document).on('click',function(event) {
if (!$(event.target).closest("#selector").length) {
if ($('#selector').is(":visible"))
$('#selector').slideUp();
}
});
Would have posted it as a comment but I don't have enough reputation for that.
$('.clickable-row').on("click",function(){
window.location = $(this).data('href');
return false;
});
$("td > a").on("click",function(e){
e.stopPropagation();
});
or
jQuery(document).ready(function($) {
$('.clickable-row').on("click",function(){
window.location = $(this).data('href');
return false;
});
$("td > a").on("click",function(e){
e.stopPropagation();
});
});

Is there any Prototype Javascript function similar to Jquery Live to trace dynamic dom elements?

Event.observe(window,"load",function() {
$$(".elem_classs").findAll(function(node){
return node.getAttribute('title');
}).each(function(node){
new Tooltip(node,node.title);
node.removeAttribute("title");
});
});
Using above method, I can retrieve all elements having ".elem_class" and apply some javascript functions on them. But I have a modal/popup box which has some elements also having ".elem_class" and these dont get in the scope of findAll/each as they are loaded into the dom thru ajax.
How do I apply the same to dynamically loaded elements as well?
I am using Prototype Library. (I have used JQuery's Live function which keeps track of all future elements, but need to achieve something similar using Prototype)
Thanks.
As far as I know, event delegation is bot built into Prototype, but it shouldn't be too difficult to do on your own. Simply add a handler to observe the event on the body then use Event#findElement to check if it matches your selector.
Here is a sample function that sets up the delegation for you (run this on load):
/**
* event_type: 'click', 'keydown', etc.
* selector: the selector to check against
* handler: a function that takes the element and the event as a parameter
*/
function event_delegator(event_type, selector, handler) {
Event.observe(document.body, event_type, function(event) {
var elt = Event.findElement(event, selector);
if (elt != document)
handler(event, elt);
});
}
You could probably extend Element to handle this for you, streamlining everything. Hope this helps!
Edit: The hover event (or mousein/mouseout) should be a good event for a tooltip. Also, don't get all the elements on load, that's unnecessary when using event delegation. Here's a link to more about event delegation: http://www.sitepoint.com/blogs/2008/07/23/javascript-event-delegation-is-easier-than-you-think/

Categories