Skip to content

Commit ae5b144

Browse files
committed
Initial commit
0 parents  commit ae5b144

File tree

8 files changed

+392
-0
lines changed

8 files changed

+392
-0
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
node_modules/
2+
.idea

.jscsrc

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"preset": "google",
3+
"excludeFiles": ["node_modules"],
4+
"requireCurlyBraces": [
5+
"else",
6+
"for",
7+
"while",
8+
"do",
9+
"try",
10+
"catch"
11+
],
12+
"disallowMultipleVarDecl": "exceptUndefined",
13+
"disallowSpacesInsideObjectBrackets": null,
14+
"maximumLineLength": {
15+
"value": 120,
16+
"allowComments": true,
17+
"allowRegex": true
18+
},
19+
"validateJSDoc": {
20+
"checkParamNames": false,
21+
"checkRedundantParams": false,
22+
"requireParamTypes": true
23+
}
24+
}

LICENSE

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) 2015 Bram Borggreve
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
PAGINATION
2+
=============
3+
4+
This module is designed for the [Strongloop Loopback](https://github.com/strongloop/loopback) framework.
5+
It provides a mixin that makes it easy to add pagination to an existing model
6+
7+
INSTALL
8+
=============
9+
10+
```bash
11+
npm install --save loopback-ds-pagination-mixin
12+
```
13+
14+
SERVER.JS
15+
=============
16+
17+
In your `server/server.js` file add the following line before the
18+
`boot(app, __dirname);` line.
19+
20+
```javascript
21+
...
22+
var app = module.exports = loopback();
23+
...
24+
// Add Readonly Mixin to loopback
25+
require('loopback-ds-pagination-mixin')(app);
26+
27+
boot(app, __dirname, function(err) {
28+
'use strict';
29+
if (err) throw err;
30+
31+
// start the server if `$ node server.js`
32+
if (require.main === module)
33+
app.start();
34+
});
35+
```
36+
37+
CONFIG
38+
=============
39+
40+
To use with your Models add the `mixins` attribute to the definition object of your model config.
41+
42+
```json
43+
{
44+
"name": "Item",
45+
"properties": {
46+
"name": "String",
47+
"description": "String",
48+
"status": "String"
49+
},
50+
"mixins": {
51+
"Pagination": {
52+
"config": {
53+
"limit": "10"
54+
}
55+
}
56+
}
57+
}
58+
```
59+
60+
61+
TESTING
62+
=============
63+
64+
Run the tests in `test.js`
65+
66+
```bash
67+
npm test
68+
```
69+
70+
Run with debugging output on:
71+
72+
```bash
73+
DEBUG='loopback-ds-pagination-mixin' npm test
74+
```

circle.yml

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
machine:
2+
node:
3+
version: 0.10.22

index.js

+102
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
var debug = require('debug')('loopback-ds-paginate-mixin');
2+
var utils = require('loopback-datasource-juggler/lib/utils');
3+
var assert = require('assert');
4+
var _ = require('lodash');
5+
6+
function Paginate(Model, config) {
7+
'use strict';
8+
9+
var mixinName = 'Paginate';
10+
var modelName = Model.definition.name;
11+
var debugPrefix = mixinName + ': ' + modelName + ': ';
12+
debug(debugPrefix + 'Loading with config %o', config);
13+
14+
Model.paginate = function(page, limit, query, cb) {
15+
cb = cb || utils.createPromiseCallback();
16+
17+
// Check if page is passed otherwise default to 1
18+
if (_.isUndefined(page)) {
19+
debug(debugPrefix + 'paginate: page undefined: %s', page);
20+
page = 1;
21+
} else {
22+
debug(debugPrefix + 'paginate: page defined: %s', page);
23+
}
24+
25+
// Check if limit is passed otherwise set to mixin config or default
26+
if (_.isUndefined(limit)) {
27+
limit = config.options.limit || 10;
28+
}
29+
30+
// Do some assertions
31+
// TODO: These values should never be negative
32+
assert(typeof page, 'number', 'Page should always be a number');
33+
assert(typeof limit, 'number', 'Limit should always be a number');
34+
35+
// Define the initial params object
36+
var params = {
37+
skip: (page - 1) * limit,
38+
limit: limit
39+
};
40+
41+
// Check if additional query parameters are passed
42+
if (!_.isUndefined(query)) {
43+
44+
// Check each of the following properties and add to params object
45+
var queryParams = ['fields', 'include', 'where', 'order'];
46+
queryParams.map(function(queryParam) {
47+
48+
if (!_.isUndefined(query[queryParam])) {
49+
params[queryParam] = query[queryParam];
50+
51+
debug(debugPrefix + 'paginate: adding param: %s = %o', queryParam,
52+
query[queryParam]);
53+
}
54+
});
55+
56+
}
57+
58+
debug(debugPrefix + 'paginate: params: %o', params);
59+
60+
// Define where query used for counter
61+
var countWhere = params.where || {};
62+
63+
// Get all the objects based on the params
64+
Model.all(params).then(function(items) {
65+
66+
// Get total number of objects based on countWhere
67+
Model.count(countWhere).then(function(count) {
68+
69+
// Format the result
70+
var result = {
71+
paging: {
72+
totalItems: count,
73+
totalPages: Math.ceil(count / limit),
74+
itemsPerPage: limit,
75+
currentPage: page
76+
},
77+
result: items
78+
};
79+
80+
debug(debugPrefix + 'paginate: result: %o', result);
81+
cb(null, result);
82+
}).catch(cb);
83+
}).catch(cb);
84+
85+
return cb.promise;
86+
};
87+
88+
Model.remoteMethod('paginate', {
89+
accepts: [
90+
{arg: 'page', type: 'number'},
91+
{arg: 'limit', type: 'number'},
92+
{arg: 'query', type: 'object'}
93+
],
94+
returns: {arg: 'result', type: 'string', root: true},
95+
http: {path: '/paginate', verb: 'get'}
96+
});
97+
98+
}
99+
100+
module.exports = function mixin(app) {
101+
app.loopback.modelBuilder.mixins.define('Paginate', Paginate);
102+
};

package.json

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
{
2+
"name": "loopback-ds-paginate-mixin",
3+
"version": "1.0.0",
4+
"description": "A mixin to enable easy pagination for a loopback Model.",
5+
"main": "index.js",
6+
"keywords": [
7+
"loopback",
8+
"strongloop",
9+
"mixin"
10+
],
11+
"author": "Bram Borggreve",
12+
"license": "MIT",
13+
"repository": {
14+
"type": "git",
15+
"url": "https://github.com/fullcube/loopback-ds-paginate-mixin"
16+
},
17+
"dependencies": {
18+
"debug": "2.x",
19+
"lodash": "3.9.3",
20+
"loopback-datasource-juggler": "2.30.1"
21+
},
22+
"devDependencies": {
23+
"bluebird": "2.9.24",
24+
"chai": "latest",
25+
"jscs": "1.13.x",
26+
"jshint": "latest",
27+
"loopback": "2.x",
28+
"loopback-connector-mongodb": "^1.11.1",
29+
"loopback-datasource-juggler": "^2.30.1",
30+
"loopback-testing": "1.x",
31+
"mocha": "latest",
32+
"mocha-sinon": "latest",
33+
"sinon": "latest",
34+
"sinon-chai": "latest"
35+
},
36+
"scripts": {
37+
"lint": "jscs index.js && jshint index.js",
38+
"test": "mocha -R spec --timeout 10000 test.js",
39+
"test:watch": "npm run test -- -w",
40+
"pretest": "npm run lint"
41+
}
42+
}

test.js

+124
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
/* jshint mocha: true */
2+
3+
var debug = require('debug')('loopback-ds-pagination-mixin');
4+
5+
var loopback = require('loopback');
6+
var lt = require('loopback-testing');
7+
8+
var chai = require('chai');
9+
var expect = chai.expect;
10+
var assert = chai.assert;
11+
12+
var sinon = require('sinon');
13+
chai.use(require('sinon-chai'));
14+
require('mocha-sinon');
15+
16+
// Create a new loopback app.
17+
var app = loopback();
18+
19+
// Set up promise support for loopback in non-ES6 runtime environment.
20+
global.Promise = require('bluebird');
21+
22+
// import our Changed mixin.
23+
require('./')(app);
24+
25+
// Configure datasource
26+
dbConnector = loopback.memory();
27+
28+
describe('loopback datasource paginate mixin', function() {
29+
30+
beforeEach(function(done) {
31+
32+
// A model with 2 Changed properties.
33+
var Item = this.Item = loopback.PersistedModel.extend('item', {
34+
name: String,
35+
description: String,
36+
status: String
37+
}, {
38+
mixins: {
39+
Paginate: {
40+
options: {
41+
limit: '10'
42+
}
43+
}
44+
}
45+
});
46+
47+
Item.attachTo(dbConnector);
48+
app.model(Item);
49+
50+
app.use(loopback.rest());
51+
app.set('legacyExplorer', false);
52+
done();
53+
});
54+
55+
lt.beforeEach.withApp(app);
56+
57+
describe('Testing behaviour', function() {
58+
for(var i = 1; i <= 49; i++) {
59+
lt.beforeEach.givenModel('item', {
60+
name:'Item' + i,
61+
description: 'This is item with id' + i,
62+
status: 'active'
63+
}, 'item' + i);
64+
}
65+
66+
67+
describe('Model.find', function() {
68+
it('Default find operation.', function(done) {
69+
70+
this.Item.find().then(function(result){
71+
assert.equal(result.length, 49, 'Should return all items');
72+
done();
73+
});
74+
75+
});
76+
});
77+
78+
describe('Model.paginate', function() {
79+
it('Paginate without parameters', function(done) {
80+
this.Item.paginate().then(function(result){
81+
console.log('result', result.paging);
82+
assert.equal(result.paging.totalItems, 49, 'Should return total items');
83+
assert.equal(result.paging.totalPages, 5, 'Should return total pages');
84+
assert.equal(result.paging.itemsPerPage, 10, 'Should items per page');
85+
assert.equal(result.paging.currentPage, 1, 'Should return current page');
86+
assert.equal(result.result.length, 10, 'Should return one page');
87+
done();
88+
});
89+
});
90+
91+
it('Paginate with where filter', function(done) {
92+
var page = 1;
93+
var limit = 10;
94+
this.Item.paginate(page, limit, {
95+
where: {
96+
name: 'Item1'
97+
}
98+
}).then(function(result){
99+
assert.equal(result.paging.totalItems, 1, 'Should return total items');
100+
assert.equal(result.paging.totalPages, 1, 'Should return total pages');
101+
assert.equal(result.paging.itemsPerPage, limit, 'Should items per page');
102+
assert.equal(result.paging.currentPage, page, 'Should return current page');
103+
assert.equal(result.result.length, 1, 'Should return the right number of items');
104+
done();
105+
});
106+
});
107+
108+
109+
it('Paginate with small page', function(done) {
110+
var page = 1;
111+
var limit = 4;
112+
this.Item.paginate(page, limit).then(function(result){
113+
assert.equal(result.paging.totalItems, 49, 'Should return total items');
114+
assert.equal(result.paging.totalPages, 13, 'Should return total pages');
115+
assert.equal(result.paging.itemsPerPage, limit, 'Should items per page');
116+
assert.equal(result.paging.currentPage, page, 'Should return current page');
117+
assert.equal(result.result.length, limit, 'Should return the right number of items');
118+
done();
119+
});
120+
});
121+
});
122+
123+
});
124+
});

0 commit comments

Comments
 (0)