/**
* 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; 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 ); }); $(document).on('keyup.HSDropDown', function(e){ if(e.keyCode && e.keyCode == 27) { self._pageCollection.each( function(i, el){ $(el).data('HSDropDown').hide(); } ); } }); $(window).on('scroll.HSDropDown', function(e){ self._pageCollection.each(function(i, el){ var DropDown = $(el).data('HSDropDown'); if(DropDown.getOption('dropdownHideOnScroll')) { 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 $invoker = $(this); if( !$invoker.data('HSDropDown') ) return; $invoker.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() { 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() { 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() { 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() { 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; 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; 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);