diff --git a/packages/main/src/Select.ts b/packages/main/src/Select.ts index 8735e2d3f2c9..4e257d76ad96 100644 --- a/packages/main/src/Select.ts +++ b/packages/main/src/Select.ts @@ -358,7 +358,6 @@ class Select extends UI5Element implements IFormElement { responsivePopover!: ResponsivePopover; selectedItem?: string | null; valueStatePopover?: Popover; - value!: string; selectMenu?: SelectMenu; @@ -460,6 +459,9 @@ class Select extends UI5Element implements IFormElement { if (menu) { menu.value = this.value; + // To cause invalidation when the menu is used for another Select that could have the same value as the previous. + // Otherwise, the menu won't re-render. + menu.selectId = this.__id; } else { this._syncSelection(); } @@ -504,6 +506,34 @@ class Select extends UI5Element implements IFormElement { return staticAreaItem!.querySelector("[ui5-responsive-popover]")!; } + /** + * Defines the value of the component: + *
+ * - when get - returns the value of the component, e.g. the value property of the selected option or its text content. + *
+ * - when set - selects the option with matching value property or text content. + *

+ * Note: If the given value does not match any existing option, + * the first option will get selected. + * + * @public + * @type { string } + * @defaultvalue "" + * @name sap.ui.webc.main.Select.prototype.value + * @since 1.20.0 + * @formProperty + * @formEvents change liveChange + */ + set value(newValue: string) { + this.selectOptions.forEach(option => { + option.selected = !!((option.value || option.textContent) === newValue); + }); + } + + get value(): string { + return this.selectedOption?.value || this.selectedOption?.textContent || ""; + } + /** * Currently selected ui5-option element. * @readonly @@ -592,8 +622,7 @@ class Select extends UI5Element implements IFormElement { firstEnabledOptionIndex = -1; const options = this._filteredItems; const syncOpts = options.map((opt, index) => { - if (opt.selected || opt.textContent === this.value) { - // The second condition in the IF statement is added because of Angular Reactive Forms Support(Two way data binding) + if (opt.selected) { lastSelectedOptionIndex = index; } if (firstEnabledOptionIndex === -1) { @@ -674,7 +703,7 @@ class Select extends UI5Element implements IFormElement { formSupport.syncNativeHiddenInput(this, (element: IFormElement, nativeInput: NativeFormElement) => { const selectElement = (element as Select); nativeInput.disabled = !!element.disabled; - nativeInput.value = selectElement._currentlySelectedOption ? selectElement._currentlySelectedOption.value : ""; + nativeInput.value = selectElement.value; }); } else if (this.name) { console.warn(`In order for the "name" property to have effect, you should also: import "@ui5/webcomponents/dist/features/InputElementsFormSupport.js";`); // eslint-disable-line diff --git a/packages/main/src/SelectMenu.hbs b/packages/main/src/SelectMenu.hbs index bc08ff919a67..6d6e83b9ef3f 100644 --- a/packages/main/src/SelectMenu.hbs +++ b/packages/main/src/SelectMenu.hbs @@ -30,7 +30,7 @@ {{/if}} {{#unless _isPhone}} {{#if hasValueState}} -
+
{{> valueStateMessage}}
diff --git a/packages/main/src/SelectMenu.ts b/packages/main/src/SelectMenu.ts index ab0309545d54..bfedae0b3976 100644 --- a/packages/main/src/SelectMenu.ts +++ b/packages/main/src/SelectMenu.ts @@ -135,9 +135,12 @@ class SelectMenu extends UI5Element { @property() valueStateText!: string; - @property() + @property({ type: String, noAttribute: true }) value!: string; + @property({ type: String, noAttribute: true }) + selectId?: string; + valueStateMessageText: Array; _headerTitleText?: string; @@ -178,8 +181,7 @@ class SelectMenu extends UI5Element { selectedIndex; const options = this.options; options.forEach((opt, index) => { - if (opt.selected || opt.textContent === this.value) { - // The second condition in the IF statement is added because of Angular Reactive Forms Support(Two way data binding) + if (opt.selected) { lastSelectedOptionIndex = index; } if (firstEnabledOptionIndex === -1) { @@ -266,8 +268,7 @@ class SelectMenu extends UI5Element { get styles() { return { - responsivePopoverHeader: { - "display": this.options.length && this.respPopover?.offsetWidth === 0 ? "none" : "inline-block", + valueStatePopover: { "width": `${this.selectWidth!}px`, }, responsivePopover: { diff --git a/packages/main/test/pages/FormSupport.html b/packages/main/test/pages/FormSupport.html index 709c71f51cac..e7eb335991f2 100644 --- a/packages/main/test/pages/FormSupport.html +++ b/packages/main/test/pages/FormSupport.html @@ -17,6 +17,11 @@
+ + Cozy + Compact + Condensed +

diff --git a/packages/main/test/pages/Select.html b/packages/main/test/pages/Select.html index cb4e180cc6ca..a1a123328f87 100644 --- a/packages/main/test/pages/Select.html +++ b/packages/main/test/pages/Select.html @@ -152,6 +152,18 @@

Select with additional text

Australia + +
+

Select "value" property

+ + Item1 + Item2 + Item3 + + select.value = "Item2" + select.value = "NAN" +
+ diff --git a/packages/main/test/pages/SelectMenu.html b/packages/main/test/pages/SelectMenu.html index e18887a10309..7b9b267f9efc 100644 --- a/packages/main/test/pages/SelectMenu.html +++ b/packages/main/test/pages/SelectMenu.html @@ -18,6 +18,7 @@


+
@@ -52,7 +53,9 @@ Reset counters Focus out -
+ select.value = "item1" + select.value = "nan" +
Select + Option
@@ -74,7 +77,7 @@ - +
T-shirt [1]
@@ -84,7 +87,7 @@
- +
Dress [2]
@@ -94,7 +97,7 @@
- +
Skirt [3]
@@ -174,15 +177,15 @@ - +
Item 1
- +
Item 2
- +
Item 3
@@ -617,10 +620,12 @@ sel.addEventListener("ui5-change", function(e) { lbl.innerHTML = "CHANGE EVENT :: " + e.detail.selectedOption.localName + " :: <" + e.detail.selectedOption.textContent + "> :: event counter :: <" + (++counter) + ">"; + lblValue.innerHTML = "Select value :: " + sel.value; }); sel.addEventListener("ui5-live-change", function(e) { lblLive.innerHTML = "PREVIEW CHANGE EVENT :: " + e.detail.selectedOption.localName + " :: <" + e.detail.selectedOption.textContent + "> :: event counter :: <" + (++liveCounter) + ">"; + lblValue.innerHTML = "Select value :: " + sel.value; mainText.innerHTML = e.detail.selectedOption.getAttribute("data-display-text") avatar.initials = e.detail.selectedOption.getAttribute("data-additional-text"); }); @@ -663,5 +668,12 @@ testOpen.value = testOpenCounter; testClose.value = testCloseCounter; }); + + btnSetValue.addEventListener("click", function(e) { + selTest.value = "item1"; + }); + btnSetInvalidValue.addEventListener("click", function(e) { + selTest.value = "nan"; + }); diff --git a/packages/main/test/specs/FormSupport.spec.js b/packages/main/test/specs/FormSupport.spec.js index 5e1b1445e090..7a5ab95c3f50 100644 --- a/packages/main/test/specs/FormSupport.spec.js +++ b/packages/main/test/specs/FormSupport.spec.js @@ -28,7 +28,7 @@ describe("Form support", () => { await submitButton.click(); const formWasSubmitted = await browser.executeAsync(done => { - const expectedFormData = "?input=ok&ta=ok%0D%0Aok&dp=Apr+10%2C+2019&cb=on&radio=b&si=5"; + const expectedFormData = "?input=ok&sel=condensed&ta=ok%0D%0Aok&dp=Apr+10%2C+2019&cb=on&radio=b&si=5"; done(location.href.endsWith(expectedFormData)); }); assert.ok(formWasSubmitted, "For was submitted and URL changed"); diff --git a/packages/main/test/specs/Select.spec.js b/packages/main/test/specs/Select.spec.js index 3d306c439c99..b19a97cd1513 100644 --- a/packages/main/test/specs/Select.spec.js +++ b/packages/main/test/specs/Select.spec.js @@ -32,6 +32,7 @@ describe("Select general interaction", () => { assert.strictEqual(await inputResult.getProperty("value"), "1", "Fired change event is called once."); const selectTextHtml = await selectText.getHTML(false); + assert.strictEqual(await select.getProperty("value"), EXPECTED_SELECTION_TEXT, "The 'value' property is correct."); assert.include(selectTextHtml, EXPECTED_SELECTION_TEXT, "Select label is correct."); }); @@ -47,6 +48,7 @@ describe("Select general interaction", () => { await secondItem.click(); const selectTextHtml = await selectText.getHTML(false); + assert.strictEqual(await select.getProperty("value"), EXPECTED_SELECTION_TEXT, "The 'value' property is correct."); assert.include(selectTextHtml, EXPECTED_SELECTION_TEXT, "Select label is not changed (reverted on third item)."); }); @@ -91,6 +93,7 @@ describe("Select general interaction", () => { it("fires change on selection with keyboard handling", async () => { await browser.url(`test/pages/Select.html`); + const selectHost = await browser.$("#errorSelect") const select = await browser.$("#errorSelect").shadow$(".ui5-select-root"); const selectText = await browser.$("#errorSelect").shadow$(".ui5-select-label-root"); const inputResult = await browser.$("#inputResult"); @@ -104,6 +107,7 @@ describe("Select general interaction", () => { assert.strictEqual(await inputResult.getProperty("value"), "1", "Fired change event is called once more."); let selectTextHtml = await selectText.getHTML(false); assert.include(selectTextHtml, EXPECTED_SELECTION_TEXT1, "Select label is correct."); + assert.strictEqual(await selectHost.getProperty("value"), EXPECTED_SELECTION_TEXT1, "The 'value' property is correct."); await select.click(); await select.keys("ArrowDown"); @@ -112,7 +116,7 @@ describe("Select general interaction", () => { assert.strictEqual(await inputResult.getProperty("value"), "2", "Fired change event is called once more."); selectTextHtml = await selectText.getHTML(false); assert.include(selectTextHtml, EXPECTED_SELECTION_TEXT2, "Select label is correct."); - + assert.strictEqual(await selectHost.getProperty("value"), EXPECTED_SELECTION_TEXT2, "The 'value' property is correct."); }); it("changes selection while closed with Arrow Up/Down", async () => { @@ -131,10 +135,12 @@ describe("Select general interaction", () => { await select.keys("ArrowUp"); let selectTextHtml = await selectText.getHTML(false); assert.include(selectTextHtml, EXPECTED_SELECTION_TEXT1, "Arrow Up should change selected item"); + assert.strictEqual(await select.getProperty("value"), EXPECTED_SELECTION_TEXT1, "The 'value' property is correct."); await select.keys("ArrowDown"); selectTextHtml = await selectText.getHTML(false); assert.include(selectTextHtml, EXPECTED_SELECTION_TEXT2, "Arrow Down should change selected item"); + assert.strictEqual(await select.getProperty("value"), EXPECTED_SELECTION_TEXT2, "The 'value' property is correct."); assert.strictEqual(await inputResult.getProperty("value"), "2", "Change event should have fired twice"); }); @@ -213,7 +219,7 @@ describe("Select general interaction", () => { const selectTextHtml = await selectText.getHTML(false); assert.include(selectTextHtml, EXPECTED_SELECTION_TEXT, "Arrow Up should change selected item"); - + const focusedElementId = await browser.executeAsync(done => { done(document.activeElement.id); }); @@ -306,6 +312,32 @@ describe("Select general interaction", () => { assert.include(selectTextHtml, EXPECTED_SELECTION_TEXT, "Typing text should change selection"); }); + it("changes selection using 'value'", async () => { + const select = await browser.$("#mySelect7"); + const btnSetValue = await browser.$("#btnSetValue"); + const btnSetInvalidValue = await browser.$("#btnSetInvalidValue"); + const selectText = await select.shadow$(".ui5-select-label-root"); + const EXPECTED_SELECTION_TEXT1 = "Item1"; + const EXPECTED_SELECTION_TEXT2 = "Item2"; + + + await btnSetValue.click(); + let selectTextHtml = await selectText.getHTML(false); + + assert.strictEqual(await select.getProperty("value"), + EXPECTED_SELECTION_TEXT2, "Second option is selected."); + assert.include(selectTextHtml, EXPECTED_SELECTION_TEXT2, + "Select label is " + EXPECTED_SELECTION_TEXT2); + + await btnSetInvalidValue.click(); + selectTextHtml = await selectText.getHTML(false); + + assert.strictEqual(await select.getProperty("value"), + EXPECTED_SELECTION_TEXT1, "First option is selected as value did not match any options."); + assert.include(selectTextHtml, EXPECTED_SELECTION_TEXT1, + "Select label is " + EXPECTED_SELECTION_TEXT1); + }); + it("opens upon space", async () => { await browser.url(`test/pages/Select.html`); @@ -573,5 +605,5 @@ describe("Attributes propagation", () => { assert.strictEqual(await firstOption.getProperty("additionalText"), EXPECTED_ADDITIONAL_TEXT, "The additional text is set"); assert.strictEqual(await firstItem.getProperty("additionalText"), EXPECTED_ADDITIONAL_TEXT, "The additional text is correct"); - }); + }); }); diff --git a/packages/main/test/specs/SelectMenu.spec.js b/packages/main/test/specs/SelectMenu.spec.js index 9e79a38b9392..0bafdfe90032 100644 --- a/packages/main/test/specs/SelectMenu.spec.js +++ b/packages/main/test/specs/SelectMenu.spec.js @@ -12,6 +12,7 @@ describe("Select Menu general interaction", () => { const selectTextHtml = await selectText.getHTML(false); assert.include(selectTextHtml, EXPECTED_SELECTION_TEXT, "Select label is correct."); + assert.strictEqual(await select.getProperty("value"), EXPECTED_SELECTION_TEXT, "The 'value' property is correct."); }); it("fires 'open' and 'close' events", async () => { @@ -41,10 +42,12 @@ describe("Select Menu general interaction", () => { await select.keys("ArrowDown"); let selectTextHtml = await selectText.getHTML(false); assert.include(selectTextHtml, EXPECTED_SELECTION_TEXT1, "Arrow Up should change selected item"); + assert.strictEqual(await select.getProperty("value"), EXPECTED_SELECTION_TEXT1, "The 'value' property is correct."); await select.keys("ArrowUp"); selectTextHtml = await selectText.getHTML(false); assert.include(selectTextHtml, EXPECTED_SELECTION_TEXT2, "Arrow Down should change selected item"); + assert.strictEqual(await select.getProperty("value"), EXPECTED_SELECTION_TEXT2, "The 'value' property is correct."); assert.strictEqual(await inpTestChange.getProperty("value"), "2", "Change event should have fired twice"); }); @@ -68,6 +71,7 @@ describe("Select Menu general interaction", () => { const selectTextHtml = await selectText.getHTML(false); assert.include(selectTextHtml, EXPECTED_SELECTION_TEXT, "Select label is correct."); + assert.strictEqual(await select.getProperty("value"), EXPECTED_SELECTION_TEXT, "The 'value' property is correct."); }); it("fires 'change' and 'live-change' on Arrow Down, Arrow Up", async () => { @@ -91,6 +95,7 @@ describe("Select Menu general interaction", () => { assert.strictEqual(await inpTestChange.getProperty("value"), "1", "Fired 'change' event once."); let selectTextHtml = await selectText.getHTML(false); assert.include(selectTextHtml, EXPECTED_SELECTION_TEXT1, "Select label is correct."); + assert.strictEqual(await select.getProperty("value"), EXPECTED_SELECTION_TEXT1, "The 'value' property is correct."); await select.click(); await select.keys("ArrowUp"); @@ -100,6 +105,7 @@ describe("Select Menu general interaction", () => { assert.strictEqual(await inpTestChange.getProperty("value"), "2", "Fired 'change' event once more."); selectTextHtml = await selectText.getHTML(false); assert.include(selectTextHtml, EXPECTED_SELECTION_TEXT2, "Select label is correct."); + assert.strictEqual(await select.getProperty("value"), EXPECTED_SELECTION_TEXT2, "The 'value' property is correct."); }); it("reverts value on ESC key", async () => {