├── .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 | [](https://packagist.org/packages/magepow/infinitescroll)
8 | [](https://packagist.org/packages/magepow/infinitescroll)
9 | [](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 | 
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 | 
94 | 
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 | [](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 |
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: '',
1418 | textPrev: 'Load previous items',
1419 | htmlPrev: '',
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 |
--------------------------------------------------------------------------------