About the grid:

Angularjs grid is an open source data grid for angularjs framework which can be used for free in open source and commercial applications.


Angularjs grid borrows all functionality of ParamQuery grid
( which is written in javascript/jQuery ) and is encapsulated in a directive for seamless integration with angularjs framework. For you as a developer, jQuery knowledge is optional and is not necessary for implementation of angularjs grid.


User interface regions of the grid like toolbar, grid body cells, header cells, row details, nested grids, editors, validations, etc feature two way data angularjs bindings i.e., any change in the model variables update the UI and any UI updates by the user update the model variables. The events in the grid can also be bound to controller methods through attributes.

 

Getting Started:

First of all include the additional dependencies in this order ( after the files mentioned in the include files section for ParamQuery grid):

  1. angular.min.js which can be downloaded from https://angularjs.org or point it to CDN: http://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.5/angular.min.js
  2. ng.pqgrid.min.js which contains the module and directive of pqgrid for Angularjs.

Add the pq.grid module to our main module.

    angular.module('myApp', ['pq.grid'])

Define the grid options as model properties by adding it to $scope variable in our controller.

    controller('myCtrl', function($scope){
        $scope.gridModel = {
            width: "80%",
            height: 400,
            ...
        };
    });

We pass on the gridModel as options attribute to pq-grid directive in our HTML view.

<div ng-controller="myCtrl">
...
    <pq-grid options="gridModel" ></pq-grid>
...
</div>

Few noteworthy points about grid instance and scope of grid:

grid instance is used to call API of the grid in order to manipulate it. i.e., grid.sort( params.. )

When grid is being initialized, grid instance is added to gridModel as its property i.e., gridModel.grid.

When grid is initialized, grid gets a new scope prototypically inherited from its parent scope.

grid instance is added as a property to grid scope as $scope.grid which implies that it can be accessed from any bindings or directives which use the grid scope or have child scopes protypically inherited from grid's scope.

Conversely, the scope of the grid can be obtained from grid instance as grid.$scope which is convenient to access or manipulate grid scope inside grid event handlers.

Controller As:

Dot notation or defining model variables as properties of objects is recommended over adding them directly to $scope to avoid problems with setting model variable values from child scopes that prototypically inherit from parent scopes. controllerAs offers a much cleaner solution by offering this variable inside the controller so that the model variables can be declared as properties of this variable.

Controller:

    controller('myCtrl', function(){
        var vm = this;
        vm.gridModel = {
            width: "80%",
            height: 400,
            ...
        };
    });

View:

<div ng-controller="myCtrl as vm">
...
    <pq-grid options="vm.gridModel" ></pq-grid>
...
</div>

Toolbar binding:

For toolbar binding, we set gridModel.ngModel.compileToolbar = true and add angularjs directives or expressions in gridModel.toolbar.items[].type or gridModel.toolbar.items[].attr properties.

Toolbar gets the grid scope.

ngModel: {compileToolbar: true},
toolbar: {
    items: [
        {
            type: 'select',
            label: 'Select number of frozen rows: ',
            attr: 'ng-model="vm.gridModel.freezeRows" ng-options="o as o for o in vm.options"'
        },
        { type: 'separator' },
        {
            type: 'checkbox',
            label: 'Exclude frozen rows while sorting',
            attr: 'ng-model="vm.sortAll"'
        }
    ]
}

Cell binding:

Every row in grid gets a new scope prototypically derived from the grid scope when either gridModel.ngModel.compileRows = true or there is a template property in any of the columns. The row scope has properties:

  • rd which is reference to the rowData of current row.
  • ri rowIndx of current row which is similar to $index in ng-repeat.
For cell binding, we add angularjs directives in column.template property. e.g.,

{
    title: "Company",
    dataIndx: "company",
    template: '<span ng-class="ri%2==0?\'bold_cls\':\'\'">{{rd.company}}</span>'
}

If we need column specific properties in the cell templates, then we use render callback and return template which has access to column specific properties e.g., colIndx, column, dataIndx through ui argument inside the render callback. Note that it's required to add compileRows: true to ngModel in this case.

{ title: "", align: 'center', editable: false,
    render: function( ui ){
        var width = ui.column.outerWidth, ci = ui.colIndx
        return '<button class="btn-primary btn" ng-click="vm.showMe(ri,rd,'+width+','+ci+')">Click Me</button>';
    }
}

Header cell binding:

Header of the grid is compiled with grid scope when gridModel.ngModel.compileHeader = true. For header cells binding, we add angularjs expressions in column.title property. e.g.,

{ title: "No of clicks: {{vm.ri}}", dataIndx: "company" }

If we need column specific properties, then we use callback variant of column.title which has access to column specific properties e.g., colIndx, column, dataIndx through ui argument.

