Last active
August 29, 2015 14:17
-
-
Save CWSpear/7867b5cc12a764d07460 to your computer and use it in GitHub Desktop.
Handling filtering, sorting and pagination without any controller logic
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!-- "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> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(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'); | |
} | |
} | |
} | |
})(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(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)">«</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)">»</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; | |
} | |
} | |
} | |
})(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(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; | |
} | |
} | |
} | |
})(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(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