├── app
├── .gitkeep
├── components
│ └── paginate-collection.js
└── templates
│ └── components
│ └── paginate-collection.hbs
├── addon
├── .gitkeep
├── mixins
│ ├── controllers
│ │ └── jsonapi-pagination.js
│ └── routes
│ │ └── jsonapi-pagination.js
└── components
│ └── paginate-collection.js
├── vendor
└── .gitkeep
├── tests
├── unit
│ ├── .gitkeep
│ ├── mixins
│ │ ├── controllers
│ │ │ └── jsonapi-pagination-test.js
│ │ └── routes
│ │ │ └── jsonapi-pagination-test.js
│ └── components
│ │ └── paginate-collection-test.js
├── integration
│ ├── .gitkeep
│ └── components
│ │ └── paginate-collection-test.js
├── dummy
│ ├── app
│ │ ├── helpers
│ │ │ └── .gitkeep
│ │ ├── models
│ │ │ ├── .gitkeep
│ │ │ └── rental.js
│ │ ├── routes
│ │ │ ├── .gitkeep
│ │ │ └── rentals
│ │ │ │ └── index.js
│ │ ├── components
│ │ │ └── .gitkeep
│ │ ├── controllers
│ │ │ ├── .gitkeep
│ │ │ └── rentals
│ │ │ │ └── index.js
│ │ ├── templates
│ │ │ ├── components
│ │ │ │ └── .gitkeep
│ │ │ ├── application.hbs
│ │ │ └── rentals
│ │ │ │ └── index.hbs
│ │ ├── styles
│ │ │ └── app.scss
│ │ ├── resolver.js
│ │ ├── router.js
│ │ ├── app.js
│ │ └── index.html
│ ├── public
│ │ ├── robots.txt
│ │ └── crossdomain.xml
│ ├── mirage
│ │ ├── models
│ │ │ └── rental.js
│ │ ├── serializers
│ │ │ ├── application.js
│ │ │ └── rental.js
│ │ ├── factories
│ │ │ └── rental.js
│ │ ├── scenarios
│ │ │ └── default.js
│ │ └── config.js
│ └── config
│ │ └── environment.js
├── test-helper.js
├── helpers
│ ├── destroy-app.js
│ ├── resolver.js
│ ├── start-app.js
│ └── module-for-acceptance.js
├── .jshintrc
├── index.html
└── acceptance
│ └── pagination-test.js
├── .watchmanconfig
├── .bowerrc
├── index.js
├── config
├── environment.js
└── ember-try.js
├── .npmignore
├── testem.js
├── bower.json
├── CHANGELOG.md
├── .ember-cli
├── .gitignore
├── ember-cli-build.js
├── .jshintrc
├── .editorconfig
├── .travis.yml
├── LICENSE.md
├── package.json
└── README.md
/app/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/addon/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/vendor/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/unit/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/integration/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/dummy/app/helpers/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/dummy/app/models/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/dummy/app/routes/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/dummy/app/components/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/dummy/app/controllers/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/dummy/app/templates/components/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/dummy/app/styles/app.scss:
--------------------------------------------------------------------------------
1 | @import "bootstrap";
2 |
--------------------------------------------------------------------------------
/.watchmanconfig:
--------------------------------------------------------------------------------
1 | {
2 | "ignore_dirs": ["tmp", "dist"]
3 | }
4 |
--------------------------------------------------------------------------------
/.bowerrc:
--------------------------------------------------------------------------------
1 | {
2 | "directory": "bower_components",
3 | "analytics": false
4 | }
5 |
--------------------------------------------------------------------------------
/tests/dummy/public/robots.txt:
--------------------------------------------------------------------------------
1 | # http://www.robotstxt.org
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/tests/dummy/app/resolver.js:
--------------------------------------------------------------------------------
1 | import Resolver from 'ember-resolver';
2 |
3 | export default Resolver;
4 |
--------------------------------------------------------------------------------
/tests/dummy/mirage/models/rental.js:
--------------------------------------------------------------------------------
1 | import { Model } from 'ember-cli-mirage';
2 |
3 | export default Model.extend();
4 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | /* jshint node: true */
2 | 'use strict';
3 |
4 | module.exports = {
5 | name: 'ember-cli-jsonapi-pagination'
6 | };
7 |
--------------------------------------------------------------------------------
/tests/dummy/app/templates/application.hbs:
--------------------------------------------------------------------------------
1 |
Dummy App
2 |
3 | {{#link-to "rentals"}}Rentals{{/link-to}}
4 |
5 | {{outlet}}
6 |
--------------------------------------------------------------------------------
/tests/dummy/app/models/rental.js:
--------------------------------------------------------------------------------
1 | import DS from 'ember-data';
2 |
3 | export default DS.Model.extend({
4 | name: DS.attr('string')
5 | });
6 |
--------------------------------------------------------------------------------
/config/environment.js:
--------------------------------------------------------------------------------
1 | /*jshint node:true*/
2 | 'use strict';
3 |
4 | module.exports = function(/* environment, appConfig */) {
5 | return { };
6 | };
7 |
--------------------------------------------------------------------------------
/tests/test-helper.js:
--------------------------------------------------------------------------------
1 | import resolver from './helpers/resolver';
2 | import {
3 | setResolver
4 | } from 'ember-qunit';
5 |
6 | setResolver(resolver);
7 |
--------------------------------------------------------------------------------
/tests/dummy/mirage/serializers/application.js:
--------------------------------------------------------------------------------
1 | import { JSONAPISerializer } from 'ember-cli-mirage';
2 |
3 | export default JSONAPISerializer.extend();
4 |
5 |
--------------------------------------------------------------------------------
/app/components/paginate-collection.js:
--------------------------------------------------------------------------------
1 | import PaginateCollection from 'ember-cli-jsonapi-pagination/components/paginate-collection';
2 |
3 | export default PaginateCollection;
4 |
--------------------------------------------------------------------------------
/tests/dummy/mirage/factories/rental.js:
--------------------------------------------------------------------------------
1 | import { Factory } from 'ember-cli-mirage';
2 |
3 | export default Factory.extend({
4 | name(i) { return `Rental ${i + 1}`; },
5 | });
6 |
--------------------------------------------------------------------------------
/tests/helpers/destroy-app.js:
--------------------------------------------------------------------------------
1 | /* global server */
2 | import Ember from 'ember';
3 |
4 | export default function destroyApp(application) {
5 | Ember.run(application, 'destroy');
6 | server.shutdown();
7 | }
8 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | /bower_components
2 | /config/ember-try.js
3 | /dist
4 | /tests
5 | /tmp
6 | **/.gitkeep
7 | .bowerrc
8 | .editorconfig
9 | .ember-cli
10 | .gitignore
11 | .jshintrc
12 | .watchmanconfig
13 | .travis.yml
14 | bower.json
15 | ember-cli-build.js
16 | testem.js
17 |
--------------------------------------------------------------------------------
/tests/dummy/app/templates/rentals/index.hbs:
--------------------------------------------------------------------------------
1 |
2 | {{#each model as |rental|}}
3 | - {{rental.name}}
4 | {{/each}}
5 |
6 |
7 | {{paginate-collection totalPages=totalPages currentPage=number setCurrentPage=(action "setCurrentPage")}}
8 |
--------------------------------------------------------------------------------
/testem.js:
--------------------------------------------------------------------------------
1 | /*jshint node:true*/
2 | module.exports = {
3 | "framework": "qunit",
4 | "test_page": "tests/index.html?hidepassed",
5 | "disable_watching": true,
6 | "launch_in_ci": [
7 | "PhantomJS"
8 | ],
9 | "launch_in_dev": [
10 | "PhantomJS",
11 | "Chrome"
12 | ]
13 | };
14 |
--------------------------------------------------------------------------------
/tests/dummy/app/routes/rentals/index.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import Pagination from 'ember-cli-jsonapi-pagination/mixins/routes/jsonapi-pagination';
3 |
4 | export default Ember.Route.extend(Pagination, {
5 | model(params) {
6 | return this.queryPaginated('rental', params);
7 | }
8 | });
9 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ember-cli-jsonapi-pagination",
3 | "dependencies": {
4 | "ember": "~2.6.0",
5 | "ember-cli-shims": "0.1.1",
6 | "ember-cli-test-loader": "0.2.2",
7 | "ember-qunit-notifications": "0.1.0",
8 | "pretender": "~1.1.0",
9 | "Faker": "~3.1.0"
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # ember-cli-jsonapi-pagination Changelog
2 |
3 | ## 0.0.2
4 |
5 | Update notes:
6 | - None
7 |
8 | Changes:
9 | - [FEATURE] #2 improve performance for generating paginator by @Azdaroth
10 |
11 |
12 | ## 0.0.1
13 |
14 | Changes:
15 | - #1 implement pagination using paged strategy by @Azdaroth
16 |
--------------------------------------------------------------------------------
/.ember-cli:
--------------------------------------------------------------------------------
1 | {
2 | /**
3 | Ember CLI sends analytics information by default. The data is completely
4 | anonymous, but there are times when you might want to disable this behavior.
5 |
6 | Setting `disableAnalytics` to true will prevent any data from being sent.
7 | */
8 | "disableAnalytics": false
9 | }
10 |
--------------------------------------------------------------------------------
/tests/dummy/app/router.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import config from './config/environment';
3 |
4 | const Router = Ember.Router.extend({
5 | location: config.locationType
6 | });
7 |
8 | Router.map(function() {
9 | this.route('rentals', function() {
10 |
11 | });
12 | });
13 |
14 | export default Router;
15 |
--------------------------------------------------------------------------------
/tests/helpers/resolver.js:
--------------------------------------------------------------------------------
1 | import Resolver from '../../resolver';
2 | import config from '../../config/environment';
3 |
4 | const resolver = Resolver.create();
5 |
6 | resolver.namespace = {
7 | modulePrefix: config.modulePrefix,
8 | podModulePrefix: config.podModulePrefix
9 | };
10 |
11 | export default resolver;
12 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # compiled output
4 | /dist
5 | /tmp
6 |
7 | # dependencies
8 | /node_modules
9 | /bower_components
10 |
11 | # misc
12 | /.sass-cache
13 | /connect.lock
14 | /coverage/*
15 | /libpeerconnection.log
16 | npm-debug.log
17 | testem.log
18 |
--------------------------------------------------------------------------------
/tests/dummy/mirage/scenarios/default.js:
--------------------------------------------------------------------------------
1 | export default function(server) {
2 |
3 | /*
4 | Seed your development database using your factories.
5 | This data will not be loaded in your tests.
6 |
7 | Make sure to define a factory for each model you want to create.
8 | */
9 |
10 | server.createList('rental', 100);
11 | }
12 |
--------------------------------------------------------------------------------
/tests/dummy/mirage/serializers/rental.js:
--------------------------------------------------------------------------------
1 | import ApplicationSerializer from './application';
2 |
3 | export default ApplicationSerializer.extend({
4 | serialize(object) {
5 | let json = ApplicationSerializer.prototype.serialize.apply(this, arguments);
6 | json.meta = {
7 | "total-pages": object.totalPages
8 | };
9 | return json;
10 | }
11 | });
12 |
13 |
--------------------------------------------------------------------------------
/tests/dummy/app/controllers/rentals/index.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import Pagination from 'ember-cli-jsonapi-pagination/mixins/controllers/jsonapi-pagination';
3 |
4 | export default Ember.Controller.extend(Pagination, {
5 | size: 5,
6 |
7 | totalPages: Ember.computed('size', 'number', 'model.[]', function() {
8 | return this.get('model.meta.total-pages');
9 | })
10 | });
11 |
--------------------------------------------------------------------------------
/addon/mixins/controllers/jsonapi-pagination.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 |
3 | export default Ember.Mixin.create({
4 | size: 15,
5 | number: 1,
6 | queryParams: ['size', 'number'],
7 |
8 | actions: {
9 | setCurrentPage: function(currentPage) {
10 | this.set('number', currentPage);
11 | },
12 |
13 | setCurrentSize: function(currentSize) {
14 | this.set('size', currentSize);
15 | }
16 | }
17 | });
18 |
--------------------------------------------------------------------------------
/tests/dummy/app/app.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import Resolver from './resolver';
3 | import loadInitializers from 'ember-load-initializers';
4 | import config from './config/environment';
5 |
6 | let App;
7 |
8 | Ember.MODEL_FACTORY_INJECTIONS = true;
9 |
10 | App = Ember.Application.extend({
11 | modulePrefix: config.modulePrefix,
12 | podModulePrefix: config.podModulePrefix,
13 | Resolver
14 | });
15 |
16 | loadInitializers(App, config.modulePrefix);
17 |
18 | export default App;
19 |
--------------------------------------------------------------------------------
/tests/helpers/start-app.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import Application from '../../app';
3 | import config from '../../config/environment';
4 |
5 | export default function startApp(attrs) {
6 | let application;
7 |
8 | let attributes = Ember.merge({}, config.APP);
9 | attributes = Ember.merge(attributes, attrs); // use defaults, but you can override;
10 |
11 | Ember.run(() => {
12 | application = Application.create(attributes);
13 | application.setupForTesting();
14 | application.injectTestHelpers();
15 | });
16 |
17 | return application;
18 | }
19 |
--------------------------------------------------------------------------------
/ember-cli-build.js:
--------------------------------------------------------------------------------
1 | /*jshint node:true*/
2 | /* global require, module */
3 | var EmberAddon = require('ember-cli/lib/broccoli/ember-addon');
4 |
5 | module.exports = function(defaults) {
6 | var app = new EmberAddon(defaults, {
7 | // Add options here
8 | });
9 |
10 | /*
11 | This build file specifies the options for the dummy test app of this
12 | addon, located in `/tests/dummy`
13 | This build file does *not* influence how the addon or the app using it
14 | behave. You most likely want to be modifying `./index.js` or app's build file
15 | */
16 |
17 | return app.toTree();
18 | };
19 |
--------------------------------------------------------------------------------
/addon/mixins/routes/jsonapi-pagination.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 |
3 | export default Ember.Mixin.create({
4 | queryParams: {
5 | size: {
6 | refreshModel: true
7 | },
8 | number: {
9 | refreshModel: true
10 | }
11 | },
12 |
13 | queryPaginated: function(modelName, params) {
14 | this.resolveParamsForPagination(params);
15 | return this.store.query(modelName, params);
16 | },
17 |
18 | resolveParamsForPagination: function(params) {
19 | params.page = { number: params.number, size: params.size };
20 |
21 | delete params.number;
22 | delete params.size;
23 | }
24 | });
25 |
--------------------------------------------------------------------------------
/tests/dummy/public/crossdomain.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
15 |
16 |
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "predef": [
3 | "server",
4 | "document",
5 | "window",
6 | "-Promise"
7 | ],
8 | "browser": true,
9 | "boss": true,
10 | "curly": true,
11 | "debug": false,
12 | "devel": true,
13 | "eqeqeq": true,
14 | "evil": true,
15 | "forin": false,
16 | "immed": false,
17 | "laxbreak": false,
18 | "newcap": true,
19 | "noarg": true,
20 | "noempty": false,
21 | "nonew": false,
22 | "nomen": false,
23 | "onevar": false,
24 | "plusplus": false,
25 | "regexp": false,
26 | "undef": true,
27 | "sub": true,
28 | "strict": false,
29 | "white": false,
30 | "eqnull": true,
31 | "esnext": true,
32 | "unused": true
33 | }
34 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig helps developers define and maintain consistent
2 | # coding styles between different editors and IDEs
3 | # editorconfig.org
4 |
5 | root = true
6 |
7 |
8 | [*]
9 | end_of_line = lf
10 | charset = utf-8
11 | trim_trailing_whitespace = true
12 | insert_final_newline = true
13 | indent_style = space
14 | indent_size = 2
15 |
16 | [*.js]
17 | indent_style = space
18 | indent_size = 2
19 |
20 | [*.hbs]
21 | insert_final_newline = false
22 | indent_style = space
23 | indent_size = 2
24 |
25 | [*.css]
26 | indent_style = space
27 | indent_size = 2
28 |
29 | [*.html]
30 | indent_style = space
31 | indent_size = 2
32 |
33 | [*.{diff,md}]
34 | trim_trailing_whitespace = false
35 |
--------------------------------------------------------------------------------
/tests/dummy/mirage/config.js:
--------------------------------------------------------------------------------
1 | export default function() {
2 | this.get('/rentals', (schema, request) => {
3 | let allRentals = schema.rentals.all();
4 | if (request.queryParams["page[number]"] && request.queryParams["page[size]"]) {
5 | let pageNumber = Number(request.queryParams["page[number]"]);
6 | let pageSize = Number(request.queryParams["page[size]"]);
7 | allRentals.totalPages = Math.ceil(allRentals.models.length / pageSize);
8 |
9 | let fromIndex = (pageNumber - 1) * pageSize;
10 | let rentalsForCurrentPage = allRentals.models.splice(fromIndex, pageSize);
11 |
12 | allRentals.models = rentalsForCurrentPage;
13 | }
14 | return allRentals;
15 | });
16 | }
17 |
--------------------------------------------------------------------------------
/tests/dummy/app/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Dummy
7 |
8 |
9 |
10 | {{content-for "head"}}
11 |
12 |
13 |
14 |
15 | {{content-for "head-footer"}}
16 |
17 |
18 | {{content-for "body"}}
19 |
20 |
21 |
22 |
23 | {{content-for "body-footer"}}
24 |
25 |
26 |
--------------------------------------------------------------------------------
/tests/helpers/module-for-acceptance.js:
--------------------------------------------------------------------------------
1 | import { module } from 'qunit';
2 | import Ember from 'ember';
3 | import startApp from '../helpers/start-app';
4 | import destroyApp from '../helpers/destroy-app';
5 |
6 | const { RSVP: { Promise } } = Ember;
7 |
8 | export default function(name, options = {}) {
9 | module(name, {
10 | beforeEach() {
11 | this.application = startApp();
12 |
13 | if (options.beforeEach) {
14 | return options.beforeEach.apply(this, arguments);
15 | }
16 | },
17 |
18 | afterEach() {
19 | let afterEach = options.afterEach && options.afterEach.apply(this, arguments);
20 | return Promise.resolve(afterEach).then(() => destroyApp(this.application));
21 | }
22 | });
23 | }
24 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | ---
2 | language: node_js
3 | node_js:
4 | - "4"
5 |
6 | sudo: false
7 |
8 | cache:
9 | directories:
10 | - node_modules
11 |
12 | env:
13 | - EMBER_TRY_SCENARIO=default
14 | - EMBER_TRY_SCENARIO=2.0
15 | - EMBER_TRY_SCENARIO=2.1
16 | - EMBER_TRY_SCENARIO=2.2
17 | - EMBER_TRY_SCENARIO=2.3
18 | - EMBER_TRY_SCENARIO=2.4
19 | - EMBER_TRY_SCENARIO=2.5
20 | - EMBER_TRY_SCENARIO=2.6
21 | - EMBER_TRY_SCENARIO=2.7
22 | - EMBER_TRY_SCENARIO=ember-release
23 | - EMBER_TRY_SCENARIO=ember-beta
24 | - EMBER_TRY_SCENARIO=ember-canary
25 |
26 | matrix:
27 | fast_finish: true
28 | allow_failures:
29 | - env: EMBER_TRY_SCENARIO=ember-canary
30 |
31 | before_install:
32 | - npm config set spin false
33 | - npm install -g bower
34 | - npm install phantomjs-prebuilt
35 |
36 | install:
37 | - npm install
38 | - bower install
39 |
40 | script:
41 | # Usually, it's ok to finish the test scenario without reverting
42 | # to the addon's original dependency state, skipping "cleanup".
43 | - ember try $EMBER_TRY_SCENARIO test --skip-cleanup
44 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
10 |
--------------------------------------------------------------------------------
/tests/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "predef": [
3 | "server",
4 | "document",
5 | "window",
6 | "location",
7 | "setTimeout",
8 | "$",
9 | "-Promise",
10 | "define",
11 | "console",
12 | "visit",
13 | "exists",
14 | "fillIn",
15 | "click",
16 | "keyEvent",
17 | "triggerEvent",
18 | "find",
19 | "findWithAssert",
20 | "wait",
21 | "DS",
22 | "andThen",
23 | "currentURL",
24 | "currentPath",
25 | "currentRouteName"
26 | ],
27 | "node": false,
28 | "browser": false,
29 | "boss": true,
30 | "curly": true,
31 | "debug": false,
32 | "devel": false,
33 | "eqeqeq": true,
34 | "evil": true,
35 | "forin": false,
36 | "immed": false,
37 | "laxbreak": false,
38 | "newcap": true,
39 | "noarg": true,
40 | "noempty": false,
41 | "nonew": false,
42 | "nomen": false,
43 | "onevar": false,
44 | "plusplus": false,
45 | "regexp": false,
46 | "undef": true,
47 | "sub": true,
48 | "strict": false,
49 | "white": false,
50 | "eqnull": true,
51 | "esnext": true,
52 | "unused": true
53 | }
54 |
--------------------------------------------------------------------------------
/tests/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Dummy Tests
7 |
8 |
9 |
10 | {{content-for "head"}}
11 | {{content-for "test-head"}}
12 |
13 |
14 |
15 |
16 |
17 | {{content-for "head-footer"}}
18 | {{content-for "test-head-footer"}}
19 |
20 |
21 | {{content-for "body"}}
22 | {{content-for "test-body"}}
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | {{content-for "body-footer"}}
32 | {{content-for "test-body-footer"}}
33 |
34 |
35 |
--------------------------------------------------------------------------------
/tests/dummy/config/environment.js:
--------------------------------------------------------------------------------
1 | /* jshint node: true */
2 |
3 | module.exports = function(environment) {
4 | var ENV = {
5 | modulePrefix: 'dummy',
6 | environment: environment,
7 | baseURL: '/',
8 | locationType: 'auto',
9 | EmberENV: {
10 | FEATURES: {
11 | // Here you can enable experimental features on an ember canary build
12 | // e.g. 'with-controller': true
13 | }
14 | },
15 |
16 | APP: {
17 | // Here you can pass flags/options to your application instance
18 | // when it is created
19 | }
20 | };
21 |
22 | if (environment === 'development') {
23 | // ENV.APP.LOG_RESOLVER = true;
24 | // ENV.APP.LOG_ACTIVE_GENERATION = true;
25 | // ENV.APP.LOG_TRANSITIONS = true;
26 | // ENV.APP.LOG_TRANSITIONS_INTERNAL = true;
27 | // ENV.APP.LOG_VIEW_LOOKUPS = true;
28 | }
29 |
30 | if (environment === 'test') {
31 | // Testem prefers this...
32 | ENV.baseURL = '/';
33 | ENV.locationType = 'none';
34 |
35 | // keep test console output quieter
36 | ENV.APP.LOG_ACTIVE_GENERATION = false;
37 | ENV.APP.LOG_VIEW_LOOKUPS = false;
38 |
39 | ENV.APP.rootElement = '#ember-testing';
40 | }
41 |
42 | if (environment === 'production') {
43 |
44 | }
45 |
46 | return ENV;
47 | };
48 |
--------------------------------------------------------------------------------
/app/templates/components/paginate-collection.hbs:
--------------------------------------------------------------------------------
1 | {{#if shouldDisplayPaginator}}
2 |
45 | {{/if}}
46 |
--------------------------------------------------------------------------------
/tests/unit/mixins/controllers/jsonapi-pagination-test.js:
--------------------------------------------------------------------------------
1 | import {
2 | test
3 | } from 'ember-qunit';
4 |
5 | import {
6 | module
7 | } from 'qunit';
8 |
9 | import Ember from 'ember';
10 | import PaginationMixin from 'ember-cli-jsonapi-pagination/mixins/controllers/jsonapi-pagination';
11 |
12 | module('mixins:controllers/jsonapi-pagination');
13 |
14 | test('it has size and number query params with default values: 15 and 1 accordingly', function(assert) {
15 | assert.expect(3);
16 |
17 | let extendable = Ember.Object.extend(PaginationMixin).create();
18 |
19 | assert.deepEqual(extendable.get('queryParams'), ['size', 'number']);
20 | assert.equal(extendable.get('size'), 15);
21 | assert.equal(extendable.get('number'), 1);
22 | });
23 |
24 | test('setCurrentPage sets number property to be equal to the specified page number', function(assert) {
25 | assert.expect(2);
26 |
27 | let extendable;
28 |
29 | Ember.run(() => {
30 | extendable = Ember.Object.extend(PaginationMixin).create();
31 | });
32 |
33 | assert.equal(extendable.get('number'), 1);
34 |
35 | Ember.run(() => {
36 | extendable.get('actions').setCurrentPage.call(extendable, 5);
37 | });
38 |
39 | assert.equal(extendable.get('number'), 5);
40 | });
41 |
42 |
43 | test('setCurrentSize sets size property to be equal to the specified size', function(assert) {
44 | assert.expect(2);
45 |
46 | let extendable = Ember.Object.extend(PaginationMixin).create();
47 |
48 | assert.equal(extendable.get('size'), 15);
49 |
50 | Ember.run(() => {
51 | extendable.get('actions').setCurrentSize.call(extendable, 20);
52 | });
53 |
54 | assert.equal(extendable.get('size'), 20);
55 | });
56 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ember-cli-jsonapi-pagination",
3 | "version": "0.0.2",
4 | "description": "Dedicated solution for pagination with JSONAPI compliant API",
5 | "directories": {
6 | "doc": "doc",
7 | "test": "tests"
8 | },
9 | "scripts": {
10 | "build": "ember build",
11 | "start": "ember server",
12 | "test": "ember try:each"
13 | },
14 | "repository": "https://github.com/BookingSync/ember-cli-jsonapi-pagination",
15 | "engines": {
16 | "node": ">= 0.10.0"
17 | },
18 | "author": "Karol Galanciak",
19 | "license": "MIT",
20 | "devDependencies": {
21 | "broccoli-asset-rev": "^2.4.2",
22 | "ember-ajax": "^2.0.1",
23 | "ember-cli": "2.6.3",
24 | "ember-cli-app-version": "^1.0.0",
25 | "ember-cli-bootstrap-sassy": "0.5.4",
26 | "ember-cli-dependency-checker": "^1.2.0",
27 | "ember-cli-htmlbars": "^1.0.3",
28 | "ember-cli-htmlbars-inline-precompile": "^0.3.1",
29 | "ember-cli-inject-live-reload": "^1.4.0",
30 | "ember-cli-jshint": "^1.0.0",
31 | "ember-cli-mirage": "0.2.1",
32 | "ember-cli-qunit": "^1.4.0",
33 | "ember-cli-release": "^0.2.9",
34 | "ember-cli-sass": "5.5.1",
35 | "ember-cli-sri": "^2.1.0",
36 | "ember-cli-uglify": "^1.2.0",
37 | "ember-data": "^2.6.0",
38 | "ember-disable-prototype-extensions": "^1.1.0",
39 | "ember-export-application-global": "^1.0.5",
40 | "ember-load-initializers": "^0.5.1",
41 | "ember-resolver": "^2.0.3",
42 | "ember-welcome-page": "^1.0.1",
43 | "loader.js": "^4.0.1"
44 | },
45 | "keywords": [
46 | "ember-addon",
47 | "pagination",
48 | "jsonapi"
49 | ],
50 | "dependencies": {
51 | "ember-cli-babel": "^5.1.6"
52 | },
53 | "ember-addon": {
54 | "configPath": "tests/dummy/config"
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/tests/unit/components/paginate-collection-test.js:
--------------------------------------------------------------------------------
1 | import {
2 | moduleForComponent,
3 | test
4 | } from 'ember-qunit';
5 |
6 | moduleForComponent('paginate-collection', {
7 | unit: true
8 | });
9 |
10 | test('it sets currentPageToBeSet from currentPage on setUp', function(assert) {
11 | let component = this.subject({setCurrentPage: 'setCurrentPage', currentPage: 10});
12 |
13 | assert.equal(component.get('currentPageToBeSet'), 10);
14 | });
15 |
16 | test('shouldDisplayPaginator returns true if the totalPages is greater than 1', function(assert) {
17 | let component = this.subject({totalPages: 10});
18 |
19 | assert.ok(component.get('shouldDisplayPaginator'));
20 |
21 | component.set('totalPages', 1);
22 |
23 | assert.ok(!component.get('shouldDisplayPaginator'));
24 | });
25 |
26 | test('hasPreviousPage returns true if the totalPages is greater than 1 and currentPage is not the first page', function(assert) {
27 | let component = this.subject({totalPages: 10, currentPage: 2});
28 |
29 | assert.ok(component.get('hasPreviousPage'));
30 |
31 | component.set('currentPage', 1);
32 |
33 | assert.ok(!component.get('hasPreviousPage'));
34 |
35 | component.set('currentPage', 2);
36 |
37 | assert.ok(component.get('hasPreviousPage'));
38 |
39 | component.set('totalPages', 1);
40 |
41 | assert.ok(!component.get('hasPreviousPage'));
42 | });
43 |
44 | test('hasNextPage returns true if the totalPages is greater than 1 and currentPage is not the last page', function(assert) {
45 | let component = this.subject({totalPages: 10, currentPage: 2});
46 |
47 | assert.ok(component.get('hasNextPage'));
48 |
49 | component.set('currentPage', 10);
50 |
51 | assert.ok(!component.get('hasNextPage'));
52 |
53 | component.set('currentPage', 2);
54 |
55 | assert.ok(component.get('hasNextPage'));
56 |
57 | component.set('totalPages', 1);
58 |
59 | assert.ok(!component.get('hasNextPage'));
60 | });
61 |
--------------------------------------------------------------------------------
/tests/unit/mixins/routes/jsonapi-pagination-test.js:
--------------------------------------------------------------------------------
1 | import {
2 | test
3 | } from 'ember-qunit';
4 |
5 | import {
6 | module
7 | } from 'qunit';
8 |
9 | import Ember from 'ember';
10 | import PaginationMixin from 'ember-cli-jsonapi-pagination/mixins/routes/jsonapi-pagination';
11 |
12 | module('mixins:routes/jsonapi-pagination');
13 |
14 | test('it has queryParams with size and number set to refresh model', function(assert) {
15 | assert.expect(3);
16 |
17 | let extendable = Ember.Object.extend(PaginationMixin).create();
18 |
19 | assert.deepEqual(Object.keys(extendable.get('queryParams')), ['size', 'number']);
20 | assert.deepEqual(extendable.get('queryParams.size'), { refreshModel: true });
21 | assert.deepEqual(extendable.get('queryParams.number'), { refreshModel: true });
22 | });
23 |
24 |
25 | test('queryPaginated calls #query on store with model name and formatted params', function(assert) {
26 | assert.expect(2);
27 |
28 | let calledModelName, calledParams;
29 |
30 | let extendable = Ember.Object.extend(PaginationMixin).create();
31 | let storeStub = Ember.Object.extend({
32 | query: function(modelName, params) {
33 | calledModelName = modelName;
34 | calledParams = params;
35 | }
36 | }).create();
37 |
38 | Ember.run(() => {
39 | extendable.set('store', storeStub);
40 | });
41 |
42 | let params = { size: 10, number: 1 };
43 | extendable.queryPaginated('user', params);
44 |
45 | assert.deepEqual(calledParams, { page: { size: 10, number: 1 } });
46 | assert.equal(calledModelName, 'user');
47 | });
48 |
49 | test('resolveParamsForPagination formats params to be compliant with jsonapi', function(assert) {
50 | assert.expect(1);
51 |
52 | let params = { size: 10, number: 1 };
53 | let extendable = Ember.Object.extend(PaginationMixin).create();
54 |
55 | extendable.resolveParamsForPagination(params);
56 | assert.deepEqual(params, { page: { size: 10, number: 1 } });
57 | });
58 |
59 |
60 |
--------------------------------------------------------------------------------
/tests/acceptance/pagination-test.js:
--------------------------------------------------------------------------------
1 | /* global server */
2 |
3 | import { test } from 'qunit';
4 | import moduleForAcceptance from '../../tests/helpers/module-for-acceptance';
5 |
6 | moduleForAcceptance('Acceptance | pagination');
7 |
8 | test('pagination link work', function(assert) {
9 | server.createList('rental', 100);
10 | assert.expect(12);
11 |
12 | visit('/rentals');
13 |
14 | andThen(() => {
15 | let $rentalsList = find('.rentals-list li');
16 | let rentalNames = extractRentalNames($rentalsList);
17 |
18 | assert.equal($rentalsList.length, 5);
19 | assert.equal(find('li.active').text().trim(), '1');
20 | assert.deepEqual(rentalNames, ['Rental 1', 'Rental 2', 'Rental 3', 'Rental 4', 'Rental 5']);
21 |
22 | $(find('.page-number a')[2]).click();
23 | });
24 |
25 | andThen(() => {
26 | let $rentalsList = find('.rentals-list li');
27 | let rentalNames = extractRentalNames($rentalsList);
28 |
29 | assert.equal($rentalsList.length, 5);
30 | assert.equal(find('li.active').text().trim(), '3');
31 | assert.deepEqual(rentalNames, ['Rental 11', 'Rental 12', 'Rental 13', 'Rental 14', 'Rental 15']);
32 |
33 | find('li.next a').click();
34 | });
35 |
36 | andThen(() => {
37 | let $rentalsList = find('.rentals-list li');
38 | let rentalNames = extractRentalNames($rentalsList);
39 |
40 | assert.equal($rentalsList.length, 5);
41 | assert.equal(find('li.active').text().trim(), '4');
42 | assert.deepEqual(rentalNames, ['Rental 16', 'Rental 17', 'Rental 18', 'Rental 19', 'Rental 20']);
43 |
44 | find('li.prev a').click();
45 | });
46 |
47 | andThen(() => {
48 | let $rentalsList = find('.rentals-list li');
49 | let rentalNames = extractRentalNames($rentalsList);
50 |
51 | assert.equal($rentalsList.length, 5);
52 | assert.equal(find('li.active').text().trim(), '3');
53 | assert.deepEqual(rentalNames, ['Rental 11', 'Rental 12', 'Rental 13', 'Rental 14', 'Rental 15']);
54 | });
55 | });
56 |
57 |
58 | function extractRentalNames($listElements) {
59 | return Array.prototype.map.call($listElements, (el) => {
60 | return $(el).text();
61 | });
62 | }
63 |
--------------------------------------------------------------------------------
/config/ember-try.js:
--------------------------------------------------------------------------------
1 | /*jshint node:true*/
2 | module.exports = {
3 | scenarios: [
4 | {
5 | name: 'default',
6 | bower: {
7 | dependencies: { }
8 | }
9 | },
10 | {
11 | name: '2.0',
12 | dependencies: {
13 | 'ember': '2.0.3',
14 | 'ember-data': '2.0.1',
15 | "ember-cli-shims": "0.0.6",
16 | }
17 | },
18 | {
19 | name: '2.1',
20 | dependencies: {
21 | 'ember': '2.1.2',
22 | 'ember-data': '2.1.0',
23 | "ember-cli-shims": "0.0.6",
24 | }
25 | },
26 | {
27 | name: '2.2',
28 | dependencies: {
29 | 'ember': '2.2.2',
30 | 'ember-data': '2.2.1',
31 | "ember-cli-shims": "0.0.6",
32 | }
33 | },
34 | {
35 | name: '2.3',
36 | dependencies: {
37 | 'ember': '2.3.2',
38 | 'ember-data': '2.3.3',
39 | "ember-cli-shims": "0.1.0",
40 | }
41 | },
42 | {
43 | name: '2.4',
44 | dependencies: {
45 | 'ember': '2.4.6',
46 | 'ember-data': '2.4.3',
47 | "ember-cli-shims": "0.1.0",
48 | }
49 | },
50 | {
51 | name: '2.5',
52 | dependencies: {
53 | 'ember': '2.5.1',
54 | 'ember-data': '2.5.3',
55 | "ember-cli-shims": "0.1.0",
56 | }
57 | },
58 | {
59 | name: '2.6',
60 | dependencies: {
61 | 'ember': '2.6.2',
62 | 'ember-data': '2.6.1',
63 | "ember-cli-shims": "0.1.0",
64 | }
65 | },
66 | {
67 | name: '2.7',
68 | dependencies: {
69 | 'ember': '2.7.0',
70 | 'ember-data': '2.7.0',
71 | "ember-cli-shims": "0.1.0",
72 | }
73 | },
74 | {
75 | name: 'default',
76 | dependencies: {
77 |
78 | }
79 | },
80 | {
81 | name: 'ember-release',
82 | bower: {
83 | dependencies: {
84 | 'ember': 'components/ember#release'
85 | },
86 | resolutions: {
87 | 'ember': 'release'
88 | }
89 | }
90 | },
91 | {
92 | name: 'ember-beta',
93 | bower: {
94 | dependencies: {
95 | 'ember': 'components/ember#beta'
96 | },
97 | resolutions: {
98 | 'ember': 'beta'
99 | }
100 | }
101 | },
102 | {
103 | name: 'ember-canary',
104 | bower: {
105 | dependencies: {
106 | 'ember': 'components/ember#canary'
107 | },
108 | resolutions: {
109 | 'ember': 'canary'
110 | }
111 | }
112 | }
113 | ]
114 | };
115 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ember-cli-jsonapi-pagination [](https://travis-ci.org/BookingSync/ember-cli-jsonapi-pagination) [](https://badge.fury.io/js/ember-cli-jsonapi-pagination)
2 |
3 | An addon adding support for pagination with JSONAPI backend.
4 |
5 | 
6 |
7 | Currently only a paged-based strategy is supported, more details about JSONAPI pagination: [http://jsonapi.org/format/#fetching-pagination](http://jsonapi.org/format/#fetching-pagination).
8 |
9 | ## Usage
10 |
11 | To install the addon, run:
12 |
13 | ```
14 | ember install ember-cli-jsonapi-pagination
15 | ```
16 |
17 | You need to include pagination mixins in your route and controller which are responsible for paginated resource:
18 |
19 | ```js
20 | // app/routes/models/index.js
21 |
22 | import Ember from 'ember';
23 | import Pagination from 'ember-cli-jsonapi-pagination/mixins/routes/jsonapi-pagination';
24 |
25 | export default Ember.Route.extend(Pagination);
26 | ```
27 |
28 | ```js
29 | // app/controllers/models/index.js
30 |
31 | import Ember from 'ember';
32 | import Pagination from 'ember-cli-jsonapi-pagination/mixins/controllers/jsonapi-pagination';
33 |
34 | export default Ember.Controller.extend(Pagination);
35 | ```
36 |
37 | That way the query params (`size` - by default equal to 15 and `number` - by default equal to 1) and required actions (`setCurrentPage` for setting current page in query params and `setCurrentSize` for setting size in query params) are available in the controller / route. To to perform query with pagination params use `queryPaginated` function which takes model name and params as arguments:
38 |
39 | ``` js
40 | // app/routes/models/index.js
41 | import Ember from 'ember';
42 | import Pagination from 'ember-cli-jsonapi-pagination/mixins/routes/jsonapi-pagination';
43 |
44 | export default Ember.Route.extend(Pagination, {
45 | model(params) {
46 | return this.queryPaginated('rental', params);
47 | }
48 | });
49 | ```
50 |
51 | You also need to define a property that will return the amount of all pages, this value will most likely come from the meta data in API response, here's one example:
52 |
53 |
54 | ``` js
55 | // app/controllers/models/index.js
56 |
57 | import Ember from 'ember';
58 | import Pagination from 'ember-cli-jsonapi-pagination/mixins/controllers/jsonapi-pagination';
59 |
60 | export default Ember.Controller.extend(Pagination, {
61 | totalPages: Ember.computed('size', 'number', 'model.[]', function() {
62 | return this.get('model.meta.total-pages');
63 | })
64 | });
65 | ```
66 |
67 | To render the paginator in your templates, use `paginate-collection` component:
68 |
69 | ``` hbs
70 | // app/templates/some-template.hbs
71 |
72 | {{paginate-collection totalPages=totalPages currentPage=number setCurrentPage=(action "setCurrentPage")}}
73 | ```
74 |
75 | `totalPages` should be total amount of pages (returned most likely in the meta data in API response), `number` query param comes from the controller and `setCurrentPage` also comes from controller.
76 |
77 | That's all you need to do to handle pagination with JSONAPI!
78 |
79 | Currently the HTML template for paginator is based on Bootstrap 3.
80 |
81 |
82 | ## Installation
83 |
84 | * `git clone` this repository
85 | * `npm install`
86 | * `bower install`
87 |
88 | ## Running
89 |
90 | * `ember server`
91 | * Visit your app at http://localhost:4200.
92 |
93 | ## Running Tests
94 |
95 | * `npm test` (Runs `ember try:testall` to test your addon against multiple Ember versions)
96 | * `ember test`
97 | * `ember test --server`
98 |
99 | ## Building
100 |
101 | * `ember build`
102 |
103 | For more information on using ember-cli, visit [http://ember-cli.com/](http://ember-cli.com/).
104 |
--------------------------------------------------------------------------------
/addon/components/paginate-collection.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 |
3 | let PageObject = Ember.Object;
4 |
5 | export default Ember.Component.extend({
6 | outerWindow: 3,
7 |
8 | setup: Ember.on('init', function() {
9 | this.set('currentPageToBeSet', this.get('currentPage'));
10 | }),
11 |
12 | shouldDisplayPaginator: Ember.computed('totalPages', function() {
13 | return Number(this.get('totalPages')) > 1;
14 | }),
15 |
16 | hasPreviousPage: Ember.computed('currentPage', 'totalPages', function() {
17 | let totalPages = this.get('totalPages');
18 | let currentPage = this.get('currentPage');
19 | return Number(currentPage) !== 1 && Number(totalPages) > 1;
20 | }),
21 |
22 | hasNextPage: Ember.computed('currentPage', 'totalPages', function() {
23 | let totalPages = this.get('totalPages');
24 | let currentPage = this.get('currentPage');
25 | return Number(currentPage) !== Number(totalPages) && Number(totalPages) > 1;
26 | }),
27 |
28 | pageObjects: Ember.computed('totalPages', 'currentPage', function() {
29 | let totalPages = this.get('totalPages');
30 | let currentPage = this.get('currentPage');
31 | let pages = Ember.A([]);
32 | let outerWindow = Number(this.get('outerWindow'));
33 | let leftDotsDisplayed = false;
34 | let rightDotsDisplayed = false;
35 | let pageStartAtValue = 1;
36 | let pageEndAtValue = totalPages;
37 | let pageValues = extractPageValues(outerWindow, currentPage, pageStartAtValue, pageEndAtValue);
38 |
39 | pageValues.forEach((page) => {
40 | let shouldDisplay, displayAsDots, isCurrentPage;
41 |
42 | if (page === currentPage) {
43 | isCurrentPage = true;
44 | } else {
45 | isCurrentPage = false;
46 | }
47 |
48 | if (shouldConsiderOuterWindowSettings(totalPages, outerWindow)) {
49 | if (isCurrentPage) {
50 | shouldDisplay = true;
51 | displayAsDots = false;
52 | } else if (isWithinOuterWindowLimit(outerWindow, totalPages, page)) {
53 | shouldDisplay = true;
54 | displayAsDots = false;
55 | } else if (isAdjacentToCurrentPage(currentPage, page)) {
56 | shouldDisplay = true;
57 | displayAsDots = false;
58 | } else if (page < currentPage && !leftDotsDisplayed) {
59 | leftDotsDisplayed = true;
60 | shouldDisplay = true;
61 | displayAsDots = true;
62 | } else if (page > currentPage && !rightDotsDisplayed) {
63 | rightDotsDisplayed = true;
64 | shouldDisplay = true;
65 | displayAsDots = true;
66 | }
67 | } else {
68 | shouldDisplay = true;
69 | displayAsDots = false;
70 | }
71 |
72 | pages.pushObject(PageObject.create({
73 | value: page,
74 | isCurrentPage: isCurrentPage,
75 | shouldDisplay: shouldDisplay,
76 | displayAsDots: displayAsDots,
77 | })
78 | );
79 | });
80 |
81 | return pages;
82 | }),
83 |
84 | actions: {
85 | setCurrentPage(pageNumber) {
86 | this.set('currentPage', pageNumber);
87 | if (this.attrs.setCurrentPage) {
88 | this.attrs.setCurrentPage(this.get('currentPage'));
89 | }
90 | },
91 |
92 | nextPage() {
93 | let currentPage = this.get('currentPage');
94 | this.set('currentPage', currentPage + 1);
95 | if (this.attrs.setCurrentPage) {
96 | this.attrs.setCurrentPage(this.get('currentPage'));
97 | }
98 | },
99 |
100 | previousPage() {
101 | let currentPage = this.get('currentPage');
102 | this.set('currentPage', currentPage - 1);
103 | if (this.attrs.setCurrentPage) {
104 | this.attrs.setCurrentPage(this.get('currentPage'));
105 | }
106 | }
107 | }
108 | });
109 |
110 | // TODO: use Set here when time is right, apparently it's not widely supported yet
111 | function extractPageValues(outerWindow, currentPage, pageStartAtValue, pageEndAtValue) {
112 | let pageValues = [];
113 | pageValues.push(pageStartAtValue);
114 | pageValues.push(pageEndAtValue);
115 |
116 | let addPageValue = function(page) {
117 | pageValues.push(page);
118 | };
119 |
120 | if (outerWindow > 0) {
121 | let leftOuterWindowStartAtValue = pageStartAtValue + 1;
122 | let leftOuterWindowEndAtValue = pageStartAtValue + outerWindow;
123 |
124 | let rightOuterWindowStartAtValue = pageEndAtValue - 1 - outerWindow;
125 | let rightOuterWindowEndAtValue = pageEndAtValue - 1;
126 |
127 | createRange(leftOuterWindowStartAtValue, leftOuterWindowEndAtValue).forEach(addPageValue);
128 | createRange(rightOuterWindowStartAtValue, rightOuterWindowEndAtValue).forEach(addPageValue);
129 | }
130 |
131 | // add / subtract 2 for the dots display for the inner window
132 | // to handle exactly this use case (if 100 is current page): ... 99 100 101 ...
133 | let innerWindowStartAtValue = currentPage - 2;
134 | let innerWindowEndAtValue = currentPage + 2;
135 | createRange(innerWindowStartAtValue, innerWindowEndAtValue).forEach(addPageValue);
136 |
137 | let takeUnique = function(page, idx, array) {
138 | return array.indexOf(page) === idx;
139 | };
140 |
141 | return pageValues
142 | .sort((a, b) => a - b)
143 | .filter((page) => page >= pageStartAtValue && page <= pageEndAtValue)
144 | .filter(takeUnique);
145 |
146 | }
147 |
148 | function shouldConsiderOuterWindowSettings(totalPages, outerWindow) {
149 | let firstPageContribution = 1;
150 | let lastPageContribution = 1;
151 | let extraCurrentPageContribution = 1;
152 |
153 | return totalPages > outerWindow * 2 + firstPageContribution + lastPageContribution + extraCurrentPageContribution;
154 | }
155 |
156 | function isAdjacentToCurrentPage(currentPage, page) {
157 | return (page === currentPage - 1) || (page === currentPage + 1);
158 | }
159 |
160 | function isWithinOuterWindowLimit(outerWindow, totalPages, page) {
161 | return isWithinOuterWindowLimitFromLeft(outerWindow, page) ||
162 | isWithinOuterWindowLimitFromRight(outerWindow, totalPages, page);
163 | }
164 |
165 | function isWithinOuterWindowLimitFromLeft(outerWindow, page) {
166 | return page <= (outerWindow + 1);
167 | }
168 |
169 | function isWithinOuterWindowLimitFromRight(outerWindow, totalPages, page) {
170 | return (totalPages - page) <= outerWindow;
171 | }
172 |
173 | function createRange(start, end) {
174 | let range = [];
175 | for (let i = start; i <= end; i++) {
176 | range.push(i);
177 | }
178 | return range;
179 | }
180 |
--------------------------------------------------------------------------------
/tests/integration/components/paginate-collection-test.js:
--------------------------------------------------------------------------------
1 | import { moduleForComponent, test } from 'ember-qunit';
2 | import hbs from 'htmlbars-inline-precompile';
3 |
4 | moduleForComponent('paginate-collection', 'Integration | Component | paginate collection', {
5 | integration: true
6 | });
7 |
8 | test('it renders pagination links considering the outer window which is 3 by default', function(assert) {
9 | let paginationLinksContent;
10 |
11 | this.render(hbs`
12 | {{paginate-collection totalPages=5 currentPage=1}}
13 | `);
14 |
15 | paginationLinksContent = extractPaginationLinksContent(this);
16 | assert.deepEqual(paginationLinksContent, ["«", "1", "2", "3", "4", "5", "»"]);
17 |
18 | this.render(hbs`
19 | {{paginate-collection totalPages=15 currentPage=1}}
20 | `);
21 |
22 | paginationLinksContent = extractPaginationLinksContent(this);
23 | assert.deepEqual(paginationLinksContent, ["«", "1", "2", "3", "4", "...", "12", "13", "14", "15", "»"]);
24 |
25 | this.render(hbs`
26 | {{paginate-collection totalPages=15 currentPage=4}}
27 | `);
28 |
29 | paginationLinksContent = extractPaginationLinksContent(this);
30 | assert.deepEqual(paginationLinksContent, ["«", "1", "2", "3", "4", "5", "...", "12", "13", "14", "15", "»"]);
31 |
32 | this.render(hbs`
33 | {{paginate-collection totalPages=15 currentPage=5}}
34 | `);
35 |
36 | paginationLinksContent = extractPaginationLinksContent(this);
37 | assert.deepEqual(paginationLinksContent, ["«", "1", "2", "3", "4", "5", "6", "...", "12", "13", "14", "15", "»"]);
38 |
39 | this.render(hbs`
40 | {{paginate-collection totalPages=15 currentPage=7}}
41 | `);
42 |
43 | paginationLinksContent = extractPaginationLinksContent(this);
44 | assert.deepEqual(paginationLinksContent, ["«", "1", "2", "3", "4", "...", "6", "7", "8", "...", "12", "13", "14", "15", "»"]);
45 |
46 | this.render(hbs`
47 | {{paginate-collection totalPages=15 currentPage=11}}
48 | `);
49 |
50 | paginationLinksContent = extractPaginationLinksContent(this);
51 | assert.deepEqual(paginationLinksContent, ["«", "1", "2", "3", "4", "...", "10", "11", "12", "13", "14", "15", "»"]);
52 |
53 | this.render(hbs`
54 | {{paginate-collection totalPages=15 currentPage=12}}
55 | `);
56 |
57 | paginationLinksContent = extractPaginationLinksContent(this);
58 | assert.deepEqual(paginationLinksContent, ["«", "1", "2", "3", "4", "...", "11", "12", "13", "14", "15", "»"]);
59 |
60 | this.render(hbs`
61 | {{paginate-collection totalPages=15 currentPage=15}}
62 | `);
63 |
64 | paginationLinksContent = extractPaginationLinksContent(this);
65 | assert.deepEqual(paginationLinksContent, ["«", "1", "2", "3", "4", "...", "12", "13", "14", "15", "»"]);
66 |
67 | this.render(hbs`
68 | {{paginate-collection totalPages=15 currentPage=7 outerWindow=2}}
69 | `);
70 |
71 | paginationLinksContent = extractPaginationLinksContent(this);
72 | assert.deepEqual(paginationLinksContent, ["«", "1", "2", "3", "...", "6", "7", "8", "...", "13", "14", "15", "»"]);
73 |
74 | this.render(hbs`
75 | {{paginate-collection totalPages=15 currentPage=3 outerWindow=2}}
76 | `);
77 |
78 | paginationLinksContent = extractPaginationLinksContent(this);
79 | assert.deepEqual(paginationLinksContent, ["«", "1", "2", "3", "4", "...", "13", "14", "15", "»"]);
80 | });
81 |
82 | test('it sets active class for current page and disables the link', function(assert) {
83 | this.render(hbs`
84 | {{paginate-collection totalPages=15 currentPage=3}}
85 | `);
86 |
87 | assert.equal(this.$().find('.active').length, 1);
88 | assert.equal(this.$().find('.active').find('a').attr('class'), 'disabled');
89 | assert.equal(this.$().find('.active').find('a').html(), '3');
90 | });
91 |
92 | test('it disables previous page link for first page', function(assert) {
93 | this.render(hbs`
94 | {{paginate-collection totalPages=15 currentPage=1}}
95 | `);
96 |
97 | assert.equal($(this.$().find('li')[0]).attr('class'), 'arrow prev disabled');
98 | assert.equal($(this.$().find('li')[10]).attr('class'), 'arrow next enabled-arrow');
99 | });
100 |
101 | test('it disables next page link for last page', function(assert) {
102 | this.render(hbs`
103 | {{paginate-collection totalPages=15 currentPage=15}}
104 | `);
105 |
106 | assert.equal($(this.$().find('li')[0]).attr('class'), 'arrow prev enabled-arrow');
107 | assert.equal($(this.$().find('li')[10]).attr('class'), 'arrow next disabled');
108 | });
109 |
110 | test('for not last, not first page, the links for previous and next pages are enabled', function(assert) {
111 | this.render(hbs`
112 | {{paginate-collection totalPages=15 currentPage=2}}
113 | `);
114 |
115 | assert.equal($(this.$().find('li')[0]).attr('class'), 'arrow prev enabled-arrow');
116 | assert.equal($(this.$().find('li')[10]).attr('class'), 'arrow next enabled-arrow');
117 | });
118 |
119 | test('it does not display paginator if totalPages is not more than 1', function(assert) {
120 | this.render(hbs`
121 | {{paginate-collection totalPages=1 currentPage=1}}
122 | `);
123 |
124 | assert.equal(this.$().text().trim(), '');
125 | });
126 |
127 | test('clicking on link with given page sets current page to the specified value and calls setCurrentPage external action', function(assert) {
128 | this.set('setCurrentPage', (passedNumber) => {
129 | assert.equal(passedNumber, 4);
130 | });
131 |
132 | this.render(hbs`
133 | {{paginate-collection totalPages=15 currentPage=1 setCurrentPage=(action setCurrentPage)}}
134 | `);
135 |
136 | $(this.$().find('li')[4]).find('a').click();
137 |
138 | assert.equal(this.$().find('li.active').text().trim(), '4');
139 | });
140 |
141 | test('clicking on arrow for next page sets currentPage to next page and calls setCurrentPage external action', function(assert) {
142 | this.set('setCurrentPage', (passedNumber) => {
143 | assert.equal(passedNumber, 2);
144 | });
145 |
146 | this.render(hbs`
147 | {{paginate-collection totalPages=15 currentPage=1 setCurrentPage=(action setCurrentPage)}}
148 | `);
149 |
150 | this.$().find('li.next').find('a').click();
151 |
152 | assert.equal(this.$().find('li.active').text().trim(), '2');
153 | });
154 |
155 |
156 | test('clicking on arrow for previous page sets currentPage to previous page and calls setCurrentPage external action', function(assert) {
157 | this.set('setCurrentPage', (passedNumber) => {
158 | assert.equal(passedNumber, 1);
159 | });
160 |
161 | this.render(hbs`
162 | {{paginate-collection totalPages=15 currentPage=2 setCurrentPage=(action setCurrentPage)}}
163 | `);
164 |
165 | this.$().find('li.prev').find('a').click();
166 |
167 | assert.equal(this.$().find('li.active').text().trim(), '1');
168 | });
169 |
170 | function extractPaginationLinksContent(context) {
171 | return Array.prototype.slice.call(context.$().find('a').map(function(i, el) {
172 | return $(el).html();
173 | }));
174 | }
175 |
--------------------------------------------------------------------------------