fredyounan
4/17/2016 - 2:18 PM

Paginator module for AngularJS, in combination with Laravel's paginator class

Paginator module for AngularJS, in combination with Laravel's paginator class

/**
 * Paginator
 * 
 * The paginator can be used within any controller to easily set up
 * some pagination options, including number of pages, page navigation
 * and more. It is built to work with Laravel's own pagination library,
 * which returns data in a particular format.
 *
 * Usage:
 * Before you can use paginator, make sure you specify the URLs to your pagination 
 * templates, like so. In this example, I will assume that App is your angular application instance:
 * 
 * App.value('paginator.config', {
 *     templates: {
 *	       info: '/partials/pagination/info.html',
 *         pagination: '/partials/pagination/pagination.html'
 *     }
 * });
 *
 * Example:
 * Inside your controller, add the following.  The first argument is your scope variable.
 * Second argument is the name of the service/module you'll be using to fetch the data. Third
 * is the name of the $scope.params.property that you want to store the data on, and 4th is the callback
 * method, if needed, for doing data transformations.
 *
 *  Paginate($scope, Category, 'categories');
 *
 * @param object $scope
 * @param object Service - The Service to use. This could be the actual service (Such as User, in which
 *                         case Paginator will assume the method "get"), or a custom method for the service,
 *                         such as User.retrieve.
 * @param string records - Name of the records variable to set the data to on $scope
 * @param function callback - For when the paginator makes queries,
 *		can allow the developer to customize the data's format
 * @author Kirk Bushell
 * @date 9th January 2013
 */
var module = angular.module('core.paginator', []);

module.factory('Paginate', [function() {
	return function($scope, Service, records, callback) {
		if (!$scope.params) $scope.params = {};
		
		// Defaults
		$scope.currentPage = 0;
		$scope.params.per_page = 10;
		$scope.params.order = null;

		var fieldOrder = 'asc';
		
		function setPagination(data) {
			$scope[records] = data;
			$scope.params.per_page = $scope[records].per_page;
			$scope.total = $scope[records].total;
			$scope.totalPages = $scope[records].last;
			$scope.pages = [];
			
			for(var i=1; i<=$scope.totalPages;i++) {
				$scope.pages.push(i); 
			}
			
			$scope.nextPage = function() {
				if($scope.currentPage < $scope.totalPages) {
					$scope.currentPage++;
				}
			}
			
			$scope.prevPage = function() {
				if($scope.currentPage > 1) {
					$scope.currentPage--;
				}
			}
			
			$scope.firstPage = function() {
				$scope.currentPage = 1;
			}
			
			$scope.lastPage = function() {
				$scope.currentPage = $scope.totalPages;
			}
			
			$scope.setPage = function(page) {
				$scope.currentPage = page;
				$scope.params.page = page;
			}
			
			if (callback) callback($scope);
			
			// Trigger an event that the pagination has been updated.
			$scope.$broadcast('pagination.updated');
		}
		
		/**
		 * Trigger to update the pagination and the table data.
		 * 
		 * The only optional param is which page to update to. If nothing
		 * is passed, it will return to page 1.
		 */
		$scope.$on('pagination.update', function( e , newPage ) {
			if ( !newPage ) newPage = 1;
			
			$scope.params.page = newPage;

			for ( var i in $scope.params ) {
				if ( $scope.params[i] == null || $scope.params[i] == 'null' ) {
					delete $scope.params[i];
				}
			}
			
			var caller = Service.get;
			
			caller($scope.params, function(data) {
				setPagination(data);
			});
		})
		
		/**
		 * Trigger to reload data based on filters.
		 * 
		 * If we're on the first page, we trigger the update function, but if
		 * we're on any other page, all we need to do is update the variable
		 * and that will automatically trigger the update.
		 */
		$scope.filter = function() {
			if ( $scope.currentPage == 1 ) {
				$scope.$broadcast('pagination.update', [ 1 ]);
			}
			else {
				$scope.currentPage = 1;
			}
		};

		/**
		 * Sets the ordering for the data to be retrieved from the server
		 */
		$scope.order = function(field) {
			if ($scope.params.order == field) {
				fieldOrder = fieldOrder == 'asc' ? 'desc' : 'asc';
			}

			$scope.params.direction = fieldOrder;
			$scope.params.order = field;

			// Trigger an update for the pagination.
			$scope.$broadcast('pagination.update', [ 1 ]);
		};
		
		// Watch the currentPage variable and update the pagination whenever it changes.
		$scope.$watch('currentPage', function( newPage , oldPage ) {
			$scope.$broadcast('pagination.update', [ newPage ]);
		});
		
		// Execute first call
		$scope.currentPage = 1;
	}
}]);

/**
 * Used for rendering the actual pagination.
 */
module.directive('pagination', ['paginator.config', function(config) {
	return {
		templateUrl: viewPath(config.templates.pagination)
	};
}]);

/**
 * Used for rendering the actual pagination.
 */
module.directive('paginationInfo', ['paginator.config', function(config) {
	return {
		templateUrl: viewPath(config.templates.info),
		link: function(scope, iElement, iAttrs) {
			scope.min = 0;
			scope.max = 0;
			
			// Listen to the event 'pagination.updated' and update the pagination
			// info directive.
			scope.$on('pagination.updated', function( e ) {
				// Calculate the min/max by comparing the params.page variable, instead of the
				// currentPage. The params.page variable is ALWAYS updated whereas currentPage will
				// not be updated in cases where all items are deleted from page 2.
				scope.min = scope.params.page * scope.params.per_page - scope.params.per_page + 1;
				scope.max = scope.params.page * scope.params.per_page;
				
				// Obviously if max is greater than total, we have very few records
				// or we're at the end of the pagination.
				if (scope.max > scope.total) scope.max = scope.total;
			});
		}
	};
}]);

/**
 * Used for setting sort options for a field
 */
module.directive('paginateSort', ['paginator.config', function(config) {
	return {
		link: function(scope, element, attrs) {
			var field = attrs.paginateSort;
			
			// Add the class 'sortable' to give a default styling.
			element.addClass('sortable');
			
			element.on('click', function() {
				scope.order(field);
				
				element.removeClass('sort-asc sort-desc').addClass( 'sort-' + scope.params.direction );
			});
			
			scope.$watch('params.order', function(newValue, oldValue) {
				if (newValue && newValue != field)
					element.removeClass('sort-asc sort-desc');
			});
		}
	};
}]);