Created
September 19, 2016 16:13
-
-
Save anonymous/6e4024a87a5ba98508811ca26af8f32b to your computer and use it in GitHub Desktop.
CRUD com AngularJS e CodeIgniter - Paginação
This file contains 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
/** | |
* dirPagination - AngularJS module for paginating (almost) anything. | |
* | |
* | |
* Credits | |
* ======= | |
* | |
* Daniel Tabuenca: https://groups.google.com/d/msg/angular/an9QpzqIYiM/r8v-3W1X5vcJ | |
* for the idea on how to dynamically invoke the ng-repeat directive. | |
* | |
* I borrowed a couple of lines and a few attribute names from the AngularUI Bootstrap project: | |
* https://github.com/angular-ui/bootstrap/blob/master/src/pagination/pagination.js | |
* | |
* Copyright 2014 Michael Bromley <michael@michaelbromley.co.uk> | |
*/ | |
(function() { | |
/** | |
* Config | |
*/ | |
var moduleName = 'angularUtils.directives.dirPagination'; | |
var DEFAULT_ID = '__default'; | |
/** | |
* Module | |
*/ | |
angular.module(moduleName, []) | |
.directive('dirPaginate', ['$compile', '$parse', 'paginationService', dirPaginateDirective]) | |
.directive('dirPaginateNoCompile', noCompileDirective) | |
.directive('dirPaginationControls', ['paginationService', 'paginationTemplate', dirPaginationControlsDirective]) | |
.filter('itemsPerPage', ['paginationService', itemsPerPageFilter]) | |
.service('paginationService', paginationService) | |
.provider('paginationTemplate', paginationTemplateProvider) | |
.run(['$templateCache',dirPaginationControlsTemplateInstaller]); | |
function dirPaginateDirective($compile, $parse, paginationService) { | |
return { | |
terminal: true, | |
multiElement: true, | |
priority: 100, | |
compile: dirPaginationCompileFn | |
}; | |
function dirPaginationCompileFn(tElement, tAttrs){ | |
var expression = tAttrs.dirPaginate; | |
// regex taken directly from https://github.com/angular/angular.js/blob/v1.4.x/src/ng/directive/ngRepeat.js#L339 | |
var match = expression.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+track\s+by\s+([\s\S]+?))?\s*$/); | |
var filterPattern = /\|\s*itemsPerPage\s*:\s*(.*\(\s*\w*\)|([^\)]*?(?=\s+as\s+))|[^\)]*)/; | |
if (match[2].match(filterPattern) === null) { | |
throw 'pagination directive: the \'itemsPerPage\' filter must be set.'; | |
} | |
var itemsPerPageFilterRemoved = match[2].replace(filterPattern, ''); | |
var collectionGetter = $parse(itemsPerPageFilterRemoved); | |
addNoCompileAttributes(tElement); | |
// If any value is specified for paginationId, we register the un-evaluated expression at this stage for the benefit of any | |
// dir-pagination-controls directives that may be looking for this ID. | |
var rawId = tAttrs.paginationId || DEFAULT_ID; | |
paginationService.registerInstance(rawId); | |
return function dirPaginationLinkFn(scope, element, attrs){ | |
// Now that we have access to the `scope` we can interpolate any expression given in the paginationId attribute and | |
// potentially register a new ID if it evaluates to a different value than the rawId. | |
var paginationId = $parse(attrs.paginationId)(scope) || attrs.paginationId || DEFAULT_ID; | |
paginationService.registerInstance(paginationId); | |
var repeatExpression = getRepeatExpression(expression, paginationId); | |
addNgRepeatToElement(element, attrs, repeatExpression); | |
removeTemporaryAttributes(element); | |
var compiled = $compile(element); | |
var currentPageGetter = makeCurrentPageGetterFn(scope, attrs, paginationId); | |
paginationService.setCurrentPageParser(paginationId, currentPageGetter, scope); | |
if (typeof attrs.totalItems !== 'undefined') { | |
paginationService.setAsyncModeTrue(paginationId); | |
scope.$watch(function() { | |
return $parse(attrs.totalItems)(scope); | |
}, function (result) { | |
if (0 <= result) { | |
paginationService.setCollectionLength(paginationId, result); | |
} | |
}); | |
} else { | |
scope.$watchCollection(function() { | |
return collectionGetter(scope); | |
}, function(collection) { | |
if (collection) { | |
var collectionLength = (collection instanceof Array) ? collection.length : Object.keys(collection).length; | |
paginationService.setCollectionLength(paginationId, collectionLength); | |
} | |
}); | |
} | |
// Delegate to the link function returned by the new compilation of the ng-repeat | |
compiled(scope); | |
}; | |
} | |
/** | |
* If a pagination id has been specified, we need to check that it is present as the second argument passed to | |
* the itemsPerPage filter. If it is not there, we add it and return the modified expression. | |
* | |
* @param expression | |
* @param paginationId | |
* @returns {*} | |
*/ | |
function getRepeatExpression(expression, paginationId) { | |
var repeatExpression, | |
idDefinedInFilter = !!expression.match(/(\|\s*itemsPerPage\s*:[^|]*:[^|]*)/); | |
if (paginationId !== DEFAULT_ID && !idDefinedInFilter) { | |
repeatExpression = expression.replace(/(\|\s*itemsPerPage\s*:\s*[^|\s]*)/, "$1 : '" + paginationId + "'"); | |
} else { | |
repeatExpression = expression; | |
} | |
return repeatExpression; | |
} | |
/** | |
* Adds the ng-repeat directive to the element. In the case of multi-element (-start, -end) it adds the | |
* appropriate multi-element ng-repeat to the first and last element in the range. | |
* @param element | |
* @param attrs | |
* @param repeatExpression | |
*/ | |
function addNgRepeatToElement(element, attrs, repeatExpression) { | |
if (element[0].hasAttribute('dir-paginate-start') || element[0].hasAttribute('data-dir-paginate-start')) { | |
// using multiElement mode (dir-paginate-start, dir-paginate-end) | |
attrs.$set('ngRepeatStart', repeatExpression); | |
element.eq(element.length - 1).attr('ng-repeat-end', true); | |
} else { | |
attrs.$set('ngRepeat', repeatExpression); | |
} | |
} | |
/** | |
* Adds the dir-paginate-no-compile directive to each element in the tElement range. | |
* @param tElement | |
*/ | |
function addNoCompileAttributes(tElement) { | |
angular.forEach(tElement, function(el) { | |
if (el.nodeType === 1) { | |
angular.element(el).attr('dir-paginate-no-compile', true); | |
} | |
}); | |
} | |
/** | |
* Removes the variations on dir-paginate (data-, -start, -end) and the dir-paginate-no-compile directives. | |
* @param element | |
*/ | |
function removeTemporaryAttributes(element) { | |
angular.forEach(element, function(el) { | |
if (el.nodeType === 1) { | |
angular.element(el).removeAttr('dir-paginate-no-compile'); | |
} | |
}); | |
element.eq(0).removeAttr('dir-paginate-start').removeAttr('dir-paginate').removeAttr('data-dir-paginate-start').removeAttr('data-dir-paginate'); | |
element.eq(element.length - 1).removeAttr('dir-paginate-end').removeAttr('data-dir-paginate-end'); | |
} | |
/** | |
* Creates a getter function for the current-page attribute, using the expression provided or a default value if | |
* no current-page expression was specified. | |
* | |
* @param scope | |
* @param attrs | |
* @param paginationId | |
* @returns {*} | |
*/ | |
function makeCurrentPageGetterFn(scope, attrs, paginationId) { | |
var currentPageGetter; | |
if (attrs.currentPage) { | |
currentPageGetter = $parse(attrs.currentPage); | |
} else { | |
// If the current-page attribute was not set, we'll make our own. | |
// Replace any non-alphanumeric characters which might confuse | |
// the $parse service and give unexpected results. | |
// See https://github.com/michaelbromley/angularUtils/issues/233 | |
var defaultCurrentPage = (paginationId + '__currentPage').replace(/\W/g, '_'); | |
scope[defaultCurrentPage] = 1; | |
currentPageGetter = $parse(defaultCurrentPage); | |
} | |
return currentPageGetter; | |
} | |
} | |
/** | |
* This is a helper directive that allows correct compilation when in multi-element mode (ie dir-paginate-start, dir-paginate-end). | |
* It is dynamically added to all elements in the dir-paginate compile function, and it prevents further compilation of | |
* any inner directives. It is then removed in the link function, and all inner directives are then manually compiled. | |
*/ | |
function noCompileDirective() { | |
return { | |
priority: 5000, | |
terminal: true | |
}; | |
} | |
function dirPaginationControlsTemplateInstaller($templateCache) { | |
$templateCache.put('angularUtils.directives.dirPagination.template', '<ul class="pagination" ng-if="1 < pages.length || !autoHide"><li ng-if="boundaryLinks" ng-class="{ disabled : pagination.current == 1 }"><a href="" ng-click="setCurrent(1)">«</a></li><li ng-if="directionLinks" ng-class="{ disabled : pagination.current == 1 }"><a href="" ng-click="setCurrent(pagination.current - 1)">‹</a></li><li ng-repeat="pageNumber in pages track by tracker(pageNumber, $index)" ng-class="{ active : pagination.current == pageNumber, disabled : pageNumber == \'...\' || ( ! autoHide && pages.length === 1 ) }"><a href="" ng-click="setCurrent(pageNumber)">{{ pageNumber }}</a></li><li ng-if="directionLinks" ng-class="{ disabled : pagination.current == pagination.last }"><a href="" ng-click="setCurrent(pagination.current + 1)">›</a></li><li ng-if="boundaryLinks" ng-class="{ disabled : pagination.current == pagination.last }"><a href="" ng-click="setCurrent(pagination.last)">»</a></li></ul>'); | |
} | |
function dirPaginationControlsDirective(paginationService, paginationTemplate) { | |
var numberRegex = /^\d+$/; | |
return { | |
restrict: 'AE', | |
templateUrl: function(elem, attrs) { | |
return attrs.templateUrl || paginationTemplate.getPath(); | |
}, | |
scope: { | |
maxSize: '=?', | |
onPageChange: '&?', | |
paginationId: '=?', | |
autoHide: '=?' | |
}, | |
link: dirPaginationControlsLinkFn | |
}; | |
function dirPaginationControlsLinkFn(scope, element, attrs) { | |
// rawId is the un-interpolated value of the pagination-id attribute. This is only important when the corresponding dir-paginate directive has | |
// not yet been linked (e.g. if it is inside an ng-if block), and in that case it prevents this controls directive from assuming that there is | |
// no corresponding dir-paginate directive and wrongly throwing an exception. | |
var rawId = attrs.paginationId || DEFAULT_ID; | |
var paginationId = scope.paginationId || attrs.paginationId || DEFAULT_ID; | |
if (!paginationService.isRegistered(paginationId) && !paginationService.isRegistered(rawId)) { | |
var idMessage = (paginationId !== DEFAULT_ID) ? ' (id: ' + paginationId + ') ' : ' '; | |
if (window.console) { | |
console.warn('Pagination directive: the pagination controls' + idMessage + 'cannot be used without the corresponding pagination directive, which was not found at link time.'); | |
} | |
} | |
if (!scope.maxSize) { scope.maxSize = 9; } | |
scope.autoHide = scope.autoHide === undefined ? true : scope.autoHide; | |
scope.directionLinks = angular.isDefined(attrs.directionLinks) ? scope.$parent.$eval(attrs.directionLinks) : true; | |
scope.boundaryLinks = angular.isDefined(attrs.boundaryLinks) ? scope.$parent.$eval(attrs.boundaryLinks) : false; | |
var paginationRange = Math.max(scope.maxSize, 5); | |
scope.pages = []; | |
scope.pagination = { | |
last: 1, | |
current: 1 | |
}; | |
scope.range = { | |
lower: 1, | |
upper: 1, | |
total: 1 | |
}; | |
scope.$watch('maxSize', function(val) { | |
if (val) { | |
paginationRange = Math.max(scope.maxSize, 5); | |
generatePagination(); | |
} | |
}); | |
scope.$watch(function() { | |
if (paginationService.isRegistered(paginationId)) { | |
return (paginationService.getCollectionLength(paginationId) + 1) * paginationService.getItemsPerPage(paginationId); | |
} | |
}, function(length) { | |
if (0 < length) { | |
generatePagination(); | |
} | |
}); | |
scope.$watch(function() { | |
if (paginationService.isRegistered(paginationId)) { | |
return (paginationService.getItemsPerPage(paginationId)); | |
} | |
}, function(current, previous) { | |
if (current != previous && typeof previous !== 'undefined') { | |
goToPage(scope.pagination.current); | |
} | |
}); | |
scope.$watch(function() { | |
if (paginationService.isRegistered(paginationId)) { | |
return paginationService.getCurrentPage(paginationId); | |
} | |
}, function(currentPage, previousPage) { | |
if (currentPage != previousPage) { | |
goToPage(currentPage); | |
} | |
}); | |
scope.setCurrent = function(num) { | |
if (paginationService.isRegistered(paginationId) && isValidPageNumber(num)) { | |
num = parseInt(num, 10); | |
paginationService.setCurrentPage(paginationId, num); | |
} | |
}; | |
/** | |
* Custom "track by" function which allows for duplicate "..." entries on long lists, | |
* yet fixes the problem of wrongly-highlighted links which happens when using | |
* "track by $index" - see https://github.com/michaelbromley/angularUtils/issues/153 | |
* @param id | |
* @param index | |
* @returns {string} | |
*/ | |
scope.tracker = function(id, index) { | |
return id + '_' + index; | |
}; | |
function goToPage(num) { | |
if (paginationService.isRegistered(paginationId) && isValidPageNumber(num)) { | |
var oldPageNumber = scope.pagination.current; | |
scope.pages = generatePagesArray(num, paginationService.getCollectionLength(paginationId), paginationService.getItemsPerPage(paginationId), paginationRange); | |
scope.pagination.current = num; | |
updateRangeValues(); | |
// if a callback has been set, then call it with the page number as the first argument | |
// and the previous page number as a second argument | |
if (scope.onPageChange) { | |
scope.onPageChange({ | |
newPageNumber : num, | |
oldPageNumber : oldPageNumber | |
}); | |
} | |
} | |
} | |
function generatePagination() { | |
if (paginationService.isRegistered(paginationId)) { | |
var page = parseInt(paginationService.getCurrentPage(paginationId)) || 1; | |
scope.pages = generatePagesArray(page, paginationService.getCollectionLength(paginationId), paginationService.getItemsPerPage(paginationId), paginationRange); | |
scope.pagination.current = page; | |
scope.pagination.last = scope.pages[scope.pages.length - 1]; | |
if (scope.pagination.last < scope.pagination.current) { | |
scope.setCurrent(scope.pagination.last); | |
} else { | |
updateRangeValues(); | |
} | |
} | |
} | |
/** | |
* This function updates the values (lower, upper, total) of the `scope.range` object, which can be used in the pagination | |
* template to display the current page range, e.g. "showing 21 - 40 of 144 results"; | |
*/ | |
function updateRangeValues() { | |
if (paginationService.isRegistered(paginationId)) { | |
var currentPage = paginationService.getCurrentPage(paginationId), | |
itemsPerPage = paginationService.getItemsPerPage(paginationId), | |
totalItems = paginationService.getCollectionLength(paginationId); | |
scope.range.lower = (currentPage - 1) * itemsPerPage + 1; | |
scope.range.upper = Math.min(currentPage * itemsPerPage, totalItems); | |
scope.range.total = totalItems; | |
} | |
} | |
function isValidPageNumber(num) { | |
return (numberRegex.test(num) && (0 < num && num <= scope.pagination.last)); | |
} | |
} | |
/** | |
* Generate an array of page numbers (or the '...' string) which is used in an ng-repeat to generate the | |
* links used in pagination | |
* | |
* @param currentPage | |
* @param rowsPerPage | |
* @param paginationRange | |
* @param collectionLength | |
* @returns {Array} | |
*/ | |
function generatePagesArray(currentPage, collectionLength, rowsPerPage, paginationRange) { | |
var pages = []; | |
var totalPages = Math.ceil(collectionLength / rowsPerPage); | |
var halfWay = Math.ceil(paginationRange / 2); | |
var position; | |
if (currentPage <= halfWay) { | |
position = 'start'; | |
} else if (totalPages - halfWay < currentPage) { | |
position = 'end'; | |
} else { | |
position = 'middle'; | |
} | |
var ellipsesNeeded = paginationRange < totalPages; | |
var i = 1; | |
while (i <= totalPages && i <= paginationRange) { | |
var pageNumber = calculatePageNumber(i, currentPage, paginationRange, totalPages); | |
var openingEllipsesNeeded = (i === 2 && (position === 'middle' || position === 'end')); | |
var closingEllipsesNeeded = (i === paginationRange - 1 && (position === 'middle' || position === 'start')); | |
if (ellipsesNeeded && (openingEllipsesNeeded || closingEllipsesNeeded)) { | |
pages.push('...'); | |
} else { | |
pages.push(pageNumber); | |
} | |
i ++; | |
} | |
return pages; | |
} | |
/** | |
* Given the position in the sequence of pagination links [i], figure out what page number corresponds to that position. | |
* | |
* @param i | |
* @param currentPage | |
* @param paginationRange | |
* @param totalPages | |
* @returns {*} | |
*/ | |
function calculatePageNumber(i, currentPage, paginationRange, totalPages) { | |
var halfWay = Math.ceil(paginationRange/2); | |
if (i === paginationRange) { | |
return totalPages; | |
} else if (i === 1) { | |
return i; | |
} else if (paginationRange < totalPages) { | |
if (totalPages - halfWay < currentPage) { | |
return totalPages - paginationRange + i; | |
} else if (halfWay < currentPage) { | |
return currentPage - halfWay + i; | |
} else { | |
return i; | |
} | |
} else { | |
return i; | |
} | |
} | |
} | |
/** | |
* This filter slices the collection into pages based on the current page number and number of items per page. | |
* @param paginationService | |
* @returns {Function} | |
*/ | |
function itemsPerPageFilter(paginationService) { | |
return function(collection, itemsPerPage, paginationId) { | |
if (typeof (paginationId) === 'undefined') { | |
paginationId = DEFAULT_ID; | |
} | |
if (!paginationService.isRegistered(paginationId)) { | |
throw 'pagination directive: the itemsPerPage id argument (id: ' + paginationId + ') does not match a registered pagination-id.'; | |
} | |
var end; | |
var start; | |
if (angular.isObject(collection)) { | |
itemsPerPage = parseInt(itemsPerPage) || 9999999999; | |
if (paginationService.isAsyncMode(paginationId)) { | |
start = 0; | |
} else { | |
start = (paginationService.getCurrentPage(paginationId) - 1) * itemsPerPage; | |
} | |
end = start + itemsPerPage; | |
paginationService.setItemsPerPage(paginationId, itemsPerPage); | |
if (collection instanceof Array) { | |
// the array just needs to be sliced | |
return collection.slice(start, end); | |
} else { | |
// in the case of an object, we need to get an array of keys, slice that, then map back to | |
// the original object. | |
var slicedObject = {}; | |
angular.forEach(keys(collection).slice(start, end), function(key) { | |
slicedObject[key] = collection[key]; | |
}); | |
return slicedObject; | |
} | |
} else { | |
return collection; | |
} | |
}; | |
} | |
/** | |
* Shim for the Object.keys() method which does not exist in IE < 9 | |
* @param obj | |
* @returns {Array} | |
*/ | |
function keys(obj) { | |
if (!Object.keys) { | |
var objKeys = []; | |
for (var i in obj) { | |
if (obj.hasOwnProperty(i)) { | |
objKeys.push(i); | |
} | |
} | |
return objKeys; | |
} else { | |
return Object.keys(obj); | |
} | |
} | |
/** | |
* This service allows the various parts of the module to communicate and stay in sync. | |
*/ | |
function paginationService() { | |
var instances = {}; | |
var lastRegisteredInstance; | |
this.registerInstance = function(instanceId) { | |
if (typeof instances[instanceId] === 'undefined') { | |
instances[instanceId] = { | |
asyncMode: false | |
}; | |
lastRegisteredInstance = instanceId; | |
} | |
}; | |
this.isRegistered = function(instanceId) { | |
return (typeof instances[instanceId] !== 'undefined'); | |
}; | |
this.getLastInstanceId = function() { | |
return lastRegisteredInstance; | |
}; | |
this.setCurrentPageParser = function(instanceId, val, scope) { | |
instances[instanceId].currentPageParser = val; | |
instances[instanceId].context = scope; | |
}; | |
this.setCurrentPage = function(instanceId, val) { | |
instances[instanceId].currentPageParser.assign(instances[instanceId].context, val); | |
}; | |
this.getCurrentPage = function(instanceId) { | |
var parser = instances[instanceId].currentPageParser; | |
return parser ? parser(instances[instanceId].context) : 1; | |
}; | |
this.setItemsPerPage = function(instanceId, val) { | |
instances[instanceId].itemsPerPage = val; | |
}; | |
this.getItemsPerPage = function(instanceId) { | |
return instances[instanceId].itemsPerPage; | |
}; | |
this.setCollectionLength = function(instanceId, val) { | |
instances[instanceId].collectionLength = val; | |
}; | |
this.getCollectionLength = function(instanceId) { | |
return instances[instanceId].collectionLength; | |
}; | |
this.setAsyncModeTrue = function(instanceId) { | |
instances[instanceId].asyncMode = true; | |
}; | |
this.isAsyncMode = function(instanceId) { | |
return instances[instanceId].asyncMode; | |
}; | |
} | |
/** | |
* This provider allows global configuration of the template path used by the dir-pagination-controls directive. | |
*/ | |
function paginationTemplateProvider() { | |
var templatePath = 'angularUtils.directives.dirPagination.template'; | |
this.setPath = function(path) { | |
templatePath = path; | |
}; | |
this.$get = function() { | |
return { | |
getPath: function() { | |
return templatePath; | |
} | |
}; | |
}; | |
} | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment