angular-dropdown-component
Version:
Even though a dropdown (select-option) is a pretty common utility but it still doesn't support basics such as search.
362 lines (355 loc) • 17.2 kB
JavaScript
import { Component, Input, Output, EventEmitter, ViewChild, Pipe, NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
/**
* @fileoverview added by tsickle
* @suppress {checkTypes} checked by tsc
*/
class DropdownComponent {
constructor() {
this.selectedOptionChange = new EventEmitter();
this.ifContainerFocused = false;
}
/**
* @return {?}
*/
ngAfterViewInit() {
this.mutationObserverDOM = new MutationObserver(mutations => {
this.setContainerDimensions();
});
this.mutationObserverDOM.observe(this.dropdownMenu.nativeElement, {
childList: true
});
this.mutationObserverDOM.observe(this.dropdownMenu.nativeElement.children[1], {
childList: true
});
if (this.filter)
this.mutationObserverDOM.observe(this.dropdownMenu.nativeElement.children[1].children[1], {
childList: true
});
else
this.mutationObserverDOM.observe(this.dropdownMenu.nativeElement.children[1].children[0], {
childList: true
});
}
/**
* @param {?} simpleChanges
* @return {?}
*/
ngOnChanges(simpleChanges) {
if (simpleChanges.hasOwnProperty("data")) {
this._data = this.data;
}
if (this.data && !this.selectedOption)
this.setDefaultOption();
else if (this.data && this.selectedOption) {
if (this.selectedOption.hasOwnProperty("id")) {
this.data.forEach(obj => {
if (obj.id == this.selectedOption["id"])
this._selectedOption = obj;
});
}
else if (this.selectedOption.hasOwnProperty("name")) {
this.data.forEach(obj => {
if (obj.name == this.selectedOption["name"])
this._selectedOption = obj;
});
}
else if (this.editable) {
this._selectedOption = this.selectedOption;
return;
}
if (!this._selectedOption)
this.setDefaultOption();
}
}
/**
* @return {?}
*/
setDefaultOption() {
if (this.editable)
this._selectedOption = "";
else
this._selectedOption = this.data[0];
}
/**
* @return {?}
*/
setContainerDimensions() {
let /** @type {?} */ defaultDropdownHeight = this.dropdownMenu.nativeElement.children[0].offsetHeight;
let /** @type {?} */ actualList;
if (this.filter) {
defaultDropdownHeight += this.dropdownMenu.nativeElement.children[1].children[0].offsetHeight;
actualList = this.dropdownMenu.nativeElement.children[1].children[1].children;
}
else
actualList = this.dropdownMenu.nativeElement.children[1].children[0].children;
defaultDropdownHeight += Array.from(actualList).reduce((accumulator, currentValue) => accumulator + currentValue["offsetHeight"], 0);
let /** @type {?} */ dropdownButtonElement = this.dropdownButton.nativeElement;
let /** @type {?} */ dropdownButtonRect = dropdownButtonElement.getBoundingClientRect();
let /** @type {?} */ dropdownFilterHeight = this.filter ? this.dropdownFilter.nativeElement.offsetHeight : 0;
let /** @type {?} */ distanceFromTop = dropdownButtonRect.top;
let /** @type {?} */ distanceFromLeft = dropdownButtonRect.left;
let /** @type {?} */ distanceFromBottom = window.innerHeight - distanceFromTop - dropdownButtonElement.offsetHeight;
let /** @type {?} */ distanceFromRight = window.innerWidth - distanceFromLeft - dropdownButtonElement.offsetWidth;
this.selectedItemFocusedStyles = {
"width": dropdownButtonElement.offsetWidth - 2 + "px",
"height": dropdownButtonElement.offsetHeight - 2 + "px"
};
this.restOfListWithoutFilterFocusedStyles = { "margin-top": dropdownFilterHeight + "px" };
// If the space below is more than the dropdown height
if (distanceFromBottom > defaultDropdownHeight) {
// Un-reversify the list of array
this.containerFocusedStyles = {
"top": distanceFromTop + "px",
"left": distanceFromLeft + "px",
"width": dropdownButtonElement.offsetWidth + 'px',
"flex-direction": "column"
};
this.restOfListFocusedStyles = { "margin-top": dropdownButtonElement.offsetHeight + "px" };
}
else if (distanceFromTop > defaultDropdownHeight) {
// If the space above is more than the dropdown height
// Reversify the list of array
this.containerFocusedStyles = {
"bottom": distanceFromBottom + "px",
"left": distanceFromLeft + "px",
"width": dropdownButtonElement.offsetWidth + 'px',
"flex-direction": "column-reverse"
};
this.restOfListFocusedStyles = { "margin-bottom": dropdownButtonElement.offsetHeight + "px" };
this.selectedItemFocusedStyles = Object.assign({}, this.selectedItemFocusedStyles, { "border-top": "1px solid #e0e0e0" });
}
else {
// If space above and below both are less, show where it is maximum
// When space below is more
if (distanceFromBottom > distanceFromTop) {
this.containerFocusedStyles = {
"top": distanceFromTop + "px",
"bottom": "20px",
"left": distanceFromLeft + "px",
"width": dropdownButtonElement.offsetWidth + 'px',
"flex-direction": "column"
};
this.restOfListFocusedStyles = { "margin-top": dropdownButtonElement.offsetHeight + "px" };
}
else {
// When space above is more
this.containerFocusedStyles = {
"top": "20px",
"bottom": distanceFromBottom + "px",
"left": distanceFromLeft + "px",
"width": dropdownButtonElement.offsetWidth + 'px',
"flex-direction": "column-reverse"
};
this.restOfListFocusedStyles = { "margin-bottom": dropdownButtonElement.offsetHeight + "px" };
this.selectedItemFocusedStyles = Object.assign({}, this.selectedItemFocusedStyles, { "border-top": "1px solid #e0e0e0" });
}
}
}
/**
* @param {?} $event
* @return {?}
*/
onFilterSearch($event) {
this.setSearchedItems($event.target.value);
}
/**
* @param {?} value
* @return {?}
*/
setSearchedItems(value) {
this._data = this.data.filter(element => element.name
.toLowerCase()
.indexOf(value.toLowerCase()) !== -1);
}
/**
* @param {?} $event
* @return {?}
*/
onInputFocus($event) {
this.dropdownInput.nativeElement.focus();
this.ifContainerFocused = false;
}
/**
* @param {?} $event
* @return {?}
*/
onInputChange($event) {
this._selectedOption = $event.target.value;
// Broadcast Event
this.selectedOptionChange.emit(this._selectedOption);
}
/**
* @param {?} $event
* @param {?} option
* @return {?}
*/
onDropdownItemSelect($event, option) {
this._selectedOption = option;
// if (this.selectedOption.hasOwnProperty("id"))
// this.selectedOption = { id: this._selectedOption.id };
// else this.selectedOption = { name: this._selectedOption.name };
// Broadcast Event
this.selectedOptionChange.emit(option);
this.hideDropdown();
}
/**
* @param {?} $event
* @return {?}
*/
onDropdownMenuClick($event) {
this.clearSearchFilter();
this.setContainerDimensions();
if ($event.target instanceof HTMLInputElement)
return;
else
this.showDropdown();
}
/**
* @param {?} $event
* @return {?}
*/
onDropdownBlur($event) {
if (this.dropdownFilter && $event.relatedTarget == this.dropdownFilter.nativeElement && $event.relatedTarget instanceof HTMLInputElement)
return;
this.ifContainerFocused = false;
this.hideDropdown();
}
/**
* @return {?}
*/
clearSearchFilter() {
if (!this.dropdownFilter)
return;
this.dropdownFilter.nativeElement.value = "";
this.setSearchedItems("");
}
/**
* @return {?}
*/
showDropdown() {
this.dropdownMenu.nativeElement.focus();
}
/**
* @return {?}
*/
hideDropdown() {
this.dropdownMenu.nativeElement.blur();
}
/**
* @return {?}
*/
ngOnDestroy() {
this.mutationObserverDOM.disconnect();
}
}
DropdownComponent.decorators = [
{ type: Component, args: [{
selector: "ng-dropdown",
template: `<ng-container>
<div class="ng-dropdown">
<span *ngIf="dTitle" [innerText]="dTitle" class="ng-dropdown__title"></span>
<ul class="ng-dropdown__container" tabindex="0" (focus)="ifContainerFocused=true" (blur)="onDropdownBlur($event)" [ngStyle]="ifContainerFocused ? containerFocusedStyles: {}"
#dropdownMenu>
<li [ngStyle]="ifContainerFocused ? selectedItemFocusedStyles: {}">
<a (click)="hideDropdown()">
<span *ngIf="!editable; else ifEditableList" (focus)="onInputFocus($event)" [innerText]="_selectedOption?.name"></span>
<ng-template #ifEditableList>
<input class="ng-dropdown__container--editable" [ngModel]="_selectedOption | GetSelectedNamePipe" (focus)="onInputFocus($event)"/>
</ng-template>
<img src="assets/downPopup.png" />
</a>
</li>
<ul class="ng-dropdown__container--list" [ngStyle]="ifContainerFocused ? restOfListFocusedStyles: {}">
<li *ngIf="filter" [ngStyle]="ifContainerFocused ? selectedItemFocusedStyles: {}">
<input #dropdownFilter class="ng-dropdown__container--list__filter" placeholder="Search" (keyup)="onFilterSearch($event)" (blur)="onDropdownBlur($event)" />
</li>
<ul class="ng-dropdown__container--list__sublist" [ngStyle]="ifContainerFocused ? restOfListWithoutFilterFocusedStyles: {}">
<ng-container *ngIf="_data?.length else noResults">
<li *ngFor="let option of _data" [ngClass]="_selectedOption?.name == option.name ? 'active' : ''">
<a [innerText]="option.name" (click)="onDropdownItemSelect($event, option)"></a>
</li>
</ng-container>
<ng-template #noResults>
<li>
<a>No Results Found</a>
</li>
</ng-template>
</ul>
</ul>
</ul>
<div class="ng-dropdown__button" [ngClass]="disabled ? 'disabled' : ''" (mouseup)="onDropdownMenuClick($event)" #dropdownButton>
<span *ngIf="!editable; else ifEditable" [innerText]="_selectedOption?.name"></span>
<ng-template #ifEditable>
<input #dropdownInput (keyup)="onInputChange($event)" [ngModel]="_selectedOption | GetSelectedNamePipe" />
</ng-template>
<img src="assets/downPopup.png" />
</div>
</div>
</ng-container>
`,
styles: [`.ng-dropdown{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;position:relative;padding-bottom:10px}.ng-dropdown .ng-dropdown__title{padding-bottom:5px}.ng-dropdown .ng-dropdown__button{background:#fff;border:1px solid #c9c9c9;padding:2px 10px;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:center;-ms-flex-align:center;align-items:center;cursor:pointer;height:18px}.ng-dropdown .ng-dropdown__button span,.ng-dropdown__container>li a span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.ng-dropdown .ng-dropdown__button img{height:10px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.ng-dropdown .ng-dropdown__button input,.ng-dropdown .ng-dropdown__container li input.ng-dropdown__container--editable{width:100%;height:100%;border:none;outline:0;overflow:hidden;text-overflow:ellipsis}.ng-dropdown .ng-dropdown__button.disabled{pointer-events:none;opacity:.7}.ng-dropdown .ng-dropdown__container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;background:#fff;list-style-type:none;border:1px solid #c9c9c9;position:fixed;left:-10000px;outline:0;-webkit-box-sizing:border-box;box-sizing:border-box;z-index:100000;padding:0;margin:0;-webkit-box-shadow:0 3px 6px rgba(0,0,0,.16),0 3px 6px rgba(0,0,0,.23);box-shadow:0 3px 6px rgba(0,0,0,.16),0 3px 6px rgba(0,0,0,.23)}.ng-dropdown .ng-dropdown__container:focus{left:0}.ng-dropdown .ng-dropdown__container:focus+.ng-dropdown__button{pointer-events:none}.ng-dropdown .ng-dropdown__container li:hover{background-color:#e0e0e0;color:#000}.ng-dropdown .ng-dropdown__container>li:first-child a{height:100%}.ng-dropdown .ng-dropdown__container>li:first-child,.ng-dropdown__container--list>li:first-child{position:fixed;background-color:#fff}.ng-dropdown .ng-dropdown__container>li:first-child a,.ng-dropdown__container--list>li:first-child a{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.ng-dropdown .ng-dropdown__container>li:first-child a img,.ng-dropdown__container--list>li:first-child a img{height:10px}.ng-dropdown .ng-dropdown__container>li:first-child:hover,.ng-dropdown__container--list>li:first-child:hover{color:initial}.ng-dropdown .ng-dropdown__container li a,.ng-dropdown__container--list>li:first-child li a{padding:0 10px;height:22px;line-height:22px;cursor:pointer;-webkit-box-flex:1;-ms-flex:1;flex:1;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;text-overflow:ellipsis;overflow:hidden;white-space:nowrap;display:block;text-align:left}.ng-dropdown .ng-dropdown__container li input.ng-dropdown__container--list__filter{width:100%;padding:0 9px;height:22px;outline:0;-webkit-box-sizing:border-box;box-sizing:border-box;border:none;border-bottom:1px solid #e0e0e0;border-top:1px solid #e0e0e0}.ng-dropdown .ng-dropdown__container ul{list-style-type:none;padding:0;margin:0;overflow:auto}.ng-dropdown__container--list__sublist li:first-child{position:initial}.ng-dropdown__container--list__sublist li.active{background-color:#6fbbff;color:#fff}`]
},] },
];
/** @nocollapse */
DropdownComponent.ctorParameters = () => [];
DropdownComponent.propDecorators = {
"data": [{ type: Input },],
"disabled": [{ type: Input },],
"dTitle": [{ type: Input },],
"selectedOption": [{ type: Input },],
"filter": [{ type: Input },],
"editable": [{ type: Input },],
"selectedOptionChange": [{ type: Output },],
"dropdownMenu": [{ type: ViewChild, args: ["dropdownMenu",] },],
"dropdownButton": [{ type: ViewChild, args: ["dropdownButton",] },],
"dropdownInput": [{ type: ViewChild, args: ["dropdownInput",] },],
"dropdownFilter": [{ type: ViewChild, args: ["dropdownFilter",] },],
};
/**
* @fileoverview added by tsickle
* @suppress {checkTypes} checked by tsc
*/
class GetSelectedNamePipe {
/**
* @param {?} selectedOption
* @return {?}
*/
transform(selectedOption) {
return typeof selectedOption == 'object' ? selectedOption.name : selectedOption;
}
}
GetSelectedNamePipe.decorators = [
{ type: Pipe, args: [{ name: 'GetSelectedNamePipe' },] },
];
/** @nocollapse */
GetSelectedNamePipe.ctorParameters = () => [];
/**
* @fileoverview added by tsickle
* @suppress {checkTypes} checked by tsc
*/
class DropdownModule {
}
DropdownModule.decorators = [
{ type: NgModule, args: [{
imports: [CommonModule, FormsModule],
declarations: [DropdownComponent, GetSelectedNamePipe],
exports: [DropdownComponent]
},] },
];
/** @nocollapse */
DropdownModule.ctorParameters = () => [];
/**
* @fileoverview added by tsickle
* @suppress {checkTypes} checked by tsc
*/
/**
* @fileoverview added by tsickle
* @suppress {checkTypes} checked by tsc
*/
/**
* Generated bundle index. Do not edit.
*/
export { DropdownModule, DropdownComponent as ɵa, GetSelectedNamePipe as ɵb };
//# sourceMappingURL=angular-dropdown-component.js.map