Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ui5-select): introduce value property #7865

Merged
merged 13 commits into from
Dec 1, 2023
37 changes: 33 additions & 4 deletions packages/main/src/Select.ts
Original file line number Diff line number Diff line change
Expand Up @@ -358,7 +358,6 @@ class Select extends UI5Element implements IFormElement {
responsivePopover!: ResponsivePopover;
selectedItem?: string | null;
valueStatePopover?: Popover;
value!: string;

selectMenu?: SelectMenu;

Expand Down Expand Up @@ -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();
}
Expand Down Expand Up @@ -504,6 +506,34 @@ class Select extends UI5Element implements IFormElement {
return staticAreaItem!.querySelector<ResponsivePopover>("[ui5-responsive-popover]")!;
}

/**
* Defines the value of the component:
* <br>
* - when get - returns the value of the component, e.g. the <code>value</code> property of the selected option or its text content.
* <br>
* - when set - selects the option with matching <code>value</code> property or text content.
* <br><br>
* <b>Note:</b> 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 <code>ui5-option</code> element.
* @readonly
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion packages/main/src/SelectMenu.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
{{/if}}
{{#unless _isPhone}}
{{#if hasValueState}}
<div class="{{classes.popoverValueState}}" style={{styles.responsivePopoverHeader}}>
<div class="{{classes.popoverValueState}}" style={{styles.valueStatePopover}}>
<ui5-icon class="ui5-input-value-state-message-icon" name="{{_valueStateMessageInputIcon}}"></ui5-icon>
{{> valueStateMessage}}
</div>
Expand Down
11 changes: 6 additions & 5 deletions packages/main/src/SelectMenu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Node>;

_headerTitleText?: string;
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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: {
Expand Down
5 changes: 5 additions & 0 deletions packages/main/test/pages/FormSupport.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@
<form method="get">
<ui5-input name="input" value="ok"></ui5-input>
<ui5-input name="input_disabled" disabled value="ok"></ui5-input>
<ui5-select name="sel">
<ui5-option value="cozy">Cozy</ui5-option>
<ui5-option value="compact">Compact</ui5-option>
<ui5-option value="condensed" selected>Condensed</ui5-option>
</ui5-select>
<br><br>
<ui5-textarea id="ta" name="ta" value="ok"></ui5-textarea>
<ui5-textarea name="ta_disabled" disabled value="ok"></ui5-textarea>
Expand Down
19 changes: 19 additions & 0 deletions packages/main/test/pages/Select.html
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,18 @@ <h2>Select with additional text</h2>
<ui5-option additional-text="AU">Australia</ui5-option>
</ui5-select>
</section>

<section>
<h2>Select "value" property</h2>
<ui5-select id="mySelect7">
<ui5-option>Item1</ui5-option>
<ui5-option>Item2</ui5-option>
<ui5-option selected>Item3</ui5-option>
</ui5-select>
<ui5-button id="btnSetValue">select.value = "Item2"</ui5-button>
<ui5-button id="btnSetInvalidValue">select.value = "NAN"</ui5-button>
</section>

</body>
<script>
var countries = [{ key: "Aus", text: "Australia" }, { key: "Aruba", text: "Aruba" }, { key: "Antigua", text: "Antigua and Barbuda" }, { key: "Bel", text: "Belgium" }, { key: "Bg", text: "Bulgaria" }, { key: "Bra", text: "Brazil" }];
Expand Down Expand Up @@ -266,5 +278,12 @@ <h2>Select with additional text</h2>
restoreItemsBtn.addEventListener("click", function(event) {
select.innerHTML = initialItemsHTML;
});

btnSetValue.addEventListener("click", function(e) {
mySelect7.value = "Item2";
});
btnSetInvalidValue.addEventListener("click", function(e) {
mySelect7.value = "NAN";
});
</script>
</html>
26 changes: 19 additions & 7 deletions packages/main/test/pages/SelectMenu.html
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
<label id="lblLive"></label><br>
<label id="lblOpen"></label><br>
<label id="lblClose"></label><br>
<label id="lblValue"></label><br>

<div class="app-inner">
<section>
Expand Down Expand Up @@ -52,7 +53,9 @@

<ui5-button id="clearCounter">Reset counters</ui5-button>
<ui5-button id="btnFocusOut">Focus out</ui5-button>
</section>
<ui5-button id="btnSetValue">select.value = "item1"</ui5-button>
<ui5-button id="btnSetInvalidValue">select.value = "nan"</ui5-button>
</section>

<section>
<ui5-title>Select + Option</ui5-title><br>
Expand All @@ -74,7 +77,7 @@

<ui5-select-menu id="selectOptions">

<ui5-select-menu-option data-display-text="T-shirt [1]" data-additional-text="S" accessible-name="T-shirt size S">
<ui5-select-menu-option data-display-text="T-shirt [1]" data-additional-text="S" accessible-name="T-shirt size S" value="tshirt">
<div class="selectMenuOption">
T-shirt [1]
<div>
Expand All @@ -84,7 +87,7 @@
</div>
</ui5-select-menu-option>

<ui5-select-menu-option data-display-text="Dress [2]" data-additional-text="M" accessible-name="Dress size M" style="background-color: red">
<ui5-select-menu-option data-display-text="Dress [2]" value="dress" data-additional-text="M" accessible-name="Dress size M" style="background-color: red">
<div class="selectMenuOption selectMenuOption--warning">
Dress [2]
<div>
Expand All @@ -94,7 +97,7 @@
</div>
</ui5-select-menu-option>

<ui5-select-menu-option data-display-text="Skirt [3]" data-additional-text="L" accessible-name="Skirt size L" selected>
<ui5-select-menu-option data-display-text="Skirt [3]" value="skirt" data-additional-text="L" accessible-name="Skirt size L" selected>
<div class="selectMenuOption">
Skirt [3]
<div>
Expand Down Expand Up @@ -174,15 +177,15 @@
</ui5-select-menu>

<ui5-select-menu id="selectOptionsTest">
<ui5-select-menu-option id="selOption1" display-text="item1">
<ui5-select-menu-option id="selOption1" display-text="item1" value="item1">
<div>Item 1</div>
</ui5-select-menu-option>

<ui5-select-menu-option id="selOption2" display-text="item2">
<ui5-select-menu-option id="selOption2" display-text="item2" value="item2">
<div>Item 2</div>
</ui5-select-menu-option>

<ui5-select-menu-option id="selOption3" display-text="item3">
<ui5-select-menu-option id="selOption3" display-text="item3" value="item3">
<div>Item 3</div>
</ui5-select-menu-option>
</ui5-select-menu>
Expand Down Expand Up @@ -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");
});
Expand Down Expand Up @@ -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";
});
</script>
</html>
2 changes: 1 addition & 1 deletion packages/main/test/specs/FormSupport.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
38 changes: 35 additions & 3 deletions packages/main/test/specs/Select.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.");
});

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

Expand Down Expand Up @@ -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");
Expand All @@ -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");
Expand All @@ -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 () => {
Expand All @@ -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");
});
Expand Down Expand Up @@ -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);
});
Expand Down Expand Up @@ -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`);

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