1
\$\begingroup\$

I'm using Angular Smart Tables with data that might become a huge set of documents and rows. So rather than client side processing of the data for pagination, I borrowing code from two Mongoose.js plugins, put together a plugin to do server side processing for pagination with Angular Smart Tables. The plugins I used are mongoose-paginate and mongoose-list.

The concept is to take the tableState object from Smart Tables, serialize it, and pass it directly to the server expecting an object returned that contains the table row object for the current viewed page of the table and the total number of pages in the document set as specified by Smart Tables.

The plugin simply abstracts the business logic. However, I needed some flexibility with selecting fields to return and query conditions. I would have preferred to wrap the Mongoose query and extend it, but I didn't know how to do that. So I created a class that looks like chaining a Mongoose query but is limited to just what I need.

One feature that I might add at a later point is normalization of fields that become sortable columns. MongoDB doesn't do case insensitive sorting. So it would be nice if the plugin took an option that adds a normalized to lowercase property which it indexes and uses for case insensitive sorting.

server.plugin.js:

'use strict';

var async = require('async');

module.exports = exports = function (schema, options){

    schema.statics.stPagination = function stPagination(tableState) {

        var model = this;

        function StPagination (tableState) {
            var conditions = {};

            this.model = model;
            this.start = tableState.pagination.start || 0;
            this.number = tableState.pagination.number || 10;

            if (typeof tableState.search !== 'undefined' && Object.keys(tableState.search.predicateObject).length) {
                conditions = {$and: []};
                Object.keys(tableState.search.predicateObject).forEach(function(path) {
                    var condition = {},
                        re = new RegExp(tableState.search.predicateObject[path], 'i');
                    condition[path] = re;
                    conditions.$and.push(condition);
                });
            }

            this.query = model.find(conditions);

            this.query.skip(this.start);
            this.query.limit(this.number);

            if (typeof tableState.sort !== 'undefined' && Object.keys(tableState.sort).length) {
                var obj = {};
                obj[tableState.sort.predicate] = tableState.sort.reverse === 'true' ? -1 : 1;
                this.query.sort(obj);
            }
        }

        StPagination.prototype.select = function(value) {
            this.query.select(value);
            return this;
        };

        StPagination.prototype.where = function(path, condition) {
            this.query.where(path, condition);
            return this;
        }

        StPagination.prototype.exec = function(callback) {
            var _this = this;
            async.parallel({
                results: function(fn) {
                    _this.query.exec(function(err, results) {
                        if (err) return fn(err);
                        fn(null,results);
                    })
                },
                count: function(fn) {
                    _this.query.count(function(err, count) {
                        if (err) return fn(err);
                        fn(null,count);
                    })
                }
            }, function(err, data) {
                if (err) return callback(err);
                callback(null, {
                    numberOfPages: Math.ceil(data.count / _this.number),
                    data: data.results
                })
            });
        };

        return new StPagination(tableState);
    }
}

template.tpl.html:

<div class="row import-queue">
<div class="col-sm-12">
    <div class="panel panel-default">
        <div class="panel-heading clearfix">
            <h3 class="pull-left">Import queue</h3>
            <button type="button" style="margin-top:20px;"
                    class="btn btn-sm btn-info pull-right"
                    ng-click="eventFeedCtrl.importEvents()">Update</button>
        </div>
        <div class="panel-body">
            <table st-table="eventFeedCtrl.events"
                   st-pipe="eventFeedCtrl.getEventFeed"
                   class="table table-hover">
                <thead>
                    <tr>
                        <th st-sort="id">ID</th>
                        <th st-sort="eventName">Event name</th>
                        <th st-sort="eventDate">Date</th>
                        <th>Actions</th>
                    </tr>
                    <tr>
                        <th>
                            <input st-search="id" placeholder="search for event by id" class="input-sm form-control" type="search"/>
                        </th>
                        <th>
                            <input st-search="eventName" placeholder="search for event" class="input-sm form-control" type="search"/>
                        </th>
                        <th></th>
                        <th></th>
                    </tr>

                </thead>
                <tbody>
                    <tr ng-repeat="event in eventFeedCtrl.events track by $index">
                        <td>{{event.id}}</td>
                        <td>{{event.eventName}}</td>
                        <td>{{event.eventDate | date : short}}</td>
                        <td>
                            <button ng-disabled="event.processed"
                                    type="button"
                                    class="btn btn-xs btn-warning">Process</button>
                        </td>
                    </tr>
                </tbody>
                <tfoot>
                    <tr>
                        <td colspan="5" class="text-center">
                            <div st-pagination="" st-items-by-page="15" st-displayed-pages="7"></div>
                        </td>
                    </tr>
                </tfoot>
            </table>
        </div>
    </div>
</div>

controller.js:

this.getEventFeed = function(tableState) {

    EventFeedService
        .getEventFeed(tableState)
        .then(function(response) {
            _this.events = response.data;
            tableState.pagination.numberOfPages = response.numberOfPages;
        });
};

service.js:

function getEventFeed(tableState) {
    var deferred = $q.defer();

    $http({
            url: '/api/feeds/event-feed',
            method: 'GET',
            params: {tableState: tableState},
            paramSerializer: '$httpParamSerializerJQLike'
        })
        .then(function(response) {
            console.log(response.data);
            deferred.resolve(response.data);
        }, function (err) {
            console.log(err);
            deferred.reject(err);
        });

    return deferred.promise;
}

server.controller.js:

exports.getEventFeed = function(req, res, next) {
    var tableState = req.query.tableState;

    EventFeed
        .stPagination(tableState)
        .select('id eventDate eventName processed')
        .where('eventDate', {$gte: new Date()})
        .exec(function (err, results) {
            if (err) return next(err);
            res.json(results);
        });
};

server.model.js:

'use strict';

var mongoose = require('mongoose'),
    Schema = mongoose.Schema,
    mongooseStPagination = require('../lib/mongoose-st-        pagination/mongoose-st-pagination.js');

var EventFeedSchema = new Schema({
    id: String,
    eventDate: Date,
    eventName: String,
    processed: {
        type: Boolean,
        default: false
    },
    event: Schema.Types.Mixed
});

EventFeedSchema.plugin(mongooseStPagination);

mongoose.model('EventFeed', EventFeedSchema);
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.