src/combobox/combobox.component.ts
ComboBoxes are similar to dropdowns, except a combobox provides an input field for users to search items and (optionally) add their own. Multi-select comboboxes also provide "pills" of selected items.
OnChanges
OnInit
AfterViewInit
AfterContentInit
| providers |
{
: , : , : true
}
|
| selector | ibm-combo-box |
Properties |
|
Methods |
|
Inputs |
Outputs |
HostBindings |
HostListeners |
constructor(elementRef: ElementRef)
|
||||||||
|
Defined in src/combobox/combobox.component.ts:205
|
||||||||
|
Creates an instance of ComboBox.
Parameters :
|
disabled
|
Set to
Default value: |
|
Defined in src/combobox/combobox.component.ts:146
|
|
items
|
List of items to fill the content with. Example:
Type: |
|
Defined in src/combobox/combobox.component.ts:130
|
|
placeholder
|
Text to show when nothing is selected.
Default value: |
|
Defined in src/combobox/combobox.component.ts:134
|
|
size
|
Combo box render size.
Type:
Default value: |
|
Defined in src/combobox/combobox.component.ts:142
|
|
type
|
Combo box type (supporting single or multi selection of items).
Type:
Default value: |
|
Defined in src/combobox/combobox.component.ts:138
|
|
close
|
emits an empty event when the menu is closed $event type: EventEmitter<any>
|
|
Defined in src/combobox/combobox.component.ts:188
|
|
selected
|
Emits a ListItem Example: $event type: EventEmitter<ListItem>
|
|
Defined in src/combobox/combobox.component.ts:158
|
|
submit
|
Bubbles from Emits an event that includes the current item list, the suggested index for the new item, and a simple ListItem Example: $event type: EventEmitter<any>
|
|
Defined in src/combobox/combobox.component.ts:186
|
|
| attr.role |
attr.role:
|
Default value : combobox
|
|
Defined in src/combobox/combobox.component.ts:193
|
| class |
class:
|
Default value : bx--combo-box bx--list-box
|
|
Defined in src/combobox/combobox.component.ts:192
|
| style.display |
style.display:
|
Default value : block
|
|
Defined in src/combobox/combobox.component.ts:194
|
| keydown |
Arguments : '$event'
|
keydown(ev: KeyboardEvent)
|
|
Defined in src/combobox/combobox.component.ts:288
|
| _noop |
_noop()
|
|
Defined in src/combobox/combobox.component.ts:303
|
|
Returns :
void
|
| Public clearSelected |
clearSelected()
|
|
Defined in src/combobox/combobox.component.ts:338
|
|
Returns :
void
|
| Public closeDropdown |
closeDropdown()
|
|
Defined in src/combobox/combobox.component.ts:356
|
|
Closes the dropdown and emits the close event.
Returns :
void
|
| ngAfterContentInit |
ngAfterContentInit()
|
|
Defined in src/combobox/combobox.component.ts:238
|
|
Sets initial state that depends on child components Subscribes to select events and handles focus/filtering/initial list updates
Returns :
void
|
| ngAfterViewInit |
ngAfterViewInit()
|
|
Defined in src/combobox/combobox.component.ts:273
|
|
Binds event handlers against the rendered view
Returns :
void
|
| ngOnChanges | ||||||||
ngOnChanges(changes: )
|
||||||||
|
Defined in src/combobox/combobox.component.ts:221
|
||||||||
|
Lifecycle hook. Updates pills if necessary.
Parameters :
Returns :
void
|
| ngOnInit |
ngOnInit()
|
|
Defined in src/combobox/combobox.component.ts:228
|
|
Returns :
void
|
| onBlur |
onBlur()
|
|
Defined in src/combobox/combobox.component.ts:318
|
|
Returns :
void
|
| Public onSearch | ||||||||
onSearch(searchString: )
|
||||||||
|
Defined in src/combobox/combobox.component.ts:385
|
||||||||
|
Sets the list group filter, and manages single select item selection.
Parameters :
Returns :
void
|
| Public onSubmit | ||||||||
onSubmit(ev: )
|
||||||||
|
Defined in src/combobox/combobox.component.ts:423
|
||||||||
|
Bubbles from
Parameters :
Returns :
void
|
| Public openDropdown |
openDropdown()
|
|
Defined in src/combobox/combobox.component.ts:365
|
|
Opens the dropdown.
Returns :
void
|
| registerOnChange | ||||||||
registerOnChange(fn: any)
|
||||||||
|
Defined in src/combobox/combobox.component.ts:322
|
||||||||
|
Parameters :
Returns :
void
|
| registerOnTouched | ||||||||
registerOnTouched(fn: any)
|
||||||||
|
Defined in src/combobox/combobox.component.ts:326
|
||||||||
|
Parameters :
Returns :
void
|
| Public toggleDropdown |
toggleDropdown()
|
|
Defined in src/combobox/combobox.component.ts:373
|
|
Toggles the dropdown.
Returns :
void
|
| Public updatePills |
updatePills()
|
|
Defined in src/combobox/combobox.component.ts:333
|
|
Called by
Returns :
void
|
| Private updateSelected |
updateSelected()
|
|
Defined in src/combobox/combobox.component.ts:438
|
|
Returns :
void
|
| writeValue | ||||||||
writeValue(value: any)
|
||||||||
|
Defined in src/combobox/combobox.component.ts:308
|
||||||||
|
Parameters :
Returns :
void
|
| dropdownMenu |
dropdownMenu:
|
Decorators : ViewChild
|
|
Defined in src/combobox/combobox.component.ts:191
|
| Private noop |
noop:
|
|
Defined in src/combobox/combobox.component.ts:203
|
| Private onTouchedCallback |
onTouchedCallback:
|
Type : function
|
|
Defined in src/combobox/combobox.component.ts:204
|
| Public open |
open:
|
Default value : false
|
|
Defined in src/combobox/combobox.component.ts:196
|
| Public pills |
pills:
|
|
Defined in src/combobox/combobox.component.ts:199
|
|
Selected items for multi-select combo-boxes. |
| Private propagateChangeCallback |
propagateChangeCallback:
|
Type : function
|
|
Defined in src/combobox/combobox.component.ts:205
|
| Public selectedValue |
selectedValue:
|
|
Defined in src/combobox/combobox.component.ts:201
|
|
used to update the displayValue of |
| view |
view:
|
Type : AbstractDropdownView
|
Decorators : ContentChild
|
|
Defined in src/combobox/combobox.component.ts:190
|
|
ContentChild reference to the instantiated dropdown list |
import {
Component,
OnChanges,
ContentChild,
Input,
Output,
HostListener,
ElementRef,
ViewChild,
EventEmitter,
AfterViewInit,
AfterContentInit,
HostBinding,
OnInit
} from "@angular/core";
import { AbstractDropdownView } from "./../dropdown/abstract-dropdown-view.class";
import { ListItem } from "./../dropdown/list-item.interface";
import { NG_VALUE_ACCESSOR } from "@angular/forms";
/**
* ComboBoxes are similar to dropdowns, except a combobox provides an input field for users to search items and (optionally) add their own.
* Multi-select comboboxes also provide "pills" of selected items.
*
* @export
* @class ComboBox
* @implements {OnChanges}
* @implements {AfterViewInit}
* @implements {AfterContentInit}
*/
@Component({
selector: "ibm-combo-box",
template: `
<div
[attr.aria-expanded]="open"
role="button"
class="bx--list-box__field"
tabindex="0"
type="button"
aria-label="close menu"
aria-haspopup="true">
<div
*ngIf="type === 'multi' && pills.length > 0"
(click)="clearSelected()"
role="button"
class="bx--list-box__selection bx--list-box__selection--multi"
tabindex="0"
title="Clear all selected items">
{{ pills.length }}
<svg
fill-rule="evenodd"
height="10"
role="img"
viewBox="0 0 10 10"
width="10"
focusable="false"
aria-label="Clear all selected items"
alt="Clear all selected items">
<title>Clear all selected items</title>
<path d="M6.32 5L10 8.68 8.68 10 5 6.32 1.32 10 0 8.68 3.68 5 0 1.32 1.32 0 5 3.68 8.68 0 10 1.32 6.32 5z"></path>
</svg>
</div>
<input
[disabled]="disabled"
(click)="toggleDropdown()"
(keyup)="onSearch($event.target.value)"
[value]="selectedValue"
class="bx--text-input"
aria-label="ListBox input field"
autocomplete="off"
[placeholder]="placeholder"/>
<div
[ngClass]="{'bx--list-box__menu-icon--open': open}"
class="bx--list-box__menu-icon">
<svg
fill-rule="evenodd"
height="5"
role="img"
viewBox="0 0 10 5"
width="10"
alt="Close menu"
aria-label="Close menu">
<title>Close menu</title>
<path d="M0 0l5 4.998L10 0z"></path>
</svg>
</div>
</div>
<div
#dropdownMenu
*ngIf="open">
<ng-content></ng-content>
</div>
`,
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: ComboBox,
multi: true
}
]
})
export class ComboBox implements OnChanges, OnInit, AfterViewInit, AfterContentInit {
/**
* List of items to fill the content with.
*
* **Example:**
* ```javascript
* items = [
* {
* content: "Abacus",
* selected: false
* },
* {
* content: "Byte",
* selected: false,
* },
* {
* content: "Computer",
* selected: false
* },
* {
* content: "Digital",
* selected: false
* }
* ];
* ```
*
* @type {Array<ListItem>}
* @memberof ComboBox
*/
@Input() items: Array<ListItem> = [];
/**
* Text to show when nothing is selected.
*/
@Input() placeholder = "Filter...";
/**
* Combo box type (supporting single or multi selection of items).
*/
@Input() type: "single" | "multi" = "single";
/**
* Combo box render size.
*/
@Input() size: "sm" | "md" | "lg" = "md";
/**
* Set to `true` to disable combobox.
*/
@HostBinding("attr.aria-disabled") @Input() disabled = false;
/**
* Emits a ListItem
*
* Example:
* ```javascript
* {
* content: "one",
* selected: true
* }
* ```
*/
@Output() selected: EventEmitter<ListItem> = new EventEmitter<ListItem>();
/**
* Bubbles from `n-pill-input` when the user types a value and presses enter. Intended to be used to add items to the list.
*
* Emits an event that includes the current item list, the suggested index for the new item, and a simple ListItem
*
* Example:
* ```javascript
* {
* items: [{content: "one", selected: true}, {content: "two", selected: true}],
* index: 1,
* value: {
* content: "some user string",
* selected: false
* }
* }
* ```
*
* @param ev event from `n-pill-input`, includes the typed value and the index of the pill the user typed after
*
* Example:
* ```javascript
* {
* after: 1,
* value: "some user string"
* }
* ```
*/
@Output() submit: EventEmitter<any> = new EventEmitter<any>();
/** emits an empty event when the menu is closed */
@Output() close: EventEmitter<any> = new EventEmitter<any>();
/** ContentChild reference to the instantiated dropdown list */
@ContentChild(AbstractDropdownView) view: AbstractDropdownView;
@ViewChild("dropdownMenu") dropdownMenu;
@HostBinding("class") class = "bx--combo-box bx--list-box";
@HostBinding("attr.role") role = "combobox";
@HostBinding("style.display") display = "block";
public open = false;
/** Selected items for multi-select combo-boxes. */
public pills = [];
/** used to update the displayValue of `n-pill-input` */
public selectedValue = "";
private noop = this._noop.bind(this);
private onTouchedCallback: () => void = this._noop;
private propagateChangeCallback: (_: any) => void = this._noop;
/**
* Creates an instance of ComboBox.
* @param {ElementRef} elementRef
* @memberof ComboBox
*/
constructor(private elementRef: ElementRef) {}
/**
* Lifecycle hook.
* Updates pills if necessary.
*
* @param {any} changes
* @memberof ComboBox
*/
ngOnChanges(changes) {
if (changes.items) {
this.view["updateList"](changes.items.currentValue);
this.updateSelected();
}
}
ngOnInit() {
if (this.type === "multi") {
this.class = "bx--multi-select bx--combo-box bx--list-box";
}
}
/**
* Sets initial state that depends on child components
* Subscribes to select events and handles focus/filtering/initial list updates
*/
ngAfterContentInit() {
if (this.view) {
this.view.type = this.type;
this.view.select.subscribe(event => {
if (this.type === "multi") {
this.updatePills();
this.propagateChangeCallback(this.view.getSelected());
} else {
if (event.item && event.item.selected) {
this.selectedValue = event.item.content;
this.propagateChangeCallback(event.item);
} else {
this.selectedValue = "";
this.propagateChangeCallback(null);
}
// not gaurding these since the nativeElement has to be loaded
// for select to even fire
this.elementRef.nativeElement.querySelector("input").focus();
this.closeDropdown();
}
this.selected.emit(event);
this.view["filterBy"]("");
});
this.view["updateList"](this.items);
// update the rest of combobox with any pre-selected items
// setTimeout just defers the call to the next check cycle
setTimeout(() => {
this.updateSelected();
});
}
}
/**
* Binds event handlers against the rendered view
*/
ngAfterViewInit() {
document.addEventListener("click", ev => {
if (!this.elementRef.nativeElement.contains(ev.target)) {
if (this.open) {
this.closeDropdown();
}
}
});
}
/**
* Handles `Escape` key closing the dropdown, and arrow up/down focus to/from the dropdown list.
* @param {KeyboardEvent} ev
*/
@HostListener("keydown", ["$event"])
hostkeys(ev: KeyboardEvent) {
if (ev.key === "Escape") {
this.closeDropdown();
} else if (ev.key === "ArrowDown" && !this.dropdownMenu.nativeElement.contains(ev.target)) {
ev.stopPropagation();
this.openDropdown();
setTimeout(() => this.view.getCurrentElement().focus(), 0);
} else if (ev.key === "ArrowUp" && this.dropdownMenu.nativeElement.contains(ev.target) && !this.view["hasPrevElement"]()) {
this.elementRef.nativeElement.querySelector(".combobox_input").focus();
}
}
/*
* no-op method for null event listeners, and other no op calls
*/
_noop() {}
/*
* propagates the value provided from ngModel
*/
writeValue(value: any) {
if (value) {
if (this.type === "single") {
this.view.propagateSelected([value]);
} else {
this.view.propagateSelected(value);
}
}
}
onBlur() {
this.onTouchedCallback();
}
registerOnChange(fn: any) {
this.propagateChangeCallback = fn;
}
registerOnTouched(fn: any) {
this.onTouchedCallback = fn;
}
/**
* Called by `n-pill-input` when the selected pills have changed.
*/
public updatePills() {
this.pills = this.view.getSelected() || [];
this.propagateChangeCallback(this.view.getSelected());
}
public clearSelected() {
this.items = this.items.map(item => {
if (!item.disabled) {
item.selected = false;
}
return item;
});
this.view["updateList"](this.items);
this.updatePills();
// clearSelected can only fire on type=multi
// so we just emit getSelected() (just in case there's any disabled but selected items)
this.selected.emit(this.view.getSelected() as any);
}
/**
* Closes the dropdown and emits the close event.
* @memberof ComboBox
*/
public closeDropdown() {
this.open = false;
this.close.emit();
}
/**
* Opens the dropdown.
* @memberof ComboBox
*/
public openDropdown() {
this.open = true;
}
/**
* Toggles the dropdown.
* @memberof ComboBox
*/
public toggleDropdown() {
if (this.open) {
this.closeDropdown();
} else {
this.openDropdown();
}
}
/**
* Sets the list group filter, and manages single select item selection.
* @param {string} searchString
*/
public onSearch(searchString) {
this.view["filterBy"](searchString);
if (searchString !== "") {
this.openDropdown();
} else {
this.selectedValue = "";
}
if (this.type === "single") {
// deselect if the input doesn't match the content
// of any given item
const matches = this.view.items.some(item => item.content.toLowerCase().includes(searchString.toLowerCase()));
if (!matches) {
const selected = this.view.getSelected();
if (selected) {
selected[0].selected = false;
// notify that the selection has changed
this.view.select.emit({ item: selected[0] });
this.propagateChangeCallback(null);
} else {
this.view["filterBy"]("");
}
}
}
}
/**
* Bubbles from `n-pill-input` when the user types a value and presses enter. Intended to be used to add items to the list.
*
* @param {any} ev event from `n-pill-input`, includes the typed value and the index of the pill the user typed after
*
* Example:
* ```javascript
* {
* after: 1,
* value: "some user string"
* }
* ```
*/
public onSubmit(ev) {
let index = 0;
if (ev.after) {
index = this.view.items.indexOf(ev.after) + 1;
}
this.submit.emit({
items: this.view.items,
index,
value: {
content: ev.value,
selected: false
}
});
}
private updateSelected() {
const selected = this.view.getSelected();
if (selected) {
if (this.type === "multi") {
this.updatePills();
} else {
this.selectedValue = selected[0].content;
this.propagateChangeCallback(selected[0]);
}
}
}
}