UNPKG

ngx-multi-selector

Version:

A small plugin which generates multi-selector control, it supports web api loading. Only supported angular 5.

578 lines (574 loc) 22.9 kB
import { __decorate, __metadata } from 'tslib'; import { Input, ViewChild, ElementRef, TemplateRef, Component, forwardRef, NgModule } from '@angular/core'; import { NG_VALUE_ACCESSOR, FormsModule } from '@angular/forms'; import { ReplaySubject, EMPTY, timer, of } from 'rxjs'; import { debounce, map, distinctUntilChanged, switchMap } from 'rxjs/operators'; import { CommonModule } from '@angular/common'; var NgxMultiSelectorComponent_1; let NgxMultiSelectorComponent = NgxMultiSelectorComponent_1 = class NgxMultiSelectorComponent { //#endregion //#region Constructor /* * Initiate components with injectors (as available) * */ constructor() { //#region Properties /* * Delay time between 2 item search requests. * */ this._defaultItemsInterval = 150; /* * Character which is used for separating selected items. * Default: , * */ this._separationCharacter = ','; /* * Maximum items that can be displayed in context menu. * */ this._maximumContextItems = 0; /* * How many items can be selected. * */ this._maximumSelectableItems = 0; /* * How much time should component raise another one about its changes. * */ this._loadItemsInterval = this._defaultItemsInterval; this._selectedItems = []; this.items = []; // By default, items amount is limited to 10. this._maximumSelectableItems = 10; this._updateAvailableItemsSubject = new ReplaySubject(1); this._updateAvailableItemsSubscription = this._updateAvailableItemsSubject .pipe(debounce((command) => { if (command.shouldIgnoreInterval) { return EMPTY; } return timer(this._loadItemsInterval); }), map((command) => { // Format keyword. let keyword = this._keyword; if (keyword && keyword.length) { keyword = keyword.trim(); } return { keyword, command }; }), distinctUntilChanged((previous, next) => { // Get current command. const nextCommand = next.command; if (!nextCommand.shouldIgnoreDuplicate) { // Values are equal, it will not emit the current value return previous.keyword === next.keyword; } // Values are different, so it will emit return false; }), switchMap((model) => { return this.loadAvailableItemsAsync(model.keyword); })) .subscribe(availableItems => { this._availableItems = [...availableItems]; }); } //#endregion //#region Accessors /* * Keyword is used for searching for items. * */ get keyword() { return this._keyword; } /* * Keyword is used for searching for items. * */ set keyword(value) { this._keyword = value; // Initialize control update command. const command = { shouldIgnoreDuplicate: false, shouldIgnoreInterval: false }; this._updateAvailableItemsSubject .next(command); } /* * Character which is used for dividing searched items. * */ set separationCharacter(value) { if (!value || !value.length) { this._separationCharacter = ','; return; } this._separationCharacter = value; } /* * Items which have been selected. * */ get selectedItems() { return this._selectedItems; } /* * Get selected items. * */ set selectedItems(values) { if (!values || values.length < 0) { this._selectedItems = []; return; } this._selectedItems = values; } /* * How much time should component raise another one about its changes. * */ set interval(value) { if (value < this._defaultItemsInterval) { this._loadItemsInterval = this._defaultItemsInterval; return; } this._loadItemsInterval = value; } /* * List of item which should be displayed on drop-down menu. * */ set items(values) { if (this.loadAvailableItemsAsyncHandler && this._updateAvailableItemsSubject) { this._updateAvailableItemsSubject.next(null); return; } if (!values || !values.length || !(values instanceof Array)) { this._originalItems = []; this._availableItems = []; return; } this._originalItems = values; this._availableItems = values; } /* * Get available items for displaying in drop-down list. * */ get items() { return this._availableItems; } /* * Get the maximum items that can be displayed in context menu. * */ get maximumContextItem() { return this._maximumContextItems; } /* * How many items should be shown to be selected. * */ set maximumContextItems(maximumItems) { if (maximumItems < 0) { maximumItems = 0; } this._maximumContextItems = maximumItems; } /* * Get maximum selectable items. * */ get maximumSelectableItems() { return this._maximumSelectableItems; } /* * Update the maximum selectable items. * */ set maximumSelectableItems(value) { if (value < 0 || isNaN(value)) { this._maximumSelectableItems = 0; return; } this._maximumSelectableItems = value; } /* * @deprecated * Please consider using key-property instead. * Key which is for recognizing whether item is in the chosen list or not. * */ set key(value) { this._key = value; } /* * Please consider using key-property instead. * Key which is for recognizing whether item is in the chosen list or not. * */ set keyProperty(value) { this._key = value; } /* * Which property should be used as display. * Null is about using object as display. * */ set displayProperty(value) { this._displayProperty = value; } /* * Which property should be used as selected value. * Null is about using object as selected value. * */ set valueProperty(value) { this._valueProperty = value; } //#endregion //#region Methods ngOnDestroy() { if (this._updateAvailableItemsSubscription && !this._updateAvailableItemsSubscription.closed) { this._updateAvailableItemsSubscription.unsubscribe(); } } /* * Check whether item has been chosen or not. * */ hasItemSelected(item) { return this.loadSelectedItemIndex(item) != -1; } /* * Find chosen item index in array. * */ loadSelectedItemIndex(item) { // Items list is empty. if (this._selectedItems == null || this._selectedItems.length < 1) { return -1; } // Get item index. const itemIndex = this._selectedItems.findIndex(selectedItem => this.loadItemUniqueValue(selectedItem) === this.loadItemUniqueValue(item)); return itemIndex; } /* * Get title which is used for being displayed on search box. * */ loadSelectedItemsTitle() { if (this.selectedItems == null || this.selectedItems.length < 1) { return ''; } // Find separation character. let separationCharacter = this._separationCharacter; if (!separationCharacter) { separationCharacter = ','; } return this.selectedItems .map(selectedItem => this.loadItemDisplay(selectedItem)) .join(separationCharacter) .slice(0, 255); } /* * Clear chosen items list. * */ deleteSelectedItems(shouldRaiseOnChangeCallback) { // Initiate new array. this.selectedItems = new Array(); if (shouldRaiseOnChangeCallback) { this.ngOnChangeCallback(this.selectedItems); } } /* * Find selected value and delete it. * */ deleteSelectedValue(value) { // Get index of value. if (!this.selectedItems || !this.selectedItems.length) { return; } const itemIndex = this.selectedItems.findIndex(selectedItem => this.loadItemValue(selectedItem) == value); if (itemIndex < 0) { return; } // Remove item from list. const selectedValues = [...this.selectedItems]; selectedValues.splice(itemIndex, 1); if (!selectedValues || !selectedValues.length) { this.selectedItems = null; this.ngOnChangeCallback(null); } else { this.selectedItems = selectedValues; this.ngOnChangeCallback(this.selectedItems.map(selectedItem => this.loadItemValue(selectedItem))); } } /* * Select an item in list. * */ clickSelectItem(item) { // Enlist of selected items. let selectedItems; if (!this.selectedItems) { selectedItems = []; } else { selectedItems = [...this.selectedItems]; } // Get item in the selected value. const itemIndex = this.loadSelectedItemIndex(item); // Item hasn't been chosen. if (itemIndex == null || itemIndex < 0) { // Maximum selected item exceeded. if (this.maximumSelectableItems != null && (this.maximumSelectableItems > 0 && selectedItems.length >= this.maximumSelectableItems)) { return; } // Get item value. selectedItems.push(item); this.selectedItems = selectedItems; this.ngOnChangeCallback(selectedItems.map(selectedItem => this.loadItemValue(selectedItem))); return; } selectedItems.splice(itemIndex, 1); if (!selectedItems || !selectedItems.length) { this.selectedItems = null; this.ngOnChangeCallback(this.selectedItems); } else { this.selectedItems = selectedItems; this.ngOnChangeCallback(this.selectedItems); } } /* * Close drop-down menu. * */ close() { // Drop-down menu is invalid. if (this.multiSelectorDropDown == null) { return; } // Find drop-down native element. const nativeElement = this.multiSelectorDropDown.nativeElement; if (nativeElement == null) { return; } // Remove class .open from classes list. nativeElement.classList.remove('open'); } /* * Open drop-down menu. * */ open() { // Component has been disabled. if (this.disabled) { return; } // Drop-down menu is invalid. if (this.multiSelectorDropDown == null) { return; } // Find drop-down native element. const nativeElement = this.multiSelectorDropDown.nativeElement; if (nativeElement == null) { return; } // Remove class .open from classes list. this.multiSelectorDropDown.nativeElement.classList.add('open'); } /* * Callback which is fired when component receives information from external source. * */ writeValue(selectedValues) { // No value has been selected. if (!selectedValues || !selectedValues.length) { this.selectedItems = null; return; } this.selectedItems = selectedValues; } /* * Callback which is fired when on-change register has been initiated. * */ registerOnChange(fn) { this.ngOnChangeCallback = fn; } /* * Callback which is fired when on-touch register has been initiated. * */ registerOnTouched(fn) { this.ngOnTouchedCallback = fn; } /* * Set item to be disabled | enabled. * */ setDisabledState(isDisabled) { this.disabled = isDisabled; } /* * Load available items asynchronously. * This function will be used in offline mode. * */ loadAvailableItemsAsync(keyword) { // Make the keyword to be upper cased. const upperCasedKeyword = (keyword == null) ? null : keyword.toUpperCase(); // Get the handler. const loadAvailableItemsAsyncHandler = this.loadAvailableItemsAsyncHandler; // No handler is defined for remote data loading. if (!loadAvailableItemsAsyncHandler) { // Load available items locally. return of([...this._originalItems]) .pipe(map((items) => { if (!items || !items.length) { return []; } return items .filter(item => { // Get the display property. const itemDisplay = this.loadItemDisplay(item); if (!upperCasedKeyword) { return items; } return itemDisplay.toString().toUpperCase().indexOf(upperCasedKeyword) !== -1; }); })); } // Load available items remotely. return this.loadAvailableItemsAsyncHandler(keyword); } /* * Load item display. * */ loadItemDisplay(item) { // Get the display property. const displayProperty = this._displayProperty; if (!displayProperty || !displayProperty.length) { return item.toString(); } return item[displayProperty]; } /* * Load item value. * */ loadItemValue(item) { // Get defined key. const valueProperty = this._valueProperty; if (!valueProperty || !valueProperty.length) { return item; } return item[valueProperty]; } /* * Base on key to get item unique value. * */ loadItemUniqueValue(item) { if (!item) { return item; } // Get key. const key = this._key; if (!key || key.length < 1) { return item; } return item[key]; } ngOnInit() { if (!this.loadAvailableItemsAsyncHandler) { return; } if (!this._updateAvailableItemsSubject) { return; } const updateControlCommand = { shouldIgnoreDuplicate: true, shouldIgnoreInterval: true }; return this._updateAvailableItemsSubject.next(updateControlCommand); } }; __decorate([ Input('load-available-items-handler'), __metadata("design:type", Function) ], NgxMultiSelectorComponent.prototype, "loadAvailableItemsAsyncHandler", void 0); __decorate([ ViewChild('multiSelectorDropdownMenu', { static: false }), __metadata("design:type", ElementRef) ], NgxMultiSelectorComponent.prototype, "multiSelectorDropDown", void 0); __decorate([ Input('is-clear-button-available'), __metadata("design:type", Boolean) ], NgxMultiSelectorComponent.prototype, "shouldClearButtonAvailable", void 0); __decorate([ Input('is-search-box-available'), __metadata("design:type", Boolean) ], NgxMultiSelectorComponent.prototype, "shouldSearchBoxAvailable", void 0); __decorate([ Input('placeholder-search-drop-down'), __metadata("design:type", String) ], NgxMultiSelectorComponent.prototype, "placeholderSearchDropDown", void 0); __decorate([ Input('placeholder-title-drop-down'), __metadata("design:type", String) ], NgxMultiSelectorComponent.prototype, "placeholderTitleDropDown", void 0); __decorate([ Input('disabled'), __metadata("design:type", Boolean) ], NgxMultiSelectorComponent.prototype, "disabled", void 0); __decorate([ Input('item-template'), __metadata("design:type", TemplateRef) ], NgxMultiSelectorComponent.prototype, "itemTemplate", void 0); __decorate([ Input('separation-character'), __metadata("design:type", String), __metadata("design:paramtypes", [String]) ], NgxMultiSelectorComponent.prototype, "separationCharacter", null); __decorate([ Input('interval'), __metadata("design:type", Number), __metadata("design:paramtypes", [Number]) ], NgxMultiSelectorComponent.prototype, "interval", null); __decorate([ Input('items'), __metadata("design:type", Array), __metadata("design:paramtypes", [Array]) ], NgxMultiSelectorComponent.prototype, "items", null); __decorate([ Input('limit-item-amount'), __metadata("design:type", Number), __metadata("design:paramtypes", [Number]) ], NgxMultiSelectorComponent.prototype, "maximumContextItems", null); __decorate([ Input('limit-item-selection'), __metadata("design:type", Number), __metadata("design:paramtypes", [Number]) ], NgxMultiSelectorComponent.prototype, "maximumSelectableItems", null); __decorate([ Input('key'), __metadata("design:type", String), __metadata("design:paramtypes", [String]) ], NgxMultiSelectorComponent.prototype, "key", null); __decorate([ Input('key-property'), __metadata("design:type", String), __metadata("design:paramtypes", [String]) ], NgxMultiSelectorComponent.prototype, "keyProperty", null); __decorate([ Input('display-property'), __metadata("design:type", String), __metadata("design:paramtypes", [String]) ], NgxMultiSelectorComponent.prototype, "displayProperty", null); __decorate([ Input('value-property'), __metadata("design:type", String), __metadata("design:paramtypes", [String]) ], NgxMultiSelectorComponent.prototype, "valueProperty", null); NgxMultiSelectorComponent = NgxMultiSelectorComponent_1 = __decorate([ Component({ selector: 'ngx-multi-selector', exportAs: 'ngx-multi-selector', template: "<div class=\"dropdown\">\r\n\r\n <!--Text input-->\r\n <div #multiSelectorDropdownMenu\r\n class=\"input-group ngx-multi-selector\">\r\n <div class=\"dropdown-toggle\"\r\n [attr.data-toggle]=\"disabled ? '': 'dropdown'\"\r\n aria-haspopup=\"true\"\r\n aria-expanded=\"false\"\r\n [attr.disabled]=\"disabled\">\r\n <input class=\"form-control ngx-multi-selector-title-box\"\r\n [class.disabled]=\"disabled\"\r\n [placeholder]=\"!placeholderTitleDropDown ? '' : placeholderTitleDropDown\"\r\n readonly=\"readonly\"\r\n [value]=\"loadSelectedItemsTitle()\">\r\n </div>\r\n <span class=\"input-group-addon\"\r\n *ngIf=\"shouldClearButtonAvailable\"\r\n [class.disabled]=\"disabled\"\r\n (click)=\"deleteSelectedItems()\">\r\n <span class=\"glyphicon glyphicon-remove\"></span>\r\n </span>\r\n\r\n <!--Dropdown-->\r\n <span class=\"input-group-addon dropdown-toggle\"\r\n [attr.data-toggle]=\"disabled ? '': 'dropdown'\"\r\n [class.disabled]=\"disabled\"\r\n aria-haspopup=\"true\"\r\n aria-expanded=\"false\">\r\n <span class=\"caret\"></span>\r\n </span>\r\n\r\n <!--Dropdown menu-->\r\n <ul class=\"dropdown-menu\"\r\n (click)=\"$event.stopPropagation();\">\r\n <li *ngIf=\"shouldSearchBoxAvailable\">\r\n <div class=\"col-lg-12\">\r\n <div class=\"form-group\">\r\n <div class=\"input-group\">\r\n <input class=\"form-control\"\r\n [(ngModel)]=\"keyword\"\r\n [placeholder]=\"!placeholderSearchDropDown ? '' : placeholderSearchDropDown\">\r\n <span class=\"input-group-addon\">\r\n <span class=\"glyphicon glyphicon-search\"></span>\r\n </span>\r\n </div>\r\n </div>\r\n </div>\r\n </li>\r\n\r\n <ng-template ngFor\r\n let-item\r\n let-i=\"index\"\r\n [ngForOf]=\"items\">\r\n\r\n <ng-container *ngIf=\"maximumContextItem < 1 || (maximumContextItem > 0 && i < maximumContextItem)\">\r\n <!--Item template-->\r\n <ng-template [ngTemplateOutlet]=\"itemTemplate || defaultItemTemplate\"\r\n [ngTemplateOutletContext]=\"{item:item, index:i, selected: hasItemSelected(item), instance: this}\">\r\n </ng-template>\r\n </ng-container>\r\n\r\n </ng-template>\r\n </ul>\r\n </div>\r\n</div>\r\n\r\n<!--Default item template-->\r\n<ng-template #defaultItemTemplate\r\n let-item=\"item\"\r\n let-i=\"index\"\r\n let-selected=\"selected\"\r\n let-instance=\"instance\">\r\n\r\n <li [class.active]=\"hasItemSelected(item)\"\r\n (click)=\"instance.clickSelectItem(item)\">\r\n <a href=\"javascript:void(0);\">\r\n <span class=\"glyphicon glyphicon-check\"\r\n *ngIf=\"selected\"></span>\r\n {{loadItemDisplay(item)}}\r\n </a>\r\n </li>\r\n</ng-template>\r\n", providers: [ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => NgxMultiSelectorComponent_1), multi: true } ], styles: [".ngx-multi-selector .dropdown-menu{width:100%}.ngx-multi-selector input.ngx-multi-selector-title-box{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;background-color:#fff!important;cursor:default}.ngx-multi-selector .input-group-addon.disabled,.ngx-multi-selector .input-group-addon:disabled,.ngx-multi-selector input.ngx-multi-selector-title-box.disabled,.ngx-multi-selector input.ngx-multi-selector-title-box:disabled{background-color:#eee!important;cursor:not-allowed;pointer-events:none}"] }), __metadata("design:paramtypes", []) ], NgxMultiSelectorComponent); let NgxMultiSelectorModule = class NgxMultiSelectorModule { }; NgxMultiSelectorModule = __decorate([ NgModule({ imports: [CommonModule, FormsModule], declarations: [NgxMultiSelectorComponent], exports: [NgxMultiSelectorComponent] }) ], NgxMultiSelectorModule); export { NgxMultiSelectorComponent, NgxMultiSelectorModule }; //# sourceMappingURL=ngx-multi-selector.js.map