import {Component, ElementRef, AfterViewChecked, Input, Output, EventEmitter, ChangeDetectorRef} from '@angular/core'; import {NG_VALUE_ACCESSOR, ControlValueAccessor} from '@angular/forms'; import {AbstractComponent} from 'gp-admin-abstract'; @Component({ selector: 'gp-multiselect', template: `
{{title}}
{{error}}
{{selected[0].title}} + {{selected.length - 1}}
{{item.title}}
`, styles: [ `@charset "UTF-8"; /** * Переменные */ :root { --color-white: white; --color-success: #00afec; --color-warning: #ffb827; --color-error: #f35252; --color-disabled: #dbdbdb; --color-black: black; --color-span: #b5b5b5; --color-span-second: #212121; --color-bg: #f8f8f8; --color-bg-second: #f8f8f8; --color-bg-success: rgba(0, 175, 236, 0.1); --color-bg-warning: var(--color-warning); --color-bg-error: var(--color-error); --color-placeholder: #b5b5b5; --color-border: #dbdbdb; --color-radio-border: #d3d3d3; --font-roboto: 'Roboto', sans-serif; --font-helvetica: 'Helvetica', sans-serif; } /** * */ /** * Стили для компонента "gp-multiselect" */ .gp-multiselect { min-width: 18rem; } .gp-multiselect--open .gp-multiselect__arrow-icon { -moz-transform: scale(-1); -o-transform: scale(-1); -ms-transform: scale(-1); -webkit-transform: scale(-1); transform: scale(-1); } .gp-multiselect--open .gp-multiselect__dropdown { max-height: 12.5rem; padding: 0 0.5rem 0.5rem 0.5rem; visibility: visible; opacity: 1; -moz-transition-delay: 0ms, 0ms; -o-transition-delay: 0ms, 0ms; -webkit-transition-delay: 0ms, 0ms; transition-delay: 0ms, 0ms; -moz-transition-property: max-height, padding; -o-transition-property: max-height, padding; -webkit-transition-property: max-height, padding; transition-property: max-height, padding; } .gp-multiselect--success .gp-multiselect__area { border-color: #00afec; } .gp-multiselect--success .gp-multiselect__title { color: #00afec; } .gp-multiselect--success .gp-multiselect__arrow-icon { border-top-color: #00afec; } .gp-multiselect--success .gp-multiselect__dropdown { border-right-color: #00afec; border-bottom-color: #00afec; border-left-color: #00afec; } .gp-multiselect--error .gp-multiselect__area { border-color: #f35252; } .gp-multiselect--error .gp-multiselect__title { color: #f35252; } .gp-multiselect--error .gp-multiselect__error { display: block; } .gp-multiselect--error .gp-multiselect__arrow-icon { border-top-color: #f35252; } .gp-multiselect--error .gp-multiselect__dropdown { border-right-color: #f35252; border-bottom-color: #f35252; border-left-color: #f35252; } .gp-multiselect--disabled .gp-multiselect__area { cursor: not-allowed; color: #b5b5b5; border-color: #dbdbdb; background-color: #f8f8f8; } .gp-multiselect--disabled .gp-multiselect__arrow-icon { border-top-color: #dbdbdb; } .gp-multiselect--disabled .gp-multiselect__dropdown { border-right-color: #dbdbdb; border-bottom-color: #dbdbdb; border-left-color: #dbdbdb; background-color: #f8f8f8; cursor: not-allowed; } .gp-multiselect--disabled .gp-multiselect__dropdown-item { color: #b5b5b5; background-color: #f8f8f8; cursor: not-allowed; } .gp-multiselect__title { font: 400 1.2rem/1.4rem "Roboto", sans-serif; margin-bottom: 0.5rem; color: #b5b5b5; -webkit-touch-callout: none; -webkit-user-select: none; -khtml-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } .gp-multiselect__error { font: 400 1.2rem/1.4rem "Roboto", sans-serif; display: none; color: #f35252; } .gp-multiselect__area { font: 1.3rem/1.5rem "Roboto", sans-serif; width: 100%; min-height: 3.5rem; padding: 0 2.5rem 0 0.5rem; color: #212121; border: 0.1rem solid #dbdbdb; border-radius: 0.2rem; outline: none; background-color: var(--color-white); cursor: pointer; } .gp-multiselect ::-webkit-multiselect-placeholder, .gp-multiselect :-ms-multiselect-placeholder, .gp-multiselect ::placeholder { color: #b5b5b5; -webkit-touch-callout: none; -webkit-user-select: none; -khtml-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } .gp-multiselect__arrow { top: 2rem; right: 0.1rem; height: 3.4rem; width: 2.6rem; border-radius: 0.1rem; cursor: pointer; } .gp-multiselect__arrow-icon { -moz-transition: transform 150ms ease; -o-transition: transform 150ms ease; -webkit-transition: transform 150ms ease; transition: transform 150ms ease; border-top: 0.4rem solid #c4c4c4; border-right: 0.3rem solid transparent; border-bottom: 0 solid transparent; border-left: 0.3rem solid transparent; } .gp-multiselect__dropdown { position: absolute; top: calc(100% - 0.5rem); right: 0; left: 0; overflow: scroll; max-height: 0; margin: 0; padding: 0; opacity: 0; visibility: hidden; list-style: none; -moz-transition-property: max-height, padding, opacity, visibility; -o-transition-property: max-height, padding, opacity, visibility; -webkit-transition-property: max-height, padding, opacity, visibility; transition-property: max-height, padding, opacity, visibility; -moz-transition-property: 300ms, 0ms, 0ms, 0ms; -o-transition-property: 300ms, 0ms, 0ms, 0ms; -webkit-transition-property: 300ms, 0ms, 0ms, 0ms; transition-property: 300ms, 0ms, 0ms, 0ms; -moz-transition-delay: 0ms, 300ms, 300ms, 300ms; -o-transition-delay: 0ms, 300ms, 300ms, 300ms; -webkit-transition-delay: 0ms, 300ms, 300ms, 300ms; transition-delay: 0ms, 300ms, 300ms, 300ms; -moz-transition-timing-function: ease-in-out; -o-transition-timing-function: ease-in-out; -webkit-transition-timing-function: ease-in-out; transition-timing-function: ease-in-out; border-right: 0.1rem solid #dbdbdb; border-bottom: 0.1rem solid #dbdbdb; border-left: 0.1rem solid #dbdbdb; border-radius: 0 0 0.3rem 0.3rem; background-color: var(--color-white); z-index: 5; } .gp-multiselect__dropdown-item { font: 1.3rem/1.5rem "Roboto", sans-serif; cursor: pointer; -moz-transition: all 150ms ease-in-out; -o-transition: all 150ms ease-in-out; -webkit-transition: all 150ms ease-in-out; transition: all 150ms ease-in-out; letter-spacing: 0.03rem; color: #212121; border-radius: 0.3rem; background-color: var(--color-white); -webkit-box-flex: 1 100%; -moz-box-flex: 1 100%; -webkit-flex: 1 100%; -ms-flex: 1 100%; flex: 1 100%; } .gp-multiselect__dropdown-item /deep/ .gp-checkbox__label { padding: 0.5rem 1rem; } .gp-multiselect__dropdown-item:hover { background-color: #f8f8f8; } .gp-multiselect__dropdown-item--disabled, .gp-multiselect__dropdown-item[disabled] { color: #b5b5b5; pointer-events: none; } .gp-multiselect__selected-block { font: 400 1.3rem/1.4rem "Roboto", sans-serif; position: relative; margin-right: 0.7rem; padding: 0 1.7rem 0 0.5rem; -webkit-touch-callout: none; -webkit-user-select: none; -khtml-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; letter-spacing: 0.03rem; } .gp-multiselect__selected-block-close { background-image: url("/assets/img/icon--close.svg"); background-position: center center; background-repeat: no-repeat; width: 1.7rem; height: 1.7rem; display: block; position: absolute; top: 0.1rem; right: 0.1rem; } `], providers: [ { provide: NG_VALUE_ACCESSOR, useExisting: MultiselectComponent, multi: true } ], host: { '(document:click)': 'onDocumentClick($event)', }, }) export class MultiselectComponent extends AbstractComponent implements ControlValueAccessor, AfterViewChecked { public hasSelected: boolean = false; public selected: any[] = []; public options: any[] = []; public opened: boolean = false; @Input() disabled: boolean = false; @Input() title: string; @Input() error: string; @Input() asObject: boolean; @Output() onScrollUp: EventEmitter = new EventEmitter(); @Output() onScrollDown: EventEmitter = new EventEmitter(); constructor(protected el: ElementRef, private changeDetector: ChangeDetectorRef) { super(el); } public onDocumentClick(e: Event) { if (!this.getElementRef().nativeElement.contains(e.target)) { this.opened = false; } } ngAfterViewChecked(): void { const opts = this.getElementRef().nativeElement.querySelectorAll('option'); if (opts.length > 0) { let needRefresh: boolean = false; for (const opt of opts) { const exists = this.options.find(f => f.value === opt.value); if (!exists) { this.addOption(opt.value, opt.text, opt.selected); needRefresh = true; } } if (needRefresh) { this.hasSelected = this.isHasSelected(); this.changeDetector.detectChanges(); console.log(this.options); } } } propagateChange = (_: any) => { } writeValue(_value: string|number|any): void { if (!Array.isArray(_value)) { _value = _value ? [_value] : []; } let i: number; for (i = 0; i < _value.length; i++) { const el = _value[i]; if (el && (typeof el === 'object')) { const keys = Object.keys(el); let j; for (j = 0; j < keys.length; j++) { const k = keys[j]; const founded = this.options.find(f => f.value === k); if (founded) { founded.isSelected = true; } else { this.addOption(k, el[k], true, true); } } } else { const founded = this.options.find(f => f.value === el); if (founded) { founded.isSelected = true; if (!this.isSelected(founded.value)) { this.addSelected(founded.value, founded.title, false); } } } } this.hasSelected = this.isHasSelected(); } registerOnChange(fn: any): void { this.propagateChange = fn; } registerOnTouched(fn: any): void { } setDisabledState(isDisabled: boolean): void { this.disabled = isDisabled; } toggleOpen(e: any): void { if (!this.disabled) { if (!this.opened || (this.opened && !e.target.closest('input') && !e.target.closest('a'))) { this.opened = !this.opened; } } } scrollDown(): void { this.onScrollDown.emit(); } scrollUp(): void { this.onScrollUp.emit(); } changeOption(item: any): void { const founded = this.options.find(f => f.value === item.value); if (founded) { founded.isSelected = !item.isSelected; if (founded.isSelected && !this.isSelected(founded.value)) { this.addSelected(founded.value, founded.title, false); } else if (!founded.isSelected && this.isSelected(founded.value)) { this.removeSelected(founded.value); } this.hasSelected = this.isHasSelected(); this.notifyModel(); } } // removeOption(item: any): void { // let index: number = this.options.findIndex(f => f.value == item.value); // if (index >= 0) { // this.options[index].isSelected = false; // } // this.removeSelected(item.value); // this.hasSelected = this.isHasSelected(); // this.notifyModel(); // } private addOption(value: string|number, title: string|number, isSelected: boolean, isPseudo: boolean = false) { const exists = this.options.find(f => f.value === value); isSelected = isSelected || this.isSelected(value); if (!exists) { this.options.push({value: value, title: title, isSelected: isSelected, isPseudo: isPseudo}); } else { exists.isSelected = isSelected; } if (isSelected && !this.isSelected(value)) { this.addSelected(value, title, isPseudo); } } private addSelected(value: string|number, title: string|number, isPseudo: boolean = false) { const exists = this.selected.find(f => f.value === value); if (!exists) { this.selected.push({value: value, title: title, isSelected: true, isPseudo: isPseudo}); } } private removeSelected(value: string|number) { const index: number = this.selected.findIndex(f => f.value === value); if (index >= 0) { this.selected.splice(index, 1); } } private isSelected(value: string|number): boolean { const exists = this.selected.find(f => f.value === value); return !!exists; } private isHasSelected(): boolean { return (Object.keys(this.selected).length > 0); } private notifyModel(): void { const result: any = {}; let i: number; for (i = 0; i < this.selected.length; i++) { const el = this.selected[i]; result[el.value] = el.title; } this.propagateChange(this.asObject ? result : Object.keys(result)); } showErrorText(): boolean { return ((typeof this.error === 'string') && !!this.error); } }