Checkbox Range Selection (a la GMail)

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.

23 responses to “Checkbox Range Selection (a la GMail)”

  1. Dan G. Switzer, II

    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.

    (function($){
    $.fn.checkboxesForRangeSelection = function(v){
    var lastCheckbox = null;

    return this.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;
    });
    };
    })(jQuery);

    Now you should be able to just do:
    $("input.image-checkbox").checkboxesForRangeSelection();

  2. Dan G. Switzer, II

    FYI – I should clarify that I didn't actually test the code above (so there may be a bug.)

  3. Weixi Yen

    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?

  4. Weixi Yen

    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

  5. Weixi Yen

    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

  6. Weixi Yen

    Hi Barney,

    Thanks, I found it

    $("myobject").unbind("click")
    $("myobject").enableCheckboxRangeSelection()

    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!

  7. Weixi Yen

    Hi Barney,

    I am using this for sort plugins:
    jQuery.fn.sort = function()
    {
    return this.pushStack([].sort.apply(this,arguments), []);
    }

    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

  8. Dan G. Switzer, II

    @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.

  9. Weixi Yen

    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!

  10. » jQuery的checkbox插件(Shift多选,类似Gmail) Combie’s Blog

    [...] 引自:http://www.barneyb.com/barneyblog/2008/01/08/checkbox-range-selection-a-la-gmail/ Blogroll [...]

  11. Matt Simp

    you rock

  12. Henrik N

    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.

  13. Henrik N

    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.

  14. Henrik N

    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.

  15. scorphus

    Works charmingly well! Thank you very much!

Leave a Reply