blob: b77be9a2bd1986972646e03bdc008b37fa1d6cd8 [file] [log] [blame]
/*
* Copyright (c) 2014-2018 BSI Business Systems Integration AG.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* BSI Business Systems Integration AG - initial API and implementation
*/
import {
arrays,
DateFormat,
DatePickerPopup,
DatePredictionFailedStatus,
dates,
DateTimeCompositeLayout,
Device,
fields,
focusUtils,
FormField,
HtmlComponent,
InputFieldKeyStrokeContext,
keys,
objects,
ParsingFailedStatus,
scout,
Status,
strings,
styles,
TimePickerPopup,
ValueField
} from '../../../index';
import * as $ from 'jquery';
export default class DateField extends ValueField {
constructor() {
super();
this.popup = null;
this.autoDate = null;
this.dateDisplayText = null;
this.dateHasText = false;
this.dateFocused = false;
this.dateFormatPattern = null;
this.disabledCopyOverlay = true;
this.hasDate = true;
this.oldDisplayText = null;
this.touchMode = false;
this.embedded = false;
this.hasTime = false;
this.hasTimePopup = true;
this.timeDisplayText = null;
this.timeHasText = false;
this.timeDisplayText = null;
this.timePickerResolution = 30;
this.timeFormatPattern = null;
this.timeFocused = false;
this.$dateField = null;
this.$timeField = null;
this.$dateFieldIcon = null;
this.$timeFieldIcon = null;
this.$dateClearIcon = null;
this.$timeClearIcon = null;
this._$predictDateField = null;
this._$predictTimeField = null;
// This is the storage for the time (as date) while the focus in the field (e.g. when
// pressing up/down). In date fields, the date picker is used for that purposes.
this._tempTimeDate = null;
this.invalidValueMessageKey = 'ui.InvalidDate';
this._addCloneProperties(['hasDate', 'hasTime', 'dateFormatPattern', 'timeFormatPattern', 'allowedDates', 'autoDate']);
}
static ErrorCode = {
PARSE_ERROR: -1
};
/**
* Predicate function to find a PARSE_ERROR.
*/
static PARSE_ERROR_PREDICATE = function(status) {
return status.code === DateField.ErrorCode.PARSE_ERROR;
};
/**
* @override Widget.js
*/
_createKeyStrokeContext() {
return new InputFieldKeyStrokeContext();
}
_init(model) {
super._init(model);
fields.initTouch(this, model);
this.popup = model.popup;
this._setAutoDate(this.autoDate);
this._setDisplayText(this.displayText);
this._setAllowedDates(this.allowedDates);
this._setTimePickerResolution(this.timePickerResolution);
}
/**
* Initializes the date format before calling set value.
* This cannot be done in _init because the value field would call _setValue first
*/
_initValue(value) {
this._setDateFormatPattern(this.dateFormatPattern);
this._setTimeFormatPattern(this.timeFormatPattern);
super._initValue(value);
}
createDatePopup() {
var popupType = this.touchMode ? 'DatePickerTouchPopup' : 'DatePickerPopup';
return scout.create(popupType, {
parent: this,
$anchor: this.$field,
boundToAnchor: !this.touchMode,
cssClass: this._errorStatusClass(),
closeOnAnchorMouseDown: false,
field: this,
allowedDates: this.allowedDates,
dateFormat: this.isolatedDateFormat,
displayText: this.dateDisplayText
});
}
createTimePopup() {
var popupType = this.touchMode ? 'TimePickerTouchPopup' : 'TimePickerPopup';
return scout.create(popupType, {
parent: this,
$anchor: this.$timeField,
boundToAnchor: !this.touchMode,
cssClass: this._errorStatusClass(),
closeOnAnchorMouseDown: false,
field: this,
timeResolution: this.timePickerResolution
});
}
_render() {
this.addContainer(this.$parent, 'date-field');
this.addLabel();
this.addField(this.$parent.makeDiv('date-time-composite'));
this.addStatus(this.$field);
if (!this.embedded) {
this.addMandatoryIndicator();
}
this.htmlDateTimeComposite = HtmlComponent.install(this.$field, this.session);
this.htmlDateTimeComposite.setLayout(new DateTimeCompositeLayout(this));
}
_renderProperties() {
this._renderHasDate();
this._renderHasTime();
// Has to be the last call, otherwise _renderErrorStatus() would operate on the wrong state.
super._renderProperties();
this._renderDateHasText();
this._renderTimeHasText();
}
_remove() {
super._remove();
this.$dateField = null;
this.$timeField = null;
this.$dateFieldIcon = null;
this.$timeFieldIcon = null;
this.$dateClearIcon = null;
this.$timeClearIcon = null;
this._$predictDateField = null;
this._$predictTimeField = null;
this.popup = null;
}
setHasDate(hasDate) {
this.setProperty('hasDate', hasDate);
}
_setHasDate(hasDate) {
this._setProperty('hasDate', hasDate);
if (this.initialized) {
// if property changes on the fly, update the display text
this._updateDisplayTextProperty();
}
}
_renderHasDate() {
if (this.hasDate && !this.$dateField) {
// Add $dateField
this.$dateField = fields.makeInputOrDiv(this, 'date')
.on('mousedown', this._onDateFieldMouseDown.bind(this))
.appendTo(this.$field);
if (this.$timeField) {
// make sure date field comes before time field, otherwise tab won't work as expected
this.$dateField.insertBefore(this.$timeField);
}
if (!this.touchMode) {
this.$dateField
.on('keydown', this._onDateFieldKeyDown.bind(this))
.on('input', this._onDateFieldInput.bind(this))
.on('blur', this._onDateFieldBlur.bind(this))
.on('focus', this._onDateFieldFocus.bind(this));
}
this._linkWithLabel(this.$dateField);
HtmlComponent.install(this.$dateField, this.session);
this.$dateFieldIcon = fields.appendIcon(this.$field, 'date')
.on('mousedown', this._onDateIconMouseDown.bind(this));
// avoid fastclick on icon. Otherwise the blur event overtakes the mousedown event.
this.$dateFieldIcon.addClass('needsclick');
} else if (!this.hasDate && this.$dateField) {
// Remove $dateField
this.$dateField.remove();
this.$dateField = null;
this.$dateFieldIcon.remove();
this.$dateFieldIcon = null;
}
if (!this.rendering) {
this._renderDisplayText();
this._renderFieldStyle();
this._renderEnabled();
this.htmlDateTimeComposite.invalidateLayoutTree();
}
this._renderDateClearable();
}
setHasTime(hasTime) {
this.setProperty('hasTime', hasTime);
}
_setHasTime(hasTime) {
this._setProperty('hasTime', hasTime);
if (this.initialized) {
// if property changes on the fly, update the display text
this._updateDisplayTextProperty();
}
}
_renderHasTime() {
if (this.hasTime && !this.$timeField) {
// Add $timeField
this.$timeField = fields.makeInputOrDiv(this, 'time')
.on('mousedown', this._onTimeFieldMouseDown.bind(this))
.appendTo(this.$field);
if (this.$dateField) {
// make sure time field comes after date field, otherwise tab won't work as expected
this.$timeField.insertAfter(this.$dateField);
}
if (!this.touchMode || !this.hasTimePopup) {
this.$timeField
.on('keydown', this._onTimeFieldKeyDown.bind(this))
.on('input', this._onTimeFieldInput.bind(this))
.on('blur', this._onTimeFieldBlur.bind(this))
.on('focus', this._onTimeFieldFocus.bind(this));
}
this._linkWithLabel(this.$timeField);
HtmlComponent.install(this.$timeField, this.session);
this.$timeFieldIcon = fields.appendIcon(this.$field, 'time')
.on('mousedown', this._onTimeIconMouseDown.bind(this));
// avoid fastclick on icon. Otherwise the blur event overtakes the mousedown event.
this.$timeFieldIcon.addClass('needsclick');
} else if (!this.hasTime && this.$timeField) {
// Remove $timeField
this.$timeField.remove();
this.$timeField = null;
this.$timeFieldIcon.remove();
this.$timeFieldIcon = null;
}
if (!this.rendering) {
this._renderDisplayText();
this._renderFieldStyle();
this._renderEnabled();
this.htmlDateTimeComposite.invalidateLayoutTree();
}
this._renderTimeClearable();
}
setTimePickerResolution(timePickerResolution) {
this.setProperty('timePickerResolution', timePickerResolution);
}
_setTimePickerResolution(timePickerResolution) {
if (timePickerResolution < 1) {
// default
timePickerResolution = 10;
this.hasTimePopup = false;
} else {
this.hasTimePopup = true;
}
this._setProperty('timePickerResolution', timePickerResolution);
}
/**
* @override FormField.js
*/
_renderPlaceholder($field) {
super._renderPlaceholder(
this._fieldForPlaceholder());
}
/**
* @override FormField.js
*/
_removePlaceholder($field) {
super._removePlaceholder(
this._fieldForPlaceholder());
}
_fieldForPlaceholder() {
if (this.hasDate) {
return this.$dateField;
} else if (this.hasTime) {
return this.$timeField;
}
return null;
}
setDateFormatPattern(dateFormatPattern) {
this.setProperty('dateFormatPattern', dateFormatPattern);
}
_setDateFormatPattern(dateFormatPattern) {
if (!dateFormatPattern) {
dateFormatPattern = this.session.locale.dateFormatPatternDefault;
}
this._setProperty('dateFormatPattern', dateFormatPattern);
this.isolatedDateFormat = new DateFormat(this.session.locale, this.dateFormatPattern);
if (this.initialized) {
// if format changes on the fly, just update the display text
this._updateDisplayText();
}
}
setTimeFormatPattern(timeFormatPattern) {
this.setProperty('timeFormatPattern', timeFormatPattern);
}
_setTimeFormatPattern(timeFormatPattern) {
if (!timeFormatPattern) {
timeFormatPattern = this.session.locale.timeFormatPatternDefault;
}
this._setProperty('timeFormatPattern', timeFormatPattern);
this.isolatedTimeFormat = new DateFormat(this.session.locale, this.timeFormatPattern);
if (this.initialized) {
// if format changes on the fly, just update the display text
this._updateDisplayText();
}
}
/**
* @override FormField.js
*/
_renderEnabled() {
super._renderEnabled();
this.$container.setEnabled(this.enabledComputed);
if (this.$dateField) {
this.$dateField.setEnabled(this.enabledComputed);
}
if (this.$timeField) {
this.$timeField.setEnabled(this.enabledComputed);
}
// Workaround for IE11 issue: other than all other browsers IE11 doesn't trigger a blur event when the field is disabled.
// In cases where the field is disabled asynchronously, the popup would stay opened by error #240929.
if (Device.get().isInternetExplorer() && !this.enabledComputed) {
this.closePopup();
}
}
/**
* @override ValueField.js
*/
_renderDisplayText() {
if (this.hasDate) {
this._renderDateDisplayText();
}
if (this.hasTime) {
this._renderTimeDisplayText();
}
this._removePredictionFields();
}
_readDisplayText() {
var dateDisplayText, timeDisplayText;
if (this.hasDate) {
dateDisplayText = this._readDateDisplayText();
}
if (this.hasTime) {
timeDisplayText = this._readTimeDisplayText();
}
return this._computeDisplayText(dateDisplayText, timeDisplayText);
}
_renderDateDisplayText() {
fields.valOrText(this.$dateField, this.dateDisplayText);
this._updateDateHasText();
}
_readDateDisplayText() {
return (this._$predictDateField ? fields.valOrText(this._$predictDateField) : fields.valOrText(this.$dateField));
}
_renderTimeDisplayText() {
fields.valOrText(this.$timeField, this.timeDisplayText);
this._updateTimeHasText();
}
_readTimeDisplayText() {
return (this._$predictTimeField ? fields.valOrText(this._$predictTimeField) : fields.valOrText(this.$timeField));
}
/**
* @override
*/
setDisplayText(displayText) {
// Overridden to avoid the equals check -> make sure renderDisplayText is executed whenever setDisplayText is called
// Reason: key up/down and picker day click modify the display text, but input doesn't
// -> reverting to a date using day click or up down after the input changed would not work anymore
// changing 'onXyInput' to always update the display text would fix that, but would break acceptInput
this._setDisplayText(displayText);
if (this.rendered) {
this._renderDisplayText();
}
}
_setDisplayText(displayText) {
this.oldDisplayText = this.displayText;
this._setProperty('displayText', displayText);
var parts = this._splitDisplayText(displayText);
if (this.hasDate) {
// preserve dateDisplayText if hasDate is set to false (only override if it is true)
this.dateDisplayText = parts.dateText;
}
if (this.hasTime) {
// preserve timeDisplayText if hasTime is set to false (only override if it is true)
this.timeDisplayText = parts.timeText;
}
}
/**
* @override
*/
_ensureValue(value) {
return dates.ensure(value);
}
/**
* @param {Date} value the date to validate
* @return {Date} the validated date
* @override
*/
_validateValue(value) {
if (objects.isNullOrUndefined(value)) {
return value;
}
if (!(value instanceof Date)) {
throw this.session.text(this.invalidValueMessageKey);
}
if (!this.hasDate && !this.value) {
// truncate to 01.01.1970 if no date was entered before. Otherwise preserve date part (important for toggling hasDate on the fly)
value = dates.combineDateTime(null, value);
}
return value;
}
_valueEquals(valueA, valueB) {
return dates.equals(valueA, valueB);
}
setAutoDate(autoDate) {
this.setProperty('autoDate', autoDate);
}
_setAutoDate(autoDate) {
autoDate = dates.ensure(autoDate);
this._setProperty('autoDate', autoDate);
}
_setAllowedDates(allowedDates) {
if (Array.isArray(allowedDates)) {
allowedDates = allowedDates.map(function(date) {
return dates.ensure(date);
});
this._setProperty('allowedDates', allowedDates);
} else {
this._setProperty('allowedDates', null);
}
}
/**
* @override FormField.js
*/
_renderErrorStatus() {
super._renderErrorStatus();
var hasStatus = !!this.errorStatus,
statusClass = this._errorStatusClass();
if (this.$dateField) {
this._updateErrorStatusClassesOnElement(this.$dateField, statusClass, hasStatus);
// Because the error color of field icons depends on the error status of sibling <input> elements.
// The prediction fields are clones of the input fields, so the 'has-error' class has to be
// removed from them as well to make the icon "valid".
this._updateErrorStatusClassesOnElement(this._$predictDateField, statusClass, hasStatus);
}
// Do the same for the time field
if (this.$timeField) {
this._updateErrorStatusClassesOnElement(this.$timeField, statusClass, hasStatus);
this._updateErrorStatusClassesOnElement(this._$predictTimeField, statusClass, hasStatus);
}
if (this.popup) {
this._updateErrorStatusClassesOnElement(this.popup.$container, statusClass, hasStatus);
}
}
_errorStatusClass() {
return (this.errorStatus && !this._isSuppressStatusField()) ? 'has-' + this.errorStatus.cssClass() : '';
}
/**
* @Override FormField.js
*/
_renderFont() {
this.$dateField && styles.legacyFont(this, this.$dateField);
this.$timeField && styles.legacyFont(this, this.$timeField);
}
/**
* @Override FormField.js
*/
_renderForegroundColor() {
this.$dateField && styles.legacyForegroundColor(this, this.$dateField);
this.$timeField && styles.legacyForegroundColor(this, this.$timeField);
}
/**
* @override FormField.js
*/
_renderBackgroundColor() {
this.$dateField && styles.legacyBackgroundColor(this, this.$dateField);
this.$timeField && styles.legacyBackgroundColor(this, this.$timeField);
}
/**
* @override
*/
activate() {
if (!this.enabledComputed || !this.rendered) {
return;
}
if (this.$dateField) {
this.$dateField.focus();
this._onDateFieldMouseDown();
} else if (this.$timeField) {
this.$timeField.focus();
this._onTimeFieldMouseDown();
}
}
/**
* @override
*/
getFocusableElement() {
if (this.$dateField) {
return this.$dateField;
}
if (this.$timeField) {
return this.$timeField;
}
return null;
}
_onDateFieldMouseDown() {
if (fields.handleOnClick(this)) {
this.openDatePopupAndSelect(this.value);
}
}
_onTimeFieldMouseDown() {
if (fields.handleOnClick(this)) {
this.openTimePopupAndSelect(this.value);
}
}
setDateFocused(dateFocused) {
this.setProperty('dateFocused', dateFocused);
}
_renderDateFocused() {
this.$container.toggleClass('date-focused', this.dateFocused);
}
_updateTimeHasText() {
this.setTimeHasText(strings.hasText(this._readTimeDisplayText()));
}
setTimeHasText(timeHasText) {
this.setProperty('timeHasText', timeHasText);
}
_renderTimeHasText() {
if (this.$timeField) {
this.$timeField.toggleClass('has-text', this.timeHasText);
}
this.$container.toggleClass('time-has-text', this.timeHasText);
}
_updateDateHasText() {
this.setDateHasText(strings.hasText(this._readDateDisplayText()));
}
setDateHasText(dateHasText) {
this.setProperty('dateHasText', dateHasText);
}
_renderDateHasText() {
if (this.$dateField) {
this.$dateField.toggleClass('has-text', this.dateHasText);
}
this.$container.toggleClass('date-has-text', this.dateHasText);
}
/**
* @override
*/
clear() {
if (!(this.hasDate && this.hasTime)) {
super.clear();
return;
}
this._clear();
// If field shows date and time, don't accept input while one field has the focus
// Reason: x icon is shown in one field, pressing that icon should clear the content of that field.
// Accept input would set the value to '', thus clearing both fields which may be unexpected.
if (!this.dateFocused && !this.timeFocused) {
this.acceptInput();
}
this._triggerClear();
}
_clear() {
this._removePredictionFields();
if (this.hasDate && !this.timeFocused) {
fields.valOrText(this.$dateField, '');
this._setDateValid(true);
this._updateDateHasText();
}
if (this.hasTime && !this.dateFocused) {
fields.valOrText(this.$timeField, '');
this._setTimeValid(true);
this._updateTimeHasText();
}
}
_onDateClearIconMouseDown(event) {
if (!this.enabledComputed) {
return;
}
this.$dateField.focus();
this.clear();
if (this.value) {
this.selectDate(this.value, false);
} else {
this.preselectDate(this._referenceDate(), false);
}
event.preventDefault();
}
_onDateIconMouseDown(event) {
if (!this.enabledComputed) {
return;
}
this.$dateField.focus();
if (!this.embedded) {
this.openDatePopupAndSelect(this.value);
}
}
setTimeFocused(timeFocused) {
this.setProperty('timeFocused', timeFocused);
}
_renderTimeFocused() {
this.$container.toggleClass('time-focused', this.timeFocused);
}
_renderClearable() {
this._renderDateClearable();
this._renderTimeClearable();
this._updateClearableStyles();
}
_renderDateClearable() {
if (this.hasDate || this.isClearable()) {
if (!this.$dateClearIcon) {
// date clear icon
this.$dateClearIcon = this.$field.appendSpan('icon date-clear unfocusable')
.on('mousedown', this._onDateClearIconMouseDown.bind(this));
// avoid fastclick on icon. Otherwise the blur event overtakes the mousedown event.
this.$dateClearIcon.addClass('needsclick');
}
} else {
if (this.$dateClearIcon) {
// Remove clear icon
this.$dateClearIcon.remove();
this.$dateClearIcon = null;
}
}
}
_renderTimeClearable() {
if (this.hasTime && this.isClearable() && !this.$timeClearIcon) {
// date clear icon
this.$timeClearIcon = this.$field.appendSpan('icon time-clear unfocusable')
.on('mousedown', this._onTimeClearIconMouseDown.bind(this));
// avoid fastclick on icon. Otherwise the blur event overtakes the mousedown event.
this.$timeClearIcon.addClass('needsclick');
} else if ((!this.hasTime || !this.isClearable()) && this.$timeClearIcon) {
// Remove $dateField
this.$timeClearIcon.remove();
this.$timeClearIcon = null;
}
}
_onTimeClearIconMouseDown(event) {
if (!this.enabledComputed) {
return;
}
this.$timeField.focus();
this.clear();
if (this.value) {
this.selectTime(this.value, false);
} else {
this.preselectTime(this._referenceDate(), false);
}
event.preventDefault();
}
_onTimeIconMouseDown(event) {
if (!this.enabledComputed) {
return;
}
this.$timeField.focus();
if (!this.embedded) {
this.openTimePopupAndSelect(this.value);
}
}
_onDateFieldBlur(event) {
this.setFocused(false);
this.setDateFocused(false);
if (this.embedded) {
// Don't execute, otherwise date would be accepted even though touch popup is still open.
// This prevents following behavior: user clears date by pressing x and then selects another date. Now a blur event is triggered which would call acceptDate and eventually remove the time
// -> Don't accept as long as touch dialog is open
return;
}
// Close picker and update model
if (this.popup instanceof DatePickerPopup) {
// in embedded mode we must update the date prediction but not close the popup (don't accidentally close time picker popup)
this.closePopup();
}
this.setDateFocused(false);
this.acceptDate();
this._removePredictionFields();
}
_onDateFieldFocus(event) {
this.setFocused(true);
this.setDateFocused(true);
}
_onTimeFieldBlur(event) {
this._tempTimeDate = null;
this.setFocused(false);
this.setTimeFocused(false);
if (this.embedded) {
// Don't execute, otherwise time would be accepted even though touch popup is still open.
// This prevents following behavior: user clears time by pressing x and then selects another time. Now a blur event is triggered which would call acceptTime and eventually remove the date
// -> Don't accept as long as touch dialog is open
return;
}
// Close picker and update model
if (this.popup instanceof TimePickerPopup) {
// in embedded mode we must update the date prediction but not close the popup
this.closePopup();
}
this._tempTimeDate = null;
this.setTimeFocused(false);
this.acceptTime();
this._removePredictionFields();
}
_onTimeFieldFocus() {
this.setFocused(true);
this.setTimeFocused(true);
}
/**
* Handle "navigation" keys, i.e. keys that don't emit visible characters. Character input is handled
* in _onDateFieldInput(), which is fired after 'keydown'.
*/
_onDateFieldKeyDown(event) {
var delta = 0,
diffYears = 0,
diffMonths = 0,
diffDays = 0,
cursorPos = this.$dateField[0].selectionStart,
displayText = fields.valOrText(this.$dateField),
prediction = this._$predictDateField && fields.valOrText(this._$predictDateField),
modifierCount = (event.ctrlKey ? 1 : 0) + (event.shiftKey ? 1 : 0) + (event.altKey ? 1 : 0) + (event.metaKey ? 1 : 0),
pickerStartDate = this.value || this._referenceDate(),
shiftDate = true;
// Don't propagate tab to cell editor -> tab should focus time field
if (this.hasTime &&
this.mode === FormField.Mode.CELLEDITOR &&
event.which === keys.TAB &&
modifierCount === 0) {
event.stopPropagation();
return;
}
if (event.which === keys.TAB ||
event.which === keys.SHIFT ||
event.which === keys.HOME ||
event.which === keys.END ||
event.which === keys.CTRL ||
event.which === keys.ALT) {
// Default handling
return;
}
if (event.which === keys.ENTER) {
if (this.popup || this._$predictDateField) {
// Close the picker and accept the current prediction (if available)
this.acceptDate();
this.closePopup();
$.suppressEvent(event);
}
return;
}
if (event.which === keys.ESC) {
if (this.popup) {
// Close the picker, but don't do anything else
this.closePopup();
$.suppressEvent(event);
}
return;
}
if (event.which === keys.RIGHT && cursorPos === displayText.length) {
// Move cursor one right and apply next char of the prediction
if (prediction) {
this._setDateDisplayText(prediction.substring(0, displayText.length + 1));
}
return;
}
if (event.which === keys.UP || event.which === keys.DOWN ||
event.which === keys.PAGE_UP || event.which === keys.PAGE_DOWN) {
if (displayText && !this._isDateValid()) {
// If there is an error, try to parse the date. If it may be parsed, the error was likely a validation error.
// In that case use the parsed date as starting point and not the for the user invisible value
var parsedValue = this.isolatedDateFormat.parse(displayText, pickerStartDate);
if (parsedValue) {
pickerStartDate = parsedValue;
this._setDateValid(true);
}
}
}
if (event.which === keys.PAGE_UP || event.which === keys.PAGE_DOWN) {
if (!displayText || !this._isDateValid()) {
// If input is empty or invalid, set picker to reference date
pickerStartDate = this._referenceDate();
if (this.hasTime) { // keep time part
pickerStartDate = dates.combineDateTime(pickerStartDate, this.value || this._referenceDate());
}
this.openDatePopupAndSelect(pickerStartDate);
this._updateDisplayText(pickerStartDate);
this._setDateValid(true);
shiftDate = false; // don't shift if field has no value yet and popup was not open
} else if (!this.popup) {
// Otherwise, ensure picker is open
this.openDatePopupAndSelect(pickerStartDate);
}
if (shiftDate) {
diffMonths = (event.which === keys.PAGE_UP ? -1 : 1);
this.shiftSelectedDate(0, diffMonths, 0);
this._updateDisplayText(this.getDatePicker().selectedDate);
}
$.suppressEvent(event);
return;
}
if (event.which === keys.UP || event.which === keys.DOWN) {
delta = (event.which === keys.UP ? -1 : 1);
// event.ctrlKey || event.metaKey --> some keystrokes with ctrl modifier are captured and suppressed by osx, use command key instead
if ((event.ctrlKey || event.metaKey) && modifierCount === 1) { // only ctrl
diffYears = delta;
} else if (event.shiftKey && modifierCount === 1) { // only shift
diffMonths = delta;
} else if (modifierCount === 0) { // no modifier
diffDays = delta;
} else {
// Unsupported modifier or too many modifiers
$.suppressEvent(event);
return;
}
if (!displayText || !this._isDateValid()) {
// If input is empty or invalid, set picker to reference date
pickerStartDate = this._referenceDate();
if (this.hasTime) { // keep time part
pickerStartDate = dates.combineDateTime(pickerStartDate, this.value || this._referenceDate());
}
this.openDatePopupAndSelect(pickerStartDate);
this._updateDisplayText(pickerStartDate);
this._setDateValid(true);
shiftDate = false; // don't shift if field has no value yet and popup was not open
} else if (!this.popup) {
// Otherwise, ensure picker is open
this.openDatePopupAndSelect(pickerStartDate);
}
if (shiftDate) {
this.shiftSelectedDate(diffYears, diffMonths, diffDays);
this._updateDisplayText(this.getDatePicker().selectedDate);
}
$.suppressEvent(event);
}
}
/**
* Handle changed input. This method is fired when the field's content has been altered by a user
* action (not by JS) such as pressing a character key, deleting a character using DELETE or
* BACKSPACE, cutting or pasting text with ctrl-x / ctrl-v or mouse drag'n'drop.
* Keys that don't alter the content (e.g. modifier keys, arrow keys, home, end etc.) are handled
* in _onDateFieldKeyDown().
*/
_onDateFieldInput(event) {
var displayText = fields.valOrText(this.$dateField);
// If the focus has changed to another field in the meantime, don't predict anything and
// don't show the picker. Just validate the input.
if (this.$dateField[0] !== this.$dateField.activeElement(true)) {
return;
}
// Create $predictDateField if necessary
if (!this._$predictDateField) {
this._$predictDateField = this._createPredictionField(this.$dateField);
}
// Predict date
this._removePredictErrorStatus();
var datePrediction = this._predictDate(displayText); // this also updates the errorStatus
if (datePrediction) {
fields.valOrText(this._$predictDateField, datePrediction.text);
this.openDatePopupAndSelect(datePrediction.date);
} else {
// No valid prediction!
this._removePredictionFields();
}
this._updateDateHasText();
// Hide the prediction field if input field is scrolled to the left. Otherwise, the
// two fields would not be aligned correctly, which looks bad. This can only happen
// when the fields are rather small, so the prediction would be of limited use anyway.
// Unfortunately, most browsers don't fire 'scroll' events for input fields. Also,
// when the 'input' even is fired, the scrollLeft() position sometimes has not been
// updated yet, that's why we must use setTimeout() with a short delay.
setTimeout(function() {
if (this._$predictDateField) {
this._$predictDateField.setVisible(this.$dateField.scrollLeft() === 0);
}
}.bind(this), 50);
}
acceptInput() {
var displayText = scout.nvl(this._readDisplayText(), '');
var inputChanged = this._checkDisplayTextChanged(displayText);
if (inputChanged) {
this.parseAndSetValue(displayText);
} else {
var oldValue = this.value;
this.parseAndSetValue(displayText);
if (!dates.equals(this.value, oldValue)) {
inputChanged = true;
}
}
if (inputChanged) {
this._triggerAcceptInput();
}
}
/**
* Clears the time field if date field is empty before accepting the input.<br/>
* Don't delete invalid input from the time field.
*/
acceptDate() {
var invalid = this.containsStatus(ParsingFailedStatus);
if (this.hasTime && !invalid && strings.empty(this.$dateField.val())) {
this.$timeField.val('');
}
this.acceptInput();
}
/**
* Clears the date field if time field is empty before accepting the input.<br/>
* Don't delete invalid input from the time field.
*/
acceptTime() {
var invalid = this.containsStatus(ParsingFailedStatus);
if (this.hasDate && !invalid && strings.empty(this.$timeField.val())) {
this.$dateField.val('');
}
this.acceptInput();
}
acceptDateTime(acceptDate, acceptTime) {
if (acceptDate) {
this.acceptDate();
} else if (acceptTime) {
this.acceptTime();
}
}
/**
* Handle "navigation" keys, i.e. keys that don't emit visible characters. Character input is handled
* in _onTimeFieldInput(), which is fired after 'keydown'.
*/
_onTimeFieldKeyDown(event) {
var delta = 0,
diffHours = 0,
diffMinutes = 0,
diffSeconds = 0,
cursorPos = this.$timeField[0].selectionStart,
displayText = this.$timeField.val(),
prediction = this._$predictTimeField && this._$predictTimeField.val(),
modifierCount = (event.ctrlKey ? 1 : 0) + (event.shiftKey ? 1 : 0) + (event.altKey ? 1 : 0) + (event.metaKey ? 1 : 0),
pickerStartTime = this.value || this._referenceDate(),
shiftTime = true;
// Don't propagate shift-tab to cell editor -> shift tab should focus date field
if (this.hasDate &&
this.mode === FormField.Mode.CELLEDITOR &&
event.which === keys.TAB &&
event.shiftKey &&
modifierCount === 1) {
event.stopPropagation();
return;
}
if (event.which === keys.TAB ||
event.which === keys.SHIFT ||
event.which === keys.HOME ||
event.which === keys.END ||
event.which === keys.CTRL ||
event.which === keys.ALT) {
// Default handling
return;
}
if (event.which === keys.ENTER) {
// Timefield is shown in touch popup, so we need to make sure time gets accepted and popup closed, even if the regular time field itself has no popup
if (this.popup || this._$predictDateField) {
// Accept the current prediction (if available)
this._tempTimeDate = null;
this.acceptTime();
this.closePopup();
$.suppressEvent(event);
}
return;
}
if (event.which === keys.ESC) {
if (this.popup) {
// Close the picker, but don't do anything else
this.closePopup();
$.suppressEvent(event);
}
return;
}
if (event.which === keys.RIGHT && cursorPos === displayText.length) {
// Move cursor one right and apply next char of the prediction
if (prediction) {
this._setTimeDisplayText(prediction.substring(0, displayText.length + 1));
}
return;
}
if (event.which === keys.UP || event.which === keys.DOWN) {
delta = (event.which === keys.UP ? -1 : 1);
if (event.ctrlKey && modifierCount === 1) { // only ctrl
diffSeconds = delta;
} else if (event.shiftKey && modifierCount === 1) { // only shift
diffHours = delta;
} else if (modifierCount === 0) { // no modifier
diffMinutes = delta;
} else {
// Unsupported modifier or too many modifiers
$.suppressEvent(event);
return;
}
if (this.hasTimePopup) {
if (!displayText || !this._isTimeValid()) {
// If input is empty or invalid, set picker to reference date
pickerStartTime = this._referenceDate();
this.openTimePopupAndSelect(pickerStartTime);
this._updateDisplayText(pickerStartTime);
this._setTimeValid(true);
shiftTime = false; // don't shift if field has no value yet and popup was not open
} else if (!this.popup) {
// Otherwise, ensure picker is open
this.openTimePopupAndSelect(pickerStartTime);
}
if (shiftTime) {
this.shiftSelectedTime(diffHours, diffMinutes, diffSeconds);
this._updateDisplayText(this.getTimePicker().selectedTime);
}
$.suppressEvent(event);
} else {
// without picker
if (!this._tempTimeDate) {
var timePrediction = this._predictTime(displayText); // this also updates the errorStatus
if (timePrediction && timePrediction.date) {
this._tempTimeDate = timePrediction.date;
} else {
this._tempTimeDate = this._referenceDate();
shiftTime = false;
}
}
if (shiftTime) {
this._tempTimeDate = dates.shiftTime(this._tempTimeDate, diffHours, diffMinutes, diffSeconds);
}
if (this.hasDate) {
// Combine _tempTimeDate with existing date part
this._tempTimeDate = dates.combineDateTime(this.value || this._referenceDate(), this._tempTimeDate);
}
this._updateDisplayText(this._tempTimeDate);
this._setTimeValid(true);
$.suppressEvent(event);
}
}
}
/**
* Handle changed input. This method is fired when the field's content has been altered by a user
* action (not by JS) such as pressing a character key, deleting a character using DELETE or
* BACKSPACE, cutting or pasting text with ctrl-x / ctrl-v or mouse drag'n'drop.
* Keys that don't alter the content (e.g. modifier keys, arrow keys, home, end etc.) are handled
* in _onTimeFieldKeyDown().
*/
_onTimeFieldInput(event) {
var displayText = this.$timeField.val();
// If the focus has changed to another field in the meantime, don't predict anything and
// don't show the picker. Just validate the input.
if (this.$timeField[0] !== this.$timeField.activeElement(true)) {
return;
}
// Create $predictTimeField if necessary
if (!this._$predictTimeField) {
this._$predictTimeField = this._createPredictionField(this.$timeField);
}
// Predict time
var timePrediction = this._predictTime(displayText); // this also updates the errorStatus
if (timePrediction) {
this._$predictTimeField.val(timePrediction.text);
this.openTimePopupAndSelect(timePrediction.date);
} else {
// No valid prediction!
this._tempTimeDate = null;
this._removePredictionFields();
}
this._updateTimeHasText();
// See comment for similar code in _onDateFieldInput()
setTimeout(function() {
if (this._$predictTimeField) {
this._$predictTimeField.setVisible(this.$timeField.scrollLeft() === 0);
}
}.bind(this), 50);
}
_onDatePickerDateSelect(event) {
this._setDateValid(true);
this._setTimeValid(true);
var newValue = this._newTimestampAsDate(event.date, this.value);
this.setValue(newValue);
this.closePopup();
this._triggerAcceptInput();
}
_onTimePickerTimeSelect(event) {
this._setDateValid(true);
this._setTimeValid(true);
var newValue = this._newTimestampAsDate(this.value, event.time);
this.setValue(newValue);
this.closePopup();
this._triggerAcceptInput();
}
_createPredictionField($inputField) {
this.setSuppressStatus(FormField.SuppressStatus.ALL);
var $predictionField = $inputField.clone()
.addClass('predict')
.attr('tabIndex', '-1')
.insertBefore($inputField);
if ($inputField.hasClass('has-error')) {
$predictionField.addClass('has-error');
}
return $predictionField;
}
_removePredictionFields() {
this.setSuppressStatus(null);
if (this._$predictDateField) {
this._$predictDateField.remove();
this._$predictDateField = null;
}
if (this._$predictTimeField) {
this._$predictTimeField.remove();
this._$predictTimeField = null;
}
}
_setDateDisplayText(displayText) {
this.dateDisplayText = displayText;
this._updateDisplayTextProperty();
if (this.rendered) {
this._renderDateDisplayText();
}
}
_setTimeDisplayText(displayText) {
this.timeDisplayText = displayText;
this._updateDisplayTextProperty();
if (this.rendered) {
this._renderTimeDisplayText();
}
}
_computeDisplayText(dateDisplayText, timeDisplayText) {
var dateText = dateDisplayText || '',
timeText = timeDisplayText || '';
// do not use strings.join which ignores empty components
var displayText = (this.hasDate ? dateText : '') + (this.hasDate && this.hasTime ? '\n' : '') + (this.hasTime ? timeText : '');
// empty display text should always be just an empty string
if (displayText === '\n') {
displayText = '';
}
return displayText;
}
_splitDisplayText(displayText) {
var dateText = '',
timeText = '';
if (strings.hasText(displayText)) {
var parts = displayText.split('\n');
dateText = this.hasDate ? parts[0] : '';
timeText = this.hasTime ? (this.hasDate ? parts[1] : parts[0]) : '';
}
return {
dateText: dateText,
timeText: timeText
};
}
_updateDisplayTextProperty() {
this._setProperty('displayText', this._computeDisplayText(this.dateDisplayText, this.timeDisplayText));
}
/**
* @override ValueField.js
*/
aboutToBlurByMouseDown(target) {
var dateFieldActive, timeFieldActive, eventOnDatePicker, eventOnTimePicker,
eventOnDateField = this.$dateField ? (this.$dateField.isOrHas(target) || this.$dateFieldIcon.isOrHas(target) || (this.$dateClearIcon && this.$dateClearIcon.isOrHas(target))) : false,
eventOnTimeField = this.$timeField ? (this.$timeField.isOrHas(target) || this.$timeFieldIcon.isOrHas(target) || (this.$timeClearIcon && this.$timeClearIcon.isOrHas(target))) : false,
eventOnPopup = this.popup && this.popup.$container.isOrHas(target),
eventOnStatus = this.fieldStatus && this.fieldStatus.$container.isOrHas(target),
datePicker = this.getDatePicker(),
timePicker = this.getTimePicker();
if (!eventOnDateField && !eventOnTimeField && !eventOnPopup && !eventOnStatus) {
// event outside this field.
dateFieldActive = focusUtils.isActiveElement(this.$dateField);
timeFieldActive = focusUtils.isActiveElement(this.$timeField);
// Accept only the currently focused part (the other one cannot have a pending change)
this.acceptDateTime(dateFieldActive, timeFieldActive);
return;
}
// when date-field is embedded, time-prediction must be accepted before
// the date-picker triggers the 'dateSelect' event.
if (this.embedded) {
eventOnDatePicker = datePicker && datePicker.$container.isOrHas(target);
eventOnTimePicker = timePicker && timePicker.$container.isOrHas(target);
if (eventOnDatePicker && eventOnTimePicker) {
this.acceptTime();
}
}
}
/**
* Returns null if both arguments are not set. Otherwise, this.value or the current date
* is used as basis and the given arguments are applied to that date. The result is returned.
*/
_newTimestampAsDate(date, time) {
var result = null;
if (date || time) {
result = this.value || this._referenceDate();
if (date) {
result = dates.combineDateTime(date, result);
}
if (time) {
result = dates.combineDateTime(result, time);
}
}
return result;
}
/**
* Returns the reference date for this date field, which is used in various places (i.e. opening the date picker, analyzing user inputs).
*
* The reference date is either (in that order):
* - the model's "auto timestamp" (as date), or
* - the current date/time
*/
_referenceDate() {
var referenceDate = this.autoDate || dates.ceil(dates.newDate(), this.timePickerResolution);
if (this.autoDate) {
referenceDate = this.autoDate;
} else if (this.hasTime) {
referenceDate = dates.ceil(dates.newDate(), this.timePickerResolution);
} else {
referenceDate = dates.trunc(dates.newDate());
}
if (this.allowedDates) {
referenceDate = this._findAllowedReferenceDate(referenceDate);
}
return referenceDate;
}
/**
* Find nearest allowed date which is equals or greater than the current referenceDate.
*/
_findAllowedReferenceDate(referenceDate) {
var i, allowedDate;
// 1st: try to find a date which is equals or greater than the referenceDate (today)
for (i = 0; i < this.allowedDates.length; i++) {
allowedDate = this.allowedDates[i];
if (dates.compare(allowedDate, referenceDate) >= 0) {
return allowedDate;
}
}
// 2nd: try to find an allowed date in the past
for (i = this.allowedDates.length - 1; i >= 0; i--) {
allowedDate = this.allowedDates[i];
if (dates.compare(allowedDate, referenceDate) <= 0) {
return allowedDate;
}
}
return referenceDate;
}
openDatePopup(date) {
if (this.popup) {
// already open
return;
}
this.popup = this.createDatePopup();
this.popup.open();
this.$dateField.addClass('focused');
this.popup.on('remove', function(event) {
this._onPopupRemove(event);
this.popup = null;
this.$dateField.removeClass('focused');
}.bind(this));
this.getDatePicker().on('dateSelect', this._onDatePickerDateSelect.bind(this));
}
closePopup() {
if (this.popup) {
this.popup.close();
}
}
toggleDatePopup() {
$.log.isInfoEnabled() && $.log.info('(DateField#toggleDatePopup) popupOpen=', !!this.popup);
if (this.popup) {
this.closePopup();
} else {
this.openDatePopupAndSelect(this.value);
}
}
openTimePopup(date) {
if (!this.hasTimePopup || this.popup) {
// already open
return;
}
this.popup = this.createTimePopup();
this.popup.open();
this.$timeField.addClass('focused');
this.popup.on('remove', function(event) {
this._onPopupRemove(event);
this.popup = null;
this.$timeField.removeClass('focused');
}.bind(this));
this.getTimePicker().on('timeSelect', this._onTimePickerTimeSelect.bind(this));
}
toggleTimePopup() {
$.log.isInfoEnabled() && $.log.info('(DateField#toggleTimePopup) popupOpen=', !!this.popup);
if (this.popup) {
this.closePopup();
} else {
this.openTimePopupAndSelect(this.value);
}
}
_parseValue(displayText) {
var parts = this._splitDisplayText(displayText);
var dateText = parts.dateText;
var datePrediction = {};
var timeText = parts.timeText;
var timePrediction = {};
var success = true;
this._removePredictErrorStatus();
if (this.hasDate) {
datePrediction = this._predictDate(dateText); // this also updates the errorStatus
if (!datePrediction) {
success = false;
}
this._setDateDisplayText(dateText);
}
if (this.hasTime) {
timePrediction = this._predictTime(timeText); // this also updates the errorStatus
if (!timePrediction) {
success = false;
}
this._setTimeDisplayText(timeText);
}
// Error status was already set by _predict functions, throw this typed Status so DateField knows it must
// not set the error status again when the parse error is catched
if (!success) {
throw new DatePredictionFailedStatus();
}
// parse success -> return new value
if (datePrediction.date || timePrediction.date) {
return this._newTimestampAsDate(datePrediction.date, timePrediction.date);
}
return null;
}
/**
* Don't add error if it is a DatePredictionFailedStatus because the predict function
* has already added a parsing error.
*/
_addParsingFailedErrorStatus(displayText, error) {
if (error instanceof DatePredictionFailedStatus) {
return;
}
super._addParsingFailedErrorStatus(displayText, error);
}
/**
* @returns null if input is invalid, otherwise an object with properties 'date' and 'text'
*/
_predictDate(inputText) {
inputText = inputText || '';
// "Date calculations"
var m = inputText.match(/^([+-])(\d*)$/);
if (m) {
var now = dates.newDate();
var daysToAdd = Number(m[1] + (m[2] || '0'));
now.setDate(now.getDate() + daysToAdd);
if (isNaN(now.valueOf()) || now.getDate() < 0) { // Some older browsers don't set NaN but return invalid values
this._setDateValid(false);
return null;
}
this._setDateValid(true);
return {
date: now,
text: inputText
};
}
var analyzeInfo = this._analyzeInputAsDate(inputText, this.value || this._referenceDate());
if (analyzeInfo.error) {
this._setDateValid(false);
return null;
}
// No predicted date? -> return empty string (may happen if inputText is empty)
if (!analyzeInfo.predictedDate) {
this._setDateValid(true);
return {
date: null,
text: ''
};
}
var predictedDate = analyzeInfo.predictedDate;
var predictionFormat = new DateFormat(this.isolatedDateFormat.locale, analyzeInfo.parsedPattern);
var predictedDateFormatted = predictionFormat.format(predictedDate, true);
// If predicted date format starts with validatedText, ensure that the capitalization matches.
// Example: input = 'frid', predicted = 'Friday, 1.10.2014' --> return 'friday, 1.10.2014')
m = predictedDateFormatted.match(new RegExp('^' + strings.quote(inputText) + '(.*)$', 'i'));
if (m) {
predictedDateFormatted = inputText + m[1];
}
this._setDateValid(true);
return {
date: predictedDate,
text: predictedDateFormatted
};
}
/**
* @returns null if input is invalid, otherwise an object with properties 'date' and 'text'
*/
_predictTime(inputText) {
inputText = inputText || '';
var analyzeInfo = this._analyzeInputAsTime(inputText, this.value || this._referenceDate());
if (analyzeInfo.error) {
this._setTimeValid(false);
return null;
}
// No predicted date? -> return empty string (may happen if inputText is empty)
if (!analyzeInfo.predictedDate) {
this._setTimeValid(true);
return {
date: null,
text: ''
};
}
var predictedDate = analyzeInfo.predictedDate;
var predictionFormat = new DateFormat(this.isolatedTimeFormat.locale, analyzeInfo.parsedPattern);
var predictedTimeFormatted = predictionFormat.format(predictedDate, true);
// If predicted date format starts with validatedText, ensure that the capitalization matches.
// Example: input = 'frid', predicted = 'Friday, 1.10.2014' --> return 'friday, 1.10.2014')
var m = predictedTimeFormatted.match(new RegExp('^' + strings.quote(inputText) + '(.*)$', 'i'));
if (m) {
predictedTimeFormatted = inputText + m[1];
}
this._setTimeValid(true);
return {
date: predictedDate,
text: predictedTimeFormatted
};
}
_analyzeInputAsDate(inputText, startDate) {
return this.isolatedDateFormat.analyze(inputText, startDate);
}
_analyzeInputAsTime(inputText, startDate) {
return this.isolatedTimeFormat.analyze(inputText, startDate);
}
/**
* This method updates the parts (date, time) of the error status.
*/
_setErrorStatusPart(property, valid) {
// check if date/time error exists
var status = null;
var storedStatus = null;
if (this.errorStatus) {
storedStatus = arrays.find(this.errorStatus.asFlatList(), DateField.PARSE_ERROR_PREDICATE);
}
if (storedStatus) {
status = storedStatus;
} else {
status = new ParsingFailedStatus({
message: this.session.text('ui.InvalidDate'),
severity: Status.Severity.ERROR,
code: DateField.ErrorCode.PARSE_ERROR
});
}
if (valid) {
delete status[property];
} else {
status[property] = true;
}
if (!status.hasOwnProperty('invalidDate') && !status.hasOwnProperty('invalidTime')) {
status = null;
}
if (status && !storedStatus) {
this.addErrorStatus(status);
} else if (!status && storedStatus) {
this._removePredictErrorStatus();
} // else: just update existing error
}
_removePredictErrorStatus() {
this.removeErrorStatusByPredicate(DateField.PARSE_ERROR_PREDICATE);
}
/**
* @override
*/
_createInvalidValueStatus(statusType, value, error) {
var errorStatus = super._createInvalidValueStatus(statusType, value, error);
// Set date and time to invalid, otherwise isDateValid and isTimeValid return false even though there is a validation error
errorStatus.invalidDate = true;
errorStatus.invalidTime = true;
errorStatus.code = DateField.ErrorCode.PARSE_ERROR;
return errorStatus;
}
_setDateValid(valid) {
this._setErrorStatusPart('invalidDate', valid);
}
_setTimeValid(valid) {
this._setErrorStatusPart('invalidTime', valid);
}
_isErrorStatusPartValid(property) {
if (!this.errorStatus) {
return true;
}
// return false if one of the status has the invalid* property
return !this.errorStatus.asFlatList().some(function(status) {
return !!status[property];
});
}
_isDateValid() {
return this._isErrorStatusPartValid('invalidDate');
}
_isTimeValid() {
return this._isErrorStatusPartValid('invalidTime');
}
/**
* Method invoked if being rendered within a cell-editor (mode='scout.FormField.Mode.CELLEDITOR'), and once the editor finished its rendering.
*/
onCellEditorRendered(options) {
if (options.openFieldPopup) {
if (this.hasDate && !this.hasTime) {
this.openDatePopupAndSelect(this.value);
} else if (!this.hasDate && this.hasTime) {
this.openTimePopupAndSelect(this.value);
} else if (!Device.get().supportsOnlyTouch()) {
// If date AND time are active, don't open popup on touch devices because the user has to choose first what he wants to edit
this.openDatePopupAndSelect(this.value);
}
}
if (this.touchMode) {
this._cellEditorPopup = options.cellEditorPopup;
}
}
_onPopupRemove(event) {
if (!this.touchMode || !this._cellEditorPopup) {
return;
}
if (this.hasDate && this.hasTime) {
// If date and time is shown, user might want to change both, let him close the cell editor when he is finished
return;
}
// Close cell editor when touch popup closes
this._cellEditorPopup.completeEdit();
this._cellEditorPopup = null;
}
/**
* @override FormField.js
*/
prepareForCellEdit(opts) {
opts = opts || {};
super.prepareForCellEdit(opts);
this.$field.removeClass('cell-editor-field first last');
if (this.$dateField) {
this.addCellEditorFieldCssClasses(this.$dateField, opts);
}
if (this.$timeField) {
if (!this.$dateField) {
opts.cssClass = '';
}
this.addCellEditorFieldCssClasses(this.$timeField, opts);
}
}
/**
* @returns DatePicker instance from popup, because the property name is different
* for DatePickerPopup and DatePickerTouchPopup.
*/
getDatePicker() {
if (this.popup && this.popup.getDatePicker) {
return this.popup.getDatePicker();
}
}
/**
* Opens picker and selects date
*
* @param date
* optional, Date to pass to the date picker. If no date is specified, the reference date
* is preselected (not selected!).
*/
openDatePopupAndSelect(date) {
this.openDatePopup();
if (!date) {
this.preselectDate(this._referenceDate(), false);
} else {
this.selectDate(date, false);
}
}
preselectDate(date, animated) {
var datePicker = this.getDatePicker();
if (datePicker) {
datePicker.preselectDate(date, animated);
}
}
selectDate(date, animated) {
var datePicker = this.getDatePicker();
if (datePicker) {
datePicker.selectDate(date, animated);
}
}
/**
* @returns DatePicker instance from popup, because the property name is different
* for DatePickerPopup and DatePickerTouchPopup.
*/
getTimePicker() {
if (this.popup && this.popup.getTimePicker) {
return this.popup.getTimePicker();
}
}
/**
* Opens picker and selects date
*
* @param date
* optional, Date to pass to the date picker. If no date is specified, the reference date
* is preselected (not selected!).
*/
openTimePopupAndSelect(time) {
// resolution < 1 means no picker required
if (!this.hasTimePopup) {
return;
}
this.openTimePopup();
if (!time) {
this.preselectTime(this._referenceDate());
} else {
this.selectTime(time);
}
}
preselectTime(time) {
var timePicker = this.getTimePicker();
if (timePicker) {
timePicker.preselectTime(time);
}
}
selectTime(time) {
var timePicker = this.getTimePicker();
if (timePicker) {
timePicker.selectTime(time);
}
}
shiftSelectedDate(years, months, days) {
this.openDatePopup();
this.getDatePicker().shiftSelectedDate(years, months, days);
}
shiftSelectedTime(hourUnits, minuteUnits, secondUnits) {
this.openTimePopup();
this.getTimePicker().shiftSelectedTime(hourUnits, minuteUnits, secondUnits);
}
_formatValue(value) {
var
dateText = '',
timeText = '';
if (this.hasDate) {
if (value) {
dateText = this.isolatedDateFormat.format(value);
}
this.dateDisplayText = dateText;
}
if (this.hasTime) {
if (value) {
timeText = this.isolatedTimeFormat.format(value);
}
this.timeDisplayText = timeText;
}
return this._computeDisplayText(this.dateDisplayText, this.timeDisplayText);
}
/**
* @override
*/
_renderFieldStyle() {
super._renderFieldStyle();
this._renderFieldStyleInternal(this.$dateField);
this._renderFieldStyleInternal(this.$timeField);
}
/**
* @override
*/
_renderDisabledStyle() {
super._renderDisabledStyle();
this._renderDisabledStyleInternal(this.$dateField);
this._renderDisabledStyleInternal(this.$timeField);
}
/**
* @override
*/
_triggerAcceptInput() {
var event = {
displayText: this.displayText,
errorStatus: this.errorStatus,
value: this.value
};
this.trigger('acceptInput', event);
}
}