Skip to content

Commit

Permalink
feat(filters): add option to filter empty values for select filter
Browse files Browse the repository at this point in the history
  • Loading branch information
ghiscoding committed Apr 14, 2021
1 parent d012a1f commit 7c9ce5a
Show file tree
Hide file tree
Showing 6 changed files with 135 additions and 65 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { Column, FilterArguments, GridOption } from '../../models';
import { CollectionService } from '../../services/collection.service';
import { Filters } from '..';
import { MultipleSelectFilter } from '../multipleSelectFilter';
import { of, Subject } from 'rxjs';

const containerId = 'demo-container';

Expand All @@ -30,7 +29,7 @@ describe('MultipleSelectFilter', () => {
let divContainer: HTMLDivElement;
let filter: MultipleSelectFilter;
let filterArguments: FilterArguments;
let spyGetHeaderRow;
let spyGetHeaderRow: any;
let mockColumn: Column;
let collectionService: CollectionService;
let translate: TranslateService;
Expand Down Expand Up @@ -67,13 +66,14 @@ describe('MultipleSelectFilter', () => {
});

it('should be a multiple-select filter', () => {
mockColumn.filter.collection = [{ value: 'male', label: 'male' }, { value: 'female', label: 'female' }];
mockColumn.filter!.collection = [{ value: 'male', label: 'male' }, { value: 'female', label: 'female' }];
filter = new MultipleSelectFilter(translate, collectionService);
filter.init(filterArguments, true);
filter.init(filterArguments);
const filterCount = divContainer.querySelectorAll('select.ms-filter.search-filter.filter-gender').length;

expect(spyGetHeaderRow).toHaveBeenCalled();
expect(filterCount).toBe(1);
expect(filter.isMultipleSelect).toBe(true);
expect(filter.columnDef.filter!.emptySearchTermReturnAllValues).toBeFalse();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { Column, FilterArguments, GridOption } from '../../models';
import { CollectionService } from '../../services/collection.service';
import { Filters } from '..';
import { SingleSelectFilter } from '../singleSelectFilter';
import { of, Subject } from 'rxjs';

const containerId = 'demo-container';

Expand All @@ -30,7 +29,7 @@ describe('SingleSelectFilter', () => {
let divContainer: HTMLDivElement;
let filter: SingleSelectFilter;
let filterArguments: FilterArguments;
let spyGetHeaderRow;
let spyGetHeaderRow: any;
let mockColumn: Column;
let collectionService: CollectionService;
let translate: TranslateService;
Expand Down Expand Up @@ -81,21 +80,22 @@ describe('SingleSelectFilter', () => {
});

it('should be a single-select filter', () => {
mockColumn.filter.collection = [{ value: 'male', label: 'male' }, { value: 'female', label: 'female' }];
mockColumn.filter!.collection = [{ value: 'male', label: 'male' }, { value: 'female', label: 'female' }];
filter = new SingleSelectFilter(translate, collectionService);
filter.init(filterArguments, true);
filter.init(filterArguments);
const filterCount = divContainer.querySelectorAll('select.ms-filter.search-filter.filter-gender').length;

expect(spyGetHeaderRow).toHaveBeenCalled();
expect(filterCount).toBe(1);
expect(filter.isMultipleSelect).toBe(false);
expect(filter.columnDef.filter!.emptySearchTermReturnAllValues).toBeUndefined();
});

it('should create the select filter with empty search term when passed an empty string as a filter argument and not expect "filled" css class either', () => {
mockColumn.filter.collection = [{ value: '', label: '' }, { value: 'male', label: 'male' }, { value: 'female', label: 'female' }];
mockColumn.filter!.collection = [{ value: '', label: '' }, { value: 'male', label: 'male' }, { value: 'female', label: 'female' }];

filterArguments.searchTerms = [''];
filter.init(filterArguments, true);
filter.init(filterArguments);
const filterListElm = divContainer.querySelectorAll<HTMLInputElement>(`[name=filter-gender].ms-drop ul>li input[type=radio]`);

const filterFilledElms = divContainer.querySelectorAll<HTMLDivElement>('.ms-parent.ms-filter.search-filter.filter-gender.filled');
Expand All @@ -105,10 +105,10 @@ describe('SingleSelectFilter', () => {

it('should trigger single select change event and expect the callback to be called when we select a single search term from dropdown list', () => {
const spyCallback = jest.spyOn(filterArguments, 'callback');
mockColumn.filter.collection = [{ value: 'male', label: 'male' }, { value: 'female', label: 'female' }];
mockColumn.filter!.collection = [{ value: 'male', label: 'male' }, { value: 'female', label: 'female' }];

filter.init(filterArguments, true);
const filterBtnElm = divContainer.querySelector<HTMLButtonElement>('.ms-parent.ms-filter.search-filter.filter-gender button.ms-choice');
filter.init(filterArguments);
const filterBtnElm = divContainer.querySelector('.ms-parent.ms-filter.search-filter.filter-gender button.ms-choice') as HTMLButtonElement;
const filterListElm = divContainer.querySelectorAll<HTMLInputElement>(`[name=filter-gender].ms-drop ul>li input[type=radio]`);
filterBtnElm.click();

Expand All @@ -134,10 +134,10 @@ describe('SingleSelectFilter', () => {
};

filterArguments.searchTerms = ['male', 'female'];
filter.init(filterArguments, true);
filter.init(filterArguments);

setTimeout(() => {
const filterBtnElm = divContainer.querySelector<HTMLButtonElement>('.ms-parent.ms-filter.search-filter.filter-gender button.ms-choice');
const filterBtnElm = divContainer.querySelector('.ms-parent.ms-filter.search-filter.filter-gender button.ms-choice') as HTMLButtonElement;
const filterListElm = divContainer.querySelectorAll<HTMLSpanElement>(`[name=filter-gender].ms-drop ul>li span`);
const filterOkElm = divContainer.querySelectorAll<HTMLButtonElement>(`[name=filter-gender].ms-drop .ms-ok-button`);
const filterSelectAllElm = divContainer.querySelectorAll<HTMLSpanElement>('.filter-gender .ms-select-all label span');
Expand Down Expand Up @@ -167,9 +167,9 @@ describe('SingleSelectFilter', () => {
};

filterArguments.searchTerms = ['male', 'female'];
filter.init(filterArguments, true);
filter.init(filterArguments);
setTimeout(() => {
const filterBtnElm = divContainer.querySelector<HTMLButtonElement>('.ms-parent.ms-filter.search-filter.filter-gender button.ms-choice');
const filterBtnElm = divContainer.querySelector('.ms-parent.ms-filter.search-filter.filter-gender button.ms-choice') as HTMLButtonElement;
const filterListElm = divContainer.querySelectorAll<HTMLSpanElement>(`[name=filter-gender].ms-drop ul>li span`);
filterBtnElm.click();

Expand Down
7 changes: 7 additions & 0 deletions src/app/modules/angular-slickgrid/filters/selectFilter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,13 @@ export class SelectFilter implements Filter {
}
this.defaultOptions.placeholder = placeholder || '';

// when we're using a multiple-select filter and we have an empty select option,
// we probably want this value to be a valid filter option that will ONLY return value that are empty (not everything like its default behavior)
// user can still override it by defining it
if (this._isMultipleSelect && this.columnDef?.filter) {
this.columnDef.filter.emptySearchTermReturnAllValues = this.columnDef.filter?.emptySearchTermReturnAllValues ?? false;
}

// always render the Select (dropdown) DOM element, even if user passed a "collectionAsync",
// if that is the case, the Select will simply be without any options but we still have to render it (else SlickGrid would throw an error)
const newCollection = this.columnFilter.collection || [];
Expand Down
11 changes: 11 additions & 0 deletions src/app/modules/angular-slickgrid/models/columnFilter.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,17 @@ export interface ColumnFilter {
*/
queryField?: string;

/**
* Defaults to true, should an empty search term have the effect of returning all results?
* Typically that would be True except for a dropdown Select Filter,
* we might really want to filter an empty string and/or `undefined` and for these special cases we can set this flag to `false`.
*
* NOTE: for a dropdown Select Filter, we will assume that on a multipleSelect Filter it should default to `false`
* however for a singleSelect Filter (and any other type of Filters) it should default to `true`.
* In any case, the user can overrides it this flag.
*/
emptySearchTermReturnAllValues?: boolean;

/** What is the Field Type that can be used by the Filter (as precedence over the "type" set the column definition) */
type?: FieldType;

Expand Down
Loading

0 comments on commit 7c9ce5a

Please sign in to comment.