We can create a custom list widget that can have pagination, ascending order, descending order, and create a filter sample widget attached below.

Here is the HTML code below
<div>
<div class="filter-breadcrumbs">
<sp-widget widget="data.filterBreadcrumbs"></sp-widget>
</div>
<table id="customers">
<tr>
<th ng-repeat="x1 in data.header" ng-click="sort(data.fieldList[$index])">{{x1}}
<span class="sortorder descending" ng-show="(sortKey==data.fieldList[$index] && reverse==false)"></span>
<span class="sortorder" ng-show="(sortKey==data.fieldList[$index] && reverse==true)"></span>
</th>
</tr>
<tr ng-repeat="c in data.rec | orderBy: sortKey:reverse | limitTo:maxInc: currentIndex">
<td ng-repeat="cal in data.fieldList">
{{c[cal]}}
</td>
</tr>
</table>
<div>
<ul class="pagination" style="float:right">
<li ng-repeat="i in getNumber(pageNumber) track by $index" ng-click="pageChange($index)" ng-class="isActive($index)" ><a>{{$index+1}}</a></li>
</ul>
</div>
</div>
Client Script will be like below
api.controller=function($scope,spUtil) {
/* widget controller */
// Start for pagination
$scope.totalInc= $scope.data.rec.length;
$scope.maxInc= 10; $scope.currentIndex=0;
$scope.pageNumber= Math.ceil($scope.totalInc/$scope.maxInc);
$scope.currentPage=0;
$scope.getNumber=function (number){
return new Array(number);
}
$scope.pageChange = function (index){
$scope.currentPage=index;
if(index==0)
$scope.currentIndex=0;
else
$scope.currentIndex= $scope.maxInc*(index);
}
$scope.isActive = function (index){
if($scope.currentPage == index)
return "active";
else
return "";
}
// End for pagination
/* For breadcams */
var c = this;
$scope.$on('widget-filter-breadcrumbs.queryModified', function(e, newFilter){
$scope.data.filter = newFilter;
spUtil.update($scope).then(function(){
$scope.totalInc= $scope.data.rec.length;
$scope.maxInc= 10; $scope.currentIndex=0;
$scope.pageNumber= Math.ceil($scope.totalInc/$scope.maxInc);
$scope.currentPage=0;
});
});
// End Here
//Shorting
$scope.reverse = false;
$scope.sortKey = 'number';
$scope.sort = function (keyname) {
$scope.sortKey = keyname;
$scope.reverse = !$scope.reverse;
$scope.currentIndex=0;
}
//End Shorting
};
Server Script like below:
(function() {
data.table = 'incident';
data.fieldList= 'number,short_description,caller_id,priority';
data.fieldList=data.fieldList.split(',');
var header='Number,Short Description,Caller Id,Priority';
data.header=header.split(',');
data.rec=[];
data.filter = data.filter || input.filter;
data.filter2 = data.filter2|| input.filter2;
var gr1= new GlideRecord('incident');
var gr = new GlideRecord(data.table);
gr.addEncodedQuery('active=true')
if(data.filter)gr.addEncodedQuery(data.filter);
gr.orderByDesc('sys_created_on');
gr.query();
while(gr.next()){
var j={};
for(var i=0; i<data.fieldList.length; i++){
j[data.fieldList[i]]=gr.getDisplayValue(data.fieldList[i]);
}
j.sys_id=gr.getValue('sys_id');
j.checkBox=false;
data.rec.push(j);
}
var breadcrumbWidgetParams = {
table: 'incident',
query: data.filter,
enable_filter: true
};
data.filterBreadcrumbs = $sp.getWidget('widget-filter-breadcrumbs', breadcrumbWidgetParams);
})();
css will like below:
#customers {
font-family: Arial, Helvetica, sans-serif;
border-collapse: collapse;
width: 100%;
}
#customers td, #customers th {
border: 1px solid #ddd;
padding: 8px;
}
#customers tr:nth-child(even){background-color: #f2f2f2;}
#customers tr:hover {background-color: #ddd;}
#customers th {
padding-top: 12px;
padding-bottom: 12px;
text-align: left;
background-color: #04AA6D;
color: white;
}
.sortorder:after {
content: '\25b2';
}
.sortorder.descending:after {
content: '\25bc';
}