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';
}
}
});
Related
HTML:
<h2>CHECK AS MANY AS YOU CAN</h2>
<form id="boxone">
</form>
JS:
$boxone = $("#boxone");
$boxone.html('<input type="checkbox" class="fourthboxes">');
$fourthboxes = $(".fourthboxes");
for(var i=0; i <341; i++) {
$fourthboxes.clone(true, true).appendTo($boxone);
}
$fourthboxes.change(function() {
alert('yo');
});
The rest of the checkboxes don't alert when I click on them, only the original one does
I even tried $fourthboxes.on('click'... instead and still nothing.
I took a look at this question and tried the solution but it didn't work.
jQuery clone() not cloning event bindings, even with on()
Use .on():
$(document).on('change', '.fourthboxes', function() {
alert('yo');
});
This makes your event handler work for current elements, but also future added elements, that match the .fourthboxes selector. This uses the principle of delegated events.
From the documentation:
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.
Delegated events have the advantage that they can process events from descendant elements that are added to the document at a later time.
The issue is because although you use clone(true, true) you're cloning the elements before you add the change event handler to them. You just need to swap the logic around:
$boxone = $("#boxone");
$boxone.html('<input type="checkbox" class="fourthboxes">');
$fourthboxes = $(".fourthboxes");
$fourthboxes.change(function() {
alert('yo');
});
for(var i=0; i <341; i++) {
$fourthboxes.clone(true, true).appendTo($boxone);
}
However, it would be much better to use a single delegated event handler, like this:
var $boxone = $("#boxone").on('change', '.fourthboxes', function() {
alert('yo');
});
var $fourthboxes = $('<input type="checkbox" class="fourthboxes">').appendTo('#boxone');
for (var i = 0; i < 341; i++) {
$fourthboxes.clone().appendTo($boxone);
}
Working example
Since you assign $(".fourthboxes") to the variable $fourthboxes before you add the 340 other checkboxes, the variable still holds only that one checkbox when you add the change-function.
Put the change-function in front of the for-loop and everything works as expected.
It only selects the original because you never select the new elements. The selector only has the original, it is not a live collection. So you need to reselect them.
$(".fourthboxes").on("change", ...)
You can use event delegation so you are not selecting all the checkboxes. Listen for the change event on the form.
$("#boxone").on("change", ".fourthboxes", function(){});
When you clone an object, only the objects are cloned. And not their events. Because the events are bound to the original object (based on the jQuery selector used) before creating the clone.
As #trincot mentioned in his answer, you need to have an event at the document level.
For eg. Let's say my DOM contains three input checkboxes
<input type="checkbox" class="fourthboxes">
<input type="checkbox" class="fourthboxes">
<input type="checkbox" class="fourthboxes">
Now when you bind events using the jQuery selector like this,
$(".fourthboxes").change(function() {
alert('yo');
});
Things to note, is that this jQuery selector returns an array of DOM elements which are present on the page, at that instant of time. And then the onchange event is registered on each of them. It is equivalent to binding the event to each of the existing DOM.(In this case, three checkboxes)
Thank you in advance for looking at this.
My webapp allows a user to select choices from four different drop-down menus. When the user makes a selection, the program successfully performs the following click() function which creates a new span element within a div element:
var activeFilterNames = [];
$('.filter').click(function()
{
if (!this.classList.contains('disabled'))
{
//above checks to see if link has already been clicked
//and is therefore disabled. If not, go ahead.
activeFilterNames.push(this.textContent);
//above adds name of selected link to array
i = activeFilterNames.length;
var newFilter = document.createElement('span');
newFilter.id = activeFilterNames[i-1];
newFilter.className = this.className+" activated";
//above adds a new class to identify the filter as 'activated'
//above retains existing classname to identify
//which of the four filters it came from
newFilter.innerHTML = activeFilterNames[i-1];
//above creates display text to be rendered in browser
document.getElementById('active_filters').appendChild(newFilter);
//above is the div in which span will be appended to other spans.
$("#active_filters > span").removeClass('filter');
//above removes .filter class so that this newly created span does
//not respond to the .filter click() function.
$(this).addClass('disabled');
//above adds 'disabled' class to the originally
//clicked link, causing it to skip this block of code
}
}
);
Everything appears to work fine up to this point (though I may be missing something). Essentially I am creating span elements that come out looking like this in html:
<span id="id_name" class="menu_name activated">Rendered Name</span>
And since the above span does not have the filter class, I then try to create a new function in javascript (just as a test) to make the element responsive:
$('.activated').click(function()
{
alert('hi');
}
);
But no luck. I've tried to re-render the dynamically created elements by nesting div or a inside the span, modifying the code as needed, but still nothing. I would like to keep the span because it's the only way I've found to wrap these dynamically generated elements to a second line within the div (#active_filters) where they are being created.
Can anyone see what I'm doing wrong given that I want to make the activated click() function responsive within each newly created span element?
Your binding will not work if you attempt to bind to DOM elements contained in $('.activated') before creating them. What this usually means is that you need that event listener to bind after creating. If you're dynamically creating DOM elements, you need to do something like this:
var activeFilterNames = [];
$('.filter').click(function()
{
if (!this.classList.contains('disabled'))
{
activeFilterNames.push(this.textContent);
i = activeFilterNames.length;
var newFilter = document.createElement('span');
newFilter.id = activeFilterNames[i-1];
newFilter.className = this.className+" activated";
newFilter.innerHTML = activeFilterNames[i-1];
document.getElementById('active_filters').appendChild(newFilter);
$('.activated').unbind('click');
$('.activated').click(function()
{
alert('hi');
}
);
$("#active_filters > span").removeClass('filter');
$(this).addClass('disabled');
}
}
);
Notice, before binding we unbind. This makes sure that if you do this multiple times, you aren't binding 2, 3, 4 times to the same DOM element.
You need to attach click event on dynamically created elements. In jQuery this can be done using on method if you will pass your selector as second argument and attach click to some parent element, body for example:
$( 'body' ).on( 'click', '.activated', function()
{
alert('hi');
}
);
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.
I have a couple of drop down boxes with ids country1, country2, ... When the country is changed in a drop down the value of the country shoudl be displayed in an alert box.
if I add the onchange handler for one box like this it works fine:
$('#country1') .live('change', function(e){
var selectedCountry = e.target.options[e.target.selectedIndex].value;
alert(selectedCountry);
});
But I need to do this dynamically for all drop down boxes so I tried:
$(document).ready(function() {
$('[id^=country]') .each(function(key,element){
$(this).live('change', function(e){
var selectedCountry = e.target.options[e.target.selectedIndex].value;
alert(selectedCountry);
});
});
});
This doesn't work. No syntax error but just nothing happens when the seleted country is changed. I am sure that the each loop is performed a couple of times and the array contains the select boxes.
Any idea on that?
Thanks,
Paul
The reason .live() existed was to account for elements not present when you call the selector.
$('[id^=country]') .each(function(key,element){ iterates over elements that have an id that starts with country, but only those that exist when you run the selector. It won't work for elements that you create after you call .each(), so using .live() wouldn't do you much good.
Use the new style event delegation syntax with that selector and it should work:
$(document).on('change', '[id^=country]', function(e) {
// ...
});
Replace document with the closest parent that doesn't get dynamically generated.
Also, consider adding a class to those elements along with the id attribute.
Instead of incremental ids I'd use a class. Then the live method is deprecated but you may use on with delegation on the closest static parent or on document otherwise.
$('#closestStaticParent').on('change', '.country', function() {
// this applies to all current and future .country elements
});
You don't need an each loop this way; plus events are attached to all the elements in the jQuery collection, in this case all .country elements.
I'm making a group of elements from a JSON data:
Example:
{
'name':'form',
'elements':[
{'name':'bt1','type':'button','value':'hello','order':'1'},
{'name':'img1','type':'image','value':'http://www.images.com/img.jpg','order':'2'}]
}
What i do with this json is create a form with the elements described in 'elements' with a code like this:
(I've got this draft in mumbo jumbo + jquery code)
$('#container').html();//clears the container
for each element in elements do
switch element.type
case 'button':
$('#container').append('<input type="submit" value="'+element.value + ... etc');
end case
case 'image':
insert image bla bla bla
end switch
end for each
I want to detect if an element gets clicked or another kind of action, like mouse hover, etc. How do i bind this to the elements?
Also, how do i update the elements without destroying them?
EDIT: I implied something important, my bad:
I need to link the data in the elements javascript object with the generated html elements. A data field wich i retrieve when an action is triggered. That's the porpouse of all this.
You have two options. You can bind the listeners after you've created the elements, like this:
var $input = $('<input type="submit" value="'+element.value + ... etc')
.focus(...).blur(...).etc.;
$('#container').append($input);
Or, you can use event delegation. On your initial page load you can do this:
$("#container").on( "focus", "input", function(){...});
This will cover all input elements in #container either currently or dynamically added later. You can read more about event delegation in the on docs.
Building the form is really very easy, since you've basically mapped all of the attributes of the elements in an object sytanx. As such, we can create these elements with nothing more than choosing a tag, and passing the attribute object in as the second parameter of the jQuery function:
/* Container reference, counting variable */
var container = $("#container"), i = 0;
/* Clear out the container */
container.html("");
/* Cycle through each element */
while ( current = data.elements[i++] ) {
/* Evaluate the value of the current type */
switch ( current.type ) {
/* Since <input type='button|image'> are so similar, we fall-through */
case "button":
case "image" :
/* Choose a base element, pass in object of properties, and append */
$("<input>", current).appendTo(container);
break;
}
}
When it comes to registering clicks, or any other type of event, we'll use the $.on method. Because we're passing in a selector ( "input" in this case ), this will not only match all present elements, but all future elements as well.
/* Listen for all clicks on input elements within the container */
container.on("click", "input", function(){
/* We have a button, and an image. Alert either the value or src */
alert( this.value || this.src );
});
Online Demo: http://jsbin.com/izimut/edit#javascript,html
To detect events on dynamically added elements, you should use on() for jQuery 1.7+ and .live() for previous versions.
EDIT: And yes, as James pointed out in the comments, delegate() is always recommended over live().
if your js code is short, just add your js code in the append function.
append('<input type="submit" onclick="xxxx" value="'+element.value + ... etc');
if your js code is long, you can add an id to your new element.
and add event to the id.
$("#idxx").click(function(){alert("Hello");});
Either bind the element directly
$input = $('<input type="submit" value="'+element.value + ... etc');
$input.on('click', function() {
// do something
}
$('#container').append($input);
or put the bind on a parent that checks the select of what was click inside..
$('#container').on('click', 'input', function() {
// do something
}