<!DOCTYPE html>
<html ng-app="myApp">

  <head>
    <meta charset="utf-8" />
    <title>Multiple-key Object Sort</title>
    <script>document.write('<base href="' + document.location + '" />');</script>
    <link href="style.css" rel="stylesheet" />
    <script data-semver="1.2.13" src="http://code.angularjs.org/1.2.13/angular.js" data-require="angular.js@1.2.x"></script>
    <script src="multiSort.js"></script>
  </head>

  <body ng-controller="MyCtrl">
        <b>Sort Keys:</b>
        <ol>
            <li ng-repeat="cKey in sortKeyOptions">
                <div>
                    <select ng-model="sortKeys[$index]" ng-options="k as k.displayName for k in sortKeyOptions[$index]" ng-change="sortKeyOnChange($index)">
                        <option value="">-- choose sort key --</option>
                    </select>
                    <input ng-disabled="!(sortKeys[$index])" ng-change="sortData()" id="directionAsc{{$index}}" type="radio" ng-model="sortKeys[$index].direction" value="asc"><label for="directionAsc{{$index}}">ascending</label>
                    <input ng-disabled="!(sortKeys[$index])" ng-change="sortData()" id="directionDesc{{$index}}" type="radio" ng-model="sortKeys[$index].direction" value="desc"><label for="directionDesc{{$index}}">decending</label>
                </div>
            </li>
        </ol>
        <br>
        <table style="border: thin solid grey">
            <tr>
                <th style="border: thin solid grey" ng-repeat="key in columnKeys">{{key.displayName}}</th>
            </tr>
            <tr ng-repeat="user in myData">
                <td style="border: thin solid grey" ng-repeat="key in columnKeys">{{user[key.field]}}</td>
            </tr>
        </table>
    </body>

</html>
// main.js
var app = angular.module('myApp', []);

app.controller('MyCtrl', function($scope, $log) {
    $scope.myData = [
        {
          "name": "John W. Smith",
          "familyName": "Smith",
          "birthDayNumber": 2418938.000000, // Julian Calendar Day. It's just a floating-point number. Higher values are later dates.
          "birthCountry": "United States"
        },
        {
          "name": "Moroni W. Smith",
          "familyName": "Smith",
          "birthDayNumber": 2418930.000000, // Julian Calendar Day. It's just a floating-point number. Higher values are later dates.
          "birthCountry": "United States"
        },
        {
          "name": "Moroni W. Smith",
          "familyName": "Smith",
          "birthDayNumber": 2418930.000000, // Julian Calendar Day. It's just a floating-point number. Higher values are later dates.
          "birthCountry": "Peru"
        },
        {
          "name": "Tiancum W. Benson",
          "familyName": "Benson",
          "birthDayNumber": 2413538.000000, // Julian Calendar Day. It's just a floating-point number. Higher values are later dates.
          "birthCountry": "Mexico"
        },
        {
          "name": "Enos W. Benson",
          "familyName": "Benson",
          "birthDayNumber": 2418738.000000, // Julian Calendar Day. It's just a floating-point number. Higher values are later dates.
          "birthCountry": "Chile"
        },
    ];

    $scope.columnKeys = [
      {field:'name', displayName:'Name'},
      {field:'familyName', displayName:'Last Name'},
      {field:'birthDayNumber', displayName:'Birth Day #'},
      {field:'birthCountry', displayName:'Birth Country'}
    ];

    $scope.sortKeys = [];

    $scope.sortKeyOptions = [
      $scope.columnKeys
    ];

    $scope.sortKeyOnChange = function (index) {
      //  Anything above index (if it exists) is now invalid - Remove it
      while ($scope.sortKeys.length > index + 1)
      {
        $scope.sortKeys.pop();
      }
      while ($scope.sortKeyOptions.length > index + 1)
      {
        $scope.sortKeyOptions.pop();
      }

      //  User set a value, so default asc/desc then add a new sortKeyOption if not too long
      if ($scope.sortKeys[index])
      {
        $scope.sortKeys[index].direction = "asc";
        if ($scope.sortKeyOptions.length < $scope.columnKeys.length)
        {
          $scope.sortKeyOptions[index + 1] = [];
          $scope.copyPreviousSortKeyOptionExceptingGivenField($scope.sortKeyOptions[index + 1], $scope.sortKeys[index].field, $scope.sortKeyOptions[index]);
        }
      }
      //  User reset the value, so existing sortKeyOption okay and remove extra sortKey
      else
      {
        $scope.sortKeys.pop();
      }

      //  Now Sort the table
      $scope.myData.sort($scope.sortMyData);
    };

    $scope.copyPreviousSortKeyOptionExceptingGivenField = function (newSortKeyOption, field, prevSortKeyOption) {
      for (var i = 0; i < prevSortKeyOption.length; i++)
      {
        if (prevSortKeyOption[i].field !== field)
        {
          newSortKeyOption.push(prevSortKeyOption[i]);
        }
      }
    };

    $scope.sortData = function () {
      $scope.myData.sort($scope.sortMyData);
    };

    $scope.sortMyData = function (a, b)
    {
      var retVal = 0, key;
      for (var i = 0; i < $scope.sortKeys.length; i++)
      {
        if (retVal !== 0)
        {
          break;
        }
        else
        {
          key = $scope.sortKeys[i];
          if ('asc' === key.direction)
          {
            retVal = (a[key.field] < b[key.field]) ? -1 : (a[key.field] > b[key.field]) ? 1 : 0;
          }
          else
          {
            retVal = (a[key.field] < b[key.field]) ? 1 : (a[key.field] > b[key.field]) ? -1 : 0;
          }
        }
      }
      return retVal;
    };
});
Multiple-Key Object Sort
------------------------

Created by: Ken Severn

The purpose of this project was to create "a reusable library that allows users to sort lists of persons by multiple keys." The following is a discussion on the trade-offs I faced and the decisions I made during the design and implementation.

- I derived the logic to accomplish the sort relatively quickly with the help of Array.sort(), Stack Overflow and my own enhancements for multiple keys. The more engaging challenge was the intuitive interface I used to select and define the multiple keys upon which to sort. Using jQuery would have been easy, but less flexible. Therefore, I chose Angular JS. Though the sort-key-definition interface is simplistic, it felt natural and worth pursuing. The result, I believe, is robust, easy to use and requires no instruction. Finally, though this particular interface, increased the size and difficulty of my goal, I chose it, because I think usability is key to software's value.
- I chose to include some code that is specific to the "person" object shown in the demo. However, I designed the rest of the code to handle just about any consistently-keyed object array. Here, I attempted to achieve reasonable flexibility without trying to solve world hunger.
- I decided to make my solution's scalability a secondary concern. Typically, highly-scalable solutions are more practical to consider once a working prototype is available. Since, my goal was to produce a working prototype, scalability fit nicely on the shelf. As is, it performs acceptably up to about 1000 records.
- I chose to leave aesthetics out of the mix as it was also out of the scope of my goal. I figure if beauty is in the eye of the beholder, they can code their own CSS.

You're comments are welcome: [Ken Severn]

  [ken Severn]: mailto:plunkerMultiSort@ksev.org