/* Copyright (c) 2010, Sage Software, Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import declare from 'dojo/_base/declare';
import event from 'dojo/_base/event';
import string from 'dojo/string';
import format from '../Format';
import FieldManager from '../FieldManager';
import EditorField from './EditorField';
import DateTimePicker from '../DateTimePicker';
import RelativeDateTimePicker from '../RelativeDateTimePicker';
import getResource from '../I18n';
const resource = getResource('dateField');
const dtFormatResource = getResource('dateFieldDateTimeFormat');
/**
* @class argos.Fields.DateField
* @classdesc The DateField is an extension of the {@link EditorField EditorField} by accepting Date Objects
* for values and using the {@link Calendar Calendar} view for user input.
*
* @example
*
* {
* name: 'StartDate',
* property: 'StartDate',
* label: this.startDateText,
* type: 'date',
* dateFormatText: 'MM/DD HH:mm:ss',
* showTimerPicker: true,
* showRelativeDateTime: true
* }
*
* @extends argos.Fields.EditorField
* @requires argos.Calendar
* @requires argos.FieldManager
* @requires argos.Format
*/
const control = declare('argos.Fields.DateField', [EditorField], /** @lends argos.Fields.DateField# */{
// Localization
/**
* @cfg {String}
* The text shown when no value (or null/undefined) is set to the field.
*/
emptyText: resource.emptyText,
dateFormatText: dtFormatResource.dateFormatText,
/**
* @property {String}
* The error validation message for this field.
*
* `${0}` => Label
*/
invalidDateFormatErrorText: resource.invalidDateFormatErrorText,
/**
* @property {Simplate}
* Simplate that defines the fields HTML Markup
*
* * `$` => Field instance
* * `$$` => Owner View instance
*
*/
widgetTemplate: new Simplate([
`<label for="{%= $.name %}"
{% if ($.required) { %}
class="required"
{% } %}>
{%: $.label %}
</label>
<div class="field field-control-wrapper">
<button
data-dojo-attach-point="triggerNode"
data-action="showModal"
class="button field-control-trigger whiteButton"
aria-label="{%: $.lookupLabelText %}">
<svg class="icon" focusable="false" aria-hidden="true" role="presentation">
<use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#icon-{%: $.iconClass %}"></use>
</svg>
</button>
<input
data-dojo-attach-point="inputNode"
data-dojo-attach-event="onchange:_onChange"
type="text"
{% if ($.required) { %}
data-validate="required"
class="required"
{% } %}
/>
</div>`,
]),
iconClass: 'calendar',
/**
* @property {String}
* The target view id that will provide the user input, this should always be to set to the
* {@link Calendar Calendars} view id.
*/
view: 'generic_calendar',
/**
* required should be true if the field requires input. Defaults to false.
* @type {Boolean}
*/
required: false,
/**
* @cfg {Boolean}
* Sent as part of navigation options to {@link Calendar Calendar}, where it controls the
* display of the hour/minute inputs.
*/
showTimePicker: false,
/**
* @cfg {Boolean}
* Sent as part of navigation options to {@link Calendar Calendar}, where it controls the
* display of the relative date time picker.
*/
showRelativeDateTime: false,
/**
* @cfg {Boolean}
* Used in formatted and sent as part of navigation options to {@link Calendar Calendar},
* where it controls the the conversion to/from UTC and setting the hour:min:sec to 00:00:05.
*/
timeless: false,
modal: null,
dateTimePicker: null,
_modalListener: null,
/**
* Takes a date object and calls {@link format#date format.date} passing the current
* `dateFormatText` and `timeless` values, formatting the date into a string representation.
* @param {Date} value Date to be converted
* @return {String}
*/
formatValue: function formatValue(value) {
return format.date(value, this.dateFormatText, this.timeless);
},
/**
* When a value changes it checks that the text in the input field matches the defined
* `dateFormatText` by using it to parse it back into a Date Object. If this succeeds then
* sets the current value to the Date object and removes any validation warnings. If it
* doesn't then current value is empties and the validation styling is added.
* @param {Event} evt Event that caused change to fire.
*/
_onChange: function _onChange(/* evt*/) {
const jsDate = new Date(this.inputNode.value);
let date = moment(this.inputNode.value, this.dateFormatText, true);
if (moment(jsDate).isValid() && !date.isValid()) {
date = moment(jsDate);
}
const val = date.isValid();
if (val) {
this.validationValue = this.currentValue = date.toDate();
if (this.inputNode.value !== date.format(this.dateFormatText)) {
this.inputNode.value = date.format(this.dateFormatText);
}
$(this.containerNode).removeClass('row-error'); // todo: not the right spot for this, add validation eventing
} else {
this.validationValue = this.currentValue = null;
$(this.containerNode).addClass('row-error'); // todo: not the right spot for this, add validation eventing
}
if (this.previousValue !== this.currentValue) {
this.onChange(this.currentValue, this);
}
this.previousValue = this.currentValue;
},
/**
* Extends the parent {@link EditorField#createNavigationOptions createNavigationOptions} to
* also include the properties `date`, `showTimePicker` and `timeless` with `date` being the current value
* @return {Object} Navigation options
*/
createNavigationOptions: function createNavigationOptions() {
const options = this.inherited(arguments);
if (this.currentValue !== '' && this.currentValue !== null) {
options.date = this.currentValue;
} else {
options.date = moment();
}
options.showTimePicker = this.showTimePicker;
options.timeless = this.timeless;
return options;
},
/**
* Retrieves the date from the {@link Calendar#getDateTime Calendar} view and sets it to currentValue.
*/
getValuesFromView: function getValuesFromView() {
const view = App.getPrimaryActiveView();
if (view) {
this.currentValue = this.validationValue = view.getDateTime();
$(this.containerNode).removeClass('row-error'); // todo: not the right spot for this, add validation eventing
}
},
getValuesFromModal: function getValuesFromModal(data = {}) {
if (data.calendar.selectedDateMoment) {
// This is the case where the DateTimePicker was used to select the date
const date = data.calendar.selectedDateMoment.clone();
if (data.time) {
date.hours(data.time.hours);
date.minutes(data.time.minutes);
date.seconds(data.time.seconds);
}
this.currentValue = this.validationValue = date.toDate();
this.inputNode.value = this.formatValue(this.currentValue);
} else {
this.currentValue = this.validationValue = data.toDate();
this.inputNode.value = this.formatValue(this.currentValue);
}
$(this.containerNode).removeClass('row-error'); // todo: not the right spot for this, add validation eventing
this._onChange();
},
/**
* Determines if the current value has been modified from the original value.
* @return {Boolean}
*/
isDirty: function isDirty() {
return this.originalValue instanceof Date && this.currentValue instanceof Date ? this.originalValue.getTime() !== this.currentValue.getTime() : this.originalValue !== this.currentValue;
},
/**
* Extends the parent {@link EditorField#clearValue clearValue} to also include removing the
* error validation styling.
*/
clearValue: function clearValue() {
this.inherited(arguments);
$(this.containerNode).removeClass('row-error'); // todo: not the right spot for this, add validation eventing
},
showModal: function showModal() {
if (this.isDisabled()) {
return;
}
const options = this.createNavigationOptions();
let toolbar;
if (this.showRelativeDateTime && !options.timeless) {
this.dateTimePicker = new RelativeDateTimePicker({ id: `relative-datetime-picker-modal ${this.id}`, isModal: true });
toolbar = [
{
action: 'cancel',
className: 'button--flat button--flat--split',
text: resource.cancelText,
}, {
action: this.dateTimePicker.toDateTimePicker,
className: 'button--flat button--flat--split',
text: resource.advancedText,
context: this.dateTimePicker,
},
];
} else {
this.dateTimePicker = new DateTimePicker({ id: `datetime-picker-modal ${this.id}`, isModal: true });
toolbar = [
{
action: 'cancel',
className: 'button--flat button--flat--split',
text: resource.cancelText,
}, {
action: 'resolve',
className: 'button--flat button--flat--split',
text: resource.confirmText,
},
];
}
App.modal.add(this.dateTimePicker, toolbar, options).then(this.getValuesFromModal.bind(this));
},
_onClick: function _onClick(evt) {
event.stop(evt);
},
/**
* Extends the parent {@link EditorField#validate validate} with a check that makes sure if
* the user has inputted a date manually into the input field that it had successfully validated
* in the {@link #_onChange _onChange} function.
* @return {Boolean/Object} False for no errors. True/Object for invalid.
*/
validate: function validate() {
if (this.inputNode.value !== '' && !this.currentValue) {
return string.substitute(this.invalidDateFormatErrorText, [this.label]);
}
return this.inherited(arguments);
},
});
export default FieldManager.register('date', control);