Code Review Stack Exchange is a question and answer site for peer programmer code reviews. Join them; it only takes a minute:

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

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);
share|improve this question

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Browse other questions tagged or ask your own question.