I have a jQuery plugin (which I've written) which has an option to match an optional child selector. Ie: If I have this markup:
<div class="header">
<a id="item-name">Blah</a>
<span class="some-data">Numbers and Stuff</a>
<span class="other-data">Bananas</a>
</div>
Orignally, I delegated the click event to the whole .header using:
headerDiv.on('click', function () {
But then needed to limit the click to only the anchor, so to maintain compatibility, I used the alternative syntax:
headerDiv.on('click',headerSelector, function () {
Where headerSelector, by default is *, but I could pass a to limit it to just the anchor. The default * selector I'm now using seems problematic though, sometimes clicks aren't registered. Is there a proper way to match the element itself when using the delegated event syntax?
By default you can do
headerSelector = headerDiv
I think it will be equal to headerDiv.on('click', function () {
Use document as the parent element and inside selector use headerDiv and then a *. if child is given replace * with it.
$(document).on('click', "headerDiv *", function(){})
Related
I am creating a form that implements a bunch of similar elements. They are custom select boxes, created out of <ul>s.
Some of these elements are slightly different in the way I want the mousedown event to be handled though.
The way I have it set up currently is that, by appending _custom_select to the end of an elements class name, it will be treated as one of these special elements as far as CSS is concerned.
However, when the string selections is found inside a class name (that will coincidentally also end with _custom_select in order to apply the proper styling) I want to use a different mousedown event handler.
This is the relevant section of my event listener set up:
$('[class$="_custom_select"] li').mousedown(function(event){
var opt= event.target;
if(opt.className!='li_disabled' && event.which==1)
{
if(opt.className=='li_unselected'){
opt.className= 'li_selected';
}
else{
opt.className= 'li_unselected';
}
update_selections(opt.parentElement);
}
});
$('[class*="selections"]').mousedown(function(event){
var opt=event.target;
if(event.which==1){
if(opt.className=='li_unselected'){
opt.className= 'li_selected_2';
}
else{
opt.className= 'li_unselected';
}
}
});
This code works, but notice how, in the second binding, I had to bind the event listener to the ul that holds the li that is actually being clicked.(The ul is the element whose class name matches the pattern) In the first one however, I can bind the event listener directly to the li elements contained within the ul.
If I change the second jQuery selector to $('[class*="selections"] li') the event listener is never bound to the corresponding lis.
What is causing this behavior?
I am aware that I can just check event.target.tagName to ensure the event is bubbling up from an <li>, but that is not what the question is about.
I originally thought it had something to do with precedence and that the listeners weren't being bound because the lis that would have matched the second selector already matched against the first selector.
However, after implementing logging and looking at the DOM I have determined that when I change the second selector to: $('[class*="selections"] li') neither event listener is bound to the lis that match the second selector.
Here is a link to a JS fiddle of the 'working version'. If you add ' li' to the second selector and then try to click the <li>s in the box to the right, you will see that they no longer become green.
jsFiddle
https://jsfiddle.net/6sg6z33u/4/
Okay, thanks for posting the jsFiddle. This is an easy fix!
The elements in your second li are being added dynamically. When you bind to elements using the shortcut methods like .click() it only binds to the elements on the page when it initially bound
The fix: use the .on() method, which is the preferred method per jQuery foundation. This method allows for live binding meaning it will pick up on dynamic elements.
$('[class*="selections"]').on( 'mousedown', 'li', function(event) {
var opt = event.target;
if (event.which == 1) {
if (opt.className == 'li_unselected') {
opt.className = 'li_selected_2';
} else {
opt.className = 'li_unselected';
}
}
});
Inside an event handler, why does $(this) return something else than $('.selector')?
Example:
$('.container').click(function () {
console.log($(this));
console.log($('.container'));
});
jsFiddle
When you look in the console the results are different.
this is always the element on which the event originated, in other words which of the .container elements you clicked exactly.
e.g.:
<div class="container">container1</div>
<span class="container">container2</span>
as Jonathan Lonowski notes, $(".container") selects both .container elements but this is the one you clicked, either the span or the div.
Also, $(this) just wraps that element into a JQuery object, the this keyword itself is native javascript.
Inside the of the event handler, this will normally refer to the single .container element that captured the event.
While the selector will once again find all of the .containers throughout the document.
How do i even put these, let me try. In the following sets of codes, i want to click 'parentclass' and have an alert value of 'child1' and when i click the class below it which is 'Parent 2' have an alert fire with a value of 'child2'
So this must alert the content of that class only and not the entire class.
Here's some Javascript in Jquery.
var childclass = $('.childclass').html();
$('.parentclass').click(function(e) {
alert (childclass)
});
$('.childclass').click(function(e) {
e.stopPropagation()
e.preventDefault()
});
And HTML
<a href="" onClick="return false">
<div class='parentclass'>
Parent 1
<div style="display:none" class="childclass">child1</div>
</div>
</a>
<a href="" onClick="return false">
<div class='parentclass'>
Parent 2
<div style="display:none" class="childclass">child2</div>
</div>
</a>
This line var childclass = $('.childclass').html(); doesnt make sense as it doesn't know which element in particular you mean. The result of that will just be child1child2 which is just a concatenation of the .html() of all the elements with class childclass. This is obviously not what you want.
Therefore you should dynamically find the child with a class of childclass upon receiving the click event.
$('.parentclass').click(function(e) {
alert($(this).find('.childclass').html())
});
Also, you should know that your child class event handler is useless as we don't care if the event gets propogated downwards. If you DID care, then your e.stopPropagation() and e.preventDefault() should be in the event handler of the parent class.
You need to fetch the html of the clicked parent element within the click handler
$('.parentclass').click(function (e) {
alert($(this).find('.childclass').html())
});
$('.childclass').click(function (e) {
e.stopPropagation()
e.preventDefault()
});
Demo: Fiddle
Several ways you can go about this.
First, if your HTML will not be dynamic (elements already exist when page loads), then you can select elements by the parent class name and assign click event as so:
$('.parentclass').click(function(e) {
// the first variable here is selecting the inner elements having class 'childclass'
// keep in mind, if more than one child having that class is present within this parent, it will select all of them
var child = $(this).find('.childclass');
// here we alert the text of the inner child found
// if it is more than one, you will have undesired results. you may want to specify `.first()`
alert(child.text())
})
For newer jQuery you can also use $('.parentclass').on('click', function(e) {.
If you expect any pieces of parentclass to be dynamic, then you'll want to delegate the event based on either a static parent to the parents or document. This can be like so:
$(document).on('click', '.parentclass', function(e) {
alert($(this).find('.childclass').text())
})
Or, if you have a static (already there when page loads) wrapping element, give it an ID like `parentClassWrapper' and assign the click event dynamically as:
$('#parentClassWrapper').on('click', '.parentclass', function(e) {
alert($(this).find('.childclass').text())
})
Some helpful links:
jQuery API
jQuery Selectors
.click()
.on()
Some info on Event Delegation
jquery on vs click methods
jQuery .on('click') vs. .click() and .delegate('click')
jquery .live('click') vs .click()
I made several adjustments to your html that are worth noting. There's no need for the <a> tag. Don't use inline js - onlick in your html. Note that I wrapped the text inside of the div in the <a> tag instead. This markup is more semantic. Also, move your styles to css rather than in the html.
<div class="parent">
<a>Parent 1</a>
<a class="child">child of parent 1 contents</a>
</div>
<div class="parent">
<a>Parent 2</a>
<a class="child">child of parent 2 contents</a>
</div>
css:
.parent > .child { /* good practice: only select an immediate child of the parent */
display: none;
}
The other answers here are using find() to select the child, but I recommend children() instead. For example, if you had additional nested .childs, find() will select them all, but children() will only select direct .childs of the parent, so it is better in this case. I also recommend using the console for debugging rather than alert.
Live demo here (click).
$('.parent').click(function() {
var $child = $(this).children('.child');
var cls = $child.attr('class');
console.log(cls);
$child.show(); //added so that you can click the child
});
$('.child').click(function() {
var html = $(this).html();
console.log(html);
//if you just want the text, use this instead:
var text = $(this).text();
console.log(text);
});
Scenario
I have a list of users, within each list item is a <header> and a <div> wrapper - they are siblings:
<li class="user">
<header>
// ...
</header>
<div class="user-wrapper">
// ...
</div>
</li>
I am toggling the div wrapper when the user header is clicked.
What currently works:
// the handler is called
$('li.user > header').live('click', function () {
$(this).next('.user-wrapper').toggle();
});
As live() has been deprecated and I am using jQuery 1.7.2, I want to use on().
What does not work:
// Direct style
$('li.user > header').on('click', function () {
$(this).next('.user-wrapper').toggle();
});
// Delegated style
$('li.user').on('click', 'header', function () {
$(this).next('.user-wrapper').toggle();
});
In either on() scenario, the anonymous handler function is not being called.
Question
First... why is on() choking?
Second... if/when it works, using the delegate style, can i still reference the header's sibling div in the same manner as above?
[EDITED]
For delegate event handling the syntax of .on() is:
// 'element' must be static and an ancestor to 'target'.
$(element).on(eventName, target, handlerFunction);
Your delegate scenario above should work, assuming your li.user tags are static at the time of binding.
$('li.user').on('click', 'header', function () {
$(this).next('.user-wrapper').toggle();
});
If you test this in jsFiddle, it works as is. It seems like your li.user elements are being created dynamically.
If li.user is dynamically created then use a different (static) parent selector. If your list ul is always present, for example:
// HTML
<ul class="user-list">
<li class="user">
<header>
// ...
</header>
<div class="user-wrapper">
// ...
</div>
</li>
</ul>
// JavaScript
$('ul.user-list').on('click', 'li.user > header', function() {
$(this).next('.user-wrapper').toggle();
});
Thanks
Lots of gratitude to #thecodeparadox - his answer explains the issue best and is marked as such. My answer below just explains the details of how/why the list items became dynamic.
Further explanation
Knockout.js is being used to template the li elements. This alone was not the issue as the li elements were created before the .on() bindings occurred and are still treated as "static".
The killer was a ko.computed() property on the knockout model. The property reloads the collection here/there. From the moment the collection is reloaded, the scaffolding of the li items is redone and all the .on() break.
This question already has answers here:
jquery stop child triggering parent event
(7 answers)
Closed 8 years ago.
I am not sure this is really bubbling, I will explain.
I have this:
<div>
<div>
text here
</div>
</div>
How to bind an on click event so that it will affect only the enclosed div? If I set it like this:
jQuery('div').bind('click', function() {
jQuery(this).css('background','blue');
});
it makes blue all the divs. If I add false as the third argument(prevent bubbling) to the bind function it does nothing.
How can I solved this?
http://api.jquery.com/event.stopPropagation/
Add event.stopPropagation(); inside the hander.
(It might be better, though, to assign an ID or class to the nested DIV so you can be sure it's the only one affected.)
You should really use identifiers like IDs or classes, but for your example, you could do this:
jQuery('div > div').bind('click', function() {
jQuery(this).css('background','blue');
});
...which will bind the handler to any div that is a direct descendant of another div.
So either make your initial selection specific to the element(s) you want to affect, or use event delegation, placing a handler on an ancestor, and testing for the element you want.
Delegation example: http://jsbin.com/ehemac/edit#javascript,live
<div id="container">
<div class="outer">
<div>
text here
</div>
</div>
<div class="outer">
<div>
text here
</div>
</div>
</div>
jQuery('#container').delegate( '.outer > div', 'click', function() {
jQuery(this).css('background','blue');
});
This uses the delegate()[docs] method that places a handler on the ancestor with the ID #container.
The first argument to .delegate() is a selector. Any elements that are clicked inside #container will have that selector compared against the element clicked. If the selector matches, the handler will be invoked.
http://jsfiddle.net/vol7ron/WzSkj/
Targeting the last descendant
Credit to Patrick DW:
jQuery('div:not(:has(div))').bind('click', function() {
jQuery(this).css('background','blue');
});
This should be all you need as it will look at all div and find those that don't have child divs (thus, they will be the last descendant of that element type. You could further filter this to make sure they have a parent that is a div, if you wanted to exclude those divs that are standalone.
Older Answer:
This is not by any means meant to be a complete/robust plugin. It serves as only an example of how to target the last element in a chain. See the revision history for a way to do it w/o the plugin. This should be modified if you wish to use it for production.
Plugin:
(function($){
$.fn.lastDescendant = function(el){
var found = jQuery(el + ':first').siblings(el).andSelf();
var prev, curr;
var stack = this;
for (var i=0,n=found.length; i<n; i++){
curr = found.eq(i).find(el);
while (curr.length){
prev = curr;
curr = curr.find(el);
}
stack = stack.add(prev);
}
return stack;
};
})( jQuery );
Example Call:
jQuery.fn.lastDescendant('div')
.click(function(){
jQuery(this).css("background","#09c");
});
Note:
this will not select the first (ancestor) element. If you want to select that as well, you could wrap the whole thing in a new div, and then do the above.
if I were to make this a production plugin, I would include checking the parameter, and allow you to be able to pass in an object and a starting point (so that siblings are not selected)
To fix this just use a more specific selector
jQuery('div > div').bind('click', function() {
jQuery(this).css('background','blue');
})
The best way to solve it would be to give your inner div an identifiable feature such as a class, e.g., <div class="inner"></div>.
Alternatively, change your selector:
$('div > div').click(function() {
$(this).css('background', 'blue');
}
try giving the inner div an id tag and refer to it...
<div><div id=foo>text goes here</div></div>
...
$('#foo').bind('click', function() {
$(this).css('background','blue');
});
HTH
-- Joe