Monday, October 21, 2013

A Data Pager with Angular

I had created a data pager to use in a knockout / MVC4 project. Recently I adapted it to angular for another ASP.NET/MVC 4 project. I was told this may be useful for the community, so here it is. I will put this up on github soon.

The controller method: Should return a json object that has the following objects in it: TotalRecords, Records. TotalRecords is an int that tells the pager the total number of records in the source that passed the query.

Consuming javascript: Should initiate the pager, and handle the server requests. Here is a snippet that illustrates this:
                scope.pager = new pager();
                scope.filter = [[someFilterYouPassToControllerMethod]]
                scope.pager.onGetRecords = function(){
                    scope.filter.StartRecord = scope.pager.currentBeginRecord;
                    scope.filter.NumRecords = scope.pager.numRecordsPerPage;
                    http({
                        method: 'GET',
                        url: [[controllerMethodUrl]],
                        params: scope.filter
                    })
                        .success(function (data) {
                            scope.pager.onRecordsFetched(data);
                        });
                };

Here is an example of html/ng snippet that renders the pager (uses twitter bootstrap styles and font-awesome):
--------------
<div class="container">
        <div class="row">
            <div class="col-md-6 form-inline">
                <div class="input-prepend input-append">
                    <div class="btn-group">
                        <button class="btn btn-default btn-sm" ng-disabled="pager.cannotGoBack()" ng-click="pager.goToBeginning()"><i class="icon-fast-backward"></i></button>
                        <button class="btn btn-default btn-sm" ng-disabled="pager.cannotGoBack()" ng-click="pager.goBack()"><i class="icon-step-backward"></i></button>
                    </div>
                    <input type="text" ng-model="pager.currentPage" class="form-control" ng-model-onblur  ng-change="pager.goToPage()" style="width: 54px;" />
                    <span class="add-on"> / {{pager.totalPages}} pages</span>
                    <div class="btn-group">
                        <button class="btn btn-default btn-sm" ng-disabled="pager.cannotGoForward()" ng-click="pager.goForward()"><i class="icon-step-forward"></i></button>
                        <button class="btn btn-default btn-sm" ng-disabled="pager.cannotGoForward()" ng-click="pager.goToEnd()"><i class="icon-fast-forward"></i></button>
                    </div>
                    <div class="add-on" ng-show="pager.isBusy">
                        <i class="icon-spinner icon-spin"></i>
                    </div>
                </div>
            </div>
            <div class="col-md-6 form-inline">
                <small class="pull-right"> showing {{pager.displayBeginRecord()}} to {{pager.displayEndRecord()}} out of {{pager.totalRecords}} records
                    .. <input type="text" ng-model="pager.numRecordsPerPage" class="form-control" ng-model-onblur ng-change="pager.initAndGetRecords()" style="width:54px"/> records per page</small>
            </div>
        </div>
    </div>
----------------
Finally, the pager code:
function pager() {
    var that = this;
    that.isBusy = false;
    that.currentBeginRecord = 0;
    that.totalRecords = 0;
    that.numRecordsPerPage = 10;

    that.currentPage = 1;
    that.totalPages = 1;

    that.records = [];

    that.currentEndRecord = function () {
        return (that.currentBeginRecord + that.records.length - 1);
    };

    that.canGoBack = function () {
        return (that.currentBeginRecord > 0 && !that.isBusy);
    };

    var isNumeric = function (n) {
        return !isNaN(parseFloat(n)) && isFinite(n);
    };

    that.canGoForward = function () {
        return (that.currentBeginRecord + that.records.length < that.totalRecords && !that.isBusy);
    };

    that.cannotGoBack = function () { return !that.canGoBack(); }
    that.cannotGoForward = function () { return !that.canGoForward(); }

    that.displayBeginRecord = function () {
        return that.currentBeginRecord + 1;
    };

    that.displayEndRecord = function () {
        return that.currentEndRecord() + 1;
    };

    that.onRecordsFetched = function (data) {
        that.records = data.Records;
        that.totalRecords = data.TotalRecords;
        that.totalPages = Math.ceil(data.TotalRecords / that.numRecordsPerPage);
        that.currentPage = Math.ceil((that.currentBeginRecord + 1) / that.numRecordsPerPage);
        that.isBusy = false;
    };

    that.goToPage = function () {
        var cp = that.currentPage;
        if (isNumeric(cp) && cp > 0 && cp <= that.totalPages) {
            that.currentBeginRecord = (cp - 1) * that.numRecordsPerPage;
            that.getRecords();
        }
    }

    that.getRecords = function () {
        that.isBusy = true;
        if (typeof this.onGetRecords === "function") {
            this.onGetRecords();
        }
    }

    that.initAndGetRecords = function () {
        that.currentBeginRecord = 0;
        that.getRecords();
    }

    that.goToBeginning = function () {
        that.currentBeginRecord = 0;
        that.getRecords();
    };

    that.pageRefresh = function () {
        that.getRecords();
    };


    that.goForward = function () {
        that.currentBeginRecord = that.currentBeginRecord + that.records.length;
        that.getRecords();
    };

    that.goBack = function () {
        that.currentBeginRecord = that.currentBeginRecord - that.numRecordsPerPage;
        if (that.currentBeginRecord < 0) that.currentBeginRecord = 0;
        that.getRecords();
    };

    that.goToEnd = function () {
        var recsInLastPage = that.totalRecords % that.numRecordsPerPage;
        if (recsInLastPage < 1) recsInLastPage = that.numRecordsPerPage;
        that.currentBeginRecord = that.totalRecords - recsInLastPage;
        if (that.currentBeginRecord < 0) that.currentBeginRecord = 0;
        that.getRecords();
    };
}