From 24308a6b2a35e4ef993bef9329e1d6f08c216fd6 Mon Sep 17 00:00:00 2001
From: Dharan <14145706+dhrn@users.noreply.github.com>
Date: Mon, 29 Nov 2021 15:10:59 +0530
Subject: [PATCH] [AAE-6623] Search and select multiple dropdown options
(#7391)
* [AAE-6623] Search and select multiple dropdown options
* [ci:force] fix comment
---
.../select-filter-input.component.spec.ts | 177 +++++++++++++-----
.../select-filter-input.component.ts | 28 +++
2 files changed, 160 insertions(+), 45 deletions(-)
diff --git a/lib/core/card-view/components/card-view-selectitem/select-filter-input/select-filter-input.component.spec.ts b/lib/core/card-view/components/card-view-selectitem/select-filter-input/select-filter-input.component.spec.ts
index c110dcb939..d6d05df6ea 100644
--- a/lib/core/card-view/components/card-view-selectitem/select-filter-input/select-filter-input.component.spec.ts
+++ b/lib/core/card-view/components/card-view-selectitem/select-filter-input/select-filter-input.component.spec.ts
@@ -20,12 +20,55 @@ import { setupTestBed } from '../../../../testing/setup-test-bed';
import { CoreTestingModule } from '../../../../testing/core.testing.module';
import { TranslateModule } from '@ngx-translate/core';
import { SelectFilterInputComponent } from './select-filter-input.component';
-import { MatSelect } from '@angular/material/select';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
+import { Component, ViewChild } from '@angular/core';
+import { By } from '@angular/platform-browser';
import { ESCAPE } from '@angular/cdk/keycodes';
+import { MatSelect } from '@angular/material/select';
+
+@Component({
+ selector: 'adf-test-filter',
+ template: `
+
+
+ {{opt.name}}
+
+
+ `
+})
+export class TestComponent {
+ @ViewChild(SelectFilterInputComponent) filterInputComponent: SelectFilterInputComponent;
+ field: any = { value : '' };
+ showInputFilter = true;
+ multiple = false;
+ standardOptions = [
+ { 'id': '1', 'name': 'one' },
+ { 'id': '2', 'name': 'two' },
+ { 'id': '3', 'name': 'three' }
+ ];
+ options = this.standardOptions;
+
+ compare(obj1, obj2) {
+ if (!obj1 || !obj2) {
+ return false;
+ }
+ return obj1.id === obj2.id;
+ }
+
+ onChange(search: string) {
+ if (!search) {
+ this.options = this.standardOptions;
+ } else {
+ this.options = this.standardOptions.filter(({ name }) => name.includes(search));
+ }
+ }
+}
describe('SelectFilterInputComponent', () => {
-
+ let testFixture: ComponentFixture;
+ let testComponent: TestComponent;
let fixture: ComponentFixture;
let component: SelectFilterInputComponent;
let matSelect: MatSelect;
@@ -36,58 +79,102 @@ describe('SelectFilterInputComponent', () => {
CoreTestingModule,
NoopAnimationsModule
],
- providers: [ MatSelect ]
+ declarations: [
+ TestComponent
+ ],
+ providers: [
+ MatSelect
+ ]
});
- beforeEach(() => {
- fixture = TestBed.createComponent(SelectFilterInputComponent);
- component = fixture.componentInstance;
- matSelect = TestBed.inject(MatSelect);
- fixture.detectChanges();
+ describe('component', () => {
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(SelectFilterInputComponent);
+ component = fixture.componentInstance;
+ matSelect = TestBed.inject(MatSelect);
+ fixture.detectChanges();
+ });
+
+ it('should focus input on initialization', async () => {
+ spyOn(component.selectFilterInput.nativeElement, 'focus');
+ matSelect.openedChange.next(true);
+
+ fixture.detectChanges();
+ await fixture.whenStable();
+
+ expect(component.selectFilterInput.nativeElement.focus).toHaveBeenCalled();
+ });
+
+ it('should clear search term on close', async () => {
+ component.onModelChange('some-search-term');
+ expect(component.term).toBe('some-search-term');
+
+ matSelect.openedChange.next(false);
+
+ fixture.detectChanges();
+ await fixture.whenStable();
+
+ expect(component.term).toBe('');
+ });
+
+ it('should emit event when value changes', async () => {
+ spyOn(component.change, 'next');
+ component.onModelChange('some-search-term');
+ expect(component.change.next).toHaveBeenCalledWith('some-search-term');
+ });
+
+ it('should reset value on reset() event', () => {
+ component.onModelChange('some-search-term');
+ expect(component.term).toBe('some-search-term');
+
+ component.reset();
+ expect(component.term).toBe('');
+ });
+
+ it('should reset value on Escape event', () => {
+ component.onModelChange('some-search-term');
+ expect(component.term).toBe('some-search-term');
+
+ component.selectFilterInput.nativeElement.dispatchEvent(new KeyboardEvent('keydown', {'keyCode': ESCAPE} as any));
+ fixture.detectChanges();
+ expect(component.term).toBe('');
+ });
+
});
- it('should focus input on initialization', async () => {
- spyOn(component.selectFilterInput.nativeElement, 'focus');
- matSelect.openedChange.next(true);
+ describe('testComponent', () => {
+ beforeEach(() => {
+ testFixture = TestBed.createComponent(TestComponent);
+ testComponent = testFixture.componentInstance;
+ });
- fixture.detectChanges();
- await fixture.whenStable();
+ afterEach(() => testFixture.destroy());
- expect(component.selectFilterInput.nativeElement.focus).toHaveBeenCalled();
- });
+ it('should preserve the values for multiple search', async () => {
+ const userSelection = [{ 'id': '3', 'name': 'three' }];
+ const preSelected = [
+ { 'id': '1', 'name': 'one' },
+ { 'id': '2', 'name': 'two' }
+ ];
+ testComponent.field.value = preSelected;
+ testComponent.multiple = true;
+ testFixture.detectChanges();
- it('should clear search term on close', async () => {
- component.onModelChange('some-search-term');
- expect(component.term).toBe('some-search-term');
+ const dropdown: HTMLElement = testFixture.nativeElement.querySelector('.mat-select-trigger');
+ dropdown.click();
+ await testFixture.whenStable();
+ testFixture.detectChanges();
- matSelect.openedChange.next(false);
+ const filter = testFixture.debugElement.query(By.css('input'));
+ filter.triggerEventHandler('input', { target: { value: 'three' } });
+ testFixture.detectChanges();
- fixture.detectChanges();
- await fixture.whenStable();
+ const option = testFixture.debugElement.query(By.css('mat-option'));
+ option.triggerEventHandler('click', null);
+ testFixture.detectChanges();
- expect(component.term).toBe('');
- });
-
- it('should emit event when value changes', async () => {
- spyOn(component.change, 'next');
- component.onModelChange('some-search-term');
- expect(component.change.next).toHaveBeenCalledWith('some-search-term');
- });
-
- it('should reset value on reset() event', () => {
- component.onModelChange('some-search-term');
- expect(component.term).toBe('some-search-term');
-
- component.reset();
- expect(component.term).toBe('');
- });
-
- it('should reset value on Escape event', () => {
- component.onModelChange('some-search-term');
- expect(component.term).toBe('some-search-term');
-
- component.selectFilterInput.nativeElement.dispatchEvent(new KeyboardEvent('keydown', {'keyCode': ESCAPE} as any));
- fixture.detectChanges();
- expect(component.term).toBe('');
+ expect(testComponent.field.value).toEqual([...preSelected, ...userSelection]);
+ });
});
});
diff --git a/lib/core/card-view/components/card-view-selectitem/select-filter-input/select-filter-input.component.ts b/lib/core/card-view/components/card-view-selectitem/select-filter-input/select-filter-input.component.ts
index ec17755c07..2ed6cbf379 100644
--- a/lib/core/card-view/components/card-view-selectitem/select-filter-input/select-filter-input.component.ts
+++ b/lib/core/card-view/components/card-view-selectitem/select-filter-input/select-filter-input.component.ts
@@ -33,6 +33,7 @@ export class SelectFilterInputComponent implements OnDestroy {
@Output() change = new EventEmitter();
term = '';
+ previousSelected: any[];
private onDestroy$ = new Subject();
constructor(@Inject(MatSelect) private matSelect: MatSelect) {}
@@ -55,6 +56,33 @@ export class SelectFilterInputComponent implements OnDestroy {
this.change.next('');
}
});
+
+ if (this.matSelect.ngControl) {
+ this.previousSelected = this.matSelect.ngControl.value;
+ this.matSelect.ngControl.valueChanges
+ .pipe(takeUntil(this.onDestroy$))
+ .subscribe((values) => {
+ let restoreSelection = false;
+ if (this.matSelect.multiple && Array.isArray(this.previousSelected)) {
+ if (!Array.isArray(values)) {
+ values = [];
+ }
+ const options = this.matSelect.options.map(option => option.value);
+ this.previousSelected.forEach((previous) => {
+ const isSelected = [...values, ...options].some(current => this.matSelect.compareWith(current, previous));
+ if (!isSelected) {
+ values.push(previous);
+ restoreSelection = true;
+ }
+ });
+ }
+
+ this.previousSelected = values;
+ if (restoreSelection) {
+ this.matSelect._onChange(values);
+ }
+ });
+ }
}
reset(event?: Event) {