📜 ⬆️ ⬇️

Practice AngularJS - development of the administrative panel (part 2)

In the first part , the basic admin functionality was implemented on the AngularJS javascript framework - loading data from the backend, adding / changing records. In the second part, we will look at the implementation of sorting the table and page breaks, deleting records.


Sorting


Add the tablehead array in the ListCtrl controller in the controllers.js file to set the default sort order. Numbers greater than 0 — sort in ascending order; less than 0 — in descending order. The number modulus shows the sort order by columns.
... $scope.tablehead = [ {name:'title', title:"", sort:-2}, {name:'category', title:"", list:$scope.categories, sort:1}, {name:'answerer', title:" ", list:$scope.answerers}, {name:'author', title:""}, {name:'created', title:""}, {name:'answered', title:""}, {name:'shown', title:""} ]; ... 

Sorting should work when you click on the title, which means that we will attach functionality to it. On AngularJS it is very simple. Replace the table header in the list.html template:
 ... <thead> <tr ng-mousedown="$event.preventDefault()" onselectstart="return false"> <th ng-repeat="head in tablehead" ng-click="sortReorder(head.name,$event)" ng-class="{'sort-asc':head.sort>0,'sort-desc':head.sort<0}">{{head.title}}</th> </tr> </thead> <tbody> <tr ng-repeat="item in items | filterEx:tablehead:filter | orderByEx:tablehead:sortBy()"> ... 

And add sorting functions to the controller:
 ... $scope.sortBy = function() { var order = []; angular.forEach($scope.tablehead, function(h){ if (h.sort>0) order[h.sort-1] = h.name; if (h.sort<0) order[Math.abs(h.sort)-1] = '-'+h.name; }); return order; }; $scope.sortReorder = function(col,e) { if (e.shiftKey) { var sortIndex = 0; angular.forEach($scope.tablehead, function(el) { if (Math.abs(el.sort)>sortIndex) sortIndex = Math.abs(el.sort); }); angular.forEach($scope.tablehead, function(el) { if (el.name==col) el.sort = el.sort?-el.sort:sortIndex+1; }); } else { angular.forEach($scope.tablehead, function(el) { if (el.name==col) el.sort = el.sort>0?-1:1; else el.sort = null; }); } }; ... 

The sortBy () function is called directly when orderByEx is sorting the filter, and returns the column names in the correct order, the minus sign indicates reverse sorting. The sortReorder () function reorders the sorting, with the Shift key held down, you can add a new column, and pressing the selected column again changes the sort order of this column.
In this task, I again encountered the fact that the built-in orderBy filter takes the source data of the columns before the substitution for sorting, and sorts the Category and Responder columns incorrectly. I ripped the orderBy filter code from AngularJS code and made changes to it. The changes are minor (4 lines were added, causing a comparator with the necessary data), so I will not give it here (you can see the filter code on GitHub).

Pagination


An important function is breaking a large table into pages. To do this, add the ListCtrl controller (file /js/controllers.js):
 ... $scope.paginator = { count: 5, // -    page: 1, pages: 1, setPages: function(itemsCount){ this.pages = Math.ceil(itemsCount/this.count); } }; $scope.items = Items.query(function(data){ $scope.paginator.setPages($scope.items.length); //    var i = 0; angular.forEach(data, function(v,k) { data[k]._id = i++; }); }); $scope.$watch('items',function() { $scope.paginator.setPages($scope.items.length); }); $scope.$watch('paginator.page',function() { if ($scope.paginator.page<1) $scope.paginator.page = 1; if ($scope.paginator.page>$scope.paginator.pages) $scope.paginator.page = $scope.paginator.pages; angular.forEach($scope.items, function(v,k) { $scope.items[k].selected = false; }); }); ... 

In this code, the $ watch () function is interesting - it is called whenever any change of the expression specified in it ( for more information about the $ watch function ). It was interesting to experiment with the this variable inside the function ...
Note that the paginator.setPages () method is called twice - the Items callback and in the $ watch ('items') function. The fact is that $ scope.items = Items.query () returns a promise object for assignment of which $ watch is triggered, but then when loading data into it, it no longer exists, since internal changes occur in the promise object.
Add a few lines to the list.html template. Let's add iterator of rows of the table:
 ... <tr ng-repeat="item in items | filterEx:tablehead:filter | orderByEx:tablehead:sortBy() | showPage:paginator"> ... 

And add the page control buttons after the table:
 ... <div id="table-tools"> <div class="pull-left">  {{Math.min(paginator.count,items.length)}}   {{items.length}} </div> <div class="controls input-append pull-right"> <input type="button" ng-click="paginator.page=1" class="btn btn-small" value="<<"> <input type="button" ng-click="paginator.page=paginator.page-1 || 1" class="btn btn-small" value="<"> <input ng-model="paginator.page" class="paginator-page"><span class="add-on"> {{paginator.pages}} .</span> <input type="button" ng-click="paginator.page=Math.min(paginator.page+1,paginator.pages)" class="btn btn-small" value=">"> <input type="button" ng-click="paginator.page=paginator.pages" class="btn btn-small" value=">>"> </div> <div class="clear"></div> </div> ... 

Button handlers have conditional expressions that limit the page change range and reduce the number of calls to the $ watch () handler function.
Well, the showPage filter code in the filters.js file:
 ... .filter('showPage', function() { return function(list, paginator) { if (paginator.page<1) paginator.page = 1; if (paginator.count<1) paginator.count = 1; if (paginator.pages && paginator.page>paginator.pages) paginator.page = paginator.pages; return list.slice(paginator.count*(paginator.page-1), paginator.count*paginator.page); }; }); 

The code is fairly obvious; I see no point in explaining it.
')

Select and delete rows


Left just a little bit. Let's write the code allowing to select and delete lines. In the list.html template (for the last time), we change the row iterator by adding a click handler and adding a class to visually highlight the record:
 ... <tr ng-repeat="item in items | filterEx:tablehead:filter | orderByEx:tablehead:sortBy() | showPage:paginator" ng-click="selectItem($event)" ng-class="item.selected && 'selected'"> ... 

Add a couple of buttons in front of the table (in the div.tools section):
 ... <button ng-click="deleteItem(1)" class="btn btn-danger" ng-show="selected.length==1">  </button> <button ng-click="deleteItem()" class="btn btn-danger" ng-show="selected.length>1">   ({{selected.length}})</button> ... 

These buttons are shown (ng-show == true) if one or more entries are selected.

And add a couple of functions to the ListCtrl controller:
 ... $scope.selected = []; $scope.deleteItem = function(one) { if (one) { var _id = $scope.selected[0]; Items['delete']({id:$scope.items[_id].id}, function() { $scope.items.splice(_id,1); $scope.selected = []; }); } else { var ids = []; angular.forEach($scope.selected, function(_id) { ids.push($scope.items[_id].id); }); Items['delete']({ids:ids}, function(){ angular.forEach($scope.selected, function(_id) { $scope.items.splice(_id,1); }); $scope.selected = []; }); } }; $scope.selectItem = function(e) { if ((e.target||e.srcElement).tagName!='TD') return; var state = this.item.selected = !this.item.selected, _id = this.item._id; if (state) $scope.selected.push(_id); else angular.forEach($scope.selected, function(v,k) { if (v==_id) { $scope.selected.splice(k,1); return false; } }); }; ... 

The selectItem () function sets the selected property of an item and adds its number to the special $ scope.selected array. By the way, the element number is in its _id property, which we fill in when receiving items from the backend, AngularJS does not add it. The deleteItem () function deletes, respectively, the items listed in the $ scope.selected array. The delete () method built into the $ resource object is used. (It is called using the expression Items ['delete'] (), and not Items.delete (), because my IDE believes that delete is the built-in Javascript operator, and shows an error ugly ... But everybody knows that case of Items.delete objects === Items ['delete'])

Other


I will add the story with those moments that are not included in the main text.
1. In the templates in {{curly brackets}}, variables (and functions) are declared, declared as properties of the $ scope object of the constructor.
2. Global objects in the template are not available, for access to them you have to explicitly assign them to a separate property, for example, like this: $ scope.Math = Math; and then in the template use this: {{Math.min (a, b)}}.
3. It is not very clear how to access the scope of another controller. Surely you can, but I have not yet found how ...
4. It is not very clear how to access the scope of the controllers from code that is not placed in the controller, for example, from graphic libraries. But you don’t need to do that, because all the code should be contained in the controllers ...
5. The $ scope. $ Watch () function does not work when the properties of arrays are processed by splice / push functions and the like.

Result


A working demo is available here: http://lexxpavlov.com/ng-admin/v2/ (read-only)
Sources can be viewed on GitHub: https://github.com/lexxpavlov/angular-admin/

Source: https://habr.com/ru/post/150321/


All Articles