-
-
Notifications
You must be signed in to change notification settings - Fork 131
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add afterInfinityModel #105
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -115,30 +115,39 @@ and ember-infinity will be set up to parse the total number of pages from a JSON | |
} | ||
``` | ||
|
||
You may override `updateInfinityModel` to customize how the route's `model` should be updated with new objects. Let's say we only want to show objects with `isPublished === true`: | ||
### Cursor-based pagination | ||
|
||
```js | ||
updateInfinityModel(newObjects) { | ||
let infinityModel = this.get(this.get('_modelPath')); | ||
|
||
let content = newObjects.get('content'); | ||
let filtered = content.filter(obj => { return obj.get('isPublished'); }); | ||
|
||
return infinityModel.pushObjects(filtered); | ||
} | ||
``` | ||
If you are serving a continuously updating stream, it's helpful to keep track | ||
of your place in the list while paginating, to avoid duplicates. This is known | ||
as **cursor-based pagination** and is common in popular APIs like Twitter, | ||
Facebook, and Instagram. Instead of relying on `page_number` to paginate, | ||
you'll want to extract the `min_id` or `min_updated_at` from each page of | ||
results, so that you can fetch the next page without risking duplicates if new | ||
items are added to the top of the list by other users in between requests. | ||
|
||
You may also invoke this method directly to manually push new objects into the model: | ||
To do this, implement the `afterInfinityModel` hook as follows: | ||
|
||
```js | ||
actions: { | ||
pushHughsRecordsIntoInfinityModel() [ | ||
var updatedInfinityModel = this.updateInfinityModel(Ember.A([ | ||
{ id: 1, name: "Hugh Francis Discography", isPublished: true } | ||
])); | ||
console.log(updatedInfinityModel); | ||
export default Ember.Route.extend(InfinityRoute, { | ||
_minId: undefined, | ||
_minUpdatedAt: undefined, | ||
_canLoadMore: true, | ||
|
||
model() { | ||
return this.infinityModel("post", {}, { | ||
min_id: '_minId', | ||
min_updated_at: '_minUpdatedAt' | ||
}); | ||
}, | ||
|
||
afterInfinityModel(posts) { | ||
loadedAny = posts.get('length') > 0; | ||
this.set('_canLoadMore', loadedAny); | ||
|
||
this.set('_minId', posts.get('lastObject.id')); | ||
this.set('_minUpdatedAt', posts.get('lastObject.updated_at').toISOString()); | ||
} | ||
} | ||
}); | ||
``` | ||
|
||
### infinityModel | ||
|
@@ -164,11 +173,11 @@ import InfinityRoute from 'ember-infinity/mixins/route'; | |
export default Ember.Route.extend(InfinityRoute, { | ||
... | ||
|
||
prod: function () { return this.get('cat'); }.property('cat'), | ||
prod: Ember.computed('cat', function () { return this.get('cat'); }), | ||
country: '', | ||
cat: 'shipped', | ||
|
||
model: function () { | ||
model() { | ||
return this.infinityModel("product", { perPage: 12, startingPage: 1, make: "original" }, { country: "country", category: "prod" }); | ||
} | ||
}); | ||
|
@@ -193,18 +202,53 @@ When you need to pass in bound parameters but no static parameters or custom pag | |
|
||
`modelPath` is optional parameter for situations when you are overriding `setupController` | ||
or when your model is on different location than `controller.model`. | ||
|
||
```js | ||
model: function() { | ||
model() { | ||
return this.infinityModel("product", { | ||
perPage: 12, | ||
startingPage: 1, | ||
modelPath: 'controller.products' | ||
}); | ||
}, | ||
setupController: function(controller, model) { | ||
setupController(controller, model) { | ||
controller.set('products', model); | ||
} | ||
``` | ||
|
||
### afterInfinityModel | ||
|
||
In some cases, a single call to your data store isn't enough. The afterInfinityModel | ||
method is available for those cases when you need to chain together functions or | ||
promises after fetching a model. | ||
|
||
As a simple example, let's say you had a blog and just needed to set a property | ||
on each Post model after fetching all of them: | ||
|
||
```js | ||
model() { | ||
return this.infinityModel("post"); | ||
}, | ||
|
||
afterInfinityModel(posts) { | ||
posts.setEach('author', 'Jane Smith'); | ||
} | ||
``` | ||
|
||
As a more complex example, let's say you had a blog with Posts and Authors as separate | ||
related models and you needed to extract an association from Posts. In that case, | ||
return the collection you want from afterInfinityModel: | ||
|
||
```js | ||
model() { | ||
return this.infinityModel("post"); | ||
}, | ||
|
||
afterInfinityModel(posts) { | ||
return posts.mapBy('author').uniq(); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This would need to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This example isn't great - it could just do afterInfinityModel(posts) {
return posts.mapBy('author');
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. no need for a promise at all |
||
``` | ||
|
||
### Event Hooks | ||
|
||
The route mixin also provides following event hooks: | ||
|
@@ -237,15 +281,15 @@ import InfinityRoute from 'ember-infinity/mixins/route'; | |
export default Ember.Route.extend(InfinityRoute, { | ||
... | ||
|
||
model: function () { | ||
model() { | ||
/* Load pages of the Product Model, starting from page 1, in groups of 12. */ | ||
return this.infinityModel("product", { perPage: 12, startingPage: 1 }); | ||
}, | ||
|
||
infinityModelUpdated: function(totalPages) { | ||
infinityModelUpdated(totalPages) { | ||
Ember.Logger.debug('updated with more items'); | ||
}, | ||
infinityModelLoaded: function(lastPageLoaded, totalPages, infinityModel) { | ||
infinityModelLoaded(lastPageLoaded, totalPages, infinityModel) { | ||
Ember.Logger.info('no more items to load'); | ||
} | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,7 +12,7 @@ const keys = Object.keys || Ember.keys; | |
@module ember-infinity/mixins/route | ||
@extends Ember.Mixin | ||
*/ | ||
export default Ember.Mixin.create({ | ||
const RouteMixin = Ember.Mixin.create({ | ||
|
||
/** | ||
@private | ||
|
@@ -199,6 +199,26 @@ export default Ember.Mixin.create({ | |
return this._loadNextPage(); | ||
}, | ||
|
||
/** | ||
Call additional functions after finding the infinityModel in the Ember data store. | ||
@private | ||
@method _afterInfinityModel | ||
@param {Function} infinityModelPromise The resolved result of the Ember store find method. Passed in automatically. | ||
@return {Ember.RSVP.Promise} | ||
*/ | ||
_afterInfinityModel(_this) { | ||
return function(infinityModelPromiseResult) { | ||
if (typeof _this.afterInfinityModel === 'function') { | ||
let result = _this.afterInfinityModel(infinityModelPromiseResult); | ||
if (result) { | ||
return result; | ||
} | ||
} | ||
|
||
return infinityModelPromiseResult; | ||
}; | ||
}, | ||
|
||
/** | ||
Trigger a load of the next page of results. | ||
|
||
|
@@ -250,7 +270,8 @@ export default Ember.Mixin.create({ | |
const nextPage = this.incrementProperty('currentPage'); | ||
const params = this._buildParams(nextPage); | ||
|
||
return this.store[this._storeFindMethod](modelName, params); | ||
return this.store[this._storeFindMethod](modelName, params).then( | ||
this._afterInfinityModel(this)); | ||
}, | ||
|
||
/** | ||
|
@@ -280,11 +301,16 @@ export default Ember.Mixin.create({ | |
Update the infinity model with new objects | ||
Only called on the second page and following | ||
|
||
@deprecated | ||
@method updateInfinityModel | ||
@param {Ember.Enumerable} newObjects The new objects to add to the model | ||
@return {Ember.Array} returns the new objects | ||
*/ | ||
updateInfinityModel(newObjects) { | ||
return this._doUpdate(newObjects); | ||
}, | ||
|
||
_doUpdate(newObjects) { | ||
let infinityModel = this._infinityModel(); | ||
return infinityModel.pushObjects(newObjects.get('content')); | ||
}, | ||
|
@@ -303,7 +329,19 @@ export default Ember.Mixin.create({ | |
let infinityModel = newObjects; | ||
|
||
if (this.get('_firstPageLoaded')) { | ||
infinityModel = this.updateInfinityModel(newObjects); | ||
if (typeof this.updateInfinityModel === 'function' && | ||
(this.updateInfinityModel !== | ||
Ember.Object.extend(RouteMixin).create().updateInfinityModel)) { | ||
Ember.deprecate("EmberInfinity.updateInfinityModel is deprecated. "+ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
"Please use EmberInfinity.afterInfinityModel.", | ||
false, | ||
{id: 'ember-infinity.updateInfinityModel', until: '2.1'} | ||
); | ||
|
||
infinityModel = this.updateInfinityModel(newObjects); | ||
} else { | ||
infinityModel = this._doUpdate(newObjects); | ||
} | ||
} | ||
|
||
this.set('_firstPageLoaded', true); | ||
|
@@ -352,3 +390,5 @@ export default Ember.Mixin.create({ | |
Ember.run.scheduleOnce('afterRender', this, 'infinityModelLoaded', { totalPages: totalPages }); | ||
} | ||
}); | ||
|
||
export default RouteMixin; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NICE