11

I'm trying to sort a table of data which is populated from a JSON source. The code I have is as follows:

HTML:

<div ng-app="myApp">
    <div ng-controller="PurchasesCtrl">
        <table cellspacing="0">
            <tr class="first">
                <th class="first" ng:click="changeSorting(purchases.date)">Date</th>
                <th ng:click="changeSorting(purchases.text)">Description</th>
                <th ng:click="changeSorting(purchases.price)">Amount</th>
                <th ng:click="changeSorting(purchases.availability)">Status</th>
            </tr>
            <tr ng-repeat="purchase in purchases.data">
                <td class="first">{{purchase.date}}</td>
                <td>{{purchase.text}}</td>
                <td>{{purchase.price}}</td>
                <td>{{purchase.availability}}</td>
            </tr>
        </table>
    </div>
</div>

JS:

var myApp = angular.module("myApp",[]);

myApp.factory("Purchases", function(){
    var Purchases = {};

    Purchases.data = [
        {
            date: "10/05/2012",
            text: "1 Lorem ipsum dolor sit amet ipsum dolor",
            price: "£123.45",
            availability: "1 Available until 10th Dec 2013"
        },
        {
            date: "24/05/2012",
            text: "2 Lorem ipsum dolor sit amet ipsum dolor",
            price: "£234.56",
            availability: "2 Available until 10th Dec 2013"
        },
        {
            date: "20/05/2012",
            text: "3 Lorem ipsum dolor sit amet ipsum dolor",
            price: "£345.67",
            availability: "3 Available until 10th Dec 2013"
        }
    ];
    return Purchases;
});

function PurchasesCtrl($scope, Purchases){
    $scope.purchases = Purchases;

    $scope.changeSorting = function(column) {
        var sort = $scope.sort;

        if (sort.column == column) {
            sort.descending = !sort.descending;
        } else {
            sort.column = column;
            sort.descending = false;
        }
    };
}

Fiddle: http://jsfiddle.net/7czsM/1/

As you can see I've tried to add a click function to the table headers to call a function that sorts the data, but it's not working.

I've seen an example of this kind of thing which does work, here: http://jsfiddle.net/vojtajina/js64b/14/, but when I try to apply the same kind of thing to my scenario it breaks very quickly; for example, I tried adding the table headers programatically in JSON by adding the following:

var Purchases = {};

