diff --git a/README.md b/README.md
index b80844b6e..bc49a055d 100644
--- a/README.md
+++ b/README.md
@@ -31,7 +31,7 @@ You like and use this great library `Angular-Slickgrid`? Please upvote :star: an
- version `2.x.x` for Angular 7+
#### Angular 8
-When running `ng update` to upgrade to Angular 8, one of the biggest change that is noticeable is that they change the target to `ES2015`, which does not play well with SlickGrid core library (which is all written in plain ES5 javascript). So for that reason, you need to switch back the `target` to `ES5` in your `tsconfig.json` file (`"target": "es5"`). This might be fixeable in the future, but for now that is the quick fix to get Angular 8 to work.
+When running `ng update` to upgrade to Angular 8, one of the biggest change that is noticeable is that they change the target to `ES2015`, which does not play well with SlickGrid core library (which is all written in plain ES5 javascript). So for that reason, you need to switch back the `target` to `ES5` in your `tsconfig.json` file (`"target": "es5"`). This might be fixable in the future, but for now that is the quick fix to get Angular 8 to work.
### Installation
Refer to the [Wiki - HOWTO Step by Step](https://github.com/ghiscoding/angular-slickgrid/wiki/HOWTO---Step-by-Step)
diff --git a/src/app/modules/angular-slickgrid/editors/selectEditor.ts b/src/app/modules/angular-slickgrid/editors/selectEditor.ts
index ffa1d6ca2..6e23b49fe 100644
--- a/src/app/modules/angular-slickgrid/editors/selectEditor.ts
+++ b/src/app/modules/angular-slickgrid/editors/selectEditor.ts
@@ -432,8 +432,9 @@ export class SelectEditor implements Editor {
}
protected renderDomElement(collection: any[]) {
- if (!Array.isArray(collection) && this.collectionOptions && this.collectionOptions.collectionInObjectProperty) {
- collection = getDescendantProperty(collection, this.collectionOptions.collectionInObjectProperty);
+ if (!Array.isArray(collection) && this.collectionOptions && (this.collectionOptions.collectionInsideObjectProperty || this.collectionOptions.collectionInObjectProperty)) {
+ const collectionInsideObjectProperty = this.collectionOptions.collectionInsideObjectProperty || this.collectionOptions.collectionInObjectProperty;
+ collection = getDescendantProperty(collection, collectionInsideObjectProperty);
}
if (!Array.isArray(collection)) {
throw new Error('The "collection" passed to the Select Editor is not a valid array');
diff --git a/src/app/modules/angular-slickgrid/filters/__tests__/multipleSelectFilter.spec.ts b/src/app/modules/angular-slickgrid/filters/__tests__/multipleSelectFilter.spec.ts
new file mode 100644
index 000000000..b85bb84df
--- /dev/null
+++ b/src/app/modules/angular-slickgrid/filters/__tests__/multipleSelectFilter.spec.ts
@@ -0,0 +1,79 @@
+// import 3rd party lib multiple-select for the tests
+import '../../../../../assets/lib/multiple-select/multiple-select';
+
+import { TestBed } from '@angular/core/testing';
+import { TranslateService, TranslateModule } from '@ngx-translate/core';
+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';
+
+// define a
container to simulate the grid container
+const template = `
`;
+
+const gridOptionMock = {
+ enableFiltering: true,
+ enableFilterTrimWhiteSpace: true,
+} as GridOption;
+
+const gridStub = {
+ getOptions: () => gridOptionMock,
+ getColumns: jest.fn(),
+ getHeaderRowColumn: jest.fn(),
+ render: jest.fn(),
+};
+
+describe('MultipleSelectFilter', () => {
+ let divContainer: HTMLDivElement;
+ let filter: MultipleSelectFilter;
+ let filterArguments: FilterArguments;
+ let spyGetHeaderRow;
+ let mockColumn: Column;
+ let collectionService: CollectionService;
+ let translate: TranslateService;
+
+ beforeEach(() => {
+ divContainer = document.createElement('div');
+ divContainer.innerHTML = template;
+ document.body.appendChild(divContainer);
+ spyGetHeaderRow = jest.spyOn(gridStub, 'getHeaderRowColumn').mockReturnValue(divContainer);
+
+ mockColumn = {
+ id: 'gender', field: 'gender', filterable: true,
+ filter: {
+ model: Filters.multipleSelect,
+ }
+ };
+ filterArguments = {
+ grid: gridStub,
+ columnDef: mockColumn,
+ callback: jest.fn()
+ };
+
+ TestBed.configureTestingModule({
+ providers: [CollectionService],
+ imports: [TranslateModule.forRoot()]
+ });
+ collectionService = TestBed.get(CollectionService);
+ translate = TestBed.get(TranslateService);
+ filter = new MultipleSelectFilter(translate, collectionService);
+ });
+
+ afterEach(() => {
+ filter.destroy();
+ });
+
+ it('should be a multiple-select filter', () => {
+ mockColumn.filter.collection = [{ value: 'male', label: 'male' }, { value: 'female', label: 'female' }];
+ filter = new MultipleSelectFilter(translate, collectionService);
+ filter.init(filterArguments, true);
+ const filterCount = divContainer.querySelectorAll('select.ms-filter.search-filter.filter-gender').length;
+
+ expect(spyGetHeaderRow).toHaveBeenCalled();
+ expect(filterCount).toBe(1);
+ expect(filter.isMultipleSelect).toBe(true);
+ });
+});
diff --git a/src/app/modules/angular-slickgrid/filters/__tests__/selectFilter.spec.ts b/src/app/modules/angular-slickgrid/filters/__tests__/selectFilter.spec.ts
new file mode 100644
index 000000000..33bed652e
--- /dev/null
+++ b/src/app/modules/angular-slickgrid/filters/__tests__/selectFilter.spec.ts
@@ -0,0 +1,670 @@
+// import 3rd party lib multiple-select for the tests
+import '../../../../../assets/lib/multiple-select/multiple-select';
+
+import { TestBed } from '@angular/core/testing';
+import { TranslateService, TranslateModule } from '@ngx-translate/core';
+import { Column, FilterArguments, GridOption, FieldType, OperatorType } from '../../models';
+import { CollectionService } from './../../services/collection.service';
+import { Filters } from '..';
+import { SelectFilter } from '../selectFilter';
+import { of, Subject } from 'rxjs';
+
+const containerId = 'demo-container';
+
+// define a
container to simulate the grid container
+const template = `
`;
+
+const gridOptionMock = {
+ enableFiltering: true,
+ enableFilterTrimWhiteSpace: true,
+} as GridOption;
+
+const gridStub = {
+ getOptions: () => gridOptionMock,
+ getColumns: jest.fn(),
+ getHeaderRowColumn: jest.fn(),
+ render: jest.fn(),
+};
+
+describe('SelectFilter', () => {
+ let divContainer: HTMLDivElement;
+ let filter: SelectFilter;
+ let filterArguments: FilterArguments;
+ let spyGetHeaderRow;
+ let mockColumn: Column;
+ let collectionService: CollectionService;
+ let translate: TranslateService;
+
+ beforeEach(() => {
+ divContainer = document.createElement('div');
+ divContainer.innerHTML = template;
+ document.body.appendChild(divContainer);
+ spyGetHeaderRow = jest.spyOn(gridStub, 'getHeaderRowColumn').mockReturnValue(divContainer);
+
+ mockColumn = {
+ id: 'gender', field: 'gender', filterable: true,
+ filter: {
+ model: Filters.multipleSelect,
+ }
+ };
+ filterArguments = {
+ grid: gridStub,
+ columnDef: mockColumn,
+ callback: jest.fn()
+ };
+
+ TestBed.configureTestingModule({
+ providers: [CollectionService],
+ imports: [TranslateModule.forRoot()]
+ });
+ collectionService = TestBed.get(CollectionService);
+ translate = TestBed.get(TranslateService);
+
+ translate.setTranslation('en', {
+ ALL_SELECTED: 'All Selected',
+ FEMALE: 'Female',
+ MALE: 'Male',
+ OK: 'OK',
+ OTHER: 'Other',
+ SELECT_ALL: 'Select All',
+ X_OF_Y_SELECTED: '# of % selected',
+ });
+ translate.setTranslation('fr', {
+ ALL_SELECTED: 'Tout sélectionnés',
+ FEMALE: 'Femme',
+ MALE: 'Mâle',
+ OK: 'Terminé',
+ OTHER: 'Autre',
+ SELECT_ALL: 'Sélectionner tout',
+ X_OF_Y_SELECTED: '# de % sélectionnés',
+ });
+ translate.setDefaultLang('en');
+
+ filter = new SelectFilter(translate, collectionService);
+ });
+
+ afterEach(() => {
+ filter.destroy();
+ });
+
+ it('should throw an error when trying to call init without any arguments', () => {
+ expect(() => filter.init(null, true)).toThrowError('[Angular-SlickGrid] A filter must always have an "init()" with valid arguments.');
+ });
+
+ it('should throw an error when there is no collection provided in the filter property', (done) => {
+ try {
+ mockColumn.filter.collection = undefined;
+ filter.init(filterArguments, true);
+ } catch (e) {
+ expect(e.toString()).toContain(`[Angular-SlickGrid] You need to pass a "collection" (or "collectionAsync") for the MultipleSelect/SingleSelect Filter to work correctly.`);
+ done();
+ }
+ });
+
+ it('should throw an error when collection is not a valid array', (done) => {
+ try {
+ // @ts-ignore
+ mockColumn.filter.collection = { hello: 'world' };
+ filter.init(filterArguments, true);
+ } catch (e) {
+ expect(e.toString()).toContain(`The "collection" passed to the Select Filter is not a valid array.`);
+ done();
+ }
+ });
+
+ it('should throw an error when collection is not a valid value/label pair array', (done) => {
+ try {
+ mockColumn.filter.collection = [{ hello: 'world' }];
+ filter.init(filterArguments, true);
+ } catch (e) {
+ expect(e.toString()).toContain(`[select-filter] A collection with value/label (or value/labelKey when using Locale) is required to populate the Select list`);
+ done();
+ }
+ });
+
+ it('should throw an error when "enableTranslateLabel" is set without a valid TranslateService', (done) => {
+ try {
+ translate = undefined;
+ mockColumn.filter.enableTranslateLabel = true;
+ mockColumn.filter.collection = [{ value: 'male', label: 'male' }, { value: 'female', label: 'female' }];
+ filter = new SelectFilter(translate, collectionService);
+
+ filter.init(filterArguments, true);
+ } catch (e) {
+ expect(e.toString()).toContain(`[select-filter] The ngx-translate TranslateService is required for the Select Filter to work correctly when "enableTranslateLabel" is set.`);
+ done();
+ }
+ });
+
+ it('should initialize the filter', () => {
+ mockColumn.filter.collection = [{ value: 'male', label: 'male' }, { value: 'female', label: 'female' }];
+ filter.init(filterArguments, true);
+ const filterCount = divContainer.querySelectorAll('select.ms-filter.search-filter.filter-gender').length;
+
+ expect(spyGetHeaderRow).toHaveBeenCalled();
+ expect(filterCount).toBe(1);
+ });
+
+ it('should be a multiple-select filter by default when it is not specified in the constructor', () => {
+ mockColumn.filter.collection = [{ value: 'male', label: 'male' }, { value: 'female', label: 'female' }];
+ filter = new SelectFilter(translate, collectionService);
+ filter.init(filterArguments, true);
+ const filterCount = divContainer.querySelectorAll('select.ms-filter.search-filter.filter-gender').length;
+
+ expect(spyGetHeaderRow).toHaveBeenCalled();
+ expect(filterCount).toBe(1);
+ expect(filter.isMultipleSelect).toBe(true);
+ });
+
+ it('should have a placeholder when defined in its column definition', () => {
+ const testValue = 'test placeholder';
+ mockColumn.filter.placeholder = testValue;
+ mockColumn.filter.collection = [{ value: 'male', label: 'male' }, { value: 'female', label: 'female' }];
+
+ filter.init(filterArguments, true);
+ const filterElm = divContainer.querySelector
('.ms-filter.search-filter.filter-gender .placeholder');
+
+ expect(filterElm.innerHTML).toBe(testValue);
+ });
+
+ it('should trigger multiple select change event and expect the callback to be called with the search terms we select from dropdown list', () => {
+ const spyCallback = jest.spyOn(filterArguments, 'callback');
+ mockColumn.filter.collection = [{ value: 'male', label: 'male' }, { value: 'female', label: 'female' }];
+
+ filter.init(filterArguments, true);
+ const filterBtnElm = divContainer.querySelector('.ms-parent.ms-filter.search-filter.filter-gender button.ms-choice');
+ const filterListElm = divContainer.querySelectorAll(`[name=filter-gender].ms-drop ul>li input[type=checkbox]`);
+ const filterOkElm = divContainer.querySelector(`[name=filter-gender].ms-drop .ms-ok-button`);
+ filterBtnElm.click();
+
+ // we can use property "checked" or dispatch an event
+ filterListElm[0].dispatchEvent(new CustomEvent('click'));
+ filterOkElm.click();
+
+ const filterFilledElms = divContainer.querySelectorAll('.ms-parent.ms-filter.search-filter.filter-gender.filled');
+ expect(filterListElm.length).toBe(2);
+ expect(filterFilledElms.length).toBe(1);
+ expect(spyCallback).toHaveBeenCalledWith(undefined, { columnDef: mockColumn, operator: 'IN', searchTerms: ['male'], shouldTriggerQuery: true });
+ });
+
+ it('should trigger multiple select change event and expect this to work with a regular array of strings', () => {
+ const spyCallback = jest.spyOn(filterArguments, 'callback');
+
+ mockColumn.filter.collection = ['male', 'female'];
+ filter.init(filterArguments, true);
+ const filterBtnElm = divContainer.querySelector('.ms-parent.ms-filter.search-filter.filter-gender button.ms-choice');
+ const filterListElm = divContainer.querySelectorAll(`[name=filter-gender].ms-drop ul>li input[type=checkbox]`);
+ const filterOkElm = divContainer.querySelector(`[name=filter-gender].ms-drop .ms-ok-button`);
+ filterBtnElm.click();
+
+ // here we use "checked" property instead of dispatching an event
+ filterListElm[0].checked = true;
+ filterOkElm.click();
+
+ const filterFilledElms = divContainer.querySelectorAll('.ms-parent.ms-filter.search-filter.filter-gender.filled');
+ expect(filterListElm.length).toBe(2);
+ expect(filterFilledElms.length).toBe(1);
+ expect(spyCallback).toHaveBeenCalledWith(undefined, { columnDef: mockColumn, operator: 'IN', searchTerms: ['male'], shouldTriggerQuery: true });
+ });
+
+ it('should pass a different operator then trigger an input change event and expect the callback to be called with the search terms we select from dropdown list', () => {
+ mockColumn.filter.operator = 'NIN';
+ mockColumn.filter.collection = [{ value: 'male', label: 'male' }, { value: 'female', label: 'female' }];
+ const spyCallback = jest.spyOn(filterArguments, 'callback');
+
+ filter.init(filterArguments, true);
+ const filterBtnElm = divContainer.querySelector('.ms-parent.ms-filter.search-filter.filter-gender button.ms-choice');
+ const filterListElm = divContainer.querySelectorAll(`[name=filter-gender].ms-drop ul>li input[type=checkbox]`);
+ const filterOkElm = divContainer.querySelector(`[name=filter-gender].ms-drop .ms-ok-button`);
+ filterBtnElm.click();
+
+ filterListElm[0].checked = true;
+ filterOkElm.click();
+
+ const filterFilledElms = divContainer.querySelectorAll('.ms-parent.ms-filter.search-filter.filter-gender.filled');
+ expect(filterListElm.length).toBe(2);
+ expect(filterFilledElms.length).toBe(1);
+ expect(spyCallback).toHaveBeenCalledWith(undefined, { columnDef: mockColumn, operator: 'NIN', searchTerms: ['male'], shouldTriggerQuery: true });
+ });
+
+ it('should have same value in "getValues" after being set in "setValues"', () => {
+ mockColumn.filter.collection = [{ value: 'male', label: 'male' }, { value: 'female', label: 'female' }];
+ filter.init(filterArguments, true);
+ filter.setValues('female');
+ const values = filter.getValues();
+
+ expect(values).toEqual(['female']);
+ expect(values.length).toBe(1);
+ });
+
+ it('should have empty array returned from "getValues" when nothing is set', () => {
+ mockColumn.filter.collection = [{ value: 'male', label: 'male' }, { value: 'female', label: 'female' }];
+ filter.init(filterArguments, true);
+ const values = filter.getValues();
+
+ expect(values).toEqual([]);
+ expect(values.length).toBe(0);
+ });
+
+ it('should have empty array returned from "getValues" even when filter is not yet created', () => {
+ const values = filter.getValues();
+
+ expect(values).toEqual([]);
+ expect(values.length).toBe(0);
+ });
+
+ it('should create the multi-select filter with a default search term when passed as a filter argument', () => {
+ mockColumn.filter.collection = [{ value: 'male', label: 'male' }, { value: 'female', label: 'female' }];
+ const spyCallback = jest.spyOn(filterArguments, 'callback');
+
+ filterArguments.searchTerms = ['female'];
+ filter.init(filterArguments, true);
+ const filterBtnElm = divContainer.querySelector('.ms-parent.ms-filter.search-filter.filter-gender button.ms-choice');
+ const filterListElm = divContainer.querySelectorAll(`[name=filter-gender].ms-drop ul>li input[type=checkbox]`);
+ const filterFilledElms = divContainer.querySelectorAll('.ms-parent.ms-filter.search-filter.filter-gender.filled');
+ const filterOkElm = divContainer.querySelector(`[name=filter-gender].ms-drop .ms-ok-button`);
+ filterBtnElm.click();
+ filterOkElm.click();
+
+ expect(filterListElm.length).toBe(2);
+ expect(filterFilledElms.length).toBe(1);
+ expect(filterListElm[1].checked).toBe(true);
+ expect(spyCallback).toHaveBeenCalledWith(undefined, { columnDef: mockColumn, operator: 'IN', searchTerms: ['female'], shouldTriggerQuery: true });
+ });
+
+ it('should create the multi-select filter with a default search term when passed as a filter argument even with collection an array of strings', () => {
+ const spyCallback = jest.spyOn(filterArguments, 'callback');
+ mockColumn.filter.collection = ['male', 'female'];
+
+ filterArguments.searchTerms = ['female'];
+ filter.init(filterArguments, true);
+ const filterBtnElm = divContainer.querySelector('.ms-parent.ms-filter.search-filter.filter-gender button.ms-choice');
+ const filterListElm = divContainer.querySelectorAll(`[name=filter-gender].ms-drop ul>li input[type=checkbox]`);
+ const filterFilledElms = divContainer.querySelectorAll('.ms-parent.ms-filter.search-filter.filter-gender.filled');
+ const filterOkElm = divContainer.querySelector(`[name=filter-gender].ms-drop .ms-ok-button`);
+ filterBtnElm.click();
+ filterOkElm.click();
+
+ expect(filterListElm.length).toBe(2);
+ expect(filterFilledElms.length).toBe(1);
+ expect(filterListElm[1].checked).toBe(true);
+ expect(spyCallback).toHaveBeenCalledWith(undefined, { columnDef: mockColumn, operator: 'IN', searchTerms: ['female'], shouldTriggerQuery: true });
+ });
+
+ it('should create the multi-select filter and sort the string collection when "collectionSortBy" is set', () => {
+ mockColumn.filter = {
+ collection: ['other', 'male', 'female'],
+ collectionSortBy: {
+ sortDesc: true,
+ fieldType: FieldType.string
+ }
+ };
+
+ filter.init(filterArguments, true);
+ const filterBtnElm = divContainer.querySelector('.ms-parent.ms-filter.search-filter.filter-gender button.ms-choice');
+ const filterListElm = divContainer.querySelectorAll(`[name=filter-gender].ms-drop ul>li span`);
+ filterBtnElm.click();
+
+ expect(filterListElm.length).toBe(3);
+ expect(filterListElm[0].textContent).toBe('other');
+ expect(filterListElm[1].textContent).toBe('male');
+ expect(filterListElm[2].textContent).toBe('female');
+ });
+
+ it('should create the multi-select filter and sort the value/label pair collection when "collectionSortBy" is set', () => {
+ mockColumn.filter = {
+ collection: [{ value: 'other', description: 'other' }, { value: 'male', description: 'male' }, { value: 'female', description: 'female' }],
+ collectionSortBy: {
+ property: 'value',
+ sortDesc: false,
+ fieldType: FieldType.string
+ },
+ customStructure: {
+ value: 'value',
+ label: 'description',
+ },
+ };
+
+ filter.init(filterArguments, true);
+ const filterBtnElm = divContainer.querySelector('.ms-parent.ms-filter.search-filter.filter-gender button.ms-choice');
+ const filterListElm = divContainer.querySelectorAll(`[name=filter-gender].ms-drop ul>li span`);
+ filterBtnElm.click();
+
+ expect(filterListElm.length).toBe(3);
+ expect(filterListElm[0].textContent).toBe('female');
+ expect(filterListElm[1].textContent).toBe('male');
+ expect(filterListElm[2].textContent).toBe('other');
+ });
+
+ it('should create the multi-select filter and filter the string collection when "collectionFilterBy" is set', () => {
+ mockColumn.filter = {
+ collection: ['other', 'male', 'female'],
+ collectionFilterBy: {
+ operator: OperatorType.equal,
+ value: 'other'
+ }
+ };
+
+ filter.init(filterArguments, true);
+ const filterBtnElm = divContainer.querySelector('.ms-parent.ms-filter.search-filter.filter-gender button.ms-choice');
+ const filterListElm = divContainer.querySelectorAll(`[name=filter-gender].ms-drop ul>li span`);
+ filterBtnElm.click();
+
+ expect(filterListElm.length).toBe(1);
+ expect(filterListElm[0].textContent).toBe('other');
+ });
+
+ it('should create the multi-select filter and filter the value/label pair collection when "collectionFilterBy" is set', () => {
+ mockColumn.filter = {
+ collection: [{ value: 'other', description: 'other' }, { value: 'male', description: 'male' }, { value: 'female', description: 'female' }],
+ collectionFilterBy: [
+ { property: 'value', operator: OperatorType.notEqual, value: 'other' },
+ { property: 'value', operator: OperatorType.notEqual, value: 'male' }
+ ],
+ customStructure: {
+ value: 'value',
+ label: 'description',
+ },
+ };
+
+ filter.init(filterArguments, true);
+ const filterBtnElm = divContainer.querySelector('.ms-parent.ms-filter.search-filter.filter-gender button.ms-choice');
+ const filterListElm = divContainer.querySelectorAll(`[name=filter-gender].ms-drop ul>li span`);
+ filterBtnElm.click();
+
+ expect(filterListElm.length).toBe(1);
+ expect(filterListElm[0].textContent).toBe('female');
+ });
+
+ it('should create the multi-select filter and filter the value/label pair collection when "collectionFilterBy" is set and "filterResultAfterEachPass" is set to "merge"', () => {
+ mockColumn.filter = {
+ collection: [{ value: 'other', description: 'other' }, { value: 'male', description: 'male' }, { value: 'female', description: 'female' }],
+ collectionFilterBy: [
+ { property: 'value', operator: OperatorType.equal, value: 'other' },
+ { property: 'value', operator: OperatorType.equal, value: 'male' }
+ ],
+ collectionOptions: {
+ filterResultAfterEachPass: 'merge'
+ },
+ customStructure: {
+ value: 'value',
+ label: 'description',
+ },
+ };
+
+ filter.init(filterArguments, true);
+ const filterBtnElm = divContainer.querySelector('.ms-parent.ms-filter.search-filter.filter-gender button.ms-choice');
+ const filterListElm = divContainer.querySelectorAll(`[name=filter-gender].ms-drop ul>li span`);
+ filterBtnElm.click();
+
+ expect(filterListElm.length).toBe(2);
+ expect(filterListElm[0].textContent).toBe('other');
+ expect(filterListElm[1].textContent).toBe('male');
+ });
+
+ it('should create the multi-select filter with a value/label pair collection that is inside an object when "collectionInObjectProperty" is defined with a dot notation', () => {
+ mockColumn.filter = {
+ // @ts-ignore
+ collection: { deep: { myCollection: [{ value: 'other', description: 'other' }, { value: 'male', description: 'male' }, { value: 'female', description: 'female' }] } },
+ collectionOptions: {
+ collectionInObjectProperty: 'deep.myCollection'
+ },
+ customStructure: {
+ value: 'value',
+ label: 'description',
+ },
+ };
+
+ filter.init(filterArguments, true);
+ const filterBtnElm = divContainer.querySelector('.ms-parent.ms-filter.search-filter.filter-gender button.ms-choice');
+ const filterListElm = divContainer.querySelectorAll(`[name=filter-gender].ms-drop ul>li span`);
+ filterBtnElm.click();
+
+ expect(filterListElm.length).toBe(3);
+ expect(filterListElm[0].textContent).toBe('other');
+ expect(filterListElm[1].textContent).toBe('male');
+ expect(filterListElm[2].textContent).toBe('female');
+ });
+
+ it('should create the multi-select filter with a value/label pair collectionAsync that is inside an object when "collectionInObjectProperty" is defined with a dot notation', (done) => {
+ mockColumn.filter = {
+ collectionAsync: of({ deep: { myCollection: [{ value: 'other', description: 'other' }, { value: 'male', description: 'male' }, { value: 'female', description: 'female' }] } }),
+ collectionOptions: {
+ collectionInObjectProperty: 'deep.myCollection'
+ },
+ customStructure: {
+ value: 'value',
+ label: 'description',
+ },
+ };
+
+ filter.init(filterArguments, true);
+
+ setTimeout(() => {
+ const filterBtnElm = divContainer.querySelector('.ms-parent.ms-filter.search-filter.filter-gender button.ms-choice');
+ const filterListElm = divContainer.querySelectorAll(`[name=filter-gender].ms-drop ul>li span`);
+ filterBtnElm.click();
+
+ expect(filterListElm.length).toBe(3);
+ expect(filterListElm[0].textContent).toBe('other');
+ expect(filterListElm[1].textContent).toBe('male');
+ expect(filterListElm[2].textContent).toBe('female');
+ done();
+ });
+ });
+
+ it('should create the multi-select filter with a default search term when using "collectionAsync" as an Observable', (done) => {
+ const spyCallback = jest.spyOn(filterArguments, 'callback');
+ mockColumn.filter.collectionAsync = of(['male', 'female']);
+
+ filterArguments.searchTerms = ['female'];
+ filter.init(filterArguments, true);
+
+ setTimeout(() => {
+ const filterBtnElm = divContainer.querySelector('.ms-parent.ms-filter.search-filter.filter-gender button.ms-choice');
+ const filterListElm = divContainer.querySelectorAll(`[name=filter-gender].ms-drop ul>li input[type=checkbox]`);
+ const filterFilledElms = divContainer.querySelectorAll('.ms-parent.ms-filter.search-filter.filter-gender.filled');
+ const filterOkElm = divContainer.querySelector(`[name=filter-gender].ms-drop .ms-ok-button`);
+ filterBtnElm.click();
+ filterOkElm.click();
+
+ expect(filterListElm.length).toBe(2);
+ expect(filterFilledElms.length).toBe(1);
+ expect(filterListElm[1].checked).toBe(true);
+ expect(spyCallback).toHaveBeenCalledWith(undefined, { columnDef: mockColumn, operator: 'IN', searchTerms: ['female'], shouldTriggerQuery: true });
+ done();
+ });
+ });
+
+ it('should create the multi-select filter with a "collectionAsync" as an Observable and be able to call next on it', (done) => {
+ const mockCollection = ['male', 'female'];
+ mockColumn.filter.collectionAsync = of(mockCollection);
+
+ filterArguments.searchTerms = ['female'];
+ filter.init(filterArguments, true);
+
+ setTimeout(() => {
+ const filterBtnElm = divContainer.querySelector('.ms-parent.ms-filter.search-filter.filter-gender button.ms-choice');
+ const filterListElm = divContainer.querySelectorAll(`[name=filter-gender].ms-drop ul>li input[type=checkbox]`);
+ filterBtnElm.click();
+
+ expect(filterListElm.length).toBe(2);
+ expect(filterListElm[1].checked).toBe(true);
+
+ // after await (or timeout delay) we'll get the Subject Observable
+ mockCollection.push('other');
+ (mockColumn.filter.collectionAsync as Subject).next(mockCollection);
+
+ const filterUpdatedListElm = divContainer.querySelectorAll(`[name=filter-gender].ms-drop ul>li input[type=checkbox]`);
+ expect(filterUpdatedListElm.length).toBe(3);
+ done();
+ });
+ });
+
+ xit('should throw an error when "collectionAsync" Observable does not return a valid array', (done) => {
+ try {
+ mockColumn.filter.collectionAsync = of({ hello: 'world' });
+ filter.init(filterArguments, true);
+ } catch (e) {
+ expect(e.toString()).toContain(`Something went wrong while trying to pull the collection from the "collectionAsync" call in the Select Filter, the collection is not a valid array.`);
+ done();
+ }
+ });
+
+ it('should create the multi-select filter with a default search term and have the HTML rendered when "enableRenderHtml" is set', () => {
+ mockColumn.filter = {
+ enableRenderHtml: true,
+ collection: [{ value: true, label: 'True', labelPrefix: ` ` }, { value: false, label: 'False' }],
+ customStructure: {
+ value: 'isEffort',
+ label: 'label',
+ labelPrefix: 'labelPrefix',
+ },
+ };
+
+ filter.init(filterArguments, true);
+ const filterBtnElm = divContainer.querySelector('.ms-parent.ms-filter.search-filter.filter-gender button.ms-choice');
+ const filterListElm = divContainer.querySelectorAll(`[name=filter-gender].ms-drop ul>li span`);
+ filterBtnElm.click();
+
+ expect(filterListElm.length).toBe(2);
+ expect(filterListElm[0].innerHTML).toBe(' True');
+ });
+
+ it('should create the multi-select filter with a default search term and have the HTML rendered and sanitized when "enableRenderHtml" is set and has ` }, { value: false, label: 'False' }],
+ customStructure: {
+ value: 'isEffort',
+ label: 'label',
+ labelPrefix: 'labelPrefix',
+ },
+ };
+
+ filter.init(filterArguments, true);
+ const filterBtnElm = divContainer.querySelector('.ms-parent.ms-filter.search-filter.filter-gender button.ms-choice');
+ const filterListElm = divContainer.querySelectorAll(`[name=filter-gender].ms-drop ul>li span`);
+ filterBtnElm.click();
+
+ expect(filterListElm.length).toBe(2);
+ expect(filterListElm[0].innerHTML).toBe(' True');
+ });
+
+ it('should create the multi-select filter with a blank entry at the beginning of the collection when "addBlankEntry" is set in the "collectionOptions" property', () => {
+ filterArguments.searchTerms = ['female'];
+ mockColumn.filter.collection = [{ value: 'male', label: 'male' }, { value: 'female', label: 'female' }];
+ mockColumn.filter.collectionOptions = { addBlankEntry: true };
+ const spyCallback = jest.spyOn(filterArguments, 'callback');
+
+ filter.init(filterArguments, true);
+ const filterBtnElm = divContainer.querySelector('.ms-parent.ms-filter.search-filter.filter-gender button.ms-choice');
+ const filterListElm = divContainer.querySelectorAll(`[name=filter-gender].ms-drop ul>li input[type=checkbox]`);
+ const filterFilledElms = divContainer.querySelectorAll('.ms-parent.ms-filter.search-filter.filter-gender.filled');
+ const filterOkElm = divContainer.querySelector(`[name=filter-gender].ms-drop .ms-ok-button`);
+ filterBtnElm.click();
+ filterOkElm.click();
+
+ expect(filterListElm.length).toBe(3);
+ expect(filterFilledElms.length).toBe(1);
+ expect(filterListElm[2].checked).toBe(true);
+ expect(spyCallback).toHaveBeenCalledWith(undefined, { columnDef: mockColumn, operator: 'IN', searchTerms: ['female'], shouldTriggerQuery: true });
+ });
+
+ it('should trigger a callback with the clear filter set when calling the "clear" method', () => {
+ filterArguments.searchTerms = ['female'];
+ mockColumn.filter.collection = [{ value: 'male', label: 'male' }, { value: 'female', label: 'female' }];
+ const spyCallback = jest.spyOn(filterArguments, 'callback');
+
+ filter.init(filterArguments, true);
+ filter.clear();
+ const filterFilledElms = divContainer.querySelectorAll('.ms-parent.ms-filter.search-filter.filter-gender.filled');
+
+ expect(filter.searchTerms.length).toBe(0);
+ expect(filterFilledElms.length).toBe(0);
+ expect(spyCallback).toHaveBeenCalledWith(undefined, { columnDef: mockColumn, clearFilterTriggered: true, shouldTriggerQuery: true });
+ });
+
+ it('should trigger a callback with the clear filter but without querying when when calling the "clear" method with False as argument', () => {
+ mockColumn.filter.collection = [{ value: 'male', label: 'male' }, { value: 'female', label: 'female' }];
+ const spyCallback = jest.spyOn(filterArguments, 'callback');
+
+ filterArguments.searchTerms = ['female'];
+ filter.init(filterArguments, true);
+ filter.clear(false);
+ const filterFilledElms = divContainer.querySelectorAll('.ms-parent.ms-filter.search-filter.filter-gender.filled');
+
+ expect(filter.searchTerms.length).toBe(0);
+ expect(filterFilledElms.length).toBe(0);
+ expect(spyCallback).toHaveBeenCalledWith(undefined, { columnDef: mockColumn, clearFilterTriggered: true, shouldTriggerQuery: false });
+ });
+
+ it('should work with English locale when locale is changed', (done) => {
+ translate.use('en');
+ gridOptionMock.enableTranslate = true;
+ mockColumn.filter = {
+ enableTranslateLabel: true,
+ collection: [
+ { value: 'other', labelKey: 'OTHER' },
+ { value: 'male', labelKey: 'MALE' },
+ { value: 'female', labelKey: 'FEMALE' }
+ ],
+ filterOptions: { minimumCountSelected: 1 }
+ };
+
+ filterArguments.searchTerms = ['male', 'female'];
+ filter.init(filterArguments, true);
+
+ setTimeout(() => {
+ const filterSelectAllElm = divContainer.querySelector('.filter-gender .ms-select-all label span');
+ const filterBtnElm = divContainer.querySelector('.ms-parent.ms-filter.search-filter.filter-gender button.ms-choice');
+ const filterListElm = divContainer.querySelectorAll(`[name=filter-gender].ms-drop ul>li span`);
+ const filterOkElm = divContainer.querySelector(`[name=filter-gender].ms-drop .ms-ok-button`);
+ const filterParentElm = divContainer.querySelector(`.ms-parent.ms-filter.search-filter.filter-gender button`);
+ filterBtnElm.click();
+
+ expect(filterListElm.length).toBe(3);
+ expect(filterListElm[0].textContent).toBe('Other');
+ expect(filterListElm[1].textContent).toBe('Male');
+ expect(filterListElm[2].textContent).toBe('Female');
+ expect(filterOkElm.textContent).toBe('OK');
+ expect(filterSelectAllElm.textContent).toBe('Select All');
+ expect(filterParentElm.textContent).toBe('2 of 3 selected');
+ done();
+ });
+ });
+
+ it('should work with French locale when locale is changed', (done) => {
+ translate.use('fr');
+ gridOptionMock.enableTranslate = true;
+ mockColumn.filter = {
+ enableTranslateLabel: true,
+ collection: [
+ { value: 'other', labelKey: 'OTHER' },
+ { value: 'male', labelKey: 'MALE' },
+ { value: 'female', labelKey: 'FEMALE' }
+ ],
+ filterOptions: { minimumCountSelected: 1 }
+ };
+
+ filterArguments.searchTerms = ['male', 'female'];
+ filter.init(filterArguments, true);
+ setTimeout(() => {
+ const filterSelectAllElm = divContainer.querySelector('.filter-gender .ms-select-all label span');
+ const filterBtnElm = divContainer.querySelector('.ms-parent.ms-filter.search-filter.filter-gender button.ms-choice');
+ const filterListElm = divContainer.querySelectorAll(`[name=filter-gender].ms-drop ul>li span`);
+ const filterOkElm = divContainer.querySelector(`[name=filter-gender].ms-drop .ms-ok-button`);
+ const filterParentElm = divContainer.querySelector(`.ms-parent.ms-filter.search-filter.filter-gender button`);
+ filterBtnElm.click();
+
+ expect(filterListElm.length).toBe(3);
+ expect(filterListElm[0].textContent).toBe('Autre');
+ expect(filterListElm[1].textContent).toBe('Mâle');
+ expect(filterListElm[2].textContent).toBe('Femme');
+ expect(filterOkElm.textContent).toBe('Terminé');
+ expect(filterSelectAllElm.textContent).toBe('Sélectionner tout');
+ expect(filterParentElm.textContent).toBe('2 de 3 sélectionnés');
+ done();
+ });
+ });
+});
diff --git a/src/app/modules/angular-slickgrid/filters/__tests__/selectFilterNoLibLoaded.spec.ts b/src/app/modules/angular-slickgrid/filters/__tests__/selectFilterNoLibLoaded.spec.ts
new file mode 100644
index 000000000..42b0378a8
--- /dev/null
+++ b/src/app/modules/angular-slickgrid/filters/__tests__/selectFilterNoLibLoaded.spec.ts
@@ -0,0 +1,76 @@
+// import 3rd party lib multiple-select for the tests
+// import '../../../../../assets/lib/multiple-select/multiple-select';
+
+import { TestBed } from '@angular/core/testing';
+import { TranslateService, TranslateModule } from '@ngx-translate/core';
+import { Column, FilterArguments, GridOption } from '../../models';
+import { CollectionService } from '../../services/collection.service';
+import { Filters } from '..';
+import { SelectFilter } from '../selectFilter';
+
+const containerId = 'demo-container';
+
+// define a container to simulate the grid container
+const template = `
`;
+
+const gridOptionMock = {
+ enableFiltering: true,
+ enableFilterTrimWhiteSpace: true,
+} as GridOption;
+
+const collectionServiceStub = {
+
+} as CollectionService;
+
+const gridStub = {
+ getOptions: () => gridOptionMock,
+ getColumns: jest.fn(),
+ getHeaderRowColumn: jest.fn(),
+ render: jest.fn(),
+};
+
+describe('SelectFilter', () => {
+ let divContainer: HTMLDivElement;
+ let filter: SelectFilter;
+ let filterArguments: FilterArguments;
+ let spyGetHeaderRow;
+ let mockColumn: Column;
+ let translate: TranslateService;
+
+ beforeEach(() => {
+ divContainer = document.createElement('div');
+ divContainer.innerHTML = template;
+ document.body.appendChild(divContainer);
+ spyGetHeaderRow = jest.spyOn(gridStub, 'getHeaderRowColumn').mockReturnValue(divContainer);
+
+ mockColumn = {
+ id: 'gender', field: 'gender', filterable: true,
+ filter: {
+ model: Filters.select,
+ collection: [{ value: '', label: '' }, { value: 'male', label: 'male' }, { value: 'female', label: 'female' }]
+ }
+ };
+ filterArguments = {
+ grid: gridStub,
+ columnDef: mockColumn,
+ callback: jest.fn()
+ };
+
+ TestBed.configureTestingModule({
+ providers: [{ provide: CollectionService, useValue: collectionServiceStub }],
+ imports: [TranslateModule.forRoot()]
+ });
+ translate = TestBed.get(TranslateService);
+ translate.setDefaultLang('en');
+
+ filter = new SelectFilter(translate, collectionServiceStub);
+ });
+
+ afterEach(() => {
+ filter.destroy();
+ });
+
+ it('should throw an error when multiple-select.js is not provided or imported', () => {
+ expect(() => filter.init(filterArguments, true)).toThrowError(`multiple-select.js was not found, make sure to modify your "angular-cli.json" file`);
+ });
+});
diff --git a/src/app/modules/angular-slickgrid/filters/__tests__/singleSelectFilter.spec.ts b/src/app/modules/angular-slickgrid/filters/__tests__/singleSelectFilter.spec.ts
new file mode 100644
index 000000000..b340ae896
--- /dev/null
+++ b/src/app/modules/angular-slickgrid/filters/__tests__/singleSelectFilter.spec.ts
@@ -0,0 +1,170 @@
+// import 3rd party lib multiple-select for the tests
+import '../../../../../assets/lib/multiple-select/multiple-select';
+
+import { TestBed } from '@angular/core/testing';
+import { TranslateService, TranslateModule } from '@ngx-translate/core';
+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';
+
+// define a
container to simulate the grid container
+const template = `
`;
+
+const gridOptionMock = {
+ enableFiltering: true,
+ enableFilterTrimWhiteSpace: true,
+} as GridOption;
+
+const gridStub = {
+ getOptions: () => gridOptionMock,
+ getColumns: jest.fn(),
+ getHeaderRowColumn: jest.fn(),
+ render: jest.fn(),
+};
+
+describe('SingleSelectFilter', () => {
+ let divContainer: HTMLDivElement;
+ let filter: SingleSelectFilter;
+ let filterArguments: FilterArguments;
+ let spyGetHeaderRow;
+ let mockColumn: Column;
+ let collectionService: CollectionService;
+ let translate: TranslateService;
+
+ beforeEach(() => {
+ divContainer = document.createElement('div');
+ divContainer.innerHTML = template;
+ document.body.appendChild(divContainer);
+ spyGetHeaderRow = jest.spyOn(gridStub, 'getHeaderRowColumn').mockReturnValue(divContainer);
+
+ mockColumn = {
+ id: 'gender', field: 'gender', filterable: true,
+ filter: {
+ model: Filters.singleSelect,
+ }
+ };
+ filterArguments = {
+ grid: gridStub,
+ columnDef: mockColumn,
+ callback: jest.fn()
+ };
+
+ TestBed.configureTestingModule({
+ providers: [CollectionService],
+ imports: [TranslateModule.forRoot()]
+ });
+ collectionService = TestBed.get(CollectionService);
+ translate = TestBed.get(TranslateService);
+
+ translate.setTranslation('en', {
+ FEMALE: 'Female',
+ MALE: 'Male',
+ OTHER: 'Other',
+ });
+ translate.setTranslation('fr', {
+ FEMALE: 'Femme',
+ MALE: 'Mâle',
+ OTHER: 'Autre',
+ });
+ translate.setDefaultLang('en');
+
+ filter = new SingleSelectFilter(translate, collectionService);
+ });
+
+ afterEach(() => {
+ filter.destroy();
+ });
+
+ it('should be a single-select filter', () => {
+ mockColumn.filter.collection = [{ value: 'male', label: 'male' }, { value: 'female', label: 'female' }];
+ filter = new SingleSelectFilter(translate, collectionService);
+ filter.init(filterArguments, true);
+ const filterCount = divContainer.querySelectorAll('select.ms-filter.search-filter.filter-gender').length;
+
+ expect(spyGetHeaderRow).toHaveBeenCalled();
+ expect(filterCount).toBe(1);
+ expect(filter.isMultipleSelect).toBe(false);
+ });
+
+ 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' }];
+
+ filter.init(filterArguments, true);
+ const filterBtnElm = divContainer.querySelector
('.ms-parent.ms-filter.search-filter.filter-gender button.ms-choice');
+ const filterListElm = divContainer.querySelectorAll(`[name=filter-gender].ms-drop ul>li input[type=radio]`);
+ filterBtnElm.click();
+
+ filterListElm[1].dispatchEvent(new CustomEvent('click'));
+
+ const filterFilledElms = divContainer.querySelectorAll('.ms-parent.ms-filter.search-filter.filter-gender.filled');
+ expect(filterListElm.length).toBe(2);
+ expect(filterFilledElms.length).toBe(1);
+ expect(spyCallback).toHaveBeenCalledWith(undefined, { columnDef: mockColumn, operator: 'EQ', searchTerms: ['female'], shouldTriggerQuery: true });
+ });
+
+ it('should work with English locale when locale is changed', (done) => {
+ translate.use('en');
+ gridOptionMock.enableTranslate = true;
+ mockColumn.filter = {
+ enableTranslateLabel: true,
+ collection: [
+ { value: 'other', labelKey: 'OTHER' },
+ { value: 'male', labelKey: 'MALE' },
+ { value: 'female', labelKey: 'FEMALE' }
+ ],
+ filterOptions: { minimumCountSelected: 1 }
+ };
+
+ filterArguments.searchTerms = ['male', 'female'];
+ filter.init(filterArguments, true);
+
+ setTimeout(() => {
+ const filterBtnElm = divContainer.querySelector('.ms-parent.ms-filter.search-filter.filter-gender button.ms-choice');
+ const filterListElm = divContainer.querySelectorAll(`[name=filter-gender].ms-drop ul>li span`);
+ const filterOkElm = divContainer.querySelectorAll(`[name=filter-gender].ms-drop .ms-ok-button`);
+ const filterSelectAllElm = divContainer.querySelectorAll('.filter-gender .ms-select-all label span');
+ filterBtnElm.click();
+
+ expect(filterOkElm.length).toBe(0);
+ expect(filterSelectAllElm.length).toBe(0);
+ expect(filterListElm.length).toBe(3);
+ expect(filterListElm[0].textContent).toBe('Other');
+ expect(filterListElm[1].textContent).toBe('Male');
+ expect(filterListElm[2].textContent).toBe('Female');
+ done();
+ });
+ });
+
+ it('should work with French locale when locale is changed', (done) => {
+ translate.use('fr');
+ gridOptionMock.enableTranslate = true;
+ mockColumn.filter = {
+ enableTranslateLabel: true,
+ collection: [
+ { value: 'other', labelKey: 'OTHER' },
+ { value: 'male', labelKey: 'MALE' },
+ { value: 'female', labelKey: 'FEMALE' }
+ ],
+ filterOptions: { minimumCountSelected: 1 }
+ };
+
+ filterArguments.searchTerms = ['male', 'female'];
+ filter.init(filterArguments, true);
+ setTimeout(() => {
+ const filterBtnElm = divContainer.querySelector('.ms-parent.ms-filter.search-filter.filter-gender button.ms-choice');
+ const filterListElm = divContainer.querySelectorAll(`[name=filter-gender].ms-drop ul>li span`);
+ filterBtnElm.click();
+
+ expect(filterListElm.length).toBe(3);
+ expect(filterListElm[0].textContent).toBe('Autre');
+ expect(filterListElm[1].textContent).toBe('Mâle');
+ expect(filterListElm[2].textContent).toBe('Femme');
+ done();
+ });
+ });
+});
diff --git a/src/app/modules/angular-slickgrid/filters/__tests__/sliderRangeFilter.spec.ts b/src/app/modules/angular-slickgrid/filters/__tests__/sliderRangeFilter.spec.ts
index a9a2cbae6..eda9168ca 100644
--- a/src/app/modules/angular-slickgrid/filters/__tests__/sliderRangeFilter.spec.ts
+++ b/src/app/modules/angular-slickgrid/filters/__tests__/sliderRangeFilter.spec.ts
@@ -51,21 +51,23 @@ describe('SliderRangeFilter', () => {
expect(() => filter.init(null)).toThrowError('[Angular-SlickGrid] A filter must always have an "init()" with valid arguments.');
});
- it('should throw an error when trying override the slider "change" method', () => {
+ it('should throw an error when trying override the slider "change" method', (done) => {
try {
mockColumn.filter.filterOptions = { change: () => { } } as JQueryUiSliderOption;
filter.init(filterArguments);
} catch (e) {
expect(e.toString()).toContain(`[Angular-Slickgrid] You cannot override the "change" and/or the "slide" callback methods`);
+ done();
}
});
- it('should throw an error when trying override the slider "slide" method', () => {
+ it('should throw an error when trying override the slider "slide" method', (done) => {
try {
mockColumn.filter.filterOptions = { slide: () => { } } as JQueryUiSliderOption;
filter.init(filterArguments);
} catch (e) {
expect(e.toString()).toContain(`[Angular-Slickgrid] You cannot override the "change" and/or the "slide" callback methods`);
+ done();
}
});
diff --git a/src/app/modules/angular-slickgrid/filters/autoCompleteFilter.ts b/src/app/modules/angular-slickgrid/filters/autoCompleteFilter.ts
index 6422624e6..517d56782 100644
--- a/src/app/modules/angular-slickgrid/filters/autoCompleteFilter.ts
+++ b/src/app/modules/angular-slickgrid/filters/autoCompleteFilter.ts
@@ -211,8 +211,9 @@ export class AutoCompleteFilter implements Filter {
* and reinitialize filter collection with this new collection
*/
protected renderDomElementFromCollectionAsync(collection) {
- if (this.collectionOptions && this.collectionOptions.collectionInObjectProperty) {
- collection = getDescendantProperty(collection, this.collectionOptions.collectionInObjectProperty);
+ if (this.collectionOptions && (this.collectionOptions.collectionInsideObjectProperty || this.collectionOptions.collectionInObjectProperty)) {
+ const collectionInsideObjectProperty = this.collectionOptions.collectionInsideObjectProperty || this.collectionOptions.collectionInObjectProperty;
+ collection = getDescendantProperty(collection, collectionInsideObjectProperty);
}
if (!Array.isArray(collection)) {
throw new Error('Something went wrong while trying to pull the collection from the "collectionAsync" call in the AutoComplete Filter, the collection is not a valid array.');
@@ -227,8 +228,9 @@ export class AutoCompleteFilter implements Filter {
}
protected renderDomElement(collection: any[]) {
- if (!Array.isArray(collection) && this.collectionOptions && this.collectionOptions.collectionInObjectProperty) {
- collection = getDescendantProperty(collection, this.collectionOptions.collectionInObjectProperty);
+ if (!Array.isArray(collection) && this.collectionOptions && (this.collectionOptions.collectionInsideObjectProperty || this.collectionOptions.collectionInObjectProperty)) {
+ const collectionInsideObjectProperty = this.collectionOptions.collectionInsideObjectProperty || this.collectionOptions.collectionInObjectProperty;
+ collection = getDescendantProperty(collection, collectionInsideObjectProperty);
}
if (!Array.isArray(collection)) {
throw new Error('The "collection" passed to the Autocomplete Filter is not a valid array');
diff --git a/src/app/modules/angular-slickgrid/filters/selectFilter.ts b/src/app/modules/angular-slickgrid/filters/selectFilter.ts
index e6cbca9d1..a6b28a35f 100644
--- a/src/app/modules/angular-slickgrid/filters/selectFilter.ts
+++ b/src/app/modules/angular-slickgrid/filters/selectFilter.ts
@@ -28,6 +28,7 @@ declare var $: any;
export class SelectFilter implements Filter {
private _isFilterFirstRender = true;
+ private _isMultipleSelect = true;
private _locales: Locale;
private _shouldTriggerQuery = true;
@@ -57,7 +58,9 @@ export class SelectFilter implements Filter {
/**
* Initialize the Filter
*/
- constructor(@Optional() protected translate: TranslateService, protected collectionService: CollectionService, protected isMultipleSelect = true) { }
+ constructor(@Optional() protected translate: TranslateService, protected collectionService: CollectionService, isMultipleSelect = true) {
+ this._isMultipleSelect = isMultipleSelect;
+ }
/** Getter for the Column Filter itself */
protected get columnFilter(): ColumnFilter {
@@ -79,6 +82,11 @@ export class SelectFilter implements Filter {
return (this.grid && this.grid.getOptions) ? this.grid.getOptions() : {};
}
+ /** Getter to know if the current filter is a multiple-select (false means it's a single select) */
+ get isMultipleSelect(): boolean {
+ return this._isMultipleSelect;
+ }
+
/** Getter for the filter operator */
get operator(): OperatorType | OperatorString {
if (this.columnDef && this.columnDef.filter && this.columnDef.filter.operator) {
@@ -91,6 +99,9 @@ export class SelectFilter implements Filter {
* Initialize the filter template
*/
init(args: FilterArguments, isFilterFirstRender: boolean) {
+ if (!args) {
+ throw new Error('[Angular-SlickGrid] A filter must always have an "init()" with valid arguments.');
+ }
this._isFilterFirstRender = isFilterFirstRender;
this.grid = args.grid;
this.callback = args.callback;
@@ -109,7 +120,7 @@ export class SelectFilter implements Filter {
this.valueName = this.customStructure && this.customStructure.value || 'value';
if (this.enableTranslateLabel && !this.gridOptions.enableTranslate && (!this.translate || typeof this.translate.instant !== 'function')) {
- throw new Error(`[select-editor] The ngx-translate TranslateService is required for the Select Filter to work correctly`);
+ throw new Error(`[select-filter] The ngx-translate TranslateService is required for the Select Filter to work correctly when "enableTranslateLabel" is set.`);
}
// get locales provided by user in forRoot or else use default English locales via the Constants
@@ -171,11 +182,22 @@ export class SelectFilter implements Filter {
this.subscriptions = unsubscribeAllObservables(this.subscriptions);
}
+ /**
+ * Get selected values retrieved from the multiple-selected element
+ * @params selected items
+ */
+ getValues(): any[] {
+ if (this.$filterElm && typeof this.$filterElm.multipleSelect === 'function') {
+ return this.$filterElm.multipleSelect('getSelects');
+ }
+ return [];
+ }
+
/**
* Set value(s) on the DOM element
*/
setValues(values: SearchTerm | SearchTerm[]) {
- if (values) {
+ if (values && this.$filterElm && typeof this.$filterElm.multipleSelect === 'function') {
values = Array.isArray(values) ? values : [values];
this.$filterElm.multipleSelect('setSelects', values);
}
@@ -248,8 +270,9 @@ export class SelectFilter implements Filter {
* and reinitialize filter collection with this new collection
*/
protected renderDomElementFromCollectionAsync(collection) {
- if (this.collectionOptions && this.collectionOptions.collectionInObjectProperty) {
- collection = getDescendantProperty(collection, this.collectionOptions.collectionInObjectProperty);
+ if (this.collectionOptions && (this.collectionOptions.collectionInsideObjectProperty || this.collectionOptions.collectionInObjectProperty)) {
+ const collectionInsideObjectProperty = this.collectionOptions.collectionInsideObjectProperty || this.collectionOptions.collectionInObjectProperty;
+ collection = getDescendantProperty(collection, collectionInsideObjectProperty);
}
if (!Array.isArray(collection)) {
throw new Error('Something went wrong while trying to pull the collection from the "collectionAsync" call in the Select Filter, the collection is not a valid array.');
@@ -264,11 +287,12 @@ export class SelectFilter implements Filter {
}
protected renderDomElement(collection) {
- if (!Array.isArray(collection) && this.collectionOptions && this.collectionOptions.collectionInObjectProperty) {
- collection = getDescendantProperty(collection, this.collectionOptions.collectionInObjectProperty);
+ if (!Array.isArray(collection) && this.collectionOptions && (this.collectionOptions.collectionInsideObjectProperty || this.collectionOptions.collectionInObjectProperty)) {
+ const collectionInsideObjectProperty = this.collectionOptions.collectionInsideObjectProperty || this.collectionOptions.collectionInObjectProperty;
+ collection = getDescendantProperty(collection, collectionInsideObjectProperty);
}
if (!Array.isArray(collection)) {
- throw new Error('The "collection" passed to the Select Filter is not a valid array');
+ throw new Error('The "collection" passed to the Select Filter is not a valid array.');
}
// user can optionally add a blank entry at the beginning of the collection
@@ -295,6 +319,7 @@ export class SelectFilter implements Filter {
let options = '';
const fieldId = this.columnDef && this.columnDef.id;
const separatorBetweenLabels = this.collectionOptions && this.collectionOptions.separatorBetweenTextLabels || '';
+ const isEnableTranslate = this.gridOptions && this.gridOptions.enableTranslate;
const isRenderHtmlEnabled = this.columnFilter && this.columnFilter.enableRenderHtml || false;
const sanitizedOptions = this.gridOptions && this.gridOptions.sanitizeHtmlOptions || {};
@@ -318,16 +343,16 @@ export class SelectFilter implements Filter {
}
const labelKey = (option.labelKey || option[this.labelName]) as string;
const selected = (searchTerms.findIndex((term) => term === option[this.valueName]) >= 0) ? 'selected' : '';
- const labelText = ((option.labelKey || this.enableTranslateLabel) && labelKey && this.gridOptions.enableTranslate) ? this.translate && this.translate.instant(labelKey || ' ') : labelKey;
+ const labelText = ((option.labelKey || this.enableTranslateLabel) && labelKey && isEnableTranslate) ? this.translate && this.translate.instant(labelKey || ' ') : labelKey;
let prefixText = option[this.labelPrefixName] || '';
let suffixText = option[this.labelSuffixName] || '';
let optionLabel = option.hasOwnProperty(this.optionLabel) ? option[this.optionLabel] : '';
optionLabel = optionLabel.toString().replace(/\"/g, '\''); // replace double quotes by single quotes to avoid interfering with regular html
// also translate prefix/suffix if enableTranslateLabel is true and text is a string
- prefixText = (this.enableTranslateLabel && this.gridOptions.enableTranslate && prefixText && typeof prefixText === 'string') ? this.translate && this.translate.instant(prefixText || ' ') : prefixText;
- suffixText = (this.enableTranslateLabel && this.gridOptions.enableTranslate && suffixText && typeof suffixText === 'string') ? this.translate && this.translate.instant(suffixText || ' ') : suffixText;
- optionLabel = (this.enableTranslateLabel && this.gridOptions.enableTranslate && optionLabel && typeof optionLabel === 'string') ? this.translate && this.translate.instant(optionLabel || ' ') : optionLabel;
+ prefixText = (this.enableTranslateLabel && isEnableTranslate && prefixText && typeof prefixText === 'string') ? this.translate && this.translate.instant(prefixText || ' ') : prefixText;
+ suffixText = (this.enableTranslateLabel && isEnableTranslate && suffixText && typeof suffixText === 'string') ? this.translate && this.translate.instant(suffixText || ' ') : suffixText;
+ optionLabel = (this.enableTranslateLabel && isEnableTranslate && optionLabel && typeof optionLabel === 'string') ? this.translate && this.translate.instant(optionLabel || ' ') : optionLabel;
// add to a temp array for joining purpose and filter out empty text
const tmpOptionArray = [prefixText, labelText !== undefined ? labelText.toString() : labelText, suffixText].filter((text) => text);
@@ -424,14 +449,14 @@ export class SelectFilter implements Filter {
single: true,
textTemplate: ($elm) => {
- // render HTML code or not, by default it is sanitized and won't be rendered
+ // are we rendering HTML code? by default it is sanitized and won't be rendered
const isRenderHtmlEnabled = this.columnDef && this.columnDef.filter && this.columnDef.filter.enableRenderHtml || false;
return isRenderHtmlEnabled ? $elm.text() : $elm.html();
},
onClose: () => {
// we will subscribe to the onClose event for triggering our callback
// also add/remove "filled" class for styling purposes
- const selectedItems = this.$filterElm.multipleSelect('getSelects');
+ const selectedItems = this.getValues();
if (Array.isArray(selectedItems) && selectedItems.length > 1 || (selectedItems.length === 1 && selectedItems[0] !== '')) {
this.isFilled = true;
this.$filterElm.addClass('filled').siblings('div .search-filter').addClass('filled');
@@ -441,6 +466,7 @@ export class SelectFilter implements Filter {
this.$filterElm.siblings('div .search-filter').removeClass('filled');
}
+ this.searchTerms = selectedItems;
this.callback(undefined, { columnDef: this.columnDef, operator: this.operator, searchTerms: selectedItems, shouldTriggerQuery: this._shouldTriggerQuery });
// reset flag for next use
this._shouldTriggerQuery = true;
diff --git a/src/app/modules/angular-slickgrid/models/collectionFilterBy.interface.ts b/src/app/modules/angular-slickgrid/models/collectionFilterBy.interface.ts
index 0cffd52a8..a537b7883 100644
--- a/src/app/modules/angular-slickgrid/models/collectionFilterBy.interface.ts
+++ b/src/app/modules/angular-slickgrid/models/collectionFilterBy.interface.ts
@@ -1,7 +1,12 @@
import { OperatorType } from './operatorType.enum';
export interface CollectionFilterBy {
- property: string;
+ /** Object Property name when the collection is an array of objects */
+ property?: string;
+
+ /** Value to filter from the collection */
value: any;
- operator?: OperatorType.equal | OperatorType.notEqual | OperatorType.contains | OperatorType.notContains;
+
+ /** Operator to use when filtering the value from the collection, we can only use */
+ operator?: OperatorType.equal | OperatorType.notEqual | OperatorType.contains | OperatorType.notContains | 'EQ' | 'NE' | 'Contains' | 'NOT_CONTAINS' | 'Not_Contains';
}
diff --git a/src/app/modules/angular-slickgrid/models/collectionOption.interface.ts b/src/app/modules/angular-slickgrid/models/collectionOption.interface.ts
index ec57ebb8c..de9f36b34 100644
--- a/src/app/modules/angular-slickgrid/models/collectionOption.interface.ts
+++ b/src/app/modules/angular-slickgrid/models/collectionOption.interface.ts
@@ -8,15 +8,18 @@ export interface CollectionOption {
*/
addBlankEntry?: boolean;
+ /** @deprecated please use "collectionInsideObjectProperty" instead */
+ collectionInObjectProperty?: string;
+
/**
* When the collection is inside an object descendant property
* we can optionally pass a dot (.) notation string to pull the collection from an object property.
- * For example if our output data is:
+ * For example if our output data returned by the collectionAsync is inside an object of the following format:
* myData = { someProperty: { myCollection: [] }, otherProperty: 'something' }
* We can pass the dot notation string
- * collectionInObjectProperty: 'someProperty.myCollection'
+ * collectionInsideObjectProperty: 'someProperty.myCollection'
*/
- collectionInObjectProperty?: string;
+ collectionInsideObjectProperty?: string;
/**
* Defaults to "chain", when using multiple "collectionFilterBy", do we want to "merge" or "chain" the result after each pass?
diff --git a/src/app/modules/angular-slickgrid/models/collectionSortBy.interface.ts b/src/app/modules/angular-slickgrid/models/collectionSortBy.interface.ts
index 8bec31efd..e978cb656 100644
--- a/src/app/modules/angular-slickgrid/models/collectionSortBy.interface.ts
+++ b/src/app/modules/angular-slickgrid/models/collectionSortBy.interface.ts
@@ -1,7 +1,12 @@
-import { FieldType, OperatorType } from './index';
+import { FieldType } from './index';
export interface CollectionSortBy {
- property: string;
+ /** Object Property name when the collection is an array of objects */
+ property?: string;
+
+ /** defaults to false, is it in a descending order? */
sortDesc?: boolean;
+
+ /** Field type of the value or object value content */
fieldType?: FieldType;
}
diff --git a/src/app/modules/angular-slickgrid/models/operatorString.ts b/src/app/modules/angular-slickgrid/models/operatorString.ts
index 5c405a5e1..4dbe9f939 100644
--- a/src/app/modules/angular-slickgrid/models/operatorString.ts
+++ b/src/app/modules/angular-slickgrid/models/operatorString.ts
@@ -1 +1 @@
-export type OperatorString = '' | '<>' | '!=' | '=' | '==' | '>' | '>=' | '<' | '<=' | '*' | 'a*' | '*z' | 'EQ' | 'GE' | 'GT' | 'NE' | 'LE' | 'LT' | 'IN' | 'NIN' | 'NOT_IN' | 'IN_CONTAINS' | 'NIN_CONTAINS' | 'NOT_IN_CONTAINS' | 'NOT_CONTAINS' | 'Not_Contains' | 'Contains' | 'EndsWith' | 'StartsWith' | 'RangeInclusive' | 'RangeExclusive';
+export type OperatorString = '' | '<>' | '!=' | '=' | '==' | '>' | '>=' | '<' | '<=' | '*' | 'a*' | '*z' | 'EQ' | 'GE' | 'GT' | 'NE' | 'LE' | 'LT' | 'IN' | 'NIN' | 'NOT_IN' | 'IN_CONTAINS' | 'NIN_CONTAINS' | 'NOT_IN_CONTAINS' | 'NOT_CONTAINS' | 'Not_Contains' | 'CONTAINS' | 'Contains' | 'EndsWith' | 'StartsWith' | 'RangeInclusive' | 'RangeExclusive';
diff --git a/src/app/modules/angular-slickgrid/services/__tests__/collection.service.spec.ts b/src/app/modules/angular-slickgrid/services/__tests__/collection.service.spec.ts
index 72d50e31c..dfac12c8e 100644
--- a/src/app/modules/angular-slickgrid/services/__tests__/collection.service.spec.ts
+++ b/src/app/modules/angular-slickgrid/services/__tests__/collection.service.spec.ts
@@ -12,6 +12,7 @@ import {
describe('CollectionService', () => {
let collection = [];
+ let stringCollection = [];
let translate: TranslateService;
let service: CollectionService;
@@ -51,6 +52,8 @@ describe('CollectionService', () => {
{ firstName: 'John', lastName: 'Doe', position: 'DEVELOPER', order: 4 },
{ firstName: 'John Foo', lastName: 'Bar', position: 'SALES_REP', order: 8 },
];
+
+ stringCollection = ['John', 'Jane', 'Ava Luna', '', 'Bob', 'John', null, 'John Foo'];
});
afterEach(() => {
@@ -61,220 +64,280 @@ describe('CollectionService', () => {
expect(service).toBeTruthy();
});
- describe('filterCollection method', () => {
- it('should return on the columns that have firstName filled when the filtered value is actually undefined but will be checked as an empty string', () => {
- const filterBy = { property: 'firstName', operator: 'EQ', value: undefined } as CollectionFilterBy;
+ describe('Collection of Objects', () => {
+ describe('filterCollection method', () => {
+ it('should return on the columns that have firstName filled when the filtered value is actually undefined but will be checked as an empty string', () => {
+ const filterBy = { property: 'firstName', operator: 'EQ', value: undefined } as CollectionFilterBy;
+
+ const result = service.filterCollection(collection, filterBy);
+
+ expect(result).toEqual([
+ { firstName: '', lastName: 'Cash', position: 'SALES_REP', order: 3 }
+ ]);
+ });
+
+ it('should return an array without certain filtered values', () => {
+ const filterBy = { property: 'firstName', operator: 'NE', value: 'John' } as CollectionFilterBy;
+
+ const result = service.filterCollection(collection, filterBy);
+
+ expect(result).toEqual([
+ { firstName: 'Jane', lastName: 'Doe', position: 'FINANCE_MANAGER', order: 1 },
+ { firstName: 'Ava Luna', lastName: null, position: 'HUMAN_RESOURCES', order: 13 },
+ { firstName: '', lastName: 'Cash', position: 'SALES_REP', order: 3 },
+ { firstName: 'Bob', lastName: 'Cash', position: 'SALES_REP', order: 0 },
+ { firstName: 'John Foo', lastName: 'Bar', position: 'SALES_REP', order: 8 },
+ ]);
+ });
+
+ it('should return an array without certain values filtered in a "chain" way', () => {
+ const filterBy = [
+ { property: 'firstName', operator: 'NE', value: 'John' },
+ { property: 'lastName', operator: 'NE', value: 'Doe' }
+ ] as CollectionFilterBy[];
+
+ const result1 = service.filterCollection(collection, filterBy);
+ const result2 = service.filterCollection(collection, filterBy, 'chain'); // chain is default
+
+ expect(result1).toEqual([
+ { firstName: 'Ava Luna', lastName: null, position: 'HUMAN_RESOURCES', order: 13 },
+ { firstName: '', lastName: 'Cash', position: 'SALES_REP', order: 3 },
+ { firstName: 'Bob', lastName: 'Cash', position: 'SALES_REP', order: 0 },
+ { firstName: 'John Foo', lastName: 'Bar', position: 'SALES_REP', order: 8 },
+ ]);
+ expect(result1).toEqual(result2);
+ });
+
+ it('should return an array with merged output of filtered values', () => {
+ const filterBy = [
+ { property: 'firstName', operator: OperatorType.equal, value: 'John' },
+ { property: 'lastName', value: 'Doe' } // ommitted Operator are Equal by default
+ ] as CollectionFilterBy[];
+
+ const result = service.filterCollection(collection, filterBy, FilterMultiplePassType.merge);
+
+ expect(result).toEqual([
+ // the array will have all "John" 1st, then all "Doe"
+ { firstName: 'John', lastName: 'Z', position: 'SALES_REP', order: 10 },
+ { firstName: 'John', lastName: 'Doe', position: null, order: 5 },
+ { firstName: 'John', lastName: 'Zachary', position: 'SALES_REP', order: 2 },
+ { firstName: 'John', lastName: 'Doe', position: 'DEVELOPER', order: 4 },
+ { firstName: 'Jane', lastName: 'Doe', position: 'FINANCE_MANAGER', order: 1 },
+ ]);
+ });
+ });
- const result = service.filterCollection(collection, filterBy);
+ describe('singleFilterCollection method', () => {
+ it('should return an array by using the "contains" filter type', () => {
+ const filterBy = { property: 'firstName', operator: OperatorType.contains, value: 'Foo' } as CollectionFilterBy;
- expect(result).toEqual([
- { firstName: '', lastName: 'Cash', position: 'SALES_REP', order: 3 }
- ]);
- });
+ const result = service.singleFilterCollection(collection, filterBy);
- it('should return an array without certain filtered values', () => {
- const filterBy = { property: 'firstName', operator: 'NE', value: 'John' } as CollectionFilterBy;
+ expect(result).toEqual([{ firstName: 'John Foo', lastName: 'Bar', position: 'SALES_REP', order: 8 }]);
+ });
- const result = service.filterCollection(collection, filterBy);
+ it('should return an array by using the "notContains" filter type', () => {
+ const filterBy = { property: 'firstName', operator: OperatorType.notContains, value: 'John' } as CollectionFilterBy;
- expect(result).toEqual([
- { firstName: 'Jane', lastName: 'Doe', position: 'FINANCE_MANAGER', order: 1 },
- { firstName: 'Ava Luna', lastName: null, position: 'HUMAN_RESOURCES', order: 13 },
- { firstName: '', lastName: 'Cash', position: 'SALES_REP', order: 3 },
- { firstName: 'Bob', lastName: 'Cash', position: 'SALES_REP', order: 0 },
- { firstName: 'John Foo', lastName: 'Bar', position: 'SALES_REP', order: 8 },
- ]);
- });
+ const result = service.singleFilterCollection(collection, filterBy);
- it('should return an array without certain filtered valuess in a "chain" way', () => {
- const filterBy = [
- { property: 'firstName', operator: 'NE', value: 'John' },
- { property: 'lastName', operator: 'NE', value: 'Doe' }
- ] as CollectionFilterBy[];
-
- const result1 = service.filterCollection(collection, filterBy);
- const result2 = service.filterCollection(collection, filterBy, 'chain'); // chain is default
-
- expect(result1).toEqual([
- { firstName: 'Ava Luna', lastName: null, position: 'HUMAN_RESOURCES', order: 13 },
- { firstName: '', lastName: 'Cash', position: 'SALES_REP', order: 3 },
- { firstName: 'Bob', lastName: 'Cash', position: 'SALES_REP', order: 0 },
- { firstName: 'John Foo', lastName: 'Bar', position: 'SALES_REP', order: 8 },
- ]);
- expect(result1).toEqual(result2);
+ expect(result).toEqual([
+ { firstName: 'Jane', lastName: 'Doe', position: 'FINANCE_MANAGER', order: 1 },
+ { firstName: 'Ava Luna', lastName: null, position: 'HUMAN_RESOURCES', order: 13 },
+ { firstName: '', lastName: 'Cash', position: 'SALES_REP', order: 3 },
+ { firstName: 'Bob', lastName: 'Cash', position: 'SALES_REP', order: 0 },
+ ]);
+ });
});
- it('should return an array with merged output of filtered values', () => {
- const filterBy = [
- { property: 'firstName', operator: OperatorType.equal, value: 'John' },
- { property: 'lastName', value: 'Doe' } // ommitted operator are Equal by default
- ] as CollectionFilterBy[];
-
- const result = service.filterCollection(collection, filterBy, FilterMultiplePassType.merge);
-
- expect(result).toEqual([
- // the array will have all "John" 1st, then all "Doe"
- { firstName: 'John', lastName: 'Z', position: 'SALES_REP', order: 10 },
- { firstName: 'John', lastName: 'Doe', position: null, order: 5 },
- { firstName: 'John', lastName: 'Zachary', position: 'SALES_REP', order: 2 },
- { firstName: 'John', lastName: 'Doe', position: 'DEVELOPER', order: 4 },
- { firstName: 'Jane', lastName: 'Doe', position: 'FINANCE_MANAGER', order: 1 },
- ]);
+ describe('sortCollection method', () => {
+ it('should return a collection sorted by a "dataKey"', () => {
+ const columnDef = { id: 'users', field: 'users', dataKey: 'lastName' } as Column;
+
+ const result = service.sortCollection(columnDef, collection, { property: 'lastName', sortDesc: true, fieldType: FieldType.string });
+
+ expect(result).toEqual([
+ { firstName: 'John', lastName: 'Zachary', position: 'SALES_REP', order: 2 },
+ { firstName: 'John', lastName: 'Z', position: 'SALES_REP', order: 10 },
+ { firstName: 'Jane', lastName: 'Doe', position: 'FINANCE_MANAGER', order: 1 },
+ { firstName: 'John', lastName: 'Doe', position: null, order: 5 },
+ { firstName: 'John', lastName: 'Doe', position: 'DEVELOPER', order: 4 },
+ { firstName: '', lastName: 'Cash', position: 'SALES_REP', order: 3 },
+ { firstName: 'Bob', lastName: 'Cash', position: 'SALES_REP', order: 0 },
+ { firstName: 'John Foo', lastName: 'Bar', position: 'SALES_REP', order: 8 },
+ { firstName: 'Ava Luna', lastName: null, position: 'HUMAN_RESOURCES', order: 13 },
+ ]);
+ });
+
+ it('should return a collection sorted by multiple sortBy entities', () => {
+ const columnDef = { id: 'users', field: 'users', dataKey: 'lastName' } as Column;
+ const sortBy = [
+ { property: 'firstName', sortDesc: false, fieldType: FieldType.string },
+ { property: 'lastName', sortDesc: true, fieldType: FieldType.string },
+ ] as CollectionSortBy[];
+
+ const result = service.sortCollection(columnDef, collection, sortBy);
+
+ expect(result).toEqual([
+ { firstName: '', lastName: 'Cash', position: 'SALES_REP', order: 3 },
+ { firstName: 'Ava Luna', lastName: null, position: 'HUMAN_RESOURCES', order: 13 },
+ { firstName: 'Bob', lastName: 'Cash', position: 'SALES_REP', order: 0 },
+ { firstName: 'Jane', lastName: 'Doe', position: 'FINANCE_MANAGER', order: 1 },
+ { firstName: 'John', lastName: 'Zachary', position: 'SALES_REP', order: 2 },
+ { firstName: 'John', lastName: 'Z', position: 'SALES_REP', order: 10 },
+ { firstName: 'John', lastName: 'Doe', position: null, order: 5 },
+ { firstName: 'John', lastName: 'Doe', position: 'DEVELOPER', order: 4 },
+ { firstName: 'John Foo', lastName: 'Bar', position: 'SALES_REP', order: 8 },
+ ]);
+ });
+
+ it('should return a collection sorted by a sortyBy entity being a number', () => {
+ const columnDef = { id: 'users', field: 'users', dataKey: 'lastName' } as Column;
+ const sortBy = [
+ { property: 'order', sortDesc: true, fieldType: FieldType.number },
+ ] as CollectionSortBy[];
+
+ const result = service.sortCollection(columnDef, collection, sortBy);
+
+ expect(result).toEqual([
+ { firstName: 'Ava Luna', lastName: null, position: 'HUMAN_RESOURCES', order: 13 },
+ { firstName: 'John', lastName: 'Z', position: 'SALES_REP', order: 10 },
+ { firstName: 'John Foo', lastName: 'Bar', position: 'SALES_REP', order: 8 },
+ { firstName: 'John', lastName: 'Doe', position: null, order: 5 },
+ { firstName: 'John', lastName: 'Doe', position: 'DEVELOPER', order: 4 },
+ { firstName: '', lastName: 'Cash', position: 'SALES_REP', order: 3 },
+ { firstName: 'John', lastName: 'Zachary', position: 'SALES_REP', order: 2 },
+ { firstName: 'Jane', lastName: 'Doe', position: 'FINANCE_MANAGER', order: 1 },
+ { firstName: 'Bob', lastName: 'Cash', position: 'SALES_REP', order: 0 },
+ ]);
+ });
+
+ it('should return a collection sorted by multiple sortBy entities and their translated value', () => {
+ translate.use('fr');
+ const columnDef = { id: 'users', field: 'users', dataKey: 'lastName' } as Column;
+ const sortBy = [
+ { property: 'firstName', sortDesc: false, fieldType: FieldType.string },
+ { property: 'position', sortDesc: true }, // fieldType is string by default
+ ] as CollectionSortBy[];
+
+ const result = service.sortCollection(columnDef, collection, sortBy, true);
+
+ expect(result).toEqual([
+ { firstName: '', lastName: 'Cash', position: 'SALES_REP', order: 3 },
+ { firstName: 'Ava Luna', lastName: null, position: 'HUMAN_RESOURCES', order: 13 },
+ { firstName: 'Bob', lastName: 'Cash', position: 'SALES_REP', order: 0 },
+ { firstName: 'Jane', lastName: 'Doe', position: 'FINANCE_MANAGER', order: 1 },
+ { firstName: 'John', lastName: 'Z', position: 'SALES_REP', order: 10 },
+ { firstName: 'John', lastName: 'Zachary', position: 'SALES_REP', order: 2 },
+ { firstName: 'John', lastName: 'Doe', position: 'DEVELOPER', order: 4 },
+ { firstName: 'John', lastName: 'Doe', position: null, order: 5 },
+ { firstName: 'John Foo', lastName: 'Bar', position: 'SALES_REP', order: 8 },
+ ]);
+ });
+
+ it('should return a collection sorted by a single sortBy entity and their translated value', () => {
+ translate.use('en');
+ const columnDef = { id: 'users', field: 'users' } as Column;
+ const sortBy = { property: 'position', sortDesc: false } as CollectionSortBy; // fieldType is string by default
+
+ const result = service.sortCollection(columnDef, collection, sortBy, true);
+
+ expect(result).toEqual([
+ { firstName: 'John', lastName: 'Doe', position: null, order: 5 },
+ { firstName: 'John', lastName: 'Doe', position: 'DEVELOPER', order: 4 },
+ { firstName: 'Jane', lastName: 'Doe', position: 'FINANCE_MANAGER', order: 1 },
+ { firstName: 'Ava Luna', lastName: null, position: 'HUMAN_RESOURCES', order: 13 },
+ { firstName: 'John', lastName: 'Z', position: 'SALES_REP', order: 10 },
+ { firstName: '', lastName: 'Cash', position: 'SALES_REP', order: 3 },
+ { firstName: 'Bob', lastName: 'Cash', position: 'SALES_REP', order: 0 },
+ { firstName: 'John', lastName: 'Zachary', position: 'SALES_REP', order: 2 },
+ { firstName: 'John Foo', lastName: 'Bar', position: 'SALES_REP', order: 8 },
+ ]);
+ });
});
});
- describe('singleFilterCollection method', () => {
- it('should return an array by using the "contains" filter type', () => {
- const filterBy = { property: 'firstName', operator: OperatorType.contains, value: 'Foo' } as CollectionFilterBy;
+ describe('Collection of Strings/Numbers', () => {
+ describe('filterCollection method', () => {
+ it('should return on the columns that have firstName filled when the filtered value is actually undefined but will be checked as an empty string', () => {
+ const filterBy = { operator: 'EQ', value: undefined } as CollectionFilterBy;
- const result = service.singleFilterCollection(collection, filterBy);
+ const result = service.filterCollection(stringCollection, filterBy);
- expect(result).toEqual([{ firstName: 'John Foo', lastName: 'Bar', position: 'SALES_REP', order: 8 }]);
- });
+ expect(result).toEqual(['']);
+ });
- it('should return an array by using the "notContains" filter type', () => {
- const filterBy = { property: 'firstName', operator: OperatorType.notContains, value: 'John' } as CollectionFilterBy;
+ it('should return an array without certain values filtered in a "chain" way', () => {
+ const filterBy = [
+ { operator: 'NE', value: 'John' },
+ { operator: 'NE', value: 'Bob' }
+ ] as CollectionFilterBy[];
- const result = service.singleFilterCollection(collection, filterBy);
+ const result1 = service.filterCollection(stringCollection, filterBy);
+ const result2 = service.filterCollection(stringCollection, filterBy, 'chain'); // chain is default
- expect(result).toEqual([
- { firstName: 'Jane', lastName: 'Doe', position: 'FINANCE_MANAGER', order: 1 },
- { firstName: 'Ava Luna', lastName: null, position: 'HUMAN_RESOURCES', order: 13 },
- { firstName: '', lastName: 'Cash', position: 'SALES_REP', order: 3 },
- { firstName: 'Bob', lastName: 'Cash', position: 'SALES_REP', order: 0 },
- ]);
- });
- });
+ expect(result1).toEqual(['Jane', 'Ava Luna', '', null, 'John Foo']);
+ expect(result1).toEqual(result2);
+ });
- describe('sortCollection method', () => {
- it('should return a collection sorted by a "dataKey"', () => {
- const columnDef = { id: 'users', field: 'users', dataKey: 'lastName' } as Column;
-
- const result = service.sortCollection(columnDef, collection, { property: 'lastName', sortDesc: true, fieldType: FieldType.string });
-
- expect(result).toEqual([
- { firstName: 'John', lastName: 'Zachary', position: 'SALES_REP', order: 2 },
- { firstName: 'John', lastName: 'Z', position: 'SALES_REP', order: 10 },
- { firstName: 'Jane', lastName: 'Doe', position: 'FINANCE_MANAGER', order: 1 },
- { firstName: 'John', lastName: 'Doe', position: null, order: 5 },
- { firstName: 'John', lastName: 'Doe', position: 'DEVELOPER', order: 4 },
- { firstName: '', lastName: 'Cash', position: 'SALES_REP', order: 3 },
- { firstName: 'Bob', lastName: 'Cash', position: 'SALES_REP', order: 0 },
- { firstName: 'John Foo', lastName: 'Bar', position: 'SALES_REP', order: 8 },
- { firstName: 'Ava Luna', lastName: null, position: 'HUMAN_RESOURCES', order: 13 },
- ]);
- });
+ it('should return an array with merged (unique values no duplicate) output of filtered values', () => {
+ const filterBy = [
+ { operator: OperatorType.equal, value: 'John' },
+ { value: 'Bob' } // ommitted Operator are Equal by default
+ ] as CollectionFilterBy[];
- it('should return a collection sorted by multiple sortBy entities', () => {
- const columnDef = { id: 'users', field: 'users', dataKey: 'lastName' } as Column;
- const sortBy = [
- { property: 'firstName', sortDesc: false, fieldType: FieldType.string },
- { property: 'lastName', sortDesc: true, fieldType: FieldType.string },
- ] as CollectionSortBy[];
-
- const result = service.sortCollection(columnDef, collection, sortBy);
-
- expect(result).toEqual([
- { firstName: '', lastName: 'Cash', position: 'SALES_REP', order: 3 },
- { firstName: 'Ava Luna', lastName: null, position: 'HUMAN_RESOURCES', order: 13 },
- { firstName: 'Bob', lastName: 'Cash', position: 'SALES_REP', order: 0 },
- { firstName: 'Jane', lastName: 'Doe', position: 'FINANCE_MANAGER', order: 1 },
- { firstName: 'John', lastName: 'Zachary', position: 'SALES_REP', order: 2 },
- { firstName: 'John', lastName: 'Z', position: 'SALES_REP', order: 10 },
- { firstName: 'John', lastName: 'Doe', position: null, order: 5 },
- { firstName: 'John', lastName: 'Doe', position: 'DEVELOPER', order: 4 },
- { firstName: 'John Foo', lastName: 'Bar', position: 'SALES_REP', order: 8 },
- ]);
- });
+ const result = service.filterCollection(stringCollection, filterBy, FilterMultiplePassType.merge);
- it('should return a collection sorted by a sortyBy entity being a number', () => {
- const columnDef = { id: 'users', field: 'users', dataKey: 'lastName' } as Column;
- const sortBy = [
- { property: 'order', sortDesc: true, fieldType: FieldType.number },
- ] as CollectionSortBy[];
-
- const result = service.sortCollection(columnDef, collection, sortBy);
-
- expect(result).toEqual([
- { firstName: 'Ava Luna', lastName: null, position: 'HUMAN_RESOURCES', order: 13 },
- { firstName: 'John', lastName: 'Z', position: 'SALES_REP', order: 10 },
- { firstName: 'John Foo', lastName: 'Bar', position: 'SALES_REP', order: 8 },
- { firstName: 'John', lastName: 'Doe', position: null, order: 5 },
- { firstName: 'John', lastName: 'Doe', position: 'DEVELOPER', order: 4 },
- { firstName: '', lastName: 'Cash', position: 'SALES_REP', order: 3 },
- { firstName: 'John', lastName: 'Zachary', position: 'SALES_REP', order: 2 },
- { firstName: 'Jane', lastName: 'Doe', position: 'FINANCE_MANAGER', order: 1 },
- { firstName: 'Bob', lastName: 'Cash', position: 'SALES_REP', order: 0 },
- ]);
+ expect(result).toEqual(['John', 'Bob']);
+ });
});
- it('should return a collection sorted by multiple sortBy entities and their translated value', () => {
- translate.use('fr');
- const columnDef = { id: 'users', field: 'users', dataKey: 'lastName' } as Column;
- const sortBy = [
- { property: 'firstName', sortDesc: false, fieldType: FieldType.string },
- { property: 'position', sortDesc: true }, // fieldType is string by default
- ] as CollectionSortBy[];
-
- const result = service.sortCollection(columnDef, collection, sortBy, true);
-
- expect(result).toEqual([
- { firstName: '', lastName: 'Cash', position: 'SALES_REP', order: 3 },
- { firstName: 'Ava Luna', lastName: null, position: 'HUMAN_RESOURCES', order: 13 },
- { firstName: 'Bob', lastName: 'Cash', position: 'SALES_REP', order: 0 },
- { firstName: 'Jane', lastName: 'Doe', position: 'FINANCE_MANAGER', order: 1 },
- { firstName: 'John', lastName: 'Z', position: 'SALES_REP', order: 10 },
- { firstName: 'John', lastName: 'Zachary', position: 'SALES_REP', order: 2 },
- { firstName: 'John', lastName: 'Doe', position: 'DEVELOPER', order: 4 },
- { firstName: 'John', lastName: 'Doe', position: null, order: 5 },
- { firstName: 'John Foo', lastName: 'Bar', position: 'SALES_REP', order: 8 },
- ]);
- });
+ describe('singleFilterCollection method', () => {
+ // stringCollection = ['John', 'Jane', 'Ava Luna', '', 'Bob', 'John', null, 'John Foo'];
- it('should return a collection sorted by a single sortBy entity and their translated value', () => {
- translate.use('en');
- const columnDef = { id: 'users', field: 'users' } as Column;
- const sortBy = { property: 'position', sortDesc: false } as CollectionSortBy; // fieldType is string by default
-
- const result = service.sortCollection(columnDef, collection, sortBy, true);
-
- expect(result).toEqual([
- { firstName: 'John', lastName: 'Doe', position: null, order: 5 },
- { firstName: 'John', lastName: 'Doe', position: 'DEVELOPER', order: 4 },
- { firstName: 'Jane', lastName: 'Doe', position: 'FINANCE_MANAGER', order: 1 },
- { firstName: 'Ava Luna', lastName: null, position: 'HUMAN_RESOURCES', order: 13 },
- { firstName: 'John', lastName: 'Z', position: 'SALES_REP', order: 10 },
- { firstName: '', lastName: 'Cash', position: 'SALES_REP', order: 3 },
- { firstName: 'Bob', lastName: 'Cash', position: 'SALES_REP', order: 0 },
- { firstName: 'John', lastName: 'Zachary', position: 'SALES_REP', order: 2 },
- { firstName: 'John Foo', lastName: 'Bar', position: 'SALES_REP', order: 8 },
- ]);
- });
+ it('should return an array by using the "contains" filter type', () => {
+ const filterBy = { operator: OperatorType.contains, value: 'Foo' } as CollectionFilterBy;
+
+ const result = service.singleFilterCollection(stringCollection, filterBy);
- it('should return a collection of numbers sorted', () => {
- translate.use('en');
- const columnDef = { id: 'count', field: 'count', fieldType: FieldType.number } as Column;
+ expect(result).toEqual(['John Foo']);
+ });
- const result1 = service.sortCollection(columnDef, [0, -11, 3, 99999, -200], { property: '', sortDesc: false } as CollectionSortBy);
- const result2 = service.sortCollection(columnDef, [0, -11, 3, 99999, -200], { property: '', sortDesc: true } as CollectionSortBy);
+ it('should return an array by using the "notContains" filter type', () => {
+ const filterBy = { operator: OperatorType.notContains, value: 'John' } as CollectionFilterBy;
- expect(result1).toEqual([-200, -11, 0, 3, 99999]);
- expect(result2).toEqual([99999, 3, 0, -11, -200]);
+ const result = service.singleFilterCollection(stringCollection, filterBy);
+
+ expect(result).toEqual(['Jane', 'Ava Luna', '', 'Bob']);
+ });
});
- it('should return a collection of translation values sorted', () => {
- translate.use('en');
- const roleCollection = ['SALES_REP', 'DEVELOPER', 'SALES_REP', null, 'HUMAN_RESOURCES', 'FINANCE_MANAGER', 'UNKNOWN'];
- const columnDef = { id: 'count', field: 'count', fieldType: FieldType.string } as Column;
+ describe('sortCollection method', () => {
+ it('should return a collection of numbers sorted', () => {
+ translate.use('en');
+ const columnDef = { id: 'count', field: 'count', fieldType: FieldType.number } as Column;
+
+ const result1 = service.sortCollection(columnDef, [0, -11, 3, 99999, -200], { sortDesc: false } as CollectionSortBy);
+ const result2 = service.sortCollection(columnDef, [0, -11, 3, 99999, -200], { sortDesc: true } as CollectionSortBy);
- const result1 = service.sortCollection(columnDef, [...roleCollection], { property: '', sortDesc: false } as CollectionSortBy, true);
- const result2 = service.sortCollection(columnDef, [...roleCollection], { property: '', sortDesc: true } as CollectionSortBy, true);
+ expect(result1).toEqual([-200, -11, 0, 3, 99999]);
+ expect(result2).toEqual([99999, 3, 0, -11, -200]);
+ });
- expect(result1).toEqual([null, 'DEVELOPER', 'FINANCE_MANAGER', 'HUMAN_RESOURCES', 'SALES_REP', 'SALES_REP', 'UNKNOWN']);
- expect(result2).toEqual(['UNKNOWN', 'SALES_REP', 'SALES_REP', 'HUMAN_RESOURCES', 'FINANCE_MANAGER', 'DEVELOPER', null]);
+ it('should return a collection of translation values sorted', () => {
+ translate.use('en');
+ const roleCollection = ['SALES_REP', 'DEVELOPER', 'SALES_REP', null, 'HUMAN_RESOURCES', 'FINANCE_MANAGER', 'UNKNOWN'];
+ const columnDef = { id: 'count', field: 'count', fieldType: FieldType.string } as Column;
+
+ const result1 = service.sortCollection(columnDef, [...roleCollection], { sortDesc: false } as CollectionSortBy, true);
+ const result2 = service.sortCollection(columnDef, [...roleCollection], { sortDesc: true } as CollectionSortBy, true);
+
+ expect(result1).toEqual([null, 'DEVELOPER', 'FINANCE_MANAGER', 'HUMAN_RESOURCES', 'SALES_REP', 'SALES_REP', 'UNKNOWN']);
+ expect(result2).toEqual(['UNKNOWN', 'SALES_REP', 'SALES_REP', 'HUMAN_RESOURCES', 'FINANCE_MANAGER', 'DEVELOPER', null]);
+ });
});
- });
- });
+ }); // Collection of strings/numbers
+ }); // with ngx-translate
describe('without ngx-translate', () => {
beforeEach(() => {
diff --git a/src/app/modules/angular-slickgrid/services/__tests__/export.service.spec.ts b/src/app/modules/angular-slickgrid/services/__tests__/export.service.spec.ts
index 52b26d7c6..e515d1d3f 100644
--- a/src/app/modules/angular-slickgrid/services/__tests__/export.service.spec.ts
+++ b/src/app/modules/angular-slickgrid/services/__tests__/export.service.spec.ts
@@ -1,4 +1,3 @@
-import { Sorters } from './../../sorters/index';
import { TestBed } from '@angular/core/testing';
import { TranslateModule, TranslateService } from '@ngx-translate/core';
import {
@@ -10,8 +9,9 @@ import {
FieldType,
SortDirectionNumber,
} from '../../models';
-import { Formatters } from './../../formatters/index';
import { ExportService } from '../export.service';
+import { Formatters } from './../../formatters/index';
+import { Sorters } from './../../sorters/index';
import { GroupTotalFormatters } from '../..';
function removeMultipleSpaces(textS) {
diff --git a/src/app/modules/angular-slickgrid/services/__tests__/utilities.spec.ts b/src/app/modules/angular-slickgrid/services/__tests__/utilities.spec.ts
index f3d991f7f..8d185096e 100644
--- a/src/app/modules/angular-slickgrid/services/__tests__/utilities.spec.ts
+++ b/src/app/modules/angular-slickgrid/services/__tests__/utilities.spec.ts
@@ -688,9 +688,11 @@ describe('Service/Utilies', () => {
it('should return default OperatoryType associated to contains', () => {
const output1 = mapOperatorType('');
const output2 = mapOperatorType('Contains');
+ const output3 = mapOperatorType('CONTAINS');
expect(output1).toBe(OperatorType.contains);
expect(output2).toBe(OperatorType.contains);
+ expect(output3).toBe(OperatorType.contains);
});
});
diff --git a/src/app/modules/angular-slickgrid/services/collection.service.ts b/src/app/modules/angular-slickgrid/services/collection.service.ts
index bd385f04b..aa1fa510d 100644
--- a/src/app/modules/angular-slickgrid/services/collection.service.ts
+++ b/src/app/modules/angular-slickgrid/services/collection.service.ts
@@ -53,25 +53,41 @@ export class CollectionService {
singleFilterCollection(collection: any[], filterBy: CollectionFilterBy): any[] {
let filteredCollection: any[] = [];
- if (filterBy && filterBy.property) {
- const property = filterBy.property;
+ if (filterBy) {
+ const objectProperty = filterBy.property;
const operator = filterBy.operator || OperatorType.equal;
// just check for undefined since the filter value could be null, 0, '', false etc
const value = typeof filterBy.value === 'undefined' ? '' : filterBy.value;
switch (operator) {
case OperatorType.equal:
- filteredCollection = collection.filter((item) => item[property] === value);
+ if (objectProperty) {
+ filteredCollection = collection.filter((item) => item[objectProperty] === value);
+ } else {
+ filteredCollection = collection.filter((item) => item === value);
+ }
break;
case OperatorType.contains:
- filteredCollection = collection.filter((item) => item[property].toString().indexOf(value.toString()) !== -1);
+ if (objectProperty) {
+ filteredCollection = collection.filter((item) => item[objectProperty].toString().indexOf(value.toString()) !== -1);
+ } else {
+ filteredCollection = collection.filter((item) => (item !== null && item !== undefined) && item.toString().indexOf(value.toString()) !== -1);
+ }
break;
case OperatorType.notContains:
- filteredCollection = collection.filter((item) => item[property].toString().indexOf(value.toString()) === -1);
+ if (objectProperty) {
+ filteredCollection = collection.filter((item) => item[objectProperty].toString().indexOf(value.toString()) === -1);
+ } else {
+ filteredCollection = collection.filter((item) => (item !== null && item !== undefined) && item.toString().indexOf(value.toString()) === -1);
+ }
break;
case OperatorType.notEqual:
default:
- filteredCollection = collection.filter((item) => item[property] !== value);
+ if (objectProperty) {
+ filteredCollection = collection.filter((item) => item[objectProperty] !== value);
+ } else {
+ filteredCollection = collection.filter((item) => item !== value);
+ }
}
}
@@ -100,11 +116,12 @@ export class CollectionService {
const sortBy = sortByOptions[i];
if (sortBy && sortBy.property) {
+ // collection of objects with a property name provided
const sortDirection = sortBy.sortDesc ? SortDirectionNumber.desc : SortDirectionNumber.asc;
- const propertyName = sortBy.property;
+ const objectProperty = sortBy.property;
const fieldType = sortBy.fieldType || FieldType.string;
- const value1 = (enableTranslateLabel) ? this.translate.instant(dataRow1[propertyName] || ' ') : dataRow1[propertyName];
- const value2 = (enableTranslateLabel) ? this.translate.instant(dataRow2[propertyName] || ' ') : dataRow2[propertyName];
+ const value1 = (enableTranslateLabel) ? this.translate.instant(dataRow1[objectProperty] || ' ') : dataRow1[objectProperty];
+ const value2 = (enableTranslateLabel) ? this.translate.instant(dataRow2[objectProperty] || ' ') : dataRow2[objectProperty];
const sortResult = sortByFieldType(fieldType, value1, value2, sortDirection, columnDef);
if (sortResult !== SortDirectionNumber.neutral) {
@@ -116,19 +133,22 @@ export class CollectionService {
});
} else if (sortByOptions && sortByOptions.property) {
// single sort
- const propertyName = sortByOptions.property;
+ // collection of objects with a property name provided
+ const objectProperty = sortByOptions.property;
const sortDirection = sortByOptions.sortDesc ? SortDirectionNumber.desc : SortDirectionNumber.asc;
const fieldType = sortByOptions.fieldType || FieldType.string;
- sortedCollection = collection.sort((dataRow1: any, dataRow2: any) => {
- const value1 = (enableTranslateLabel) ? this.translate.instant(dataRow1[propertyName] || ' ') : dataRow1[propertyName];
- const value2 = (enableTranslateLabel) ? this.translate.instant(dataRow2[propertyName] || ' ') : dataRow2[propertyName];
- const sortResult = sortByFieldType(fieldType, value1, value2, sortDirection, columnDef);
- if (sortResult !== SortDirectionNumber.neutral) {
- return sortResult;
- }
- return SortDirectionNumber.neutral;
- });
+ if (objectProperty) {
+ sortedCollection = collection.sort((dataRow1: any, dataRow2: any) => {
+ const value1 = (enableTranslateLabel) ? this.translate.instant(dataRow1[objectProperty] || ' ') : dataRow1[objectProperty];
+ const value2 = (enableTranslateLabel) ? this.translate.instant(dataRow2[objectProperty] || ' ') : dataRow2[objectProperty];
+ const sortResult = sortByFieldType(fieldType, value1, value2, sortDirection, columnDef);
+ if (sortResult !== SortDirectionNumber.neutral) {
+ return sortResult;
+ }
+ return SortDirectionNumber.neutral;
+ });
+ }
} else if (sortByOptions && !sortByOptions.property) {
const sortDirection = sortByOptions.sortDesc ? SortDirectionNumber.desc : SortDirectionNumber.asc;
const fieldType = sortByOptions.fieldType || FieldType.string;
diff --git a/src/app/modules/angular-slickgrid/services/utilities.ts b/src/app/modules/angular-slickgrid/services/utilities.ts
index 90f22d29a..30ad04127 100644
--- a/src/app/modules/angular-slickgrid/services/utilities.ts
+++ b/src/app/modules/angular-slickgrid/services/utilities.ts
@@ -481,6 +481,7 @@ export function mapOperatorType(operator: string): OperatorType {
map = OperatorType.notContains;
break;
case 'Contains':
+ case 'CONTAINS':
default:
map = OperatorType.contains;
break;
diff --git a/src/assets/i18n/fr.json b/src/assets/i18n/fr.json
index 3a0785c0a..7d970a532 100644
--- a/src/assets/i18n/fr.json
+++ b/src/assets/i18n/fr.json
@@ -34,7 +34,7 @@
"SYNCHRONOUS_RESIZE": "Redimension synchrone",
"TOGGLE_FILTER_ROW": "Basculer la ligne des filtres",
"TOGGLE_PRE_HEADER_ROW": "Basculer la ligne de pré-en-tête",
- "X_OF_Y_SELECTED": "# de % sélectionné",
+ "X_OF_Y_SELECTED": "# de % sélectionnés",
"BILLING": {
"ADDRESS": {
"STREET": "Adresse de facturation",