{ align: 'center', editable: false, dataIndx: 'rank', sortable: false,
    title: function( ui ){
        return '<button class="btn-primary btn" ng-click="vm.showMe('+ui.column.outerWidth+')">Click Me</button>';
    }
},

Events binding:

Events of the grid can be subscribed to in 2 ways:

  1. Add inline event handlers as property of gridModel e.g.,

    //inline property of gridModel.
    beforeSort: function( evt, ui ){
    
    }

    The context this in this case is grid which emits the event.

  2. Specify event handler as attribute in directive and add event handler method in the controller e.g., to listen to beforeSort event.

    <!--Note: the event attribute name starts with on- and put in lower case separated by hyphens -->
    <pq-grid on-before-sort="vm.beforeSort(evt, ui, grid)" options="vm.gridModel" ></pq-grid>
    //add event handler in the controller.
    vm.beforeSort = function( evt, ui, grid ){
    
    }

    The context this in this case is controller. So to get access to grid that emits the event, we can pass grid as one of the parameters to the event. This way of event binding also ensures that at least one $root $digest takes place after call to event handler.

Whole data binding:

Two way data binding of the whole data in grid can be obtained by:

  • Assign data to an object property inside the controller i.e.,

    vm.myData = data;

    where vm is an object defined on the $scope or vm points to this variable inside the controller when used with controllerAs syntax.

  • Assign model property name along with object name in dot notation as a string e.g., 'vm.myData' to gridModel.dataModel.data.

    gridModel.dataModel = { data: 'vm.myData' };

Observe gridModel changes: rebind

When gridModel properties change after initialization of grid, rebind attribute indicates grid to observe for those changes and refresh view according to the changes. rebind is passed to grid directive as an attribute whose value is the name of a single or more options ( separated by empty space in latter case ) whose values are observed by the grid during every $digest. Note that it is supported only for scalar properties currently.

<pq-grid rebind="height selectionModel.type"></pq-grid>

rebind has special value all which indicate that the grid should observe all scalar properties of the gridModel.

<pq-grid rebind="all"></pq-grid>

Custom Editors:

Custom editors are implemented by writing a directive and assigning it to column.editorTemplate Directives are assigned a scope prototypically derived from grid scope and has following properties.

  • ui: An object having same ui properties received in callbacks of column.editor
Link function in the directive is supposed to attach custom editor to ui.$cell.

angular.module('pq.grid')
.directive('autocomplete', function(){
    return{
        restrict: 'AE',
        link: function($scope, $ele, attr){
            debugger;
            var ui = $scope.ui,
                $cell = ui.$cell,
                rowData = ui.rowData,
                dataIndx = ui.dataIndx,
                width = ui.column.width,
                cls = ui.cls,
                dc = $.trim(rowData[dataIndx]);

            var $inp = $("<input type='text' name='" + dataIndx + "' class='" + cls + " pq-ac-editor' />")
                .width(width - 6)
                .appendTo($cell)
                .val(dc);

            $inp.autocomplete({
                source: attr.options? $scope.$eval(attr.options): attr.url,
                minLength: 0
            }).focus(function () {
                //open the autocomplete upon focus
                $(this).autocomplete("search", "");
            });
        }
    };
})

Custom Validations:

Custom validations are implemented by writing a directive and assigning it to column.validations[].template Directives are assigned a scope prototypically derived from grid scope and has following properties.

Link function in the directives are supposed to attach return property to scope signifying validation success or fail.

angular.module('pq.grid')
//validation directives.
.directive('inList',function(){
    return{
        restrict: 'E',
        link: function($scope, $ele, $attr){
            var ui = $scope.ui, value = ui.value, list = $scope.$eval($attr.options);
            if ( list.indexOf( value ) == -1) {
                ui.msg = value + " not found in list";
                $scope.return = false;
            }
        }
    };
})

Row detail or nested grids:

Row details are implemented by writing a directive and assigning it to detailTemplate. Directive is assigned a scope prototypically derived from grid scope and has following properties.

  • ui: An object having same ui properties received by detailModel.init callback.
Link function in the directive is supposed to compile detail template and return it.

angular.module('pq.grid')
.directive('pqGridDetail', ["$compile", function($compile){
    function link($scope, $ele){
        $scope.rd = $scope.ui.rowData;
        $ele = $compile( $ele )( $scope );

        //link should return the compiled $ele.
        return $ele;
    }
    return{
        template: function($ele, attr){
            //template requires one root element.
            return "<div>" + $("#"+attr.templateId).html() + "</div>";
        },
        terminal: true,
        restrict: 'AE',
        replace: true,
        link: link
    };
}]);

Copyright @ 2016 Paramvir Dhindsa (http://paramquery.com)