How do I limit an event to a single element in a jQuery collection?
In the case below, I've tried using .one() to limit the behaviour (inserting the <li class='close'>Close</li> line of HTML) to a single instance. The behaviour does indeed happen only once, but on EVERY matched element of $( "ul>li>a" ). How do I make it happen only once, to only ONE of the matched elements in the collection?
Any ideas?
$( "ul>li>a" ).one(
"click",
function(){
$( "ul ul")
.prepend("<li class='close'>Close</li>")
}
);
Thanks in advance.
-AS
A jQuery selection returns an array. Therefore $("selection")[0] can work. However there are better abstracted methods for this, like .get(0) or .first() (in case you're looking for the first element of the selection/array).
$("selection").get(index) returns the pure DOM element (at that specific index) of the selection, and is not wrapped in the jQuery object.
$("selection").first() returns the first element of the selection, and wraps it in a jQuery object.
So if you don't necessarely want to return the first element, but still want jQuery functionality, you can do $($("selection").get(index)).
Given your situation, this should work fine:
// bind the 'onclick' event only on the first element of the selection
$( "ul>li>a" ).first().click(function() {
$( "ul ul").prepend("<li class='close'>Close</li>");
});
Which is equivalent to this:
$($( "ul>li>a" ).get(0)).click(function() {
$( "ul ul").prepend("<li class='close'>Close</li>");
});
And this:
$($( "ul>li>a" )[0]).click(function() {
$( "ul ul").prepend("<li class='close'>Close</li>");
});
I must disagree with Ryan, working on the CSS selection string to filter the result is rather expensive compared to the native JavaScript array functionality.
Try first(), it selects the first element:
$( "ul>li>a" ).first().one('click',
function(){
$( "ul ul").prepend("<li class='close'>Close</li>")
}
);
one() is used, as you already noticed, to handle an event only once.
You have to specify the index of the element you want to work with.
If your selector returns more than one element you can do one of a couple things...
You can isolate your elements by giving them a class or id attribute in your html and alter the selector to select only the class/id of the element/s you wish to select or you can specify the index of the element you're trying to work with. The later method is a bit sloppy but works as long as your page structure doesn't ever change.
So for the first method I spoke of you'd change your selector to this after applying a class/id to your elements:
$("ul>li>a.class")
or
$("ul>li>a#id")
For the second method I mentioned you'd change your selector to this:
$("ul>li>a:eq(index)")
Where index is the zero based index of the element you're trying to select.
You can call the first method, which will return a new jQuery object containing only the first element in the original one.
However, in your case, you might as well use the (equivalent) :first selector, like this:
$("ul > li > a:first").click(function() { ... });
If you only want to handle the first click event and ignore any subsequent clicks, you'll need to use .one(), like you already are.
You need to combine first() with one():
$( "ul>li>a" ).first().one('click', function () {});
More general:
$( "ul>li>a:eq(n)" ).one('click', 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';
}
}
});
Quick, simple question.
I have this function working at the moment ;
$("#menuopties").click(function(){
$("p").toggle();
});
However this toggles every p tag.
I just want to toggle the p tags which are under the div #menuopties (which has been clicked)
Thanks.
Your current selector "p" will get all the elements of type p instead of getting the p within the current object. Use find() to get the descendant of current element. you will get the source of event object using $(this)
$("#menuopties").click(function(){
$(this).find("p").toggle();
});
You can use pass current object in context of the selector using jQuery( selector [, context ] )
$("#menuopties").click(function(){
$("p", this).toggle();
});
Try like this
$("#menuopties").click(function(){
$(this).find("p").toggle();
});
How about:
$("p", this).toggle();
try:
$("#menuopties").click(function(){
$(this).find("p").toggle();
});
hope that helped.
Change $('p').toggle(); to $(this).children('p').toggle();
$(this) refers to the current jQuery object (in this case wrapping the #menuopties DOM element), so running .children() allows you to filter its descendant elements by whatever selector you want (in this case p).
edit: as buzzsawddog pointed out, it's important to note .children() only returns the child elements a single level below in the DOM, so if your p tags are not immediate children of #menuopties you should use .find() instead.
I have an element which listens to the onclick event. It calls a function once it was clicked. After that element is a < dd > which I want to select in a CSS selector. The element which is clicked, is a < select >. How would I do that?
This is the HTML:
<select onclick="myFunction();">...</select>
<dd>...</dd>
function myFunction() {
// What do I have to write for the ??????
$$('?????? dd').toggle();
}
Note: There are many of those select/dd combination, so I really have to get the next dd after the firing element.
The minimal change is: Pass this into your function:
<select onclick="myFunction(this);">...</select>
...and then:
function myFunction(select) {
$(select).next().toggle();
}
$ enhances the element, then you can use next to move to the next element. If you like, you can use .next('dd'), but in your case the dd is the next element.
That still uses onxyz attributes, which is a bit old-hat. You might consider hooking things up via observe instead.
I am guessing you mean this:
this.next("dd");
(specifying dd so when there's an error in the mark up, no other element is selected)
If you are trying CSS selectors only, try the following:
$("select + dd").toggle();
Note: this will toggle all dds that follow a select.
Note 2: apparently this does not work in Prototype but it does work in jQuery.
See T.J.Crowder's comment:
[This doesn't work in Prototype] because $ in Prototype looks up elements by ID. $$ is more like
jQuery's $, but what it returns doesn't do set-based operations like
jQuery does (or rather, not the same set-based operations as the ops
you can do on individual elements; you have to use invoke).
next() works on both jQuery as Prototype.
Use:
$(this).next("dd").toggle(); --> this is Jquery
$(element).next("dd").toggle();
see the link Element.next
<select>...</select>
<dd>...</dd>
$('select').change(function(){
$(this).next("dd").toggle();
});
Better use unobtrusive javascript, so your js is better coupled from html markup.
HTML:
<select><option value="test">Test</option></select>
<dd>Test</dd>
JS:
//Event.observe(window, "load", function() {
document.observe('dom:loaded', function() {
$$('select')[0].observe('click', function(event) {
var next = event.element().next();
next.toggle();
});
});
JSFiddle
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 a little confused. Basically, I've got 16 span elements on my page which are displaying brands. I want to show only 4 brands at a time, and then set an interval to show the next 4 brands until I reach the end, at which point I'll reset a counter and start from the beginning again. Here is what I was thinking:
Display all brand span elements
Put all of the brand elements into an array
Count first 4 items (brands) and give them the class of visible
After 5 seconds, hide all elements with class of visible
Display the next 4 items in the array
When the end of the array is reached, reset the counter and start again
Some general advice or help with jquery arrays would be appreciated. I'm looking for the most dynamic and simple solution possible.
Thanks :)
Here, it's a bit hackish though...
var elems = $( 'span' ).hide().get();
(function loop () {
var i = 0, pointer;
pointer = $( elems ).filter( ':visible:last' )[0] || $( elems ).last()[0];
$( elems ).hide();
while ( i < 4 ) {
pointer = $( pointer ).next()[0] || $( elems ).first()[0];
$( pointer ).show();
i += 1;
}
setTimeout( loop, 5000 );
})();
Live demo: http://jsfiddle.net/hBrt6/
If you require an explanation of the code, just let me know...
A few explanations:
.get() returns an array of the DOM elements inside the jQuery object.
So
$( 'div' ).get()
gives you an array of all the DIV elements on the page.
Using the property accessor operator [i] will give you the i-th DOM element from the jQuery object.
So
$( 'div' )[4]
returns the fifth DIV element on the page.
It is important to understand that you cannot invoke jQuery objects on DOM elements itself (or arrays of DOM elements).
So this
$( 'div' )[4].hide();
throws an error. DOM elements don't have a hide method.
If you want to target a specific element inside a jQuery object and while still retaining a jQuery object, use .eq().
This
$( 'div' ).eq( 4 ).hide();
works just fine.
Now that you understand this difference, let me explain why I use get() and [i] in my code: The thing is, I don't like to store jQuery objects inside variables. Instead I prefer to work with DOM elements and arrays of DOM elements directly.
When I need to invoke a jQuery method on some element or array of elements, I just wrap it inside the $() function first.
So this
$( elems ).hide();
works just fine.
And when the jQuery method has done the job, I just append .get() or [0] to "unwrap" the jQuery object = to get my element(s) back.
If the data exist as a JavaScript object then you can use JQuery templates to render the HTML.
You would do the following
Get all data in array of JavaScript objects (might already have this)
Empty the target element container you are inserting too ($('#target').empty())
Slice the array to only have the 4 elements you want (data.slice(index, index+4))
Render template into target element container with sliced array as the model ($('#template').tmpl(slicedArray).appendTo('#target'))
You should be able to do step 3 with a for loop that steps by 4. And in the template use the {{each}} method to loop over the row creation.
Ok, my solution will be as dynamic and simple as I will think to be possible. I'm good at making things like that and am quite good at jQuery and JavaScript so I thought I'd try my hand at this. I'll just write the best code I can think of here and show where you need to edit your other code after.
$("#ContainerElement.span:gt(4)").hide();
var i = 0;
var b = setInterval("BrandChange();",5000);
function BrandChange()
{
$("#ContainerElement.span:eq(i),
#ContainerElement.span:eq(i+1),
#ContainerElement.span:eq(i+2),
#ContainerElement.span:eq(i+3)").hide(normal,function(){
i+=4;
if(i == 16) i = 0;
$("#ContainerElement.span:eq(i),
#ContainerElement.span:eq(i+1),
#ContainerElement.span:eq(i+2),
#ContainerElement.span:eq(i+3)").show(normal);
});
}
I had to look up some jQuery selectors to figure this out, but that doesn't disprove it's validity, basically it first hides all the span elements in the Element which holds them, which will need an id of "ContainerElement" starts an interval for the BrandChange function, which fades out and hides the four current brands, then fades in and shows the next four brands, while incrementing the i variable to keep count, then it waits for the interval to happen again in five seconds. It's not necessarily the way you planned to do it, but I think you'll find it does exactly what you want it to. If you have any questions, just ask. :)