@furo/ui5
Version:
SAP UI5 Web Components data bindings for furo-web
399 lines (365 loc) • 12 kB
JavaScript
import '@ui5/webcomponents/dist/generated/i18n/i18n-defaults.js';
import * as DatePicker from '@ui5/webcomponents/dist/DatePicker.js';
// eslint-disable-next-line import/no-extraneous-dependencies
import { FieldNodeAdapter } from '@furo/data/src/lib/FieldNodeAdapter.js';
import { Events } from './lib/Events.js';
import '@ui5/webcomponents-icons/dist/alert.js';
import '@ui5/webcomponents-icons/dist/information.js';
import '@ui5/webcomponents-icons/dist/error.js';
/**
* The furo-ui5-date-picker component allows the user to bind an date object like google.type.Date or a date string
* with ISO 8061 format like "2020-12-31" to the ui5 datepicker and edit it.
*
* It supports all features from the [SAP ui5 DatePicker element](https://sap.github.io/ui5-webcomponents/playground/components/DatePicker/).
*
* You can define the formatPattern (e.g. 'dd.MM.yyyy' ) to show the date according to format pattern.
*
* You can bind a `string`, `furo.type.Date` or `google.type.Date`.
*
* ```html
* <furo-ui5-date-picker
* fn-bind-data="--dao(FIELDNODE)">
* </furo-ui5-date-picker>
* ```
*
* The text field can be editable or read-only (readonly property), and it can be enabled or disabled (enabled property).
* To visualize semantic states, such as "error" or "warning", the valueState property is provided.
* When the user makes changes to the date, the change event is fired, which enables you to react on any date change.
*
* ## supported meta and constraints
* - **readonly: true** , set the element to readonly
* - **placeholder:"some string"** set the placeholder for the element
* - **min:"1999-12-31"** set the minDate for the element (use iso date in the constraint)
* - **max:"1999-12-31"** set the maxDate for the element (use iso date in the constraint)
* - **pattern:"1999-12-31"** set the pattern for the element
*
* The constraint **required** will mark the element as required
*
* ## Methods
* **bind-data(fieldNode)**
* Bind a entity field. You can use the entity even when no data was received.
*
* When you use at-object-ready from a furo-data-object which emits a EntityNode, just bind the field with --entity(*.fields.fieldname)
*
* @fires {Date} furo-value-changed - Fired when the input operation has finished by pressing Enter or on focusout.
* @fires {Date} change -Fired when the input operation has finished by pressing Enter or on focusout.
*
* @summary bindable datepicker field
* @element furo-ui5-date-picker
* @demo demo-furo-ui5-date-picker Basic Usage
*/
export class FuroUi5DatePicker extends FieldNodeAdapter(DatePicker.default) {
constructor() {
super();
/**
* The format pattern for the date.
* @type {string}
*/
this.formatPattern = ''; // needed to avoid cldr errors
/**
* used to restore the state after a invalidation -> validation change
* @type {{state: string, message: string}}
* @private
*/
this._previousValueState = { state: 'None', message: '' };
/**
*
* @type {{min: undefined, readonly: undefined, max: undefined, pattern: undefined, placeholder: undefined}}
* @private
*/
this._attributesFromFNA = {
readonly: undefined,
placeholder: undefined,
min: undefined,
max: undefined,
pattern: undefined,
};
/**
*
* @type {{required: undefined}}
* @private
*/
this._constraintsFromFNA = {
required: undefined,
};
/**
* a list of privileged attributes. when those attributes are set in number-input components initially.
* they can not be modified later via response or spec
* null is used because getAttribute returns null or value
* @type {{minDate: null, readonly: null, formatPattern: null, disabled: null, maxDate: null, placeholder: null, required: null}}
* @private
*/
this._privilegedAttributes = {
readonly: null,
placeholder: null,
required: null,
disabled: null,
maxDate: null,
minDate: null,
formatPattern: null,
};
this.addEventListener('input', this._updateFNA);
// changed is fired when the input operation has finished by pressing Enter or on focusout.
this.addEventListener('change', v => {
const e = v.detail;
const type = this.getDataType();
switch (type) {
case 'furo.type.Date':
case 'google.type.Date':
if (e.value !== '' && e.valid) {
this.setFnaFieldValue({
year: this.dateValue.getFullYear(),
month: this.dateValue.getMonth() + 1,
day: this.dateValue.getDate(),
});
} else {
this.setFnaFieldValue({});
}
break;
case 'string':
default:
if (e.value !== '' && e.valid) {
this.setFnaFieldValue(
`${this.dateValue.getFullYear()}-${(this.dateValue.getMonth() + 1)
.toString()
.padStart(2, '0')}-${this.dateValue
.getDate()
.toString()
.padStart(2, '0')}`
);
} else {
this.setFnaFieldValue('');
}
}
this.dispatchEvent(Events.buildChangeEvent(this.dateValue));
});
}
/**
* Workaround for language request
* @private
*/
get valueStateMessage() {
return super.valueStateMessage || [];
}
/**
* @private
*/
onFnaFieldValueChanged(value) {
const type = this.getDataType();
switch (type) {
case 'furo.type.Date':
case 'google.type.Date':
if (value.month && value.day) {
this.value = this.formatValue(
new Date(value.year, value.month - 1, value.day)
);
} else {
this.value = '';
}
break;
case 'string':
default:
if (value === '' || value === null || value === undefined) {
this.value = '';
} else {
const parts = value.match(/\d+/g);
this.value = this.formatValue(
new Date(parts[0], parts[1] - 1, parts[2])
);
}
}
}
/**
* @private
*/
connectedCallback() {
// eslint-disable-next-line wc/guard-super-call
super.connectedCallback();
this.readAttributes();
}
/**
* Adds a div with slot="valueStateMessage" to show
* field related information if the attribute value-state is set.
* @returns {HTMLDivElement}
* @private
*/
_addValueStateMessage() {
const EXISTING_VSE = this.querySelector('div[slot="valueStateMessage"]');
if (EXISTING_VSE !== null) {
return EXISTING_VSE;
}
// we only create the ValueStateContainer if none already exists.
const VALUE_STATE_MESSAGE_ELEMENT = document.createElement('div');
VALUE_STATE_MESSAGE_ELEMENT.setAttribute('slot', 'valueStateMessage');
// eslint-disable-next-line wc/no-constructor-attributes
this.appendChild(VALUE_STATE_MESSAGE_ELEMENT);
return VALUE_STATE_MESSAGE_ELEMENT;
}
/**
* Removes <div slot="valueStateMessage"></div>
* @private
*/
_removeValueStateMessage() {
const VALUE_STATE_MESSAGE_ELEMENT = this.querySelector(
'div[slot="valueStateMessage"]'
);
if (VALUE_STATE_MESSAGE_ELEMENT !== null) {
VALUE_STATE_MESSAGE_ELEMENT.remove();
}
}
/**
* Binds a FieldNode to the component.
*
* Supported types: `string`, `furo.type.Date`, `google.type.Date`
* @param fieldNode {FieldNode} of type: `string`, `furo.type.Date`, `google.type.Date`
*/
bindData(fieldNode) {
return super.bindData(fieldNode);
}
/**
* Reads the attributes which are set on the component dom.
* Attributes that can be se are `value-state`, `placeholder`, `required`,`readonly`,`disabled` `max-date`, `min-date`, `format-pattern`,
* Use this after manual or scripted update of the attributes.
*/
readAttributes() {
this._previousValueState.state = this.getAttribute('value-state')
? this.getAttribute('value-state')
: 'None';
// save the original attribute for later usages, we do this, because some components reflect
Object.keys(this._privilegedAttributes).forEach(attr => {
this._privilegedAttributes[attr] = this.getAttribute(attr);
});
}
/**
* set the value state
* @param state
*/
onFnaFieldStateChanged(state) {
this._setValueStateMessage(state.state, state.description);
}
/**
* overwrite onFnaFieldNodeBecameInvalid function
* @private
*/
onFnaFieldNodeBecameInvalid(validity) {
if (validity.description) {
// this value state should not be saved as a previous value state
this._setValueStateMessage('Error', validity.description);
} else {
this.valueState = 'Error';
}
}
/**
* overwrite onFnaFieldNodeBecameValid function
* @private
*/
onFnaFieldNodeBecameValid() {
this._resetValueStateMessage();
}
/**
* updates the value state and the value state message on demand
*
* @param valueState
* @param message
* @private
*/
_setValueStateMessage(valueState, message) {
const VSE = this._addValueStateMessage();
this.valueState = valueState;
if (VSE !== null) {
VSE.innerText = message || '';
}
}
/**
* resets value-state and valueStateMessage to previous value state
* If no previous message is set, the valueStateMessage container is removed.
* @private
*/
_resetValueStateMessage() {
this.valueState = this._previousValueState.state;
if (this._previousValueState?.message?.length) {
this._setValueStateMessage(
this._previousValueState.state,
this._previousValueState.message
);
} else {
this._removeValueStateMessage();
}
}
/**
* overwrite onFnaPlaceholderChanged function
* @private
* @param placeholder
*/
onFnaPlaceholderChanged(placeholder) {
this._attributesFromFNA.placeholder = placeholder;
if (this._privilegedAttributes.placeholder === null) {
this.placeholder = placeholder;
}
}
/**
* overwrite onFnaReadonlyChanged function
* @private
* @param readonly
*/
onFnaReadonlyChanged(readonly) {
this._attributesFromFNA.readonly = readonly;
if (this._privilegedAttributes.readonly === null) {
this.readonly = readonly;
}
}
/**
* overwrite onFnaConstraintsChanged function
* @private
* @param constraints
*/
onFnaConstraintsChanged(constraints) {
// set this first if available, the following constraints uses formatValue()
if (constraints.pattern !== undefined) {
this._constraintsFromFNA.pattern = constraints.pattern;
if (this._privilegedAttributes.formatPattern === null) {
this.formatPattern = constraints.pattern.is;
}
}
// required
if (constraints.required !== undefined) {
this._constraintsFromFNA.required = constraints.required;
if (this._privilegedAttributes.required === null) {
this.required = constraints.required.is === 'true';
}
}
if (constraints.min !== undefined) {
this._constraintsFromFNA.min = constraints.min;
if (this._privilegedAttributes.minDate === null) {
const parts = constraints.min.is.match(/\d+/g);
this.minDate = this.formatValue(
new Date(parts[0], parts[1] - 1, parts[2])
);
}
}
if (constraints.max !== undefined) {
this._constraintsFromFNA.max = constraints.max;
if (this._privilegedAttributes.maxDate === null) {
const parts = constraints.max.is.match(/\d+/g);
this.maxDate = this.formatValue(
new Date(parts[0], parts[1] - 1, parts[2])
);
}
}
}
/**
* @private
*/
static get metadata() {
return { tag: 'furo-ui5-date-picker' };
}
static get styles() {
return [
super.styles,
{
content: ':host .ui5-date-picker-input{min-width: unset}',
},
];
}
}
FuroUi5DatePicker.define();