HTML5 Table Sorting and Pagination using AngularJS

To display data in a HTML document we do use table. While presenting data in a table to make this more user friendly we need some minimum operations like Sorting & Pagination. In this article I am using a HTML5 table to bind data using AngularJS. Including data bind here I implemented sorting & pagination features. Look at the example below.

html-ng-table

index.html

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html ng-app="tblModule">
<head>
<meta charset="utf-8">
<title>HTML5 Table Sorting & Pagination using AngularJS</title>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.0-rc.0/angular.min.js" ng:autobind></script>
<link href="http://netdna.bootstrapcdn.com/twitter-bootstrap/2.1.1/css/bootstrap.no-icons.min.css" rel="stylesheet">
<link href="http://netdna.bootstrapcdn.com/font-awesome/2.0/css/font-awesome.css" rel="stylesheet">
<link rel="stylesheet" href="bootstrap/css/bootstrap.min.css" type="text/css">
<link rel="stylesheet" href="bootstrap/css/bootstrap-theme.min.css" type="text/css">
<script type="text/javascript" src="app.js"></script>
<link rel="stylesheet" href="app-style.css" type="text/css">
<script src="http://code.jquery.com/jquery-1.11.0.min.js">,</script>
</head>
<body>
<script type="text/javascript">
var sortingOrder = 'name';
</script>
<my-customer tbl-width="900"></my-customer>
</body>
</html>

my-customer.html

<div class="input-append">
<input type="text" ng-model="query" ng-change="search()" class="input-large search-query" placeholder="Search">
<span class="add-on"><i class="icon-search"></i></span>
</div>
<table class="table table-striped table-condensed table-hover">
<thead>
<tr>
<th class="id">Id&nbsp;<a ng-click="sort_by('id')"><i class="icon-sort"></i></a></th>
<th class="name">Name&nbsp;<a ng-click="sort_by('name')"><i class="icon-sort"></i></a></th>
<th class="designation">Designation&nbsp;<a ng-click="sort_by('designation')"><i class="icon-sort"></i></a></th>
<th class="employeeid">Employee ID&nbsp;<a ng-click="sort_by('employeeid')"><i class="icon-sort"></i></a></th>
<th class="email">Email&nbsp;<a ng-click="sort_by('email')"><i class="icon-sort"></i></a></th>
<th class="location">Location&nbsp;<a ng-click="sort_by('location')"><i class="icon-sort"></i></a></th>
</tr>
</thead>
<tfoot>
<td colspan="6">
<div class="pagination pull-right">
<ul>
<li ng-class="{disabled: currentPage == 0}">
<a href ng-click="prevPage()">« Prev</a>
</li>
<li ng-repeat="n in range(pagedItems.length)" ng-class="{active: n == currentPage}" ng-click="setPage()">
<a href ng-bind="n + 1">1</a>
</li>
<li ng-class="{disabled: currentPage == pagedItems.length - 1}">
<a href ng-click="nextPage()">Next »</a>
</li>
</ul>
</div>
</td>
</tfoot>
<tbody>
<tr ng-repeat="item in pagedItems[currentPage] | orderBy:sortingOrder:reverse">
<td>{{ item.id }}</td>
<td>{{ item.name }}</td>
<td>{{ item.designation }}</td>
<td>{{ item.employeeid }}</td>
<td><a href="mailto:{{ item.email }}">{{ item.email }}</a></td>
<td>{{ item.location }}</td>
</tr>
</tbody>
</table>

app.js