Purchases.head = [
        {
            date: "Date",
            text: "Text column",
            price: "Price column",
            availability: "Availability column"
        }

    Purchases.data = [
        {
            date: "10/05/2012",
            text: "1 Lorem ipsum dolor sit amet ipsum dolor",
            price: "£123.45",
            availability: "1 Available until 10th Dec 2013"
        },

This just prevents anything from working, but I thought it would be possible to add multiple sets of data to an Angular variable?

I'm a total new-comer to Angular so I'm really stuck with this. Any pointers would be much appreciated, thanks.

Dan
  • 5,836
  • 22
  • 86
  • 140

5 Answers5

27

Updated jsfiddle: http://jsfiddle.net/gweur/

sza is right, you did forget the $scope.sort object, but you are also missing the orderBy filter in your ng-repeat

|orderBy:sort.column:sort.descending

Additionally, you'll need to explicitly pass the column name to the changeSorting() function, like

ng-click="changeSorting('text')"  

not sure if there is a different way you can handle this.

Finally, ng-click is the correct syntax for the version of AngularJS you are using.

kmdsax
  • 1,361
  • 9
  • 12
8

Another very good example of making table sortable

http://jsfiddle.net/vojtajina/js64b/14/

<th ng:repeat="(i,th) in head" ng:class="selectedCls(i)" ng:click="changeSorting(i)">{{th}}</th>

scope.changeSorting = function(column) {
    var sort = scope.sort;
    if (sort.column == column) {
        sort.descending = !sort.descending;
    } else {
        sort.column = column;
        sort.descending = false;
    }
};
Srisudhir T
  • 685
  • 10
  • 24
5

Here is my solution. I also wrap the whole thing to a directive. The only dependency is UI.Bootstrap.pagination, which did a great job on pagination.

Here is the plunker

Here is the github source code.

maxisam
  • 21,975
  • 9
  • 75
  • 84
  • Your sorting is kind of messed up. Click in the name col and you will see: "name 1, name 10, name 11, name 12" and so on (when it should be "name 1, name 2, name 3, ...") – darksoulsong Oct 03 '14 at 12:58
  • It is base on string. you can create you own sorting method to replace it. The idea is pretty much the same. – maxisam Oct 03 '14 at 16:30
0

Or you can use #ngTasty as simple table directive. github : https://github.com/Zizzamia/ng-tasty docs : http://zizzamia.com/ng-tasty/directive/table

zizzamia
  • 241
  • 3
  • 9
0

I think some of your boilerplate code for AngularJS, specifically setting up your Controller and Factory, might have had had a syntax error.

Here is an example of your data working and expanded on your table column sorting method. Since AngularJS is good at processing javascript data-structures for displaying in HTML, you can just rearrange the javascript-arrays in memory, and AngularJS picks up on the changes. This example allows clicking the headers of the table, which will trigger a sorting based on that columns data type. If it is already sorted on that column, it will reverse-sort the column. The type detection is done through the presented isNumeric() function, and one two-tiny tweaks:

  1. Added checks if inputting on the '#' symbol as a header and sorts as a number in the toggleSort method. This can easily be removed by users if preferred.
  2. When toggleSort attempts to sort alphabetically, if it catches a TypeError it then switches to sorting on numbers.

var myApp = angular.module("myApp", []);

myApp.factory("Purchases", function() {
  var Purchases = {};

  Purchases.data = [{
    date: "10/05/2012",
    text: "1 Lorem ipsum dolor sit amet ipsum dolor",
    price: "£123.45",
    availability: "1 Available until 10th Dec 2013"
  }, {
    date: "24/05/2012",
    text: "2 Lorem ipsum dolor sit amet ipsum dolor",
    price: "£234.56",
    availability: "2 Available until 10th Dec 2013"
  }, {
    date: "20/05/2012",
    text: "3 Lorem ipsum dolor sit amet ipsum dolor",
    price: "£345.67",
    availability: "3 Available until 10th Dec 2013"
  }];
  return Purchases;
});

myApp.controller("PurchasesCtrl", function($scope, Purchases) {
  $scope.purchases = Purchases.data;

  // Dynamically get the entry headers to use with displaying the nested data via header-key lookups
  // Assumes all lines contain same key-text data
  $scope.purchasesHeaderKeys = []; // Contains only the key-data, not the values
  for (var key in $scope.purchases[0]) {
    if ($scope.purchases[0].hasOwnProperty(key)) {
      $scope.purchasesHeaderKeys.push(key);
    }
  }


  /**
   * Determine if the input value is a number or not.
   * @param n The input value to be checked for numeric status.
   * @returns true if parameter is numeric, or false otherwise.
   * 
   * This method uses the following evaluations to determine if input is a numeric:
   * 
   *   (5); // true  
   *   ('123'); // true  
   *   ('123abc'); // false  
   *   ('q345'); // false
   *   (null); // false
   *   (""); // false
   *  ([]); // false
   *   ('   '); // false
   *   (true); // false
   *   (false); // false
   *   (undefined); // false
   *   (new String('')); // false
   * 
   * @author C.D. (modified by)
   * @original https://stackoverflow.com/a/1421988/10930451
   * 
   */
  function isNumeric(n) {
    if (!isNaN(parseFloat(n)) && !isNaN(n - 0) && n !== null && n !== "") {
      return true;
    }
    return false;
  }

  /**
   * Column Sort Method (generic). Sort based on target column header or reverse sort if already selected on that.
   * @param dataSource The array of JSON data to be sorted
   * @param headers The array of JSON object-keys (table column headers) to be referenced
   * @param index The target JSON object-key to sort the table columns based upon
   * 
   * @author C.D.
   */
  $scope.lastSortIndex = 0;
  $scope.changeSorting = function(dataSource, headers, index) {
    if ($scope.lastSortIndex === index) {
      dataSource.reverse();
    } else {
      var key = headers[index];
      if (key === "#" || isNumeric(dataSource[key])) { // Compare as numeric or on '#' sign
        dataSource.sort((a, b) => parseFloat(a[key]) - parseFloat(b[key]));
      } else // Compare as Strings
      {
        try { // Attempt to sort as Strings
          dataSource.sort((a, b) => a[key].localeCompare(b[key]));
        } catch (error) {
          if (error.name === 'TypeError') { // Catch type error, actually sort as Numeric
            dataSource.sort((a, b) => parseFloat(a[key]) - parseFloat(b[key]));
          }
        }
      }
      $scope.lastSortIndex = index;
      $scope.reverseSorted = false;
    }
  }
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.13/angular.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<html ng-app="myApp">

<head>
  <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
  <title>AngularJS - Hello World</title>


  <script src="//netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"></script>
  <link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css">

  <link rel="stylesheet" href="style.css" />
  <script src="script.js"></script>
</head>

<body ng-controller="PurchasesCtrl">
  <div class="container">
    <table class="table table-hover table-sm">
    <thead>
     <tr>
      <th ng-repeat="header in purchasesHeaderKeys">
       <a ng-click="changeSorting(purchases, purchasesHeaderKeys, $index)">{{ header }}</a>
      </th>
     </tr>
    </thead>
  
    <tbody>
     <!-- Data is nested, so double-repeat to extract and display -->
     <tr ng-repeat="row in purchases" >
      <td ng-repeat="key in purchasesHeaderKeys">
       {{row[key]}}
      </td>
     </tr>
    </tbody>
   </table>
  </div>
</body>

</html>

I have put together a working Plunker example to demonstrate. Just click on the headers and they will sort the array in memory, where AngularJS will pick up on the changes and refresh that portion of the DOM.

C.D.
  • 406
  • 1
  • 5
  • 11