UNPKG

blumjs

Version:
242 lines (207 loc) 8.03 kB
import { Component, Input, Output, EventEmitter, ContentChildren, QueryList, AfterContentInit, HostListener, ElementRef } from "@angular/core"; import {OptionComponent} from "./option.component"; @Component({ selector: 'bl-select', template: ` <div class="dropdown" *ngIf="!hidden" #element> <button *ngIf="!multiple" (click)="toggle()" [ngClass]="rtl ? 'rtl-button' : 'ltr-button'" [disabled]="disabled"> <span *ngIf="selectedOptions[0]">{{selectedOptions[0].text}}</span> <i class="fa" [ngClass]="isOpen ? 'fa-chevron-up': 'fa-chevron-down'"></i> </button> <div *ngIf="multiple" class="multiple-selection" (click)="toggle()"> <span class="tag" *ngFor="let selectedOption of selectedOptions">{{selectedOption.text}}</span> <i class="fa" [ngClass]="isOpen ? 'fa-chevron-up': 'fa-chevron-down'"></i> </div> <div [hidden]="!isOpen" class="dropdown-content" [style.width]="element.clientWidth + 'px'" > <input *ngIf="searchable" [(ngModel)]="searchTerm" (keyup)="searchTermChanged()"> <div class="list"> <ng-content *ngIf="options"></ng-content> <span class="no-result" *ngIf="!options || noResult">{{noResultMsg}}</span> </div> </div> </div> `, styles: [` .rtl-button { text-align: right; } .rtl-button > i { float: left; } .ltr-button { text-align: left; } .ltr-button > i { float: right; } .dropdown { position: relative; display: inline-block; width: inherit; font-size: 0.9em; } .dropdown>button { border: 1px solid rgb(34, 116, 71); background-color: white; height: 33px; width: 100%; } .dropdown-content>input { display: inline-block; width: 95%; height: 23px; align-self: center; margin: 5px; } .dropdown>button:hover { cursor: pointer; } .dropdown>button[disabled]:hover { cursor: not-allowed; } .dropdown>button:focus, .dropdown-content>input { outline: none; } .dropdown-content { position: fixed; background-color: white; box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2); z-index: 8888; overflow: hidden; } .list { max-height: 150px; overflow-y: auto; } .no-result { display: block; } .tag { background-color: lightgrey; border-radius: 3px; padding: 3px; margin: 1px; } .multiple-selection { border: 1px solid rgb(34, 116, 71); cursor: pointer; width: 100%; padding: 5px; } `] }) export class SelectComponent implements AfterContentInit { @Input() model: any; @Input() searchable: boolean = true; @Input() searchTerm: string = ""; @Input() disabled: boolean = false; @Input() hidden: boolean = false; @Input() multiple: boolean = false; @Input() noResultMsg: string = "نتیجه‌ای یافت نشد."; @Output() modelChange: EventEmitter<any> = new EventEmitter<any>(); @Output() change: EventEmitter<any> = new EventEmitter<any>(); @Output() searchTermChange: EventEmitter<string> = new EventEmitter<string>(); private rtl: boolean = true; private isOpen: boolean = false; private selectedOptions: OptionComponent[] = []; private noResult: boolean = false; private tempOptions: {option: OptionComponent, text: string}[] = []; @ContentChildren(OptionComponent) options: QueryList<OptionComponent>; constructor(private elementRef: ElementRef){} ngAfterContentInit(): void { console.time("Select Init"); this.listenToOptionEvents(); this.options.forEach((option: OptionComponent) => { this.tempOptions.push({text: option.getText(), option: option}); option = Object.assign({text: option.getText()}, option); if (option.value == this.model) this.selectedOptions.push(option); }); console.timeEnd("Select Init"); } private modelChanged(): void { console.time("Model Changed"); this.model = this.selectedOptions[0].value; this.searchTerm = ''; this.searchTermChanged(); this.modelChange.emit(this.model); console.timeEnd("Model Changed"); } private searchTermChanged(): void { console.time("Searching"); if (this.searchTerm === '') { this.options.forEach((option) => { option.hidden = false; }); this.noResult = false; return; } this.noResult = true; this.tempOptions.forEach((tempOption) => { tempOption.option.hidden = tempOption.text.toLowerCase().indexOf(this.searchTerm.toLowerCase()) <= -1; if (!tempOption.option.hidden) this.noResult = false; }); this.searchTermChange.emit(this.searchTerm); console.timeEnd("Searching"); } private changed(): void { this.change.emit(this.model); } private toggle(): void { this.isOpen ? this.close() : this.open(); } private open(): void { console.time("Opening"); if (this.disabled) return; this.isOpen = true; console.timeEnd("Opening"); } private close(): void { this.searchTerm = ""; this.isOpen = false; } private listenToOptionEvents(): void { this.options.forEach((option: OptionComponent) => { const sub = option.onSelect.subscribe( (event) => { console.time("Option Clicked"); if (this.multiple) { let found: boolean = false; for (let i = 0; i < this.selectedOptions.length; i++) { if (this.selectedOptions[i] == Object.assign({text: event.getText()}, event)) { this.selectedOptions[i] = Object.assign({text: event.getText()}, event); found = true; } } if (!found) this.selectedOptions.push(Object.assign({text: event.getText()}, event)); } else { this.selectedOptions[0] = Object.assign({text: event.getText()}, event); } console.timeEnd("Option Clicked"); this.modelChanged(); this.changed(); this.close(); } ) }) } @HostListener('document:click', ['$event', '$event.target']) public onClick(event: MouseEvent, targetElement: HTMLElement): void { if (!targetElement) { return; } if (!this.elementRef.nativeElement.contains(targetElement)) { this.close(); } }; }