vendor: 10,343 bytes, lines 1-286
1/* jshint forin:true, noarg:true, noempty:true, eqeqeq:true, boss:true, undef:true, curly:true, browser:true, jquery:true */ 2/* 3 * jQuery MultiSelect UI Widget Filtering Plugin 3.0.0 4 * Copyright (c) 2012 Eric Hynds 5 * 6 * http://www.erichynds.com/jquery/jquery-ui-multiselect-widget/ 7 * 8 * Depends: 9 * - jQuery UI MultiSelect widget 10 * 11 * Dual licensed under the MIT and GPL licenses: 12 * http://www.opensource.org/licenses/mit-license.php 13 * http://www.gnu.org/licenses/gpl.html 14 * 15 */ 16(function($) { 17 var rEscape = /[\-\[\]{}()*+?.,\\\^$|#\s]/g; 18 19 // "{{term}}" is a placeholder below for where the search term 20 // would be inserted in the resulting regular expression. 21 var filterRules = { 22 contains: '{{term}}', 23 beginsWith: '^{{term}}', 24 endsWith: '{{term}}$', 25 exactMatch: '^{{term}}$', 26 containsNumber: '\d', 27 isNumeric: '^\d+$', 28 isNonNumeric: '^\D+$', 29 }; 30 31 var headerSelector = '.ui-multiselect-header'; 32 var hasFilterClass = 'ui-multiselect-hasfilter'; 33 var filterClass = 'ui-multiselect-filter'; 34 var optgroupClass = 'ui-multiselect-optgroup'; 35 var groupLabelClass = 'ui-multiselect-grouplabel'; 36 var hiddenClass = 'ui-multiselect-excluded'; 37 38 /** 39 * This comes courtesy of underscore.js 40 * @param {function} func to debounce 41 * @param {number} wait period 42 * @param {bool} immediate perform once immediately 43 * @return {function} input function with a debounce period 44 */ 45 function debounce(func, wait, immediate) { 46 var timeout; 47 return function() { 48 var context = this; var args = arguments; // eslint-disable-line prefer-rest-params 49 var later = function() { 50 timeout = null; 51 if (!immediate) { 52 func.apply(context, args); 53 } 54 }; 55 var callNow = immediate && !timeout; 56 clearTimeout(timeout); 57 timeout = setTimeout(later, wait); 58 if (callNow) { 59 func.apply(context, args); 60 } 61 }; 62 } 63 64 $.widget('ech.multiselectfilter', { 65 66 options: { 67 label: 'Filter:', // (string) The label to show with the input 68 placeholder: 'Enter keywords', // (string) The placeholder text to show in the input 69 filterRule: 'contains', // (string) Either a named filter rule from above or a regular expression containing {{term}} as a placeholder 70 searchGroups: false, // (true | false) If true, search option group labels and show an entire group on a match. 71 autoReset: false, // (true | false) If true, clear the filter each time the widget menu is closed. 72 width: null, // (number) Override default width set in css file (px). null will inherit 73 debounceMS: 250, // (number) Number of milleseconds to wait between running the search handler. 74 }, 75 76 /** 77 * Performs widget creation 78 * Widget API has already set this.element and this.options for us 79 * - Find the multiselect widget. 80 * - Create the filter input 81 * - Set up event handlers 82 * - Insert in header 83 * - Create text cache 84 * - Override toggleState 85 */ 86 _create: function() { 87 var opts = this.options; 88 var $element = this.element; 89 90 // get the multiselect instance 91 this.instance = $element.data('ech-multiselect'); 92 93 // store header; add filter class so the close/check all/uncheck all links can be positioned correctly 94 this.$header = this.instance.$menu.find(headerSelector).addClass(hasFilterClass); 95 96 // wrapper $element 97 this.$input = $(document.createElement('input')) 98 .attr({ 99 placeholder: opts.placeholder, 100 type: 'search', 101 }) 102 .css({width: (typeof opts.width === 'string') 103 ? this.instance._parse2px(opts.width, this.$header).px + 'px' 104 : (/\d/.test(opts.width) ? opts.width + 'px' : null), 105 }); 106 this._bindInputEvents(); 107 // automatically reset the widget on close? 108 if (this.options.autoReset) { 109 $element.on('multiselectbeforeclose', $.proxy(this._reset, this)); 110 } 111 112 var $label = $(document.createElement('label')).text(opts.label).append(this.$input).addClass('ui-multiselect-filter-label'); 113 this.$wrapper = $(document.createElement('div')) 114 .addClass(filterClass) 115 .append($label) 116 .prependTo(this.$header); 117 118 // If menu already opened, have to reset menu height since 119 // addition of the filter input changes the header height calc. 120 if (!!this.instance._isOpen) { 121 this.instance._setMenuHeight(true); 122 } 123 124 // cache input values for searching 125 this.updateCache(); 126 127 // Change the normal _toggleChecked fxn behavior so that when checkAll/uncheckAll 128 // is fired, only the currently displayed filtered inputs are checked if filter entered. 129 var instance = this.instance; 130 var filter = this.$input[0]; 131 instance._oldToggleChecked = instance._toggleChecked; 132 instance._toggleChecked = function(flag, group) { 133 instance._oldToggleChecked(flag, group, !!filter.value); 134 }; 135 }, 136 137 /** 138 * Binds keyboard events to the input 139 * This is where special behavior like ALT-R for reset is bound 140 */ 141 _bindInputEvents: function() { 142 this.$input.on({ 143 keydown: function(e) { 144 // prevent the enter key from submitting the form / closing the widget 145 if (e.which === 13) { 146 e.preventDefault(); 147 } else if (e.which === 27) { 148 $element.multiselect('close'); 149 e.preventDefault(); 150 } else if (e.which === 9 && e.shiftKey) { 151 $element.multiselect('close'); 152 e.preventDefault(); 153 } else if (e.altKey) { 154 switch (e.which) { 155 case 82: 156 e.preventDefault(); 157 $(this).val('').trigger('input', ''); 158 break; 159 case 65: 160 $element.multiselect('checkAll'); 161 break; 162 case 85: 163 $element.multiselect('uncheckAll'); 164 break; 165 case 70: 166 $element.multiselect('flipAll'); 167 break; 168 case 76: 169 $element.multiselect('instance').$labels.first().trigger('mouseenter'); 170 break; 171 } 172 } 173 }, 174 input: $.proxy(debounce(this._handler, this.options.debounceMS), this), 175 search: $.proxy(this._handler, this), 176 }); 177 }, 178 179 /** 180 * Handles searches as text is entered in the filter box. 181 * Uses a text cache to speed up searching. 182 * Debouncing is done to limit how often this is ran. 183 * Alternate filter rules can be used. 184 * Option group labels may be searched, also. 185 * @param {event} e event object from original event. 186 */ 187 _handler: function(e) { 188 var term = this.$input[0].value.toLowerCase().replace(/^\s+|\s+$/g, ''); 189 var filterRule = this.options.filterRule || 'contains'; 190 var regex = new RegExp( ( filterRules[filterRule] || filterRule ).replace('{{term}}', term.replace(rEscape, '\\$&')), 'i'); 191 var searchGroups = !!this.options.searchGroups; 192 var $checkboxes = this.instance.$checkboxes; 193 var cache = this.cache; // Cached text() object 194 195 this.$rows.toggleClass(hiddenClass, !!term); 196 var filteredInputs = $checkboxes.children().map(function(x) { 197 var elem = this; 198 var $groupItems = $(elem); 199 var groupShown = false; 200 201 // Account for optgroups 202 // If we are searching in option group labels and we match an optgroup label, 203 // then show all its children and return all its inputs also. 204 if (elem.classList.contains(optgroupClass)) { 205 var $groupItems = $groupItems.find('li'); 206 if (searchGroups && regex.test( cache[x] ) ) { 207 elem.classList.remove(hiddenClass); 208 $groupItems.removeClass(hiddenClass); 209 return $groupItems.find('input').get(); 210 } 211 } 212 213 return $groupItems.map(function(y) { 214 if ( regex.test( cache[x + '.' + y] ) ) { 215 // Show the opt group heading if needed 216 if (!groupShown) { 217 elem.classList.remove(hiddenClass); 218 groupShown = true; 219 } 220 this.classList.remove(hiddenClass); 221 return this.getElementsByTagName('input')[0]; 222 } 223 return null; 224 }); 225 }); 226 if (term) { 227 this._trigger('filter', e, filteredInputs); 228 } 229 if (!this.instance.options.listbox && this.instance._isOpen) { 230 this.instance._setMenuHeight(true); 231 this.instance.position(); 232 } 233 return; 234 }, 235 236 _reset: function() { 237 this.$input.val(''); 238 var event = document.createEvent('Event'); 239 event.initEvent('reset', true, true); 240 this.$input.get(0).dispatchEvent(event); 241 this._handler(event); 242 }, 243 244 /** 245 * Creates a text cache object from the widget options' text. 246 * @param {Boolean} alsoRefresh causes the displayed search results to refresh. 247 */ 248 updateCache: function(alsoRefresh) { 249 var cache = {}; // keys are like 0, 0.1, 1, 1.0, 1.1 etc. 250 this.instance.$checkboxes.children().each(function(x) { 251 var $element = $(this); 252 // Account for optgroups 253 if (this.classList.contains(optgroupClass)) { 254 // Single number keys are the option labels 255 cache[x] = this.getElementsByClassName(groupLabelClass)[0].textContent; 256 $element = $element.find('li'); 257 } 258 $element.each(function(y) { 259 cache[x + '.' + y] = this.textContent; 260 }); 261 }); 262 this.cache = cache; 263 this.$rows = this.instance.$checkboxes.find('li'); 264 if (!!alsoRefresh) { 265 this._handler(); 266 } 267 }, 268 269 /** 270 * @return {object} Returns the input wrapper div 271 */ 272 widget: function() { 273 return this.$wrapper; 274 }, 275 276 /** 277 * Destroys this widget 278 */ 279 destroy: function() { 280 $.Widget.prototype.destroy.call(this); 281 this.$input.val('').trigger('keyup').off('keydown input search'); 282 this.instance.$menu.find(headerSelector).removeClass(hasFilterClass); 283 this.$wrapper.remove(); 284 }, 285 }); 286})(jQuery);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.