Ember CLI Pagination
Simple pagination addon for your Ember CLI app.
Features:
- Supports multiple types of pagination:
- Local
- Remote
- Infinite
- Default pagination template - but you can write your own
- Current page bound to the
page
query param - Compatible with the Kaminari API Rails gem
Questions?
This is a new project, but many people are already using it successfully. If you have any trouble, open an issue, and you should get help quickly.
Requirements
- ember-cli 1.13.0 or higher (For earlier versions use ember-cli-pagination 0.6.6)
- ember-cli-pagination 0.9.0 or higher for current docs.
Installation
ember install ember-cli-pagination
For ember-cli < 1.13.0:
npm install ember-cli-pagination@0.6.6 --save-dev
Usage
Scenarios
- Local Store
- Remote Paginated API
- Remote Unpaginated API
- Paginating a Filtered List
- Infinite Pagination with All Records Present Locally
- Infinite Pagination with a Remote Paginated API
Primitives
Other
Scenarios
Local Store
This scenario applies if:
- Have all of your records already loaded client-side.
- Wish to display one page of records at a time.
- Want to have a page query parameter (optional).
;; Controller;
{{#each pagedContent as |post|}} {{! your app's display logic}}{{/each}} {{page-numbers content=pagedContent}}
If you don't want to have query params, you may leave them out, along with the 3 bindings. The rest will still work.
In older versions of Ember you would have done:
// ...// can be called anything, I've called it pagedContent// remember to iterate over pagedContent in your templatepagedContent:// binding the property on the paged array// to a property on the controllertotalPagesBinding: "totalPages"
Notes
- There is no need to touch the route in this scenario.
- There used to be route and controller mixins, and they may return in the future. For now, they were too much overhead, and they were too much magic. If you think getting rid of the mixins is a mistake, please open an issue and let me know.
Remote Paginated API
This scenario applies if:
- Loading your records from a remote pagination-enabled API.
- Wish to display one page of records at a time.
- Want to have a page query parameter. (optional)
- Need to access a zero Based Index remote pagination-enabled API. (optional)
1:1 based page index
;; Route;
Zero based page index
;; Route;
; Controller;
{{#each model}} {{! your app's display logic}}{{/each}} {{page-numbers content=content}}
If you don't want to have query params, you may leave them out, along with the 3 bindings. The rest will still work.
In older versions of Ember you would have done:
// ...// binding the property on the paged array// to the query params on the controllerpageBinding: "content.page"perPageBinding: "content.perPage"totalPagesBinding: "content.totalPages"
Passing other params to findPaged
If your params object has other params, they will be passed to your backend.
EmberRoute;
Using other names for page/perPage/total_pages
You may pass an optional paramMapping arg. This is a hash that allows you to change the param names for page/perPage/total_pages.
Note that the default param name for perPage is per_page.
page
and perPage
control what is sent to the backend. total_pages
controls where we expect to find the total pages value in the response from the backend.
;; Route;
You can also pass a mapping function for the paramMapping. A common usage for this would be a limit and offset API backend. This is done by passing an array as the mapping. The first item in the array being the param name, and the second item being the value mapping function. The function should accept one parameter, an object with keys page
and perPage
and their respective values.
;; Route;
Get updates outside
Sometimes you may need to handle remote paginated API without refreshModel
param, to provide more smooth update of data. In that case you could use
default method to track it or add a custom observer.
Here is an example how to use the default one:
// routes/index.jsRoute; //controllers/index.jsController;
As far as returned from '.findPaged()' method instance of PagedRemoteArray
inherits Ember.Evented, you can subscribe on contentWillChange
and
contentUpdated
events.
Notes
- There used to be a controller mixin, and they may return in the future. For now, it was too much overhead, and it was too much magic. If you think getting rid of the mixin is a mistake, please open an issue and let me know.
- Related: Setup a Paginated Rails API
Force reloading data
This scenario assumes that we know we need to refresh the data from server. For example when we sent new data to the server and we want to display them in our application:
// route.js;
{{!-- template.hbs --}} <button {{action "createNewPost"}}>Click me to create new post</button> <ul> {{#each model as |post|}} <li>{{post.id}}</li> {{/each}}</ul>
// controller.js;
Remote Unpaginated API
This scenario applies if:
- Loading your records from a remote API that is not pagination-enabled.
- You are ok with loading all records from the API in order to display one page at a time.
- Wish to display one page of records at a time.
- Want to have a page query parameter (optional).
This scenario is identical to the Local Store scenario.
Paginating a Filtered List
This scenario applies if:
- Have all of your records already loaded client-side.
- You are filtering those records down to create a subset for display
- Wish to display one page of records at a time.
- Want to have a page query parameter (optional).
;; Controller;
{{#each pagedContent}} {{! your app's display logic}}{{/each}} {{page-numbers content=pagedContent}}
If you don't want to have query params, you may leave them out, along with the 3 bindings. The rest will still work.
In older versions of Ember you would have done:
// ...// binding the property on the paged array// to the query params on the controllerpageBinding: "pagedContent.page"perPageBinding: "pagedContent.perPage"totalPagesBinding: "pagedContent.totalPages"
Notes
- There is no need to touch the route in this scenario.
Infinite Pagination with All Records Present Locally
The infinite pagination sections of the docs is not yet up to my preferred quality level. If you have any questions or problems, please do not hesitate to make an issue.
The example below does not use a page query param, although that is certainly possible.
Controller:
;; Controller;
"unpaged"
in this example indicates the source array (the content
property) is a regular (unpaged) array, as opposed to a PagedArray.
Infinite Pagination with a Remote Paginated API
The example below does not use a page query param, although that is certainly possible.
// controller ;; Controller;
{infinite: true}
in this example indicates the source array (the content
property) is a paged array, in this case a PagedRemoteArray.
// route ;; Route;
Primitives
page-numbers
Component
Displays pagination controls.
Features:
- A clickable link for each page.
- Previous and next buttons, disabled when appropriate.
- The link to the current page receives the .active class.
- Styling with bootstrap, if included.
Including in your template
There are two ways to use this component.
Backed by a PagedArray
This is the easier and most common way.
EmberController;
{{#each pagedContent}} {{! your app's display logic}}{{/each}} {{page-numbers content=pagedContent}}
Clicking a page number will:
- Display the rows on the clicked page.
- Update
pagedContent.page
to the clicked page.
See the pagedArray doc for more information on the pagedArray helper.
currentPage
and totalPages
to your properties directly
Bind EmberObject;
{{page-numbers currentPage=page totalPages=totalPages}}
Clicking a page number will:
- Update the
page
property to the clicked page.
Customization
You can use your own template for the pagination controls. Create it in your app at app/templates/components/page-numbers.hbs and it will be used automatically. Note: do not use ember generate component page-numbers
, as this will also create an empty JavaScript controller file. Create/copy the page-numbers.hbs file yourself.
See the default template for an example.
To always show the first and last pages (in addition to the pages that would be shown normally), set the showFL property
page-numbers content=content showFL=true
Future Additions
- <</>> links to move more than one page at a time.
- Configuration settings to change behavior, remove arrows, etc.
pagedArray
Computed Helper
Creates a computed property representing a PagedArray.
A PagedArray acts just like a normal array containing only the records on the current page.
Takes two arguments:
- A
contentProperty
argument, representing the name of the "all objects" property on the source object. - An optional
options
hash. Currently the only allowed options are page and perPage, both integers
A PagedArray has several properties you may find useful:
page
: the current page (Default: 1)perPage
: how many records to have on each page (Default: 10)totalPages
: the total number of pages
; Object;
In this example, these properties will be available:
pagedContent.page
pagedContent.perPage
pagedContent.totalPages
The pagedContent property can serve as a backing array for pagination controls. See the page-numbers component for details.
PagedLocalArray
PagedLocalArray represents a page of records from the list of all records.
All records must be loaded client-side in order to use PagedArray.
It takes three arguments at creation, in a standard options hash passed to PagedArray#create:
- content - list of all records
- page - Optional (Default 1)
- perPage - Optional (Default 10)
Once the data is loaded, you may iterate over a PagedArray as you would a normal array.
The object acts as a promise, with a working then
method.
; var all = Ember;var paged = PagedArray; ;; paged;; all;;
Updating
A Paged will be updated when the page property is changed.
Binding
You may bind PagedArray#page like any property.
To update records when a page property changes:
EmberController;
In older versions of Ember you would have done:
EmberController;
PagedRemoteArray
PagedRemoteArray represents a page of records fetched from a remote pagination-enabled API.
It takes six arguments at creation, in a standard options hash passed to PagedRemoteArray#create:
- modelName - singular
- store
- page
- perPage
- otherParams - optional. If provided, will be passed on to server at same level as page and perPage
- paramMapping - optional. Allows configuration of param names for page/perPage/total_pages
Once the data is loaded, you may iterate over a PagedRemoteArray as you would a normal array.
The object acts as a promise, with a working then
method. If you are manually iterating over records outside of the standard workflow, make sure to use then
with standard promise semantics, just as you would an object returned from a normal store.find
call.
; Route;
Updating
A PagedRecordArray will make a new remote call to update records when the page property is changed. Again, standard promise usage applies here.
// pagedArray represents a PagedRemoteArray, already created and loaded with data, with page=1// var pagedArray = .... // this will trigger the remote call for new datapagedArray; pagedArray;
Reloading
A PagedRecordArray has a reload method which you can use to refresh the current data. All params passed when constructing the PagedRecordArray will remain unchanged. The method returns a promise which is resolved when new data are loaded.
// pagedArray represents a PagedRemoteArray, already created// var pagedArray = .... // this will trigger the remote call to refresh the current pagepagedArray;
Binding
You may bind PagedRemoteArray#page like any property.
To update records when a page property changes:
EmberController;
In older versions of Ember you would have done:
EmberController;
otherParams
PagedRemoteArray takes an optional otherParams arg. These params will be passed to the server when the request is made.
var paged = PagedRemoteArray; // server will receive params page=1, perPage=2, name=Adam
paramMapping
PagedRemoteArray takes an optional paramMapping arg. This is a hash that allows you to change the param names for page/perPage/total_pages.
Note that the default param name for perPage is per_page.
page
and perPage
control what is sent to the backend. total_pages
controls where we expect to find the total pages value in the response from the backend.
// This will send a request with pageNum and limit params,// and expect a response with a num_pages param in the meta.var paged = PagedRemoteArray;
Other
Testing
We include some helpers to make testing pagination easier.
The helper used here is responseHash, in the context of a Pretender definition.
It takes the request, all fixtures, and the model name, and returns the appropriate response (with meta tag).
import Todo from '../../models/todo'import Helpers from 'ember-cli-pagination/test-helpers' = -> server = -> @get "/todos" res = HelpersresponseHashrequestTodoFIXTURES'todo' 200"Content-Type": "application/json"JSONstringifyres export default c
Setup Paginated Rails API
# Gemfile
# controller # I'm fairly sure you shouldn't need to set the meta manually, but for now that's what I'm doing. page = (params[:page] || 1).to_i todos = Todo.page(page).per(10) render json: todos, meta: {total_pages: todos.total_pages} endend
Contributors
You guys rock!
- @broerse
- @robdel12
- @samselikoff
- @pedrokiefer
- @gcollazo
- @johanneswuerback
- @tonycoco
- @dlameri
- @piotrpalek
- @robertleib
- @halfdan
- @bschnelle
- @mcm-ham
- @jcope2013
- @thejchap
- @sarupbanskota
- @chrisccerami
- @potato20
- @aleontiev
- @jeffreybiles
- @fidlip
- @lancedikson
- @marceloandrader
- @asermax
- @balupton
- @noslouch
- @irruputuncu
- @thomaswelton
- @brentdanley
- @pleszkowicz
- @mixonic
- @chrisdevor
- @MichalBryxi
- @flyrev
- @armiiller
- @artemgurzhii
- @iezer
- @jlami
- @synaptiko
- @rinoldsimon
- @fivetanley