diff options
Diffstat (limited to 'src/angular/form-elements/dropdown')
5 files changed, 314 insertions, 0 deletions
diff --git a/src/angular/form-elements/dropdown/dropdown-models.ts b/src/angular/form-elements/dropdown/dropdown-models.ts new file mode 100644 index 0000000..fa8dc23 --- /dev/null +++ b/src/angular/form-elements/dropdown/dropdown-models.ts @@ -0,0 +1,18 @@ +export enum DropDownTypes { + Regular, + Headless, + Auto +} + +export enum DropDownOptionType { + Simple, // default + Header, + Disable, + HorizontalLine +} + +export interface IDropDownOption { + value: any; + label: string; + type?: DropDownOptionType; +} diff --git a/src/angular/form-elements/dropdown/dropdown-trigger.directive.ts b/src/angular/form-elements/dropdown/dropdown-trigger.directive.ts new file mode 100644 index 0000000..94ab3bc --- /dev/null +++ b/src/angular/form-elements/dropdown/dropdown-trigger.directive.ts @@ -0,0 +1,17 @@ +import { Directive, Input, HostBinding, HostListener } from "@angular/core"; +import { DropDownComponent } from "./dropdown.component"; + +@Directive({ + selector: '[SdcDropdownTrigger]' +}) + +export class DropDownTriggerDirective { + + @HostBinding('class.js-sdc-dropdown--toggle-hook') true; + @Input() dropDown: DropDownComponent; + + @HostListener('click', ['$event']) onClick = (event) => { + this.dropDown.toggleDropdown(event); + } + +} diff --git a/src/angular/form-elements/dropdown/dropdown.component.html.ts b/src/angular/form-elements/dropdown/dropdown.component.html.ts new file mode 100644 index 0000000..a4247a4 --- /dev/null +++ b/src/angular/form-elements/dropdown/dropdown.component.html.ts @@ -0,0 +1,59 @@ +export default ` +<div class="sdc-dropdown" #dropDownWrapper + [ngClass]="{ + 'headless': type === cIDropDownTypes.Headless, + 'sdc-dropdown__error': !valid, + 'open-bottom': show && bottomVisible, + 'open-top':show && !bottomVisible}"> + <label *ngIf="label" class="sdc-dropdown__label" [ngClass]="{'required':required}">{{label}}</label> + <div class="sdc-dropdown__component-container"> + + <!--[DROP-DOWN AUTO HEADER START]--> + <div *ngIf="type===cIDropDownTypes.Auto" class="sdc-dropdown-auto__wrapper"> + <input class="sdc-dropdown__header js-sdc-dropdown--toggle-hook" + [(ngModel)]="this.filterValue" + (ngModelChange)="filterOptions(this.filterValue)" + placeholder="{{this.selectedOption?.label || this.selectedOption?.value || placeHolder}}"> + <svg-icon name="caret1-down-o" mode="secondary" size="small" (click)="toggleDropdown($event)"></svg-icon> + </div> + <!--[DROP-DOWN AUTO HEADER END]--> + + <!--[DROP-DOWN REGULAR HEADER START]--> + <button *ngIf="type===cIDropDownTypes.Regular" + class="sdc-dropdown__header js-sdc-dropdown--toggle-hook" + (click)="toggleDropdown($event)" + [ngClass]="{'disabled': disabled, 'placeholder':!this.selectedOption}"> + {{ this.selectedOption?.label || this.selectedOption?.value || placeHolder}} + <svg-icon name="caret1-down-o" mode="secondary" size="small"></svg-icon> + </button> + <!--[DROP-DOWN HEADER END]--> + + <!--[DROP-DOWN OPTIONS START]--> + <div class="sdc-dropdown__options-wrapper--frame" [ngClass]="{ + 'sdc-dropdown__options-wrapper--top':!bottomVisible, + 'sdc-dropdown__options-wrapper--uncollapsed':show + }"> + <ul #optionsContainerElement *ngIf="options" class="sdc-dropdown__options-list" [ngClass]="{ + 'sdc-dropdown__options-list--headless': headless, + 'sdc-dropdown__options-list--animation-init':animation_init + }"> + <ng-container *ngFor="let option of options; let i = index"> + <!--[Drop down item list or string list start]--> + <li *ngIf="option" class="sdc-dropdown__option" + [ngClass]="{ + 'selected': option == selectedOption, + 'sdc-dropdown__option--group':isGroupDesign, + 'sdc-dropdown__option--header': option.type && option.type === cIDropDownOptionType.Header, + 'sdc-dropdown__option--disabled': option.type && option.type === cIDropDownOptionType.Disable, + 'sdc-dropdown__option--hr': option.type && option.type === cIDropDownOptionType.HorizontalLine + }" + (click)="selectOption(option.value, $event)">{{option.label || String(option.value)}}</li> + <!--[Drop down item list or string list end]--> + </ng-container> + </ul> + </div> + <!--[DROP-DOWN OPTIONS END]--> + + </div> +</div> +`; diff --git a/src/angular/form-elements/dropdown/dropdown.component.spec.ts b/src/angular/form-elements/dropdown/dropdown.component.spec.ts new file mode 100644 index 0000000..1c0cb4d --- /dev/null +++ b/src/angular/form-elements/dropdown/dropdown.component.spec.ts @@ -0,0 +1,71 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { DropDownComponent } from './dropdown.component'; +import { IDropDownOption, DropDownTypes } from "./dropdown-models"; +import { FormsModule } from "@angular/forms"; +import {SvgIconModule} from "../../svg-icon/svg-icon.module"; + + +const label:string = "DropDown example"; +const placeHolder:string = "Please choose option"; +const options:IDropDownOption[] = [ + { + label:'First Option', + value: 'First Option' + }, + { + label:'Second Option', + value: 'Second Option' + }, + { + label:'Third Option', + value: 'Third Option' + } +]; + +describe('DropDown component', () => { + let fixture: ComponentFixture<DropDownComponent>; + let component: DropDownComponent; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ DropDownComponent ], + imports:[ + FormsModule, + SvgIconModule + ] + }).compileComponents(); + fixture = TestBed.createComponent(DropDownComponent); + component = fixture.componentInstance; + + })); + + beforeEach(()=>{ + component.label = label; + component.placeHolder = placeHolder; + component.options = options; + component.type = DropDownTypes.Regular; + console.log('herer we got component', component) + fixture.detectChanges(); + }); + + it('component should be created', () => { + expect(component).toBeTruthy(); + }); + + it('component should export the selected value', () => { + const option = options[1]; + component.selectOption(option); + fixture.detectChanges(); + expect(component.selectedOption).toEqual(option); + }); + + it('component should have autocomplite', () => { + expect(component.options.length).toEqual(3); + component.type = DropDownTypes.Auto; + component.filterValue = 'testERrorotesttresadfadfasdfasf'; + fixture.detectChanges(); + component.filterOptions(component.filterValue); + expect(component.options.length).toEqual(0); + }); + +}); diff --git a/src/angular/form-elements/dropdown/dropdown.component.ts b/src/angular/form-elements/dropdown/dropdown.component.ts new file mode 100644 index 0000000..a23072f --- /dev/null +++ b/src/angular/form-elements/dropdown/dropdown.component.ts @@ -0,0 +1,149 @@ +import { Component, EventEmitter, Input, Output, forwardRef, OnChanges, SimpleChanges, OnInit, ElementRef, ViewChild, AfterViewInit, HostListener, Renderer } from '@angular/core'; +import { IDropDownOption, DropDownOptionType, DropDownTypes } from "./dropdown-models"; +import { ValidatableComponent } from './../validation/validatable.component'; +import template from './dropdown.component.html'; + +@Component({ + selector: 'sdc-dropdown', + template: template +}) +export class DropDownComponent extends ValidatableComponent implements OnChanges, OnInit { + + @Output('changed') changeEmitter:EventEmitter<IDropDownOption> = new EventEmitter<IDropDownOption>(); + @Input() label: string; + @Input() options: IDropDownOption[]; + @Input() disabled: boolean; + @Input() placeHolder: string; + @Input() required: boolean; + @Input() maxHeight: number; + @Input() selectedOption: IDropDownOption; + @Input() type: DropDownTypes = DropDownTypes.Regular; + @ViewChild('dropDownWrapper') dropDownWrapper: ElementRef; + @ViewChild('optionsContainerElement') optionsContainerElement: ElementRef; + @HostListener('document:click', ['$event']) onClick(e) { + this.onClickDocument(e); + } + + private bottomVisible = true; + private myRenderer: Renderer; + + // Drop-down show/hide flag. default is false (closed) + public show = false; + + // Export DropDownOptionType enum so we can use it on the template + public cIDropDownOptionType = DropDownOptionType; + public cIDropDownTypes = DropDownTypes; + + // Configure unselectable option types + private unselectableOptions = [ + DropDownOptionType.Disable, + DropDownOptionType.Header, + DropDownOptionType.HorizontalLine + ]; + + // Set or unset Group style on drop-down + public isGroupDesign = false; + public animation_init = false; + public allOptions: IDropDownOption[]; + public filterValue: string; + + constructor(public renderer: Renderer) { + super(); + this.myRenderer = renderer; + this.maxHeight = 244; + this.filterValue = ''; + } + + ngOnInit(): void { + if (this.options) { + this.allOptions = this.options; + if (this.options.find(option => option.type === DropDownOptionType.Header)) { + this.isGroupDesign = true; + } + } + } + + ngOnChanges(changes: SimpleChanges): void { + console.log("ngOnChanges"); + if (changes.selectedOption && changes.selectedOption.currentValue !== changes.selectedOption.previousValue) { + if (typeof changes.selectedOption.currentValue === 'string' && this.isSelectable(changes.selectedOption.currentValue)) { + this.setSelected(changes.selectedOption.currentValue); + } else if (this.isSelectable(changes.selectedOption.currentValue.value)) { + this.setSelected(changes.selectedOption.currentValue.value); + } else { + this.setSelected(undefined); + } + } + } + + public getValue(): any { + return this.selectedOption && this.selectedOption.value; + } + + public selectOption = (option: IDropDownOption | string, event?): void => { + if (event) { event.stopPropagation(); } + if (this.type === DropDownTypes.Headless) { + // Hide the options when in headless mode and user select option. + this.myRenderer.setElementStyle(this.dropDownWrapper.nativeElement, 'display', 'none'); + } + if (typeof option === 'string' && this.isSelectable(option)) { + this.setSelected(option); + } else if (this.isSelectable((option as IDropDownOption).value)) { + this.setSelected((option as IDropDownOption).value); + } + } + + public toggleDropdown = (event?): void => { + if (event) { event.stopPropagation(); } + if (this.type === DropDownTypes.Headless) { + // Show the options when in headless mode. + this.myRenderer.setElementStyle(this.dropDownWrapper.nativeElement, 'display', 'block'); + } + if (this.disabled) { return; } + this.animation_init = true; + this.bottomVisible = this.isBottomVisible(); + this.show = !this.show; + } + + public filterOptions = (filterValue): void => { + if (filterValue.length >= 1 && !this.show) { this.toggleDropdown(); } + if (this.selectedOption) { this.selectedOption = null; } + this.options = this.allOptions.filter((option) => { + return option.value.toLowerCase().indexOf(filterValue.toLowerCase()) > -1; + }); + } + + private isSelectable = (value: string): boolean => { + const option: IDropDownOption = this.options.find(o => o.value === value); + if (!option) { return false; } + if (!option.type) { return true; } + return !this.unselectableOptions.find(optionType => optionType === option.type); + } + + private setSelected = (value: string): void => { + this.selectedOption = this.options.find(o => o.value === value); + if (this.type === DropDownTypes.Auto) { this.filterValue = value; } + this.show = false; + this.changeEmitter.next(this.selectedOption); + } + + private isBottomVisible = (): boolean => { + const windowPos = window.innerHeight + window.pageYOffset; + const boundingRect = this.dropDownWrapper.nativeElement.getBoundingClientRect(); + const dropDownPos = boundingRect.top + boundingRect.height + this.maxHeight; + return windowPos > dropDownPos; + } + + private onClickDocument = (event): void => { + if (this.type === DropDownTypes.Headless) { + if (!this.optionsContainerElement.nativeElement.contains(event.target)) { + this.show = false; + } + } else { + if (!this.dropDownWrapper.nativeElement.contains(event.target)) { + this.show = false; + } + } + } + +} |