/* 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 2.0.0 * Copyright (c) 2012 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; //Courtesy of underscore.js function debounce(func, wait, immediate) { var timeout; return function() { var context = this, args = arguments; var later = function() { timeout = null; if (!immediate) { func.apply(context, args); } }; var callNow = immediate && !timeout; clearTimeout(timeout); timeout = setTimeout(later, wait); if (callNow) { func.apply(context, args); } }; } $.widget('ech.multiselectfilter', { options: { label: 'Filter:', width: null, /* override default width set in css file (px). null will inherit */ placeholder: 'Enter keywords', autoReset: false, debounceMS: 250 }, _create: function() { var opts = this.options; var elem = $(this.element); // get the multiselect instance this.instance = elem.multiselect('instance'); // store header; add filter class so the close/check all/uncheck all links can be positioned correctly this.header = this.instance.menu.find('.ui-multiselect-header').addClass('ui-multiselect-hasfilter'); // wrapper elem this.input = $("").attr({ placeholder: opts.placeholder, type: "search" }).css({ width: (/\d/.test(opts.width) ? opts.width + 'px' : null) }).bind({ keydown: function(e) { // prevent the enter key from submitting the form / closing the widget if(e.which === 13) { e.preventDefault(); } else if(e.which === 27) { elem.multiselect('close'); e.preventDefault(); } else if(e.which === 9 && e.shiftKey) { elem.multiselect('close'); e.preventDefault(); } else if(e.altKey) { switch(e.which) { case 82: e.preventDefault(); $(this).val('').trigger('input', ''); break; case 65: elem.multiselect('checkAll'); break; case 85: elem.multiselect('uncheckAll'); break; case 76: elem.multiselect('instance').labels.first().trigger("mouseenter"); break; } } }, input: $.proxy(debounce(this._handler, opts.debounceMS), this), search: $.proxy(this._handler, this) }); // automatically reset the widget on close? if(this.options.autoReset) { elem.bind('multiselectclose', $.proxy(this._reset, this)); } // rebuild cache when multiselect is updated elem.bind('multiselectrefresh', $.proxy(function() { this.updateCache(); this._handler(); }, this)); this.wrapper = $("
").addClass("ui-multiselect-filter").text(opts.label).append(this.input).prependTo(this.header); // reference to the actual inputs this.inputs = this.instance.menu.find('input[type="checkbox"], input[type="radio"]'); // 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 this.instance._toggleChecked = function(flag, group) { var $inputs = (group && group.length) ? group : this.labels.find('input'); var _self = this; // do not include hidden elems if the menu isn't open. var selector = _self._isOpen ? ':disabled, :hidden' : ':disabled'; $inputs = $inputs .not(selector) .each(this._toggleState('checked', flag)); // update text this.update(); // gather an array of the values that actually changed var values = {}; $inputs.each(function() { values[this.value] = true; }); // select option tags this.element.find('option').filter(function() { if(!this.disabled && values[this.value]) { _self._toggleState('selected', flag).call(this); } }); // trigger the change event on the select if($inputs.length) { this.element.trigger('change'); } }; }, // 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; var $groups = this.instance.menu.find(".ui-multiselect-optgroup"); $groups.show(); 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 $groups.each(function() { var $this = $(this); if(!$this.children("li:visible").length) { $this.hide(); } }); this.instance._setMenuHeight(); }, _reset: function() { this.input.val('').trigger('input', ''); }, updateCache: function() { // each list item this.rows = this.instance.labels.parent(); // cache this.cache = this.element.children().map(function() { var elem = $(this); // account for optgroups if(this.tagName.toLowerCase() === "optgroup") { elem = elem.children(); } return elem.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);