/*! * Accordion Slider - v2.9 * Homepage: http://bqworks.net/accordion-slider/ * Author: bqworks * Author URL: http://bqworks.net/ */ ;(function(window, $) { "use strict"; /* Static methods for Accordion Slider */ $.AccordionSlider = { modules: {}, addModule: function(name, module, target) { if (typeof this.modules[target] === 'undefined') this.modules[target] = []; this.modules[target].push(name); if (target === 'accordion') $.extend(AccordionSlider.prototype, module); else if (target === 'panel') $.extend(AccordionSliderPanel.prototype, module); } }; // namespace var NS = $.AccordionSlider.namespace = 'AccordionSlider'; var AccordionSlider = function(instance, options) { // reference to the accordion jQuery object this.$accordion = $(instance); // reference to the container of the panels this.$panelsContainer = null; // reference to the container that will mask the panels this.$maskContainer = null; // holds the options specified when the accordion was instantiated this.options = options; // holds the final settings of the accordion this.settings = {}; // keep a separate reference of the settings which will not be altered by breakpoints or by other means this.originalSettings = {}; // the index of the currently opened panel (starts with 0) this.currentIndex = -1; // the index of the current page this.currentPage = 0; // the size, in pixels, of the accordion this.totalSize = 0; // the size of the panels' container this.totalPanelsSize = 0; // the computed size, in pixels, of the opened panel this.computedOpenedPanelSize = 0; // the computed maximum allowed size, in pixels, of the opened panel this.maxComputedOpenedPanelSize = 0; // the size, in pixels, of the collapsed panels this.collapsedPanelSize = 0; // the size, in pixels, of the closed panels this.closedPanelSize = 0; // the distance, in pixels, between the accordion's panels this.computedPanelDistance = 0; // array that contains the AccordionSliderPanel objects this.panels = []; // timer used for delaying the opening of the panel on mouse hover this.mouseDelayTimer = 0; // simple objects to be used for animation this.openPanelAnimation = {}; this.closePanelsAnimation = {}; // generate a unique ID to be used for event listening this.uniqueId = new Date().valueOf(); // stores size breakpoints in an array for sorting purposes this.breakpoints = []; // indicates the current size breakpoint this.currentBreakpoint = -1; // keeps a reference to the previous number of visible panels this.previousVisiblePanels = -1; // indicates whether the accordion is currently scrolling this.isPageScrolling = false; // indicates the left or top property based on the orientation of the accordion this.positionProperty = 'left'; // indicates the width or height property based on the orientation of the accordion this.sizeProperty = 'width'; // keeps a reference to the ratio between the size actual size of the accordion and the set size this.autoResponsiveRatio = 1; // indicates whether the panels will overlap, based on the set panelOverlap property // and also based on the computed distance between panels this.isOverlapping = false; // initialize the accordion this._init(); }; AccordionSlider.prototype = { /* The starting place for the accordion */ _init: function() { var that = this; this.$accordion.removeClass('as-no-js'); // get reference to the panels' container and // create additional mask container, which will mask the panels' container this.$maskContainer = $('
').appendTo(this.$accordion); this.$panelsContainer = this.$accordion.find('.as-panels').appendTo(this.$maskContainer); // create the 'as-panels' element if it wasn't created manually if (this.$panelsContainer.length === 0) this.$panelsContainer = $('
').appendTo(this.$maskContainer); // initialize accordion modules var modules = $.AccordionSlider.modules.accordion; // Merge the modules' default settings with the core's default settings if (typeof modules !== 'undefined') { for (var i = 0; i < modules.length; i++) { var defaults = modules[i] + 'Defaults'; if ( typeof this[defaults] !== 'undefined') { $.extend(this.defaults, this[defaults]); } else { defaults = modules[i].substring(0, 1).toLowerCase() + modules[i].substring(1) + 'Defaults'; if (typeof this[defaults] !== 'undefined') { $.extend(this.defaults, this[defaults]); } } } } // Merge the user defined settings with the default settings this.settings = $.extend({}, this.defaults, this.options); // Initialize the modules if (typeof modules !== 'undefined') { for (var j = 0; j < modules.length; j++) { if (typeof this['init' + modules[j]] !== 'undefined') { this['init' + modules[j]](); } } } // keep a reference of the original settings and use it // to restore the settings when the breakpoints are used this.originalSettings = $.extend({}, this.settings); if (this.settings.shuffle === true) { var shuffledPanels = this.$panelsContainer.find('.as-panel').sort(function() { return 0.5 - Math.random(); }); this.$panelsContainer.empty().append(shuffledPanels); } // set a panel to be opened from the start this.currentIndex = this.settings.startPanel; if (this.currentIndex === -1) this.$accordion.addClass('as-closed'); else this.$accordion.addClass('as-opened'); // if a panels was not set to be opened but a page was specified, // set that page index to be opened if (this.settings.startPage !== -1) this.currentPage = this.settings.startPage; // parse the breakpoints object and store the values into an array // sorting them in ascending order based on the specified size if (this.settings.breakpoints !== null) { for (var sizes in this.settings.breakpoints) { this.breakpoints.push({size: parseInt(sizes, 10), properties:this.settings.breakpoints[sizes]}); } this.breakpoints = this.breakpoints.sort(function(a, b) { return a.size >= b.size ? 1: -1; }); } // prepare request animation frame this._prepareRAF(); // update the accordion this.update(); // if there is a panel opened at start handle that panel as if it was manually opened if (this.currentIndex !== -1) { this.$accordion.find('.as-panel').eq(this.currentIndex).addClass('as-opened'); // fire 'panelOpen' event var eventObject = {type: 'panelOpen', index: this.currentIndex, previousIndex: -1}; this.trigger(eventObject); if ($.isFunction(this.settings.panelOpen)) this.settings.panelOpen.call(this, eventObject); } // listen for 'mouseenter' events this.on('mouseenter.' + NS, function(event) { var eventObject = {type: 'accordionMouseOver'}; that.trigger(eventObject); if ($.isFunction(that.settings.accordionMouseOver)) that.settings.accordionMouseOver.call(that, eventObject); }); // listen for 'mouseleave' events this.on('mouseleave.' + NS, function(event) { clearTimeout(that.mouseDelayTimer); // close the panels if (that.settings.closePanelsOnMouseOut === true) that.closePanels(); var eventObject = {type: 'accordionMouseOut'}; that.trigger(eventObject); if ($.isFunction(that.settings.accordionMouseOut)) that.settings.accordionMouseOut.call(that, eventObject); }); // resize the accordion when the browser resizes $(window).on('resize.' + this.uniqueId + '.' + NS, function() { that.resize(); }); // fire the 'init' event this.trigger({type: 'init'}); if ($.isFunction(this.settings.init)) this.settings.init.call(this, {type: 'init'}); }, /* Update the accordion after a property was changed or panels were added/removed */ update: function() { var that = this; // add a class to the accordion based on the orientation // to be used in CSS if (this.settings.orientation === 'horizontal') { this.$accordion.removeClass('as-vertical').addClass('as-horizontal'); this.positionProperty = 'left'; this.sizeProperty = 'width'; } else if (this.settings.orientation === 'vertical') { this.$accordion.removeClass('as-horizontal').addClass('as-vertical'); this.positionProperty = 'top'; this.sizeProperty = 'height'; } // if the number of visible panels has change, update the current page to reflect // the same relative position of the panels if (this.settings.visiblePanels === -1) { this.currentPage = 0; } else if (this.currentIndex !== -1) { this.currentPage = Math.floor(this.currentIndex / this.settings.visiblePanels); } else if (this.settings.visiblePanels !== this.previousVisiblePanels && this.previousVisiblePanels !== -1) { var correctPage = Math.round((this.currentPage * this.previousVisiblePanels) / this.settings.visiblePanels); if (this.currentPage !== correctPage) this.currentPage = correctPage; } // if there is distance between the panels, the panels can't overlap if (this.settings.panelDistance > 0 || this.settings.panelOverlap === false) { this.isOverlapping = false; this.$accordion.removeClass('as-overlap'); } else if (this.settings.panelOverlap === true) { this.isOverlapping = true; this.$accordion.addClass('as-overlap'); } // clear inline size of the background images because the orientation might have changes this.$accordion.find('img.as-background, img.as-background-opened').css({'width': '', 'height': ''}); // update panels this._updatePanels(); // create or update the pagination buttons this._updatePaginationButtons(); // create or remove the shadow if (this.settings.shadow === true) { this.$accordion.find('.as-panel').addClass('as-shadow'); } else if (this.settings.shadow === false) { this.$accordion.find('.as-shadow').removeClass('as-shadow'); } // reset the panels' container position this.$panelsContainer.attr('style', ''); // set the size of the accordion this.resize(); // fire the update event var eventObject = {type: 'update'}; that.trigger(eventObject); if ($.isFunction(that.settings.update)) that.settings.update.call(that, eventObject); }, /* Create, remove or update panels based on the HTML specified in the accordion */ _updatePanels: function() { var that = this; // check if there are removed items in the DOM and remove the from the array of panels for (var i = this.panels.length - 1; i >= 0; i--) { if (this.$accordion.find('.as-panel[data-index="' + i + '"]').length === 0) { var panel = this.panels[i]; panel.off('panelMouseOver.' + NS); panel.off('panelMouseOut.' + NS); panel.off('panelClick.' + NS); panel.off('imagesComplete.' + NS); panel.destroy(); this.panels.splice(i, 1); } } // parse the DOM and create uninstantiated panels and reset the indexes this.$accordion.find('.as-panel').each(function(index, element) { var panel = $(element); if (typeof panel.attr('data-init') === 'undefined') { that._createPanel(index, panel); } else { that.panels[index].setIndex(index); that.panels[index].update(); } }); }, /* Create an individual panel */ _createPanel: function(index, element) { var that = this, $element = $(element); // create a panel instance and add it to the array of panels var panel = new AccordionSliderPanel($element, this, index); this.panels.splice(index, 0, panel); // listen for 'panelMouseOver' events panel.on('panelMouseOver.' + NS, function(event) { if (that.isPageScrolling === true) return; if (that.settings.openPanelOn === 'hover') { clearTimeout(that.mouseDelayTimer); // open the panel, but only after a short delay in order to prevent // opening panels that the user doesn't intend that.mouseDelayTimer = setTimeout(function() { that.openPanel(event.index); }, that.settings.mouseDelay); } var eventObject = {type: 'panelMouseOver', index: index}; that.trigger(eventObject); if ($.isFunction(that.settings.panelMouseOver)) that.settings.panelMouseOver.call(that, eventObject); }); // listen for 'panelMouseOut' events panel.on('panelMouseOut.' + NS, function(event) { if (that.isPageScrolling === true) return; var eventObject = {type: 'panelMouseOut', index: index}; that.trigger(eventObject); if ($.isFunction(that.settings.panelMouseOut)) that.settings.panelMouseOut.call(that, eventObject); }); // listen for 'panelClick' events panel.on('panelClick.' + NS, function(event) { if (that.$accordion.hasClass('as-swiping')) return; if (that.settings.openPanelOn === 'click') { // open the panel if it's not already opened // and close the panels if the clicked panel is opened if (index !== that.currentIndex) { that.openPanel(event.index); } else { that.closePanels(); } } var eventObject = {type: 'panelClick', index: index}; that.trigger(eventObject); if ($.isFunction(that.settings.panelClick)) that.settings.panelClick.call(that, eventObject); }); // disable links if the panel should open on click and it wasn't opened yet panel.on('panelMouseDown.' + NS, function(event) { $(this).find('a').off('click.disablePanelLink'); if (index !== that.currentIndex && that.settings.openPanelOn === 'click') { $(this).find('a').one('click.disablePanelLink', function(event) { event.preventDefault(); }); } }); // listen for 'imagesComplete' events and if the images were loaded in // the panel that is currently opened and the size of the panel is different // than the currently computed size of the panel, force the re-opening of the panel // to the correct size panel.on('imagesComplete.' + NS, function(event) { if (event.index === that.currentIndex && event.contentSize !== that.computedOpenedPanelSize) { that.openPanel(event.index, true); } }); }, /* Removes all panels */ removePanels: function() { $.each(this.panels, function(index, element) { element.off('panelMouseOver.' + NS); element.off('panelMouseOut.' + NS); element.off('panelClick.' + NS); element.off('imagesComplete.' + NS); element.destroy(); }); this.panels.length = 0; }, /* Called when the accordion needs to resize */ resize: function() { var that = this; this.$maskContainer.attr('style', ''); // prepare the accordion for responsiveness if (this.settings.responsive === true) { // if the accordion is responsive set the width to 100% and use // the specified width and height as a max-width and max-height this.$accordion.css({width: '100%', height: this.settings.height, maxWidth: this.settings.width, maxHeight: this.settings.height}); // if an aspect ratio was not specified, set the aspect ratio // based on the specified width and height if (this.settings.aspectRatio === -1) this.settings.aspectRatio = this.settings.width / this.settings.height; this.$accordion.css('height', this.$accordion.innerWidth() / this.settings.aspectRatio); if (this.settings.responsiveMode === 'auto') { // get the accordion's size ratio based on the set size and the actual size this.autoResponsiveRatio = this.$accordion.innerWidth() / this.settings.width; this.$maskContainer.css('width', this.settings.width); if ( isNaN( this.settings.height ) ) this.$maskContainer.css('height', Math.min(this.settings.width / this.settings.aspectRatio, parseInt(this.settings.height, 10) / 100 * $(window).height())); else this.$maskContainer.css('height', Math.min(this.settings.width / this.settings.aspectRatio, this.settings.height)); // scale the mask container based on the current ratio if ( this.autoResponsiveRatio < 1 ) { this.$maskContainer.css({ '-webkit-transform': 'scaleX(' + this.autoResponsiveRatio + ') scaleY(' + this.autoResponsiveRatio + ')', '-ms-transform': 'scaleX(' + this.autoResponsiveRatio + ') scaleY(' + this.autoResponsiveRatio + ')', 'transform': 'scaleX(' + this.autoResponsiveRatio + ') scaleY(' + this.autoResponsiveRatio + ')', '-webkit-transform-origin': 'top left', '-ms-transform-origin': 'top left', 'transform-origin': 'top left' }); } else { this.$maskContainer.css({ '-webkit-transform': '', '-ms-transform': '', 'transform': '', '-webkit-transform-origin': '', '-ms-transform-origin': '', 'transform-origin': '' }); } this.totalSize = this.settings.orientation === "horizontal" ? this.$maskContainer.innerWidth() : this.$maskContainer.innerHeight(); } else { this.totalSize = this.settings.orientation === "horizontal" ? this.$accordion.innerWidth() : this.$accordion.innerHeight(); } } else { this.$accordion.css({width: this.settings.width, height: this.settings.height, maxWidth: '', maxHeight: ''}); this.totalSize = this.settings.orientation === "horizontal" ? this.$accordion.innerWidth() : this.$accordion.innerHeight(); } // set the size of the background images explicitly because of a bug? // that causes anchors not to adapt their size to the size of the image, // when the image size is set in percentages, which causes the total size // of the panel to be bigger than it should if (this.settings.orientation === 'horizontal') this.$accordion.find('img.as-background, img.as-background-opened').css('height', this.$panelsContainer.innerHeight()); else this.$accordion.find('img.as-background, img.as-background-opened').css('width', this.$panelsContainer.innerWidth()); // set the initial computedPanelDistance to the value defined in the options this.computedPanelDistance = this.settings.panelDistance; // parse computedPanelDistance and set it to a pixel value if (typeof this.computedPanelDistance === 'string') { if (this.computedPanelDistance.indexOf('%') !== -1) { this.computedPanelDistance = this.totalSize * (parseInt(this.computedPanelDistance, 10)/ 100); } else if (this.computedPanelDistance.indexOf('px') !== -1) { this.computedPanelDistance = parseInt(this.computedPanelDistance, 10); } } // set the size, in pixels, of the closed panels this.closedPanelSize = (this.totalSize - (this.getVisiblePanels() - 1) * this.computedPanelDistance) / this.getVisiblePanels(); // round the value this.closedPanelSize = Math.floor(this.closedPanelSize); // set the initial computedOpenedPanelSize to the value defined in the options this.computedOpenedPanelSize = this.settings.openedPanelSize; // if the panels are set to open to their maximum size, // parse maxComputedOpenedPanelSize and set it to a pixel value if (this.settings.openedPanelSize === 'max') { // set the initial maxComputedOpenedPanelSize to the value defined in the options this.maxComputedOpenedPanelSize = this.settings.maxOpenedPanelSize; if (typeof this.maxComputedOpenedPanelSize === 'string') { if (this.maxComputedOpenedPanelSize.indexOf('%') !== -1) { this.maxComputedOpenedPanelSize = this.totalSize * (parseInt(this.maxComputedOpenedPanelSize, 10)/ 100); } else if (this.maxComputedOpenedPanelSize.indexOf('px') !== -1) { this.maxComputedOpenedPanelSize = parseInt(this.maxComputedOpenedPanelSize, 10); } } } // parse computedOpenedPanelSize and set it to a pixel value if (typeof this.computedOpenedPanelSize === 'string') { if (this.computedOpenedPanelSize.indexOf('%') !== -1) { this.computedOpenedPanelSize = this.totalSize * (parseInt(this.computedOpenedPanelSize, 10)/ 100); } else if (this.computedOpenedPanelSize.indexOf('px') !== -1) { this.computedOpenedPanelSize = parseInt(this.computedOpenedPanelSize, 10); } else if (this.computedOpenedPanelSize === 'max' && this.currentIndex !== -1) { var contentSize = this.getPanelAt(this.currentIndex).getContentSize(); this.computedOpenedPanelSize = contentSize === 'loading' ? this.closedPanelSize : Math.min(contentSize, this.maxComputedOpenedPanelSize); } } // set the size, in pixels, of the collapsed panels this.collapsedPanelSize = (this.totalSize - this.computedOpenedPanelSize - (this.getVisiblePanels() - 1) * this.computedPanelDistance) / (this.getVisiblePanels() - 1); // round the values this.computedOpenedPanelSize = Math.floor(this.computedOpenedPanelSize); this.collapsedPanelSize = Math.floor(this.collapsedPanelSize); // get the total size of the panels' container this.totalPanelsSize = this.closedPanelSize * this.getTotalPanels() + this.computedPanelDistance * (this.getTotalPanels() - 1); this.$panelsContainer.css(this.sizeProperty, this.totalPanelsSize); // recalculate the totalSize due to the fact that rounded sizes can cause incorrect positioning // since the actual size of all panels from a page might be smaller than the whole width of the accordion this.totalSize = this.closedPanelSize * this.getVisiblePanels() + this.computedPanelDistance * (this.getVisiblePanels() - 1); if (this.settings.responsiveMode === 'custom' || this.settings.responsive === false) { this.$accordion.css(this.sizeProperty, this.totalSize); } else { this.$accordion.css(this.sizeProperty, this.totalSize * this.autoResponsiveRatio); this.$maskContainer.css(this.sizeProperty, this.totalSize); } // if there are multiple pages, set the correct position of the panels' container if (this.settings.visiblePanels !== -1) { var cssObj = {}, targetPosition = - (this.totalSize + this.computedPanelDistance) * this.currentPage; if (this.currentPage === this.getTotalPages() - 1) targetPosition = - (this.closedPanelSize * this.getTotalPanels() + this.computedPanelDistance * (this.getTotalPanels() - 1) - this.totalSize); cssObj[this.positionProperty] = targetPosition; this.$panelsContainer.css(cssObj); } // calculate missing panels for the last page of panels var missingPanels = (this.currentPage === this.getTotalPages() - 1) && (this.getTotalPanels() % this.settings.visiblePanels) !== 0 ? this.settings.visiblePanels - this.getTotalPanels() % this.settings.visiblePanels : 0; // set the position and size of each panel $.each(this.panels, function(index, element) { // get the position of the panel based on the currently selected index and the panel's index var position; if (that.currentIndex === -1) { position = index * (that.closedPanelSize + that.computedPanelDistance); } else if (that.settings.visiblePanels === -1) { position = index * (that.collapsedPanelSize + that.computedPanelDistance) + (index > that.currentIndex ? that.computedOpenedPanelSize - that.collapsedPanelSize : 0); } else { if (that._getPageOfPanel(index) === that.currentPage) { position = that.currentPage * (that.totalSize + that.computedPanelDistance) + (index + missingPanels - that.currentPage * that.settings.visiblePanels) * (that.collapsedPanelSize + that.computedPanelDistance) + (index > that.currentIndex ? that.computedOpenedPanelSize - that.collapsedPanelSize : 0); if (that.currentPage === that.getTotalPages() - 1 && missingPanels !== 0) position -= (that.getTotalPages() - that.getTotalPanels() / that.settings.visiblePanels) * (that.totalSize + that.computedPanelDistance); } else { position = index * (that.closedPanelSize + that.computedPanelDistance); } } element.setPosition(position); // get the size of the panel based on the state of the panel (opened, closed or collapsed) if (that.isOverlapping === false) { var size = (that.currentIndex === -1 || (that.settings.visiblePanels !== -1 && that._getPageOfPanel(index) !== that.currentPage)) ? (that.closedPanelSize) : (index === that.currentIndex ? that.computedOpenedPanelSize : that.collapsedPanelSize); element.setSize(size); } }); // check if the current window width is bigger than the biggest breakpoint // and if necessary reset the properties to the original settings // if the window width is smaller than a certain breakpoint, apply the settings specified // for that breakpoint but only after merging them with the original settings // in order to make sure that only the specified settings for the breakpoint are applied if (this.settings.breakpoints !== null && this.breakpoints.length > 0) { if ($(window).width() > this.breakpoints[this.breakpoints.length - 1].size && this.currentBreakpoint !== -1) { this.currentBreakpoint = -1; this._setProperties(this.originalSettings, false); } else { for (var i = 0, n = this.breakpoints.length; i < n; i++) { if ($(window).width() <= this.breakpoints[i].size) { if (this.currentBreakpoint !== this.breakpoints[i].size) { var eventObject = {type: 'breakpointReach', size: this.breakpoints[i].size, settings: this.breakpoints[i].properties}; that.trigger(eventObject); if ($.isFunction(that.settings.breakpointReach)) that.settings.breakpointReach.call(that, eventObject); this.currentBreakpoint = this.breakpoints[i].size; var settings = $.extend({}, this.originalSettings, this.breakpoints[i].properties); this._setProperties(settings, false); } break; } } } } }, /* Set properties on runtime */ _setProperties: function(properties, store) { // parse the properties passed as an object for (var prop in properties) { // if the number of visible panels is changed, store a reference of the previous value // which will be used to move the panels to the corresponding page if (prop === 'visiblePanels' && this.settings.visiblePanels !== -1) this.previousVisiblePanels = this.settings.visiblePanels; this.settings[prop] = properties[prop]; // alter the original settings as well unless 'false' is passed to the 'store' parameter if (store !== false) this.originalSettings[prop] = properties[prop]; } this.update(); }, /* Destroy the Accordion Slider instance */ destroy: function() { // remove the stored reference to this instance this.$accordion.removeData('accordionSlider'); // remove inline style this.$accordion.attr('style', ''); this.$panelsContainer.attr('style', ''); // detach event handlers this.off('mouseenter.' + NS); this.off('mouseleave.' + NS); $(window).off('resize.' + this.uniqueId + '.' + NS); // stop animations this._stopPanelsAnimation(this.openPanelAnimation); this._stopPanelsAnimation(this.closePanelsAnimation); // destroy modules var modules = $.AccordionSlider.modules.accordion; if (typeof modules !== 'undefined') for (var i = 0; i < modules.length; i++) { if (typeof this['destroy' + modules[i]] !== 'undefined') this['destroy' + modules[i]](); } // destroy all panels this.removePanels(); // move the panels from the mask container back in the main accordion container this.$panelsContainer.appendTo(this.$accordion); // remove elements that were created by the script this.$maskContainer.remove(); this.$accordion.find('.as-pagination-buttons').remove(); }, /* Attach an event handler to the accordion */ on: function(type, callback) { return this.$accordion.on(type, callback); }, /* Detach an event handler */ off: function(type) { return this.$accordion.off(type); }, /* Trigger an event on the accordion */ trigger: function(data) { return this.$accordion.triggerHandler(data); }, /* Return the panel at the specified index */ getPanelAt: function(index) { return this.panels[index]; }, /* Return the index of the currently opened panel */ getCurrentIndex: function() { return this.currentIndex; }, /* Return the total amount of panels */ getTotalPanels: function() { return this.panels.length; }, /* Open the next panel */ nextPanel: function() { var index = (this.currentIndex >= this.getTotalPanels() - 1) ? 0 : (this.currentIndex + 1); this.openPanel(index); }, /* Open the previous panel */ previousPanel: function() { var index = this.currentIndex <= 0 ? (this.getTotalPanels() - 1) : (this.currentIndex - 1); this.openPanel(index); }, /* Animate the panels using request animation frame */ _animatePanels: function(target, args) { var startTime = new Date().valueOf(), progress = 0; target.isRunning = true; target.timer = window.requestAnimationFrame(_animate); function _animate() { if (progress < 1) { // get the progress by calculating the elapsed time progress = (new Date().valueOf() - startTime) / args.duration; if (progress > 1) progress = 1; // apply swing easing progress = 0.5 - Math.cos(progress * Math.PI) / 2; args.step(progress); target.timer = window.requestAnimationFrame(_animate); } else { args.complete(); target.isRunning = false; window.cancelAnimationFrame(target.timer); } } }, /* Stop running panel animations */ _stopPanelsAnimation: function(target) { if (typeof target.isRunning !== 'undefined' && target.isRunning === true) { target.isRunning = false; window.cancelAnimationFrame(target.timer); } }, /* Check if window.requestAnimationFrame exists in the browser and if it doesn't, try alternative function names or implement window.requestAnimationFrame using window.setTimeout */ _prepareRAF: function() { if (typeof window.requestAnimationFrame === 'undefined') { var vendorPrefixes = ['webkit', 'moz']; for(var i = 0; i < vendorPrefixes.length; i++) { window.requestAnimationFrame = window[vendorPrefixes[i] + 'RequestAnimationFrame']; window.cancelAnimationFrame = window.cancelAnimationFrame || window[vendorPrefixes[i] + 'CancelAnimationFrame'] || window[vendorPrefixes[i] + 'CancelRequestAnimationFrame']; } } // polyfill inspired from Erik Moller if (typeof window.requestAnimationFrame === 'undefined') { var lastTime = 0; window.requestAnimationFrame = function(callback, element) { var currentTime = new Date().valueOf(), timeToCall = Math.max(0, 16 - (currentTime - lastTime)); var id = window.setTimeout(function() { callback(currentTime + timeToCall); }, timeToCall); lastTime = currentTime + timeToCall; return id; }; window.cancelAnimationFrame = function(id) { clearTimeout(id); }; } }, /* Open the panel at the specified index */ openPanel: function(index, force) { if (index === this.currentIndex && force !== true) return; // remove the "closed" class and add the "opened" class, which indicates // that the accordion has an opened panel if (this.$accordion.hasClass('as-opened') === false) { this.$accordion.removeClass('as-closed'); this.$accordion.addClass('as-opened'); } var previousIndex = this.currentIndex; this.currentIndex = index; // synchronize the page with the selected panel by navigating to the page that // contains the panel if necessary. // if the last page is already selected and the selected panel is on this last page // don't navigate to a different page no matter what panel is selected and whether // the panel actually belongs to the previous page if (this.settings.visiblePanels !== -1 && !(this.currentPage === this.getTotalPages() - 1 && index >= this.getTotalPanels() - this.settings.visiblePanels)) { var page = Math.floor(this.currentIndex / this.settings.visiblePanels); if (page !== this.currentPage) this.gotoPage(page); // reset the current index because when the closePanels was called inside gotoPage the current index became -1 this.currentIndex = index; } var that = this, targetSize = [], targetPosition = [], startSize = [], startPosition = [], animatedPanels = [], firstPanel = this._getFirstPanelFromPage(), lastPanel = this._getLastPanelFromPage(), counter = 0; this.$accordion.find('.as-panel.as-opened').removeClass('as-opened'); this.$accordion.find('.as-panel').eq(this.currentIndex).addClass('as-opened'); // check if the panel needs to open to its maximum size and recalculate // the size of the opened panel and the size of the collapsed panel if (this.settings.openedPanelSize === 'max') { var contentSize = this.getPanelAt(this.currentIndex).getContentSize(); this.computedOpenedPanelSize = contentSize === 'loading' ? this.closedPanelSize : Math.min(contentSize, this.maxComputedOpenedPanelSize); this.collapsedPanelSize = (this.totalSize - this.computedOpenedPanelSize - (this.getVisiblePanels() - 1) * this.computedPanelDistance) / (this.getVisiblePanels() - 1); } // get the starting and target position and size of each panel for (var i = firstPanel; i <= lastPanel; i++) { var panel = this.getPanelAt(i); startPosition[i] = panel.getPosition(); targetPosition[i] = this.currentPage * (this.totalSize + this.computedPanelDistance) + counter * (this.collapsedPanelSize + this.computedPanelDistance) + (i > this.currentIndex ? this.computedOpenedPanelSize - this.collapsedPanelSize : 0); // the last page might contain less panels than the set number of visible panels. // in this situation, the last page will contain some panels from the previous page // and this requires the panels from the last page to be positioned differently than // the rest of the panels. this requires some amendments to the position of the last panels // by replacing the current page index with a float number: this.getTotalPanels() / this.settings.visiblePanels, // which would represent the actual number of existing pages. // here we subtract the float number from the formal number of pages in order to calculate // how much length it's necessary to subtract from the initially calculated value if (this.settings.visiblePanels !== -1 && this.currentPage === this.getTotalPages() - 1) targetPosition[i] -= (this.getTotalPages() - this.getTotalPanels() / this.settings.visiblePanels) * (this.totalSize + this.computedPanelDistance); // check if the panel's position needs to change if (targetPosition[i] !== startPosition[i]) animatedPanels.push(i); if (this.isOverlapping === false) { startSize[i] = panel.getSize(); targetSize[i] = i === this.currentIndex ? this.computedOpenedPanelSize : this.collapsedPanelSize; // check if the panel's size needs to change if (targetSize[i] !== startSize[i] && $.inArray(i, animatedPanels) === -1) animatedPanels.push(i); } counter++; } var totalPanels = animatedPanels.length; // stop the close panels animation if it's on the same page if (this.closePanelsAnimation.page === this.currentPage) this._stopPanelsAnimation(this.closePanelsAnimation); // stop any running animations this._stopPanelsAnimation(this.openPanelAnimation); // assign the current page this.openPanelAnimation.page = this.currentPage; // animate the panels this._animatePanels(this.openPanelAnimation, { duration: this.settings.openPanelDuration, step: function(progress) { for (var i = 0; i < totalPanels; i++) { var value = animatedPanels[i], panel = that.getPanelAt(value); panel.setPosition(progress * (targetPosition[value] - startPosition[value]) + startPosition[value]); if (that.isOverlapping === false) panel.setSize(progress * (targetSize[value] - startSize[value]) + startSize[value]); } }, complete: function() { // fire 'panelOpenComplete' event var eventObject = {type: 'panelOpenComplete', index: that.currentIndex}; that.trigger(eventObject); if ($.isFunction(that.settings.panelOpenComplete)) that.settings.panelOpenComplete.call(that, eventObject); } }); // fire 'panelOpen' event var eventObject = {type: 'panelOpen', index: index, previousIndex: previousIndex}; this.trigger(eventObject); if ($.isFunction(this.settings.panelOpen)) this.settings.panelOpen.call(this, eventObject); }, /* Close the panels */ closePanels: function() { var previousIndex = this.currentIndex; this.currentIndex = -1; // remove the "opened" class and add the "closed" class, which indicates // that the accordion is closed if (this.$accordion.hasClass('as-closed') === false) { this.$accordion.removeClass('as-opened'); this.$accordion.addClass('as-closed'); } // remove the "opened" class from the previously opened panel this.$accordion.find('.as-panel.as-opened').removeClass('as-opened'); clearTimeout(this.mouseDelayTimer); var that = this, targetSize = [], targetPosition = [], startSize = [], startPosition = [], firstPanel = this._getFirstPanelFromPage(), lastPanel = this._getLastPanelFromPage(), counter = 0; // get the starting and target size and position of each panel for (var i = firstPanel; i <= lastPanel; i++) { var panel = this.getPanelAt(i); startPosition[i] = panel.getPosition(); targetPosition[i] = this.currentPage * (this.totalSize + this.computedPanelDistance) + counter * (this.closedPanelSize + this.computedPanelDistance); // same calculations as in openPanel if (this.settings.visiblePanels !== -1 && this.currentPage === this.getTotalPages() - 1) targetPosition[i] -= (this.getTotalPages() - this.getTotalPanels() / this.settings.visiblePanels) * (this.totalSize + this.computedPanelDistance); if (this.isOverlapping === false) { startSize[i] = panel.getSize(); targetSize[i] = this.closedPanelSize; } counter++; } // stop the open panel animation if it's on the same page if (this.openPanelAnimation.page === this.currentPage) this._stopPanelsAnimation(this.openPanelAnimation); // stop any running animations this._stopPanelsAnimation(this.closePanelsAnimation); // assign the current page this.closePanelsAnimation.page = this.currentPage; // animate the panels this._animatePanels(this.closePanelsAnimation, { duration: this.settings.closePanelDuration, step: function(progress) { for (var i = firstPanel; i <= lastPanel; i++) { var panel = that.getPanelAt(i); panel.setPosition(progress * (targetPosition[i] - startPosition[i]) + startPosition[i]); if (that.isOverlapping === false) panel.setSize(progress * (targetSize[i] - startSize[i]) + startSize[i]); } }, complete: function() { // fire 'panelsCloseComplete' event var eventObject = {type: 'panelsCloseComplete', previousIndex: previousIndex}; that.trigger(eventObject); if ($.isFunction(that.settings.panelsCloseComplete)) that.settings.panelsCloseComplete.call(that, eventObject); } }); // fire 'panelsClose' event var eventObject = {type: 'panelsClose', previousIndex: previousIndex}; this.trigger(eventObject); if ($.isFunction(this.settings.panelsClose)) this.settings.panelsClose.call(this, eventObject); }, /* Return the number of visible panels */ getVisiblePanels: function() { return this.settings.visiblePanels === -1 ? this.getTotalPanels() : this.settings.visiblePanels; }, /* Return the total number of pages */ getTotalPages: function() { if (this.settings.visiblePanels === -1) return 1; return Math.ceil(this.getTotalPanels() / this.settings.visiblePanels); }, /* Return the current page */ getCurrentPage: function() { return this.settings.visiblePanels === -1 ? 0 : this.currentPage; }, /* Navigate to the indicated page */ gotoPage: function(index) { // close any opened panels before scrolling to a different page if (this.currentIndex !== -1) this.closePanels(); this.currentPage = index; this.isPageScrolling = true; var that = this, animObj = {}, targetPosition = - (index * this.totalSize + this.currentPage * this.computedPanelDistance); if (this.currentPage === this.getTotalPages() - 1) targetPosition = - (this.totalPanelsSize - this.totalSize); animObj[this.positionProperty] = targetPosition; // fire 'pageScroll' event var eventObject = {type: 'pageScroll', index: this.currentPage}; this.trigger(eventObject); if ($.isFunction(this.settings.pageScroll)) this.settings.pageScroll.call(this, eventObject); this.$panelsContainer.stop().animate(animObj, this.settings.pageScrollDuration, this.settings.pageScrollEasing, function() { that.isPageScrolling = false; // fire 'pageScrollComplete' event var eventObject = {type: 'pageScrollComplete', index: that.currentPage}; that.trigger(eventObject); if ($.isFunction(that.settings.pageScrollComplete)) that.settings.pageScrollComplete.call(that, eventObject); }); }, /* Navigate to the next page */ nextPage: function() { var index = (this.currentPage >= this.getTotalPages() - 1) ? 0 : (this.currentPage + 1); this.gotoPage(index); }, /* Navigate to the previous page */ previousPage: function() { var index = this.currentPage <= 0 ? (this.getTotalPages() - 1) : (this.currentPage - 1); this.gotoPage(index); }, /* Calculate and return the first panel from the current page */ _getFirstPanelFromPage: function() { if (this.settings.visiblePanels === -1) { return 0; } else if (this.currentPage === this.getTotalPages() - 1 && this.currentPage !== 0) { return this.getTotalPanels() - this.settings.visiblePanels; } else { return this.currentPage * this.settings.visiblePanels; } }, /* Calculate and return the last panel from the current page */ _getLastPanelFromPage: function() { if (this.settings.visiblePanels === -1) { return this.getTotalPanels() - 1; } else if (this.currentPage === this.getTotalPages() - 1) { return this.getTotalPanels() - 1; } else { return (this.currentPage + 1) * this.settings.visiblePanels - 1; } }, /* Return the page that the specified panel belongs to */ _getPageOfPanel: function(index) { if (this.currentPage === this.getTotalPages() - 1 && index >= this.getTotalPanels() - this.settings.visiblePanels) return this.getTotalPages() - 1; return Math.floor(index / this.settings.visiblePanels); }, /* Create or update the pagination buttons */ _updatePaginationButtons: function() { var paginationButtons = this.$accordion.find('.as-pagination-buttons'), that = this, totalPages = this.getTotalPages(); // remove the buttons if there are no more pages if (totalPages <= 1 && paginationButtons.length !== 0) { paginationButtons.remove(); paginationButtons.off('click.' + NS, '.as-pagination-button'); this.off('pageScroll.' + NS); this.$accordion.removeClass('as-has-buttons'); // if there are pages and the buttons were not created yet, create them now } else if (totalPages > 1 && paginationButtons.length === 0) { // create the buttons' container paginationButtons = $('
').appendTo(this.$accordion); // create the buttons for (var i = 0; i < this.getTotalPages(); i++) { $('
').appendTo(paginationButtons); } // listen for button clicks paginationButtons.on('click.' + NS, '.as-pagination-button', function() { that.gotoPage($(this).index()); }); // set the initially selected button paginationButtons.find('.as-pagination-button').eq(this.currentPage).addClass('as-selected'); // select the corresponding panel when the page changes and change the selected button this.on('pageScroll.' + NS, function(event) { paginationButtons.find('.as-selected').removeClass('as-selected'); paginationButtons.find('.as-pagination-button').eq(event.index).addClass('as-selected'); }); this.$accordion.addClass('as-has-buttons'); // update the buttons if they already exist but their number differs from // the number of existing pages } else if (totalPages > 1 && paginationButtons.length !== 0) { paginationButtons.empty(); // create the buttons for (var j = 0; j < this.getTotalPages(); j++) { $('
').appendTo(paginationButtons); } // change the selected the buttons paginationButtons.find('.as-selected').removeClass('as-selected'); paginationButtons.find('.as-pagination-button').eq(this.currentPage).addClass('as-selected'); } }, /* The default options of the accordion */ defaults: { width: 800, height: 400, responsive: true, responsiveMode: 'auto', aspectRatio: -1, orientation: 'horizontal', startPanel: -1, openedPanelSize: 'max', maxOpenedPanelSize: '80%', openPanelOn: 'hover', closePanelsOnMouseOut: true, mouseDelay: 200, panelDistance: 0, openPanelDuration: 700, closePanelDuration: 700, pageScrollDuration: 500, pageScrollEasing: 'swing', breakpoints: null, visiblePanels: -1, startPage: 0, shadow: true, shuffle: false, panelOverlap: true, init: function() {}, update: function() {}, accordionMouseOver: function() {}, accordionMouseOut: function() {}, panelClick: function() {}, panelMouseOver: function() {}, panelMouseOut: function() {}, panelOpen: function() {}, panelsClose: function() {}, pageScroll: function() {}, panelOpenComplete: function() {}, panelsCloseComplete: function() {}, pageScrollComplete: function() {}, breakpointReach: function() {} } }; var AccordionSliderPanel = function(panel, accordion, index) { // reference to the panel jQuery object this.$panel = panel; // reference to the accordion object this.accordion = accordion; // reference to the global settings of the accordion this.settings = this.accordion.settings; // set a namespace for the panel this.panelNS = 'AccordionSliderPanel' + index + '.' + NS; this.isLoading = false; this.isLoaded = false; // set the index of the panel this.setIndex(index); // initialize the panel this._init(); }; AccordionSliderPanel.prototype = { /* The starting point for the panel */ _init: function() { var that = this; this.$panel.attr('data-init', true); // listen for 'mouseenter' events this.on('mouseenter.' + this.panelNS, function() { that.trigger({type: 'panelMouseOver.' + NS, index: that.index}); }); // listen for 'mouseleave' events this.on('mouseleave.' + this.panelNS, function() { that.trigger({type: 'panelMouseOut.' + NS, index: that.index}); }); // listen for 'click' events this.on('click.' + this.panelNS, function() { that.trigger({type: 'panelClick.' + NS, index: that.index}); }); // listen for 'mousedown' events this.on('mousedown.' + this.panelNS, function() { that.trigger({type: 'panelMouseDown.' + NS, index: that.index}); }); // set position and size properties this.update(); // initialize panel modules var modules = $.AccordionSlider.modules.panel; if (typeof modules !== 'undefined') for (var i = 0; i < modules.length; i++) { if (typeof this['init' + modules[i]] !== 'undefined') this['init' + modules[i]](); } }, /* Update the panel */ update: function() { // get the new position and size properties this.positionProperty = this.settings.orientation === 'horizontal' ? 'left' : 'top'; this.sizeProperty = this.settings.orientation === 'horizontal' ? 'width' : 'height'; // reset the current size and position this.$panel.css({top: '', left: '', width: '', height: ''}); }, /* Destroy the panel */ destroy: function() { // detach all event listeners this.off('mouseenter.' + this.panelNS); this.off('mouseleave.' + this.panelNS); this.off('click.' + this.panelNS); this.off('mousedown.' + this.panelNS); // clean the element from attached styles and data this.$panel.attr('style', ''); this.$panel.removeAttr('data-init'); this.$panel.removeAttr('data-index'); // destroy panel modules var modules = $.AccordionSlider.modules.panel; if (typeof modules !== 'undefined') for (var i = 0; i < modules.length; i++) { if (typeof this['destroy' + modules[i]] !== 'undefined') this['destroy' + modules[i]](); } }, /* Return the index of the panel */ getIndex: function() { return this.index; }, /* Set the index of the panel */ setIndex: function(index) { this.index = index; this.$panel.attr('data-index', this.index); }, /* Return the position of the panel */ getPosition: function() { return parseInt(this.$panel.css(this.positionProperty), 10); }, /* Set the position of the panel */ setPosition: function(value) { this.$panel.css(this.positionProperty, value); }, /* Return the size of the panel */ getSize: function() { return parseInt(this.$panel.css(this.sizeProperty), 10); }, /* Set the size of the panel */ setSize: function(value) { this.$panel.css(this.sizeProperty, value); }, /* Get the real size of the panel's content */ getContentSize: function() { // check if there are loading images if (this.isLoaded === false) if (this.checkImagesComplete() === 'loading') return 'loading'; this.$panel.find( '.as-opened' ).css( 'display', 'none' ); var size = this.sizeProperty === 'width' ? this.$panel[0].scrollWidth : this.$panel[0].scrollHeight; this.$panel.find( '.as-opened' ).css( 'display', '' ); return size; }, /* Check the status of all images from the panel */ checkImagesComplete: function() { if (this.isLoading === true) return 'loading'; var that = this, status = 'complete'; // check if there is any unloaded image inside the panel this.$panel.find('img').each(function(index) { var image = $(this)[0]; if (image.complete === false || typeof $(this).attr('data-src') !== 'undefined') status = 'loading'; }); // continue checking until all images have loaded if (status === 'loading') { this.isLoading = true; var checkImage = setInterval(function() { var loaded = true; that.$panel.find('img').each(function(index) { var image = $(this)[0]; if (image.complete === false || typeof $(this).attr('data-src') !== 'undefined') loaded = false; }); if (loaded === true) { that.isLoading = false; that.isLoaded = true; clearInterval(checkImage); that.trigger({type: 'imagesComplete.' + NS, index: that.index, contentSize: that.getContentSize()}); } }, 100); } else { this.isLoaded = true; } return status; }, /* Attach an event handler to the panel */ on: function(type, callback) { return this.$panel.on(type, callback); }, /* Detach an event handler to the panel */ off: function(type) { return this.$panel.off(type); }, /* Trigger an event on the panel */ trigger: function(data) { return this.$panel.triggerHandler(data); } }; window.AccordionSlider = AccordionSlider; window.AccordionSliderPanel = AccordionSliderPanel; $.fn.accordionSlider = function(options) { var args = Array.prototype.slice.call(arguments, 1); return this.each(function() { // instantiate the accordion or alter it if (typeof $(this).data('accordionSlider') === 'undefined') { var newInstance = new AccordionSlider(this, options); // store a reference to the instance created $(this).data('accordionSlider', newInstance); } else if (typeof options !== 'undefined') { var currentInstance = $(this).data('accordionSlider'); // check the type of argument passed if (typeof currentInstance[options] === 'function') { currentInstance[options].apply(currentInstance, args); } else if (typeof currentInstance.settings[options] !== 'undefined') { var obj = {}; obj[options] = args[0]; currentInstance._setProperties(obj); } else if (typeof options === 'object') { currentInstance._setProperties(options); } else { $.error(options + ' does not exist in accordionSlider.'); } } }); }; })(window, jQuery); /* Autoplay module for Accordion Slider Adds autoplay functionality to the accordion */ ;(function(window, $) { "use strict"; var NS = $.AccordionSlider.namespace; var Autoplay = { autoplayIndex: -1, autoplayTimer: null, isTimerRunning: false, isTimerPaused: false, initAutoplay: function() { var that = this; if (this.settings.autoplay === true) this.startAutoplay(); // start the autoplay timer each time the panel opens this.on('panelOpen.Autoplay.' + NS, function(event) { that.autoplayIndex = event.index; if (that.settings.autoplay === true) { // stop previous timers before starting a new one if (that.isTimerRunning === true) that.stopAutoplay(); if (that.isTimerPaused === false) that.startAutoplay(); } }); // store the index of the previously opened panel this.on('panelsClose.Autoplay.' + NS, function(event) { if (event.previousIndex !== -1) that.autoplayIndex = event.previousIndex; }); // store the index of the first panel from the new page this.on('pageScroll.Autoplay.' + NS, function(event) { that.autoplayIndex = that._getFirstPanelFromPage() - 1; }); // on accordion hover stop the autoplay if autoplayOnHover is set to pause or stop this.on('mouseenter.Autoplay.' + NS, function(event) { if (that.settings.autoplay === true && that.isTimerRunning && (that.settings.autoplayOnHover === 'pause' || that.settings.autoplayOnHover === 'stop')) { that.stopAutoplay(); that.isTimerPaused = true; } }); // on accordion hover out restart the autoplay this.on('mouseleave.Autoplay.' + NS, function(event) { if (that.settings.autoplay === true && that.isTimerRunning === false && that.settings.autoplayOnHover !== 'stop') { that.startAutoplay(); that.isTimerPaused = false; } }); }, startAutoplay: function() { var that = this; this.isTimerRunning = true; this.autoplayTimer = setTimeout(function() { // check if there is a stored index from which the autoplay needs to continue if (that.autoplayIndex !== -1) { that.currentIndex = that.autoplayIndex; that.autoplayIndex = -1; } if (that.settings.autoplayDirection === 'normal') { that.nextPanel(); } else if (that.settings.autoplayDirection === 'backwards') { that.previousPanel(); } }, this.settings.autoplayDelay); }, stopAutoplay: function() { this.isTimerRunning = false; clearTimeout(this.autoplayTimer); }, destroyAutoplay: function() { clearTimeout(this.autoplayTimer); this.off('panelOpen.Autoplay.' + NS); this.off('pageScroll.Autoplay.' + NS); this.off('mouseenter.Autoplay.' + NS); this.off('mouseleave.Autoplay.' + NS); }, autoplayDefaults: { autoplay: true, autoplayDelay: 5000, autoplayDirection: 'normal', autoplayOnHover: 'pause' } }; $.AccordionSlider.addModule('Autoplay', Autoplay, 'accordion'); })(window, jQuery); /* Deep Linking module for Accordion Slider Adds the possibility to access the accordion using hyperlinks */ ;(function(window, $) { "use strict"; var NS = $.AccordionSlider.namespace; var DeepLinking = { initDeepLinking: function() { var that = this; // ignore the startPanel setting if there is a if (this._parseHash(window.location.hash) !== false) this.options.startPanel = -1; // parse the initial hash this.on('init.DeepLinking.' + NS, function() { that._gotoHash(window.location.hash); }); // check when the hash changes $(window).on('hashchange.DeepLinking.' + this.uniqueId + '.' + NS, function() { that._gotoHash(window.location.hash); }); }, _parseHash: function(hash) { if (hash !== '') { // eliminate the # symbol hash = hash.substring(1); // get the specified accordion id and panel id var values = hash.split('/'), panelId = values.pop(), accordionId = hash.slice(0, - panelId.toString().length - 1); if (this.$accordion.attr('id') === accordionId) return {'accordionID': accordionId, 'panelId': panelId}; } return false; }, _gotoHash: function(hash) { var result = this._parseHash(hash); if (result === false) return; var panelId = result.panelId, panelIdNumber = parseInt(panelId, 10); // check if the specified panel id is a number or string if (isNaN(panelIdNumber)) { // get the index of the panel based on the specified id var panelIndex = this.$accordion.find('.as-panel#' + panelId).index(); if (panelIndex !== -1) this.openPanel(panelIndex); } else { this.openPanel(panelIdNumber); } }, destroyDeepLinking: function() { $(window).off('hashchange.DeepLinking.' + this.uniqueId + '.' + NS); } }; $.AccordionSlider.addModule('DeepLinking', DeepLinking, 'accordion'); })(window, jQuery); /* JSON module for Accordion Slider Creates the panels based on JSON data */ ;(function(window, $) { "use strict"; var NS = $.AccordionSlider.namespace; var JSON = { JSONDataAttributesMap : { 'width': 'data-width', 'height': 'data-height', 'depth': 'data-depth', 'position': 'data-position', 'horizontal': 'data-horizontal', 'vertical': 'data-vertical', 'showTransition': 'data-show-transition', 'showOffset': 'data-show-offset', 'showDelay': 'data-show-delay', 'showDuration': 'data-show-duration', 'showEasing': 'data-show-easing', 'hideTransition': 'data-hide-transition', 'hideOffset': 'data-', 'hideDelay': 'data-hide-delay', 'hideDuration': 'data-hide-duration', 'hideEasing': 'data-hide-easing' }, initJSON: function() { if (this.settings.JSONSource !== null) this.updateJSON(); }, updateJSON: function() { var that = this; // clear existing content and data this.removePanels(); this.$panelsContainer.empty(); this.off('JSONReady.' + NS); // parse the JSON data and construct the panels this.on('JSONReady.' + NS, function(event) { var jsonData = event.jsonData, panels = jsonData.accordion.panels; // check if lazy loading is enabled var lazyLoading = jsonData.accordion.lazyLoading; $.each(panels, function(index, value) { var panel = value, backgroundLink, backgroundOpenedLink; // create the panel element var panelElement = $('
').appendTo(that.$panelsContainer); // create the background image and link if (typeof panel.backgroundLink !== 'undefined') { backgroundLink = $(''); $.each(panel.backgroundLink, function(name, value) { if (name !== 'address') backgroundLink.attr(name, value); }); backgroundLink.appendTo(panelElement); } if (typeof panel.background !== 'undefined') { var background = $(''); // check if the image will be lazy loaded if (typeof lazyLoading !== 'undefined') background.attr({'src': lazyLoading, 'data-src': panel.background.source}); else background.attr({'src': panel.background.source}); // check if a retina image was specified if (typeof panel.backgroundRetina !== 'undefined') background.attr({'data-retina': panel.backgroundRetina.source}); $.each(panel.background, function(name, value) { if (name !== 'source') background.attr(name, value); }); background.appendTo(typeof backgroundLink !== 'undefined' ? backgroundLink : panelElement); } // create the background image and link for the opened state of the panel if (typeof panel.backgroundOpenedLink !== 'undefined') { backgroundOpenedLink = $(''); $.each(panel.backgroundOpenedLink, function(name, value) { if (name !== 'address') backgroundOpenedLink.attr(name, value); }); backgroundOpenedLink.appendTo(panelElement); } if (typeof panel.backgroundOpened !== 'undefined') { var backgroundOpened = $(''); // check if the image will be lazy loaded if (typeof lazyLoading !== 'undefined') backgroundOpened.attr({'src': lazyLoading, 'data-src': panel.backgroundOpened.source}); else backgroundOpened.attr({'src': panel.backgroundOpened.source}); // check if a retina image was specified if (typeof panel.backgroundOpenedRetina !== 'undefined') backgroundOpened.attr({'data-retina': panel.backgroundOpenedRetina.source}); $.each(panel.backgroundOpened, function(name, value) { if (name !== 'source') backgroundOpened.attr(name, value); }); backgroundOpened.appendTo(typeof backgroundOpenedLink !== 'undefined' ? backgroundOpenedLink : panelElement); } // parse the layers recursively if (typeof panel.layers !== 'undefined') that._parseLayers(panel.layers, panelElement); }); that.update(); }); this._loadJSON(); }, _parseLayers: function(target, parent) { var that = this; $.each(target, function(index, value) { var layer = value, classes = '', dataAttributes = ''; // parse the data specified for the layer and extract the classes and data attributes $.each(layer, function(name, value) { if (name === 'style') { var classList = value.split(' '); $.each(classList, function(classIndex, className) { classes += ' as-' + className; }); } else if (name !== 'content' && name !== 'layers'){ dataAttributes += ' ' + that.JSONDataAttributesMap[name] + '="' + value + '"'; } }); // create the layer element var layerElement = $('
').appendTo(parent); // check if there are inner layers and parse those if (typeof value.layers !== 'undefined') that._parseLayers(value.layers, layerElement); else layerElement.html(layer.content); }); }, _loadJSON: function() { var that = this; if (this.settings.JSONSource.slice(-5) === '.json') { $.getJSON(this.settings.JSONSource, function(result) { that.trigger({type: 'JSONReady.' + NS, jsonData: result}); }); } else { var jsonData = $.parseJSON(this.settings.JSONSource); that.trigger({type: 'JSONReady.' + NS, jsonData: jsonData}); } }, destroyJSON: function() { this.off('JSONReady.' + NS); }, JSONDefaults: { JSONSource: null } }; $.AccordionSlider.addModule('JSON', JSON, 'accordion'); })(window, jQuery); /* Keyboard module for Accordion Slider Adds keyboard navigation support to the accordion */ ;(function(window, $) { "use strict"; var NS = $.AccordionSlider.namespace; var Keyboard = { initKeyboard: function() { var that = this, hasFocus = false; if (this.settings.keyboard === false) return; this.$accordion.on('focus.Keyboard.' + NS, function() { hasFocus = true; }); this.$accordion.on('blur.Keyboard.' + NS, function() { hasFocus = false; }); $(document).on('keydown.Keyboard.' + this.uniqueId + '.' + NS, function(event) { if (that.settings.keyboardOnlyOnFocus === true && hasFocus === false) return; if (event.which === 37) { if (that.settings.keyboardTarget === 'page') that.previousPage(); else that.previousPanel(); } else if (event.which === 39) { if (that.settings.keyboardTarget === 'page') that.nextPage(); else that.nextPanel(); } else if (event.which === 13) { var link = that.$accordion.find('.as-panel').eq(that.currentIndex).children('a'); if ( link.length !== 0 ) { link[0].click(); } } }); }, destroyKeyboard: function() { this.$accordion.off('focus.Keyboard.' + NS); this.$accordion.off('blur.Keyboard.' + NS); $(document).off('keydown.Keyboard.' + this.uniqueId + '.' + NS); }, keyboardDefaults: { keyboard: true, keyboardOnlyOnFocus: false, keyboardTarget: 'panel' } }; $.AccordionSlider.addModule('Keyboard', Keyboard, 'accordion'); })(window, jQuery); /* Layers module for Accordion Slider Adds support for animated and static layers. */ ;(function(window, $) { "use strict"; var NS = $.AccordionSlider.namespace, // detect the current browser name and version userAgent = window.navigator.userAgent.toLowerCase(), rmsie = /(msie) ([\w.]+)/, browserDetect = rmsie.exec(userAgent) || [], browserName = browserDetect[1], browserVersion = browserDetect[2]; var Layers = { initLayers: function() { // holds references to the layers this.layers = []; // reference to the panel object var that = this; // iterate through the panel's layer jQuery objects // and create Layer instances for each object this.$panel.find('.as-layer').each(function() { var layer = new Layer($(this)); that.layers.push(layer); }); // check the index pf the panel against the index of the selected/opened panel if (this.index === this.accordion.getCurrentIndex()) this._handleLayersInOpenedState(); else this._handleLayersInClosedState(); // listen when a panel is opened and when the panels are closed, and handle // the layer's behavior based on the state of the panel this.accordion.on('panelOpen.Layers.' + this.panelNS, function(event) { if (event.index === event.previousIndex) return; if (that.index === event.previousIndex) that._handleLayersInClosedState(); if (that.index === event.index) that._handleLayersInOpenedState(); }); this.accordion.on('panelsClose.Layers.' + this.panelNS, function(event) { if (that.index === event.previousIndex) that._handleLayersInClosedState(); }); }, _handleLayersInOpenedState: function() { // show 'opened' layers and close 'closed' layers $.each(this.layers, function(index, layer) { if (layer.visibleOn === 'opened') layer.show(); if (layer.visibleOn === 'closed') layer.hide(); }); }, _handleLayersInClosedState: function() { // hide 'opened' layers and show 'closed' layers $.each(this.layers, function(index, layer) { if (layer.visibleOn === 'opened') layer.hide(); if (layer.visibleOn === 'closed') layer.show(); }); }, destroyLayers: function() { this.accordion.off('panelOpen.Layers.' + this.panelNS); this.accordion.off('panelsClose.Layers.' + this.panelNS); $.each(this.layers, function(index, layer) { layer.destroy(); }); } }; var Layer = function(layer) { // reference to the layer jQuery object this.$layer = layer; // indicates when will the layer be visible // can be visible when the panel is opened, when the panel is closed or always this.visibleOn = 'n/a'; // indicates whether a layer is currently visible (or hidden) this.isVisible = false; // indicates whether the layer was styled this.styled = false; this._init(); }; Layer.prototype = { _init: function() { // hide the layer by default this.$layer.css({'visibility': 'hidden', 'display': 'none'}); if (this.$layer.hasClass('as-opened')) { this.visibleOn = 'opened'; } else if (this.$layer.hasClass('as-closed')) { this.visibleOn = 'closed'; } else { this.visibleOn = 'always'; this.show(); } }, /* Set the size and position of the layer */ _setStyle: function() { this.styled = true; this.$layer.css({'display': '', 'margin': 0}); // get the data attributes specified in HTML this.data = this.$layer.data(); if (typeof this.data.width !== 'undefined') this.$layer.css('width', this.data.width); if (typeof this.data.height !== 'undefined') this.$layer.css('height', this.data.height); if (typeof this.data.depth !== 'undefined') this.$layer.css('z-index', this.data.depth); this.position = this.data.position ? (this.data.position).toLowerCase() : 'topleft'; this.horizontalPosition = this.position.indexOf('right') !== -1 ? 'right' : 'left'; this.verticalPosition = this.position.indexOf('bottom') !== -1 ? 'bottom' : 'top'; this._setPosition(); }, /* Set the position of the layer */ _setPosition: function() { // set the horizontal position of the layer based on the data set if (typeof this.data.horizontal !== 'undefined') { if (this.data.horizontal === 'center') { // prevent content wrapping while setting the width if (this.$layer.attr('style').indexOf('width') === -1 && this.$layer.is('img') === false) { this.$layer.css('white-space', 'nowrap'); this.$layer.css('width', this.$layer.outerWidth(true)); } // center horizontally this.$layer.css({'marginLeft': 'auto', 'marginRight': 'auto', 'left': 0, 'right': 0}); } else { this.$layer.css(this.horizontalPosition, this.data.horizontal); } } else { this.$layer.css(this.horizontalPosition, 0); } // set the vertical position of the layer based on the data set if (typeof this.data.vertical !== 'undefined') { if (this.data.vertical === 'center') { // prevent content wrapping while setting the height if (this.$layer.attr('style').indexOf('height') === -1 && this.$layer.is('img') === false) { this.$layer.css('white-space', 'nowrap'); this.$layer.css('height', this.$layer.outerHeight(true)); } // center vertically this.$layer.css({'marginTop': 'auto', 'marginBottom': 'auto', 'top': 0, 'bottom': 0}); } else { this.$layer.css(this.verticalPosition, this.data.vertical); } } else { this.$layer.css(this.verticalPosition, 0); } }, /* Show the layer */ show: function() { if (this.isVisible === true) return; this.isVisible = true; if (this.styled === false) this._setStyle(); var that = this, offset = typeof this.data.showOffset !== 'undefined' ? this.data.showOffset : 50, duration = typeof this.data.showDuration !== 'undefined' ? this.data.showDuration / 1000 : 0.4, delay = typeof this.data.showDelay !== 'undefined' ? this.data.showDelay : 10; if (this.visibleOn === 'always' || browserName === 'msie' && parseInt(browserVersion, 10) <= 7) { this.$layer.css('visibility', 'visible'); } else if (browserName === 'msie' && parseInt(browserVersion, 10) <= 9) { this.$layer.stop() .delay(delay) .css({'opacity': 0, 'visibility': 'visible'}) .animate({'opacity': 1}, duration * 1000); } else { var start = { 'opacity': 0, 'visibility': 'visible' }, transformValues = ''; if (this.data.showTransition === 'left') transformValues = offset + 'px, 0'; else if (this.data.showTransition === 'right') transformValues = '-' + offset + 'px, 0'; else if (this.data.showTransition === 'up') transformValues = '0, ' + offset + 'px'; else if (this.data.showTransition === 'down') transformValues = '0, -' + offset + 'px'; start.transform = LayersHelper.useTransforms() === '3d' ? 'translate3d(' + transformValues + ', 0)' : 'translate(' + transformValues + ')'; start['-webkit-transform'] = start['-ms-transform'] = start.transform; var target = { 'opacity': 1, 'transition': 'all ' + duration + 's' }; if (typeof this.data.showTransition !== 'undefined') { target.transform = LayersHelper.useTransforms() === '3d' ? 'translate3d(0, 0, 0)' : 'translate(0, 0)'; target['-webkit-transform'] = target['-ms-transform'] = target.transform; } // listen when the layer animation is complete this.$layer.on('transitionend webkitTransitionEnd oTransitionEnd msTransitionEnd', function() { that.$layer.off('transitionend webkitTransitionEnd oTransitionEnd msTransitionEnd'); // remove the transition property in order to prevent other animations of the element that.$layer.css('transition', ''); // remove transform property to prevent issue where layer text is disappearing after animation that.$layer.css('transform', ''); }); this.$layer.css(start) .delay(delay) .queue(function() { that.$layer.css(target); $(this).dequeue(); }); } }, /* Hide the layer */ hide: function() { if (this.isVisible === false) return; this.isVisible = false; var that = this, offset = typeof this.data.hideOffset !== 'undefined' ? this.data.hideOffset : 50, duration = typeof this.data.hideDuration !== 'undefined' ? this.data.hideDuration / 1000 : 0.4, delay = typeof this.data.hideDelay !== 'undefined' ? this.data.hideDelay : 10; if (this.visibleOn === 'always' || browserName === 'msie' && parseInt(browserVersion, 10) <= 7) { this.$layer.css('visibility', 'hidden'); } else if (browserName === 'msie' && parseInt(browserVersion, 10) <= 9) { this.$layer.stop() .delay(delay) .animate({'opacity': 0}, duration * 1000, function() { $(this).css({'visibility': 'hidden'}); }); } else { var target = { 'opacity': 0, 'transition': 'all ' + duration + 's' }, transformValues = ''; if (this.data.hideTransition === 'left') transformValues = '-' + offset + 'px, 0'; else if (this.data.hideTransition === 'right') transformValues = offset + 'px, 0'; else if (this.data.hideTransition === 'up') transformValues = '0, -' + offset + 'px'; else if (this.data.hideTransition === 'down') transformValues = '0, ' + offset + 'px'; target.transform = LayersHelper.useTransforms() === '3d' ? 'translate3d(' + transformValues + ', 0)' : 'translate(' + transformValues + ')'; target['-webkit-transform'] = target['-ms-transform'] = target.transform; // listen when the layer animation is complete this.$layer.on('transitionend webkitTransitionEnd oTransitionEnd msTransitionEnd', function() { that.$layer.off('transitionend webkitTransitionEnd oTransitionEnd msTransitionEnd'); // remove the transition property in order to prevent other animations of the element that.$layer.css('transition', ''); // remove transform property to prevent issue where layer text is disappearing after animation that.$layer.css('transform', ''); // hide the layer after transition if (that.isVisible === false) that.$layer.css('visibility', 'hidden'); }); this.$layer.delay(delay) .queue(function() { that.$layer.css(target); $(this).dequeue(); }); } }, destroy: function() { this.$layer.attr('style', ''); } }; $.AccordionSlider.addModule('Layers', Layers, 'panel'); var LayersHelper = { checked: false, transforms: '', /* Check if 2D and 3D transforms are supported Inspired by Modernizr */ useTransforms: function() { if (this.checked === true) return this.transforms; this.checked = true; var div = document.createElement('div'); // check if 3D transforms are supported if (typeof div.style.WebkitPerspective !== 'undefined' || typeof div.style.perspective !== 'undefined') this.transforms = '3d'; // additional checks for Webkit if (this.transforms === '3d' && typeof div.styleWebkitPerspective !== 'undefined') { var style = document.createElement('style'); style.textContent = '@media (transform-3d),(-webkit-transform-3d){#test-3d{left:9px;position:absolute;height:5px;margin:0;padding:0;border:0;}}'; document.getElementsByTagName('head')[0].appendChild(style); div.id = 'test-3d'; document.body.appendChild(div); if (!(div.offsetLeft === 9 && div.offsetHeight === 5)) this.transforms = ''; style.parentNode.removeChild(style); div.parentNode.removeChild(div); } // check if 2D transforms are supported if (this.transforms === '' && (typeof div.style['-webkit-transform'] !== 'undefined' || typeof div.style.transform !== 'undefined')) this.transforms = '2d'; return this.transforms; } }; })(window, jQuery); /* Lazy Loading module for Accordion Slider Loads marked images only when they are in the view */ ;(function(window, $) { "use strict"; var NS = $.AccordionSlider.namespace; var LazyLoading = { initLazyLoading: function() { // listen when the page changes or when the accordion is updated (because the number of visible panels might change) this.on('update.LazyLoading.' + NS, $.proxy(this._checkImages, this)); this.on('pageScroll.LazyLoading.' + NS, $.proxy(this._checkImages, this)); }, _checkImages: function() { var that = this, firstVisiblePanel = this._getFirstPanelFromPage(), lastVisiblePanel = this._getLastPanelFromPage(), // get all panels that are currently visible panelsToCheck = lastVisiblePanel !== this.getTotalPanels() - 1 ? this.panels.slice(firstVisiblePanel, lastVisiblePanel + 1) : this.panels.slice(firstVisiblePanel); // loop through all the visible panels, verify if there are unloaded images, and load them $.each(panelsToCheck, function(index, element) { var $panel = element.$panel; if (typeof $panel.attr('data-loaded') === 'undefined') { $panel.attr('data-loaded', true); $panel.find('img').each(function() { var image = $(this); that._loadImage(image, element); }); } }); }, _loadImage: function(image, panel) { if (typeof image.attr('data-src') !== 'undefined') { // create a new image element var newImage = $(new Image()); // copy the class(es) and inline style newImage.attr('class', image.attr('class')); newImage.attr('style', image.attr('style')); // copy the data attributes $.each(image.data(), function(name, value) { newImage.attr('data-' + name, value); }); // copy the width and height attributes if they exist if (typeof image.attr('width') !== 'undefined') newImage.attr('width', image.attr('width')); if (typeof image.attr('height') !== 'undefined') newImage.attr('height', image.attr('height')); if (typeof image.attr('alt') !== 'undefined') newImage.attr('alt', image.attr('alt')); if (typeof image.attr('title') !== 'undefined') newImage.attr('title', image.attr('title')); // assign the source of the image newImage.attr('src', image.attr('data-src')); newImage.removeAttr('data-src'); // add the new image in the same container and remove the older image newImage.insertAfter(image); image.remove(); } }, destroyLazyLoading: function() { this.off('update.LazyLoading.' + NS); this.off('pageScroll.LazyLoading.' + NS); } }; $.AccordionSlider.addModule('LazyLoading', LazyLoading, 'accordion'); })(window, jQuery); /* MouseWheel module for Accordion Slider Adds mouse wheel support for scrolling through pages or individual panels */ ;(function(window, $) { "use strict"; var NS = $.AccordionSlider.namespace; var MouseWheel = { mouseWheelEventType: '', allowMouseWheelScroll: true, initMouseWheel: function() { var that = this; if (this.settings.mouseWheel === false) return; // get the current mouse wheel event used in the browser if ('onwheel' in document) this.mouseWheelEventType = 'wheel'; else if ('onmousewheel' in document) this.mouseWheelEventType = 'mousewheel'; else if ('onDomMouseScroll' in document) this.mouseWheelEventType = 'DomMouseScroll'; else if ('onMozMousePixelScroll' in document) this.mouseWheelEventType = 'MozMousePixelScroll'; this.on(this.mouseWheelEventType + '.' + NS, function(event) { event.preventDefault(); var eventObject = event.originalEvent, delta; // get the movement direction and speed indicated in the delta property if (typeof eventObject.detail !== 'undefined') delta = eventObject.detail; if (typeof eventObject.wheelDelta !== 'undefined') delta = eventObject.wheelDelta; if (typeof eventObject.deltaY !== 'undefined') delta = eventObject.deltaY * -1; if (that.allowMouseWheelScroll === true && Math.abs(delta) >= that.settings.mouseWheelSensitivity) { that.allowMouseWheelScroll = false; setTimeout(function() { that.allowMouseWheelScroll = true; }, 500); if (delta <= -that.settings.mouseWheelSensitivity) if (that.settings.mouseWheelTarget === 'page') that.nextPage(); else that.nextPanel(); else if (delta >= that.settings.mouseWheelSensitivity) if (that.settings.mouseWheelTarget === 'page') that.previousPage(); else that.previousPanel(); } }); }, destroyMouseWheel: function() { this.off(this.mouseWheelEventType + '.' + NS); }, mouseWheelDefaults: { mouseWheel: true, mouseWheelSensitivity: 10, mouseWheelTarget: 'panel' } }; $.AccordionSlider.addModule('MouseWheel', MouseWheel, 'accordion'); })(window, jQuery); /* Retina module for Accordion Slider Checks if a high resolution image was specified and replaces the default image with the high DPI one */ ;(function(window, $) { "use strict"; var NS = $.AccordionSlider.namespace; var Retina = { initRetina: function() { var that = this; // check if the current display supports high PPI if (this._isRetina() === false) return; // check if the Lazy Loading module is enabled and overwrite its loading method // if not, check all images from the accordion if (typeof this._loadImage !== 'undefined') { this._loadImage = this._loadRetinaImage; } else { this.on('update.Retina.' + NS, $.proxy(this._checkRetinaImages, this)); } }, _isRetina: function() { if (window.devicePixelRatio >= 2) return true; if (window.matchMedia && (window.matchMedia("(-webkit-min-device-pixel-ratio: 2),(min-resolution: 2dppx)").matches)) return true; return false; }, _checkRetinaImages: function() { var that = this; this.off('update.Retina.' + NS); $.each(this.panels, function(index, element) { var $panel = element.$panel; if (typeof $panel.attr('data-loaded') === 'undefined') { $panel.attr('data-loaded', true); $panel.find('img').each(function() { var image = $(this); that._loadRetinaImage(image, element); }); } }); }, _loadRetinaImage: function(image, panel) { var retinaFound = false, newImagePath = ''; // check if there is a retina image specified if (typeof image.attr('data-retina') !== 'undefined') { retinaFound = true; newImagePath = image.attr('data-retina'); image.removeAttr('data-retina'); } // check if there is a lazy loaded, non-retina, image specified if (typeof image.attr('data-src') !== 'undefined') { if (retinaFound === false) newImagePath = image.attr('data-src'); image.removeAttr('data-src'); } // replace the image if (newImagePath !== '') { // create a new image element var newImage = $(new Image()); // copy the class(es) and inline style newImage.attr('class', image.attr('class')); newImage.attr('style', image.attr('style')); // copy the data attributes $.each(image.data(), function(name, value) { newImage.attr('data-' + name, value); }); // copy the width and height attributes if they exist if (typeof image.attr('width') !== 'undefined') newImage.attr('width', image.attr('width')); if (typeof image.attr('height') !== 'undefined') newImage.attr('height', image.attr('height')); if (typeof image.attr('alt') !== 'undefined') newImage.attr('alt', image.attr('alt')); if (typeof image.attr('title') !== 'undefined') newImage.attr('title', image.attr('title')); // assign the source of the image newImage.attr('src', newImagePath); // add the new image in the same container and remove the older image newImage.insertAfter(image); image.remove(); } }, destroyRetina: function() { } }; $.AccordionSlider.addModule('Retina', Retina, 'accordion'); })(window, jQuery); /* Smart Video module for Accordion Slider Adds automatic control for several video players and providers */ ;(function(window, $) { "use strict"; var NS = $.AccordionSlider.namespace, // detect the current browser name and version userAgent = window.navigator.userAgent.toLowerCase(); var SmartVideo = { initSmartVideo: function() { this._setupVideos(); }, _setupVideos: function() { var that = this; // find all video elements from the accordion, instantiate the SmartVideo for each of the video, // and trigger the set actions for the videos' events this.$accordion.find('.as-video').each(function() { var video = $(this); video.videoController(); video.on('videoPlay.SmartVideo', function() { if (that.settings.playVideoAction === 'stopAutoplay' && typeof that.stopAutoplay !== 'undefined') { that.stopAutoplay(); that.settings.autoplay = false; } var eventObject = {type: 'videoPlay', video: video}; that.trigger(eventObject); if ($.isFunction(that.settings.videoPlay)) that.settings.videoPlay.call(that, eventObject); }); video.on('videoPause.SmartVideo', function() { if (that.settings.pauseVideoAction === 'startAutoplay' && typeof that.startAutoplay !== 'undefined') { that.startAutoplay(); that.settings.autoplay = true; } var eventObject = {type: 'videoPause', video: video}; that.trigger(eventObject); if ($.isFunction(that.settings.videoPause)) that.settings.videoPause.call(that, eventObject); }); video.on('videoEnded.SmartVideo', function() { if (that.settings.endVideoAction === 'startAutoplay' && typeof that.startAutoplay !== 'undefined') { that.startAutoplay(); that.settings.autoplay = true; } else if (that.settings.endVideoAction === 'nextPanel') { that.nextPanel(); } else if (that.settings.endVideoAction === 'replayVideo') { video.videoController('replay'); } var eventObject = {type: 'videoEnd', video: video}; that.trigger(eventObject); if ($.isFunction(that.settings.videoEnd)) that.settings.videoEnd.call(that, eventObject); }); }); // when a panel opens, check to see if there are video actions associated // with the opening an closing of individual panels this.on('panelOpen.SmartVideo.' + NS, function(event) { // handle the video from the closed panel if (event.previousIndex !== -1 && that.$panelsContainer.find('.as-panel').eq(event.previousIndex).find('.as-video').length !== 0) { var previousVideo = that.$panelsContainer.find('.as-panel').eq(event.previousIndex).find('.as-video'); if (that.settings.closePanelVideoAction === 'stopVideo') previousVideo.videoController('stop'); else if (that.settings.closePanelVideoAction === 'pauseVideo') previousVideo.videoController('pause'); } // handle the video from the opened panel if (that.$panelsContainer.find('.as-panel').eq(event.index).find('.as-video').length !== 0) { var currentVideo = that.$panelsContainer.find('.as-panel').eq(event.index).find('.as-video'); if (that.settings.openPanelVideoAction === 'playVideo') currentVideo.videoController('play'); } }); // when all panels close, check to see if there is a video in the // previously opened panel and handle it this.on('panelsClose.SmartVideo.' + NS, function(event) { // handle the video from the closed panel if (event.previousIndex !== -1 && that.$panelsContainer.find('.as-panel').eq(event.previousIndex).find('.as-video').length !== 0) { var previousVideo = that.$panelsContainer.find('.as-panel').eq(event.previousIndex).find('.as-video'); if (that.settings.closePanelVideoAction === 'stopVideo') previousVideo.videoController('stop'); else if (that.settings.closePanelVideoAction === 'pauseVideo') previousVideo.videoController('pause'); } }); }, destroySmartVideo: function() { this.$accordion.find('.as-video').each(function() { var video = $(this); video.off('SmartVideo'); $(this).videoController('destroy'); }); this.off('panelOpen.SmartVideo.' + NS); this.off('panelsClose.SmartVideo.' + NS); }, smartVideoDefaults: { openPanelVideoAction: 'playVideo', closePanelVideoAction: 'pauseVideo', playVideoAction: 'stopAutoplay', pauseVideoAction: 'none', endVideoAction: 'none', videoPlay: function() {}, videoPause: function() {}, videoEnd: function() {} } }; $.AccordionSlider.addModule('SmartVideo', SmartVideo, 'accordion'); })(window, jQuery); // Video Controller jQuery plugin // Creates a universal controller for multiple video types and providers ;(function( $ ) { "use strict"; // Check if an iOS device is used. // This information is important because a video can not be // controlled programmatically unless the user has started the video manually. var isIOS = window.navigator.userAgent.match( /(iPad|iPhone|iPod)/g ) ? true : false; var VideoController = function( instance, options ) { this.$video = $( instance ); this.options = options; this.settings = {}; this.player = null; this._init(); }; VideoController.prototype = { _init: function() { this.settings = $.extend( {}, this.defaults, this.options ); var that = this, players = $.VideoController.players, videoID = this.$video.attr( 'id' ); // Loop through the available video players // and check if the targeted video element is supported by one of the players. // If a compatible type is found, store the video type. for ( var name in players ) { if ( typeof players[ name ] !== 'undefined' && players[ name ].isType( this.$video ) ) { this.player = new players[ name ]( this.$video ); break; } } // Return if the player could not be instantiated if ( this.player === null ) { return; } // Add event listeners var events = [ 'ready', 'start', 'play', 'pause', 'ended' ]; $.each( events, function( index, element ) { var event = 'video' + element.charAt( 0 ).toUpperCase() + element.slice( 1 ); that.player.on( element, function() { that.trigger({ type: event, video: videoID }); if ( $.isFunction( that.settings[ event ] ) ) { that.settings[ event ].call( that, { type: event, video: videoID } ); } }); }); }, play: function() { if ( isIOS === true && this.player.isStarted() === false || this.player.getState() === 'playing' ) { return; } this.player.play(); }, stop: function() { if ( isIOS === true && this.player.isStarted() === false || this.player.getState() === 'stopped' ) { return; } this.player.stop(); }, pause: function() { if ( isIOS === true && this.player.isStarted() === false || this.player.getState() === 'paused' ) { return; } this.player.pause(); }, replay: function() { if ( isIOS === true && this.player.isStarted() === false ) { return; } this.player.replay(); }, on: function( type, callback ) { return this.$video.on( type, callback ); }, off: function( type ) { return this.$video.off( type ); }, trigger: function( data ) { return this.$video.triggerHandler( data ); }, destroy: function() { if ( this.player.isStarted() === true ) { this.stop(); } this.player.off( 'ready' ); this.player.off( 'start' ); this.player.off( 'play' ); this.player.off( 'pause' ); this.player.off( 'ended' ); this.$video.removeData( 'videoController' ); }, defaults: { videoReady: function() {}, videoStart: function() {}, videoPlay: function() {}, videoPause: function() {}, videoEnded: function() {} } }; $.VideoController = { players: {}, addPlayer: function( name, player ) { this.players[ name ] = player; } }; $.fn.videoController = function( options ) { var args = Array.prototype.slice.call( arguments, 1 ); return this.each(function() { // Instantiate the video controller or call a function on the current instance if ( typeof $( this ).data( 'videoController' ) === 'undefined' ) { var newInstance = new VideoController( this, options ); // Store a reference to the instance created $( this ).data( 'videoController', newInstance ); } else if ( typeof options !== 'undefined' ) { var currentInstance = $( this ).data( 'videoController' ); // Check the type of argument passed if ( typeof currentInstance[ options ] === 'function' ) { currentInstance[ options ].apply( currentInstance, args ); } else { $.error( options + ' does not exist in videoController.' ); } } }); }; // Base object for the video players var Video = function( video ) { this.$video = video; this.player = null; this.ready = false; this.started = false; this.state = ''; this.events = $({}); this._init(); }; Video.prototype = { _init: function() {}, play: function() {}, pause: function() {}, stop: function() {}, replay: function() {}, isType: function() {}, isReady: function() { return this.ready; }, isStarted: function() { return this.started; }, getState: function() { return this.state; }, on: function( type, callback ) { return this.events.on( type, callback ); }, off: function( type ) { return this.events.off( type ); }, trigger: function( data ) { return this.events.triggerHandler( data ); } }; // YouTube video var YoutubeVideoHelper = { youtubeAPIAdded: false, youtubeVideos: [] }; var YoutubeVideo = function( video ) { this.init = false; var youtubeAPILoaded = window.YT && window.YT.Player; if ( typeof youtubeAPILoaded !== 'undefined' ) { Video.call( this, video ); } else { YoutubeVideoHelper.youtubeVideos.push({ 'video': video, 'scope': this }); if ( YoutubeVideoHelper.youtubeAPIAdded === false ) { YoutubeVideoHelper.youtubeAPIAdded = true; var tag = document.createElement( 'script' ); tag.src = "//www.youtube.com/player_api"; var firstScriptTag = document.getElementsByTagName( 'script' )[0]; firstScriptTag.parentNode.insertBefore( tag, firstScriptTag ); window.onYouTubePlayerAPIReady = function() { $.each( YoutubeVideoHelper.youtubeVideos, function( index, element ) { Video.call( element.scope, element.video ); }); }; } } }; YoutubeVideo.prototype = new Video(); YoutubeVideo.prototype.constructor = YoutubeVideo; $.VideoController.addPlayer( 'YoutubeVideo', YoutubeVideo ); YoutubeVideo.isType = function( video ) { if ( video.is( 'iframe' ) ) { var src = video.attr( 'src' ); if ( src.indexOf( 'youtube.com' ) !== -1 || src.indexOf( 'youtu.be' ) !== -1 ) { return true; } } return false; }; YoutubeVideo.prototype._init = function() { this.init = true; this._setup(); }; YoutubeVideo.prototype._setup = function() { var that = this; // Get a reference to the player this.player = new YT.Player( this.$video[0], { events: { 'onReady': function() { that.trigger({ type: 'ready' }); that.ready = true; }, 'onStateChange': function( event ) { switch ( event.data ) { case YT.PlayerState.PLAYING: if (that.started === false) { that.started = true; that.trigger({ type: 'start' }); } that.state = 'playing'; that.trigger({ type: 'play' }); break; case YT.PlayerState.PAUSED: that.state = 'paused'; that.trigger({ type: 'pause' }); break; case YT.PlayerState.ENDED: that.state = 'ended'; that.trigger({ type: 'ended' }); break; } } } }); }; YoutubeVideo.prototype.play = function() { var that = this; if ( this.ready === true ) { this.player.playVideo(); } else { var timer = setInterval(function() { if ( that.ready === true ) { clearInterval( timer ); that.player.playVideo(); } }, 100 ); } }; YoutubeVideo.prototype.pause = function() { // On iOS, simply pausing the video can make other videos unresponsive // so we stop the video instead. if ( isIOS === true ) { this.stop(); } else { this.player.pauseVideo(); } }; YoutubeVideo.prototype.stop = function() { this.player.seekTo( 1 ); this.player.stopVideo(); this.state = 'stopped'; }; YoutubeVideo.prototype.replay = function() { this.player.seekTo( 1 ); this.player.playVideo(); }; YoutubeVideo.prototype.on = function( type, callback ) { var that = this; if ( this.init === true ) { Video.prototype.on.call( this, type, callback ); } else { var timer = setInterval(function() { if ( that.init === true ) { clearInterval( timer ); Video.prototype.on.call( that, type, callback ); } }, 100 ); } }; // Vimeo video var VimeoVideoHelper = { vimeoAPIAdded: false, vimeoVideos: [] }; var VimeoVideo = function( video ) { this.init = false; if ( typeof window.Vimeo !== 'undefined' ) { Video.call( this, video ); } else { VimeoVideoHelper.vimeoVideos.push({ 'video': video, 'scope': this }); if ( VimeoVideoHelper.vimeoAPIAdded === false ) { VimeoVideoHelper.vimeoAPIAdded = true; var tag = document.createElement('script'); tag.src = "//player.vimeo.com/api/player.js"; var firstScriptTag = document.getElementsByTagName( 'script' )[0]; firstScriptTag.parentNode.insertBefore( tag, firstScriptTag ); var checkVimeoAPITimer = setInterval(function() { if ( typeof window.Vimeo !== 'undefined' ) { clearInterval( checkVimeoAPITimer ); $.each( VimeoVideoHelper.vimeoVideos, function( index, element ) { Video.call( element.scope, element.video ); }); } }, 100 ); } } }; VimeoVideo.prototype = new Video(); VimeoVideo.prototype.constructor = VimeoVideo; $.VideoController.addPlayer( 'VimeoVideo', VimeoVideo ); VimeoVideo.isType = function( video ) { if ( video.is( 'iframe' ) ) { var src = video.attr('src'); if ( src.indexOf( 'vimeo.com' ) !== -1 ) { return true; } } return false; }; VimeoVideo.prototype._init = function() { this.init = true; this._setup(); }; VimeoVideo.prototype._setup = function() { var that = this; // Get a reference to the player this.player = new Vimeo.Player( this.$video[0] ); that.ready = true; that.trigger({ type: 'ready' }); that.player.on( 'play', function() { if ( that.started === false ) { that.started = true; that.trigger({ type: 'start' }); } that.state = 'playing'; that.trigger({ type: 'play' }); }); that.player.on( 'pause', function() { that.state = 'paused'; that.trigger({ type: 'pause' }); }); that.player.on( 'ended', function() { that.state = 'ended'; that.trigger({ type: 'ended' }); }); }; VimeoVideo.prototype.play = function() { var that = this; if ( this.ready === true ) { this.player.play(); } else { var timer = setInterval(function() { if ( that.ready === true ) { clearInterval( timer ); that.player.play(); } }, 100 ); } }; VimeoVideo.prototype.pause = function() { this.player.pause(); }; VimeoVideo.prototype.stop = function() { var that = this; this.player.setCurrentTime( 0 ).then( function() { that.player.pause(); that.state = 'stopped'; }); }; VimeoVideo.prototype.replay = function() { var that = this; this.player.setCurrentTime( 0 ).then( function() { that.player.play(); }); }; VimeoVideo.prototype.on = function( type, callback ) { var that = this; if ( this.init === true ) { Video.prototype.on.call( this, type, callback ); } else { var timer = setInterval(function() { if ( that.init === true ) { clearInterval( timer ); Video.prototype.on.call( that, type, callback ); } }, 100 ); } }; // HTML5 video var HTML5Video = function( video ) { Video.call( this, video ); }; HTML5Video.prototype = new Video(); HTML5Video.prototype.constructor = HTML5Video; $.VideoController.addPlayer( 'HTML5Video', HTML5Video ); HTML5Video.isType = function( video ) { if ( video.is( 'video' ) && video.hasClass( 'video-js' ) === false && video.hasClass( 'sublime' ) === false ) { return true; } return false; }; HTML5Video.prototype._init = function() { var that = this; // Get a reference to the player this.player = this.$video[0]; this.ready = true; this.player.addEventListener( 'play', function() { if ( that.started === false ) { that.started = true; that.trigger({ type: 'start' }); } that.state = 'playing'; that.trigger({ type: 'play' }); }); this.player.addEventListener( 'pause', function() { that.state = 'paused'; that.trigger({ type: 'pause' }); }); this.player.addEventListener( 'ended', function() { that.state = 'ended'; that.trigger({ type: 'ended' }); }); }; HTML5Video.prototype.play = function() { this.player.play(); }; HTML5Video.prototype.pause = function() { this.player.pause(); }; HTML5Video.prototype.stop = function() { this.player.currentTime = 0; this.player.pause(); this.state = 'stopped'; }; HTML5Video.prototype.replay = function() { this.player.currentTime = 0; this.player.play(); }; // VideoJS video var VideoJSVideo = function( video ) { Video.call( this, video ); }; VideoJSVideo.prototype = new Video(); VideoJSVideo.prototype.constructor = VideoJSVideo; $.VideoController.addPlayer( 'VideoJSVideo', VideoJSVideo ); VideoJSVideo.isType = function( video ) { if ( ( typeof video.attr( 'data-videojs-id' ) !== 'undefined' || video.hasClass( 'video-js' ) ) && typeof videojs !== 'undefined' ) { return true; } return false; }; VideoJSVideo.prototype._init = function() { var that = this, videoID = this.$video.hasClass( 'video-js' ) ? this.$video.attr( 'id' ) : this.$video.attr( 'data-videojs-id' ); this.player = videojs( videoID ); this.player.ready(function() { that.ready = true; that.trigger({ type: 'ready' }); that.player.on( 'play', function() { if ( that.started === false ) { that.started = true; that.trigger({ type: 'start' }); } that.state = 'playing'; that.trigger({ type: 'play' }); }); that.player.on( 'pause', function() { that.state = 'paused'; that.trigger({ type: 'pause' }); }); that.player.on( 'ended', function() { that.state = 'ended'; that.trigger({ type: 'ended' }); }); }); }; VideoJSVideo.prototype.play = function() { this.player.play(); }; VideoJSVideo.prototype.pause = function() { this.player.pause(); }; VideoJSVideo.prototype.stop = function() { this.player.currentTime( 0 ); this.player.pause(); this.state = 'stopped'; }; VideoJSVideo.prototype.replay = function() { this.player.currentTime( 0 ); this.player.play(); }; // Sublime video var SublimeVideo = function( video ) { Video.call( this, video ); }; SublimeVideo.prototype = new Video(); SublimeVideo.prototype.constructor = SublimeVideo; $.VideoController.addPlayer( 'SublimeVideo', SublimeVideo ); SublimeVideo.isType = function( video ) { if ( video.hasClass( 'sublime' ) && typeof sublime !== 'undefined' ) { return true; } return false; }; SublimeVideo.prototype._init = function() { var that = this; sublime.ready(function() { // Get a reference to the player that.player = sublime.player( that.$video.attr( 'id' ) ); that.ready = true; that.trigger({ type: 'ready' }); that.player.on( 'play', function() { if ( that.started === false ) { that.started = true; that.trigger({ type: 'start' }); } that.state = 'playing'; that.trigger({ type: 'play' }); }); that.player.on( 'pause', function() { that.state = 'paused'; that.trigger({ type: 'pause' }); }); that.player.on( 'stop', function() { that.state = 'stopped'; that.trigger({ type: 'stop' }); }); that.player.on( 'end', function() { that.state = 'ended'; that.trigger({ type: 'ended' }); }); }); }; SublimeVideo.prototype.play = function() { this.player.play(); }; SublimeVideo.prototype.pause = function() { this.player.pause(); }; SublimeVideo.prototype.stop = function() { this.player.stop(); }; SublimeVideo.prototype.replay = function() { this.player.stop(); this.player.play(); }; // JWPlayer video var JWPlayerVideo = function( video ) { Video.call( this, video ); }; JWPlayerVideo.prototype = new Video(); JWPlayerVideo.prototype.constructor = JWPlayerVideo; $.VideoController.addPlayer( 'JWPlayerVideo', JWPlayerVideo ); JWPlayerVideo.isType = function( video ) { if ( ( typeof video.attr( 'data-jwplayer-id' ) !== 'undefined' || video.hasClass( 'jwplayer' ) || video.find( "object[data*='jwplayer']" ).length !== 0 ) && typeof jwplayer !== 'undefined') { return true; } return false; }; JWPlayerVideo.prototype._init = function() { var that = this, videoID; if ( this.$video.hasClass( 'jwplayer' ) ) { videoID = this.$video.attr( 'id' ); } else if ( typeof this.$video.attr( 'data-jwplayer-id' ) !== 'undefined' ) { videoID = this.$video.attr( 'data-jwplayer-id'); } else if ( this.$video.find( "object[data*='jwplayer']" ).length !== 0 ) { videoID = this.$video.find( 'object' ).attr( 'id' ); } // Get a reference to the player this.player = jwplayer( videoID ); this.player.onReady(function() { that.ready = true; that.trigger({ type: 'ready' }); that.player.onPlay(function() { if ( that.started === false ) { that.started = true; that.trigger({ type: 'start' }); } that.state = 'playing'; that.trigger({ type: 'play' }); }); that.player.onPause(function() { that.state = 'paused'; that.trigger({ type: 'pause' }); }); that.player.onComplete(function() { that.state = 'ended'; that.trigger({ type: 'ended' }); }); }); }; JWPlayerVideo.prototype.play = function() { this.player.play( true ); }; JWPlayerVideo.prototype.pause = function() { this.player.pause( true ); }; JWPlayerVideo.prototype.stop = function() { this.player.stop(); this.state = 'stopped'; }; JWPlayerVideo.prototype.replay = function() { this.player.seek( 0 ); this.player.play( true ); }; })( jQuery ); /* Swap Background module for Accordion Slider Allows a different image to be displayed as the panel's background when the panel is selected */ ;(function(window, $) { "use strict"; var NS = $.AccordionSlider.namespace; var SwapBackgroundHelper = { cssTransitions: null, cssTransitionEndEvents: 'transitionend webkitTransitionEnd oTransitionEnd msTransitionEnd', checkCSSTransitions: function() { if (this.cssTransitions !== null) return this.cssTransitions; var element = document.body || document.documentElement, elementStyle = element.style; if (typeof elementStyle.transition !== 'undefined' || typeof elementStyle.WebkitTransition !== 'undefined' || typeof elementStyle.MozTransition !== 'undefined' || typeof elementStyle.OTransition !== 'undefined') this.cssTransitions = true; else this.cssTransitions = false; return this.cssTransitions; } }; var SwapBackground = { initSwapBackground: function() { var that = this; this.on('panelOpen.SwapBackground.' + NS, function(event) { // get the currently opened panel var panel = that.getPanelAt(event.index), background = panel.$panel.find('.as-background'), opened = panel.$panel.find('.as-background-opened'); // fade in the opened content if (opened.length !== 0) { opened.css({'visibility': 'visible', 'opacity': 0}); that._fadeInBackground(opened); if (background.length !== 0 && that.settings.fadeOutBackground === true) that._fadeOutBackground(background); } if (event.previousIndex !== -1 && event.index !== event.previousIndex) { // get the previously opened panel var previousPanel = that.getPanelAt(event.previousIndex), previousBackground = previousPanel.$panel.find('.as-background'), previousOpened = previousPanel.$panel.find('.as-background-opened'); // fade out the opened content if (previousOpened.length !== 0) { that._fadeOutBackground(previousOpened); if (previousBackground.length !== 0 && that.settings.fadeOutBackground === true) that._fadeInBackground(previousBackground); } } }); this.on('panelsClose.SwapBackground.' + NS, function(event) { if (event.previousIndex === -1) return; // get the previously opened panel var panel = that.getPanelAt(event.previousIndex), background = panel.$panel.find('.as-background'), opened = panel.$panel.find('.as-background-opened'); // fade out the opened content if (opened.length !== 0) { that._fadeOutBackground(opened); if (background.length !== 0 && that.settings.fadeOutBackground === true) that._fadeInBackground(background); } }); }, _fadeInBackground: function(target) { var duration = this.settings.swapBackgroundDuration; target.css({'visibility': 'visible'}); if (SwapBackgroundHelper.checkCSSTransitions() === true) { // remove the transition property after the animation completes target.off(SwapBackgroundHelper.cssTransitionEndEvents).on(SwapBackgroundHelper.cssTransitionEndEvents, function( event ) { if ( event.target !== event.currentTarget ) { return; } target.off(SwapBackgroundHelper.cssTransitionEndEvents); target.css({'transition': ''}); }); setTimeout(function() { target.css({'opacity': 1, 'transition': 'all ' + duration / 1000 + 's'}); }, 100); } else { target.stop().animate({'opacity': 1}, duration); } }, _fadeOutBackground: function(target) { var duration = this.settings.swapBackgroundDuration; if (SwapBackgroundHelper.checkCSSTransitions() === true) { // remove the transition property and make the image invisible after the animation completes target.off(SwapBackgroundHelper.cssTransitionEndEvents).on(SwapBackgroundHelper.cssTransitionEndEvents, function( event ) { if ( event.target !== event.currentTarget ) { return; } target.off(SwapBackgroundHelper.cssTransitionEndEvents); target.css({'visibility': 'hidden', 'transition': ''}); }); setTimeout(function() { target.css({'opacity': 0, 'transition': 'all ' + duration / 1000 + 's'}); }, 100); } else { target.stop().animate({'opacity': 0}, duration, function() { target.css({'visibility': 'hidden'}); }); } }, destroySwapBackground: function() { this.off('panelOpen.SwapBackground.' + NS); this.off('panelsClose.SwapBackground.' + NS); }, swapBackgroundDefaults: { swapBackgroundDuration: 700, fadeOutBackground: false } }; $.AccordionSlider.addModule('SwapBackground', SwapBackground, 'accordion'); })(window, jQuery); /* TouchSwipe module for Accordion Slider Adds touch swipe support for scrolling through pages */ ;(function(window, $) { "use strict"; var NS = $.AccordionSlider.namespace; var TouchSwipe = { touchStartPoint: {x: 0, y: 0}, touchEndPoint: {x: 0, y: 0}, touchDistance: {x: 0, y: 0}, touchStartPosition: 0, isTouchMoving: false, touchSwipeEvents: { startEvent: '', moveEvent: '', endEvent: '' }, // Indicates whether the previous 'start' event was a 'touchstart' or 'mousedown' previousStartEvent: '', initTouchSwipe: function() { var that = this; // check if touch swipe is enabled if (this.settings.touchSwipe === false) return; this.touchSwipeEvents.startEvent = 'touchstart' + '.' + NS + ' mousedown' + '.' + NS; this.touchSwipeEvents.moveEvent = 'touchmove' + '.' + NS + ' mousemove' + '.' + NS; this.touchSwipeEvents.endEvent = 'touchend' + '.' + this.uniqueId + '.' + NS + ' mouseup' + '.' + this.uniqueId + '.' + NS; this.$panelsContainer.on(this.touchSwipeEvents.startEvent, $.proxy(this._onTouchStart, this)); this.$panelsContainer.on( 'dragstart.' + NS, function( event ) { event.preventDefault(); }); // prevent 'click' events unless there is intention for a 'click' this.$panelsContainer.find( 'a' ).on( 'click.' + NS, function( event ) { if ( that.$accordion.hasClass( 'as-swiping' ) ) { event.preventDefault(); } }); // re-enable links this.$panelsContainer.on( 'touchstart.' + NS, function( event ) { $(this).find('[data-disabledlink]').css('pointer-events', '').removeAttr('data-disabledlink'); }); // prevent 'tap' events unless the panel is opened this.$panelsContainer.find( 'a' ).on( 'touchend.' + NS, function( event ) { if ( $(this).parents('.as-panel').hasClass( 'as-opened' ) === false ) { $(this).css('pointer-events', 'none'); $(this).attr('data-disabledlink', 'true'); } }); this.on('update.TouchSwipe.' + NS, function() { // add or remove grabbing icon if (that.getTotalPages() > 1) that.$panelsContainer.addClass('as-grab'); else that.$panelsContainer.removeClass('as-grab'); }); }, _onTouchStart: function(event) { // return if a 'mousedown' event follows a 'touchstart' event if ( event.type === 'mousedown' && this.previousStartEvent === 'touchstart' ) { this.previousStartEvent = event.type; return; } // assign the new 'start' event this.previousStartEvent = event.type; var that = this, eventObject = typeof event.originalEvent.touches !== 'undefined' ? event.originalEvent.touches[0] : event.originalEvent; // disable dragging if the element is set to allow selections if ($(event.target).closest('.as-selectable').length >= 1 || (typeof event.originalEvent.touches === 'undefined' && this.getTotalPages() === 1)) return; // get the initial position of the mouse pointer and the initial position of the panels' container this.touchStartPoint.x = eventObject.pageX || eventObject.clientX; this.touchStartPoint.y = eventObject.pageY || eventObject.clientY; this.touchStartPosition = parseInt(this.$panelsContainer.css(this.positionProperty), 10); // clear the distance this.touchDistance.x = this.touchDistance.y = 0; // listen for 'move' and 'end' events this.$panelsContainer.on(this.touchSwipeEvents.moveEvent, $.proxy(this._onTouchMove, this)); $(document).on(this.touchSwipeEvents.endEvent, $.proxy(this._onTouchEnd, this)); // swap grabbing icons this.$panelsContainer.removeClass('as-grab').addClass('as-grabbing'); }, _onTouchMove: function(event) { var eventObject = typeof event.originalEvent.touches !== 'undefined' ? event.originalEvent.touches[0] : event.originalEvent; // indicate that the 'move' event is being fired this.isTouchMoving = true; if ( this.$accordion.hasClass('as-swiping') === false ) { this.$accordion.addClass('as-swiping'); } // get the current position of the mouse pointer this.touchEndPoint.x = eventObject.pageX || eventObject.clientX; this.touchEndPoint.y = eventObject.pageY || eventObject.clientY; // calculate the distance of the movement on both axis this.touchDistance.x = this.touchEndPoint.x - this.touchStartPoint.x; this.touchDistance.y = this.touchEndPoint.y - this.touchStartPoint.y; var distance = this.settings.orientation === 'horizontal' ? this.touchDistance.x : this.touchDistance.y, oppositeDistance = this.settings.orientation === 'horizontal' ? this.touchDistance.y : this.touchDistance.x; if (Math.abs(distance) <= Math.abs(oppositeDistance) || this.getTotalPages() === 1 || (this.getTotalPages() > 1 && this.currentPage === this.getTotalPages() - 1)) return; event.preventDefault(); // get the current position of panels' container var currentPanelsPosition = parseInt(this.$panelsContainer.css(this.positionProperty), 10); // reduce the movement speed if the panels' container is outside its bounds if ((currentPanelsPosition >= 0 && this.currentPage === 0) || (currentPanelsPosition <= - this.totalPanelsSize + this.totalSize && this.currentPage === this.getTotalPages() - 1)) distance = distance * 0.2; // move the panels' container this.$panelsContainer.css(this.positionProperty, this.touchStartPosition + distance); }, _onTouchEnd: function(event) { var that = this; // remove the 'move' and 'end' listeners this.$panelsContainer.off(this.touchSwipeEvents.moveEvent); $(document).off(this.touchSwipeEvents.endEvent); // swap grabbing icons this.$panelsContainer.removeClass('as-grabbing').addClass('as-grab'); // check if there is intention for a tap if (this.isTouchMoving === false || this.isTouchMoving === true && Math.abs(this.touchDistance.x) < 10 && Math.abs(this.touchDistance.y) < 10) { var index = $(event.target).parents('.as-panel').index(); if (typeof event.originalEvent.touches !== 'undefined' && index !== this.currentIndex && index !== -1 && this.openPanelOn !== 'never') { this.openPanel(index); } } // remove the 'as-swiping' class with a delay, to allow // other event listeners (i.e. click) to check the existance // of the swipe event. if ( this.$accordion.hasClass('as-swiping') ) { setTimeout(function() { that.$accordion.removeClass('as-swiping'); }, 100); } // return if there was no movement and re-enable click events on links if (this.isTouchMoving === false) { return; } this.isTouchMoving = false; var noScrollAnimObj = {}; noScrollAnimObj[this.positionProperty] = this.touchStartPosition; // set the accordion's page based on the distance of the movement and the accordion's settings if (this.settings.orientation === 'horizontal') { if (this.touchDistance.x > this.settings.touchSwipeThreshold) { if (this.currentPage > 0) { this.previousPage(); } else { this.$panelsContainer.stop().animate(noScrollAnimObj, 300); } } else if (- this.touchDistance.x > this.settings.touchSwipeThreshold) { if (this.currentPage < this.getTotalPages() - 1) { this.nextPage(); } else { this.gotoPage(this.currentPage); } } else if (Math.abs(this.touchDistance.x) < this.settings.touchSwipeThreshold) { this.$panelsContainer.stop().animate(noScrollAnimObj, 300); } } else if (this.settings.orientation === 'vertical') { if (this.touchDistance.y > this.settings.touchSwipeThreshold) { if (this.currentPage > 0) { this.previousPage(); } else { this.$panelsContainer.stop().animate(noScrollAnimObj, 300); } } else if (- this.touchDistance.y > this.settings.touchSwipeThreshold) { if (this.currentPage < this.getTotalPages() - 1) { this.nextPage(); } else { this.$panelsContainer.animate(noScrollAnimObj, 300); } } else if (Math.abs(this.touchDistance.y) < this.settings.touchSwipeThreshold) { this.$panelsContainer.stop().animate(noScrollAnimObj, 300); } } }, destroyTouchSwipe: function() { this.$panelsContainer.off( 'dragstart.' + NS ); this.$panelsContainer.find( 'a' ).off( 'click.' + NS ); this.$panelsContainer.find( 'a' ).off( 'touchstart.' + NS ); this.$panelsContainer.off(this.touchSwipeEvents.startEvent); this.$panelsContainer.off(this.touchSwipeEvents.moveEvent); $(document).off(this.touchSwipeEvents.endEvent); this.off('update.TouchSwipe.' + NS); this.$panelsContainer.removeClass('as-grab'); }, touchSwipeDefaults: { touchSwipe: true, touchSwipeThreshold: 50 } }; $.AccordionSlider.addModule('TouchSwipe', TouchSwipe, 'accordion'); })(window, jQuery); /* XML module for Accordion Slider Creates the panels based on XML data */ ;(function(window, $) { "use strict"; var NS = $.AccordionSlider.namespace, // detect the current browser name and version userAgent = window.navigator.userAgent.toLowerCase(), rmsie = /(msie) ([\w.]+)/, browserDetect = rmsie.exec(userAgent) || [], browserName = browserDetect[1]; var XML = { XMLDataAttributesMap : { 'width': 'data-width', 'height': 'data-height', 'depth': 'data-depth', 'position': 'data-position', 'horizontal': 'data-horizontal', 'vertical': 'data-vertical', 'showTransition': 'data-show-transition', 'showOffset': 'data-show-offset', 'showDelay': 'data-show-delay', 'showDuration': 'data-show-duration', 'showEasing': 'data-show-easing', 'hideTransition': 'data-hide-transition', 'hideOffset': 'data-', 'hideDelay': 'data-hide-delay', 'hideDuration': 'data-hide-duration', 'hideEasing': 'data-hide-easing' }, initXML: function() { if (this.settings.XMLSource !== null) this.updateXML(); }, updateXML: function() { var that = this; // clear existing content and data this.removePanels(); this.$panelsContainer.empty(); this.off('XMLReady.' + NS); // parse the XML data and construct the panels this.on('XMLReady.' + NS, function(event) { var xmlData = $(event.xmlData); // check if lazy loading is enabled var lazyLoading = xmlData.find('accordion')[0].attributes.lazyLoading; if (typeof lazyLoading !== 'undefined') lazyLoading = lazyLoading.nodeValue; // parse the panel node xmlData.find('panel').each(function() { var xmlPanel = $(this), xmlBackground = xmlPanel.find('background'), xmlBackgroundRetina = xmlPanel.find('backgroundRetina'), xmlBackgroundLink = xmlPanel.find('backgroundLink'), xmlBackgroundOpened = xmlPanel.find('backgroundOpened'), xmlBackgroundOpenedRetina = xmlPanel.find('backgroundOpenedRetina'), xmlBackgroundOpenedLink = xmlPanel.find('backgroundOpenedLink'), xmlLayer = xmlPanel.find('layer'), backgroundLink, backgroundOpenedLink; // create the panel element var panel = $('
').appendTo(that.$panelsContainer); // create the background image and link if (xmlBackgroundLink.length >= 1) { backgroundLink = $(''); $.each(xmlBackgroundLink[0].attributes, function(index, attribute) { backgroundLink.attr(attribute.nodeName, attribute.nodeValue); }); backgroundLink.appendTo(panel); } if (xmlBackground.length >= 1) { var background = $(''); if (typeof lazyLoading !== 'undefined') background.attr({'src': lazyLoading, 'data-src': xmlBackground.text()}); else background.attr({'src': xmlBackground.text()}); if (xmlBackgroundRetina.length >= 1) background.attr({'data-retina': xmlBackgroundRetina.text()}); $.each(xmlBackground[0].attributes, function(index, attribute) { background.attr(attribute.nodeName, attribute.nodeValue); }); background.appendTo(xmlBackgroundLink.length ? backgroundLink : panel); } // create the background image and link for the opened state of the panel if (xmlBackgroundOpenedLink.length >= 1) { backgroundOpenedLink = $(''); $.each(xmlBackgroundOpenedLink[0].attributes, function(index, attribute) { backgroundOpenedLink.attr(attribute.nodeName, attribute.nodeValue); }); backgroundOpenedLink.appendTo(panel); } if (xmlBackgroundOpened.length >= 1) { var backgroundOpened = $(''); if (typeof lazyLoading !== 'undefined') backgroundOpened.attr({'src': lazyLoading, 'data-src': xmlBackgroundOpened.text()}); else backgroundOpened.attr({'src': xmlBackgroundOpened.text()}); if (xmlBackgroundOpenedRetina.length >= 1) backgroundOpened.attr({'data-retina': xmlBackgroundOpenedRetina.text()}); $.each(xmlBackgroundOpened[0].attributes, function(index, attribute) { backgroundOpened.attr(attribute.nodeName, attribute.nodeValue); }); backgroundOpened.appendTo(xmlBackgroundOpenedLink.length ? backgroundOpenedLink : panel); } // parse the layer(s) if (xmlLayer.length >= 1) $.each(xmlLayer, function() { var xmlLayerItem = $(this), classes = '', dataAttributes = '', parent = panel; // parse the attributes specified for the layer and extract the classes and data attributes $.each(xmlLayerItem[0].attributes, function(attributeIndex, attribute) { if (attribute.nodeName === 'style') { var classList = attribute.nodeValue.split(' '); $.each(classList, function(classIndex, className) { classes += ' as-' + className; }); } else { dataAttributes += ' ' + that.XMLDataAttributesMap[attribute.nodeName] + '="' + attribute.nodeValue + '"'; } }); // create the layer element var layer = $('
'); // check if the layer is a container for other layers and if so // assign it a unique class in order to target it when the child layers // are added if (xmlLayerItem.find('layer').length >= 1) { var id = new Date().valueOf(); xmlLayerItem.attr('parentID', id); layer.attr('class', layer.attr('class') + ' ' + id); } else { layer.html(xmlLayerItem.text()); } // check if the XML parent element is a layer and // find the corresponding HTML parent if (xmlLayerItem.parent().is('layer')) parent = panel.find('.' + xmlLayerItem.parent().attr('parentID')); // add the layer to its parent layer.appendTo(parent); }); }); that.update(); }); // load the XML this._loadXML(); }, _loadXML: function() { var that = this; if (this.settings.XMLSource.slice(-4) === '.xml') { $.ajax({type: 'GET', url: this.settings.XMLSource, dataType: browserName === 'msie' ? 'text' : 'xml', success: function(result) { var xmlData; if (browserName === 'msie') { xmlData = new ActiveXObject('Microsoft.XMLDOM'); xmlData.async = false; xmlData.loadXML(result); } else { xmlData = result; } that.trigger({type: 'XMLReady.' + NS, xmlData: xmlData}); } }); } else { var xmlData = $.parseXML(this.settings.XMLSource); that.trigger({type: 'XMLReady.' + NS, xmlData: xmlData}); } }, destroyXML: function() { this.off('XMLReady.' + NS); }, XMLDefaults: { XMLSource: null } }; $.AccordionSlider.addModule('XML', XML, 'accordion'); })(window, jQuery);