Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ __Extra Options:__

* `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`.
* `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.
* `aggregateQuery` (*Array*) - Run custom aggregate query (pipeline). Be sure to manually select which data fields will be returned using projection stage and name those fields according to jquery datatable column names. Filtering (comming from front-end jquery datatable), sorting, and pagination will be automatically executed as final stages of the aggregate pipeline.
* `caseInsensitiveSearch` (*Boolean*) - To enable case insensitive search, set this option value to `true`. It is case sensitive by default.

#### Search Operation
Expand Down Expand Up @@ -181,4 +182,38 @@ router.get('/data.json', function(req, res, next) {
});
});
...
```

* Using custom `aggregateQuery` option. Front-end jquery datatable has two columns: `role` and `count`

```js
var express = require('express');
var mongodb = require('mongodb');
var MongoDataTable = require('mongo-datatable');
var MongoClient = mongodb.MongoClient;
var router = express.Router();

router.get('/data.json', function(req, res, next) {
var options = req.query; //query comming from front-end jquery datatable
options.showAlertOnError = true;

options.aggregateQuery = [
{$group: {
_id: '$role',
role: {$last: '$role'}
count: {$sum: '1'}
}},
{$project: {
role: 1,
count: 1
}}
];

MongoClient.connect('mongodb://localhost/database', function(err, db) {
new MongoDataTable(db).get('collection', options, function(err, result) {
res.json(result);
});
});
});
...
```
81 changes: 67 additions & 14 deletions lib/MongoDataTable.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ MongoDataTable.prototype.get = function(collectionName, options, onDataReady) {
};

var searchCriteria = cols.buildSearchCriteria(options);
var aggQuery = cols.buildAggregateQuery(searchCriteria, options);

function getCollectionLength(callback) {
if (self.db === null || typeof self.db === 'undefined') {
Expand All @@ -31,19 +32,46 @@ MongoDataTable.prototype.get = function(collectionName, options, onDataReady) {
var earlyCollection = self.db.collection(collectionName);
response.draw = parseInt(options.draw, 10);

earlyCollection
.find(searchCriteria, columns)
.count(function(error, result) {
if (options.aggregateQuery) {

var aggCountQuery = aggQuery.concat([{$group: { _id: null, count: { $sum: 1 } }}]);

earlyCollection.aggregate(aggCountQuery).toArray(function(error, result) {

if (error) {
return callback(error, null);
}

response.recordsTotal = result;
response.recordsFiltered = result;
if (result.length==1) {
response.recordsTotal = result[0]['count'];
response.recordsFiltered = result[0]['count'];
} else {
response.recordsTotal = 0;
response.recordsFiltered = 0;
}


return callback(null);
});

} else {

earlyCollection
.find(searchCriteria, columns)
.count(function(error, result) {

if (error) {
return callback(error, null);
}

response.recordsTotal = result;
response.recordsFiltered = result;

return callback(null);
});

}

}

function validateOptions(callback) {
Expand All @@ -61,20 +89,45 @@ MongoDataTable.prototype.get = function(collectionName, options, onDataReady) {
}

function getAndSortData(callback) {
var sortOrder = cols.buildColumnSortOrder(options);

var collection = self.db.collection(collectionName);

collection = collection.find(searchCriteria, columns);
if (options.aggregateQuery) {

var aggSortOrder = cols.buildAggregateQuerySortOrder(options);

if (Object.keys(aggSortOrder['$sort']).length>0) {
aggQuery.push(aggSortOrder);
}

if (parseInt(options.length) > 0) {
aggQuery.push({
'$skip': parseInt(options.start),
});
aggQuery.push({
'$limit': parseInt(options.length),
});
}

collection = collection.aggregate(aggQuery);

} else {

var sortOrder = cols.buildColumnSortOrder(options);

collection = collection.find(searchCriteria, columns);
if (parseInt(options.length) > 0) {
collection = collection
.skip(parseInt(options.start))
.limit(parseInt(options.length));
}

forEach(sortOrder, function(order) {
collection = collection.sort(order);
});

if (parseInt(options.length) > 0) {
collection = collection
.skip(parseInt(options.start))
.limit(parseInt(options.length));
}

forEach(sortOrder, function(order) {
collection = collection.sort(order);
});

collection.toArray(callback);
}
Expand Down
40 changes: 40 additions & 0 deletions lib/columns.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,44 @@ function buildColumnSortOrder(options) {
return sortOrder;
}

function buildAggregateQuery(searchCriteria, options) {

var aggQuery = [];

if (options.aggregateQuery) {

options.aggregateQuery.forEach(function (query){
aggQuery.push(query);
});

if (Object.keys(searchCriteria).length>0) {
aggQuery.push({
'$match': searchCriteria
});
}

}

return aggQuery;

}

function buildAggregateQuerySortOrder(options) {
var aggSortOrder = {'$sort':{}};
var columns = options.columns;
var currentColumn;

forEach(options.order, function(order) {
currentColumn = columns[order.column];

if (currentColumn.orderable === 'true' || currentColumn.orderable === true) {
aggSortOrder['$sort'][currentColumn.data] = (order.dir === 'asc') ? 1 : -1;
}
});

return aggSortOrder;
}

function extractColumns(options) {
var columns = {};

Expand Down Expand Up @@ -122,5 +160,7 @@ function parseSearchValue(value, caseInsensitive) {

exports.buildSearchCriteria = buildSearchCriteria;
exports.buildColumnSortOrder = buildColumnSortOrder;
exports.buildAggregateQuerySortOrder = buildAggregateQuerySortOrder;
exports.buildAggregateQuery = buildAggregateQuery;
exports.extractColumns = extractColumns;
exports.getSearchableColumns = getSearchableColumns;
5 changes: 5 additions & 0 deletions lib/validator.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
var util = require('util');

function isOptionsValid(options, immediateCallback) {
if (typeof options === 'undefined')
return immediateCallback(new Error('Options must be defined!'));
Expand All @@ -11,6 +13,9 @@ function isOptionsValid(options, immediateCallback) {
if (typeof options.search === 'undefined')
return immediateCallback(new Error('Search field must be defined!'));

if (typeof options.aggregateQuery !== 'undefined' && (!util.isArray(options.aggregateQuery) || options.aggregateQuery.length==0) )
return immediateCallback(new Error('Aggregate query must be non empty array object!'));

var isStartValid = (typeof options.start !== 'undefined'
|| parseInt(options.start, 10) >= 0);

Expand Down