Skip to content

Commit

Permalink
feat(ui5-datepicker): implement valuestatemessage slot (#1476)
Browse files Browse the repository at this point in the history
fifoosid authored May 5, 2020

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
1 parent ec14719 commit 82b3d41
Showing 11 changed files with 130 additions and 38 deletions.
5 changes: 5 additions & 0 deletions packages/main/src/DatePicker.hbs
Original file line number Diff line number Diff line change
@@ -18,6 +18,11 @@
data-sap-focus-ref
._inputAccInfo ="{{accInfo}}"
>

{{#if valueStateMessage.length}}
<slot name="valueStateMessage" slot="valueStateMessage"></slot>
{{/if}}

{{#unless readonly}}
<ui5-icon
slot="icon"
21 changes: 21 additions & 0 deletions packages/main/src/DatePicker.js
Original file line number Diff line number Diff line change
@@ -37,6 +37,7 @@ import ResponsivePopoverCommonCss from "./generated/themes/ResponsivePopoverComm
*/
const metadata = {
tag: "ui5-datepicker",
managedSlots: true,
properties: /** @lends sap.ui.webcomponents.main.DatePicker.prototype */ {
/**
* Defines a formatted date value.
@@ -199,6 +200,26 @@ const metadata = {
type: Object,
},
},

slots: /** @lends sap.ui.webcomponents.main.DatePicker.prototype */ {
/**
* Defines the value state message that will be displayed as pop up under the <code>ui5-datepicker</code>.
* <br><br>
*
* <b>Note:</b> If not specified, a default text (in the respective language) will be displayed.
* <br>
* <b>Note:</b> The <code>valueStateMessage</code> would be displayed,
* when the <code>ui5-datepicker</code> is in <code>Information</code>, <code>Warning</code> or <code>Error</code> value state.
* @type {HTMLElement}
* @since 1.0.0-rc.7
* @slot
* @public
*/
valueStateMessage: {
type: HTMLElement,
},
},

events: /** @lends sap.ui.webcomponents.main.DatePicker.prototype */ {

/**
65 changes: 46 additions & 19 deletions packages/main/src/Input.js
Original file line number Diff line number Diff line change
@@ -300,6 +300,11 @@ const metadata = {
type: Boolean,
noAttribute: true,
},

_inputIconFocused: {
type: Boolean,
noAttribute: true,
},
},
events: /** @lends sap.ui.webcomponents.main.Input.prototype */ {
/**
@@ -474,7 +479,7 @@ class Input extends UI5Element {

if (!isPhone() && shouldOpenSuggestions) {
// Set initial focus to the native input
this.getInputDOMRef().focus();
this.inputDomRef.focus();
}
}

@@ -535,15 +540,21 @@ class Input extends UI5Element {
}
}

_onfocusin(event) {
async _onfocusin(event) {
this.focused = true; // invalidating property
this.previousValue = this.value;

await this.getInputDOMRef();
this._inputIconFocused = event.target === this.querySelector("ui5-icon");
}

_onfocusout(event) {
// if focusout is triggered by pressing on suggestion item skip invalidation, because re-rendering
const focusedOutToSuggestions = this.Suggestions && event.relatedTarget && event.relatedTarget.shadowRoot && event.relatedTarget.shadowRoot.contains(this.Suggestions.responsivePopover);
const focusedOutToValueStateMessage = event.relatedTarget && event.relatedTarget.shadowRoot && event.relatedTarget.shadowRoot.querySelector(".ui5-valuestatemessage-root");

// if focusout is triggered by pressing on suggestion item or value state message popover, skip invalidation, because re-rendering
// will happen before "itemPress" event, which will make item "active" state not visualized
if (this.Suggestions && event.relatedTarget && event.relatedTarget.shadowRoot && event.relatedTarget.shadowRoot.contains(this.Suggestions.responsivePopover)) {
if (focusedOutToSuggestions || focusedOutToValueStateMessage) {
return;
}

@@ -567,8 +578,9 @@ class Input extends UI5Element {
this.fireEvent(this.EVENT_CHANGE);
}

_handleInput(event) {
if (event.target === this.getInputDOMRef()) {
async _handleInput(event) {
await this.getInputDOMRef();
if (event.target === this.inputDomRef) {
// stop the native event, as the semantic "input" would be fired.
event.stopImmediatePropagation();
}
@@ -577,7 +589,7 @@ class Input extends UI5Element {
- value of the host and the internal input should be differnt in case of actual input
- input is called when a key is pressed => keyup should not be called yet
*/
const skipFiring = (this.getInputDOMRef().value === this.value) && isIE() && !this._keyDown && !!this.placeholder;
const skipFiring = (this.inputDomRef.value === this.value) && isIE() && !this._keyDown && !!this.placeholder;

!skipFiring && this.fireEventByAction(this.ACTION_USER_INPUT);

@@ -589,19 +601,18 @@ class Input extends UI5Element {
}

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

_closeRespPopover() {
this.Suggestions.close();
}

_afterOpenPopover() {
async _afterOpenPopover() {
// Set initial focus to the native input
if (isPhone()) {
this.getInputDOMRef().focus();
await this.getInputDOMRef();
this.inputDomRef.focus();
}
}

@@ -692,7 +703,9 @@ class Input extends UI5Element {
this.value = item.group ? "" : item.textContent;
}

fireEventByAction(action) {
async fireEventByAction(action) {
await this.getInputDOMRef();

if (this.disabled || this.readonly) {
return;
}
@@ -724,23 +737,25 @@ class Input extends UI5Element {
getInputValue() {
const inputDOM = this.getDomRef();
if (inputDOM) {
return this.getInputDOMRef().value;
return this.inputDomRef.value;
}
return "";
}

getInputDOMRef() {
async getInputDOMRef() {
let inputDomRef;

if (isPhone()) {
if (isPhone() && this.Suggestions) {
await this.Suggestions._respPopover();
inputDomRef = this.Suggestions && this.Suggestions.responsivePopover.querySelector(".ui5-input-inner-phone");
}

if (!inputDomRef) {
inputDomRef = this.getDomRef().querySelector(`#${this.getInputId()}`);
}

return inputDomRef;
this.inputDomRef = inputDomRef;
return this.inputDomRef;
}

getLabelableElementId() {
@@ -842,7 +857,17 @@ class Input extends UI5Element {
}

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

this.valueStateMessage.forEach(el => {
if (el.localName === "slot") {
el.assignedNodes({ flatten: true }).forEach(assignedNode => {
valueStateMessage.push(assignedNode.cloneNode(true));
});
} else {
valueStateMessage.push(el.cloneNode(true));
}
});

return valueStateMessage;
}
@@ -860,7 +885,9 @@ class Input extends UI5Element {
}

get hasValueStateMessage() {
return this.hasValueState && this.valueState !== ValueState.Success;
return this.hasValueState && this.valueState !== ValueState.Success
&& (!this._inputIconFocused // Handles the cases when valueStateMessage is forwarded (from datepicker e.g.)
|| (this._isPhone && this.Suggestions)); // Handles Input with suggestions on mobile
}

get valueStateText() {
10 changes: 5 additions & 5 deletions packages/main/src/InputPopover.hbs
Original file line number Diff line number Diff line change
@@ -34,7 +34,7 @@
{{#if hasValueStateMessage}}
<div class="row {{classes.popoverValueState}}" style="{{styles.suggestionPopoverHeader}}">
{{> valueStateMessage}}
</div>
</div>
{{/if}}
</div>
{{/if}}
@@ -43,23 +43,23 @@
{{#if hasValueStateMessage}}
<div slot="header" class="ui5-responsive-popover-header {{classes.popoverValueState}}" style={{styles.suggestionPopoverHeader}}>
{{> valueStateMessage}}
</div>
</div>
{{/if}}
{{/unless}}


<ui5-list separators="Inner">
{{#each suggestionsTexts}}
{{#if group}}
<ui5-li-groupheader>{{ this.text }}</ui5-groupheader>
<ui5-li-groupheader>{{ this.text }}</ui5-li-groupheader>
{{else}}
<ui5-li
image="{{this.image}}"
icon="{{this.icon}}"
description="{{this.description}}"
info="{{this.info}}"
info-state="{{this.infoState}}"
@ui5-_itemPress="{{ fnOnSuggestionItemPress }}"
@ui5-_itemPress="{{ fnOnSuggestionItemPress }}"
>{{ this.text }}</ui5-li>
{{/if}}
{{/each}}
@@ -96,4 +96,4 @@
{{this}}
{{/each}}
{{/if}}
{{/inline}}
{{/inline}}
4 changes: 4 additions & 0 deletions packages/main/test/pageobjects/DatePickerTestPage.js
Original file line number Diff line number Diff line change
@@ -39,6 +39,10 @@ class DatePickerTestPage {
return browser.$(this._sut).shadow$("ui5-input").shadow$("input");
}

get inputStaticAreaItem() {
return browser.$(`.${this.input.getProperty("_id")}`);
}

hasIcon() {
return browser.execute(function(id) {
return !!document.querySelector(id).shadowRoot.querySelector("ui5-icon");
8 changes: 8 additions & 0 deletions packages/main/test/pages/DatePicker.html
Original file line number Diff line number Diff line change
@@ -41,6 +41,14 @@
title='Delivery Date!'>
</ui5-datepicker>

<ui5-datepicker id='ui5-datepicker-value-state-message'
placeholder='Delivery Date...'
value-state="Error"
title='Delivery Date!'>
<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>
<div slot="valueStateMessage">Information message 2. 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-datepicker>

<h3>placeholder + title + events</h3>
<ui5-datepicker id='dp5'
placeholder='Delivery Date...'
9 changes: 9 additions & 0 deletions packages/main/test/pages/DatePicker_test_page.html
Original file line number Diff line number Diff line change
@@ -56,6 +56,15 @@ <h3>Test placeholder</h3>
<ui5-datepicker id="dp14" format-pattern="MMM d, y"></ui5-datepicker>
<ui5-datepicker id="dp15" format-pattern="MMM d, y" placeholder="Delivery date"></ui5-datepicker>

<h3>DatePicker with valueStateMessage</h3>
<ui5-datepicker
id="dp17"
value-state="Error">
<div slot="valueStateMessage" id="coolValueStateMessage">
This date is wrong
</div>
</ui5-datepicker>

<script>
var originalGetDate = Date.prototype.getDate;

1 change: 1 addition & 0 deletions packages/main/test/pages/Input.html
Original file line number Diff line number Diff line change
@@ -103,6 +103,7 @@ <h3>Input with valueState and Dynamic suggestions</h3>
value-state="Error"
placeholder="Search for a country ...">
<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>
<div slot="valueStateMessage">Information message 2. 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-input>

<h3>Input suggestions with valueState and ui5-li</h3>
10 changes: 7 additions & 3 deletions packages/main/test/samples/DatePicker.sample.html
Original file line number Diff line number Diff line change
@@ -19,14 +19,18 @@ <h3>Basic DatePicker</h3>
</section>

<section>
<h3>DatePicker with Placeholder, Tooltip, Events, and ValueState</h3>
<h3>DatePicker with Placeholder, Tooltip, Events, ValueState and valueStateMessage</h3>
<div class="snippet">
<div class="datepicker-width">
<ui5-datepicker id='myDatepicker2' placeholder='Delivery Date...' title='Delivery Date!'></ui5-datepicker>
<ui5-datepicker id='myDatepicker2' placeholder='Delivery Date...' title='Delivery Date!'>
<div slot="valueStateMessage">The value is not valid. Please provide valid value</div>
</ui5-datepicker>
</div>
</div>
<pre class="prettyprint lang-html"><xmp>
<ui5-datepicker id="myDatepicker2" placeholder='Delivery Date...' title='Delivery Date!'></ui5-datepicker>
<ui5-datepicker id='myDatepicker2' placeholder='Delivery Date...' title='Delivery Date!'>
<div slot="valueStateMessage">The value is not valid. Please provide valid value</div>
</ui5-datepicker>
<script>
const dp = document.getElementById('myDatepicker2');
dp.addEventListener('change', (e) => {
33 changes: 23 additions & 10 deletions packages/main/test/specs/DatePicker.spec.js
Original file line number Diff line number Diff line change
@@ -39,6 +39,26 @@ describe("Date Picker Tests", () => {
assert.ok(contentWrapper.isDisplayedInViewport(), "content wrapper has error styles");
});

it("Can focus the input after open", () => {
datepicker.id = "#dp1";
datepicker.openPicker({ focusInput: true });
const a = datepicker.innerInput.isFocusedDeep();

console.log(datepicker.innerInput.isFocusedDeep());
assert.ok(a, "inner input is focused");
});

it("Value State Message", () => {
datepicker.id = "#dp17"
datepicker.root.click();

const inputStaticAreaItem = datepicker.inputStaticAreaItem;
const popover = inputStaticAreaItem.shadow$("ui5-popover");

const slot = popover.$("#coolValueStateMessage");
assert.notOk(slot.error, "Value State message slot is working");
});

it("disabled", () => {
datepicker.id = "#dp2";
datepicker.root.setAttribute("disabled", "");
@@ -81,13 +101,6 @@ describe("Date Picker Tests", () => {
assert.equal(datepicker.innerInput.getAttribute("value"), "Rab. I 6, 1440 AH", "input has correct Islamic value");
});

it("Can focus the input after open", () => {
datepicker.id = "#dp1";
datepicker.openPicker({ focusInput: true });

assert.ok(datepicker.innerInput.isFocusedDeep(), "inner input is focused");
});

it("Selected date from daypicker is the same as datepicker date", () => {
datepicker.id = "#dp4";

@@ -551,7 +564,7 @@ describe("Date Picker Tests", () => {
while(datepicker.root.getValue() !== ""){
datepicker.root.keys("Backspace");
}

datepicker.root.keys("May 5, 2100");
datepicker.root.keys("Enter");

@@ -568,7 +581,7 @@ describe("Date Picker Tests", () => {
while(datepicker.root.getValue() !== ""){
datepicker.root.keys("Backspace");
}

datepicker.root.keys("Jan 8, 2100");
datepicker.root.keys("Enter");

@@ -578,7 +591,7 @@ describe("Date Picker Tests", () => {
while(datepicker.root.getValue() !== ""){
datepicker.root.keys("Backspace");
}

datepicker.root.keys("Jan 1, 2000");
datepicker.root.keys("Enter");

2 changes: 1 addition & 1 deletion packages/tools/components-package/wdio.js
Original file line number Diff line number Diff line change
@@ -57,7 +57,7 @@ exports.config = {
// to run chrome headless the following flags are required
// (see https://developers.google.com/web/updates/2017/04/headless-chrome)
args: ['--headless', '--disable-gpu'],
//args: ['--disable-gpu'],
// args: ['--disable-gpu'],
}
}],
//

0 comments on commit 82b3d41

Please sign in to comment.