UNPKG

@atlassian/aui

Version:

Atlassian User Interface Framework

412 lines (330 loc) 16.2 kB
'use strict'; 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;