MTMultiSelect

A paginated, filterable multi-select widget for MooTools

The standard HTML list box is a much maligned user interface element. It requires use of both mouse and keyboard to operate, and with any more than a few elements it requires the user to scroll. It's also fairly ugly and difficult to style. I've seen a few nice javascript widgets that improve them, but often they still forced the user to scroll, or simply created lists that pushed page contents down.

I decided to write my own, mostly as an exercise, but also to fit a few requirements I thought were essential to an easy-to-use multi-select element: it should occupy a fixed vertical space, the user should never have to scroll, the user should be able to quickly see the elements she has selected and the elements she has yet to select, and finally, it should allow the user to filter down the elements that are important. Also, I wanted something that worked with MooTools, a great javascript framework.

MTMultiselect is my answer. It's a easy-to-use, paginated, filterable multi-select widget built on MooTools. Take a look at the demo page or download the code from bitbucket.org.

Posted: August 23, 2009 / 12 comments

Comments

  1. Dave Hunter on September 16, 2009

    That is genius, you should make more of the fact that it degrades back to a standard multiselect if javascript is disabled. Great job

  2. Robert Visser on September 17, 2009

    Justin,

    Hi. Thanks for making this available. Would like to see an a multicolumn example using MooTools sortable columns. Would also like to see the multicolumn example ported to jQuery.

    Thanks.

    Rob

  3. Jakub Miara on September 18, 2009

    Hi Justin

    the script is really great ! I'd consider two things though: 1. this.setpagenum(evt.target.text); doesn't work in IE 6 (nor does the png transparency) i'd suggest to use this.setpagenum(evt.target.innerHTML); instead.

    2.perhaps it'd be resonable to filter case insensitively - like this: filter_by_text = function(item){ //return item.text.contains(evt.target.value); return item.text.toLowerCase().contains(evt.target.value.toLowerCase()); };

    Thanks a lot for your work - it has helped me a lot ;)

  4. Jakub Miara on September 18, 2009

    one more thing:

    I'd use some non existent target for paginator href (#asdklhsldkh instead of just #) otherwise every click on paginator scrolls the page to the top. It behaves weirdly in IE anyway, but scrolls only to the top of the control.

    Regards, Jakub

  5. Justin Donato on September 20, 2009

    Jakub - Thanks for the IE 6 catch. Also, I think I'll make the search case-insensitivity an option.

  6. Jean-Phi on October 06, 2009

    Good job,

    how to select item with keyboard?

  7. Adam on October 07, 2009

    Nice job.

    I haven't taken a look at your code too much in depth yet. Not sure if it exists yet (and if not I might try to add this in myself), but what I think might be a useful option is a "max select amount" to limit the number of selected options that can be made.

  8. Adam on October 07, 2009

    Okay, well that was a SIMPLE fix.

    If you want to add a maximum to the allowable select, try amending the code as follows:

    1) In the DisplayList class, under options, add this: max_select: 2

    2) In the DisplayList class, amend the build function: build: function(opts){

            // If there's already an ol, remove it.
            var old = this.options.view.getElement('ol');
            if(old !== null) old.destroy();
    
            // create the list to hold the visible elements
            list = new Element('ol');
            place = this.options.paginator_on_bottom ? 'before' : 'after';
            list.inject(this.options.view.getLast(), place);
            //this.options.view.grab(list);
    
            opts.each(function(item){ 
                    var li = new Element('li',{
                        'class': item.selected ? this.options.selectedcls : null,
                        'text': item.get('text')
                    });
                    li.store('select', item);                    
                    list.grab(li);
    
                    li.addEvent('click', function(evt){
                        if(this.numselected() < this.options.max_select){  //this is the change here
                            evt.target.toggleClass('selected');
                            evt.target.retrieve('select').selected = evt.target.hasClass('selected');                        
                            this.fireEvent('rebuild', this.numselected());
                        }
                        else{ /*something to do if there are too many selected items*/ }
                    }.bind(this));
                }.bind(this));
        }
    

    Simple edits that require adding a conditional during the click event.

  9. Adam on October 07, 2009

    Sorry for the repeat posting. As a quick change to the above item, I need to change my conditional to the code below. If you don't change this, then you won't be able to un-select an option!

                        if(this.numselected() < this.options.max_select || evt.target.hasClass('selected') == true){
    
  10. marius on January 14, 2010

    Very usefull your comment Adam. I set only one option, and I want that when a user press another box even other box is selected to unselect the selected box and select the box he clicks.

    I belive that this should be done on else, but I don't know how to deselect all selected items.

    Thanks, Marius.

  11. Luke on February 10, 2010

    Just wondering, how can i start this js Object with several options already selected? I want it to remember the options after a form is submitted.

  12. Justin Donato on February 19, 2010

    Luke,

    Just set the options to selected in the HTML and they'll be selected in the widget. It should work seamlessly with any form you have.