implement filter context

This commit is contained in:
AllenFang 2018-05-20 14:36:16 +08:00
parent 2f7d0104a0
commit 8f4dc9907a
6 changed files with 132 additions and 117 deletions

View File

@ -3,12 +3,12 @@ import SelectFilter from './src/components/select';
import MultiSelectFilter from './src/components/multiselect';
import NumberFilter from './src/components/number';
import DateFilter from './src/components/date';
import wrapperFactory from './src/wrapper';
import createContext from './src/context';
import * as Comparison from './src/comparison';
import { FILTER_TYPE } from './src/const';
export default (options = {}) => ({
wrapperFactory,
createContext,
options
});

View File

@ -0,0 +1,94 @@
/* eslint react/prop-types: 0 */
/* eslint react/require-default-props: 0 */
import React from 'react';
import PropTypes from 'prop-types';
import { filters } from './filter';
import { LIKE, EQ } from './comparison';
import { FILTER_TYPE } from './const';
export default (
_,
isRemoteFiltering,
handleFilterChange
) => {
const FilterContext = React.createContext();
class FilterProvider extends React.Component {
static propTypes = {
data: PropTypes.array.isRequired,
columns: PropTypes.array.isRequired
}
constructor(props) {
super(props);
this.currFilters = {};
this.onFilter = this.onFilter.bind(this);
this.onExternalFilter = this.onExternalFilter.bind(this);
}
onFilter(column, filterType) {
return (filterVal) => {
// watch out here if migration to context API, #334
const currFilters = Object.assign({}, this.currFilters);
const { dataField, filter } = column;
const needClearFilters =
!_.isDefined(filterVal) ||
filterVal === '' ||
filterVal.length === 0;
if (needClearFilters) {
delete currFilters[dataField];
} else {
// select default comparator is EQ, others are LIKE
const {
comparator = (filterType === FILTER_TYPE.SELECT ? EQ : LIKE),
caseSensitive = false
} = filter.props;
currFilters[dataField] = { filterVal, filterType, comparator, caseSensitive };
}
this.currFilters = currFilters;
if (isRemoteFiltering()) {
handleFilterChange(currFilters);
// when remote filtering is enable, dont set currFilters state
// in the componentWillReceiveProps,
// it's the key point that we can know the filter is changed
return;
}
this.forceUpdate();
};
}
onExternalFilter(column, filterType) {
return (value) => {
this.onFilter(column, filterType)(value);
};
}
render() {
let { data } = this.props;
if (!isRemoteFiltering()) {
data = filters(data, this.props.columns, _)(this.currFilters);
}
return (
<FilterContext.Provider value={ {
data,
onFilter: this.onFilter,
onExternalFilter: this.onExternalFilter
} }
>
{ this.props.children }
</FilterContext.Provider>
);
}
}
return {
Provider: FilterProvider,
Consumer: FilterContext.Consumer
};
};

View File

