@atlassian/aui
Version:
Atlassian User Interface Framework
412 lines (330 loc) • 16.2 kB
JavaScript
import $ from './jquery';
import '../../js-vendor/jquery/jquery-ui/jquery.ui.datepicker';
import * as logger from './internal/log';
import { supportsDateField } from './internal/browser';
import globalize from './internal/globalize';
import InlineDialog from './inline-dialog';
import keyCode from './key-code';
import i18n from './i18n';
var datePickerCounter = 0;
function DatePicker (field, options) {
var datePicker;
var initPolyfill;
var $field;
var datePickerUUID;
var parentPopup;
datePicker = {};
datePickerUUID = datePickerCounter++;
// ---------------------------------------------------------------------
// fix up arguments ----------------------------------------------------
// ---------------------------------------------------------------------
$field = $(field);
$field.attr('data-aui-dp-uuid', datePickerUUID);
options = $.extend(undefined, DatePicker.prototype.defaultOptions, options);
// ---------------------------------------------------------------------
// expose arguments with getters ---------------------------------------
// ---------------------------------------------------------------------
datePicker.getField = function () {
return $field;
};
datePicker.getOptions = function () {
return options;
};
// ---------------------------------------------------------------------
// exposed methods -----------------------------------------------------
// ---------------------------------------------------------------------
initPolyfill = function () {
var calendar;
var handleDatePickerFocus;
var handleFieldBlur;
var handleFieldFocus;
var handleFieldUpdate;
var initCalendar;
var isSuppressingShow;
var isTrackingDatePickerFocus;
var popup;
var popupContents;
// -----------------------------------------------------------------
// expose methods for controlling the popup ------------------------
// -----------------------------------------------------------------
datePicker.hide = function () {
popup.hide();
};
datePicker.show = function () {
popup.show();
};
datePicker.setDate = function (value) {
if (typeof calendar !== 'undefined') {
calendar.datepicker('setDate', value);
}
};
datePicker.getDate = function () {
if (typeof calendar !== 'undefined') {
return calendar.datepicker('getDate');
}
};
// -----------------------------------------------------------------
// initialise the calendar -----------------------------------------
// -----------------------------------------------------------------
initCalendar = function (i18nConfig) {
popupContents.off();
if (options.hint) {
var $hint = $('<div/>').addClass('aui-datepicker-hint');
$hint.append('<span/>').text(options.hint);
popupContents.append($hint);
}
calendar = $('<div/>');
calendar.attr('data-aui-dp-popup-uuid', datePickerUUID);
popupContents.append(calendar);
var config = {
'dateFormat': options.dateFormat,
'defaultDate': $field.val(),
'maxDate': $field.attr('max'),
'minDate': $field.attr('min'),
'nextText': '>',
'onSelect': function (dateText) {
$field.val(dateText);
$field.change();
datePicker.hide();
isSuppressingShow = true;
$field.focus();
options.onSelect && options.onSelect.call(this, dateText);
},
onChangeMonthYear: function () {
// defer rehresh call until current stack has cleared (after month has rendered)
setTimeout(popup.refresh, 0);
},
'prevText': '<'
};
$.extend(config, i18nConfig);
if (options.firstDay > -1) {
config.firstDay = options.firstDay;
}
if (typeof $field.attr('step') !== 'undefined') {
logger.log('WARNING: The date picker polyfill currently does not support the step attribute!');
}
calendar.datepicker(config);
// bind additional field processing events
$('body').on('keydown', handleDatePickerFocus);
$field.on('focusout keydown', handleFieldBlur);
$field.on('propertychange keyup input paste', handleFieldUpdate);
};
// -----------------------------------------------------------------
// event handler wrappers ------------------------------------------
// -----------------------------------------------------------------
handleDatePickerFocus = function (event) {
var $eventTarget = $(event.target);
var isTargetInput = $eventTarget.closest(popupContents).length || $eventTarget.is($field);
var isTargetPopup = $eventTarget.closest('.ui-datepicker-header').length;
// Hide if we're clicking anywhere else but the input or popup OR if esc is pressed.
if ((!isTargetInput && !isTargetPopup) || event.keyCode === keyCode.ESCAPE) {
datePicker.hide();
return;
}
if ($eventTarget[0] !== $field[0]) {
event.preventDefault();
}
};
handleFieldBlur = function () {
// Trigger blur if event is keydown and esc OR is focusout.
if (!(isTrackingDatePickerFocus)) {
$('body').on('focus blur click mousedown', '*', handleDatePickerFocus);
isTrackingDatePickerFocus = true;
}
};
handleFieldFocus = function () {
if (!(isSuppressingShow)) {
datePicker.show();
} else {
isSuppressingShow = false;
}
};
handleFieldUpdate = function () {
var val = $(this).val();
// IE10/11 fire the 'input' event when internally showing and hiding
// the placeholder of an input. This was cancelling the inital click
// event and preventing the selection of the first date. The val check here
// is a workaround to assure we have legitimate user input that should update
// the calendar
if (val) {
calendar.datepicker('setDate', $field.val());
calendar.datepicker('option', {
'maxDate': $field.attr('max'),
'minDate': $field.attr('min')
});
}
};
// -----------------------------------------------------------------
// undo (almost) everything ----------------------------------------
// -----------------------------------------------------------------
datePicker.destroyPolyfill = function () {
// goodbye, cruel world!
datePicker.hide();
$field.attr('placeholder', null);
$field.off('propertychange keyup input paste', handleFieldUpdate);
$field.off('focus click', handleFieldFocus);
$field.off('focusout keydown', handleFieldBlur);
$('body').off('keydown', handleFieldBlur);
if (DatePicker.prototype.browserSupportsDateField) {
$field[0].type = 'date';
}
if (typeof calendar !== 'undefined') {
calendar.datepicker('destroy');
}
// TODO: figure out a way to tear down the popup (if necessary)
delete datePicker.destroyPolyfill;
delete datePicker.show;
delete datePicker.hide;
};
// -----------------------------------------------------------------
// polyfill bootstrap ----------------------------------------------
// -----------------------------------------------------------------
isSuppressingShow = false; // used to stop the popover from showing when focus is restored to the field after a date has been selected
isTrackingDatePickerFocus = false; // used to prevent multiple bindings of handleDatePickerFocus within handleFieldBlur
if (!(options.languageCode in DatePicker.prototype.localisations)) {
options.languageCode = '';
}
var i18nConfig = DatePicker.prototype.localisations;
var containerClass = '';
var width = 240;
if (i18nConfig.size === 'large') {
width = 325;
containerClass = 'aui-datepicker-dialog-large';
}
var dialogOptions = {
'hideCallback': function () {
$('body').off('focus blur click mousedown', '*', handleDatePickerFocus);
isTrackingDatePickerFocus = false;
if (parentPopup && parentPopup._datePickerPopup) {
delete parentPopup._datePickerPopup;
}
},
'hideDelay': null,
'noBind': true,
'persistent': true,
'closeOthers': false,
'width': width
};
if (options.position) {
dialogOptions.calculatePositions = function (popup, targetPosition) {
// create a jQuery object from the internal
var vanilla = $(popup[0]);
return options.position.call(this, vanilla, targetPosition);
}
}
popup = InlineDialog($field, undefined, function (contents, trigger, showPopup) {
if (typeof calendar === 'undefined') {
popupContents = contents;
initCalendar(i18nConfig);
}
parentPopup = $(trigger).closest('.aui-inline-dialog').get(0);
if (parentPopup) {
parentPopup._datePickerPopup = popup; // AUI-2696 - hackish coupling to control inline-dialog close behaviour.
}
showPopup();
}, dialogOptions);
popup.addClass('aui-datepicker-dialog');
popup.addClass(containerClass);
// bind what we need to start off with
$field.on('focus click', handleFieldFocus); // the click is for fucking opera... Y U NO FIRE FOCUS EVENTS PROPERLY???
// give users a hint that this is a date field; note that placeholder isn't technically a valid attribute
// according to the spec...
$field.attr('placeholder', options.dateFormat);
// override the browser's default date field implementation (if applicable)
// since IE doesn't support date input fields, we should be fine...
if (options.overrideBrowserDefault && DatePicker.prototype.browserSupportsDateField) {
$field[0].type = 'text';
//workaround for this issue in Edge: https://connect.microsoft.com/IE/feedback/details/1603512/changing-an-input-type-to-text-does-not-set-the-value
var value = $field[0].getAttribute('value'); //can't use jquery to get the attribute because it doesn't work in Edge
if (value) {
$field[0].value = value;
}
}
};
datePicker.reset = function () {
if (typeof datePicker.destroyPolyfill === 'function') {
datePicker.destroyPolyfill();
}
if ((!(DatePicker.prototype.browserSupportsDateField)) || options.overrideBrowserDefault) {
initPolyfill();
}
};
// ---------------------------------------------------------------------
// bootstrap -----------------------------------------------------------
// ---------------------------------------------------------------------
datePicker.reset();
return datePicker;
};
// -------------------------------------------------------------------------
// things that should be common --------------------------------------------
// -------------------------------------------------------------------------
DatePicker.prototype.browserSupportsDateField = supportsDateField();
DatePicker.prototype.defaultOptions = {
overrideBrowserDefault: false,
firstDay: -1,
languageCode: $('html').attr('lang') || 'en-AU',
dateFormat: $.datepicker.W3C // same as $.datepicker.ISO_8601
};
// adapted from the jQuery UI Datepicker widget (v1.8.16), with the following changes:
// - dayNamesShort -> dayNamesMin
// - unnecessary attributes omitted
/*
CODE to extract codes out:
var langCode, langs, out;
langs = jQuery.datepicker.regional;
out = {};
for (langCode in langs) {
if (langs.hasOwnProperty(langCode)) {
out[langCode] = {
'dayNames': langs[langCode].dayNames,
'dayNamesMin': langs[langCode].dayNamesShort, // this is deliberate
'firstDay': langs[langCode].firstDay,
'isRTL': langs[langCode].isRTL,
'monthNames': langs[langCode].monthNames,
'showMonthAfterYear': langs[langCode].showMonthAfterYear,
'yearSuffix': langs[langCode].yearSuffix
};
}
}
*/
DatePicker.prototype.localisations = {
"dayNames": [i18n.getText('ajs.datepicker.localisations.day-names.sunday'),
i18n.getText('ajs.datepicker.localisations.day-names.monday'),
i18n.getText('ajs.datepicker.localisations.day-names.tuesday'),
i18n.getText('ajs.datepicker.localisations.day-names.wednesday'),
i18n.getText('ajs.datepicker.localisations.day-names.thursday'),
i18n.getText('ajs.datepicker.localisations.day-names.friday'),
i18n.getText('ajs.datepicker.localisations.day-names.saturday')],
"dayNamesMin": [i18n.getText('ajs.datepicker.localisations.day-names-min.sunday'),
i18n.getText('ajs.datepicker.localisations.day-names-min.monday'),
i18n.getText('ajs.datepicker.localisations.day-names-min.tuesday'),
i18n.getText('ajs.datepicker.localisations.day-names-min.wednesday'),
i18n.getText('ajs.datepicker.localisations.day-names-min.thursday'),
i18n.getText('ajs.datepicker.localisations.day-names-min.friday'),
i18n.getText('ajs.datepicker.localisations.day-names-min.saturday')],
"firstDay": i18n.getText('ajs.datepicker.localisations.first-day'),
"isRTL": i18n.getText('ajs.datepicker.localisations.is-RTL') === "true" ? true : false,
"monthNames": [i18n.getText('ajs.datepicker.localisations.month-names.january'),
i18n.getText('ajs.datepicker.localisations.month-names.february'),
i18n.getText('ajs.datepicker.localisations.month-names.march'),
i18n.getText('ajs.datepicker.localisations.month-names.april'),
i18n.getText('ajs.datepicker.localisations.month-names.may'),
i18n.getText('ajs.datepicker.localisations.month-names.june'),
i18n.getText('ajs.datepicker.localisations.month-names.july'),
i18n.getText('ajs.datepicker.localisations.month-names.august'),
i18n.getText('ajs.datepicker.localisations.month-names.september'),
i18n.getText('ajs.datepicker.localisations.month-names.october'),
i18n.getText('ajs.datepicker.localisations.month-names.november'),
i18n.getText('ajs.datepicker.localisations.month-names.december')],
"showMonthAfterYear": i18n.getText('ajs.datepicker.localisations.show-month-after-year') === "true" ? true : false,
"yearSuffix": i18n.getText('ajs.datepicker.localisations.year-suffix')
}
// -------------------------------------------------------------------------
// finally, integrate with jQuery for convenience --------------------------
// -------------------------------------------------------------------------
$.fn.datePicker = function (options) {
return new DatePicker(this, options);
};
globalize('DatePicker', DatePicker);
export default DatePicker;
;