This repository has been archived by the owner on Apr 14, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
20 changed files
with
548 additions
and
71 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
# Dropdown Module | ||
|
||
The `ngx-plug-n-play` Dropdown Module contains the `DropdownComponent` and a utility file. The component has the two `Inputs`, both of which default to true. They are: | ||
|
||
## Dropdown Component API | ||
|
||
- `hideResultsOnSelect: boolean` — If this is set to true, an event will be emitted after an item is selected telling the parent component that it's okay to hide the option list | ||
- `closeOnOuterClick: boolean` — If this is set to true, the options list will close when the user clicks outside of the dropdown component. | ||
|
||
There are two `Output`s for the component as well, and they are related to the `Input`s above: | ||
|
||
- `updateShowResults: EventEmitter<Boolean>` — If the `hideResultsOnSelect` `Input` is true, or the `closeOnOuterClick` is true, then this `Output` will emit `true` so that the parent component can react accordingly. | ||
- `dropdownItemSelected: EventEmitter<DropdownSelectedItem>` — When an item is selected, its `index` and the `textContent` of the item are emitted as an object of type `DropdownSelectedItem` | ||
|
||
The utility file has one function in it that will be useful if you want to hide the currently selected item from the list. It's called `getRealItemFromListAfterSelection`. | ||
|
||
- `getRealItemFromListAfterSelection: DropdownSelectedItem` — The indices of the items in the list get messed up if you are hiding the currently selected item from the list, so this function will make sure that you get the correct item from the list all the time. Use this function if you want to hide the currently selected item. | ||
|
||
## Dropdown Component Demo | ||
|
||
Here's an example of how to use the `DropdownComponent`: | ||
|
||
```html | ||
<pnp-dropdown | ||
(updateShowResults)="showDropdownResults = $event" | ||
[closeOnOuterClick]="true" | ||
(dropdownItemSelected)="selectedItemUpdated($event)" | ||
> | ||
<button #dropdownTrigger dropdown-trigger>{{ triggerText }}</button> | ||
<ul #dropdownOptions dropdown-options [class.open]="showDropdownResults"> | ||
<ng-template ngFor let-item [ngForOf]="dropdownItems" let-i="index"> | ||
<li *ngIf="!selectedItem || selectedItem.index !== i">{{ item }}</li> | ||
</ng-template> | ||
</ul> | ||
</pnp-dropdown> | ||
``` | ||
|
||
The `Inputs` and `Outputs` are discussed above, but the important part here to point out is that two elements need to be passed in via content projection, a dropdown trigger and a dropdown options element. The dropdown trigger needs an attribute of `dropdown-trigger` and a template variable of `#dropdownTrigger`. The dropdown options need an attribute of `dropdown-options` and a template variable of `#dropdownOptions`. |
4 changes: 4 additions & 0 deletions
4
projects/ngx-plug-n-play-lib/src/lib/dropdown/dropdown-selected-item.interface.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
export interface DropdownSelectedItem { | ||
index: number; | ||
textContent: string; | ||
} |
12 changes: 12 additions & 0 deletions
12
projects/ngx-plug-n-play-lib/src/lib/dropdown/dropdown.module.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import { NgModule } from '@angular/core'; | ||
import { CommonModule } from '@angular/common'; | ||
import { DropdownComponent } from './dropdown/dropdown.component'; | ||
|
||
export * from './dropdown.util'; | ||
|
||
@NgModule({ | ||
declarations: [DropdownComponent], | ||
imports: [CommonModule], | ||
exports: [DropdownComponent], | ||
}) | ||
export class DropdownModule {} |
34 changes: 34 additions & 0 deletions
34
projects/ngx-plug-n-play-lib/src/lib/dropdown/dropdown.util.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
import { getRealItemFromListAfterSelection } from './dropdown.util'; | ||
|
||
describe('DropdownUtil', () => { | ||
it('should return the newItem if the previouslySelectedItem is falsy', () => { | ||
const selectedItem = getRealItemFromListAfterSelection(null, { index: 0, textContent: 'Test' }); | ||
expect(selectedItem.index).toBe(0); | ||
}); | ||
|
||
it('should return the previouslySelectedItem if the newItem is falsy', () => { | ||
const selectedItem = getRealItemFromListAfterSelection({ index: 0, textContent: 'Test' }, null); | ||
expect(selectedItem.index).toBe(0); | ||
}); | ||
|
||
it('should return null if both items are falsy', () => { | ||
const selectedItem = getRealItemFromListAfterSelection(undefined, null); | ||
expect(selectedItem).toBeFalsy(); | ||
}); | ||
|
||
it('should return the an index of 1 if both items index is 0', () => { | ||
const selectedItem = getRealItemFromListAfterSelection( | ||
{ index: 0, textContent: 'Test Item 1' }, | ||
{ index: 0, textContent: 'Test Item 2' }, | ||
); | ||
expect(selectedItem.index).toBe(1); | ||
}); | ||
|
||
it('should return the an index of 2 if the new item has a lower index number than the previous item', () => { | ||
const selectedItem = getRealItemFromListAfterSelection( | ||
{ index: 2, textContent: 'Test Item 3' }, | ||
{ index: 1, textContent: 'Test Item 2' }, | ||
); | ||
expect(selectedItem.index).toBe(1); | ||
}); | ||
}); |
21 changes: 21 additions & 0 deletions
21
projects/ngx-plug-n-play-lib/src/lib/dropdown/dropdown.util.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import { DropdownSelectedItem } from './dropdown-selected-item.interface'; | ||
|
||
export function getRealItemFromListAfterSelection( | ||
previouslySelectedItem: DropdownSelectedItem, | ||
newItem: DropdownSelectedItem, | ||
) { | ||
if (!previouslySelectedItem && newItem) { | ||
return newItem; | ||
} | ||
if (!newItem && previouslySelectedItem) { | ||
return previouslySelectedItem; | ||
} | ||
if (!newItem && !previouslySelectedItem) { | ||
return null; | ||
} | ||
|
||
return { | ||
index: previouslySelectedItem.index <= newItem.index ? newItem.index + 1 : newItem.index, | ||
textContent: newItem.textContent, | ||
}; | ||
} |
Empty file.
2 changes: 2 additions & 0 deletions
2
projects/ngx-plug-n-play-lib/src/lib/dropdown/dropdown/dropdown.component.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
<ng-content select="[dropdown-trigger]"></ng-content> | ||
<ng-content select="[dropdown-options]"></ng-content> |
103 changes: 103 additions & 0 deletions
103
projects/ngx-plug-n-play-lib/src/lib/dropdown/dropdown/dropdown.component.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
import { async, ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing'; | ||
|
||
import { DropdownComponent } from './dropdown.component'; | ||
import { Component, ViewChild, ElementRef } from '@angular/core'; | ||
import { By } from '@angular/platform-browser'; | ||
|
||
@Component({ | ||
selector: 'app-test-host', | ||
template: ` | ||
<pnp-dropdown (updateShowResults)="showResults = $event"> | ||
<button #dropdownTrigger dropdown-trigger class="btn btn-primary">Dropdown Trigger</button> | ||
<ul #dropdownOptions dropdown-options class="{{ showResults ? 'open' : '' }}"> | ||
<li>Item 1</li> | ||
<li>Item 2</li> | ||
<li>Item 3</li> | ||
<li>Item 4</li> | ||
</ul> | ||
</pnp-dropdown> | ||
`, | ||
}) | ||
class TestHostComponent { | ||
@ViewChild(DropdownComponent) dropdownComponent: DropdownComponent; | ||
public typeaheadDebounceTime: number = 300; | ||
public showResults: boolean; | ||
valueChanged(newValue: string) {} | ||
} | ||
|
||
describe('DropdownComponent', () => { | ||
let component: TestHostComponent; | ||
let fixture: ComponentFixture<TestHostComponent>; | ||
|
||
beforeEach(async(() => { | ||
TestBed.configureTestingModule({ | ||
declarations: [TestHostComponent, DropdownComponent], | ||
}).compileComponents(); | ||
})); | ||
|
||
beforeEach(() => { | ||
fixture = TestBed.createComponent(TestHostComponent); | ||
component = fixture.componentInstance; | ||
fixture.detectChanges(); | ||
}); | ||
|
||
it('should create', () => { | ||
expect(component).toBeTruthy(); | ||
}); | ||
|
||
it('should toggle the showResults value', () => { | ||
expect(component.dropdownComponent.showResults).toBeFalsy(); | ||
component.dropdownComponent.toggleShowResults(); | ||
fixture.detectChanges(); | ||
expect(component.dropdownComponent.showResults).toBeTruthy(); | ||
}); | ||
|
||
it('should not have the open class when the showResults variable is false', () => { | ||
const options: ElementRef = fixture.debugElement.query(By.css('[dropdown-options]')); | ||
|
||
expect(options.nativeElement.classList.contains('open')).toBeFalsy(); | ||
}); | ||
|
||
it('should have the open class when the showResults variable is true', () => { | ||
component.dropdownComponent.showResults = true; | ||
component.showResults = true; | ||
fixture.detectChanges(); | ||
|
||
const options: ElementRef = fixture.debugElement.query(By.css('[dropdown-options]')); | ||
|
||
expect(options.nativeElement.classList.contains('open')).toBeTruthy(); | ||
}); | ||
|
||
it('should listen for clicks on the options', fakeAsync(() => { | ||
spyOn(component.dropdownComponent, 'itemSelected'); | ||
spyOn(component.dropdownComponent.dropdownItemSelected, 'emit'); | ||
component.dropdownComponent.showResults = true; | ||
component.showResults = true; | ||
fixture.detectChanges(); | ||
|
||
const optionsElements: any[] = fixture.debugElement.queryAll(By.css('[dropdown-options] li')); | ||
|
||
optionsElements[0].nativeElement.click(); | ||
tick(); | ||
expect(component.dropdownComponent.itemSelected).toHaveBeenCalled(); | ||
})); | ||
|
||
it('should output the item that is clicked on in the list', fakeAsync(() => { | ||
spyOn(component.dropdownComponent.dropdownItemSelected, 'emit'); | ||
component.dropdownComponent.showResults = true; | ||
component.showResults = true; | ||
fixture.detectChanges(); | ||
const optionsElements: any[] = fixture.debugElement.queryAll(By.css('[dropdown-options] li')); | ||
const evt: Partial<Event> = { | ||
target: optionsElements[0].nativeElement, | ||
}; | ||
|
||
component.dropdownComponent.itemSelected(evt); | ||
|
||
expect(component.dropdownComponent.dropdownItemSelected.emit).toHaveBeenCalled(); | ||
expect(component.dropdownComponent.dropdownItemSelected.emit).toHaveBeenCalledWith({ | ||
index: 0, | ||
textContent: 'Item 1', | ||
}); | ||
})); | ||
}); |
84 changes: 84 additions & 0 deletions
84
projects/ngx-plug-n-play-lib/src/lib/dropdown/dropdown/dropdown.component.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
import { | ||
Component, | ||
ContentChild, | ||
AfterContentInit, | ||
OnDestroy, | ||
ElementRef, | ||
HostListener, | ||
Output, | ||
EventEmitter, | ||
Input, | ||
} from '@angular/core'; | ||
import { fromEvent, Subject } from 'rxjs'; | ||
import { takeUntil } from 'rxjs/operators'; | ||
import { DropdownSelectedItem } from '../dropdown-selected-item.interface'; | ||
|
||
@Component({ | ||
selector: 'pnp-dropdown', | ||
templateUrl: './dropdown.component.html', | ||
styleUrls: ['./dropdown.component.css'], | ||
}) | ||
export class DropdownComponent implements AfterContentInit, OnDestroy { | ||
@ContentChild('dropdownTrigger') dropdownTrigger: ElementRef; | ||
@ContentChild('dropdownOptions') dropdownOptions: ElementRef; | ||
@Output() updateShowResults: EventEmitter<boolean> = new EventEmitter<boolean>(); | ||
@Output() dropdownItemSelected: EventEmitter<DropdownSelectedItem> = new EventEmitter<DropdownSelectedItem>(); | ||
@Input() hideResultsOnSelect: boolean = true; | ||
@Input() closeOnOuterClick: boolean = true; | ||
public showResults: boolean = false; | ||
private destroy$: Subject<boolean> = new Subject<boolean>(); | ||
private isComponentClicked: boolean; | ||
|
||
constructor() {} | ||
|
||
ngOnDestroy() { | ||
this.destroy$.next(true); | ||
} | ||
|
||
ngAfterContentInit() { | ||
this.setUpButtonClickListener(); | ||
this.setUpListItemClickListener(); | ||
} | ||
|
||
@HostListener('click') | ||
clickInside() { | ||
this.isComponentClicked = true; | ||
} | ||
|
||
@HostListener('document:click', ['$event']) | ||
clickout(evt: Event) { | ||
if (!this.isComponentClicked && this.showResults && this.closeOnOuterClick) { | ||
this.toggleShowResults(); | ||
} | ||
this.isComponentClicked = false; | ||
} | ||
|
||
toggleShowResults() { | ||
this.showResults = !this.showResults; | ||
this.updateShowResults.emit(this.showResults); | ||
} | ||
|
||
setUpButtonClickListener() { | ||
fromEvent(this.dropdownTrigger.nativeElement, 'click') | ||
.pipe(takeUntil(this.destroy$)) | ||
.subscribe(() => this.toggleShowResults()); | ||
} | ||
|
||
setUpListItemClickListener() { | ||
fromEvent(this.dropdownOptions.nativeElement, 'click') | ||
.pipe(takeUntil(this.destroy$)) | ||
.subscribe((evt: any) => { | ||
this.itemSelected(evt); | ||
}); | ||
} | ||
|
||
itemSelected(evt: any) { | ||
const lisArray = Array.from(this.dropdownOptions.nativeElement.children); | ||
const index = lisArray.indexOf(evt.target); | ||
const textContent = evt.target.textContent; | ||
this.dropdownItemSelected.emit({ index, textContent }); | ||
if (this.hideResultsOnSelect) { | ||
this.toggleShowResults(); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.