@ -229,9 +229,9 @@ export const filterFactory = _ => (filterType) => {
return filterFn;
};
export const filters = (store, columns, _) => (currFilters) => {
export const filters = (data, columns, _) => (currFilters) => {
const factory = filterFactory(_);
let result = store.getAllData();
let result = data;
let filterFn;
Object.keys(currFilters).forEach((dataField) => {
const filterObj = currFilters[dataField];

View File

@ -1,107 +0,0 @@
/* eslint no-param-reassign: 0 */
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { filters } from './filter';
import { LIKE, EQ } from './comparison';
import { FILTER_TYPE } from './const';
export default (Base, {
_,
remoteResolver
}) =>
class FilterWrapper extends remoteResolver(Component) {
static propTypes = {
store: PropTypes.object.isRequired,
columns: PropTypes.array.isRequired
}
constructor(props) {
super(props);
this.state = { currFilters: {}, isDataChanged: props.isDataChanged || false };
this.onFilter = this.onFilter.bind(this);
this.onExternalFilter = this.onExternalFilter.bind(this);
}
componentWillReceiveProps({ isDataChanged, store, columns }) {
// consider to use lodash.isEqual
const isRemoteFilter = this.isRemoteFiltering() || this.isRemotePagination();
if (isRemoteFilter ||
JSON.stringify(this.state.currFilters) !== JSON.stringify(store.filters)) {
// I think this condition only isRemoteFilter is enough
store.filteredData = store.getAllData();
this.setState(() => ({ isDataChanged: true, currFilters: store.filters }));
} else {
if (Object.keys(this.state.currFilters).length > 0) {
store.filteredData = filters(store, columns, _)(this.state.currFilters);
}
this.setState(() => ({ isDataChanged }));
}
}
/**
* filter the table like below:
* onFilter(column, filterType)(filterVal)
* @param {Object} column
* @param {String} filterType
* @param {String} filterVal - user input for filtering.
*/
onFilter(column, filterType) {
return (filterVal) => {
const { store, columns } = this.props;
// watch out here if migration to context API, #334
const currFilters = Object.assign({}, store.filters);
const { dataField, filter } = column;
const needClearFilters =
!_.isDefined(filterVal) ||
filterVal === '' ||
filterVal.length === 0;
if (needClearFilters) {
delete currFilters[dataField];
} else {
// select default comparator is EQ, others are LIKE
const {
comparator = (
(filterType === FILTER_TYPE.SELECT) || (
filterType === FILTER_TYPE.MULTISELECT) ? EQ : LIKE
),
caseSensitive = false
} = filter.props;
currFilters[dataField] = { filterVal, filterType, comparator, caseSensitive };
}
store.filters = currFilters;
if (this.isRemoteFiltering() || this.isRemotePagination()) {
this.handleRemoteFilterChange();
// when remote filtering is enable, dont set currFilters state
// in the componentWillReceiveProps,
// it's the key point that we can know the filter is changed
return;
}
store.filteredData = filters(store, columns, _)(currFilters);
this.setState(() => ({ currFilters, isDataChanged: true }));
};
}
onExternalFilter(column, filterType) {
return (value) => {
this.onFilter(column, filterType)(value);
};
}
render() {
return (
<Base
{ ...this.props }
data={ this.props.store.data }
onFilter={ this.onFilter }
onExternalFilter={ this.onExternalFilter }
isDataChanged={ this.state.isDataChanged }
/>
);
}
};

View File

@ -1,4 +1,5 @@
/* eslint no-return-assign: 0 */
/* eslint class-methods-use-this: 0 */
import React, { Component } from 'react';
import _ from '../utils';
import createDataContext from './data-context';
@ -12,6 +13,7 @@ const withContext = (Base) => {
let SelectionContext;
let CellEditContext;
let SortContext;
let FilterContext;
return class BootstrapTableContainer extends remoteResolver(Component) {
constructor(props) {
@ -23,6 +25,10 @@ const withContext = (Base) => {
CellEditContext = props.cellEdit.createContext(
_, dataOperator, this.isRemoteCellEdit, this.handleCellChange);
}
if (props.filter) {
FilterContext = props.filter.createContext(
_, this.isRemoteFiltering, this.handleRemoteFilterChange);
}
}
componentWillReceiveProps(nextProps) {
@ -32,13 +38,13 @@ const withContext = (Base) => {
}
renderBase(baseProps) {
return (rootProps, cellEditProps) => (
return (rootProps, cellEditProps, filterProps) => (
<SortContext.Provider
{ ...baseProps }
ref={ n => this.sortContext = n }
defaultSorted={ this.props.defaultSorted }
defaultSortDirection={ this.props.defaultSortDirection }
data={ rootProps.data }
data={ filterProps ? filterProps.data : rootProps.data }
>
<SortContext.Consumer>
{
@ -56,6 +62,7 @@ const withContext = (Base) => {
{ ...selectionProps }
{ ...sortProps }
{ ...cellEditProps }
{ ...filterProps }
data={ sortProps.data }
/>
)
@ -69,6 +76,22 @@ const withContext = (Base) => {
);
}
renderWithFilter(base, baseProps) {
return (rootProps, cellEditprops) => (
<FilterContext.Provider
{ ...baseProps }
ref={ n => this.filterContext = n }
data={ rootProps.data }
>
<FilterContext.Consumer>
{
filterProps => base(rootProps, cellEditprops, filterProps)
}
</FilterContext.Consumer>
</FilterContext.Provider>
);
}
renderWithCellEdit(base, baseProps) {
return rootProps => (
<CellEditContext.Provider
@ -91,6 +114,10 @@ const withContext = (Base) => {
let base = this.renderBase(baseProps);
if (FilterContext) {
base = this.renderWithFilter(base, baseProps);
}
if (CellEditContext) {
base = this.renderWithCellEdit(base, baseProps);
}

View File

@ -19,6 +19,7 @@ export default ExtendBase =>
sortField: this.sortContext.state.sortColumn ?
this.sortContext.state.sortColumn.dataField :
null,
filters: this.filterContext ? this.filterContext.currFilters : {},
...state,
data: this.props.data
};
@ -29,9 +30,9 @@ export default ExtendBase =>
return remote === true || (_.isObject(remote) && remote.pagination);
}
isRemoteFiltering() {
isRemoteFiltering = () => {
const { remote } = this.props;
return remote === true || (_.isObject(remote) && remote.filter);
return remote === true || (_.isObject(remote) && remote.filter) || this.isRemotePagination();
}
isRemoteSort = () => {
@ -48,8 +49,8 @@ export default ExtendBase =>
this.props.onTableChange('pagination', this.getNewestState());
}
handleRemoteFilterChange() {
const newState = {};
handleRemoteFilterChange = (filters) => {
const newState = { filters };
if (this.isRemotePagination()) {
const options = this.props.pagination.options || {};
newState.page = _.isDefined(options.pageStartIndex) ? options.pageStartIndex : 1;