Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@CWSpear
Last active August 29, 2015 14:17
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save CWSpear/7867b5cc12a764d07460 to your computer and use it in GitHub Desktop.
Save CWSpear/7867b5cc12a764d07460 to your computer and use it in GitHub Desktop.
Handling filtering, sorting and pagination without any controller logic
<!-- "items" is the items in the full list, "displayed" is the items it'll show after filtering, sorting and paging -->
<awesome-list items="users" displayed="displayedUsers">
<div class="pre-table-header">
<!-- input for search placed here -->
<awesome-search></awesome-search>
<button class="btn btn-primary" ui-sref=".user({ id: 'new' })">Add User</button>
</div>
<table class="table">
<thead>
<tr>
<th awesome-sort="name">Name</th>
<th awesome-sort="email">Email</th>
<!-- awesomeSort can handle complex sort keys -->
<th awesome-sort="roles[0].name">User Role</th>
</tr>
</thead>
<tbody>
<!-- note we're iterating the value of "displayed" attr from awesome-list -->
<tr class="selectable-row" ui-sref=".user({ id: user.id })" ng-repeat="user in displayedUsers">
<td>{{ user.name }}</td>
<td>{{ user.email }}</td>
<td>{{ user.roles[0].name }}</td>
</tr>
</tbody>
</table>
<!-- single tag pagination! (opts forthcoming) -->
<awesome-pagination></awesome-pagination>
</awesome-list>
(function () {
'use strict';
angular
.module('awesomeList', [])
.directive('awesomeList', awesomeList);
function awesomeList($filter, $parse) {
return {
scope: { items: '=', displayed: '=' },
// that word you use... I do not think it means what you think it means
transclude: true,
replace: true,
template: '<div class="awesome-list" ng-transclude></div>',
controller: controllerFn,
// we don't actually use this in the template, but it's needed for bindToController
controllerAs: 'awl',
// this makes it easier to work with the controller in other directives
bindToController: true
};
function controllerFn($scope, $attrs) {
this.page = 0;
this.perPage = -1;
this.resetSortClasses = resetSortClasses;
$scope.$watch(() => {
// not sure if there's a better way to do this than to just listen to everything!
return [this.items, this.search, this.sort, this.reverse, this.page, this.perPage];
}, ([items, search, sort, reverse, page, perPage]) => {
var filtered = $filter('filter')(items, search) || [];
this.filtered = $filter('orderBy')(filtered, sort, reverse);
var start = page * perPage;
var end = start + perPage;
this.displayed = this.filtered.slice(start, end);
}, true);
function resetSortClasses() {
// this ensures we're only resetting the classes of *this* directive's children.
// that way, we can have multiple awesomeLists on one page
$scope.$broadcast('awesomeSort.resetClass');
}
}
}
})();
(function () {
'use strict';
angular
.module('awesomeList')
.directive('awesomePagination', awesomePagination);
function awesomePagination() {
return {
require: '^awesomeList',
scope: {},
template: `
<div class="awesome-pagination">
<span ng-click="jump(curPage - 1)">&laquo;</span>
<span ng-click="jump(page)" ng-repeat="page in pages" ng-class="{ selected: curPage == page }">{{ page + 1 }}</span>
<span ng-click="jump(curPage + 1)">&raquo;</span>
</div>
`,
link: linkFn
};
function linkFn(scope, elem, attrs, ctrl) {
scope.curPage = ctrl.page = 0;
ctrl.perPage = 10;
scope.jump = setPage;
scope.$watch(() => ctrl.filtered.length, len => {
scope.pageCount = Math.ceil(len / ctrl.perPage);
scope.pages = range(0, scope.pageCount);
setPage(ctrl.page);
});
function setPage(page) {
// ensure current page is within bounds, 0 >= page < pageCount
if (page < 0) page = 0;
else if (page >= scope.pageCount) page = scope.pageCount - 1;
scope.curPage = ctrl.page = page;
}
function range(start, end) {
var ret = [];
for (let i = start; i < end; i++) ret.push(i);
return ret;
}
}
}
})();
(function () {
'use strict';
angular
.module('awesomeList')
.directive('awesomeSearch', awesomeSearch);
function awesomeSearch() {
return {
require: '^awesomeList',
scope: {},
replace: true,
template: '<input type="search" class="awesome-search" ng-model="search" ng-change="update(search)">',
link: linkFn
};
function linkFn(scope, elem, attrs, ctrl) {
// using an ng-change instead of a $watch for performance
scope.update = function (search) {
ctrl.search = search;
}
}
}
})();
(function () {
'use strict';
angular
.module('awesomeList')
.directive('awesomeSort', awesomeSort);
function awesomeSort() {
const SORTED_CLASS = 'awesome-sorted';
const SORTED_CLASS_REVERSE = 'awesome-sorted-reverse';
const SORTABLE_CLASS = 'awesome-sortable';
return {
require: '^awesomeList',
scope: {},
controller: controllerFn,
link: linkFn
};
function controllerFn($scope, $element) {
// we listen to the parent to broadcast so we're only
// resetting the awesomeSort classes in this awesomeList
$scope.$on('awesomeSort.resetClass', function () {
$element.removeClass(`${SORTED_CLASS} ${SORTED_CLASS_REVERSE}`);
});
}
function linkFn(scope, elem, attrs, listCtrl) {
elem.addClass(SORTABLE_CLASS);
elem.bind('click', function () {
scope.$apply(function () {
if (listCtrl.sort == attrs.awesomeSort) {
listCtrl.reverse = !listCtrl.reverse;
elem.toggleClass(SORTED_CLASS_REVERSE, listCtrl.reverse);
} else {
listCtrl.reverse = false;
listCtrl.sort = attrs.awesomeSort;
// this triggers broadcast('awesomeSort.resetClass')
listCtrl.resetSortClasses();
elem.addClass(SORTED_CLASS);
}
});
});
}
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment