The Ol' Switcheroo
You have an element and within that element you want an item to be clicked on and set as active. Then you want to click on another item and have it to be set as active. Maybe it's navigation, maybe it's tabs. This is a common pattern. So much so, that I've rewritten this code a few times but keep losing track of it.
This is a jQuery plugin that does the ol' switcheroo and is very simple. When you click on an element, it'll set it as active. When you click on another, it'll remove the active class from the currently active item and then enable the active item. Super simple, nothing more, nothing less.
(function($){
$.fn.switcheroo = function(els){
this.each(function(){
var p = $(this);
$(els||'li', p).click(function(){
$('.active', p).removeClass('active');
$(this).addClass('active');
});
});
}
}(jQuery));
Then, it can be attached to an element using a couple different approaches:
$('ul').switcheroo();
$('ul').switcheroo('a');
Since this is most often used with lists, the first line of code omits any parameter as the plugin automatically looks for LIs contained within the parent element. However, if you want to be specific, you can specify the selector as the one and only parameter. That's all there is to it.
This can, of course, be used as a basic framework for building additional functionality in, like callback functions and animations. In any case, you can check out this demo page to see it in action.
Conversation
That's useful.
That's damn useful - thanks Jonathan! *adds to bookmarks*
I've always wondered: what's the purpose of the (function($){}(jQuery)) syntax?
Also, your comment form appears to be broken: http://img.skitch.com/20081106-et1bbbhufbwwstw9w5jsx5afpi.jpg
@Kyle: The function self instantiates itself, passing in the jQuery object, aliasing it to the dollar sign function. But it's within the scope of the function, preventing it from conflicting with other libraries.
Thanks for pointing out the error. I'll be moving servers soon and will be addressing a couple issues like that.
what about some "return false" action so we don't have to see the "#" in the URL?
I have some suggestions for improvements, here's my modified version of your script:
I return the original result set to not break chaining, and also only bind the event on direct descendants to avoid unexpected behaviors with nested items.
Great piece of code.
Isn't this want .toggleClass() is for?
You could also use this:
@sho'fr: Sure. You can either throw a return false into the switcheroo or add an extra click event in the chaining. $('ul').click(function(){return false}).switcheroo();
@Gerry: Yes, I forgot to add the
return
but I didn't usechildren
on purpose. If you notice the first example in the demo, your code wouldn't work because the links aren't direct descendants. I see what you mean though about nested items and I'd have to try some stuff out to avoid that (like maybe$('> li', p)
)@Gilbert: no, this is different. toggleClass toggles the class on one element. In this case, we're turning a class off one element and turning it on another.
@Rémi: The problem with your code is that it isn't isolated, meaning you'd have to duplicate the block for each block of code on the page. It also only works if you have a very specific HTML structure. For the LI example, you'd have to restructure the code since the relationship between the elements is different. I do like the succinctness though and could see me using that.
@Jonathan: Yes, I understand your point about not using children(). How about this:
I typically use a slightly different technique. Realizing that I may have more than one set of tabs on a page, I will contain a set of "a" or "li" tags within a "div" or "ul", and then parse only through that set (instead of going across the entire page).
And yes, each section would remember its active state.
@sho'fr
Here's the proper jQuery way to prevent the click event from finishing and showing a pound sign (changes from original in bold):
The "e" passed into the anonymous click function will be the click event object, to which jQuery conveniently appends the preventDefault() method. This added method standardizes event cancellation across browsers so you can avoid multiple if/else statements and effectively makes your code behave like the user didn't do anything.
For more information about jQuery's event system and normalization:
jQuery Documentation: Events Guide
Michael Thompson, I've noticed that "return false;" also prevents showing a # sign / jumping to a URL as a browser would expect. So for example:
Would this not also be correct?
@Michael Thompson eh... nevermind. Reread your post for the 5th and I finally got what you were saying. :)
Seems like a very smart and easy to use solution! Thanks for sharing!
why "p" ?
p = parent. Admittedly, I tend to be concise in my variable naming. I probably shouldn't be when it comes to sharing code. :)
Why not cache the last active element? Or is that too obvious?
I'm no jQuery-guy so I'll just post the lines you'd need. Not sure you'd need the parent at all then. I hope it makes sense ;)
I'd like to pass the class name also. So I can use any name, instead of .active.
(function($){
$.fn.switcheroo = function(els, activedClass){
if(!activedClass) {
activedClass= ".active"; //it's now a suggestion, not a fixed one
} else { //concatenate a dot
activedClass= "."+activedClass;
};
this.each(function(){
var p = $(this);
$(els||'li', p).click(function(e){
$(activedClass, p).removeClass(activedClass);
$(this).addClass(activedClass);
e.preventDefault();
});
});
}
}(jQuery));
Is it actually necessary to concatenate the "." into the className above?
I thought the addClass function would take care of that.
TEST1
Hey Jonathan,
I'm looking to really go further with JQuery and wondering how you learnt so much - was there a specific resource you used, such as a website/book etc?
Thanks!
Jack F
@Jack F: I'm self-taught so no specific site except the jQuery docs and learning from what other people put out. Half the battle is understanding the methodology of the library (for jQuery, knowing that most(/all?) calls return the jQuery object, how plugins work, learning the API, etc).