CRM-16019 - Migrate Angular pages to use new crmDatepicker widget
authorColeman Watts <coleman@civicrm.org>
Tue, 3 Mar 2015 19:38:57 +0000 (14:38 -0500)
committerColeman Watts <coleman@civicrm.org>
Thu, 5 Mar 2015 16:30:28 +0000 (11:30 -0500)
js/angular-crm-ui.js
js/angular-crmMailing/directives.js
partials/crmExample/example.html
partials/crmMailing/schedule.html
partials/crmMailingAB/selectWinner.html
partials/crmMailingAB/setup.html
partials/crmUi/datetime.html [deleted file]
tests/karma/unit/crmMailingRadioDateSpec.js

index 34450648bf93fc0088d5e0836bc81235918f44cb..48c688955d025d1284055eb54b8c86f82b624adb 100644 (file)
       };
     })
 
-    // Display a date widget.
-    // example: <input crm-ui-date ng-model="myobj.datefield" />
-    // example: <input crm-ui-date ng-model="myobj.datefield" crm-ui-date-format="yy-mm-dd" />
-    .directive('crmUiDate', function ($parse, $timeout) {
+    // Simple wrapper around $.crmDatepicker.
+    // example with no time input: <input crm-ui-datepicker="{time: false}" ng-model="myobj.datefield"/>
+    // example with custom date format: <input crm-ui-datepicker="{dateFormat: 'm/d/y'}" ng-model="myobj.datefield"/>
+    .directive('crmUiDatepicker', function () {
       return {
         restrict: 'AE',
         require: 'ngModel',
         scope: {
-          crmUiDateFormat: '@' // expression, date format (default: "yy-mm-dd")
+          crmUiDatepicker: '='
         },
         link: function (scope, element, attrs, ngModel) {
-          var fmt = attrs.crmUiDateFormat ? $parse(attrs.crmUiDateFormat)() : "yy-mm-dd";
-
-          element.addClass('dateplugin');
-          $(element).datepicker({
-            dateFormat: fmt
-          });
-
-          ngModel.$render = function $render() {
-            $(element).datepicker('setDate', ngModel.$viewValue);
-          };
-          var updateParent = (function() {
-            $timeout(function () {
-              ngModel.$setViewValue(element.val());
+          element
+            .crmDatepicker(scope.crmUiDatepicker)
+            .on('change', function() {
+              var requiredLength = 16;
+              if (scope.crmUiDatepicker && scope.crmUiDatepicker.time === false) {
+                requiredLength = 10;
+              }
+              if (scope.crmUiDatepicker && scope.crmUiDatepicker.date === false) {
+                requiredLength = 5;
+              }
+              ngModel.$setValidity('incompleteDateTime', !($(this).val().length && $(this).val().length !== requiredLength));
             });
-          });
-
-          element.on('change', updateParent);
-        }
-      };
-    })
-
-    // Display a date-time widget.
-    // example: <div crm-ui-date-time ng-model="myobj.mydatetimefield"></div>
-    .directive('crmUiDateTime', function ($parse) {
-      return {
-        restrict: 'AE',
-        require: 'ngModel',
-        scope: {
-          ngRequired: '@'
-        },
-        templateUrl: '~/crmUi/datetime.html',
-        link: function (scope, element, attrs, ngModel) {
-          var ts = scope.ts = CRM.ts(null);
-          scope.dateLabel = ts('Date');
-          scope.timeLabel = ts('Time');
-          element.addClass('crm-ui-datetime');
-
-          ngModel.$render = function $render() {
-            if (!_.isEmpty(ngModel.$viewValue)) {
-              var dtparts = ngModel.$viewValue.split(/ /);
-              scope.dtparts = {date: dtparts[0], time: dtparts[1]};
-            }
-            else {
-              scope.dtparts = {date: '', time: ''};
-            }
-            validate();
-          };
-
-          function updateParent() {
-            validate();
-
-            if (_.isEmpty(scope.dtparts.date) && _.isEmpty(scope.dtparts.time)) {
-              ngModel.$setViewValue(' ');
-            }
-            else {
-              //ngModel.$setViewValue(scope.dtparts.date + ' ' + scope.dtparts.time);
-              ngModel.$setViewValue((scope.dtparts.date ? scope.dtparts.date : '') + ' ' + (scope.dtparts.time ? scope.dtparts.time : ''));
-            }
-          }
-
-          scope.$watch('dtparts.date', updateParent);
-          scope.$watch('dtparts.time', updateParent);
-
-          function validate() {
-            var incompleteDateTime = _.isEmpty(scope.dtparts.date) ^ _.isEmpty(scope.dtparts.time);
-            ngModel.$setValidity('incompleteDateTime', !incompleteDateTime);
-          }
-
-          function updateRequired() {
-            scope.required = scope.$parent.$eval(attrs.ngRequired);
-          }
-
-          if (attrs.ngRequired) {
-            updateRequired();
-            scope.$parent.$watch(attrs.ngRequired, updateRequired);
-          }
-
-          scope.reset = function reset() {
-            scope.dtparts = {date: '', time: ''};
-            ngModel.$setViewValue('');
-          };
         }
       };
     })
       };
     })
 
-    // Display a time-entry field.
-    // example: <input crm-ui-time ng-model="myobj.mytimefield" />
-    .directive('crmUiTime', function ($parse, $timeout) {
-      return {
-        restrict: 'AE',
-        require: 'ngModel',
-        scope: {
-        },
-        link: function (scope, element, attrs, ngModel) {
-          element.addClass('crm-form-text six');
-          element.timeEntry({show24Hours: true});
-
-          ngModel.$render = function $render() {
-            element.timeEntry('setTime', ngModel.$viewValue);
-          };
-
-          var updateParent = (function () {
-            $timeout(function () {
-              ngModel.$setViewValue(element.val());
-            });
-          });
-          element.on('change', updateParent);
-        }
-      };
-    })
-
     // Generic, field-independent form validator.
     // example: <span ng-model="placeholder" crm-ui-validate="foo && bar || whiz" />
     // example: <span ng-model="placeholder" crm-ui-validate="foo && bar || whiz" crm-ui-validate-name="myError" />
index 7ae1cad7e4b5ff0307c7058da7ece4148c4c6cc8..a175bf6c86f29f827f4b29f0cbbc0c1dda0870a7 100644 (file)
         };
 
         ngModel.$render = function $render() {
-          var sched = ngModel.$viewValue;
-          if (!_.isEmpty(sched)) {
-            schedule.mode = 'at';
-            schedule.datetime = sched;
-          }
-          else {
-            schedule.mode = 'now';
-          }
           validate();
         };
