Skip to content

Commit

Permalink
Merge pull request #2237 from umbraco/v14/feature/validation-message-…
Browse files Browse the repository at this point in the history
…localization

Feature: Localization of validation messages
  • Loading branch information
nielslyngsoe authored Aug 30, 2024
2 parents 700a6fb + 68d4b3d commit bb3c019
Show file tree
Hide file tree
Showing 9 changed files with 120 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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}
`;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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">
Expand Down Expand Up @@ -183,7 +183,7 @@ export class UmbMultipleColorPickerItemInputElement extends UUIFormControlMixin(
`,
)}
</div>
</uui-form-validation-message>
</umb-form-validation-message>
`;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"
Expand All @@ -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"
Expand All @@ -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>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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>
`;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
}
3 changes: 2 additions & 1 deletion src/packages/core/validation/index.ts
Original file line number Diff line number Diff line change
@@ -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';
10 changes: 6 additions & 4 deletions src/packages/core/validation/mixins/form-control.mixin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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);
Expand All @@ -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) {
Expand Down

0 comments on commit bb3c019

Please sign in to comment.