Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/adopted-ember-addons/ember-impagination
An Ember Addon that puts the fun back in asynchronous, paginated datasets
https://github.com/adopted-ember-addons/ember-impagination
dataset ember ember-addon infinite-scroll pagination
Last synced: 5 days ago
JSON representation
An Ember Addon that puts the fun back in asynchronous, paginated datasets
- Host: GitHub
- URL: https://github.com/adopted-ember-addons/ember-impagination
- Owner: adopted-ember-addons
- License: mit
- Created: 2015-10-19T21:30:49.000Z (about 9 years ago)
- Default Branch: master
- Last Pushed: 2023-04-17T10:56:42.000Z (over 1 year ago)
- Last Synced: 2024-10-22T07:53:11.992Z (14 days ago)
- Topics: dataset, ember, ember-addon, infinite-scroll, pagination
- Language: JavaScript
- Homepage: https://ember-impagination.netlify.com/
- Size: 1.76 MB
- Stars: 122
- Watchers: 15
- Forks: 14
- Open Issues: 28
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE.md
- Code of conduct: CODE_OF_CONDUCT.md
Awesome Lists containing this project
README
# Ember-Impagination
[![npm version](https://badge.fury.io/js/ember-impagination.svg)](https://badge.fury.io/js/ember-impagination)
[![Ember Observer Score](http://emberobserver.com/badges/ember-impagination.svg)](http://emberobserver.com/addons/ember-impagination)
[![Build Status](https://travis-ci.org/adopted-ember-addons/ember-impagination.svg?branch=master)](https://travis-ci.org/adopted-ember-addons/ember-impagination)
[![Netlify Status](https://api.netlify.com/api/v1/badges/f31e187a-b0aa-4b4f-9567-9bcb6873f0a5/deploy-status)](https://app.netlify.com/sites/ember-impagination/deploys)_Ember-Impagination_ is an Ember binding for
[Impagination](https://github.com/flexyford/impagination), a front-end
data-layer for the paginated API on your server. _Ember-Impagination_
leverages the power of Glimmer and provides your component the data it
needs to render quickly each and every time.> Impagination README:
> Whatever your use-case: infinite scrolling lists, a carousel
> browser, or even a classic page-by-page result list, Impagination
> frees you to focus on what you want to do with your data, not the
> micro-logistics of when to fetch it. All you provide Impagination is
> the logic to fetch a single page, plus how many pages you want it to
> pre-fetch ahead of you, and it will figure out the rest.
> Impagination is built using an event-driven immutable style, so it
> is ideal for use with UI frameworks like Ember . . .Hence, we present _Ember-Impagination_.
_Ember-Impagination_ provides you with a component,
`{{impagination-dataset as |data|}}`, you can use to feed data into
your templates while having that data look exactly like an
`Ember.Array`.> Note: _Ember-Impagination_ does not provide any of the visual
> elements in a system like infinite scroll. You'll still need to use
> a special component like `virtual-each` or
> `ember-collection`. Instead, _Ember-Impagination_ simplifies feeding
> your components fetched data.## Installation
- `ember install ember-impagination`
## Demo
[Ember-Impagination Demo](https://ember-impagination.netlify.com/)
The demo presents a finite scroll implementation of
_Ember-Impagination_. It scrolls through the ROYGBIV color spectrum by
loading and unloading pages of records, where each record is a unique
color-hue. At the top of the demo, you will find a visualization for
pages. Resolved (Loaded) Pages are green, Pending (Loading) pages are
white, and Unrequested (Unloaded) pages are black. The white-bar
represents the top-most index of the scroll view.![ember-impagination](http://g.recordit.co/iltQTaYwSb.gif)
The demo is implemented using
[virtual-each](https://github.com/jasonmit/virtual-each) due to the
simplicity of the component. However, _Ember-Impagination_ can also be
utilized with other components like
[ember-collection](https://github.com/emberjs/ember-collection), or
even a simple `{{each}}`. By design, _Ember-Impagination_ leverages
Glimmer and yields paginated data from your server's API to components
which expect an array.## Usage
### Impagination-Dataset Component
To create an `impagination-dataset` there are two _required_
parameters, `fetch` and `page-size`. Optional parameters include
`load-horizon`, `unload-horizon`, `unfetch`. See
[Impagination](https://github.com/flexyford/impagination) for detailed
attribute descriptions.```hbs
{{!-- app/templates/index.hbs --}}
{{#impagination-dataset
fetch=fetch
page-size=pageSize
load-horizon=loadHorizon
as |records|}}
Total Records: {{records.length}}
{{#each records as |record|}}
Record {{record.content.id}}
{{/each}}
{{/impagination-dataset}}
```Now, in your route, you can define the actual `(un)fetch` functions
that tell `{{impagination-dataset}}` how it should request each
individual page, and the `(un)loadHorizon` which specify how many
pages to request ahead/behind.```javascript
// app/route/record.js
export default Ember.Route.extend({
pageSize: 5, // fetch records in pages of 5 (*required*)
loadHorizon: 10, // fetch records "inclusive" (+/- loadHorizon) of the current readOffset (default: pageSize)
//unloadHorizon: Infinity, // unload records "exclusive" (+/- unloadHorizon) of the current readOffset (default: Infinity)
//readOffset: 0, // the initial readOffset of the dataset (default: 0)// fetch() function is invoked whenever a page is requested within the loadHorizon
fetch: function(pageOffset, pageSize, stats) {
// function which returns a "thenable" (*required*)
let params = {
page: pageOffset
};
// fetch a page of records at the pageOffset
return this.store.query("record", params).then(data => {
let meta = data.get("meta");
stats.totalPages = meta.totalPages;
return data.toArray();
});
},
// unfetch() function is invoked whenever a page is unloaded
unfetch: function(records, pageOffset) {
this.store.findByIds(
"record",
records
.map(r => r.id)
.then(function(records) {
records.forEach(record => record.deleteRecord());
})
);
}
});
```This setup will immediatly call fetch twice (for records 0-4 [page 0]
and records 5-9 [page 1])```text
Total Records: 10
Record 0
Record 1
Record 2
...
Record 9
```#### Passing the Fetch Function
In **Ember 1.13 and above**, we can use closure-actions to pass the
fetch function into `ember-impagination````handlebars
{{#impagination-dataset fetch=(action "fetch")}}
``````javascript
// app/route/record.js
export default Ember.Route.extend({
// fetch() function is invoked whenever a page is requested within the loadHorizon
actions: {
fetch(pageOffset, pageSize, stats) {
// function which returns a "thenable" (*required*)
let params = {
query: query
};
// fetch a page of records at the pageOffset
return this.store.query("record", params).then(data => {
let meta = data.get("meta");
stats.totalPages = meta.totalPages;
return data.toArray();
});
}
}
});
```> We do not recommend defining `fetch` inside your controller because
> it requires
> [injecting the store into the controller](https://github.com/adopted-ember-addons/ember-impagination/issues/39#issuecomment-172101680)In **Ember 1.12 and below** we cannot define `fetch` in our actions
hash. We must instead bind it to our controller.```handlebars
{{#impagination-dataset fetch=fetch)}}
``````javascript
// app/route/record.js
export default Ember.Route.extend({
fetch: function(pageOffset, pageSize, stats) {
return this.store.query(...);
},
setupController: function(controller, model){
this._super.apply(this, arguments);
controller.set('fetch', this.fetch.bind(this));
}
});
```### Filtering Records
We fetch records using an immutable style, but we often require
filtering by mutable records in our dataset. To enable filtering, pass
a filter `callback` to `ember-impagination` as you would to
`Array.prototype.filter()`. The filters are applied as soon as a page
is resolved. To filter a page at other times in your application see
[`refilter`](#dataset-actions).```handlebars
{{#impagination-dataset fetch=(action "fetch") filter=filterCallback}}
``````javascript
// app/route/record.js
export default Ember.Route.extend({
// filter() function is invoked whenever a page is resolved or refiltered
filterCallback(record /*, index, records*/) {
// function which rejects deleted records
return !record.get("isDeleted");
}
});
```### Dataset API
There are a number actions to update the dataset.
#### Updating the Dataset
| Actions | Parameters | Description |
| ------------- | :--------------: | :------------------------------------------------------------------------------------------------------------- |
| refilter | [filterCallback] | Reapplies the filter for all resolved pages. If `filterCallback` is provided, applies and sets the new filter. |
| reset | [offset] | Unfetches all pages and clears the `state`. If `offset` is provided, fetches records starting at `offset`. |
| setReadOffset | [offset] | Sets the `readOffset` and fetches records resuming at `offset` |
| ~~reload~~ | ~~[index]~~ | _Removed in `1.0.0` release. Please use `reset` instead._ |#### Updating the State
| Actions | Parameters | Defaults | Description |
| ------- | :---------: | :----------------------- | :------------------------------------- |
| post | data, index | index = 0 | Creates record with `data` at `index`. |
| put | data, index | index = state.readOffset | Merges `data` into record at `index`. |
| delete | index | index= state.readOffset | Deletes record at `index`. |These functions can be called from the route/controller or from child
components in the handlebars templates. In the examples below, we
`reset` the dataset upon search queries through the {{search-pane}}
component using both options.#### resetting from the parent route
In order to call dataset actions from the route, we will have to
observe the latest dataset and dataset-actions with the `on-observe`
parameter.```handlebars
{{#search-pane search=(action "search")}}
{{#impagination-dataset on-observe=(action "observeDataset") fetch=(action "fetch") as |dataset|}}
{{#ember-collection items=dataset as |record|}}
{{chat-search-result result=record}}
{{/ember-collection}}
{{/impagination-dataset}}
{{/search-pane}}
``````javascript
_resetDataset() {
this.get('dataset').reset();
},actions: {
observeDataset: function(dataset) {
this.set('dataset', dataset);
},
search(query) {
this.set('searchParams', query);
this._resetDataset();
},
fetch(pageOffset, pageSize, stats) {
params = this.get('params');
return this.store.query('records', params);
}
}
```#### resetting from child components
Here we do not need to utilize `impagination-dataset`'s `on-observe`
parameter. The `reset` action is simply called by a child component.```handlebars
{{#impagination-dataset fetch=(action "fetch") as |dataset|}}
{{!-- reset dataset and start fetching at record index 0 --}}
{{#search-pane search=(action "search") on-search-results=(action dataset.reset 0)}}
{{#ember-collection items=dataset as |record|}}
{{chat-search-result result=record}}
{{/ember-collection}}
{{/search-pane}}
{{/impagination-dataset}}
```### Create your own Dataset
If `{{impagination-dataset}}` is not an ideal component for your
unique `Impagination` needs, you can get into the nitty gritty, and
use `Impagination` directly. If you find yourself creating your own
`Dataset`, let us know how you are using `Dataset` and
`Impagination`. It may be a reason for improvements or another ember
addon.```js
import Dataset from 'impagination/dataset';let dataset = new Dataset({
pageSize: 5,
fetch: function(pageOffset, pageSize, stats) {
return new Ember.RSVP.Promise((resolve)=> {
let data = // Array
resolve(data);
},return this.store.query('record', params).then((data) => {
let meta = result.get('meta');
stats.totalPages = meta.totalPages;
return result.toArray();
});
},
observe: (state) => {}
});
```### Running tests
- `ember test` – Runs the test suite on the current Ember version
- `ember test --server` – Runs the test suite in "watch mode"
- `ember try:each` – Runs the test suite against multiple Ember versions### Running the dummy application
- `ember serve`
- Visit the dummy application at [http://localhost:4200](http://localhost:4200).For more information on using ember-cli, visit [https://ember-cli.com/](https://ember-cli.com/).
### Code of Conduct
Please note that this project is released with a Contributor Code of
Conduct. By participating in this project you agree to abide by its
terms, which can be found in the `CODE_OF_CONDUCT.md` file in this
repository.### License
---
This project is licensed under the [MIT License](LICENSE.md).