Skip to content

Commit

Permalink
refactor: move errorMessage logic to ErrorController (#3270)
Browse files Browse the repository at this point in the history
  • Loading branch information
web-padawan committed Jan 31, 2022
1 parent 467244b commit fac49f2
Show file tree
Hide file tree
Showing 15 changed files with 300 additions and 175 deletions.
8 changes: 6 additions & 2 deletions packages/component-base/src/slot-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* Copyright (c) 2021 - 2022 Vaadin Ltd.
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
*/
import { dashToCamelCase } from '@polymer/polymer/lib/utils/case-map.js';
import { FlattenedNodesObserver } from '@polymer/polymer/lib/utils/flattened-nodes-observer.js';

/**
Expand Down Expand Up @@ -30,10 +31,13 @@ export class SlotController extends EventTarget {
static generateId(slotName, host) {
const prefix = slotName || 'default';

// Support dash-case slot names e.g. "error-message"
const field = `${dashToCamelCase(prefix)}Id`;

// Maintain the unique ID counter for a given prefix.
this[`${prefix}Id`] = 1 + this[`${prefix}Id`] || 0;
this[field] = 1 + this[field] || 0;

return `${prefix}-${host.localName}-${this[`${prefix}Id`]}`;
return `${prefix}-${host.localName}-${this[field]}`;
}

hostConnected() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -229,34 +229,34 @@ snapshots["vaadin-email-field shadow theme"] =
/* end snapshot vaadin-email-field shadow theme */

snapshots["vaadin-email-field slots default"] =
`<div
hidden=""
slot="error-message"
>
</div>
<input
`<input
autocapitalize="off"
slot="input"
type="email"
>
<label slot="label">
</label>
`;
/* end snapshot vaadin-email-field slots default */

snapshots["vaadin-email-field slots helper"] =
`<div
<div
hidden=""
slot="error-message"
>
</div>
<input
`;
/* end snapshot vaadin-email-field slots default */

snapshots["vaadin-email-field slots helper"] =
`<input
autocapitalize="off"
slot="input"
type="email"
>
<label slot="label">
</label>
<div
hidden=""
slot="error-message"
>
</div>
<div slot="helper">
Helper
</div>
Expand Down
36 changes: 36 additions & 0 deletions packages/field-base/src/error-controller.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/**
* @license
* Copyright (c) 2021 - 2022 Vaadin Ltd.
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
*/
import { SlotController } from '@vaadin/component-base/src/slot-controller.js';

/**
* A controller that manages the error message node content.
*/
export class ErrorController extends SlotController {
/**
* ID attribute value set on the error message element.
*/
readonly errorId: string;

/**
* String used for the error message text content.
*/
protected errorMessage: string | null | undefined;

/**
* Set to true when the host element is invalid.
*/
protected invalid: boolean;

/**
* Set the error message element text content.
*/
setErrorMessage(errorMessage: string | null | undefined): void;

/**
* Set invalid state for detecting whether to show error message.
*/
setInvalid(invalid: boolean): void;
}
134 changes: 134 additions & 0 deletions packages/field-base/src/error-controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
/**
* @license
* Copyright (c) 2021 - 2022 Vaadin Ltd.
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
*/
import { SlotController } from '@vaadin/component-base/src/slot-controller.js';

/**
* A controller that manages the error message node content.
*/
export class ErrorController extends SlotController {
constructor(host) {
super(
host,
'error-message',
() => document.createElement('div'),
(_host, node) => {
this.__updateErrorId(node);

this.__updateHasError();
}
);
}

/**
* ID attribute value set on the error message element.
*
* @return {string}
*/
get errorId() {
return this.node && this.node.id;
}

/**
* Set the error message element text content.
*
* @param {string} errorMessage
*/
setErrorMessage(errorMessage) {
this.errorMessage = errorMessage;

this.__updateHasError();
}

/**
* Set invalid state for detecting whether to show error message.
*
* @param {boolean} invalid
*/
setInvalid(invalid) {
this.invalid = invalid;

this.__updateHasError();
}

/**
* Override to initialize the newly added custom label.
*
* @param {Node} errorNode
* @protected
* @override
*/
initCustomNode(errorNode) {
this.__updateErrorId(errorNode);

// Save the custom error message content on the host.
if (errorNode.textContent && !this.errorMessage) {
this.errorMessage = errorNode.textContent.trim();
}

this.__updateHasError();
}

/**
* Override to cleanup label node when it's removed.
*
* @param {Node} node
* @protected
* @override
*/
teardownNode(node) {
let errorNode = this.getSlotChild();

// If custom error was removed, restore the default one.
if (!errorNode && node !== this.defaultNode) {
errorNode = this.attachDefaultNode();

// Run initializer to update default label and ID.
this.initNode(errorNode);
}

this.__updateHasError();
}

/**
* @param {string} error
* @private
*/
__isNotEmpty(error) {
return Boolean(error && error.trim() !== '');
}

/** @private */
__updateHasError() {
const errorNode = this.node;
const hasError = Boolean(this.invalid && this.__isNotEmpty(this.errorMessage));

// Update both default and custom error message node.
if (errorNode) {
errorNode.textContent = hasError ? this.errorMessage : '';
errorNode.hidden = !hasError;

// Role alert will make the error message announce immediately
// as the field becomes invalid
if (hasError) {
errorNode.setAttribute('role', 'alert');
} else {
errorNode.removeAttribute('role');
}
}

this.host.toggleAttribute('has-error-message', hasError);
}

/**
* @param {HTMLElement} errorNode
* @private
*/
__updateErrorId(errorNode) {
if (!errorNode.id) {
errorNode.id = this.defaultId;
}
}
}
Loading

0 comments on commit fac49f2

Please sign in to comment.