From 2f31bc16342b926b13945c0ab90e17b15514ad0c Mon Sep 17 00:00:00 2001 From: Tim Otten Date: Sun, 1 Feb 2015 20:59:09 -0800 Subject: [PATCH] CRM-15856 - Convert date/time widgets to use ngModel (which supports validation). --- css/civicrm.css | 3 + js/angular-crm-ui.js | 101 +++++++++++++++------------- js/angular-crmMailing/directives.js | 24 +++---- partials/crmMailing/schedule.html | 4 +- partials/crmUi/datetime.html | 18 +++++ 5 files changed, 90 insertions(+), 60 deletions(-) create mode 100644 partials/crmUi/datetime.html diff --git a/css/civicrm.css b/css/civicrm.css index 3995a2925d..54508992c0 100644 --- a/css/civicrm.css +++ b/css/civicrm.css @@ -4541,3 +4541,6 @@ span.crm-status-icon { .crm-container table.mergecontact thead th { width:30%; } +.crm-container .crm-ui-datetime.ng-dirty input.incomplete { + border: 1px solid red; +} diff --git a/js/angular-crm-ui.js b/js/angular-crm-ui.js index 2bc7019931..d9f590678a 100644 --- a/js/angular-crm-ui.js +++ b/js/angular-crm-ui.js @@ -24,76 +24,92 @@ }) // Display a date widget. - // example: - // example: - // WISHLIST: use ngModel + // example: + // example: .directive('crmUiDate', function ($parse, $timeout) { return { restrict: 'AE', + require: 'ngModel', scope: { - crmUiDate: '@', // expression, model binding crmUiDateFormat: '@' // expression, date format (default: "yy-mm-dd") }, - link: function (scope, element, attrs) { + link: function (scope, element, attrs, ngModel) { var fmt = attrs.crmUiDateFormat ? $parse(attrs.crmUiDateFormat)() : "yy-mm-dd"; - var model = $parse(attrs.crmUiDate); element.addClass('dateplugin'); $(element).datepicker({ dateFormat: fmt }); - var updateChildren = (function() { - element.off('change', updateParent); - $(element).datepicker('setDate', model(scope.$parent)); - element.on('change', updateParent); - }); + ngModel.$render = function $render() { + $(element).datepicker('setDate', ngModel.$viewValue); + }; var updateParent = (function() { $timeout(function () { - model.assign(scope.$parent, $(element).val()); + ngModel.$setViewValue(element.val()); }); }); - updateChildren(); - scope.$parent.$watch(attrs.crmUiDate, updateChildren); element.on('change', updateParent); } }; }) // Display a date-time widget. - // example:
- // WISHLIST: use ngModel + // example:
.directive('crmUiDateTime', function ($parse) { return { restrict: 'AE', + require: 'ngModel', scope: { - crmUiDateTime: '@' + ngRequired: '@' }, - template: ' ', - link: function (scope, element, attrs) { - var model = $parse(attrs.crmUiDateTime); + 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'); - var updateChildren = (function () { - var value = model(scope.$parent); - if (value) { - var dtparts = value.split(/ /); + 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: ''}; } - }); - var updateParent = (function () { - model.assign(scope.$parent, scope.dtparts.date + " " + scope.dtparts.time); - }); + }; + + function updateParent() { + var incompleteDateTime = _.isEmpty(scope.dtparts.date) ^ _.isEmpty(scope.dtparts.time); + ngModel.$setValidity('incompleteDateTime', !incompleteDateTime); + + 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 : '')); + } + } - updateChildren(); - scope.$parent.$watch(attrs.crmUiDateTime, updateChildren); scope.$watch('dtparts.date', updateParent); scope.$watch('dtparts.time', updateParent); + + 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(''); + }; } }; }) @@ -437,33 +453,26 @@ }) // Display a time-entry field. - // example: - // WISHLIST: use ngModel + // example: .directive('crmUiTime', function ($parse, $timeout) { return { restrict: 'AE', + require: 'ngModel', scope: { - crmUiTime: '@' }, - link: function (scope, element, attrs) { - var model = $parse(attrs.crmUiTime); - + link: function (scope, element, attrs, ngModel) { element.addClass('crm-form-text six'); - $(element).timeEntry({show24Hours: true}); + element.timeEntry({show24Hours: true}); + + ngModel.$render = function $render() { + element.timeEntry('setTime', ngModel.$viewValue); + }; - var updateChildren = (function() { - element.off('change', updateParent); - $(element).timeEntry('setTime', model(scope.$parent)); - element.on('change', updateParent); - }); var updateParent = (function () { $timeout(function () { - model.assign(scope.$parent, element.val()); + ngModel.$setViewValue(element.val()); }); }); - - updateChildren(); - scope.$parent.$watch(attrs.crmUiTime, updateChildren); element.on('change', updateParent); } }; diff --git a/js/angular-crmMailing/directives.js b/js/angular-crmMailing/directives.js index f88d1afc44..b161cfa8c1 100644 --- a/js/angular-crmMailing/directives.js +++ b/js/angular-crmMailing/directives.js @@ -101,46 +101,46 @@ }); // Represent a datetime field as if it were a radio ('schedule.mode') and a datetime ('schedule.datetime'). - // example:
...
- // FIXME: use ngModel instead of adhoc crmModel + // example:
...
angular.module('crmMailing').directive('crmMailingRadioDate', function ($parse) { return { - link: function ($scope, element, attrs) { - var schedModel = $parse(attrs.crmModel); + require: 'ngModel', + link: function ($scope, element, attrs, ngModel) { var schedule = $scope[attrs.crmMailingRadioDate] = { mode: 'now', datetime: '' }; - var updateChildren = (function () { - var sched = schedModel($scope); - if (sched) { + + ngModel.$render = function $render() { + var sched = ngModel.$viewValue; + if (!_.isEmpty(sched)) { schedule.mode = 'at'; schedule.datetime = sched; } else { schedule.mode = 'now'; } - }); + }; + var updateParent = (function () { switch (schedule.mode) { case 'now': - schedModel.assign($scope, null); + ngModel.$setViewValue(null); break; case 'at': - schedModel.assign($scope, schedule.datetime); + ngModel.$setViewValue(schedule.datetime); break; default: throw 'Unrecognized schedule mode: ' + schedule.mode; } }); - $scope.$watch(attrs.crmModel, updateChildren); $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 (!newValue || newValue == " ") { + if (_.isEmpty(newValue) || newValue == " ") { schedule.mode = 'now'; } else { diff --git a/partials/crmMailing/schedule.html b/partials/crmMailing/schedule.html index a68afcd6fd..4dcdc66457 100644 --- a/partials/crmMailing/schedule.html +++ b/partials/crmMailing/schedule.html @@ -1,4 +1,4 @@ -
+
@@ -7,7 +7,7 @@
- +
diff --git a/partials/crmUi/datetime.html b/partials/crmUi/datetime.html new file mode 100644 index 0000000000..e7c455efee --- /dev/null +++ b/partials/crmUi/datetime.html @@ -0,0 +1,18 @@ + + + -- 2.25.1