woese/guara-crm

View on GitHub
app/assets/javascripts/jquery.multiselect.filter.xjs

Summary

Maintainability
Test Coverage
/* jshint forin:true, noarg:true, noempty:true, eqeqeq:true, boss:true, undef:true, curly:true, browser:true, jquery:true */
/*
 * jQuery MultiSelect UI Widget Filtering Plugin 1.4
 * Copyright (c) 2011 Eric Hynds
 *
 * http://www.erichynds.com/jquery/jquery-ui-multiselect-widget/
 *
 * Depends:
 *   - jQuery UI MultiSelect widget
 *
 * Dual licensed under the MIT and GPL licenses:
 *   http://www.opensource.org/licenses/mit-license.php
 *   http://www.gnu.org/licenses/gpl.html
 *
*/
(function($){
    var rEscape = /[\-\[\]{}()*+?.,\\\^$|#\s]/g;
    
    $.widget("ech.multiselectfilter", {
        
        options: {
            label: "Filter:",
            width: null, /* override default width set in css file (px). null will inherit */
            placeholder: "Enter keywords",
            autoReset: false
        },
        
        _create: function(){
            var self = this,
                opts = this.options,
                instance = (this.instance = $(this.element).data("multiselect")),
                
                // store header; add filter class so the close/check all/uncheck all links can be positioned correctly
                header = (this.header = instance.menu.find(".ui-multiselect-header").addClass("ui-multiselect-hasfilter")),
                
                // wrapper elem
                wrapper = (this.wrapper = $('<div class="ui-multiselect-filter">'+(opts.label.length ? opts.label : '')+'<input placeholder="'+opts.placeholder+'" type="search"' + (/\d/.test(opts.width) ? 'style="width:'+opts.width+'px"' : '') + ' /></div>').prependTo( this.header ));

            // reference to the actual inputs
            this.inputs = instance.menu.find('input[type="checkbox"], input[type="radio"]');
            
            // build the input box
            this.input = wrapper
            .find("input")
            .bind({
                keydown: function( e ){
                    // prevent the enter key from submitting the form / closing the widget
                    if( e.which === 13 ){
                        e.preventDefault();
                    }
                },
                keyup: $.proxy(self._handler, self),
                click: $.proxy(self._handler, self)
            });
            
            // cache input values for searching
            this.updateCache();
            
            // rewrite internal _toggleChecked fn so that when checkAll/uncheckAll is fired,
            // only the currently filtered elements are checked
            instance._toggleChecked = function(flag, group){
                var $inputs = (group && group.length) ?
                        group :
                        this.labels.find('input'),
                    
                    _self = this,

                    // do not include hidden elems if the menu isn't open.
                    selector = self.instance._isOpen ?
                        ":disabled, :hidden" :
                        ":disabled";

                $inputs = $inputs.not( selector ).each(this._toggleState('checked', flag));
                
                // update text
                this.update();
                
                // figure out which option tags need to be selected
                var values = $inputs.map(function(){
                    return this.value;
                }).get();
                
                // select option tags
                this.element
                    .find('option')
                    .filter(function(){
                        if( !this.disabled && $.inArray(this.value, values) > -1 ){
                            _self._toggleState('selected', flag).call( this );
                        }
                    });
            };
            
            // rebuild cache when multiselect is updated
            var doc = $(document).bind("multiselectrefresh", function(){
                self.updateCache();
                self._handler();
            });

            // automatically reset the widget on close?
            if(this.options.autoReset) {
                doc.bind("multiselectclose", $.proxy(this._reset, this));
            }
        },
        
        // thx for the logic here ben alman
        _handler: function( e ){
            var term = $.trim( this.input[0].value.toLowerCase() ),
            
                // speed up lookups
                rows = this.rows, inputs = this.inputs, cache = this.cache;
            
            if( !term ){
                rows.show();
            } else {
                rows.hide();
                
                var regex = new RegExp(term.replace(rEscape, "\\$&"), 'gi');
                
                this._trigger( "filter", e, $.map(cache, function(v, i){
                    if( v.search(regex) !== -1 ){
                        rows.eq(i).show();
                        return inputs.get(i);
                    }
                    
                    return null;
                }));
            }

            // show/hide optgroups
            this.instance.menu.find(".ui-multiselect-optgroup-label").each(function(){
                var $this = $(this);
                var isVisible = $this.nextUntil('.ui-multiselect-optgroup-label').filter(function(){
                  return $.css(this, "display") !== 'none';
                }).length;
                
                $this[ isVisible ? 'show' : 'hide' ]();
            });
        },

        _reset: function() {
            this.input.val('').trigger('keyup');
        },
        
        updateCache: function(){
            // each list item
            this.rows = this.instance.menu.find(".ui-multiselect-checkboxes li:not(.ui-multiselect-optgroup-label)");
            
            // cache
            this.cache = this.element.children().map(function(){
                var self = $(this);
                
                // account for optgroups
                if( this.tagName.toLowerCase() === "optgroup" ){
                    self = self.children();
                }
                
                return self.map(function(){
                    return this.innerHTML.toLowerCase();
                }).get();
            }).get();
        },
        
        widget: function(){
            return this.wrapper;
        },
        
        destroy: function(){
            $.Widget.prototype.destroy.call( this );
            this.input.val('').trigger("keyup");
            this.wrapper.remove();
        }
    });
})(jQuery);