CRM-15970 - crmUiOrder - Add directives for managing sort order.
authorTim Otten <totten@civicrm.org>
Tue, 17 Feb 2015 23:51:53 +0000 (15:51 -0800)
committerTim Otten <totten@civicrm.org>
Wed, 18 Feb 2015 03:13:13 +0000 (19:13 -0800)
js/angular-crm-ui.js
tests/karma/unit/crmUiOrderSpec.js [new file with mode: 0644]

index 28ccdc93e2d59e114dd9de1545531cebd14360ce..731941bfe1e12ccef9720ad5ea79e56de26ee5d1 100644 (file)
       };
     })
 
+    // CrmUiOrderCtrl is a controller class which manages sort orderings.
+    // Ex:
+    //   JS:   $scope.myOrder = new CrmUiOrderCtrl(['+field1', '-field2]);
+    //         $scope.myOrder.toggle('field1');
+    //         $scope.myOrder.setDir('field2', '');
+    //   HTML: <tr ng-repeat="... | order:myOrder.get()">...</tr>
+    .service('CrmUiOrderCtrl', function(){
+      //
+      function CrmUiOrderCtrl(defaults){
+        this.values = defaults;
+      }
+      angular.extend(CrmUiOrderCtrl.prototype, {
+        get: function get() {
+          return this.values;
+        },
+        getDir: function getDir(name) {
+          if (this.values.indexOf(name) >= 0 || this.values.indexOf('+' + name) >= 0) {
+            return '+';
+          }
+          if (this.values.indexOf('-' + name) >= 0) {
+            return '-';
+          }
+          return '';
+        },
+        // @return bool TRUE if something is removed
+        remove: function remove(name) {
+          var idx = this.values.indexOf(name);
+          if (idx >= 0) {
+            this.values.splice(idx, 1);
+            return true;
+          }
+          else {
+            return false;
+          }
+        },
+        setDir: function setDir(name, dir) {
+          return this.toggle(name, dir);
+        },
+        // Toggle sort order on a field.
+        // To set a specific order, pass optional parameter 'next' ('+', '-', or '').
+        toggle: function toggle(name, next) {
+          if (!next && next !== '') {
+            next = '+';
+            if (this.remove(name) || this.remove('+' + name)) {
+              next = '-';
+            }
+            if (this.remove('-' + name)) {
+              next = '';
+            }
+          }
+
+          if (next == '+') {
+            this.values.unshift('+' + name);
+          }
+          else if (next == '-') {
+            this.values.unshift('-' + name);
+          }
+        }
+      });
+      return CrmUiOrderCtrl;
+    })
+
+    // Define a controller which manages sort order. You may interact with the controller
+    // directly ("myOrder.toggle('fieldname')") order using the helper, crm-ui-order-by.
+    // example:
+    //   <span crm-ui-order="{var: 'myOrder', defaults: {'-myField'}}"></span>
+    //   <th><a crm-ui-order-by="[myOrder,'myField']">My Field</a></th>
+    //   <tr ng-repeat="... | order:myOrder.get()">...</tr>
+    //   <button ng-click="myOrder.toggle('myField')">
+    .directive('crmUiOrder', function(CrmUiOrderCtrl) {
+      return {
+        link: function(scope, element, attrs){
+          var options = angular.extend({var: 'crmUiOrderBy'}, scope.$eval(attrs.crmUiOrder));
+          scope[options.var] = new CrmUiOrderCtrl(options.defaults);
+        }
+      };
+    })
+
+    // For usage, see crmUiOrder (above)
+    .directive('crmUiOrderBy', function() {
+      return {
+        link: function(scope, element, attrs) {
+          function updateClass(crmUiOrderCtrl, name) {
+            var dir = crmUiOrderCtrl.getDir(name);
+            element
+              .toggleClass('sorting_asc', dir === '+')
+              .toggleClass('sorting_desc', dir === '-')
+              .toggleClass('sorting', dir === '');
+          }
+
+          element.on('click', function(e){
+            var tgt = scope.$eval(attrs.crmUiOrderBy);
+            tgt[0].toggle(tgt[1]);
+            updateClass(tgt[0], tgt[1]);
+            e.preventDefault();
+            scope.$digest();
+          });
+
+          var tgt = scope.$eval(attrs.crmUiOrderBy);
+          updateClass(tgt[0], tgt[1]);
+        }
+      };
+    })
+
     // Display a fancy SELECT (based on select2).
     // usage: <select crm-ui-select="{placeholder:'Something',allowClear:true,...}" ng-model="myobj.field"><option...></select>
     .directive('crmUiSelect', function ($parse, $timeout) {
diff --git a/tests/karma/unit/crmUiOrderSpec.js b/tests/karma/unit/crmUiOrderSpec.js
new file mode 100644 (file)
index 0000000..162a059
--- /dev/null
@@ -0,0 +1,87 @@
+'use strict';
+
+describe('crmUiOrder', function() {
+
+  beforeEach(function() {
+    module('crmResource');
+    module('crmUtil');
+    module('crmUi');
+  });
+
+  describe('crmUiOrder', function() {
+    var $compile, $q, $rootScope, rows, element;
+
+    var html = '<div>' +
+      '  <span crm-ui-order="{var: \'myOrder\', defaults: [\'-num\']}"></span>' +
+      '  <table>' +
+      '    <thead>' +
+      '      <tr>' +
+      '        <th><a crm-ui-order-by="[myOrder,\'name\']" id="th-name">Name</a></th>' +
+      '        <th><a crm-ui-order-by="[myOrder,\'num\']" id="th-num">Num</a></th>' +
+      '      </tr>' +
+      '    </thead>' +
+      '    <tbody>' +
+      '      <tr ng-repeat="r in rows|orderBy:myOrder.get()">' +
+      '        <td class="row-value">{{r.name}}</td>' +
+      '      </tr>' +
+      '    </tbody>' +
+      '  </table>' +
+      '</div>';
+
+    beforeEach(inject(function(_$compile_, _$rootScope_, _$q_) {
+      $compile = _$compile_;
+      $rootScope = _$rootScope_;
+      $q = _$q_;
+
+      $rootScope.rows = rows = [
+        {name: 'a', num: 200},
+        {name: 'c', num: 300},
+        {name: 'b', num: 100},
+        {name: 'd', num: 0}
+      ];
+
+    }));
+
+    it('changes primary ordering on click', function() {
+      element = $compile(html)($rootScope);
+      $rootScope.$digest();
+      expect($rootScope.myOrder).toEqual(jasmine.any(Object));
+      expect(element.find('.row-value').text()).toBe('cabd');
+
+      element.find('#th-name').click();
+      $rootScope.$digest();
+      expect(element.find('.row-value').text()).toBe('abcd');
+    });
+
+    it('cycles through ascending/descending orderings on multiple clicks', function() {
+      // default: -num
+      element = $compile(html)($rootScope);
+      $rootScope.$digest();
+      expect($rootScope.myOrder.get()).toEqual(['-num']);
+      expect($rootScope.myOrder.getDir('num')).toEqual('-');
+      expect(element.find('.row-value').text()).toBe('cabd');
+
+      // toggle: "-num" => ""
+      element.find('#th-num').click();
+      $rootScope.$digest();
+      expect($rootScope.myOrder.get()).toEqual([]);
+      expect($rootScope.myOrder.getDir('num')).toEqual('');
+      expect(element.find('.row-value').text()).toBe('acbd');
+
+      // toggle: "" => "+num"
+      element.find('#th-num').click();
+      $rootScope.$digest();
+      expect($rootScope.myOrder.get()).toEqual(['+num']);
+      expect($rootScope.myOrder.getDir('num')).toEqual('+');
+      expect(element.find('.row-value').text()).toBe('dbac');
+
+      // toggle: "+num" => "-num"
+      element.find('#th-num').click();
+      $rootScope.$digest();
+      expect($rootScope.myOrder.get()).toEqual(['-num']);
+      expect($rootScope.myOrder.getDir('num')).toEqual('-');
+      expect(element.find('.row-value').text()).toBe('cabd');
+    });
+
+  });
+});