Search code examples
javascripthtmlangularjsangularjs-ng-repeat

Maintain Insertion Order in a Sorted Table


I have a table that allow me to insert a new row above or below another row. The table can be sorted by column.

The problem is that new rows with empty fields that are added into my sorted table will always appear on the top of the table.

This is the HTML:

<table>
    <th ng-repeat= "header in headers">
        <a href="#" ng-click="orderByField='name'; reverseSort = !reverseSort"> Name </a>
    </th>
    <tr ng-repeat "result in results | orderBy:orderByField:reverseSort">
        <td> 
          <span>{{result}}</span>
          <md-icon ng-click="addRow(result, true)"></md-icon>
          <md-icon ng-click="addRow(result, false)"></md-icon>
        </td>
    </tr>
</table>

This is my controller:

function addRow(selectedRow, isAbove) {
  var selectedPos = $scope.results.indexOf(selectedRow);
  var newRow = "";

  if (isAbove) {
    $scope.results.splice(selectedPos, 0, newRow);
  } else {
    $scope.results.splice(selectedPos + 1, 0, newRow);
  }
}

So if my table looks like this, after sorting:

[Before sort]       [After sort]

Name                Name
----                ----
D                   A
----                ----
C                   B
----                ----
B                   C
----                ----
A                   D
----                ----

and I insert a new row above and below D, my table now looks like this:

Name 
-----
""
-----
""
-----
A
-----
B
-----
C
-----
D
-----

Expected result:

Name
----
A
----
B
----
C
----
""
----
D
----
""
----

Why is it that the new rows don't appear directly above or below the row that I've chosen to add from?


Solution

  • Here's a pretty basic directive that does the minimum you mentioned in comments. It only orders the data once when it's first loaded, and isn't very reactive, but it lets you toggle and should be pretty easy to add watches, etc. for more features.

    var app = angular.module('myApp',[]);
    app.controller('ctrlMain', function($scope, filterFilter){
      $scope.reverse = false;  // <-- `reverse` boolean
      // Data
      $scope.results = [
        { name: "b" },
        { name: "g" },
        { name: "x" },
        { name: "a" },
        { name: "p" },
      ];
    });
    app.directive('orderer', function($filter) {
      return {
        scope: {
          data: '=orderer',
          reverse: '='
        },
        link: function(scope,element,attrs){
          scope.ordered = $filter('orderBy')(scope.data, 'name', scope.reverse);
          scope.formatted = [];
          
          scope.addRow = function(i, isAbove) {
            let index = i + !isAbove
            if (scope.reverse) {
              index = scope.ordered.length - index;
            }
            scope.ordered.splice(index, 0, { name: '*' });
          }
          scope.setFormatted = function(isReversed) {
          	scope.formatted = scope.reverse ? scope.ordered.slice().reverse() : scope.ordered;
          };
          scope.$watch('reverse', function(n, o){
            scope.setFormatted();
          });
          scope.$watch('ordered', function(n, o){
            scope.setFormatted();
          }, true);
        },
        template: `
          <tr ng-repeat="item in formatted">
            <td>{{ item.name }}</td>
            <td><button ng-click="addRow($index, true)">O</button</td>
            <td><button ng-click="addRow($index, false)">U</button</td>
          </tr>
        `
      }
    });
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.9/angular.min.js"></script>
    
    <body ng-app="myApp">
      <div ng-controller="ctrlMain">
        <button ng-click="reverse = !reverse">toggle</button>
        <table>
          <tr><th>Header</th></tr>
          <tbody orderer="results" reverse="reverse"></tbody>
        </table>
      </div>
    </body>