AngularJS Workshop - Admin Panel Development (Part 2)

    In the first part , the basic functionality of the admin panel on the AngularJS javascript framework was implemented - loading data from the backend, adding / changing entries. In the second part, we will consider the implementation of sorting tables and paging, deleting records.


    Sorting


    We supplement the tablehead array in the ListCtrl controller in the controllers.js file to set the default sort order. Numbers greater than 0 - sort ascending, less than 0 - descending. The number module shows the sort order by column.
    ...
      $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 header, which means that we will attach the functionality to it. On AngularJS, this is very simple. Replace the table title in the list.html template:
    ...
    
      
        {{head.title}}
      
    
    
      
    ...
    

    And add the 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 sorting with the filter orderByEx, and returns the column names in the desired order, a minus sign indicates reverse sorting. The sortReorder () function reorders the sorting, with the Shift key held down, you can add a new column, repeated clicking on the selected column changes the sort order of this column.
    In this problem, I again came across the fact that the built-in orderBy filter takes the source data of the columns before sorting to sort, and sorts the Category and Response columns incorrectly. I tore out the orderBy filter code from AngularJS code and made changes to it. The changes are insignificant (4 lines added, calling the comparator with the necessary data), so I won’t give it here (you can see the filter code on github).

    Pagination


    An important function is to break 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 when any expression specified in it is changed ( more about the $ watch function ). It was interesting to experiment with the this variable inside the function ...
    Please note that the paginator.setPages () method is called twice - in the Items download callback and in the $ watch ('items') function. The fact is that $ scope.items = Items.query () returns a promise-object, the assignment of which $ watch is triggered, but then when loading data into it, it no longer exists, since there are internal changes to the promise-object.
    Add a few lines to the list.html template. We supplement the table series iterator:
    ...
    
    ...
    

    And add page control buttons after the table:
    ...
    
    Показано {{Math.min(paginator.count,items.length)}} записей из {{items.length}}
    из {{paginator.pages}} стр.
    ...

    Button handlers contain conditional expressions that limit the page 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 quite obvious; I don’t see the point of explaining it.

    Highlight and delete lines


    Left just a little bit. We’ll write code to select and delete lines. In the list.html template (for the last time), change the line iterator by adding a click handler and adding a class to visually highlight the entry:
    ...
    
    ...
    

    Add a couple of buttons in front of the table (in the div.tools section):
    ...
    
    ...
    

    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 element 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 elements from the backend; AngularJS itself does not add it. The deleteItem () function deletes, respectively, the elements listed in the $ scope.selected array. The built-in delete object uses the delete () method. (It is called using the expression Items ['delete'] (), and not Items.delete (), because my IDE believes that delete is a built-in Javascript operator, and ugly shows an error ... But everyone knows that in case of objects Items.delete === Items ['delete'])

    Other


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

    Result


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

    Also popular now: