implement localization of validation messages

This commit is contained in:
Niels Lyngsø
2024-08-24 21:32:23 +02:00
parent 419920cb32
commit 96945bb1f6
9 changed files with 120 additions and 20 deletions

View File

@@ -284,7 +284,7 @@ export class UmbBlockGridEntriesElement extends UmbFormControlMixin(UmbLitElemen
</umb-block-grid-entry>`,
)}
</div>
<uui-form-validation-message .for=${this}></uui-form-validation-message>
<umb-form-validation-message .for=${this}></umb-form-validation-message>
${this._canCreate ? this.#renderCreateButton() : nothing}
`;
}

View File

@@ -163,13 +163,13 @@ export class UmbPropertyEditorUIBlockListElement
this.addValidator(
'rangeUnderflow',
() => this.localize.term('validation_entriesShort'),
() => '#validation_entriesShort',
() => !!this._limitMin && this.#entriesContext.getLength() < this._limitMin,
);
this.addValidator(
'rangeOverflow',
() => this.localize.term('validation_entriesExceed'),
() => '#validation_entriesExceed',
() => !!this._limitMax && this.#entriesContext.getLength() > this._limitMax,
);

View File

@@ -134,7 +134,7 @@ export class UmbMultipleColorPickerItemInputElement extends UUIFormControlMixin(
override render() {
//TODO: Using native input=color element instead of uui-color-picker due to its huge size and bad adaptability as a pop up
return html`
<uui-form-validation-message id="validation-message" @invalid=${this.#onInvalid} @valid=${this.#onValid}>
<umb-form-validation-message id="validation-message" @invalid=${this.#onInvalid} @valid=${this.#onValid}>
<div id="item">
${this.disabled || this.readonly ? nothing : html`<uui-icon name="icon-navigation"></uui-icon>`}
<div class="color-wrapper">
@@ -183,7 +183,7 @@ export class UmbMultipleColorPickerItemInputElement extends UUIFormControlMixin(
`,
)}
</div>
</uui-form-validation-message>
</umb-form-validation-message>
`;
}

View File

@@ -79,7 +79,7 @@ export class UmbInputMultipleTextStringItemElement extends UUIFormControlMixin(U
return html`
${this.disabled || this.readonly ? nothing : html`<uui-icon name="icon-navigation" class="handle"></uui-icon>`}
<uui-form-validation-message id="validation-message" @invalid=${this.#onInvalid} @valid=${this.#onValid}>
<umb-form-validation-message id="validation-message" @invalid=${this.#onInvalid} @valid=${this.#onValid}>
<uui-input
id="input"
label="Value"
@@ -90,7 +90,7 @@ export class UmbInputMultipleTextStringItemElement extends UUIFormControlMixin(U
?readonly=${this.readonly}
required=${this.required}
required-message="Value is missing"></uui-input>
</uui-form-validation-message>
</umb-form-validation-message>
${when(
!this.readonly,

View File

@@ -195,7 +195,7 @@ export class UmbPropertyTypeWorkspaceViewSettingsElement extends UmbLitElement i
return html`
<uui-box class="uui-text">
<div class="container">
<uui-form-validation-message>
<umb-form-validation-message>
<uui-input
id="name-input"
name="name"
@@ -208,8 +208,8 @@ export class UmbPropertyTypeWorkspaceViewSettingsElement extends UmbLitElement i
${umbFocus()}>
<!-- TODO: validation for bad characters -->
</uui-input>
</uui-form-validation-message>
<uui-form-validation-message>
</umb-form-validation-message>
<umb-form-validation-message>
<uui-input-lock
id="alias-input"
name="alias"
@@ -222,7 +222,7 @@ export class UmbPropertyTypeWorkspaceViewSettingsElement extends UmbLitElement i
@input=${this.#onAliasChange}
@lock-change=${this.#onToggleAliasLock}>
</uui-input-lock>
</uui-form-validation-message>
</umb-form-validation-message>
<uui-textarea
id="description-input"
name="description"
@@ -231,13 +231,13 @@ export class UmbPropertyTypeWorkspaceViewSettingsElement extends UmbLitElement i
placeholder=${this.localize.term('placeholders_enterDescription')}
.value=${this._data?.description}></uui-textarea>
</div>
<uui-form-validation-message>
<umb-form-validation-message>
<umb-data-type-flow-input
.value=${this._data?.dataType?.unique ?? ''}
@change=${this.#onDataTypeIdChange}
required
${umbBindToValidation(this, '$.dataType.unique')}></umb-data-type-flow-input>
</uui-form-validation-message>
</umb-form-validation-message>
<hr />
<div class="container">
<b><umb-localize key="validation_validation">Validation</umb-localize></b>

View File

@@ -81,9 +81,9 @@ export class UmbPropertyLayoutElement extends UmbLitElement {
<slot name="description"></slot>
</div>
<div id="editorColumn">
<uui-form-validation-message>
<umb-form-validation-message>
<slot name="editor"></slot>
</uui-form-validation-message>
</umb-form-validation-message>
</div>
`;
}

View File

@@ -0,0 +1,97 @@
import { UmbValidationInvalidEvent, UmbValidationValidEvent } from '../events/index.js';
import type { UmbFormControlMixinInterface } from '../mixins/index.js';
import { css, customElement, html, property, repeat, unsafeHTML } from '@umbraco-cms/backoffice/external/lit';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
/**
* @description - Component for displaying one or more validation messages from UMB/UUI Form Control within the given scope.
* Notice: Only supports components that is build on the UMB / UUI FormControlMixing.
* @slot - for button contents
* @slot message - for extras in the messages container
* @see FormControlMixin
*/
@customElement('umb-form-validation-message')
export class UmbFormValidationMessageElement extends UmbLitElement {
/**
* Set the element containing Form Controls of interest.
* @type {string}
* @default
*/
@property({ reflect: false, attribute: true })
public get for(): HTMLElement | string | null {
return this._for;
}
public set for(value: HTMLElement | string | null) {
let element = null;
if (typeof value === 'string') {
const scope = this.getRootNode();
element = (scope as DocumentFragment)?.getElementById(value);
} else if (value instanceof HTMLElement) {
element = value;
}
const newScope = element ?? this;
const oldScope = this._for;
if (oldScope === newScope) {
return;
}
if (oldScope !== null) {
oldScope.removeEventListener(UmbValidationInvalidEvent.TYPE, this.#onControlInvalid as EventListener);
oldScope.removeEventListener(UmbValidationValidEvent.TYPE, this.#onControlValid as EventListener);
}
this._for = newScope;
this._for.addEventListener(UmbValidationInvalidEvent.TYPE, this.#onControlInvalid as EventListener);
this._for.addEventListener(UmbValidationValidEvent.TYPE, this.#onControlValid as EventListener);
}
private _for: HTMLElement | null = null;
constructor() {
super();
if (this.for === null) {
this.for = this;
}
}
private _messages = new Map<UmbFormControlMixinInterface<unknown>, string>();
#onControlInvalid = async (e: UmbValidationInvalidEvent) => {
const ctrl = (e as any).composedPath()[0];
if (ctrl.pristine === false) {
// Currently we only show message from components who does have the pristine property. (we only want to show messages from fields that are NOT pristine aka. that are dirty or in a from that has been submitted)
// Notice we use the localization controller here, this is different frm the UUI component which uses the same name.
this._messages.set(ctrl, this.localize.string(ctrl.validationMessage));
} else {
this._messages.delete(ctrl);
}
this.requestUpdate();
};
#onControlValid = (e: UmbValidationValidEvent) => {
const ctrl = (e as any).composedPath()[0];
this._messages.delete(ctrl);
this.requestUpdate();
};
override render() {
return html`
<slot></slot>
<div id="messages">
${repeat(this._messages, (item) => html`<div>${unsafeHTML(item[1])}</div>`)}
<slot name="message"></slot>
</div>
`;
}
static override styles = [
css`
#messages {
color: var(--uui-color-danger-standalone);
}
`,
];
}
declare global {
interface HTMLElementTagNameMap {
'umb-form-validation-message': UmbFormValidationMessageElement;
}
}

View File

@@ -1,9 +1,10 @@
export * from './components/form-validation-message.element.js';
export * from './const.js';
export * from './context/index.js';
export * from './controllers/index.js';
export * from './directives/bind-to-validation.lit-directive.js';
export * from './events/index.js';
export * from './interfaces/index.js';
export * from './mixins/index.js';
export * from './translators/index.js';
export * from './utils/index.js';
export * from './directives/bind-to-validation.lit-directive.js';

View File

@@ -99,6 +99,7 @@ export declare abstract class UmbFormControlMixinElement<ValueType>
* The mixin allows a custom element to participate in HTML forms.
* @param {object} superClass - superclass to be extended.
* @param defaultValue
* @returns {class} - The mixin class.
* @mixin
*/
export function UmbFormControlMixin<
@@ -172,7 +173,7 @@ export function UmbFormControlMixin<
* Get internal form element.
* This has to be implemented to provide a FormControl Element of choice for the given context. The element is used as anchor for validation-messages.
* @function getFormElement
* @returns {HTMLElement | undefined | null}
* @returns {HTMLElement | undefined | null} - Returns the form element or undefined if not found.
*/
protected getFormElement(): HTMLElement | undefined | null {
return this.#formCtrlElements.find((el) => el.validity.valid === false);
@@ -181,7 +182,7 @@ export function UmbFormControlMixin<
/**
* Focus first element that is invalid.
* @function focusFirstInvalidElement
* @returns {HTMLElement | undefined}
* @returns {HTMLElement | undefined} - Returns the first invalid element or undefined if no invalid elements are found.
*/
focusFirstInvalidElement() {
const firstInvalid = this.#formCtrlElements.find((el) => el.validity.valid === false);
@@ -219,6 +220,7 @@ export function UmbFormControlMixin<
* @param {FlagTypes} flagKey the type of validation.
* @param {method} getMessageMethod method to retrieve relevant message. Is executed every time the validator is re-executed.
* @param {method} checkMethod method to determine if this validator should invalidate this form control. Return true if this should prevent submission.
* @returns {UmbFormControlValidatorConfig} - The added validator configuration.
*/
addValidator(
flagKey: FlagTypes,
@@ -252,7 +254,7 @@ export function UmbFormControlMixin<
/**
* @function addFormControlElement
* @description Important notice if adding a native form control then ensure that its value and thereby validity is updated when value is changed from the outside.
* @param element {UmbNativeFormControlElement} - element to validate and include as part of this form association.
* @param {UmbNativeFormControlElement} element - element to validate and include as part of this form association.
*/
protected addFormControlElement(element: UmbNativeFormControlElement) {
this.#formCtrlElements.push(element);
@@ -275,7 +277,7 @@ export function UmbFormControlMixin<
/**
* @function setCustomValidity
* @description Set custom validity state, set to empty string to remove the custom message.
* @param message {string} - The message to be shown
* @param {string} message - The message to be shown
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLObjectElement/setCustomValidity|HTMLObjectElement:setCustomValidity}
*/
protected setCustomValidity(message: string | null) {