├── .npmignore
├── .gitignore
├── index.js
├── test
├── views
│ ├── error.html
│ ├── subs.html
│ ├── index.html
│ └── base.html
├── public
│ ├── stylesheets
│ │ └── style.css
│ └── javascripts
│ │ ├── init-subs-datatable.js
│ │ └── init-zipcodes-datatable.js
├── config
│ ├── local.js
│ └── dycode.js
├── package.json
├── bower.json
├── routes
│ └── index.js
├── README.md
└── app.js
├── lib
├── string.js
├── validator.js
├── MongoDataTable.js
└── columns.js
├── package.json
├── LICENSE
├── HISTORY.md
└── README.md
/.npmignore:
--------------------------------------------------------------------------------
1 | test/
2 | bower_components/
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | bower_components
3 | *.log
4 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | module.exports = require('./lib/MongoDataTable');
2 |
--------------------------------------------------------------------------------
/test/views/error.html:
--------------------------------------------------------------------------------
1 |
{{ message }}
2 | {{ error.status }}
3 | {{ error.stack }}
4 |
--------------------------------------------------------------------------------
/test/public/stylesheets/style.css:
--------------------------------------------------------------------------------
1 | body {
2 | padding: 50px;
3 | font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
4 | }
5 |
6 | a {
7 | color: #00B7FF;
8 | }
--------------------------------------------------------------------------------
/test/config/local.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | mongodb: {
3 | host: 'localhost',
4 | port: 27017,
5 | dbname: 'samples',
6 | username: '',
7 | password: '',
8 | get connectionUri() {
9 | return 'mongodb://' + this.host + ':' + this.port + '/' + this.dbname;
10 | }
11 | }
12 | }
--------------------------------------------------------------------------------
/test/config/dycode.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | mongodb: {
3 | host: '192.168.1.157',
4 | port: 27018,
5 | dbname: 'cloud',
6 | username: '',
7 | password: '',
8 | get connectionUri() {
9 | return 'mongodb://' + this.host + ':' + this.port + '/' + this.dbname;
10 | }
11 | }
12 | }
--------------------------------------------------------------------------------
/test/views/subs.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block initscripts %}
4 |
5 | {% endblock %}
6 |
7 | {% block table %}
8 |
9 |
10 |
11 | | Id |
12 | Name |
13 | Screen Name |
14 | Services |
15 | State |
16 |
17 |
18 |
19 | {% endblock %}
--------------------------------------------------------------------------------
/lib/string.js:
--------------------------------------------------------------------------------
1 | function escapeNonAlphanumeric(string) {
2 | return string.replace(/[\W\s]/g, '\\$&');
3 | }
4 |
5 | function stringToBoolean(string) {
6 | if (typeof string === 'boolean') {
7 | return string;
8 | }
9 |
10 | if (string === 'true') {
11 | return true;
12 | } else if (string === 'false') {
13 | return false;
14 | }
15 |
16 | throw new Error('Argument passed is not boolean type');
17 | }
18 |
19 | exports.escapeNonAlphanumeric = escapeNonAlphanumeric;
20 | exports.stringToBoolean = stringToBoolean;
--------------------------------------------------------------------------------
/test/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mongo-datatable-test",
3 | "version": "0.1.0",
4 | "description": "Express-based app for handling jquery datatables server-side processing using mongo-datatable module.",
5 | "private": true,
6 | "scripts": {
7 | "start": "node app.js"
8 | },
9 | "dependencies": {
10 | "body-parser": "~1.12.4",
11 | "cookie-parser": "~1.3.5",
12 | "debug": "~2.2.0",
13 | "express": "~4.12.4",
14 | "mongodb": "^2.0.36",
15 | "morgan": "~1.5.3",
16 | "serve-favicon": "~2.2.1",
17 | "swig": "^1.4.2"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mongo-datatable",
3 | "version": "1.1.1",
4 | "description": "NodeJS module for server-side processing using jquery datatables and mongodb native driver.",
5 | "repository": {
6 | "type": "git",
7 | "url": "https://github.com/dycodedev/mongo-datatable.git"
8 | },
9 | "main": "index.js",
10 | "dependencies": {
11 | "async": "^1.3.0",
12 | "lodash": "^4.17.10"
13 | },
14 | "keywords": [
15 | "mongo",
16 | "mongodb",
17 | "datatable"
18 | ],
19 | "author": "Alwin Arrasyid ",
20 | "license": "MIT"
21 | }
22 |
--------------------------------------------------------------------------------
/test/views/index.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block initscripts %}
4 |
5 | {% endblock %}
6 |
7 | {% block table %}
8 |
9 |
10 |
11 | | Id |
12 | City |
13 | State |
14 | Population |
15 | Location |
16 |
17 |
18 |
19 |
20 |
21 | | Id |
22 | City |
23 | State |
24 | Population |
25 | Location |
26 |
27 |
28 |
29 | {% endblock %}
--------------------------------------------------------------------------------
/test/public/javascripts/init-subs-datatable.js:
--------------------------------------------------------------------------------
1 | $(document).ready(function() {
2 | $('#subs-table').DataTable({
3 | serverSide: true,
4 | processing: true,
5 | autoWidth: false,
6 | ajax: 'subs/subs.json',
7 | columns: [
8 | { data: '_id' },
9 | { data: 'subs_name' },
10 | { data: 'subs_screen_name' },
11 | {
12 | data: 'services',
13 | render: function(data) {
14 | return data.join(', ');
15 | }
16 | },
17 | { data: 'state' }
18 | ]
19 | });
20 |
21 | $('#subs-table')
22 | .removeClass('display')
23 | .addClass('table table-stripped table-bordered');
24 | });
--------------------------------------------------------------------------------
/test/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mongo-datatable-test",
3 | "version": "0.1.0",
4 | "homepage": "https://github.com/dycodedev/mongo-datatable",
5 | "private": true,
6 | "authors": [
7 | "Alwin Arrasyid "
8 | ],
9 | "description": "Express-based app for handling jquery datatables server-side processing using mongo-datatable module.",
10 | "license": "MIT",
11 | "ignore": [
12 | "**/.*",
13 | "node_modules",
14 | "bower_components",
15 | "test",
16 | "tests"
17 | ],
18 | "dependencies": {
19 | "datatables": "^1.10.16",
20 | "bootstrap": "~3.3.5",
21 | "Plugins": "git://github.com/DataTables/Plugins.git#~1.10.7"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/test/views/base.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ title }}
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | {% block initscripts %}
16 |
17 | {% endblock %}
18 |
19 |
20 |
21 | {% block table %}
22 |
23 | {% endblock %}
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/test/routes/index.js:
--------------------------------------------------------------------------------
1 | var express = require('express');
2 | var router = express.Router();
3 | var MongoClient = require('mongodb').MongoClient;
4 |
5 | // does not require mongo-datatable from npm.
6 | // Uses locally developed version instead
7 | var MongoDataTable = require('../../index');
8 | var config = require('../config/local');
9 |
10 | router.get('/', function(req, res, next) {
11 | res.render('index', { title: 'MongoDB Datatable' });
12 | });
13 |
14 | router.get('/zipcodes.json', function(req, res) {
15 | var options = req.query;
16 | options.caseInsensitiveSearch = true;
17 | options.showAlertOnError = true;
18 | // Select data with state MA
19 | options.customQuery = {
20 | state: 'MA',
21 | };
22 |
23 | MongoClient.connect(config.mongodb.connectionUri, function(err, db) {
24 | if (err) {
25 | console.error(err);
26 | }
27 |
28 | new MongoDataTable(db).get('zipcodes', options, function(err, result) {
29 | if (err) {
30 | console.error(err);
31 | }
32 |
33 | res.json(result);
34 | });
35 | });
36 | });
37 |
38 | module.exports = router;
--------------------------------------------------------------------------------
/lib/validator.js:
--------------------------------------------------------------------------------
1 | function isOptionsValid(options, immediateCallback) {
2 | if (typeof options === 'undefined')
3 | return immediateCallback(new Error('Options must be defined!'));
4 |
5 | if (typeof options.columns === 'undefined')
6 | return immediateCallback(new Error('Columns must be defined!'));
7 |
8 | if (typeof options.order === 'undefined')
9 | return immediateCallback(new Error('Columns order field must be defined!'));
10 |
11 | if (typeof options.search === 'undefined')
12 | return immediateCallback(new Error('Search field must be defined!'));
13 |
14 | var isStartValid = (typeof options.start !== 'undefined'
15 | || parseInt(options.start, 10) >= 0);
16 |
17 | var isLengthValid = (typeof options.length !== 'undefined'
18 | || parseInt(options.length, 10) > 0);
19 |
20 | if (!isStartValid)
21 | return immediateCallback(new Error('Start field must be defined!'));
22 |
23 | if (!isLengthValid)
24 | return immediateCallback(new Error('Length field must be defined!'));
25 |
26 | return immediateCallback(null);
27 | }
28 |
29 | exports.isOptionsValid = isOptionsValid;
30 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Alwin Arrasyid
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.
22 |
23 |
--------------------------------------------------------------------------------
/test/public/javascripts/init-zipcodes-datatable.js:
--------------------------------------------------------------------------------
1 | $(document).ready(function() {
2 | $('#zipcodes-table tfoot th').each(function() {
3 | var title = $('#zipcodes-table tfoot th').eq($(this).index()).text();
4 | $(this).html('');
5 | });
6 |
7 | var table = $('#zipcodes-table').DataTable({
8 | serverSide: true,
9 | processing: true,
10 | autoWidth: false,
11 | lengthMenu: [[10, 25, 50, -1], [10, 25, 50, 'All']],
12 | ajax: 'zipcodes.json',
13 | columns: [
14 | { data: '_id', searchable: false, name: 'id' },
15 | { data: 'city', name: 'city' },
16 | { data: 'state', name: 'state' },
17 | { data: 'pop', name: 'population'},
18 | {
19 | data: 'loc',
20 | name: 'location',
21 | render: function(data) {
22 | return data.join(', ');
23 | }
24 | }
25 | ]
26 | });
27 |
28 | $('#zipcodes-table')
29 | .removeClass('display')
30 | .addClass('table table-stripped table-bordered');
31 |
32 | table.columns().every(function() {
33 | var that = this;
34 |
35 | $('input', this.footer()).on('keyup change', function() {
36 | that
37 | .search(this.value)
38 | .draw();
39 | });
40 | });
41 | });
--------------------------------------------------------------------------------
/test/README.md:
--------------------------------------------------------------------------------
1 | # Mongo Datatable Test
2 |
3 | Express-based app for handling jquery datatables server-side processing using mongo-datatable module.
4 |
5 | ## Data source
6 |
7 | In this example, I am using database `samples` and collection `zipcodes`. And documents for `zipcodes` can be downloaded by clicking [this link](http://media.mongodb.org/zips.json?_ga=1.69149396.496420190.1431955345). You can import it to your collection by using `mongoimport` command which is explained below.
8 |
9 | The data model is described [here](http://docs.mongodb.org/manual/tutorial/aggregation-zip-code-data-set/).
10 |
11 | ## Importing Data
12 |
13 | You can import your data using following command
14 |
15 | ```bash
16 | mongoimport --host localhost --port 27017 --username youruser --password yourpassword --collection zipcodes --db samples --file zips.json
17 | ```
18 |
19 | You can omit `--username` and `--password` if you don't have any user, and you can omit `--port` as well if your mongodb server is using default port.
20 |
21 | Note that you must be in the same directory as `zips.json` file to run the above command, otherwise you should move to directory where `zips.json` file lies or specify either absolute or relative path to that file.
22 |
23 | You can find more detail about importing data to mongodb in [here](http://docs.mongodb.org/manual/reference/program/mongoimport/)
24 |
25 | ## Running the app
26 |
27 | Using port number 8000 or any available port numbers.
28 |
29 | ```bash
30 | PORT=8000 node app.js
31 | ```
--------------------------------------------------------------------------------
/test/app.js:
--------------------------------------------------------------------------------
1 | var express = require('express');
2 | var path = require('path');
3 | var favicon = require('serve-favicon');
4 | var logger = require('morgan');
5 | var cookieParser = require('cookie-parser');
6 | var bodyParser = require('body-parser');
7 | var swig = require('swig');
8 | var http = require('http');
9 |
10 | var indexRoutes = require('./routes/index');
11 |
12 | var app = express();
13 |
14 | // view engine setup
15 | app.engine('html', swig.renderFile);
16 | app.set('views', path.join(__dirname, 'views'));
17 | app.set('view engine', 'html');
18 | app.set('view cache', false);
19 |
20 | // no view cache in development environment
21 | if (app.get('env') === 'development') {
22 | swig.setDefaults({ cache: false });
23 | }
24 |
25 | // uncomment after placing your favicon in /public
26 | //app.use(favicon(__dirname + '/public/favicon.ico'));
27 | app.use(logger('dev'));
28 | app.use(bodyParser.json());
29 | app.use(bodyParser.urlencoded({ extended: false }));
30 | app.use(cookieParser());
31 | app.use(express.static(path.join(__dirname, 'public')));
32 | app.use(express.static(path.join(__dirname, 'bower_components')));
33 |
34 | app.use('/', indexRoutes);
35 |
36 | // catch 404 and forward to error handler
37 | app.use(function(req, res, next) {
38 | var err = new Error('Not Found');
39 | err.status = 404;
40 | next(err);
41 | });
42 |
43 | // error handlers
44 |
45 | // development error handler
46 | // will print stacktrace
47 | if (app.get('env') === 'development') {
48 | app.use(function(err, req, res, next) {
49 | res.status(err.status || 500);
50 | res.render('error', {
51 | message: err.message,
52 | error: err
53 | });
54 | });
55 | }
56 |
57 | // production error handler
58 | // no stacktraces leaked to user
59 | app.use(function(err, req, res, next) {
60 | res.status(err.status || 500);
61 | res.render('error', {
62 | message: err.message,
63 | error: {}
64 | });
65 | });
66 |
67 |
68 | var server = http.createServer(app);
69 | var port = process.env.PORT || 3000;
70 |
71 | server.listen(port);
72 |
73 | server.on('listening', function() {
74 | console.log('MongoDataTable Test Server is running on port', port);
75 | })
76 |
77 | process.on('uncaughtException', function(exception) {
78 | console.error('uncaughtException', exception.stack);
79 | });
80 |
--------------------------------------------------------------------------------
/lib/MongoDataTable.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var asyncjs = require('async');
4 | var forEach = require('lodash/forEach');
5 | var cols = require('./columns');
6 | var validator = require('./validator');
7 |
8 | function MongoDataTable(dbObject) {
9 | this.db = dbObject;
10 | }
11 |
12 | MongoDataTable.prototype.get = function(collectionName, options, onDataReady) {
13 | var self = this;
14 | var columns = cols.extractColumns(options);
15 |
16 | var response = {
17 | draw: 0,
18 | recordsTotal: 0,
19 | recordsFiltered: 0,
20 | data: [],
21 | error: null
22 | };
23 |
24 | var searchCriteria = cols.buildSearchCriteria(options);
25 |
26 | function getCollectionLength(callback) {
27 | if (self.db === null || typeof self.db === 'undefined') {
28 | return callback(new Error('You are not connected to any database server'));
29 | }
30 |
31 | var earlyCollection = self.db.collection(collectionName);
32 | response.draw = parseInt(options.draw, 10);
33 |
34 | earlyCollection
35 | .find(searchCriteria, columns)
36 | .count(function(error, result) {
37 |
38 | if (error) {
39 | return callback(error, null);
40 | }
41 |
42 | response.recordsTotal = result;
43 | response.recordsFiltered = result;
44 |
45 | return callback(null);
46 | });
47 | }
48 |
49 | function validateOptions(callback) {
50 | validator.isOptionsValid(options, callback);
51 | }
52 |
53 | function buildDefaultValue(callback) {
54 | var showAlertOnError = options.showAlertOnError;
55 |
56 | if (!showAlertOnError) {
57 | options.showAlertOnError = false;
58 | }
59 |
60 | return callback(null);
61 | }
62 |
63 | function getAndSortData(callback) {
64 | var sortOrder = cols.buildColumnSortOrder(options);
65 | var collection = self.db.collection(collectionName);
66 |
67 | collection = collection.find(searchCriteria, columns);
68 |
69 | if (parseInt(options.length) > 0) {
70 | collection = collection
71 | .skip(parseInt(options.start))
72 | .limit(parseInt(options.length));
73 | }
74 |
75 | forEach(sortOrder, function(order) {
76 | collection = collection.sort(order);
77 | });
78 |
79 | collection.toArray(callback);
80 | }
81 |
82 | function returnData(error, result) {
83 | if (error) {
84 | if (options.showAlertOnError) {
85 | response.error = error.message;
86 | }
87 |
88 | return onDataReady(error, response);
89 | }
90 |
91 | // Everything's ok!
92 | response.data = result;
93 | return onDataReady(null, response);
94 | }
95 |
96 | var tasks = [
97 | validateOptions,
98 | buildDefaultValue,
99 | getCollectionLength,
100 | getAndSortData
101 | ];
102 |
103 | asyncjs.waterfall(tasks, returnData);
104 | };
105 |
106 | module.exports = MongoDataTable;
107 |
--------------------------------------------------------------------------------
/HISTORY.md:
--------------------------------------------------------------------------------
1 | # Project History
2 |
3 | All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/).
4 |
5 | ### [1.1.1] - 2018-05-20
6 |
7 | #### Added
8 | * Example for mongodb native driver v3
9 |
10 | #### Modified
11 | * Internal naming of reference to `async` module so that it would not conflict with `async` keyword.
12 | * Using strict mode in MongoDataTable.js
13 | * Updated lodash version
14 |
15 | ### [1.1.0] - 2017-09-15
16 |
17 | #### Added
18 | * Support for case insensitive search through `caseInsensitiveSearch` option.
19 |
20 | ### [1.0.1] - 2016-09-20
21 |
22 | #### Modified
23 | * Fixed left curly braces
24 |
25 | ### [1.0.0] - 2016-09-20
26 |
27 | #### Modified
28 | * Refactored validator and entry point.
29 | * Only generate search query once.
30 |
31 | ### [0.4.1] - 2016-06-25
32 |
33 | #### Modified
34 | * Upgrade lodash to version 4.13.1. (See [#2](https://github.com/dycodedev/mongo-datatable/pull/2))
35 |
36 | ### [0.4.0] - 2015-10-20
37 |
38 | #### Added
39 | * Support for showing all rows using -1 page length configuration. See [length menu](https://datatables.net/examples/advanced_init/length_menu.html).
40 |
41 | ### [0.3.1] - 2015-10-20
42 |
43 | #### Fixed
44 | * jquery datatables now display error when showAlertOnError is true
45 |
46 | ### [0.3.0] - 2015-10-19
47 |
48 | #### Added
49 | * `customQuery` option.
50 | * Specific column search in global search input element.
51 |
52 |
53 | #### Changed
54 | * `showAlert` option changed to `showAlertOnError`.
55 | * Using lodash to process request data.
56 | * Express-based example app is no longer using `bin/www` to start app.
57 | * Express-based example app uses newly added feature.
58 |
59 | #### Removed
60 | * `emptyOnError` option as this module will always return empty data when error occurs.
61 |
62 | ### [0.2.1] - 2015-09-05
63 |
64 | #### Changed
65 | * Revised README.md
66 |
67 | #### Fixed
68 | * Treat numeric search value differently.
69 |
70 | ### [0.2.0] - 2015-07-13
71 |
72 | #### Added
73 | * Added `emptyOnError` and `showAlert` to `options`.
74 |
75 | ### [0.1.2] - 2015-07-11
76 |
77 | #### Added
78 | * Express-based app to test this module
79 | * `.npmignore` to ignore that test app
80 |
81 | #### Changed
82 | * `.gitignore` ignores log files
83 | * Revised README.md
84 |
85 | #### Fixed
86 | * Throws error if `db` object is undefined or null
87 |
88 | ### [0.1.1] - 2015-07-09
89 |
90 | #### Changed
91 | * Revised README.md
92 | * Removed `console.log`
93 |
94 | ### 0.1.0 - 2015-07-09
95 |
96 | #### Added
97 | * README.md
98 | * Multiple columns search
99 | * Columns sorting
100 | * Options validation
101 | * Non alphanumeric string replacement
102 |
103 | [1.1.1]: https://github.com/dycodedev/mongo-datatable/compare/1.1.0...1.1.1
104 | [1.1.0]: https://github.com/dycodedev/mongo-datatable/compare/1.0.1...1.1.0
105 | [1.0.1]: https://github.com/dycodedev/mongo-datatable/compare/1.0.0...1.0.1
106 | [1.0.0]: https://github.com/dycodedev/mongo-datatable/compare/0.4.1...1.0.0
107 | [0.4.1]: https://github.com/dycodedev/mongo-datatable/compare/0.4.0...0.4.1
108 | [0.4.0]: https://github.com/dycodedev/mongo-datatable/compare/0.3.1...0.4.0
109 | [0.3.1]: https://github.com/dycodedev/mongo-datatable/compare/0.3.0...0.3.1
110 | [0.3.0]: https://github.com/dycodedev/mongo-datatable/compare/0.2.1...0.3.0
111 | [0.2.1]: https://github.com/dycodedev/mongo-datatable/compare/0.2.0...0.2.1
112 | [0.2.0]: https://github.com/dycodedev/mongo-datatable/compare/0.1.2...0.2.0
113 | [0.1.2]: https://github.com/dycodedev/mongo-datatable/compare/0.1.1...0.1.2
114 | [0.1.1]: https://github.com/dycodedev/mongo-datatable/compare/0.1.0...0.1.1
--------------------------------------------------------------------------------
/lib/columns.js:
--------------------------------------------------------------------------------
1 | var filter = require('lodash/filter');
2 | var forEach = require('lodash/forEach');
3 | var merge = require('lodash/merge');
4 | var string = require('./string');
5 |
6 | function buildSearchCriteria(options) {
7 | var globalSearchValue = options.search.value;
8 | var searchCriteria = {};
9 | var searchAbleColumns = getSearchableColumns(options);
10 | var globallySearchedColumns = [];
11 | var currentColumn;
12 | var currentSearch;
13 | var currentSearchValue;
14 | var escapedSearchValue;
15 |
16 | var shouldCaseInsensitive = typeof options.caseInsensitiveSearch !== 'undefined'
17 | ? string.stringToBoolean(options.caseInsensitiveSearch)
18 | : false;
19 |
20 | forEach(searchAbleColumns, function(column) {
21 | if (column.search.value.length > 0) {
22 | currentSearchValue = column.search.value;
23 |
24 | escapedSearchValue = string.escapeNonAlphanumeric(currentSearchValue);
25 | currentSearch = parseSearchValue(escapedSearchValue, shouldCaseInsensitive);
26 |
27 | searchCriteria[column.data] = currentSearch;
28 | }
29 | else {
30 | globallySearchedColumns.push(column);
31 | }
32 | });
33 |
34 | // If global search value is provided
35 |
36 | if (globalSearchValue.length > 0 && globallySearchedColumns.length > 0) {
37 | searchCriteria['$or'] = [];
38 |
39 | if (globalSearchValue.indexOf(':') > 0) {
40 | var splitted = globalSearchValue.split(':');
41 | var matchingColumn = filter(searchAbleColumns, function(column) {
42 | return column.name.toLowerCase() === splitted[0].toLowerCase();
43 | })[0];
44 |
45 | // Column name and search data match.
46 | if (matchingColumn) {
47 | currentSearch = {};
48 | currentSearch[matchingColumn.data] = parseSearchValue(splitted[1], shouldCaseInsensitive);
49 | searchCriteria['$or'].push(currentSearch);
50 | }
51 | }
52 | else {
53 | globalSearchValue = string.escapeNonAlphanumeric(globalSearchValue);
54 |
55 | forEach(globallySearchedColumns, function(column) {
56 | currentSearch = {};
57 | currentSearch[column.data] = parseSearchValue(globalSearchValue, shouldCaseInsensitive);
58 |
59 | searchCriteria['$or'].push(currentSearch);
60 | });
61 | }
62 | }
63 |
64 | if (Object.keys(searchCriteria).length < 1)
65 | searchCriteria = {};
66 |
67 | if (options.customQuery)
68 | searchCriteria = merge(searchCriteria, options.customQuery);
69 |
70 | return searchCriteria;
71 | }
72 |
73 | function buildColumnSortOrder(options) {
74 | var sortOrder = [];
75 | var columns = options.columns;
76 | var currentOrder;
77 | var currentColumn;
78 |
79 | forEach(options.order, function(order) {
80 | currentColumn = columns[order.column];
81 |
82 | if (currentColumn.orderable === 'true' || currentColumn.orderable === true) {
83 | currentOrder = {};
84 | currentOrder[currentColumn.data] = (order.dir === 'asc') ? 1 : -1;
85 |
86 | sortOrder.push(currentOrder);
87 | }
88 | });
89 |
90 | return sortOrder;
91 | }
92 |
93 | function extractColumns(options) {
94 | var columns = {};
95 |
96 | forEach(options.columns, function(column){
97 | columns[column.data] = 1;
98 | });
99 |
100 | return columns;
101 | }
102 |
103 | function getSearchableColumns(options) {
104 | return filter(options.columns, function(column) {
105 | return column.searchable === 'true' || column.searchable === true;
106 | });
107 | }
108 |
109 | function parseSearchValue(value, caseInsensitive) {
110 | if (!isNaN(Number(value)))
111 | return Number(value);
112 |
113 | // default to case sensitive
114 | var shouldCaseInsensitive = typeof caseInsensitive === 'undefined'
115 | ? false
116 | : caseInsensitive;
117 |
118 | return shouldCaseInsensitive === true
119 | ? new RegExp(value, 'ig')
120 | : new RegExp(value, 'g');
121 | }
122 |
123 | exports.buildSearchCriteria = buildSearchCriteria;
124 | exports.buildColumnSortOrder = buildColumnSortOrder;
125 | exports.extractColumns = extractColumns;
126 | exports.getSearchableColumns = getSearchableColumns;
127 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Mongo DataTable
2 |
3 | Node.js module for server-side processing using jQuery datatables and MongoDB native driver.
4 |
5 | Supports:
6 |
7 | * jQuery Datatables v1.10
8 | * mongodb native driver v2.0 and later
9 | * MongoDB database server v2.4 and later
10 |
11 | ## Install
12 |
13 | ```bash
14 | npm install mongo-datatable
15 | ```
16 |
17 | ## Documentation
18 | This module returns `MongoDataTable` constructor when loaded using `require`.
19 |
20 | ### MongoDataTable(db)
21 |
22 | This constructor takes one argument and must be instantiated using `new` keyword.
23 |
24 | __Argument:__
25 |
26 | * `db` - An instance of `Db` from `mongodb` module.
27 |
28 | ### MongoDataTable.prototype.get(collection, options, callback)
29 |
30 | This method validates the `options` argument and checks the connection to database. If the `options` is invalid or there is no connection made to database, the callback will be called immediately with error. If everything is ok, the callback will be called with `result`.
31 |
32 | __Arguments:__
33 |
34 | * `collection` (*String*) - A string represents name of a collection in your database.
35 | * `options` (*Object*) - An object identic to [sent parameter](https://www.datatables.net/manual/server-side#Sent-parameters) by jquery datatables.
36 | * `callback(error, result)` (*Function*) - The `result` parameter is an object identic to [returned data](https://www.datatables.net/manual/server-side#Returned-data) to jquery datatables.
37 |
38 | __Extra Options:__
39 |
40 | * `showAlertOnError` (*Boolean*) - If this field is set to `true` and `callback` is called with `error`, the error message will be displayed to the user by the datatables. The default value is `false`.
41 | * `customQuery` (*Object*) - Add custom query. Suppose you have a user collection with each user has either admin or user role and you want to display only users with admin role. You can add something like `{ role: 'admin' }` to this field. This query has higher precedence over constructed query.
42 | * `caseInsensitiveSearch` (*Boolean*) - To enable case insensitive search, set this option value to `true`. It is case sensitive by default.
43 |
44 | #### Search Operation
45 |
46 | * If both individual column and global search value are not given, then the search query will be an empty object. Therefore this method will fetch all documents inside the collection.
47 |
48 | * If there is no individual column search value is given and global search value is given, then the global search value will be used as each column's search value. Then, the search query will be like `{ $or: [{ column_0: value }, ... , { column_n: value }] }`.
49 |
50 | * If there is one or more individual column search value is given and the global search value is not given, then the search query will be like `{ column_0: value_0, ... , column_n: value_n }`.
51 |
52 | * If both individual column and global search value are given, then the search query will be like `{ column_0: value_0, column_1: value_1, $or: [{ column_2 : value }, ... , { column_n: value }] }`.
53 |
54 | __There's More:__
55 | You can search data in a specific column using global search input element with `column_name:value` format. This will be useful if you want to search data in a specific column or field but you don't want to display search input element for that column.
56 |
57 | Note that this will work only if you specify `name` in `columns` configuration. See [columns.name configuration](https://datatables.net/reference/option/columns.name).
58 |
59 |
60 | ## Usage
61 |
62 | These examples assume that you are using Express v4
63 |
64 | * Using `MongoClient`
65 |
66 | ```js
67 | var express = require('express');
68 | var mongodb = require('mongodb');
69 | var MongoDataTable = require('mongo-datatable');
70 | var MongoClient = mongodb.MongoClient;
71 | var router = express.Router();
72 |
73 | router.get('/data.json', function(req, res, next) {
74 | var options = req.query;
75 | options.showAlertOnError = true;
76 |
77 | /**
78 | * Using customQuery for specific needs such as
79 | * filtering data which has `role` property set to user
80 | */
81 | options.customQuery = {
82 | role: 'user'
83 | };
84 |
85 | /* uncomment the line below to enable case insensitive search */
86 | // options.caseInsensitiveSearch = true;
87 |
88 | MongoClient.connect('mongodb://localhost/database', function(err, db) {
89 | new MongoDataTable(db).get('collection', options, function(err, result) {
90 | if (err) {
91 | // handle the error
92 | }
93 |
94 | res.json(result);
95 | });
96 | });
97 | });
98 | ...
99 | ```
100 |
101 | * With MongoDB native driver v3
102 |
103 | ```js
104 | var express = require('express');
105 | var mongodb = require('mongodb');
106 | var MongoDataTable = require('mongo-datatable');
107 | var MongoClient = mongodb.MongoClient;
108 | var router = express.Router();
109 |
110 | router.get('/data.json', function(req, res, next) {
111 | var options = req.query;
112 | options.showAlertOnError = true;
113 |
114 | /**
115 | * Using customQuery for specific needs such as
116 | * filtering data which has `role` property set to user
117 | */
118 | options.customQuery = {
119 | role: 'user'
120 | };
121 |
122 | // uncomment the line below to enable case insensitive search
123 | // options.caseInsensitiveSearch = true;
124 |
125 |
126 | /**
127 | * MongoDB native driver v3, MongoClient.connect no longer yields instance of Db.
128 | * It yields the instance of MongoClient instead.
129 | * To get Db instance, you can call `db` method of client with the database name as the argument
130 | */
131 |
132 | MongoClient.connect('mongodb://localhost/database', function(err, client) {
133 | var db = client.db('database');
134 | new MongoDataTable(db).get('collection', options, function(err, result) {
135 | if (err) {
136 | // handle the error
137 | }
138 |
139 | res.json(result);
140 | });
141 | });
142 | });
143 | ...
144 | ```
145 |
146 | * Using `Db` and `Server`
147 |
148 | ```js
149 | var express = require('express');
150 | var mongodb = require('mongodb');
151 | var MongoDataTable = require('mongo-datatable');
152 | var Db = mongodb.Db;
153 | var Server = mongodb.Server;
154 | var router = express.Router();
155 |
156 | router.get('/data.json', function(req, res, next) {
157 | var options = req.query;
158 | var db = new Db('database', new Server('localhost', 27017));
159 |
160 | options.showAlertOnError = true;
161 |
162 | /**
163 | * Using customQuery for specific needs such as
164 | * filtering data which has `role` property set to user
165 | */
166 | options.customQuery = {
167 | role: 'user'
168 | };
169 |
170 | // uncomment the line below to enable case insensitive search
171 | // options.caseInsensitiveSearch = true;
172 |
173 | db.open(function(error, db) {
174 | new MongoDataTable(db).get('collection', options, function(err, result) {
175 | if (err) {
176 | // handle the error
177 | }
178 |
179 | res.json(result);
180 | });
181 | });
182 | });
183 | ...
184 | ```
--------------------------------------------------------------------------------