If any of you use GMail, you'll know that you can shift click the checkboxes on the conversation list to select a range of conversations (i.e. click the second conversation's checkbox and then shift-click the tenth conversation's checkbox). You can also deselect the same way (click the seventh, and then shift-click the fourth). Finally, you can shift-click a second time (third click total) to extend the range. I wanted that functionality in one of my apps, and here it is.
Update: I've repackaged the code as a jQuery plugin based on Dan Switzer's suggestion. I've left the original code in place, just struck it out.
Update: I've added namespacing of the event handler, and support for the meta key (Command on Mac) as well as shift based on Henrik's comments below.
function configureCheckboxesForRangeSelection(spec) {
var lastCheckbox = null;
jQuery(function($) { // for Prototype protection
var $spec = $(spec);
$spec.bind("click", function(e) {
if (lastCheckbox != null && e.shiftKey) {
$spec.slice(
Math.min($spec.index(lastCheckbox), $spec.index(e.target)),
Math.max($spec.index(lastCheckbox), $spec.index(e.target)) + 1
).attr({checked: e.target.checked ? "checked" : ""});
}
lastCheckbox = e.target;
});
}); // for Prototype protection
};
(function($) {
$.fn.enableCheckboxRangeSelection = function() {
var lastCheckbox = null;
var $spec = this;
$spec.unbind("click.checkboxrange");
$spec.bind("click.checkboxrange", function(e) {
if (lastCheckbox != null && (e.shiftKey || e.metaKey)) {
$spec.slice(
Math.min($spec.index(lastCheckbox), $spec.index(e.target)),
Math.max($spec.index(lastCheckbox), $spec.index(e.target)) + 1
).attr({checked: e.target.checked ? "checked" : ""});
}
lastCheckbox = e.target;
});
};
})(jQuery);
It requires jQuery to be available (I used 1.2.1 and 1.2.3), and is safe to use with Prototype also in-scope (regardless of which owns the $ function). Call that function passing in a jQuery expression (NOT a jQuery object) that describes the checkboxes you want to be range-selectable:
configureCheckboxesForRangeSelection("input.image-checkbox");
$("input.image-checkbox").enableCheckboxRangeSelection();
Thanks to Matt Wood (a coworker) for the slice/index suggestion. My initial implementation had used an each with a conditional and a status variable – definitely less elegant. Check the project page for any additional updates.
My jQuery Field Plug-in (http://plugins.jquery.com/project/field) contains a method which does the same thing. It also has some other helpful form functions (like auto tab advancing) and limiting the number of selection you can make.
Also, with a little refactoring you could make your code an actual true jQuery plug-in–which makes it a little more intuitive to use.
Now you should be able to just do:
Nice one Dan. This was my first foray into anything jQuery, and obviously missed a pile of stuff. ;)
FYI – I should clarify that I didn't actually test the code above (so there may be a bug.)
Dan,
Yeah, there are some variable reference issues. I repackaged as a plugin, and updated the code and example in the post.
Please see example here:
http://resopollution.com/granicus/training/library.html
This works, until I sort the columns. Then the checkboxes indexing is messed up. Is there anyway to re-define the index values?
Weixi,
There's not a way in the plugin to unbind it's listener, so no, not really. It'd be fairly trivial to add however. Then when you reorder, you'd remove the listener, and readd it with the checkboxes in the right order.
Thanks for the feedback!
Please excuse me as I am new to jquery, but what do you mean when you are talking about listener?
Thanks,
Weixi
Weixi,
The .bind() function binds a listener for a given event to the DOM elements selected by the jQuery expression. The first param is the event name, and the second is a closure that gets called on the event (the listener). If you unbind the listener, you'll be back to the default state of "nothing extra happens". Then you can rebind with the DOM elements in the new order.
Hi Barney,
Thanks, I found it
This did the trick!
Unfortunately, the checkboxes now go unchecked when I sort. I wish there was a way to maintain the checkboxes that are checked but with the new indexes… i'll keep working on it.
Thanks for all the help so far!
Hi Barney,
I am using this for sort plugins:
I realized something odd. Firefox and IE7 does cache the checkboxes, just IE6 does not. Anyways, that is good enough. It pretty much works well enough.
Another question if I may. In Gmail, I noticed that the background container gets highlighted when someone clicks a checkbox. Could you possibly point me in the right direction how to link the action of shift-clicking a checkbox and binding another object action?
Thank you,
Weixi
Hi Barney,
Thanks for explaining. However, I still don't quite understand how the unbind works. Is there an unbind() function? If so, which object should I apply it to?
Thanks,
Weixi
Weixi,
How does your sort work? It might be the only option if they're getting unchecked because they're being removed from the DOM and then reinserted (which will clear them). You could cache the checked checkboxes, sort, and then recheck them, I suppose.
Weixi,
You can bind multiple listeners for a single event.
@Weixi:
IE6 loses the state of most form fields when the element is cloned. I would bet you might have to do something more complex to actually move the nodes other than the sort code you have above, but before implementing any more code I'd make sure you test the latest version of jQuery.
Thanks for the insights guys. Instead of keeping the selection, I actually decided to remove caching for consistency's sake for now. I did get the highlighting to work too. I just appended some code to the plugin. Not sure if there was a better way to do it:
http://resopollution.com/granicus/training/js/ui.checkboxrange.js
I am really happy with the result I have so far though. Just discovered I could use tags to sync with the checkboxes and enable the entire row to be shift-clickable.
http://resopollution.com/granicus/training/library.html
I could even get rid of the checkboxes visually by hiding them behind other divs or using position:absolute and left:-10000px
This is a great plugin and thanks for the great support!
you rock
Thanks for this.
I made a slight modification, namespacing the click event (so it can be unbound unambiguously) and unbinding it before reapplying.
http://pastie.textmate.org/316003
I have checkboxes in drag-and-drop sortables. This way, I can re-apply enableCheckboxRangeSelection after sorting to get the order right.
I also changed e.shiftKey to (e.shiftKey || e.metaKey) so Command (in OS X) can be used in addition to Shift. Feels better somehow.
Thanks Henrik. I didn't know you could "name" handlers like that – very handy. I've updated the code in the post with both of your suggestions.
Barney, I made Gist with my modifications to this method as well as some other useful checkbox methods. I linked back here – let me know if your code is under some specific license or you want me to change how I credit you.
Henrik,
Cool. Attribution is perfectly fine as-is. I'm not a licensing nazi, even though I probably should be.
Works charmingly well! Thank you very much!
By putting the LastCheckBox assignment inside an ELSE clause, you can toggle the selected checkboxes by shift-clicking again.
I know this is an old thread, but i was inspired by the function of the original post. Still it didnt work exactly like the solution gmail uses.
I experimented a lot these 2 days to get the same behavior gamail uses and figured out how to get a correct implementation. Be aware: The code is much longer :(
Solution is cross browser compatible
Thanks a lot! I needed to customize because i have ajax function when clicking a checkbox. I added a section to launch the ajax commands:
don't forget to init the function when document.ready:
I think the code should be changed from:
to
You don't need the "checked" or "", simply true or false with attributed checked is good. People also need to understand that the first "checked" is a string not a variable (for those who didn`t know). Using "checked" and "" instead of true or false, was making my code bug thus i couldn't uncheck my boxes.