1(function($) { 2 3 String.prototype.unaccented = function() { 4 var accent = [ 5 /[\300-\306]/g, /[\340-\346]/g, // A, a 6 /[\310-\313]/g, /[\350-\353]/g, // E, e 7 /[\314-\317]/g, /[\354-\357]/g, // I, i 8 /[\322-\330]/g, /[\362-\370]/g, // O, o 9 /[\331-\334]/g, /[\371-\374]/g, // U, u 10 /[\321]/g, /[\361]/g, // N, n 11 /[\307]/g, /[\347]/g, // C, c 12 ]; 13 var noaccent = ['A','a','E','e','I','i','O','o','U','u','N','n','C','c']; 14 15 var str = this; 16 for (var i = 0; i < accent.length; i++) { 17 str = str.replace(accent[i], noaccent[i]); 18 } 19 20 return str; 21 } 22 23 $.fn.fSelect = function(options) { 24 25 if ('string' === typeof options) { 26 var settings = options; 27 } 28 else { 29 var settings = $.extend({ 30 placeholder: 'Select some options', 31 numDisplayed: 3, 32 overflowText: '{n} selected', 33 searchText: 'Search', 34 noResultsText: 'No results found', 35 showSearch: true, 36 optionFormatter: false 37 }, options); 38 } 39 40 41 /** 42 * Constructor 43 */ 44 function fSelect(select, settings) { 45 this.$select = $(select); 46 this.settings = settings; 47 this.create(); 48 } 49 50 51 /** 52 * Prototype class 53 */ 54 fSelect.prototype = { 55 create: function() { 56 this.settings.multiple = this.$select.is('[multiple]'); 57 var multiple = this.settings.multiple ? ' multiple' : ''; 58 this.$select.wrap('<div class="fs-wrap' + multiple + '" tabindex="0" />'); 59 this.$select.before('<div class="fs-label-wrap"><div class="fs-label">' + this.settings.placeholder + '</div><span class="fs-arrow"></span></div>'); 60 this.$select.before('<div class="fs-dropdown hidden"><div class="fs-options"></div></div>'); 61 this.$select.addClass('hidden'); 62 this.$wrap = this.$select.closest('.fs-wrap'); 63 this.$wrap.data('id', window.fSelect.num_items); 64 window.fSelect.num_items++; 65 this.reload(); 66 }, 67 68 reload: function() { 69 if (this.settings.showSearch) { 70 var search = '<div class="fs-search"><input type="search" placeholder="' + this.settings.searchText + '" /></div>'; 71 this.$wrap.find('.fs-dropdown').prepend(search); 72 } 73 if ('' !== this.settings.noResultsText) { 74 var no_results_text = '<div class="fs-no-results hidden">' + this.settings.noResultsText + '</div>'; 75 this.$wrap.find('.fs-options').before(no_results_text); 76 } 77 this.idx = 0; 78 this.optgroup = 0; 79 this.selected = [].concat(this.$select.val()); // force an array 80 var choices = this.buildOptions(this.$select); 81 this.$wrap.find('.fs-options').html(choices); 82 this.reloadDropdownLabel(); 83 }, 84 85 destroy: function() { 86 this.$wrap.find('.fs-label-wrap').remove(); 87 this.$wrap.find('.fs-dropdown').remove(); 88 this.$select.unwrap().removeClass('hidden'); 89 }, 90 91 buildOptions: function($element) { 92 var $this = this; 93 94 var choices = ''; 95 $element.children().each(function(i, el) { 96 var $el = $(el); 97 98 if ('optgroup' == $el.prop('nodeName').toLowerCase()) { 99 choices += '<div class="fs-optgroup-label" data-group="' + $this.optgroup + '">' + $el.prop('label') + '</div>'; 100 choices += $this.buildOptions($el); 101 $this.optgroup++; 102 } 103 else { 104 var val = $el.prop('value'); 105 106 // exclude the first option in multi-select mode 107 if (0 < $this.idx || '' != val || ! $this.settings.multiple) { 108 var disabled = $el.is(':disabled') ? ' disabled' : ''; 109 var selected = -1 < $.inArray(val, $this.selected) ? ' selected' : ''; 110 var group = ' g' + $this.optgroup; 111 var row = '<div class="fs-option' + selected + disabled + group + '" data-value="' + val + '" data-index="' + $this.idx + '"><span class="fs-checkbox"><i></i></span><div class="fs-option-label">' + $el.html() + '</div></div>'; 112 113 if ('function' === typeof $this.settings.optionFormatter) { 114 row = $this.settings.optionFormatter(row); 115 } 116 117 choices += row; 118 $this.idx++; 119 } 120 } 121 }); 122 123 return choices; 124 }, 125 126 reloadDropdownLabel: function() { 127 var settings = this.settings; 128 var labelText = []; 129 130 this.$wrap.find('.fs-option.selected').each(function(i, el) { 131 labelText.push($(el).find('.fs-option-label').html()); 132 }); 133 134 if (labelText.length < 1) { 135 labelText = settings.placeholder; 136 } 137 else if (labelText.length > settings.numDisplayed) { 138 labelText = settings.overflowText.replace('{n}', labelText.length); 139 } 140 else { 141 labelText = labelText.join(', '); 142 } 143 144 this.$wrap.find('.fs-label').html(labelText); 145 this.$wrap.toggleClass('fs-default', labelText === settings.placeholder); 146 this.$select.change(); 147 } 148 } 149 150 151 /** 152 * Loop through each matching element 153 */ 154 return this.each(function() { 155 var data = $(this).data('fSelect'); 156 157 if (!data) { 158 data = new fSelect(this, settings); 159 $(this).data('fSelect', data); 160 } 161 162 if ('string' === typeof settings) { 163 data[settings](); 164 } 165 }); 166 } 167 168 169 /** 170 * Events 171 */ 172 window.fSelect = { 173 'num_items': 0, 174 'active_id': null, 175 'active_el': null, 176 'last_choice': null, 177 'idx': -1 178 }; 179 180 $(document).on('click', '.fs-option:not(.hidden, .disabled)', function(e) { 181 var $wrap = $(this).closest('.fs-wrap'); 182 var do_close = false;
183 184 // prevent selections 185 if ($wrap.hasClass('fs-disabled')) { 186 return; 187 } 188 189 if ($wrap.hasClass('multiple')) { 190 var selected = []; 191 192 // shift + click support 193 if (e.shiftKey && null != window.fSelect.last_choice) { 194 var current_choice = parseInt($(this).attr('data-index')); 195 var addOrRemove = ! $(this).hasClass('selected'); 196 var min = Math.min(window.fSelect.last_choice, current_choice); 197 var max = Math.max(window.fSelect.last_choice, current_choice); 198 199 for (i = min; i <= max; i++) { 200 $wrap.find('.fs-option[data-index='+ i +']') 201 .not('.hidden, .disabled') 202 .each(function() { 203 $(this).toggleClass('selected', addOrRemove); 204 }); 205 } 206 } 207 else { 208 window.fSelect.last_choice = parseInt($(this).attr('data-index')); 209 $(this).toggleClass('selected'); 210 } 211 212 $wrap.find('.fs-option.selected').each(function(i, el) { 213 selected.push($(el).attr('data-value')); 214 }); 215 } 216 else { 217 var selected = $(this).attr('data-value'); 218 $wrap.find('.fs-option').removeClass('selected'); 219 $(this).addClass('selected'); 220 do_close = true; 221 } 222 223 $wrap.find('select').val(selected); 224 $wrap.find('select').fSelect('reloadDropdownLabel'); 225 226 // fire an event 227 $(document).trigger('fs:changed', $wrap); 228 229 if (do_close) { 230 closeDropdown($wrap); 231 } 232 }); 233 234 $(document).on('keyup', '.fs-search input', function(e) { 235 if (40 == e.which) { // down 236 $(this).blur(); 237 return; 238 } 239 240 var $wrap = $(this).closest('.fs-wrap'); 241 var matchOperators = /[|\\{}()[\]^$+*?.]/g; 242 var keywords = $(this).val().replace(matchOperators, '\\$&'); 243 244 $wrap.find('.fs-option, .fs-optgroup-label').removeClass('hidden'); 245 246 if ('' != keywords) { 247 $wrap.find('.fs-option').each(function() { 248 var regex = new RegExp(keywords.unaccented(), 'gi'); 249 var formatedValue = $(this).find('.fs-option-label').text().unaccented(); 250 251 if (null === formatedValue.match(regex)) { 252 $(this).addClass('hidden'); 253 } 254 }); 255 256 $wrap.find('.fs-optgroup-label').each(function() { 257 var group = $(this).attr('data-group'); 258 var num_visible = $(this).closest('.fs-options').find('.fs-option.g' + group + ':not(.hidden)').length; 259 if (num_visible < 1) { 260 $(this).addClass('hidden'); 261 } 262 }); 263 } 264 265 setIndexes($wrap); 266 checkNoResults($wrap); 267 }); 268 269 $(document).on('click', function(e) { 270 var $el = $(e.target); 271 var $wrap = $el.closest('.fs-wrap'); 272 273 if (0 < $wrap.length) { 274 275 // user clicked another fSelect box 276 if ($wrap.data('id') !== window.fSelect.active_id) { 277 closeDropdown(); 278 } 279 280 // fSelect box was toggled 281 if ($el.hasClass('fs-label') || $el.hasClass('fs-arrow')) { 282 var is_hidden = $wrap.find('.fs-dropdown').hasClass('hidden'); 283 284 if (is_hidden) { 285 openDropdown($wrap); 286 } 287 else { 288 closeDropdown($wrap); 289 } 290 } 291 } 292 // clicked outside, close all fSelect boxes 293 else { 294 closeDropdown(); 295 } 296 }); 297 298 $(document).on('keydown', function(e) { 299 var $wrap = window.fSelect.active_el; 300 var $target = $(e.target); 301 302 // toggle the dropdown on space 303 if ($target.hasClass('fs-wrap')) { 304 if (32 == e.which || 13 == e.which) { 305 e.preventDefault(); 306 $target.find('.fs-label').trigger('click'); 307 return; 308 } 309 }
310 // preserve spaces during search 311 else if (0 < $target.closest('.fs-search').length) { 312 if (32 == e.which) { 313 return; 314 } 315 } 316 else if (null === $wrap) { 317 return; 318 } 319 320 if (38 == e.which) { // up 321 e.preventDefault(); 322 323 $wrap.find('.fs-option.hl').removeClass('hl'); 324 325 var $current = $wrap.find('.fs-option[data-index=' + window.fSelect.idx + ']'); 326 var $prev = $current.prevAll('.fs-option:not(.hidden, .disabled)'); 327 328 if ($prev.length > 0) { 329 window.fSelect.idx = parseInt($prev.attr('data-index')); 330 $wrap.find('.fs-option[data-index=' + window.fSelect.idx + ']').addClass('hl'); 331 setScroll($wrap); 332 } 333 else { 334 window.fSelect.idx = -1; 335 $wrap.find('.fs-search input').focus(); 336 } 337 } 338 else if (40 == e.which) { // down 339 e.preventDefault(); 340 341 var $current = $wrap.find('.fs-option[data-index=' + window.fSelect.idx + ']'); 342 if ($current.length < 1) { 343 var $next = $wrap.find('.fs-option:not(.hidden, .disabled):first'); 344 } 345 else { 346 var $next = $current.nextAll('.fs-option:not(.hidden, .disabled)'); 347 } 348 349 if ($next.length > 0) { 350 window.fSelect.idx = parseInt($next.attr('data-index')); 351 $wrap.find('.fs-option.hl').removeClass('hl'); 352 $wrap.find('.fs-option[data-index=' + window.fSelect.idx + ']').addClass('hl'); 353 setScroll($wrap); 354 } 355 } 356 else if (32 == e.which || 13 == e.which) { // space, enter 357 e.preventDefault(); 358 359 $wrap.find('.fs-option.hl').click(); 360 } 361 else if (27 == e.which) { // esc 362 closeDropdown($wrap); 363 } 364 }); 365 366 function checkNoResults($wrap) { 367 var addOrRemove = $wrap.find('.fs-option:not(.hidden)').length > 0; 368 $wrap.find('.fs-no-results').toggleClass('hidden', addOrRemove); 369 } 370 371 function setIndexes($wrap) { 372 $wrap.find('.fs-option.hl').removeClass('hl'); 373 $wrap.find('.fs-search input').focus(); 374 window.fSelect.idx = -1; 375 } 376 377 function setScroll($wrap) { 378 var $container = $wrap.find('.fs-options'); 379 var $selected = $wrap.find('.fs-option.hl'); 380 381 var itemMin = $selected.offset().top + $container.scrollTop(); 382 var itemMax = itemMin + $selected.outerHeight(); 383 var containerMin = $container.offset().top + $container.scrollTop(); 384 var containerMax = containerMin + $container.outerHeight(); 385 386 if (itemMax > containerMax) { // scroll down 387 var to = $container.scrollTop() + itemMax - containerMax; 388 $container.scrollTop(to); 389 } 390 else if (itemMin < containerMin) { // scroll up 391 var to = $container.scrollTop() - containerMin - itemMin; 392 $container.scrollTop(to); 393 } 394 } 395 396 function openDropdown($wrap) { 397 window.fSelect.active_el = $wrap; 398 window.fSelect.active_id = $wrap.data('id'); 399 window.fSelect.initial_values = $wrap.find('select').val(); 400 $wrap.find('.fs-dropdown').removeClass('hidden'); 401 $wrap.addClass('fs-open'); 402 setIndexes($wrap); 403 checkNoResults($wrap); 404 } 405 406 function closeDropdown($wrap) { 407 if ('undefined' == typeof $wrap && null != window.fSelect.active_el) { 408 $wrap = window.fSelect.active_el; 409 } 410 if ('undefined' !== typeof $wrap) { 411 // only trigger if the values have changed 412 var initial_values = window.fSelect.initial_values; 413 var current_values = $wrap.find('select').val(); 414 if (JSON.stringify(initial_values) != JSON.stringify(current_values)) { 415 $(document).trigger('fs:closed', $wrap); 416 } 417 } 418 419 $('.fs-wrap').removeClass('fs-open'); 420 $('.fs-dropdown').addClass('hidden'); 421 window.fSelect.active_el = null; 422 window.fSelect.active_id = null; 423 window.fSelect.last_choice = null; 424 } 425 426})(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.