@experionathira/ng-multiselect-dropdown
Version:
Angular Multi-Select Dropdown
485 lines (477 loc) • 25.2 kB
JavaScript
import { __decorate } from 'tslib';
import { Pipe, forwardRef, EventEmitter, ChangeDetectorRef, Input, Output, HostListener, Component, ChangeDetectionStrategy, ElementRef, Directive, NgModule } from '@angular/core';
import { NG_VALUE_ACCESSOR, FormsModule } from '@angular/forms';
import { CommonModule } from '@angular/common';
class ListItem {
constructor(source) {
if (typeof source === "string" || typeof source === "number") {
this.id = this.text = source;
this.isDisabled = false;
this.property = "";
this.displayOrder = 0;
}
if (typeof source === "object") {
this.id = source.id;
this.text = source.text;
this.isDisabled = source.isDisabled;
this.property = source.property;
this.displayOrder = source.displayOrder;
}
}
}
let ListFilterPipe = class ListFilterPipe {
transform(items, filter) {
if (!items || !filter) {
return items;
}
return items.filter((item) => this.applyFilter(item, filter));
}
applyFilter(item, filter) {
if (typeof item.text === 'string' && typeof filter.text === 'string') {
return !(filter.text && item.text && item.text.toLowerCase().indexOf(filter.text.toLowerCase()) === -1);
}
else {
return !(filter.text && item.text && item.text.toString().toLowerCase().indexOf(filter.text.toString().toLowerCase()) === -1);
}
}
};
ListFilterPipe = __decorate([
Pipe({
name: 'multiSelectFilter',
pure: false
})
], ListFilterPipe);
const DROPDOWN_CONTROL_VALUE_ACCESSOR = {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => MultiSelectComponent),
multi: true,
};
const noop = () => { };
const ɵ0 = noop;
let MultiSelectComponent = class MultiSelectComponent {
constructor(cdr, listFilterPipe) {
this.cdr = cdr;
this.listFilterPipe = listFilterPipe;
this._data = [];
this.selectedItems = [];
this.isDropdownOpen = true;
this._placeholder = "Select";
this._sourceDataType = null; // to keep note of the source data type. could be array of string/number/object
this._sourceDataFields = []; // store source data fields names
this.filter = new ListItem(this.data);
this.defaultSettings = {
singleSelection: false,
idField: "id",
textField: "text",
disabledField: "isDisabled",
enableCheckAll: true,
selectAllText: "Select All",
unSelectAllText: "UnSelect All",
allowSearchFilter: false,
limitSelection: -1,
clearSearchFilter: true,
maxHeight: 197,
itemsShowLimit: 999999999999,
searchPlaceholderText: "Search",
noDataAvailablePlaceholderText: "No data available",
closeDropDownOnSelection: false,
showSelectedItemsAtTop: false,
defaultOpen: false,
allowRemoteDataSearch: false,
property: "property",
placement: "bottom",
displayOrder: "displayOrder",
includeDisabledCount: false,
};
this.disabled = false;
this.onFilterChange = new EventEmitter();
this.onDropDownClose = new EventEmitter();
this.onSelect = new EventEmitter();
this.onDeSelect = new EventEmitter();
this.onSelectAll = new EventEmitter();
this.onDeSelectAll = new EventEmitter();
this.onTouchedCallback = noop;
this.onChangeCallback = noop;
}
set placeholder(value) {
if (value) {
this._placeholder = value;
}
else {
this._placeholder = "Select";
}
}
set settings(value) {
if (value) {
this._settings = Object.assign(this.defaultSettings, value);
}
else {
this._settings = Object.assign(this.defaultSettings);
}
}
set data(value) {
if (!value) {
this._data = [];
}
else {
const firstItem = value[0];
this._sourceDataType = typeof firstItem;
this._sourceDataFields = this.getFields(firstItem);
this._data = value.map((item) => typeof item === "string" || typeof item === "number"
? new ListItem(item)
: new ListItem({
id: item[this._settings.idField],
text: item[this._settings.textField],
isDisabled: item[this._settings.disabledField],
property: item[this._settings.property],
displayOrder: item[this._settings.displayOrder],
}));
}
}
onFilterTextChange($event) {
this.onFilterChange.emit($event);
}
onItemClick($event, item) {
if (this.disabled || item.isDisabled) {
return false;
}
const found = this.isSelected(item);
const allowAdd = this._settings.limitSelection === -1 ||
(this._settings.limitSelection > 0 &&
this.selectedItems.length < this._settings.limitSelection);
if (!found) {
if (allowAdd) {
this.addSelected(item);
}
}
else {
this.removeSelected(item);
}
if (this._settings.singleSelection &&
this._settings.closeDropDownOnSelection) {
this.closeDropdown();
}
}
writeValue(value) {
if (value !== undefined && value !== null && value.length > 0) {
if (this._settings.singleSelection) {
try {
if (value.length >= 1) {
const firstItem = value[0];
this.selectedItems = [
typeof firstItem === "string" || typeof firstItem === "number"
? new ListItem(firstItem)
: new ListItem({
id: firstItem[this._settings.idField],
text: firstItem[this._settings.textField],
isDisabled: firstItem[this._settings.disabledField],
property: firstItem[this._settings.property],
displayOrder: firstItem[this._settings.displayOrder],
}),
];
this.selectedItems.sort((a, b) => a.displayOrder > b.displayOrder ? 1 : -1);
}
}
catch (e) {
// console.error(e.body.msg);
}
}
else {
const _data = value.map((item) => typeof item === "string" || typeof item === "number"
? new ListItem(item)
: new ListItem({
id: item[this._settings.idField],
text: item[this._settings.textField],
isDisabled: item[this._settings.disabledField],
property: item[this._settings.property],
displayOrder: item[this._settings.displayOrder],
}));
if (this._settings.limitSelection > 0) {
this.selectedItems = _data.splice(0, this._settings.limitSelection);
this.selectedItems.sort((a, b) => a.displayOrder > b.displayOrder ? 1 : -1);
}
else {
this.selectedItems = _data;
this.selectedItems.sort((a, b) => a.displayOrder > b.displayOrder ? 1 : -1);
}
}
}
else {
this.selectedItems = [];
}
this.onChangeCallback(value);
}
// From ControlValueAccessor interface
registerOnChange(fn) {
this.onChangeCallback = fn;
}
// From ControlValueAccessor interface
registerOnTouched(fn) {
this.onTouchedCallback = fn;
}
// Set touched on blur
onTouched() {
this.closeDropdown();
this.onTouchedCallback();
}
trackByFn(index, item) {
return item.id;
}
isSelected(clickedItem) {
let found = false;
this.selectedItems.forEach((item) => {
if (clickedItem.id === item.id) {
found = true;
}
});
let obj = this.selectedItems.find(o => o.displayOrder > 0);
if (obj) {
this.selectedItems.sort((a, b) => a.displayOrder > b.displayOrder ? 1 : -1);
}
return found;
}
isLimitSelectionReached() {
return this._settings.limitSelection === this.selectedItems.length;
}
isAllItemsSelected() {
// get disabld item count
let filteredItems = this.listFilterPipe.transform(this._data, this.filter);
const itemDisabledCount = filteredItems.filter((item) => item.isDisabled)
.length;
// take disabled items into consideration when checking
if ((!this.data || this.data.length === 0) &&
this._settings.allowRemoteDataSearch) {
return false;
}
if (this._settings.includeDisabledCount) {
return filteredItems.length === this.selectedItems.length;
}
return (filteredItems.length === this.selectedItems.length + itemDisabledCount);
}
showButton() {
if (!this._settings.singleSelection) {
if (this._settings.limitSelection > 0) {
return false;
}
// this._settings.enableCheckAll = this._settings.limitSelection === -1 ? true : false;
return true; // !this._settings.singleSelection && this._settings.enableCheckAll && this._data.length > 0;
}
else {
// should be disabled in single selection mode
return false;
}
}
itemShowRemaining() {
return this.selectedItems.length - this._settings.itemsShowLimit;
}
addSelected(item) {
if (this._settings.singleSelection) {
this.selectedItems = [];
this.selectedItems.push(item);
this.selectedItems.sort((a, b) => a.displayOrder > b.displayOrder ? 1 : -1);
}
else {
this.selectedItems.push(item);
this.selectedItems.sort((a, b) => a.displayOrder > b.displayOrder ? 1 : -1);
}
this.onChangeCallback(this.emittedValue(this.selectedItems));
this.onSelect.emit(this.emittedValue(item));
}
removeSelected(itemSel) {
this.selectedItems.forEach((item) => {
if (itemSel.id === item.id) {
this.selectedItems.splice(this.selectedItems.indexOf(item), 1);
}
});
this.selectedItems.sort((a, b) => a.displayOrder > b.displayOrder ? 1 : -1);
this.onChangeCallback(this.emittedValue(this.selectedItems));
this.onDeSelect.emit(this.emittedValue(itemSel));
}
emittedValue(val) {
const selected = [];
if (Array.isArray(val)) {
val.map((item) => {
selected.push(this.objectify(item));
});
}
else {
if (val) {
return this.objectify(val);
}
}
return selected;
}
objectify(val) {
if (this._sourceDataType === "object") {
const obj = {};
obj[this._settings.idField] = val.id;
obj[this._settings.textField] = val.text;
// obj[this._settings.displayOrder] = val.displayOrder;
if (this._sourceDataFields.includes(this._settings.disabledField)) {
obj[this._settings.disabledField] = val.isDisabled;
}
if (this._sourceDataFields.includes(this._settings.property)) {
obj[this._settings.property] = val.property;
}
if (this._sourceDataFields.includes(this._settings.displayOrder)) {
obj[this._settings.displayOrder] = val.displayOrder;
}
return obj;
}
if (this._sourceDataType === "number") {
return Number(val.id);
}
else {
return val.text;
}
}
toggleDropdown(evt) {
evt.preventDefault();
if (this.disabled && this._settings.singleSelection) {
return;
}
this._settings.defaultOpen = !this._settings.defaultOpen;
if (!this._settings.defaultOpen) {
this.onDropDownClose.emit();
}
}
closeDropdown() {
this._settings.defaultOpen = false;
// clear search text
if (this._settings.clearSearchFilter) {
this.filter.text = "";
}
this.onDropDownClose.emit();
}
toggleSelectAll() {
if (this.disabled) {
return false;
}
if (!this.isAllItemsSelected()) {
// filter out disabled item first before slicing
if (!this._settings.includeDisabledCount) {
this.selectedItems = this.listFilterPipe
.transform(this._data, this.filter)
.filter((item) => !item.isDisabled)
.slice();
}
else {
this.selectedItems = this.listFilterPipe.transform(this._data, this.filter);
}
this.selectedItems.sort((a, b) => a.displayOrder > b.displayOrder ? 1 : -1);
this.onSelectAll.emit(this.emittedValue(this.selectedItems));
}
else {
this.selectedItems = [];
this.onDeSelectAll.emit(this.emittedValue(this.selectedItems));
}
this.onChangeCallback(this.emittedValue(this.selectedItems));
}
getFields(inputData) {
const fields = [];
if (typeof inputData !== "object") {
return fields;
}
// tslint:disable-next-line:forin
for (const prop in inputData) {
fields.push(prop);
}
return fields;
}
};
MultiSelectComponent.ctorParameters = () => [
{ type: ChangeDetectorRef },
{ type: ListFilterPipe }
];
__decorate([
Input()
], MultiSelectComponent.prototype, "placeholder", null);
__decorate([
Input()
], MultiSelectComponent.prototype, "disabled", void 0);
__decorate([
Input()
], MultiSelectComponent.prototype, "settings", null);
__decorate([
Input()
], MultiSelectComponent.prototype, "data", null);
__decorate([
Output("onFilterChange")
], MultiSelectComponent.prototype, "onFilterChange", void 0);
__decorate([
Output("onDropDownClose")
], MultiSelectComponent.prototype, "onDropDownClose", void 0);
__decorate([
Output("onSelect")
], MultiSelectComponent.prototype, "onSelect", void 0);
__decorate([
Output("onDeSelect")
], MultiSelectComponent.prototype, "onDeSelect", void 0);
__decorate([
Output("onSelectAll")
], MultiSelectComponent.prototype, "onSelectAll", void 0);
__decorate([
Output("onDeSelectAll")
], MultiSelectComponent.prototype, "onDeSelectAll", void 0);
__decorate([
HostListener("blur")
], MultiSelectComponent.prototype, "onTouched", null);
MultiSelectComponent = __decorate([
Component({
selector: "ng-multiselect-dropdown",
template: "<div tabindex=\"=0\" (blur)=\"onTouched()\" class=\"multiselect-dropdown\" (clickOutside)=\"closeDropdown()\">\r\n <div [class.disabled]=\"disabled\">\r\n <span tabindex=\"-1\" class=\"dropdown-btn\" (click)=\"toggleDropdown($event)\">\r\n <span class=\"placeholder\" *ngIf=\"selectedItems.length == 0\">{{_placeholder}}</span>\r\n <span *ngFor=\"let item of selectedItems;trackBy: trackByFn;let k = index\" [hidden]=\"k > _settings.itemsShowLimit-1\" [ngClass]=\"{'newly-added-item' : item.property === 'new-item', 'selected-item' :(item.property !== 'new-item' && item.property !== 'mandatory'), 'mandatory-item' : item.property === 'mandatory', 'disabled-item': item.isDisabled}\" >\r\n {{item.text}}\r\n <a style=\"padding-top:2px;padding-left:2px;color:white\" (click)=\"onItemClick($event,item)\">x</a>\r\n </span>\r\n <span style=\"padding-right: 6px; display: inline-block; margin-top: 5px; max-width: 50%;\" *ngIf=\"itemShowRemaining()>0\">+{{itemShowRemaining()}}</span>\r\n <span class=\"icon\">\r\n <span [ngClass]=\"_settings.defaultOpen ? 'dropdown-up' : 'dropdown-down'\"></span>\r\n </span>\r\n </span>\r\n </div>\r\n <div class=\"dropdown-list\" [hidden]=\"!_settings.defaultOpen\" [class.dropdown-placement]=\"_settings.placement === 'top'\">\r\n <ul class=\"item1\">\r\n <li (click)=\"toggleSelectAll()\" *ngIf=\"(_data.length > 0 || _settings.allowRemoteDataSearch) && !_settings.singleSelection && _settings.enableCheckAll && _settings.limitSelection===-1\" class=\"multiselect-item-checkbox\" style=\"border-bottom: 1px solid #ccc;padding:10px\">\r\n <input type=\"checkbox\" aria-label=\"multiselect-select-all\" [checked]=\"isAllItemsSelected()\" [disabled]=\"disabled || isLimitSelectionReached()\" />\r\n <div>{{!isAllItemsSelected() ? _settings.selectAllText : _settings.unSelectAllText}}</div>\r\n </li>\r\n <li class=\"filter-textbox\" *ngIf=\"(_data.length>0 || _settings.allowRemoteDataSearch) && _settings.allowSearchFilter\">\r\n <input type=\"text\" aria-label=\"multiselect-search\" [readOnly]=\"disabled\" [placeholder]=\"_settings.searchPlaceholderText\" [(ngModel)]=\"filter.text\" (ngModelChange)=\"onFilterTextChange($event)\">\r\n </li>\r\n </ul>\r\n <ul class=\"item2\" [style.maxHeight]=\"_settings.maxHeight+'px'\">\r\n <li *ngFor=\"let item of _data | multiSelectFilter:filter; let i = index;\" (click)=\"onItemClick($event,item)\" class=\"multiselect-item-checkbox\">\r\n <input type=\"checkbox\" aria-label=\"multiselect-item\" [checked]=\"isSelected(item)\" [disabled]=\"disabled || (isLimitSelectionReached() && !isSelected(item)) || item.isDisabled\" />\r\n <div>{{item.text}}</div>\r\n </li>\r\n <li class='no-data' *ngIf=\"_data.length == 0 && !_settings.allowRemoteDataSearch\">\r\n <h5>{{_settings.noDataAvailablePlaceholderText}}</h5>\r\n </li>\r\n </ul>\r\n </div>\r\n</div>\r\n",
providers: [DROPDOWN_CONTROL_VALUE_ACCESSOR],
changeDetection: ChangeDetectionStrategy.OnPush,
styles: [".multiselect-dropdown{position:relative;width:100%;font-size:inherit;font-family:inherit}.multiselect-dropdown .dropdown-btn{display:inline-block;border:1px solid #adadad;width:100%;padding:6px 12px;margin-bottom:0;font-weight:400;line-height:1.52857143;vertical-align:middle;cursor:pointer;background-image:none;border-radius:4px}.multiselect-dropdown .dropdown-btn .selected-item{border:1px solid #55198b;margin-right:4px;background:#55198b;padding:0 5px;color:#fff;border-radius:2px;float:left}.multiselect-dropdown .dropdown-btn .selected-item a{text-decoration:none}.multiselect-dropdown .dropdown-btn .newly-added-item{border:1px solid #55198b;margin-right:4px;background:#000;padding:0 5px;color:#fff;border-radius:2px;float:left}.multiselect-dropdown .dropdown-btn .newly-added-item a{text-decoration:none}.multiselect-dropdown .dropdown-btn .mandatory-item{border:1px solid #55198b;margin-right:4px;background:#000;padding:0 5px;color:#fff;border-radius:2px;float:left}.multiselect-dropdown .dropdown-btn .mandatory-item a{text-decoration:none}.multiselect-dropdown .dropdown-btn .mandatory-item:hover,.multiselect-dropdown .dropdown-btn .new-subject-item:hover,.multiselect-dropdown .dropdown-btn .selected-item:hover{box-shadow:1px 1px #959595}.multiselect-dropdown .dropdown-btn .dropdown-down{display:inline-block;top:10px;width:0;height:0;border-top:10px solid #adadad;border-left:10px solid transparent;border-right:10px solid transparent}.multiselect-dropdown .dropdown-btn .dropdown-up{display:inline-block;width:0;height:0;border-bottom:10px solid #adadad;border-left:10px solid transparent;border-right:10px solid transparent}.multiselect-dropdown .disabled>span{background-color:#eceeef}.dropdown-list{position:absolute;padding-top:6px;width:100%;z-index:9999;border:1px solid #ccc;border-radius:3px;background:#fff;margin-top:10px;box-shadow:0 1px 5px #959595}.dropdown-list ul{padding:0;list-style:none;overflow:auto;margin:0}.dropdown-list li{padding:6px 10px;cursor:pointer;text-align:left}.dropdown-list .filter-textbox{border-bottom:1px solid #ccc;position:relative;padding:10px}.dropdown-list .filter-textbox input{border:0;width:100%;padding:0 0 0 26px}.dropdown-list .filter-textbox input:focus{outline:0}.multiselect-item-checkbox input[type=checkbox]{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.multiselect-item-checkbox input[type=checkbox]:focus+div:before,.multiselect-item-checkbox input[type=checkbox]:hover+div:before{border-color:#55198b;background-color:#f2f2f2}.multiselect-item-checkbox input[type=checkbox]:active+div:before{transition-duration:0s}.multiselect-item-checkbox input[type=checkbox]+div{position:relative;padding-left:2em;vertical-align:middle;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;cursor:pointer;margin:0;color:#000}.multiselect-item-checkbox input[type=checkbox]+div:before{box-sizing:content-box;content:\"\";color:#55198b;position:absolute;top:50%;left:0;width:14px;height:14px;margin-top:-9px;border:1px solid #a1aab2;text-align:center;transition:.4s;border-radius:4px}.multiselect-item-checkbox input[type=checkbox]+div:after{box-sizing:content-box;content:\"\";position:absolute;transform:scale(0);transform-origin:50%;transition:transform .2s ease-out;background-color:transparent;top:50%;left:4px;width:6px;height:2px;margin-top:-4px;border-style:solid;border-color:#fff;border-width:0 0 2px 2px;-o-border-image:none;border-image:none;transform:rotate(-45deg) scale(0)}.multiselect-item-checkbox input[type=checkbox]:disabled:focus+div:before .multiselect-item-checkbox input[type=checkbox]:disabled:hover+div:before{background-color:inherit}.multiselect-item-checkbox input[type=checkbox]:disabled+div:before,.multiselect-item-checkbox input[type=checkbox]:disabled:checked+div:before{background-color:#ccc}.multiselect-item-checkbox input[type=checkbox]:checked+div:after{content:\"\";transition:transform .2s ease-out;transform:rotate(-45deg) scale(1)}.multiselect-item-checkbox input[type=checkbox]:checked+div:before{-webkit-animation:.2s ease-in borderscale;animation:.2s ease-in borderscale;background:#55198b}@-webkit-keyframes borderscale{50%{box-shadow:0 0 0 2px #55198b}}@keyframes borderscale{50%{box-shadow:0 0 0 2px #55198b}}.dropdown-placement{bottom:100%}"]
})
], MultiSelectComponent);
let ClickOutsideDirective = class ClickOutsideDirective {
constructor(_elementRef) {
this._elementRef = _elementRef;
this.clickOutside = new EventEmitter();
}
onClick(event, targetElement) {
if (!targetElement) {
return;
}
const clickedInside = this._elementRef.nativeElement.contains(targetElement);
if (!clickedInside) {
this.clickOutside.emit(event);
}
}
};
ClickOutsideDirective.ctorParameters = () => [
{ type: ElementRef }
];
__decorate([
Output()
], ClickOutsideDirective.prototype, "clickOutside", void 0);
__decorate([
HostListener('document:click', ['$event', '$event.target'])
], ClickOutsideDirective.prototype, "onClick", null);
ClickOutsideDirective = __decorate([
Directive({
selector: '[clickOutside]'
})
], ClickOutsideDirective);
var NgMultiSelectDropDownModule_1;
let NgMultiSelectDropDownModule = NgMultiSelectDropDownModule_1 = class NgMultiSelectDropDownModule {
static forRoot() {
return {
ngModule: NgMultiSelectDropDownModule_1
};
}
};
NgMultiSelectDropDownModule = NgMultiSelectDropDownModule_1 = __decorate([
NgModule({
imports: [CommonModule, FormsModule],
declarations: [MultiSelectComponent, ClickOutsideDirective, ListFilterPipe],
providers: [ListFilterPipe],
exports: [MultiSelectComponent]
})
], NgMultiSelectDropDownModule);
/**
* Generated bundle index. Do not edit.
*/
export { MultiSelectComponent, NgMultiSelectDropDownModule, DROPDOWN_CONTROL_VALUE_ACCESSOR as ɵa, ListFilterPipe as ɵb, ClickOutsideDirective as ɵc };
//# sourceMappingURL=experionathira-ng-multiselect-dropdown.js.map