/** * Unfold Content component. * * @author Htmlstream * @version 1.0 */ ; (function ($) { 'use strict'; $.HSCore.components.HSUnfold = { /** * Base configuration of the component. * * @private */ _baseConfig: { unfoldEvent: 'click', unfoldType: 'simple', unfoldDuration: 300, unfoldEasing: 'linear', unfoldAnimationIn: 'fadeIn', unfoldAnimationOut: 'fadeOut', unfoldHideOnScroll: true, unfoldHideOnBlur: false, unfoldDelay: 350, unfoldOpenedElement: 'init', unfoldOverlay: false, afterOpen: function (invoker) { }, beforeClose: 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('HSUnfold')) return; itemConfig = config && $.isPlainObject(config) ? $.extend(true, {}, self._baseConfig, config, $this.data()) : $.extend(true, {}, self._baseConfig, $this.data()); switch (itemConfig.unfoldType) { case 'css-animation' : $this.data('HSUnfold', new UnfoldCSSAnimation($this, itemConfig)); break; case 'jquery-slide' : $this.data('HSUnfold', new UnfoldJSlide($this, itemConfig)); break; default : $this.data('HSUnfold', new UnfoldSimple($this, itemConfig)); } self._pageCollection = self._pageCollection.add($this); self._bindEvents($this, itemConfig.unfoldEvent, itemConfig.unfoldDelay); var UnFold = $(el).data('HSUnfold'); fieldsQty = $(UnFold.target).find('input, textarea').length; if ($(UnFold.target).find('[data-unfold-target]').length) { $this.addClass('target-of-invoker-has-unfolds'); } }); $(document).on('click touchstart', 'body', function (e) { if (e.target.id === self._baseConfig.unfoldOpenedElement) return; if ($(e.target).closest('#' + self._baseConfig.unfoldOpenedElement).length) return; self._pageCollection.each(function (i, el) { var windW = window.innerWidth, optIsMobileOnly = Boolean($(el).data('is-mobile-only')); if (!optIsMobileOnly) { $(el).data('HSUnfold').hide(); } else if (optIsMobileOnly && windW < 769) { $(el).data('HSUnfold').hide(); } if ($(el).data('HSUnfold').config.unfoldOverlay) { $(el).data('HSUnfold').config.unfoldDelay = setTimeout(function () { $('.' + $(el).data('HSUnfold').config.unfoldOverlay.className).fadeOut($(el).data('HSUnfold').config.unfoldOverlay.animationSpeed ? $(el).data('HSUnfold').config.unfoldOverlay.animationSpeed : 200); }, $(el).data('HSUnfold').config.unfoldDelay) } $(el).data('HSUnfold').config.beforeClose.call(self.target, self.element); }); }); $(window).on('scroll.HSUnfold', function () { self._pageCollection.each(function (i, el) { var UnFold = $(el).data('HSUnfold'); if (UnFold.getOption('unfoldHideOnScroll') && fieldsQty === 0) { UnFold.hide(); } else if (UnFold.getOption('unfoldHideOnScroll') && !(/iPhone|iPad|iPod/i.test(navigator.userAgent))) { UnFold.hide(); } }); }); $(window).on('resize.HSUnfold', function () { if (self._resizeTimeOutId) clearTimeout(self._resizeTimeOutId); self._resizeTimeOutId = setTimeout(function () { self._pageCollection.each(function (i, el) { var UnFold = $(el).data('HSUnfold'); UnFold.smartPosition(UnFold.target); }); }, 50); }); $(document).on('keydown.HSUnfold', function (e) { if ($('body').hasClass('u-unfold-opened')) { if (e.keyCode && e.keyCode === 38 || e.keyCode && e.keyCode === 40) { e.preventDefault(); } if (e.keyCode && e.keyCode === 27) { self._pageCollection.each(function (i, el) { $(el).data('HSUnfold').hide(); }); $('body').removeClass('u-unfold-opened'); } } }); return collection; }, /** * Binds necessary events. * * @param {jQuery} $invoker * @param {String} eventType * @param {Number} delay * @private */ _bindEvents: function ($invoker, eventType, delay) { var self = this, $unfold = $($invoker.data('unfold-target')); if (eventType === 'hover' && !_isTouch()) { $invoker.on('mouseenter.HSUnfold', function () { var $invoker = $(this), HSUnfold = $invoker.data('HSUnfold'); if (!HSUnfold) return; if (HSUnfold.unfoldTimeOut) clearTimeout(HSUnfold.unfoldTimeOut); HSUnfold.show(); $('body').addClass('u-unfold-opened'); }) .on('mouseleave.HSUnfold', function () { var $invoker = $(this), HSUnfold = $invoker.data('HSUnfold'); if (!HSUnfold) return; HSUnfold.unfoldTimeOut = setTimeout(function () { HSUnfold.hide(); $('body').removeClass('u-unfold-opened'); }, delay); }); if ($unfold.length) { $unfold.on('mouseenter.HSUnfold', function () { var HSUnfold = $invoker.data('HSUnfold'); if (HSUnfold.unfoldTimeOut) clearTimeout(HSUnfold.unfoldTimeOut); HSUnfold.show(); }) .on('mouseleave.HSUnfold', function () { var HSUnfold = $invoker.data('HSUnfold'); HSUnfold.unfoldTimeOut = setTimeout(function () { HSUnfold.hide(); }, delay); }); } } else { $invoker.on('click.HSUnfold', function (e) { var $curInvoker = $(this), $unfoldNotHasInnerUnfolds = $('[data-unfold-target].active:not(.target-of-invoker-has-unfolds)'), $unfoldHasInnerUnfold = $('[data-unfold-target].active.target-of-invoker-has-unfolds'); self._baseConfig.unfoldOpenedElement = $curInvoker.data('HSUnfold').target[0].id; if (!$curInvoker.data('HSUnfold')) return; if (!$curInvoker.hasClass('target-of-invoker-has-unfolds')) { if ($unfoldNotHasInnerUnfolds.length) { $unfoldNotHasInnerUnfolds.data('HSUnfold').toggle(); } } else { if ($unfoldHasInnerUnfold.length) { $unfoldHasInnerUnfold.data('HSUnfold').toggle(); } } $curInvoker.data('HSUnfold').toggle(); $('body').toggleClass('u-unfold-opened'); e.stopPropagation(); e.preventDefault(); }); if (Boolean($invoker.data('unfold-target-is-menu'))) { var $target = $($invoker.data('unfold-target')), $targetItems = $target.children(); $targetItems.on('click', function () { $invoker.data('HSUnfold').toggle(); }); } } } }; function _isTouch() { return 'ontouchstart' in window; } /** * Abstract Unfold class. * * @param {jQuery} element * @param {Object} config * @abstract */ function AbstractUnfold(element, config) { var $self = this; if (!element.length) return false; $self.element = element; $self.config = config; $self.target = $($self.element.data('unfold-target')); $self.allInvokers = $('[data-unfold-target="' + $self.element.data('unfold-target') + '"]'); $self.toggle = function () { if (!$self.target.length) return $self; var dataset = $self.element[0].dataset, overlay = dataset.unfoldOverlay ? JSON.parse(dataset.unfoldOverlay) : false; if ($self.defaultState) { if (dataset.unfoldOverlay) { if (!$('.' + overlay.className).length) { $('
').prependTo('body'); } if ($('.' + overlay.className).length) { $('.' + overlay.className).fadeIn(overlay.animationSpeed ? overlay.animationSpeed : 200); } } $self.show(); } else { if ($('.' + overlay.className).length) { $self.config.unfoldTimeOut = setTimeout(function () { $('.' + overlay.className).fadeOut(overlay.animationSpeed ? overlay.animationSpeed : 200); }, $self.config.unfoldDelay) } $self.hide(); } return $self; }; this.smartPosition = function (target) { if (target.data('baseDirection')) { target.css( target.data('baseDirection').direction, target.data('baseDirection').value ); } target.removeClass('u-unfold--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-unfold--reverse-y'); } }; this.getOption = function (option) { return this.config[option] ? this.config[option] : null; }; return true; } /** * UnfoldSimple constructor. * * @param {jQuery} element * @param {Object} config * @constructor */ function UnfoldSimple(element, config) { if (!AbstractUnfold.call(this, element, config)) return; Object.defineProperty(this, 'defaultState', { get: function () { return this.target.hasClass('u-unfold--hidden'); } }); this.target.addClass('u-unfold--simple'); this.hide(); } /** * UnfoldCSSAnimation constructor. * * @param {jQuery} element * @param {Object} config * @constructor */ function UnfoldCSSAnimation(element, config) { if (!AbstractUnfold.call(this, element, config)) return; var self = this; this.target .addClass('u-unfold--css-animation u-unfold--hidden') .css('animation-duration', self.config.unfoldDuration + 'ms'); Object.defineProperty(this, 'defaultState', { get: function () { return this.target.hasClass('u-unfold--hidden'); } }); if (this.target.length) { this.target.on('webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', function (e) { if (self.target.hasClass(self.config.unfoldAnimationOut)) { self.target.removeClass(self.config.unfoldAnimationOut) .addClass('u-unfold--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.unfoldAnimationIn)) { if (self.allInvokers.length) self.allInvokers.attr('aria-expanded', 'true'); self.config.afterOpen.call(self.target, self.element); } e.preventDefault(); e.stopPropagation(); }); } } /** * UnfoldSlide constructor. * * @param {jQuery} element * @param {Object} config * @constructor */ function UnfoldJSlide(element, config) { if (!AbstractUnfold.call(this, element, config)) return; this.target.addClass('u-unfold--jquery-slide u-unfold--hidden').hide(); Object.defineProperty(this, 'defaultState', { get: function () { return this.target.hasClass('u-unfold--hidden'); } }); } /** * Shows Unfold. * * @public * @return {UnfoldSimple} */ UnfoldSimple.prototype.show = function () { var activeEls = $(this)[0].config.unfoldTarget; $('[data-unfold-target="' + activeEls + '"]').addClass('active'); this.smartPosition(this.target); this.target.removeClass('u-unfold--hidden'); if (this.allInvokers.length) this.allInvokers.attr('aria-expanded', 'true'); this.config.afterOpen.call(this.target, this.element); return this; }; /** * Hides Unfold. * * @public * @return {UnfoldSimple} */ UnfoldSimple.prototype.hide = function () { var activeEls = $(this)[0].config.unfoldTarget; $('[data-unfold-target="' + activeEls + '"]').removeClass('active'); this.target.addClass('u-unfold--hidden'); if (this.allInvokers.length) this.allInvokers.attr('aria-expanded', 'false'); this.config.afterClose.call(this.target, this.element); return this; }; /** * Shows Unfold. * * @public * @return {UnfoldCSSAnimation} */ UnfoldCSSAnimation.prototype.show = function () { var self = this, activeEls = $(self)[0].config.unfoldTarget; $('[data-unfold-target="' + activeEls + '"]').addClass('active'); self.smartPosition(self.target); self.target.removeClass('u-unfold--hidden') .removeClass(self.config.unfoldAnimationOut); if (!self.config.unfoldOverlay) { self.target.addClass(self.config.unfoldAnimationIn); } else { setTimeout(function () { self.target.addClass(self.config.unfoldAnimationIn); }); } self.config.afterOpen.call(self.target, self.element); }; /** * Hides Unfold. * * @public * @return {UnfoldCSSAnimation} */ UnfoldCSSAnimation.prototype.hide = function () { var activeEls = $(this)[0].config.unfoldTarget; $('[data-unfold-target="' + activeEls + '"]').removeClass('active'); this.target.removeClass(this.config.unfoldAnimationIn) .addClass(this.config.unfoldAnimationOut); }; /** * Shows Unfold. * * @public * @return {UnfoldJSlide} */ UnfoldJSlide.prototype.show = function () { var self = this; var activeEls = $(this)[0].config.unfoldTarget; $('[data-unfold-target="' + activeEls + '"]').addClass('active'); this.smartPosition(this.target); this.target.removeClass('u-unfold--hidden').stop().slideDown({ duration: self.config.unfoldDuration, easing: self.config.unfoldEasing, complete: function () { self.config.afterOpen.call(self.target, self.element); } }); }; /** * Hides Unfold. * * @public * @return {UnfoldJSlide} */ UnfoldJSlide.prototype.hide = function () { var self = this; var activeEls = $(this)[0].config.unfoldTarget; $('[data-unfold-target="' + activeEls + '"]').removeClass('active'); this.target.slideUp({ duration: self.config.unfoldDuration, easing: self.config.unfoldEasing, complete: function () { self.config.afterClose.call(self.target, self.element); self.target.addClass('u-unfold--hidden'); } }); } })(jQuery);