import {Component, OnInit, OnChanges, Input, Output, EventEmitter, HostListener, ElementRef, SimpleChanges} from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { every, forEach, find, sortBy, isEqual } from 'lodash';
import { BsModalService } from 'ngx-bootstrap/modal';
import { MultiSelectValue, SingleSelectValue } from '../../../utils';
import { ENTER_KEY } from '../../../utils/global.const';
import { Utils } from '../../../utils/utils';
import { MultipleSearchComponent } from './multiple-search-modal/multiple-search-modal.component';
/*
* Usage: Set the container width, to fix the dropdown width.
*
*
*
*/
@Component({
selector: 'esp-multiselect-dropdown',
templateUrl: './multiselect-dropdown.component.html',
styleUrls: ['./multiselect-dropdown.component.scss'],
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: MultiselectDropdownComponent,
multi: true
}]
})
export class MultiselectDropdownComponent implements OnInit, OnChanges, ControlValueAccessor {
@Input() multiSelectOptions: MultiSelectValue[];
@Input() singleSelectOptions: SingleSelectValue[];
@Input() selectAllLabel: string;
@Input() showSelectAll = false;
@Input() selectNoneLabel: string;
@Input() dropdownName: string;
@Input() dropdownType: string;
@Input() disabled = false;
@Input() dropdownPosition: string;
@Input() iconClass = 'fa-ellipsis-v';
@Input() showBorder = true;
@Input() textSize = '';
@Input() placeholder: string;
@Input() multiSelectClass = 'dropdown-label';
@Input() labelClass: string;
@Input() selectionLimit: any;
@Input() isChartEllipsis = false;
@Input() isInfiniteScroll = false;
@Input() showSearch = false;
@Input() searchText = '';
@Input() i18n=null;
@Input() showClearSelection: boolean; // need to be true to show clear selection
@Input() version = 'v1';
@Input() dropdownDisabled: boolean;
@Input() forcePlaceHolder: boolean;
@Input() minimumOneSelected=false;
@Input() hideDropdownIcon=false;
@Input() showScroll = false;
@Input() dropUp = false;
@Output() selectChange: EventEmitter = new EventEmitter();
@Output() clearSingleSelection: EventEmitter = new EventEmitter();
@Output() dropdownScrolled: EventEmitter = new EventEmitter();
@Output() dropdownSearch: EventEmitter = new EventEmitter();
@Output() selectAllChange: EventEmitter = new EventEmitter();
@Output() multiSelectChange: EventEmitter = new EventEmitter();
fromMobile: boolean;
updatedFilter = null;
toggleSelectAll = true;
showOptions = false;
dropdownLabel = '';
singleSelectedOptionIndex = -1;
disableSelection = false;
lockSingleOption = false;
multiSearchItems = [];
showModal = false;
onChange: (value: string | string[]) => void;
@Input() forceSelectAll? = true;
constructor(private eRef: ElementRef, private modalService: BsModalService) {}
// Below function reads the value from the form, and updates the respective change in our custom dropdown.
writeValue(obj: any): void {
if(this.dropdownType.toLowerCase().indexOf('singleselect') > -1) {
this.singleSelectOptions.forEach((option) => {
option.selected = false;
if(obj === option.value) {
option.selected = true
}
});
} else {
this.multiSelectOptions.forEach((option) => {
option.selected = false
if(obj?.indexOf(option.value) > -1) {
option.selected = true
}
});
}
this.dropdownLabel = this.getDropdownLabel();
}
/* Below function is called as soon as there is change in the dropdown, we can update onChange function to detect the change and set the form value.
For singleselect and multiselect we can have different onChange functions to reduce it to different form values. */
registerOnChange(fn: any): void {
this.onChange = fn;
}
registerOnTouched(fn: any): void {
}
setDisabledState?(isDisabled: boolean): void {
}
@HostListener('document:click', ['$event'])
onMenuDeselect(event) {
if (!this.eRef.nativeElement.contains(event.target) && this.showOptions || this.showModal) {
this.showOptions = this.showModal ? this.showModal : false;
/* Disabling the icon multiselect for side filter panel until the tab config is fixed */
// if ((this.dropdownType === 'multiSelect' || this.dropdownType === 'iconMultiSelect') && this.updatedFilter) {
this.handleMultiSelectDropdownEvent();
}
}
ngOnInit(): void {
this.version = this.version ? this.version : 'v1';
if (!this.dropdownDisabled) {
this.dropdownLabel = this.getDropdownLabel();
this.fromMobile = this.dropdownName === 'lateAdjustMobile';
} else {
this.dropdownLabel = this.placeholder;
}
if (this.selectionLimit) {
this.disableSelection = true;
this.getSelectedOption();
}
}
ngOnChanges(): void {
if (!this.dropdownDisabled && !this.showSearch && !this.forcePlaceHolder) {
this.dropdownLabel = this.getDropdownLabel();
}
if(this.forcePlaceHolder) {
this.dropdownLabel = this.placeholder;
this.toggleSelectAll = this.forceSelectAll;
} else if(this.dropdownType === 'multiSelect' && this.showSearch) {
//If search has changed and type is multiselect, update the updated filter to selected all options.
this.toggleSelectAll = every(this.multiSelectOptions, ['selected', true]);
this.dropdownLabel = this.getDropdownLabel();
if(this.searchText === '') {
this.updatedFilter?.multiSelectOptions?.map(option => option.selected = true);
}
}
this.singleSelectedOptionIndex = -1;
}
ngAfterContentChecked() {
if (this.singleSelectOptions?.length > 0) {
this.singleSelectOptions.forEach((item, index) => {
if (item.selected) {
this.singleSelectedOptionIndex = index;
this.dropdownLabel = this.getDropdownLabel(index);
}
});
}
}
changeMultiSelect($event, index) {
this.multiSelectOptions[index].selected = !this.multiSelectOptions[index].selected;
this.lockSingleOption = false;
// below condition is added to restrict the user to have atleast one selection
if(this.minimumOneSelected && this.multiSelectOptions.filter(option => option.selected).length === 0) {
this.multiSelectOptions[index].selected = !this.multiSelectOptions[index].selected;
this.lockSingleOption = true;
return;
}
this.updatedFilter = { name: this.dropdownName, multiSelectOptions: this.multiSelectOptions };
this.dropdownLabel = this.getDropdownLabel();
if(this.onChange) {
this.onChange(this.updatedFilter?.multiSelectOptions?.reduce(function(filtered, option) {
if (option.selected) {
filtered.push(option.value);
}
return filtered;
}, [])
)
}
this.multiSelectChange.emit(this.updatedFilter);
if (this.selectionLimit) {
this.getSelectedOption();
}
}
changeSingleSelect($event, index): void {
//whenever we have search enabled, don't disable selection after matching index
if (this.singleSelectedOptionIndex === index && !this.showSearch) {
this.showOptions = false;
return;
}
this.singleSelectedOptionIndex = index;
const valueToBeUnselected = find(this.singleSelectOptions, option => option.selected);
if (valueToBeUnselected) {
valueToBeUnselected.selected = false;
}
this.singleSelectOptions[index].selected = !this.singleSelectOptions[index].selected;
const updatedFilter = {
name: this.dropdownName ? this.dropdownName : 'selectedOption',
singleSelectOptions: this.singleSelectOptions[index],
index,
};
this.dropdownLabel = this.getDropdownLabel(index);
this.showOptions = false;
if(this.onChange) {
this.onChange(updatedFilter.singleSelectOptions.value);
}
// Emit the change after setting the current form value
this.selectChange.emit(updatedFilter);
}
handleMultiSelectDropdownEvent() {
if (this.dropdownType === 'multiSelect' || this.dropdownType === 'iconMultiSelect' && this.updatedFilter) {
if(this.updatedFilter && this.onChange) {
this.onChange(this.updatedFilter?.multiSelectOptions?.reduce(function(filtered, option) {
if (option.selected) {
filtered.push(option.value);
}
return filtered;
}, [])
)
}
// Emit the change after setting the current form value
this.selectChange.emit(this.updatedFilter);
}
}
toggleDropdown(): void {
if (!this.dropdownDisabled) {
if (!this.disabled) {
this.showOptions && this.handleMultiSelectDropdownEvent();
this.showOptions = !this.showOptions;
}
}
}
toggleAll(event): void {
this.toggleSelectAll = !this.toggleSelectAll;
this.multiSelectOptions.forEach(option => (option.selected = this.toggleSelectAll));
this.selectAllChange.emit(this.toggleSelectAll);
this.updatedFilter = { name: this.dropdownName, multiSelectOptions: this.multiSelectOptions };
if (!this.toggleSelectAll) {
this.dropdownLabel = 'None';
} else {
if(this.multiSelectOptions.length === 1) {
this.dropdownLabel = this.multiSelectOptions[0].label || this.multiSelectOptions[0].value || 'All Selected';
}
else {
this.dropdownLabel = this.selectAllLabel;
}
}
}
getDropdownLabel(index?: number): string {
if (this.singleSelectOptions?.length > 0 || this.multiSelectOptions?.length > 0) {
switch (this.dropdownType) {
case 'singleSelect': {
return this.handleSingleSelection(index);
}
case 'multiSelect' || 'iconMultiSelect': {
return this.handleMultiSelection(index);
}
case 'iconTextSingleSelect': {
return this.handleIconTextSingleSelection(index);
}
default:
return 'Choose';
}
}
else {
return this.i18n && this.i18n['none'];
}
}
handleSingleSelection(index?: number): string {
if (index !== undefined) {
forEach(this.singleSelectOptions, option => {
option.selected = false;
});
this.singleSelectOptions[index].selected = true;
return (
this.singleSelectOptions[index].label ||
this.singleSelectOptions[index].value ||
this.singleSelectOptions[index].name
);
} else if (every(this.singleSelectOptions, ['selected', false])) {
if (this.placeholder !== undefined) {
return this.placeholder;
} else {
this.singleSelectOptions[0].selected = true;
return (
this.singleSelectOptions[0].label || this.singleSelectOptions[0].value || this.singleSelectOptions[0].name
);
}
} else {
if (this.dropdownName === 'lateAdjustMobile') {
return this.singleSelectOptions[0].name;
} else if (this.dropdownName === 'leftHierarchy') {
const selIndex = this.singleSelectOptions.findIndex(item => item.selected);
return selIndex === -1 ? this.singleSelectOptions[0].value : this.singleSelectOptions[selIndex].value;
} else {
const selectedOption = find(this.singleSelectOptions, option => option.selected);
return selectedOption.label || selectedOption.value;
}
}
}
handleMultiSelection(index?: number): string {
if (this.multiSelectOptions.length && every(this.multiSelectOptions, ['selected', true])) {
this.toggleSelectAll = true;
if(this.multiSelectOptions.length === 1) {
return this.multiSelectOptions[0].label || this.multiSelectOptions[0].value || "All Selected";
}
else {
return this.selectAllLabel ? this.selectAllLabel : 'All selected';
}
} else if (every(this.multiSelectOptions, ['selected', false])) {
this.toggleSelectAll = false;
if (this.dropdownName === 'Routes') {
return this.placeholder;
}
return this.selectNoneLabel ? this.selectNoneLabel : 'None';
} else {
let dropdownLabel = '';
forEach(this.multiSelectOptions, option => {
if (option.selected) {
dropdownLabel = dropdownLabel ? dropdownLabel + ', ' : dropdownLabel;
dropdownLabel = dropdownLabel + (option.label ? option.label : option.value);
} else {
this.toggleSelectAll = false;
}
});
return dropdownLabel;
}
}
handleIconTextSingleSelection(index?: number): string {
if (index !== undefined) {
forEach(this.singleSelectOptions, option => {
option.selected = false;
});
this.singleSelectOptions[index].selected = true;
return (
this.singleSelectOptions[index].icon
);
} else if (every(this.singleSelectOptions, ['selected', false])) {
if (this.placeholder !== undefined) {
return this.placeholder;
} else {
this.singleSelectOptions[0].selected = true;
return (
this.singleSelectOptions[0].icon
);
}
} else {
const selectedOption = find(this.singleSelectOptions, option => option.selected);
return selectedOption.icon;
}
}
isSelected(): boolean {
let result = false;
for(let i = 0; i < this.singleSelectOptions.length; i++) {
if (this.singleSelectOptions[i].selected) {
result = true;
break;
}
}
return result;
}
clearSingleSelectionOption(dropdownName): void {
this.singleSelectOptions.map(option => {
option.selected = false;
})
this.dropdownLabel = this.placeholder;
this.singleSelectedOptionIndex = -1;
if(this.onChange) {
this.onChange('');
}
// Emit the change after setting the current form value
this.clearSingleSelection.emit(dropdownName);
}
getSelectedOption() {
if (this.multiSelectOptions?.length > 0) {
const selectedLength = this.multiSelectOptions.filter(option => option.selected).length;
this.disableSelection = selectedLength >= this.selectionLimit;
}
}
onScrollDown() {
this.dropdownScrolled.emit({
name: this.dropdownName,
cursor: this.multiSelectOptions ? this.multiSelectOptions.length : this.singleSelectOptions.length,
});
}
onSearchTextTypingDone(value) {
this.dropdownSearch.emit({ name: this.dropdownName, text: value });
}
clearSearch(event) {
if(this.multiSearchItems?.length || this.searchText) {
this.multiSearchItems = [];
this.searchText = '';
Utils.debounce(this.onSearchTextTypingDone.bind(this), 500, [this.searchText]);
}
}
onSearchChange(event) {
const value = (event.target as HTMLInputElement).value?.trim();
if (this.version === 'v2') {
if (this.multiSearchItems?.length && this.multiSearchItems[0] === value) {
return;
}
this.multiSearchItems = value ? [value] : [];
}
Utils.debounce(this.onSearchTextTypingDone.bind(this), 500, [value.trim()]);
}
openMultipleSearchModal(event) {
this.showModal = true;
const config = {
class: 'multiple-search-modal-container',
initialState: {
config: {
i18n: this.i18n,
searchedItems: this.searchText?.length ? this.multiSearchItems || [] : [],
searchCallback: event => {
this.searchCallback(event);
},
cancelCallback: () => {
this.hideMultipleSearchModal()
}
},
},
};
this.modalService.show(MultipleSearchComponent, Object.assign({ ignoreBackdropClick: true }, config));
}
searchCallback(event) {
this.hideMultipleSearchModal();
if (!isEqual(sortBy(event), sortBy(this.multiSearchItems))) {
this.multiSearchItems = event;
this.dropdownSearch.emit({
name: this.dropdownName,
text: this.multiSearchItems.length ? this.multiSearchItems.length === 1 ? this.multiSearchItems[0] : this.multiSearchItems : ''
});
}
}
hideMultipleSearchModal() {
setTimeout(() => {
this.showModal = false;
}, 50);
}
}