/** * Owl carousel * @version 2.0.0 * @author Bartosz Wojciechowski * @license The MIT License (MIT) * @todo Lazy Load Icon * @todo prevent animationend bubling * @todo itemsScaleUp * @todo Test Zepto * @todo stagePadding calculate wrong active classes */ ;(function($, window, document, undefined) { var item, dom, width, num, pos, drag, speed, state, e; /** * Template for the data of each item respectively its DOM element. * @private */ item = { index: false, indexAbs: false, posLeft: false, clone: false, active: false, loaded: false, lazyLoad: false, current: false, width: false, center: false, page: false, hasVideo: false, playVideo: false }; /** * Template for the references to DOM elements, those with `$` sign are `jQuery` objects. * @private */ dom = { el: null, // main element $el: null, // jQuery main element stage: null, // stage $stage: null, // jQuery stage oStage: null, // outer stage $oStage: null, // $ outer stage $items: null, // all items, clones and originals included $oItems: null, // original items $cItems: null, // cloned items only $content: null }; /** * Template for the widths of some elements. * @private */ width = { el: 0, stage: 0, item: 0, prevWindow: 0, cloneLast: 0 }; /** * Template for counting to some properties. * @private */ num = { items: 0, oItems: 0, cItems: 0, active: 0, merged: [] }; /** * Template for status information about drag and touch events. * @private */ drag = { start: 0, startX: 0, startY: 0, current: 0, currentX: 0, currentY: 0, offsetX: 0, offsetY: 0, distance: null, startTime: 0, endTime: 0, updatedX: 0, targetEl: null }; /** * Template for some status informations. * @private */ state = { isTouch: false, isScrolling: false, isSwiping: false, direction: false, inMotion: false }; /** * Event functions references. * @private */ e = { _onDragStart: null, _onDragMove: null, _onDragEnd: null, _transitionEnd: null, _resizer: null, _responsiveCall: null, _goToLoop: null, _checkVisibile: null }; /** * Creates a carousel. * @class The Owl Carousel. * @public * @param {HTMLElement|jQuery} element - The element to create the carousel for. * @param {Object} [options] - The options */ function Owl(element, options) { // add basic Owl information to dom element element.owlCarousel = { 'name': 'Owl Carousel', 'author': 'Bartosz Wojciechowski', 'version': '2.0.0-beta.2.1' }; /** * Current settings for the carousel. * @protected */ this.settings = null; /** * * @protected * @todo Must be dosumented. */ this.options = $.extend({}, Owl.Defaults, options); /** * Template for the data of each item. * @protected */ this.itemData = $.extend({}, item); /** * Contains references to DOM elements, those with `$` sign are `jQuery` objects. * @protected */ this.dom = $.extend({}, dom); /** * Caches the widths of some elements. * @protected */ this.width = $.extend({}, width); /** * Caches some count informations. * @protected */ this.num = $.extend({}, num); /** * Caches informations about drag and touch events. */ this.drag = $.extend({}, drag); /** * Caches some status informations. * @protected */ this.state = $.extend({}, state); /** * @protected * @todo Must be documented */ this.e = $.extend({}, e); /** * References to the running plugins of this carousel. * @protected */ this.plugins = {}; /** * Currently suppressed events to prevent them from beeing retriggered. * @protected */ this._supress = {}; /** * The absolute current position. * @protected */ this._current = null; /** * The animation speed in milliseconds. * @protected */ this._speed = null; /** * The coordinates of all items in pixel. */ this._coordinates = null; this.dom.el = element; this.dom.$el = $(element); for (var plugin in Owl.Plugins) { this.plugins[plugin[0].toLowerCase() + plugin.slice(1)] = new Owl.Plugins[plugin](this); } this.init(); } /** * Default options for the carousel. * @public */ Owl.Defaults = { items: 3, loop: false, center: false, mouseDrag: true, touchDrag: true, pullDrag: true, freeDrag: false, margin: 0, stagePadding: 0, merge: false, mergeFit: true, autoWidth: false, startPosition: 0, smartSpeed: 250, fluidSpeed: false, dragEndSpeed: false, responsive: {}, responsiveRefreshRate: 200, responsiveBaseElement: window, responsiveClass: false, fallbackEasing: 'swing', info: false, nestedItemSelector: false, itemElement: 'div', stageElement: 'div', // Classes and Names themeClass: 'owl-theme', baseClass: 'owl-carousel', itemClass: 'owl-item', centerClass: 'center', activeClass: 'active' }; /** * Contains all registered plugins. * @public */ Owl.Plugins = {}; /** * Initializes the carousel. * @protected */ Owl.prototype.init = function() { // Update options.items on given size this.setResponsiveOptions(); this.trigger('initialize'); // Add base class if (!this.dom.$el.hasClass(this.settings.baseClass)) { this.dom.$el.addClass(this.settings.baseClass); } // Add theme class if (!this.dom.$el.hasClass(this.settings.themeClass)) { this.dom.$el.addClass(this.settings.themeClass); } // Add theme class if (this.settings.rtl) { this.dom.$el.addClass('owl-rtl'); } // Check support this.browserSupport(); if (this.settings.autoWidth && this.state.imagesLoaded !== true) { var imgs, nestedSelector, width; imgs = this.dom.$el.find('img'); nestedSelector = this.settings.nestedItemSelector ? '.' + this.settings.nestedItemSelector : undefined; width = this.dom.$el.children(nestedSelector).width(); if (imgs.length && width <= 0) { this.preloadAutoWidthImages(imgs); return false; } } // Get and store window width // iOS safari likes to trigger unnecessary resize event this.width.prevWindow = this.viewport(); // create stage object this.createStage(); // Append local content this.fetchContent(); // attach generic events this.eventsCall(); // attach generic events this.internalEvents(); this.dom.$el.addClass('owl-loading'); this.refresh(true); this.dom.$el.removeClass('owl-loading').addClass('owl-loaded'); this.trigger('initialized'); // attach custom control events this.addTriggerableEvents(); }; /** * Sets responsive options. * @protected */ Owl.prototype.setResponsiveOptions = function() { if (!this.options.responsive) { this.settings = $.extend({}, this.options); } else { var viewport = this.viewport(), overwrites = this.options.responsive, match = -1; $.each(overwrites, function(breakpoint) { if (breakpoint <= viewport && breakpoint > match) { match = Number(breakpoint); } }); this.settings = $.extend({}, this.options, overwrites[match]); delete this.settings.responsive; // Responsive Class if (this.settings.responsiveClass) { this.dom.$el.attr('class', function(i, c) { return c.replace(/\b owl-responsive-\S+/g, ''); }).addClass('owl-responsive-' + match); } } }; /** * Updates option logic if necessery. * @protected */ Owl.prototype.optionsLogic = function() { // Toggle Center class this.dom.$el.toggleClass('owl-center', this.settings.center); // if items number is less than in body if (this.settings.loop && this.num.oItems < this.settings.items) { this.settings.loop = false; } if (this.settings.autoWidth) { this.settings.stagePadding = false; this.settings.merge = false; } }; /** * Creates stage and outer-stage elements. * @protected */ Owl.prototype.createStage = function() { var oStage = document.createElement('div'), stage = document.createElement(this.settings.stageElement); oStage.className = 'owl-stage-outer'; stage.className = 'owl-stage'; oStage.appendChild(stage); this.dom.el.appendChild(oStage); this.dom.oStage = oStage; this.dom.$oStage = $(oStage); this.dom.stage = stage; this.dom.$stage = $(stage); oStage = null; stage = null; }; /** * Creates an item container. * @protected * @returns {jQuery} - The item container. */ Owl.prototype.createItemContainer = function() { var item = document.createElement(this.settings.itemElement); item.className = this.settings.itemClass; return $(item); }; /** * Fetches the content. * @protected */ Owl.prototype.fetchContent = function(extContent) { if (extContent) { this.dom.$content = (extContent instanceof jQuery) ? extContent : $(extContent); } else if (this.settings.nestedItemSelector) { this.dom.$content = this.dom.$el.find('.' + this.settings.nestedItemSelector).not('.owl-stage-outer'); } else { this.dom.$content = this.dom.$el.children().not('.owl-stage-outer'); } // content length this.num.oItems = this.dom.$content.length; // init Structure if (this.num.oItems !== 0) { this.initStructure(); } }; /** * Initializes the content struture. * @protected */ Owl.prototype.initStructure = function() { this.createNormalStructure(); }; /** * Creates small/mid weight content structure. * @protected * @todo This results in a poor performance, * but this is due to the approach of completely * rebuild the existing DOM tree from scratch, * rather to use them. The effort to implement * this with a good performance, while maintaining * the original approach is disproportionate. */ Owl.prototype.createNormalStructure = function() { var i, $item; for (i = 0; i < this.num.oItems; i++) { $item = this.createItemContainer(); this.initializeItemContainer($item, this.dom.$content[i]); this.dom.$stage.append($item); } this.dom.$content = null; }; /** * Creates custom content structure. * @protected */ Owl.prototype.createCustomStructure = function(howManyItems) { var i, $item; for (i = 0; i < howManyItems; i++) { $item = this.createItemContainer(); this.createItemContainerData($item); this.dom.$stage.append($item); } }; /** * Initializes item container with provided content. * @protected * @param {jQuery} item - The item that has to be filled. * @param {HTMLElement|jQuery|string} content - The content that fills the item. */ Owl.prototype.initializeItemContainer = function(item, content) { this.trigger('change', { property: { name: 'item', value: item } }); this.createItemContainerData(item); item.append(content); this.trigger('changed', { property: { name: 'item', value: item } }); }; /** * Creates item container data. * @protected * @param {jQuery} item - The item for which the data are to be set. * @param {jQuery} [source] - The item whose data are to be copied. */ Owl.prototype.createItemContainerData = function(item, source) { var data = $.extend({}, this.itemData); if (source) { $.extend(data, source.data('owl-item')); } item.data('owl-item', data); }; /** * Clones an item container. * @protected * @param {jQuery} item - The item to clone. * @returns {jQuery} - The cloned item. */ Owl.prototype.cloneItemContainer = function(item) { var $clone = item.clone(true, true).addClass('cloned'); // somehow data references the same object this.createItemContainerData($clone, $clone); $clone.data('owl-item').clone = true; return $clone; }; /** * Updates original items index data. * @protected */ Owl.prototype.updateLocalContent = function() { var k, item; this.dom.$oItems = this.dom.$stage.find('.' + this.settings.itemClass).filter(function() { return $(this).data('owl-item').clone === false; }); this.num.oItems = this.dom.$oItems.length; // update index on original items for (k = 0; k < this.num.oItems; k++) { item = this.dom.$oItems.eq(k); item.data('owl-item').index = k; } }; /** * Creates clones for infinity loop. * @protected */ Owl.prototype.loopClone = function() { if (!this.settings.loop || this.num.oItems < this.settings.items) { return false; } var append, prepend, i, items = this.settings.items, last = this.num.oItems - 1; // if neighbour margin then add one more duplicat if (this.settings.stagePadding && this.settings.items === 1) { items += 1; } this.num.cItems = items * 2; for (i = 0; i < items; i++) { append = this.cloneItemContainer(this.dom.$oItems.eq(i)); prepend = this.cloneItemContainer(this.dom.$oItems.eq(last - i)); this.dom.$stage.append(append); this.dom.$stage.prepend(prepend); } this.dom.$cItems = this.dom.$stage.find('.' + this.settings.itemClass).filter(function() { return $(this).data('owl-item').clone === true; }); }; /** * Update cloned elements. * @protected */ Owl.prototype.reClone = function() { // remove cloned items if (this.dom.$cItems !== null) { // && (this.num.oItems !== 0 && // this.num.oItems <= // this.settings.items)){ this.dom.$cItems.remove(); this.dom.$cItems = null; this.num.cItems = 0; } if (!this.settings.loop) { return; } // generete new elements this.loopClone(); }; /** * Updates all items index data. * @protected */ Owl.prototype.calculate = function() { var i, j, elMinusMargin, dist, allItems, iWidth, mergeNumber, posLeft = 0, fullWidth = 0; // element width minus neighbour this.width.el = this.dom.$el.width() - (this.settings.stagePadding * 2); // to check this.width.view = this.dom.$el.width(); // calculate width minus addition margins elMinusMargin = this.width.el - (this.settings.margin * (this.settings.items === 1 ? 0 : this.settings.items - 1)); // calculate element width and item width this.width.el = this.width.el + this.settings.margin; this.width.item = ((elMinusMargin / this.settings.items) + this.settings.margin).toFixed(3); this.dom.$items = this.dom.$stage.find('.owl-item'); this.num.items = this.dom.$items.length; // change to autoWidths if (this.settings.autoWidth) { this.dom.$items.css('width', ''); } // Set grid array this._coordinates = []; this.num.merged = []; // item distances if (this.settings.rtl) { dist = this.settings.center ? -((this.width.el) / 2) : 0; } else { dist = this.settings.center ? (this.width.el) / 2 : 0; } this.width.mergeStage = 0; // Calculate items positions for (i = 0; i < this.num.items; i++) { // check merged items if (this.settings.merge) { mergeNumber = this.dom.$items.eq(i).find('[data-merge]').attr('data-merge') || 1; if (this.settings.mergeFit && mergeNumber > this.settings.items) { mergeNumber = this.settings.items; } this.num.merged.push(parseInt(mergeNumber)); this.width.mergeStage += this.width.item * this.num.merged[i]; } else { this.num.merged.push(1); } iWidth = this.width.item * this.num.merged[i]; // autoWidth item size if (this.settings.autoWidth) { iWidth = this.dom.$items.eq(i).width() + this.settings.margin; if (this.settings.rtl) { this.dom.$items[i].style.marginLeft = this.settings.margin + 'px'; } else { this.dom.$items[i].style.marginRight = this.settings.margin + 'px'; } } // push item position into array this._coordinates.push(dist); // update item data this.dom.$items.eq(i).data('owl-item').posLeft = posLeft; this.dom.$items.eq(i).data('owl-item').width = iWidth; // dist starts from middle of stage if center // posLeft always starts from 0 if (this.settings.rtl) { dist += iWidth; posLeft += iWidth; } else { dist -= iWidth; posLeft -= iWidth; } fullWidth -= Math.abs(iWidth); // update position if center if (this.settings.center) { this._coordinates[i] = !this.settings.rtl ? this._coordinates[i] - (iWidth / 2) : this._coordinates[i] + (iWidth / 2); } } if (this.settings.autoWidth) { this.width.stage = this.settings.center ? Math.abs(fullWidth) : Math.abs(dist); } else { this.width.stage = Math.abs(fullWidth); } // update indexAbs on all items allItems = this.num.oItems + this.num.cItems; for (j = 0; j < allItems; j++) { this.dom.$items.eq(j).data('owl-item').indexAbs = j; } // Recalculate grid this.setSizes(); }; /** * Set sizes on elements from `collectData`. * @protected * @todo CRAZY FIX!!! Doublecheck this! */ Owl.prototype.setSizes = function() { // show neighbours if (this.settings.stagePadding !== false) { this.dom.oStage.style.paddingLeft = this.settings.stagePadding + 'px'; this.dom.oStage.style.paddingRight = this.settings.stagePadding + 'px'; } // if(this.width.stagePrev > this.width.stage){ if (this.settings.rtl) { window.setTimeout($.proxy(function() { this.dom.stage.style.width = this.width.stage + 'px'; }, this), 0); } else { this.dom.stage.style.width = this.width.stage + 'px'; } for (var i = 0; i < this.num.items; i++) { // Set items width if (!this.settings.autoWidth) { this.dom.$items[i].style.width = this.width.item - (this.settings.margin) + 'px'; } // add margin if (this.settings.rtl) { this.dom.$items[i].style.marginLeft = this.settings.margin + 'px'; } else { this.dom.$items[i].style.marginRight = this.settings.margin + 'px'; } if (this.num.merged[i] !== 1 && !this.settings.autoWidth) { this.dom.$items[i].style.width = (this.width.item * this.num.merged[i]) - (this.settings.margin) + 'px'; } } // save prev stage size this.width.stagePrev = this.width.stage; }; /** * Updates all data by calling `refresh`. * @protected */ Owl.prototype.responsive = function() { if (!this.num.oItems) { return false; } // If El width hasnt change then stop responsive var elChanged = this.isElWidthChanged(); if (!elChanged) { return false; } if (this.trigger('resize').isDefaultPrevented()) { return false; } this.state.responsive = true; this.refresh(); this.state.responsive = false; this.trigger('resized'); }; /** * Refreshes the carousel primarily for adaptive purposes. * @public */ Owl.prototype.refresh = function() { var current = this.dom.$oItems && this.dom.$oItems.eq(this.normalize(this.current(), true)); this.trigger('refresh'); // Update Options for given width this.setResponsiveOptions(); // update info about local content this.updateLocalContent(); // udpate options this.optionsLogic(); // if no items then stop if (this.num.oItems === 0) { return false; } // Hide and Show methods helps here to set a proper widths. // This prevents Scrollbar to be calculated in stage width this.dom.$stage.addClass('owl-refresh'); // Remove clones and generate new ones this.reClone(); // calculate this.calculate(); // aaaand show. this.dom.$stage.removeClass('owl-refresh'); if (!current) { this.dom.oStage.scrollLeft = 0; this.reset(this.dom.$oItems.eq(0).data('owl-item').indexAbs); } else { this.reset(current.data('owl-item').indexAbs); // fix that } this.state.orientation = window.orientation; this.watchVisibility(); this.trigger('refreshed'); }; /** * Updates information about current state of items (visibile, hidden, active, etc.). * @protected */ Owl.prototype.updateActiveItems = function() { this.trigger('change', { property: { name: 'items', value: this.dom.$items } }); var i, j, item, ipos, iwidth, outsideView; // clear states for (i = 0; i < this.num.items; i++) { this.dom.$items.eq(i).data('owl-item').active = false; this.dom.$items.eq(i).data('owl-item').current = false; this.dom.$items.eq(i).removeClass(this.settings.activeClass).removeClass(this.settings.centerClass); } this.num.active = 0; padding = this.settings.stagePadding * 2; stageX = this.coordinates(this.current()) + padding; view = this.settings.rtl ? this.width.view : -this.width.view; for (j = 0; j < this.num.items; j++) { item = this.dom.$items.eq(j); ipos = item.data('owl-item').posLeft; iwidth = item.data('owl-item').width; outsideView = this.settings.rtl ? ipos - iwidth - padding : ipos - iwidth + padding; if ((this.op(ipos, '<=', stageX) && (this.op(ipos, '>', stageX + view))) || (this.op(outsideView, '<', stageX) && this.op(outsideView, '>', stageX + view))) { this.num.active++; item.data('owl-item').active = true; item.data('owl-item').current = true; item.addClass(this.settings.activeClass); if (!this.settings.lazyLoad) { item.data('owl-item').loaded = true; } if (this.settings.loop) { this.updateClonedItemsState(item.data('owl-item').index); } } } if (this.settings.center) { this.dom.$items.eq(this.current()).addClass(this.settings.centerClass).data('owl-item').center = true; } this.trigger('changed', { property: { name: 'items', value: this.dom.$items } }); }; /** * Sets current state on sibilings items for center. * @protected */ Owl.prototype.updateClonedItemsState = function(activeIndex) { // find cloned center var center, $el, i; if (this.settings.center) { center = this.dom.$items.eq(this.current()).data('owl-item').index; } for (i = 0; i < this.num.items; i++) { $el = this.dom.$items.eq(i); if ($el.data('owl-item').index === activeIndex) { $el.data('owl-item').current = true; if ($el.data('owl-item').index === center) { $el.addClass(this.settings.centerClass); } } } }; /** * Save internal event references and add event based functions. * @protected */ Owl.prototype.eventsCall = function() { // Save events references this.e._onDragStart = $.proxy(function(e) { this.onDragStart(e); }, this); this.e._onDragMove = $.proxy(function(e) { this.onDragMove(e); }, this); this.e._onDragEnd = $.proxy(function(e) { this.onDragEnd(e); }, this); this.e._transitionEnd = $.proxy(function(e) { this.transitionEnd(e); }, this); this.e._resizer = $.proxy(function() { this.responsiveTimer(); }, this); this.e._responsiveCall = $.proxy(function() { this.responsive(); }, this); this.e._preventClick = $.proxy(function(e) { this.preventClick(e); }, this); }; /** * Checks window `resize` event. * @protected */ Owl.prototype.responsiveTimer = function() { if (this.viewport() === this.width.prevWindow) { return false; } window.clearTimeout(this.resizeTimer); this.resizeTimer = window.setTimeout(this.e._responsiveCall, this.settings.responsiveRefreshRate); this.width.prevWindow = this.viewport(); }; /** * Checks for touch/mouse drag options and add necessery event handlers. * @protected */ Owl.prototype.internalEvents = function() { var isTouch = isTouchSupport(), isTouchIE = isTouchSupportIE(); if (isTouch && !isTouchIE) { this.dragType = [ 'touchstart', 'touchmove', 'touchend', 'touchcancel' ]; } else if (isTouch && isTouchIE) { this.dragType = [ 'MSPointerDown', 'MSPointerMove', 'MSPointerUp', 'MSPointerCancel' ]; } else { this.dragType = [ 'mousedown', 'mousemove', 'mouseup' ]; } if ((isTouch || isTouchIE) && this.settings.touchDrag) { // touch cancel event this.on(document, this.dragType[3], this.e._onDragEnd); } else { // firefox startdrag fix - addeventlistener doesnt work here :/ this.dom.$stage.on('dragstart', function() { return false; }); if (this.settings.mouseDrag) { // disable text select this.dom.stage.onselectstart = function() { return false; }; } else { // enable text select this.dom.$el.addClass('owl-text-select-on'); } } // Catch transitionEnd event if (this.transitionEndVendor) { this.on(this.dom.stage, this.transitionEndVendor, this.e._transitionEnd, false); } // Responsive if (this.settings.responsive !== false) { this.on(window, 'resize', this.e._resizer, false); } this.dragEvents(); }; /** * Triggers event handlers for drag events. * @protected */ Owl.prototype.dragEvents = function() { if (this.settings.touchDrag && (this.dragType[0] === 'touchstart' || this.dragType[0] === 'MSPointerDown')) { this.on(this.dom.stage, this.dragType[0], this.e._onDragStart, false); } else if (this.settings.mouseDrag && this.dragType[0] === 'mousedown') { this.on(this.dom.stage, this.dragType[0], this.e._onDragStart, false); } else { this.off(this.dom.stage, this.dragType[0], this.e._onDragStart); } }; /** * Handles touchstart/mousedown event. * @protected * @param {Event} event - The event arguments. */ Owl.prototype.onDragStart = function(event) { var ev, isTouchEvent, pageX, pageY, animatedPos; ev = event.originalEvent || event || window.event; // prevent right click if (ev.which === 3) { return false; } if (this.dragType[0] === 'mousedown') { this.dom.$stage.addClass('owl-grab'); } this.trigger('drag'); this.drag.startTime = new Date().getTime(); this.speed(0); this.state.isTouch = true; this.state.isScrolling = false; this.state.isSwiping = false; this.drag.distance = 0; // if is 'touchstart' isTouchEvent = ev.type === 'touchstart'; pageX = isTouchEvent ? event.targetTouches[0].pageX : (ev.pageX || ev.clientX); pageY = isTouchEvent ? event.targetTouches[0].pageY : (ev.pageY || ev.clientY); // get stage position left this.drag.offsetX = this.dom.$stage.position().left - this.settings.stagePadding; this.drag.offsetY = this.dom.$stage.position().top; if (this.settings.rtl) { this.drag.offsetX = this.dom.$stage.position().left + this.width.stage - this.width.el + this.settings.margin; } // catch position // ie to fix if (this.state.inMotion && this.support3d) { animatedPos = this.getTransformProperty(); this.drag.offsetX = animatedPos; this.animate(animatedPos); this.state.inMotion = true; } else if (this.state.inMotion && !this.support3d) { this.state.inMotion = false; return false; } this.drag.startX = pageX - this.drag.offsetX; this.drag.startY = pageY - this.drag.offsetY; this.drag.start = pageX - this.drag.startX; this.drag.targetEl = ev.target || ev.srcElement; this.drag.updatedX = this.drag.start; // to do/check // prevent links and images dragging; if (this.drag.targetEl.tagName === "IMG" || this.drag.targetEl.tagName === "A") { this.drag.targetEl.draggable = false; } this.on(document, this.dragType[1], this.e._onDragMove, false); this.on(document, this.dragType[2], this.e._onDragEnd, false); }; /** * Handles the touchmove/mousemove events. * @todo Simplify * @protected * @param {Event} event - The event arguments. */ Owl.prototype.onDragMove = function(event) { var ev, isTouchEvent, pageX, pageY, minValue, maxValue, pull; if (!this.state.isTouch) { return; } if (this.state.isScrolling) { return; } ev = event.originalEvent || event || window.event; // if is 'touchstart' isTouchEvent = ev.type == 'touchmove'; pageX = isTouchEvent ? ev.targetTouches[0].pageX : (ev.pageX || ev.clientX); pageY = isTouchEvent ? ev.targetTouches[0].pageY : (ev.pageY || ev.clientY); // Drag Direction this.drag.currentX = pageX - this.drag.startX; this.drag.currentY = pageY - this.drag.startY; this.drag.distance = this.drag.currentX - this.drag.offsetX; // Check move direction if (this.drag.distance < 0) { this.state.direction = this.settings.rtl ? 'right' : 'left'; } else if (this.drag.distance > 0) { this.state.direction = this.settings.rtl ? 'left' : 'right'; } // Loop if (this.settings.loop) { if (this.op(this.drag.currentX, '>', this.coordinates(this.minimum())) && this.state.direction === 'right') { this.drag.currentX -= (this.settings.center && this.coordinates(0)) - this.coordinates(this.num.oItems); } else if (this.op(this.drag.currentX, '<', this.coordinates(this.maximum())) && this.state.direction === 'left') { this.drag.currentX += (this.settings.center && this.coordinates(0)) - this.coordinates(this.num.oItems); } } else { // pull minValue = this.settings.rtl ? this.coordinates(this.maximum()) : this.coordinates(this.minimum()); maxValue = this.settings.rtl ? this.coordinates(this.minimum()) : this.coordinates(this.maximum()); pull = this.settings.pullDrag ? this.drag.distance / 5 : 0; this.drag.currentX = Math.max(Math.min(this.drag.currentX, minValue + pull), maxValue + pull); } // Lock browser if swiping horizontal if ((this.drag.distance > 8 || this.drag.distance < -8)) { if (ev.preventDefault !== undefined) { ev.preventDefault(); } else { ev.returnValue = false; } this.state.isSwiping = true; } this.drag.updatedX = this.drag.currentX; // Lock Owl if scrolling if ((this.drag.currentY > 16 || this.drag.currentY < -16) && this.state.isSwiping === false) { this.state.isScrolling = true; this.drag.updatedX = this.drag.start; } this.animate(this.drag.updatedX); }; /** * Handles the touchend/mouseup events. * @protected */ Owl.prototype.onDragEnd = function() { var compareTimes, distanceAbs, closest; if (!this.state.isTouch) { return; } if (this.dragType[0] === 'mousedown') { this.dom.$stage.removeClass('owl-grab'); } this.trigger('dragged'); // prevent links and images dragging; this.drag.targetEl.removeAttribute("draggable"); // remove drag event listeners this.state.isTouch = false; this.state.isScrolling = false; this.state.isSwiping = false; // to check if (this.drag.distance === 0 && this.state.inMotion !== true) { this.state.inMotion = false; return false; } // prevent clicks while scrolling this.drag.endTime = new Date().getTime(); compareTimes = this.drag.endTime - this.drag.startTime; distanceAbs = Math.abs(this.drag.distance); // to test if (distanceAbs > 3 || compareTimes > 300) { this.removeClick(this.drag.targetEl); } closest = this.closest(this.drag.updatedX); this.speed(this.settings.dragEndSpeed || this.settings.smartSpeed); this.current(closest); // if pullDrag is off then fire transitionEnd event manually when stick // to border if (!this.settings.pullDrag && this.drag.updatedX === this.coordinates(closest)) { this.transitionEnd(); } this.drag.distance = 0; this.off(document, this.dragType[1], this.e._onDragMove); this.off(document, this.dragType[2], this.e._onDragEnd); }; /** * Attaches `preventClick` to disable link while swipping. * @protected * @param {HTMLElement} [target] - The target of the `click` event. */ Owl.prototype.removeClick = function(target) { this.drag.targetEl = target; $(target).on('click.preventClick', this.e._preventClick); // to make sure click is removed: window.setTimeout(function() { $(target).off('click.preventClick'); }, 300); }; /** * Suppresses click event. * @protected * @param {Event} ev - The event arguments. */ Owl.prototype.preventClick = function(ev) { if (ev.preventDefault) { ev.preventDefault(); } else { ev.returnValue = false; } if (ev.stopPropagation) { ev.stopPropagation(); } $(ev.target).off('click.preventClick'); }; /** * Catches stage position while animate (only CSS3). * @protected * @returns */ Owl.prototype.getTransformProperty = function() { var transform, matrix3d; transform = window.getComputedStyle(this.dom.stage, null).getPropertyValue(this.vendorName + 'transform'); // var transform = this.dom.$stage.css(this.vendorName + 'transform') transform = transform.replace(/matrix(3d)?\(|\)/g, '').split(','); matrix3d = transform.length === 16; return matrix3d !== true ? transform[4] : transform[12]; }; /** * Gets absolute position of the closest item for a coordinate. * @protected * @param {Number} coordinate - The coordinate in pixel. * @return {Number} - The absolute position of the closest item. */ Owl.prototype.closest = function(coordinate) { var position = 0, pull = 30; if (!this.settings.freeDrag) { // check closest item $.each(this.coordinates(), $.proxy(function(index, value) { if (coordinate > value - pull && coordinate < value + pull) { position = index; } else if (this.op(coordinate, '<', value) && this.op(coordinate, '>', this.coordinates(index + 1) || value - this.width.el)) { position = this.state.direction === 'left' ? index + 1 : index; } }, this)); } if (!this.settings.loop) { // non loop boundries if (this.op(coordinate, '>', this.coordinates(this.minimum()))) { position = coordinate = this.minimum(); } else if (this.op(coordinate, '<', this.coordinates(this.maximum()))) { position = coordinate = this.maximum(); } } return position; }; /** * Animates the stage. * @public * @param {Number} coordinate - The coordinate in pixels. */ Owl.prototype.animate = function(coordinate) { this.trigger('translate'); this.state.inMotion = this.speed() > 0; if (this.support3d) { this.dom.$stage.css({ transform: 'translate3d(' + coordinate + 'px' + ',0px, 0px)', transition: (this.speed() / 1000) + 's' }); } else if (this.state.isTouch) { this.dom.$stage.css({ left: coordinate + 'px' }); } else { this.dom.$stage.animate({ left: coordinate }, this.speed() / 1000, this.settings.fallbackEasing, $.proxy(function() { if (this.state.inMotion) { this.transitionEnd(); } }, this)); } }; /** * Sets the absolute position of the current item. * @public * @param {Number} [position] - The new absolute position or nothing to leave it unchanged. * @returns {Number} - The absolute position of the current item. */ Owl.prototype.current = function(position) { if (position === undefined) { return this._current; } if (this.num.oItems === 0) { return undefined; } position = this.normalize(position); if (this._current === position) { this.animate(this.coordinates(this._current)); } else { var event = this.trigger('change', { property: { name: 'position', value: position } }); if (event.data !== undefined) { position = this.normalize(event.data); } this._current = position; this.animate(this.coordinates(this._current)); this.updateActiveItems(); this.trigger('changed', { property: { name: 'position', value: this._current } }); } return this._current; }; /** * Resets the absolute position of the current item. * @public * @param {Number} position - The absolute position of the new item. */ Owl.prototype.reset = function(position) { this.suppress([ 'change', 'changed' ]); this.speed(0); this.current(position); this.release([ 'change', 'changed' ]); }; /** * Normalizes an absolute position for an item. * @public * @param {Number} position - The absolute position to normalize. * @param {Boolean} [relative=false] - Whether to return a relative position or not. * @return {Number} - The normalized position. */ Owl.prototype.normalize = function(position, relative) { if (position === undefined || !this.dom.$items) { return undefined; } if (this.settings.loop) { var n = this.dom.$items.length; position = ((position % n) + n) % n; } else { position = Math.max(this.minimum(), Math.min(this.maximum(), position)); } return relative ? this.dom.$items.eq(position).data('owl-item').index : position; }; /** * Gets the absolute maximum position for an item. * @public * @returns {Number} */ Owl.prototype.maximum = function() { var maximum, width, settings = this.settings; if (!settings.loop && settings.center) { maximum = this.num.oItems - 1; } else if (!settings.loop && !settings.center) { maximum = this.num.oItems - settings.items; } else if (settings.loop || settings.center) { maximum = this.num.oItems + settings.items; } else if (settings.autoWidth || settings.merge) { revert = settings.rtl ? 1 : -1; width = this.dom.$stage.width() - this.$el.width(); $.each(this.coordinates(), function(index, coordinate) { if (coordinate * revert >= width) { return false; } maximum = index + 1; }); } else { throw 'Can not detect maximum absolute position.' } return maximum; }; /** * Gets the absolute minimum position for an item. * @public * @returns {Number} */ Owl.prototype.minimum = function() { return this.dom.$oItems.eq(0).data('owl-item').indexAbs; }; /** * Sets the current animation speed. * @public * @param {Number} [speed] - The animation speed in millisecondsor nothing to leave it unchanged. * @returns {Number} - The current animation speed in milliseconds. */ Owl.prototype.speed = function(speed) { if (speed !== undefined) { this._speed = speed; } return this._speed; }; /** * Gets the coordinate for an item. * @public * @param {Number} [position] - The absolute position of the item. * @returns {Number|Array.} - The coordinate of the item in pixel or all coordinates. */ Owl.prototype.coordinates = function(position) { return position !== undefined ? this._coordinates[position] : this._coordinates; }; /** * Calculates the speed for a translation. * @protected * @param {Number} from - The absolute position of the start item. * @param {Number} to - The absolute position of the target item. * @param {Number} [factor=undefined] - The time factor in milliseconds. * @returns {Number} - The time in milliseconds for the translation. */ Owl.prototype.duration = function(from, to, factor) { return Math.min(Math.max(Math.abs(to - from), 1), 6) * Math.abs((factor || this.settings.smartSpeed)); }; /** * Slides to the specified item. * @public * @param {Number} position - The position of the item. * @param {Number} [speed] - The time in milliseconds for the transition. */ Owl.prototype.to = function(position, speed) { if (this.settings.loop) { var distance = position - this.normalize(this.current(), true), revert = this.current(), before = this.current(), after = this.current() + distance, direction = before - after < 0 ? true : false; if (after < this.settings.items && direction === false) { revert = this.num.items - (this.settings.items - before) - this.settings.items; this.reset(revert); } else if (after >= this.num.items - this.settings.items && direction === true) { revert = before - this.num.oItems; this.reset(revert); } window.clearTimeout(this.e._goToLoop); this.e._goToLoop = window.setTimeout($.proxy(function() { this.speed(this.duration(this.current(), revert + distance, speed)); this.current(revert + distance); }, this), 30); } else { this.speed(this.duration(this.current(), position, speed)); this.current(position); } }; /** * Slides to the next item. * @public * @param {Number} [speed] - The time in milliseconds for the transition. */ Owl.prototype.next = function(speed) { speed = speed || false; this.to(this.normalize(this.current(), true) + 1, speed); }; /** * Slides to the previous item. * @public * @param {Number} [speed] - The time in milliseconds for the transition. */ Owl.prototype.prev = function(speed) { speed = speed || false; this.to(this.normalize(this.current(), true) - 1, speed); }; /** * Handles the end of an animation. * @protected * @param {Event} event - The event arguments. */ Owl.prototype.transitionEnd = function(event) { // if css2 animation then event object is undefined if (event !== undefined) { event.stopPropagation(); // Catch only owl-stage transitionEnd event var eventTarget = event.target || event.srcElement || event.originalTarget; if (eventTarget !== this.dom.stage) { return false; } } this.state.inMotion = false; this.trigger('translated'); }; /** * Checks if element width has changed * @protected * @returns {Booelan} */ Owl.prototype.isElWidthChanged = function() { var newElWidth = this.dom.$el.width() - this.settings.stagePadding, // to // check prevElWidth = this.width.el + this.settings.margin; return newElWidth !== prevElWidth; }; /** * Gets viewport width. * @protected * @return {Number} - The width in pixel. */ Owl.prototype.viewport = function() { var width; if (this.options.responsiveBaseElement !== window) { width = $(this.options.responsiveBaseElement).width(); } else if (window.innerWidth) { width = window.innerWidth; } else if (document.documentElement && document.documentElement.clientWidth) { width = document.documentElement.clientWidth; } else { throw 'Can not detect viewport width.'; } return width; }; /** * Replaces the current content. * @public * @param {HTMLElement|jQuery|String} content - The new content. */ Owl.prototype.insertContent = function(content) { this.dom.$stage.empty(); this.fetchContent(content); this.refresh(); }; /** * Adds an item. * @public * @param {HTMLElement|jQuery|String} content - The item content to add. * @param {Number} [position=0] - The position at which to insert the item. */ Owl.prototype.addItem = function(content, position) { var $item = this.createItemContainer(); position = position || 0; // wrap content this.initializeItemContainer($item, content); // if carousel is empty then append item if (this.dom.$oItems.length === 0) { this.dom.$stage.append($item); } else { // append item if (pos !== -1) { this.dom.$oItems.eq(position).before($item); } else { this.dom.$oItems.eq(position).after($item); } } // update and calculate carousel this.refresh(); }; /** * Removes an item. * @public * @param {Number} pos - The position of the item. */ Owl.prototype.removeItem = function(pos) { this.dom.$oItems.eq(pos).remove(); this.refresh(); }; /** * Adds triggerable events. * @protected */ Owl.prototype.addTriggerableEvents = function() { var handler = $.proxy(function(callback, event) { return $.proxy(function(e) { if (e.relatedTarget !== this) { this.suppress([ event ]); callback.apply(this, [].slice.call(arguments, 1)); this.release([ event ]); } }, this); }, this); $.each({ 'next': this.next, 'prev': this.prev, 'to': this.to, 'destroy': this.destroy, 'refresh': this.refresh, 'replace': this.insertContent, 'add': this.addItem, 'remove': this.removeItem }, $.proxy(function(event, callback) { this.dom.$el.on(event + '.owl.carousel', handler(callback, event + '.owl.carousel')); }, this)); }; /** * Watches the visibility of the carousel element. * @protected */ Owl.prototype.watchVisibility = function() { // test on zepto if (!isElVisible(this.dom.el)) { this.dom.$el.addClass('owl-hidden'); window.clearInterval(this.e._checkVisibile); this.e._checkVisibile = window.setInterval($.proxy(checkVisible, this), 500); } function isElVisible(el) { return el.offsetWidth > 0 && el.offsetHeight > 0; } function checkVisible() { if (isElVisible(this.dom.el)) { this.dom.$el.removeClass('owl-hidden'); this.refresh(); window.clearInterval(this.e._checkVisibile); } } }; /** * Preloads images with auto width. * @protected * @todo Still to test */ Owl.prototype.preloadAutoWidthImages = function(imgs) { var loaded, that, $el, img; loaded = 0; that = this; imgs.each(function(i, el) { $el = $(el); img = new Image(); img.onload = function() { loaded++; $el.attr('src', img.src); $el.css('opacity', 1); if (loaded >= imgs.length) { that.state.imagesLoaded = true; that.init(); } }; img.src = $el.attr('src') || $el.attr('data-src') || $el.attr('data-src-retina'); }); }; /** * Destroys the carousel. * @public */ Owl.prototype.destroy = function() { if (this.dom.$el.hasClass(this.settings.themeClass)) { this.dom.$el.removeClass(this.settings.themeClass); } if (this.settings.responsive !== false) { this.off(window, 'resize', this.e._resizer); } if (this.transitionEndVendor) { this.off(this.dom.stage, this.transitionEndVendor, this.e._transitionEnd); } for ( var i in this.plugins) { this.plugins[i].destroy(); } if (this.settings.mouseDrag || this.settings.touchDrag) { this.off(this.dom.stage, this.dragType[0], this.e._onDragStart); if (this.settings.mouseDrag) { this.off(document, this.dragType[3], this.e._onDragStart); } if (this.settings.mouseDrag) { this.dom.$stage.off('dragstart', function() { return false; }); this.dom.stage.onselectstart = function() { }; } } // Remove event handlers in the ".owl.carousel" namespace this.dom.$el.off('.owl'); if (this.dom.$cItems !== null) { this.dom.$cItems.remove(); } this.e = null; this.dom.$el.data('owlCarousel', null); delete this.dom.el.owlCarousel; this.dom.$stage.unwrap(); this.dom.$items.unwrap(); this.dom.$items.contents().unwrap(); this.dom = null; }; /** * Operators to calculate right-to-left and left-to-right. * @protected * @param {Number} [a] - The left side operand. * @param {String} [o] - The operator. * @param {Number} [b] - The right side operand. */ Owl.prototype.op = function(a, o, b) { var rtl = this.settings.rtl; switch (o) { case '<': return rtl ? a > b : a < b; case '>': return rtl ? a < b : a > b; case '>=': return rtl ? a <= b : a >= b; case '<=': return rtl ? a >= b : a <= b; default: break; } }; /** * Attaches to an internal event. * @protected * @param {HTMLElement} element - The event source. * @param {String} event - The event name. * @param {Function} listener - The event handler to attach. * @param {Boolean} capture - Wether the event should be handled at the capturing phase or not. */ Owl.prototype.on = function(element, event, listener, capture) { if (element.addEventListener) { element.addEventListener(event, listener, capture); } else if (element.attachEvent) { element.attachEvent('on' + event, listener); } }; /** * Detaches from an internal event. * @protected * @param {HTMLElement} element - The event source. * @param {String} event - The event name. * @param {Function} listener - The attached event handler to detach. * @param {Boolean} capture - Wether the attached event handler was registered as a capturing listener or not. */ Owl.prototype.off = function(element, event, listener, capture) { if (element.removeEventListener) { element.removeEventListener(event, listener, capture); } else if (element.detachEvent) { element.detachEvent('on' + event, listener); } }; /** * Triggers an public event. * @protected * @param {String} name - The event name. * @param {*} [data=null] - The event data. * @param {String} [namespace=.owl.carousel] - The event namespace. * @returns {Event} - The event arguments. */ Owl.prototype.trigger = function(name, data, namespace) { var status = { item: { count: this.num.oItems, index: this.current() } }, handler = $.camelCase( $.grep([ 'on', name, namespace ], function(v) { return v }) .join('-').toLowerCase() ), event = $.Event( [ name, 'owl', namespace || 'carousel' ].join('.').toLowerCase(), $.extend({ relatedTarget: this }, status, data) ); if (!this._supress[event.type]) { $.each(this.plugins, function(name, plugin) { if (plugin.onTrigger) { plugin.onTrigger(event); } }); this.dom.$el.trigger(event); if (typeof this.settings[handler] === 'function') { this.settings[handler].apply(this, event); } } return event; }; /** * Suppresses events. * @protected * @param {Array.} events - The events to suppress. */ Owl.prototype.suppress = function(events) { $.each(events, $.proxy(function(index, event) { this._supress[event] = true; }, this)); } /** * Releases suppressed events. * @protected * @param {Array.} events - The events to release. */ Owl.prototype.release = function(events) { $.each(events, $.proxy(function(index, event) { delete this._supress[event]; }, this)); } /** * Checks the availability of some browser features. * @protected */ Owl.prototype.browserSupport = function() { this.support3d = isPerspective(); if (this.support3d) { this.transformVendor = isTransform(); // take transitionend event name by detecting transition var endVendors = [ 'transitionend', 'webkitTransitionEnd', 'transitionend', 'oTransitionEnd' ]; this.transitionEndVendor = endVendors[isTransition()]; // take vendor name from transform name this.vendorName = this.transformVendor.replace(/Transform/i, ''); this.vendorName = this.vendorName !== '' ? '-' + this.vendorName.toLowerCase() + '-' : ''; } this.state.orientation = window.orientation; }; /** * Checks for CSS support. * @private * @param {Array} array - The CSS properties to check for. * @returns {Array} - Contains the supported CSS property name and its index or `false`. */ function isStyleSupported(array) { var p, s, fake = document.createElement('div'), list = array; for (p in list) { s = list[p]; if (typeof fake.style[s] !== 'undefined') { fake = null; return [ s, p ]; } } return [ false ]; } /** * Checks for CSS transition support. * @private * @todo Realy bad design * @returns {Number} */ function isTransition() { return isStyleSupported([ 'transition', 'WebkitTransition', 'MozTransition', 'OTransition' ])[1]; } /** * Checks for CSS transform support. * @private * @returns {String} The supported property name or false. */ function isTransform() { return isStyleSupported([ 'transform', 'WebkitTransform', 'MozTransform', 'OTransform', 'msTransform' ])[0]; } /** * Checks for CSS perspective support. * @private * @returns {String} The supported property name or false. */ function isPerspective() { return isStyleSupported([ 'perspective', 'webkitPerspective', 'MozPerspective', 'OPerspective', 'MsPerspective' ])[0]; } /** * Checks wether touch is supported or not. * @private * @returns {Boolean} */ function isTouchSupport() { return 'ontouchstart' in window || !!(navigator.msMaxTouchPoints); } /** * Checks wether touch is supported or not for IE. * @private * @returns {Boolean} */ function isTouchSupportIE() { return window.navigator.msPointerEnabled; } /** * The jQuery Plugin for the Owl Carousel * @public */ $.fn.owlCarousel = function(options) { return this.each(function() { if (!$(this).data('owlCarousel')) { $(this).data('owlCarousel', new Owl(this, options)); } }); }; /** * The constructor for the jQuery Plugin * @public */ $.fn.owlCarousel.Constructor = Owl; })(window.Zepto || window.jQuery, window, document); /** * LazyLoad Plugin * @version 2.0.0 * @author Bartosz Wojciechowski * @license The MIT License (MIT) */ ;(function($, window, document, undefined) { /** * Creates the lazy load plugin. * @class The Lazy Load Plugin * @param {Owl} scope - The Owl Carousel */ LazyLoad = function(scope) { this.owl = scope; this.owl.options = $.extend({}, LazyLoad.Defaults, this.owl.options); this.handlers = { 'changed.owl.carousel': $.proxy(function(e) { if (e.property.name == 'items' && e.property.value && !e.property.value.is(':empty')) { this.check(); } }, this) }; this.owl.dom.$el.on(this.handlers); }; /** * Default options. * @public */ LazyLoad.Defaults = { lazyLoad: false }; /** * Checks all items and if necessary, calls `preload`. * @protected */ LazyLoad.prototype.check = function() { var attr = window.devicePixelRatio > 1 ? 'data-src-retina' : 'data-src', src, img, i, $item; for (i = 0; i < this.owl.num.items; i++) { $item = this.owl.dom.$items.eq(i); if ($item.data('owl-item').current === true && $item.data('owl-item').loaded === false) { img = $item.find('.owl-lazy'); src = img.attr(attr); src = src || img.attr('data-src'); if (src) { img.css('opacity', '0'); this.preload(img, $item); } } } }; /** * Preloads the images of an item. * @protected * @param {jQuery} images - The images to load. * @param {jQuery} $item - The item for which the images are loaded. */ LazyLoad.prototype.preload = function(images, $item) { var $el, img, srcType; images.each($.proxy(function(i, el) { this.owl.trigger('load', null, 'lazy'); $el = $(el); img = new Image(); srcType = window.devicePixelRatio > 1 ? $el.attr('data-src-retina') : $el.attr('data-src'); srcType = srcType || $el.attr('data-src'); img.onload = $.proxy(function() { $item.data('owl-item').loaded = true; if ($el.is('img')) { $el.attr('src', img.src); } else { $el.css('background-image', 'url(' + img.src + ')'); } $el.css('opacity', 1); this.owl.trigger('loaded', null, 'lazy'); }, this); img.src = srcType; }, this)); }; /** * Destroys the plugin. * @public */ LazyLoad.prototype.destroy = function() { var handler, property; for (handler in this.handlers) { this.owl.dom.$el.off(handler, this.handlers[handler]); } for (property in Object.getOwnPropertyNames(this)) { typeof this[property] != 'function' && (this[property] = null); } }; $.fn.owlCarousel.Constructor.Plugins.lazyLoad = LazyLoad; })(window.Zepto || window.jQuery, window, document); /** * AutoHeight Plugin * @version 2.0.0 * @author Bartosz Wojciechowski * @license The MIT License (MIT) */ ;(function($, window, document, undefined) { /** * Creates the auto height plugin. * @class The Auto Height Plugin * @param {Owl} scope - The Owl Carousel */ AutoHeight = function(scope) { this.owl = scope; this.owl.options = $.extend({}, AutoHeight.Defaults, this.owl.options); this.handlers = { 'changed.owl.carousel': $.proxy(function(e) { if (e.property.name == 'position' && this.owl.settings.autoHeight){ this.setHeight(); } }, this) }; this.owl.dom.$el.on(this.handlers); }; /** * Default options. * @public */ AutoHeight.Defaults = { autoHeight: false, autoHeightClass: 'owl-height' }; /** * * @param {Boolean} callback - Whether * @returns {Boolean} */ AutoHeight.prototype.setHeight = function() { var loaded = this.owl.dom.$items.eq(this.owl.current()), stage = this.owl.dom.$oStage, iterations = 0, isLoaded; if (!this.owl.dom.$oStage.hasClass(this.owl.settings.autoHeightClass)) { this.owl.dom.$oStage.addClass(this.owl.settings.autoHeightClass); } isLoaded = window.setInterval(function() { iterations += 1; if (loaded.data('owl-item').loaded) { stage.height(loaded.height() + 'px'); clearInterval(isLoaded); } else if (iterations === 500) { clearInterval(isLoaded); } }, 100); }; AutoHeight.prototype.destroy = function() { var handler, property; for (handler in this.handlers) { this.owl.dom.$el.off(handler, this.handlers[handler]); } for (property in Object.getOwnPropertyNames(this)) { typeof this[property] != 'function' && (this[property] = null); } }; $.fn.owlCarousel.Constructor.Plugins.autoHeight = AutoHeight; })(window.Zepto || window.jQuery, window, document); /** * Video Plugin * @version 2.0.0 * @author Bartosz Wojciechowski * @license The MIT License (MIT) */ ;(function($, window, document, undefined) { /** * Creates the video plugin. * @class The Video Plugin * @param {Owl} scope - The Owl Carousel */ Video = function(scope) { this.owl = scope; this.owl.options = $.extend({}, Video.Defaults, this.owl.options); this.handlers = { 'resize.owl.carousel': $.proxy(function(e) { if (this.owl.settings.video && !this.isInFullScreen()) { e.preventDefault(); } }, this), 'refresh.owl.carousel changed.owl.carousel': $.proxy(function(e) { if (this.owl.state.videoPlay) { this.stopVideo(); } }, this), 'refresh.owl.carousel refreshed.owl.carousel': $.proxy(function(e) { if (!this.owl.settings.video) { return false; } this.refreshing = e.type == 'refresh'; }, this), 'changed.owl.carousel': $.proxy(function(e) { if (this.refreshing && e.property.name == 'items' && e.property.value && !e.property.value.is(':empty')) { this.checkVideoLinks(); } }, this) }; this.owl.dom.$el.on(this.handlers); this.owl.dom.$el.on('click.owl.video', '.owl-video-play-icon', $.proxy(function(e) { this.playVideo(e); }, this)); }; /** * Default options. * @public */ Video.Defaults = { video: false, videoHeight: false, videoWidth: false }; /** * Checks if for any videos links exists. * @protected */ Video.prototype.checkVideoLinks = function() { var videoEl, item, i; for (i = 0; i < this.owl.num.items; i++) { item = this.owl.dom.$items.eq(i); if (item.data('owl-item').hasVideo) { continue; } videoEl = item.find('.owl-video'); if (videoEl.length) { this.owl.state.hasVideos = true; this.owl.dom.$items.eq(i).data('owl-item').hasVideo = true; videoEl.css('display', 'none'); this.getVideoInfo(videoEl, item); } } }; /** * Gets the video ID and the type (YouTube/Vimeo only). * @protected * @param {jQuery} videoEl - The element containing the video data. * @param {jQuery} item - The item containing the video. */ Video.prototype.getVideoInfo = function(videoEl, item) { var info, type, id, dimensions, vimeoId = videoEl.data('vimeo-id'), youTubeId = videoEl.data('youtube-id'), width = videoEl.data('width') || this.owl.settings.videoWidth, height = videoEl.data('height') || this.owl.settings.videoHeight, url = videoEl.attr('href'); if (vimeoId) { type = 'vimeo'; id = vimeoId; } else if (youTubeId) { type = 'youtube'; id = youTubeId; } else if (url) { id = url.match(/(http:|https:|)\/\/(player.|www.)?(vimeo\.com|youtu(be\.com|\.be|be\.googleapis\.com))\/(video\/|embed\/|watch\?v=|v\/)?([A-Za-z0-9._%-]*)(\&\S+)?/); if (id[3].indexOf('youtu') > -1) { type = 'youtube'; } else if (id[3].indexOf('vimeo') > -1) { type = 'vimeo'; } id = id[6]; } else { throw new Error('Missing video link.'); } item.data('owl-item').videoType = type; item.data('owl-item').videoId = id; item.data('owl-item').videoWidth = width; item.data('owl-item').videoHeight = height; info = { type: type, id: id }; // Check dimensions dimensions = width && height ? 'style="width:' + width + 'px;height:' + height + 'px;"' : ''; // wrap video content into owl-video-wrapper div videoEl.wrap('
'); this.createVideoTn(videoEl, info); }; /** * Creates video thumbnail. * @protected * @param {jQuery} videoEl - The element containing the video data. * @param {Object} info - The video info object. * @see `getVideoInfo` */ Video.prototype.createVideoTn = function(videoEl, info) { var tnLink, icon, path, customTn = videoEl.find('img'), srcType = 'src', lazyClass = '', that = this.owl; if (this.owl.settings.lazyLoad) { srcType = 'data-src'; lazyClass = 'owl-lazy'; } // Custom thumbnail if (customTn.length) { addThumbnail(customTn.attr(srcType)); customTn.remove(); return false; } function addThumbnail(tnPath) { icon = '
'; if (that.settings.lazyLoad) { tnLink = '
'; } else { tnLink = '
'; } videoEl.after(tnLink); videoEl.after(icon); } if (info.type === 'youtube') { path = "http://img.youtube.com/vi/" + info.id + "/hqdefault.jpg"; addThumbnail(path); } else if (info.type === 'vimeo') { $.ajax({ type: 'GET', url: 'http://vimeo.com/api/v2/video/' + info.id + '.json', jsonp: 'callback', dataType: 'jsonp', success: function(data) { path = data[0].thumbnail_large; addThumbnail(path); if (that.settings.loop) { that.updateActiveItems(); } } }); } }; /** * Stops the current video. * @public */ Video.prototype.stopVideo = function() { this.owl.trigger('stop', null, 'video'); var item = this.owl.dom.$items.eq(this.owl.state.videoPlayIndex); item.find('.owl-video-frame').remove(); item.removeClass('owl-video-playing'); this.owl.state.videoPlay = false; }; /** * Starts the current video. * @public * @param {Event} ev - The event arguments. */ Video.prototype.playVideo = function(ev) { this.owl.trigger('play', null, 'video'); if (this.owl.state.videoPlay) { this.stopVideo(); } var videoLink, videoWrap, videoType, target = $(ev.target || ev.srcElement), item = target.closest('.' + this.owl.settings.itemClass); videoType = item.data('owl-item').videoType, id = item.data('owl-item').videoId, width = item .data('owl-item').videoWidth || Math.floor(item.data('owl-item').width - this.owl.settings.margin), height = item.data('owl-item').videoHeight || this.owl.dom.$stage.height(); if (videoType === 'youtube') { videoLink = ""; } else if (videoType === 'vimeo') { videoLink = ''; } item.addClass('owl-video-playing'); this.owl.state.videoPlay = true; this.owl.state.videoPlayIndex = item.data('owl-item').indexAbs; videoWrap = $('
' + videoLink + '
'); target.after(videoWrap); }; /** * Checks whether an video is currently in full screen mode or not. * @protected * @returns {Boolean} */ Video.prototype.isInFullScreen = function() { // if Vimeo Fullscreen mode var fullscreenElement = document.fullscreenElement || document.mozFullScreenElement || document.webkitFullscreenElement; if (fullscreenElement) { if ($(fullscreenElement.parentNode).hasClass('owl-video-frame')) { this.owl.speed(0); this.owl.state.isFullScreen = true; } } if (fullscreenElement && this.owl.state.isFullScreen && this.owl.state.videoPlay) { return false; } // Comming back from fullscreen if (this.owl.state.isFullScreen) { this.owl.state.isFullScreen = false; return false; } // check full screen mode and window orientation if (this.owl.state.videoPlay) { if (this.owl.state.orientation !== window.orientation) { this.owl.state.orientation = window.orientation; return false; } } return true; }; /** * Destroys the plugin. */ Video.prototype.destroy = function() { var handler, property; this.owl.dom.$el.off('click.owl.video'); for (handler in this.handlers) { this.owl.dom.$el.off(handler, this.handlers[handler]); } for (property in Object.getOwnPropertyNames(this)) { typeof this[property] != 'function' && (this[property] = null); } }; $.fn.owlCarousel.Constructor.Plugins.video = Video; })(window.Zepto || window.jQuery, window, document); /** * Animate Plugin * @version 2.0.0 * @author Bartosz Wojciechowski * @license The MIT License (MIT) */ ;(function($, window, document, undefined) { /** * Creates the animate plugin. * @class The Navigation Plugin * @param {Owl} scope - The Owl Carousel */ Animate = function(scope) { this.core = scope; this.core.options = $.extend({}, Animate.Defaults, this.core.options); this.swapping = true; this.previous = undefined; this.next = undefined; this.handlers = { 'change.owl.carousel': $.proxy(function(e) { if (e.property.name == 'position') { this.previous = this.core.current(); this.next = e.property.value; } }, this), 'drag.owl.carousel dragged.owl.carousel translated.owl.carousel': $.proxy(function(e) { this.swapping = e.type == 'translated'; }, this), 'translate.owl.carousel': $.proxy(function(e) { if (this.swapping && (this.core.options.animateOut || this.core.options.animateIn)) { this.swap(); } }, this) }; this.core.dom.$el.on(this.handlers); }; /** * Default options. * @public */ Animate.Defaults = { animateOut: false, animateIn: false }; /** * Toggles the animation classes whenever an translations starts. * @protected * @returns {Boolean|undefined} */ Animate.prototype.swap = function() { if (this.core.settings.items !== 1 || !this.core.support3d) { return; } this.core.speed(0); var left, clear = $.proxy(this.clear, this), previous = this.core.dom.$items.eq(this.previous), next = this.core.dom.$items.eq(this.next), incoming = this.core.settings.animateIn, outgoing = this.core.settings.animateOut; if (this.core.current() === this.previous) { return; } if (outgoing) { left = this.core.coordinates(this.previous) - this.core.coordinates(this.next); previous.css( { 'left': left + 'px' } ) .addClass('animated owl-animated-out') .addClass(outgoing) .one('webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', clear); } if (incoming) { next.addClass('animated owl-animated-in') .addClass(incoming) .one('webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', clear); } }; Animate.prototype.clear = function(e) { $(e.target).css( { 'left': '' } ) .removeClass('animated owl-animated-out owl-animated-in') .removeClass(this.core.settings.animateIn) .removeClass(this.core.settings.animateOut); this.core.transitionEnd(); } /** * Destroys the plugin. * @public */ Animate.prototype.destroy = function() { var handler, property; for (handler in this.handlers) { this.core.dom.$el.off(handler, this.handlers[handler]); } for (property in Object.getOwnPropertyNames(this)) { typeof this[property] != 'function' && (this[property] = null); } }; $.fn.owlCarousel.Constructor.Plugins.Animate = Animate; })(window.Zepto || window.jQuery, window, document); /** * Autoplay Plugin * @version 2.0.0 * @author Bartosz Wojciechowski * @license The MIT License (MIT) */ ;(function($, window, document, undefined) { /** * Creates the autoplay plugin. * @class The Autoplay Plugin * @param {Owl} scope - The Owl Carousel */ Autoplay = function(scope) { this.core = scope; this.core.options = $.extend({}, Autoplay.Defaults, this.core.options); this.handlers = { 'translated.owl.carousel refreshed.owl.carousel': $.proxy(function() { this.autoplay(); }, this), 'play.owl.autoplay': $.proxy(function(e, t, s) { this.play(t, s); }, this), 'stop.owl.autoplay': $.proxy(function() { this.stop(); }, this), 'mouseover.owl.autoplay': $.proxy(function() { if (this.core.settings.autoplayHoverPause) { this.pause(); } }, this), 'mouseleave.owl.autoplay': $.proxy(function() { if (this.core.settings.autoplayHoverPause) { this.autoplay(); } }, this) }; this.core.dom.$el.on(this.handlers); }; /** * Default options. * @public */ Autoplay.Defaults = { autoplay: false, autoplayTimeout: 5000, autoplayHoverPause: false, autoplaySpeed: false }; /** * @protected * @todo Must be documented. */ Autoplay.prototype.autoplay = function() { if (this.core.settings.autoplay && !this.core.state.videoPlay) { window.clearInterval(this.interval); this.interval = window.setInterval($.proxy(function() { this.play(); }, this), this.core.settings.autoplayTimeout); } else { window.clearInterval(this.interval); } }; /** * Starts the autoplay. * @public * @param {Number} [timeout] - ... * @param {Number} [speed] - ... * @returns {Boolean|undefined} - ... * @todo Must be documented. */ Autoplay.prototype.play = function(timeout, speed) { // if tab is inactive - doesnt work in maximum ? current >= maximum ? minimum : maximum : e.property.value < minimum ? maximum : e.property.value; } this.filling = this.core.settings.dotsData && e.property.name == 'item' && e.property.value && e.property.value.is(':empty'); }, this), 'refreshed.owl.carousel': $.proxy(function() { if (this.initialized) { this.update(); this.draw(); } }, this) }; // set default options this.core.options = $.extend({}, Navigation.Defaults, this.core.options); // register event handlers this.$element.on(this.handlers); } /** * Default options. * @public * @todo Rename `slideBy` to `navBy` */ Navigation.Defaults = { nav: false, navRewind: true, navText: [ 'prev', 'next' ], navSpeed: false, navElement: 'div', navContainer: false, navContainerClass: 'owl-nav', navClass: [ 'owl-prev', 'owl-next' ], slideBy: 1, dotClass: 'owl-dot', dotsClass: 'owl-dots', dots: true, dotsEach: false, dotData: false, dotsSpeed: false, dotsContainer: false, controlsClass: 'owl-controls' } /** * Initializes the layout of the plugin and extends the carousel. * @protected */ Navigation.prototype.initialize = function() { var $container, override, options = this.core.settings; // create the indicator template if (!options.dotsData) { this.template = $('
') .addClass(options.dotClass) .append($('')) .prop('outerHTML'); } // create controls container if needed if (!options.navContainer || !options.dotsContainer) { this.controls.$container = $('
') .addClass(options.controlsClass) .appendTo(this.$element); } // create DOM structure for absolute navigation this.controls.$indicators = options.dotsContainer ? $(options.dotsContainer) : $('
').hide().addClass(options.dotsClass).appendTo(this.controls.$container); this.controls.$indicators.on(this.core.dragType[2], 'div', $.proxy(function(e) { var index = $(e.target).parent().is(this.controls.$indicators) ? $(e.target).index() : $(e.target).parent().index(); e.preventDefault(); this.to(index, options.dotsSpeed); }, this)); // create DOM structure for relative navigation $container = options.navContainer ? $(options.navContainer) : $('
').addClass(options.navContainerClass).prependTo(this.controls.$container); this.controls.$next = $('<' + options.navElement + '>'); this.controls.$previous = this.controls.$next.clone(); this.controls.$previous .addClass(options.navClass[0]) .html(options.navText[0]) .hide() .prependTo($container) .on(this.core.dragType[2], $.proxy(function(e) { this.prev(); }, this)); this.controls.$next .addClass(options.navClass[1]) .html(options.navText[1]) .hide() .appendTo($container) .on(this.core.dragType[2], $.proxy(function(e) { this.next(); }, this)); // override public methods of the carousel for (override in this.overrides) { this.core[override] = $.proxy(this[override], this); } } /** * Destroys the plugin. * @protected */ Navigation.prototype.destroy = function() { var handler, control, property, override; for (handler in this.handlers) { this.$element.off(handler, this.handlers[handler]); } for (control in this.controls) { this.controls[control].remove(); } for (override in this.overides) { this.core[override] = this.overrides[override]; } for (property in Object.getOwnPropertyNames(this)) { typeof this[property] != 'function' && (this[property] = null); } } /** * Updates the internal state. * @protected */ Navigation.prototype.update = function() { var i, j, k, options = this.core.settings, lower = this.core.num.cItems / 2, upper = this.core.num.items - lower, size = options.center || options.autoWidth || options.dotData ? 1 : options.dotsEach || options.items; if (options.slideBy !== 'page') { options.slideBy = Math.min(options.slideBy, options.items); } if (options.dots) { this.pages = []; for (i = lower, j = 0, k = 0; i < upper; i++) { if (j >= size || j === 0) { this.pages.push({ start: i - lower, end: i - lower + size - 1 }); j = 0, ++k; } j += this.core.num.merged[i]; } } } /** * Draws the user interface. * @protected */ Navigation.prototype.draw = function() { var difference, i, html = '', options = this.core.settings, $items = this.core.dom.$oItems, index = this.core.normalize(this.core.current(), true); if (options.nav && !options.loop && !options.navRewind) { this.controls.$previous.toggleClass('disabled', index <= 0); this.controls.$next.toggleClass('disabled', index >= this.core.maximum()); } this.controls.$previous.toggle(options.nav); this.controls.$next.toggle(options.nav); if (options.dots) { difference = this.pages.length - this.controls.$indicators.children().length; if (difference > 0) { for (i = 0; i < Math.abs(difference); i++) { html += options.dotData ? $items.eq(i).data('owl-item').dot : this.template; } this.controls.$indicators.append(html); } else if (difference < 0) { this.controls.$indicators.children().slice(difference).remove(); } this.controls.$indicators.find('.active').removeClass('active'); this.controls.$indicators.children().eq($.inArray(this.current(), this.pages)).addClass('active'); } this.controls.$indicators.toggle(options.dots); } /** * Extends event data. * @protected * @param {Event} event - The event object which gets thrown. */ Navigation.prototype.onTrigger = function(event) { var options = this.core.settings; event.page = { index: $.inArray(this.current(), this.pages), count: this.pages.length, size: options.center || options.autoWidth || options.dotData ? 1 : options.dotsEach || options.items }; } /** * Gets the current page position of the carousel. * @protected * @returns {Number} */ Navigation.prototype.current = function() { var index = this.core.normalize(this.core.current(), true); return $.grep(this.pages, function(o) { return o.start <= index && o.end >= index; }).pop(); } /** * Gets the current succesor/predecessor position. * @protected * @returns {Number} */ Navigation.prototype.getPosition = function(successor) { var position, length, options = this.core.settings; if (options.slideBy == 'page') { position = $.inArray(this.current(), this.pages); length = this.pages.length; successor ? ++position : --position; position = this.pages[((position % length) + length) % length].start; } else { position = this.core.normalize(this.core.current(), true); length = this.core.num.oItems; successor ? position += options.slideBy : position -= options.slideBy; } return position; } /** * Slides to the next item or page. * @public * @param {Number} [speed=false] - The time in milliseconds for the transition. */ Navigation.prototype.next = function(speed) { $.proxy(this.overrides.to, this.core)(this.getPosition(true), speed); } /** * Slides to the previous item or page. * @public * @param {Number} [speed=false] - The time in milliseconds for the transition. */ Navigation.prototype.prev = function(speed) { $.proxy(this.overrides.to, this.core)(this.getPosition(false), speed); } /** * Slides to the specified item or page. * @public * @param {Number} position - The position of the item or page. * @param {Number} [speed] - The time in milliseconds for the transition. * @param {Boolean} [standard=false] - Whether to use the standard behaviour or not. */ Navigation.prototype.to = function(position, speed, standard) { var length; if (!standard) { length = this.pages.length; $.proxy(this.overrides.to, this.core)(this.pages[((position % length) + length) % length].start, speed); } else { $.proxy(this.overrides.to, this.core)(position, speed); } } $.fn.owlCarousel.Constructor.Plugins.Navigation = Navigation; })(window.Zepto || window.jQuery, window, document); /** * Hash Plugin * @version 2.0.0 * @author Artus Kolanowski * @license The MIT License (MIT) */ ;(function($, window, document, undefined) { 'use strict'; /** * Creates the hash plugin. * @class The Hash Plugin * @param {Owl} carousel - The Owl Carousel */ var Hash = function(carousel) { /** * Reference to the core. * @type {Owl} */ this.core = carousel; /** * Hash table for the hashes. * @type {Object} */ this.hashes = {}; /** * The carousel element. * @type {jQuery} */ this.$element = this.core.dom.$el; /** * All event handlers. * @type {Object} */ this.handlers = { 'initialized.owl.carousel': $.proxy(function() { if (window.location.hash.substring(1)) { $(window).trigger('hashchange.owl.navigation'); } }, this), 'changed.owl.carousel': $.proxy(function(e) { if (this.filling) { e.property.value.data('owl-item').hash = $(':first-child', e.property.value).find('[data-hash]').andSelf().data('hash'); this.hashes[e.property.value.data('owl-item').hash] = e.property.value; } }, this), 'change.owl.carousel': $.proxy(function(e) { if (e.property.name == 'position' && this.core.current() === undefined && this.core.settings.startPosition == 'URLHash') { e.data = this.hashes[window.location.hash.substring(1)]; } this.filling = e.property.name == 'item' && e.property.value && e.property.value.is(':empty'); }, this), }; // set default options this.core.options = $.extend({}, Hash.Defaults, this.core.options); // register the event handlers this.$element.on(this.handlers); // register event listener for hash navigation $(window).on('hashchange.owl.navigation', $.proxy(function() { var hash = window.location.hash.substring(1), items = this.core.dom.$oItems, position = this.hashes[hash] && items.index(this.hashes[hash]) || 0; if (!hash) { return false; } this.core.dom.oStage.scrollLeft = 0; this.core.to(position, false, true); }, this)); } /** * Default options. * @public */ Hash.Defaults = { URLhashListener: false } /** * Destroys the plugin. * @public */ Hash.prototype.destroy = function() { var handler, property; $(window).off('hashchange.owl.navigation'); for (handler in this.handlers) { this.owl.dom.$el.off(handler, this.handlers[handler]); } for (property in Object.getOwnPropertyNames(this)) { typeof this[property] != 'function' && (this[property] = null); } } $.fn.owlCarousel.Constructor.Plugins.Hash = Hash; })(window.Zepto || window.jQuery, window, document);