diff --git a/projects/go-lib/karma.conf.js b/projects/go-lib/karma.conf.js
index fb859c3ea..0e00ef2b4 100644
--- a/projects/go-lib/karma.conf.js
+++ b/projects/go-lib/karma.conf.js
@@ -22,7 +22,7 @@ module.exports = function (config) {
thresholds: {
statements: 93,
lines: 92,
- branches: 79,
+ branches: 75,
functions: 88,
},
},
diff --git a/projects/go-lib/src/lib/components/go-select/go-select.component.html b/projects/go-lib/src/lib/components/go-select/go-select.component.html
index 355136a30..c3ac2862b 100644
--- a/projects/go-lib/src/lib/components/go-select/go-select.component.html
+++ b/projects/go-lib/src/lib/components/go-select/go-select.component.html
@@ -6,10 +6,11 @@
{{ label }}
-
+ (close)="onClose()"
+ (scroll)="onScroll($event)"
+ #select
+>
+ ng-header-tmp
+ >
+
@@ -78,4 +86,4 @@
[theme]="theme">
-
\ No newline at end of file
+
diff --git a/projects/go-lib/src/lib/components/go-select/go-select.component.spec.ts b/projects/go-lib/src/lib/components/go-select/go-select.component.spec.ts
index 0192a663c..ab68c1bb9 100644
--- a/projects/go-lib/src/lib/components/go-select/go-select.component.spec.ts
+++ b/projects/go-lib/src/lib/components/go-select/go-select.component.spec.ts
@@ -7,6 +7,7 @@ import { GoFormErrorsModule } from '../go-form-errors/go-form-errors.module';
import { GoHintModule } from '../go-hint/go-hint.module';
import { GoRequiredTextModule } from '../go-required-text/go-required-text.module';
import { GoSelectComponent } from './go-select.component';
+import { Subject } from 'rxjs';
describe('GoSelectComponent', () => {
let component: GoSelectComponent;
@@ -25,8 +26,7 @@ describe('GoSelectComponent', () => {
FormsModule,
ReactiveFormsModule
]
- })
- .compileComponents();
+ }).compileComponents();
}));
beforeEach(() => {
@@ -41,6 +41,11 @@ describe('GoSelectComponent', () => {
});
describe('onSelectAll()', () => {
+
+ beforeEach(() => {
+ component.multiple = true;
+ });
+
it('adds all of the available items to the form control value', () => {
component.bindValue = undefined;
component.items = [
@@ -66,15 +71,111 @@ describe('GoSelectComponent', () => {
expect(component.control.value).toEqual([1, 2, 3]);
});
+
+ it('should select only filtered list, when filtered and selectAll', () => {
+ component.bindValue = 'id';
+ component.items = [
+ { id: 1, label: 'banana' },
+ { id: 2, label: 'apple' },
+ { id: 3, label: 'green apple' }
+ ];
+ const filteredItems: any[] = [
+ { id: 2, label: 'apple' },
+ { id: 3, label: 'green apple' }
+ ];
+ component.ngSelect.searchTerm = 'apple';
+ component.handleInput({ items: filteredItems, term: 'apple' });
+ component.onSelectAll();
+ expect(component.control.value).toEqual([2, 3]);
+ });
+
+ it('should select filtered list with existing items in control value, when filtered and selectAll', () => {
+ component.bindValue = 'id';
+ component.control.patchValue([4]);
+ component.items = [
+ { id: 1, label: 'banana' },
+ { id: 2, label: 'apple' },
+ { id: 3, label: 'green apple' },
+ { id: 4, label: 'grapes' }
+ ];
+ const filteredItems: any[] = [
+ { id: 2, label: 'apple' },
+ { id: 3, label: 'green apple' }
+ ];
+ component.ngSelect.searchTerm = 'apple';
+ component.handleInput({ items: filteredItems, term: 'apple' });
+ component.onSelectAll();
+ expect(component.control.value.length).toEqual(3);
+ });
+ });
+
+ describe('onSelectAll() with typeahead', () => {
+ beforeEach(() => {
+ component.typeahead = new Subject();
+ component.multiple = true;
+ });
+
+ it('should store items in previousSelectedItems', () => {
+ const initialItems: any[] = [
+ { id: 1, label: 'banana' },
+ { id: 2, label: 'apple' },
+ ];
+ component.items = initialItems;
+ component['handleTypeAheadSelectAll']();
+ expect(component['previousSelectedItems']).toEqual(initialItems);
+ });
+
+ it('should add items in previousSelectedItems', () => {
+ component.handleItemAdd({ id: 1, label: 'banana' });
+ expect(component['previousSelectedItems']).toEqual([
+ { id: 1, label: 'banana' },
+ ]);
+ });
+
+ it('should remove items from previousSelectedItems', () => {
+ component['previousSelectedItems'] = [{ id: 1, label: 'banana' }];
+ component.handleItemRemove({ value: { id: 1, label: 'banana' } });
+ expect(component['previousSelectedItems']).toEqual([]);
+ });
+
+ it('handleControlInitialValue(), should assign previousSelectedItems', () => {
+ component.control.patchValue([1]);
+ component.bindValue = 'id';
+ component.items = [
+ { id: 1, label: 'banana' },
+ { id: 2, label: 'apple' },
+ ];
+ component['handleControlInitialValue']();
+ expect(component['previousSelectedItems']).toEqual([
+ { id: 1, label: 'banana' },
+ ]);
+ });
+ });
+
+ describe('processSelectAll', () => {
+ it('process select all and patch value in form', () => {
+ component.bindValue = 'id';
+ const items: any[] = [
+ { id: 1, label: 'banana' },
+ { id: 2, label: 'apple' },
+ { id: 3, label: 'green apple' },
+ { id: 4, label: 'grapes' },
+ ];
+
+ component['processSelectAll'](items);
+
+ expect(component.control.value).toEqual([1, 2, 3, 4]);
+ });
});
describe('onRemoveAll', () => {
it('uses removed the selected values', () => {
component.bindValue = 'id';
+ spyOn(component, 'resetTypeAheadItems');
component.items = [
{ id: 1, label: 'Label 1' },
{ id: 2, label: 'Label 2' },
- { id: 3, label: 'Label 3' }
+ { id: 3, label: 'Label 3' },
];
component.onSelectAll();
@@ -82,6 +183,7 @@ describe('GoSelectComponent', () => {
component.onRemoveAll();
expect(component.control.value).toBeNull();
+ expect(component['resetTypeAheadItems']).toHaveBeenCalled();
});
});
});
diff --git a/projects/go-lib/src/lib/components/go-select/go-select.component.ts b/projects/go-lib/src/lib/components/go-select/go-select.component.ts
index 9cb67b68a..6de5341bb 100644
--- a/projects/go-lib/src/lib/components/go-select/go-select.component.ts
+++ b/projects/go-lib/src/lib/components/go-select/go-select.component.ts
@@ -1,6 +1,18 @@
-import { Component, ContentChild, EventEmitter, Input, OnInit, Output, TemplateRef, ViewEncapsulation } from '@angular/core';
-import { Subject } from 'rxjs';
+import {
+ Component,
+ ContentChild,
+ EventEmitter,
+ Input,
+ OnDestroy,
+ OnInit,
+ Output,
+ TemplateRef,
+ ViewChild,
+ ViewEncapsulation
+} from '@angular/core';
+import { Subject, Subscription } from 'rxjs';
import { GoFormBaseComponent } from '../go-form-base/go-form-base.component';
+import { NgSelectComponent } from '@ng-select/ng-select';
@Component({
encapsulation: ViewEncapsulation.None,
@@ -8,7 +20,8 @@ import { GoFormBaseComponent } from '../go-form-base/go-form-base.component';
templateUrl: './go-select.component.html',
styleUrls: ['./go-select.component.scss']
})
-export class GoSelectComponent extends GoFormBaseComponent implements OnInit {
+export class GoSelectComponent extends GoFormBaseComponent implements OnInit, OnDestroy {
+ @ViewChild(NgSelectComponent) ngSelect: NgSelectComponent;
@Input() appendTo: string;
@Input() bindLabel: string;
@@ -34,22 +47,47 @@ export class GoSelectComponent extends GoFormBaseComponent implements OnInit {
@Input() virtualScroll: boolean = false;
@Output() scrollToEnd: EventEmitter = new EventEmitter();
- @Output() scroll: EventEmitter<{ start: number, end: number }> = new EventEmitter<{ start: number; end: number }>();
+ @Output() scroll: EventEmitter<{ start: number, end: number }> = new EventEmitter<{ start: number, end: number }>();
@ContentChild('goSelectOption') goSelectOption: TemplateRef;
@ContentChild('goSelectOptionGroup') goSelectOptionGroup: TemplateRef;
@ContentChild('goSelectSelectedOption') goSelectSelectedOption: TemplateRef;
+ // store refined items after search
+ refinedItems: any[] = [];
+ // stores previous selected items when typeahead is enabled only in case of selectAll.
+ private previousSelectedItems: any[] = [];
+ private controlSubscription: Subscription;
+
ngOnInit(): void {
this.closeOnSelect = this.multiple ? false : this.closeOnSelect;
+ this.handleControlInitialValue();
+ this.subscribeToControlChanges();
+ }
+
+ ngOnDestroy(): void {
+ this.controlSubscription?.unsubscribe();
}
onSelectAll(): void {
- this.control.patchValue(this.items.map((item: any) => this.bindValue ? item[this.bindValue] : item));
+ if (this.typeahead) {
+ this.handleTypeAheadSelectAll();
+ return;
+ }
+
+ const items: any[] = this.ngSelect.searchTerm ? this.refinedItems : this.items;
+ this.processSelectAll(items);
+ }
+
+ handleInput(search: { term: string; items: any[] }): void {
+ if (this.multiple) {
+ this.refinedItems = search.items;
+ }
}
onRemoveAll(): void {
this.control.reset();
+ this.resetTypeAheadItems();
}
onScrollToEnd(): void {
@@ -61,4 +99,107 @@ export class GoSelectComponent extends GoFormBaseComponent implements OnInit {
onScroll($event: { start: number; end: number }): void {
this.scroll.emit($event);
}
+
+ onClose(): void {
+ this.emptyRefinedItems();
+ }
+
+ // store previous selected items incase of multiple and typeahead.
+ handleItemAdd(item: any): void {
+ if (!this.multiple || !this.typeahead) {
+ return;
+ }
+ this.previousSelectedItems.push(item);
+ }
+
+ // remove item from previous selected items incase of multiple and typeahead.
+ handleItemRemove(item: any): void {
+ if (!this.multiple || !this.typeahead ) {
+ return;
+ }
+
+ const index: number = this.previousSelectedItems.findIndex((prev: any) => prev[this.bindValue] === item.value[this.bindValue]);
+ this.previousSelectedItems.splice(index, 1);
+ }
+
+ private subscribeToControlChanges(): void {
+ if (this.multiple && this.showSelectAll) {
+ this.controlSubscription = this.control.valueChanges.subscribe((value: any) => {
+ this.handleMultipleControlChanges(value);
+ });
+ }
+ }
+
+ private handleMultipleControlChanges(value: any): void {
+ this.emptyRefinedItems();
+ if (!value?.length) {
+ this.resetTypeAheadItems();
+ }
+ }
+
+ private handleTypeAheadSelectAll(): void {
+ // because spread operator is not supported due to tslib version
+ const items: any[] = JSON.parse(JSON.stringify(this.items));
+ for (const previousItem of this.previousSelectedItems) {
+ const exists: boolean = items.some(
+ (item: any) => item[this.bindValue] === previousItem[this.bindValue]
+ );
+ if (!exists) {
+ items.unshift(previousItem);
+ }
+ }
+ this.previousSelectedItems = items;
+ this.items = items;
+ this.control.reset([], { emitEvent: false });
+ this.processSelectAll(items);
+ }
+
+ private resetTypeAheadItems(): void {
+ if (this.typeahead) {
+ this.items = [];
+ this.previousSelectedItems = [];
+ }
+ }
+
+ private emptyRefinedItems(): void {
+ if (!this.ngSelect.searchTerm) {
+ this.refinedItems = [];
+ }
+ }
+
+ private processSelectAll(items: any[]): void {
+ const refinedArr: any[] = items.map((item: any) =>
+ this.bindValue ? item[this.bindValue] : item
+ );
+
+ const existing: any[] = Array.isArray(this.control.value) ? this.control.value : [];
+ const uniq: any[] = Array.from(new Set(existing.concat(refinedArr)));
+ this.control.patchValue(uniq);
+ this.ngSelect.searchTerm = '';
+ this.ngSelect.itemsList.resetFilteredItems();
+ }
+
+ private shouldHandleControlInitialValue(): boolean {
+ return (this.typeahead || this.multiple) && Array.isArray(this.control.value);
+ }
+
+ private findItemByValue(value: any): any {
+ return this.items.find((item: any) => item[this.bindValue] === value);
+ }
+
+ private handleControlInitialValue(): void {
+ if (!this.shouldHandleControlInitialValue()) {
+ return;
+ }
+
+ const selected: any[] = this.control.value;
+
+ for (const value of selected) {
+ const exist: any = this.findItemByValue(value);
+ if (exist) {
+ this.previousSelectedItems.push(exist);
+ }
+ }
+ }
+
}