};
})
- // 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" />
};
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();
});
}
};
<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}}">
<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>
<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>
<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>
<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>
<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>
+++ /dev/null
-<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>
'use strict';
+/* global CRM:true */
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>';
$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: ''
};
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() {
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);