├── .gitignore ├── Block └── InfiniteScroll.php ├── Helper └── Data.php ├── README.md ├── composer.json ├── etc ├── acl.xml ├── adminhtml │ └── system.xml ├── config.xml ├── module.xml └── routes.xml ├── media ├── backend_config.png ├── result_frontend_1.png └── result_frontend_2.png ├── registration.php └── view └── frontend ├── layout ├── ajaxscroll.xml ├── catalog_category_view.xml ├── catalogsearch_advanced_result.xml └── catalogsearch_result_index.xml ├── requirejs-config.js ├── templates └── infinitescroll.phtml └── web ├── css └── source │ └── _module.less └── js └── plugin └── infinitescroll.js /.gitignore: -------------------------------------------------------------------------------- 1 | # General 2 | .DS_Store 3 | .AppleDouble 4 | .LSOverride 5 | 6 | # Icon must end with two \r 7 | Icon 8 | 9 | # Thumbnails 10 | ._* 11 | 12 | # Files that might appear in the root of a volume 13 | .DocumentRevisions-V100 14 | .fseventsd 15 | .Spotlight-V100 16 | .TemporaryItems 17 | .Trashes 18 | .VolumeIcon.icns 19 | .com.apple.timemachine.donotpresent 20 | 21 | # Directories potentially created on remote AFP share 22 | .AppleDB 23 | .AppleDesktop 24 | Network Trash Folder 25 | Temporary Items 26 | .apdisk -------------------------------------------------------------------------------- /Block/InfiniteScroll.php: -------------------------------------------------------------------------------- 1 | _storeManager->getStore()->getBaseUrl(\Magento\Framework\UrlInterface::URL_TYPE_MEDIA); 34 | } catch (NoSuchEntityException $e) { 35 | echo 'Error: ' . $e; 36 | } 37 | 38 | return $img ? $urlMedia . $img : $urlMedia; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Helper/Data.php: -------------------------------------------------------------------------------- 1 | configModule = $this->getConfig(strtolower($this->_getModuleName())); 19 | } 20 | 21 | /** 22 | * @param string $cfg 23 | * @return \Magento\Framework\App\Config\ScopeConfigInterface|mixed 24 | */ 25 | public function getConfig(string $cfg = '') 26 | { 27 | if ($cfg) { 28 | return $this->scopeConfig->getValue($cfg, \Magento\Store\Model\ScopeInterface::SCOPE_STORE); 29 | } 30 | return $this->scopeConfig; 31 | } 32 | 33 | /** 34 | * @param string $cfg 35 | * @param null $value 36 | * @return array|\Magento\Framework\App\Config\ScopeConfigInterface|mixed|null 37 | */ 38 | public function getConfigModule(string $cfg = '', $value = null) 39 | { 40 | $values = $this->configModule; 41 | if (!$cfg) { 42 | return $values; 43 | } 44 | $config = explode('/', (string) $cfg); 45 | $end = count($config) - 1; 46 | foreach ($config as $key => $vl) { 47 | if (isset($values[$vl])) { 48 | if ($key === $end) { 49 | $value = $values[$vl]; 50 | } else { 51 | $values = $values[$vl]; 52 | } 53 | } 54 | 55 | } 56 | return $value; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [](https://shopify.pxf.io/VyL446) 2 | 3 | ## Magento 2 Infinite Scroll (Magepow Infinite Scroll extension Free) 4 | 5 | **Infinite scroll** for Magento 2 automatically loads product catalog without reloading the page. Your customers will be pleasantly surprised with supportive navigation and high performance of your web store. 6 | 7 | [![Latest Stable Version](https://poser.pugx.org/magepow/infinitescroll/v/stable)](https://packagist.org/packages/magepow/infinitescroll) 8 | [![Total Downloads](https://poser.pugx.org/magepow/infinitescroll/downloads)](https://packagist.org/packages/magepow/infinitescroll) 9 | [![Daily Downloads](https://poser.pugx.org/magepow/infinitescroll/d/daily)](https://packagist.org/packages/magepow/infinitescroll) 10 | 11 | **See more information**: 12 | 13 | - [](https://demo.magepow.com/infinitescroll) 14 | 15 | - [Detail](https://magepow.com/magento-2-infinite-scroll-extension.html) 16 | 17 | - [Documentation](https://docs.alothemes.com/m2/extension/infinitescroll/) 18 | 19 | - [Video tutorial](https://www.youtube.com/watch?v=gTemvUzrOJg&t=57s) 20 | 21 | - [](https://apps.shopify.com/magepow-infinite-scroll)[Shopify version](https://apps.shopify.com/magepow-infinite-scroll) 22 | 23 | ## Highlight Features 24 | 25 | - Automatically load content and images in just one page. 26 | 27 | - Visitors can see all in just one page 28 | 29 | - Display load more chart to help users see more products. 30 | 31 | - Reduce the request load to the server, increase website speed 32 | 33 | - Increase professional animation effects for Magento website. 34 | 35 | - Support to increase website ranking on search engines 36 | 37 | - Responsive 38 | 39 | ## How to use Infinite Scroll extension 40 | Before you continue, ensure you meet the following requirements: 41 | 42 | * You have installed magento2 43 | * You are using a Linux or Mac OS machine. Windows is not currently supported. 44 | Install Magento 2 Infinite Scroll extension 45 | 46 | ### Step 1 : Download Magento 2 Infinitescroll Extension 47 | 48 | #### Install via composer (recommend) 49 | Run the following commands in Magento 2 root folder: 50 | ``` 51 | composer require magepow/infinitescroll 52 | php bin/magento setup:upgrade 53 | php bin/magento setup:static-content:deploy -f 54 | ``` 55 | 56 | ### Step 2: User guide 57 | #### Key features of Magento 2 Infinite scroll Extension: 58 | * Ajax scroll without interruption. 59 | * Freely scroll down & See what page of the catalog they're on. 60 | * Automatically loading pages. 61 | * Show Loading Button. 62 | * Possibility to give/ share links to a certain positions. 63 | * Easy to customize. 64 | * Similar technique as seen on Twitter, Facebook. 65 | * Increase the conversion rate at your store. 66 | * Easy to Change Button and Loading Text. 67 | 68 | ### 2.1. General configuration 69 | 70 | `Login to Magento admin > Stores > Configuration > Magepow > Infinitescroll > Enable > Choose Yes to enable the module.` 71 | 72 | ![Image of Magento admin config](https://github.com/magepow/magento2-infinitescroll/blob/master/media/backend_config.png) 73 | 74 | In `Stores > Configuration > Magepow > Infinitescroll` we set: 75 | * **Delay (ms)**: Delay time for the scroll down, default 600. 76 | * **Content**: Select for the elements that surrounds the items you will be loading more of (For Ex. = .classname/#id). 77 | * **Pagination**: Select class, id for paging loaded more. 78 | * **Next**: Select class, id for the link to to the next page. 79 | * **Item**: Select for the class name that you want to config all items you will load more. 80 | * **Loading text**: Place any text you want when loading the page. 81 | * **Done text**: When the download is completed, the text you configured will appear. 82 | * **Loading Image placeholder**: The icons you want are displayed while downloading more, you can change it arbitrarily or use Magento's default icons. 83 | * **Load More threshold**: When this page number is reached, a button to load more products will be shown instead of continue loading products automatically with the scroll. 84 | * **Load More button text**: Configure the download button text. 85 | After you finish configuring, save and clear the cache. 86 | Run the following command: 87 | 88 | ``` 89 | php bin/magento cache:clean 90 | ``` 91 | ### 2.2. Result 92 | 93 | ![Image of magento store front](https://github.com/magepow/magento2-infinitescroll/blob/master/media/result_frontend_1.png) 94 | ![Image of magento store front](https://github.com/magepow/magento2-infinitescroll/blob/master/media/result_frontend_2.png) 95 | 96 | ### 3. Events 97 | * Refresh Infinite Scroll update with Ajax use code: 98 | ``` 99 | $('body').trigger('collectionUpdated'); 100 | ``` 101 | ## Donation 102 | 103 | If this project help you reduce time to develop, you can give me a cup of coffee :) 104 | 105 | [![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](https://www.paypal.com/paypalme/alopay) 106 | 107 | 108 | **[Our Magento 2 Extensions](https://magepow.com/magento-2-extensions.html)** 109 | 110 | * [Magento 2 Recent Sales Notification](https://magepow.com/magento-2-recent-order-notification.html) 111 | 112 | * [Magento 2 Categories Extension](https://magepow.com/magento-categories-extension.html) 113 | 114 | * [Magento 2 Sticky Cart](https://magepow.com/magento-sticky-cart.html) 115 | 116 | * [Magento 2 Ajax Contact](https://magepow.com/magento-ajax-contact-form.html) 117 | 118 | * [Magento 2 Lazy Load](https://magepow.com/magento-lazy-load.html) 119 | 120 | * [Magento 2 Mutil Translate](https://magepow.com/magento-multi-translate.html) 121 | 122 | * [Magento 2 Instagram Integration](https://magepow.com/magento-2-instagram.html) 123 | 124 | * [Magento 2 Lookbook Pin Products](https://magepow.com/lookbook-pin-products.html) 125 | 126 | * [Magento 2 Product Slider](https://magepow.com/magento-product-slider.html) 127 | 128 | * [Magento 2 Product Banner](https://magepow.com/magento-2-banner-slider.html) 129 | 130 | **[Our Magento 2 services](https://magepow.com/magento-services.html)** 131 | 132 | * [PSD to Magento 2 Theme Conversion](https://alothemes.com/psd-to-magento-theme-conversion.html) 133 | 134 | * [Magento 2 Speed Optimization Service](https://magepow.com/magento-speed-optimization-service.html) 135 | 136 | * [Magento 2 Security Patch Installation](https://magepow.com/magento-security-patch-installation.html) 137 | 138 | * [Magento 2 Website Maintenance Service](https://magepow.com/website-maintenance-service.html) 139 | 140 | * [Magento 2 Professional Installation Service](https://magepow.com/professional-installation-service.html) 141 | 142 | * [Magento 2 Upgrade Service](https://magepow.com/magento-upgrade-service.html) 143 | 144 | * [Magento 2 Customization Service](https://magepow.com/customization-service.html) 145 | 146 | * [Hire Magento 2 Developer](https://magepow.com/hire-magento-developer.html) 147 | 148 | **[Our Magento 2 Themes](https://alothemes.com/)** 149 | 150 | * [Expert Multipurpose Responsive Magento 2 Theme](https://1.envato.market/c/1314680/275988/4415?u=https://themeforest.net/item/expert-premium-responsive-magento-2-and-1-support-rtl-magento-2-/21667789) 151 | 152 | * [Gecko Premium Responsive Magento 2 Theme](https://1.envato.market/c/1314680/275988/4415?u=https://themeforest.net/item/gecko-responsive-magento-2-theme-rtl-supported/24677410) 153 | 154 | * [Milano Fashion Responsive Magento 2 Theme](https://1.envato.market/c/1314680/275988/4415?u=https://themeforest.net/item/milano-fashion-responsive-magento-1-2-theme/12141971) 155 | 156 | * [Electro 2 Responsive Magento 2 Theme](https://1.envato.market/c/1314680/275988/4415?u=https://themeforest.net/item/electro2-premium-responsive-magento-2-rtl-supported/26875864) 157 | 158 | * [Electro Responsive Magento 2 Theme](https://1.envato.market/c/1314680/275988/4415?u=https://themeforest.net/item/electro-responsive-magento-1-2-theme/17042067) 159 | 160 | * [Pizzaro Food responsive Magento 2 Theme](https://1.envato.market/c/1314680/275988/4415?u=https://themeforest.net/item/pizzaro-food-responsive-magento-1-2-theme/19438157) 161 | 162 | * [Biolife organic responsive Magento 2 Theme](https://1.envato.market/c/1314680/275988/4415?u=https://themeforest.net/item/biolife-organic-food-magento-2-theme-rtl-supported/25712510) 163 | 164 | * [Market responsive Magento 2 Theme](https://1.envato.market/c/1314680/275988/4415?u=https://themeforest.net/item/market-responsive-magento-2-theme/22997928) 165 | 166 | * [Kuteshop responsive Magento 2 Theme](https://1.envato.market/c/1314680/275988/4415?u=https://themeforest.net/item/kuteshop-multipurpose-responsive-magento-1-2-theme/12985435) 167 | 168 | * [Bencher - Responsive Magento 2 Theme](https://1.envato.market/c/1314680/275988/4415?u=https://themeforest.net/item/bencher-responsive-magento-1-2-theme/15787772) 169 | 170 | * [Supermarket Responsive Magento 2 Theme](https://1.envato.market/c/1314680/275988/4415?u=https://themeforest.net/item/supermarket-responsive-magento-1-2-theme/18447995) 171 | 172 | **[Our Shopify Themes](https://themeforest.net/user/alotheme)** 173 | 174 | * [Dukamarket - Multipurpose Shopify Theme](https://1.envato.market/c/1314680/275988/4415?u=https://themeforest.net/item/dukamarket-multipurpose-shopify-theme/36158349) 175 | 176 | * [Ohey - Multipurpose Shopify Theme](https://1.envato.market/c/1314680/275988/4415?u=https://themeforest.net/item/ohey-multipurpose-shopify-theme/34624195) 177 | 178 | * [Flexon - Multipurpose Shopify Theme](https://1.envato.market/c/1314680/275988/4415?u=https://themeforest.net/item/flexon-multipurpose-shopify-theme/33461048) 179 | 180 | **[Our Shopify App](https://apps.shopify.com/partners/maggicart)** 181 | 182 | * [Magepow Infinite Scroll](https://apps.shopify.com/magepow-infinite-scroll) 183 | 184 | * [Magepow Promotionbar](https://apps.shopify.com/magepow-promotionbar) 185 | 186 | * [Magepow Size Chart](https://apps.shopify.com/magepow-size-chart) 187 | 188 | **[Our WordPress Theme](https://themeforest.net/user/alotheme/portfolio)** 189 | 190 | * [SadesMarket - Multipurpose WordPress Theme](https://1.envato.market/c/1314680/275988/4415?u=https://themeforest.net/item/sadesmarket-multipurpose-wordpress-theme/35369933) 191 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "magepow/infinitescroll", 3 | "description": "Infinite scroll for magento 2 automatically loads product catalog without reloading the page.", 4 | "require": { 5 | "magepow/core": "^1.0.0" 6 | }, 7 | "type": "magento2-module", 8 | "license": [ 9 | "OSL-3.0", 10 | "AFL-3.0" 11 | ], 12 | "authors": [ 13 | { 14 | "name": "Magepow", 15 | "email": "support@magepow.com", 16 | "homepage": "https://magepow.com", 17 | "role": "Technical Support" 18 | } 19 | ], 20 | "autoload": { 21 | "files": [ 22 | "registration.php" 23 | ], 24 | "psr-4": { 25 | "Magepow\\InfiniteScroll\\": "" 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /etc/acl.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /etc/adminhtml/system.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | separator-top 10 | 11 | magepow 12 | Magepow_InfiniteScroll::config 13 | 14 | 15 | 16 | 17 | Magento\Config\Model\Config\Source\Yesno 18 | 19 | 20 | 21 | 22 | 23 | required-entry validate-digits validate-zero-or-greater 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | Allowed file types: jpg, jpeg, gif, png. 45 | Magento\Config\Model\Config\Backend\Image 46 | magepow/infinitescroll 47 | magepow/infinitescroll 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | required-entry validate-digits 59 | 60 | 61 | 62 | 63 | 64 | 65 |
66 |
67 |
68 | -------------------------------------------------------------------------------- /etc/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 1 8 | 600 9 | .main 10 | .pages-items, .pager__list 11 | .next 12 | .product-item 13 | 14 | Loading - please wait...]]> 15 | You've reached the end of the item.]]> 16 | -1 17 | Load more 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /etc/module.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /etc/routes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /media/backend_config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/magepow/magento-2-infinite-scroll/ed3e4dbdc35a898f6f0be9f40734bb44f362f791/media/backend_config.png -------------------------------------------------------------------------------- /media/result_frontend_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/magepow/magento-2-infinite-scroll/ed3e4dbdc35a898f6f0be9f40734bb44f362f791/media/result_frontend_1.png -------------------------------------------------------------------------------- /media/result_frontend_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/magepow/magento-2-infinite-scroll/ed3e4dbdc35a898f6f0be9f40734bb44f362f791/media/result_frontend_2.png -------------------------------------------------------------------------------- /registration.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /view/frontend/layout/catalog_category_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /view/frontend/layout/catalogsearch_advanced_result.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /view/frontend/layout/catalogsearch_result_index.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /view/frontend/requirejs-config.js: -------------------------------------------------------------------------------- 1 | 2 | var config = { 3 | map: { 4 | '*': { 5 | infinitescroll: 'Magepow_InfiniteScroll/js/plugin/infinitescroll', 6 | } 7 | }, 8 | paths: { 9 | 'magepow/infinitescroll': 'Magepow_InfiniteScroll/js/plugin/infinitescroll', 10 | }, 11 | shim: { 12 | 'magepow/infinitescroll': { 13 | deps: ['jquery'] 14 | } 15 | } 16 | 17 | }; -------------------------------------------------------------------------------- /view/frontend/templates/infinitescroll.phtml: -------------------------------------------------------------------------------- 1 | helper('Magepow\InfiniteScroll\Helper\Data'); 7 | $isEnabled = $helper->getConfigModule('general/enabled'); 8 | $delay = $helper->getConfigModule('general/delay'); 9 | $content = $helper->getConfigModule('general/content'); 10 | $pagination = $helper->getConfigModule('general/pagination'); 11 | $next = $helper->getConfigModule('general/next'); 12 | $item = $helper->getConfigModule('general/item'); 13 | $loadingText = $helper->getConfigModule('general/loading_text'); 14 | $doneText = $helper->getConfigModule('general/done_text'); 15 | $loadMore = $helper->getConfigModule('general/load_more'); 16 | $loadMoreText = $helper->getConfigModule('general/load_more_text'); 17 | $loadingImage = $helper->getConfigModule('general/loading_image'); 18 | $imgPath = 'magepow/infinitescroll/'. $loadingImage; 19 | if ($loadingImage) { 20 | $loadingImage = $block->getMedia($imgPath); 21 | } else { 22 | $loadingImage = $this->getViewFileUrl('images/loader-1.gif'); 23 | } 24 | ?> 25 | 66 | -------------------------------------------------------------------------------- /view/frontend/web/css/source/_module.less: -------------------------------------------------------------------------------- 1 | & when (@media-common = true) { 2 | .iass-spinner{ 3 | text-align: center; 4 | font-size: 16px; 5 | color: #333; 6 | display: block; 7 | } 8 | .ias-noneleft{ 9 | text-align: center; 10 | color: #333; 11 | letter-spacing: 0px; 12 | font-size: 1em; 13 | padding: 30px 0; 14 | } 15 | .iass-spinner img, 16 | .ias-noneleft img{ 17 | display:block; 18 | margin-left:auto; 19 | margin-right:auto; 20 | } 21 | .iass-spinner, 22 | .ias-noneleft{ 23 | display: inline-block; 24 | width: 100%; 25 | } 26 | 27 | .ias-trigger-next{ 28 | text-align: center; 29 | cursor: pointer; 30 | display: inline-block; 31 | width: 100%; 32 | } 33 | 34 | .load-more{ 35 | font-size:15px; 36 | border: none; 37 | } 38 | .load-more::hover{ 39 | background:#0491ff; 40 | } 41 | .ias-trigger-prev{ 42 | text-align: center; 43 | cursor: pointer; 44 | } 45 | .iass-spinner img { 46 | height: 40px; 47 | width: 40px; 48 | margin-bottom: 7px; 49 | } 50 | .infinitescroll .toolbar-bottom{ 51 | display:none 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /view/frontend/web/js/plugin/infinitescroll.js: -------------------------------------------------------------------------------- 1 | if(typeof(IASCallbacks) == "undefined"){ 2 | /** 3 | * IASCallbacks 4 | * http://infiniteajaxscroll.com 5 | * 6 | * This file is part of the Infinite AJAX Scroll package 7 | * 8 | */ 9 | 10 | var IASCallbacks = function () { 11 | this.list = []; 12 | this.fireStack = []; 13 | this.isFiring = false; 14 | this.isDisabled = false; 15 | 16 | /** 17 | * Calls all added callbacks 18 | * 19 | * @private 20 | * @param args 21 | */ 22 | this.fire = function (args) { 23 | var context = args[0], 24 | deferred = args[1], 25 | callbackArguments = args[2]; 26 | this.isFiring = true; 27 | 28 | for (var i = 0, l = this.list.length; i < l; i++) { 29 | if (false === this.list[i].fn.apply(context, callbackArguments)) { 30 | deferred.reject(); 31 | 32 | break; 33 | } 34 | } 35 | 36 | this.isFiring = false; 37 | 38 | deferred.resolve(); 39 | 40 | if (this.fireStack.length) { 41 | this.fire(this.fireStack.shift()); 42 | } 43 | }; 44 | 45 | /** 46 | * Returns index of the callback in the list in a similar way as 47 | * the indexOf function. 48 | * 49 | * @param callback 50 | * @param {number} index index to start the search from 51 | * @returns {number} 52 | */ 53 | this.inList = function (callback, index) { 54 | index = index || 0; 55 | 56 | for (var i = index, length = this.list.length; i < length; i++) { 57 | if (this.list[i].fn === callback || (callback.guid && this.list[i].fn.guid && callback.guid === this.list[i].fn.guid)) { 58 | return i; 59 | } 60 | } 61 | 62 | return -1; 63 | }; 64 | 65 | return this; 66 | }; 67 | 68 | IASCallbacks.prototype = { 69 | /** 70 | * Adds a callback 71 | * 72 | * @param callback 73 | * @returns {IASCallbacks} 74 | * @param priority 75 | */ 76 | add: function (callback, priority) { 77 | var callbackObject = {fn: callback, priority: priority}; 78 | 79 | priority = priority || 0; 80 | 81 | for (var i = 0, length = this.list.length; i < length; i++) { 82 | if (priority > this.list[i].priority) { 83 | this.list.splice(i, 0, callbackObject); 84 | 85 | return this; 86 | } 87 | } 88 | 89 | this.list.push(callbackObject); 90 | 91 | return this; 92 | }, 93 | 94 | /** 95 | * Removes a callback 96 | * 97 | * @param callback 98 | * @returns {IASCallbacks} 99 | */ 100 | remove: function (callback) { 101 | var index = 0; 102 | 103 | while (( index = this.inList(callback, index) ) > -1) { 104 | this.list.splice(index, 1); 105 | } 106 | 107 | return this; 108 | }, 109 | 110 | /** 111 | * Checks if callback is added 112 | * 113 | * @param callback 114 | * @returns {*} 115 | */ 116 | has: function (callback) { 117 | return (this.inList(callback) > -1); 118 | }, 119 | 120 | 121 | /** 122 | * Calls callbacks with a context 123 | * 124 | * @param context 125 | * @param args 126 | * @returns {object|void} 127 | */ 128 | fireWith: function (context, args) { 129 | var deferred = jQuery.Deferred(); 130 | 131 | if (this.isDisabled) { 132 | return deferred.reject(); 133 | } 134 | 135 | args = args || []; 136 | args = [ context, deferred, args.slice ? args.slice() : args ]; 137 | 138 | if (this.isFiring) { 139 | this.fireStack.push(args); 140 | } else { 141 | this.fire(args); 142 | } 143 | 144 | return deferred; 145 | }, 146 | 147 | /** 148 | * Disable firing of new events 149 | */ 150 | disable: function () { 151 | this.isDisabled = true; 152 | }, 153 | 154 | /** 155 | * Enable firing of new events 156 | */ 157 | enable: function () { 158 | this.isDisabled = false; 159 | } 160 | }; 161 | } 162 | /** 163 | * Infinite Ajax Scroll v2.1.3 164 | * A jQuery plugin for infinite scrolling 165 | * http://infiniteajaxscroll.com 166 | * 167 | * Commercial use requires one-time purchase of a commercial license 168 | * http://infiniteajaxscroll.com/docs/license.html 169 | * 170 | * Non-commercial use is licensed under the MIT License 171 | * 172 | * Copyright 2014 Webcreate (Jeroen Fiege) 173 | */ 174 | (function($) { 175 | 176 | 'use strict'; 177 | 178 | var UNDETERMINED_SCROLLOFFSET = -1; 179 | 180 | var IAS = function($element, options) { 181 | this.itemsContainerSelector = options.container; 182 | this.itemSelector = options.item; 183 | this.nextSelector = options.next; 184 | this.paginationSelector = options.pagination; 185 | this.$scrollContainer = $element; 186 | this.$itemsContainer = $(this.itemsContainerSelector); 187 | this.$container = (window === $element.get(0) ? $(document) : $element); 188 | this.defaultDelay = options.delay; 189 | this.negativeMargin = options.negativeMargin; 190 | this.nextUrl = null; 191 | this.isBound = false; 192 | this.listeners = { 193 | next: new IASCallbacks(), 194 | load: new IASCallbacks(), 195 | loaded: new IASCallbacks(), 196 | render: new IASCallbacks(), 197 | rendered: new IASCallbacks(), 198 | scroll: new IASCallbacks(), 199 | noneLeft: new IASCallbacks(), 200 | ready: new IASCallbacks() 201 | }; 202 | this.extensions = []; 203 | 204 | /** 205 | * Scroll event handler 206 | * 207 | * Note: calls to this functions should be throttled 208 | * 209 | * @private 210 | */ 211 | this.scrollHandler = function() { 212 | var currentScrollOffset = this.getCurrentScrollOffset(this.$scrollContainer), 213 | scrollThreshold = this.getScrollThreshold() 214 | ; 215 | 216 | // the throttle method can call the scrollHandler even thought we have called unbind() 217 | if (!this.isBound) { 218 | return; 219 | } 220 | 221 | // invalid scrollThreshold. The DOM might not have loaded yet... 222 | if (UNDETERMINED_SCROLLOFFSET == scrollThreshold) { 223 | return; 224 | } 225 | 226 | this.fire('scroll', [currentScrollOffset, scrollThreshold]); 227 | 228 | if (currentScrollOffset >= scrollThreshold) { 229 | this.next(); 230 | } 231 | }; 232 | 233 | /** 234 | * Returns the last item currently in the DOM 235 | * 236 | * @private 237 | * @returns {object} 238 | */ 239 | this.getLastItem = function() { 240 | return $(this.itemSelector, this.$itemsContainer.get(0)).last(); 241 | }; 242 | 243 | /** 244 | * Returns the first item currently in the DOM 245 | * 246 | * @private 247 | * @returns {object} 248 | */ 249 | this.getFirstItem = function() { 250 | return $(this.itemSelector, this.$itemsContainer.get(0)).first(); 251 | }; 252 | 253 | /** 254 | * Returns scroll threshold. This threshold marks the line from where 255 | * IAS should start loading the next page. 256 | * 257 | * @private 258 | * @param negativeMargin defaults to {this.negativeMargin} 259 | * @return {number} 260 | */ 261 | this.getScrollThreshold = function(negativeMargin) { 262 | var $lastElement; 263 | 264 | negativeMargin = negativeMargin || this.negativeMargin; 265 | negativeMargin = (negativeMargin >= 0 ? negativeMargin * -1 : negativeMargin); 266 | 267 | $lastElement = this.getLastItem(); 268 | 269 | // if the don't have a last element, the DOM might not have been loaded, 270 | // or the selector is invalid 271 | if (0 === $lastElement.length) { 272 | return UNDETERMINED_SCROLLOFFSET; 273 | } 274 | 275 | return ($lastElement.offset().top + $lastElement.height() + negativeMargin); 276 | }; 277 | 278 | /** 279 | * Returns current scroll offset for the given scroll container 280 | * 281 | * @private 282 | * @param $container 283 | * @returns {number} 284 | */ 285 | this.getCurrentScrollOffset = function($container) { 286 | var scrollTop = 0, 287 | containerHeight = $container.height(); 288 | 289 | if (window === $container.get(0)) { 290 | scrollTop = $container.scrollTop(); 291 | } else { 292 | scrollTop = $container.offset().top; 293 | } 294 | 295 | // compensate for iPhone 296 | if (navigator.platform.indexOf("iPhone") != -1 || navigator.platform.indexOf("iPod") != -1) { 297 | containerHeight += 80; 298 | } 299 | 300 | return (scrollTop + containerHeight); 301 | }; 302 | 303 | /** 304 | * Returns the url for the next page 305 | * 306 | * @private 307 | */ 308 | this.getNextUrl = function(container) { 309 | if (!container) { 310 | container = this.$container; 311 | } 312 | 313 | // always take the last matching item 314 | var next_url = $(this.nextSelector, container).last().attr('href'); 315 | if(typeof(next_url) != 'undefined') { 316 | next_url += next_url.includes('?') ? '&ajaxscroll=1' : '?ajaxscroll=1'; 317 | } else { 318 | next_url = ''; 319 | } 320 | 321 | return next_url; 322 | }; 323 | 324 | /** 325 | * Loads a page url 326 | * 327 | * @param url 328 | * @param callback 329 | * @param delay 330 | * @returns {object} jsXhr object 331 | */ 332 | this.load = function(url, callback, delay) { 333 | var self = this, 334 | $itemContainer, 335 | items = [], 336 | timeStart = +new Date(), 337 | timeDiff; 338 | 339 | delay = delay || this.defaultDelay; 340 | 341 | var loadEvent = { 342 | url: url 343 | }; 344 | 345 | self.fire('load', [loadEvent]); 346 | 347 | return $.get(loadEvent.url, null, $.proxy(function(data) { 348 | $itemContainer = $(this.itemsContainerSelector, data).eq(0); 349 | if (0 === $itemContainer.length) { 350 | $itemContainer = $(data).filter(this.itemsContainerSelector).eq(0); 351 | } 352 | 353 | if ($itemContainer) { 354 | $itemContainer.find(this.itemSelector).each(function() { 355 | items.push(this); 356 | }); 357 | } 358 | 359 | self.fire('loaded', [data, items]); 360 | 361 | if (callback) { 362 | timeDiff = +new Date() - timeStart; 363 | if (timeDiff < delay) { 364 | setTimeout(function() { 365 | callback.call(self, data, items); 366 | }, delay - timeDiff); 367 | } else { 368 | callback.call(self, data, items); 369 | } 370 | } 371 | }, self), 'html'); 372 | }; 373 | 374 | /** 375 | * Renders items 376 | * 377 | * @param callback 378 | * @param items 379 | */ 380 | this.render = function(items, callback) { 381 | var self = this, 382 | $lastItem = this.getLastItem(), 383 | count = 0; 384 | 385 | var promise = this.fire('render', [items]); 386 | 387 | promise.done(function() { 388 | $(items).hide(); // at first, hide it so we can fade it in later 389 | 390 | $lastItem.after(items); 391 | 392 | $(items).fadeIn(400, function() { 393 | // complete callback get fired for each item, 394 | // only act on the last item 395 | if (++count < items.length) { 396 | return; 397 | } 398 | 399 | self.fire('rendered', [items]); 400 | 401 | if (callback) { 402 | callback(); 403 | } 404 | }); 405 | }); 406 | }; 407 | 408 | /** 409 | * Hides the pagination 410 | */ 411 | this.hidePagination = function() { 412 | if (this.paginationSelector) { 413 | $(this.paginationSelector, this.$container).hide(); 414 | } 415 | }; 416 | 417 | /** 418 | * Restores the pagination 419 | */ 420 | this.restorePagination = function() { 421 | if (this.paginationSelector) { 422 | $(this.paginationSelector, this.$container).show(); 423 | } 424 | }; 425 | 426 | /** 427 | * Throttles a method 428 | * 429 | * Adopted from Ben Alman's jQuery throttle / debounce plugin 430 | * 431 | * @param callback 432 | * @param delay 433 | * @return {object} 434 | */ 435 | this.throttle = function(callback, delay) { 436 | var lastExecutionTime = 0, 437 | wrapper, 438 | timerId 439 | ; 440 | 441 | wrapper = function() { 442 | var that = this, 443 | args = arguments, 444 | diff = +new Date() - lastExecutionTime; 445 | 446 | function execute() { 447 | lastExecutionTime = +new Date(); 448 | callback.apply(that, args); 449 | } 450 | 451 | if (!timerId) { 452 | execute(); 453 | } else { 454 | clearTimeout(timerId); 455 | } 456 | 457 | if (diff > delay) { 458 | execute(); 459 | } else { 460 | timerId = setTimeout(execute, delay); 461 | } 462 | }; 463 | 464 | if ($.guid) { 465 | wrapper.guid = callback.guid = callback.guid || $.guid++; 466 | } 467 | 468 | return wrapper; 469 | }; 470 | 471 | /** 472 | * Fires an event with the ability to cancel further processing. This 473 | * can be achieved by returning false in a listener. 474 | * 475 | * @param event 476 | * @param args 477 | * @returns {*} 478 | */ 479 | this.fire = function(event, args) { 480 | return this.listeners[event].fireWith(this, args); 481 | }; 482 | 483 | return this; 484 | }; 485 | 486 | /** 487 | * Initialize IAS 488 | * 489 | * Note: Should be called when the document is ready 490 | * 491 | * @public 492 | */ 493 | IAS.prototype.initialize = function() { 494 | var currentScrollOffset = this.getCurrentScrollOffset(this.$scrollContainer), 495 | scrollThreshold = this.getScrollThreshold(); 496 | 497 | this.hidePagination(); 498 | this.bind(); 499 | 500 | for (var i = 0, l = this.extensions.length; i < l; i++) { 501 | this.extensions[i].bind(this); 502 | } 503 | 504 | this.fire('ready'); 505 | 506 | this.nextUrl = this.getNextUrl(); 507 | 508 | // start loading next page if content is shorter than page fold 509 | if (currentScrollOffset >= scrollThreshold && this.nextUrl) { 510 | this.next(); 511 | } 512 | 513 | return this; 514 | }; 515 | 516 | /** 517 | * Binds IAS to DOM events 518 | * 519 | * @public 520 | */ 521 | IAS.prototype.bind = function() { 522 | if (this.isBound) { 523 | return; 524 | } 525 | 526 | this.$scrollContainer.on('scroll', $.proxy(this.throttle(this.scrollHandler, 150), this)); 527 | 528 | this.isBound = true; 529 | }; 530 | 531 | /** 532 | * Unbinds IAS to events 533 | * 534 | * @public 535 | */ 536 | IAS.prototype.unbind = function() { 537 | if (!this.isBound) { 538 | return; 539 | } 540 | 541 | this.$scrollContainer.off('scroll', this.scrollHandler); 542 | 543 | this.isBound = false; 544 | }; 545 | 546 | /** 547 | * Destroys IAS instance 548 | * 549 | * @public 550 | */ 551 | IAS.prototype.destroy = function() { 552 | this.unbind(); 553 | }; 554 | 555 | /** 556 | * Registers an eventListener 557 | * 558 | * Note: chainable 559 | * 560 | * @public 561 | * @returns IAS 562 | */ 563 | IAS.prototype.on = function(event, callback, priority) { 564 | if (typeof this.listeners[event] == 'undefined') { 565 | throw new Error('There is no event called "' + event + '"'); 566 | } 567 | 568 | priority = priority || 0; 569 | 570 | this.listeners[event].add($.proxy(callback, this), priority); 571 | 572 | return this; 573 | }; 574 | 575 | /** 576 | * Registers an eventListener which only gets 577 | * fired once. 578 | * 579 | * Note: chainable 580 | * 581 | * @public 582 | * @returns IAS 583 | */ 584 | IAS.prototype.one = function(event, callback) { 585 | var self = this; 586 | 587 | var remover = function() { 588 | self.off(event, callback); 589 | self.off(event, remover); 590 | }; 591 | 592 | this.on(event, callback); 593 | this.on(event, remover); 594 | 595 | return this; 596 | }; 597 | 598 | /** 599 | * Removes an eventListener 600 | * 601 | * Note: chainable 602 | * 603 | * @public 604 | * @returns IAS 605 | */ 606 | IAS.prototype.off = function(event, callback) { 607 | if (typeof this.listeners[event] == 'undefined') { 608 | throw new Error('There is no event called "' + event + '"'); 609 | } 610 | 611 | this.listeners[event].remove(callback); 612 | 613 | return this; 614 | }; 615 | 616 | /** 617 | * Load the next page 618 | * 619 | * @public 620 | */ 621 | IAS.prototype.next = function() { 622 | var url = this.nextUrl, 623 | self = this; 624 | 625 | this.unbind(); 626 | 627 | if (!url) { 628 | this.fire('noneLeft', [this.getLastItem()]); 629 | this.listeners['noneLeft'].disable(); // disable it so it only fires once 630 | 631 | self.bind(); 632 | 633 | return false; 634 | } 635 | 636 | var promise = this.fire('next', [url]); 637 | 638 | promise.done(function() { 639 | self.load(url, function(data, items) { 640 | self.render(items, function() { 641 | self.nextUrl = self.getNextUrl(data); 642 | 643 | self.bind(); 644 | }); 645 | }); 646 | }); 647 | 648 | promise.fail(function() { 649 | self.bind(); 650 | }); 651 | 652 | return true; 653 | }; 654 | 655 | /** 656 | * Adds an extension 657 | * 658 | * @public 659 | */ 660 | IAS.prototype.extension = function(extension) { 661 | if (typeof extension['bind'] == 'undefined') { 662 | throw new Error('Extension doesn\'t have required method "bind"'); 663 | } 664 | 665 | if (typeof extension['initialize'] != 'undefined') { 666 | extension.initialize(this); 667 | } 668 | 669 | this.extensions.push(extension); 670 | 671 | return this; 672 | }; 673 | 674 | /** 675 | * Shortcut. Sets the window as scroll container. 676 | * 677 | * @public 678 | * @param option 679 | * @returns {*} 680 | */ 681 | $.ias = function(option) { 682 | var $window = $(window); 683 | 684 | return $window.ias.apply($window, arguments); 685 | }; 686 | 687 | /** 688 | * jQuery plugin initialization 689 | * 690 | * @public 691 | * @param option 692 | * @returns {*} the last IAS instance will be returned 693 | */ 694 | $.fn.ias = function(option) { 695 | var args = Array.prototype.slice.call(arguments); 696 | var retval = this; 697 | 698 | this.each(function() { 699 | var $this = $(this), 700 | data = $this.data('ias'), 701 | options = $.extend({}, $.fn.ias.defaults, $this.data(), typeof option == 'object' && option) 702 | ; 703 | 704 | // set a new instance as data 705 | if (!data) { 706 | $this.data('ias', (data = new IAS($this, options))); 707 | 708 | $(document).ready($.proxy(data.initialize, data)); 709 | } 710 | 711 | // when the plugin is called with a method 712 | if (typeof option === 'string') { 713 | if (typeof data[option] !== 'function') { 714 | throw new Error('There is no method called "' + option + '"'); 715 | } 716 | 717 | args.shift(); // remove first argument ('option') 718 | data[option].apply(data, args); 719 | 720 | if (option === 'destroy') { 721 | $this.data('ias', null); 722 | } 723 | } 724 | 725 | retval = $this.data('ias'); 726 | }); 727 | 728 | return retval; 729 | }; 730 | 731 | /** 732 | * Plugin defaults 733 | * 734 | * @public 735 | * @type {object} 736 | */ 737 | $.fn.ias.defaults = { 738 | item: '.item', 739 | container: '.listing', 740 | next: '.next', 741 | pagination: false, 742 | delay: 600, 743 | negativeMargin: 10 744 | }; 745 | })(jQuery); 746 | if(typeof(IASHistoryExtension) == "undefined"){ 747 | /** 748 | * IAS History Extension 749 | * An IAS extension to enable browser history 750 | * http://infiniteajaxscroll.com 751 | * 752 | * This file is part of the Infinite AJAX Scroll package 753 | * 754 | * Copyright 2014 Webcreate (Jeroen Fiege) 755 | */ 756 | 757 | var IASHistoryExtension = function (options) { 758 | options = jQuery.extend({}, this.defaults, options); 759 | 760 | this.ias = null; 761 | this.prevSelector = options.prev; 762 | this.prevUrl = null; 763 | this.listeners = { 764 | prev: new IASCallbacks() 765 | }; 766 | 767 | /** 768 | * @private 769 | * @param pageNum 770 | * @param scrollOffset 771 | * @param url 772 | */ 773 | this.onPageChange = function (pageNum, scrollOffset, url) { 774 | var state = {}; 775 | 776 | if (!window.history || !window.history.replaceState) { 777 | return; 778 | } 779 | 780 | history.replaceState(state, document.title, url); 781 | }; 782 | 783 | /** 784 | * @private 785 | * @param currentScrollOffset 786 | * @param scrollThreshold 787 | */ 788 | this.onScroll = function (currentScrollOffset, scrollThreshold) { 789 | var firstItemScrollThreshold = this.getScrollThresholdFirstItem(); 790 | 791 | if (!this.prevUrl) { 792 | return; 793 | } 794 | 795 | currentScrollOffset -= this.ias.$scrollContainer.height(); 796 | 797 | if (currentScrollOffset <= firstItemScrollThreshold) { 798 | this.prev(); 799 | } 800 | }; 801 | 802 | /** 803 | * Returns the url for the next page 804 | * 805 | * @private 806 | */ 807 | this.getPrevUrl = function (container) { 808 | if (!container) { 809 | container = this.ias.$container; 810 | } 811 | 812 | // always take the last matching item 813 | var prev_url = jQuery(this.prevSelector, container).last().attr('href'); 814 | if(typeof(prev_url) != 'undefined') { 815 | prev_url += prev_url.includes('?') ? '&ajaxscroll=1' : '?ajaxscroll=1'; 816 | } else { 817 | prev_url = ''; 818 | } 819 | return prev_url; 820 | }; 821 | 822 | /** 823 | * Returns scroll threshold. This threshold marks the line from where 824 | * IAS should start loading the next page. 825 | * 826 | * @private 827 | * @return {number} 828 | */ 829 | this.getScrollThresholdFirstItem = function () { 830 | var $firstElement; 831 | 832 | $firstElement = this.ias.getFirstItem(); 833 | 834 | // if the don't have a first element, the DOM might not have been loaded, 835 | // or the selector is invalid 836 | if (0 === $firstElement.length) { 837 | return -1; 838 | } 839 | 840 | return ($firstElement.offset().top); 841 | }; 842 | 843 | /** 844 | * Renders items 845 | * 846 | * @private 847 | * @param items 848 | * @param callback 849 | */ 850 | this.renderBefore = function (items, callback) { 851 | var ias = this.ias, 852 | $firstItem = ias.getFirstItem(), 853 | count = 0; 854 | 855 | ias.fire('render', [items]); 856 | 857 | jQuery(items).hide(); // at first, hide it so we can fade it in later 858 | 859 | $firstItem.before(items); 860 | 861 | jQuery(items).fadeIn(400, function () { 862 | if (++count < items.length) { 863 | return; 864 | } 865 | 866 | ias.fire('rendered', [items]); 867 | 868 | if (callback) { 869 | callback(); 870 | } 871 | }); 872 | }; 873 | 874 | return this; 875 | }; 876 | 877 | /** 878 | * @public 879 | */ 880 | IASHistoryExtension.prototype.initialize = function (ias) { 881 | var self = this; 882 | 883 | this.ias = ias; 884 | 885 | // expose the extensions listeners 886 | jQuery.extend(ias.listeners, this.listeners); 887 | 888 | // expose prev method 889 | ias.prev = function() { 890 | return self.prev(); 891 | }; 892 | 893 | this.prevUrl = this.getPrevUrl(); 894 | }; 895 | 896 | /** 897 | * Bind to events 898 | * 899 | * @public 900 | * @param ias 901 | */ 902 | IASHistoryExtension.prototype.bind = function (ias) { 903 | var self = this; 904 | 905 | ias.on('pageChange', jQuery.proxy(this.onPageChange, this)); 906 | ias.on('scroll', jQuery.proxy(this.onScroll, this)); 907 | ias.on('ready', function () { 908 | var currentScrollOffset = ias.getCurrentScrollOffset(ias.$scrollContainer), 909 | firstItemScrollThreshold = self.getScrollThresholdFirstItem(); 910 | 911 | currentScrollOffset -= ias.$scrollContainer.height(); 912 | 913 | if (currentScrollOffset <= firstItemScrollThreshold) { 914 | self.prev(); 915 | } 916 | }); 917 | }; 918 | 919 | /** 920 | * Load the prev page 921 | * 922 | * @public 923 | */ 924 | IASHistoryExtension.prototype.prev = function () { 925 | var url = this.prevUrl, 926 | self = this, 927 | ias = this.ias; 928 | 929 | if (!url) { 930 | return false; 931 | } 932 | 933 | ias.unbind(); 934 | 935 | var promise = ias.fire('prev', [url]); 936 | 937 | promise.done(function () { 938 | ias.load(url, function (data, items) { 939 | self.renderBefore(items, function () { 940 | self.prevUrl = self.getPrevUrl(data); 941 | 942 | ias.bind(); 943 | 944 | if (self.prevUrl) { 945 | self.prev(); 946 | } 947 | }); 948 | }); 949 | }); 950 | 951 | promise.fail(function () { 952 | ias.bind(); 953 | }); 954 | 955 | return true; 956 | }; 957 | 958 | /** 959 | * @public 960 | */ 961 | IASHistoryExtension.prototype.defaults = { 962 | prev: ".prev" 963 | }; 964 | } 965 | if(typeof(IASNoneLeftExtension) == "undefined"){ 966 | /** 967 | * IAS None Left Extension 968 | * An IAS extension to show a message when there are no more pages te load 969 | * http://infiniteajaxscroll.com 970 | * 971 | * This file is part of the Infinite AJAX Scroll package 972 | * 973 | * Copyright 2014 Webcreate (Jeroen Fiege) 974 | */ 975 | 976 | var IASNoneLeftExtension = function(options) { 977 | options = jQuery.extend({}, this.defaults, options); 978 | 979 | this.ias = null; 980 | this.uid = (new Date()).getTime(); 981 | this.html = (options.html).replace('{text}', options.text); 982 | 983 | /** 984 | * Shows none left message 985 | */ 986 | this.showNoneLeft = function() { 987 | var $element = jQuery(this.html).attr('id', 'ias_noneleft_' + this.uid), 988 | $lastItem = this.ias.getLastItem(); 989 | 990 | $lastItem.after($element); 991 | $element.fadeIn(); 992 | }; 993 | 994 | return this; 995 | }; 996 | 997 | /** 998 | * @public 999 | */ 1000 | IASNoneLeftExtension.prototype.bind = function(ias) { 1001 | this.ias = ias; 1002 | 1003 | ias.on('noneLeft', jQuery.proxy(this.showNoneLeft, this)); 1004 | }; 1005 | 1006 | /** 1007 | * @public 1008 | */ 1009 | IASNoneLeftExtension.prototype.defaults = { 1010 | text: 'You reached the end.', 1011 | html: '
{text}
' 1012 | }; 1013 | } 1014 | if(typeof(IASPagingExtension) == "undefined"){ 1015 | /** 1016 | * IAS Paging Extension 1017 | * An IAS extension providing additional events 1018 | * http://infiniteajaxscroll.com 1019 | * 1020 | * This file is part of the Infinite AJAX Scroll package 1021 | * 1022 | * Copyright 2014 Webcreate (Jeroen Fiege) 1023 | */ 1024 | 1025 | var IASPagingExtension = function() { 1026 | this.ias = null; 1027 | this.pagebreaks = [[0, document.location.toString()]]; 1028 | this.lastPageNum = 1; 1029 | this.enabled = true; 1030 | this.listeners = { 1031 | pageChange: new IASCallbacks() 1032 | }; 1033 | 1034 | /** 1035 | * Fires pageChange event 1036 | * 1037 | * @param currentScrollOffset 1038 | * @param scrollThreshold 1039 | */ 1040 | this.onScroll = function(currentScrollOffset, scrollThreshold) { 1041 | if (!this.enabled) { 1042 | return; 1043 | } 1044 | 1045 | var ias = this.ias, 1046 | currentPageNum = this.getCurrentPageNum(currentScrollOffset), 1047 | currentPagebreak = this.getCurrentPagebreak(currentScrollOffset), 1048 | urlPage; 1049 | 1050 | if (this.lastPageNum !== currentPageNum) { 1051 | urlPage = currentPagebreak[1]; 1052 | 1053 | ias.fire('pageChange', [currentPageNum, currentScrollOffset, urlPage]); 1054 | } 1055 | 1056 | this.lastPageNum = currentPageNum; 1057 | }; 1058 | 1059 | /** 1060 | * Keeps track of pagebreaks 1061 | * 1062 | * @param url 1063 | */ 1064 | this.onNext = function(url) { 1065 | var currentScrollOffset = this.ias.getCurrentScrollOffset(this.ias.$scrollContainer); 1066 | 1067 | this.pagebreaks.push([currentScrollOffset, url]); 1068 | 1069 | // trigger pageChange and update lastPageNum 1070 | var currentPageNum = this.getCurrentPageNum(currentScrollOffset) + 1; 1071 | 1072 | this.ias.fire('pageChange', [currentPageNum, currentScrollOffset, url]); 1073 | 1074 | this.lastPageNum = currentPageNum; 1075 | }; 1076 | 1077 | /** 1078 | * Keeps track of pagebreaks 1079 | * 1080 | * @param url 1081 | */ 1082 | this.onPrev = function(url) { 1083 | var self = this, 1084 | ias = self.ias, 1085 | currentScrollOffset = ias.getCurrentScrollOffset(ias.$scrollContainer), 1086 | prevCurrentScrollOffset = currentScrollOffset - ias.$scrollContainer.height(), 1087 | $firstItem = ias.getFirstItem(); 1088 | 1089 | this.enabled = false; 1090 | 1091 | this.pagebreaks.unshift([0, url]); 1092 | 1093 | ias.one('rendered', function() { 1094 | // update pagebreaks 1095 | for (var i = 1, l = self.pagebreaks.length; i < l; i++) { 1096 | self.pagebreaks[i][0] = self.pagebreaks[i][0] + $firstItem.offset().top; 1097 | } 1098 | 1099 | // trigger pageChange and update lastPageNum 1100 | var currentPageNum = self.getCurrentPageNum(prevCurrentScrollOffset) + 1; 1101 | 1102 | ias.fire('pageChange', [currentPageNum, prevCurrentScrollOffset, url]); 1103 | 1104 | self.lastPageNum = currentPageNum; 1105 | 1106 | self.enabled = true; 1107 | }); 1108 | }; 1109 | 1110 | return this; 1111 | }; 1112 | 1113 | /** 1114 | * @public 1115 | */ 1116 | IASPagingExtension.prototype.initialize = function(ias) { 1117 | this.ias = ias; 1118 | 1119 | // expose the extensions listeners 1120 | jQuery.extend(ias.listeners, this.listeners); 1121 | }; 1122 | 1123 | /** 1124 | * @public 1125 | */ 1126 | IASPagingExtension.prototype.bind = function(ias) { 1127 | try { 1128 | ias.on('prev', jQuery.proxy(this.onPrev, this), this.priority); 1129 | } catch (exception) {} 1130 | 1131 | ias.on('next', jQuery.proxy(this.onNext, this), this.priority); 1132 | ias.on('scroll', jQuery.proxy(this.onScroll, this), this.priority); 1133 | }; 1134 | 1135 | /** 1136 | * Returns current page number based on scroll offset 1137 | * 1138 | * @param {number} scrollOffset 1139 | * @returns {number} 1140 | */ 1141 | IASPagingExtension.prototype.getCurrentPageNum = function(scrollOffset) { 1142 | for (var i = (this.pagebreaks.length - 1); i > 0; i--) { 1143 | if (scrollOffset > this.pagebreaks[i][0]) { 1144 | return i + 1; 1145 | } 1146 | } 1147 | 1148 | return 1; 1149 | }; 1150 | 1151 | /** 1152 | * Returns current pagebreak information based on scroll offset 1153 | * 1154 | * @param {number} scrollOffset 1155 | * @returns {number}|null 1156 | */ 1157 | IASPagingExtension.prototype.getCurrentPagebreak = function(scrollOffset) { 1158 | for (var i = (this.pagebreaks.length - 1); i >= 0; i--) { 1159 | if (scrollOffset > this.pagebreaks[i][0]) { 1160 | return this.pagebreaks[i]; 1161 | } 1162 | } 1163 | 1164 | return null; 1165 | }; 1166 | 1167 | /** 1168 | * @public 1169 | * @type {number} 1170 | */ 1171 | IASPagingExtension.prototype.priority = 500; 1172 | 1173 | } 1174 | if(typeof(IASSpinnerExtension) == "undefined"){ 1175 | /** 1176 | * IAS Spinner Extension 1177 | * An IAS extension to show a spinner when loading 1178 | * http://infiniteajaxscroll.com 1179 | * 1180 | * This file is part of the Infinite AJAX Scroll package 1181 | * 1182 | * Copyright 2014 Webcreate (Jeroen Fiege) 1183 | */ 1184 | 1185 | var IASSpinnerExtension = function(options) { 1186 | options = jQuery.extend({}, this.defaults, options); 1187 | 1188 | this.ias = null; 1189 | this.uid = new Date().getTime(); 1190 | this.src = options.src; 1191 | this.html = (options.html).replace('{src}', this.src); 1192 | 1193 | /** 1194 | * Shows spinner 1195 | */ 1196 | this.showSpinner = function() { 1197 | var $spinner = this.getSpinner() || this.createSpinner(), 1198 | $lastItem = this.ias.getLastItem(); 1199 | 1200 | $lastItem.after($spinner); 1201 | $spinner.fadeIn(); 1202 | }; 1203 | 1204 | /** 1205 | * Shows spinner 1206 | */ 1207 | this.showSpinnerBefore = function() { 1208 | var $spinner = this.getSpinner() || this.createSpinner(), 1209 | $firstItem = this.ias.getFirstItem(); 1210 | 1211 | $firstItem.before($spinner); 1212 | $spinner.fadeIn(); 1213 | }; 1214 | 1215 | /** 1216 | * Removes spinner 1217 | */ 1218 | this.removeSpinner = function() { 1219 | if (this.hasSpinner()) { 1220 | this.getSpinner().remove(); 1221 | } 1222 | }; 1223 | 1224 | /** 1225 | * @returns {jQuery|boolean} 1226 | */ 1227 | this.getSpinner = function() { 1228 | var $spinner = jQuery('#ias_spinner_' + this.uid); 1229 | 1230 | if ($spinner.length > 0) { 1231 | return $spinner; 1232 | } 1233 | 1234 | return false; 1235 | }; 1236 | 1237 | /** 1238 | * @returns {boolean} 1239 | */ 1240 | this.hasSpinner = function() { 1241 | var $spinner = jQuery('#ias_spinner_' + this.uid); 1242 | 1243 | return ($spinner.length > 0); 1244 | }; 1245 | 1246 | /** 1247 | * @returns {jQuery} 1248 | */ 1249 | this.createSpinner = function() { 1250 | var $spinner = jQuery(this.html).attr('id', 'ias_spinner_' + this.uid); 1251 | 1252 | $spinner.hide(); 1253 | 1254 | return $spinner; 1255 | }; 1256 | 1257 | return this; 1258 | }; 1259 | 1260 | /** 1261 | * @public 1262 | */ 1263 | IASSpinnerExtension.prototype.bind = function(ias) { 1264 | this.ias = ias; 1265 | 1266 | ias.on('next', jQuery.proxy(this.showSpinner, this)); 1267 | 1268 | try { 1269 | ias.on('prev', jQuery.proxy(this.showSpinnerBefore, this)); 1270 | } catch (exception) {} 1271 | 1272 | ias.on('render', jQuery.proxy(this.removeSpinner, this)); 1273 | }; 1274 | 1275 | /** 1276 | * @public 1277 | */ 1278 | IASSpinnerExtension.prototype.defaults = { 1279 | src: 'data:image/gif;base64,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', 1280 | html: '
' 1281 | }; 1282 | } 1283 | if(typeof(IASTriggerExtension) == "undefined"){ 1284 | /** 1285 | * IAS Trigger Extension 1286 | * An IAS extension to show a trigger link to load the next page 1287 | * http://infiniteajaxscroll.com 1288 | * 1289 | * This file is part of the Infinite AJAX Scroll package 1290 | * 1291 | * Copyright 2014 Webcreate (Jeroen Fiege) 1292 | */ 1293 | 1294 | var IASTriggerExtension = function(options) { 1295 | options = jQuery.extend({}, this.defaults, options); 1296 | 1297 | this.ias = null; 1298 | this.html = (options.html).replace('{text}', options.text); 1299 | this.htmlPrev = (options.htmlPrev).replace('{text}', options.textPrev); 1300 | this.enabled = true; 1301 | this.count = 0; 1302 | this.offset = options.offset; 1303 | this.$triggerNext = null; 1304 | this.$triggerPrev = null; 1305 | 1306 | /** 1307 | * Shows trigger for next page 1308 | */ 1309 | this.showTriggerNext = function() { 1310 | if (!this.enabled) { 1311 | return true; 1312 | } 1313 | 1314 | if (false === this.offset || ++this.count < this.offset) { 1315 | return true; 1316 | } 1317 | 1318 | var $trigger = this.$triggerNext || (this.$triggerNext = this.createTrigger(this.next, this.html)); 1319 | var $lastItem = this.ias.getLastItem(); 1320 | 1321 | $lastItem.after($trigger); 1322 | $trigger.fadeIn(); 1323 | 1324 | return false; 1325 | }; 1326 | 1327 | /** 1328 | * Shows trigger for previous page 1329 | */ 1330 | this.showTriggerPrev = function() { 1331 | if (!this.enabled) { 1332 | return true; 1333 | } 1334 | 1335 | var $trigger = this.$triggerPrev || (this.$triggerPrev = this.createTrigger(this.prev, this.htmlPrev)); 1336 | var $firstItem = this.ias.getFirstItem(); 1337 | 1338 | $firstItem.before($trigger); 1339 | $trigger.fadeIn(); 1340 | 1341 | return false; 1342 | }; 1343 | 1344 | /** 1345 | * @param clickCallback 1346 | * @returns {*|jQuery} 1347 | * @param {string} html 1348 | */ 1349 | this.createTrigger = function(clickCallback, html) { 1350 | var uid = (new Date()).getTime(), 1351 | $trigger; 1352 | 1353 | html = html || this.html; 1354 | $trigger = jQuery(html).attr('id', 'ias_trigger_' + uid); 1355 | 1356 | $trigger.hide(); 1357 | $trigger.on('click', jQuery.proxy(clickCallback, this)); 1358 | 1359 | return $trigger; 1360 | }; 1361 | 1362 | return this; 1363 | }; 1364 | 1365 | /** 1366 | * @public 1367 | * @param {object} ias 1368 | */ 1369 | IASTriggerExtension.prototype.bind = function(ias) { 1370 | var self = this; 1371 | 1372 | this.ias = ias; 1373 | 1374 | try { 1375 | ias.on('prev', jQuery.proxy(this.showTriggerPrev, this), this.priority); 1376 | } catch (exception) {} 1377 | 1378 | ias.on('next', jQuery.proxy(this.showTriggerNext, this), this.priority); 1379 | ias.on('rendered', function () { self.enabled = true; }, this.priority); 1380 | }; 1381 | 1382 | /** 1383 | * @public 1384 | */ 1385 | IASTriggerExtension.prototype.next = function() { 1386 | this.enabled = false; 1387 | this.ias.unbind(); 1388 | 1389 | if (this.$triggerNext) { 1390 | this.$triggerNext.remove(); 1391 | this.$triggerNext = null; 1392 | } 1393 | 1394 | this.ias.next(); 1395 | }; 1396 | 1397 | /** 1398 | * @public 1399 | */ 1400 | IASTriggerExtension.prototype.prev = function() { 1401 | this.enabled = false; 1402 | this.ias.unbind(); 1403 | 1404 | if (this.$triggerPrev) { 1405 | this.$triggerPrev.remove(); 1406 | this.$triggerPrev = null; 1407 | } 1408 | 1409 | this.ias.prev(); 1410 | }; 1411 | 1412 | /** 1413 | * @public 1414 | */ 1415 | IASTriggerExtension.prototype.defaults = { 1416 | text: 'Load more items', 1417 | html: '
{text}
', 1418 | textPrev: 'Load previous items', 1419 | htmlPrev: '
{text}
', 1420 | offset: 0 1421 | }; 1422 | 1423 | /** 1424 | * @public 1425 | * @type {number} 1426 | */ 1427 | IASTriggerExtension.prototype.priority = 1000; 1428 | } 1429 | 1430 | window.IASCallbacks=IASCallbacks;window.IASHistoryExtension=IASHistoryExtension;window.IASTriggerExtension=IASTriggerExtension;window.IASSpinnerExtension=IASSpinnerExtension;window.IASPagingExtension=IASPagingExtension;window.IASNoneLeftExtension=IASNoneLeftExtension; 1431 | --------------------------------------------------------------------------------