Skip to content

Commit

Permalink
feat(ui5-input): implement valueStateMessage (#1297)
Browse files Browse the repository at this point in the history
- Feature
A popover with a message that appears right below the input field, when the Input is in "Error", "Warning" or ""Information" value state.

- Usage
To inform the user about the reason for that state.
  • Loading branch information
ivoplashkov authored Mar 25, 2020
1 parent 0af6c3d commit 538a79a
Show file tree
Hide file tree
Showing 7 changed files with 190 additions and 8 deletions.
135 changes: 133 additions & 2 deletions packages/main/src/Input.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import UI5Element from "@ui5/webcomponents-base/dist/UI5Element.js";
import litRender from "@ui5/webcomponents-base/dist/renderer/LitRenderer.js";
import ResizeHandler from "@ui5/webcomponents-base/dist/delegate/ResizeHandler.js";
import { isIE, isPhone } from "@ui5/webcomponents-base/dist/Device.js";
import ValueState from "@ui5/webcomponents-base/dist/types/ValueState.js";
import { getFeature } from "@ui5/webcomponents-base/dist/FeaturesRegistry.js";
Expand All @@ -13,12 +14,14 @@ import Integer from "@ui5/webcomponents-base/dist/types/Integer.js";
import { fetchI18nBundle, getI18nBundle } from "@ui5/webcomponents-base/dist/i18nBundle.js";
import "@ui5/webcomponents-icons/dist/icons/decline.js";
import InputType from "./types/InputType.js";
import Popover from "./Popover.js";
// Templates
import InputTemplate from "./generated/templates/InputTemplate.lit.js";
import InputPopoverTemplate from "./generated/templates/InputPopoverTemplate.lit.js";

import {
VALUE_STATE_SUCCESS,
VALUE_STATE_INFORMATION,
VALUE_STATE_ERROR,
VALUE_STATE_WARNING,
INPUT_SUGGESTIONS,
Expand All @@ -28,6 +31,7 @@ import {
// Styles
import styles from "./generated/themes/Input.css.js";
import ResponsivePopoverCommonCss from "./generated/themes/ResponsivePopoverCommon.css.js";
import ValueStateMessageCss from "./generated/themes/ValueStateMessage.css.js";

/**
* @public
Expand Down Expand Up @@ -87,6 +91,19 @@ const metadata = {
formSupport: {
type: HTMLElement,
},

/**
* The slot is used in order to display a valueStateMessage.
* <br><br>
* <b>Note:</b> The valueStateMessage would be displayed only if the <code>ui5-input</code> has
* a valueState of type <code>Information</code>, <code>Warning</code> or <code>Error</code>.
* @type {HTMLElement[]}
* @slot
* @public
*/
valueStateMessage: {
type: HTMLElement,
},
},
properties: /** @lends sap.ui.webcomponents.main.Input.prototype */ {

Expand Down Expand Up @@ -248,6 +265,15 @@ const metadata = {
_wrapperAccInfo: {
type: Object,
},

_inputWidth: {
type: Integer,
},

_isPopoverOpen: {
type: Boolean,
noAttribute: true,
},
},
events: /** @lends sap.ui.webcomponents.main.Input.prototype */ {
/**
Expand Down Expand Up @@ -350,7 +376,7 @@ class Input extends UI5Element {
}

static get staticAreaStyles() {
return ResponsivePopoverCommonCss;
return [ResponsivePopoverCommonCss, ValueStateMessageCss];
}

constructor() {
Expand Down Expand Up @@ -383,6 +409,16 @@ class Input extends UI5Element {
this.suggestionsTexts = [];

this.i18nBundle = getI18nBundle("@ui5/webcomponents");

this._handleResizeBound = this._handleResize.bind(this);
}

onEnterDOM() {
ResizeHandler.register(this, this._handleResizeBound);
}

onExitDOM() {
ResizeHandler.deregister(this, this._handleResizeBound);
}

onBeforeRendering() {
Expand Down Expand Up @@ -412,6 +448,10 @@ class Input extends UI5Element {
}
}

if (!this.firstRendering && !this.Suggestions && this.hasValueStateMessage) {
this.toggle(this.shouldDisplayOnlyValueStateMessage);
}

this.firstRendering = false;
}

Expand Down Expand Up @@ -477,6 +517,10 @@ class Input extends UI5Element {
return;
}

if (this.popover) {
this.popover.close(false, false, true);
}

this.previousValue = "";
this.focused = false; // invalidating property
}
Expand Down Expand Up @@ -513,6 +557,12 @@ class Input extends UI5Element {
}
}

_handleResize() {
if (this.hasValueStateMessage) {
this._inputWidth = this.offsetWidth;
}
}

_closeRespPopover() {
this.Suggestions.close();
}
Expand All @@ -531,6 +581,41 @@ class Input extends UI5Element {
}
}

toggle(isToggled) {
if (isToggled) {
this.openPopover();
} else {
this.closePopover();
}
}

/**
* Checks if the popover is open.
* @returns {Boolean} true if the popover is open, false otherwise
* @public
*/
isOpen() {
return !!this._isPopoverOpen;
}

async openPopover() {
this._isPopoverOpen = true;
this.popover = await this._getPopover();
this.popover.openBy(this);
}

closePopover() {
if (this.isOpen()) {
this._isPopoverOpen = false;
this.popover.close();
}
}

async _getPopover() {
const staticAreaItem = await this.getStaticAreaItemDomRef();
return staticAreaItem.querySelector("ui5-popover");
}

enableSuggestions() {
if (this.Suggestions) {
return;
Expand Down Expand Up @@ -655,6 +740,7 @@ class Input extends UI5Element {

return {
"Success": i18nBundle.getText(VALUE_STATE_SUCCESS),
"Information": i18nBundle.getText(VALUE_STATE_INFORMATION),
"Error": i18nBundle.getText(VALUE_STATE_ERROR),
"Warning": i18nBundle.getText(VALUE_STATE_WARNING),
};
Expand Down Expand Up @@ -699,10 +785,52 @@ class Input extends UI5Element {
};
}

get classes() {
return {
popoverValueState: {
"ui5-input-valuestatemessage-root": true,
"ui5-input-valuestatemessage-success": this.valueState === ValueState.Success,
"ui5-input-valuestatemessage-error": this.valueState === ValueState.Error,
"ui5-input-valuestatemessage-warning": this.valueState === ValueState.Warning,
"ui5-input-valuestatemessage-information": this.valueState === ValueState.Information,
},
};
}

get styles() {
return {
root: {
"min-height": "1rem",
"box-shadow": "none",
},
header: {
"width": `${this._inputWidth}px`,
},
};
}

get valueStateMessageText() {
const valueStateMessage = this.valueStateMessage.map(x => x.cloneNode(true));

return valueStateMessage;
}

get shouldDisplayOnlyValueStateMessage() {
return this.hasValueStateMessage && !this.suggestionItems.length && this.focused;
}

get shouldDisplayDefaultValueStateMessage() {
return !this.valueStateMessage.length && this.hasValueStateMessage;
}

get hasValueState() {
return this.valueState !== ValueState.None;
}

get hasValueStateMessage() {
return this.hasValueState && this.valueState !== ValueState.Success;
}

get valueStateText() {
return this.valueStateTextMappings()[this.valueState];
}
Expand All @@ -712,7 +840,10 @@ class Input extends UI5Element {
}

static async onDefine() {
await fetchI18nBundle("@ui5/webcomponents");
await Promise.all([
Popover.define(),
fetchI18nBundle("@ui5/webcomponents"),
]);
}
}

Expand Down
15 changes: 14 additions & 1 deletion packages/main/src/InputPopover.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,17 @@
>OK</ui5-button>
</div>
</ui5-responsive-popover>
{{/if}}
{{/if}}
{{#if hasValueStateMessage}}
<ui5-popover class="ui5-input-valuestatemessage-popover" skip-registry-update placement-type="Bottom" no-padding no-arrow style={{styles.root}}>
<div slot="header" class="ui5-responsive-popover-header {{classes.popoverValueState}}" style={{styles.header}}>
{{#if shouldDisplayDefaultValueStateMessage}}
{{valueStateText}}
{{else}}
{{#each valueStateMessageText}}
{{this}}
{{/each}}
{{/if}}
</div>
</ui5-popover>
{{/if}}
4 changes: 2 additions & 2 deletions packages/main/src/Popover.js
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,7 @@ class Popover extends UI5Element {
* Closes the popover.
* @public
*/
close(escPressed = false, preventRegitryUpdate = false) {
close(escPressed = false, preventRegitryUpdate = false, preventFocusRestore = false) {
if (!this.opened) {
return;
}
Expand All @@ -353,7 +353,7 @@ class Popover extends UI5Element {
removeOpenedPopover(this);
}

if (!this._prevetFocusRestore) {
if (!preventFocusRestore) {
this.resetFocus();
}

Expand Down
3 changes: 3 additions & 0 deletions packages/main/src/i18n/messagebundle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -303,5 +303,8 @@ VALUE_STATE_ERROR=Invalid entry
#XTOL: text that is appended to the tooltips of input fields etc. which are marked to have a warning
VALUE_STATE_WARNING=Warning issued

#XTOL: text that is appended to the tooltips of input fields etc. which are marked to be informative
VALUE_STATE_INFORMATION=Informative entry

#XTOL: text that is appended to the tooltips of input fields etc. which are marked to be in success state
VALUE_STATE_SUCCESS=Entry successfully validated
26 changes: 26 additions & 0 deletions packages/main/src/themes/ValueStateMessage.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
.ui5-input-valuestatemessage-root {
box-sizing: border-box;
display: inline-block;
color: var(--sapUiBaseColor);
font-size: var(--sapFontSmallSize);
font-family: var(--sapFontFamily);
padding: .3rem .625rem;
overflow: hidden;
text-overflow: ellipsis;
}

.ui5-input-valuestatemessage-success {
background: var(--sapSuccessBackground);
}

.ui5-input-valuestatemessage-warning {
background: var(--sapWarningBackground);
}

.ui5-input-valuestatemessage-error {
background: var(--sapErrorBackground);
}

.ui5-input-valuestatemessage-information {
background: var(--sapInformationBackground);
}
5 changes: 2 additions & 3 deletions packages/main/test/pages/Input.html
Original file line number Diff line number Diff line change
Expand Up @@ -103,13 +103,12 @@ <h3> 'suggestionItemSelect' event result on group item</h3>

<h3> Test 'input' event</h3>
<ui5-input id="input2" value-state="Error" placeholder="Error state ...">
<div slot="valueStateMessage">Information message. This is a <a href="#">Link</a>. Extra long text used as an information message. Extra long text used as an information message - 2. Extra long text used as an information message - 3.</div>
<ui5-icon slot="icon" name="message-error"></ui5-icon>
</ui5-input>
<h3> 'input' test result</h3>
<ui5-input id="inputLiveChangeResult"></ui5-input>



<h3> Input test change</h3>
<ui5-input id="inputChange"> </ui5-input>
<h3> Input test change result</h3>
Expand Down Expand Up @@ -161,7 +160,7 @@ <h3> Inputs alignment</h3>
<ui5-option selected>Compact</ui5-option>
<ui5-option>Condensed</ui5-option>
</ui5-select>

<ui5-input value="input" value-state="Error">
<ui5-icon slot="icon" name="message-warning"></ui5-icon>
</ui5-input>
Expand Down
10 changes: 10 additions & 0 deletions packages/main/test/specs/Input.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -211,4 +211,14 @@ describe("Input general interaction", () => {
assert.ok(input5.getProperty("maxlength"), "Input's maxlength property should be applied.");
assert.strictEqual(inputShadowRef.getAttribute("maxlength"), "10", "Input's maxlength attribute should be applied.");
});

it("Checks if valueStateMessage is shown", () => {
let inputShadowRef = browser.$("#input2").shadow$("input");
let staticAreaItemClassName = browser.getStaticAreaItemClassName("#input2");
let popover = browser.$(`.${staticAreaItemClassName}`).shadow$("ui5-popover");

inputShadowRef.click();

assert.ok(popover.getProperty("opened"), "Popover with valueStateMessage should be opened.");
});
});

0 comments on commit 538a79a

Please sign in to comment.