Skip to content

Commit 2e9c06d

Browse files
add package files
1 parent 1633c27 commit 2e9c06d

6 files changed

+405
-0
lines changed

CHANGELOG.md

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
## 0.9.2
2+
- Customizable loading template
3+
4+
## 0.9.1
5+
- Addded `ecmascript` dependency, thanks @JulianKingman
6+
7+
## 0.9.0
8+
- Thanks to @mikepaszkiewicz for his contribution to this release.
9+
- Added example application
10+
- Collection is now looked up by name
11+
- No more ``app`` globals
12+
13+
## 0.8.0
14+
- Remove dependency on Counts, didn't work out so well
15+
- Customizable class on the loader template
16+
- Exposes `InfiniteScroll.triggerLoadMore` so you can use custom scroll regions (eg. `this.$('.results-list').on('resize scroll', _.throttle(InfiniteScroll.triggerLoadMore, 300));`)
17+
18+
## 0.7.2
19+
- Added check package to dependencies
20+
21+
## 0.7.0
22+
- Add sort functionality
23+
24+
## 0.6.1
25+
- Added Tracker.afterFlush when checking to load more data
26+
27+
## 0.6.0
28+
- Overhauled reliability of the loader template and triggers
29+
- Removed `Template.instance().infiniteReady()` in favor of `Template.instance().infiniteSub.ready()`
30+
- New dependency on `tmeasday:publish-counts` for more reliable loading
31+
32+
# 0.5.0
33+
- Fixed an issue with tall window heights
34+
35+
# 0.4.0
36+
- Requires Meteor 1.2 or greater
37+
- Fixed a bug where the loader would not show on the initial subscription load
38+
- Fixed a bug where the subscription query would be mutated by the call to Meteor.subscribe
39+
40+
# 0.3.0
41+
- Minor fixes
42+
43+
# 0.2.0
44+
- Added `subManager` property to set the subscription on a `meteorhacks:subs-manager` Subscription Manager instead of the template. This allows the data to persist when the template is destroyed.
45+
46+
# 0.1.0
47+
- Initial release

README.md

