PageSourceSearch

https://vagur.fo/ease/3rdparty/js/Qodio/q.selectator.jquery.js?v=3.28.6.13-3_0

js vagur.fo collected 2026-10-02 23:09:54 UTC 29,211 bytes, 823 lines download raw bytes

1/*
2 Selectator jQuery Plugin
3 A plugin for select elements
4 version 3.2, Apr 9th, 2020
5 by Ingi á Steinamørk
6
7 The MIT License (MIT)
8
9 Copyright (c) 2013 QODIO
10
11 Permission is hereby granted, free of charge, to any person obtaining a copy of
12 this software and associated documentation files (the "Software"), to deal in
13 the Software without restriction, including without limitation the rights to
14 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
15 the Software, and to permit persons to whom the Software is furnished to do so,
16 subject to the following conditions:
17
18 The above copyright notice and this permission notice shall be included in all
19 copies or substantial portions of the Software.
20
21 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
23 FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
24 COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
25 IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
26 CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27*/
28
29(function($) {
30	'use strict';
31	$.selectator = function (_element, _options) {
32		var defaults = {
33			prefix: 'selectator_',
34			height: 'auto',
35			useDimmer: false,
36			useSearch: true,
37			showAllOptionsOnFocus: false,
38			selectFirstOptionOnSearch: true,
39			keepOpen: false,
40			submitCallback: function () {},
41			load: null,
42			delay: 0,
43			minSearchLength: 0,
44			valueField: 'value',
45			textField: 'text',
46			searchFields: ['value', 'text'],
47			placeholder: '',
48			render: {
49				selected_item: function (_item, escape) {
50					var html = '';
51					if (typeof _item.left !== 'undefined')
52						html += '<div class="' + self.options.prefix + 'selected_item_left"><img src="' + escape(_item.left) + '" alt=""></div>';
53					if (typeof _item.right !== 'undefined')
54						html += '<div class="' + self.options.prefix + 'selected_item_right">' + escape(_item.right) + '</div>';
55					html += '<div class="' + self.options.prefix + 'selected_item_title">' + ((typeof _item.text !== 'undefined') ? escape(_item.text) : '') + '</div>';
56					if (typeof _item.subtitle !== 'undefined')
57						html += '<div class="' + self.options.prefix + 'selected_item_subtitle">' + escape(_item.subtitle) + '</div>';
58					html += '<div class="' + self.options.prefix + 'selected_item_remove">X</div>';
59					return html;
60				},
61				option: function (_item, escape) {
62					var html = '';
63					if (typeof _item.left !== 'undefined')
64						html += '<div class="' + self.options.prefix + 'option_left"><img src="' + escape(_item.left) + '" alt=""></div>';
65					if (typeof _item.right !== 'undefined')
66						html += '<div class="' + self.options.prefix + 'option_right">' + escape(_item.right) + '</div>';
67					html += '<div class="' + self.options.prefix + 'option_title">' + ((typeof _item.text !== 'undefined') ? escape(_item.text) : '') + '</div>';
68					if (typeof _item.subtitle !== 'undefined')
69						html += '<div class="' + self.options.prefix + 'option_subtitle">' + escape(_item.subtitle) + '</div>';
70					return html;
71				}
72			},
73			labels: {
74				search: 'Search...'
75			}
76		};
77
78		var self = this;
79		self.options = {};
80		self.$source_element = $(_element);
81		self.$container_element = null;
82		self.$selecteditems_element = null;
83		self.$input_element = null;
84		self.$textlength_element = null;
85		self.$options_element = null;
86		self.$mask_element = null;
87		self.usefilterResults = true;
88		var is_single = self.$source_element.attr('multiple') === undefined;
89		var is_multiple = !is_single;
90		var using_remote_data = false;
91		var has_visible_options = true;
92		var delayTimer = null;
93		var key = {
94			backspace: 8,
95			tab:       9,
96			enter:    13,
97			shift:    16,
98			ctrl:     17,
99			alt:      18,
100			capslock: 20,
101			escape:   27,
102			pageup:   33,
103			pagedown: 34,
104			end:      35,
105			home:     36,
106			left:     37,
107			up:       38,
108			right:    39,
109			down:     40
110		};
111
112
113
114		// INITIALIZE PLUGIN
115		self.init = function () {
116			self.options = $.extend(true, {}, defaults, _options);
117			$.each(self.$source_element.data(), function (_key, _value) {
118				if (_key.substring(0, 10) === 'selectator') {
119					self.options[_key.substring(10, 11).toLowerCase() + _key.substring(11)] = _value;
120				}
121			});
122			self.options.searchFields = typeof self.options.searchFields === 'string' ? self.options.searchFields.split(' ') : self.options.searchFields;
123			self.$source_element.find('option').each(function () {
124				$(this).data('value', this.value);
125				$(this).data('text', this.text);
126			});
127			using_remote_data = self.options.load !== null;
128
129			//// ================== CREATE ELEMENTS ================== ////
130			// mask
131			self.$mask_element = $('#' + self.options.prefix + 'mask');
132			if (self.$mask_element.length === 0) {
133				self.$mask_element = $(document.createElement('div'));
134				self.$mask_element.attr('id', self.options.prefix + 'mask');
135				self.$mask_element.attr('onclick', 'void(0)');
136				self.$mask_element.on('click', function () {
137					$(':focus').blur();
138				});
139				self.$mask_element.hide();
140				$(document.body).prepend(self.$mask_element);
141			}
142			// source element
143			self.$source_element.addClass('selectator');
144			if (self.$source_element.attr('placeholder')) {
145				self.options.placeholder = self.$source_element.attr('placeholder');
146			}
147			// container element
148			self.$container_element = $(document.createElement('div'));
149			if (self.$source_element.attr('id') !== undefined) {
150				self.$container_element.attr('id', self.options.prefix + self.$source_element.attr('id'));
151			}
152			self.$container_element.addClass(self.options.prefix + 'element ' + (is_multiple ? 'multiple ' : 'single ') + 'options-hidden');
153			if (!self.options.useSearch) {
154				self.$container_element.addClass('disable_search');
155			}
156			self.$container_element.css({
157				width: self.$source_element.css('width'),
158				minHeight: self.$source_element.css('height'),
159				padding: self.$source_element.css('padding'),
160				'flex-grow': self.$source_element.css('flex-grow'),
161				position: 'relative'
162			});
163			if (self.options.height === 'element') {
164				self.$container_element.css({
165					height: self.$source_element.outerHeight() + 'px'
166				});
167			}
168			// textlength element
169			self.$textlength_element = $(document.createElement('span'));
170			self.$textlength_element.addClass(self.options.prefix + 'textlength');
171			self.$textlength_element.css({
172				position: 'absolute',
173				visibility: 'hidden'
174			});
175			self.$container_element.append(self.$textlength_element);
176			// selected items element
177			self.$selecteditems_element = $(document.createElement('div'));
178			self.$selecteditems_element.addClass(self.options.prefix + 'selected_items');
179			self.$container_element.append(self.$selecteditems_element);
180			// input element
181			self.$input_element = $(document.createElement('input'));
182			self.$input_element.addClass(self.options.prefix + 'input');
183			self.$input_element.attr('tabindex', self.$source_element.attr('tabindex'));
184			if (!self.options.useSearch) {
185				self.$input_element.attr('readonly', true);
186				self.$input_element.css({
187					'width': '0px',
188					'height': '0px',
189					'overflow': 'hidden',
190					'border': 0,
191					'padding': 0,
192					'position': 'absolute'
193				});
194			} else {
195				if (is_single) {
196					self.$input_element.attr('placeholder', self.options.labels.search);
197				} else {
198					if (self.options.placeholder !== '') {
199						self.$input_element.attr('placeholder', self.options.placeholder);
200					}
201					self.$input_element.width(20);
202				}
203			}
204			self.$input_element.attr('autocomplete', 'false');
205			self.$container_element.append(self.$input_element);
206			// options element
207			self.$options_element = $(document.createElement('ul'));
208			self.$options_element.addClass(self.options.prefix + 'options');
209
210			self.$container_element.append(self.$options_element);
211			self.$source_element.after(self.$container_element);
212			self.$source_element.hide();
213
214
215			//// ================== BIND ELEMENTS EVENTS ================== ////
216
217			// source element
218			self.$source_element.change(function () {
219				renderSelectedItems();
220			});
221
222			// container element
223			self.$container_element.on('focus', function () {
224				self.$input_element.focus();
225				self.$input_element.trigger('focus');
226			});
227			self.$container_element.on('mousedown', function (_e) {
228				_e.preventDefault();
229				self.$input_element.focus();
230				self.$input_element.trigger('focus');
231				// put text caret to end of search field
232				if (self.$input_element[0].setSelectionRange) {
233					self.$input_element[0].setSelectionRange(self.$input_element.val().length, self.$input_element.val().length);
234				} else if (self.$input_element[0].createTextRange) {
235					var range = self.$input_element[0].createTextRange();
236					range.collapse(true);
237					range.moveEnd('character', self.$input_element.val().length);
238					range.moveStart('character', self.$input_element.val().length);
239					range.select();
240				}
241			});
242			self.$container_element.on('click', function () {
243				self.$input_element.focus();
244				self.$input_element.trigger('focus');
245			});
246			self.$container_element.on('dblclick', function () {
247				self.$input_element.select();
248				self.$input_element.trigger('focus');
249			});
250
251			// input element
252			self.$input_element.on('keydown', function (_e) {
253				var keyCode = _e.keyCode || _e.which;
254				var $active = null;
255				var $newActive = null;
256				switch (keyCode) {
257					case key.up:
258						_e.preventDefault();
259						showDropdown();
260						$active = self.$options_element.find('.active');
261						if ($active.length !== 0) {
262							$newActive = $active.prevUntil('.' + self.options.prefix + 'option:visible').add($active).first().prev('.' + self.options.prefix + 'option:visible');
263							$active.removeClass('active');
264							$newActive.addClass('active');
265						} else {
266							self.$options_element.find('.' + self.options.prefix + 'option').filter(':visible').last().addClass('active');
267						}
268						scrollToActiveOption();
269						break;
270					case key.down:
271						_e.preventDefault();
272						showDropdown();
273						$active = self.$options_element.find('.active');
274						if ($active.length !== 0) {
275							$newActive = $active.nextUntil('.' + self.options.prefix + 'option:visible').add($active).last().next('.' + self.options.prefix + 'option:visible');
276							$active.removeClass('active');
277							$newActive.addClass('active');
278						} else {
279							self.$options_element.find('.' + self.options.prefix + 'option').filter(':visible').first().addClass('active');
280						}
281						scrollToActiveOption();
282						break;
283					case key.escape:
284						_e.preventDefault();
285						break;
286					case key.enter:
287						_e.preventDefault();
288						$active = self.$options_element.find('.active');
289						if ($active.length !== 0) {
290							selectOption();
291						} else {
292							if (self.$input_element.val() !== '') {
293								self.options.submitCallback(self.$input_element.val());
294							}
295						}
296						resizeSearchInput();
297						break;
298					case key.backspace:
299						if (self.options.useSearch) {
300							if (self.$input_element.val() === '' && is_multiple && self.$source_element.find('option:selected').length) {
301								var lastSelectedItem = self.$source_element.find('option:selected').last()[0];
302								lastSelectedItem.removeAttribute('selected');
303								lastSelectedItem.selected = false;
304								self.$source_element.trigger('change');
305								renderSelectedItems();
306							}
307							resizeSearchInput();
308						} else {
309							_e.preventDefault();
310						}
311						break;
312					default:
313						resizeSearchInput();
314						break;
315				}
316			});
317			self.$input_element.on('keyup', function (_e) {
318				_e.preventDefault();
319				_e.stopPropagation();
320				var keyCode = _e.which;
321				switch (keyCode) {
322					case key.escape:
323						hideDropdown();
324						break;
325					case key.enter:
326						if (!self.options.keepOpen) {
327							hideDropdown();
328						}
329						break;
330					case key.left:
331					case key.right:
332					case key.up:
333					case key.down:
334					case key.tab:
335					case key.shift:
336						// Prevent any action
337						break;
338					default:
339						load();
340						break;
341				}
342				if (self.$container_element.hasClass('options-hidden') && (keyCode === key.left || keyCode === key.right || 
342keyCode === key.up || keyCode === key.down)) {
343					showDropdown();
344				}
345				resizeSearchInput();
346			});
347			self.$input_element.on('focus', function () {
348				self.$container_element.addClass('focused');
349				if (is_single || self.options.showAllOptionsOnFocus || !self.options.useSearch) {
350					if (!self.options.useSearch && using_remote_data) {
351						load()
352					}
353					showDropdown();
354				}
355			});
356			self.$input_element.on('blur', function () {
357				self.$container_element.removeClass('focused');
358				hideDropdown();
359			});
360
361			// bind selected item events
362			self.$container_element.on('mousedown', '.' + self.options.prefix + 'selected_item_remove', function () {
363				var source_item_element = $(this).closest('.' + self.options.prefix + 'selected_item').data('source_item_element');
364				source_item_element.removeAttribute('selected');
365				source_item_element.selected = false;
366				if (is_single && self.$source_element.find('[value=""]').length) {
367					var noValueOption = self.$source_element.find('[value=""]')[0];
368					noValueOption.setAttribute('selected', '');
369					noValueOption.selected = true;
370				}
371				self.$source_element.trigger('change');
372				filterResults(self.usefilterResults);
373				renderOptions();
374				renderSelectedItems();
375			});
376
377			// bind option events
378			self.$container_element.on('mouseover', '.' + self.options.prefix + 'option', function () {
379				var $active = self.$options_element.find('.active');
380				$active.removeClass('active');
381				var $this = $(this);
382				$this.addClass('active');
383			});
384			self.$container_element.on('mousedown', '.' + self.options.prefix + 'option', function (_e) {
385				_e.preventDefault();
386				_e.stopPropagation();
387			});
388			self.$container_element.on('mouseup', '.' + self.options.prefix + 'option', function () {
389				selectOption();
390			});
391			self.$container_element.on('click', '.' + self.options.prefix + 'option', function (_e) {
392				_e.stopPropagation();
393				var $active = self.$options_element.find('.active');
394				$active.removeClass('active');
395				var $this = $(this);
396				$this.addClass('active');
397			});
398
399			// Make elements accesible from options
400			self.options.$source_element = self.$source_element;
401			self.options.$container_element = self.$container_element;
402			self.options.$selecteditems_element = self.$selecteditems_element;
403			self.options.$input_element = self.$input_element;
404			self.options.$textlength_element = self.$textlength_element;
405			self.options.$options_element = self.$options_element;
406
407			// Render
408			renderOptions();
409			renderSelectedItems();
410			resizeSearchInput();
411		};
412
413
414		// RESIZE INPUT
415		var resizeSearchInput = function () {
416			if (is_multiple) {
417				self.$textlength_element.text(self.$input_element.val() === '' && self.options.placeholder !== '' ? self.options.placeholder : self.$input_element.val());
418				var width = self.$textlength_element.width() > (self.$container_element.width() - 30) ? (self.$container_element.width() - 30) : (self.$textlength_element.width() + 30);
419				self.$input_element.css({ width: width + 'px' });
420			}
421		};
422
423
424		// RENDER SELECTED ITEMS
425		var renderSelectedItems = function () {
426			self.$selecteditems_element.empty();
427			self.$source_element.find('option').each(function () {
428				var $option = $(this);
429				if (this.selected) {
430					var $item_element = $(document.createElement('div'));
431					$item_element.data('source_item_element', this);
432					$item_element.addClass(self.options.prefix + 'selected_item');
433					$item_element.addClass(self.options.prefix + 'value_' + $option.val().replace(/\W/g, ''));
434					if ($option.attr('class') !== undefined) {
435						$item_element.addClass($option.attr('class'));
436					}
437
438					// fetch data
439					var data = {
440						value: this.value,
441						text: this.text
442					};
443					$.each(this.attributes, function() {
444						if(this.specified) {
445							data[this.name.replace('data-', '')] = this.value;
446						}
447					});
448					$.extend(data, $(this).data('item_data'));
449					$item_element.append(self.options.render.selected_item(data, escape));
450					if (is_single && (data[self.options.valueField] === '' || typeof data[self.options.valueField] === 'undefined' || self.$source_element.find('[value=""]').length === 0)) {
451						$item_element.find('.' + self.options.prefix + 'selected_item_remove').remove();
452					}
453					self.$selecteditems_element.append($item_element);
454				}
455			});
456			if (is_single) {
457				if (self.options.placeholder !== '' && (self.$source_element.val() === '' || self.$source_element.val() === null)) {
458					self.$selecteditems_element.empty();
459					self.$selecteditems_element.append('<div class="' + self.options.prefix + 'placeholder">' + self.options.placeholder + '</div>');
460				} else {
461					self.$selecteditems_element.find('.' + self.options.prefix + 'placeholder').remove();
462				}
463			}
464		};
465
466
467		// RENDER OPTIONS
468		var renderOptions = function () {
469			self.$options_element.empty();
470			var optionsArray = [];
471			self.$source_element.children().each(function () {
472				if ($(this).prop('tagName').toLowerCase() === 'optgroup') {
473					var $group = $(this);
474					if ($group.children('option').length !== 0) {
475						var groupOptionsArray = [];
476						$group.children('option').each(function () {
477							groupOptionsArray.push({
478								type: 'option',
479								text: $(this).html(),
480								element: this
481							});
482						});
483						optionsArray.push({
484							type: 'group',
485							text: $group.attr('label'),
486							options: groupOptionsArray,
487							element: $group
488						});
489					}
490				} else {
491					optionsArray.push({
492						type: 'option',
493						text: $(this).html(),
494						element: this
495					});
496				}
497			});
498
499			$(optionsArray).each(function () {
500				if (this.type === 'group') {
501					var $group_element = $(document.createElement('li'));
502					$group_element.addClass(self.options.prefix + 'group');
503					if ($(this.element).attr('class') !== undefined) {
504						$group_element.addClass($(this.element).attr('class'));
505					}
506					$group_element.html($(this.element).attr('label'));
507					self.$options_element.append($group_element);
508
509					$(this.options).each(function () {
510						var option = createOption.call(this.element, true);
511						self.$options_element.append(option);
512					});
513
514				} else {
515					var option = createOption.call(this.element, false);
516					self.$options_element.append(option);
517				}
518			});
519			filterResults(self.usefilterResults);
520		};
521
522
523		// CREATE RESULT OPTION
524		var createOption = function (_isGroupOption) {
525			// holder li
526			var $option = $(document.createElement('li'));
527			$option.data('source_option_element', this);
528			$option.attr('onclick', 'void(0)');
529			$option.addClass(self.options.prefix + 'option');
530			$option.addClass(self.options.prefix + 'value_' + $(this).val().replace(/\W/g, ''));
531			if (_isGroupOption) {
532				$option.addClass(self.options.prefix + 'group_option');
533			}
534			if (this.selected) {
535				$option.addClass('active');
536			}
537			// class
538			if ($(this).attr('class') !== undefined) {
539				$option.addClass($(this).attr('class'));
540			}
541			// fetch data
542			var data = {
543				value: this.value,
544				text : this.text
545			};
546			$.each(this.attributes, function() {
547				if(this.specified) {
548					data[this.name.replace('data-', '')] = this.value;
549				}
550			});
551			$.extend(data, $(this).data('item_data'));
552			if (is_multiple && this.selected) {
553				$option.hide();
554			}
555
556			$option.append(self.options.render.option(data, escape));
557
558			return $option;
559		};
560
561
562		// LOAD SEARCH RESULTS (IF NEEDED)
563		var load = function () {
564			clearTimeout(delayTimer);
565			delayTimer = setTimeout(function () {
566				self.$container_element.addClass('loading');
567				if (using_remote_data) {
568					self.options.load(self.$input_element.val(), function (results, usefilterResults) {
569						self.usefilterResults = typeof usefilterResults !== 'undefined' ? usefilterResults : false;
570						self.$source_element.children('option').not(':selected').not('[value=""]').remove();
571						if (typeof results !== 'undefined') {
572							var selectedOptions = [];
573							$.each(self.$source_element.children('option:selected'), function (_key, _option) {
574								selectedOptions.push(_option.value);
575							});
576							if (is_single && self.$source_element.find('[value=""]').length === 0) {
577								self.$source_element.prepend($('<option value="">&nbsp;</option>'));
578							}
579							if (self.$input_element.val().replace(/\s/g, '').length >= self.options.minSearchLength) {
580								for (var i = 0; i < results.length; i++) {
581									var result = results[i];
582									if ($.inArray(result[self.options.valueField] + "", selectedOptions) === -1) {
583										var $option = $('<option value="' + result[self.options.valueField] + '">' + result[self.options.textField] + '</option>');
584										self.$source_element.append($option);
585										$option.data('item_data', result);
586									}
587								}
588							}
589						}
590						renderOptions();
591						self.$container_element.removeClass('loading');
592						filterResults(self.usefilterResults);
593					});
594				} else {
595					self.$container_element.removeClass('loading');
596					filterResults(self.usefilterResults);
597				}
598			}, self.options.delay);
599		};
600
601
602		// FILTER SEARCH RESULTS
603		var filterResults = function (usefilterResults) {
604			usefilterResults = typeof usefilterResults !== 'undefined' ? usefilterResults : false;
605			// bool true if search field is below required length
606			var searchIsBelowRequired = self.$input_element.val().replace(/\s/g, '').length < self.options.minSearchLength;
607			// bool true if any options are visible
608			has_visible_options = false;
609			// get sanitized search text
610			var searchFor = self.$input_element.val().toLowerCase();
611			// iterate through the options
612			self.$options_element.find('.' + self.options.prefix + 'option').each(function () {
613				var $this = $(this);
614				var source_option_element = $this.data('source_option_element');
615				var $source_option_element = $(source_option_element);
616
617				//  SHOW IF:
618				// --------------------------------------------
619				//  NOT using filter results
620				//      AND option is NOT selected
621				//  OR
622				//      using filter results
623				//      AND
624				//          option is NOT selected
625				//          OR single select
626				//      AND
627				//          use search
628				//          AND
629				//              searchtext is below required
630				//              OR option text matches searchtext
631				//              OR option value is empty
632				//          OR NOT using search
633				var match_found = false;
634				$.each(self.options.searchFields, function (key, value) {
635					if (typeof $source_option_element.data(value) !== 'undefined' && $source_option_element.data(value).toString().toLowerCase().indexOf(searchFor) !== -1) {
636						match_found = true;
637						return false;
638					}
639				});
640				if (
641					(!usefilterResults
642						&& !source_option_element.selected
643					)
644					|| (usefilterResults
645						&& (
646							!source_option_element.selected
647							|| is_single
648						)
649						&& (
650							self.options.useSearch
651							&& (
652								searchIsBelowRequired
653								|| match_found
654								|| $source_option_element.val() === ''
655							)
656							|| !self.options.useSearch
657						)
658					)
659				) {
660					$this.show();
661					has_visible_options = !has_visible_options ? true : has_visible_options;
662				} else {
663					$this.hide();
664				}
665			});
666			// iterate through the groups
667			self.$options_element.find('.' + self.options.prefix + 'group').each(function () {
668				var $this = $(this);
669				var has_visible_options = false;
670				$this.nextUntil('.' + self.options.prefix + 'group').each(function () {
671					var $option = $(this);
672					if ($option.css('display') !== 'none') {
673						has_visible_options = true;
674						return false;
675					}
676				});
677				// show if the group has any visible children
678				if (has_visible_options) {
679					$this.show();
680				} else {
681					$this.hide();
682				}
683			});
684			showDropdown();
685			if (is_multiple) {
686				self.$options_element.find('.active').removeClass('active');
687				if (!searchIsBelowRequired) {
688					self.$options_element.find('.' + self.options.prefix + 'option').filter(':visible').first().addClass('active');
689				}
690			}
691		};
692
693
694		// SHOW OPTIONS AND MASK
695		var showDropdown = function () {
696			if (self.$input_element.is(':focus') && (has_visible_options || is_single ) && !(self.$options_element.is(':empty') && !self.options.useSearch)) {
697				self.$container_element.removeClass('options-hidden').addClass('options-visible');
698				self.$mask_element.show();
699				if (self.options.useDimmer) {
700					self.$mask_element.addClass(self.options.prefix + 'mask_dimmed');
701				} else {
702					self.$mask_element.removeClass(self.options.prefix + 'mask_dimmed');
703				}
704				setTimeout(function () {
705					self.$options_element.css('top', (self.$container_element.outerHeight() + (is_multiple ? 0 : self.$input_element.outerHeight()) - 1) + 'px');
706				}, 1);
707				scrollToActiveOption();
708			} else {
709				hideDropdown();
710			}
711		};
712
713
714		// HIDE OPTIONS AND MASK
715		var hideDropdown = function () {
716			self.$container_element.removeClass('options-visible').addClass('options-hidden');
717			self.$mask_element.removeClass(self.options.prefix + 'mask_dimmed');
718			self.$mask_element.hide();
719		};
720
721
722		// SCROLL TO ACTIVE OPTION IN OPTIONS LIST
723		var scrollToActiveOption = function () {
724			var $active_element = self.$options_element.find('.' + self.options.prefix + 'option.active');
725			if ($active_element.length > 0) {
726				self.$options_element.scrollTop(self.$options_element.scrollTop() + $active_element.position().top - self.$options_element.height()/2 + $active_element.height()/2);
727			}
728		};
729
730
731		// SELECT ACTIVE OPTION
732		var selectOption = function () {
733			// select option
734			if (is_single) {
735				var lastSelectedItem = self.$source_element.find('option:selected').last()[0];
736				lastSelectedItem.removeAttribute('selected');
737				lastSelectedItem.selected = false;
738			}
739			var $active = self.$options_element.find('.active');
740			$active.data('source_option_element').setAttribute('selected', '');
741			$active.data('source_option_element').selected = true;
742			self.$source_element.trigger('change');
743			if (!self.options.keepOpen) {
744				self.$input_element.val('');
745			}
746			filterResults(self.usefilterResults);
747			renderSelectedItems();
748			resizeSearchInput();
749			if (using_remote_data && !self.options.keepOpen) {
750				self.$source_element.children('option').not(':selected').not('[value=""]').remove();
751				renderOptions();
752				hideDropdown();
753			}
754			if (!self.options.keepOpen) {
755				hideDropdown();
756			}
757		};
758
759
760		// ESCAPE FUNCTION
761		var escape = function(str) {
762			return (str + '')
763				.replace(/&/g, '&amp;')
764				.replace(/</g, '&lt;')
765				.replace(/>/g, '&gt;')
766				.replace(/"/g, '&quot;');
767		};
768
769
770		// REFRESH PLUGIN
771		self.refresh = function () {
772			renderSelectedItems();
773		};
774
775
776		// REMOVE PLUGIN AND REVERT SELECT ELEMENT TO ORIGINAL STATE
777		self.destroy = function () {
778			self.$container_element.remove();
779			$.removeData(_element, 'selectator');
780			self.$source_element.show();
781			if ($('.' + self.options.prefix + 'element').length === 0) {
782				self.$mask_element.remove();
783			}
784		};
785
786
787		// Initialize plugin
788		self.init();
789	};
790
791	// Initializer
792	$.fn.selectator = function(_options) {
793		var options = _options !== undefined ? _options : {};
794		return this.each(function () {
795			var $this = $(this);
796			if (typeof(options) === 'object') {
797				if ($this.data('selectator') === undefined) {
798					var self = new $.selectator(this, options);
799					$this.data('selectator', self);
800				}
801			} else if ($this.data('selectator')[options]) {
802				$this.data('selectator')[options].apply(this, Array.prototype.slice.call(arguments, 1));
803			} else {
804				$.error('Method ' + options + ' does not exist in $.selectator');
805			}
806		});
807	};
808}(jQuery));
809
810// Markup initializer
811$(function () {
812	'use strict';
813	$('.selectator').each(function () {
814		var $this = $(this);
815		var options = {};
816		$.each($this.data(), function (_key, _value) {
817			if (_key.substring(0, 10) === 'selectator') {
818				options[_key.substring(10, 11).toLowerCase() + _key.substring(11)] = _value;
819			}
820		});
821		$this.selectator(options);
822	});
823});

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.