-
+        // Update time value based on radio selection
         var updateParent = (function () {
           switch (schedule.mode) {
             case 'now':
               ngModel.$setViewValue(null);
-              schedule.datetime = ' ';
+              schedule.datetime = '';
               break;
             case 'at':
               ngModel.$setViewValue(schedule.datetime);
               throw 'Unrecognized schedule mode: ' + schedule.mode;
           }
           validate();
+          // Angular model changes don't seem to trigger the "change" event.
+          // Redundantly setting the value here to ensure it happens before the event and not after
+          $('.crm-hidden-date', element).val(schedule.datetime).trigger('change');
         });
 
         function validate() {
               ngModel.$setValidity('empty', true);
               break;
             case 'at':
-              ngModel.$setValidity('empty', !_.isEmpty(schedule.datetime) && schedule.datetime !== ' ');
+              ngModel.$setValidity('empty', !_.isEmpty(schedule.datetime));
               break;
             default:
               throw 'Unrecognized schedule mode: ' + schedule.mode;
         }
 
         $scope.$watch(attrs.crmMailingRadioDate + '.mode', updateParent);
-        $scope.$watch(attrs.crmMailingRadioDate + '.datetime', function (newValue, oldValue) {
-          // automatically switch mode based on datetime entry
-          if (oldValue != newValue) {
-            if (_.isEmpty(newValue) || newValue == " ") {
-              schedule.mode = 'now';
-            }
-            else {
-              schedule.mode = 'at';
-            }
+
+        // Watch changes in the date element, but only those initiated by the user
+        // (model-based changes to this field should not force-update other parts of the model)
+        $(element).on('change', '.crm-hidden-date', function(e, context) {
+          if (context === 'userInput' || context === 'crmClear') {
+            schedule.mode = $(this).val() ? 'at' : 'now';
+            updateParent();
           }
-          updateParent();
         });
       }
     };
index f19fca560fe07e3ba6acad4049fc2e7f1ec250bc..c9b6abd03f7d57a58ff2828524e36f6d608ac82e 100644 (file)
@@ -15,9 +15,6 @@
       <td>{{exName}}</td>
       <td>{{example.value}}</td>
       <td>
-        <!--<input crm-ui-date ng-model="example.value" name="{{exName}}" ng-required="example.required"/> -->
-        <!--<input crm-ui-time ng-model="example.value" name="{{exName}}" ng-required="example.required"/> -->
-        <!-- <div crm-ui-date-time ng-model="example.value" name="{{exName}}" ng-required="example.required"/> -->
         <div class="crmMailing-schedule-outer" crm-mailing-radio-date="schedule" ng-model="example.value"
              name="{{exName}}">
 
@@ -29,7 +26,7 @@
             <div>
               <input ng-model="schedule.mode" type="radio" name="send_{{exName}}" value="at" id="schedule-send-at"/>
               <label for="schedule-send-at">{{ts('Send at:')}}</label>
-              <span crm-ui-date-time ng-model="schedule.datetime" ng-required="schedule.mode == 'at'"></span>
+              <input crm-ui-datepicker ng-model="schedule.datetime" ng-required="schedule.mode == 'at'"/>
             </div>
           </div>
         </div>
index 4dcdc66457ee1a611d5eb672a082f557ba0c1729..75cf19b2cb293ced597ab4a2ad2c44ee2aff66fa 100644 (file)
@@ -7,7 +7,7 @@
     <div>
       <input ng-model="schedule.mode" type="radio" name="send" value="at" id="schedule-send-at"/>
       <label for="schedule-send-at">{{ts('Send at:')}}</label>
-      <span crm-ui-date-time ng-model="schedule.datetime" ng-required="schedule.mode == 'at'"></span>
+      <input crm-ui-datepicker ng-model="schedule.datetime" ng-required="schedule.mode == 'at'"/>
     </div>
   </div>
 </div>
index 67a8a316eb188ffe1e1a753e00eb0607a24c3229..3e1f31e7370943e7800d6ceed4f7573c4f410dc5 100644 (file)
@@ -12,7 +12,7 @@
       <div>
         <input ng-model="schedule.mode" type="radio" name="send" value="at" id="schedule-send-at"/>
         <label for="schedule-send-at">{{ts('Send final mailing at:')}}</label>
-        <span crm-ui-date-time ng-model="schedule.datetime"></span>
+        <input crm-ui-datepicker ng-model="schedule.datetime"/>
       </div>
     </div>
   </form>
index 2b15e66ca536c3c374bfd10c03f13c9155efb193..7167566373d509fac83dc75e9acf0a03585407b4 100644 (file)
@@ -50,7 +50,7 @@
           <div>
             <input ng-model="schedule.mode" type="radio" name="send" value="at" id="schedule-send-at"/>
             <label for="schedule-send-at">{{ts('Send A/B test at:')}}</label>
-            <span crm-ui-date-time ng-model="schedule.datetime"></span>
+            <input crm-ui-datepicker ng-model="schedule.datetime"/>
           </div>
       </div>
     </div>
@@ -63,7 +63,7 @@
         <div>
           <input ng-model="assessSched.mode" type="radio" name="assess" value="at" id="schedule-assess-at"/>
           <label for="schedule-assess-at">{{ts('Assess A/B test at:')}}</label>
-          <span crm-ui-date-time ng-model="assessSched.datetime"></span>
+          <input crm-ui-datepicker ng-model="assessSched.datetime"/>
         </div>
       </div>
     </div>
diff --git a/partials/crmUi/datetime.html b/partials/crmUi/datetime.html
deleted file mode 100644 (file)
index 1533bb5..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-<input
-  crm-ui-date
-  ng-model="dtparts.date"
-  ng-required="required"
-  ng-class="{incomplete: !dtparts.date && (required||dtparts.time)}"
-  placeholder="{{dateLabel}}"/>
-<input
-  crm-ui-time
-  ng-model="dtparts.time"
-  ng-required="required"
-  ng-class="{incomplete: !dtparts.time && (required||dtparts.date)}"
-  placeholder="{{timeLabel}}"/>
-<a
-  crm-icon="close"
-  class="crm-hover-button"
-  ng-click="reset()"
-  ng-show="dtparts.date || dtparts.time"
-  title="Clear"
-  ></a>
index e9dc448e0c1cb48ebc2039c38a7abc6f103223fe..196b1152ca3d626a6addbbedd0df9565bd6fda12 100644 (file)
@@ -1,4 +1,5 @@
 'use strict';
+/* global CRM:true */
 
 describe('crmMailingRadioDate', function() {
 
@@ -12,7 +13,7 @@ describe('crmMailingRadioDate', function() {
     '  <div crm-mailing-radio-date="mySchedule" ng-model="model.the_date" name="myRadioDate">' +
     '    <input ng-model="mySchedule.mode" type="radio" name="send" value="now" class="radio-now" />' +
     '    <input ng-model="mySchedule.mode" type="radio" name="send" value="at" class="radio-at" />' +
-    '    <span crm-ui-date-time ng-model="mySchedule.datetime" ng-required="mySchedule.mode == \'at\'"/>' +
+    '    <input crm-ui-datepicker ng-model="mySchedule.datetime" ng-required="mySchedule.mode == \'at\'"/>' +
     '  </div>' +
     '</form>';
 
@@ -30,6 +31,12 @@ describe('crmMailingRadioDate', function() {
       $interval = _$interval_;
       $timeout = _$timeout_;
 
+      // Global settings needed for crmUiDatepicker
+      CRM = CRM || {};
+      CRM.config = CRM.config || {};
+      CRM.config.dateInputFormat = 'mm/dd/yy';
+      CRM.config.timeIs24Hr = true;
+
       $rootScope.model = model = {
         the_date: ''
       };
@@ -43,40 +50,40 @@ describe('crmMailingRadioDate', function() {
       expect($rootScope.myForm.$valid).toBe(true);
       expect(element.find('.radio-now').prop('checked')).toBe(true);
       expect(element.find('.radio-at').prop('checked')).toBe(false);
-      expect(element.find('.dateplugin').datepicker('getDate')).toBe(null);
-      expect(element.find('.hasTimeEntry').timeEntry('getTime')).toBe(null);
+      expect(element.find('.crm-form-date').datepicker('getDate')).toBe(null);
+      expect(element.find('.crm-form-time').timeEntry('getTime')).toBe(null);
 
       model.the_date = ' ';
       $rootScope.$digest();
       expect($rootScope.myForm.$valid).toBe(false);
       expect(element.find('.radio-now').prop('checked')).toBe(false);
       expect(element.find('.radio-at').prop('checked')).toBe(true);
-      expect(element.find('.dateplugin').datepicker('getDate')).toBe(null);
-      expect(element.find('.hasTimeEntry').timeEntry('getTime')).toBe(null);
+      expect(element.find('.crm-form-date').datepicker('getDate')).toBe(null);
+      expect(element.find('.crm-form-time').timeEntry('getTime')).toBe(null);
 
       model.the_date = '2014-01-01 ';
       $rootScope.$digest();
       expect($rootScope.myForm.$valid).toBe(false);
       expect(element.find('.radio-now').prop('checked')).toBe(false);
       expect(element.find('.radio-at').prop('checked')).toBe(true);
-      expect(element.find('.dateplugin').datepicker('getDate').toDateString()).toEqual('Wed Jan 01 2014');
-      expect(element.find('.hasTimeEntry').timeEntry('getTime')).toBe(null);
+      expect(element.find('.crm-form-date').datepicker('getDate').toDateString()).toEqual('Wed Jan 01 2014');
+      expect(element.find('.crm-form-time').timeEntry('getTime')).toBe(null);
 
-      model.the_date = ' 02:03:04';
+      model.the_date = '02:03:04';
       $rootScope.$digest();
       expect($rootScope.myForm.$valid).toBe(false);
       expect(element.find('.radio-now').prop('checked')).toBe(false);
       expect(element.find('.radio-at').prop('checked')).toBe(true);
-      expect(element.find('.dateplugin').datepicker('getDate')).toBe(null);
-      expect(element.find('.hasTimeEntry').timeEntry('getTime').getMinutes()).toBe(3);
+      expect(element.find('.crm-form-date').datepicker('getDate')).toBe(null);
+      expect(element.find('.crm-form-time').timeEntry('getTime').getMinutes()).toBe(3);
 
       model.the_date = '2014-01-02 02:03:04';
       $rootScope.$digest();
       expect($rootScope.myForm.$valid).toBe(true);
       expect(element.find('.radio-now').prop('checked')).toBe(false);
       expect(element.find('.radio-at').prop('checked')).toBe(true);
-      expect(element.find('.dateplugin').datepicker('getDate').toDateString()).toEqual('Thu Jan 02 2014');
-      expect(element.find('.hasTimeEntry').timeEntry('getTime').getMinutes()).toBe(3);
+      expect(element.find('.crm-form-date').datepicker('getDate').toDateString()).toEqual('Thu Jan 02 2014');
+      expect(element.find('.crm-form-time').timeEntry('getTime').getMinutes()).toBe(3);
     });
 
     it('should update the model after changing the date and time', function() {
@@ -87,23 +94,23 @@ describe('crmMailingRadioDate', function() {
       expect(element.find('.radio-now').prop('checked')).toBe(true);
       expect(element.find('.radio-at').prop('checked')).toBe(false);
 
-      element.find('.dateplugin').datepicker('setDate', '2014-01-03').trigger('change');
+      element.find('.crm-form-date').datepicker('setDate', '2014-01-03').trigger('change');
       $rootScope.$digest();
-      expect(model.the_date).toBe('2014-01-03 ');
+      expect(model.the_date).toBe('2014-01-03');
       expect($rootScope.myForm.$valid).toBe(false);
       expect(element.find('.radio-now').prop('checked')).toBe(false);
       expect(element.find('.radio-at').prop('checked')).toBe(true);
 
-      element.find('.hasTimeEntry').timeEntry('setTime', '04:05').trigger('change');
+      element.find('.crm-form-time').timeEntry('setTime', '04:05').trigger('change');
       $rootScope.$digest();
       expect(model.the_date).toBe('2014-01-03 04:05');
       expect($rootScope.myForm.$valid).toBe(true);
       expect(element.find('.radio-now').prop('checked')).toBe(false);
       expect(element.find('.radio-at').prop('checked')).toBe(true);
 
-      element.find('.dateplugin').datepicker('setDate', '').trigger('change');
+      element.find('.crm-form-date').datepicker('setDate', '').trigger('change');
       $rootScope.$digest();
-      expect(model.the_date).toBe(' 04:05');
+      expect(model.the_date).toBe('04:05');
       expect($rootScope.myForm.$valid).toBe(false);
       expect(element.find('.radio-now').prop('checked')).toBe(false);
       expect(element.find('.radio-at').prop('checked')).toBe(true);