Skip to content

Commit d522271

Browse files
committed
feat(dropdown): support passing Observables of Array<ListItem>
1 parent f1412b8 commit d522271

File tree

5 files changed

+111
-53
lines changed

5 files changed

+111
-53
lines changed

src/combobox/combobox.component.ts

Lines changed: 10 additions & 13 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(() => {
@@ -290,7 +290,7 @@ export class ComboBox implements OnChanges, OnInit, AfterViewInit, AfterContentI
290290
ev.stopPropagation();
291291
this.openDropdown();
292292
setTimeout(() => this.view.getCurrentElement().focus(), 0);
293-
} else if (ev.key === "ArrowUp" && this.dropdownMenu.nativeElement.contains(ev.target) && !this.view["hasPrevElement"]()) {
293+
} else if (ev.key === "ArrowUp" && this.dropdownMenu.nativeElement.contains(ev.target) && !this.view.hasPrevElement()) {
294294
this.elementRef.nativeElement.querySelector(".combobox_input").focus();
295295
}
296296
}
@@ -340,7 +340,7 @@ export class ComboBox implements OnChanges, OnInit, AfterViewInit, AfterContentI
340340
}
341341
return item;
342342
});
343-
this.view["updateList"](this.items);
343+
this.view.items = this.items;
344344
this.updatePills();
345345
// clearSelected can only fire on type=multi
346346
// so we just emit getSelected() (just in case there's any disabled but selected items)
@@ -349,7 +349,6 @@ export class ComboBox implements OnChanges, OnInit, AfterViewInit, AfterContentI
349349

350350
/**
351351
* Closes the dropdown and emits the close event.
352-
* @memberof ComboBox
353352
*/
354353
public closeDropdown() {
355354
this.open = false;
@@ -358,15 +357,13 @@ export class ComboBox implements OnChanges, OnInit, AfterViewInit, AfterContentI
358357

359358
/**
360359
* Opens the dropdown.
361-
* @memberof ComboBox
362360
*/
363361
public openDropdown() {
364362
this.open = true;
365363
}
366364

367365
/**
368366
* Toggles the dropdown.
369-
* @memberof ComboBox
370367
*/
371368
public toggleDropdown() {
372369
if (this.open) {
@@ -381,7 +378,7 @@ export class ComboBox implements OnChanges, OnInit, AfterViewInit, AfterContentI
381378
* @param {string} searchString
382379
*/
383380
public onSearch(searchString) {
384-
this.view["filterBy"](searchString);
381+
this.view.filterBy(searchString);
385382
if (searchString !== "") {
386383
this.openDropdown();
387384
} else {
@@ -390,7 +387,7 @@ export class ComboBox implements OnChanges, OnInit, AfterViewInit, AfterContentI
390387
if (this.type === "single") {
391388
// deselect if the input doesn't match the content
392389
// of any given item
393-
const matches = this.view.items.some(item => item.content.toLowerCase().includes(searchString.toLowerCase()));
390+
const matches = this.view.getListItems().some(item => item.content.toLowerCase().includes(searchString.toLowerCase()));
394391
if (!matches) {
395392
const selected = this.view.getSelected();
396393
if (selected) {
@@ -399,7 +396,7 @@ export class ComboBox implements OnChanges, OnInit, AfterViewInit, AfterContentI
399396
this.view.select.emit({ item: selected[0] });
400397
this.propagateChangeCallback(null);
401398
} else {
402-
this.view["filterBy"]("");
399+
this.view.filterBy("");
403400
}
404401
}
405402
}
@@ -421,10 +418,10 @@ export class ComboBox implements OnChanges, OnInit, AfterViewInit, AfterContentI
421418
public onSubmit(ev) {
422419
let index = 0;
423420
if (ev.after) {
424-
index = this.view.items.indexOf(ev.after) + 1;
421+
index = this.view.getListItems().indexOf(ev.after) + 1;
425422
}
426423
this.submit.emit({
427-
items: this.view.items,
424+
items: this.view.getListItems(),
428425
index,
429426
value: {
430427
content: ev.value,

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

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

45

56
/**
@@ -14,13 +15,16 @@ export class AbstractDropdownView {
1415
/**
1516
* The items to be displayed in the list within the `AbstractDropDownView`.
1617
* @type {Array<ListItem>}
17-
* @memberof AbstractDropdownView
1818
*/
19-
@Input() items: Array<ListItem>;
19+
@Input() set items(value: Array<ListItem> | Observable<Array<ListItem>>) {
20+
}
21+
22+
get items(): Array<ListItem> | Observable<Array<ListItem>> {
23+
return;
24+
}
2025
/**
2126
* Emits selection events to other class.
2227
* @type {EventEmitter<Object>}
23-
* @memberof AbstractDropdownView
2428
*/
2529
@Output() select: EventEmitter<Object>;
2630
/**
@@ -36,6 +40,10 @@ export class AbstractDropdownView {
3640
* Returns the `ListItem` that is subsequent to the selected item in the `DropdownList`.
3741
*/
3842
getNextItem(): ListItem { return; }
43+
/**
44+
* Returns a boolean if the currently selected item is preceded by another
45+
*/
46+
hasNextElement(): boolean { return; }
3947
/**
4048
* Returns the `HTMLElement` for the item that is subsequent to the selected item.
4149
*/
@@ -44,6 +52,10 @@ export class AbstractDropdownView {
4452
* Returns the `ListItem` that precedes the selected item within `DropdownList`.
4553
*/
4654
getPrevItem(): ListItem { return; }
55+
/**
56+
* Returns a boolean if the currently selected item is followed by another
57+
*/
58+
hasPrevElement(): boolean { return; }
4759
/**
4860
* Returns the `HTMLElement` for the item that precedes the selected item.
4961
*/
@@ -60,13 +72,21 @@ export class AbstractDropdownView {
6072
* Returns the `HTMLElement` for the item that is selected within the `DropdownList`.
6173
*/
6274
getCurrentElement(): HTMLElement { return; }
75+
/**
76+
* Returns the current items as an Array
77+
*/
78+
getListItems(): Array<ListItem> { return; }
6379
/**
6480
* Transforms array input list of items to the correct state by updating the selected item(s).
6581
*/
6682
propagateSelected(value: Array<ListItem>): void {}
67-
6883
/**
69-
* Initalizes focus in the list
84+
*
85+
* @param value value to filter the list by
86+
*/
87+
filterBy(value: string): void {}
88+
/**
89+
* Initializes focus in the list
7090
* In most cases this just calls `getCurrentElement().focus()`
7191
*/
7292
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)