From b5733f8a1fa7c3070100d5df2f48f499907f6e0b Mon Sep 17 00:00:00 2001 From: benflowers Date: Thu, 5 Oct 2017 22:06:02 +0100 Subject: [PATCH 01/11] Reformat --- .../selectors/__tests__/localSelectorsTest.js | 56 +++++++++---------- src/plugins/local/selectors/localSelectors.js | 2 +- 2 files changed, 28 insertions(+), 30 deletions(-) diff --git a/src/plugins/local/selectors/__tests__/localSelectorsTest.js b/src/plugins/local/selectors/__tests__/localSelectorsTest.js index 51978194..402df651 100644 --- a/src/plugins/local/selectors/__tests__/localSelectorsTest.js +++ b/src/plugins/local/selectors/__tests__/localSelectorsTest.js @@ -4,9 +4,9 @@ import Immutable from 'immutable'; import * as selectors from '../localSelectors'; test('gets data', test => { - const state = new Immutable.Map({ data: 'hi' }); + const state = new Immutable.Map({ data: 'hi' }); - test.deepEqual(selectors.dataSelector(state), 'hi'); + test.deepEqual(selectors.dataSelector(state), 'hi'); }); test('gets current page', test => { @@ -65,8 +65,8 @@ test('gets empty string when filter not present', test => { test('gets sort properties', test => { const state = new Immutable.fromJS({ sortProperties: [ - { id: 'one', sortAscending: true }, - { id: 'two', sortAscending: false } + { id: 'one', sortAscending: true }, + { id: 'two', sortAscending: false }, ] }); @@ -113,7 +113,7 @@ test('gets column orders', test => { test('gets visible columns when columns specified without order', test => { const state = new Immutable.fromJS({ data: [ - { one: 'hi', two: 'hello', three: 'this should not show up'} + { one: 'hi', two: 'hello', three: 'this should not show up' }, ], renderProperties: { columnProperties: { @@ -129,7 +129,7 @@ test('gets visible columns when columns specified without order', test => { test('gets visible columns in order when columns specified', test => { const state = new Immutable.fromJS({ data: [ - { one: 'hi', two: 'hello', three: 'this should not show up'} + { one: 'hi', two: 'hello', three: 'this should not show up' }, ], renderProperties: { columnProperties: { @@ -145,7 +145,7 @@ test('gets visible columns in order when columns specified', test => { test('gets all columns as visible columns when no columns specified', test => { const state = new Immutable.fromJS({ data: [ - { one: 'hi', two: 'hello', three: 'this should not show up'} + { one: 'hi', two: 'hello', three: 'this should not show up' }, ] }); @@ -175,7 +175,7 @@ test('hasNextSelector returns true when more pages', test => { test('hasNextSelector returns false when no more pages', test => { const state = new Immutable.fromJS({ - data: [ + data: [ { one: 1 }, { two: 2 }, { three: 3 }, @@ -218,14 +218,12 @@ test('filteredDataSelector returns all data when no filter present', test => { const state = new Immutable.fromJS({ data: [ { id: '1', name: 'luke skywalker' }, - { id: '2', name: 'han solo' } - ] + { id: '2', name: 'han solo' }, + ], }); - test.deepEqual(selectors.filteredDataSelector(state).toJSON(), [ - { id: '1', name: 'luke skywalker' }, - { id: '2', name: 'han solo' } - ]); + test.deepEqual(selectors.filteredDataSelector(state).toJSON(), + state.get('data').toJSON()); }); test('filteredDataSelector filters data when filter string present', test => { @@ -233,12 +231,12 @@ test('filteredDataSelector filters data when filter string present', test => { filter: 'luke', data: [ { id: '1', name: 'luke skywalker' }, - { id: '2', name: 'han solo' } - ] + { id: '2', name: 'han solo' }, + ], }); test.deepEqual(selectors.filteredDataSelector(state).toJSON(), [ - { id: '1', name: 'luke skywalker' } + { id: '1', name: 'luke skywalker' }, ]); }); @@ -251,18 +249,18 @@ test('filteredDataSelector filters data respecting filterable', test => { }, weapon: { filterable: true, - } - } + }, + }, }, filter: 'H', data: [ { id: '1', name: 'luke skywalker', weapon: 'light saber' }, { id: '2', name: 'han solo', weapon: 'blaster' }, - ] + ], }); test.deepEqual(selectors.filteredDataSelector(state).toJSON(), [ - { id: '1', name: 'luke skywalker', weapon: 'light saber' } + { id: '1', name: 'luke skywalker', weapon: 'light saber' }, ]); }); @@ -348,7 +346,7 @@ test('sortedDataSelector works with multiple sortOptions', test => { { id: '1', name: 'luke skywalker', food: 'orange' }, { id: '2', name: 'han solo', food: 'banana' }, { id: '3', name: 'han solo', food: 'apple' }, - { id: '4', name: 'luke skywalker', food: 'apple'} + { id: '4', name: 'luke skywalker', food: 'apple' }, ], sortProperties: [ { id: 'name', sortAscending: true }, @@ -370,7 +368,7 @@ test('current page data selector gets correct page', test => { { id: '1', name: 'luke skywalker', food: 'orange' }, { id: '2', name: 'han solo', food: 'banana' }, { id: '3', name: 'han solo', food: 'apple' }, - { id: '4', name: 'luke skywalker', food: 'apple'} + { id: '4', name: 'luke skywalker', food: 'apple' }, ], pageProperties: { currentPage: 3, @@ -387,7 +385,7 @@ test('visible data selector gets only visible columns', test => { { id: '1', name: 'luke skywalker', food: 'orange' }, { id: '2', name: 'han solo', food: 'banana' }, { id: '3', name: 'han solo', food: 'apple' }, - { id: '4', name: 'luke skywalker', food: 'apple'} + { id: '4', name: 'luke skywalker', food: 'apple' }, ], renderProperties: { columnProperties: { @@ -414,7 +412,7 @@ test('visibleRowIdsSelector gets row ids', test => { { id: '1', name: 'luke skywalker', food: 'orange', griddleKey: 1 }, { id: '2', name: 'han solo', food: 'banana', griddleKey: 2 }, { id: '3', name: 'han solo', food: 'apple', griddleKey: 3 }, - { id: '4', name: 'luke skywalker', food: 'apple', griddleKey: 4} + { id: '4', name: 'luke skywalker', food: 'apple', griddleKey: 4 }, ], renderProperties: { columnProperties: { @@ -438,7 +436,7 @@ test('hidden columns selector shows all columns that are not visible', test => { { id: '1', name: 'luke skywalker', food: 'orange' }, { id: '2', name: 'han solo', food: 'banana' }, { id: '3', name: 'han solo', food: 'apple' }, - { id: '4', name: 'luke skywalker', food: 'apple'} + { id: '4', name: 'luke skywalker', food: 'apple' }, ], renderProperties: { columnProperties: { @@ -457,12 +455,12 @@ test('hidden columns selector shows all columns that are not visible', test => { }); test('columnIdsSelector gets all column ids', test => { - const state = new Immutable.fromJS({ + const state = new Immutable.fromJS({ data: [ { id: '1', name: 'luke skywalker', food: 'orange' }, { id: '2', name: 'han solo', food: 'banana' }, { id: '3', name: 'han solo', food: 'apple' }, - { id: '4', name: 'luke skywalker', food: 'apple'} + { id: '4', name: 'luke skywalker', food: 'apple' }, ], renderProperties: { columnProperties: { @@ -495,7 +493,7 @@ test('columnTitlesSelector gets all column titles', test => { { id: '1', name: 'luke skywalker', food: 'orange' }, { id: '2', name: 'han solo', food: 'banana' }, { id: '3', name: 'han solo', food: 'apple' }, - { id: '4', name: 'luke skywalker', food: 'apple'} + { id: '4', name: 'luke skywalker', food: 'apple' }, ], renderProperties: { columnProperties: { diff --git a/src/plugins/local/selectors/localSelectors.js b/src/plugins/local/selectors/localSelectors.js index 41d9e35b..a99a5d2e 100644 --- a/src/plugins/local/selectors/localSelectors.js +++ b/src/plugins/local/selectors/localSelectors.js @@ -185,7 +185,7 @@ export const columnIdsSelector = createSelector( visibleDataSelector, renderPropertiesSelector, (visibleData, renderProperties) => { - if(visibleData.size > 0) { + if (visibleData.size > 0) { return Object.keys(visibleData.get(0).toJSON()).map(k => renderProperties.getIn(['columnProperties', k, 'id']) || k ) From 3404f69637382707b5d30eb63ffa029f2892ef0e Mon Sep 17 00:00:00 2001 From: benflowers Date: Thu, 5 Oct 2017 22:06:02 +0100 Subject: [PATCH 02/11] adds object/function filter to local plugin --- .../selectors/__tests__/localSelectorsTest.js | 48 +++++++++++++ src/plugins/local/selectors/localSelectors.js | 68 ++++++++++++++----- 2 files changed, 100 insertions(+), 16 deletions(-) diff --git a/src/plugins/local/selectors/__tests__/localSelectorsTest.js b/src/plugins/local/selectors/__tests__/localSelectorsTest.js index 402df651..f732118a 100644 --- a/src/plugins/local/selectors/__tests__/localSelectorsTest.js +++ b/src/plugins/local/selectors/__tests__/localSelectorsTest.js @@ -240,6 +240,54 @@ test('filteredDataSelector filters data when filter string present', test => { ]); }); +test('filteredDataSelector filters data when filter function present', test => { + const state = new Immutable.fromJS({ + filter: function (row) { + return row.get("name") === 'luke skywalker'; + }, + data: [ + { id: '1', name: 'luke skywalker' }, + { id: '2', name: 'han solo' }, + ], + }); + + test.deepEqual(selectors.filteredDataSelector(state).toJSON(), [ + { id: '1', name: 'luke skywalker' }, + ]); +}); + +test('filteredDataSelector filters data when filter object present', test => { + const state = Immutable.Map({ + filter: { name: 'luke' }, + data: Immutable.List.of( + Immutable.Map({ id: '1', name: 'luke skywalker' }), + Immutable.Map({ id: '2', name: 'han solo' }), + ), + }); + test.deepEqual(selectors.filteredDataSelector(state).toJSON(), [ + { id: '1', name: 'luke skywalker' }, + ]); +}); + +test('filteredDataSelector filters data when filter object with filter function', test => { + const state = Immutable.Map({ + filter: { + name: function (rowName) { + return rowName.length === 14 + }, + }, + data: Immutable.List.of( + Immutable.Map({ id: '1', name: 'luke skywalker' }), + Immutable.Map({ id: '2', name: 'han solo' }), + ), + }); + + test.deepEqual(selectors.filteredDataSelector(state).toJSON(), [ + { id: '1', name: 'luke skywalker' }, + ]); +}); + + test('filteredDataSelector filters data respecting filterable', test => { const state = new Immutable.fromJS({ renderProperties: { diff --git a/src/plugins/local/selectors/localSelectors.js b/src/plugins/local/selectors/localSelectors.js index a99a5d2e..265aabfe 100644 --- a/src/plugins/local/selectors/localSelectors.js +++ b/src/plugins/local/selectors/localSelectors.js @@ -37,6 +37,43 @@ export const metaDataColumnsSelector = dataSelectors.metaDataColumnsSelector; const columnPropertiesSelector = state => state.getIn(['renderProperties', 'columnProperties']); +const substringSearch = (value, filter) => { + const filterToLower = filter.toLowerCase(); + return value && value.toString().toLowerCase().indexOf(filterToLower) > -1; +}; + +const textFilterRowSearch = (row, columnProperties, filter) => { + return row.keySeq() + .some((key) => { + const filterable = columnProperties && columnProperties.getIn([key, 'filterable']); + if (filterable === false) { + return false; + } + return substringSearch(row.get(key), filter); + }); +}; + +const objectFilterRowSearch = (row, columnProperties, filter) => { + return row.keySeq().some((key) => { + const filterable = columnProperties && columnProperties.getIn([key, 'filterable']); + if (filterable === false) { + return false; + } + const keyFilter = filter[key]; + switch (typeof (keyFilter)) { + case 'string': + return substringSearch(row.get(key), keyFilter) + break; + case 'function': + return keyFilter(row.get(key)) + break; + default: + return false + break; + } + }) +} + /** Gets the data filtered by the current filter */ export const filteredDataSelector = createSelector( @@ -48,22 +85,21 @@ export const filteredDataSelector = createSelector( return data; } - const filterToLower = filter.toLowerCase(); - return data.filter(row => - row.keySeq() - .some((key) => { - if (key === 'griddleKey') { - return false; - } else if (columnProperties) { - if (columnProperties.get(key) === undefined || - columnProperties.getIn([key, 'filterable']) === false) { - return false; - } - } - const value = row.get(key); - return value && - value.toString().toLowerCase().indexOf(filterToLower) > -1; - })); + return data.filter(row => { + switch (typeof (filter)) { + case 'string': + return textFilterRowSearch(row, columnProperties, filter) + break; + case 'object': + return objectFilterRowSearch(row, columnProperties, filter) + case 'function': + return filter(row) + default: + return data; + break; + } + + }); } ); From b040e65c09eb2b8e9e45894f104c8e7a383c9918 Mon Sep 17 00:00:00 2001 From: hank Date: Sat, 14 Apr 2018 19:58:10 +0200 Subject: [PATCH 03/11] Performance improved; Test added --- .../selectors/__tests__/localSelectorsTest.js | 15 ++++++ src/plugins/local/selectors/localSelectors.js | 46 +++++++++---------- 2 files changed, 38 insertions(+), 23 deletions(-) diff --git a/src/plugins/local/selectors/__tests__/localSelectorsTest.js b/src/plugins/local/selectors/__tests__/localSelectorsTest.js index f732118a..7729c77a 100644 --- a/src/plugins/local/selectors/__tests__/localSelectorsTest.js +++ b/src/plugins/local/selectors/__tests__/localSelectorsTest.js @@ -269,6 +269,21 @@ test('filteredDataSelector filters data when filter object present', test => { ]); }); +test('filteredDataSelector filters data with multiple filters', test => { + const state = Immutable.Map({ + filter: { id: '1', name: 'luke' }, + data: Immutable.List.of( + Immutable.Map({ id: '1', name: 'luke skywalker' }), + Immutable.Map({ id: '1', name: 'han solo' }), + Immutable.Map({ id: '3', name: 'luke skywalker' }), + Immutable.Map({ id: '2', name: 'han solo' }) + ) + }) + test.deepEqual(selectors.filteredDataSelector(state).toJSON(), [ + { id: '1', name: 'luke skywalker' }, + ]); +}); + test('filteredDataSelector filters data when filter object with filter function', test => { const state = Immutable.Map({ filter: { diff --git a/src/plugins/local/selectors/localSelectors.js b/src/plugins/local/selectors/localSelectors.js index 265aabfe..fc8e6a6a 100644 --- a/src/plugins/local/selectors/localSelectors.js +++ b/src/plugins/local/selectors/localSelectors.js @@ -42,7 +42,7 @@ const substringSearch = (value, filter) => { return value && value.toString().toLowerCase().indexOf(filterToLower) > -1; }; -const textFilterRowSearch = (row, columnProperties, filter) => { +const textFilterRowSearch = (columnProperties, filter) => (row) => { return row.keySeq() .some((key) => { const filterable = columnProperties && columnProperties.getIn([key, 'filterable']); @@ -53,8 +53,9 @@ const textFilterRowSearch = (row, columnProperties, filter) => { }); }; -const objectFilterRowSearch = (row, columnProperties, filter) => { - return row.keySeq().some((key) => { +const objectFilterRowSearch = (columnProperties, filter) => (row) => { + let found; + row.keySeq().some((key) => { const filterable = columnProperties && columnProperties.getIn([key, 'filterable']); if (filterable === false) { return false; @@ -62,17 +63,21 @@ const objectFilterRowSearch = (row, columnProperties, filter) => { const keyFilter = filter[key]; switch (typeof (keyFilter)) { case 'string': - return substringSearch(row.get(key), keyFilter) + if (found || found === undefined) { + found = substringSearch(row.get(key), keyFilter); + } break; case 'function': - return keyFilter(row.get(key)) + if (found === undefined) { + found = keyFilter(row.get(key)); + } break; default: - return false break; } - }) -} + }); + return found === undefined ? false : found; +}; /** Gets the data filtered by the current filter */ @@ -85,21 +90,16 @@ export const filteredDataSelector = createSelector( return data; } - return data.filter(row => { - switch (typeof (filter)) { - case 'string': - return textFilterRowSearch(row, columnProperties, filter) - break; - case 'object': - return objectFilterRowSearch(row, columnProperties, filter) - case 'function': - return filter(row) - default: - return data; - break; - } - - }); + switch (typeof (filter)) { + case 'string': + return data.filter(textFilterRowSearch(columnProperties, filter)); + case 'object': + return data.filter(objectFilterRowSearch(columnProperties, filter)); + case 'function': + return data.filter(row => filter(row)); + default: + return data; + } } ); From 1e8898267aadac210f85122c0b00f9aa7d80f098 Mon Sep 17 00:00:00 2001 From: Keith Dahlby Date: Sat, 14 Apr 2018 15:05:27 -0500 Subject: [PATCH 04/11] Use fromJS for consistency --- .../selectors/__tests__/localSelectorsTest.js | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/plugins/local/selectors/__tests__/localSelectorsTest.js b/src/plugins/local/selectors/__tests__/localSelectorsTest.js index 7729c77a..5a8fa753 100644 --- a/src/plugins/local/selectors/__tests__/localSelectorsTest.js +++ b/src/plugins/local/selectors/__tests__/localSelectorsTest.js @@ -257,12 +257,12 @@ test('filteredDataSelector filters data when filter function present', test => { }); test('filteredDataSelector filters data when filter object present', test => { - const state = Immutable.Map({ + const state = Immutable.fromJS({ filter: { name: 'luke' }, - data: Immutable.List.of( - Immutable.Map({ id: '1', name: 'luke skywalker' }), - Immutable.Map({ id: '2', name: 'han solo' }), - ), + data: [ + { id: '1', name: 'luke skywalker' }, + { id: '2', name: 'han solo' }, + ], }); test.deepEqual(selectors.filteredDataSelector(state).toJSON(), [ { id: '1', name: 'luke skywalker' }, @@ -270,31 +270,31 @@ test('filteredDataSelector filters data when filter object present', test => { }); test('filteredDataSelector filters data with multiple filters', test => { - const state = Immutable.Map({ + const state = Immutable.fromJS({ filter: { id: '1', name: 'luke' }, - data: Immutable.List.of( - Immutable.Map({ id: '1', name: 'luke skywalker' }), - Immutable.Map({ id: '1', name: 'han solo' }), - Immutable.Map({ id: '3', name: 'luke skywalker' }), - Immutable.Map({ id: '2', name: 'han solo' }) - ) - }) + data: [ + { id: '1', name: 'luke skywalker' }, + { id: '1', name: 'han solo' }, + { id: '3', name: 'luke skywalker' }, + { id: '2', name: 'han solo' }, + ], + }); test.deepEqual(selectors.filteredDataSelector(state).toJSON(), [ { id: '1', name: 'luke skywalker' }, ]); }); test('filteredDataSelector filters data when filter object with filter function', test => { - const state = Immutable.Map({ + const state = Immutable.fromJS({ filter: { name: function (rowName) { return rowName.length === 14 }, }, - data: Immutable.List.of( - Immutable.Map({ id: '1', name: 'luke skywalker' }), - Immutable.Map({ id: '2', name: 'han solo' }), - ), + data: [ + { id: '1', name: 'luke skywalker' }, + { id: '2', name: 'han solo' }, + ], }); test.deepEqual(selectors.filteredDataSelector(state).toJSON(), [ From 2e0495cb1af17710e37935b50af3af02b38fd51f Mon Sep 17 00:00:00 2001 From: Keith Dahlby Date: Sat, 14 Apr 2018 15:13:33 -0500 Subject: [PATCH 05/11] Simplify object search with more tests --- .../selectors/__tests__/localSelectorsTest.js | 76 ++++++++++++++++++- src/plugins/local/selectors/localSelectors.js | 21 +++-- 2 files changed, 84 insertions(+), 13 deletions(-) diff --git a/src/plugins/local/selectors/__tests__/localSelectorsTest.js b/src/plugins/local/selectors/__tests__/localSelectorsTest.js index 5a8fa753..3636ca0d 100644 --- a/src/plugins/local/selectors/__tests__/localSelectorsTest.js +++ b/src/plugins/local/selectors/__tests__/localSelectorsTest.js @@ -226,6 +226,32 @@ test('filteredDataSelector returns all data when no filter present', test => { state.get('data').toJSON()); }); +test('filteredDataSelector returns all data when filter is null', test => { + const state = new Immutable.fromJS({ + filter: null, + data: [ + { id: '1', name: 'luke skywalker' }, + { id: '2', name: 'han solo' }, + ], + }); + + test.deepEqual(selectors.filteredDataSelector(state).toJSON(), + state.get('data').toJSON()); +}); + +test('filteredDataSelector returns all data when filter is empty string', test => { + const state = new Immutable.fromJS({ + filter: '', + data: [ + { id: '1', name: 'luke skywalker' }, + { id: '2', name: 'han solo' }, + ], + }); + + test.deepEqual(selectors.filteredDataSelector(state).toJSON(), + state.get('data').toJSON()); +}); + test('filteredDataSelector filters data when filter string present', test => { const state = new Immutable.fromJS({ filter: 'luke', @@ -256,6 +282,22 @@ test('filteredDataSelector filters data when filter function present', test => { ]); }); +test('filteredDataSelector returns all data when filter object has null or empty string', test => { + const state = new Immutable.fromJS({ + filter: { + id: null, + name: '', + }, + data: [ + { id: '1', name: 'luke skywalker' }, + { id: '2', name: 'han solo' }, + ], + }); + + test.deepEqual(selectors.filteredDataSelector(state).toJSON(), + state.get('data').toJSON()); +}); + test('filteredDataSelector filters data when filter object present', test => { const state = Immutable.fromJS({ filter: { name: 'luke' }, @@ -302,8 +344,7 @@ test('filteredDataSelector filters data when filter object with filter function' ]); }); - -test('filteredDataSelector filters data respecting filterable', test => { +test('filteredDataSelector filter by string respects filterable', test => { const state = new Immutable.fromJS({ renderProperties: { columnProperties: { @@ -327,6 +368,37 @@ test('filteredDataSelector filters data respecting filterable', test => { ]); }); +test('filteredDataSelector filter by object respects filterable', test => { + const state = new Immutable.fromJS({ + renderProperties: { + columnProperties: { + id: { + filterable: false, + }, + name: { + filterable: false, + }, + weapon: { + filterable: true, + } + }, + }, + filter: { + id: () => false, + name: 'z', + weapon: 'g', + }, + data: [ + { id: '1', name: 'luke skywalker', weapon: 'light saber' }, + { id: '2', name: 'han solo', weapon: 'blaster' }, + ], + }); + + test.deepEqual(selectors.filteredDataSelector(state).toJSON(), [ + { id: '1', name: 'luke skywalker', weapon: 'light saber' }, + ]); +}); + test('filteredDataSelector matches ColumnDefinition fields only', test => { const state = new Immutable.fromJS({ renderProperties: { diff --git a/src/plugins/local/selectors/localSelectors.js b/src/plugins/local/selectors/localSelectors.js index fc8e6a6a..881677ef 100644 --- a/src/plugins/local/selectors/localSelectors.js +++ b/src/plugins/local/selectors/localSelectors.js @@ -38,6 +38,10 @@ export const metaDataColumnsSelector = dataSelectors.metaDataColumnsSelector; const columnPropertiesSelector = state => state.getIn(['renderProperties', 'columnProperties']); const substringSearch = (value, filter) => { + if (!filter) { + return true; + } + const filterToLower = filter.toLowerCase(); return value && value.toString().toLowerCase().indexOf(filterToLower) > -1; }; @@ -54,29 +58,24 @@ const textFilterRowSearch = (columnProperties, filter) => (row) => { }; const objectFilterRowSearch = (columnProperties, filter) => (row) => { - let found; - row.keySeq().some((key) => { + return row.keySeq().every((key) => { const filterable = columnProperties && columnProperties.getIn([key, 'filterable']); if (filterable === false) { - return false; + return true; } - const keyFilter = filter[key]; + const keyFilter = filter.get(key); switch (typeof (keyFilter)) { case 'string': - if (found || found === undefined) { - found = substringSearch(row.get(key), keyFilter); - } + return substringSearch(row.get(key), keyFilter); break; case 'function': - if (found === undefined) { - found = keyFilter(row.get(key)); - } + return keyFilter(row.get(key)); break; default: + return true; break; } }); - return found === undefined ? false : found; }; /** Gets the data filtered by the current filter From cb92033ea5b5ef35ba1d06da536f239c2cd1f323 Mon Sep 17 00:00:00 2001 From: Keith Dahlby Date: Sat, 14 Apr 2018 15:30:04 -0500 Subject: [PATCH 06/11] Expose index and data to filter functions --- .../local/selectors/__tests__/localSelectorsTest.js | 12 ++++++++---- src/plugins/local/selectors/localSelectors.js | 6 +++--- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/plugins/local/selectors/__tests__/localSelectorsTest.js b/src/plugins/local/selectors/__tests__/localSelectorsTest.js index 3636ca0d..ba7514ac 100644 --- a/src/plugins/local/selectors/__tests__/localSelectorsTest.js +++ b/src/plugins/local/selectors/__tests__/localSelectorsTest.js @@ -268,10 +268,12 @@ test('filteredDataSelector filters data when filter string present', test => { test('filteredDataSelector filters data when filter function present', test => { const state = new Immutable.fromJS({ - filter: function (row) { - return row.get("name") === 'luke skywalker'; + filter: function (row, i, data) { + test.deepEqual(data, state.get('data')); + return row.get("name") === 'luke skywalker' && i % 2; }, data: [ + { id: '0', name: 'luke skywalker' }, { id: '1', name: 'luke skywalker' }, { id: '2', name: 'han solo' }, ], @@ -329,11 +331,13 @@ test('filteredDataSelector filters data with multiple filters', test => { test('filteredDataSelector filters data when filter object with filter function', test => { const state = Immutable.fromJS({ filter: { - name: function (rowName) { - return rowName.length === 14 + name: function (rowName, i, data) { + test.deepEqual(data, state.get('data')); + return rowName.length === 14 && i % 2; }, }, data: [ + { id: '0', name: 'luke skywalker' }, { id: '1', name: 'luke skywalker' }, { id: '2', name: 'han solo' }, ], diff --git a/src/plugins/local/selectors/localSelectors.js b/src/plugins/local/selectors/localSelectors.js index 881677ef..614e11ab 100644 --- a/src/plugins/local/selectors/localSelectors.js +++ b/src/plugins/local/selectors/localSelectors.js @@ -57,7 +57,7 @@ const textFilterRowSearch = (columnProperties, filter) => (row) => { }); }; -const objectFilterRowSearch = (columnProperties, filter) => (row) => { +const objectFilterRowSearch = (columnProperties, filter) => (row, i, data) => { return row.keySeq().every((key) => { const filterable = columnProperties && columnProperties.getIn([key, 'filterable']); if (filterable === false) { @@ -69,7 +69,7 @@ const objectFilterRowSearch = (columnProperties, filter) => (row) => { return substringSearch(row.get(key), keyFilter); break; case 'function': - return keyFilter(row.get(key)); + return keyFilter(row.get(key), i, data); break; default: return true; @@ -95,7 +95,7 @@ export const filteredDataSelector = createSelector( case 'object': return data.filter(objectFilterRowSearch(columnProperties, filter)); case 'function': - return data.filter(row => filter(row)); + return data.filter(filter); default: return data; } From 96b592d12efa2d44db6e74dfeddb8c3eb090fc06 Mon Sep 17 00:00:00 2001 From: Keith Dahlby Date: Sat, 14 Apr 2018 15:49:38 -0500 Subject: [PATCH 07/11] Stop filter from matching invisible properties Reimplements 920d90beb163bf9ae042c97e83da8bd84fc28115 --- src/plugins/local/selectors/localSelectors.js | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/plugins/local/selectors/localSelectors.js b/src/plugins/local/selectors/localSelectors.js index 614e11ab..33ec878d 100644 --- a/src/plugins/local/selectors/localSelectors.js +++ b/src/plugins/local/selectors/localSelectors.js @@ -46,11 +46,23 @@ const substringSearch = (value, filter) => { return value && value.toString().toLowerCase().indexOf(filterToLower) > -1; }; +const filterable = (columnProperties, key) => { + if (key === 'griddleKey') { + return false; + } + if (columnProperties) { + if (columnProperties.get(key) === undefined || + columnProperties.getIn([key, 'filterable']) === false) { + return false; + } + } + return true; +}; + const textFilterRowSearch = (columnProperties, filter) => (row) => { return row.keySeq() .some((key) => { - const filterable = columnProperties && columnProperties.getIn([key, 'filterable']); - if (filterable === false) { + if (!filterable(columnProperties, key)) { return false; } return substringSearch(row.get(key), filter); @@ -59,8 +71,7 @@ const textFilterRowSearch = (columnProperties, filter) => (row) => { const objectFilterRowSearch = (columnProperties, filter) => (row, i, data) => { return row.keySeq().every((key) => { - const filterable = columnProperties && columnProperties.getIn([key, 'filterable']); - if (filterable === false) { + if (!filterable(columnProperties, key)) { return true; } const keyFilter = filter.get(key); From 859e55fb33526cb32e2fb581cd5c97409e4080f6 Mon Sep 17 00:00:00 2001 From: hank Date: Sun, 15 Apr 2018 11:44:08 +0200 Subject: [PATCH 08/11] StoryBook: new story for custom Object-Filtering --- stories/index.tsx | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/stories/index.tsx b/stories/index.tsx index 05b8b844..bce4b3ab 100644 --- a/stories/index.tsx +++ b/stories/index.tsx @@ -1210,6 +1210,35 @@ storiesOf('Filter', module) ) }) + .add('with Custom Filter for the column "name"', () => { + class CustomFilter extends React.Component<{setFilter: (e: any) => any, style: any, className: any}, {}> { + public setFilter = (e: any) => { + console.log(e.target.value); + const filterAsMap = new Map(); + filterAsMap.set('name', e.target.value); + this.props.setFilter(filterAsMap); + } + public render() { + return ( + + ); + } + } + + return ( + + + + + ) + }) storiesOf('Redux', module) .add('with custom filter connected to another Redux store', () => { From 7bacdb46e235d3715640788e2738ad58160aefec Mon Sep 17 00:00:00 2001 From: Keith Dahlby Date: Sun, 15 Apr 2018 12:09:05 -0500 Subject: [PATCH 09/11] Internalize use of ImmutableJS for filter state --- .../reducers/__tests__/localReducerTests.js | 40 +++++++++++++++++-- src/plugins/local/reducers/index.js | 3 +- stories/index.tsx | 7 ++-- 3 files changed, 42 insertions(+), 8 deletions(-) diff --git a/src/plugins/local/reducers/__tests__/localReducerTests.js b/src/plugins/local/reducers/__tests__/localReducerTests.js index 42e2080d..6a12d873 100644 --- a/src/plugins/local/reducers/__tests__/localReducerTests.js +++ b/src/plugins/local/reducers/__tests__/localReducerTests.js @@ -40,12 +40,46 @@ test('sets page size', test => { test.is(state.getIn(['pageProperties', 'pageSize']), 11); }); -test('sets filter', test => { +test('sets filter null', test => { const state = reducers.GRIDDLE_SET_FILTER(new Immutable.Map(), { - filter: 'onetwothree', + filter: null, }); - test.is(state.get('filter'), 'onetwothree'); + test.is(state.get('filter'), null); + test.is(state.getIn(['pageProperties', 'currentPage']), 1) +}); + +test('sets filter string', test => { + const filter = 'onetwothree'; + const state = reducers.GRIDDLE_SET_FILTER(new Immutable.Map(), { + filter + }); + + test.is(state.get('filter'), filter); + test.is(state.getIn(['pageProperties', 'currentPage']), 1) +}); + +test('sets filter function', test => { + const filter = (v, i) => i % 2; + const state = reducers.GRIDDLE_SET_FILTER(new Immutable.Map(), { + filter, + }); + + test.is(state.get('filter'), filter); + test.is(state.getIn(['pageProperties', 'currentPage']), 1) +}); + +test('sets filter object', test => { + const filter = { + id: (v, i) => i % 2, + name: 'ben', + }; + const state = reducers.GRIDDLE_SET_FILTER(new Immutable.Map(), { + filter, + }); + + test.not(state.get('filter'), filter); + test.deepEqual(state.get('filter').toJS(), filter); test.is(state.getIn(['pageProperties', 'currentPage']), 1) }); diff --git a/src/plugins/local/reducers/index.js b/src/plugins/local/reducers/index.js index 7b8dec64..4cc9ad85 100644 --- a/src/plugins/local/reducers/index.js +++ b/src/plugins/local/reducers/index.js @@ -1,3 +1,4 @@ +import Immutable from 'immutable'; import { maxPageSelector, currentPageSelector } from '../selectors/localSelectors'; import * as dataReducers from '../../../reducers//dataReducer'; @@ -63,7 +64,7 @@ export function GRIDDLE_PREVIOUS_PAGE(state, action) { */ export function GRIDDLE_SET_FILTER(state, action) { return state - .set('filter', action.filter) + .set('filter', action.filter && Immutable.fromJS(action.filter)) .setIn(['pageProperties', 'currentPage'], 1); }; diff --git a/stories/index.tsx b/stories/index.tsx index bce4b3ab..8352bd64 100644 --- a/stories/index.tsx +++ b/stories/index.tsx @@ -1213,10 +1213,9 @@ storiesOf('Filter', module) .add('with Custom Filter for the column "name"', () => { class CustomFilter extends React.Component<{setFilter: (e: any) => any, style: any, className: any}, {}> { public setFilter = (e: any) => { - console.log(e.target.value); - const filterAsMap = new Map(); - filterAsMap.set('name', e.target.value); - this.props.setFilter(filterAsMap); + this.props.setFilter({ + name: e.target.value, + }); } public render() { return ( From f51bad2833c0140079b743cb15bd36a9bb4c4ba5 Mon Sep 17 00:00:00 2001 From: Keith Dahlby Date: Sun, 15 Apr 2018 12:09:43 -0500 Subject: [PATCH 10/11] Add Name label to customer filter story --- stories/index.tsx | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/stories/index.tsx b/stories/index.tsx index 8352bd64..3c0cb435 100644 --- a/stories/index.tsx +++ b/stories/index.tsx @@ -1219,13 +1219,16 @@ storiesOf('Filter', module) } public render() { return ( - + ); } } From 0f514460a5ff16d4fad82f6d448c3db746e1249e Mon Sep 17 00:00:00 2001 From: Keith Dahlby Date: Sun, 15 Apr 2018 12:12:47 -0500 Subject: [PATCH 11/11] Add Filter details to type definition --- src/module.d.ts | 26 +++++++++++++++++++++----- stories/index.tsx | 2 +- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/module.d.ts b/src/module.d.ts index facf4148..6d575f38 100644 --- a/src/module.d.ts +++ b/src/module.d.ts @@ -1,4 +1,5 @@ import * as React from 'react'; +import * as Immutable from 'immutable'; import { connect as originalConnect } from 'react-redux'; import { ActionCreator, Middleware } from 'redux'; @@ -203,6 +204,18 @@ const SettingsContainer: (OriginalComponent: any) => any; const SettingsComponents: PropertyBag>; +export interface FilterProps { + setFilter?: (filter: GriddleFilter) => void; + placeholder?: string; + className?: string; + style?: React.CSSProperties; + + [x: string]: any; +} + +class Filter extends React.Component { +} + } // namespace components export interface GriddleComponents { @@ -216,10 +229,10 @@ export interface GriddleComponents { StyleContainer?: (OriginalComponent: GriddleComponent) => GriddleComponent; StyleContainerEnhancer?: (OriginalComponent: GriddleComponent) => GriddleComponent; - Filter?: GriddleComponent; - FilterEnhancer?: (OriginalComponent: GriddleComponent) => GriddleComponent; - FilterContainer?: (OriginalComponent: GriddleComponent) => GriddleComponent; - FilterContainerEnhancer?: (OriginalComponent: GriddleComponent) => GriddleComponent; + Filter?: GriddleComponent; + FilterEnhancer?: (OriginalComponent: GriddleComponent) => GriddleComponent; + FilterContainer?: (OriginalComponent: GriddleComponent) => GriddleComponent; + FilterContainerEnhancer?: (OriginalComponent: GriddleComponent) => GriddleComponent; SettingsWrapper?: GriddleComponent; SettingsWrapperEnhancer?: (OriginalComponent: GriddleComponent) => GriddleComponent; @@ -294,12 +307,15 @@ export interface GriddleComponents { PreviousButtonContainerEnhancer?: (OriginalComponent: GriddleComponent) => GriddleComponent; } +export type RowFilter = (row: any, index: number, data: Immutable.List) => boolean; +export type GriddleFilter = string | RowFilter | PropertyBag; + export interface GriddleActions extends PropertyBag | undefined> { onSort?: (sortProperties: any) => void; onNext?: () => void; onPrevious?: () => void; onGetPage?: (pageNumber: number) => void; - setFilter?: (filterText: string) => void; + setFilter?: (filter: GriddleFilter) => void; } export interface GriddleEvents extends GriddleActions { diff --git a/stories/index.tsx b/stories/index.tsx index 3c0cb435..aff876e0 100644 --- a/stories/index.tsx +++ b/stories/index.tsx @@ -1211,7 +1211,7 @@ storiesOf('Filter', module) ) }) .add('with Custom Filter for the column "name"', () => { - class CustomFilter extends React.Component<{setFilter: (e: any) => any, style: any, className: any}, {}> { + class CustomFilter extends components.Filter { public setFilter = (e: any) => { this.props.setFilter({ name: e.target.value,