/** * IAS History Extension * An IAS extension to enable browser history * http://infiniteajaxscroll.com * * This file is part of the Infinite AJAX Scroll package * * Copyright 2014-2016 Webcreate (Jeroen Fiege) */ var IASHistoryExtension = function (options) { options = jQuery.extend({}, this.defaults, options); this.ias = null; this.prevSelector = options.prev; this.prevUrl = null; this.listeners = { prev: new IASCallbacks() }; /** * @private * @param pageNum * @param scrollOffset * @param url */ this.onPageChange = function (pageNum, scrollOffset, url) { if (!window.history || !window.history.replaceState) { return; } var state = history.state; history.replaceState(state, document.title, url); }; /** * @private * @param currentScrollOffset * @param scrollThreshold */ this.onScroll = function (currentScrollOffset, scrollThreshold) { var firstItemScrollThreshold = this.getScrollThresholdFirstItem(); if (!this.prevUrl) { return; } currentScrollOffset -= this.ias.$scrollContainer.height(); if (currentScrollOffset <= firstItemScrollThreshold) { this.prev(); } }; this.onReady = function () { var currentScrollOffset = this.ias.getCurrentScrollOffset(this.ias.$scrollContainer), firstItemScrollThreshold = this.getScrollThresholdFirstItem(); currentScrollOffset -= this.ias.$scrollContainer.height(); if (currentScrollOffset <= firstItemScrollThreshold) { this.prev(); } }; /** * Returns the url for the next page * * @private */ this.getPrevUrl = function (container) { if (!container) { container = this.ias.$container; } // always take the last matching item return jQuery(this.prevSelector, container).last().attr('href'); }; /** * Returns scroll threshold. This threshold marks the line from where * IAS should start loading the next page. * * @private * @return {number} */ this.getScrollThresholdFirstItem = function () { var $firstElement; $firstElement = this.ias.getFirstItem(); // if the don't have a first element, the DOM might not have been loaded, // or the selector is invalid if (0 === $firstElement.length) { return -1; } return ($firstElement.offset().top); }; /** * Renders items * * @private * @param items * @param callback */ this.renderBefore = function (items, callback) { var ias = this.ias, $firstItem = ias.getFirstItem(), count = 0; ias.fire('render', [items]); jQuery(items).hide(); // at first, hide it so we can fade it in later $firstItem.before(items); jQuery(items).fadeIn(400, function () { if (++count < items.length) { return; } ias.fire('rendered', [items]); if (callback) { callback(); } }); }; return this; }; /** * @public */ IASHistoryExtension.prototype.initialize = function (ias) { var self = this; this.ias = ias; // expose the extensions listeners jQuery.extend(ias.listeners, this.listeners); // expose prev method ias.prev = function() { return self.prev(); }; this.prevUrl = this.getPrevUrl(); }; /** * Bind to events * * @public * @param ias */ IASHistoryExtension.prototype.bind = function (ias) { ias.on('pageChange', jQuery.proxy(this.onPageChange, this)); ias.on('scroll', jQuery.proxy(this.onScroll, this)); ias.on('ready', jQuery.proxy(this.onReady, this)); }; /** * @public * @param {object} ias */ IASHistoryExtension.prototype.unbind = function(ias) { ias.off('pageChange', this.onPageChange); ias.off('scroll', this.onScroll); ias.off('ready', this.onReady); }; /** * Load the prev page * * @public */ IASHistoryExtension.prototype.prev = function () { var url = this.prevUrl, self = this, ias = this.ias; if (!url) { return false; } ias.pause(); var promise = ias.fire('prev', [url]); promise.done(function () { ias.load(url, function (data, items) { self.renderBefore(items, function () { self.prevUrl = self.getPrevUrl(data); ias.resume(); if (self.prevUrl) { self.prev(); } }); }); }); promise.fail(function () { ias.resume(); }); return true; }; /** * @public */ IASHistoryExtension.prototype.defaults = { prev: ".prev" }; /** * IAS None Left Extension * An IAS extension to show a message when there are no more pages te load * http://infiniteajaxscroll.com * * This file is part of the Infinite AJAX Scroll package * * Copyright 2014-2016 Webcreate (Jeroen Fiege) */ var IASNoneLeftExtension = function(options) { options = jQuery.extend({}, this.defaults, options); this.ias = null; this.uid = (new Date()).getTime(); this.html = (options.html).replace('{text}', options.text); /** * Shows none left message */ this.showNoneLeft = function() { var $element = jQuery(this.html).attr('id', 'ias_noneleft_' + this.uid), $lastItem = this.ias.getLastItem(); $lastItem.after($element); $element.fadeIn(); }; return this; }; /** * @public */ IASNoneLeftExtension.prototype.bind = function(ias) { this.ias = ias; ias.on('noneLeft', jQuery.proxy(this.showNoneLeft, this)); }; /** * @public * @param {object} ias */ IASNoneLeftExtension.prototype.unbind = function(ias) { ias.off('noneLeft', this.showNoneLeft); }; /** * @public */ IASNoneLeftExtension.prototype.defaults = { text: 'You reached the end.', html: '
{text}
' }; /** * IAS Paging Extension * An IAS extension providing additional events * http://infiniteajaxscroll.com * * This file is part of the Infinite AJAX Scroll package * * Copyright 2014-2016 Webcreate (Jeroen Fiege) */ var IASPagingExtension = function() { this.ias = null; this.pagebreaks = [[0, document.location.toString()]]; this.lastPageNum = 1; this.enabled = true; this.listeners = { pageChange: new IASCallbacks() }; /** * Fires pageChange event * * @param currentScrollOffset * @param scrollThreshold */ this.onScroll = function(currentScrollOffset, scrollThreshold) { if (!this.enabled) { return; } var ias = this.ias, currentPageNum = this.getCurrentPageNum(currentScrollOffset), currentPagebreak = this.getCurrentPagebreak(currentScrollOffset), urlPage; if (this.lastPageNum !== currentPageNum) { urlPage = currentPagebreak[1]; ias.fire('pageChange', [currentPageNum, currentScrollOffset, urlPage]); } this.lastPageNum = currentPageNum; }; /** * Keeps track of pagebreaks * * @param url */ this.onNext = function(url) { var currentScrollOffset = this.ias.getCurrentScrollOffset(this.ias.$scrollContainer); this.pagebreaks.push([currentScrollOffset, url]); // trigger pageChange and update lastPageNum var currentPageNum = this.getCurrentPageNum(currentScrollOffset) + 1; this.ias.fire('pageChange', [currentPageNum, currentScrollOffset, url]); this.lastPageNum = currentPageNum; }; /** * Keeps track of pagebreaks * * @param url */ this.onPrev = function(url) { var self = this, ias = self.ias, currentScrollOffset = ias.getCurrentScrollOffset(ias.$scrollContainer), prevCurrentScrollOffset = currentScrollOffset - ias.$scrollContainer.height(), $firstItem = ias.getFirstItem(); this.enabled = false; this.pagebreaks.unshift([0, url]); ias.one('rendered', function() { // update pagebreaks for (var i = 1, l = self.pagebreaks.length; i < l; i++) { self.pagebreaks[i][0] = self.pagebreaks[i][0] + $firstItem.offset().top; } // trigger pageChange and update lastPageNum var currentPageNum = self.getCurrentPageNum(prevCurrentScrollOffset) + 1; ias.fire('pageChange', [currentPageNum, prevCurrentScrollOffset, url]); self.lastPageNum = currentPageNum; self.enabled = true; }); }; return this; }; /** * @public */ IASPagingExtension.prototype.initialize = function(ias) { this.ias = ias; // expose the extensions listeners jQuery.extend(ias.listeners, this.listeners); }; /** * @public */ IASPagingExtension.prototype.bind = function(ias) { try { ias.on('prev', jQuery.proxy(this.onPrev, this), this.priority); } catch (exception) {} ias.on('next', jQuery.proxy(this.onNext, this), this.priority); ias.on('scroll', jQuery.proxy(this.onScroll, this), this.priority); }; /** * @public * @param {object} ias */ IASPagingExtension.prototype.unbind = function(ias) { try { ias.off('prev', this.onPrev); } catch (exception) {} ias.off('next', this.onNext); ias.off('scroll', this.onScroll); }; /** * Returns current page number based on scroll offset * * @param {number} scrollOffset * @returns {number} */ IASPagingExtension.prototype.getCurrentPageNum = function(scrollOffset) { for (var i = (this.pagebreaks.length - 1); i > 0; i--) { if (scrollOffset > this.pagebreaks[i][0]) { return i + 1; } } return 1; }; /** * Returns current pagebreak information based on scroll offset * * @param {number} scrollOffset * @returns {number}|null */ IASPagingExtension.prototype.getCurrentPagebreak = function(scrollOffset) { for (var i = (this.pagebreaks.length - 1); i >= 0; i--) { if (scrollOffset > this.pagebreaks[i][0]) { return this.pagebreaks[i]; } } return null; }; /** * @public * @type {number} */ IASPagingExtension.prototype.priority = 500; /** * IAS Spinner Extension * An IAS extension to show a spinner when loading * http://infiniteajaxscroll.com * * This file is part of the Infinite AJAX Scroll package * * Copyright 2014-2016 Webcreate (Jeroen Fiege) */ var IASSpinnerExtension = function(options) { options = jQuery.extend({}, this.defaults, options); this.ias = null; this.uid = new Date().getTime(); this.src = options.src; this.html = (options.html).replace('{src}', this.src); /** * Shows spinner */ this.showSpinner = function() { var $spinner = this.getSpinner() || this.createSpinner(), $lastItem = this.ias.getLastItem(); $lastItem.after($spinner); $spinner.fadeIn(); }; /** * Shows spinner */ this.showSpinnerBefore = function() { var $spinner = this.getSpinner() || this.createSpinner(), $firstItem = this.ias.getFirstItem(); $firstItem.before($spinner); $spinner.fadeIn(); }; /** * Removes spinner */ this.removeSpinner = function() { if (this.hasSpinner()) { this.getSpinner().remove(); } }; /** * @returns {jQuery|boolean} */ this.getSpinner = function() { var $spinner = jQuery('#ias_spinner_' + this.uid); if ($spinner.length > 0) { return $spinner; } return false; }; /** * @returns {boolean} */ this.hasSpinner = function() { var $spinner = jQuery('#ias_spinner_' + this.uid); return ($spinner.length > 0); }; /** * @returns {jQuery} */ this.createSpinner = function() { var $spinner = jQuery(this.html).attr('id', 'ias_spinner_' + this.uid); $spinner.hide(); return $spinner; }; return this; }; /** * @public */ IASSpinnerExtension.prototype.bind = function(ias) { this.ias = ias; ias.on('next', jQuery.proxy(this.showSpinner, this)); ias.on('render', jQuery.proxy(this.removeSpinner, this)); try { ias.on('prev', jQuery.proxy(this.showSpinnerBefore, this)); } catch (exception) {} }; /** * @public * @param {object} ias */ IASSpinnerExtension.prototype.unbind = function(ias) { ias.off('next', this.showSpinner); ias.off('render', this.removeSpinner); try { ias.off('prev', this.showSpinnerBefore); } catch (exception) {} }; /** * @public */ IASSpinnerExtension.prototype.defaults = { src: 'data:image/gif;base64,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', html: '
' }; /** * IAS Trigger Extension * An IAS extension to show a trigger link to load the next page * http://infiniteajaxscroll.com * * This file is part of the Infinite AJAX Scroll package * * Copyright 2014-2016 Webcreate (Jeroen Fiege) */ var IASTriggerExtension = function(options) { options = jQuery.extend({}, this.defaults, options); this.ias = null; this.html = (options.html).replace('{text}', options.text); this.htmlPrev = (options.htmlPrev).replace('{text}', options.textPrev); this.enabled = true; this.count = 0; this.offset = options.offset; this.$triggerNext = null; this.$triggerPrev = null; /** * Shows trigger for next page */ this.showTriggerNext = function() { if (!this.enabled) { return true; } if (false === this.offset || ++this.count < this.offset) { return true; } var $trigger = this.$triggerNext || (this.$triggerNext = this.createTrigger(this.next, this.html)); var $lastItem = this.ias.getLastItem(); $lastItem.after($trigger); $trigger.fadeIn(); return false; }; /** * Shows trigger for previous page */ this.showTriggerPrev = function() { if (!this.enabled) { return true; } var $trigger = this.$triggerPrev || (this.$triggerPrev = this.createTrigger(this.prev, this.htmlPrev)); var $firstItem = this.ias.getFirstItem(); $firstItem.before($trigger); $trigger.fadeIn(); return false; }; this.onRendered = function() { this.enabled = true; }; /** * @param clickCallback * @returns {*|jQuery} * @param {string} html */ this.createTrigger = function(clickCallback, html) { var uid = (new Date()).getTime(), $trigger; html = html || this.html; $trigger = jQuery(html).attr('id', 'ias_trigger_' + uid); $trigger.hide(); $trigger.on('click', jQuery.proxy(clickCallback, this)); return $trigger; }; return this; }; /** * @public * @param {object} ias */ IASTriggerExtension.prototype.bind = function(ias) { var self = this; this.ias = ias; ias.on('next', jQuery.proxy(this.showTriggerNext, this), this.priority); ias.on('rendered', jQuery.proxy(this.onRendered, this), this.priority); try { ias.on('prev', jQuery.proxy(this.showTriggerPrev, this), this.priority); } catch (exception) {} }; /** * @public * @param {object} ias */ IASTriggerExtension.prototype.unbind = function(ias) { ias.off('next', this.showTriggerNext); ias.off('rendered', this.onRendered); try { ias.off('prev', this.showTriggerPrev); } catch (exception) {} }; /** * @public */ IASTriggerExtension.prototype.next = function() { this.enabled = false; this.ias.pause(); if (this.$triggerNext) { this.$triggerNext.remove(); this.$triggerNext = null; } this.ias.next(); }; /** * @public */ IASTriggerExtension.prototype.prev = function() { this.enabled = false; this.ias.pause(); if (this.$triggerPrev) { this.$triggerPrev.remove(); this.$triggerPrev = null; } this.ias.prev(); }; /** * @public */ IASTriggerExtension.prototype.defaults = { text: 'Load more items', html: '
{text}
', textPrev: 'Load previous items', htmlPrev: '
{text}
', offset: 0 }; /** * @public * @type {number} */ IASTriggerExtension.prototype.priority = 1000; /** * IASCallbacks v2.2.2 * http://infiniteajaxscroll.com * * This file is part of the Infinite AJAX Scroll package * * Copyright 2014-2016 Webcreate (Jeroen Fiege) */ var IASCallbacks = function () { this.list = []; this.fireStack = []; this.isFiring = false; this.isDisabled = false; /** * Calls all added callbacks * * @private * @param args */ this.fire = function (args) { var context = args[0], deferred = args[1], callbackArguments = args[2]; this.isFiring = true; for (var i = 0, l = this.list.length; i < l; i++) { if (this.list[i] != undefined) { if (false === this.list[i].fn.apply(context, callbackArguments)) { deferred.reject(); break; } } } this.isFiring = false; deferred.resolve(); if (this.fireStack.length) { this.fire(this.fireStack.shift()); } }; /** * Returns index of the callback in the list in a similar way as * the indexOf function. * * @param callback * @param {number} index index to start the search from * @returns {number} */ this.inList = function (callback, index) { index = index || 0; for (var i = index, length = this.list.length; i < length; i++) { if (this.list[i].fn === callback || (callback.guid && this.list[i].fn.guid && callback.guid === this.list[i].fn.guid)) { return i; } } return -1; }; return this; }; IASCallbacks.prototype = { /** * Adds a callback * * @param callback * @returns {IASCallbacks} * @param priority */ add: function (callback, priority) { var callbackObject = {fn: callback, priority: priority}; priority = priority || 0; for (var i = 0, length = this.list.length; i < length; i++) { if (priority > this.list[i].priority) { this.list.splice(i, 0, callbackObject); return this; } } this.list.push(callbackObject); return this; }, /** * Removes a callback * * @param callback * @returns {IASCallbacks} */ remove: function (callback) { var index = 0; while (( index = this.inList(callback, index) ) > -1) { this.list.splice(index, 1); } return this; }, /** * Checks if callback is added * * @param callback * @returns {*} */ has: function (callback) { return (this.inList(callback) > -1); }, /** * Calls callbacks with a context * * @param context * @param args * @returns {object|void} */ fireWith: function (context, args) { var deferred = jQuery.Deferred(); if (this.isDisabled) { return deferred.reject(); } args = args || []; args = [ context, deferred, args.slice ? args.slice() : args ]; if (this.isFiring) { this.fireStack.push(args); } else { this.fire(args); } return deferred; }, /** * Disable firing of new events */ disable: function () { this.isDisabled = true; }, /** * Enable firing of new events */ enable: function () { this.isDisabled = false; } }; /** * Infinite Ajax Scroll v2.2.2 * A jQuery plugin for infinite scrolling * http://infiniteajaxscroll.com * * Commercial use requires one-time purchase of a commercial license * http://infiniteajaxscroll.com/docs/license.html * * Non-commercial use is licensed under the MIT License * * Copyright 2014-2016 Webcreate (Jeroen Fiege) */ (function($) { 'use strict'; var UNDETERMINED_SCROLLOFFSET = -1; var IAS = function($element, options) { this.itemsContainerSelector = options.container; this.itemSelector = options.item; this.nextSelector = options.next; this.paginationSelector = options.pagination; this.$scrollContainer = $element; this.$container = (window === $element.get(0) ? $(document) : $element); this.defaultDelay = options.delay; this.negativeMargin = options.negativeMargin; this.nextUrl = null; this.isBound = false; this.isPaused = false; this.isInitialized = false; this.jsXhr = false; this.listeners = { next: new IASCallbacks(), load: new IASCallbacks(), loaded: new IASCallbacks(), render: new IASCallbacks(), rendered: new IASCallbacks(), scroll: new IASCallbacks(), noneLeft: new IASCallbacks(), ready: new IASCallbacks() }; this.extensions = []; /** * Scroll event handler * * Note: calls to this functions should be throttled * * @private */ this.scrollHandler = function() { // the throttle method can call the scrollHandler even thought we have called unbind() if (!this.isBound || this.isPaused) { return; } var currentScrollOffset = this.getCurrentScrollOffset(this.$scrollContainer), scrollThreshold = this.getScrollThreshold() ; // invalid scrollThreshold. The DOM might not have loaded yet... if (UNDETERMINED_SCROLLOFFSET == scrollThreshold) { return; } this.fire('scroll', [currentScrollOffset, scrollThreshold]); if (currentScrollOffset >= scrollThreshold) { this.next(); } }; /** * Returns the items container currently in the DOM * * @private * @returns {object} */ this.getItemsContainer = function() { return $(this.itemsContainerSelector, this.$container); }; /** * Returns the last item currently in the DOM * * @private * @returns {object} */ this.getLastItem = function() { return $(this.itemSelector, this.getItemsContainer().get(0)).last(); }; /** * Returns the first item currently in the DOM * * @private * @returns {object} */ this.getFirstItem = function() { return $(this.itemSelector, this.getItemsContainer().get(0)).first(); }; /** * Returns scroll threshold. This threshold marks the line from where * IAS should start loading the next page. * * @private * @param negativeMargin defaults to {this.negativeMargin} * @return {number} */ this.getScrollThreshold = function(negativeMargin) { var $lastElement; negativeMargin = negativeMargin || this.negativeMargin; negativeMargin = (negativeMargin >= 0 ? negativeMargin * -1 : negativeMargin); $lastElement = this.getLastItem(); // if the don't have a last element, the DOM might not have been loaded, // or the selector is invalid if (0 === $lastElement.length) { return UNDETERMINED_SCROLLOFFSET; } return ($lastElement.offset().top + $lastElement.height() + negativeMargin); }; /** * Returns current scroll offset for the given scroll container * * @private * @param $container * @returns {number} */ this.getCurrentScrollOffset = function($container) { var scrollTop = 0, containerHeight = $container.height(); if (window === $container.get(0)) { scrollTop = $container.scrollTop(); } else { scrollTop = $container.offset().top; } // compensate for iPhone if (navigator.platform.indexOf("iPhone") != -1 || navigator.platform.indexOf("iPod") != -1) { containerHeight += 80; } return (scrollTop + containerHeight); }; /** * Returns the url for the next page * * @private */ this.getNextUrl = function(container) { container = container || this.$container; // always take the last matching item return $(this.nextSelector, container).last().attr('href'); }; /** * Loads a page url * * @param url * @param callback * @param delay * @returns {object} jsXhr object */ this.load = function(url, callback, delay) { var self = this, $itemContainer, items = [], timeStart = +new Date(), timeDiff; delay = delay || this.defaultDelay; var loadEvent = { url: url }; self.fire('load', [loadEvent]); this.jsXhr = $.get(loadEvent.url, null, $.proxy(function(data) { $itemContainer = $(this.itemsContainerSelector, data).eq(0); if (0 === $itemContainer.length) { $itemContainer = $(data).filter(this.itemsContainerSelector).eq(0); } if ($itemContainer) { $itemContainer.find(this.itemSelector).each(function() { items.push(this); }); } self.fire('loaded', [data, items]); if (callback) { timeDiff = +new Date() - timeStart; if (timeDiff < delay) { setTimeout(function() { callback.call(self, data, items); }, delay - timeDiff); } else { callback.call(self, data, items); } } }, self), 'html'); return this.jsXhr; }; /** * Renders items * * @param callback * @param items */ this.render = function(items, callback) { var self = this, $lastItem = this.getLastItem(), count = 0; var promise = this.fire('render', [items]); promise.done(function() { $(items).hide(); // at first, hide it so we can fade it in later $lastItem.after(items); $(items).fadeIn(400, function() { // complete callback get fired for each item, // only act on the last item if (++count < items.length) { return; } self.fire('rendered', [items]); if (callback) { callback(); } }); }); promise.fail(function() { if (callback) { callback(); } }); }; /** * Hides the pagination */ this.hidePagination = function() { if (this.paginationSelector) { $(this.paginationSelector, this.$container).hide(); } }; /** * Restores the pagination */ this.restorePagination = function() { if (this.paginationSelector) { $(this.paginationSelector, this.$container).show(); } }; /** * Throttles a method * * Adopted from Ben Alman's jQuery throttle / debounce plugin * * @param callback * @param delay * @return {object} */ this.throttle = function(callback, delay) { var lastExecutionTime = 0, wrapper, timerId ; wrapper = function() { var that = this, args = arguments, diff = +new Date() - lastExecutionTime; function execute() { lastExecutionTime = +new Date(); callback.apply(that, args); } if (!timerId) { execute(); } else { clearTimeout(timerId); } if (diff > delay) { execute(); } else { timerId = setTimeout(execute, delay); } }; if ($.guid) { wrapper.guid = callback.guid = callback.guid || $.guid++; } return wrapper; }; /** * Fires an event with the ability to cancel further processing. This * can be achieved by returning false in a listener. * * @param event * @param args * @returns {*} */ this.fire = function(event, args) { return this.listeners[event].fireWith(this, args); }; /** * Pauses the scroll handler * * Note: internal use only, if you need to pause IAS use `unbind` method. * * @private */ this.pause = function() { this.isPaused = true; }; /** * Resumes the scroll handler * * Note: internal use only, if you need to resume IAS use `bind` method. * * @private */ this.resume = function() { this.isPaused = false; }; return this; }; /** * Initialize IAS * * Note: Should be called when the document is ready * * @public */ IAS.prototype.initialize = function() { if (this.isInitialized) { return false; } var supportsOnScroll = (!!('onscroll' in this.$scrollContainer.get(0))), currentScrollOffset = this.getCurrentScrollOffset(this.$scrollContainer), scrollThreshold = this.getScrollThreshold(); // bail out when the browser doesn't support the scroll event if (!supportsOnScroll) { return false; } this.hidePagination(); this.bind(); this.fire('ready'); this.nextUrl = this.getNextUrl(); // start loading next page if content is shorter than page fold if (currentScrollOffset >= scrollThreshold) { this.next(); // flag as initialized when rendering is completed this.one('rendered', function() { this.isInitialized = true; }); } else { this.isInitialized = true; } return this; }; /** * Reinitializes IAS, for example after an ajax page update * * @public */ IAS.prototype.reinitialize = function () { this.isInitialized = false; this.unbind(); this.initialize(); }; /** * Binds IAS to DOM events * * @public */ IAS.prototype.bind = function() { if (this.isBound) { return; } this.$scrollContainer.on('scroll', $.proxy(this.throttle(this.scrollHandler, 150), this)); for (var i = 0, l = this.extensions.length; i < l; i++) { this.extensions[i].bind(this); } this.isBound = true; this.resume(); }; /** * Unbinds IAS to events * * @public */ IAS.prototype.unbind = function() { if (!this.isBound) { return; } this.$scrollContainer.off('scroll', this.scrollHandler); // notify extensions about unbinding for (var i = 0, l = this.extensions.length; i < l; i++) { if (typeof this.extensions[i]['unbind'] != 'undefined') { this.extensions[i].unbind(this); } } this.isBound = false; }; /** * Destroys IAS instance * * @public */ IAS.prototype.destroy = function() { try { this.jsXhr.abort(); } catch (e) {} this.unbind(); this.$scrollContainer.data('ias', null); }; /** * Registers an eventListener * * Note: chainable * * @public * @returns IAS */ IAS.prototype.on = function(event, callback, priority) { if (typeof this.listeners[event] == 'undefined') { throw new Error('There is no event called "' + event + '"'); } priority = priority || 0; this.listeners[event].add($.proxy(callback, this), priority); return this; }; /** * Registers an eventListener which only gets * fired once. * * Note: chainable * * @public * @returns IAS */ IAS.prototype.one = function(event, callback) { var self = this; var remover = function() { self.off(event, callback); self.off(event, remover); }; this.on(event, callback); this.on(event, remover); return this; }; /** * Removes an eventListener * * Note: chainable * * @public * @returns IAS */ IAS.prototype.off = function(event, callback) { if (typeof this.listeners[event] == 'undefined') { throw new Error('There is no event called "' + event + '"'); } this.listeners[event].remove(callback); return this; }; /** * Load the next page * * @public */ IAS.prototype.next = function() { var url = this.nextUrl, self = this; this.pause(); if (!url) { this.fire('noneLeft', [this.getLastItem()]); this.listeners['noneLeft'].disable(); // disable it so it only fires once self.resume(); return false; } var promise = this.fire('next', [url]); promise.done(function() { self.load(url, function(data, items) { self.render(items, function() { self.nextUrl = self.getNextUrl(data); self.resume(); }); }); }); promise.fail(function() { self.resume(); }); return true; }; /** * Adds an extension * * @public */ IAS.prototype.extension = function(extension) { if (typeof extension['bind'] == 'undefined') { throw new Error('Extension doesn\'t have required method "bind"'); } if (typeof extension['initialize'] != 'undefined') { extension.initialize(this); } this.extensions.push(extension); if (this.isInitialized) { this.reinitialize(); } return this; }; /** * Shortcut. Sets the window as scroll container. * * @public * @param option * @returns {*} */ $.ias = function(option) { var $window = $(window); return $window.ias.apply($window, arguments); }; /** * jQuery plugin initialization * * @public * @param option * @returns {*} the last IAS instance will be returned */ $.fn.ias = function(option) { var args = Array.prototype.slice.call(arguments); var retval = this; this.each(function() { var $this = $(this), instance = $this.data('ias'), options = $.extend({}, $.fn.ias.defaults, $this.data(), typeof option == 'object' && option) ; // set a new instance as data if (!instance) { $this.data('ias', (instance = new IAS($this, options))); $(document).ready($.proxy(instance.initialize, instance)); } // when the plugin is called with a method if (typeof option === 'string') { if (typeof instance[option] !== 'function') { throw new Error('There is no method called "' + option + '"'); } args.shift(); // remove first argument ('option') instance[option].apply(instance, args); } retval = instance; }); return retval; }; /** * Plugin defaults * * @public * @type {object} */ $.fn.ias.defaults = { item: '.item', container: '.listing', next: '.next', pagination: false, delay: 600, negativeMargin: 10 }; })(jQuery); (function ($) { var ias = $.ias({ container: '.ias-container', item: '.ias-item', pagination: '.ias-pagination', next: '.ias-nav-next a' }); ias.extension(new IASSpinnerExtension({ html: '
' })); ias.extension(new IASNoneLeftExtension({ text: translations.noMoreResults, html: '

{text}

' })); })(jQuery); // Product Image Gallery Settings (function ($) { $('.slick-product-gallery').slick({ asNavFor: '.slick-product-nav', arrows: false, dots: false, adaptiveHeight: true, slidesToShow: 1, autoplay: false, infinite: true, mobileFirst: true, pauseOnHover: true, swipe: true }); $('.slick-product-nav').slick({ asNavFor: '.slick-product-gallery', infinite: true, mobileFirst: true, arrows: false, slidesToShow: 4, slidesToScroll: 1, focusOnSelect: true }); })(jQuery); // Relations Image Gallery Settings (function ($) { $('.slick-relations-gallery').slick({ arrows: true, dots: true, slidesToShow: 1, slidesToScroll: 1, infinite: false, autoplay: false, mobileFirst: true, pauseOnHover: true, swipe: true, responsive: [{ breakpoint: 544, settings: { slidesToShow: 2, slidesToScroll: 2 } }] }); })(jQuery);