igniteui-angular
Version:
Ignite UI for Angular is a dependency-free Angular toolkit for building modern web apps
1,555 lines • 173 kB
JavaScript
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
import * as tslib_1 from "tslib";
import { ConnectedPositioningStrategy } from './../services/overlay/position/connected-positioning-strategy';
import { CommonModule } from '@angular/common';
import { ChangeDetectorRef, Component, ContentChild, ElementRef, EventEmitter, HostBinding, HostListener, Input, NgModule, Output, TemplateRef, ViewChild, Optional, Inject, Injector, forwardRef } from '@angular/core';
import { IgxComboItemDirective, IgxComboEmptyDirective, IgxComboHeaderItemDirective, IgxComboHeaderDirective, IgxComboFooterDirective, IgxComboAddItemDirective, IgxComboToggleIconDirective, IgxComboClearIconDirective } from './combo.directives';
import { FormsModule, ReactiveFormsModule, NG_VALUE_ACCESSOR, NgControl } from '@angular/forms';
import { IgxCheckboxModule } from '../checkbox/checkbox.component';
import { IgxSelectionAPIService } from '../core/selection';
import { cloneArray } from '../core/utils';
import { IgxStringFilteringOperand, IgxBooleanFilteringOperand } from '../data-operations/filtering-condition';
import { FilteringLogic } from '../data-operations/filtering-expression.interface';
import { SortingDirection } from '../data-operations/sorting-expression.interface';
import { IgxForOfModule, IgxForOfDirective } from '../directives/for-of/for_of.directive';
import { IgxIconModule } from '../icon/index';
import { IgxRippleModule } from '../directives/ripple/ripple.directive';
import { IgxToggleModule } from '../directives/toggle/toggle.directive';
import { IgxButtonModule } from '../directives/button/button.directive';
import { IgxDropDownModule } from '../drop-down/index';
import { IgxInputGroupModule } from '../input-group/input-group.component';
import { IgxComboItemComponent } from './combo-item.component';
import { IgxComboDropDownComponent } from './combo-dropdown.component';
import { IgxComboFilterConditionPipe, IgxComboFilteringPipe, IgxComboGroupingPipe, IgxComboSortingPipe } from './combo.pipes';
import { AbsoluteScrollStrategy } from '../services';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { DeprecateProperty } from '../core/deprecateDecorators';
import { DefaultSortingStrategy } from '../data-operations/sorting-strategy';
import { DisplayDensityBase, DisplayDensityToken } from '../core/density';
import { IGX_COMBO_COMPONENT } from './combo.common';
import { IgxComboAddItemComponent } from './combo-add-item.component';
import { IgxComboAPIService } from './combo.api';
import { take } from 'rxjs/operators';
/**
* Custom strategy to provide the combo with callback on initial positioning
*/
class ComboConnectedPositionStrategy extends ConnectedPositioningStrategy {
/**
* @param {?} callback
*/
constructor(callback) {
super();
this._callback = callback;
}
/**
* @param {?} contentElement
* @param {?} size
* @param {?=} document
* @param {?=} initialCall
* @return {?}
*/
position(contentElement, size, document, initialCall) {
if (initialCall) {
this._callback();
}
super.position(contentElement, size);
}
}
if (false) {
/**
* @type {?}
* @private
*/
ComboConnectedPositionStrategy.prototype._callback;
}
/** @enum {string} */
const DataTypes = {
EMPTY: 'empty',
PRIMITIVE: 'primitive',
COMPLEX: 'complex',
PRIMARYKEY: 'valueKey',
};
/** @enum {number} */
const IgxComboState = {
/**
* Combo with initial state.
*/
INITIAL: 0,
/**
* Combo with valid state.
*/
VALID: 1,
/**
* Combo with invalid state.
*/
INVALID: 2,
};
export { IgxComboState };
IgxComboState[IgxComboState.INITIAL] = 'INITIAL';
IgxComboState[IgxComboState.VALID] = 'VALID';
IgxComboState[IgxComboState.INVALID] = 'INVALID';
/**
* @record
*/
export function IComboSelectionChangeEventArgs() { }
if (false) {
/** @type {?} */
IComboSelectionChangeEventArgs.prototype.oldSelection;
/** @type {?} */
IComboSelectionChangeEventArgs.prototype.newSelection;
/** @type {?|undefined} */
IComboSelectionChangeEventArgs.prototype.event;
}
/**
* @record
*/
export function IComboItemAdditionEvent() { }
if (false) {
/** @type {?} */
IComboItemAdditionEvent.prototype.oldCollection;
/** @type {?} */
IComboItemAdditionEvent.prototype.addedItem;
/** @type {?} */
IComboItemAdditionEvent.prototype.newCollection;
}
/** @type {?} */
let NEXT_ID = 0;
/** @type {?} */
const noop = () => { };
const ɵ0 = noop;
export class IgxComboComponent extends DisplayDensityBase {
/**
* @param {?} elementRef
* @param {?} cdr
* @param {?} selection
* @param {?} comboAPI
* @param {?} _displayDensityOptions
* @param {?} injector
*/
constructor(elementRef, cdr, selection, comboAPI, _displayDensityOptions, injector) {
super(_displayDensityOptions);
this.elementRef = elementRef;
this.cdr = cdr;
this.selection = selection;
this.comboAPI = comboAPI;
this._displayDensityOptions = _displayDensityOptions;
this.injector = injector;
/**
* @hidden \@internal
*/
this.customValueFlag = true;
/**
* @hidden \@internal
*/
this.defaultFallbackGroup = 'Other';
this.stringFilters = IgxStringFilteringOperand;
this.booleanFilters = IgxBooleanFilteringOperand;
this._filteringLogic = FilteringLogic.Or;
this._filteringExpressions = [];
this._sortingExpressions = [];
this._groupKey = '';
this._prevInputValue = '';
this._dataType = '';
this.ngControl = null;
this.destroy$ = new Subject();
this._data = [];
this._filteredData = [];
this._onChangeCallback = noop;
this.overlaySettings = {
scrollStrategy: new AbsoluteScrollStrategy(),
modal: false,
closeOnOutsideClick: true,
excludePositionTarget: true
};
this._value = '';
/**
* @hidden \@internal
*/
this.searchInput = null;
/**
* @hidden \@internal
*/
this.comboInput = null;
/**
* The custom template, if any, that should be used when rendering ITEMS in the combo list
*
* ```typescript
* // Set in typescript
* const myCustomTemplate: TemplateRef<any> = myComponent.customTemplate;
* myComponent.combo.itemTemplate = myCustomTemplate;
* ```
* ```html
* <!-- Set in markup -->
* <igx-combo #combo>
* ...
* <ng-template igxComboItem>
* <div class="custom-item" let-item let-key="valueKey">
* <div class="custom-item__name">{{ item[key] }}</div>
* <div class="custom-item__cost">{{ item.cost }}</div>
* </div>
* </ng-template>
* </igx-combo>
* ```
*/
this.itemTemplate = null;
/**
* The custom template, if any, that should be used when rendering the HEADER for the combo items list
*
* ```typescript
* // Set in typescript
* const myCustomTemplate: TemplateRef<any> = myComponent.customTemplate;
* myComponent.combo.headerTemplate = myCustomTemplate;
* ```
* ```html
* <!-- Set in markup -->
* <igx-combo #combo>
* ...
* <ng-template igxComboHeader>
* <div class="combo__header">
* This is a custom header
* </div>
* </ng-template>
* </igx-combo>
* ```
*/
this.headerTemplate = null;
/**
* The custom template, if any, that should be used when rendering the FOOTER for the combo items list
*
* ```typescript
* // Set in typescript
* const myCustomTemplate: TemplateRef<any> = myComponent.customTemplate;
* myComponent.combo.footerTemplate = myCustomTemplate;
* ```
* ```html
* <!-- Set in markup -->
* <igx-combo #combo>
* ...
* <ng-template igxComboFooter>
* <div class="combo__footer">
* This is a custom footer
* </div>
* </ng-template>
* </igx-combo>
* ```
*/
this.footerTemplate = null;
/**
* The custom template, if any, that should be used when rendering HEADER ITEMS for groups in the combo list
*
* ```typescript
* // Set in typescript
* const myCustomTemplate: TemplateRef<any> = myComponent.customTemplate;
* myComponent.combo.headerItemTemplate = myCustomTemplate;
* ```
* ```html
* <!-- Set in markup -->
* <igx-combo #combo>
* ...
* <ng-template igxComboHeaderItem let-item let-key="groupKey">
* <div class="custom-item--group">Group header for {{ item[key] }}</div>
* </ng-template>
* </igx-combo>
* ```
*/
this.headerItemTemplate = null;
/**
* The custom template, if any, that should be used when rendering the ADD BUTTON in the combo drop down
*
* ```typescript
* // Set in typescript
* const myCustomTemplate: TemplateRef<any> = myComponent.customTemplate;
* myComponent.combo.addItemTemplate = myCustomTemplate;
* ```
* ```html
* <!-- Set in markup -->
* <igx-combo #combo>
* ...
* <ng-template igxComboAddItem>
* <button class="combo__add-button">
* Click to add item
* </button>
* </ng-template>
* </igx-combo>
* ```
*/
this.addItemTemplate = null;
/**
* The custom template, if any, that should be used when rendering the ADD BUTTON in the combo drop down
*
* ```typescript
* // Set in typescript
* const myCustomTemplate: TemplateRef<any> = myComponent.customTemplate;
* myComponent.combo.emptyTemplate = myCustomTemplate;
* ```
* ```html
* <!-- Set in markup -->
* <igx-combo #combo>
* ...
* <ng-template igxComboEmpty>
* <div class="combo--emtpy">
* There are no items to display
* </div>
* </ng-template>
* </igx-combo>
* ```
*/
this.emptyTemplate = null;
/**
* The custom template, if any, that should be used when rendering the combo TOGGLE(open/close) button
*
* ```typescript
* // Set in typescript
* const myCustomTemplate: TemplateRef<any> = myComponent.customTemplate;
* myComponent.combo.toggleIconTemplate = myCustomTemplate;
* ```
* ```html
* <!-- Set in markup -->
* <igx-combo #combo>
* ...
* <ng-template igxComboToggleIcon let-collapsed>
* <igx-icon>{{ collapsed ? 'remove_circle' : 'remove_circle_outline'}}</igx-icon>
* </ng-template>
* </igx-combo>
* ```
*/
this.toggleIconTemplate = null;
/**
* The custom template, if any, that should be used when rendering the combo CLEAR button
*
* ```typescript
* // Set in typescript
* const myCustomTemplate: TemplateRef<any> = myComponent.customTemplate;
* myComponent.combo.clearIconTemplate = myCustomTemplate;
* ```
* ```html
* <!-- Set in markup -->
* <igx-combo #combo>
* ...
* <ng-template igxComboClearIcon>
* <igx-icon>clear</igx-icon>
* </ng-template>
* </igx-combo>
* ```
*/
this.clearIconTemplate = null;
this.dropdownContainer = null;
/**
* Emitted when item selection is changing, before the selection completes
*
* ```html
* <igx-combo (onSelectionChange)='handleSelection()'></igx-combo>
* ```
*/
this.onSelectionChange = new EventEmitter();
/**
* Emitted before the dropdown is opened
*
* ```html
* <igx-combo onOpening='handleOpening($event)'></igx-combo>
* ```
*/
this.onOpening = new EventEmitter();
/**
* Emitted after the dropdown is opened
*
* ```html
* <igx-combo (onOpened)='handleOpened()'></igx-combo>
* ```
*/
this.onOpened = new EventEmitter();
/**
* Emitted before the dropdown is closed
*
* ```html
* <igx-combo (onClosing)='handleClosing($event)'></igx-combo>
* ```
*/
this.onClosing = new EventEmitter();
/**
* Emitted after the dropdown is closed
*
* ```html
* <igx-combo (onClosed)='handleClosed()'></igx-combo>
* ```
*/
this.onClosed = new EventEmitter();
/**
* Emitted when an item is being added to the data collection
*
* ```html
* <igx-combo (onAddition)='handleAdditionEvent()'></igx-combo>
* ```
*/
this.onAddition = new EventEmitter();
/**
* Emitted when the value of the search input changes (e.g. typing, pasting, clear, etc.)
*
* ```html
* <igx-combo (onSearchInput)='handleSearchInputEvent()'></igx-combo>
* ```
*/
this.onSearchInput = new EventEmitter();
/**
* Emitted when new chunk of data is loaded from the virtualization
*
* ```html
* <igx-combo (onDataPreLoad)='handleDataPreloadEvent()'></igx-combo>
* ```
*/
this.onDataPreLoad = new EventEmitter();
/**
* Gets/gets combo id.
*
* ```typescript
* // get
* let id = this.combo.id;
* ```
*
* ```html
* <!--set-->
* <igx-combo [id]='combo1'></igx-combo>
* ```
*/
this.id = `igx-combo-${NEXT_ID++}`;
/**
* @hidden \@internal
*/
this.cssClass = 'igx-combo'; // Independent of display density, at the time being
// Independent of display density, at the time being
/**
* @hidden \@internal
*/
this.role = 'combobox';
/**
* Controls whether custom values can be added to the collection
*
* ```typescript
* // get
* let comboAllowsCustomValues = this.combo.allowCustomValues;
* ```
*
* ```html
* <!--set-->
* <igx-combo [allowCustomValues]='true'></igx-combo>
* ```
*/
this.allowCustomValues = false;
/**
* Configures the drop down list height
*
* ```typescript
* // get
* let myComboItemsMaxHeight = this.combo.itemsMaxHeight;
* ```
*
* ```html
* <!--set-->
* <igx-combo [itemsMaxHeight]='320'></igx-combo>
* ```
*/
this.itemsMaxHeight = 480;
/**
* Configures the drop down list item height
*
* ```typescript
* // get
* let myComboItemHeight = this.combo.itemHeight;
* ```
*
* ```html
* <!--set-->
* <igx-combo [itemHeight]='32'></igx-combo>
* ```
*/
this.itemHeight = 48;
/**
* @hidden \@internal
*/
this.filteringLogic = FilteringLogic.Or;
/**
* Defines the placeholder value for the combo value field
*
* ```typescript
* // get
* let myComboPlaceholder = this.combo.placeholder;
* ```
*
* ```html
* <!--set-->
* <igx-combo [placeholder]='newPlaceHolder'></igx-combo>
* ```
*/
this.placeholder = '';
/**
* Defines the placeholder value for the combo dropdown search field
*
* ```typescript
* // get
* let myComboSearchPlaceholder = this.combo.searchPlaceholder;
* ```
*
* ```html
* <!--set-->
* <igx-combo [searchPlaceholder]='newPlaceHolder'></igx-combo>
* ```
*/
this.searchPlaceholder = 'Enter a Search Term';
/**
* An \@Input property that enabled/disables filtering in the list. The default is `true`.
* ```html
* <igx-combo [filterable]="'false'">
* ```
*/
this.filterable = true;
/**
* An \@Input property that enabled/disables combo. The default is `false`.
* ```html
* <igx-combo [disabled]="'true'">
* ```
*/
this.disabled = false;
/**
* An \@Input property that sets how the combo will be styled.
* The allowed values are `line`, `box`, `border` and `search`. The default is `box`.
* ```html
* <igx-combo [type]="'line'">
* ```
*/
this.type = 'box';
/**
* Gets/Sets if control is valid, when used in a form
*
* ```typescript
* // get
* let valid = this.combo.valid;
* ```
* ```typescript
* // set
* this.combo.valid = IgxComboState.INVALID;
* ```
*/
this.valid = IgxComboState.INITIAL;
/**
* @hidden \@internal
*/
this.searchValue = '';
this.onStatusChanged = () => {
if ((this.ngControl.control.touched || this.ngControl.control.dirty) &&
(this.ngControl.control.validator || this.ngControl.control.asyncValidator)) {
this.valid = this.ngControl.valid ? IgxComboState.VALID : IgxComboState.INVALID;
}
};
this.comboAPI.register(this);
}
/**
* @hidden \@internal
* @return {?}
*/
get displaySearchInput() {
return this.filterable || this.allowCustomValues;
}
/**
* @hidden \@internal
* @private
* @param {?} template
* @return {?}
*/
set oldEmptyTemplate(template) {
if (template) {
this.emptyTemplate = template;
}
}
/**
* @hidden \@internal
* @private
* @param {?} template
* @return {?}
*/
set oldHeaderTemplate(template) {
if (template) {
this.headerTemplate = template;
}
}
/**
* @hidden \@internal
* @private
* @param {?} template
* @return {?}
*/
set oldFooterTemplate(template) {
if (template) {
this.footerTemplate = template;
}
}
/**
* @hidden \@internal
* @private
* @param {?} template
* @return {?}
*/
set oldItemTemplate(template) {
if (template) {
this.itemTemplate = template;
}
}
/**
* @hidden \@internal
* @private
* @param {?} template
* @return {?}
*/
set oldAddItemTemplate(template) {
if (template) {
this.addItemTemplate = template;
}
}
/**
* @hidden \@internal
* @private
* @param {?} template
* @return {?}
*/
set oldHeaderItemTemplate(template) {
if (template) {
this.headerItemTemplate = template;
}
}
/**
* @hidden \@internal
* @return {?}
*/
get validClass() {
return this.valid === IgxComboState.VALID;
}
/**
* @hidden \@internal
* @return {?}
*/
get invalidClass() {
return this.valid === IgxComboState.INVALID;
}
/**
* @hidden \@internal
* @return {?}
*/
get ariaExpanded() {
return !this.dropdown.collapsed;
}
/**
* @hidden \@internal
* @return {?}
*/
get hasPopUp() {
return 'listbox';
}
/**
* @hidden \@internal
* @return {?}
*/
get ariaOwns() {
return this.dropdown.id;
}
/**
* @hidden \@internal
* @return {?}
*/
get inputEmpty() {
return !this.value && !this.placeholder;
}
/**
* Combo data source.
*
* ```html
* <!--set-->
* <igx-combo [data]='items'></igx-combo>
* ```
* @return {?}
*/
get data() {
return this._data;
}
/**
* @param {?} val
* @return {?}
*/
set data(val) {
this._data = (val) ? val : [];
}
/**
* @param {?} val
* @return {?}
*/
set displayKey(val) {
this._displayKey = val;
}
/**
* Combo text data source propery.
*
* ```typescript
* // get
* let myComboDisplayKey = this.combo.displayKey;
*
* // set
* this.combo.displayKey = 'val';
*
* ```
*
* ```html
* <!--set-->
* <igx-combo [displayKey]='mydisplayKey'></igx-combo>
* ```
* @return {?}
*/
get displayKey() {
return this._displayKey ? this._displayKey : this.valueKey;
}
/**
* The item property by which items should be grouped inside the items list. Not usable if data is not of type Object[].
*
* ```html
* <!--set-->
* <igx-combo [groupKey]='newGroupKey'></igx-combo>
* ```
* @param {?} val
* @return {?}
*/
set groupKey(val) {
this.clearSorting(this._groupKey);
this._groupKey = val;
this.sort(this._groupKey);
}
/**
* The item property by which items should be grouped inside the items list. Not usable if data is not of type Object[].
*
* ```typescript
* // get
* let currentGroupKey = this.combo.groupKey;
* ```
* @return {?}
*/
get groupKey() {
return this._groupKey;
}
/**
* @hidden \@internal
* @param {?} event
* @return {?}
*/
onArrowDown(event) {
event.preventDefault();
event.stopPropagation();
this.open();
}
/**
* @hidden \@internal
* @param {?} event
* @return {?}
*/
onInputClick(event) {
event.stopPropagation();
event.preventDefault();
this.toggle();
}
/**
* Defines the current state of the virtualized data. It contains `startIndex` and `chunkSize`
*
* ```typescript
* // get
* let state = this.combo.virtualizationState;
* ```
* @return {?}
*/
get virtualizationState() {
return this.dropdown.verticalScrollContainer.state;
}
/**
* Sets the current state of the virtualized data.
*
* ```typescript
* // set
* this.combo.virtualizationState(state);
* ```
* @param {?} state
* @return {?}
*/
set virtualizationState(state) {
this.dropdown.verticalScrollContainer.state = state;
}
/**
* Gets total count of the virtual data items, when using remote service.
*
* ```typescript
* // get
* let count = this.combo.totalItemCount;
* ```
* @return {?}
*/
get totalItemCount() {
return this.dropdown.verticalScrollContainer.totalItemCount;
}
/**
* Sets total count of the virtual data items, when using remote service.
*
* ```typescript
* // set
* this.combo.totalItemCount(remoteService.count);
* ```
* @param {?} count
* @return {?}
*/
set totalItemCount(count) {
this.dropdown.verticalScrollContainer.totalItemCount = count;
this.cdr.detectChanges();
}
/**
* @hidden \@internal
* @return {?}
*/
get filteringExpressions() {
return this.filterable ? this._filteringExpressions : [];
}
/**
* @hidden \@internal
* @param {?} value
* @return {?}
*/
set filteringExpressions(value) {
this._filteringExpressions = value;
this.cdr.markForCheck();
}
/**
* @hidden \@internal
* @return {?}
*/
get sortingExpressions() {
return this._sortingExpressions;
}
/**
* @hidden \@internal
* @param {?} value
* @return {?}
*/
set sortingExpressions(value) {
this._sortingExpressions = value;
this.cdr.markForCheck();
}
/**
* @protected
* @param {?=} field
* @return {?}
*/
clearSorting(field) {
if (field === undefined || field === null) {
this.sortingExpressions = [];
return;
}
/** @type {?} */
const currentState = cloneArray(this.sortingExpressions);
/** @type {?} */
const index = currentState.findIndex((expr) => expr.fieldName === field);
if (index > -1) {
currentState.splice(index, 1);
this.sortingExpressions = currentState;
}
}
/**
* The text displayed in the combo input
*
* ```typescript
* // get
* let comboValue = this.combo.value;
* ```
* @return {?}
*/
get value() {
return this._value;
}
/**
* @hidden \@internal
* @return {?}
*/
get filteredData() {
return this.filterable ? this._filteredData : this.data;
}
/**
* @hidden \@internal
* @param {?} val
* @return {?}
*/
set filteredData(val) {
this._filteredData = this.groupKey ? (val || []).filter((e) => e.isHeader !== true) : val;
this.checkMatch();
}
/**
* @hidden \@internal
* @param {?} event
* @return {?}
*/
handleKeyUp(event) {
if (event.key === 'ArrowDown' || event.key === 'Down') {
this.dropdown.focusedItem = this.dropdown.items[0];
this.dropdownContainer.nativeElement.focus();
}
else if (event.key === 'Escape' || event.key === 'Esc') {
this.toggle();
}
}
/**
* @hidden \@internal
* @param {?} event
* @return {?}
*/
handleKeyDown(event) {
if (event.key === 'ArrowUp' || event.key === 'Up') {
event.preventDefault();
event.stopPropagation();
this.close();
}
}
/**
* @private
* @return {?}
*/
checkMatch() {
/** @type {?} */
const displayKey = this.displayKey;
/** @type {?} */
const matchFn = (e) => {
/** @type {?} */
const value = displayKey ? e[displayKey] : e;
return value.toString().toLowerCase() === this.searchValue.trim().toLowerCase();
};
/** @type {?} */
const itemMatch = this.filteredData.some(matchFn);
this.customValueFlag = this.allowCustomValues && !itemMatch;
}
/**
* @hidden \@internal
* @param {?=} event
* @return {?}
*/
handleInputChange(event) {
/** @type {?} */
let cdrFlag = false;
/** @type {?} */
const vContainer = this.dropdown.verticalScrollContainer;
if (event !== undefined && this._prevInputValue === event) {
// Nothing has changed
return;
}
else {
this._prevInputValue = event !== undefined ? event : '';
}
if (event !== undefined) {
// Do not scroll if not scrollable
if (vContainer.isScrollable()) {
vContainer.scrollTo(0);
}
else {
cdrFlag = true;
}
this.onSearchInput.emit(event);
}
if (this.filterable) {
this.filter();
// If there was no scroll before filtering, check if there is after and detect changes
if (cdrFlag) {
vContainer.onChunkLoad.pipe(take(1)).subscribe(() => {
if (vContainer.isScrollable()) {
this.cdr.detectChanges();
}
});
}
}
else {
this.checkMatch();
}
}
/**
* @hidden \@internal
* @param {?} fieldName
* @param {?=} dir
* @param {?=} ignoreCase
* @param {?=} strategy
* @return {?}
*/
sort(fieldName, dir = SortingDirection.Asc, ignoreCase = true, strategy = DefaultSortingStrategy.instance()) {
if (!fieldName) {
return;
}
/** @type {?} */
const sortingState = cloneArray(this.sortingExpressions, true);
this.prepare_sorting_expression(sortingState, fieldName, dir, ignoreCase, strategy);
this.sortingExpressions = sortingState;
}
/**
* @hidden \@internal
* @param {?} val
* @return {?}
*/
getValueByValueKey(val) {
if (!val && val !== 0) {
return undefined;
}
return this.valueKey ?
this.data.filter((e) => e[this.valueKey] === val)[0] :
this.data.filter((e) => e === val);
}
/**
* @protected
* @param {?} state
* @param {?} fieldName
* @param {?} dir
* @param {?} ignoreCase
* @param {?} strategy
* @return {?}
*/
prepare_sorting_expression(state, fieldName, dir, ignoreCase, strategy) {
if (dir === SortingDirection.None) {
state.splice(state.findIndex((expr) => expr.fieldName === fieldName), 1);
return;
}
/** @type {?} */
const expression = state.find((expr) => expr.fieldName === fieldName);
if (!expression) {
state.push({ fieldName, dir, ignoreCase, strategy });
}
else {
Object.assign(expression, { fieldName, dir, ignoreCase });
}
}
/**
* @hidden \@internal
* @return {?}
*/
get dataType() {
if (this.valueKey) {
return DataTypes.COMPLEX;
}
return DataTypes.PRIMITIVE;
}
/**
* @hidden \@internal
* @return {?}
*/
get isRemote() {
return this.totalItemCount > 0 &&
this.valueKey &&
this.dataType === DataTypes.COMPLEX;
}
/**
* If the data source is remote, returns JSON.stringify(itemID)
* @hidden
* \@internal
* @private
* @param {?} itemID
* @return {?}
*/
_stringifyItemID(itemID) {
return this.isRemote && typeof itemID === 'object' ? JSON.stringify(itemID) : itemID;
}
/**
* @private
* @param {?} itemID
* @return {?}
*/
_parseItemID(itemID) {
return this.isRemote && typeof itemID === 'string' ? JSON.parse(itemID) : itemID;
}
/**
* Returns if the specified itemID is selected
* @hidden
* \@internal
* @param {?} item
* @return {?}
*/
isItemSelected(item) {
return this.selection.is_item_selected(this.id, this._stringifyItemID(item));
}
/**
* Triggers change detection on the combo view
* @return {?}
*/
triggerCheck() {
this.cdr.detectChanges();
}
/**
* @hidden \@internal
* @return {?}
*/
isAddButtonVisible() {
// This should always return a boolean value. If this.searchValue was '', it returns '' instead of false;
return this.searchValue !== '' && this.customValueFlag;
}
/**
* @hidden \@internal
* @param {?} evt
* @return {?}
*/
handleSelectAll(evt) {
if (evt.checked) {
this.selectAllItems();
}
else {
this.deselectAllItems();
}
}
/**
* @hidden \@internal
* @return {?}
*/
addItemToCollection() {
if (!this.searchValue) {
return;
}
/** @type {?} */
const newValue = this.searchValue.trim();
/** @type {?} */
const addedItem = this.displayKey ? {
[this.valueKey]: newValue,
[this.displayKey]: newValue
} : newValue;
if (this.groupKey) {
Object.assign(addedItem, { [this.groupKey]: this.defaultFallbackGroup });
}
/** @type {?} */
const oldCollection = this.data;
/** @type {?} */
const newCollection = [...this.data];
newCollection.push(addedItem);
/** @type {?} */
const args = {
oldCollection, addedItem, newCollection
};
this.onAddition.emit(args);
this.data.push(addedItem);
// If you mutate the array, no pipe is invoked and the display isn't updated;
// if you replace the array, the pipe executes and the display is updated.
this.data = cloneArray(this.data);
this.selectItems([addedItem], false);
this.customValueFlag = false;
this.searchInput.nativeElement.focus();
this.dropdown.focusedItem = null;
this.handleInputChange();
}
/**
* @hidden \@internal
* @param {?=} opening
* @return {?}
*/
focusSearchInput(opening) {
if (this.displaySearchInput && this.searchInput) {
this.searchInput.nativeElement.focus();
}
else {
if (opening) {
this.dropdownContainer.nativeElement.focus();
}
else {
this.comboInput.nativeElement.focus();
this.toggle();
}
}
}
/**
* @protected
* @param {?} searchVal
* @param {?} condition
* @param {?} ignoreCase
* @param {?=} fieldName
* @return {?}
*/
prepare_filtering_expression(searchVal, condition, ignoreCase, fieldName) {
/** @type {?} */
const newArray = [...this.filteringExpressions];
/** @type {?} */
const expression = newArray.find((expr) => expr.fieldName === fieldName);
/** @type {?} */
const newExpression = { fieldName, searchVal, condition, ignoreCase };
if (!expression) {
newArray.push(newExpression);
}
else {
Object.assign(expression, newExpression);
}
if (this.groupKey) {
/** @type {?} */
const expression2 = newArray.find((expr) => expr.fieldName === 'isHeader');
/** @type {?} */
const headerExpression = {
fieldName: 'isHeader', searchVale: '',
condition: IgxBooleanFilteringOperand.instance().condition('true'), ignoreCase: true
};
if (!expression2) {
newArray.push(headerExpression);
}
else {
Object.assign(expression2, headerExpression);
}
}
this.filteringExpressions = newArray;
}
/**
* @hidden \@internal
* @return {?}
*/
onBlur() {
if (this.collapsed) {
if (this.ngControl && !this.ngControl.valid) {
this.valid = IgxComboState.INVALID;
}
else {
this.valid = IgxComboState.INITIAL;
}
}
}
/**
* @hidden \@internal
* @return {?}
*/
filter() {
this.prepare_filtering_expression(this.searchValue.trim(), IgxStringFilteringOperand.instance().condition('contains'), true, this.dataType === DataTypes.PRIMITIVE ? undefined : this.displayKey);
}
/**
* @hidden \@internal
* @return {?}
*/
ngOnInit() {
this.ngControl = this.injector.get(NgControl, null);
this._positionCallback = () => this.dropdown.updateScrollPosition();
this.overlaySettings.positionStrategy = new ComboConnectedPositionStrategy(this._positionCallback);
this.overlaySettings.positionStrategy.settings.target = this.elementRef.nativeElement;
this.selection.set(this.id, new Set());
}
/**
* @hidden \@internal
* @return {?}
*/
ngAfterViewInit() {
this.filteredData = [...this.data];
if (this.ngControl) {
this.ngControl.statusChanges.pipe(takeUntil(this.destroy$)).subscribe(this.onStatusChanged);
}
}
/**
* @hidden \@internal
* @return {?}
*/
ngOnDestroy() {
this.destroy$.complete();
this.comboAPI.clear();
this.selection.clear(this.id);
}
/**
* @hidden \@internal
* @param {?} event
* @return {?}
*/
dataLoading(event) {
this.onDataPreLoad.emit(event);
}
/**
* @hidden \@internal
* @param {?} value
* @return {?}
*/
writeValue(value) {
// selectItems can handle Array<any>, no valueKey is needed;
this.selectItems(value, true);
this.cdr.markForCheck();
}
/**
* @hidden \@internal
* @param {?} fn
* @return {?}
*/
registerOnChange(fn) {
this._onChangeCallback = fn;
}
/**
* @hidden \@internal
* @param {?} fn
* @return {?}
*/
registerOnTouched(fn) { }
/**
* @hidden \@internal
* @param {?} isDisabled
* @return {?}
*/
setDisabledState(isDisabled) {
this.disabled = isDisabled;
}
/**
* @hidden
* @return {?}
*/
getEditElement() {
return this.comboInput.nativeElement;
}
/**
* @hidden \@internal
* @return {?}
*/
get template() {
this._dataType = this.dataType;
if (this.itemTemplate) {
return this.itemTemplate;
}
if (this._dataType === DataTypes.COMPLEX) {
return this.complexTemplate;
}
return this.primitiveTemplate;
}
/**
* @hidden \@internal
* @return {?}
*/
get context() {
return {
$implicit: this
};
}
/**
* @hidden \@internal
* @param {?} event
* @return {?}
*/
handleClearItems(event) {
this.deselectAllItems(true, event);
event.stopPropagation();
}
/**
* A method that opens/closes the combo.
*
* ```html
* <button (click)="combo.toggle()">Toggle Combo</button>
* <igx-combo #combo></igx-combo>
* ```
* @return {?}
*/
toggle() {
this.dropdown.toggle(this.overlaySettings);
}
/**
* A method that opens the combo.
*
* ```html
* <button (click)="combo.open()">Open Combo</button>
* <igx-combo #combo></igx-combo>
* ```
* @return {?}
*/
open() {
this.dropdown.open(this.overlaySettings);
}
/**
* A method that closes the combo.
*
* ```html
* <button (click)="combo.close()">Close Combo</button>
* <igx-combo #combo></igx-combo>
* ```
* @return {?}
*/
close() {
this.dropdown.close();
}
/**
* Gets drop down state.
*
* ```typescript
* let state = this.combo.collapsed;
* ```
* @return {?}
*/
get collapsed() {
return this.dropdown.collapsed;
}
/**
* Get current selection state
* @return {?} Array of selected items
* ```typescript
* let selectedItems = this.combo.selectedItems();
* ```
*/
selectedItems() {
/** @type {?} */
const items = Array.from(this.selection.get(this.id));
return this.isRemote ? items.map(item => this._parseItemID(item)) : items;
}
/**
* Select defined items
* @param {?} newItems new items to be selected
* @param {?=} clearCurrentSelection if true clear previous selected items
* ```typescript
* this.combo.selectItems(["New York", "New Jersey"]);
* ```
* @param {?=} event
* @return {?}
*/
selectItems(newItems, clearCurrentSelection, event) {
if (newItems) {
/** @type {?} */
const newSelection = this.selection.add_items(this.id, newItems, clearCurrentSelection);
this.setSelection(newSelection, event);
}
}
/**
* Deselect defined items
* @param {?} items items to deselected
* ```typescript
* this.combo.deselectItems(["New York", "New Jersey"]);
* ```
* @param {?=} event
* @return {?}
*/
deselectItems(items, event) {
if (items) {
/** @type {?} */
const newSelection = this.selection.delete_items(this.id, items);
this.setSelection(newSelection, event);
}
}
/**
* Select all (filtered) items
* @param {?=} ignoreFilter if set to true, selects all items, otherwise selects only the filtered ones.
* ```typescript
* this.combo.selectAllItems();
* ```
* @param {?=} event
* @return {?}
*/
selectAllItems(ignoreFilter, event) {
/** @type {?} */
const allVisible = this.selection.get_all_ids(ignoreFilter ? this.data : this.filteredData);
/** @type {?} */
const newSelection = this.selection.add_items(this.id, allVisible);
this.setSelection(newSelection, event);
}
/**
* Deselect all (filtered) items
* @param {?=} ignoreFilter if set to true, deselects all items, otherwise deselects only the filtered ones.
* ```typescript
* this.combo.deselectAllItems();
* ```
* @param {?=} event
* @return {?}
*/
deselectAllItems(ignoreFilter, event) {
/** @type {?} */
let newSelection = this.selection.get_empty();
if (this.filteredData.length !== this.data.length && !ignoreFilter) {
newSelection = this.selection.delete_items(this.id, this.selection.get_all_ids(this.filteredData));
}
this.setSelection(newSelection, event);
}
/**
* Selects/Deselects an item using it's valueKey value
* @param {?} itemID the valueKey of the specified item
* @param {?=} select If the item should be selected (true) or deselcted (false)
*
* ```typescript
* items: { field: string, region: string}[] = data;
* this.combo.setSelectedItem('Connecticut', true);
* // combo.valueKey === 'field'
* // items[n] === { field: 'Connecticut', state: 'New England'}
* ```
* @param {?=} event
* @return {?}
*/
setSelectedItem(itemID, select = true, event) {
if (itemID === null || itemID === undefined) {
return;
}
/** @type {?} */
const itemValue = this.getValueByValueKey(itemID);
if (itemValue !== null && itemValue !== undefined) {
if (select) {
this.selectItems([itemValue], false, event);
}
else {
this.deselectItems([itemValue], event);
}
}
}
/**
* @protected
* @param {?} newSelection
* @param {?=} event
* @return {?}
*/
setSelection(newSelection, event) {
/** @type {?} */
const oldSelectionEmit = Array.from(this.selection.get(this.id) || []);
/** @type {?} */
const newSelectionEmit = Array.from(newSelection || []);
/** @type {?} */
const args = {
newSelection: newSelectionEmit,
oldSelection: oldSelectionEmit,
event,
cancel: false
};
this.onSelectionChange.emit(args);
if (!args.cancel) {
this.selection.select_items(this.id, args.newSelection, true);
this._value = this.dataType !== DataTypes.PRIMITIVE ?
args.newSelection.map((id) => this._parseItemID(id)[this.displayKey]).join(', ') :
args.newSelection.join(', ');
this._onChangeCallback(args.newSelection);
}
}
/**
* Event handlers
* @hidden
* \@internal
* @param {?} event
* @return {?}
*/
handleOpening(event) {
this.onOpening.emit(event);
if (event.cancel) {
return;
}
this.handleInputChange();
}
/**
* @hidden \@internal
* @return {?}
*/
handleOpened() {
this.triggerCheck();
this.focusSearchInput(true);
this.onOpened.emit();
}
/**
* @hidden \@internal
* @param {?} event
* @return {?}
*/
handleClosing(event) {
this.onClosing.emit(event);
if (event.cancel) {
return;
}
this.searchValue = '';
}
/**
* @hidden \@internal
* @return {?}
*/
handleClosed() {
this.comboInput.nativeElement.focus();
this.onClosed.emit();
}
}
IgxComboComponent.decorators = [
{ type: Component, args: [{
selector: 'igx-combo',
template: "<ng-template #complex let-display let-data=\"data\" let-key=\"displayKey\">\n {{display[key]}}\n</ng-template>\n<ng-template #primitive let-display>\n {{display}}\n</ng-template>\n<ng-template #empty>\n <span>The list is empty</span>\n</ng-template>\n<ng-template #addItemDefault let-control>\n <button igxButton=\"flat\" igxRipple>Add item</button>\n</ng-template>\n<ng-template #headerItemBase let-item let-key=\"valueKey\" let-groupKey=\"groupKey\">\n {{ item[key] }}\n</ng-template>\n\n<igx-input-group [displayDensity]=\"displayDensity\" [type]=\"type\" (click)=\"onInputClick($event)\">\n <ng-container ngProjectAs=\"[igxLabel]\">\n <ng-content select=\"[igxLabel]\"></ng-content>\n </ng-container>\n <ng-container ngProjectAs=\"igx-prefix\">\n <ng-content select=\"igx-prefix\"></ng-content>\n </ng-container>\n <ng-container ngProjectAs=\"igx-hint, [igxHint]\">\n <ng-content select=\"igx-hint, [igxHint]\"></ng-content>\n </ng-container>\n <input igxInput #comboInput name=\"comboInput\" type=\"text\" [value]=\"value\" readonly [placeholder]=\"placeholder\"\n [disabled]=\"disabled\" (blur)=\"onBlur()\" />\n <ng-container ngProjectAs=\"igx-suffix\">\n <ng-content select=\"igx-suffix\"></ng-content>\n </ng-container>\n <igx-suffix *ngIf=\"value.length\" aria-label=\"Clear Selection\" class=\"igx-combo__clear-button\" igxRipple (click)=\"handleClearItems($event)\">\n <ng-container *ngIf=\"clearIconTemplate\">\n <ng-container *ngTemplateOutlet=\"clearIconTemplate\"></ng-container>\n </ng-container>\n <igx-icon *ngIf=\"!clearIconTemplate\" fontSet=\"material\">clear</igx-icon>\n </igx-suffix>\n <igx-suffix igxButton=\"icon\" class=\"igx-combo__toggle-button\" igxRipple>\n <ng-container *ngIf=\"toggleIconTemplate\">\n <ng-container *ngTemplateOutlet=\"toggleIconTemplate; context: {$implicit: this.collapsed}\"></ng-container>\n </ng-container>\n <igx-icon *ngIf=\"!toggleIconTemplate\" fontSet=\"material\">{{ dropdown.collapsed ? 'arrow_drop_down' : 'arrow_drop_up'}}</igx-icon>\n </igx-suffix>\n</igx-input-group>\n<igx-combo-drop-down #igxComboDropDown class=\"igx-combo__drop-down\" [width]=\"itemsWidth || '100%'\" (onOpening)=\"handleOpening($event)\"\n (onClosing)=\"handleClosing($event)\" (onOpened)=\"handleOpened()\" (onClosed)=\"handleClosed()\">\n <igx-input-group *ngIf=\"displaySearchInput\" [displayDensity]=\"displayDensity\" class=\"igx-combo__search\">\n <input class=\"igx-combo-input\" igxInput #searchInput name=\"searchInput\" autocomplete=\"off\" type=\"text\"\n [(ngModel)]=\"searchValue\" (ngModelChange)=\"handleInputChange($event)\" (keyup)=\"handleKeyUp($event)\"\n (keydown)=\"handleKeyDown($event)\" (focus)=\"dropdown.onBlur($event)\" [placeholder]=\"searchPlaceholder\"\n aria-autocomplete=\"both\" [attr.aria-owns]=\"dropdown.id\" [attr.aria-labelledby]=\"ariaLabelledBy\" />\n </igx-input-group>\n <ng-container *ngTemplateOutlet=\"headerTemplate\">\n </ng-container>\n <div #dropdownItemContainer class=\"igx-combo__content\" [style.overflow]=\"'hidden'\" [style.maxHeight.px]=\"itemsMaxHeight\"\n [igxDropDownItemNavigation]=\"dropdown\" (focus)=\"dropdown.onFocus()\" [tabindex]=\"dropdown.collapsed ? -1 : 0\"\n role=\"listbox\" [attr.id]=\"dropdown.id\">\n <ng-template igxFor let-item let-index=\"index\" [igxForOf]=\"data | comboFiltering:filteringExpressions:filteringLogic | comboSorting:sortingExpressions | comboGrouping:groupKey\"\n [igxForScrollOrientation]=\"'vertical'\" [igxForContainerSize]=\"itemsMaxHeight\" [igxForItemSize]=\"itemHeight\"\n (onChunkPreload)=\"dataLoading($event)\">\n <igx-combo-item [itemHeight]='itemHeight' [value]=\"item\" isHeader={{item.isHeader}} role=\"option\" [index]=\"index\">\n <ng-container *ngIf=\"item.isHeader\">\n <ng-container *ngTemplateOutlet=\"headerItemTemplate ? headerItemTemplate : headerItemBase; context: {$implicit: item, data: data, valueKey: valueKey, groupKey: groupKey, displayKey: displayKey}\"></ng-container>\n </ng-container>\n <ng-container *ngIf=\"!item.isHeader\">\n <ng-container #listItem *ngTemplateOutlet=\"template; context: {$implicit: item, data: data, valueKey: valueKey, displayKey: displayKey};\"></ng-container>\n </ng-container>\n </igx-combo-item>\n </ng-template>\n </div>\n <div class=\"igx-comb