From de970ada767f3a6e35ce122480e9e2f4ea88454f Mon Sep 17 00:00:00 2001 From: Amir Hadzic Date: Wed, 2 Nov 2016 09:08:11 +0100 Subject: [PATCH] [#3] Filter by text as user types into filter box --- src/Filter.js | 5 ++++ src/actions.js | 9 +++++++ src/reducer.js | 6 +++++ src/selectors.js | 64 ++++++++++++++++++++++++++++++++---------------- src/sematable.js | 5 ++++ 5 files changed, 68 insertions(+), 21 deletions(-) diff --git a/src/Filter.js b/src/Filter.js index 77cbb83..d7d0815 100644 --- a/src/Filter.js +++ b/src/Filter.js @@ -5,6 +5,7 @@ import { createTextFilter } from './common'; const propTypes = { value: PropTypes.array, onChange: PropTypes.func.isRequired, + onTextChange: PropTypes.func.isRequired, options: PropTypes.array.isRequired, }; @@ -13,6 +14,7 @@ class Filter extends Component { const { value, onChange, + onTextChange, options, } = this.props; return ( @@ -22,6 +24,9 @@ class Filter extends Component { placeholder="Search by text or tags" promptTextCreator={(txt) => `Search for '${txt}'`} onChange={(selected) => onChange(selected)} + onInputChange={(text) => onTextChange(text)} + onBlurResetsInput={false} + onCloseResetsInput={false} newOptionCreator={({ label }) => createTextFilter(label)} value={value} multi diff --git a/src/actions.js b/src/actions.js index a11597b..0659954 100644 --- a/src/actions.js +++ b/src/actions.js @@ -3,6 +3,7 @@ export const TABLE_NEW_DATA = 'sematable/TABLE_NEW_DATA'; export const TABLE_PAGE_CHANGED = 'sematable/TABLE_PAGE_CHANGED'; export const TABLE_PAGE_SIZE_CHANGED = 'sematable/TABLE_PAGE_SIZE_CHANGED'; export const TABLE_FILTER_CHANGED = 'sematable/TABLE_FILTER_CHANGED'; +export const TABLE_FILTER_TEXT_CHANGED = 'sematable/TABLE_FILTER_TEXT_CHANGED'; export const TABLE_SORT_CHANGED = 'sematable/TABLE_SORT_CHANGED'; export const TABLE_ROW_CHECKED_CHANGED = 'sematable/TABLE_ROW_CHECKED_CHANGED'; export const TABLE_SELECT_ALL_CHANGED = 'sematable/TABLE_SELECT_ALL_CHANGED'; @@ -51,6 +52,14 @@ export const tableFilterChanged = (tableName, filter) => ({ }, }); +export const tableFilterTextChanged = (tableName, filterText) => ({ + type: TABLE_FILTER_TEXT_CHANGED, + payload: { + tableName, + filterText, + }, +}); + export const tableSortChanged = (tableName, sortKey) => ({ type: TABLE_SORT_CHANGED, payload: { diff --git a/src/reducer.js b/src/reducer.js index 503d460..008691b 100644 --- a/src/reducer.js +++ b/src/reducer.js @@ -7,6 +7,7 @@ import { TABLE_PAGE_SIZE_CHANGED, TABLE_SORT_CHANGED, TABLE_FILTER_CHANGED, + TABLE_FILTER_TEXT_CHANGED, TABLE_SELECT_ALL_CHANGED, TABLE_ROW_CHECKED_CHANGED, TABLE_DESTROY_STATE, @@ -90,6 +91,11 @@ const behaviours = { page: 0, filter: payload.filter, }), + [TABLE_FILTER_TEXT_CHANGED]: (state, { payload }) => ({ + ...state, + page: 0, + filterText: payload.filterText, + }), [TABLE_SELECT_ALL_CHANGED]: (state) => ({ ...state, selectAll: !state.selectAll, diff --git a/src/selectors.js b/src/selectors.js index 5a6f16f..ac4ed40 100644 --- a/src/selectors.js +++ b/src/selectors.js @@ -10,28 +10,46 @@ function paginate(rows, { page, pageSize }) { return rows.slice(start, start + pageSize); } -function filter(rows = [], filters = [], columns) { - if (filters.length === 0) { - return rows.slice(0); +/** + * rows - original data + * filters - list of selected filters + * filterText - currently entered text in filter input + * columns - column definitions + */ +function filter(rows = [], filters = [], filterText, columns) { + let filteredRows = rows.slice(0); + if (filters.length === 0 && !filterText) { + return filteredRows; } - // apply text filter across all columns - let filteredRows = _.filter(rows, row => _.some(columns, (column) => { - if (!column.filterable) { - return false; - } - const normalized = String(_.get(row, column.key)).toLowerCase(); - return _.every(filters, f => !f.textFilter || normalized.indexOf(f.value) > -1); - })); - - // apply value filters on taggable columns - filteredRows = _.filter(filteredRows, row => _.every(columns, column => { - if (!column.taggable) { - return true; - } - const value = _.get(row, column.key); - return _.every(filters, f => !f.valueFilter || f.key !== column.key || f.value === value); - })); + const textFilters = [ + ...(filterText ? [filterText] : []), + ...filters.filter(f => f.textFilter).map(f => f.value), + ]; + + const valueFilters = filters.filter(f => f.valueFilter); + + if (textFilters.length > 0) { + // apply text filters across all columns + filteredRows = _.filter(rows, row => _.some(columns, (column) => { + if (!column.filterable) { + return false; + } + const normalized = String(_.get(row, column.key)).toLowerCase(); + return _.every(textFilters, f => normalized.indexOf(f) > -1); + })); + } + + if (valueFilters.length > 0) { + // apply value filters on taggable columns + filteredRows = _.filter(filteredRows, row => _.every(columns, column => { + if (!column.taggable) { + return true; + } + const value = _.get(row, column.key); + return _.every(valueFilters, f => f.key !== column.key || f.value === value); + })); + } return filteredRows; } @@ -66,6 +84,7 @@ export default (tableName) => { const getIsInitialized = (state) => state.sematable[tableName] !== undefined; const getInitialData = (state) => tableProp(state, 'initialData'); const getFilter = (state) => tableProp(state, 'filter'); + const getFilterText = (state) => tableProp(state, 'filterText'); const getColumns = (state) => tableProp(state, 'columns'); const getPage = (state) => tableProp(state, 'page'); const getPrimaryKey = (state) => tableProp(state, 'primaryKey'); @@ -81,8 +100,11 @@ export default (tableName) => { const getFiltered = createSelector( getInitialData, getFilter, + getFilterText, getColumns, - (initialData, textFilter, columns) => filter(initialData, textFilter, columns) + (initialData, filters, filterText, columns) => filter( + initialData, filters, filterText, columns + ) ); const getFilterOptions = createSelector( diff --git a/src/sematable.js b/src/sematable.js index 23aee97..2b106f4 100644 --- a/src/sematable.js +++ b/src/sematable.js @@ -11,6 +11,7 @@ import { tablePageChanged, tablePageSizeChanged, tableFilterChanged, + tableFilterTextChanged, tableSortChanged, tableRowCheckedChanged, tableSelectAllChanged, @@ -34,6 +35,7 @@ const propTypes = { onPageChange: PropTypes.func.isRequired, onPageSizeChange: PropTypes.func.isRequired, onFilterChange: PropTypes.func.isRequired, + onFilterTextChange: PropTypes.func.isRequired, onHeaderClick: PropTypes.func.isRequired, onInitialize: PropTypes.func.isRequired, onNewData: PropTypes.func.isRequired, @@ -83,6 +85,7 @@ const sematable = (tableName, TableComponent, columns, configs = {}) => { onPageChange: (page) => dispatch(tablePageChanged(tableName, page)), onPageSizeChange: (pageSize) => dispatch(tablePageSizeChanged(tableName, pageSize)), onFilterChange: (filter) => dispatch(tableFilterChanged(tableName, filter)), + onFilterTextChange: (filterText) => dispatch(tableFilterTextChanged(tableName, filterText)), onHeaderClick: (sortKey) => dispatch(tableSortChanged(tableName, sortKey)), onNewData: (data) => dispatch(tableNewData(tableName, data)), onNewFilterValue: (data) => dispatch(tableSetFilter(tableName, data)), @@ -131,6 +134,7 @@ const sematable = (tableName, TableComponent, columns, configs = {}) => { onPageChange, onPageSizeChange, onFilterChange, + onFilterTextChange, onHeaderClick, onRowCheckedChange, onSelectAllChange, @@ -183,6 +187,7 @@ const sematable = (tableName, TableComponent, columns, configs = {}) => { value={filter} options={filterOptions} onChange={(f) => onFilterChange(f)} + onTextChange={(f) => onFilterTextChange(f)} />}