PageSourceSearch

https://drx.com.tw/assets/js/components/hs.dropdown.js

js drx.com.tw collected 2026-10-08 20:02:58 UTC 13,829 bytes, 596 lines download raw bytes

1/**
2 * Dropdown component.
3 *
4 * @author Htmlstream
5 * @version 1.0
6 */
7;
8(function ($) {
9  'use strict';
10
11  $.HSCore.components.HSDropdown = {
12
13    /**
14     * Base configuration of the component.
15     *
16     * @private
17     */
18    _baseConfig: {
19      dropdownEvent: 'click',
20      dropdownType: 'simple',
21      dropdownDuration: 300,
22      dropdownEasing: 'linear',
23      dropdownAnimationIn: 'fadeIn',
24      dropdownAnimationOut: 'fadeOut',
25      dropdownHideOnScroll: true,
26      dropdownHideOnBlur: false,
27      dropdownDelay: 350,
28      afterOpen: function (invoker) {
29      },
30      afterClose: function (invoker) {
31      }
32    },
33
34    /**
35     * Collection of all initialized items on the page.
36     *
37     * @private
38     */
39    _pageCollection: $(),
40
41    /**
42     * Initialization.
43     *
44     * @param {jQuery} collection
45     * @param {Object} config
46     *
47     * @public
48     * @return {jQuery}
49     */
50    init: function (collection, config) {
51
52      var self;
53
54      if (!collection || !collection.length) return;
55
56      self = this;
57
58      var fieldsQty;
59
60      collection.each(function (i, el) {
61
62        var $this = $(el), itemConfig;
63
64        if ($this.data('HSDropDown')) return;
65
66        itemConfig = config && $.isPlainObject(config) ?
67          $.extend(true, {}, self._baseConfig, config, $this.data()) :
68          $.extend(true, {}, self._baseConfig, $this.data());
69
70        switch (itemConfig.dropdownType) {
71
72          case 'css-animation' :
73
74            $this.data('HSDropDown', new DropdownCSSAnimation($this, itemConfig));
75
76            break;
77
78          case 'jquery-slide' :
79
80            $this.data('HSDropDown', new DropdownJSlide($this, itemConfig));
81
82            break;
83
84          default :
85
86            $this.data('HSDropDown', new DropdownSimple($this, itemConfig));
87
88        }
89
90        self._pageCollection = self._pageCollection.add($this);
91        self._bindEvents($this, itemConfig.dropdownEvent, itemConfig.dropdownDelay);
92        var DropDown = $(el).data('HSDropDown');
93
94        fieldsQty = $(DropDown.target).find('input, textarea').length;
95
96      });
97
98      $(document).on('keyup.HSDropDown', function (e) {
99
100        if (e.keyCode && e.keyCode == 27) {
101
102          self._pageCollection.each(function (i, el) {
103
104            var windW = $(window).width(),
105              optIsMobileOnly = Boolean($(el).data('is-mobile-only'));
106
107            if (!optIsMobileOnly) {
108              $(el).data('HSDropDown').hide();
109            } else if (optIsMobileOnly && windW < 769) {
110              $(el).data('HSDropDown').hide();
111            }
112
113          });
114
115        }
116
117      });
118
119      $(window).on('click', function (e) {
120
121        self._pageCollection.each(function (i, el) {
122
123          var windW = $(window).width(),
124            optIsMobileOnly = Boolean($(el).data('is-mobile-only'));
125
126          if (!optIsMobileOnly) {
127            $(el).data('HSDropDown').hide();
128          } else if (optIsMobileOnly && windW < 769) {
129            $(el).data('HSDropDown').hide();
130          }
131
132        });
133
134      });
135
136      self._pageCollection.each(function (i, el) {
137
138        var target = $(el).data('HSDropDown').config.dropdownTarget;
139
140        $(target).on('click', function(e) {
141
142          e.stopPropagation();
143
144        });
145
146      });
147
148      $(window).on('scroll.HSDropDown', function (e) {
149
150        self._pageCollection.each(function (i, el) {
151
152          var DropDown = $(el).data('HSDropDown');
153
154          if (DropDown.getOption('dropdownHideOnScroll') && fieldsQty === 0) {
155
156            DropDown.hide();
157
158          } else if (DropDown.getOption('dropdownHideOnScroll') && !(/iPhone|iPad|iPod/i.test(navigator.userAgent))) {
159
160            DropDown.hide();
161
162          }
163
164        });
165
166      });
167
168      $(window).on('resize.HSDropDown', function (e) {
169
170        if (self._resizeTimeOutId) clearTimeout(self._resizeTimeOutId);
171
172        self._resizeTimeOutId = setTimeout(function () {
173
174          self._pageCollection.each(function (i, el) {
175
176            var DropDown = $(el).data('HSDropDown');
177
178            DropDown.smartPosition(DropDown.target);
179
180          });
181
182        }, 50);
183
184      });
185
186      return collection;
187
188    },
189
190    /**
191     * Binds necessary events.
192     *
193     * @param {jQuery} $invoker
194     * @param {String} eventType
195     * @param {Number} delay
196     * @private
197     */
198    _bindEvents: function ($invoker, eventType, delay) {
199
200      var $dropdown = $($invoker.data('dropdown-target'));
201
202      if (eventType == 'hover' && !_isTouch()) {
203
204        $invoker.on('mouseenter.HSDropDown', function (e) {
205
206          var $invoker = $(this),
207            HSDropDown = $invoker.data('HSDropDown');
208
209          if (!HSDropDown) return;
210
211          if (HSDropDown.dropdownTimeOut) clearTimeout(HSDropDown.dropdownTimeOut);
212          HSDropDown.show();
213
214        })
215          .on('mouseleave.HSDropDown', function (e) {
216
217            var $invoker = $(this),
218              HSDropDown = $invoker.data('HSDropDown');
219
220            if (!HSDropDown) return;
221
222            HSDropDown.dropdownTimeOut = setTimeout(function () {
223
224              HSDropDown.hide();
225
226            }, delay);
227
228          });
229
230        if ($dropdown.length) {
231
232          $dropdown.on('mouseenter.HSDropDown', function (e) {
233
234            var HSDropDown = $invoker.data('HSDropDown');
235
236            if (HSDropDown.dropdownTimeOut) clearTimeout(HSDropDown.dropdownTimeOut);
237            HSDropDown.show();
238
239          })
240            .on('mouseleave.HSDropDown', function (e) {
241
242              var HSDropDown = $invoker.data('HSDropDown');
243
244              HSDropDown.dropdownTimeOut = setTimeout(function () {
245                HSDropDown.hide();
246              }, delay);
247
248            });
249        }
250
251      }
252      else {
253
254        $invoker.on('click.HSDropDown', function (e) {
255
256          var $curInvoker = $(this);
257
258          if (!$curInvoker.data('HSDropDown')) return;
259
260          if ($('[data-dropdown-target].active').length) {
261            $('[data-dropdown-target].active').data('HSDropDown').toggle();
262          }
263
264          $curInvoker.data('HSDropDown').toggle();
265
266          e.stopPropagation();
267          e.preventDefault();
268
269        });
270
271      }
272
273    }
274  };
275
276  function _isTouch() {
277    return 'ontouchstart' in window;
278  }
279
280  /**
281   * Abstract Dropdown class.
282   *
283   * @param {jQuery} element
284   * @param {Object} config
285   * @abstract
286   */
287  function AbstractDropdown(element, config) {
288
289    if (!element.length) return false;
290
291    this.element = element;
292    this.config = config;
293
294    this.target = $(this.element.data('dropdown-target'));
295
296    this.allInvokers = $('[data-dropdown-target="' + this.element.data('dropdown-target') + '"]');
297
298    this.toggle = function () {
299      if (!this.target.length) return this;
300
301      if (this.defaultState) {
302        this.show();
303      }
304      else {
305        this.hide();
306      }
307
308      return this;
309    };
310
311    this.smartPosition = function (target) {
312
313      if (target.data('baseDirection')) {
314        target.css(
315          target.data('baseDirection').direction,
316          target.data('baseDirection').value
317        );
318      }
319
320      target.removeClass('u-dropdown--reverse-y');
321
322      var $w = $(window),
323        styles = getComputedStyle(target.get(0)),
324        direction = Math.abs(parseInt(styles.left, 10)) < 40 ? 'left' : 'right',
325        targetOuterGeometry = target.offset();
326
327      // horizontal axis
328      if (direction == 'right') {
329
330        if (!target.data('baseDirection')) target.data('baseDirection', {
331          direction: 'right',
332          value: parseInt(styles.right, 10)
333        });
334
335        if (targetOuterGeometry.left < 0) {
336
337          target.css(
338            'right',
339            (parseInt(target.css('right'), 10) - (targetOuterGeometry.left - 10 )) * -1
340          );
341
342        }
343
344      }
345      else {
346
347        if (!target.data('baseDirection')) target.data('baseDirection', {
348          direction: 'left',
349          value: parseInt(styles.left, 10)
350        });
351
352        if (targetOuterGeometry.left + target.outerWidth() > $w.width()) {
353
354          target.css(
355            'left',
356            (parseInt(target.css('left'), 10) - (targetOuterGeometry.left + target.outerWidth() + 10 - $w.width()))
357          );
358
359        }
360
361      }
362
363      // vertical axis
364      if (targetOuterGeometry.top + target.outerHeight() - $w.scrollTop() > $w.height()) {
365        target.addClass('u-dropdown--reverse-y');
366      }
367
368    };
369
370    this.getOption = function (option) {
371      return this.config[option] ? this.config[option] : null;
372    };
373
374    return true;
375  }
376
377
378  /**
379   * DropdownSimple constructor.
380   *
381   * @param {jQuery} element
382   * @param {Object} config
383   * @constructor
384   */
385  function DropdownSimple(element, config) {
386    if (!AbstractDropdown.call(this, element, config)) return;
387
388    Object.defineProperty(this, 'defaultState', {
389      get: function () {
390        return this.target.hasClass('u-dropdown--hidden');
391      }
392    });
393
394    this.target.addClass('u-dropdown--simple');
395
396    this.hide();
397  }
398
399  /**
400   * Shows dropdown.
401   *
402   * @public
403   * @return {DropdownSimple}
404   */
405  DropdownSimple.prototype.show = function () {
406
407    var activeEls = $(this)[0].config.dropdownTarget;
408
409    $('[data-dropdown-target="' + activeEls + '"]').addClass('active');
410
411    this.smartPosition(this.target);
412
413    this.target.removeClass('u-dropdown--hidden');
414    if (this.allInvokers.length) this.allInvokers.attr('aria-expanded', 'true');
415    this.config.afterOpen.call(this.target, this.element);
416
417    return this;
418  }
419
420  /**
421   * Hides dropdown.
422   *
423   * @public
424   * @return {DropdownSimple}
425   */
426  DropdownSimple.prototype.hide = function () {
427
428    var activeEls = $(this)[0].config.dropdownTarget;
429
430    $('[data-dropdown-target="' + activeEls + '"]').removeClass('active');
431
432    this.target.addClass('u-dropdown--hidden');
433    if (this.allInvokers.length) this.allInvokers.attr('aria-expanded', 'false');
434    this.config.afterClose.call(this.target, this.element);
435
436    return this;
437  }
438
439  /**
440   * DropdownCSSAnimation constructor.
441   *
442   * @param {jQuery} element
443   * @param {Object} config
444   * @constructor
445   */
446  function DropdownCSSAnimation(element, config) {
447    if (!AbstractDropdown.call(this, element, config)) return;
448
449    var self = this;
450
451    this.target
452      .addClass('u-dropdown--css-animation u-dropdown--hidden')
453      .css('animation-duration', self.config.dropdownDuration + 'ms');
454
455    Object.defineProperty(this, 'defaultState', {
456      get: function () {
457        return this.target.hasClass('u-dropdown--hidden');
458      }
459    });
460
461    if (this.target.length) {
462
463      this.target.on('webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', function (e) {
464
465        if (self.target.hasClass(self.config.dropdownAnimationOut)) {
466          self.target.removeClass(self.config.dropdownAnimationOut)
467            .addClass('u-dropdown--hidden');
468
469
470          if (self.allInvokers.length) self.allInvokers.attr('aria-expanded', 'false');
471
472          self.config.afterClose.call(self.target, self.element);
473        }
474
475        if (self.target.hasClass(self.config.dropdownAnimationIn)) {
476
477          if (self.allInvokers.length) self.allInvokers.attr('aria-expanded', 'true');
478
479          self.config.afterOpen.call(self.target, self.element);
480        }
481
482        e.preventDefault();
483        e.stopPropagation();
484      });
485
486    }
487  }
488
489  /**
490   * Shows dropdown.
491   *
492   * @public
493   * @return {DropdownCSSAnimation}
494   */
495  DropdownCSSAnimation.prototype.show = function () {
496
497    var activeEls = $(this)[0].config.dropdownTarget;
498
499    $('[data-dropdown-target="' + activeEls + '"]').addClass('active');
500
501    this.smartPosition(this.target);
502
503    this.target.removeClass('u-dropdown--hidden')
504      .removeClass(this.config.dropdownAnimationOut)
505      .addClass(this.config.dropdownAnimationIn);
506
507  }
508
509  /**
510   * Hides dropdown.
511   *
512   * @public
513   * @return {DropdownCSSAnimation}
514   */
515  DropdownCSSAnimation.prototype.hide = function () {
516
517    var activeEls = $(this)[0].config.dropdownTarget;
518
519    $('[data-dropdown-target="' + activeEls + '"]').removeClass('active');
520
521    this.target.removeClass(this.config.dropdownAnimationIn)
522      .addClass(this.config.dropdownAnimationOut);
523
524  }
525
526  /**
527   * DropdownSlide constructor.
528   *
529   * @param {jQuery} element
530   * @param {Object} config
531   * @constructor
532   */
533  function DropdownJSlide(element, config) {
534    if (!AbstractDropdown.call(this, element, config)) return;
535
536    this.target.addClass('u-dropdown--jquery-slide u-dropdown--hidden').hide();
537
538    Object.defineProperty(this, 'defaultState', {
539      get: function () {
540        return this.target.hasClass('u-dropdown--hidden');
541      }
542    });
543  }
544
545  /**
546   * Shows dropdown.
547   *
548   * @public
549   * @return {DropdownJSlide}
550   */
551  DropdownJSlide.prototype.show = function () {
552
553    var self = this;
554
555    var activeEls = $(this)[0].config.dropdownTarget;
556
557    $('[data-dropdown-target="' + activeEls + '"]').addClass('active');
558
559    this.smartPosition(this.target);
560
561    this.target.removeClass('u-dropdown--hidden').stop().slideDown({
562      duration: self.config.dropdownDuration,
563      easing: self.config.dropdownEasing,
564      complete: function () {
565        self.config.afterOpen.call(self.target, self.element);
566      }
567    });
568
569  }
570
571  /**
572   * Hides dropdown.
573   *
574   * @public
575   * @return {DropdownJSlide}
576   */
577  DropdownJSlide.prototype.hide = function () {
578
579    var self = this;
580
581    var activeEls = $(this)[0].config.dropdownTarget;
582
583    $('[data-dropdown-target="' + activeEls + '"]').removeClass('active');
584
585    this.target.stop().slideUp({
586      duration: self.config.dropdownDuration,
587      easing: self.config.dropdownEasing,
588      complete: function () {
589        self.config.afterClose.call(self.target, self.element);
590        self.target.addClass('u-dropdown--hidden');
591      }
592    });
593
594  }
595
596})(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.