Skip to content

Commit fb0a3a5

Browse files
authored
feat(dropdown): support passing Observables of Array<ListItem>
feat(dropdown): support passing Observables of Array<ListItem>
2 parents 24b24bc + 16d39af commit fb0a3a5

File tree

5 files changed

+120
-57
lines changed

5 files changed

+120
-57
lines changed

src/combobox/combobox.component.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ export class ComboBox implements OnChanges, OnInit, AfterViewInit, AfterContentI
218218
*/
219219
ngOnChanges(changes) {
220220
if (changes.items) {
221-
this.view["updateList"](changes.items.currentValue);
221+
this.view.items = changes.items.currentValue;
222222
this.updateSelected();
223223
}
224224
}
@@ -254,9 +254,9 @@ export class ComboBox implements OnChanges, OnInit, AfterViewInit, AfterContentI
254254
this.closeDropdown();
255255
}
256256
this.selected.emit(event);
257-
this.view["filterBy"]("");
257+
this.view.filterBy("");
258258
});
259-
this.view["updateList"](this.items);
259+
this.view.items = this.items;
260260
// update the rest of combobox with any pre-selected items
261261
// setTimeout just defers the call to the next check cycle
262262
setTimeout(() => {
@@ -293,7 +293,7 @@ export class ComboBox implements OnChanges, OnInit, AfterViewInit, AfterContentI
293293
setTimeout(() => this.view.getCurrentElement().focus(), 0);
294294
} else if ((ev.key === "ArrowUp" || ev.key === "Up") // `"Up"` is IE specific value
295295
&& this.dropdownMenu.nativeElement.contains(ev.target)
296-
&& !this.view["hasPrevElement"]()) {
296+
&& !this.view.hasPrevElement()) {
297297
this.elementRef.nativeElement.querySelector(".bx--text-input").focus();
298298
}
299299
}
@@ -343,7 +343,7 @@ export class ComboBox implements OnChanges, OnInit, AfterViewInit, AfterContentI
343343
}
344344
return item;
345345
});
346-
this.view["updateList"](this.items);
346+
this.view.items = this.items;
347347
this.updatePills();
348348
// clearSelected can only fire on type=multi
349349
// so we just emit getSelected() (just in case there's any disabled but selected items)
@@ -381,7 +381,7 @@ export class ComboBox implements OnChanges, OnInit, AfterViewInit, AfterContentI
381381
* @param {string} searchString
382382
*/
383383
public onSearch(searchString) {
384-
this.view["filterBy"](searchString);
384+
this.view.filterBy(searchString);
385385
if (searchString !== "") {
386386
this.openDropdown();
387387
} else {
@@ -390,7 +390,7 @@ export class ComboBox implements OnChanges, OnInit, AfterViewInit, AfterContentI
390390
if (this.type === "single") {
391391
// deselect if the input doesn't match the content
392392
// of any given item
393-
const matches = this.view.items.some(item => item.content.toLowerCase().includes(searchString.toLowerCase()));
393+
const matches = this.view.getListItems().some(item => item.content.toLowerCase().includes(searchString.toLowerCase()));
394394
if (!matches) {
395395
const selected = this.view.getSelected();
396396
if (selected) {
@@ -399,7 +399,7 @@ export class ComboBox implements OnChanges, OnInit, AfterViewInit, AfterContentI
399399
this.view.select.emit({ item: selected[0] });
400400
this.propagateChangeCallback(null);
401401
} else {
402-
this.view["filterBy"]("");
402+
this.view.filterBy("");
403403
}
404404
}
405405
}
@@ -421,10 +421,10 @@ export class ComboBox implements OnChanges, OnInit, AfterViewInit, AfterContentI
421421
public onSubmit(ev) {
422422
let index = 0;
423423
if (ev.after) {
424-
index = this.view.items.indexOf(ev.after) + 1;
424+
index = this.view.getListItems().indexOf(ev.after) + 1;
425425
}
426426
this.submit.emit({
427-
items: this.view.items,
427+
items: this.view.getListItems(),
428428
index,
429429
value: {
430430
content: ev.value,

src/dropdown/abstract-dropdown-view.class.ts

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,22 @@
11
import { Input, Output, EventEmitter } from "@angular/core";
22
import { ListItem } from "./list-item.interface";
3+
import { Observable } from "rxjs";
34

45

56
/**
67
* A component that intends to be used within `Dropdown` must provide an implementation that extends this base class.
78
* It also must provide the base class in the `@Component` meta-data.
89
* ex: `providers: [{provide: AbstractDropdownView, useExisting: forwardRef(() => MyDropdownView)}]`
9-
*
10-
* @export
11-
* @class AbstractDropdownView
1210
*/
1311
export class AbstractDropdownView {
1412
/**
1513
* The items to be displayed in the list within the `AbstractDropDownView`.
16-
* @type {Array<ListItem>}
17-
* @memberof AbstractDropdownView
1814
*/
19-
@Input() items: Array<ListItem>;
15+
@Input() set items(value: Array<ListItem> | Observable<Array<ListItem>>) { }
16+
17+
get items(): Array<ListItem> | Observable<Array<ListItem>> { return; }
2018
/**
2119
* Emits selection events to other class.
22-
* @type {EventEmitter<Object>}
23-
* @memberof AbstractDropdownView
2420
*/
2521
@Output() select: EventEmitter<Object>;
2622
/**
@@ -36,6 +32,10 @@ export class AbstractDropdownView {
3632
* Returns the `ListItem` that is subsequent to the selected item in the `DropdownList`.
3733
*/
3834
getNextItem(): ListItem { return; }
35+
/**
36+
* Returns a boolean if the currently selected item is preceded by another
37+
*/
38+
hasNextElement(): boolean { return; }
3939
/**
4040
* Returns the `HTMLElement` for the item that is subsequent to the selected item.
4141
*/
@@ -44,6 +44,10 @@ export class AbstractDropdownView {
4444
* Returns the `ListItem` that precedes the selected item within `DropdownList`.
4545
*/
4646
getPrevItem(): ListItem { return; }
47+
/**
48+
* Returns a boolean if the currently selected item is followed by another
49+
*/
50+
hasPrevElement(): boolean { return; }
4751
/**
4852
* Returns the `HTMLElement` for the item that precedes the selected item.
4953
*/
@@ -60,13 +64,21 @@ export class AbstractDropdownView {
6064
* Returns the `HTMLElement` for the item that is selected within the `DropdownList`.
6165
*/
6266
getCurrentElement(): HTMLElement { return; }
67+
/**
68+
* Guaranteed to return the current items as an Array.
69+
*/
70+
getListItems(): Array<ListItem> { return; }
6371
/**
6472
* Transforms array input list of items to the correct state by updating the selected item(s).
6573
*/
6674
propagateSelected(value: Array<ListItem>): void {}
67-
6875
/**
69-
* Initalizes focus in the list
76+
*
77+
* @param value value to filter the list by
78+
*/
79+
filterBy(value: string): void {}
80+
/**
81+
* Initializes focus in the list
7082
* In most cases this just calls `getCurrentElement().focus()`
7183
*/
7284
initFocus(): void {}

src/dropdown/dropdown.component.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { throttleTime } from "rxjs/operators";
2020
import { AbstractDropdownView } from "./abstract-dropdown-view.class";
2121
import { position } from "../utils/position";
2222
import { I18n } from "./../i18n/i18n.module";
23+
import { ListItem } from "./list-item.interface";
2324

2425
/**
2526
* Drop-down lists enable users to select one or more items from a list.
@@ -258,7 +259,7 @@ export class Dropdown implements OnInit, AfterContentInit, OnDestroy {
258259
writeValue(value: any) {
259260
if (this.type === "single") {
260261
if (this.value) {
261-
const newValue = Object.assign({}, this.view.items.find(item => item[this.value] === value));
262+
const newValue = Object.assign({}, this.view.getListItems().find(item => item[this.value] === value));
262263
newValue.selected = true;
263264
this.view.propagateSelected([newValue]);
264265
} else {

src/dropdown/dropdown.stories.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { action } from "@storybook/addon-actions";
44
import { withKnobs, select, boolean, object, text } from "@storybook/addon-knobs/angular";
55

66
import { DropdownModule } from "../";
7+
import { of } from "rxjs";
78

89
storiesOf("Dropdown", module)
910
.addDecorator(
@@ -89,6 +90,32 @@ storiesOf("Dropdown", module)
8990
model: null
9091
}
9192
}))
93+
.add("With Observable items", () => ({
94+
template: `
95+
<div style="width: 300px">
96+
<ibm-dropdown
97+
[theme]="theme"
98+
placeholder="Select"
99+
[disabled]="disabled"
100+
(selected)="selected($event)"
101+
(onClose)="onClose($event)">
102+
<ibm-dropdown-list [items]="items"></ibm-dropdown-list>
103+
</ibm-dropdown>
104+
</div>
105+
`,
106+
props: {
107+
disabled: boolean("disabled", false),
108+
items: of([
109+
{ content: "one" },
110+
{ content: "two" },
111+
{ content: "three" },
112+
{ content: "four" }
113+
]),
114+
selected: action("Selected fired for dropdown"),
115+
onClose: action("Dropdown closed"),
116+
theme: select("theme", ["dark", "light"], "dark")
117+
}
118+
}))
92119
.add("Skeleton", () => ({
93120
template: `
94121
<div style="width: 300px">

0 commit comments

Comments
 (0)