If you have been working on AngularJS you must have come across a business requirement to display data in a tabular format with all basic features such as “sorting”, “filtering”, “pagination”, “showing / hiding columns”, “exporting data,” etc. Well, most developers have their own preference for executing the stated requirement using their beloved plugin. Some of the mostly preferred plugins are UiGrid, JqGrid, DataTables, and Smart Table etc.
I’m very fond of using UI-Grid for handling the above requirement. If you are not aware of what UI-Grid is please take a look at the below link.
In this article we’ll discuss about how we can create a custom multi-select filter for UI-Grid. By default UI-Grid provides us with a single selection filter. Take a look at the below link which describes how we can use single selection filter in UI-Grid.
http//ui-grid.info/docs/#/tutorial/103_filtering
In one of my project requirements I came across a situation where we need a multi-select filter for filtering UI-Grid data. The main advantage of UI-Grid is that it is fully extensible. You are free to customize each and every part of the UI-Grid right from the top headers columns template to the page navigation template of the UI-Grid. In this article we’ll be customizing the default filterHeaderTemplate template with our custom one. Our final application will look like the below figure.
Note
- Two of the filters are custom multi-select and the salary filter is the default single select filter.
- For Multi-select filter I’m going to use the Angular dropdown multi-select plugin. For more information about this plugin please go thorough the below link
To help you understand the concept I’m going to use AngularJs(1.x), Typescript as my technology stack and for editor I’m using VS2015. So let’s get started.
I’ve created a VS2015 empty Asp.NET web application solution named “UiGridCustomFilter”. Now we’ll add our required technology stack i.e. A ngularJs and Typescript. Below is how I built my application architecture.
Just to give a brief description, our application architecture contains the below files/folders
- “app” is the main folder for the AngularJs application. It contains Angular main module registration and we register our application’s sub modules i.e. app.core, app.widgets&app.modulesetc
- “app.core” where we register our third party librariesangular module as dependency
- “app.route” is the application routing file
- “app.config” is the application configuration file
- “assets” is the container for application “css”, “js”, “imgs” & “libs”
- “modules” is the container for application modules such as “home”, “menus” etc
- “modules.module.ts” contains angular module registration
- “widgets” is the container for application reusable components/directives
- “widgets.module.ts” contains angular module registration
- “Scripts/typings” is folder which contains our d.ts. files
We are done setting up our application framework, now we are ready for coding. To start with I’ve created a Ts file with named “app.ts” which is the bootstrap of our application and also for folders named “modules” & “widgets” we’ve created a ts files named “modules.module.ts” & “widgets.module.ts”. All these files basically register Angular module with Javascript IIFE (Immediately invoked function expression).
App.ts
Main application file. Here we reigster the sub modules i.e. core, widgets etc.
- (() void => {
- "use strict";
- angular.module("app", [
-
- "app.core", "app.widgets", "app.modules",
- ]);
- })();
App.core.ts
Registers an angular module named "app.core".
In this module we list our angular as well as any third party plugins we'll be using in this application.
-
-
-
-
- (() void => {
- "use strict";
- angular.module("app.core", [
-
- 'ui.router', 'ui.grid', 'ui.grid.pagination', 'ui.grid.pinning', 'ui.grid.resizeColumns', 'ui.grid.autoResize', 'ui.grid.exporter', 'ui.grid.selection',
-
- 'angularjs-dropdown-multiselect',
- ]);
- })();
- App.Route.ts
-
- (() void => {
- "use strict";
-
-
-
- angular.module("app").config(routeConfig);
-
- routeConfig.$inject = ["$stateProvider"];
- functionrouteConfig($stateProvider ng.ui.IStateProvider) {
- $stateProvider.state('index', {
- url "/index",
- templateUrl "app/index.html",
- controller "indexController",
- controllerAs "idxc"
- })
- }
- });
- Modules.module.ts
-
-
-
- (() void => {
- "use strict";
- angular.module("app.modules", [
-
- ]);
- })();
- Widgets.module.ts
-
-
-
- (() void => {
- "use strict";
- angular.module("app.widgets", [
-
- ]);
- })();
- Note“ Typescript modules” has no relation with“ angular modules” & vice versa.
- Now we’ ll create our reusable directive in widgets / uigrid - multiselectddl folder with name“ uigrid - multiselectddl.directive.ts”.This directive will be used
- for displaying custom multi - select in the ui - grid filter header template.Below is the code snippet
- for the same.
- uigrid - multiselectddl.directive.ts
- moduleapp.widgets.uigrid.multiselectddl {
- "use strict";
- interfaceIDropDown {
- id number;
- label string;
- $$hashKey ? string;
- }
-
-
-
- classDropDownimplementsIDropDown {
- id number;
- label string;
- $$hashKey string;
- constructor(id number, label string, $$hashKey ? string) {
- this.id = id;
- this.label = label;
- this.$$hashKey = $$hashKey
- }
- }
- interfaceIMultiSelectDDLDirectiveScopeextendsng.IScope {
- selectedDataModel IDropDown[];
- eventListeners Object;
- extraSettings Object;
- checkboxes boolean;
- colFilter uiGrid.IFilterOptions;
- }
-
-
-
- classMultiSelectDDLController {
- static $inject = ["$scope", "uiGridConstants"];
- constructor(private $scope IMultiSelectDDLDirectiveScope, privateuiGridConstants uiGrid.IUiGridConstants) {
- let self = this;
-
- $scope.selectedDataModel = [];
-
- $scope.eventListeners = {
-
-
-
-
- onItemSelect(checkedItem string) => {
- self.doFilter();
- },
-
-
-
-
- onItemDeselect(unCheckedItem string) => {
- self.doFilter();
- },
-
-
-
- onSelectAll() => {
- self.doFilter();
- },
-
-
-
- onDeselectAll(sendEvent any) => {
-
- self.$scope.selectedDataModel.splice(0, self.$scope.selectedDataModel.length);
- self.doFilter();
- }
- };
-
- $scope.extraSettings = {
- externalIdProp '',
- displayProp 'label',
- idProp 'value',
- showCheckAll true,
- showUncheckAll true,
- buttonDefaultText "",
- scrollable false,
- buttonClasses "btnfilterBtn"
- }
- }
-
-
-
- doFilter() {
- let self = this;
- self.$scope.colFilter.term = self.$scope.selectedDataModel.map(function(element) {
- returnelement.label
- }).join(",")
- self.$scope.colFilter.condition = newRegExp(self.$scope.selectedDataModel.map(function(element) {
- returnelement.label
- }).join("|"));
- }
- }
-
-
-
- exportclassMultiSelectDDLDirectiveimplementsng.IDirective {
-
- public restrict string = "EA";
-
- publictemplateUrl string = "widgets/uigrid-multiselectddl/uigrid-multiselectddl.html";
-
- public replace = false;
-
- public scope = false;
-
- controller = MultiSelectDDLController;
-
- constructor() {}
- static instance() ng.IDirective {
- returnnewMultiSelectDDLDirective();
- }
- }
-
- angular.module("app.widgets").directive("app.widgets.uigrid.multiselect", MultiSelectDDLDirective.instance);
- }
- uigrid - multiselectddl.directive.html < div > < style > div.dropdown - multiselect > ul.dropdown - menu > liaspan {
- font - weight bold;
- color black!important;
- } < /style> < divname = "multiDDL"
- ng - dropdown - multiselect = ""
- options = "colFilter.selectOptions"
- selected - model = "selectedDataModel"
- events = "eventListeners"
- extra - settings = "extraSettings"
- checkboxes = "true" > < /div> < /div>
Now we’ll head towards creating our indexController.ts which will be a part of modules/home folder.
Index.controller.ts
- moduleapp.modules {
-
-
-
- classStringToBooleanConverter {
- static Convert(value string) boolean {
- return (value === "true");
- }
- }
-
-
-
- interfaceIOption {
- value string;
- label string;
- }
- /
Employee Model
- interfaceIEmployee {
- empId number;
- name string;
- age number;
- designation string;
- expertiseIn string;
- salary number;
- }
- /
UiGrid Column Definition
You can add more properties to this class as required which may include "custom properties" as well as "uiGrid Col definition" properties
- /
- interfaceIUiGridColDefinition {
-
- name string;
- displayName string;
- field string;
- filterHeaderTemplate ? string;
- cellClass ? string;
- cellFilter ? string;
- enableFiltering ? string;
-
-
- useCustomFilterHeaderTemplate ? string;
-
- }
- interfaceIIndexController {
- gridApi uiGrid.IGridApi;
- gridOptions uiGrid.IGridOptions;
- dataSource IEmployee[];
- gridColumnDefinitions IUiGridColDefinition[];
- bindData() void;
- getGridSettings() void;
- configureGrid() void;
- }
- /
Index Controller
- classIndexControllerimplementsIIndexController {
- gridOptions uiGrid.IGridOptions;
- gridApi uiGrid.IGridApi;
- dataSource IEmployee[];
- gridColumnDefinitions IUiGridColDefinition[];
- static $inject = ["$scope", "$http", "$window", "uiGridConstants", "uiGridExporterConstants", "uiGridExporterService"];
- constructor(private $scope ng.IScope, private $http ng.IHttpService, private $window ng.IWindowService, privateuiGridConstants uiGrid.IUiGridConstants, privateuiGridExporterConstants uiGrid.exporter.IUiGridExporterConstants, privateuiGridExporterService uiGrid.exporter.IGridExporterApi) {
- let self = this;
-
- self.dataSource = [];
- self.gridColumnDefinitions = [];
-
- self.gridOptions = {
- excludeProperties['__metadata'],
- enableSorting true,
- showGridFooter true,
- showColumnFooter true,
- enableFiltering true,
- enableColumnResizing false,
- enablePinning false,
- enableHorizontalScrollbar true,
- minRowsToShow 10,
- enablePagination true,
- paginationPageSizes[10, 20, 30],
- paginationPageSize 10,
- rowHeight 22,
- multiSelect true,
- onRegisterApi
- function(gridApi uiGrid.IGridApi) {
- self.gridApi = gridApi;
- }
- }
-
- self.getGridSettings();
- }
- /
GET the UI-Grid settings
- getGridSettings() void {
- let self = this;
-
- letserviceUrl = self.$window.location.protocol + '//' + self.$window.location.hostname + (self.$window.location.port ? '' + self.$window.location.port '') + "/app/modules/home/grid.settings.json";
- self.$http.get(serviceUrl).then((successCallBack ng.IHttpPromiseCallbackArg < {} > ) => {
- if (successCallBack.status === 200 && successCallBack.data != null) {
- self.gridColumnDefinitions = < IUiGridColDefinition[] > successCallBack.data;
- self.bindData();
- }
- });
- }
- /
Get the Employee Data
- /
- bindData() void {
- let self = this;
-
- letserviceUrl = self.$window.location.protocol + '//' + self.$window.location.hostname + (self.$window.location.port ? '' + self.$window.location.port '') + "/app/modules/home/data.json";
-
- self.gridOptions.columnDefs = [];
- self.$http.get(serviceUrl).then((successCallBack ng.IHttpPromiseCallbackArg < {} > ) => {
- if (successCallBack.status === 200 && successCallBack.data != null) {
- console.log(successCallBack.data);
- self.dataSource = < IEmployee[] > successCallBack.data;
- self.configureGrid();
- }
- });
- }
- /
Configuring the Grid
- /
- configureGrid() void {
- let self = this;
- let props = Object.keys(self.dataSource[0]);
- console.log(props);
- for (let prop of props) {
-
- letcolDef uiGrid.IColumnDefOf < any > = {
- name prop,
- field prop,
- enablePinning false,
- cellTooltip true,
- enableColumnResizing true,
- enableHiding false
- }
- $.each(self.gridColumnDefinitions, function(index, jsonObject) {
- if (jsonObject.name == prop) {
- colDef.displayName = jsonObject.displayName;
- colDef.cellClass = jsonObject.cellClass;
- colDef.cellFilter = jsonObject.cellFilter;
- if (StringToBooleanConverter.Convert(jsonObject.enableFiltering)) {
- let options IOption[] = [];
-
- $.each(self.dataSource, function(index, objModel) {
- if (objModel[prop] != null) {
- if (options.length == 0) {
- options.push({
- value objModel[prop],
- label objModel[prop]
- });
- } else {
- letoptionAlreadyExists boolean = false;
- $.each(options, function(idx, objOption) {
- if (objOption.label == objModel[prop]) {
- optionAlreadyExists = true;
-
- returnfalse;
- }
- });
- if (!optionAlreadyExists) {
- options.push({
- value objModel[prop],
- label objModel[prop]
- });
- }
- }
- }
- });
-
- options = options.sort((a, b) => {
- if (a.label > b.label) {
- return 1;
- }
- if (a.label < b.label) {
- return -1;
- }
- return 0;
- });
-
- colDef.filter = {
- term '',
- placeholder jsonObject.name,
- selectOptions options,
- }
- if (StringToBooleanConverter.Convert(jsonObject.useCustomFilterHeaderTemplate)) {
-
- colDef.filterHeaderTemplate = jsonObject.filterHeaderTemplate;
- } else {
-
- colDef.filter.type = self.uiGridConstants.filter.SELECT;
- }
- } else {
- colDef.enableFiltering = false;
- }
-
- returnfalse;
- }
- });
-
- self.gridOptions.columnDefs.push(colDef);
- }
-
- self.gridOptions.data = self.dataSource;
- }
- }
-
- angular.module("app.modules").controller("indexController", IndexController);
- }
Note
- For the sake of simplicity I’m using json file which is holding some dummy employee details. I’ve created this file inside the modules/home folder with name “data.json”.
- Also I’ve created “grid.settings.json” which is holding the UI-Grid column definition settings such as the column name, column header css, footer css, any custom filter needs to be applied on column etc. It’s not mandatory to create this file, but it provides me more easy maintenance instead of hardcoding things at the controller level. Inside this file we are setting the Filter Header template for ui-grid column “Expertise-In” & “Designation”. Please look at this file for more information.
- Both these files have been loaded in index.controller.ts using $http service.
Grid.settings.json
- {
- "empId" {
- "cellClass"
- "ui-grid-number-cell", "cellFilter"
- "number", "displayName"
- "Emp ID", "enableFiltering"
- "false", "field"
- "{{COL_FIELD}}", "name"
- "empId"
- }, "name" {
- "cellClass"
- "ui-grid-cell", "displayName"
- "Name", "enableFiltering"
- "false", "field"
- "{{COL_FIELD}}", "name"
- "name"
- }, "age" {
- "cellClass"
- "ui-grid-number-cell", "cellFilter"
- "number", "displayName"
- "Age", "enableFiltering"
- "false", "field"
- "{{COL_FIELD}}", "name"
- "age"
- }, "expertiseIn" {
- "cellClass"
- "ui-grid-cell", "displayName"
- "Expertise In", "enableFiltering"
- "true", "filterHeaderTemplate"
- "<div class='ui-grid-filter-container' ng-repeat='colFilter in col.filters'><app.widgets.uigrid.multiselect></app.widgets.uigrid.multiselect></div>", "useCustomFilterHeaderTemplate"
- "true", "field"
- "{{COL_FIELD}}", "name"
- "expertiseIn"
- }, "designation" {
- "cellClass"
- "ui-grid-cell", "displayName"
- "Designation", "enableFiltering"
- "true", "field"
- "{{COL_FIELD}}", "filterHeaderTemplate"
- "<div class='ui-grid-filter-container' ng-repeat='colFilter in col.filters'><app.widgets.uigrid.multiselect></app.widgets.uigrid.multiselect></div>", "useCustomFilterHeaderTemplate"
- "true", "name"
- "designation"
- }, "salary" {
- "cellClass"
- "ui-grid-number-cell", "cellFilter"
- "number", "displayName"
- "Salary", "enableFiltering"
- "true", "field"
- "{{COL_FIELD}}", "useCustomFilterHeaderTemplate"
- "false", "name"
- "salary"
- }
- }
Here is our index.html
- <!DOCTYPEhtml>
- <htmlng-app="app">
-
- <head>
- <title>Index</title>
- <metaname="viewport" content="width=device-width, initial-scale=1.0" />
- <metahttp-equiv="Content-Type" content="text/html; charset=utf-8" />
- <metahttp-equiv="X-UA-Compatible" content="IE=edge">
- <linkhref="favicon.ico" rel="shortcut icon" type="image/x-icon" />
- <linkhref="assets/libs/bootstrap/bootstrap.min.css" rel="stylesheet" />
- <linkhref="assets/libs/angular-ui/ui-bootstrap-csp.css" rel="stylesheet" />
- <linkhref="assets/css/ui-grid/ui-grid.min.css" rel="stylesheet" />
- <linkhref="assets/css/angular-dropdown-multiselect/angular-dropdown-multiselect.css" rel="stylesheet" />
- <linkhref="assets/libs/fonts/font-awesome/css/font-awesome.min.css" rel="stylesheet" />
- <linkhref="assets/css/site.css" rel="stylesheet" />
- <scriptsrc="assets/libs/angularjs/angular.min.js">
- </script>
- <scriptsrc="assets/libs/jquery/jquery-1.10.2.min.js">
- </script>
- <scriptsrc="assets/libs/bootstrap/bootstrap.min.js">
- </script>
- <scriptsrc="assets/libs/angular-ui/ui-bootstrap.min.js">
- </script>
- <scriptsrc="assets/libs/angularjs/angular-ui-router.min.js">
- </script>
- <scriptsrc="assets/js/ui-grid/ui-grid.min.js">
- </script>
- <scriptsrc="assets/js/loadash/loadash.min.js">
- </script>
- <scriptsrc="assets/js/angular-dropdown-multiselect/angular-dropdown-multiselect.min.js">
- </script>
- <scriptsrc="app.js">
- </script>
- <scriptsrc="app.config.js">
- </script>
- <scriptsrc="app.core.js">
- </script>
- <scriptsrc="app.route.js">
- </script>
- <scriptsrc="widgets/widgets.module.js">
- </script>
- <scriptsrc="modules/modules.module.js">
- </script>
- <scriptsrc="widgets/uigrid-multiselectddl/uigrid-multiselectddl.directive.js">
- </script>
- <scriptsrc="modules/home/index.controller.js">
- </script>
- </head>
- <bodyng-controller="indexControllerasidxc">
- <divclass="container" style="margin-top150px;">
- <divclass="row">
- <divclass="col-lg-12">
- <divid="grid1" ui-grid="idxc.gridOptions" ui-grid-paginationui-grid-pinningui-grid-resize-columnsui-grid-exporterui-grid-auto-resizeclass="ui-grid">
- </div>
- </div>
- </div>
- </div>
- </body>
-
- </html>
Please run the application and you’ll be able to see the below output.
On checking of any item it will filter out the UI-Grid data.
Here is the filtered output
Also you could simply try to use another filter too which will further restrict the UI-Grid data.