PageSourceSearch

https://www.organspende-info.de/_assets/d62b6e995e582269ee5a4e419a…jquery.prettydropdowns.forked.js?1790237602

js organspende-info.de collected 2026-10-02 03:01:59 UTC 21,136 bytes, 485 lines download raw bytes

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