angular.module('tblModule', [])
.directive('myCustomer', function() {
return {
restrict: 'E',
transclude: true,

scope: {
"tblWidth": '='
},

link: function (scope, element) {
scope.$watch("tblWidth", function (value) {
$("#tbltemp").css({"width": value + "px"});
}, false);
},

controller: function($scope, $filter) {
$scope.sortingOrder = sortingOrder;
$scope.reverse = false;
$scope.filteredItems = [];
$scope.groupedItems = [];
$scope.itemsPerPage = 5;
$scope.pagedItems = [];
$scope.currentPage = 0;
$scope.items = [
{"id":"1","name":"Biswabhusan","designation":"Sr. UI Developer","employeeid":"012801","email":"biswabhusan@gmail.com","location":"INDIA"}, 
{"id":"2","name":"Rakesh Srivastab","designation":"Software Engineer","employeeid":"013882","email":"rakesh@gmail.com","location":"GERMANY"}, 
{"id":"3","name":"John Fernandez","designation":"Sr. Team Lead","employeeid":"055620","email":"john@gmail.com","location":"ITALY"}, 
{"id":"4","name":"Kumar Abhishek","designation":"Project Manager","employeeid":"032451","email":"ku.abhishek@gmail.com","location":"INDIA"}, 
{"id":"5","name":"Swati Rao","designation":"UI Developer","employeeid":"001235","email":"rao@gmail.com","location":"HONG KONG"}, 
{"id":"6","name":"Monika Roy","designation":"SEO","employeeid":"076565","email":"monika@gmail.com","location":"INDIA"}, 
{"id":"7","name":"Monalisha Pradhan","designation":"Sr. Software Engineer","employeeid":"012302","email":"monalisha@gmail.com","location":"US"}
];

var searchMatch = function (haystack, needle) {
if (!needle) {
return true;
}
return haystack.toLowerCase().indexOf(needle.toLowerCase()) !== -1;
};

// initializing the filtered items
$scope.search = function () {
$scope.filteredItems = $filter('filter')($scope.items, function (item) {
for(var attr in item) {
if (searchMatch(item[attr], $scope.query))
return true;
}
return false;
});
// taking care of the sorting order
if ($scope.sortingOrder !== '') {
$scope.filteredItems = $filter('orderBy')($scope.filteredItems, $scope.sortingOrder, $scope.reverse);
}
$scope.currentPage = 0;
// now group by pages
$scope.groupToPages();
};

// calculate page in place
$scope.groupToPages = function () {
$scope.pagedItems = [];

for (var i = 0; i < $scope.filteredItems.length; i++) {
if (i % $scope.itemsPerPage === 0) {
$scope.pagedItems[Math.floor(i / $scope.itemsPerPage)] = [ $scope.filteredItems[i] ];
} else {
$scope.pagedItems[Math.floor(i / $scope.itemsPerPage)].push($scope.filteredItems[i]);
}
}
};

$scope.range = function (start, end) {
var ret = [];
if (!end) {
end = start;
start = 0;
}
for (var i = start; i < end; i++) {
ret.push(i);
}
return ret;
};

$scope.prevPage = function () {
if ($scope.currentPage > 0) {
$scope.currentPage--;
}
};

$scope.nextPage = function () {
if ($scope.currentPage < $scope.pagedItems.length - 1) {
$scope.currentPage++;
}
};

$scope.setPage = function () {
$scope.currentPage = this.n;
};

// functions have been describe process the data for display
$scope.search();

// change sorting order
$scope.sort_by = function(newSortingOrder) {
if ($scope.sortingOrder == newSortingOrder)
$scope.reverse = !$scope.reverse;

$scope.sortingOrder = newSortingOrder;

// icon setup
$('th i').each(function(){
// icon reset
$(this).removeClass().addClass('icon-sort');
});
if ($scope.reverse)
$('th.'+new_sorting_order+' i').removeClass().addClass('icon-chevron-up');
else
$('th.'+new_sorting_order+' i').removeClass().addClass('icon-chevron-down');
};
},
templateUrl: 'my-customer.html'
};
});

app-style.css

thead {
background-color: lightblue;
border-bottom: 2px solid black; 
cursor: pointer;    
}
.paradiv {
float: left;
width: 50%;
}
.pagination {
margin: 0px 0px !important;
}
.borderdiv {
padding: 8px;
}
.ralign {
width:100%; 
text-align:right;	
}