PageSourceSearch

https://www.salutempharma.sk/templates/forfirst/js/jquery.selectric.js

js salutempharma.sk collected 2026-10-09 23:56:22 UTC 19,568 bytes, 538 lines download raw bytes

1;(function($) {
2  'use strict';
3
4  var pluginName = 'selectric',
5      classList = 'Input Items Open Disabled TempShow HideSelect Wrapper Hover Responsive Above Scroll Group GroupLabel',
6      bindSufix = '.sl',
7      defaults = {
8        onChange: function(elm) { $(elm).change(); },
9        maxHeight: 300,
10        keySearchTimeout: 500,
11        arrowButtonMarkup: '<b class="button">&#x25be;</b>',
12        disableOnMobile: true,
13        openOnHover: false,
14        hoverIntentTimeout: 500,
15        expandToItemText: false,
16        responsive: false,
17        preventWindowScroll: true,
18        inheritOriginalWidth: false,
19        allowWrap: true,
20        customClass: {
21          prefix: pluginName,
22          camelCase: false,
23          overwrite: true
24        },
25        optionsItemBuilder: '{text}', // function(itemData, element, index)
26        labelBuilder: '{text}' // function(currItem)
27      },
28      hooks = {
29        add: function(callbackName, hookName, fn) {
30          if ( !this[callbackName] )
31            this[callbackName] = {};
32
33          this[callbackName][hookName] = fn;
34        },
35        remove: function(callbackName, hookName) {
36          delete this[callbackName][hookName];
37        }
38      },
39      _utils = {
40        // Replace diacritics
41        replaceDiacritics: function(s) {
42          // /[\340-\346]/g, // a
43          // /[\350-\353]/g, // e
44          // /[\354-\357]/g, // i
45          // /[\362-\370]/g, // o
46          // /[\371-\374]/g, // u
47          // /[\361]/g,      // n
48          // /[\347]/g,      // c
49          // /[\377]/g       // y
50          var d = '40-46 50-53 54-57 62-70 71-74 61 47 77'.replace(/\d+/g, '\\3$&').split(' '),
51              k = d.length;
52
53          while (k--)
54            s = s.toLowerCase().replace(RegExp('[' + d[k] + ']', 'g'), 'aeiouncy'.charAt(k));
55
56          return s;
57        },
58        // https://gist.github.com/atesgoral/984375
59        format: function(f) {var a=arguments;return(""+f).replace(/{(\d+|(\w+))}/g,function(s,i,p) {return p&&a[1]?a[1][p]:a[i]})},
60        nextEnabledItem: function(selectItems, selected) {
61          while ( selectItems[ selected = (selected + 1) % selectItems.length ].disabled ) {}
62          return selected;
63        },
64        previousEnabledItem: function(selectItems, selected) {
65          while ( selectItems[ selected = (selected > 0 ? selected : selectItems.length) - 1 ].disabled ) {}
66          return selected;
67        },
68        toDash: function(str) {
69          return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
70        },
71        triggerCallback: function(fn, scope) {
72          var elm = scope.element,
73              func = scope.options['on' + fn];
74
75          if ( $.isFunction(func) )
76            func.call(elm, elm, scope);
77
78          if ( hooks[fn] ) {
79            $.each(hooks[fn], function() {
80              this.call(elm, elm, scope);
81            });
82          }
83
84          $(elm).trigger(pluginName + '-' + _utils.toDash(fn), scope);
85        }
86      },
87      $doc = $(document),
88      $win = $(window),
89      Selectric = function(element, opts) {
90        var _this = this,
91            $original = $(element),
92            $input, $items, $itemsScroll, $wrapper, $label, $outerWrapper, $li,
93            isOpen = false,
94            isEnabled = false,
95            selected,
96            currValue,
97            itemsHeight,
98            itemsInnerHeight,
99            finalWidth,
100            optionsLength,
101            eventTriggers,
102            isMobile = /android|ip(hone|od|ad)/i.test(navigator.userAgent),
103            tabindex = $original.prop('tabindex'),
104            labelBuilder;
105
106        function _init(opts) {
107          _this.options = $.extend(true, {}, defaults, _this.options, opts);
108          _this.classes = {};
109          _this.element = element;
110
111          _utils.triggerCallback('BeforeInit', _this);
112
113          // Disable on mobile browsers
114          if ( _this.options.disableOnMobile && isMobile ) {
115            _this.disableOnMobile = true;
116            return;
117          }
118
119          // Preserve data
120          _destroy(true);
121
122          // Generate classNames for elements
123          var customClass   = _this.options.customClass,
124              postfixes     = classList.split(' '),
125              originalWidth = $original.width();
126
127          $.each(postfixes, function(i, currClass) {
128            var c = customClass.prefix + '-' + currClass;
129            _this.classes[currClass.toLowerCase()] = customClass.camelCase ? c : _utils.toDash(c);
130          });
131
132          $input        = $('<input/>', { 'class': _this.classes.input, 'readonly': isMobile });
133          $items        = $('<div/>',   { 'class': _this.classes.items, 'tabindex': -1 });
134          $itemsScroll  = $('<div/>',   { 'class': _this.classes.scroll });
135          $wrapper      = $('<div/>',   { 'class': customClass.prefix, 'html': _this.options.arrowButtonMarkup });
136          $label        = $('<p class="label"/>');
137          $outerWrapper = $original.wrap('<div>').parent().append($wrapper.prepend($label), $items, $input);
138
139          eventTriggers = {
140            open    : _open,
141            close   : _close,
142            destroy : _destroy,
143            refresh : _refresh,
144            init    : _init
145          };
146
147          $original.on(eventTriggers).wrap('<div class="' + _this.classes.hideselect + '">');
148          $.extend(_this, eventTriggers);
149
150          labelBuilder = _this.options.labelBuilder;
151
152          if ( _this.options.inheritOriginalWidth && originalWidth > 0 )
153            $outerWrapper.width(originalWidth);
154
155          _populate();
156        }
157
158        // Generate options markup and event binds
159        function _populate() {
160          _this.items = [];
161
162          var $options = $original.children(),
163              _$li = '<ul>',
164              $justOptions = $original.find('option'),
165              selectedIndex = $justOptions.index($justOptions.filter(':selected')),
166              currIndex = 0;
167
168          currValue = (selected = ~selectedIndex ? selectedIndex : 0);
169
170          if ( optionsLength = $options.length ) {
171            // Build options markup
172            $options.each(function() {
173              var $elm = $(this);
174
175              if ( $elm.is('optgroup') ) {
176                var groupDisabled = $elm.prop('disabled'),
177                    $children = $elm.children();
178
179                _$li += _utils.format('<ul class="{1}"><li class="{2}">{3}</li>',
180                  $.trim([_this.classes.group, groupDisabled ? 'disabled' : '', $elm.prop('class')].join(' ')),
181                  _this.classes.grouplabel,
182                  $elm.prop('label')
183                );
184
185                if ( groupDisabled ) {
186                  $children.prop('disabled', true);
187                }
188
189                $children.each(buildOption);
190
191                _$li += '</ul>';
192              } else {
193                buildOption.call($elm);
194              }
195
196              function buildOption() {
197                var $elm = $(this),
198                    optionText = $elm.html(),
199                    selectDisabled = $elm.prop('disabled'),
200                    itemBuilder = _this.options.optionsItemBuilder;
201
202                _this.items[currIndex] = {
203                  element  : $elm,
204                  value    : $elm.val(),
205                  text     : optionText,
206                  slug     : _utils.replaceDiacritics(optionText),
207                  disabled : selectDisabled
208                };
209
210                _$li += _utils.format('<li data-index="{1}" class="{2}">{3}</li>',
211                  currIndex,
212                  $.trim([currIndex == currValue ? 'selected' : '', currIndex == optionsLength - 1 ? 'last' : '', selectDisabled ? 'disabled' : ''].join(' ')),
213                  $.isFunction(itemBuilder) ? itemBuilder(_this.items[currIndex], $elm, currIndex) : _utils.format(itemBuilder, _this.items[currIndex])
214                );
215
216                currIndex++;
217              }
218            });
219
220            $items.append( $itemsScroll.html(_$li + '</ul>') );
221
222            $label.html(
223              $.isFunction(labelBuilder) ? labelBuilder(_this.items[currValue]) : _utils.format(labelBuilder, _this.items[currValue])
224            )
225          }
226
227          $wrapper.add($original).add($outerWrapper).add($input).off(bindSufix);
228
229          $outerWrapper.prop('class', [
230            _this.classes.wrapper,
231            _this.options.customClass.overwrite ?
232              $original.prop('class').replace(/\S+/g, _this.options.customClass.prefix + '-$&') :
233              $original.prop('class'),
234            _this.options.responsive ? _this.classes.responsive : ''
235          ].join(' '));
236
237          if ( !$original.prop('disabled') ) {
238            isEnabled = true;
239
240            // Not disabled, so... Removing disabled class and bind hover
241            $outerWrapper.removeClass(_this.classes.disabled).on('mouseenter' + bindSufix + ' mouseleave' + bindSufix, function(e) {
242              $(this).toggleClass(_this.classes.hover);
243
244              // Delay close effect when openOnHover is true
245              if ( _this.options.openOnHover ) {
246                clearTimeout(_this.closeTimer);
247                e.type == 'mouseleave' ? _this.closeTimer = setTimeout(_close, _this.options.hoverIntentTimeout) : _open();
248              }
249            });
250
251            // Toggle open/close
252            $wrapper.on('click' + bindSufix, function(e) {
253              isOpen ? _close() : _open(e);
254            });
255
256            $input
257              .prop({
258                tabindex: tabindex,
259                disabled: false
260              })
261              .on('keypress' + bindSufix, _handleSystemKeys)
262              .on('keydown' + bindSufix, function(e) {
263                _handleSystemKeys(e);
264
265                // Clear search
266                clearTimeout(_this.resetStr);
267                _this.resetStr = setTimeout(function() {
268                  $input.val('');
269                }, _this.options.keySearchTimeout);
270
271                var key = e.keyCode || e.which;
272
273                // If it's a directional key
274                // 37 => Left
275                // 38 => Up
276                // 39 => Right
277                // 40 => Down
278                if ( key > 36 && key < 41 ) {
279                  if ( !_this.options.allowWrap ) {
280                    if ( (key < 39 && selected == 0) || (key > 38 && (selected + 1) == _this.items.length) ) {
281                      return;
282                    }
283                  }
284
285                  _select(_utils[(key < 39 ? 'previous' : 'next') + 'EnabledItem'](_this.items, selected));
286                }
287              })
288              .on('focusin' + bindSufix, function(e) {
289                // Stupid, but necessary... Prevent the flicker when
290                // focusing out and back again in the browser window
291                $input.one('blur', function() {
292                  $input.blur();
293                });
294
295                isOpen || _open(e);
296              })
297              .on('oninput' in $input[0] ? 'input' : 'keyup', function() {
298                if ( $input.val().length ) {
299                  // Search in select options
300                  $.each(_this.items, function(i, elm) {
301                    if ( RegExp('^' + $input.val(), 'i').test(elm.slug) && !elm.disabled ) {
302                      _select(i);
303                      return false;
304                    }
305                  });
306                }
307              });
308
309            $original.prop('tabindex', false);
310
311            // Remove styles from items box
312            // Fix incorrect height when refreshed is triggered with fewer options
313            $li = $('li', $items.removeAttr('style')).on({
314              // Prevent <input> blur on Chrome
315              mousedown: function(e) {
316                e.preventDefault();
317                e.stopPropagation();
318              },
319              click: function() {
320                // The second parameter is to close the box after click
321                _select($(this).data('index'), true);
322
323                // Chrome doesn't close options box if select is wrapped with a label
324                // We need to 'return false' to avoid that
325                return false;
326              }
327            }).filter('[data-index]');
328          } else {
329            $outerWrapper.addClass(_this.classes.disabled);
330            $input.prop('disabled', true);
331          }
332
333          _utils.triggerCallback('Init', _this);
334        }
335
336        function _refresh() {
337          _utils.triggerCallback('Refresh', _this);
338          _populate();
339        }
340
341        // Behavior when system keys is pressed
342        function _handleSystemKeys(e) {
343          var key = e.keyCode || e.which;
344
345          if ( key == 13 ) {
346            e.preventDefault();
347          }
348
349          // Tab / Enter / ESC
350          if ( /^(9|13|27)$/.test(key) ) {
351            e.stopPropagation();
352            _select(selected, true);
353          }
354        }
355
356        // Set options box width/height
357        function _calculateOptionsDimensions() {
358          // Calculate options box height
359          // Set a temporary class on the hidden parent of the element
360          var hiddenChildren = $items.closest(':visible').children(':hidden').addClass(_this.classes.tempshow),
361              maxHeight = _this.options.maxHeight,
362              itemsWidth = $items.outerWidth(),
363              wrapperWidth = $wrapper.outerWidth() - (itemsWidth - $items.width());
364
365          // Set the dimensions, minimum is wrapper width, expand for long items if option is true
366          if ( !_this.options.expandToItemText || wrapperWidth > itemsWidth )
367            finalWidth = wrapperWidth;
368          else {
369            // Make sure the scrollbar width is included
370            $items.css('overflow', 'scroll');
371
372            // Set a really long width for $outerWrapper
373            $outerWrapper.width(9e4);
374            finalWidth = $items.width();
375            // Set scroll bar to auto
376            $items.css('overflow', '');
377            $outerWrapper.width('');
378          }
379
380          $items.width(finalWidth).height() > maxHeight && $items.height(maxHeight);
381
382          // Remove the temporary class
383          hiddenChildren.removeClass(_this.classes.tempshow);
384        }
385
386        // Open the select options box
387        function _open(e) {
388          _utils.triggerCallback('BeforeOpen', _this);
389
390          if ( e ) {
391            e.preventDefault();
392            e.stopPropagation();
393          }
394
395          if ( isEnabled ) {
396            _calculateOptionsDimensions();
397
398            // Find any other opened instances of select and close it
399            $('.' + _this.classes.hideselect, '.' + _this.classes.open).children()[pluginName]('close');
400
401            isOpen = true;
402            itemsHeight = $items.outerHeight();
403            itemsInnerHeight = $items.height();
404
405            // Toggle options box visibility
406            $outerWrapper.addClass(_this.classes.open);
407
408            // Give dummy input focus
409            $input.val('').is(':focus') || $input.focus();
410
411            $doc.on('click' + bindSufix, _close).on('scroll' + bindSufix, _isInViewport);
412            _isInViewport();
413
414            // Prevent window scroll when using mouse wheel inside items box
415            if ( _this.options.preventWindowScroll ) {
416              $doc.on('mousewheel' + bindSufix + ' DOMMouseScroll' + bindSufix, '.' + _this.classes.scroll, function(e) {
417                var orgEvent = e.originalEvent,
418                    scrollTop = $(this).scrollTop(),
419                    deltaY = 0;
420
421                if ( 'detail'      in orgEvent ) { deltaY = orgEvent.detail * -1; }
422                if ( 'wheelDelta'  in orgEvent ) { deltaY = orgEvent.wheelDelta;  }
423                if ( 'wheelDeltaY' in orgEvent ) { deltaY = orgEvent.wheelDeltaY; }
424                if ( 'deltaY'      in orgEvent ) { deltaY = orgEvent.deltaY * -1; }
425
426                if ( scrollTop == (this.scrollHeight - itemsInnerHeight) && deltaY < 0 || scrollTop == 0 && deltaY > 0 ) {
427                  e.preventDefault();
428                }
429              });
430            }
431
432            _detectItemVisibility(selected);
433
434            _utils.triggerCallback('Open', _this);
435          }
436        }
437
438        // Detect is the options box is inside the window
439        function _isInViewport() {
440          $outerWrapper.toggleClass(_this.classes.above, $outerWrapper.offset().top + $outerWrapper.outerHeight() + itemsHeight > $win.scrollTop() + $win.height());
441        }
442
443        // Close the select options box
444        function _close() {
445          _utils.triggerCallback('BeforeClose', _this);
446
447          if ( currValue != selected ) {
448            _utils.triggerCallback('BeforeChange', _this);
449
450            var text = _this.items[selected].text;
451
452            // Apply changed value to original select
453            $original
454              .prop('selectedIndex', currValue = selected)
455              .data('value', text);
456
457            // Change label text
458            $label.html(
459              $.isFunction(labelBuilder) ? labelBuilder(_this.items[selected]) : _utils.format(labelBuilder, _this.items[selected])
460            )
461
462            _utils.triggerCallback('Change', _this);
463          }
464
465          // Remove custom events on document
466          $doc.off(bindSufix);
467
468          // Remove visible class to hide options box
469          $outerWrapper.removeClass(_this.classes.open);
470
471          isOpen = false;
472
473          _utils.triggerCallback('Close', _this);
474        }
475
476        // Select option
477        function _select(index, close) {
478          // Parameter index is required
479          if ( index == undefined ) {
480            return;
481          }
482
483          // If element is disabled, can't select it
484          if ( !_this.items[index].disabled ) {
485            // If 'close' is false (default), the options box won't close after
486            // each selected item, this is necessary for keyboard navigation
487            $li
488              .removeClass('selected')
489              .eq(selected = index)
490              .addClass('selected');
491
492            _detectItemVisibility(index);
493            close && _close();
494          }
495        }
496
497        // Detect if currently selected option is visible and scroll the options box to show it
498        function _detectItemVisibility(index) {
499          var liHeight = $li.eq(index).outerHeight(),
500              liTop = $li[index].offsetTop,
501              itemsScrollTop = $itemsScroll.scrollTop(),
502              scrollT = liTop + liHeight * 2;
503
504          $itemsScroll.scrollTop(
505            scrollT > itemsScrollTop + itemsHeight ? scrollT - itemsHeight :
506              liTop - liHeight < itemsScrollTop ? liTop - liHeight :
507                itemsScrollTop
508          );
509        }
510
511        // Unbind and remove
512        function _destroy(preserveData) {
513          if ( isEnabled ) {
514            $items.add($wrapper).add($input).remove();
515            !preserveData && $original.removeData(pluginName).removeData('value');
516            $original.prop('tabindex', tabindex).off(bindSufix).off(eventTriggers).unwrap().unwrap();
517            isEnabled = false;
518          }
519        }
520
521        _init(opts);
522      };
523
524  // A really lightweight plugin wrapper around the constructor,
525  // preventing against multiple instantiations
526  $.fn[pluginName] = function(args) {
527    return this.each(function() {
528      var data = $.data(this, pluginName);
529
530      if ( data && !data.disableOnMobile )
531        (''+args === args && data[args]) ? data[args]() : data.init(args);
532      else
533        $.data(this, pluginName, new Selectric(this, args));
534    });
535  };
536
537  $.fn[pluginName].hooks = hooks;
538}(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.