Skip to content

Commit

Permalink
feat(ui5-datepicker): implement valuestatemessage slot (#1476)
Browse files Browse the repository at this point in the history
  • Loading branch information
fifoosid committed May 5, 2020
1 parent ec14719 commit 82b3d41
Show file tree
Hide file tree
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
Expand Up @@ -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"
Expand Down
21 changes: 21 additions & 0 deletions packages/main/src/DatePicker.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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 */ {

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

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

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

Expand Down Expand Up @@ -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;
}

Expand All @@ -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();
}
Expand All @@ -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);

Expand All @@ -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();
}
}

Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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;
}
Expand All @@ -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() {
Expand Down
10 changes: 5 additions & 5 deletions packages/main/src/InputPopover.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
{{#if hasValueStateMessage}}
<div class="row {{classes.popoverValueState}}" style="{{styles.suggestionPopoverHeader}}">
{{> valueStateMessage}}
</div>
</div>
{{/if}}
</div>
{{/if}}
Expand All @@ -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}}
Expand Down Expand Up @@ -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
Expand Up @@ -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");
Expand Down
8 changes: 8 additions & 0 deletions packages/main/test/pages/DatePicker.html
Original file line number Diff line number Diff line change
Expand Up @@ -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...'
Expand Down
9 changes: 9 additions & 0 deletions packages/main/test/pages/DatePicker_test_page.html
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
1 change: 1 addition & 0 deletions packages/main/test/pages/Input.html
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
10 changes: 7 additions & 3 deletions packages/main/test/samples/DatePicker.sample.html
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down
33 changes: 23 additions & 10 deletions packages/main/test/specs/DatePicker.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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", "");
Expand Down Expand Up @@ -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";

Expand Down Expand Up @@ -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");

Expand All @@ -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");

Expand All @@ -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");

Expand Down
Loading

0 comments on commit 82b3d41

Please sign in to comment.