5 * @see https://docs.civicrm.org/dev/en/latest/framework/ui/#date-picker
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
12 .off('.crmDatepicker')
14 .removeClass('crm-hidden-date')
18 if (options
=== 'destroy') {
22 $dataField
= $(this).wrap('<span class="crm-form-date-wrapper" />'),
23 settings
= _
.cloneDeep(options
|| {}),
28 hasDatepicker
= settings
.date
!== false && settings
.date
!== 'yy',
29 type
= hasDatepicker
? 'text' : 'number';
31 if (settings
.allowClear
!== undefined ? settings
.allowClear
: !$dataField
.is('.required, [required]')) {
32 $clearLink
= $('<a class="crm-hover-button crm-clear-link" title="'+ _
.escape(ts('Clear')) +'"><i class="crm-i fa-times" aria-hidden="true"></i></a>')
33 .insertAfter($dataField
);
35 if (settings
.time
!== false) {
36 $timeField
= $('<input>').insertAfter($dataField
);
37 placeholder
= settings
.timePlaceholder
|| $dataField
.attr('time-placeholder');
38 CRM
.utils
.copyAttributes($dataField
, $timeField
, ['class', 'disabled']);
40 .addClass('crm-form-text crm-form-time')
41 // Set default placeholder as clock icon (`fa-clock` is Unicode f017)
42 .attr('placeholder', placeholder
=== undefined ? '\uf017' : placeholder
)
43 .attr('aria-label', placeholder
=== undefined ? ts('Time') : placeholder
)
44 .change(updateDataField
)
47 show24Hours
: settings
.time
=== true || settings
.time
=== undefined ? CRM
.config
.timeIs24Hr
: settings
.time
== '24'
50 $timeField
.addClass('crm-placeholder-icon');
53 if (settings
.date
!== false) {
54 // Render "number" field for year-only format, calendar popup for all other formats
55 $dateField
= $('<input type="' + type
+ '">').insertAfter($dataField
);
56 CRM
.utils
.copyAttributes($dataField
, $dateField
, ['style', 'class', 'disabled', 'aria-label']);
57 placeholder
= settings
.placeholder
|| $dataField
.attr('placeholder');
58 $dateField
.addClass('crm-form-' + type
);
59 if (!settings
.minDate
&& isInt(settings
.start_date_years
)) {
60 settings
.minDate
= '' + (new Date().getFullYear() - settings
.start_date_years
) + '-01-01';
62 if (!settings
.maxDate
&& isInt(settings
.end_date_years
)) {
63 settings
.maxDate
= '' + (new Date().getFullYear() + settings
.end_date_years
) + '-12-31';
66 settings
.minDate
= settings
.minDate
? CRM
.utils
.makeDate(settings
.minDate
) : null;
67 settings
.maxDate
= settings
.maxDate
? CRM
.utils
.makeDate(settings
.maxDate
) : null;
68 settings
.dateFormat
= typeof settings
.date
=== 'string' ? settings
.date
: CRM
.config
.dateInputFormat
;
69 settings
.changeMonth
= _
.includes(settings
.dateFormat
, 'm');
70 settings
.changeYear
= _
.includes(settings
.dateFormat
, 'y');
71 if (!settings
.yearRange
&& settings
.minDate
!== null && settings
.maxDate
!== null) {
72 settings
.yearRange
= '' + CRM
.utils
.formatDate(settings
.minDate
, 'yy') + ':' + CRM
.utils
.formatDate(settings
.maxDate
, 'yy');
74 $dateField
.addClass('crm-form-date').datepicker(settings
);
76 $dateField
.attr('min', settings
.minDate
? CRM
.utils
.formatDate(settings
.minDate
, 'yy') : '1000');
77 $dateField
.attr('max', settings
.maxDate
? CRM
.utils
.formatDate(settings
.maxDate
, 'yy') : '4000');
80 // Set placeholder as calendar icon (`fa-calendar` is Unicode f073)
81 $dateField
.attr({placeholder
: placeholder
=== undefined ? '\uF073' : placeholder
}).change(updateDataField
);
83 $dateField
.addClass('crm-placeholder-icon');
86 // Rudimentary validation. TODO: Roll into use of jQUery validate and ui.datepicker.validation
87 function isValidDate() {
88 // FIXME: parseDate doesn't work with incomplete date formats; skip validation if no month, day or year in format
89 var lowerFormat
= settings
.dateFormat
.toLowerCase();
90 if (lowerFormat
.indexOf('y') < 0 || lowerFormat
.indexOf('m') < 0 || !dateHasDay()) {
94 $.datepicker
.parseDate(settings
.dateFormat
, $dateField
.val());
102 * Does the date format contain the day.
106 function dateHasDay() {
107 var lowerFormat
= settings
.dateFormat
.toLowerCase();
108 return lowerFormat
.indexOf('d') >= 0;
110 function updateInputFields(e
, context
) {
111 var val
= $dataField
.val(),
113 if (context
!== 'userInput' && context
!== 'crmClear') {
115 $dateField
.datepicker('setDate', _
.includes(val
, '-') ? $.datepicker
.parseDate('yy-mm-dd', val
) : null);
116 } else if ($dateField
.length
) {
117 $dateField
.val(val
.slice(0, 4));
119 if ($timeField
.length
) {
120 if (val
.length
=== 8) {
122 } else if (val
.length
=== 19) {
123 time
= val
.split(' ')[1];
125 $timeField
.timeEntry('setTime', time
);
128 $clearLink
.css('visibility', val
? 'visible' : 'hidden');
130 function updateDataField(e
, context
) {
131 // The crmClear event wipes all the field values anyway, so no need to respond
132 if (context
!== 'crmClear') {
134 if ($dateField
.val()) {
135 if (hasDatepicker
&& isValidDate() && dateHasDay()) {
136 val
= $.datepicker
.formatDate('yy-mm-dd', $dateField
.datepicker('getDate'));
137 $dateField
.removeClass('crm-error');
138 } else if (!hasDatepicker
) {
139 val
= $dateField
.val() + '-01-01';
141 else if (!dateHasDay()) {
142 // This would be a Year-month date (yyyy-mm)
143 // it could be argued it should not use a datepicker....
144 val
= $dateField
.val() + '-01';
146 $dateField
.addClass('crm-error');
149 if ($timeField
.val()) {
150 val
+= (val
? ' ' : '') + $timeField
.timeEntry('getTime').toTimeString().substr(0, 8);
152 $dataField
.val(val
).trigger('change', ['userInput']);
155 $dataField
.hide().addClass('crm-hidden-date').on('change.crmDatepicker', updateInputFields
);
160 function isInt(value
) {
164 var x
= parseFloat(value
);
165 return (x
| 0) === x
;
168 })(jQuery
, CRM
, CRM
._
);