UNPKG

@amblines/react-table-filter

Version:

> Module creates Excel like Column Filters for Table. The filter list contains all the unique items present in every column. See image below for example.

306 lines (270 loc) 10.3 kB
import {Component} from 'react'; import Template from './template.js'; import { isUndefined, } from './lib/util'; import { filterAction, filterActions, filtersReset, } from './lib/filter'; import { sortAction, } from './lib/sort'; import PropTypes from 'prop-types'; /** * TableFilter Main Component * @extends Component */ class TableFilter extends Component { /** * constructor * @param {Object} props */ constructor(props) { super(props); this.currentFilters = this.props.initialFilters || {}; const rows = this._applyInitialFilters(this.props.rows); const stateData = this._createData(rows); this.state = { initialData: stateData.initialData, filteredData: stateData.filteredData, sortKey: undefined, }; } /** * _applyInitialFilters If initial filters are provided we apply the filters to given data to maintain filter state * @param {Array} rows Data on which filters will be applied * @return {Array} Data with filter props applied */ _applyInitialFilters = (rows=[]) => { const currentFilters = this.currentFilters; if ( !isUndefined(currentFilters) && Object.keys(currentFilters).length > 0 ) { const filterKeys = Object.keys(currentFilters); let filteredArray; filterKeys.map((currKey) => { const filterValues = currentFilters[currKey]; const filterToApply = filterValues.map((currValue) => { return { key: currKey, value: currValue, }; }); const result = filterActions(rows, filterToApply, true, this._getValueFunctionForKey(currKey)); filteredArray = result.filteredArray; rows = result.dataWithFilter; }); this.props.onFilterUpdate && this.props.onFilterUpdate(filteredArray, currentFilters); } return rows; } /** * _getValueFunctionForKey Method to get the itemDisplayValueFunc(if any) provided to individual filter list element * @param {String} filterKey key * @return {Function} Value function for that filter list */ _getValueFunctionForKey = (filterKey) => { let valueFunc; this.props.children.map((child, index) => { if (!isUndefined(child) && !isUndefined(child.props.filterkey, true) && child.props.filterkey === filterKey) { valueFunc = child.props.itemDisplayValueFunc; } }); return valueFunc; } // This method to be done with web worker /** * _createData The data passed via props is copied to another array(to avoid mutation). // TODO - only one level * of mutation is prohibited right now. * @param {Array} rows Data passed through props * @return {Object} Created Data to be used in Filtering */ _createData = (rows = []) => { const initialData = []; const filteredData = []; rows.map((item) => { initialData.push(Object.assign({}, item)); filteredData.push(Object.assign({}, item)); }); return { initialData, filteredData, }; } /** * _filterMulipleRows function handles add/remove of multiple filters. Ex. * in case of select all or search etc * @param {Array} [addFilterArray=[]] Filters to add * @param {Array} [removeFilterArray=[]] Filter to remove * @param {Function} [valueFunc=undefined] Optional function used to get value for item */ _filterMulipleRows = (addFilterArray=[], removeFilterArray=[], valueFunc=undefined) => { const filteredData = this.state.filteredData; if (!isUndefined(addFilterArray)) { removeFilterArray.map((filterItem) => { this._updateCurrentFilter(filterItem.value, false, filterItem.key); }); addFilterArray.map((filterItem) => { this._updateCurrentFilter(filterItem.value, true, filterItem.key); }); let result = filterActions(filteredData, removeFilterArray, false, valueFunc); result = filterActions(result.dataWithFilter, addFilterArray, true, valueFunc); if (!isUndefined(result)) { const filteredArray = result.filteredArray; const dataWithFilter = result.dataWithFilter; this.setState({ filteredData: dataWithFilter, }); this.props.onFilterUpdate && this.props.onFilterUpdate(filteredArray, this._getCurrentFilters()); } } } /** * _filterRows Function passed as a prop to FilterList Componenet and called in case a filter is applied * or removed * @param {String} value Filter value * @param {String} key Filter key * @param {Boolean} addFilter Add or remove fitler * @param {Function} valueFunc(optional) Function passed to calculate the value of an item */ _filterRows = (value=undefined, key=undefined, addFilter=true, valueFunc=undefined) => { const filteredData = this.state.filteredData; if (!isUndefined(value) && !isUndefined(key)) { this._updateCurrentFilters([value], addFilter, key); const result = filterAction(filteredData, {key, value}, addFilter, valueFunc); if (!isUndefined(result)) { const filteredArray = result.filteredArray; const dataWithFilter = result.dataWithFilter; this.setState({ filteredData: dataWithFilter, }); this.props.onFilterUpdate && this.props.onFilterUpdate(filteredArray, this._getCurrentFilters()); } } } /** * _updateCurrentFilter Method to update current filter configuration, used to maintain state if component is unmounted * @param {String} filter Filter value to add/remove * @param {Boolean} add add/remove filter option * @param {String} key Filter key */ _updateCurrentFilter = (filter, add=true, key=undefined) => { if (!isUndefined(key, true) && !isUndefined(filter, false)) { if (isUndefined(this.currentFilters[key])) { this.currentFilters[key] = []; } if (add) { if (this.currentFilters[key].indexOf(filter) < 0) { this.currentFilters[key].push(filter); } } else { if (this.currentFilters[key].indexOf(filter) >= 0) { const index = this.currentFilters[key].indexOf(filter); this.currentFilters[key] = [...this.currentFilters[key].slice(0, index), ...this.currentFilters[key].slice(index + 1)]; } } } } /** * _updateCurrentFilters Mehtod to update current filter configuration with multiple values * @param {Array} filters Array of filters to add/remove * @param {Boolean} add add/remove filter option * @param {String} key Filter key */ _updateCurrentFilters = (filters=[], add=true, key) => { if (!isUndefined(filters) && !isUndefined(key)) { filters.map((filterItem) => { this._updateCurrentFilter(filterItem, add, key); }); } } /** * _getCurrentFilters get method for current filter configuration * @return {Object} current filter configuration */ _getCurrentFilters = () => { return this.currentFilters; } /** * _resetRows Function called to reset the selected filters. * @param {Array} filterValues Array of values for which filter is to be removed * @param {String} key Key of the filter to be removed * @param {Boolean} selectAll option to select all / remove all * @param {Function} valueFunc Get value function for filter */ _resetRows = (filterValues=[], key=undefined, selectAll=true, valueFunc=undefined) => { if (!isUndefined(key)) { const filteredData = this.state.filteredData; this._updateCurrentFilters(filterValues, !selectAll, key); const result = filtersReset(filteredData, filterValues, key, selectAll, valueFunc); if (!isUndefined(result)) { const filteredArray = result.filteredArray; const dataWithFilter = result.dataWithFilter; this.setState({ filteredData: dataWithFilter, }); this.props.onFilterUpdate && this.props.onFilterUpdate(filteredArray, this._getCurrentFilters()); } } } /** * _sortRows Function to sort the values according to a filter * @param {String} sortType * @param {[type]} options.valueFunc (optional) Function to calculate the value of the item * @param {Boolean} options.caseSensitive Case Sensitive or not * @param {[String]} options.key Key to sort by */ _sortRows = (sortType=undefined, {valueFunc=undefined, caseSensitive=false, key=undefined} = {}) => { if (!isUndefined(sortType)) { const filteredData = this.state.filteredData; const result = sortAction(filteredData, sortType, {valueFunc, caseSensitive, key} ); const filteredArray = []; this.setState({ filteredData: result, sortKey: key, sortType: sortType, }); result.map((item) => { if (isUndefined(item.appliedFilters) || Object.keys(item.appliedFilters).length === 0) { const itemCopy = Object.assign({}, item); delete itemCopy['appliedFilters']; filteredArray.push(itemCopy); } }); this.props.onFilterUpdate && this.props.onFilterUpdate(filteredArray, this._getCurrentFilters()); } } /** * reset Function called from parent(main code) to load/reset data of the filters * @param {Array} rows * @param {Boolean} resetFilters Option to reset current filters */ reset = (rows, resetFilters=true) => { if (!resetFilters) { rows = this._applyInitialFilters(rows); } else { this.currentFilters = {}; } const stateData = this._createData(rows); this.setState({ initialData: stateData.initialData, filteredData: stateData.filteredData, }); } /** * render * @return {JSX} */ render() { return Template.call(this); } } TableFilter.propTypes = { rows: PropTypes.array.isRequired, // Filterable Data onFilterUpdate: PropTypes.func.isRequired, // Function to be called with updated data when filters are applied or removed rowClass: PropTypes.string, // Optional class to be attached to row elements initialFilters: PropTypes.array, // Initial filter configuration if any(provided as a parameter in onFilterUpdate) rowComponent: PropTypes.func, children: PropTypes.any, }; export default TableFilter;