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