PageSourceSearch

https://www.e-steiermark.com/_assets/0a33460dc0a07202ce98b9348eacf…/jquery.prettydropdowns.js?1790172348

js e-steiermark.com collected 2026-09-24 09:37:14 UTC 27,172 bytes, 501 lines download raw bytes

1/*!
2 * jQuery Pretty Dropdowns Plugin v4.17.0 by T. H. Doan (https://thdoan.github.io/pretty-dropdowns/)
3 *
4 * jQuery Pretty Dropdowns by T. H. Doan is licensed under the MIT License.
5 * Read a copy of the license in the LICENSE file or at https://choosealicense.com/licenses/mit/
6 */
7
8(function($) {
9    $.fn.prettyDropdown = function(oOptions) {
10
11        // Default options
12        oOptions = $.extend({
13            classic: false,
14            customClass: 'arrow',
15            width: null,
16            height: 50,
17            hoverIntent: 200,
18            multiDelimiter: '; ',
19            multiVerbosity: 99,
20            selectedMarker: '✓',
21            afterLoad: function(){}
22        }, oOptions);
23
24        oOptions.selectedMarker = '<span aria-hidden="true" class="checked"> ' + oOptions.selectedMarker + '</span>';
25        // Validate options
26        if (isNaN(oOptions.width) && !/^\d+%$/.test(oOptions.width)) oOptions.width = null;
27        if (isNaN(oOptions.height)) oOptions.height = 50;
28        else if (oOptions.height<8) oOptions.height = 8;
29        if (isNaN(oOptions.hoverIntent)) oOptions.hoverIntent = 200;
30        if (isNaN(oOptions.multiVerbosity)) oOptions.multiVerbosity = 99;
31
32        // Translatable strings
33        var MULTI_NONE = 'None selected',
34            MULTI_PREFIX = 'Selected: ',
35            MULTI_POSTFIX = ' selected';
36
37        // Globals
38        var $current,
39            aKeys = [
40                '0','1','2','3','4','5','6','7','8','9',,,,,,,,
41                'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z'
42            ],
43            nCount,
44            nHoverIndex,
45            nLastIndex,
46            nTimer,
47            nTimestamp,
48
49            // Initiate pretty drop-downs
50            init = function(elSel) {
51                var $select = $(elSel),
52                    nSize = elSel.size,
53                    sId = elSel.name || elSel.id || '',
54                    sLabelId;
55                // Exit if widget has already been initiated
56                if ($select.data('loaded')) return;
57                // Remove 'size' attribute to it doesn't affect vertical alignment
58                $select.data('size', nSize).removeAttr('size');
59                // Set <select> height to reserve space for <div> container
60                $select.css('visibility', 'hidden').outerHeight(oOptions.height);
61                nTimestamp = performance.now()*100000000000000;
62                // Test whether to add 'aria-labelledby'
63                if (elSel.id) {
64                    // Look for <label>
65                    var $label = $('label[for=' + elSel.id + ']');
66                    if ($label.length) {
67                        // Add 'id' to <label> if necessary
68                        if ($label.attr('id') && !/^menu\d{13,}$/.test($label.attr('id'))) sLabelId = $label.attr('id');
69                        else $label.attr('id', (sLabelId = 'menu' + nTimestamp));
70                    }
71                }
72                nCount = 0;
73                var $items = $('optgroup, option', $select),
74                    $selected = $items.filter(':selected'),
75                    bMultiple = elSel.multiple,
76                    // Height - 2px for borders
77                    sHtml = '<ul' + (elSel.disabled ? '' : ' tabindex="0"') + ' role="listbox"'
78                        + (elSel.title ? ' title="' + elSel.title + '" aria-label="' + elSel.title + '"' : '')
79                        + (sLabelId ? ' aria-labelledby="' + sLabelId + '"' : '')
80                        + ' aria-activedescendant="item' + nTimestamp + '-1" aria-expanded="false"'
81                        + ' style="max-height:' + (oOptions.height-2) + 'px;margin:'
82                        // NOTE: $select.css('margin') returns an empty string in Firefox, so we have to get
83                        // each margin individually. See https://github.com/jquery/jquery/issues/3383
84                        + $select.css('margin-top') + ' '
85                        + $select.css('margin-right') + ' '
86                        + $select.css('margin-bottom') + ' '
87                        + $select.css('margin-left') + ';">';
88                if (bMultiple) {
89                    sHtml += renderItem(null, 'selected');
90                    $items.each(function() {
91                        if (this.selected) {
92                            sHtml += renderItem(this, '', true)
93                        } else {
94                            sHtml += renderItem(this);
95                        }
96                    });
97                } else {
98                    if (oOptions.classic) {
99                        $items.each(function() {
100                            sHtml += renderItem(this);
101                        });
102                    } else {
103                        sHtml += renderItem($selected[0], 'selected');
104                        $items.filter(':not(:selected)').each(function() {
105                            sHtml += renderItem(this);
106                        });
107                    }
108                }
109                sHtml += '</ul>';
110                $select.wrap('<div ' + (sId ? 'id="prettydropdown-' + sId + '" ' : '')
111                    + 'class="prettydropdown '
112                    + (oOptions.classic ? 'classic ' : '')
113                    + (elSel.disabled ? 'disabled ' : '')
114                    + (bMultiple ? 'multiple ' : '')
115                    + oOptions.customClass + ' loading"'
116                    // NOTE: For some reason, the container height is larger by 1px if the <select> has the
117                    // 'multiple' attribute or 'size' attribute with a value larger than 1. To fix this, we
118                    // have to inline the height.
119                    + ((bMultiple || nSize>1) ? ' style="height:' + oOptions.height + 'px;"' : '')
120                    +'></div>').before(sHtml).data('loaded', true);
121                var $dropdown = $select.parent().children('ul'),
122                    nWidth = $dropdown.outerWidth(true),
123                    nOuterWidth;
124                $items = $dropdown.children();
125                // Update default selected values for multi-select menu
126                if (bMultiple) updateSelected($dropdown);
127                else if (oOptions.classic) $('[data-value="' + $selected.val() + '"]', $dropdown).addClass('selected').append(oOptions.selectedMarker);
128                // Calculate width if initially hidden
129                if ($dropdown.width()<=0) {
130                    var $clone = $dropdown.parent().clone().css({
131                        position: 'absolute',
132                        top: '-100%'
133                    });
134                    $('body').append($clone);
135                    nWidth = $clone.children('ul').outerWidth(true);
136                    $('li', $clone).width(nWidth);
137                    nOuterWidth = $clone.children('ul').outerWidth(true);
138                    $clone.remove();
139                }
140                // Set dropdown width and event handler
141                // NOTE: Setting width using width(), then css() because width() only can return a float,
142                // which can result in a missing right border when there is a scrollbar.
143                $items.width(nWidth).css('width', $items.css('width'));
144                if (oOptions.width) {
145                    $dropdown.parent().css('min-width', $items.css('width'));
146                    $dropdown.css('width', '100%');
147                    $items.css('width', '100%');
148                }
149                $items.click(function() {
150                    var $li = $(this),
151                        $selected = $dropdown.children('.selected');
152                    // Ignore disabled menu
153                    if ($dropdown.parent().hasClass('disabled')) return;
154                    // Only update if not disabled, not a label, and a different value selected
155                    if ($dropdown.hasClass('active') && !$li.hasClass('disabled') && !$li.hasClass('label') && $li.data('value')!==$selected.data('value')) {
156                        // Select highlighted item
157                        if (bMultiple) {
158                            if ($li.children('span.checked').length) $li.children('span.checked').remove();
159                            else $li.append(oOptions.selectedMarker);
160                            // Sync <select> element
161                            $dropdown.children(':not(.selected)').each(function(nIndex) {
162                                $('optgroup, option', $select).eq(nIndex).prop('selected', $(this).children('span.checked').length>0);
163                            });
164                            // Update selected values for multi-select menu
165                            updateSelected($dropdown);
166                        } else {
167                            $selected.removeClass('selected').children('span.checked').remove();
168                            $li.addClass('selected').append(oOptions.selectedMarker);
169                            if (!oOptions.classic) $dropdown.prepend($li);
170                            $dropdown.removeClass('reverse').attr('aria-activedescendant', $li.attr('id'));
171                            if ($selected.data('group') && !oOptions.classic) $dropdown.children('.label').filter(function() {
172                                return $(this).text()===$selected.data('group');
173                            }).after($selected);
174                            // Sync <select> element
175                            $('optgroup, option', $select).filter(function() {
176                                // <option>: this.value = this.text, $li.data('value') = $li.contents()
177                                // <option value="">: this.value = "", $li.data('value') = undefined
178                                return (this.value+'|'+this.text)===($li.data('value')||'')+'|'+$li.contents().filter(function() {
179                                    // Filter out selected marker
180                                    return this.nodeType===3;
181                                }).text();
182                            }).prop('selected', true);
183                        }
184                        $select.trigger('change');
185                    }
186                    if ($li.hasClass('selected') || !bMultiple) {
187                        $dropdown.toggleClass('active');
188                        $dropdown.attr('aria-expanded', $dropdown.hasClass('active'));
189                    }
190                    // Try to keep drop-down menu within viewport
191                    if ($dropdown.hasClass('active')) {
192                        // Close any other open menus
193                        if ($('.prettydropdown > ul.active').length>1) resetDropdown($('.prettydropdown > ul.active').not($dropdown)[0]);
194                        var nWinHeight = window.innerHeight,
195                            nMaxHeight,
196                            nOffsetTop = $dropdown.offset().top,
197                            nScrollTop = $(document).scrollTop(),
198                            nDropdownHeight = $dropdown.outerHeight();
199                        if (nSize) {
200                            nMaxHeight = nSize*(oOptions.height-2);
201                            if (nMaxHeight<nDropdownHeight-2) nDropdownHeight = nMaxHeight+2;
202                        }
203                        var nDropdownBottom = nOffsetTop-nScrollTop+nDropdownHeight;
204                        if (nDropdownBottom>nWinHeight) {
205                            // Expand to direction that has the most space
206                            if (nOffsetTop-nScrollTop>nWinHeight-(nOffsetTop-nScrollTop+oOptions.height)) {
207                                $dropdown.addClass('reverse');
208                                if (!oOptions.classic) $dropdown.append($selected);
209                                if (nOffsetTop-nScrollTop+oOptions.height<nDropdownHeight) {
210                                    $dropdown.outerHeight(nOffsetTop-nScrollTop+oOptions.height);
211                                    // Ensure the selected item is in view
212                                    $dropdown.scrollTop(nDropdownHeight);
213                                }
214                            } else {
215                                $dropdown.height($dropdown.height()-(nDropdownBottom-nWinHeight));
216                            }
217                        }
218                        if (nMaxHeight && nMaxHeight<$dropdown.height()) $dropdown.css('height', nMaxHeight + 'px');
219                        // Ensure the selected item is in view
220                        if (oOptions.classic) $dropdown.scrollTop($selected.index()*(oOptions.height-2));
221                    } else {
222                        $dropdown.data('clicked', true);
223                        resetDropdown($dropdown[0]);
224                    }
225                });
226                $dropdown.on({
227                    focusin: function() {
228                        // Unregister any existing handlers first to prevent duplicate firings
229                        $(window).off('keydown', handleKeypress).on('keydown', handleKeypress);
230                    },
231                    focusout: function() {
232                        $(window).off('keydown', handleKeypress);
233                    },
234                    mouseenter: function() {
235                        $dropdown.data('hover', true);
236                    },
237                    mouseleave: resetDropdown,
238                    mousemove:  hoverDropdownItem
239                });
240                if (oOptions.hoverIntent<0) {
241                    $(document).click(function(e) {
242                        if ($dropdown.data('hover') && !$dropdown[0].contains(e.target)) resetDropdown($dropdown[0]);
243                    });
244                }
245                // Put focus on menu when user clicks on label
246                if (sLabelId) $('#' + sLabelId).off('click', handleFocus).click(handleFocus);
247                // Done with everything!
248                $dropdown.parent().width(oOptions.width||nOuterWidth||$dropdown.outerWidth(true)).removeClass('loading');
249                oOptions.afterLoad();
250            },
251
252            // Manage widget focusing
253            handleFocus = function(e) {
254                $('ul[aria-labelledby=' + e.target.id + ']').focus();
255            },
256
257            // Manage keyboard navigation
258            handleKeypress = function(e) {
259                var $dropdown = $('.prettydropdown > ul.active, .prettydropdown > ul:focus');
260                if (!$dropdown.length) return;
261                if (e.which===9) { // Tab
262                    resetDropdown($dropdown[0]);
263                    return;
264                } else {
265                    // Intercept non-Tab keys only
266                    e.preventDefault();
267                    e.stopPropagation();
268                }
269                var $items = $dropdown.children(),
270                    bOpen = $dropdown.hasClass('active'),
271                    nItemsHeight = $dropdown.height()/(oOptions.height-2),
272                    nItemsPerPage = nItemsHeight%1<0.5 ? Math.floor(nItemsHeight) : Math.ceil(nItemsHeight),
273                    sKey;
274                nHoverIndex = Math.max(0, $dropdown.children('.hover').index());
275                nLastIndex = $items.length-1;
276                $current = $items.eq(nHoverIndex);
277                $dropdown.data('lastKeypress', +new Date());
278                switch (e.which) {
279                    case 13: // Enter
280                        if (!bOpen) {
281                            $current = $items.filter('.selected');
282                            toggleHover($current, 1);
283                        }
284                        $current.click();
285                        break;
286                    case 27: // Esc
287                        if (bOpen) resetDropdown($dropdown[0]);
288                        break;
289                    case 32: // Space
290                        if (bOpen) {
291                            sKey = ' ';
292                        } else {
293                            $current = $items.filter('.selected');
294                            toggleHover($current, 1);
295                            $current.click();
296                        }
297                        break;
298                    case 33: // Page Up
299                        if (bOpen) {
300                            toggleHover($current, 0);
301                            toggleHover($items.eq(Math.max(nHoverIndex-nItemsPerPage-1, 0)), 1);
302                        }
303                        break;
304                    case 34: // Page Down
305                        if (bOpen) {
306                            toggleHover($current, 0);
307                            toggleHover($items.eq(Math.min(nHoverIndex+nItemsPerPage-1, nLastIndex)), 1);
308                        }
309                        break;
310                    case 35: // End
311                        if (bOpen) {
312                            toggleHover($current, 0);
313                            toggleHover($items.eq(nLastIndex), 1);
314                        }
315                        break;
316                    case 36: // Home
317                        if (bOpen) {
318                            toggleHover($current, 0);
319                            toggleHover($items.eq(0), 1);
320                        }
321                        break;
322                    case 38: // Up
323                        if (bOpen) {
324                            toggleHover($current, 0);
325                            // If not already key-navigated or first item is selected, cycle to the last item; or
326                            // else select the previous item
327                            toggleHover(nHoverIndex ? $items.eq(nHoverIndex-1) : $items.eq(nLastIndex), 1);
328                        }
329                        break;
330                    case 40: // Down
331                        if (bOpen) {
332                            toggleHover($current, 0);
333                            // If last item is selected, cycle to the first item; or else select the next item
334                            toggleHover(nHoverIndex===nLastIndex ? $items.eq(0) : $items.eq(nHoverIndex+1), 1);
335                        }
336                        break;
337                    default:
338                        if (bOpen) sKey = aKeys[e.which-48];
339                }
340                if (sKey) { // Alphanumeric key pressed
341                    clearTimeout(nTimer);
342                    $dropdown.data('keysPressed', $dropdown.data('keysPressed')===undefined ? sKey : $dropdown.data('keysPressed') + sKey);
343                    nTimer = setTimeout(function() {
344                        $dropdown.removeData('keysPressed');
345                        // NOTE: Windows keyboard repeat delay is 250-1000 ms. See
346                        // https://technet.microsoft.com/en-us/library/cc978658.aspx
347                    }, 300);
348                    // Build index of matches
349                    var aMatches = [],
350                        nCurrentIndex = $current.index();
351                    $items.each(function(nIndex) {
352                        if ($(this).text().toLowerCase().indexOf($dropdown.data('keysPressed'))===0) aMatches.push(nIndex);
353                    });
354                    if (aMatches.length) {
355                        // Cycle through items matching key(s) pressed
356                        for (var i=0; i<aMatches.length; ++i) {
357                            if (aMatches[i]>nCurrentIndex) {
358                                toggleHover($items, 0);
359                                toggleHover($items.eq(aMatches[i]), 1);
360                                break;
361                            }
362                            if (i===aMatches.length-1) {
363                                toggleHover($items, 0);
364                                toggleHover($items.eq(aMatches[0]), 1);
365                            }
366                        }
367                    }
368                }
369            },
370
371            // Highlight menu item
372            hoverDropdownItem = function(e) {
373                var $dropdown = $(e.currentTarget);
374                if (e.target.nodeName!=='LI' || !$dropdown.hasClass('active') || new Date()-$dropdown.data('lastKeypress')<200) return;
375                toggleHover($dropdown.children(), 0, 1);
376                toggleHover($(e.target), 1, 1);
377            },
378
379            // Construct menu item
380            // elOpt is null for first item in multi-select menus
381            renderItem = function(elOpt, sClass, bSelected) {
382                var sGroup = '',
383                    sText = '',
384                    sTitle;
385                sClass = sClass || '';
386                if (elOpt) {
387                    switch (elOpt.nodeName) {
388                        case 'OPTION':
389                            if (elOpt.parentNode.nodeName==='OPTGROUP') sGroup = elOpt.parentNode.getAttribute('label');
390                            sText = (elOpt.getAttribute('data-prefix') || '') + elOpt.text + (elOpt.getAttribute('data-suffix') || '');
391                            break;
392                        case 'OPTGROUP':
393                            sClass += ' label';
394                            sText = (elOpt.getAttribute('data-prefix') || '') + elOpt.getAttribute('label') + (elOpt.getAttribute('data-suffix') || '');
395                            break;
396                    }
397                    if (elOpt.disabled || (sGroup && elOpt.parentNode.disabled)) sClass += ' disabled';
398                    sTitle = elOpt.title;
399                    if (sGroup && !sTitle) sTitle = elOpt.parentNode.title;
400                }
401                ++nCount;
402                return '<li id="item' + nTimestamp + '-' + nCount + '"'
403                    + (sGroup ? ' data-group="' + sGroup + '"' : '')
404                    + (elOpt && (elOpt.value||oOptions.classic) ? ' data-value="' + elOpt.value + '"' : '')
405                    + (elOpt && elOpt.nodeName==='OPTION' ? ' role="option"' : '')
406                    + (sTitle ? ' title="' + sTitle + '" aria-label="' + sTitle + '"' : '')
407                    + (sClass ? ' class="' + $.trim(sClass) + '"' : '')
408                    + ((oOptions.height!==50) ? ' style="height:' + (oOptions.height-2)
409                        + 'px;line-height:' + (oOptions.height-4) + 'px;"' : '') + '>' + sText
410                    + ((bSelected || sClass==='selected') ? oOptions.selectedMarker : '') + '</li>';
411            },
412
413            // Reset menu state
414            // @param o Event or Element object
415            resetDropdown = function(o) {
416                if (oOptions.hoverIntent<0 && o.type==='mouseleave') return;
417                var $dropdown = $(o.currentTarget||o);
418                $dropdown.data('hover', false);
419                clearTimeout(nTimer);
420                nTimer = setTimeout(function() {
421                    if ($dropdown.data('hover')) return;
422                    if ($dropdown.hasClass('reverse') && !oOptions.classic) $dropdown.prepend($dropdown.children(':last-child'));
423                    $dropdown.removeClass('active reverse').removeData('clicked').attr('aria-expanded', 'false').css('height', '');
424                    $dropdown.children().removeClass('hover nohover');
425                    // Update focus for NVDA screen readers
426                    $dropdown.attr('aria-activedescendant', $dropdown.children('.selected').attr('id'));
427                }, (o.type==='mouseleave' && !$dropdown.data('clicked')) ? oOptions.hoverIntent : 0);
428            },
429
430            // Set menu item hover state
431            // bNoScroll set on hoverDropdownItem()
432            toggleHover = function($li, bOn, bNoScroll) {
433                if (bOn) {
434                    var $dropdown = $li.parent();
435                    $li.removeClass('nohover').addClass('hover');
436                    // Update focus for NVDA screen readers
437                    $dropdown.attr('aria-activedescendant', $li.attr('id'));
438                    if ($li.length===1 && $current && !bNoScroll) {
439                        // Ensure items are always in view
440                        var nDropdownHeight = $dropdown.outerHeight(),
441                            nItemOffset = $li.offset().top-$dropdown.offset().top-1; // -1px for top border
442                        if ($li.index()===0) {
443                            $dropdown.scrollTop(0);
444                        } else if ($li.index()===nLastIndex) {
445                            $dropdown.scrollTop($dropdown.children().length*oOptions.height);
446                        } else {
447                            if (nItemOffset+oOptions.height>nDropdownHeight) $dropdown.scrollTop($dropdown.scrollTop()+oOptions.height+nItemOffset-nDropdownHeight);
448                            else if (nItemOffset<0) $dropdown.scrollTop($dropdown.scrollTop()+nItemOffset);
449                        }
450                    }
451                } else {
452                    $li.removeClass('hover').addClass('nohover');
453                }
454            },
455
456            // Update selected values for multi-select menu
457            updateSelected = function($dropdown) {
458                var $select = $dropdown.parent().children('select'),
459                    aSelected = $('option', $select).map(function() {
460                        if (this.selected) return this.text;
461                    }).get(),
462                    sSelected;
463                if (oOptions.multiVerbosity>=aSelected.length) sSelected = aSelected.join(oOptions.multiDelimiter) || MULTI_NONE;
464                else sSelected = aSelected.length + '/' + $('option', $select).length + MULTI_POSTFIX;
465                if (sSelected) {
466                    var sTitle = ($select.attr('title') ? $select.attr('title') : '') + (aSelected.length ? '\n' + MULTI_PREFIX + aSelected.join(oOptions.multiDelimiter) : '');
467                    $dropdown.children('.selected').text(sSelected);
468                    $dropdown.attr({
469                        'title': sTitle,
470                        'aria-label': sTitle
471                    });
472                } else {
473                    $dropdown.children('.selected').empty();
474                    $dropdown.attr({
475                        'title': $select.attr('title'),
476                        'aria-label': $select.attr('title')
477                    });
478                }
479            };
480
481        /**
482         * Public Functions
483         */
484
485        // Resync the menu with <select> to reflect state changes
486        this.refresh = function(oOptions) {
487            return this.each(function() {
488                var $select = $(this);
489                $select.prevAll('ul').remove();
490                $select.unwrap().data('loaded', false);
491                this.size = $select.data('size');
492                init(this);
493            });
494        };
495
496        return this.each(function() {
497            init(this);
498        });
499
500    };
501}(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.