/** * Dropdown component. * * @author Htmlstream * @version 1.0 */ ; (function ($) { 'use strict'; $.HSCore.components.HSDropdown = { /** * Base configuration of the component. * * @private */ _baseConfig: { dropdownEvent: 'click', dropdownType: 'simple', dropdownDuration: 300, dropdownEasing: 'linear', dropdownAnimationIn: 'fadeIn', dropdownAnimationOut: 'fadeOut', dropdownHideOnScroll: true, dropdownHideOnBlur: false, dropdownDelay: 350, afterOpen: function (invoker) { }, afterClose: function (invoker) { } }, /** * Collection of all initialized items on the page. * * @private */ _pageCollection: $(), /** * Initialization. * * @param {jQuery} collection * @param {Object} config * * @public * @return {jQuery} */ init: function (collection, config) { var self; if (!collection || !collection.length) return; self = this; var fieldsQty; collection.each(function (i, el) { var $this = $(el), itemConfig; if ($this.data('HSDropDown')) return; itemConfig = config && $.isPlainObject(config) ? $.extend(true, {}, self._baseConfig, config, $this.data()) : $.extend(true, {}, self._baseConfig, $this.data()); switch (itemConfig.dropdownType) { case 'css-animation' : $this.data('HSDropDown', new DropdownCSSAnimation($this, itemConfig)); break; case 'jquery-slide' : $this.data('HSDropDown', new DropdownJSlide($this, itemConfig)); break; default : $this.data('HSDropDown', new DropdownSimple($this, itemConfig)); } self._pageCollection = self._pageCollection.add($this); self._bindEvents($this, itemConfig.dropdownEvent, itemConfig.dropdownDelay); var DropDown = $(el).data('HSDropDown'); fieldsQty = $(DropDown.target).find('input, textarea').length; }); $(document).on('keyup.HSDropDown', function (e) { if (e.keyCode && e.keyCode == 27) { self._pageCollection.each(function (i, el) { var windW = $(window).width(), optIsMobileOnly = Boolean($(el).data('is-mobile-only')); if (!optIsMobileOnly) { $(el).data('HSDropDown').hide(); } else if (optIsMobileOnly && windW < 769) { $(el).data('HSDropDown').hide(); } }); } }); $(window).on('click', function (e) { self._pageCollection.each(function (i, el) { var windW = $(window).width(), optIsMobileOnly = Boolean($(el).data('is-mobile-only')); if (!optIsMobileOnly) { $(el).data('HSDropDown').hide(); } else if (optIsMobileOnly && windW < 769) { $(el).data('HSDropDown').hide(); } }); }); self._pageCollection.each(function (i, el) { var target = $(el).data('HSDropDown').config.dropdownTarget; $(target).on('click', function(e) { e.stopPropagation(); }); }); $(window).on('scroll.HSDropDown', function (e) { self._pageCollection.each(function (i, el) { var DropDown = $(el).data('HSDropDown'); if (DropDown.getOption('dropdownHideOnScroll') && fieldsQty === 0) { DropDown.hide(); } else if (DropDown.getOption('dropdownHideOnScroll') && !(/iPhone|iPad|iPod/i.test(navigator.userAgent))) { DropDown.hide(); } }); }); $(window).on('resize.HSDropDown', function (e) { if (self._resizeTimeOutId) clearTimeout(self._resizeTimeOutId); self._resizeTimeOutId = setTimeout(function () { self._pageCollection.each(function (i, el) { var DropDown = $(el).data('HSDropDown'); DropDown.smartPosition(DropDown.target); }); }, 50); }); return collection; }, /** * Binds necessary events. * * @param {jQuery} $invoker * @param {String} eventType * @param {Number} delay * @private */ _bindEvents: function ($invoker, eventType, delay) { var $dropdown = $($invoker.data('dropdown-target')); if (eventType == 'hover' && !_isTouch()) { $invoker.on('mouseenter.HSDropDown', function (e) { var $invoker = $(this), HSDropDown = $invoker.data('HSDropDown'); if (!HSDropDown) return; if (HSDropDown.dropdownTimeOut) clearTimeout(HSDropDown.dropdownTimeOut); HSDropDown.show(); }) .on('mouseleave.HSDropDown', function (e) { var $invoker = $(this), HSDropDown = $invoker.data('HSDropDown'); if (!HSDropDown) return; HSDropDown.dropdownTimeOut = setTimeout(function () { HSDropDown.hide(); }, delay); }); if ($dropdown.length) { $dropdown.on('mouseenter.HSDropDown', function (e) { var HSDropDown = $invoker.data('HSDropDown'); if (HSDropDown.dropdownTimeOut) clearTimeout(HSDropDown.dropdownTimeOut); HSDropDown.show(); }) .on('mouseleave.HSDropDown', function (e) { var HSDropDown = $invoker.data('HSDropDown'); HSDropDown.dropdownTimeOut = setTimeout(function () { HSDropDown.hide(); }, delay); }); } } else { $invoker.on('click.HSDropDown', function (e) { var $curInvoker = $(this); if (!$curInvoker.data('HSDropDown')) return; if ($('[data-dropdown-target].active').length) { $('[data-dropdown-target].active').data('HSDropDown').toggle(); } $curInvoker.data('HSDropDown').toggle(); e.stopPropagation(); e.preventDefault(); }); } } }; function _isTouch() { return 'ontouchstart' in window; } /** * Abstract Dropdown class. * * @param {jQuery} element * @param {Object} config * @abstract */ function AbstractDropdown(element, config) { if (!element.length) return false; this.element = element; this.config = config; this.target = $(this.element.data('dropdown-target')); this.allInvokers = $('[data-dropdown-target="' + this.element.data('dropdown-target') + '"]'); this.toggle = function () { if (!this.target.length) return this; if (this.defaultState) { this.show(); } else { this.hide(); } return this; }; this.smartPosition = function (target) { if (target.data('baseDirection')) { target.css( target.data('baseDirection').direction, target.data('baseDirection').value ); } target.removeClass('u-dropdown--reverse-y'); var $w = $(window), styles = getComputedStyle(target.get(0)), direction = Math.abs(parseInt(styles.left, 10)) < 40 ? 'left' : 'right', targetOuterGeometry = target.offset(); // horizontal axis if (direction == 'right') { if (!target.data('baseDirection')) target.data('baseDirection', { direction: 'right', value: parseInt(styles.right, 10) }); if (targetOuterGeometry.left < 0) { target.css( 'right', (parseInt(target.css('right'), 10) - (targetOuterGeometry.left - 10 )) * -1 ); } } else { if (!target.data('baseDirection')) target.data('baseDirection', { direction: 'left', value: parseInt(styles.left, 10) }); if (targetOuterGeometry.left + target.outerWidth() > $w.width()) { target.css( 'left', (parseInt(target.css('left'), 10) - (targetOuterGeometry.left + target.outerWidth() + 10 - $w.width())) ); } } // vertical axis if (targetOuterGeometry.top + target.outerHeight() - $w.scrollTop() > $w.height()) { target.addClass('u-dropdown--reverse-y'); } }; this.getOption = function (option) { return this.config[option] ? this.config[option] : null; }; return true; } /** * DropdownSimple constructor. * * @param {jQuery} element * @param {Object} config * @constructor */ function DropdownSimple(element, config) { if (!AbstractDropdown.call(this, element, config)) return; Object.defineProperty(this, 'defaultState', { get: function () { return this.target.hasClass('u-dropdown--hidden'); } }); this.target.addClass('u-dropdown--simple'); this.hide(); } /** * Shows dropdown. * * @public * @return {DropdownSimple} */ DropdownSimple.prototype.show = function () { var activeEls = $(this)[0].config.dropdownTarget; $('[data-dropdown-target="' + activeEls + '"]').addClass('active'); this.smartPosition(this.target); this.target.removeClass('u-dropdown--hidden'); if (this.allInvokers.length) this.allInvokers.attr('aria-expanded', 'true'); this.config.afterOpen.call(this.target, this.element); return this; } /** * Hides dropdown. * * @public * @return {DropdownSimple} */ DropdownSimple.prototype.hide = function () { var activeEls = $(this)[0].config.dropdownTarget; $('[data-dropdown-target="' + activeEls + '"]').removeClass('active'); this.target.addClass('u-dropdown--hidden'); if (this.allInvokers.length) this.allInvokers.attr('aria-expanded', 'false'); this.config.afterClose.call(this.target, this.element); return this; } /** * DropdownCSSAnimation constructor. * * @param {jQuery} element * @param {Object} config * @constructor */ function DropdownCSSAnimation(element, config) { if (!AbstractDropdown.call(this, element, config)) return; var self = this; this.target .addClass('u-dropdown--css-animation u-dropdown--hidden') .css('animation-duration', self.config.dropdownDuration + 'ms'); Object.defineProperty(this, 'defaultState', { get: function () { return this.target.hasClass('u-dropdown--hidden'); } }); if (this.target.length) { this.target.on('webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', function (e) { if (self.target.hasClass(self.config.dropdownAnimationOut)) { self.target.removeClass(self.config.dropdownAnimationOut) .addClass('u-dropdown--hidden'); if (self.allInvokers.length) self.allInvokers.attr('aria-expanded', 'false'); self.config.afterClose.call(self.target, self.element); } if (self.target.hasClass(self.config.dropdownAnimationIn)) { if (self.allInvokers.length) self.allInvokers.attr('aria-expanded', 'true'); self.config.afterOpen.call(self.target, self.element); } e.preventDefault(); e.stopPropagation(); }); } } /** * Shows dropdown. * * @public * @return {DropdownCSSAnimation} */ DropdownCSSAnimation.prototype.show = function () { var activeEls = $(this)[0].config.dropdownTarget; $('[data-dropdown-target="' + activeEls + '"]').addClass('active'); this.smartPosition(this.target); this.target.removeClass('u-dropdown--hidden') .removeClass(this.config.dropdownAnimationOut) .addClass(this.config.dropdownAnimationIn); } /** * Hides dropdown. * * @public * @return {DropdownCSSAnimation} */ DropdownCSSAnimation.prototype.hide = function () { var activeEls = $(this)[0].config.dropdownTarget; $('[data-dropdown-target="' + activeEls + '"]').removeClass('active'); this.target.removeClass(this.config.dropdownAnimationIn) .addClass(this.config.dropdownAnimationOut); } /** * DropdownSlide constructor. * * @param {jQuery} element * @param {Object} config * @constructor */ function DropdownJSlide(element, config) { if (!AbstractDropdown.call(this, element, config)) return; this.target.addClass('u-dropdown--jquery-slide u-dropdown--hidden').hide(); Object.defineProperty(this, 'defaultState', { get: function () { return this.target.hasClass('u-dropdown--hidden'); } }); } /** * Shows dropdown. * * @public * @return {DropdownJSlide} */ DropdownJSlide.prototype.show = function () { var self = this; var activeEls = $(this)[0].config.dropdownTarget; $('[data-dropdown-target="' + activeEls + '"]').addClass('active'); this.smartPosition(this.target); this.target.removeClass('u-dropdown--hidden').stop().slideDown({ duration: self.config.dropdownDuration, easing: self.config.dropdownEasing, complete: function () { self.config.afterOpen.call(self.target, self.element); } }); } /** * Hides dropdown. * * @public * @return {DropdownJSlide} */ DropdownJSlide.prototype.hide = function () { var self = this; var activeEls = $(this)[0].config.dropdownTarget; $('[data-dropdown-target="' + activeEls + '"]').removeClass('active'); this.target.stop().slideUp({ duration: self.config.dropdownDuration, easing: self.config.dropdownEasing, complete: function () { self.config.afterClose.call(self.target, self.element); self.target.addClass('u-dropdown--hidden'); } }); } })(jQuery);