+99
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
# Meteor Infinite Scroll
2+
**Enables infinite scrolling at the template level**. This package allows you to increment the `limit` parameter of a MongoDB query as the user scrolls down the page. This allows Meteor to use the Oplog Observe Driver for your query, as well as leaving you in control of your publications.
3+
4+
## Usage:
5+
6+
Call `this.infiniteScroll` in the `created` or `rendered` functions for your template.
7+
8+
```js
9+
Template.comments.created = function() {
10+
// Enable infinite scrolling on this template
11+
this.infiniteScroll({
12+
perPage: 20, // How many results to load "per page"
13+
query: { // The query to use as the selector in our collection.find() query
14+
post: 71
15+
},
16+
subManager: new SubsManager(), // (optional, experimental) A meteorhacks:subs-manager to set the subscription on
17+
// Useful when you want the data to persist after this template
18+
// is destroyed.
19+
collection: 'Comments', // The name of the collection to use for counting results
20+
publication: 'CommentsInfinite' // (optional) The name of the publication to subscribe.
21+
// Defaults to {collection}Infinite
22+
container: '#selector' // (optional) Selector to scroll div.
23+
loadingTemplateName:'loading' // (optional) Name Of loading template
24+
});
25+
};
26+
```
27+
28+
Create a publication on the server:
29+
30+
```js
31+
if(Meteor.isServer){
32+
Meteor.publish('CommentsInfinite', function(limit, query) {
33+
// Don't use the query object directly in your cursor for security!
34+
var selector = {};
35+
check(limit, Number);
36+
check(query.name, String);
37+
// Assign safe values to a new object after they have been validated
38+
selector.name = query.name;
39+
40+
return Comments.find(selector, {
41+
limit: limit,
42+
// Using sort here is necessary to continue to use the Oplog Observe Driver!
43+
// https://github.com/meteor/meteor/wiki/Oplog-Observe-Driver
44+
sort: {
45+
created: 1
46+
}
47+
});
48+
});
49+
}
50+
```
51+
52+
Render your data as usual. Render the `{{> infiniteScroll }}` template after your data is rendered:
53+
54+
```handlebars
55+
<template name="comments">
56+
{{#each comments}}
57+
{{content}}
58+
{{/each}}
59+
{{> infiniteScroll }}
60+
</template>
61+
```
62+
> Infinite Scroll will increase the `limit` of the subscription as the `{{> infiniteScroll }}` template approaches the viewport.
63+
64+
Provide data to the template as you usually would. Use `Template.instance().infiniteSub.ready()` like you would use `subscriptionsReady()` on the template instance.
65+
```js
66+
Template.comments.helpers({
67+
comments: function() {
68+
return Comments.find({ post: 71 }, {
69+
sort: {
70+
created: 1
71+
}
72+
});
73+
}
74+
});
75+
```
76+
77+
### Only `limit`
78+
79+
Using `skip` will cause Meteor to use the Polling Observe Driver (see [Oplog Observe Driver in the Meteor Wiki](https://github.com/meteor/meteor/wiki/Oplog-Observe-Driver)). For a full pagination solution that uses skip, check out [alethes:pages](https://github.com/alethes/meteor-pages).
80+
81+
## Styling the loader
82+
The `{{> infiniteScroll }}` template renders:
83+
```html
84+
<template name="infiniteScroll">
85+
<div class="infinite-load-more">
86+
<div class="infinite-label">
87+
Loading...
88+
</div>
89+
</div>
90+
</template>
91+
```
92+
93+
When the subscription is loading more data, `.infinite-load-more` will receive the class `loading`. It will be removed when the subscription is marked as ready.
94+
95+
`.infinite-label` is only visible when the subscription is loading.
96+
97+
# Todo:
98+
- Customizable loading template
99+
- Customizable threshold for loading more results

infinite-scroll.js

+213
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
'use strict';
2+
3+
InfiniteScroll = {};
4+
5+
var CONTAINER;
6+
var LOADING_TEMPLATE_NAME;
7+
8+
/**
9+
* Triggers 'triggerInfiniteLoad' event when the user has scrolled
10+
* to the trigger point.
11+
*/
12+
function triggerLoadMore() {
13+
if ($('.infinite-load-more').isAlmostVisible()) {
14+
$(document).trigger('triggerInfiniteLoad');
15+
}
16+
}
17+
InfiniteScroll.triggerLoadMore = triggerLoadMore;
18+
19+
20+
/**
21+
* jQuery plugin to determine whether an element is "almost visible".
22+
* @return {Boolean}
23+
*/
24+
jQuery.fn.isAlmostVisible = function jQueryIsAlmostVisible() {
25+
if (this.length === 0) {
26+
return;
27+
}
28+
var rect = this[0].getBoundingClientRect();
29+
30+
return (
31+
rect.top >= 0 &&
32+
rect.left >= 0 &&
33+
rect.bottom <= (jQuery(CONTAINER).height() * 1.5) &&
34+
rect.right <= jQuery(CONTAINER).width()
35+
);
36+
};
37+
38+
/**
39+
* Enable infinite scrolling on a template.
40+
*/
41+
Blaze.TemplateInstance.prototype.infiniteScroll = function infiniteScroll(options) {
42+
var tpl = this, _defaults, collection, subManagerCache, limit, subscriber, loadMore;
43+
44+
/*
45+
* Create options from defaults
46+
*/
47+
_defaults = {
48+
// How many results to fetch per "page"
49+
perPage: 10,
50+
// The query to use when fetching our collection
51+
query: {},
52+
// The sorting instructions for MongoDB
53+
sort: {},
54+
// The subscription manager to use (optional)
55+
subManager: null,
56+
// Collection to use for counting the amount of results
57+
collection: null,
58+
// Publication to subscribe to, if null will use {collection}Infinite
59+
publication: null,
60+
// Container will use to scroll
61+
container: window
62+
};
63+
options = _.extend({}, _defaults, options);
64+
65+
// Validate the options
66+
check(options.perPage, Number);
67+
check(options.collection, String);
68+
check(options.publication, String);
69+
check(options.container,String);
70+
check(options.loadingTemplateName, Match.Optional(String));
71+
72+
CONTAINER = options.container || _defaults.container;
73+
LOADING_TEMPLATE_NAME = options.loadingTemplateName;
74+
75+
//using collection instances package here to scan all collection and checks ours exists
76+
//may be a more elegant packageless solution but coudn't find anything
77+
let collectionExists = Meteor.Collection.getAll().find(c => c.name === options.collection);
78+
// Collection exists?
79+
if (!collectionExists) {
80+
throw new Error('Collection does not exist: ', options.collection);
81+
} else {
82+
//set collection to cursor. collectionExists.name evaluates to a string
83+
collection = collectionExists.instance;
84+
}
85+
86+
// Generate the publication name if one hasn't been provided
87+
if(!options.publication){
88+
options.publication = options.collection + 'Infinite';
89+
}
90+
91+
// If we are using a subscription manager, cache the limit variable with the subscription
92+
if(options.subManager){
93+
// Create the cache object if it doesn't exist
94+
if(!options.subManager._infinite){
95+
options.subManager._infinite = {};
96+
options.subManager._infinite[options.publication] = {};
97+
}
98+
subManagerCache = options.subManager._infinite[options.publication];
99+
}
100+
101+
// We use 'limit' so that Meteor can continue to use the OpLogObserve driver
102+
// See: https://github.com/meteor/meteor/wiki/Oplog-Observe-Driver
103+
// (There are a few types of queries that still use PollingObserveDriver)
104+
limit = new ReactiveVar();
105+
106+
// If the query changes, the limit must reset
107+
if(options.query instanceof ReactiveVar){
108+
tpl.autorun(function(){
109+
options.query.get();
110+
limit.set(options.perPage);
111+
});
112+
}
113+
114+
// Retrieve the initial page size
115+
if(subManagerCache && subManagerCache.limit){
116+
limit.set(subManagerCache.limit);
117+
}else{
118+
limit.set(options.perPage);
119+
}
120+
121+
// Create subscription to the collection
122+
tpl.autorun(function() {
123+
// Rerun when the limit changes
124+
var lmt = limit.get();
125+
126+
// If a Subscription Manager has been supplied, use that instead to create
127+
// the subscription. This is useful if you want to keep the subscription
128+
// loaded for multiple templates.
129+
if(options.subManager){
130+
subscriber = options.subManager;
131+
// Save the limit in the subscription manager so we can look it up later
132+
subManagerCache.limit = lmt;
133+
}else{
134+
subscriber = tpl;
135+
}
136+
137+
var query;
138+
if(options.query instanceof ReactiveVar){
139+
query = options.query.get();
140+
}else{
141+
query = options.query;
142+
}
143+
144+
var sort;
145+
if(options.sort instanceof ReactiveVar){
146+
sort = options.sort.get();
147+
}else{
148+
sort = options.sort;
149+
}
150+
151+
tpl.infiniteSub = subscriber.subscribe(options.publication, lmt, query, sort, null);
152+
});
153+
154+
// Check to see if we need to load more
155+
tpl.autorun(function(){
156+
if(tpl.infiniteSub.ready()){
157+
Tracker.afterFlush(triggerLoadMore);
158+
}
159+
});
160+
161+
162+
/**
163+
* Load more results if our limit is below the total
164+
*/
165+
loadMore = function() {
166+
167+
var query;
168+
if(options.query instanceof ReactiveVar){
169+
query = options.query.get();
170+
}else{
171+
query = options.query;
172+
}
173+
174+
var lmt = limit.get(), results = collection.find(query).count();
175+
if(results >= lmt){
176+
limit.set(lmt + options.perPage);
177+
}
178+
};
179+
180+
// Trigger loadMore when we've scrolled/resized close to revealing .load-more
181+
$(document).off('triggerInfiniteLoad');
182+
$(document).on('triggerInfiniteLoad', loadMore);
183+
};
184+
185+
/**
186+
* Attempt to trigger infinite loading when resize and scroll browser
187+
* events are fired.
188+
*/
189+
Template.infiniteScroll.onCreated(function () {
190+
$(CONTAINER).on('resize scroll', _.throttle(triggerLoadMore, 500));
191+
});
192+
193+
Template.infiniteScroll.helpers({
194+
195+
loading: function(){
196+
197+
// Loop through parent templates until we find infiniteSub
198+
var tpl = Template.instance();
199+
while(!tpl.infiniteSub){
200+
var parent = tpl.parents();
201+
if(!parent){
202+
break;
203+
}
204+
tpl = parent;
205+
}
206+
207+
return !tpl.infiniteSub.ready();
208+
},
209+
loadingTemplateName: function () {
210+
return LOADING_TEMPLATE_NAME;
211+
}
212+
213+
});

infinite-scroll.less

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
.infinite-load-more{
2+
3+
.infinite-label{
4+
display:none;
5+
}
6+
7+
&.loading{
8+
.infinite-label{
9+
display:block;
10+
}
11+
}
12+
13+
}
14+

infiniteScroll.html

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<template name="infiniteScroll">
2+
<div class="infinite-load-more {{#if loading}}loading{{/if}}">
3+
<div class="infinite-label {{class}}">
4+
{{#if loadingTemplateName}}
5+
{{> Template.dynamic template=loadingTemplateName }}
6+
{{else}}
7+
Loading...
8+
{{/if}}
9+
</div>
10+
</div>
11+
</template>

0 commit comments

Comments
 (0)