Merge pull request #13787 from eileenmcnaughton/label_button
[civicrm-core.git] / js / crm.datepicker.js
1 (function($, CRM, _) {
2 "use strict";
3
4 /**
5 * @see http://wiki.civicrm.org/confluence/display/CRMDOC/crmDatepicker
6 */
7 $.fn.crmDatepicker = function(options) {
8 return $(this).each(function() {
9 if ($(this).is('.crm-form-date-wrapper .crm-hidden-date')) {
10 // Already initialized - destroy
11 $(this)
12 .off('.crmDatepicker')
13 .css('display', '')
14 .removeClass('crm-hidden-date')
15 .siblings().remove();
16 $(this).unwrap();
17 }
18 if (options === 'destroy') {
19 return;
20 }
21 var
22 $dataField = $(this).wrap('<span class="crm-form-date-wrapper" />'),
23 settings = _.cloneDeep(options || {}),
24 $dateField = $(),
25 $timeField = $(),
26 $clearLink = $(),
27 hasDatepicker = settings.date !== false && settings.date !== 'yy',
28 type = hasDatepicker ? 'text' : 'number';
29
30 if (settings.allowClear !== undefined ? settings.allowClear : !$dataField.is('.required, [required]')) {
31 $clearLink = $('<a class="crm-hover-button crm-clear-link" title="'+ _.escape(ts('Clear')) +'"><i class="crm-i fa-times"></i></a>')
32 .insertAfter($dataField);
33 }
34 if (settings.time !== false) {
35 $timeField = $('<input>').insertAfter($dataField);
36 CRM.utils.copyAttributes($dataField, $timeField, ['class', 'disabled']);
37 $timeField
38 .addClass('crm-form-text crm-form-time')
39 .attr('placeholder', $dataField.attr('time-placeholder') === undefined ? '\uf017' : $dataField.attr('time-placeholder'))
40 .attr('aria-label', $dataField.attr('time-placeholder') === undefined ? ts('Time') : $dataField.attr('time-placeholder'))
41 .change(updateDataField)
42 .timeEntry({
43 spinnerImage: '',
44 show24Hours: settings.time === true || settings.time === undefined ? CRM.config.timeIs24Hr : settings.time == '24'
45 });
46 }
47 if (settings.date !== false) {
48 // Render "number" field for year-only format, calendar popup for all other formats
49 $dateField = $('<input type="' + type + '">').insertAfter($dataField);
50 CRM.utils.copyAttributes($dataField, $dateField, ['placeholder', 'style', 'class', 'disabled', 'aria-label']);
51 $dateField.addClass('crm-form-' + type);
52 if (hasDatepicker) {
53 settings.minDate = settings.minDate ? CRM.utils.makeDate(settings.minDate) : null;
54 settings.maxDate = settings.maxDate ? CRM.utils.makeDate(settings.maxDate) : null;
55 settings.dateFormat = typeof settings.date === 'string' ? settings.date : CRM.config.dateInputFormat;
56 settings.changeMonth = _.includes(settings.dateFormat, 'm');
57 settings.changeYear = _.includes(settings.dateFormat, 'y');
58 if (!settings.yearRange && settings.minDate !== null && settings.maxDate !== null) {
59 settings.yearRange = '' + CRM.utils.formatDate(settings.minDate, 'yy') + ':' + CRM.utils.formatDate(settings.maxDate, 'yy');
60 }
61 // Set placeholder as calendar icon (`fa-calendar` is Unicode f073)
62 // and add datepicker
63 $dateField.addClass('crm-form-date').attr({placeholder: '\uF073'}).datepicker(settings);
64 } else {
65 $dateField.attr('min', settings.minDate ? CRM.utils.formatDate(settings.minDate, 'yy') : '1000');
66 $dateField.attr('max', settings.maxDate ? CRM.utils.formatDate(settings.maxDate, 'yy') : '4000');
67 }
68 $dateField.change(updateDataField);
69 }
70 // Rudimentary validation. TODO: Roll into use of jQUery validate and ui.datepicker.validation
71 function isValidDate() {
72 // FIXME: parseDate doesn't work with incomplete date formats; skip validation if no month, day or year in format
73 var lowerFormat = settings.dateFormat.toLowerCase();
74 if (lowerFormat.indexOf('y') < 0 || lowerFormat.indexOf('m') < 0 || !dateHasDay()) {
75 return true;
76 }
77 try {
78 $.datepicker.parseDate(settings.dateFormat, $dateField.val());
79 return true;
80 } catch (e) {
81 return false;
82 }
83 }
84
85 /**
86 * Does the date format contain the day.
87 *
88 * @returns {boolean}
89 */
90 function dateHasDay() {
91 var lowerFormat = settings.dateFormat.toLowerCase();
92 if (lowerFormat.indexOf('d') < 0) {
93 return false;
94 }
95 return true;
96 }
97 function updateInputFields(e, context) {
98 var val = $dataField.val(),
99 time = null;
100 if (context !== 'userInput' && context !== 'crmClear') {
101 if (hasDatepicker) {
102 $dateField.datepicker('setDate', _.includes(val, '-') ? $.datepicker.parseDate('yy-mm-dd', val) : null);
103 } else if ($dateField.length) {
104 $dateField.val(val.slice(0, 4));
105 }
106 if ($timeField.length) {
107 if (val.length === 8) {
108 time = val;
109 } else if (val.length === 19) {
110 time = val.split(' ')[1];
111 }
112 $timeField.timeEntry('setTime', time);
113 }
114 }
115 $clearLink.css('visibility', val ? 'visible' : 'hidden');
116 }
117 function updateDataField(e, context) {
118 // The crmClear event wipes all the field values anyway, so no need to respond
119 if (context !== 'crmClear') {
120 var val = '';
121 if ($dateField.val()) {
122 if (hasDatepicker && isValidDate() && dateHasDay()) {
123 val = $.datepicker.formatDate('yy-mm-dd', $dateField.datepicker('getDate'));
124 $dateField.removeClass('crm-error');
125 } else if (!hasDatepicker) {
126 val = $dateField.val() + '-01-01';
127 }
128 else if (!dateHasDay()) {
129 // This would be a Year-month date (yyyy-mm)
130 // it could be argued it should not use a datepicker....
131 val = $dateField.val() + '-01';
132 } else {
133 $dateField.addClass('crm-error');
134 }
135 }
136 if ($timeField.val()) {
137 val += (val ? ' ' : '') + $timeField.timeEntry('getTime').toTimeString().substr(0, 8);
138 }
139 $dataField.val(val).trigger('change', ['userInput']);
140 }
141 }
142 $dataField.hide().addClass('crm-hidden-date').on('change.crmDatepicker', updateInputFields);
143 updateInputFields();
144 });
145 };
146 })(jQuery, CRM, CRM._);