Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/base/cypress/specs/UI5ElementPropsAndAttrs.cy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ describe("Properties and attributes convert to each other", () => {
.should("not.have.attr", "object-prop");
});

it("Tests that array properties have no attributes", () => {
it("Tests that array properties have attributes", () => {
cy.mount(<Generic></Generic>);

cy.get("[ui5-test-generic]")
Expand All @@ -88,7 +88,7 @@ describe("Properties and attributes convert to each other", () => {
.invoke("prop", "multiProp", ["a", "b"]);

cy.get("@testGeneric")
.should("not.have.attr", "multi-prop");
.should("have.attr", "multi-prop", '["a","b"]');
});

it("Tests that noAttribute properties have no attributes", () => {
Expand Down
11 changes: 10 additions & 1 deletion packages/base/src/UI5Element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,15 @@ const defaultConverter = {
if (type === Number) {
return value === null ? undefined : parseFloat(value);
}

if (type === Object || type === Array) {
try {
return JSON.parse(value as string) as object | Array<unknown>;
} catch {
return value;
}
}

return value;
},
toAttribute(value: unknown, type: unknown) {
Expand All @@ -89,7 +98,7 @@ const defaultConverter = {

// don't set attributes for arrays and objects
if (type === Object || type === Array) {
return null;
return JSON.stringify(value);
}

// object, array, other
Expand Down
2 changes: 1 addition & 1 deletion packages/base/src/UI5ElementMetadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ class UI5ElementMetadata {
*/
hasAttribute(propName: string): boolean {
const propData = this.getProperties()[propName];
return propData.type !== Object && propData.type !== Array && !propData.noAttribute;
return propData.type !== Object && !propData.noAttribute;
}

/**
Expand Down
111 changes: 111 additions & 0 deletions packages/main/cypress/specs/MultiComboBox.cy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -815,6 +815,75 @@ describe("General", () => {
.should("have.text", resourceBundle.getText(MULTIINPUT_SHOW_MORE_TOKENS.defaultText, 1));
})
});

it("preselects items based on selectedValues property", () => {
cy.mount(
<MultiComboBox style="width: 300px" selectedValues={["al", "en"]}>
<MultiComboBoxItem text="Albania" value="al"></MultiComboBoxItem>
<MultiComboBoxItem text="Denmark" value="dk"></MultiComboBoxItem>
<MultiComboBoxItem text="England" value="en"></MultiComboBoxItem>
</MultiComboBox>
);

cy.get("ui5-multi-combobox")
.should("have.attr", "selected-values",'["al","en"]');

cy.get("[ui5-mcb-item]")
.eq(0)
.should("be.selected");

cy.get("[ui5-mcb-item]")
.eq(2)
.should("be.selected");

cy.get("[ui5-multi-combobox]")
.as("mcb")
.shadow()
.find("[ui5-tokenizer]")
.as("tokenizer");

cy.get("@tokenizer")
.find("[ui5-token]")
.should("have.length", "2");
});

it("updates selectedValues when a token is deleted", () => {
cy.mount(
<MultiComboBox style="width: 300px" selectedValues={["dk", "en"]}>
<MultiComboBoxItem text="Albania" value="al"></MultiComboBoxItem>
<MultiComboBoxItem text="Denmark" value="dk"></MultiComboBoxItem>
<MultiComboBoxItem text="England" value="en"></MultiComboBoxItem>
</MultiComboBox>
);

cy.get("[ui5-mcb-item]")
.eq(1)
.should("be.selected");

cy.get("[ui5-mcb-item]")
.eq(2)
.should("be.selected");

cy.get("[ui5-multi-combobox]")
.as("mcb")
.shadow()
.find("[ui5-tokenizer]")
.as("tokenizer");

cy.get("@tokenizer")
.find("[ui5-token]")
.eq(1)
.realClick();

cy.realPress("Backspace");

cy.get("@tokenizer")
.find("[ui5-token]")
.should("have.length", "1");

cy.get("[ui5-multi-combobox]")
.should("have.attr", "selected-values", '["dk"]');
});
});

describe("MultiComboBox Truncated Tokens", () => {
Expand Down Expand Up @@ -2225,6 +2294,48 @@ describe("Event firing", () => {
cy.get("@valueStateChangeEvent")
.should("have.been.calledTwice");
});

it("fires selection-change and updates selectedValues on token deletion", () => {
const selectionChangeSpy = cy.stub().as("selectionChangeSpy");
cy.mount(
<MultiComboBox style="width: 300px" selectedValues={["1", "3"]} onSelectionChange={selectionChangeSpy}>
<MultiComboBoxItem text="Item 1" value="1"></MultiComboBoxItem>
<MultiComboBoxItem text="Item 1" value="2"></MultiComboBoxItem>
<MultiComboBoxItem text="Item 1" value="3"></MultiComboBoxItem>
</MultiComboBox>
);

cy.get("[ui5-multi-combobox]")
.as("mcb")
.shadow()
.find("[ui5-tokenizer]")
.as("tokenizer");

cy.get("@tokenizer")
.find("[ui5-token]")
.eq(0)
.realClick();

cy.realPress("ArrowRight");
cy.get("@tokenizer")
.find("[ui5-token]")
.eq(1)
.should("be.focused");

cy.realPress("Space");
cy.realPress("Backspace");

cy.get("@tokenizer")
.should("be.empty");

cy.get("@selectionChangeSpy")
.should("have.been.calledOnce");
cy.get("@selectionChangeSpy").should('have.been.calledWithMatch', Cypress.sinon.match(event => {
return event.detail.item === undefined;
}));
cy.get("[ui5-multi-combobox]")
.should("have.attr", "selected-values", '[]');
});
});

describe("MultiComboBox RTL/LTR Arrow Navigation", () => {
Expand Down
2 changes: 1 addition & 1 deletion packages/main/src/ComboBox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,7 @@ class ComboBox extends UI5Element implements IFormInputElement {
@property({ type: Boolean, noAttribute: true })
_iconPressed = false;

@property({ type: Array })
@property({ type: Array, noAttribute: true })
_filteredItems: Array<IComboBoxItem> = [];

@property({ type: Number, noAttribute: true })
Expand Down
43 changes: 37 additions & 6 deletions packages/main/src/MultiComboBox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ import type InputComposition from "./features/InputComposition.js";
*/
interface IMultiComboBoxItem extends UI5Element {
text?: string,
value?: string,
additionalText?: string,
headerText?: string,
selected: boolean,
Expand Down Expand Up @@ -292,6 +293,15 @@ class MultiComboBox extends UI5Element implements IFormInputElement {
@property()
value = "";

/**
* Defines the values of the selected items.
* @default []
* @public
* @since 2.19.0
*/
@property({ type: Array })
selectedValues:Array<string> = [];

/**
* Determines the name by which the component will be identified upon submission in an HTML form.
*
Expand Down Expand Up @@ -447,10 +457,10 @@ class MultiComboBox extends UI5Element implements IFormInputElement {
@property()
_valueBeforeOpen = this.value;

@property({ type: Array })
@property({ type: Array, noAttribute: true })
_filteredItems!: Array<IMultiComboBoxItem>;

@property({ type: Array })
@property({ type: Array, noAttribute: true })
_previouslySelectedItems!: Array<IMultiComboBoxItem>;

@property({ type: Boolean })
Expand Down Expand Up @@ -547,7 +557,7 @@ class MultiComboBox extends UI5Element implements IFormInputElement {
@slot()
valueStateMessage!: Array<HTMLElement>;

selectedValues: Array<IMultiComboBoxItem>;
// selectedValues: Array<IMultiComboBoxItem>;
_inputLastValue: string;
_deleting: boolean;
_validationTimeout: Timeout | null;
Expand Down Expand Up @@ -611,7 +621,7 @@ class MultiComboBox extends UI5Element implements IFormInputElement {
this._filteredItems = [];
this.selectedItems = [];
this._previouslySelectedItems = [];
this.selectedValues = [];
// this.selectedValues = [];
this._itemsBeforeOpen = [];
this._inputLastValue = "";
this._deleting = false;
Expand Down Expand Up @@ -806,6 +816,11 @@ class MultiComboBox extends UI5Element implements IFormInputElement {
item.selected = false;
});

if (this.selectedValues) {
const valuesToDelete = deletingItems.map(item => item.value);
this.selectedValues = this.selectedValues.filter(value => !valuesToDelete.includes(value));
}

this._deleting = true;
this._preventTokenizerToggle = true;

Expand Down Expand Up @@ -1572,8 +1587,8 @@ class MultiComboBox extends UI5Element implements IFormInputElement {

_getSelectedItems(): Array<MultiComboBoxItem> {
// Angular 2 way data binding
this.selectedValues = this._getItems().filter(item => item.selected);
return this.selectedValues as Array<MultiComboBoxItem>;
return this._getItems().filter(item => item.selected) as Array<MultiComboBoxItem>;
// return this.selectedValues as Array<MultiComboBoxItem>;
}

_listSelectionChange(e: CustomEvent<ListSelectionChangeEventDetail>) {
Expand All @@ -1592,6 +1607,9 @@ class MultiComboBox extends UI5Element implements IFormInputElement {
if (!isPhone()) {
changePrevented = this.fireSelectionChange();

if (this.selectedValues) {
this.selectedValues = e.detail.selectedItems.map(i => (i as MultiComboBoxItem).value || "");
}
if (changePrevented) {
e.preventDefault();
this._revertSelection();
Expand Down Expand Up @@ -1781,6 +1799,15 @@ class MultiComboBox extends UI5Element implements IFormInputElement {
});
}

_syncSelection() {
// set selected property of the items based on the selection value
this._getItems().forEach(item => {
if (isInstanceOfMultiComboBoxItem(item) && item.value) {
item.selected = this.selectedValues.includes(item.value);
}
});
}

onBeforeRendering() {
const input = this._innerInput;
const autoCompletedChars = input && (input.selectionEnd || 0) - (input.selectionStart || 0);
Expand All @@ -1799,6 +1826,10 @@ class MultiComboBox extends UI5Element implements IFormInputElement {
this._filteredItems = this._getItems();
}

if (this.selectedValues) {
this._syncSelection();
}

this.tokenizerAvailable = this._getSelectedItems().length > 0;
this.style.setProperty("--_ui5-input-icons-count", `${this.iconsCount}`);

Expand Down
3 changes: 3 additions & 0 deletions packages/main/src/MultiComboBoxItem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ class MultiComboBoxItem extends ComboBoxItem implements IMultiComboBoxItem {
@property({ type: Boolean })
declare selected: boolean;

@property()
value?: string; // remove when cmb PR is reviewed

/**
* Defines whether the item is filtered
* @private
Expand Down
2 changes: 1 addition & 1 deletion packages/main/src/TabContainer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ class TabContainer extends UI5Element {
@property({ noAttribute: true })
_endOverflowText = "More";

@property({ type: Array })
@property({ type: Array, noAttribute: true })
_popoverItemsFlat: Array<ITab> = [];

@property({ type: Number, noAttribute: true })
Expand Down
11 changes: 11 additions & 0 deletions packages/main/test/pages/MultiComboBox.html
Original file line number Diff line number Diff line change
Expand Up @@ -630,6 +630,17 @@ <h3>MultiComboBox in Compact</h3>
</ui5-multi-combobox>
</div>

<div class="sample-container">
<span>MultiInput with selectedValues</span>
<ui5-multi-combobox no-validation id="multi1" selected-values='["1","3"]'>
<ui5-mcb-item text="Longest word in the whole universe" value="1"></ui5-mcb-item>
<ui5-mcb-item text="Cosy" value="2"></ui5-mcb-item>
<ui5-mcb-item text="Compact" value="3"></ui5-mcb-item>
<ui5-mcb-item text="Condensed" value="4"></ui5-mcb-item>
<ui5-mcb-item text="Longest word in the world" value="5"></ui5-mcb-item>
</ui5-multi-combobox>
</div>

<div class="sample-container">
<ui5-multi-combobox id="mcb-prevent">
<ui5-mcb-item data-id="mcbi1" text="Item 1"></ui5-mcb-item>
Expand Down
Loading