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-multi-combobox): introduce grouping functionality #5250

Merged
merged 3 commits into from
May 26, 2022
Merged
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
42 changes: 34 additions & 8 deletions packages/main/src/MultiComboBox.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import "@ui5/webcomponents-icons/dist/information.js";
import { getFeature } from "@ui5/webcomponents-base/dist/FeaturesRegistry.js";
import { getEffectiveAriaLabelText } from "@ui5/webcomponents-base/dist/util/AriaLabelHelper.js";
import MultiComboBoxItem from "./MultiComboBoxItem.js";
import MultiComboBoxGroupItem from "./MultiComboBoxGroupItem.js";
import Tokenizer from "./Tokenizer.js";
import Token from "./Token.js";
import Icon from "./Icon.js";
Expand Down Expand Up @@ -407,7 +408,7 @@ const metadata = {
* @extends UI5Element
* @tagname ui5-multi-combobox
* @public
* @appenddocs MultiComboBoxItem
* @appenddocs MultiComboBoxItem MultiComboBoxGroupItem
* @since 0.11.0
*/
class MultiComboBox extends UI5Element {
Expand Down Expand Up @@ -438,6 +439,7 @@ class MultiComboBox extends UI5Element {
static get dependencies() {
return [
MultiComboBoxItem,
MultiComboBoxGroupItem,
Tokenizer,
Token,
Icon,
Expand Down Expand Up @@ -498,7 +500,8 @@ class MultiComboBox extends UI5Element {

filterSelectedItems(event) {
this.filterSelected = event.target.pressed;
this.selectedItems = this._filteredItems.filter(item => item.selected);
const selectedItems = this._filteredItems.filter(item => item.selected);
this.selectedItems = this.items.filter((item, idx, allItems) => MultiComboBox._groupItemFilter(item, ++idx, allItems, selectedItems) || selectedItems.indexOf(item) !== -1);
}

get _showAllItemsButtonPressed() {
Expand Down Expand Up @@ -943,11 +946,11 @@ class MultiComboBox extends UI5Element {

let currentItem = this.items[++this.currentItemIdx];

while (this.currentItemIdx < itemsCount - 1 && currentItem.selected) {
while ((this.currentItemIdx < itemsCount - 1 && currentItem.selected) || currentItem.isGroupItem) {
currentItem = this.items[++this.currentItemIdx];
}

if (currentItem.selected === true) {
if (currentItem.selected === true || currentItem.isGroupItem) {
this.currentItemIdx = previousItemIdx;
return;
}
Expand Down Expand Up @@ -976,15 +979,15 @@ class MultiComboBox extends UI5Element {

let currentItem = this.items[--this.currentItemIdx];

while (currentItem && currentItem.selected && this.currentItemIdx > 0) {
while ((currentItem && this.currentItemIdx > 0) && (currentItem.selected || currentItem.isGroupItem)) {
currentItem = this.items[--this.currentItemIdx];
}

if (!currentItem) {
return;
}

if (currentItem.selected) {
if (currentItem.selected || currentItem.isGroupItem) {
this.currentItemIdx = previousItemIdx;
return;
}
Expand Down Expand Up @@ -1082,7 +1085,29 @@ class MultiComboBox extends UI5Element {
}

_filterItems(str) {
return (Filters[this.filter] || Filters.StartsWithPerTerm)(str, this.items, "text");
const itemsToFilter = this.items.filter(item => !item.isGroupItem);
Copy link
Member

@ilhan007 ilhan007 May 19, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At first glance it looks a little complicated these two methods.
Something that might help to look more straightforward is to build a map or other structure (probably onBeforeRendering) to store the connection between items and the groups they belong. And, when the user filters, it will be much less code, more readable, to get from that structure which group items should be displayed for which items that remained after filtering. But, this is more for the team review.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the same, Can we change the filters files to consider group items as that is implemented in both CB and MCB components?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as all combobox / mcb items and group items all implement the IComboBoxItem can we introduce a method e.g. #isGroupItem? or something like this?

const filteredItems = (Filters[this.filter] || Filters.StartsWithPerTerm)(str, itemsToFilter, "text");

// Return the filtered items and their group items
return this.items.filter((item, idx, allItems) => MultiComboBox._groupItemFilter(item, ++idx, allItems, filteredItems) || filteredItems.indexOf(item) !== -1);
}

/**
* Returns true if the group header should be shown (if there is a filtered suggestion item for this group item)
*
* @private
*/
static _groupItemFilter(item, idx, allItems, filteredItems) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rename the idx param to nextItemIdx for readibility

if (item.isGroupItem) {
let groupHasFilteredItems;

while (allItems[idx] && !allItems[idx].isGroupItem && !groupHasFilteredItems) {
groupHasFilteredItems = filteredItems.indexOf(allItems[idx]) !== -1;
idx++;
}

return groupHasFilteredItems;
}
}

_afterOpenPicker() {
Expand Down Expand Up @@ -1189,7 +1214,8 @@ class MultiComboBox extends UI5Element {
this._valueBeforeOpen = this.value;

if (this.filterSelected) {
this.selectedItems = this._filteredItems.filter(item => item.selected);
const selectedItems = this._filteredItems.filter(item => item.selected);
this.selectedItems = this.items.filter((item, idx, allItems) => MultiComboBox._groupItemFilter(item, ++idx, allItems, selectedItems) || selectedItems.indexOf(item) !== -1);
}
}

Expand Down
67 changes: 67 additions & 0 deletions packages/main/src/MultiComboBoxGroupItem.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import UI5Element from "@ui5/webcomponents-base/dist/UI5Element.js";
import GroupHeaderListItem from "./GroupHeaderListItem.js";

/**
* @public
*/
const metadata = {
tag: "ui5-mcb-group-item",
properties: /** @lends sap.ui.webcomponents.main.MultiComboBoxGroupItem.prototype */ {
/**
* Defines the text of the component.
*
* @type {string}
* @defaultvalue ""
* @public
*/
text: {
type: String,
},
},
slots: /** @lends sap.ui.webcomponents.main.MultiComboBoxGroupItem.prototype */ {
},
events: /** @lends sap.ui.webcomponents.main.MultiComboBoxGroupItem.prototype */ {
},
};

/**
* @class
* The <code>ui5-multi-combobox-group-item</code> is type of suggestion item,
* that can be used to split the <code>ui5-multi-combobox</code> suggestions into groups.
*
* @constructor
* @author SAP SE
* @alias sap.ui.webcomponents.main.MultiComboBoxGroupItem
* @extends UI5Element
* @tagname ui5-mcb-group-item
* @public
* @implements sap.ui.webcomponents.main.IMultiComboBoxItem
* @since 1.4.0
*/
class MultiComboBoxGroupItem extends UI5Element {
static get metadata() {
return metadata;
}

static get dependencies() {
return [
GroupHeaderListItem,
];
}

/**
* Used to avoid tag name checks
* @protected
*/
get isGroupItem() {
return true;
}

get stableDomRef() {
return this.getAttribute("stable-dom-ref") || `${this._id}-stable-dom-ref`;
}
}

MultiComboBoxGroupItem.define();

export default MultiComboBoxGroupItem;
12 changes: 10 additions & 2 deletions packages/main/src/MultiComboBoxPopover.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -70,13 +70,21 @@
{{#if filterSelected}}
<ui5-list separators="None" mode="MultiSelect" class="ui5-multi-combobox-all-items-list">
{{#each selectedItems}}
{{> listItem}}
{{#if isGroupItem}}
<ui5-li-groupheader data-ui5-stable="{{this.stableDomRef}}" @keydown="{{../_onItemKeydown}}">{{ this.text }}</ui5-li-groupheader>
{{else}}
{{> listItem}}
{{/if}}
{{/each}}
</ui5-list>
{{else}}
<ui5-list separators="None" mode="MultiSelect" class="ui5-multi-combobox-all-items-list">
{{#each _filteredItems}}
{{> listItem}}
{{#if isGroupItem}}
<ui5-li-groupheader data-ui5-stable="{{this.stableDomRef}}" @keydown="{{../_onItemKeydown}}">{{ this.text }}</ui5-li-groupheader>
{{else}}
{{> listItem}}
{{/if}}
{{/each}}
</ui5-list>
{{/if}}
Expand Down
23 changes: 23 additions & 0 deletions packages/main/test/pages/MultiComboBox.html
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,29 @@
</ui5-multi-combobox>
</div>

<div class="demo-section">
<span>MultiComboBox with grouping</span>

<br>
<ui5-multi-combobox id="mcb-grouping" allow-custom-values placeholder="Select a country">
<ui5-mcb-group-item text="Asia"></ui5-mcb-group-item>
<ui5-mcb-item text="Afghanistan"></ui5-mcb-item>
<ui5-mcb-item text="China"></ui5-mcb-item>
<ui5-mcb-item text="India"></ui5-mcb-item>
<ui5-mcb-item text="Indonesia"></ui5-mcb-item>
<ui5-mcb-group-item text="Europe"></ui5-mcb-group-item>
<ui5-mcb-item text="Austria"></ui5-mcb-item>
<ui5-mcb-item text="Bulgaria"></ui5-mcb-item>
<ui5-mcb-item text="Germany"></ui5-mcb-item>
<ui5-mcb-item text="Italy"></ui5-mcb-item>
<ui5-mcb-group-item text="North America"></ui5-mcb-group-item>
<ui5-mcb-item text="Canada"></ui5-mcb-item>
<ui5-mcb-item text="Granada"></ui5-mcb-item>
<ui5-mcb-item text="Haiti"></ui5-mcb-item>
<ui5-mcb-item text="United States"></ui5-mcb-item>
</ui5-multi-combobox>
</div>


<hr class="demo-section">

Expand Down
46 changes: 46 additions & 0 deletions packages/main/test/samples/MultiComboBox.sample.html
Original file line number Diff line number Diff line change
Expand Up @@ -155,4 +155,50 @@ <h3>MultiComboBox with Value State</h3>
</xmp></pre>
</section>

<section>
<h3>MultiComboBox with Grouping of Items</h3>
<div class="snippet responsive-snippet">
<ui5-multi-combobox placeholder="Select a country">
<ui5-mcb-group-item text="Asia"></ui5-mcb-group-item>
<ui5-mcb-item text="Afghanistan"></ui5-mcb-item>
<ui5-mcb-item text="China"></ui5-mcb-item>
<ui5-mcb-item text="India"></ui5-mcb-item>
<ui5-mcb-item text="Indonesia"></ui5-mcb-item>
<ui5-mcb-group-item text="Europe"></ui5-mcb-group-item>
<ui5-mcb-item text="Austria"></ui5-mcb-item>
<ui5-mcb-item text="Bulgaria"></ui5-mcb-item>
<ui5-mcb-item text="Germany"></ui5-mcb-item>
<ui5-mcb-item text="Italy"></ui5-mcb-item>
<ui5-mcb-group-item text="North America"></ui5-mcb-group-item>
<ui5-mcb-item text="Canada"></ui5-mcb-item>
<ui5-mcb-item text="Granada"></ui5-mcb-item>
<ui5-mcb-item text="Haiti"></ui5-mcb-item>
<ui5-mcb-item text="United States"></ui5-mcb-item>
</ui5-multi-combobox>
</div>

<pre class="prettyprint lang-html"><xmp>

<ui5-multi-combobox placeholder="Select a country">
<ui5-mcb-group-item text="Asia"></ui5-mcb-group-item>
<ui5-mcb-item text="Afghanistan"></ui5-mcb-item>
<ui5-mcb-item text="China"></ui5-mcb-item>
<ui5-mcb-item text="India"></ui5-mcb-item>
<ui5-mcb-item text="Indonesia"></ui5-mcb-item>
<ui5-mcb-group-item text="Europe"></ui5-mcb-group-item>
<ui5-mcb-item text="Austria"></ui5-mcb-item>
<ui5-mcb-item text="Bulgaria"></ui5-mcb-item>
<ui5-mcb-item text="Germany"></ui5-mcb-item>
<ui5-mcb-item text="Italy"></ui5-mcb-item>
<ui5-mcb-group-item text="North America"></ui5-cb-group-item>
<ui5-mcb-item text="Canada"></ui5-mcb-item>
<ui5-mcb-item text="Granada"></ui5-mcb-item>
<ui5-mcb-item text="Haiti"></ui5-mcb-item>
<ui5-mcb-item text="United States"></ui5-mcb-item>
</ui5-multi-combobox>

</xmp></pre>

</section>

<!-- JSDoc marker -->
Loading