From 0ff0c33aa9f60c62f34570561ae96fdb47786141 Mon Sep 17 00:00:00 2001 From: AllenFang Date: Sun, 13 May 2018 16:09:34 +0800 Subject: [PATCH 01/55] upgrade react and react-dom --- package.json | 4 ++-- yarn.lock | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 5564378..d159c7b 100644 --- a/package.json +++ b/package.json @@ -81,8 +81,8 @@ "dependencies": { "classnames": "2.2.5", "prop-types": "15.5.10", - "react": "16.0.0", - "react-dom": "16.0.0" + "react": "16.3.2", + "react-dom": "16.3.2" }, "jest": { "collectCoverageFrom": [ diff --git a/yarn.lock b/yarn.lock index 8c31ec3..5321b1a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6892,9 +6892,9 @@ rc@^1.1.7: minimist "^1.2.0" strip-json-comments "~2.0.1" -react-dom@16.0.0: - version "16.0.0" - resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.0.0.tgz#9cc3079c3dcd70d4c6e01b84aab2a7e34c303f58" +react-dom@16.3.2: + version "16.3.2" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.3.2.tgz#cb90f107e09536d683d84ed5d4888e9640e0e4df" dependencies: fbjs "^0.8.16" loose-envify "^1.1.0" @@ -6916,9 +6916,9 @@ react-test-renderer@^16.0.0-0: object-assign "^4.1.1" prop-types "^15.6.0" -react@16.0.0: - version "16.0.0" - resolved "https://registry.yarnpkg.com/react/-/react-16.0.0.tgz#ce7df8f1941b036f02b2cca9dbd0cb1f0e855e2d" +react@16.3.2: + version "16.3.2" + resolved "https://registry.yarnpkg.com/react/-/react-16.3.2.tgz#fdc8420398533a1e58872f59091b272ce2f91ea9" dependencies: fbjs "^0.8.16" loose-envify "^1.1.0" From 906180ad3f099e67e26a2d373e4ba7fce808d40d Mon Sep 17 00:00:00 2001 From: AllenFang Date: Sun, 13 May 2018 16:13:06 +0800 Subject: [PATCH 02/55] implement context-based container --- packages/react-bootstrap-table2/index.js | 4 +- .../react-bootstrap-table2/src/container.js | 71 ----------------- .../src/contexts/data-context.js | 35 +++++++++ .../src/contexts/index.js | 76 +++++++++++++++++++ 4 files changed, 113 insertions(+), 73 deletions(-) delete mode 100644 packages/react-bootstrap-table2/src/container.js create mode 100644 packages/react-bootstrap-table2/src/contexts/data-context.js create mode 100644 packages/react-bootstrap-table2/src/contexts/index.js diff --git a/packages/react-bootstrap-table2/index.js b/packages/react-bootstrap-table2/index.js index c134e64..c603b60 100644 --- a/packages/react-bootstrap-table2/index.js +++ b/packages/react-bootstrap-table2/index.js @@ -1,5 +1,5 @@ import BootstrapTable from './src/bootstrap-table'; -import withDataStore from './src/container'; +import withContext from './src/contexts'; -export default withDataStore(BootstrapTable); +export default withContext(BootstrapTable); diff --git a/packages/react-bootstrap-table2/src/container.js b/packages/react-bootstrap-table2/src/container.js deleted file mode 100644 index 195ef25..0000000 --- a/packages/react-bootstrap-table2/src/container.js +++ /dev/null @@ -1,71 +0,0 @@ -/* eslint no-return-assign: 0 */ -/* eslint react/prop-types: 0 */ -import React, { Component } from 'react'; -import Store from './store'; -import withSort from './sort/wrapper'; -import withSelection from './row-selection/wrapper'; - -import remoteResolver from './props-resolver/remote-resolver'; -import _ from './utils'; - -const withDataStore = Base => - class BootstrapTableContainer extends remoteResolver(Component) { - constructor(props) { - super(props); - this.store = new Store(props.keyField); - this.store.data = props.data; - this.wrapComponents(); - } - - componentWillReceiveProps(nextProps) { - this.store.setAllData(nextProps.data); - } - - wrapComponents() { - this.BaseComponent = Base; - const { pagination, columns, filter, selectRow, cellEdit } = this.props; - if (pagination) { - const { wrapperFactory } = pagination; - this.BaseComponent = wrapperFactory(this.BaseComponent, { - remoteResolver - }); - } - - if (columns.filter(col => col.sort).length > 0) { - this.BaseComponent = withSort(this.BaseComponent); - } - - if (filter) { - const { wrapperFactory } = filter; - this.BaseComponent = wrapperFactory(this.BaseComponent, { - _, - remoteResolver - }); - } - - if (cellEdit) { - const { wrapperFactory } = cellEdit; - this.BaseComponent = wrapperFactory(this.BaseComponent, { - _, - remoteResolver - }); - } - - if (selectRow) { - this.BaseComponent = withSelection(this.BaseComponent); - } - } - - render() { - const baseProps = { - ...this.props, - store: this.store - }; - - return ( - - ); - } - }; - -export default withDataStore; diff --git a/packages/react-bootstrap-table2/src/contexts/data-context.js b/packages/react-bootstrap-table2/src/contexts/data-context.js new file mode 100644 index 0000000..9a08976 --- /dev/null +++ b/packages/react-bootstrap-table2/src/contexts/data-context.js @@ -0,0 +1,35 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; + +export default () => { + const DataContext = React.createContext(); + + class DataProvider extends Component { + static propTypes = { + data: PropTypes.array.isRequired, + children: PropTypes.node.isRequired + } + + state = { data: this.props.data }; + + componentWillReceiveProps(nextProps) { + this.setState(() => ({ data: nextProps.data })); + } + + render() { + return ( + + { this.props.children } + + ); + } + } + return { + Provider: DataProvider, + Consumer: DataContext.Consumer + }; +}; diff --git a/packages/react-bootstrap-table2/src/contexts/index.js b/packages/react-bootstrap-table2/src/contexts/index.js new file mode 100644 index 0000000..6afb979 --- /dev/null +++ b/packages/react-bootstrap-table2/src/contexts/index.js @@ -0,0 +1,76 @@ +/* eslint no-return-assign: 0 */ +/* eslint react/prop-types: 0 */ +import React, { Component } from 'react'; +// import Store from './store'; +// import withSort from './sort/wrapper'; +// import withSelection from './row-selection/wrapper'; +import createDataContext from './data-context'; +import createSortContext from './sort-context'; +import createSelectionContext from './selection-context'; + +import remoteResolver from '../props-resolver/remote-resolver'; + + +const withContext = (Base) => { + let DataContext; + let SelectionContext; + let SortContext; + + return class BootstrapTableContainer extends remoteResolver(Component) { + constructor(props) { + super(props); + DataContext = createDataContext(this.props.data); + SelectionContext = createSelectionContext(); + SortContext = createSortContext(); + } + + render() { + const { keyField, columns, remote } = this.props; + const baseProps = { keyField, columns, remote }; + + return ( + + + { + dataProps => ( + + + { + selectionProps => ( + + + { + sortProps => ( + + ) + } + + + ) + } + + + ) + } + + + ); + } + }; +}; + +export default withContext; From 6d08a24a8fa84fc06cce6b06f1f73542ca9c7235 Mon Sep 17 00:00:00 2001 From: AllenFang Date: Sun, 13 May 2018 16:13:59 +0800 Subject: [PATCH 03/55] implement selection context --- .../src/bootstrap-table.js | 12 +- .../src/contexts/selection-context.js | 94 +++++++++++++++ .../src/row-selection/wrapper.js | 107 ------------------ .../react-bootstrap-table2/src/store/rows.js | 2 +- .../src/store/selection.js | 14 +-- 5 files changed, 106 insertions(+), 123 deletions(-) create mode 100644 packages/react-bootstrap-table2/src/contexts/selection-context.js delete mode 100644 packages/react-bootstrap-table2/src/row-selection/wrapper.js diff --git a/packages/react-bootstrap-table2/src/bootstrap-table.js b/packages/react-bootstrap-table2/src/bootstrap-table.js index e9ee145..6967b8e 100644 --- a/packages/react-bootstrap-table2/src/bootstrap-table.js +++ b/packages/react-bootstrap-table2/src/bootstrap-table.js @@ -42,7 +42,6 @@ class BootstrapTable extends PropsBaseResolver(Component) { renderTable() { const { - store, columns, keyField, id, @@ -74,8 +73,8 @@ class BootstrapTable extends PropsBaseResolver(Component) { const headerCellSelectionInfo = this.resolveSelectRowPropsForHeader({ onAllRowsSelect: this.props.onAllRowsSelect, - selected: store.selected, - allRowsSelected: isSelectedAll(store) + selected: this.props.selected, + allRowsSelected: isSelectedAll(this.state.data, this.props.selected) }); const tableCaption = (caption && { caption }); @@ -87,8 +86,8 @@ class BootstrapTable extends PropsBaseResolver(Component) {
{ + const SelectionContext = React.createContext(); + + class SelectionProvider extends React.Component { + static propTypes = { + children: PropTypes.node.isRequired, + data: PropTypes.array.isRequired, + keyField: PropTypes.string.isRequired + } + + state = { selected: (this.props.selectRow && this.props.selectRow.selected) || [] }; + + componentWillReceiveProps(nextProps) { + if (nextProps.selectRow) { + this.setState(() => ({ + selected: nextProps.selectRow.selected || this.state.selected + })); + } + } + + handleRowSelect = (rowKey, checked, rowIndex, e) => { + const { data, keyField, selectRow: { mode, onSelect } } = this.props; + const { ROW_SELECT_SINGLE } = Const; + + let currSelected = [...this.state.selected]; + + if (mode === ROW_SELECT_SINGLE) { // when select mode is radio + currSelected = [rowKey]; + } else if (checked) { // when select mode is checkbox + currSelected.push(rowKey); + } else { + currSelected = currSelected.filter(value => value !== rowKey); + } + + if (onSelect) { + const row = getRowByRowId(data, keyField, rowKey); + onSelect(row, checked, rowIndex, e); + } + + this.setState(() => ({ selected: currSelected })); + } + + handleAllRowsSelect = (e) => { + const { + data, + keyField, + selectRow: { + onSelectAll, + nonSelectable + } + } = this.props; + const { selected } = this.state; + const anySelected = selectionHandler.isAnySelectedRow(selected, nonSelectable); + + const result = !anySelected; + + const currSelected = result ? + selectionHandler.selectableKeys(data, keyField, nonSelectable) : + selectionHandler.unSelectableKeys(selected, nonSelectable); + + if (onSelectAll) { + onSelectAll(result, selectionHandler.getSelectedRows(data, keyField, currSelected), e); + } + + this.setState(() => ({ selected: currSelected })); + } + + render() { + return ( + + { this.props.children } + + ); + } + } + return { + Provider: SelectionProvider, + Consumer: SelectionContext.Consumer + }; +}; diff --git a/packages/react-bootstrap-table2/src/row-selection/wrapper.js b/packages/react-bootstrap-table2/src/row-selection/wrapper.js deleted file mode 100644 index 51472dc..0000000 --- a/packages/react-bootstrap-table2/src/row-selection/wrapper.js +++ /dev/null @@ -1,107 +0,0 @@ -/* eslint no-param-reassign: 0 */ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; - -import Const from '../const'; -import { - isAnySelectedRow, - selectableKeys, - unSelectableKeys, - getSelectedRows -} from '../store/selection'; -import { getRowByRowId } from '../store/rows'; - -export default Base => - class RowSelectionWrapper extends Component { - static propTypes = { - store: PropTypes.object.isRequired, - selectRow: PropTypes.object.isRequired - } - - constructor(props) { - super(props); - this.handleRowSelect = this.handleRowSelect.bind(this); - this.handleAllRowsSelect = this.handleAllRowsSelect.bind(this); - - props.store.selected = props.selectRow.selected || []; - this.state = { - selectedRowKeys: props.store.selected - }; - } - - componentWillReceiveProps(nextProps) { - nextProps.store.selected = nextProps.selectRow.selected || []; - this.setState(() => ({ - selectedRowKeys: nextProps.store.selected - })); - } - - /** - * row selection handler - * @param {String} rowKey - row key of what was selected. - * @param {Boolean} checked - next checked status of input button. - */ - handleRowSelect(rowKey, checked, rowIndex, e) { - const { selectRow: { mode, onSelect }, store } = this.props; - const { ROW_SELECT_SINGLE } = Const; - - let currSelected = [...store.selected]; - - if (mode === ROW_SELECT_SINGLE) { // when select mode is radio - currSelected = [rowKey]; - } else if (checked) { // when select mode is checkbox - currSelected.push(rowKey); - } else { - currSelected = currSelected.filter(value => value !== rowKey); - } - - store.selected = currSelected; - - if (onSelect) { - const row = getRowByRowId(store)(rowKey); - onSelect(row, checked, rowIndex, e); - } - - this.setState(() => ({ - selectedRowKeys: currSelected - })); - } - - /** - * handle all rows selection on header cell by store.selected - */ - handleAllRowsSelect(e) { - const { store, selectRow: { - onSelectAll, - nonSelectable - } } = this.props; - const selected = isAnySelectedRow(store)(nonSelectable); - - const result = !selected; - - const currSelected = result ? - selectableKeys(store)(nonSelectable) : - unSelectableKeys(store)(nonSelectable); - - - store.selected = currSelected; - - if (onSelectAll) { - onSelectAll(result, getSelectedRows(store), e); - } - - this.setState(() => ({ - selectedRowKeys: currSelected - })); - } - - render() { - return ( - - ); - } - }; diff --git a/packages/react-bootstrap-table2/src/store/rows.js b/packages/react-bootstrap-table2/src/store/rows.js index 4115122..c57de06 100644 --- a/packages/react-bootstrap-table2/src/store/rows.js +++ b/packages/react-bootstrap-table2/src/store/rows.js @@ -1,4 +1,4 @@ export const matchRow = (keyField, id) => row => row[keyField] === id; -export const getRowByRowId = ({ data, keyField }) => id => data.find(matchRow(keyField, id)); +export const getRowByRowId = (data, keyField, id) => data.find(matchRow(keyField, id)); diff --git a/packages/react-bootstrap-table2/src/store/selection.js b/packages/react-bootstrap-table2/src/store/selection.js index 4c6c10a..246c5f5 100644 --- a/packages/react-bootstrap-table2/src/store/selection.js +++ b/packages/react-bootstrap-table2/src/store/selection.js @@ -1,16 +1,16 @@ import _ from '../utils'; import { getRowByRowId } from './rows'; -export const isSelectedAll = ({ data, selected }) => data.length === selected.length; +export const isSelectedAll = (data, selected) => data.length === selected.length; -export const isAnySelectedRow = ({ selected }) => (skips = []) => { +export const isAnySelectedRow = (selected, skips = []) => { if (skips.length === 0) { return selected.length > 0; } return selected.filter(x => !skips.includes(x)).length; }; -export const selectableKeys = ({ data, keyField }) => (skips = []) => { +export const selectableKeys = (data, keyField, skips = []) => { if (skips.length === 0) { return data.map(row => _.get(row, keyField)); } @@ -19,15 +19,13 @@ export const selectableKeys = ({ data, keyField }) => (skips = []) => { .map(row => _.get(row, keyField)); }; -export const unSelectableKeys = ({ selected }) => (skips = []) => { +export const unSelectableKeys = (selected, skips = []) => { if (skips.length === 0) { return []; } return selected.filter(x => skips.includes(x)); }; -export const getSelectedRows = (store) => { - const getRow = getRowByRowId(store); - return store.selected.map(k => getRow(k)); -}; +export const getSelectedRows = (data, keyField, selected) => + selected.map(k => getRowByRowId(data, keyField, k)); From 2525465a5a9c2c519be6db4cd271d59a968a1d20 Mon Sep 17 00:00:00 2001 From: AllenFang Date: Sun, 13 May 2018 16:14:21 +0800 Subject: [PATCH 04/55] implement sort context --- .../src/contexts/sort-context.js | 88 +++++++++++++++++++ .../src/sort/wrapper.js | 81 ----------------- .../react-bootstrap-table2/src/store/sort.js | 22 ++--- 3 files changed, 99 insertions(+), 92 deletions(-) create mode 100644 packages/react-bootstrap-table2/src/contexts/sort-context.js delete mode 100644 packages/react-bootstrap-table2/src/sort/wrapper.js diff --git a/packages/react-bootstrap-table2/src/contexts/sort-context.js b/packages/react-bootstrap-table2/src/contexts/sort-context.js new file mode 100644 index 0000000..17d029e --- /dev/null +++ b/packages/react-bootstrap-table2/src/contexts/sort-context.js @@ -0,0 +1,88 @@ + +import React from 'react'; +import PropTypes from 'prop-types'; +import Const from '../const'; +import { sort, nextOrder } from '../store/sort'; +import remoteResolver from '../props-resolver/remote-resolver'; + +export default () => { + const SortContext = React.createContext(); + + class SortProvider extends remoteResolver(React.Component) { + static propTypes = { + columns: PropTypes.array.isRequired, + defaultSorted: PropTypes.arrayOf(PropTypes.shape({ + dataField: PropTypes.string.isRequired, + order: PropTypes.oneOf([Const.SORT_DESC, Const.SORT_ASC]).isRequired + })), + defaultSortDirection: PropTypes.oneOf([Const.SORT_DESC, Const.SORT_ASC]) + } + + constructor(props) { + super(props); + let sortOrder; + let sortColumn; + const { columns, defaultSorted, defaultSortDirection } = props; + + if (defaultSorted && defaultSorted.length > 0) { + const sortField = defaultSorted[0].dataField; + sortOrder = defaultSorted[0].order || defaultSortDirection; + const sortColumns = columns.filter(col => col.dataField === sortField); + if (sortColumns.length > 0) { + sortColumn = sortColumns[0]; + + if (sortColumn.onSort) { + sortColumn.onSort(sortField, sortOrder); + } + + if (this.isRemoteSort() || this.isRemotePagination()) { + this.handleSortChange(); + } + } + } + this.state = { sortOrder, sortColumn }; + } + + handleSort = (column) => { + const sortOrder = nextOrder(column, this.state, this.props.defaultSortDirection); + + if (column.onSort) { + column.onSort(column.dataField, sortOrder); + } + + if (this.isRemoteSort() || this.isRemotePagination()) { + this.handleSortChange(); + } else { + this.setState(() => ({ + sortOrder, + sortColumn: column + })); + } + } + + render() { + let { data } = this.props; + const { sortOrder, sortColumn } = this.state; + if (!this.isRemoteSort() && !this.isRemotePagination() && sortColumn) { + data = sort(data, sortOrder, sortColumn); + } + + return ( + + { this.props.children } + + ); + } + } + return { + Provider: SortProvider, + Consumer: SortContext.Consumer + }; +}; diff --git a/packages/react-bootstrap-table2/src/sort/wrapper.js b/packages/react-bootstrap-table2/src/sort/wrapper.js deleted file mode 100644 index 6d55f1c..0000000 --- a/packages/react-bootstrap-table2/src/sort/wrapper.js +++ /dev/null @@ -1,81 +0,0 @@ -/* eslint react/prop-types: 0 */ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; -import remoteResolver from '../props-resolver/remote-resolver'; - -export default Base => - class SortWrapper extends remoteResolver(Component) { - static propTypes = { - store: PropTypes.object.isRequired - } - - constructor(props) { - super(props); - this.handleSort = this.handleSort.bind(this); - } - - componentWillMount() { - const { columns, defaultSorted, defaultSortDirection, store } = this.props; - // defaultSorted is an array, it's ready to use as multi / single sort - // when we start to support multi sort, please update following code to use array.forEach - if (defaultSorted && defaultSorted.length > 0) { - const dataField = defaultSorted[0].dataField; - const order = defaultSorted[0].order; - const column = columns.filter(col => col.dataField === dataField); - if (column.length > 0) { - store.setSort(column[0], order, defaultSortDirection); - - if (column[0].onSort) { - column[0].onSort(store.sortField, store.sortOrder); - } - - if (this.isRemoteSort() || this.isRemotePagination()) { - this.handleSortChange(); - } else { - store.sortBy(column[0]); - } - } - } - } - - componentWillReceiveProps(nextProps) { - if (!this.isRemoteSort() && !this.isRemotePagination()) { - let sortedColumn; - for (let i = 0; i < nextProps.columns.length; i += 1) { - if (nextProps.columns[i].dataField === nextProps.store.sortField) { - sortedColumn = nextProps.columns[i]; - break; - } - } - if (sortedColumn && sortedColumn.sort) { - nextProps.store.sortBy(sortedColumn); - } - } - } - - handleSort(column) { - const { store } = this.props; - store.setSort(column, undefined, this.props.defaultSortDirection); - - if (column.onSort) { - column.onSort(store.sortField, store.sortOrder); - } - - if (this.isRemoteSort() || this.isRemotePagination()) { - this.handleSortChange(); - } else { - store.sortBy(column); - this.forceUpdate(); - } - } - - render() { - return ( - - ); - } - }; diff --git a/packages/react-bootstrap-table2/src/store/sort.js b/packages/react-bootstrap-table2/src/store/sort.js index 1987e0e..ecbfbd7 100644 --- a/packages/react-bootstrap-table2/src/store/sort.js +++ b/packages/react-bootstrap-table2/src/store/sort.js @@ -14,17 +14,17 @@ function comparator(a, b) { return result; } -export const sort = ({ data, sortOrder, sortField }) => (sortFunc) => { +export const sort = (data, sortOrder, { dataField, sortFunc }) => { const _data = [...data]; _data.sort((a, b) => { let result; - let valueA = _.get(a, sortField); - let valueB = _.get(b, sortField); + let valueA = _.get(a, dataField); + let valueB = _.get(b, dataField); valueA = _.isDefined(valueA) ? valueA : ''; valueB = _.isDefined(valueB) ? valueB : ''; if (sortFunc) { - result = sortFunc(valueA, valueB, sortOrder, sortField); + result = sortFunc(valueA, valueB, sortOrder, dataField); } else { if (sortOrder === Const.SORT_DESC) { result = comparator(valueA, valueB); @@ -37,11 +37,11 @@ export const sort = ({ data, sortOrder, sortField }) => (sortFunc) => { return _data; }; -export const nextOrder = store => (field, order, defaultOrder = Const.SORT_DESC) => { - if (order) return order; - - if (field !== store.sortField) { - return defaultOrder; - } - return store.sortOrder === Const.SORT_DESC ? Const.SORT_ASC : Const.SORT_DESC; +export const nextOrder = ( + currentSortColumn, + { sortOrder, sortColumn }, + defaultOrder = Const.SORT_DESC +) => { + if (!sortColumn || currentSortColumn.dataField !== sortColumn.dataField) return defaultOrder; + return sortOrder === Const.SORT_DESC ? Const.SORT_ASC : Const.SORT_DESC; }; From 143acde35ef7b4f35875820381b8da4cf63d79f6 Mon Sep 17 00:00:00 2001 From: AllenFang Date: Sat, 19 May 2018 13:24:15 +0800 Subject: [PATCH 05/55] refactoring remote sort --- .../src/contexts/index.js | 26 +++++++--------- .../src/contexts/sort-context.js | 31 ++++++++++--------- .../src/props-resolver/remote-resolver.js | 30 +++++++++--------- 3 files changed, 45 insertions(+), 42 deletions(-) diff --git a/packages/react-bootstrap-table2/src/contexts/index.js b/packages/react-bootstrap-table2/src/contexts/index.js index 6afb979..7267d80 100644 --- a/packages/react-bootstrap-table2/src/contexts/index.js +++ b/packages/react-bootstrap-table2/src/contexts/index.js @@ -1,16 +1,10 @@ /* eslint no-return-assign: 0 */ -/* eslint react/prop-types: 0 */ import React, { Component } from 'react'; -// import Store from './store'; -// import withSort from './sort/wrapper'; -// import withSelection from './row-selection/wrapper'; import createDataContext from './data-context'; import createSortContext from './sort-context'; import createSelectionContext from './selection-context'; - import remoteResolver from '../props-resolver/remote-resolver'; - const withContext = (Base) => { let DataContext; let SelectionContext; @@ -21,40 +15,44 @@ const withContext = (Base) => { super(props); DataContext = createDataContext(this.props.data); SelectionContext = createSelectionContext(); - SortContext = createSortContext(); + SortContext = createSortContext(this.isRemoteSort, this.handleSortChange); } render() { - const { keyField, columns, remote } = this.props; - const baseProps = { keyField, columns, remote }; + const { keyField, columns } = this.props; + const baseProps = { keyField, columns }; return ( - + { - dataProps => ( + rootProps => ( { selectionProps => ( this.sortProvider = n } { ...baseProps } defaultSorted={ this.props.defaultSorted } defaultSortDirection={ this.props.defaultSortDirection } - data={ dataProps } + data={ rootProps.data } > { sortProps => ( ) } diff --git a/packages/react-bootstrap-table2/src/contexts/sort-context.js b/packages/react-bootstrap-table2/src/contexts/sort-context.js index 17d029e..82474d1 100644 --- a/packages/react-bootstrap-table2/src/contexts/sort-context.js +++ b/packages/react-bootstrap-table2/src/contexts/sort-context.js @@ -1,16 +1,20 @@ - +/* eslint react/require-default-props: 0 */ import React from 'react'; import PropTypes from 'prop-types'; import Const from '../const'; import { sort, nextOrder } from '../store/sort'; -import remoteResolver from '../props-resolver/remote-resolver'; -export default () => { +export default ( + isRemoteSort, + handleSortChange +) => { const SortContext = React.createContext(); - class SortProvider extends remoteResolver(React.Component) { + class SortProvider extends React.Component { static propTypes = { + data: PropTypes.array.isRequired, columns: PropTypes.array.isRequired, + children: PropTypes.node.isRequired, defaultSorted: PropTypes.arrayOf(PropTypes.shape({ dataField: PropTypes.string.isRequired, order: PropTypes.oneOf([Const.SORT_DESC, Const.SORT_ASC]).isRequired @@ -35,8 +39,8 @@ export default () => { sortColumn.onSort(sortField, sortOrder); } - if (this.isRemoteSort() || this.isRemotePagination()) { - this.handleSortChange(); + if (isRemoteSort()) { + handleSortChange(sortField, sortOrder); } } } @@ -50,20 +54,19 @@ export default () => { column.onSort(column.dataField, sortOrder); } - if (this.isRemoteSort() || this.isRemotePagination()) { - this.handleSortChange(); - } else { - this.setState(() => ({ - sortOrder, - sortColumn: column - })); + if (isRemoteSort()) { + handleSortChange(column.dataField, sortOrder); } + this.setState(() => ({ + sortOrder, + sortColumn: column + })); } render() { let { data } = this.props; const { sortOrder, sortColumn } = this.state; - if (!this.isRemoteSort() && !this.isRemotePagination() && sortColumn) { + if (!isRemoteSort() && sortColumn) { data = sort(data, sortOrder, sortColumn); } diff --git a/packages/react-bootstrap-table2/src/props-resolver/remote-resolver.js b/packages/react-bootstrap-table2/src/props-resolver/remote-resolver.js index 6a44e57..dd3d665 100644 --- a/packages/react-bootstrap-table2/src/props-resolver/remote-resolver.js +++ b/packages/react-bootstrap-table2/src/props-resolver/remote-resolver.js @@ -2,17 +2,19 @@ import _ from '../utils'; export default ExtendBase => class RemoteResolver extends ExtendBase { + /* eslint class-methods-use-this: 0 */ getNewestState(state = {}) { - const store = this.store || this.props.store; - return { - page: store.page, - sizePerPage: store.sizePerPage, - filters: store.filters, - sortField: store.sortField, - sortOrder: store.sortOrder, - data: store.getAllData(), - ...state - }; + // const store = this.store || this.props.store; + // return { + // page: store.page, + // sizePerPage: store.sizePerPage, + // filters: store.filters, + // sortField: store.sortField, + // sortOrder: store.sortOrder, + // data: store.getAllData(), + // ...state + // }; + return { ...state, data: this.props.data }; } isRemotePagination() { @@ -25,9 +27,9 @@ export default ExtendBase => return remote === true || (_.isObject(remote) && remote.filter); } - isRemoteSort() { + isRemoteSort = () => { const { remote } = this.props; - return remote === true || (_.isObject(remote) && remote.sort); + return remote === true || (_.isObject(remote) && remote.sort) || this.isRemotePagination(); } isRemoteCellEdit() { @@ -48,8 +50,8 @@ export default ExtendBase => this.props.onTableChange('filter', this.getNewestState(newState)); } - handleSortChange() { - this.props.onTableChange('sort', this.getNewestState()); + handleSortChange = (sortField, sortOrder) => { + this.props.onTableChange('sort', this.getNewestState({ sortField, sortOrder })); } handleCellChange(rowId, dataField, newValue) { From 5307e5881332cb2c248ca99aa0cd8469a3ad349a Mon Sep 17 00:00:00 2001 From: AllenFang Date: Sat, 19 May 2018 16:23:28 +0800 Subject: [PATCH 06/55] implement data operator --- .../src/contexts/selection-context.js | 17 ++++++++--------- .../src/contexts/sort-context.js | 6 +++--- .../react-bootstrap-table2/src/store/mutate.js | 7 +++++++ .../src/store/operators.js | 11 +++++++++++ 4 files changed, 29 insertions(+), 12 deletions(-) create mode 100644 packages/react-bootstrap-table2/src/store/mutate.js create mode 100644 packages/react-bootstrap-table2/src/store/operators.js diff --git a/packages/react-bootstrap-table2/src/contexts/selection-context.js b/packages/react-bootstrap-table2/src/contexts/selection-context.js index 2ebfda2..2e171bb 100644 --- a/packages/react-bootstrap-table2/src/contexts/selection-context.js +++ b/packages/react-bootstrap-table2/src/contexts/selection-context.js @@ -2,11 +2,10 @@ import React from 'react'; import PropTypes from 'prop-types'; import Const from '../const'; -import { getRowByRowId } from '../store/rows'; -// Consider make selectionHandler become a part of Provider -import * as selectionHandler from '../store/selection'; -export default () => { +export default ( + dataOperator +) => { const SelectionContext = React.createContext(); class SelectionProvider extends React.Component { @@ -41,7 +40,7 @@ export default () => { } if (onSelect) { - const row = getRowByRowId(data, keyField, rowKey); + const row = dataOperator.getRowByRowId(data, keyField, rowKey); onSelect(row, checked, rowIndex, e); } @@ -58,16 +57,16 @@ export default () => { } } = this.props; const { selected } = this.state; - const anySelected = selectionHandler.isAnySelectedRow(selected, nonSelectable); + const anySelected = dataOperator.isAnySelectedRow(selected, nonSelectable); const result = !anySelected; const currSelected = result ? - selectionHandler.selectableKeys(data, keyField, nonSelectable) : - selectionHandler.unSelectableKeys(selected, nonSelectable); + dataOperator.selectableKeys(data, keyField, nonSelectable) : + dataOperator.unSelectableKeys(selected, nonSelectable); if (onSelectAll) { - onSelectAll(result, selectionHandler.getSelectedRows(data, keyField, currSelected), e); + onSelectAll(result, dataOperator.getSelectedRows(data, keyField, currSelected), e); } this.setState(() => ({ selected: currSelected })); diff --git a/packages/react-bootstrap-table2/src/contexts/sort-context.js b/packages/react-bootstrap-table2/src/contexts/sort-context.js index 82474d1..d71dbf5 100644 --- a/packages/react-bootstrap-table2/src/contexts/sort-context.js +++ b/packages/react-bootstrap-table2/src/contexts/sort-context.js @@ -2,9 +2,9 @@ import React from 'react'; import PropTypes from 'prop-types'; import Const from '../const'; -import { sort, nextOrder } from '../store/sort'; export default ( + dataOperator, isRemoteSort, handleSortChange ) => { @@ -48,7 +48,7 @@ export default ( } handleSort = (column) => { - const sortOrder = nextOrder(column, this.state, this.props.defaultSortDirection); + const sortOrder = dataOperator.nextOrder(column, this.state, this.props.defaultSortDirection); if (column.onSort) { column.onSort(column.dataField, sortOrder); @@ -67,7 +67,7 @@ export default ( let { data } = this.props; const { sortOrder, sortColumn } = this.state; if (!isRemoteSort() && sortColumn) { - data = sort(data, sortOrder, sortColumn); + data = dataOperator.sort(data, sortOrder, sortColumn); } return ( diff --git a/packages/react-bootstrap-table2/src/store/mutate.js b/packages/react-bootstrap-table2/src/store/mutate.js new file mode 100644 index 0000000..43cab2c --- /dev/null +++ b/packages/react-bootstrap-table2/src/store/mutate.js @@ -0,0 +1,7 @@ +import _ from '../utils'; +import { getRowByRowId } from './rows'; + +export const editCell = (rowId, dataField, newValue) => { + const row = getRowByRowId(this)(rowId); + if (row) _.set(row, dataField, newValue); +}; diff --git a/packages/react-bootstrap-table2/src/store/operators.js b/packages/react-bootstrap-table2/src/store/operators.js new file mode 100644 index 0000000..c01ddad --- /dev/null +++ b/packages/react-bootstrap-table2/src/store/operators.js @@ -0,0 +1,11 @@ +import * as rows from './rows'; +import * as selection from './selection'; +import * as mutate from './mutate'; +import * as sort from './sort'; + +export default { + ...rows, + ...selection, + ...mutate, + ...sort +}; From 216bc1014279acc23c3c7e9c8de325d57b18db9e Mon Sep 17 00:00:00 2001 From: AllenFang Date: Sat, 19 May 2018 18:07:07 +0800 Subject: [PATCH 07/55] implement celledit context --- .../react-bootstrap-table2-editor/index.js | 4 +- .../src/{wrapper.js => context.js} | 63 +++++----- .../src/contexts/index.js | 114 ++++++++++++------ .../src/props-resolver/remote-resolver.js | 15 ++- .../src/store/mutate.js | 4 +- 5 files changed, 122 insertions(+), 78 deletions(-) rename packages/react-bootstrap-table2-editor/src/{wrapper.js => context.js} (72%) diff --git a/packages/react-bootstrap-table2-editor/index.js b/packages/react-bootstrap-table2-editor/index.js index 3d07c2c..0bc57b5 100644 --- a/packages/react-bootstrap-table2-editor/index.js +++ b/packages/react-bootstrap-table2-editor/index.js @@ -1,4 +1,4 @@ -import wrapperFactory from './src/wrapper'; +import createContext from './src/context'; import editingCellFactory from './src/editing-cell'; import { EDITTYPE, @@ -8,7 +8,7 @@ import { } from './src/const'; export default (options = {}) => ({ - wrapperFactory, + createContext, editingCellFactory, CLICK_TO_CELL_EDIT, DBCLICK_TO_CELL_EDIT, diff --git a/packages/react-bootstrap-table2-editor/src/wrapper.js b/packages/react-bootstrap-table2-editor/src/context.js similarity index 72% rename from packages/react-bootstrap-table2-editor/src/wrapper.js rename to packages/react-bootstrap-table2-editor/src/context.js index 97138d4..da0930d 100644 --- a/packages/react-bootstrap-table2-editor/src/wrapper.js +++ b/packages/react-bootstrap-table2-editor/src/context.js @@ -1,16 +1,21 @@ /* eslint react/prop-types: 0 */ -import React, { Component } from 'react'; +/* eslint react/require-default-props: 0 */ +import React from 'react'; import PropTypes from 'prop-types'; - import { CLICK_TO_CELL_EDIT, DBCLICK_TO_CELL_EDIT } from './const'; export default ( - Base, - { _, remoteResolver } + _, + dataOperator, + isRemoteCellEdit, + handleCellChange ) => { let EditingCell; - return class CellEditWrapper extends remoteResolver(Component) { + const CellEditContext = React.createContext(); + + class CellEditProvider extends React.Component { static propTypes = { + data: PropTypes.array.isRequired, options: PropTypes.shape({ mode: PropTypes.oneOf([CLICK_TO_CELL_EDIT, DBCLICK_TO_CELL_EDIT]).isRequired, onErrorMessageDisappear: PropTypes.func, @@ -19,7 +24,7 @@ export default ( afterSaveCell: PropTypes.func, nonEditableRows: PropTypes.func, timeToCloseMessage: PropTypes.number, - errorMessage: PropTypes.string + errorMessage: PropTypes.any }) } @@ -33,41 +38,32 @@ export default ( this.state = { ridx: null, cidx: null, - message: null, - isDataChanged: false + message: null }; } componentWillReceiveProps(nextProps) { - if (nextProps.cellEdit && this.isRemoteCellEdit()) { + if (nextProps.cellEdit && isRemoteCellEdit()) { if (nextProps.cellEdit.options.errorMessage) { this.setState(() => ({ - isDataChanged: false, message: nextProps.cellEdit.options.errorMessage })); } else { - this.setState(() => ({ - isDataChanged: true - })); this.escapeEditing(); } - } else { - this.setState(() => ({ - isDataChanged: false - })); } } handleCellUpdate(row, column, newValue) { - const { keyField, cellEdit, store } = this.props; + const { keyField, cellEdit, data } = this.props; const { beforeSaveCell, afterSaveCell } = cellEdit.options; const oldValue = _.get(row, column.dataField); const rowId = _.get(row, keyField); if (_.isFunction(beforeSaveCell)) beforeSaveCell(oldValue, newValue, row, column); - if (this.isRemoteCellEdit()) { - this.handleCellChange(rowId, column.dataField, newValue); + if (isRemoteCellEdit()) { + handleCellChange(rowId, column.dataField, newValue); } else { - store.edit(rowId, column.dataField, newValue); + dataOperator.editCell(data, keyField, rowId, column.dataField, newValue); if (_.isFunction(afterSaveCell)) afterSaveCell(oldValue, newValue, row, column); this.completeEditing(); } @@ -77,8 +73,7 @@ export default ( this.setState(() => ({ ridx: null, cidx: null, - message: null, - isDataChanged: true + message: null })); } @@ -86,8 +81,7 @@ export default ( const editing = () => { this.setState(() => ({ ridx, - cidx, - isDataChanged: false + cidx })); }; @@ -103,7 +97,6 @@ export default ( } render() { - const { isDataChanged, ...stateRest } = this.state; const { cellEdit: { options: { nonEditableRows, errorMessage, ...optionsRest }, @@ -111,10 +104,11 @@ export default ( ...cellEditRest } } = this.props; + const newCellEdit = { ...optionsRest, ...cellEditRest, - ...stateRest, + ...this.state, EditingCell, nonEditableRows: _.isDefined(nonEditableRows) ? nonEditableRows() : [], onStart: this.startEditing, @@ -123,13 +117,16 @@ export default ( }; return ( - + + { this.props.children } + ); } + } + return { + Provider: CellEditProvider, + Consumer: CellEditContext.Consumer }; }; diff --git a/packages/react-bootstrap-table2/src/contexts/index.js b/packages/react-bootstrap-table2/src/contexts/index.js index 7267d80..20ed702 100644 --- a/packages/react-bootstrap-table2/src/contexts/index.js +++ b/packages/react-bootstrap-table2/src/contexts/index.js @@ -1,27 +1,100 @@ /* eslint no-return-assign: 0 */ import React, { Component } from 'react'; +import _ from '../utils'; import createDataContext from './data-context'; import createSortContext from './sort-context'; import createSelectionContext from './selection-context'; import remoteResolver from '../props-resolver/remote-resolver'; +import dataOperator from '../store/operators'; const withContext = (Base) => { let DataContext; let SelectionContext; + let CellEditContext; let SortContext; return class BootstrapTableContainer extends remoteResolver(Component) { constructor(props) { super(props); - DataContext = createDataContext(this.props.data); - SelectionContext = createSelectionContext(); - SortContext = createSortContext(this.isRemoteSort, this.handleSortChange); + DataContext = createDataContext(props.data); + SelectionContext = createSelectionContext(dataOperator); + SortContext = createSortContext(dataOperator, this.isRemoteSort, this.handleSortChange); + if (props.cellEdit && props.cellEdit.createContext) { + CellEditContext = props.cellEdit.createContext( + _, dataOperator, this.isRemoteCellEdit, this.handleCellChange); + } + } + + componentWillReceiveProps(nextProps) { + if (!nextProps.cellEdit) { + CellEditContext = null; + } + } + + renderBase(baseProps) { + return (rootProps, cellEditProps) => ( + this.sortContext = n } + defaultSorted={ this.props.defaultSorted } + defaultSortDirection={ this.props.defaultSortDirection } + data={ rootProps.data } + > + + { + sortProps => ( + + + { + selectionProps => ( + + ) + } + + + ) + } + + + ); + } + + renderWithCellEdit(base, baseProps) { + return rootProps => ( + + + { + cellEditprops => base(rootProps, cellEditprops) + } + + + ); } render() { const { keyField, columns } = this.props; const baseProps = { keyField, columns }; + let base = this.renderBase(baseProps); + + if (CellEditContext) { + base = this.renderWithCellEdit(base, baseProps); + } + return ( { > { - rootProps => ( - - - { - selectionProps => ( - this.sortProvider = n } - { ...baseProps } - defaultSorted={ this.props.defaultSorted } - defaultSortDirection={ this.props.defaultSortDirection } - data={ rootProps.data } - > - - { - sortProps => ( - - ) - } - - - ) - } - - - ) + base } diff --git a/packages/react-bootstrap-table2/src/props-resolver/remote-resolver.js b/packages/react-bootstrap-table2/src/props-resolver/remote-resolver.js index dd3d665..4d0e833 100644 --- a/packages/react-bootstrap-table2/src/props-resolver/remote-resolver.js +++ b/packages/react-bootstrap-table2/src/props-resolver/remote-resolver.js @@ -3,7 +3,7 @@ import _ from '../utils'; export default ExtendBase => class RemoteResolver extends ExtendBase { /* eslint class-methods-use-this: 0 */ - getNewestState(state = {}) { + getNewestState = (state = {}) => { // const store = this.store || this.props.store; // return { // page: store.page, @@ -14,7 +14,14 @@ export default ExtendBase => // data: store.getAllData(), // ...state // }; - return { ...state, data: this.props.data }; + return { + sortOrder: this.sortContext.state.sortOrder, + sortField: this.sortContext.state.sortColumn ? + this.sortContext.state.sortColumn.dataField : + null, + ...state, + data: this.props.data + }; } isRemotePagination() { @@ -32,7 +39,7 @@ export default ExtendBase => return remote === true || (_.isObject(remote) && remote.sort) || this.isRemotePagination(); } - isRemoteCellEdit() { + isRemoteCellEdit = () => { const { remote } = this.props; return remote === true || (_.isObject(remote) && remote.cellEdit); } @@ -54,7 +61,7 @@ export default ExtendBase => this.props.onTableChange('sort', this.getNewestState({ sortField, sortOrder })); } - handleCellChange(rowId, dataField, newValue) { + handleCellChange = (rowId, dataField, newValue) => { const cellEdit = { rowId, dataField, newValue }; this.props.onTableChange('cellEdit', this.getNewestState({ cellEdit })); } diff --git a/packages/react-bootstrap-table2/src/store/mutate.js b/packages/react-bootstrap-table2/src/store/mutate.js index 43cab2c..9e59bab 100644 --- a/packages/react-bootstrap-table2/src/store/mutate.js +++ b/packages/react-bootstrap-table2/src/store/mutate.js @@ -1,7 +1,7 @@ import _ from '../utils'; import { getRowByRowId } from './rows'; -export const editCell = (rowId, dataField, newValue) => { - const row = getRowByRowId(this)(rowId); +export const editCell = (data, keyField, rowId, dataField, newValue) => { + const row = getRowByRowId(data, keyField, rowId); if (row) _.set(row, dataField, newValue); }; From 4f6809de844d5e4bb263f34dea2accc7206d785a Mon Sep 17 00:00:00 2001 From: AllenFang Date: Sun, 20 May 2018 13:50:03 +0800 Subject: [PATCH 08/55] fix custom filter value example broken --- .../examples/column-filter/custom-filter-value.js | 4 ++-- .../react-bootstrap-table2-example/src/utils/common.js | 8 ++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/react-bootstrap-table2-example/examples/column-filter/custom-filter-value.js b/packages/react-bootstrap-table2-example/examples/column-filter/custom-filter-value.js index 0901eb6..bfc9383 100644 --- a/packages/react-bootstrap-table2-example/examples/column-filter/custom-filter-value.js +++ b/packages/react-bootstrap-table2-example/examples/column-filter/custom-filter-value.js @@ -3,9 +3,9 @@ import React from 'react'; import BootstrapTable from 'react-bootstrap-table-next'; import filterFactory, { textFilter } from 'react-bootstrap-table2-filter'; import Code from 'components/common/code-block'; -import { jobsGenerator } from 'utils/common'; +import { jobsGenerator1 } from 'utils/common'; -const jobs = jobsGenerator(5); +const jobs = jobsGenerator1(5); const owners = ['Allen', 'Bob', 'Cat']; const types = ['Cloud Service', 'Message Service', 'Add Service', 'Edit Service', 'Money']; diff --git a/packages/react-bootstrap-table2-example/src/utils/common.js b/packages/react-bootstrap-table2-example/src/utils/common.js index b8f954e..037e639 100644 --- a/packages/react-bootstrap-table2-example/src/utils/common.js +++ b/packages/react-bootstrap-table2-example/src/utils/common.js @@ -41,6 +41,14 @@ export const jobsGenerator = (quantity = 5) => type: jobType[Math.floor((Math.random() * 4) + 1)] })); +export const jobsGenerator1 = (quantity = 5) => + Array.from({ length: quantity }, (value, index) => ({ + id: index, + name: `Job name ${index}`, + owner: Math.floor((Math.random() * 2) + 1), + type: Math.floor((Math.random() * 4) + 1) + })); + export const todosGenerator = (quantity = 5) => Array.from({ length: quantity }, (value, index) => ({ id: index, From 2f7d0104a0b4907fa47f39d12cb8050b98e16162 Mon Sep 17 00:00:00 2001 From: AllenFang Date: Sun, 20 May 2018 14:33:25 +0800 Subject: [PATCH 09/55] add clear all filter story --- .../column-filter/clear-all-filters.js | 86 +++++++++++++++++++ .../stories/index.js | 4 +- 2 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 packages/react-bootstrap-table2-example/examples/column-filter/clear-all-filters.js diff --git a/packages/react-bootstrap-table2-example/examples/column-filter/clear-all-filters.js b/packages/react-bootstrap-table2-example/examples/column-filter/clear-all-filters.js new file mode 100644 index 0000000..d0005fd --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/column-filter/clear-all-filters.js @@ -0,0 +1,86 @@ +import React from 'react'; +import BootstrapTable from 'react-bootstrap-table-next'; +import filterFactory, { textFilter } from 'react-bootstrap-table2-filter'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(8); + +let nameFilter; +let priceFilter; + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name', + filter: textFilter({ + getFilter: (filter) => { + nameFilter = filter; + } + }) +}, { + dataField: 'price', + text: 'Product Price', + filter: textFilter({ + getFilter: (filter) => { + priceFilter = filter; + } + }) +}]; + +const handleClick = () => { + nameFilter(''); + priceFilter(''); +}; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; +import filterFactory, { textFilter } from 'react-bootstrap-table2-filter'; + +let nameFilter; + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name', + filter: textFilter({ + getFilter: (filter) => { + // nameFilter was assigned once the component has been mounted. + nameFilter = filter; + } + }) +}, { + dataField: 'price', + text: 'Product Price', + filter: textFilter() +}]; + +const handleClick = () => { + nameFilter(0); +}; + +export default () => ( +
+ + + +
+); +`; + +export default () => ( +
+ + + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/stories/index.js b/packages/react-bootstrap-table2-example/stories/index.js index 0617922..8f319f5 100644 --- a/packages/react-bootstrap-table2-example/stories/index.js +++ b/packages/react-bootstrap-table2-example/stories/index.js @@ -62,6 +62,7 @@ import ProgrammaticallyDateFilter from 'examples/column-filter/programmatically- import ProgrammaticallyMultiSelectFilter from 'examples/column-filter/programmatically-multi-select-filter'; import CustomFilter from 'examples/column-filter/custom-filter'; import AdvanceCustomFilter from 'examples/column-filter/advance-custom-filter'; +import ClearAllFilters from 'examples/column-filter/clear-all-filters'; // work on rows import RowStyleTable from 'examples/rows/row-style'; @@ -200,7 +201,8 @@ storiesOf('Column Filter', module) .add('Programmatically Date Filter', () => ) .add('Programmatically Multi Select Filter', () => ) .add('Custom Filter', () => ) - .add('Advance Custom Filter', () => ); + .add('Advance Custom Filter', () => ) + .add('Clear All Filters', () => ); storiesOf('Work on Rows', module) .add('Customize Row Style', () => ) From 8f4dc9907a5772041e8c6bd5dbbffb9714c57b8f Mon Sep 17 00:00:00 2001 From: AllenFang Date: Sun, 20 May 2018 14:36:16 +0800 Subject: [PATCH 10/55] implement filter context --- .../react-bootstrap-table2-filter/index.js | 4 +- .../src/context.js | 94 +++++++++++++++ .../src/filter.js | 4 +- .../src/wrapper.js | 107 ------------------ .../src/contexts/index.js | 31 ++++- .../src/props-resolver/remote-resolver.js | 9 +- 6 files changed, 132 insertions(+), 117 deletions(-) create mode 100644 packages/react-bootstrap-table2-filter/src/context.js delete mode 100644 packages/react-bootstrap-table2-filter/src/wrapper.js diff --git a/packages/react-bootstrap-table2-filter/index.js b/packages/react-bootstrap-table2-filter/index.js index 6d9368a..af67dfc 100644 --- a/packages/react-bootstrap-table2-filter/index.js +++ b/packages/react-bootstrap-table2-filter/index.js @@ -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 }); diff --git a/packages/react-bootstrap-table2-filter/src/context.js b/packages/react-bootstrap-table2-filter/src/context.js new file mode 100644 index 0000000..1459b3f --- /dev/null +++ b/packages/react-bootstrap-table2-filter/src/context.js @@ -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 ( + + { this.props.children } + + ); + } + } + + return { + Provider: FilterProvider, + Consumer: FilterContext.Consumer + }; +}; diff --git a/packages/react-bootstrap-table2-filter/src/filter.js b/packages/react-bootstrap-table2-filter/src/filter.js index c1a8666..07454c4 100644 --- a/packages/react-bootstrap-table2-filter/src/filter.js +++ b/packages/react-bootstrap-table2-filter/src/filter.js @@ -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]; diff --git a/packages/react-bootstrap-table2-filter/src/wrapper.js b/packages/react-bootstrap-table2-filter/src/wrapper.js deleted file mode 100644 index 0602ee8..0000000 --- a/packages/react-bootstrap-table2-filter/src/wrapper.js +++ /dev/null @@ -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 ( - - ); - } - }; diff --git a/packages/react-bootstrap-table2/src/contexts/index.js b/packages/react-bootstrap-table2/src/contexts/index.js index 20ed702..f01d7d3 100644 --- a/packages/react-bootstrap-table2/src/contexts/index.js +++ b/packages/react-bootstrap-table2/src/contexts/index.js @@ -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) => ( this.sortContext = n } defaultSorted={ this.props.defaultSorted } defaultSortDirection={ this.props.defaultSortDirection } - data={ rootProps.data } + data={ filterProps ? filterProps.data : rootProps.data } > { @@ -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) => ( + this.filterContext = n } + data={ rootProps.data } + > + + { + filterProps => base(rootProps, cellEditprops, filterProps) + } + + + ); + } + renderWithCellEdit(base, baseProps) { return rootProps => ( { let base = this.renderBase(baseProps); + if (FilterContext) { + base = this.renderWithFilter(base, baseProps); + } + if (CellEditContext) { base = this.renderWithCellEdit(base, baseProps); } diff --git a/packages/react-bootstrap-table2/src/props-resolver/remote-resolver.js b/packages/react-bootstrap-table2/src/props-resolver/remote-resolver.js index 4d0e833..a6c94f6 100644 --- a/packages/react-bootstrap-table2/src/props-resolver/remote-resolver.js +++ b/packages/react-bootstrap-table2/src/props-resolver/remote-resolver.js @@ -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; From 1e72c8056656f82e87713210ff823e17cb16d201 Mon Sep 17 00:00:00 2001 From: AllenFang Date: Sun, 20 May 2018 15:51:13 +0800 Subject: [PATCH 11/55] construct context dynamically --- .../src/contexts/data-context.js | 9 +- .../src/contexts/index.js | 127 +++++++++++++----- .../src/store/selection.js | 2 +- 3 files changed, 101 insertions(+), 37 deletions(-) diff --git a/packages/react-bootstrap-table2/src/contexts/data-context.js b/packages/react-bootstrap-table2/src/contexts/data-context.js index 9a08976..5e34a84 100644 --- a/packages/react-bootstrap-table2/src/contexts/data-context.js +++ b/packages/react-bootstrap-table2/src/contexts/data-context.js @@ -16,11 +16,18 @@ export default () => { this.setState(() => ({ data: nextProps.data })); } + getData = (filterProps, sortProps) => { + if (sortProps) return sortProps.data; + else if (filterProps) return filterProps.data; + return this.props.data; + } + render() { return ( { this.props.children } diff --git a/packages/react-bootstrap-table2/src/contexts/index.js b/packages/react-bootstrap-table2/src/contexts/index.js index f01d7d3..37562f4 100644 --- a/packages/react-bootstrap-table2/src/contexts/index.js +++ b/packages/react-bootstrap-table2/src/contexts/index.js @@ -19,12 +19,20 @@ const withContext = (Base) => { constructor(props) { super(props); DataContext = createDataContext(props.data); - SelectionContext = createSelectionContext(dataOperator); - SortContext = createSortContext(dataOperator, this.isRemoteSort, this.handleSortChange); + + if (props.columns.filter(col => col.sort).length > 0) { + SortContext = createSortContext(dataOperator, this.isRemoteSort, this.handleSortChange); + } + + if (props.selectRow) { + SelectionContext = createSelectionContext(dataOperator); + } + if (props.cellEdit && props.cellEdit.createContext) { CellEditContext = props.cellEdit.createContext( _, dataOperator, this.isRemoteCellEdit, this.handleCellChange); } + if (props.filter) { FilterContext = props.filter.createContext( _, this.isRemoteFiltering, this.handleRemoteFilterChange); @@ -37,38 +45,72 @@ const withContext = (Base) => { } } - renderBase(baseProps) { - return (rootProps, cellEditProps, filterProps) => ( + renderBase() { + return ( + rootProps, + cellEditProps, + filterProps, + sortProps, + selectionProps + ) => ( + + ); + } + + renderWithSelectionCtx(base, baseProps) { + return ( + rootProps, + cellEditProps, + filterProps, + sortProps + ) => ( + + + { + selectionProps => base( + rootProps, + cellEditProps, + filterProps, + sortProps, + selectionProps + ) + } + + + ); + } + + renderWithSortCtx(base, baseProps) { + return ( + rootProps, + cellEditProps, + filterProps + ) => ( this.sortContext = n } defaultSorted={ this.props.defaultSorted } defaultSortDirection={ this.props.defaultSortDirection } - data={ filterProps ? filterProps.data : rootProps.data } + data={ rootProps.getData(filterProps) } > { - sortProps => ( - - - { - selectionProps => ( - - ) - } - - + sortProps => base( + rootProps, + cellEditProps, + filterProps, + sortProps ) } @@ -76,28 +118,35 @@ const withContext = (Base) => { ); } - renderWithFilter(base, baseProps) { - return (rootProps, cellEditprops) => ( + renderWithFilterCtx(base, baseProps) { + return ( + rootProps, + cellEditprops + ) => ( this.filterContext = n } - data={ rootProps.data } + data={ rootProps.getData() } > { - filterProps => base(rootProps, cellEditprops, filterProps) + filterProps => base( + rootProps, + cellEditprops, + filterProps + ) } ); } - renderWithCellEdit(base, baseProps) { + renderWithCellEditCtx(base, baseProps) { return rootProps => ( { @@ -112,14 +161,22 @@ const withContext = (Base) => { const { keyField, columns } = this.props; const baseProps = { keyField, columns }; - let base = this.renderBase(baseProps); + let base = this.renderBase(); + + if (SelectionContext) { + base = this.renderWithSelectionCtx(base, baseProps); + } + + if (SortContext) { + base = this.renderWithSortCtx(base, baseProps); + } if (FilterContext) { - base = this.renderWithFilter(base, baseProps); + base = this.renderWithFilterCtx(base, baseProps); } if (CellEditContext) { - base = this.renderWithCellEdit(base, baseProps); + base = this.renderWithCellEditCtx(base, baseProps); } return ( diff --git a/packages/react-bootstrap-table2/src/store/selection.js b/packages/react-bootstrap-table2/src/store/selection.js index 246c5f5..6e66121 100644 --- a/packages/react-bootstrap-table2/src/store/selection.js +++ b/packages/react-bootstrap-table2/src/store/selection.js @@ -1,7 +1,7 @@ import _ from '../utils'; import { getRowByRowId } from './rows'; -export const isSelectedAll = (data, selected) => data.length === selected.length; +export const isSelectedAll = (data, selected = []) => data.length === selected.length; export const isAnySelectedRow = (selected, skips = []) => { if (skips.length === 0) { From 6c086c3892feb20cb678314bcee2e78c2294a249 Mon Sep 17 00:00:00 2001 From: AllenFang Date: Sun, 20 May 2018 16:35:35 +0800 Subject: [PATCH 12/55] implement pagination context --- .../react-bootstrap-table2-paginator/index.js | 4 +- .../src/{wrapper.js => context.js} | 149 ++++++++++-------- .../src/page.js | 31 ++-- .../src/contexts/data-context.js | 5 +- .../src/contexts/index.js | 47 +++++- .../src/props-resolver/remote-resolver.js | 49 +++--- 6 files changed, 179 insertions(+), 106 deletions(-) rename packages/react-bootstrap-table2-paginator/src/{wrapper.js => context.js} (50%) diff --git a/packages/react-bootstrap-table2-paginator/index.js b/packages/react-bootstrap-table2-paginator/index.js index edd0067..8750183 100644 --- a/packages/react-bootstrap-table2-paginator/index.js +++ b/packages/react-bootstrap-table2-paginator/index.js @@ -1,6 +1,6 @@ -import wrapperFactory from './src/wrapper'; +import createContext from './src/context'; export default (options = {}) => ({ - wrapperFactory, + createContext, options }); diff --git a/packages/react-bootstrap-table2-paginator/src/wrapper.js b/packages/react-bootstrap-table2-paginator/src/context.js similarity index 50% rename from packages/react-bootstrap-table2-paginator/src/wrapper.js rename to packages/react-bootstrap-table2-paginator/src/context.js index 784687a..2a60d14 100644 --- a/packages/react-bootstrap-table2-paginator/src/wrapper.js +++ b/packages/react-bootstrap-table2-paginator/src/context.js @@ -1,17 +1,21 @@ /* eslint react/prop-types: 0 */ -import React, { Component } from 'react'; +/* eslint react/require-default-props: 0 */ +import React from 'react'; import PropTypes from 'prop-types'; import Const from './const'; import Pagination from './pagination'; import { getByCurrPage, alignPage } from './page'; -export default (Base, { - remoteResolver -}) => - class PaginationWrapper extends remoteResolver(Component) { +export default ( + isRemotePagination, + handleRemotePageChange +) => { + const PaginationContext = React.createContext(); + + class PaginationProvider extends React.Component { static propTypes = { - store: PropTypes.object.isRequired + data: PropTypes.array.isRequired } constructor(props) { @@ -42,13 +46,13 @@ export default (Base, { currSizePerPage = sizePerPageList[0]; } - this.state = { currPage, currSizePerPage }; - this.saveToStore(currPage, currSizePerPage); + this.currPage = currPage; + this.currSizePerPage = currSizePerPage; } componentWillReceiveProps(nextProps) { let needNewState = false; - let { currPage, currSizePerPage } = this.state; + let { currPage, currSizePerPage } = this; const { page, sizePerPage, onPageChange } = nextProps.pagination.options; const pageStartIndex = typeof nextProps.pagination.options.pageStartIndex !== 'undefined' ? @@ -57,70 +61,64 @@ export default (Base, { if (typeof page !== 'undefined' && currPage !== page) { // user defined page currPage = page; needNewState = true; - } else if (nextProps.isDataChanged) { - currPage = alignPage(this.props.store, pageStartIndex, currSizePerPage); + } else { + currPage = alignPage(nextProps.data, currPage, currSizePerPage, pageStartIndex); needNewState = true; } - if (typeof currPage === 'undefined') { - currPage = pageStartIndex; - } - - if (typeof sizePerPage !== 'undefined') { + if (typeof sizePerPage !== 'undefined' && currSizePerPage !== sizePerPage) { currSizePerPage = sizePerPage; needNewState = true; } - this.saveToStore(currPage, currSizePerPage); - if (needNewState) { if (onPageChange) { onPageChange(currPage, currSizePerPage); } - this.setState(() => ({ currPage, currSizePerPage })); + + this.currPage = currPage; + this.currSizePerPage = currSizePerPage; } } - saveToStore(page, sizePerPage) { - this.props.store.page = page; - this.props.store.sizePerPage = sizePerPage; - } - handleChangePage(currPage) { - const { currSizePerPage } = this.state; + const { currSizePerPage } = this; const { pagination: { options } } = this.props; - this.saveToStore(currPage, currSizePerPage); if (options.onPageChange) { options.onPageChange(currPage, currSizePerPage); } - if (this.isRemotePagination()) { - this.handleRemotePageChange(); + + this.currPage = currPage; + + if (isRemotePagination()) { + handleRemotePageChange(currPage, currSizePerPage); return; } - this.setState(() => ({ currPage })); + this.forceUpdate(); } handleChangeSizePerPage(currSizePerPage, currPage) { const { pagination: { options } } = this.props; - this.saveToStore(currPage, currSizePerPage); if (options.onSizePerPageChange) { options.onSizePerPageChange(currSizePerPage, currPage); } - if (this.isRemotePagination()) { - this.handleRemotePageChange(); + + this.currPage = currPage; + this.currSizePerPage = currSizePerPage; + + if (isRemotePagination()) { + handleRemotePageChange(currPage, currSizePerPage); return; } - this.setState(() => ({ - currPage, - currSizePerPage - })); + this.forceUpdate(); } render() { - const { pagination: { options }, store } = this.props; - const { currPage, currSizePerPage } = this.state; + let { data } = this.props; + const { pagination: { options } } = this.props; + const { currPage, currSizePerPage } = this; const withFirstAndLast = typeof options.withFirstAndLast === 'undefined' ? Const.With_FIRST_AND_LAST : options.withFirstAndLast; const alwaysShowAllBtns = typeof options.alwaysShowAllBtns === 'undefined' ? @@ -132,37 +130,50 @@ export default (Base, { const pageStartIndex = typeof options.pageStartIndex === 'undefined' ? Const.PAGE_START_INDEX : options.pageStartIndex; - const data = this.isRemotePagination() ? - this.props.data : - getByCurrPage(store, pageStartIndex); + data = isRemotePagination() ? + data : + getByCurrPage( + data, + currPage, + currSizePerPage, + pageStartIndex + ); - return [ - , - - ]; + return ( + + { this.props.children } + + + ); } + } + + return { + Provider: PaginationProvider, + Consumer: PaginationContext.Consumer }; +}; diff --git a/packages/react-bootstrap-table2-paginator/src/page.js b/packages/react-bootstrap-table2-paginator/src/page.js index 3a9b863..40d2a8a 100644 --- a/packages/react-bootstrap-table2-paginator/src/page.js +++ b/packages/react-bootstrap-table2-paginator/src/page.js @@ -1,5 +1,3 @@ -/* eslint no-param-reassign: 0 */ - const getNormalizedPage = ( page, pageStartIndex @@ -19,25 +17,36 @@ const startIndex = ( sizePerPage, ) => end - (sizePerPage - 1); -export const alignPage = (store, pageStartIndex, sizePerPage) => { - const end = endIndex(store.page, sizePerPage, pageStartIndex); - const dataSize = store.data.length; +export const alignPage = ( + data, + page, + sizePerPage, + pageStartIndex +) => { + const end = endIndex(page, sizePerPage, pageStartIndex); + const dataSize = data.length; if (end - 1 > dataSize) { return pageStartIndex; } - return store.page; + return page; }; -export const getByCurrPage = (store, pageStartIndex) => { - const dataSize = store.data.length; +export const getByCurrPage = ( + data, + page, + sizePerPage, + pageStartIndex +) => { + const dataSize = data.length; if (!dataSize) return []; - const end = endIndex(store.page, store.sizePerPage, pageStartIndex); - const start = startIndex(end, store.sizePerPage); + + const end = endIndex(page, sizePerPage, pageStartIndex); + const start = startIndex(end, sizePerPage); const result = []; for (let i = start; i <= end; i += 1) { - result.push(store.data[i]); + result.push(data[i]); if (i + 1 === dataSize) break; } return result; diff --git a/packages/react-bootstrap-table2/src/contexts/data-context.js b/packages/react-bootstrap-table2/src/contexts/data-context.js index 5e34a84..8452509 100644 --- a/packages/react-bootstrap-table2/src/contexts/data-context.js +++ b/packages/react-bootstrap-table2/src/contexts/data-context.js @@ -16,8 +16,9 @@ export default () => { this.setState(() => ({ data: nextProps.data })); } - getData = (filterProps, sortProps) => { - if (sortProps) return sortProps.data; + getData = (filterProps, sortProps, paginationProps) => { + if (paginationProps) return paginationProps.data; + else if (sortProps) return sortProps.data; else if (filterProps) return filterProps.data; return this.props.data; } diff --git a/packages/react-bootstrap-table2/src/contexts/index.js b/packages/react-bootstrap-table2/src/contexts/index.js index 37562f4..3d6e644 100644 --- a/packages/react-bootstrap-table2/src/contexts/index.js +++ b/packages/react-bootstrap-table2/src/contexts/index.js @@ -14,6 +14,7 @@ const withContext = (Base) => { let CellEditContext; let SortContext; let FilterContext; + let PaginationContext; return class BootstrapTableContainer extends remoteResolver(Component) { constructor(props) { @@ -37,6 +38,11 @@ const withContext = (Base) => { FilterContext = props.filter.createContext( _, this.isRemoteFiltering, this.handleRemoteFilterChange); } + + if (props.pagination) { + PaginationContext = props.pagination.createContext( + this.isRemotePagination, this.handleRemotePageChange); + } } componentWillReceiveProps(nextProps) { @@ -51,6 +57,7 @@ const withContext = (Base) => { cellEditProps, filterProps, sortProps, + paginationProps, selectionProps ) => ( { { ...sortProps } { ...cellEditProps } { ...filterProps } - data={ rootProps.getData(filterProps, sortProps) } + { ...paginationProps } + data={ rootProps.getData(filterProps, sortProps, paginationProps) } /> ); } @@ -69,12 +77,13 @@ const withContext = (Base) => { rootProps, cellEditProps, filterProps, - sortProps + sortProps, + paginationProps ) => ( { @@ -83,6 +92,7 @@ const withContext = (Base) => { cellEditProps, filterProps, sortProps, + paginationProps, selectionProps ) } @@ -91,6 +101,33 @@ const withContext = (Base) => { ); } + renderWithPaginationCtx(base) { + return ( + rootProps, + cellEditProps, + filterProps, + sortProps + ) => ( + this.paginationContext = n } + pagination={ this.props.pagination } + data={ rootProps.getData(filterProps, sortProps) } + > + + { + paginationProps => base( + rootProps, + cellEditProps, + filterProps, + sortProps, + paginationProps + ) + } + + + ); + } + renderWithSortCtx(base, baseProps) { return ( rootProps, @@ -167,6 +204,10 @@ const withContext = (Base) => { base = this.renderWithSelectionCtx(base, baseProps); } + if (PaginationContext) { + base = this.renderWithPaginationCtx(base, baseProps); + } + if (SortContext) { base = this.renderWithSortCtx(base, baseProps); } diff --git a/packages/react-bootstrap-table2/src/props-resolver/remote-resolver.js b/packages/react-bootstrap-table2/src/props-resolver/remote-resolver.js index a6c94f6..f0470f4 100644 --- a/packages/react-bootstrap-table2/src/props-resolver/remote-resolver.js +++ b/packages/react-bootstrap-table2/src/props-resolver/remote-resolver.js @@ -2,30 +2,41 @@ import _ from '../utils'; export default ExtendBase => class RemoteResolver extends ExtendBase { - /* eslint class-methods-use-this: 0 */ getNewestState = (state = {}) => { - // const store = this.store || this.props.store; - // return { - // page: store.page, - // sizePerPage: store.sizePerPage, - // filters: store.filters, - // sortField: store.sortField, - // sortOrder: store.sortOrder, - // data: store.getAllData(), - // ...state - // }; - return { - sortOrder: this.sortContext.state.sortOrder, - sortField: this.sortContext.state.sortColumn ? + let sortOrder; + let sortField; + let page; + let sizePerPage; + let filters = {}; + + if (this.sortContext) { + sortOrder = this.sortContext.state.sortOrder; + sortField = this.sortContext.state.sortColumn ? this.sortContext.state.sortColumn.dataField : - null, - filters: this.filterContext ? this.filterContext.currFilters : {}, + null; + } + + if (this.filterContext) { + filters = this.filterContext.currFilters; + } + + if (this.paginationContext) { + page = this.paginationContext.currPage; + sizePerPage = this.paginationContext.currSizePerPage; + } + + return { + sortOrder, + sortField, + filters, + page, + sizePerPage, ...state, data: this.props.data }; } - isRemotePagination() { + isRemotePagination = () => { const { remote } = this.props; return remote === true || (_.isObject(remote) && remote.pagination); } @@ -45,8 +56,8 @@ export default ExtendBase => return remote === true || (_.isObject(remote) && remote.cellEdit); } - handleRemotePageChange() { - this.props.onTableChange('pagination', this.getNewestState()); + handleRemotePageChange = (page, sizePerPage) => { + this.props.onTableChange('pagination', this.getNewestState({ page, sizePerPage })); } handleRemoteFilterChange = (filters) => { From 4ecf2433d03b57e5e06a95cd5a3c0d13b8ca6427 Mon Sep 17 00:00:00 2001 From: AllenFang Date: Sun, 20 May 2018 16:54:04 +0800 Subject: [PATCH 13/55] no more state anti-pattern --- .../react-bootstrap-table2/src/bootstrap-table.js | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/packages/react-bootstrap-table2/src/bootstrap-table.js b/packages/react-bootstrap-table2/src/bootstrap-table.js index 6967b8e..aa711ff 100644 --- a/packages/react-bootstrap-table2/src/bootstrap-table.js +++ b/packages/react-bootstrap-table2/src/bootstrap-table.js @@ -15,16 +15,6 @@ class BootstrapTable extends PropsBaseResolver(Component) { constructor(props) { super(props); this.validateProps(); - - this.state = { - data: props.data - }; - } - - componentWillReceiveProps(nextProps) { - this.setState({ - data: nextProps.data - }); } render() { @@ -42,6 +32,7 @@ class BootstrapTable extends PropsBaseResolver(Component) { renderTable() { const { + data, columns, keyField, id, @@ -74,7 +65,7 @@ class BootstrapTable extends PropsBaseResolver(Component) { const headerCellSelectionInfo = this.resolveSelectRowPropsForHeader({ onAllRowsSelect: this.props.onAllRowsSelect, selected: this.props.selected, - allRowsSelected: isSelectedAll(this.state.data, this.props.selected) + allRowsSelected: isSelectedAll(data, this.props.selected) }); const tableCaption = (caption && { caption }); @@ -94,7 +85,7 @@ class BootstrapTable extends PropsBaseResolver(Component) { selectRow={ headerCellSelectionInfo } /> Date: Sun, 20 May 2018 17:59:55 +0800 Subject: [PATCH 14/55] change to partial selection when pagination enabled --- .../src/bootstrap-table.js | 13 ++++++---- .../src/contexts/selection-context.js | 15 ++++++------ .../src/props-resolver/index.js | 4 ++-- .../row-selection/selection-header-cell.js | 7 ++++-- .../src/store/selection.js | 24 +++++++++++++++---- 5 files changed, 42 insertions(+), 21 deletions(-) diff --git a/packages/react-bootstrap-table2/src/bootstrap-table.js b/packages/react-bootstrap-table2/src/bootstrap-table.js index aa711ff..5f8e5c4 100644 --- a/packages/react-bootstrap-table2/src/bootstrap-table.js +++ b/packages/react-bootstrap-table2/src/bootstrap-table.js @@ -9,7 +9,7 @@ import Caption from './caption'; import Body from './body'; import PropsBaseResolver from './props-resolver'; import Const from './const'; -import { isSelectedAll } from './store/selection'; +import { getSelectionSummary } from './store/selection'; class BootstrapTable extends PropsBaseResolver(Component) { constructor(props) { @@ -46,7 +46,8 @@ class BootstrapTable extends PropsBaseResolver(Component) { rowStyle, rowClasses, wrapperClasses, - rowEvents + rowEvents, + selected } = this.props; const tableWrapperClass = cs('react-bootstrap-table', wrapperClasses); @@ -62,10 +63,12 @@ class BootstrapTable extends PropsBaseResolver(Component) { onRowSelect: this.props.onRowSelect }); + const { allRowsSelected, allRowsNotSelected } = getSelectionSummary(data, keyField, selected); const headerCellSelectionInfo = this.resolveSelectRowPropsForHeader({ onAllRowsSelect: this.props.onAllRowsSelect, - selected: this.props.selected, - allRowsSelected: isSelectedAll(data, this.props.selected) + selected, + allRowsSelected, + allRowsNotSelected }); const tableCaption = (caption && { caption }); @@ -93,7 +96,7 @@ class BootstrapTable extends PropsBaseResolver(Component) { noDataIndication={ noDataIndication } cellEdit={ this.props.cellEdit || {} } selectRow={ cellSelectionInfo } - selectedRowKeys={ this.props.selected } + selectedRowKeys={ selected } rowStyle={ rowStyle } rowClasses={ rowClasses } rowEvents={ rowEvents } diff --git a/packages/react-bootstrap-table2/src/contexts/selection-context.js b/packages/react-bootstrap-table2/src/contexts/selection-context.js index 2e171bb..a9fb4e8 100644 --- a/packages/react-bootstrap-table2/src/contexts/selection-context.js +++ b/packages/react-bootstrap-table2/src/contexts/selection-context.js @@ -47,7 +47,7 @@ export default ( this.setState(() => ({ selected: currSelected })); } - handleAllRowsSelect = (e) => { + handleAllRowsSelect = (e, isUnSelect) => { const { data, keyField, @@ -57,16 +57,17 @@ export default ( } } = this.props; const { selected } = this.state; - const anySelected = dataOperator.isAnySelectedRow(selected, nonSelectable); - const result = !anySelected; + let currSelected; - const currSelected = result ? - dataOperator.selectableKeys(data, keyField, nonSelectable) : - dataOperator.unSelectableKeys(selected, nonSelectable); + if (!isUnSelect) { + currSelected = selected.concat(dataOperator.selectableKeys(data, keyField, nonSelectable)); + } else { + currSelected = selected.filter(s => typeof data.find(d => d[keyField] === s) === 'undefined'); + } if (onSelectAll) { - onSelectAll(result, dataOperator.getSelectedRows(data, keyField, currSelected), e); + onSelectAll(!isUnSelect, dataOperator.getSelectedRows(data, keyField, currSelected), e); } this.setState(() => ({ selected: currSelected })); diff --git a/packages/react-bootstrap-table2/src/props-resolver/index.js b/packages/react-bootstrap-table2/src/props-resolver/index.js index 4f40d97..be81f50 100644 --- a/packages/react-bootstrap-table2/src/props-resolver/index.js +++ b/packages/react-bootstrap-table2/src/props-resolver/index.js @@ -51,7 +51,7 @@ export default ExtendBase => */ resolveSelectRowPropsForHeader(options = {}) { const { selectRow } = this.props; - const { allRowsSelected, selected = [], ...rest } = options; + const { allRowsSelected, allRowsNotSelected, ...rest } = options; const { ROW_SELECT_DISABLED, CHECKBOX_STATUS_CHECKED, CHECKBOX_STATUS_INDETERMINATE, CHECKBOX_STATUS_UNCHECKED @@ -62,7 +62,7 @@ export default ExtendBase => // checkbox status depending on selected rows counts if (allRowsSelected) checkedStatus = CHECKBOX_STATUS_CHECKED; - else if (selected.length === 0) checkedStatus = CHECKBOX_STATUS_UNCHECKED; + else if (allRowsNotSelected) checkedStatus = CHECKBOX_STATUS_UNCHECKED; else checkedStatus = CHECKBOX_STATUS_INDETERMINATE; return { diff --git a/packages/react-bootstrap-table2/src/row-selection/selection-header-cell.js b/packages/react-bootstrap-table2/src/row-selection/selection-header-cell.js index 9879026..c031a72 100644 --- a/packages/react-bootstrap-table2/src/row-selection/selection-header-cell.js +++ b/packages/react-bootstrap-table2/src/row-selection/selection-header-cell.js @@ -46,9 +46,12 @@ export default class SelectionHeaderCell extends Component { } handleCheckBoxClick(e) { - const { onAllRowsSelect } = this.props; + const { onAllRowsSelect, checkedStatus } = this.props; + const isUnSelect = + checkedStatus === Const.CHECKBOX_STATUS_CHECKED || + checkedStatus === Const.CHECKBOX_STATUS_INDETERMINATE; - onAllRowsSelect(e); + onAllRowsSelect(e, isUnSelect); } render() { diff --git a/packages/react-bootstrap-table2/src/store/selection.js b/packages/react-bootstrap-table2/src/store/selection.js index 6e66121..7d3e741 100644 --- a/packages/react-bootstrap-table2/src/store/selection.js +++ b/packages/react-bootstrap-table2/src/store/selection.js @@ -1,13 +1,27 @@ import _ from '../utils'; import { getRowByRowId } from './rows'; -export const isSelectedAll = (data, selected = []) => data.length === selected.length; +export const getSelectionSummary = ( + data, + keyField, + selected = [] +) => { + let allRowsSelected = true; + let allRowsNotSelected = true; -export const isAnySelectedRow = (selected, skips = []) => { - if (skips.length === 0) { - return selected.length > 0; + const rowKeys = data.map(d => d[keyField]); + for (let i = 0; i < rowKeys.length; i += 1) { + const curr = rowKeys[i]; + if (typeof selected.find(x => x === curr) === 'undefined') { + allRowsSelected = false; + } else { + allRowsNotSelected = false; + } } - return selected.filter(x => !skips.includes(x)).length; + return { + allRowsSelected, + allRowsNotSelected + }; }; export const selectableKeys = (data, keyField, skips = []) => { From b1c086f4240a87aade44f364c80450b8e525c44f Mon Sep 17 00:00:00 2001 From: AllenFang Date: Sun, 20 May 2018 22:36:32 +0800 Subject: [PATCH 15/55] fix cache context issue --- .../src/contexts/index.js | 89 ++++++++----------- 1 file changed, 38 insertions(+), 51 deletions(-) diff --git a/packages/react-bootstrap-table2/src/contexts/index.js b/packages/react-bootstrap-table2/src/contexts/index.js index 3d6e644..e695480 100644 --- a/packages/react-bootstrap-table2/src/contexts/index.js +++ b/packages/react-bootstrap-table2/src/contexts/index.js @@ -8,49 +8,37 @@ import createSelectionContext from './selection-context'; import remoteResolver from '../props-resolver/remote-resolver'; import dataOperator from '../store/operators'; -const withContext = (Base) => { - let DataContext; - let SelectionContext; - let CellEditContext; - let SortContext; - let FilterContext; - let PaginationContext; - - return class BootstrapTableContainer extends remoteResolver(Component) { +const withContext = Base => + class BootstrapTableContainer extends remoteResolver(Component) { constructor(props) { super(props); - DataContext = createDataContext(props.data); + this.DataContext = createDataContext(props.data); if (props.columns.filter(col => col.sort).length > 0) { - SortContext = createSortContext(dataOperator, this.isRemoteSort, this.handleSortChange); + this.SortContext = createSortContext( + dataOperator, this.isRemoteSort, this.handleSortChange); } if (props.selectRow) { - SelectionContext = createSelectionContext(dataOperator); + this.SelectionContext = createSelectionContext(dataOperator); } if (props.cellEdit && props.cellEdit.createContext) { - CellEditContext = props.cellEdit.createContext( + this.CellEditContext = props.cellEdit.createContext( _, dataOperator, this.isRemoteCellEdit, this.handleCellChange); } if (props.filter) { - FilterContext = props.filter.createContext( + this.FilterContext = props.filter.createContext( _, this.isRemoteFiltering, this.handleRemoteFilterChange); } if (props.pagination) { - PaginationContext = props.pagination.createContext( + this.PaginationContext = props.pagination.createContext( this.isRemotePagination, this.handleRemotePageChange); } } - componentWillReceiveProps(nextProps) { - if (!nextProps.cellEdit) { - CellEditContext = null; - } - } - renderBase() { return ( rootProps, @@ -80,12 +68,12 @@ const withContext = (Base) => { sortProps, paginationProps ) => ( - - + { selectionProps => base( rootProps, @@ -96,8 +84,8 @@ const withContext = (Base) => { selectionProps ) } - - + + ); } @@ -108,12 +96,12 @@ const withContext = (Base) => { filterProps, sortProps ) => ( - this.paginationContext = n } pagination={ this.props.pagination } data={ rootProps.getData(filterProps, sortProps) } > - + { paginationProps => base( rootProps, @@ -123,8 +111,8 @@ const withContext = (Base) => { paginationProps ) } - - + + ); } @@ -134,14 +122,14 @@ const withContext = (Base) => { cellEditProps, filterProps ) => ( - this.sortContext = n } defaultSorted={ this.props.defaultSorted } defaultSortDirection={ this.props.defaultSortDirection } data={ rootProps.getData(filterProps) } > - + { sortProps => base( rootProps, @@ -150,8 +138,8 @@ const withContext = (Base) => { sortProps ) } - - + + ); } @@ -160,12 +148,12 @@ const withContext = (Base) => { rootProps, cellEditprops ) => ( - this.filterContext = n } data={ rootProps.getData() } > - + { filterProps => base( rootProps, @@ -173,24 +161,24 @@ const withContext = (Base) => { filterProps ) } - - + + ); } renderWithCellEditCtx(base, baseProps) { return rootProps => ( - - + { cellEditprops => base(rootProps, cellEditprops) } - - + + ); } @@ -200,40 +188,39 @@ const withContext = (Base) => { let base = this.renderBase(); - if (SelectionContext) { + if (this.SelectionContext) { base = this.renderWithSelectionCtx(base, baseProps); } - if (PaginationContext) { + if (this.PaginationContext) { base = this.renderWithPaginationCtx(base, baseProps); } - if (SortContext) { + if (this.SortContext) { base = this.renderWithSortCtx(base, baseProps); } - if (FilterContext) { + if (this.FilterContext) { base = this.renderWithFilterCtx(base, baseProps); } - if (CellEditContext) { + if (this.CellEditContext) { base = this.renderWithCellEditCtx(base, baseProps); } return ( - - + { base } - - + + ); } }; -}; export default withContext; From 400c3078715f0be83e6454ed5e6c59d755757e7c Mon Sep 17 00:00:00 2001 From: AllenFang Date: Sun, 20 May 2018 22:55:58 +0800 Subject: [PATCH 16/55] refine remote method --- packages/react-bootstrap-table2/src/contexts/index.js | 10 +++++----- .../src/props-resolver/remote-resolver.js | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/react-bootstrap-table2/src/contexts/index.js b/packages/react-bootstrap-table2/src/contexts/index.js index e695480..796efff 100644 --- a/packages/react-bootstrap-table2/src/contexts/index.js +++ b/packages/react-bootstrap-table2/src/contexts/index.js @@ -16,7 +16,7 @@ const withContext = Base => if (props.columns.filter(col => col.sort).length > 0) { this.SortContext = createSortContext( - dataOperator, this.isRemoteSort, this.handleSortChange); + dataOperator, this.isRemoteSort, this.handleRemoteSortChange); } if (props.selectRow) { @@ -25,7 +25,7 @@ const withContext = Base => if (props.cellEdit && props.cellEdit.createContext) { this.CellEditContext = props.cellEdit.createContext( - _, dataOperator, this.isRemoteCellEdit, this.handleCellChange); + _, dataOperator, this.isRemoteCellEdit, this.handleRemoteCellChange); } if (props.filter) { @@ -146,7 +146,7 @@ const withContext = Base => renderWithFilterCtx(base, baseProps) { return ( rootProps, - cellEditprops + cellEditProps ) => ( { filterProps => base( rootProps, - cellEditprops, + cellEditProps, filterProps ) } @@ -175,7 +175,7 @@ const withContext = Base => > { - cellEditprops => base(rootProps, cellEditprops) + cellEditProps => base(rootProps, cellEditProps) } diff --git a/packages/react-bootstrap-table2/src/props-resolver/remote-resolver.js b/packages/react-bootstrap-table2/src/props-resolver/remote-resolver.js index f0470f4..62cdb7a 100644 --- a/packages/react-bootstrap-table2/src/props-resolver/remote-resolver.js +++ b/packages/react-bootstrap-table2/src/props-resolver/remote-resolver.js @@ -69,11 +69,11 @@ export default ExtendBase => this.props.onTableChange('filter', this.getNewestState(newState)); } - handleSortChange = (sortField, sortOrder) => { + handleRemoteSortChange = (sortField, sortOrder) => { this.props.onTableChange('sort', this.getNewestState({ sortField, sortOrder })); } - handleCellChange = (rowId, dataField, newValue) => { + handleRemoteCellChange = (rowId, dataField, newValue) => { const cellEdit = { rowId, dataField, newValue }; this.props.onTableChange('cellEdit', this.getNewestState({ cellEdit })); } From 74bf885d47bf0e7508e3a28216c31f6925ff7698 Mon Sep 17 00:00:00 2001 From: AllenFang Date: Wed, 23 May 2018 22:25:42 +0800 Subject: [PATCH 17/55] remove useless code --- packages/react-bootstrap-table2/index.js | 1 - .../react-bootstrap-table2/src/store/index.js | 78 ------------------- 2 files changed, 79 deletions(-) delete mode 100644 packages/react-bootstrap-table2/src/store/index.js diff --git a/packages/react-bootstrap-table2/index.js b/packages/react-bootstrap-table2/index.js index c603b60..59ad74d 100644 --- a/packages/react-bootstrap-table2/index.js +++ b/packages/react-bootstrap-table2/index.js @@ -2,4 +2,3 @@ import BootstrapTable from './src/bootstrap-table'; import withContext from './src/contexts'; export default withContext(BootstrapTable); - diff --git a/packages/react-bootstrap-table2/src/store/index.js b/packages/react-bootstrap-table2/src/store/index.js deleted file mode 100644 index f0d7933..0000000 --- a/packages/react-bootstrap-table2/src/store/index.js +++ /dev/null @@ -1,78 +0,0 @@ -/* eslint no-underscore-dangle: 0 */ -import _ from '../utils'; -import { sort, nextOrder } from './sort'; -import { getRowByRowId } from './rows'; - -export default class Store { - constructor(keyField) { - this._data = []; - this._filteredData = []; - this._keyField = keyField; - this._sortOrder = undefined; - this._sortField = undefined; - this._selected = []; - this._filters = {}; - this._page = undefined; - this._sizePerPage = undefined; - } - - edit(rowId, dataField, newValue) { - const row = getRowByRowId(this)(rowId); - if (row) _.set(row, dataField, newValue); - } - - setSort({ dataField }, order, defaultOrder) { - this.sortOrder = nextOrder(this)(dataField, order, defaultOrder); - this.sortField = dataField; - } - - sortBy({ sortFunc }) { - this.data = sort(this)(sortFunc); - } - - getAllData() { - return this._data; - } - - setAllData(data) { - this._data = data; - } - - get data() { - if (Object.keys(this._filters).length > 0) { - return this._filteredData; - } - return this._data; - } - set data(data) { - if (Object.keys(this._filters).length > 0) { - this._filteredData = data; - } else { - this._data = (data ? JSON.parse(JSON.stringify(data)) : []); - } - } - - get filteredData() { return this._filteredData; } - set filteredData(filteredData) { this._filteredData = filteredData; } - - get keyField() { return this._keyField; } - set keyField(keyField) { this._keyField = keyField; } - - get sortOrder() { return this._sortOrder; } - set sortOrder(sortOrder) { this._sortOrder = sortOrder; } - - get page() { return this._page; } - set page(page) { this._page = page; } - - get sizePerPage() { return this._sizePerPage; } - set sizePerPage(sizePerPage) { this._sizePerPage = sizePerPage; } - - get sortField() { return this._sortField; } - set sortField(sortField) { this._sortField = sortField; } - - get selected() { return this._selected; } - set selected(selected) { this._selected = selected; } - - get filters() { return this._filters; } - set filters(filters) { this._filters = filters; } -} From fc0b99e8a07767d706f84840e8286321cc3dab58 Mon Sep 17 00:00:00 2001 From: AllenFang Date: Mon, 28 May 2018 18:05:33 +0800 Subject: [PATCH 18/55] patch tests for react-bootstrap-table-next --- package.json | 4 +- .../test/context.test.js | 430 ++++++++++++++++++ .../test/bootstrap-table.test.js | 23 +- .../test/container.test.js | 209 --------- .../test/contexts/data-context.test.js | 130 ++++++ .../test/contexts/index.test.js | 196 ++++++++ .../test/contexts/selection-context.test.js | 258 +++++++++++ .../test/contexts/sort-context.test.js | 255 +++++++++++ .../test/props-resolver/index.test.js | 16 +- .../props-resolver/remote-resolver.test.js | 76 +++- .../test/row-selection/wrapper.test.js | 224 --------- .../test/sort/wrapper.test.js | 230 ---------- .../test/store/index.test.js | 109 ----- .../test/store/mutate.test.js | 32 ++ .../test/store/rows.test.js | 29 +- .../test/store/selection.test.js | 113 ++--- .../test/store/sort.test.js | 73 +-- .../test/test-helpers/mock-component.js | 11 - 18 files changed, 1472 insertions(+), 946 deletions(-) create mode 100644 packages/react-bootstrap-table2-editor/test/context.test.js delete mode 100644 packages/react-bootstrap-table2/test/container.test.js create mode 100644 packages/react-bootstrap-table2/test/contexts/data-context.test.js create mode 100644 packages/react-bootstrap-table2/test/contexts/index.test.js create mode 100644 packages/react-bootstrap-table2/test/contexts/selection-context.test.js create mode 100644 packages/react-bootstrap-table2/test/contexts/sort-context.test.js delete mode 100644 packages/react-bootstrap-table2/test/row-selection/wrapper.test.js delete mode 100644 packages/react-bootstrap-table2/test/sort/wrapper.test.js delete mode 100644 packages/react-bootstrap-table2/test/store/index.test.js create mode 100644 packages/react-bootstrap-table2/test/store/mutate.test.js diff --git a/package.json b/package.json index d159c7b..15e5f9e 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "pretest": "yarn lint --cache", "test": "jest", "test:coverage": "jest --coverage", - "test:watch": "jest --watch", + "test:watch": "jest --coverage --watch", "storybook": "cd ./packages/react-bootstrap-table2-example && yarn storybook", "gh-pages:clean": "cd ./packages/react-bootstrap-table2-example && yarn gh-pages:clean", "gh-pages:build": "cd ./packages/react-bootstrap-table2-example && yarn gh-pages:build", @@ -86,7 +86,7 @@ }, "jest": { "collectCoverageFrom": [ - "packages/*/src/*.js", + "packages/*/src/**/*.js", "packages/*/index.js" ], "roots": [ diff --git a/packages/react-bootstrap-table2-editor/test/context.test.js b/packages/react-bootstrap-table2-editor/test/context.test.js new file mode 100644 index 0000000..89af0ce --- /dev/null +++ b/packages/react-bootstrap-table2-editor/test/context.test.js @@ -0,0 +1,430 @@ +import 'jsdom-global/register'; +import React from 'react'; +import { shallow } from 'enzyme'; +import _ from 'react-bootstrap-table-next/src/utils'; +import dataOperator from 'react-bootstrap-table-next/src/store/operators'; +import BootstrapTable from 'react-bootstrap-table-next/src/bootstrap-table'; + +import { + CLICK_TO_CELL_EDIT, + DBCLICK_TO_CELL_EDIT, + DELAY_FOR_DBCLICK +} from '../src/const'; +import createCellEditContext from '../src/context'; +import cellEditFactory from '../index'; + +describe('CellEditContext', () => { + let wrapper; + let cellEdit; + let CellEditContext; + + const data = [{ + id: 1, + name: 'A' + }, { + id: 2, + name: 'B' + }]; + + const keyField = 'id'; + + const columns = [{ + dataField: 'id', + text: 'ID' + }, { + dataField: 'name', + text: 'Name' + }]; + + const defaultCellEdit = { + mode: CLICK_TO_CELL_EDIT + }; + + const defaultSelectRow = undefined; + + const mockBase = jest.fn((props => ( + + ))); + + const handleCellChange = jest.fn(); + + function shallowContext( + customCellEdit = defaultCellEdit, + enableRemote = false, + selectRow = defaultSelectRow + ) { + mockBase.mockReset(); + handleCellChange.mockReset(); + CellEditContext = createCellEditContext( + _, + dataOperator, + jest.fn().mockReturnValue(enableRemote), + handleCellChange + ); + cellEdit = cellEditFactory(customCellEdit); + return ( + + + { + cellEditProps => mockBase(cellEditProps) + } + + + ); + } + + describe('default render', () => { + beforeEach(() => { + wrapper = shallow(shallowContext()); + wrapper.render(); + }); + + it('should have correct Provider property after calling createCellEditContext', () => { + expect(CellEditContext.Provider).toBeDefined(); + }); + + it('should have correct Consumer property after calling createCellEditContext', () => { + expect(CellEditContext.Consumer).toBeDefined(); + }); + + it('should have correct state.ridx', () => { + expect(wrapper.state().ridx).toBeNull(); + }); + + it('should have correct state.cidx', () => { + expect(wrapper.state().cidx).toBeNull(); + }); + + it('should have correct state.message', () => { + expect(wrapper.state().message).toBeNull(); + }); + + it('should pass correct cell editing props to children element', () => { + expect(wrapper.length).toBe(1); + expect(JSON.stringify(mockBase.mock.calls[0])).toEqual(JSON.stringify([{ + cellEdit: { + ...defaultCellEdit, + CLICK_TO_CELL_EDIT, + DBCLICK_TO_CELL_EDIT, + DELAY_FOR_DBCLICK, + ...wrapper.state(), + nonEditableRows: [] + } + }])); + }); + }); + + describe('componentWillReceiveProps', () => { + const initialState = { ridx: 1, cidx: 1, message: 'test' }; + describe('if nextProps.cellEdit is not existing', () => { + beforeEach(() => { + wrapper = shallow(shallowContext()); + wrapper.setState(initialState); + wrapper.render(); + wrapper.instance().componentWillReceiveProps({}); + }); + + it('should not set state.message', () => { + expect(wrapper.state().message).toBe(initialState.message); + }); + + it('should not set state.ridx', () => { + expect(wrapper.state().ridx).toBe(initialState.ridx); + }); + + it('should not set state.cidx', () => { + expect(wrapper.state().cidx).toBe(initialState.cidx); + }); + }); + + describe('if nextProps.cellEdit is existing but remote cell editing is disable', () => { + beforeEach(() => { + wrapper = shallow(shallowContext()); + wrapper.setState(initialState); + wrapper.render(); + wrapper.instance().componentWillReceiveProps({ + cellEdit: cellEditFactory(defaultCellEdit) + }); + }); + + it('should not set state.message', () => { + expect(wrapper.state().message).toBe(initialState.message); + }); + + it('should not set state.ridx', () => { + expect(wrapper.state().ridx).toBe(initialState.ridx); + }); + + it('should not set state.cidx', () => { + expect(wrapper.state().cidx).toBe(initialState.cidx); + }); + }); + + describe('if nextProps.cellEdit is existing and remote cell editing is enable', () => { + describe('if nextProps.cellEdit.options.errorMessage is defined', () => { + let message; + beforeEach(() => { + message = 'validation fail'; + wrapper = shallow(shallowContext(defaultCellEdit, true)); + wrapper.setState(initialState); + wrapper.render(); + wrapper.instance().componentWillReceiveProps({ + cellEdit: cellEditFactory({ + ...defaultCellEdit, + errorMessage: message + }) + }); + wrapper.update(); + }); + + it('should set state.message', () => { + expect(wrapper.state('message')).toBe(message); + }); + + it('should not set state.ridx', () => { + expect(wrapper.state().ridx).toBe(initialState.ridx); + }); + + it('should not set state.cidx', () => { + expect(wrapper.state().cidx).toBe(initialState.cidx); + }); + }); + + describe('if nextProps.cellEdit.options.errorMessage is not defined', () => { + beforeEach(() => { + wrapper = shallow(shallowContext(defaultCellEdit, true)); + wrapper.setState(initialState); + wrapper.instance().componentWillReceiveProps({ + cellEdit: cellEditFactory({ ...defaultCellEdit }) + }); + wrapper.update(); + }); + + it('should not set state.message', () => { + expect(wrapper.state('message')).toBe(initialState.message); + }); + + it('should set correct state.ridx', () => { + expect(wrapper.state().ridx).toBeNull(); + }); + + it('should set correct state.cidx', () => { + expect(wrapper.state().cidx).toBeNull(); + }); + }); + }); + }); + + describe('handleCellUpdate', () => { + const row = data[1]; + const column = columns[1]; + const newValue = 'This is new value'; + const oldValue = row[column.dataField]; + + describe('if cellEdit.beforeSaveCell prop is defined', () => { + const beforeSaveCell = jest.fn(); + + beforeEach(() => { + beforeSaveCell.mockReset(); + wrapper = shallow(shallowContext({ + ...defaultCellEdit, + beforeSaveCell + })); + wrapper.instance().handleCellUpdate( + row, + column, + newValue + ); + }); + + it('should call cellEdit.beforeSaveCell correctly', () => { + expect(beforeSaveCell).toHaveBeenCalledTimes(1); + expect(beforeSaveCell).toHaveBeenCalledWith(oldValue, newValue, row, column); + }); + }); + + describe('when remote cell editing is enable', () => { + const afterSaveCell = jest.fn(); + beforeEach(() => { + afterSaveCell.mockReset(); + wrapper = shallow(shallowContext({ + ...defaultCellEdit, + afterSaveCell + }, true)); + wrapper.instance().handleCellUpdate( + row, + column, + newValue + ); + }); + + it('should call handleCellChange correctly', () => { + expect(handleCellChange).toHaveBeenCalledTimes(1); + expect(handleCellChange).toHaveBeenCalledWith(row[keyField], column.dataField, newValue); + }); + + it('should not call cellEdit.afterSaveCell even if it is defined', () => { + expect(afterSaveCell).toHaveBeenCalledTimes(0); + }); + }); + + describe('when remote cell editing is disable', () => { + const afterSaveCell = jest.fn(); + + beforeEach(() => { + afterSaveCell.mockReset(); + wrapper = shallow(shallowContext({ + ...defaultCellEdit, + afterSaveCell + })); + wrapper.setState({ + ridx: 1, + cidx: 1 + }); + wrapper.instance().handleCellUpdate( + row, + column, + newValue + ); + }); + + it('should not call handleCellChange correctly', () => { + expect(handleCellChange).toHaveBeenCalledTimes(0); + }); + + it('should set state correctly', () => { + expect(wrapper.state('ridx')).toBeNull(); + expect(wrapper.state('cidx')).toBeNull(); + expect(wrapper.state('message')).toBeNull(); + }); + + it('should call cellEdit.afterSaveCell if it is defined', () => { + expect(afterSaveCell).toHaveBeenCalledTimes(1); + }); + }); + }); + + describe('completeEditing', () => { + const initialState = { ridx: 1, cidx: 1, message: 'test' }; + + beforeEach(() => { + wrapper = shallow(shallowContext()); + wrapper.setState(initialState); + wrapper.render(); + wrapper.instance().completeEditing(); + }); + + it('should set state correctly', () => { + expect(wrapper.state().ridx).toBeNull(); + expect(wrapper.state().cidx).toBeNull(); + expect(wrapper.state().message).toBeNull(); + }); + }); + + describe('startEditing', () => { + const ridx = 0; + const cidx = 1; + + describe('if selectRow prop is not defined', () => { + beforeEach(() => { + wrapper = shallow(shallowContext()); + wrapper.render(); + wrapper.instance().startEditing(ridx, cidx); + }); + + it('should set state correctly', () => { + expect(wrapper.state().ridx).toEqual(ridx); + expect(wrapper.state().cidx).toEqual(cidx); + }); + }); + + describe('if selectRow prop is defined', () => { + describe('and selectRow.clickToEdit is enable', () => { + beforeEach(() => { + wrapper = shallow(shallowContext( + defaultCellEdit, + false, + { + ...defaultSelectRow, + clickToEdit: true + } + )); + wrapper.render(); + wrapper.instance().startEditing(ridx, cidx); + }); + + it('should set state correctly', () => { + expect(wrapper.state().ridx).toEqual(ridx); + expect(wrapper.state().cidx).toEqual(cidx); + }); + }); + + describe('and selectRow.clickToSelect is disable', () => { + beforeEach(() => { + wrapper = shallow(shallowContext( + defaultCellEdit, + false, + { + ...defaultSelectRow, + clickToSelect: false + } + )); + wrapper.render(); + wrapper.instance().startEditing(ridx, cidx); + }); + + it('should set state correctly', () => { + expect(wrapper.state().ridx).toEqual(ridx); + expect(wrapper.state().cidx).toEqual(cidx); + }); + }); + + describe('and selectRow.clickToEdit & selectRow.clickToSelect is enable', () => { + beforeEach(() => { + wrapper = shallow(shallowContext( + defaultCellEdit, + false, + { + ...defaultSelectRow, + clickToEdit: false, + clickToSelect: true + } + )); + wrapper.render(); + wrapper.instance().startEditing(ridx, cidx); + }); + + it('should not set state', () => { + expect(wrapper.state().ridx).toBeNull(); + expect(wrapper.state().cidx).toBeNull(); + }); + }); + }); + }); + + describe('escapeEditing', () => { + const initialState = { ridx: 1, cidx: 1 }; + + beforeEach(() => { + wrapper = shallow(shallowContext()); + wrapper.setState(initialState); + wrapper.instance().escapeEditing(); + }); + + it('should set state correctly', () => { + expect(wrapper.state().ridx).toBeNull(); + expect(wrapper.state().cidx).toBeNull(); + }); + }); +}); diff --git a/packages/react-bootstrap-table2/test/bootstrap-table.test.js b/packages/react-bootstrap-table2/test/bootstrap-table.test.js index b1d4062..4991018 100644 --- a/packages/react-bootstrap-table2/test/bootstrap-table.test.js +++ b/packages/react-bootstrap-table2/test/bootstrap-table.test.js @@ -2,7 +2,6 @@ import React from 'react'; import { shallow } from 'enzyme'; import Caption from '../src/caption'; -import Store from '../src/store'; import Header from '../src/header'; import Body from '../src/body'; import BootstrapTable from '../src/bootstrap-table'; @@ -25,13 +24,10 @@ describe('BootstrapTable', () => { name: 'B' }]; - const store = new Store('id'); - store.data = data; - describe('simplest table', () => { beforeEach(() => { wrapper = shallow( - ); + ); }); it('should render successfully', () => { @@ -41,11 +37,6 @@ describe('BootstrapTable', () => { expect(wrapper.find(Body).length).toBe(1); }); - it('should have correct default state', () => { - expect(wrapper.state().data).toBeDefined(); - expect(wrapper.state().data).toEqual(store.data); - }); - it("should only have classes 'table' and 'table-bordered' as default", () => { expect(wrapper.find('table').prop('className')).toBe('table table-bordered'); }); @@ -64,7 +55,6 @@ describe('BootstrapTable', () => { keyField="id" columns={ columns } data={ data } - store={ store } classes={ classes } />); }); @@ -83,7 +73,6 @@ describe('BootstrapTable', () => { keyField="id" columns={ columns } data={ data } - store={ store } wrapperClasses={ classes } />); }); @@ -102,7 +91,6 @@ describe('BootstrapTable', () => { keyField="id" columns={ columns } data={ data } - store={ store } id={ id } />); }); @@ -115,7 +103,7 @@ describe('BootstrapTable', () => { describe('when hover props is true', () => { beforeEach(() => { wrapper = shallow( - ); + ); }); it('should have table-hover class on table', () => { @@ -126,7 +114,7 @@ describe('BootstrapTable', () => { describe('when striped props is true', () => { beforeEach(() => { wrapper = shallow( - ); + ); }); it('should have table-striped class on table', () => { @@ -137,7 +125,7 @@ describe('BootstrapTable', () => { describe('when condensed props is true', () => { beforeEach(() => { wrapper = shallow( - ); + ); }); it('should have table-condensed class on table', () => { @@ -148,7 +136,7 @@ describe('BootstrapTable', () => { describe('when bordered props is false', () => { beforeEach(() => { wrapper = shallow( - ); + ); }); it('should not have table-condensed class on table', () => { @@ -160,7 +148,6 @@ describe('BootstrapTable', () => { beforeEach(() => { wrapper = shallow( test } keyField="id" columns={ columns } diff --git a/packages/react-bootstrap-table2/test/container.test.js b/packages/react-bootstrap-table2/test/container.test.js deleted file mode 100644 index 8ceb5a5..0000000 --- a/packages/react-bootstrap-table2/test/container.test.js +++ /dev/null @@ -1,209 +0,0 @@ -/* eslint react/prefer-stateless-function: 0 */ -/* eslint react/no-multi-comp: 0 */ -import React from 'react'; -import { shallow } from 'enzyme'; - -import BootstrapTable from '../src/bootstrap-table'; -import Container from '../index.js'; - -describe('container', () => { - let wrapper; - - const keyField = 'id'; - - const columns = [{ - dataField: keyField, - text: 'ID' - }, { - dataField: 'name', - text: 'Name' - }]; - - const data = [{ - id: 1, - name: 'A' - }, { - id: 2, - name: 'B' - }]; - - describe('initialization', () => { - beforeEach(() => { - wrapper = shallow( - - ); - }); - - it('should initialize BaseComponent', () => { - expect(wrapper.instance().BaseComponent.name).toBe('BootstrapTable'); - }); - - it('should render BootstrapTable successfully', () => { - expect(wrapper.find(BootstrapTable)).toHaveLength(1); - }); - - it('should creating store successfully', () => { - const store = wrapper.instance().store; - expect(store).toBeDefined(); - expect(store.data).toEqual(data); - expect(store.keyField).toEqual(keyField); - }); - }); - - describe('when cellEdit prop is defined', () => { - const wrapperFactory = Base => class CellEditWrapper extends React.Component { - render() { return ; } - }; - - const cellEdit = { - wrapperFactory, - options: { - mode: 'click' - } - }; - - beforeEach(() => { - wrapper = shallow( - - ); - }); - - it('should initialize BaseComponent correctly', () => { - expect(wrapper.instance().BaseComponent.name).toBe('CellEditWrapper'); - }); - - it('should render CellEditWrapper component successfully', () => { - expect(wrapper.find('CellEditWrapper')).toHaveLength(1); - }); - - it('should render BootstrapTable component successfully', () => { - expect(wrapper.dive().find(BootstrapTable)).toHaveLength(1); - }); - }); - - describe('when selectRow prop is defined', () => { - const selectRow = { - mode: 'checkbox' - }; - - beforeEach(() => { - wrapper = shallow( - - ); - }); - - it('should initialize BaseComponent correctly', () => { - expect(wrapper.instance().BaseComponent.name).toBe('RowSelectionWrapper'); - }); - - it('should render BootstrapTable component successfully', () => { - expect(wrapper.dive().find(BootstrapTable)).toHaveLength(1); - }); - - it('should render RowSelectionWrapper component successfully', () => { - expect(wrapper.find('RowSelectionWrapper').length).toBe(1); - }); - }); - - describe('when pagination prop is defined', () => { - const wrapperFactory = Base => class PaginationWrapper extends React.Component { - render() { return ; } - }; - const pagination = { - wrapperFactory - }; - - beforeEach(() => { - wrapper = shallow( - - ); - }); - - it('should initialize BaseComponent correctly', () => { - expect(wrapper.instance().BaseComponent.name).toBe('PaginationWrapper'); - }); - - it('should render BootstrapTable component successfully', () => { - expect(wrapper.dive().find(BootstrapTable)).toHaveLength(1); - }); - - it('should render PaginationWrapper component successfully', () => { - expect(wrapper.find('PaginationWrapper').length).toBe(1); - }); - }); - - describe('when filter prop is defined', () => { - const wrapperFactory = Base => class FilterWrapper extends React.Component { - render() { return ; } - }; - - const filter = { wrapperFactory }; - - beforeEach(() => { - wrapper = shallow( - - ); - }); - - it('should initialize BaseComponent correctly', () => { - expect(wrapper.instance().BaseComponent.name).toBe('FilterWrapper'); - }); - - it('should render BootstrapTable component successfully', () => { - expect(wrapper.dive().find(BootstrapTable)).toHaveLength(1); - }); - - it('should render FilterWrapper component successfully', () => { - expect(wrapper.find('FilterWrapper').length).toBe(1); - }); - }); - - describe('when any column.sort is defined', () => { - beforeEach(() => { - const columnsWithSort = [{ - dataField: keyField, - text: 'ID', - sort: true - }]; - wrapper = shallow( - - ); - }); - - it('should initialize BaseComponent correctly', () => { - expect(wrapper.instance().BaseComponent.name).toBe('SortWrapper'); - }); - - it('should render BootstrapTable component successfully', () => { - expect(wrapper.dive().find(BootstrapTable)).toHaveLength(1); - }); - - it('should render SortWrapper component successfully', () => { - expect(wrapper.find('SortWrapper').length).toBe(1); - }); - }); -}); diff --git a/packages/react-bootstrap-table2/test/contexts/data-context.test.js b/packages/react-bootstrap-table2/test/contexts/data-context.test.js new file mode 100644 index 0000000..0761640 --- /dev/null +++ b/packages/react-bootstrap-table2/test/contexts/data-context.test.js @@ -0,0 +1,130 @@ +import 'jsdom-global/register'; +import React from 'react'; +import { shallow } from 'enzyme'; + +import BootstrapTable from '../../src/bootstrap-table'; +import createDataContext from '../../src/contexts/data-context'; + +describe('DataContext', () => { + let wrapper; + + const data = [{ + id: 1, + name: 'A' + }, { + id: 2, + name: 'B' + }]; + + const columns = [{ + dataField: 'id', + text: 'ID' + }, { + dataField: 'name', + text: 'Name' + }]; + + const mockBase = jest.fn((props => ( + + ))); + + const DataContext = createDataContext(); + + function shallowContext() { + return ( + + + { + dataProps => mockBase(dataProps) + } + + + ); + } + + describe('default render', () => { + beforeEach(() => { + wrapper = shallow(shallowContext()); + wrapper.render(); + }); + + it('should have correct Provider property after calling createDataContext', () => { + expect(DataContext.Provider).toBeDefined(); + }); + + it('should have correct Consumer property after calling createDataContext', () => { + expect(DataContext.Consumer).toBeDefined(); + }); + + it('should have correct state.data', () => { + expect(wrapper.state().data).toEqual(data); + }); + + it('should pass correct sort props to children element', () => { + expect(wrapper.length).toBe(1); + expect(mockBase).toHaveBeenCalledWith({ + data, + getData: wrapper.instance().getData + }); + }); + }); + + describe('componentWillReceiveProps', () => { + const newData = [...data, { id: 3, name: 'test' }]; + + beforeEach(() => { + wrapper = shallow(shallowContext()); + wrapper.instance().componentWillReceiveProps({ + data: newData + }); + }); + + it('should have correct state.data', () => { + expect(wrapper.state().data).toEqual(newData); + }); + }); + + describe('getData', () => { + let result; + const fakeData = [...data, { id: 3, name: 'test' }]; + + beforeEach(() => { + wrapper = shallow(shallowContext()); + }); + + describe('if third argument is give', () => { + it('should return the data property from third argument', () => { + result = wrapper.instance().getData(null, null, { data: fakeData }); + expect(result).toEqual(fakeData); + }); + }); + + describe('if second argument is give', () => { + it('should return the data property from second argument', () => { + result = wrapper.instance().getData(null, { data: fakeData }); + expect(result).toEqual(fakeData); + }); + }); + + describe('if first argument is give', () => { + it('should return the data property from first argument', () => { + result = wrapper.instance().getData({ data: fakeData }); + expect(result).toEqual(fakeData); + }); + }); + + describe('if no argument is give', () => { + it('should return default props.data', () => { + result = wrapper.instance().getData(); + expect(result).toEqual(data); + }); + }); + }); +}); diff --git a/packages/react-bootstrap-table2/test/contexts/index.test.js b/packages/react-bootstrap-table2/test/contexts/index.test.js new file mode 100644 index 0000000..8b00719 --- /dev/null +++ b/packages/react-bootstrap-table2/test/contexts/index.test.js @@ -0,0 +1,196 @@ +/* eslint no-param-reassign: 0 */ +import React from 'react'; +import { shallow } from 'enzyme'; + +import Base from '../../src/bootstrap-table'; +import withContext from '../../src/contexts'; + +describe('Context', () => { + let wrapper; + + const keyField = 'id'; + + let columns; + + const data = [{ + id: 1, + name: 'A' + }, { + id: 2, + name: 'B' + }]; + + const BootstrapTable = withContext(Base); + + beforeEach(() => { + columns = [{ + dataField: keyField, + text: 'ID' + }, { + dataField: 'name', + text: 'Name' + }]; + }); + + describe('basic render', () => { + beforeEach(() => { + wrapper = shallow( + + ); + wrapper.render(); + }); + + it('should create contexts correctly', () => { + expect(wrapper.instance().DataContext).toBeDefined(); + expect(wrapper.instance().SortContext).not.toBeDefined(); + expect(wrapper.instance().SelectionContext).not.toBeDefined(); + expect(wrapper.instance().CellEditContext).not.toBeDefined(); + expect(wrapper.instance().FilterContext).not.toBeDefined(); + expect(wrapper.instance().PaginationContext).not.toBeDefined(); + }); + + it('should render correctly', () => { + const dataProvider = wrapper.find(wrapper.instance().DataContext.Provider); + expect(dataProvider).toHaveLength(1); + expect(dataProvider.props().data).toEqual(data); + expect(dataProvider.props().keyField).toEqual(keyField); + expect(dataProvider.props().columns).toEqual(columns); + }); + }); + + describe('if there\'s sort is enable', () => { + beforeEach(() => { + const columnsWithSort = columns.map((c) => { + c.sort = true; + return c; + }); + wrapper = shallow( + + ); + wrapper.render(); + }); + + it('should create contexts correctly', () => { + expect(wrapper.instance().DataContext).toBeDefined(); + expect(wrapper.instance().SortContext).toBeDefined(); + expect(wrapper.instance().SelectionContext).not.toBeDefined(); + expect(wrapper.instance().CellEditContext).not.toBeDefined(); + expect(wrapper.instance().FilterContext).not.toBeDefined(); + expect(wrapper.instance().PaginationContext).not.toBeDefined(); + }); + }); + + describe('if row selection is enable', () => { + beforeEach(() => { + const selectRow = { mode: 'radio' }; + wrapper = shallow( + + ); + wrapper.render(); + }); + + it('should create contexts correctly', () => { + expect(wrapper.instance().DataContext).toBeDefined(); + expect(wrapper.instance().SortContext).not.toBeDefined(); + expect(wrapper.instance().SelectionContext).toBeDefined(); + expect(wrapper.instance().CellEditContext).not.toBeDefined(); + expect(wrapper.instance().FilterContext).not.toBeDefined(); + expect(wrapper.instance().PaginationContext).not.toBeDefined(); + }); + }); + + describe('if cell editing is enable', () => { + beforeEach(() => { + const CellEditContext = React.createContext(); + const cellEdit = { + createContext: jest.fn().mockReturnValue({ + Provider: CellEditContext.Provider, + Consumer: CellEditContext.Consumer + }) + }; + wrapper = shallow( + + ); + wrapper.render(); + }); + + it('should create contexts correctly', () => { + expect(wrapper.instance().DataContext).toBeDefined(); + expect(wrapper.instance().SortContext).not.toBeDefined(); + expect(wrapper.instance().SelectionContext).not.toBeDefined(); + expect(wrapper.instance().CellEditContext).toBeDefined(); + expect(wrapper.instance().FilterContext).not.toBeDefined(); + expect(wrapper.instance().PaginationContext).not.toBeDefined(); + }); + }); + + describe('if column filter is enable', () => { + beforeEach(() => { + const FilterContext = React.createContext(); + const filter = { + createContext: jest.fn().mockReturnValue({ + Provider: FilterContext.Provider, + Consumer: FilterContext.Consumer + }) + }; + wrapper = shallow( + + ); + wrapper.render(); + }); + + it('should create contexts correctly', () => { + expect(wrapper.instance().DataContext).toBeDefined(); + expect(wrapper.instance().SortContext).not.toBeDefined(); + expect(wrapper.instance().SelectionContext).not.toBeDefined(); + expect(wrapper.instance().CellEditContext).not.toBeDefined(); + expect(wrapper.instance().FilterContext).toBeDefined(); + expect(wrapper.instance().PaginationContext).not.toBeDefined(); + }); + }); + + describe('if pagination is enable', () => { + beforeEach(() => { + const PaginationContext = React.createContext(); + const paginator = { + createContext: jest.fn().mockReturnValue({ + Provider: PaginationContext.Provider, + Consumer: PaginationContext.Consumer + }) + }; + wrapper = shallow( + + ); + wrapper.render(); + }); + + it('should create contexts correctly', () => { + expect(wrapper.instance().DataContext).toBeDefined(); + expect(wrapper.instance().SortContext).not.toBeDefined(); + expect(wrapper.instance().SelectionContext).not.toBeDefined(); + expect(wrapper.instance().CellEditContext).not.toBeDefined(); + expect(wrapper.instance().FilterContext).not.toBeDefined(); + expect(wrapper.instance().PaginationContext).toBeDefined(); + }); + }); +}); diff --git a/packages/react-bootstrap-table2/test/contexts/selection-context.test.js b/packages/react-bootstrap-table2/test/contexts/selection-context.test.js new file mode 100644 index 0000000..819fada --- /dev/null +++ b/packages/react-bootstrap-table2/test/contexts/selection-context.test.js @@ -0,0 +1,258 @@ +import 'jsdom-global/register'; +import React from 'react'; +import { shallow } from 'enzyme'; + +import dataOperator from '../../src/store/operators'; +import BootstrapTable from '../../src/bootstrap-table'; +import createSelectionContext from '../../src/contexts/selection-context'; + +describe('DataContext', () => { + let wrapper; + + const data = [{ + id: 1, + name: 'A' + }, { + id: 2, + name: 'B' + }, { + id: 3, + name: 'B' + }]; + + const keyField = 'id'; + + const columns = [{ + dataField: 'id', + text: 'ID' + }, { + dataField: 'name', + text: 'Name' + }]; + + const mockBase = jest.fn((props => ( + + ))); + + const defaultSelectRow = { + mode: 'checkbox' + }; + const SelectionContext = createSelectionContext(dataOperator); + + function shallowContext(selectRow = defaultSelectRow) { + return ( + + + { + selectionProps => mockBase(selectionProps) + } + + + ); + } + + describe('default render', () => { + beforeEach(() => { + wrapper = shallow(shallowContext()); + wrapper.render(); + }); + + it('should have correct Provider property after calling createSelectionContext', () => { + expect(SelectionContext.Provider).toBeDefined(); + }); + + it('should have correct Consumer property after calling createSelectionContext', () => { + expect(SelectionContext.Consumer).toBeDefined(); + }); + + it('should have correct state.data', () => { + expect(wrapper.state().selected).toEqual([]); + }); + + it('should pass correct sort props to children element', () => { + expect(wrapper.length).toBe(1); + expect(mockBase).toHaveBeenCalledWith({ + selected: wrapper.state().selected, + onRowSelect: wrapper.instance().handleRowSelect, + onAllRowsSelect: wrapper.instance().handleAllRowsSelect + }); + }); + }); + + describe('componentWillReceiveProps', () => { + const newSelectRow = { + ...defaultSelectRow, + selected: [1] + }; + + beforeEach(() => { + wrapper = shallow(shallowContext()); + wrapper.instance().componentWillReceiveProps({ + selectRow: newSelectRow + }); + }); + + it('should have correct state.selected', () => { + expect(wrapper.state().selected).toEqual(newSelectRow.selected); + }); + + describe('if nextProps.selectRow is not existing', () => { + const defaultSelected = [1]; + beforeEach(() => { + wrapper = shallow(shallowContext({ + ...defaultSelectRow, + selected: defaultSelected + })); + wrapper.instance().componentWillReceiveProps({ + selectRow: defaultSelectRow + }); + }); + + it('should keep origin state.selected', () => { + expect(wrapper.state().selected).toEqual(defaultSelected); + }); + }); + + describe('if nextProps.selectRow is not existing', () => { + beforeEach(() => { + wrapper = shallow(shallowContext()); + wrapper.instance().componentWillReceiveProps({}); + }); + + it('should not set state.selected', () => { + expect(wrapper.state().selected).toEqual([]); + }); + }); + }); + + describe('when selectRow.selected prop is defined', () => { + let selectRow; + + beforeEach(() => { + selectRow = { + ...defaultSelectRow, + selected: [1] + }; + wrapper = shallow(shallowContext(selectRow)); + }); + + it('should have correct state.data', () => { + expect(wrapper.state().selected).toEqual(selectRow.selected); + }); + }); + + describe('handleRowSelect', () => { + const rowIndex = 1; + const firstSelectedRow = data[0][keyField]; + const secondSelectedRow = data[1][keyField]; + + describe('when selectRow.mode is \'radio\'', () => { + beforeEach(() => { + const selectRow = { mode: 'radio' }; + wrapper = shallow(shallowContext(selectRow)); + }); + + it('should set state.selected correctly', () => { + wrapper.instance().handleRowSelect(firstSelectedRow, true, rowIndex); + expect(wrapper.state('selected')).toEqual([firstSelectedRow]); + + wrapper.instance().handleRowSelect(secondSelectedRow, true, rowIndex); + expect(wrapper.state('selected')).toEqual([secondSelectedRow]); + }); + }); + + describe('when selectRow.mode is \'checkbox\'', () => { + beforeEach(() => { + wrapper = shallow(shallowContext()); + }); + + it('should set state.selected correctly', () => { + wrapper.instance().handleRowSelect(firstSelectedRow, true, rowIndex); + expect(wrapper.state('selected')).toEqual(expect.arrayContaining([firstSelectedRow])); + + wrapper.instance().handleRowSelect(secondSelectedRow, true, rowIndex); + expect(wrapper.state('selected')).toEqual(expect.arrayContaining([firstSelectedRow, secondSelectedRow])); + + wrapper.instance().handleRowSelect(firstSelectedRow, false, rowIndex); + expect(wrapper.state('selected')).toEqual(expect.arrayContaining([secondSelectedRow])); + + wrapper.instance().handleRowSelect(secondSelectedRow, false, rowIndex); + expect(wrapper.state('selected')).toEqual([]); + }); + }); + + describe('when selectRow.onSelect is defined', () => { + const onSelect = jest.fn(); + beforeEach(() => { + wrapper = shallow(shallowContext({ + ...defaultSelectRow, + onSelect + })); + }); + + it('call selectRow.onSelect correctly', () => { + const e = { target: {} }; + const row = dataOperator.getRowByRowId(data, keyField, firstSelectedRow); + wrapper.instance().handleRowSelect(firstSelectedRow, true, rowIndex, e); + expect(onSelect).toHaveBeenCalledWith(row, true, rowIndex, e); + }); + }); + }); + + describe('handleAllRowsSelect', () => { + const e = { target: {} }; + + describe('when isUnSelect argument is false', () => { + beforeEach(() => { + wrapper = shallow(shallowContext()); + wrapper.instance().handleAllRowsSelect(e, false); + }); + + it('should set state.selected correctly', () => { + expect(wrapper.state('selected')).toEqual(data.map(d => d[keyField])); + }); + }); + + describe('when isUnSelect argument is true', () => { + beforeEach(() => { + wrapper = shallow(shallowContext({ + ...defaultSelectRow, + selected: data.map(d => d[keyField]) + })); + wrapper.instance().handleAllRowsSelect(e, true); + }); + + it('should set state.selected correctly', () => { + expect(wrapper.state('selected')).toEqual([]); + }); + }); + + describe('when selectRow.onSelectAll is defined', () => { + const onSelectAll = jest.fn(); + beforeEach(() => { + wrapper = shallow(shallowContext({ + ...defaultSelectRow, + onSelectAll + })); + wrapper.instance().handleAllRowsSelect(e, false); + }); + + it('should call selectRow.onSelectAll correctly', () => { + expect(onSelectAll).toHaveBeenCalledWith( + true, + dataOperator.getSelectedRows(data, keyField, wrapper.state('selected')), + e + ); + }); + }); + }); +}); diff --git a/packages/react-bootstrap-table2/test/contexts/sort-context.test.js b/packages/react-bootstrap-table2/test/contexts/sort-context.test.js new file mode 100644 index 0000000..0a05861 --- /dev/null +++ b/packages/react-bootstrap-table2/test/contexts/sort-context.test.js @@ -0,0 +1,255 @@ +import 'jsdom-global/register'; +import React from 'react'; +import { shallow } from 'enzyme'; + +import Const from '../../src/const'; +import dataOperator from '../../src/store/operators'; +import BootstrapTable from '../../src/bootstrap-table'; +import createSortContext from '../../src/contexts/sort-context'; + +describe('SortContext', () => { + let wrapper; + let columns; + let SortContext; + + let data; + + const mockBase = jest.fn((props => ( + + ))); + + beforeEach(() => { + data = [{ + id: 1, + name: 'A' + }, { + id: 2, + name: 'B' + }]; + columns = [{ + dataField: 'id', + text: 'ID', + sort: true + }, { + dataField: 'name', + text: 'Name', + sort: true + }]; + }); + + const handleRemoteSortChange = jest.fn(); + + function shallowContext(enableRemote = false, providerProps = {}) { + handleRemoteSortChange.mockReset(); + mockBase.mockReset(); + SortContext = createSortContext( + dataOperator, + jest.fn().mockReturnValue(enableRemote), + handleRemoteSortChange + ); + return ( + + + { + sortProps => mockBase(sortProps) + } + + + ); + } + + describe('default render', () => { + beforeEach(() => { + wrapper = shallow(shallowContext()); + wrapper.render(); + }); + + it('should have correct Provider property after calling createSortContext', () => { + expect(SortContext.Provider).toBeDefined(); + }); + + it('should have correct Consumer property after calling createSortContext', () => { + expect(SortContext.Consumer).toBeDefined(); + }); + + it('should have correct state.sortOrder', () => { + expect(wrapper.state().sortOrder).toBe(undefined); + }); + + it('should have correct state.sortColumn', () => { + expect(wrapper.state().sortColumn).toBe(undefined); + }); + + it('should pass correct sort props to children element', () => { + expect(wrapper.length).toBe(1); + expect(mockBase).toHaveBeenCalledWith({ + data, + sortOrder: undefined, + onSort: wrapper.instance().handleSort, + sortField: null + }); + }); + }); + + describe('handleSort function', () => { + let sortColumn; + let nextOrderSpy; + + beforeEach(() => { + sortColumn = columns[0]; + nextOrderSpy = jest.spyOn(dataOperator, 'nextOrder'); + }); + + afterEach(() => { + nextOrderSpy.mockRestore(); + }); + + describe('when remote.sort is false', () => { + beforeEach(() => { + wrapper = shallow(shallowContext()); + wrapper.render(); + wrapper.instance().handleSort(sortColumn); + wrapper.update(); + wrapper.render(); + }); + + it('should set state correctly', () => { + expect(wrapper.state().sortColumn).toEqual(sortColumn); + expect(wrapper.state().sortOrder).toEqual(Const.SORT_DESC); + }); + + it('should call dataOperator.nextOrder correctly', () => { + expect(nextOrderSpy).toHaveBeenCalledTimes(1); + expect(nextOrderSpy).toHaveBeenCalledWith( + sortColumn, + { sortColumn: undefined, sortOrder: undefined }, + wrapper.props().defaultSortDirection + ); + }); + + it('should pass correct sort props to children element', () => { + expect(wrapper.length).toBe(1); + expect(mockBase).toHaveBeenLastCalledWith({ + data: data.reverse(), + sortOrder: wrapper.state().sortOrder, + onSort: wrapper.instance().handleSort, + sortField: wrapper.state().sortColumn.dataField + }); + }); + }); + + describe('when remote.sort is true', () => { + beforeEach(() => { + wrapper = shallow(shallowContext(true)); + wrapper.render(); + + nextOrderSpy = jest.spyOn(dataOperator, 'nextOrder'); + wrapper.instance().handleSort(sortColumn); + }); + + it('should set state correctly', () => { + expect(wrapper.state().sortColumn).toEqual(sortColumn); + expect(wrapper.state().sortOrder).toEqual(Const.SORT_DESC); + }); + + it('should call dataOperator.nextOrder correctly', () => { + expect(nextOrderSpy).toHaveBeenCalledTimes(1); + expect(nextOrderSpy).toHaveBeenCalledWith( + sortColumn, + { sortColumn: undefined, sortOrder: undefined }, + wrapper.props().defaultSortDirection + ); + }); + + it('should calling handleRemoteSortChange correctly', () => { + expect(handleRemoteSortChange).toHaveBeenCalledTimes(1); + expect(handleRemoteSortChange).toHaveBeenCalledWith(sortColumn.dataField, Const.SORT_DESC); + }); + }); + + describe('when column.onSort prop is defined', () => { + const onSortCB = jest.fn(); + + beforeEach(() => { + columns[0].onSort = onSortCB; + wrapper = shallow(shallowContext()); + wrapper.instance().handleSort(sortColumn); + }); + + it('should calling column.onSort function correctly', () => { + expect(onSortCB).toHaveBeenCalledTimes(1); + expect(onSortCB).toHaveBeenCalledWith(columns[0].dataField, Const.SORT_DESC); + + wrapper.instance().handleSort(sortColumn); + expect(onSortCB).toHaveBeenCalledTimes(2); + expect(onSortCB).toHaveBeenCalledWith(columns[0].dataField, Const.SORT_ASC); + }); + }); + }); + + describe('when defaultSorted prop is defined', () => { + const defaultSorted = [{ + dataField: 'name', + order: Const.SORT_DESC + }]; + + beforeEach(() => { + wrapper = shallow(shallowContext(false, { defaultSorted })); + wrapper.render(); + }); + + it('should pass correct sort props to children element', () => { + expect(wrapper.length).toBe(1); + expect(mockBase).toHaveBeenLastCalledWith({ + data: data.reverse(), + sortOrder: wrapper.state().sortOrder, + onSort: wrapper.instance().handleSort, + sortField: wrapper.state().sortColumn.dataField + }); + }); + + it('should have correct state.sortOrder', () => { + expect(wrapper.state().sortOrder).toBe(defaultSorted[0].order); + }); + + it('should have correct state.sortColumn', () => { + expect(wrapper.state().sortColumn).toBe(columns[1]); + }); + + describe('when column.onSort prop is defined', () => { + const onSortCB = jest.fn(); + + beforeEach(() => { + columns[1].onSort = onSortCB; + wrapper = shallow(shallowContext(false, { defaultSorted })); + }); + + it('should calling column.onSort function correctly', () => { + expect(onSortCB).toHaveBeenCalledTimes(1); + expect(onSortCB).toHaveBeenCalledWith(defaultSorted[0].dataField, defaultSorted[0].order); + }); + }); + + describe('when remote.sort is true', () => { + beforeEach(() => { + wrapper = shallow(shallowContext(true, { defaultSorted })); + wrapper.render(); + }); + + it('should calling handleRemoteSortChange correctly', () => { + expect(handleRemoteSortChange).toHaveBeenCalledTimes(1); + expect(handleRemoteSortChange) + .toHaveBeenCalledWith(defaultSorted[0].dataField, defaultSorted[0].order); + }); + }); + }); +}); diff --git a/packages/react-bootstrap-table2/test/props-resolver/index.test.js b/packages/react-bootstrap-table2/test/props-resolver/index.test.js index cb3a54b..d12ed1f 100644 --- a/packages/react-bootstrap-table2/test/props-resolver/index.test.js +++ b/packages/react-bootstrap-table2/test/props-resolver/index.test.js @@ -225,17 +225,9 @@ describe('TableResolver', () => { bar: expect.any(Function) })); }); - - it('should return object which can not contain allRowsSelected option', () => { - expect(headerCellSelectionInfo.allRowsSelected).not.toBeDefined(); - }); - - it('should return object which can not contain allRowsSelected option', () => { - expect(headerCellSelectionInfo.selected).not.toBeDefined(); - }); }); - describe('if all rows were selected', () => { + describe('if options.allRowsSelected is true', () => { beforeEach(() => { selectRow = {}; const selectedRowKeys = [1, 2]; @@ -258,7 +250,7 @@ describe('TableResolver', () => { }); }); - describe('if part of rows were selected', () => { + describe('if options.allRowsSelected and options.allRowsNotSelected both are false', () => { beforeEach(() => { selectRow = {}; const selectedRowKeys = [1]; @@ -269,6 +261,7 @@ describe('TableResolver', () => { wrapper = shallow(mockElement); headerCellSelectionInfo = wrapper.instance().resolveSelectRowPropsForHeader({ allRowsSelected: false, + allRowsNotSelected: false, selected: selectedRowKeys }); }); @@ -280,7 +273,7 @@ describe('TableResolver', () => { }); }); - describe('if none of row was selected', () => { + describe('if options.allRowsNotSelected is true', () => { beforeEach(() => { selectRow = {}; const selectedRowKeys = []; @@ -292,6 +285,7 @@ describe('TableResolver', () => { headerCellSelectionInfo = wrapper.instance().resolveSelectRowPropsForHeader({ allRowsSelected: false, + allRowsNotSelected: true, selected: selectedRowKeys }); }); diff --git a/packages/react-bootstrap-table2/test/props-resolver/remote-resolver.test.js b/packages/react-bootstrap-table2/test/props-resolver/remote-resolver.test.js index 4cfcc04..31c8dfc 100644 --- a/packages/react-bootstrap-table2/test/props-resolver/remote-resolver.test.js +++ b/packages/react-bootstrap-table2/test/props-resolver/remote-resolver.test.js @@ -3,7 +3,7 @@ import React from 'react'; import sinon from 'sinon'; import { shallow } from 'enzyme'; -import Container from '../../'; +import Container from '../../index'; // import remoteResolver from '../../src/props-resolver/remote-resolver'; describe('remoteResolver', () => { @@ -100,6 +100,16 @@ describe('remoteResolver', () => { expect(wrapper.instance().isRemoteFiltering()).toBeTruthy(); }); }); + + describe('when this.isRemotePagination return true', () => { + beforeEach(() => { + shallowContainer({ remote: { pagination: true } }); + }); + + it('should return true', () => { + expect(wrapper.instance().isRemoteFiltering()).toBeTruthy(); + }); + }); }); describe('isRemoteSort', () => { @@ -132,6 +142,16 @@ describe('remoteResolver', () => { expect(wrapper.instance().isRemoteSort()).toBeTruthy(); }); }); + + describe('when this.isRemotePagination return true', () => { + beforeEach(() => { + shallowContainer({ remote: { pagination: true } }); + }); + + it('should return true', () => { + expect(wrapper.instance().isRemoteSort()).toBeTruthy(); + }); + }); }); describe('isRemoteCellEdit', () => { @@ -166,7 +186,7 @@ describe('remoteResolver', () => { }); }); - describe('handleCellChange', () => { + describe('handleRemoteCellChange', () => { const onTableChangeCB = sinon.stub(); const rowId = 1; const dataField = 'name'; @@ -175,7 +195,7 @@ describe('remoteResolver', () => { beforeEach(() => { onTableChangeCB.reset(); shallowContainer({ onTableChange: onTableChangeCB }); - wrapper.instance().handleCellChange(rowId, dataField, newValue); + wrapper.instance().handleRemoteCellChange(rowId, dataField, newValue); }); it('should calling props.onTableChange correctly', () => { @@ -188,35 +208,45 @@ describe('remoteResolver', () => { describe('handleSortChange', () => { const onTableChangeCB = sinon.stub(); + const newSortFiled = 'name'; + const newSortOrder = 'asc'; beforeEach(() => { onTableChangeCB.reset(); shallowContainer({ onTableChange: onTableChangeCB }); - wrapper.instance().handleSortChange(); + wrapper.instance().handleRemoteSortChange(newSortFiled, newSortOrder); }); it('should calling props.onTableChange correctly', () => { expect(onTableChangeCB.calledOnce).toBeTruthy(); - expect(onTableChangeCB.calledWith('sort', wrapper.instance().getNewestState())).toBeTruthy(); + expect(onTableChangeCB.calledWith('sort', wrapper.instance().getNewestState({ + sortField: newSortFiled, + sortOrder: newSortOrder + }))).toBeTruthy(); }); }); describe('handleRemotePageChange', () => { const onTableChangeCB = sinon.stub(); + const newPage = 2; + const newSizePerPage = 10; beforeEach(() => { onTableChangeCB.reset(); shallowContainer({ onTableChange: onTableChangeCB }); - wrapper.instance().handleRemotePageChange(); + wrapper.instance().handleRemotePageChange(newPage, newSizePerPage); }); it('should calling props.onTableChange correctly', () => { expect(onTableChangeCB.calledOnce).toBeTruthy(); - expect(onTableChangeCB.calledWith('pagination', wrapper.instance().getNewestState())).toBeTruthy(); + expect(onTableChangeCB.calledWith('pagination', wrapper.instance().getNewestState({ + page: newPage, + sizePerPage: newSizePerPage + }))).toBeTruthy(); }); }); describe('handleRemoteFilterChange', () => { const onTableChangeCB = sinon.stub(); - + const filters = { price: { filterVal: 20, filterType: 'TEXT' } }; beforeEach(() => { onTableChangeCB.reset(); shallowContainer({ onTableChange: onTableChangeCB }); @@ -224,16 +254,16 @@ describe('remoteResolver', () => { describe('when remote pagination is disabled', () => { it('should calling props.onTableChange correctly', () => { - wrapper.instance().handleRemoteFilterChange(); + wrapper.instance().handleRemoteFilterChange(filters); expect(onTableChangeCB.calledOnce).toBeTruthy(); - expect(onTableChangeCB.calledWith('filter', wrapper.instance().getNewestState())).toBeTruthy(); + expect(onTableChangeCB.calledWith('filter', wrapper.instance().getNewestState({ + filters + }))).toBeTruthy(); }); }); describe('when remote pagination is enabled', () => { - const wrapperFactory = Base => class FilterWrapper extends React.Component { - render() { return ; } - }; + const createContext = () => {}; describe('and pagination.options.pageStartIndex is defined', () => { const options = { pageStartIndex: 0 }; @@ -241,16 +271,16 @@ describe('remoteResolver', () => { shallowContainer({ remote: true, onTableChange: onTableChangeCB, - pagination: { options, wrapperFactory } + pagination: { options, createContext } }); - wrapper.instance().store.page = 1; - wrapper.instance().store.sizePerPage = 10; - wrapper.instance().handleRemoteFilterChange(); + wrapper.instance().handleRemoteFilterChange(filters); }); - it('should calling onTableChange correctly', () => { + it('should calling onTableChange with page property by pageStartIndex', () => { expect(onTableChangeCB.calledOnce).toBeTruthy(); - const newState = wrapper.instance().getNewestState(); + const newState = wrapper.instance().getNewestState({ + filters + }); newState.page = options.pageStartIndex; expect(onTableChangeCB.calledWith('filter', newState)).toBeTruthy(); }); @@ -261,14 +291,14 @@ describe('remoteResolver', () => { shallowContainer({ remote: true, onTableChange: onTableChangeCB, - pagination: { wrapperFactory } + pagination: { createContext } }); - wrapper.instance().handleRemoteFilterChange(); + wrapper.instance().handleRemoteFilterChange(filters); }); - it('should calling onTableChange correctly', () => { + it('should calling onTableChange with page property by default 1', () => { expect(onTableChangeCB.calledOnce).toBeTruthy(); - const newState = wrapper.instance().getNewestState(); + const newState = wrapper.instance().getNewestState({ filters }); newState.page = 1; expect(onTableChangeCB.calledWith('filter', newState)).toBeTruthy(); }); diff --git a/packages/react-bootstrap-table2/test/row-selection/wrapper.test.js b/packages/react-bootstrap-table2/test/row-selection/wrapper.test.js deleted file mode 100644 index aa397ca..0000000 --- a/packages/react-bootstrap-table2/test/row-selection/wrapper.test.js +++ /dev/null @@ -1,224 +0,0 @@ -import React from 'react'; -import sinon from 'sinon'; -import { shallow } from 'enzyme'; - -import Store from '../../src/store'; -import BootstrapTable from '../../src/bootstrap-table'; -import wrapperFactory from '../../src/row-selection/wrapper'; - -describe('RowSelectionWrapper', () => { - let wrapper; - let selectRow; - - const columns = [{ - dataField: 'id', - text: 'ID' - }, { - dataField: 'name', - text: 'Name' - }]; - - const data = [{ - id: 1, - name: 'A' - }, { - id: 2, - name: 'B' - }]; - - const rowIndex = 1; - - const keyField = 'id'; - - const store = new Store(keyField); - store.data = data; - const RowSelectionWrapper = wrapperFactory(BootstrapTable); - - beforeEach(() => { - selectRow = { - mode: 'radio' - }; - wrapper = shallow( - - ); - }); - - it('should render RowSelectionWrapper correctly', () => { - expect(wrapper.length).toBe(1); - expect(wrapper.find(BootstrapTable)).toBeDefined(); - }); - - it('should have correct store.selected value', () => { - expect(store.selected).toEqual([]); - }); - - it('should have correct state', () => { - expect(wrapper.state().selectedRowKeys).toBeDefined(); - expect(wrapper.state().selectedRowKeys.length).toEqual(0); - }); - - it('should inject correct props to base component', () => { - expect(wrapper.props().onRowSelect).toBeDefined(); - expect(wrapper.props().onAllRowsSelect).toBeDefined(); - }); - - describe('componentWillReceiveProps', () => { - const nextSelected = [0]; - const nextProps = { - store: { - selected: nextSelected - }, - selectRow: { - mode: 'checkbox', - selected: nextSelected - } - }; - - it('should update state.selectedRowKeys with next selected rows', () => { - wrapper.instance().componentWillReceiveProps(nextProps); - expect(nextProps.store.selected).toEqual(nextSelected); - expect(wrapper.state('selectedRowKeys')).toEqual(nextSelected); - }); - }); - - describe('when selectRow.selected is defined', () => { - beforeEach(() => { - selectRow.mode = 'checkbox'; - selectRow.selected = [1, 3]; - wrapper = shallow( - - ); - }); - - it('should have correct store.selected value', () => { - expect(store.selected).toEqual(selectRow.selected); - }); - - it('should have correct state', () => { - expect(wrapper.state().selectedRowKeys).toEqual(selectRow.selected); - }); - }); - - describe('when selectRow.mode is \'radio\'', () => { - const firstSelectedRow = data[0][keyField]; - const secondSelectedRow = data[1][keyField]; - - it('call handleRowSelect function should setting correct state.selectedRowKeys', () => { - wrapper.instance().handleRowSelect(firstSelectedRow, rowIndex); - expect(wrapper.state('selectedRowKeys')).toEqual([firstSelectedRow]); - - wrapper.instance().handleRowSelect(secondSelectedRow, rowIndex); - expect(wrapper.state('selectedRowKeys')).toEqual([secondSelectedRow]); - }); - }); - - describe('when selectRow.mode is \'checkbox\'', () => { - const firstSelectedRow = data[0][keyField]; - const secondSelectedRow = data[1][keyField]; - - beforeEach(() => { - selectRow.mode = 'checkbox'; - wrapper = shallow( - - ); - }); - - it('call handleRowSelect function should setting correct state.selectedRowKeys', () => { - wrapper.instance().handleRowSelect(firstSelectedRow, true, rowIndex); - expect(wrapper.state('selectedRowKeys')).toEqual(expect.arrayContaining([firstSelectedRow])); - - wrapper.instance().handleRowSelect(secondSelectedRow, true, rowIndex); - expect(wrapper.state('selectedRowKeys')).toEqual(expect.arrayContaining([firstSelectedRow, secondSelectedRow])); - - wrapper.instance().handleRowSelect(firstSelectedRow, false, rowIndex); - expect(wrapper.state('selectedRowKeys')).toEqual(expect.arrayContaining([secondSelectedRow])); - - wrapper.instance().handleRowSelect(secondSelectedRow, false, rowIndex); - expect(wrapper.state('selectedRowKeys')).toEqual([]); - }); - - it('call handleAllRowsSelect function should setting correct state.selectedRowKeys', () => { - wrapper.instance().handleAllRowsSelect(); - expect(wrapper.state('selectedRowKeys')).toEqual(expect.arrayContaining([firstSelectedRow, secondSelectedRow])); - - wrapper.instance().handleAllRowsSelect(); - expect(wrapper.state('selectedRowKeys')).toEqual([]); - }); - }); - - describe('when selectRow.onSelect is defined', () => { - const selectedRow = data[0][keyField]; - const onSelectCallBack = sinon.stub(); - - beforeEach(() => { - selectRow.mode = 'checkbox'; - selectRow.onSelect = onSelectCallBack; - wrapper = shallow( - - ); - }); - - it('selectRow.onSelect callback should be called correctly when calling handleRowSelect function', () => { - wrapper.instance().handleRowSelect(selectedRow, true, rowIndex); - expect(onSelectCallBack.callCount).toEqual(1); - expect(onSelectCallBack.calledWith(data[0], true, rowIndex)).toBeTruthy(); - - wrapper.instance().handleRowSelect(selectedRow, false, rowIndex); - expect(onSelectCallBack.callCount).toEqual(2); - expect(onSelectCallBack.calledWith(data[0], false, rowIndex)).toBeTruthy(); - }); - }); - - describe('when selectRow.onSelectAll is defined', () => { - const onSelectAllCallBack = sinon.stub(); - - beforeEach(() => { - selectRow.mode = 'checkbox'; - selectRow.onSelectAll = onSelectAllCallBack; - wrapper = shallow( - - ); - }); - - it('selectRow.onSelect callback should be called correctly when calling handleRowSelect function', () => { - const e = {}; - wrapper.instance().handleAllRowsSelect(e); - expect(onSelectAllCallBack.callCount).toEqual(1); - expect(onSelectAllCallBack.calledWith(true, data, e)).toBeTruthy(); - - wrapper.instance().handleAllRowsSelect(e); - expect(onSelectAllCallBack.callCount).toEqual(2); - expect(onSelectAllCallBack.calledWith(false, [], e)).toBeTruthy(); - }); - }); -}); diff --git a/packages/react-bootstrap-table2/test/sort/wrapper.test.js b/packages/react-bootstrap-table2/test/sort/wrapper.test.js deleted file mode 100644 index 2575e86..0000000 --- a/packages/react-bootstrap-table2/test/sort/wrapper.test.js +++ /dev/null @@ -1,230 +0,0 @@ -import 'jsdom-global/register'; -import React from 'react'; -import sinon from 'sinon'; -import { shallow } from 'enzyme'; - -import Const from '../../src/const'; -import Store from '../../src/store'; -import BootstrapTable from '../../src/bootstrap-table'; -import wrapperFactory from '../../src/sort/wrapper'; - -describe('SortWrapper', () => { - let wrapper; - let columns; - - const data = [{ - id: 1, - name: 'A' - }, { - id: 2, - name: 'B' - }]; - - const keyField = 'id'; - - let store = new Store(keyField); - store.data = data; - - const SortWrapper = wrapperFactory(BootstrapTable); - - beforeEach(() => { - columns = [{ - dataField: 'id', - text: 'ID', - sort: true - }, { - dataField: 'name', - text: 'Name', - sort: true - }]; - wrapper = shallow( - - ); - }); - - it('should render SortWrapper correctly', () => { - expect(wrapper.length).toBe(1); - expect(wrapper.find(BootstrapTable)).toBeDefined(); - }); - - it('should inject correct props to base component', () => { - expect(wrapper.props().onSort).toBeDefined(); - }); - - describe('call handleSort function', () => { - let sortBySpy; - let sortColumn; - - beforeEach(() => { - sortColumn = columns[0]; - store = new Store(keyField); - store.data = data; - sortBySpy = sinon.spy(store, 'sortBy'); - }); - - describe('when remote.sort is false', () => { - beforeEach(() => { - wrapper = shallow( - - ); - - wrapper.instance().handleSort(sortColumn); - }); - - it('should operating on store correctly', () => { - expect(store.sortOrder).toEqual(Const.SORT_DESC); - expect(store.sortField).toEqual(sortColumn.dataField); - - wrapper.instance().handleSort(sortColumn); // sort same column again - expect(store.sortOrder).toEqual(Const.SORT_ASC); - expect(store.sortField).toEqual(sortColumn.dataField); - }); - - it('should calling store.sortBy correctly', () => { - expect(sortBySpy.calledOnce).toBeTruthy(); - expect(sortBySpy.calledWith(sortColumn)).toBeTruthy(); - }); - }); - - describe('when remote.sort is true', () => { - let onTableChangeCB; - - beforeEach(() => { - onTableChangeCB = sinon.stub(); - wrapper = shallow( - - ); - wrapper.instance().handleSort(sortColumn); - }); - - it('should operating on store correctly', () => { - expect(store.sortOrder).toEqual(Const.SORT_DESC); - expect(store.sortField).toEqual(sortColumn.dataField); - - wrapper.instance().handleSort(sortColumn); // sort same column again - expect(store.sortOrder).toEqual(Const.SORT_ASC); - expect(store.sortField).toEqual(sortColumn.dataField); - }); - - it('should not calling store.sortBy', () => { - expect(sortBySpy.calledOnce).toBeFalsy(); - }); - - it('should calling props.onTableChange', () => { - expect(onTableChangeCB.calledOnce).toBeTruthy(); - }); - }); - - describe('when column.onSort prop is defined', () => { - const onSortCB = jest.fn(); - - beforeEach(() => { - columns[0].onSort = onSortCB; - wrapper = shallow( - - ); - wrapper.instance().handleSort(sortColumn); - }); - - it('should calling column.onSort function correctly', () => { - expect(onSortCB).toHaveBeenCalledTimes(1); - expect(onSortCB).toHaveBeenCalledWith(columns[0].dataField, Const.SORT_DESC); - - wrapper.instance().handleSort(sortColumn); - expect(onSortCB).toHaveBeenCalledTimes(2); - expect(onSortCB).toHaveBeenCalledWith(columns[0].dataField, Const.SORT_ASC); - }); - }); - }); - - describe('when defaultSorted prop is defined', () => { - const defaultSorted = [{ - dataField: 'name', - order: Const.SORT_DESC - }]; - - beforeEach(() => { - wrapper = shallow( - - ); - }); - - it('should render table with correct default sorted', () => { - expect(wrapper.props().data).toEqual(store.data); - }); - - it('should update store.sortField correctly', () => { - expect(store.sortField).toEqual(defaultSorted[0].dataField); - }); - - it('should update store.sortOrder correctly', () => { - expect(store.sortOrder).toEqual(defaultSorted[0].order); - }); - - describe('when column.onSort prop is defined', () => { - const onSortCB = jest.fn(); - - beforeEach(() => { - columns[1].onSort = onSortCB; - wrapper = shallow( - - ); - }); - - it('should calling column.onSort function correctly', () => { - expect(onSortCB).toHaveBeenCalledTimes(1); - expect(onSortCB).toHaveBeenCalledWith(defaultSorted[0].dataField, defaultSorted[0].order); - }); - }); - }); - - describe('componentWillReceiveProps', () => { - let nextProps; - - beforeEach(() => { - nextProps = { columns, store }; - store.sortField = columns[1].dataField; - store.sortOrder = Const.SORT_DESC; - store.sortBy = sinon.stub(); - }); - - it('should sorting again', () => { - wrapper.instance().componentWillReceiveProps(nextProps); - expect(store.sortBy.calledOnce).toBeTruthy(); - }); - }); -}); diff --git a/packages/react-bootstrap-table2/test/store/index.test.js b/packages/react-bootstrap-table2/test/store/index.test.js deleted file mode 100644 index a257272..0000000 --- a/packages/react-bootstrap-table2/test/store/index.test.js +++ /dev/null @@ -1,109 +0,0 @@ -import Store from '../../src/store'; -import Const from '../../src/const'; - -describe('Store Base', () => { - let store; - let data; - - beforeEach(() => { - data = [ - { id: 3, name: 'name2' }, - { id: 2, name: 'ABC' }, - { id: 4, name: '123tester' }, - { id: 1, name: '!@#' } - ]; - store = new Store('id'); - store.data = data; - }); - - describe('initialize', () => { - it('should have correct initialize data', () => { - expect(store.sortOrder).toBeUndefined(); - expect(store.sortField).toBeUndefined(); - expect(store.data.length).toEqual(data.length); - }); - }); - - describe('setSort', () => { - let dataField; - - beforeEach(() => { - dataField = 'name'; - }); - - it('should change sortField by dataField param', () => { - store.setSort({ dataField }); - expect(store.sortField).toEqual(dataField); - }); - - it('should change sortOrder correctly when sortBy same dataField', () => { - store.setSort({ dataField }); - expect(store.sortOrder).toEqual(Const.SORT_DESC); - store.setSort({ dataField }); - expect(store.sortOrder).toEqual(Const.SORT_ASC); - }); - - it('should change sortOrder correctly when sortBy different dataField', () => { - store.setSort({ dataField }); - expect(store.sortOrder).toEqual(Const.SORT_DESC); - - dataField = 'id'; - store.setSort({ dataField }); - expect(store.sortOrder).toEqual(Const.SORT_DESC); - - dataField = 'name'; - store.setSort({ dataField }); - expect(store.sortOrder).toEqual(Const.SORT_DESC); - }); - - it('should force assign sortOrder correctly if second argument is given', () => { - store.setSort({ dataField }, Const.SORT_DESC); - expect(store.sortOrder).toEqual(Const.SORT_DESC); - }); - - it('should force assign sortOrder correctly if third argument is given', () => { - store.setSort({ dataField }, undefined, Const.SORT_ASC); - expect(store.sortOrder).toEqual(Const.SORT_ASC); - }); - }); - - describe('sortBy', () => { - let dataField; - - beforeEach(() => { - dataField = 'name'; - }); - - it('should have correct result after sortBy', () => { - store.sortBy({ dataField }); - const result = store.data.map(e => e[dataField]).sort((a, b) => b - a); - store.data.forEach((e, i) => { - expect(e[dataField]).toEqual(result[i]); - }); - }); - }); - - describe('edit', () => { - it('should update a specified field correctly', () => { - const newValue = 'newValue'; - const dataField = 'name'; - const rowId = 2; - store.edit(rowId, dataField, newValue); - - const row = store.data.find(d => d[store.keyField] === rowId); - expect(row[dataField]).toEqual(newValue); - }); - - it('should not throw any error even if rowId is not existing', () => { - expect(() => { - store.edit('123', 'name', 'value'); - }).not.toThrow(); - }); - - it('should throwing error if dataField is not existing', () => { - expect(() => { - store.edit(2, 'non_exist_field', 'value'); - }).toThrow(); - }); - }); -}); diff --git a/packages/react-bootstrap-table2/test/store/mutate.test.js b/packages/react-bootstrap-table2/test/store/mutate.test.js new file mode 100644 index 0000000..7a6b050 --- /dev/null +++ b/packages/react-bootstrap-table2/test/store/mutate.test.js @@ -0,0 +1,32 @@ +import { editCell } from '../../src/store/mutate'; + +describe('Mutate Function', () => { + const data = [ + { id: 3, name: 'name2' }, + { id: 2, name: 'ABC' }, + { id: 4, name: '123tester' }, + { id: 1, name: '!@#' } + ]; + + const keyField = 'id'; + + describe('editCell', () => { + let rowId; + const editField = 'name'; + const newValue = 'tester'; + + it('should edit successfully if row is existing', () => { + rowId = data[0][keyField]; + + editCell(data, keyField, rowId, editField, newValue); + expect(data[0][editField]).toEqual(newValue); + }); + + it('should not mutate cell if row is not existing', () => { + rowId = 100; + + editCell(data, keyField, rowId, editField, newValue); + expect(data).toEqual(data); + }); + }); +}); diff --git a/packages/react-bootstrap-table2/test/store/rows.test.js b/packages/react-bootstrap-table2/test/store/rows.test.js index e8d1731..876d085 100644 --- a/packages/react-bootstrap-table2/test/store/rows.test.js +++ b/packages/react-bootstrap-table2/test/store/rows.test.js @@ -1,5 +1,4 @@ -import Store from '../../src/store'; -import { getRowByRowId } from '../../src/store/rows'; +import { getRowByRowId, matchRow } from '../../src/store/rows'; describe('Rows Function', () => { const data = [ @@ -9,22 +8,28 @@ describe('Rows Function', () => { { id: 1, name: '!@#' } ]; const keyField = 'id'; - let store; - let fn; - - beforeEach(() => { - store = new Store(keyField); - store.data = data; - fn = getRowByRowId(store); - }); describe('getRowByRowId', () => { it('should returning correct row', () => { - expect(fn(2)).toEqual(data[1]); + expect(getRowByRowId(data, keyField, 2)).toEqual(data[1]); }); it('should returning undefined if not existing', () => { - expect(fn(20)).not.toBeDefined(); + expect(getRowByRowId(data, keyField, 20)).not.toBeDefined(); + }); + }); + + describe('matchRow', () => { + it('should return true if keyField and id is match', () => { + const row = data[0]; + const fn = matchRow(keyField, row[keyField]); + expect(fn(row)).toBeTruthy(); + }); + + it('should return false if keyField and id is not match', () => { + const row = data[0]; + const fn = matchRow(keyField, 0); + expect(fn(row)).toBeFalsy(); }); }); }); diff --git a/packages/react-bootstrap-table2/test/store/selection.test.js b/packages/react-bootstrap-table2/test/store/selection.test.js index 6d453c7..bed3f30 100644 --- a/packages/react-bootstrap-table2/test/store/selection.test.js +++ b/packages/react-bootstrap-table2/test/store/selection.test.js @@ -1,10 +1,8 @@ -import Store from '../../src/store'; import { - isSelectedAll, - isAnySelectedRow, selectableKeys, unSelectableKeys, - getSelectedRows + getSelectedRows, + getSelectionSummary } from '../../src/store/selection'; describe('Selection Function', () => { @@ -15,90 +13,73 @@ describe('Selection Function', () => { { id: 1, name: '!@#' } ]; const keyField = 'id'; - let store; let skip; - let fn; - - beforeEach(() => { - store = new Store(keyField); - store.data = data; - }); - - describe('isSelectedAll', () => { - it('should returning false when store.selected is not cover all rows', () => { - expect(isSelectedAll(store)).toBeFalsy(); - store.selected = [data[0][keyField]]; - expect(isSelectedAll(store)).toBeFalsy(); - }); - - it('should returning true when store.selected is cover all rows', () => { - store.selected = data.map(d => d[keyField]); - expect(isSelectedAll(store)).toBeTruthy(); - }); - }); - - describe('isAnySelectedRow', () => { - it('should returning false if any store.selected is empty', () => { - fn = isAnySelectedRow(store); - expect(fn()).toBeFalsy(); - }); - - it('should returning false if store.selected is have same key as skips', () => { - fn = isAnySelectedRow(store); - skip = [data[0][keyField]]; - store.selected = [data[0][keyField]]; - expect(fn(skip)).toBeFalsy(); - }); - - it('should returning true if store.selected is not empty', () => { - store.selected = [data[0][keyField]]; - fn = isAnySelectedRow(store); - expect(fn()).toBeTruthy(); - }); - - it('should returning true if length of store.selected is bigger than skips', () => { - store.selected = [data[0][keyField], data[2][keyField]]; - skip = [data[0][keyField]]; - fn = isAnySelectedRow(store); - expect(fn(skip)).toBeTruthy(); - }); - }); describe('selectableKeys', () => { - beforeEach(() => { - fn = selectableKeys(store); - }); - it('should returning all row keys if skip is empty', () => { - expect(fn()).toEqual(data.map(d => d[keyField])); + expect(selectableKeys(data, keyField)).toEqual(data.map(d => d[keyField])); }); it('should returngin row keys expect the skip', () => { skip = [data[1][keyField]]; - expect(fn(skip)).toHaveLength(data.length - skip.length); + expect(selectableKeys(data, keyField, skip)).toHaveLength(data.length - skip.length); }); }); describe('unSelectableKeys', () => { it('should returning empty array if skip is empty', () => { - fn = unSelectableKeys(store); - expect(fn()).toHaveLength(0); + expect(unSelectableKeys()).toHaveLength(0); }); it('should returning array which must contain skip', () => { skip = [data[1][keyField]]; - store.selected = data.map(d => d[keyField]); - fn = unSelectableKeys(store); - expect(fn(skip)).toHaveLength(skip.length); + const selected = data.map(d => d[keyField]); + expect(unSelectableKeys(selected, skip)).toHaveLength(skip.length); }); }); describe('getSelectedRows', () => { it('should returning rows object correctly', () => { - store.selected = data.map(d => d[keyField]); - const result = getSelectedRows(store); - expect(result).toHaveLength(store.selected.length); - expect(result).toEqual(store.data); + const selected = data.map(d => d[keyField]); + const result = getSelectedRows(data, keyField, selected); + expect(result).toHaveLength(selected.length); + expect(result).toEqual(data); + }); + }); + + describe('getSelectionSummary', () => { + let result; + + describe('if selected argument is able to cover all the data argument', () => { + it('should return an obj which allRowsSelected is true and allRowsNotSelected is false', () => { + const selected = data.map(d => d[keyField]); + result = getSelectionSummary(data, keyField, selected); + expect(result).toEqual({ + allRowsSelected: true, + allRowsNotSelected: false + }); + }); + }); + + describe('if selected argument empty', () => { + it('should return an obj which allRowsSelected is false but allRowsNotSelected is true', () => { + result = getSelectionSummary(data, keyField); + expect(result).toEqual({ + allRowsSelected: false, + allRowsNotSelected: true + }); + }); + }); + + describe('if selected argument is only cover partial data', () => { + it('should return an obj which allRowsSelected and allRowsNotSelected both are false', () => { + const selected = [1, 2]; + result = getSelectionSummary(data, keyField, selected); + expect(result).toEqual({ + allRowsSelected: false, + allRowsNotSelected: false + }); + }); }); }); }); diff --git a/packages/react-bootstrap-table2/test/store/sort.test.js b/packages/react-bootstrap-table2/test/store/sort.test.js index 33422b9..5ca05c1 100644 --- a/packages/react-bootstrap-table2/test/store/sort.test.js +++ b/packages/react-bootstrap-table2/test/store/sort.test.js @@ -1,6 +1,5 @@ import sinon from 'sinon'; -import Store from '../../src/store'; import { sort, nextOrder } from '../../src/store/sort'; import Const from '../../src/const'; @@ -12,18 +11,17 @@ describe('Sort Function', () => { { id: 1, name: '!@#' } ]; - let store; - describe('sort', () => { - beforeEach(() => { - store = new Store('id'); - store.data = data; - }); + const sortColumn = { + dataField: 'id', + text: 'ID' + }; + let sortOrder; + let result; it('should sort array with ASC order correctly', () => { - store.sortField = 'id'; - store.sortOrder = Const.SORT_ASC; - const result = sort(store)(); + sortOrder = Const.SORT_ASC; + result = sort(data, sortOrder, sortColumn); expect(result.length).toEqual(data.length); const sortedArray = data.map(e => e.id).sort((a, b) => a - b); @@ -33,9 +31,8 @@ describe('Sort Function', () => { }); it('should sort array with DESC order correctly', () => { - store.sortField = 'id'; - store.sortOrder = Const.SORT_DESC; - const result = sort(store)(); + sortOrder = Const.SORT_DESC; + result = sort(data, sortOrder, sortColumn); expect(result.length).toEqual(data.length); const sortedArray = data.map(e => e.id).sort((a, b) => b - a); @@ -46,35 +43,49 @@ describe('Sort Function', () => { it('should call custom sort function when sortFunc given', () => { const sortFunc = sinon.stub().returns(1); - store.sortField = 'id'; - store.sortOrder = Const.SORT_DESC; - sort(store)(sortFunc); + sortOrder = Const.SORT_DESC; + sort(data, sortOrder, { ...sortColumn, sortFunc }); expect(sortFunc.callCount).toBe(6); }); }); describe('nextOrder', () => { - beforeEach(() => { - store = new Store('id'); - store.data = data; + const currentSortColumn = { + dataField: 'name', + text: 'Product Name' + }; + it('should return correcly order when current sortField is not eq next sort field', () => { + const nextSort = { + sortColumn: { + dataField: 'id', + text: 'ID' + }, + sortOrder: Const.SORT_DESC + }; + expect(nextOrder(currentSortColumn, nextSort)).toBe(Const.SORT_DESC); }); - it('should return correcly order when store.sortField is not eq next sort field', () => { - expect(nextOrder(store)('name')).toBe(Const.SORT_DESC); + it('should return correcly order if even next sort column is undefined', () => { + expect(nextOrder(currentSortColumn, {})).toBe(Const.SORT_DESC); }); - it('should return correcly order when store.sortField is not eq next sort field and default sort direction is given', () => { - expect(nextOrder(store)('name', undefined, Const.SORT_ASC)).toBe(Const.SORT_ASC); + it('should return correcly order when current sortField is not eq next sort field and default sort direction is given', () => { + const nextSort = { + sortColumn: { + dataField: 'id', + text: 'ID' + }, + sortOrder: Const.SORT_DESC + }; + expect(nextOrder(currentSortColumn, nextSort, Const.SORT_ASC)).toBe(Const.SORT_ASC); }); - it('should return correcly order when store.sortField is eq next sort field', () => { - store.sortField = 'name'; - store.sortOrder = Const.SORT_DESC; - expect(nextOrder(store)('name')).toBe(Const.SORT_ASC); - }); - - it('should return correcly order when order is specified', () => { - expect(nextOrder(store)('name', Const.SORT_ASC)).toBe(Const.SORT_ASC); + it('should return correcly order when current sortField is eq next sort field', () => { + const nextSort = { + sortColumn: currentSortColumn, + sortOrder: Const.SORT_ASC + }; + expect(nextOrder(currentSortColumn, nextSort)).toBe(Const.SORT_DESC); }); }); }); diff --git a/packages/react-bootstrap-table2/test/test-helpers/mock-component.js b/packages/react-bootstrap-table2/test/test-helpers/mock-component.js index c3282a4..cb1d5b5 100644 --- a/packages/react-bootstrap-table2/test/test-helpers/mock-component.js +++ b/packages/react-bootstrap-table2/test/test-helpers/mock-component.js @@ -1,16 +1,5 @@ -import Store from '../../src/store'; export const extendTo = Base => class MockComponent extends Base { - constructor(props) { - super(props); - - const { data } = props; - - this.store = new Store(props.keyField); - this.store.data = data; - this.state = { data }; - } - render() { return null; } }; From 167352f199adb0bb37c2d1c09ee8187997b0bb37 Mon Sep 17 00:00:00 2001 From: AllenFang Date: Wed, 30 May 2018 17:50:05 +0800 Subject: [PATCH 19/55] fix selectRow doesnt pass to CellEditContext --- packages/react-bootstrap-table2-editor/src/context.js | 2 ++ packages/react-bootstrap-table2/src/contexts/index.js | 1 + 2 files changed, 3 insertions(+) diff --git a/packages/react-bootstrap-table2-editor/src/context.js b/packages/react-bootstrap-table2-editor/src/context.js index da0930d..fb12068 100644 --- a/packages/react-bootstrap-table2-editor/src/context.js +++ b/packages/react-bootstrap-table2-editor/src/context.js @@ -16,6 +16,7 @@ export default ( class CellEditProvider extends React.Component { static propTypes = { data: PropTypes.array.isRequired, + selectRow: PropTypes.object, options: PropTypes.shape({ mode: PropTypes.oneOf([CLICK_TO_CELL_EDIT, DBCLICK_TO_CELL_EDIT]).isRequired, onErrorMessageDisappear: PropTypes.func, @@ -101,6 +102,7 @@ export default ( cellEdit: { options: { nonEditableRows, errorMessage, ...optionsRest }, editingCellFactory, + createContext, ...cellEditRest } } = this.props; diff --git a/packages/react-bootstrap-table2/src/contexts/index.js b/packages/react-bootstrap-table2/src/contexts/index.js index 796efff..a7e80f5 100644 --- a/packages/react-bootstrap-table2/src/contexts/index.js +++ b/packages/react-bootstrap-table2/src/contexts/index.js @@ -170,6 +170,7 @@ const withContext = Base => return rootProps => ( From c13b3fa1975ec8ee6b770f052b823545ae6adb27 Mon Sep 17 00:00:00 2001 From: AllenFang Date: Sat, 2 Jun 2018 13:02:10 +0800 Subject: [PATCH 20/55] patch test for editor, filter, pagination --- .../test/wrapper.test.js | 330 -------- .../test/context.test.js | 197 +++++ .../test/filter.test.js | 59 +- .../test/wrapper.test.js | 252 ------ .../test/context.test.js | 769 ++++++++++++++++++ .../test/page.test.js | 28 +- .../test/wrapper.test.js | 570 ------------- 7 files changed, 995 insertions(+), 1210 deletions(-) delete mode 100644 packages/react-bootstrap-table2-editor/test/wrapper.test.js create mode 100644 packages/react-bootstrap-table2-filter/test/context.test.js delete mode 100644 packages/react-bootstrap-table2-filter/test/wrapper.test.js create mode 100644 packages/react-bootstrap-table2-paginator/test/context.test.js delete mode 100644 packages/react-bootstrap-table2-paginator/test/wrapper.test.js diff --git a/packages/react-bootstrap-table2-editor/test/wrapper.test.js b/packages/react-bootstrap-table2-editor/test/wrapper.test.js deleted file mode 100644 index 8264ca8..0000000 --- a/packages/react-bootstrap-table2-editor/test/wrapper.test.js +++ /dev/null @@ -1,330 +0,0 @@ -import React from 'react'; -import sinon from 'sinon'; -import { shallow } from 'enzyme'; - -import _ from 'react-bootstrap-table-next/src/utils'; -import remoteResolver from 'react-bootstrap-table-next/src/props-resolver/remote-resolver'; -import Store from 'react-bootstrap-table-next/src/store'; -import BootstrapTable from 'react-bootstrap-table-next/src/bootstrap-table'; -import cellEditFactory from '..'; -import * as Const from '../src/const'; -import wrapperFactory from '../src/wrapper'; - -describe('CellEditWrapper', () => { - let wrapper; - let instance; - const onTableChangeCB = sinon.stub(); - const columns = [{ - dataField: 'id', - text: 'ID' - }, { - dataField: 'name', - text: 'Name' - }]; - const data = [{ - id: 1, - name: 'A' - }, { - id: 2, - name: 'B' - }]; - - const createTableProps = (props = {}) => { - const { cellEdit, ...rest } = props; - const tableProps = { - keyField: 'id', - columns, - data, - _, - store: new Store('id'), - cellEdit: cellEditFactory(cellEdit), - onTableChange: onTableChangeCB, - ...rest - }; - tableProps.store.data = data; - return tableProps; - }; - - const CellEditWrapper = wrapperFactory(BootstrapTable, { - _, - remoteResolver - }); - - const createCellEditWrapper = (props, renderFragment = true) => { - wrapper = shallow(); - instance = wrapper.instance(); - if (renderFragment) { - const fragment = instance.render(); - wrapper = shallow(
{ fragment }
); - } - }; - - afterEach(() => { - onTableChangeCB.reset(); - }); - - beforeEach(() => { - const props = createTableProps({ - cellEdit: { mode: Const.CLICK_TO_CELL_EDIT } - }); - createCellEditWrapper(props); - }); - - it('should render CellEditWrapper correctly', () => { - expect(wrapper.length).toBe(1); - expect(wrapper.find(BootstrapTable)).toBeDefined(); - }); - - it('should have correct state', () => { - expect(instance.state.ridx).toBeNull(); - expect(instance.state.cidx).toBeNull(); - expect(instance.state.message).toBeNull(); - expect(instance.state.isDataChanged).toBeFalsy(); - }); - - it('should inject correct props to base component', () => { - const base = wrapper.find(BootstrapTable); - expect(base.props().cellEdit).toBeDefined(); - expect(base.props().cellEdit.onStart).toBeDefined(); - expect(base.props().cellEdit.onEscape).toBeDefined(); - expect(base.props().cellEdit.onUpdate).toBeDefined(); - expect(base.props().cellEdit.EditingCell).toBeDefined(); - expect(base.props().cellEdit.ridx).toBeNull(); - expect(base.props().cellEdit.cidx).toBeNull(); - expect(base.props().cellEdit.message).toBeNull(); - expect(base.props().isDataChanged).toBe(instance.state.isDataChanged); - }); - - describe('when receive new cellEdit prop', () => { - const spy = jest.spyOn(CellEditWrapper.prototype, 'escapeEditing'); - - describe('and cellEdit is not work on remote', () => { - beforeEach(() => { - const props = createTableProps({ - cellEdit: { mode: Const.CLICK_TO_CELL_EDIT } - }); - createCellEditWrapper(props); - wrapper.setProps({ cellEdit: props.cellEdit }); - }); - - it('should always setting state.isDataChanged as false', () => { - expect(instance.state.isDataChanged).toBeFalsy(); - }); - }); - - describe('and cellEdit is work on remote', () => { - let errorMessage; - let props; - beforeEach(() => { - props = createTableProps({ - cellEdit: { mode: Const.CLICK_TO_CELL_EDIT }, - remote: true - }); - }); - - describe('and cellEdit.errorMessage is defined', () => { - beforeEach(() => { - createCellEditWrapper(props, false); - errorMessage = 'test'; - const newCellEdit = { - ...props.cellEdit, - options: { ...props.cellEdit.options, errorMessage } - }; - wrapper.setProps({ cellEdit: newCellEdit }); - }); - - it('should setting correct state', () => { - expect(instance.state.isDataChanged).toBeFalsy(); - expect(instance.state.message).toEqual(errorMessage); - }); - }); - - describe('and cellEdit.errorMessage is undefined', () => { - beforeEach(() => { - errorMessage = null; - createCellEditWrapper(props, false); - const newCellEdit = { - ...props.cellEdit, - options: { ...props.cellEdit.options, errorMessage } - }; - wrapper.setProps({ cellEdit: newCellEdit }); - }); - - it('should setting correct state', () => { - expect(wrapper.state().isDataChanged).toBeTruthy(); - }); - - it('should escape current editing', () => { - expect(spy).toHaveBeenCalled(); - }); - }); - }); - }); - - describe('call escapeEditing function', () => { - it('should set state correctly', () => { - instance.escapeEditing(); - expect(instance.state.ridx).toBeNull(); - expect(instance.state.cidx).toBeNull(); - }); - }); - - describe('call startEditing function', () => { - const ridx = 1; - const cidx = 3; - - it('should set state correctly', () => { - instance.startEditing(ridx, cidx); - expect(instance.state.ridx).toEqual(ridx); - expect(instance.state.cidx).toEqual(cidx); - expect(instance.state.isDataChanged).toBeFalsy(); - }); - - describe('if selectRow.clickToSelect is defined', () => { - beforeEach(() => { - const selectRow = { mode: 'checkbox', clickToSelect: true }; - const props = createTableProps({ - cellEdit: { mode: Const.CLICK_TO_CELL_EDIT }, - selectRow - }); - createCellEditWrapper(props); - }); - - it('should not set state', () => { - instance.startEditing(ridx, cidx); - expect(instance.state.ridx).toBeNull(); - expect(instance.state.cidx).toBeDefined(); - }); - }); - - describe('if selectRow.clickToSelect and selectRow.clickToEdit is defined', () => { - beforeEach(() => { - const selectRow = { mode: 'checkbox', clickToSelect: true, clickToEdit: true }; - const props = createTableProps({ - cellEdit: { mode: Const.CLICK_TO_CELL_EDIT }, - selectRow - }); - createCellEditWrapper(props); - }); - - it('should set state correctly', () => { - instance.startEditing(ridx, cidx); - expect(instance.state.ridx).toEqual(ridx); - expect(instance.state.cidx).toEqual(cidx); - }); - }); - }); - - describe('call completeEditing function', () => { - it('should set state correctly', () => { - instance.completeEditing(); - expect(instance.state.ridx).toBeNull(); - expect(instance.state.cidx).toBeNull(); - expect(instance.state.message).toBeNull(); - expect(instance.state.isDataChanged).toBeTruthy(); - }); - }); - - describe('call handleCellUpdate function', () => { - let props; - const row = data[0]; - const column = columns[1]; - const newValue = 'new name'; - - describe('when cell edit is work on remote', () => { - const spy = jest.spyOn(CellEditWrapper.prototype, 'handleCellChange'); - - beforeEach(() => { - props = createTableProps({ - cellEdit: { mode: Const.CLICK_TO_CELL_EDIT }, - remote: true - }); - createCellEditWrapper(props); - instance.handleCellUpdate(row, column, newValue); - }); - - it('should calling handleCellChange correctly', () => { - expect(spy).toHaveBeenCalled(); - expect(spy.mock.calls).toHaveLength(1); - expect(spy.mock.calls[0]).toHaveLength(3); - expect(spy.mock.calls[0][0]).toEqual(row.id); - expect(spy.mock.calls[0][1]).toEqual(column.dataField); - expect(spy.mock.calls[0][2]).toEqual(newValue); - }); - }); - - describe('when cell edit is not work on remote', () => { - const spyOnCompleteEditing = jest.spyOn(CellEditWrapper.prototype, 'completeEditing'); - const spyOnStoreEdit = jest.spyOn(Store.prototype, 'edit'); - - beforeEach(() => { - props = createTableProps({ - cellEdit: { mode: Const.CLICK_TO_CELL_EDIT } - }); - createCellEditWrapper(props); - instance.handleCellUpdate(row, column, newValue); - }); - - afterEach(() => { - spyOnStoreEdit.mockReset(); - spyOnCompleteEditing.mockReset(); - }); - - it('should calling props.store.edit', () => { - expect(spyOnStoreEdit).toHaveBeenCalled(); - expect(spyOnStoreEdit.mock.calls).toHaveLength(1); - expect(spyOnStoreEdit.mock.calls[0]).toHaveLength(3); - expect(spyOnStoreEdit.mock.calls[0][0]).toEqual(row.id); - expect(spyOnStoreEdit.mock.calls[0][1]).toEqual(column.dataField); - expect(spyOnStoreEdit.mock.calls[0][2]).toEqual(newValue); - }); - - it('should calling completeEditing function', () => { - expect(spyOnCompleteEditing).toHaveBeenCalled(); - }); - - describe('if cellEdit.afterSaveCell prop defined', () => { - const aftereSaveCellCallBack = sinon.stub(); - - beforeEach(() => { - props = createTableProps({ - cellEdit: { - mode: Const.CLICK_TO_CELL_EDIT, - afterSaveCell: aftereSaveCellCallBack - } - }); - createCellEditWrapper(props); - instance.handleCellUpdate(row, column, newValue); - }); - - it('should calling cellEdit.afterSaveCell correctly', () => { - expect(aftereSaveCellCallBack.callCount).toBe(1); - expect(aftereSaveCellCallBack.calledWith( - row[column.dataField], newValue, row, column) - ).toBe(true); - }); - }); - }); - - describe('if cellEdit.beforeSaveCell prop defined', () => { - const beforeSaveCellCallBack = sinon.stub(); - beforeEach(() => { - props = createTableProps({ - cellEdit: { - mode: Const.CLICK_TO_CELL_EDIT, - beforeSaveCell: beforeSaveCellCallBack - } - }); - createCellEditWrapper(props); - instance.handleCellUpdate(row, column, newValue); - }); - - it('should calling cellEdit.beforeSaveCell correctly', () => { - expect(beforeSaveCellCallBack.callCount).toBe(1); - expect(beforeSaveCellCallBack.calledWith( - row[column.dataField], newValue, row, column) - ).toBe(true); - }); - }); - }); -}); diff --git a/packages/react-bootstrap-table2-filter/test/context.test.js b/packages/react-bootstrap-table2-filter/test/context.test.js new file mode 100644 index 0000000..4dd03d5 --- /dev/null +++ b/packages/react-bootstrap-table2-filter/test/context.test.js @@ -0,0 +1,197 @@ +import 'jsdom-global/register'; +import React from 'react'; +import { shallow } from 'enzyme'; +import _ from 'react-bootstrap-table-next/src/utils'; +import BootstrapTable from 'react-bootstrap-table-next/src/bootstrap-table'; + +import { + FILTER_TYPE +} from '../src/const'; +import createFilterContext from '../src/context'; +import { textFilter } from '../index'; + +describe('FilterContext', () => { + let wrapper; + // let filter; + let FilterContext; + + const data = [{ + id: 1, + name: 'A' + }, { + id: 2, + name: 'B' + }]; + + const columns = [{ + dataField: 'id', + text: 'ID', + filter: textFilter() + }, { + dataField: 'name', + text: 'Name', + filter: textFilter() + }]; + + // const defaultFilter = {}; + + const mockBase = jest.fn((props => ( + + ))); + + const handleFilterChange = jest.fn(); + + function shallowContext( + // customFilter = defaultFilter, + enableRemote = false + ) { + mockBase.mockReset(); + handleFilterChange.mockReset(); + FilterContext = createFilterContext( + _, + jest.fn().mockReturnValue(enableRemote), + handleFilterChange + ); + + return ( + + + { + filterProps => mockBase(filterProps) + } + + + ); + } + + describe('default render', () => { + beforeEach(() => { + wrapper = shallow(shallowContext()); + wrapper.render(); + }); + + it('should have correct Provider property after calling createFilterContext', () => { + expect(FilterContext.Provider).toBeDefined(); + }); + + it('should have correct Consumer property after calling createFilterContext', () => { + expect(FilterContext.Consumer).toBeDefined(); + }); + + it('should have correct currFilters', () => { + expect(wrapper.instance().currFilters).toEqual({}); + }); + + it('should pass correct cell editing props to children element', () => { + expect(wrapper.length).toBe(1); + expect(mockBase).toHaveBeenCalledWith({ + data, + onFilter: wrapper.instance().onFilter + }); + }); + }); + + describe('when remote filter is enable', () => { + beforeEach(() => { + wrapper = shallow(shallowContext(true)); + wrapper.render(); + wrapper.instance().currFilters = { price: { filterVal: 20, filterType: FILTER_TYPE.TEXT } }; + }); + + it('should pass original data without internal filtering', () => { + expect(wrapper.length).toBe(1); + expect(mockBase).toHaveBeenCalledWith({ + data, + onFilter: wrapper.instance().onFilter + }); + }); + }); + + describe('onFilter', () => { + let instance; + describe('when filterVal is empty or undefined', () => { + const filterVals = ['', undefined, []]; + + beforeEach(() => { + wrapper = shallow(shallowContext()); + wrapper.render(); + instance = wrapper.instance(); + }); + + it('should correct currFilters', () => { + filterVals.forEach((filterVal) => { + instance.onFilter(columns[1], FILTER_TYPE.TEXT)(filterVal); + expect(Object.keys(instance.currFilters)).toHaveLength(0); + }); + }); + }); + + describe('when filterVal is existing', () => { + const filterVal = '3'; + + beforeEach(() => { + wrapper = shallow(shallowContext()); + wrapper.render(); + instance = wrapper.instance(); + }); + + it('should correct currFilters', () => { + instance.onFilter(columns[1], FILTER_TYPE.TEXT)(filterVal); + expect(Object.keys(instance.currFilters)).toHaveLength(1); + }); + }); + + describe('when remote filter is enabled', () => { + const filterVal = '3'; + + beforeEach(() => { + wrapper = shallow(shallowContext(true)); + wrapper.render(); + instance = wrapper.instance(); + instance.onFilter(columns[1], FILTER_TYPE.TEXT)(filterVal); + }); + + it('should correct currFilters', () => { + expect(Object.keys(instance.currFilters)).toHaveLength(1); + }); + + it('should calling handleFilterChange correctly', () => { + expect(handleFilterChange).toHaveBeenCalledTimes(1); + expect(handleFilterChange).toHaveBeenCalledWith(instance.currFilters); + }); + }); + + describe('combination', () => { + beforeEach(() => { + wrapper = shallow(shallowContext()); + wrapper.render(); + instance = wrapper.instance(); + }); + + it('should set correct currFilters', () => { + instance.onFilter(columns[0], FILTER_TYPE.TEXT)('3'); + expect(Object.keys(instance.currFilters)).toHaveLength(1); + + instance.onFilter(columns[0], FILTER_TYPE.TEXT)('2'); + expect(Object.keys(instance.currFilters)).toHaveLength(1); + + instance.onFilter(columns[1], FILTER_TYPE.TEXT)('2'); + expect(Object.keys(instance.currFilters)).toHaveLength(2); + + instance.onFilter(columns[1], FILTER_TYPE.TEXT)(''); + expect(Object.keys(instance.currFilters)).toHaveLength(1); + + instance.onFilter(columns[0], FILTER_TYPE.TEXT)(''); + expect(Object.keys(instance.currFilters)).toHaveLength(0); + }); + }); + }); +}); diff --git a/packages/react-bootstrap-table2-filter/test/filter.test.js b/packages/react-bootstrap-table2-filter/test/filter.test.js index 79d0407..32956e4 100644 --- a/packages/react-bootstrap-table2-filter/test/filter.test.js +++ b/packages/react-bootstrap-table2-filter/test/filter.test.js @@ -1,5 +1,4 @@ import _ from 'react-bootstrap-table-next/src/utils'; -import Store from 'react-bootstrap-table-next/src/store'; import { filters } from '../src/filter'; import { FILTER_TYPE } from '../src/const'; @@ -16,14 +15,10 @@ for (let i = 0; i < 20; i += 1) { } describe('filter', () => { - let store; - let filterFn; let currFilters; let columns; beforeEach(() => { - store = new Store('id'); - store.data = data; currFilters = {}; columns = [{ dataField: 'id', @@ -41,10 +36,6 @@ describe('filter', () => { }); describe('filterByText', () => { - beforeEach(() => { - filterFn = filters(store, columns, _); - }); - describe('when filter value is not a String', () => { it('should transform to string and do the filter', () => { currFilters.name = { @@ -52,7 +43,7 @@ describe('filter', () => { filterType: FILTER_TYPE.TEXT }; - const result = filterFn(currFilters); + const result = filters(data, columns, _)(currFilters); expect(result).toBeDefined(); expect(result).toHaveLength(2); }); @@ -65,7 +56,7 @@ describe('filter', () => { filterType: FILTER_TYPE.TEXT }; - const result = filterFn(currFilters); + const result = filters(data, columns, _)(currFilters); expect(result).toBeDefined(); expect(result).toHaveLength(2); }); @@ -79,7 +70,7 @@ describe('filter', () => { filterType: FILTER_TYPE.TEXT }; - const result = filterFn(currFilters); + const result = filters(data, columns, _)(currFilters); expect(result).toBeDefined(); expect(result).toHaveLength(0); }); @@ -93,7 +84,7 @@ describe('filter', () => { comparator: EQ }; - const result = filterFn(currFilters); + const result = filters(data, columns, _)(currFilters); expect(result).toBeDefined(); expect(result).toHaveLength(1); }); @@ -102,7 +93,6 @@ describe('filter', () => { describe('column.filterValue is defined', () => { beforeEach(() => { columns[1].filterValue = jest.fn(); - filterFn = filters(store, columns, _); }); it('should calling custom filterValue callback correctly', () => { @@ -111,7 +101,7 @@ describe('filter', () => { filterType: FILTER_TYPE.TEXT }; - const result = filterFn(currFilters); + const result = filters(data, columns, _)(currFilters); expect(result).toBeDefined(); expect(columns[1].filterValue).toHaveBeenCalledTimes(data.length); // const calls = columns[1].filterValue.mock.calls; @@ -124,10 +114,6 @@ describe('filter', () => { }); describe('filterByArray', () => { - beforeEach(() => { - filterFn = filters(store, columns, _); - }); - describe('when filter value is empty array', () => { it('should return original data', () => { currFilters.name = { @@ -135,9 +121,9 @@ describe('filter', () => { filterType: FILTER_TYPE.MULTISELECT }; - const result = filterFn(currFilters); + const result = filters(data, columns, _)(currFilters); expect(result).toBeDefined(); - expect(result).toHaveLength(store.data.length); + expect(result).toHaveLength(data.length); }); }); @@ -150,7 +136,7 @@ describe('filter', () => { comparator: EQ }; - const result = filterFn(currFilters); + const result = filters(data, columns, _)(currFilters); expect(result).toBeDefined(); expect(result).toHaveLength(2); }); @@ -164,7 +150,7 @@ describe('filter', () => { comparator: LIKE }; - const result = filterFn(currFilters); + const result = filters(data, columns, _)(currFilters); expect(result).toBeDefined(); expect(result).toHaveLength(3); }); @@ -173,10 +159,6 @@ describe('filter', () => { }); describe('filterByNumber', () => { - beforeEach(() => { - filterFn = filters(store, columns, _); - }); - describe('when currFilters.filterVal.comparator is empty', () => { it('should returning correct result', () => { currFilters.price = { @@ -184,11 +166,11 @@ describe('filter', () => { filterType: FILTER_TYPE.NUMBER }; - let result = filterFn(currFilters); + let result = filters(data, columns, _)(currFilters); expect(result).toHaveLength(data.length); currFilters.price.filterVal.comparator = undefined; - result = filterFn(currFilters); + result = filters(result, columns, _)(currFilters); expect(result).toHaveLength(data.length); }); }); @@ -200,7 +182,7 @@ describe('filter', () => { filterType: FILTER_TYPE.NUMBER }; - const result = filterFn(currFilters); + const result = filters(data, columns, _)(currFilters); expect(result).toHaveLength(data.length); }); }); @@ -212,11 +194,11 @@ describe('filter', () => { filterType: FILTER_TYPE.NUMBER }; - let result = filterFn(currFilters); + let result = filters(data, columns, _)(currFilters); expect(result).toHaveLength(1); currFilters.price.filterVal.number = '0'; - result = filterFn(currFilters); + result = filters(result, columns, _)(currFilters); expect(result).toHaveLength(0); }); }); @@ -228,7 +210,7 @@ describe('filter', () => { filterType: FILTER_TYPE.NUMBER }; - const result = filterFn(currFilters); + const result = filters(data, columns, _)(currFilters); expect(result).toHaveLength(16); }); }); @@ -240,7 +222,7 @@ describe('filter', () => { filterType: FILTER_TYPE.NUMBER }; - const result = filterFn(currFilters); + const result = filters(data, columns, _)(currFilters); expect(result).toHaveLength(17); }); }); @@ -252,7 +234,7 @@ describe('filter', () => { filterType: FILTER_TYPE.NUMBER }; - const result = filterFn(currFilters); + const result = filters(data, columns, _)(currFilters); expect(result).toHaveLength(3); }); }); @@ -264,7 +246,7 @@ describe('filter', () => { filterType: FILTER_TYPE.NUMBER }; - const result = filterFn(currFilters); + const result = filters(data, columns, _)(currFilters); expect(result).toHaveLength(4); }); }); @@ -276,15 +258,16 @@ describe('filter', () => { filterType: FILTER_TYPE.NUMBER }; - const result = filterFn(currFilters); + const result = filters(data, columns, _)(currFilters); expect(result).toHaveLength(19); }); }); }); describe('filterByDate', () => { + let filterFn; beforeEach(() => { - filterFn = filters(store, columns, _); + filterFn = filters(data, columns, _); }); describe('when currFilters.filterVal.comparator is empty', () => { diff --git a/packages/react-bootstrap-table2-filter/test/wrapper.test.js b/packages/react-bootstrap-table2-filter/test/wrapper.test.js deleted file mode 100644 index 751fc2e..0000000 --- a/packages/react-bootstrap-table2-filter/test/wrapper.test.js +++ /dev/null @@ -1,252 +0,0 @@ -import React from 'react'; -import sinon from 'sinon'; -import { shallow } from 'enzyme'; - -import _ from 'react-bootstrap-table-next/src/utils'; -import remoteResolver from 'react-bootstrap-table-next/src/props-resolver/remote-resolver'; -import BootstrapTable from 'react-bootstrap-table-next/src/bootstrap-table'; -import Store from 'react-bootstrap-table-next/src/store'; -import filter, { textFilter } from '..'; -import wrapperFactory from '../src/wrapper'; -import { FILTER_TYPE } from '../src/const'; - -const data = []; -for (let i = 0; i < 20; i += 1) { - data.push({ - id: i, - name: `itme name ${i}`, - price: 200 + i - }); -} - -describe('Wrapper', () => { - let wrapper; - let instance; - const onTableChangeCB = sinon.stub(); - - afterEach(() => { - onTableChangeCB.reset(); - }); - - const createTableProps = (props) => { - const tableProps = { - keyField: 'id', - columns: [{ - dataField: 'id', - text: 'ID' - }, { - dataField: 'name', - text: 'Name', - filter: textFilter() - }, { - dataField: 'price', - text: 'Price', - filter: textFilter() - }], - data, - filter: filter(), - _, - store: new Store('id'), - onTableChange: onTableChangeCB, - ...props - }; - tableProps.store.data = data; - return tableProps; - }; - - const FilterWrapper = wrapperFactory(BootstrapTable, { - _, - remoteResolver - }); - - const createFilterWrapper = (props, renderFragment = true) => { - wrapper = shallow(); - instance = wrapper.instance(); - if (renderFragment) { - const fragment = instance.render(); - wrapper = shallow(
{ fragment }
); - } - }; - - describe('default filter wrapper', () => { - const props = createTableProps(); - - beforeEach(() => { - createFilterWrapper(props); - }); - - it('should rendering correctly', () => { - expect(wrapper.length).toBe(1); - }); - - it('should initializing state correctly', () => { - expect(instance.state.isDataChanged).toBeFalsy(); - expect(instance.state.currFilters).toEqual({}); - }); - - it('should rendering BootstraTable correctly', () => { - const table = wrapper.find(BootstrapTable); - expect(table.length).toBe(1); - expect(table.prop('onFilter')).toBeDefined(); - expect(table.prop('isDataChanged')).toEqual(instance.state.isDataChanged); - }); - }); - - describe('componentWillReceiveProps', () => { - let nextProps; - - describe('when props.store.filters is same as current state.currFilters', () => { - beforeEach(() => { - nextProps = createTableProps(); - instance.componentWillReceiveProps(nextProps); - }); - - it('should setting isDataChanged as false (Temporary solution)', () => { - expect(instance.state.isDataChanged).toBeFalsy(); - }); - }); - - describe('when props.isDataChanged is true', () => { - beforeEach(() => { - nextProps = createTableProps({ isDataChanged: true }); - instance.componentWillReceiveProps(nextProps); - }); - - it('should setting isDataChanged as true', () => { - expect(instance.state.isDataChanged).toBeTruthy(); - }); - }); - - describe('when props.store.filters is different from current state.currFilters', () => { - const nextData = []; - - beforeEach(() => { - nextProps = createTableProps(); - nextProps.store.filters = { price: { filterVal: 20, filterType: FILTER_TYPE.TEXT } }; - nextProps.store.setAllData(nextData); - instance.componentWillReceiveProps(nextProps); - }); - - it('should setting states correctly', () => { - expect(nextProps.store.filteredData).toEqual(nextData); - expect(instance.state.isDataChanged).toBeTruthy(); - expect(instance.state.currFilters).toBe(nextProps.store.filters); - }); - }); - - describe('when remote filter is enabled', () => { - let props; - const nextData = []; - - beforeEach(() => { - props = createTableProps({ remote: { filter: true } }); - createFilterWrapper(props); - nextProps = createTableProps({ remote: { filter: true } }); - nextProps.store.setAllData(nextData); - instance.componentWillReceiveProps(nextProps); - }); - - it('should setting states correctly', () => { - expect(nextProps.store.filteredData).toEqual(nextData); - expect(instance.state.isDataChanged).toBeTruthy(); - expect(instance.state.currFilters).toBe(nextProps.store.filters); - }); - }); - }); - - describe('onFilter', () => { - let props; - - beforeEach(() => { - props = createTableProps(); - createFilterWrapper(props); - }); - - describe('when filterVal is empty or undefined', () => { - const filterVals = ['', undefined, []]; - - it('should setting store object correctly', () => { - filterVals.forEach((filterVal) => { - instance.onFilter(props.columns[1], FILTER_TYPE.TEXT)(filterVal); - expect(props.store.filtering).toBeFalsy(); - }); - }); - - it('should setting state correctly', () => { - filterVals.forEach((filterVal) => { - instance.onFilter(props.columns[1], FILTER_TYPE.TEXT)(filterVal); - expect(instance.state.isDataChanged).toBeTruthy(); - expect(Object.keys(instance.state.currFilters)).toHaveLength(0); - }); - }); - }); - - describe('when filterVal is existing', () => { - const filterVal = '3'; - - it('should setting store object correctly', () => { - instance.onFilter(props.columns[1], FILTER_TYPE.TEXT)(filterVal); - expect(props.store.filters).toEqual(instance.state.currFilters); - }); - - it('should setting state correctly', () => { - instance.onFilter(props.columns[1], FILTER_TYPE.TEXT)(filterVal); - expect(instance.state.isDataChanged).toBeTruthy(); - expect(Object.keys(instance.state.currFilters)).toHaveLength(1); - }); - }); - - describe('when remote filter is enabled', () => { - const filterVal = '3'; - - beforeEach(() => { - props = createTableProps(); - props.remote = { filter: true }; - createFilterWrapper(props); - instance.onFilter(props.columns[1], FILTER_TYPE.TEXT)(filterVal); - }); - - it('should not setting store object correctly', () => { - expect(props.store.filters).not.toEqual(instance.state.currFilters); - }); - - it('should not setting state', () => { - expect(instance.state.isDataChanged).toBeFalsy(); - expect(Object.keys(instance.state.currFilters)).toHaveLength(0); - }); - - it('should calling props.onRemoteFilterChange correctly', () => { - expect(onTableChangeCB.calledOnce).toBeTruthy(); - }); - }); - - describe('combination', () => { - it('should setting store object correctly', () => { - instance.onFilter(props.columns[1], FILTER_TYPE.TEXT)('3'); - expect(props.store.filters).toEqual(instance.state.currFilters); - expect(instance.state.isDataChanged).toBeTruthy(); - expect(Object.keys(instance.state.currFilters)).toHaveLength(1); - - instance.onFilter(props.columns[1], FILTER_TYPE.TEXT)('2'); - expect(props.store.filters).toEqual(instance.state.currFilters); - expect(instance.state.isDataChanged).toBeTruthy(); - expect(Object.keys(instance.state.currFilters)).toHaveLength(1); - - instance.onFilter(props.columns[2], FILTER_TYPE.TEXT)('2'); - expect(props.store.filters).toEqual(instance.state.currFilters); - expect(instance.state.isDataChanged).toBeTruthy(); - expect(Object.keys(instance.state.currFilters)).toHaveLength(2); - - instance.onFilter(props.columns[2], FILTER_TYPE.TEXT)(''); - expect(props.store.filters).toEqual(instance.state.currFilters); - expect(instance.state.isDataChanged).toBeTruthy(); - expect(Object.keys(instance.state.currFilters)).toHaveLength(1); - - instance.onFilter(props.columns[1], FILTER_TYPE.TEXT)(''); - expect(props.store.filters).toEqual(instance.state.currFilters); - expect(instance.state.isDataChanged).toBeTruthy(); - expect(Object.keys(instance.state.currFilters)).toHaveLength(0); - }); - }); - }); -}); diff --git a/packages/react-bootstrap-table2-paginator/test/context.test.js b/packages/react-bootstrap-table2-paginator/test/context.test.js new file mode 100644 index 0000000..10820b5 --- /dev/null +++ b/packages/react-bootstrap-table2-paginator/test/context.test.js @@ -0,0 +1,769 @@ +import 'jsdom-global/register'; +import React from 'react'; +import { shallow } from 'enzyme'; +import BootstrapTable from 'react-bootstrap-table-next/src/bootstrap-table'; + +import Pagination from '../src/pagination'; +import Const from '../src/const'; +import createPaginationContext from '../src/context'; +import paginationFactory from '../index'; + +const data = []; +for (let i = 0; i < 100; i += 1) { + data.push({ + id: i, + name: `itme name ${i}` + }); +} + +describe('PaginationContext', () => { + let wrapper; + let PaginationContext; + + const columns = [{ + dataField: 'id', + text: 'ID' + }, { + dataField: 'name', + text: 'Name' + }]; + + const defaultPagination = { options: {} }; + + const mockBase = jest.fn((props => ( + + ))); + + const handleRemotePaginationChange = jest.fn(); + + function shallowContext( + customPagination = defaultPagination, + enableRemote = false + ) { + mockBase.mockReset(); + handleRemotePaginationChange.mockReset(); + PaginationContext = createPaginationContext( + jest.fn().mockReturnValue(enableRemote), + handleRemotePaginationChange + ); + + return ( + + + { + paginationProps => mockBase(paginationProps) + } + + + ); + } + + describe('default render', () => { + beforeEach(() => { + wrapper = shallow(shallowContext()); + wrapper.render(); + }); + + it('should have correct Provider property after calling createPaginationContext', () => { + expect(PaginationContext.Provider).toBeDefined(); + }); + + it('should have correct Consumer property after calling createPaginationContext', () => { + expect(PaginationContext.Consumer).toBeDefined(); + }); + + it('should have correct currPage', () => { + expect(wrapper.instance().currPage).toEqual(Const.PAGE_START_INDEX); + }); + + it('should have correct currSizePerPage', () => { + expect(wrapper.instance().currSizePerPage).toEqual(Const.SIZE_PER_PAGE_LIST[0]); + }); + + it('should render Pagination component correctly', () => { + expect(wrapper.length).toBe(1); + const instance = wrapper.instance(); + const pagination = wrapper.find(Pagination); + expect(pagination).toHaveLength(1); + expect(pagination.prop('dataSize')).toEqual(data.length); + expect(pagination.prop('currPage')).toEqual(instance.currPage); + expect(pagination.prop('currSizePerPage')).toEqual(instance.currSizePerPage); + expect(pagination.prop('onPageChange')).toEqual(instance.handleChangePage); + expect(pagination.prop('onSizePerPageChange')).toEqual(instance.handleChangeSizePerPage); + expect(pagination.prop('sizePerPageList')).toEqual(Const.SIZE_PER_PAGE_LIST); + expect(pagination.prop('paginationSize')).toEqual(Const.PAGINATION_SIZE); + expect(pagination.prop('pageStartIndex')).toEqual(Const.PAGE_START_INDEX); + expect(pagination.prop('withFirstAndLast')).toEqual(Const.With_FIRST_AND_LAST); + expect(pagination.prop('alwaysShowAllBtns')).toEqual(Const.SHOW_ALL_PAGE_BTNS); + expect(pagination.prop('firstPageText')).toEqual(Const.FIRST_PAGE_TEXT); + expect(pagination.prop('prePageText')).toEqual(Const.PRE_PAGE_TEXT); + expect(pagination.prop('nextPageText')).toEqual(Const.NEXT_PAGE_TEXT); + expect(pagination.prop('lastPageText')).toEqual(Const.LAST_PAGE_TEXT); + expect(pagination.prop('firstPageTitle')).toEqual(Const.FIRST_PAGE_TITLE); + expect(pagination.prop('prePageTitle')).toEqual(Const.PRE_PAGE_TITLE); + expect(pagination.prop('nextPageTitle')).toEqual(Const.NEXT_PAGE_TITLE); + expect(pagination.prop('lastPageTitle')).toEqual(Const.LAST_PAGE_TITLE); + expect(pagination.prop('hideSizePerPage')).toEqual(Const.HIDE_SIZE_PER_PAGE); + expect(pagination.prop('hideSizePerPage')).toEqual(Const.HIDE_SIZE_PER_PAGE); + expect(pagination.prop('paginationTotalRenderer')).toBeNull(); + }); + + it('should pass correct cell editing props to children element', () => { + expect(mockBase.mock.calls[0][0].data).toHaveLength(Const.SIZE_PER_PAGE_LIST[0]); + }); + }); + + describe('componentWillReceiveProps', () => { + let instance; + let nextProps; + + describe('when nextProps.pagination.options.page is existing', () => { + const onPageChange = jest.fn(); + afterEach(() => { + onPageChange.mockReset(); + }); + + describe('and if it is different with currPage', () => { + beforeEach(() => { + wrapper = shallow(shallowContext()); + instance = wrapper.instance(); + wrapper.render(); + nextProps = { + pagination: { + options: { + page: 2, + onPageChange + } + } + }; + instance.componentWillReceiveProps(nextProps); + }); + + it('should call options.onPageChange', () => { + expect(onPageChange).toHaveBeenCalledTimes(1); + expect(onPageChange).toHaveBeenCalledWith( + instance.currPage, + instance.currSizePerPage + ); + }); + + it('should set correct currPage', () => { + expect(instance.currPage).toEqual(nextProps.pagination.options.page); + }); + }); + + describe('and if it is same as currPage', () => { + beforeEach(() => { + wrapper = shallow(shallowContext()); + instance = wrapper.instance(); + wrapper.render(); + nextProps = { + pagination: { + options: { + page: 1, + onPageChange + } + } + }; + instance.componentWillReceiveProps(nextProps); + }); + + it('shouldn\'t call options.onPageChange', () => { + expect(onPageChange).not.toHaveBeenCalled(); + }); + + it('should have correct currPage', () => { + expect(instance.currPage).toEqual(nextProps.pagination.options.page); + }); + }); + }); + + describe('when nextProps.pagination.options.page is not existing', () => { + beforeEach(() => { + wrapper = shallow(shallowContext({ + ...defaultPagination, + page: 3 + })); + instance = wrapper.instance(); + wrapper.render(); + nextProps = { pagination: defaultPagination }; + instance.componentWillReceiveProps(nextProps); + }); + + it('should not set currPage', () => { + expect(instance.currPage).toEqual(3); + }); + }); + + describe('when nextProps.pagination.options.sizePerPage is existing', () => { + const onPageChange = jest.fn(); + afterEach(() => { + onPageChange.mockReset(); + }); + + describe('and if it is different with currSizePerPage', () => { + beforeEach(() => { + wrapper = shallow(shallowContext()); + instance = wrapper.instance(); + wrapper.render(); + nextProps = { + pagination: { + options: { + sizePerPage: Const.SIZE_PER_PAGE_LIST[2], + onPageChange + } + } + }; + instance.componentWillReceiveProps(nextProps); + }); + + it('should call options.onPageChange', () => { + expect(onPageChange).toHaveBeenCalledTimes(1); + expect(onPageChange).toHaveBeenCalledWith( + instance.currPage, + instance.currSizePerPage + ); + }); + + it('should set correct currSizePerPage', () => { + expect(instance.currSizePerPage).toEqual(nextProps.pagination.options.sizePerPage); + }); + }); + + describe('and if it is same as currSizePerPage', () => { + beforeEach(() => { + wrapper = shallow(shallowContext()); + instance = wrapper.instance(); + wrapper.render(); + nextProps = { + pagination: { + options: { + sizePerPage: Const.SIZE_PER_PAGE_LIST[0], + onPageChange + } + } + }; + instance.componentWillReceiveProps(nextProps); + }); + + it('shouldn\'t call options.onPageChange', () => { + expect(onPageChange).not.toHaveBeenCalled(); + }); + + it('should have correct currSizePerPage', () => { + expect(instance.currSizePerPage).toEqual(nextProps.pagination.options.sizePerPage); + }); + }); + }); + + describe('when nextProps.pagination.options.sizePerPage is not existing', () => { + beforeEach(() => { + wrapper = shallow(shallowContext({ + ...defaultPagination, + sizePerPage: Const.SIZE_PER_PAGE_LIST[2] + })); + instance = wrapper.instance(); + wrapper.render(); + nextProps = { pagination: defaultPagination }; + instance.componentWillReceiveProps(nextProps); + }); + + it('should not set currPage', () => { + expect(instance.currSizePerPage).toEqual(Const.SIZE_PER_PAGE_LIST[2]); + }); + }); + }); + + describe('handleChangePage', () => { + let instance; + const newPage = 3; + + describe('should update component correctly', () => { + beforeEach(() => { + wrapper = shallow(shallowContext()); + instance = wrapper.instance(); + jest.spyOn(instance, 'forceUpdate'); + instance.handleChangePage(newPage); + }); + + it('', () => { + expect(instance.currPage).toEqual(newPage); + expect(instance.forceUpdate).toHaveBeenCalledTimes(1); + }); + }); + + describe('if options.onPageChange is defined', () => { + const onPageChange = jest.fn(); + beforeEach(() => { + onPageChange.mockClear(); + wrapper = shallow(shallowContext({ + ...defaultPagination, + onPageChange + })); + instance = wrapper.instance(); + jest.spyOn(instance, 'forceUpdate'); + instance.handleChangePage(newPage); + }); + + it('should still update component correctly', () => { + expect(instance.currPage).toEqual(newPage); + expect(instance.forceUpdate).toHaveBeenCalledTimes(1); + }); + + it('should call options.onPageChange correctly', () => { + expect(onPageChange).toHaveBeenCalledTimes(1); + expect(onPageChange).toHaveBeenCalledWith(newPage, instance.currSizePerPage); + }); + }); + + describe('if remote pagination is enable', () => { + beforeEach(() => { + wrapper = shallow(shallowContext({ + ...defaultPagination + }, true)); + instance = wrapper.instance(); + jest.spyOn(instance, 'forceUpdate'); + instance.handleChangePage(newPage); + }); + + it('should still update component correctly', () => { + expect(instance.currPage).toEqual(newPage); + expect(instance.forceUpdate).toHaveBeenCalledTimes(0); + }); + + it('should call handleRemotePageChange correctly', () => { + expect(handleRemotePaginationChange).toHaveBeenCalledTimes(1); + expect(handleRemotePaginationChange) + .toHaveBeenCalledWith(newPage, instance.currSizePerPage); + }); + }); + }); + + describe('handleChangeSizePerPage', () => { + let instance; + const newPage = 2; + const newSizePerPage = 15; + + describe('should update component correctly', () => { + beforeEach(() => { + wrapper = shallow(shallowContext()); + instance = wrapper.instance(); + jest.spyOn(instance, 'forceUpdate'); + instance.handleChangeSizePerPage(newSizePerPage, newPage); + }); + + it('', () => { + expect(instance.currPage).toEqual(newPage); + expect(instance.currSizePerPage).toEqual(newSizePerPage); + expect(instance.forceUpdate).toHaveBeenCalledTimes(1); + }); + }); + + describe('if options.onSizePerPageChange is defined', () => { + const onSizePerPageChange = jest.fn(); + beforeEach(() => { + onSizePerPageChange.mockClear(); + wrapper = shallow(shallowContext({ + ...defaultPagination, + onSizePerPageChange + })); + instance = wrapper.instance(); + jest.spyOn(instance, 'forceUpdate'); + instance.handleChangeSizePerPage(newSizePerPage, newPage); + }); + + it('should still update component correctly', () => { + expect(instance.currPage).toEqual(newPage); + expect(instance.currSizePerPage).toEqual(newSizePerPage); + expect(instance.forceUpdate).toHaveBeenCalledTimes(1); + }); + + it('should call options.onSizePerPageChange correctly', () => { + expect(onSizePerPageChange).toHaveBeenCalledTimes(1); + expect(onSizePerPageChange).toHaveBeenCalledWith(newSizePerPage, newPage); + }); + }); + + describe('if remote pagination is enable', () => { + beforeEach(() => { + wrapper = shallow(shallowContext({ + ...defaultPagination + }, true)); + instance = wrapper.instance(); + jest.spyOn(instance, 'forceUpdate'); + instance.handleChangeSizePerPage(newSizePerPage, newPage); + }); + + it('should still update component correctly', () => { + expect(instance.currPage).toEqual(newPage); + expect(instance.currSizePerPage).toEqual(newSizePerPage); + expect(instance.forceUpdate).toHaveBeenCalledTimes(0); + }); + + it('should call handleRemotePageChange correctly', () => { + expect(handleRemotePaginationChange).toHaveBeenCalledTimes(1); + expect(handleRemotePaginationChange) + .toHaveBeenCalledWith(newPage, newSizePerPage); + }); + }); + }); + + describe('when options.page is defined', () => { + const page = 3; + + beforeEach(() => { + wrapper = shallow(shallowContext({ + ...defaultPagination, + page + })); + wrapper.render(); + }); + + it('should set correct currPage', () => { + expect(wrapper.instance().currPage).toEqual(page); + }); + + it('should rendering Pagination correctly', () => { + const pagination = wrapper.find(Pagination); + expect(pagination.length).toBe(1); + expect(pagination.prop('currPage')).toEqual(page); + }); + }); + + describe('when options.sizePerPage is defined', () => { + const sizePerPage = Const.SIZE_PER_PAGE_LIST[2]; + + beforeEach(() => { + wrapper = shallow(shallowContext({ + ...defaultPagination, + sizePerPage + })); + wrapper.render(); + }); + + it('should set correct currSizePerPage', () => { + expect(wrapper.instance().currSizePerPage).toEqual(sizePerPage); + }); + + it('should rendering Pagination correctly', () => { + const pagination = wrapper.find(Pagination); + expect(pagination.length).toBe(1); + expect(pagination.prop('currSizePerPage')).toEqual(sizePerPage); + }); + }); + + describe('when options.totalSize is defined', () => { + const totalSize = 100; + + beforeEach(() => { + wrapper = shallow(shallowContext({ + ...defaultPagination, + totalSize + })); + wrapper.render(); + }); + + it('should rendering Pagination correctly', () => { + const pagination = wrapper.find(Pagination); + expect(pagination.length).toBe(1); + expect(pagination.prop('dataSize')).toEqual(totalSize); + }); + }); + + describe('when options.showTotal is defined', () => { + const showTotal = true; + + beforeEach(() => { + wrapper = shallow(shallowContext({ + ...defaultPagination, + showTotal + })); + wrapper.render(); + }); + + it('should rendering Pagination correctly', () => { + const pagination = wrapper.find(Pagination); + expect(pagination.length).toBe(1); + expect(pagination.prop('showTotal')).toEqual(showTotal); + }); + }); + + describe('when options.pageStartIndex is defined', () => { + const pageStartIndex = -1; + + beforeEach(() => { + wrapper = shallow(shallowContext({ + ...defaultPagination, + pageStartIndex + })); + wrapper.render(); + }); + + it('should rendering Pagination correctly', () => { + const pagination = wrapper.find(Pagination); + expect(pagination.length).toBe(1); + expect(pagination.prop('pageStartIndex')).toEqual(pageStartIndex); + }); + }); + + describe('when options.sizePerPageList is defined', () => { + const sizePerPageList = [10, 40]; + + beforeEach(() => { + wrapper = shallow(shallowContext({ + ...defaultPagination, + sizePerPageList + })); + wrapper.render(); + }); + + it('should rendering Pagination correctly', () => { + const pagination = wrapper.find(Pagination); + expect(pagination.length).toBe(1); + expect(pagination.prop('sizePerPageList')).toEqual(sizePerPageList); + }); + }); + + describe('when options.paginationSize is defined', () => { + const paginationSize = 10; + + beforeEach(() => { + wrapper = shallow(shallowContext({ + ...defaultPagination, + paginationSize + })); + wrapper.render(); + }); + + it('should rendering Pagination correctly', () => { + const pagination = wrapper.find(Pagination); + expect(pagination.length).toBe(1); + expect(pagination.prop('paginationSize')).toEqual(paginationSize); + }); + }); + + describe('when options.withFirstAndLast is defined', () => { + const withFirstAndLast = false; + + beforeEach(() => { + wrapper = shallow(shallowContext({ + ...defaultPagination, + withFirstAndLast + })); + wrapper.render(); + }); + + it('should rendering Pagination correctly', () => { + const pagination = wrapper.find(Pagination); + expect(pagination.length).toBe(1); + expect(pagination.prop('withFirstAndLast')).toEqual(withFirstAndLast); + }); + }); + + describe('when options.alwaysShowAllBtns is defined', () => { + const alwaysShowAllBtns = true; + + beforeEach(() => { + wrapper = shallow(shallowContext({ + ...defaultPagination, + alwaysShowAllBtns + })); + wrapper.render(); + }); + + it('should rendering Pagination correctly', () => { + const pagination = wrapper.find(Pagination); + expect(pagination.length).toBe(1); + expect(pagination.prop('alwaysShowAllBtns')).toEqual(alwaysShowAllBtns); + }); + }); + + describe('when options.firstPageText is defined', () => { + const firstPageText = '1st'; + + beforeEach(() => { + wrapper = shallow(shallowContext({ + ...defaultPagination, + firstPageText + })); + wrapper.render(); + }); + + it('should rendering Pagination correctly', () => { + const pagination = wrapper.find(Pagination); + expect(pagination.length).toBe(1); + expect(pagination.prop('firstPageText')).toEqual(firstPageText); + }); + }); + + describe('when options.prePageText is defined', () => { + const prePageText = 'PRE'; + + beforeEach(() => { + wrapper = shallow(shallowContext({ + ...defaultPagination, + prePageText + })); + wrapper.render(); + }); + + it('should rendering Pagination correctly', () => { + const pagination = wrapper.find(Pagination); + expect(pagination.length).toBe(1); + expect(pagination.prop('prePageText')).toEqual(prePageText); + }); + }); + + describe('when options.nextPageText is defined', () => { + const nextPageText = 'NEXT'; + + beforeEach(() => { + wrapper = shallow(shallowContext({ + ...defaultPagination, + nextPageText + })); + wrapper.render(); + }); + + it('should rendering Pagination correctly', () => { + const pagination = wrapper.find(Pagination); + expect(pagination.length).toBe(1); + expect(pagination.prop('nextPageText')).toEqual(nextPageText); + }); + }); + + describe('when options.lastPageText is defined', () => { + const lastPageText = 'LAST'; + + beforeEach(() => { + wrapper = shallow(shallowContext({ + ...defaultPagination, + lastPageText + })); + wrapper.render(); + }); + + it('should rendering Pagination correctly', () => { + const pagination = wrapper.find(Pagination); + expect(pagination.length).toBe(1); + expect(pagination.prop('lastPageText')).toEqual(lastPageText); + }); + }); + + describe('when options.firstPageTitle is defined', () => { + const firstPageTitle = '1st'; + + beforeEach(() => { + wrapper = shallow(shallowContext({ + ...defaultPagination, + firstPageTitle + })); + wrapper.render(); + }); + + it('should rendering Pagination correctly', () => { + const pagination = wrapper.find(Pagination); + expect(pagination.length).toBe(1); + expect(pagination.prop('firstPageTitle')).toEqual(firstPageTitle); + }); + }); + + describe('when options.prePageTitle is defined', () => { + const prePageTitle = 'PRE'; + + beforeEach(() => { + wrapper = shallow(shallowContext({ + ...defaultPagination, + prePageTitle + })); + wrapper.render(); + }); + + it('should rendering Pagination correctly', () => { + const pagination = wrapper.find(Pagination); + expect(pagination.length).toBe(1); + expect(pagination.prop('prePageTitle')).toEqual(prePageTitle); + }); + }); + + describe('when options.nextPageTitle is defined', () => { + const nextPageTitle = 'NEXT'; + + beforeEach(() => { + wrapper = shallow(shallowContext({ + ...defaultPagination, + nextPageTitle + })); + wrapper.render(); + }); + + it('should rendering Pagination correctly', () => { + const pagination = wrapper.find(Pagination); + expect(pagination.length).toBe(1); + expect(pagination.prop('nextPageTitle')).toEqual(nextPageTitle); + }); + }); + + describe('when options.lastPageTitle is defined', () => { + const lastPageTitle = 'nth'; + + beforeEach(() => { + wrapper = shallow(shallowContext({ + ...defaultPagination, + lastPageTitle + })); + wrapper.render(); + }); + + it('should rendering Pagination correctly', () => { + const pagination = wrapper.find(Pagination); + expect(pagination.length).toBe(1); + expect(pagination.prop('lastPageTitle')).toEqual(lastPageTitle); + }); + }); + + describe('when options.hideSizePerPage is defined', () => { + const hideSizePerPage = true; + + beforeEach(() => { + wrapper = shallow(shallowContext({ + ...defaultPagination, + hideSizePerPage + })); + wrapper.render(); + }); + + it('should rendering Pagination correctly', () => { + const pagination = wrapper.find(Pagination); + expect(pagination.length).toBe(1); + expect(pagination.prop('hideSizePerPage')).toEqual(hideSizePerPage); + }); + }); + + describe('when options.hidePageListOnlyOnePage is defined', () => { + const hidePageListOnlyOnePage = true; + + beforeEach(() => { + wrapper = shallow(shallowContext({ + ...defaultPagination, + hidePageListOnlyOnePage + })); + wrapper.render(); + }); + + it('should rendering Pagination correctly', () => { + const pagination = wrapper.find(Pagination); + expect(pagination.length).toBe(1); + expect(pagination.prop('hidePageListOnlyOnePage')).toEqual(hidePageListOnlyOnePage); + }); + }); +}); diff --git a/packages/react-bootstrap-table2-paginator/test/page.test.js b/packages/react-bootstrap-table2-paginator/test/page.test.js index bd63574..28ab1e1 100644 --- a/packages/react-bootstrap-table2-paginator/test/page.test.js +++ b/packages/react-bootstrap-table2-paginator/test/page.test.js @@ -1,9 +1,8 @@ -import Store from 'react-bootstrap-table-next/src/store'; + import { getByCurrPage, alignPage } from '../src/page'; describe('Page Functions', () => { let data; - let store; const params = [ // [page, sizePerPage, pageStartIndex] [1, 10, 1], @@ -23,27 +22,21 @@ describe('Page Functions', () => { for (let i = 0; i < 100; i += 1) { data.push({ id: i, name: `test_name${i}` }); } - store = new Store('id'); - store.data = data; }); it('should always return correct data', () => { params.forEach(([page, sizePerPage, pageStartIndex]) => { - store.page = page; - store.sizePerPage = sizePerPage; - const rows = getByCurrPage(store, pageStartIndex); + const rows = getByCurrPage(data, page, sizePerPage, pageStartIndex); expect(rows).toBeDefined(); expect(Array.isArray(rows)).toBeTruthy(); expect(rows.every(row => !!row)).toBeTruthy(); }); }); - it('should return empty array when store.data is empty', () => { - store.data = []; + it('should return empty array when data is empty', () => { + data = []; params.forEach(([page, sizePerPage, pageStartIndex]) => { - store.page = page; - store.sizePerPage = sizePerPage; - const rows = getByCurrPage(store, pageStartIndex); + const rows = getByCurrPage(data, page, sizePerPage, pageStartIndex); expect(rows).toHaveLength(0); }); }); @@ -52,19 +45,17 @@ describe('Page Functions', () => { describe('alignPage', () => { const pageStartIndex = 1; const sizePerPage = 10; + const page = 2; describe('if the length of store.data is less than the end page index', () => { beforeEach(() => { data = []; for (let i = 0; i < 15; i += 1) { data.push({ id: i, name: `test_name${i}` }); } - store = new Store('id'); - store.data = data; - store.page = 2; }); it('should return pageStartIndex argument', () => { - expect(alignPage(store, pageStartIndex, sizePerPage)).toEqual(pageStartIndex); + expect(alignPage(data, page, sizePerPage, pageStartIndex)).toEqual(pageStartIndex); }); }); @@ -74,13 +65,10 @@ describe('Page Functions', () => { for (let i = 0; i < 30; i += 1) { data.push({ id: i, name: `test_name${i}` }); } - store = new Store('id'); - store.data = data; - store.page = 2; }); it('should return current page', () => { - expect(alignPage(store, pageStartIndex, sizePerPage)).toEqual(store.page); + expect(alignPage(data, page, sizePerPage, pageStartIndex)).toEqual(page); }); }); }); diff --git a/packages/react-bootstrap-table2-paginator/test/wrapper.test.js b/packages/react-bootstrap-table2-paginator/test/wrapper.test.js deleted file mode 100644 index f636fe3..0000000 --- a/packages/react-bootstrap-table2-paginator/test/wrapper.test.js +++ /dev/null @@ -1,570 +0,0 @@ -import React from 'react'; -import sinon from 'sinon'; -import { shallow } from 'enzyme'; - - -import BootstrapTable from 'react-bootstrap-table-next/src/bootstrap-table'; -import remoteResolver from 'react-bootstrap-table-next/src/props-resolver/remote-resolver'; -import Store from 'react-bootstrap-table-next/src/store'; -import paginator from '..'; -import wrapperFactory from '../src/wrapper'; -import Pagination from '../src/pagination'; -import Const from '../src/const'; - -const data = []; -for (let i = 0; i < 100; i += 1) { - data.push({ - id: i, - name: `item name ${i}` - }); -} - -describe('Wrapper', () => { - let wrapper; - let instance; - const onTableChangeCB = sinon.stub(); - - afterEach(() => { - onTableChangeCB.reset(); - }); - - const createTableProps = (props = {}) => { - const tableProps = { - keyField: 'id', - columns: [{ - dataField: 'id', - text: 'ID' - }, { - dataField: 'name', - text: 'Name' - }], - data, - pagination: paginator(props.options), - store: new Store('id'), - onTableChange: onTableChangeCB - }; - tableProps.store.data = data; - return tableProps; - }; - - const PaginationWrapper = wrapperFactory(BootstrapTable, { - remoteResolver - }); - - const createPaginationWrapper = (props, renderFragment = true) => { - wrapper = shallow(); - instance = wrapper.instance(); - if (renderFragment) { - const fragment = instance.render(); - wrapper = shallow(
{ fragment }
); - } - }; - - describe('default pagination', () => { - const props = createTableProps(); - - beforeEach(() => { - createPaginationWrapper(props); - }); - - it('should render correctly', () => { - expect(wrapper.length).toBe(1); - }); - - it('should initialize state correctly', () => { - expect(instance.state.currPage).toBeDefined(); - expect(instance.state.currPage).toEqual(Const.PAGE_START_INDEX); - expect(instance.state.currSizePerPage).toBeDefined(); - expect(instance.state.currSizePerPage).toEqual(Const.SIZE_PER_PAGE_LIST[0]); - }); - - it('should save page and sizePerPage to the store correctly', () => { - expect(props.store.page).toBe(instance.state.currPage); - expect(props.store.sizePerPage).toBe(instance.state.currSizePerPage); - }); - - it('should render BootstrapTable correctly', () => { - const table = wrapper.find(BootstrapTable); - expect(table.length).toBe(1); - expect(table.prop('data').length).toEqual(instance.state.currSizePerPage); - }); - - it('should render Pagination correctly', () => { - const pagination = wrapper.find(Pagination); - expect(pagination.length).toBe(1); - expect(pagination.prop('dataSize')).toEqual(props.store.data.length); - expect(pagination.prop('currPage')).toEqual(instance.state.currPage); - expect(pagination.prop('currSizePerPage')).toEqual(instance.state.currSizePerPage); - expect(pagination.prop('onPageChange')).toEqual(instance.handleChangePage); - expect(pagination.prop('onSizePerPageChange')).toEqual(instance.handleChangeSizePerPage); - expect(pagination.prop('sizePerPageList')).toEqual(Const.SIZE_PER_PAGE_LIST); - expect(pagination.prop('paginationSize')).toEqual(Const.PAGINATION_SIZE); - expect(pagination.prop('pageStartIndex')).toEqual(Const.PAGE_START_INDEX); - expect(pagination.prop('withFirstAndLast')).toEqual(Const.With_FIRST_AND_LAST); - expect(pagination.prop('alwaysShowAllBtns')).toEqual(Const.SHOW_ALL_PAGE_BTNS); - expect(pagination.prop('firstPageText')).toEqual(Const.FIRST_PAGE_TEXT); - expect(pagination.prop('prePageText')).toEqual(Const.PRE_PAGE_TEXT); - expect(pagination.prop('nextPageText')).toEqual(Const.NEXT_PAGE_TEXT); - expect(pagination.prop('lastPageText')).toEqual(Const.LAST_PAGE_TEXT); - expect(pagination.prop('firstPageTitle')).toEqual(Const.FIRST_PAGE_TITLE); - expect(pagination.prop('prePageTitle')).toEqual(Const.PRE_PAGE_TITLE); - expect(pagination.prop('nextPageTitle')).toEqual(Const.NEXT_PAGE_TITLE); - expect(pagination.prop('lastPageTitle')).toEqual(Const.LAST_PAGE_TITLE); - expect(pagination.prop('hideSizePerPage')).toEqual(Const.HIDE_SIZE_PER_PAGE); - expect(pagination.prop('showTotal')).toBeFalsy(); - }); - - describe('componentWillReceiveProps', () => { - let nextProps; - beforeEach(() => { - nextProps = createTableProps(); - }); - - describe('when options.page is existing', () => { - beforeEach(() => { - nextProps.pagination.options.page = 2; - instance.componentWillReceiveProps(nextProps); - }); - - it('should setting currPage state correctly', () => { - expect(instance.state.currPage).toEqual(nextProps.pagination.options.page); - }); - - it('should saving store.page correctly', () => { - expect(props.store.page).toEqual(instance.state.currPage); - }); - }); - - it('should not setting currPage state if options.page not existing', () => { - const { currPage } = instance.state; - instance.componentWillReceiveProps(nextProps); - expect(instance.state.currPage).toBe(currPage); - }); - - describe('when options.sizePerPage is existing', () => { - beforeEach(() => { - nextProps.pagination.options.sizePerPage = 20; - instance.componentWillReceiveProps(nextProps); - }); - - it('should setting currSizePerPage state correctly', () => { - expect(instance.state.currSizePerPage).toEqual(nextProps.pagination.options.sizePerPage); - }); - - it('should saving store.sizePerPage correctly', () => { - expect(props.store.sizePerPage).toEqual(instance.state.currSizePerPage); - }); - }); - - it('should not setting currSizePerPage state if options.sizePerPage not existing', () => { - const { currSizePerPage } = instance.state; - instance.componentWillReceiveProps(nextProps); - expect(instance.state.currSizePerPage).toBe(currSizePerPage); - }); - - describe('when nextProps.isDataChanged is true', () => { - beforeEach(() => { - nextProps.isDataChanged = true; - instance.componentWillReceiveProps(nextProps); - }); - - it('should setting currPage state correctly', () => { - expect(instance.state.currPage).toBe(Const.PAGE_START_INDEX); - }); - - it('should saving store.page correctly', () => { - expect(props.store.page).toEqual(instance.state.currPage); - }); - }); - }); - }); - - describe('when options.page is defined', () => { - const page = 3; - const props = createTableProps({ options: { page } }); - beforeEach(() => { - createPaginationWrapper(props); - }); - - it('should setting correct state.currPage', () => { - expect(instance.state.currPage).toEqual(page); - }); - - it('should rendering Pagination correctly', () => { - const pagination = wrapper.find(Pagination); - expect(wrapper.length).toBe(1); - expect(pagination.length).toBe(1); - expect(pagination.prop('currPage')).toEqual(page); - }); - }); - - describe('when options.sizePerPage is defined', () => { - const sizePerPage = 30; - const props = createTableProps({ options: { sizePerPage } }); - beforeEach(() => { - createPaginationWrapper(props); - }); - - it('should setting correct state.currPage', () => { - expect(instance.state.currSizePerPage).toEqual(sizePerPage); - }); - - it('should rendering Pagination correctly', () => { - const pagination = wrapper.find(Pagination); - expect(wrapper.length).toBe(1); - expect(pagination.length).toBe(1); - expect(pagination.prop('currSizePerPage')).toEqual(sizePerPage); - }); - }); - - describe('when options.totalSize is defined', () => { - const totalSize = 100; - const props = createTableProps({ options: { totalSize } }); - beforeEach(() => { - createPaginationWrapper(props); - }); - - it('should rendering Pagination correctly', () => { - const pagination = wrapper.find(Pagination); - expect(wrapper.length).toBe(1); - expect(pagination.length).toBe(1); - expect(pagination.prop('dataSize')).toEqual(totalSize); - }); - }); - - describe('when options.showTotal is defined', () => { - const props = createTableProps({ options: { showTotal: true } }); - beforeEach(() => { - createPaginationWrapper(props); - }); - - it('should render Pagination correctly', () => { - const pagination = wrapper.find(Pagination); - expect(wrapper.length).toBe(1); - expect(pagination.length).toBe(1); - expect(pagination.prop('showTotal')).toBeTruthy(); - }); - }); - - describe('when options.pageStartIndex is defined', () => { - const pageStartIndex = -1; - const props = createTableProps({ options: { pageStartIndex } }); - beforeEach(() => { - createPaginationWrapper(props); - }); - - it('should setting correct state.currPage', () => { - expect(instance.state.currPage).toEqual(pageStartIndex); - }); - - it('should rendering Pagination correctly', () => { - const pagination = wrapper.find(Pagination); - expect(wrapper.length).toBe(1); - expect(pagination.length).toBe(1); - expect(pagination.prop('pageStartIndex')).toEqual(pageStartIndex); - }); - }); - - describe('when options.sizePerPageList is defined', () => { - const sizePerPageList = [10, 40]; - const props = createTableProps({ options: { sizePerPageList } }); - beforeEach(() => { - createPaginationWrapper(props); - }); - - it('should rendering Pagination correctly', () => { - const pagination = wrapper.find(Pagination); - expect(wrapper.length).toBe(1); - expect(pagination.length).toBe(1); - expect(pagination.prop('sizePerPageList')).toEqual(sizePerPageList); - }); - }); - - describe('when options.paginationSize is defined', () => { - const paginationSize = 10; - const props = createTableProps({ options: { paginationSize } }); - beforeEach(() => { - createPaginationWrapper(props); - }); - - it('should rendering Pagination correctly', () => { - const pagination = wrapper.find(Pagination); - expect(wrapper.length).toBe(1); - expect(pagination.length).toBe(1); - expect(pagination.prop('paginationSize')).toEqual(paginationSize); - }); - }); - - describe('when options.withFirstAndLast is defined', () => { - const withFirstAndLast = false; - const props = createTableProps({ options: { withFirstAndLast } }); - beforeEach(() => { - createPaginationWrapper(props); - }); - - it('should rendering Pagination correctly', () => { - const pagination = wrapper.find(Pagination); - expect(wrapper.length).toBe(1); - expect(pagination.length).toBe(1); - expect(pagination.prop('withFirstAndLast')).toEqual(withFirstAndLast); - }); - }); - - describe('when options.alwaysShowAllBtns is defined', () => { - const alwaysShowAllBtns = true; - const props = createTableProps({ options: { alwaysShowAllBtns } }); - beforeEach(() => { - createPaginationWrapper(props); - }); - - it('should rendering Pagination correctly', () => { - const pagination = wrapper.find(Pagination); - expect(wrapper.length).toBe(1); - expect(pagination.length).toBe(1); - expect(pagination.prop('alwaysShowAllBtns')).toEqual(alwaysShowAllBtns); - }); - }); - - describe('when options.firstPageText is defined', () => { - const firstPageText = '1st'; - const props = createTableProps({ options: { firstPageText } }); - beforeEach(() => { - createPaginationWrapper(props); - }); - - it('should rendering Pagination correctly', () => { - const pagination = wrapper.find(Pagination); - expect(wrapper.length).toBe(1); - expect(pagination.length).toBe(1); - expect(pagination.prop('firstPageText')).toEqual(firstPageText); - }); - }); - - describe('when options.prePageText is defined', () => { - const prePageText = 'PRE'; - const props = createTableProps({ options: { prePageText } }); - beforeEach(() => { - createPaginationWrapper(props); - }); - - it('should rendering Pagination correctly', () => { - const pagination = wrapper.find(Pagination); - expect(wrapper.length).toBe(1); - expect(pagination.length).toBe(1); - expect(pagination.prop('prePageText')).toEqual(prePageText); - }); - }); - - describe('when options.nextPageText is defined', () => { - const nextPageText = 'NEXT'; - const props = createTableProps({ options: { nextPageText } }); - beforeEach(() => { - createPaginationWrapper(props); - }); - - it('should rendering Pagination correctly', () => { - const pagination = wrapper.find(Pagination); - expect(wrapper.length).toBe(1); - expect(pagination.length).toBe(1); - expect(pagination.prop('nextPageText')).toEqual(nextPageText); - }); - }); - - describe('when options.lastPageText is defined', () => { - const lastPageText = 'nth'; - const props = createTableProps({ options: { lastPageText } }); - beforeEach(() => { - createPaginationWrapper(props); - }); - - it('should rendering Pagination correctly', () => { - const pagination = wrapper.find(Pagination); - expect(wrapper.length).toBe(1); - expect(pagination.length).toBe(1); - expect(pagination.prop('lastPageText')).toEqual(lastPageText); - }); - }); - - describe('when options.firstPageTitle is defined', () => { - const firstPageTitle = '1st'; - const props = createTableProps({ options: { firstPageTitle } }); - beforeEach(() => { - createPaginationWrapper(props); - }); - - it('should rendering Pagination correctly', () => { - const pagination = wrapper.find(Pagination); - expect(wrapper.length).toBe(1); - expect(pagination.length).toBe(1); - expect(pagination.prop('firstPageTitle')).toEqual(firstPageTitle); - }); - }); - - describe('when options.prePageTitle is defined', () => { - const prePageTitle = 'PRE'; - const props = createTableProps({ options: { prePageTitle } }); - beforeEach(() => { - createPaginationWrapper(props); - }); - - it('should rendering Pagination correctly', () => { - const pagination = wrapper.find(Pagination); - expect(wrapper.length).toBe(1); - expect(pagination.length).toBe(1); - expect(pagination.prop('prePageTitle')).toEqual(prePageTitle); - }); - }); - - describe('when options.nextPageTitle is defined', () => { - const nextPageTitle = 'NEXT'; - const props = createTableProps({ options: { nextPageTitle } }); - beforeEach(() => { - createPaginationWrapper(props); - }); - - it('should rendering Pagination correctly', () => { - const pagination = wrapper.find(Pagination); - expect(wrapper.length).toBe(1); - expect(pagination.length).toBe(1); - expect(pagination.prop('nextPageTitle')).toEqual(nextPageTitle); - }); - }); - - describe('when options.lastPageTitle is defined', () => { - const lastPageTitle = 'nth'; - const props = createTableProps({ options: { lastPageTitle } }); - beforeEach(() => { - createPaginationWrapper(props); - }); - - it('should rendering Pagination correctly', () => { - const pagination = wrapper.find(Pagination); - expect(wrapper.length).toBe(1); - expect(pagination.length).toBe(1); - expect(pagination.prop('lastPageTitle')).toEqual(lastPageTitle); - }); - }); - - describe('when options.hideSizePerPage is defined', () => { - const hideSizePerPage = true; - const props = createTableProps({ options: { hideSizePerPage } }); - beforeEach(() => { - createPaginationWrapper(props); - }); - - it('should rendering Pagination correctly', () => { - const pagination = wrapper.find(Pagination); - expect(wrapper.length).toBe(1); - expect(pagination.length).toBe(1); - expect(pagination.prop('hideSizePerPage')).toEqual(hideSizePerPage); - }); - }); - - describe('when options.hidePageListOnlyOnePage is defined', () => { - const hidePageListOnlyOnePage = true; - const props = createTableProps({ options: { hidePageListOnlyOnePage } }); - beforeEach(() => { - createPaginationWrapper(props); - }); - - it('should rendering Pagination correctly', () => { - const pagination = wrapper.find(Pagination); - expect(wrapper.length).toBe(1); - expect(pagination.length).toBe(1); - expect(pagination.prop('hidePageListOnlyOnePage')).toEqual(hidePageListOnlyOnePage); - }); - }); - - describe('handleChangePage', () => { - const newPage = 3; - const props = createTableProps({ options: { onPageChange: sinon.stub() } }); - beforeEach(() => { - createPaginationWrapper(props, false); - instance.handleChangePage(newPage); - }); - - afterEach(() => { - props.pagination.options.onPageChange.reset(); - }); - - it('should setting state.currPage correctly', () => { - expect(instance.state.currPage).toEqual(newPage); - }); - - it('should calling options.onPageChange correctly when it is defined', () => { - const { onPageChange } = props.pagination.options; - expect(onPageChange.calledOnce).toBeTruthy(); - expect(onPageChange.calledWith(newPage, instance.state.currSizePerPage)).toBeTruthy(); - }); - - it('should saving page and sizePerPage to store correctly', () => { - expect(props.store.page).toBe(newPage); - expect(props.store.sizePerPage).toBe(instance.state.currSizePerPage); - }); - - describe('when pagination remote is enable', () => { - beforeEach(() => { - props.remote = true; - createPaginationWrapper(props, false); - onTableChangeCB.reset(); - instance.handleChangePage(newPage); - }); - - it('should not setting state.currPage', () => { - expect(instance.state.currPage).not.toEqual(newPage); - }); - - it('should calling props.onRemotePageChange correctly', () => { - expect(onTableChangeCB.calledOnce).toBeTruthy(); - }); - }); - }); - - describe('handleChangeSizePerPage', () => { - const newPage = 2; - const newSizePerPage = 30; - const props = createTableProps({ options: { onSizePerPageChange: sinon.stub() } }); - beforeEach(() => { - createPaginationWrapper(props, false); - instance.handleChangeSizePerPage(newSizePerPage, newPage); - }); - - afterEach(() => { - props.pagination.options.onSizePerPageChange.reset(); - }); - - it('should setting state.currPage and state.currSizePerPage correctly', () => { - expect(instance.state.currPage).toEqual(newPage); - expect(instance.state.currSizePerPage).toEqual(newSizePerPage); - }); - - it('should calling options.onSizePerPageChange correctly when it is defined', () => { - const { onSizePerPageChange } = props.pagination.options; - expect(onSizePerPageChange.calledOnce).toBeTruthy(); - expect(onSizePerPageChange.calledWith(newSizePerPage, newPage)).toBeTruthy(); - }); - - it('should saving page and sizePerPage to store correctly', () => { - expect(props.store.page).toBe(newPage); - expect(props.store.sizePerPage).toBe(newSizePerPage); - }); - - describe('when pagination remote is enable', () => { - beforeEach(() => { - props.remote = true; - createPaginationWrapper(props, false); - onTableChangeCB.reset(); - instance.handleChangeSizePerPage(newSizePerPage, newPage); - }); - - it('should not setting state.currPage', () => { - expect(instance.state.currPage).not.toEqual(newPage); - expect(instance.state.currSizePerPage).not.toEqual(newSizePerPage); - }); - - it('should calling props.onRemotePageChange correctly', () => { - expect(onTableChangeCB.calledOnce).toBeTruthy(); - }); - }); - }); -}); From 9c677fe174a4cdbe768ae714ee681cf29256d080 Mon Sep 17 00:00:00 2001 From: AllenFang Date: Fri, 15 Jun 2018 14:55:32 +0800 Subject: [PATCH 21/55] enhance remote all example --- .../examples/remote/remote-all.js | 84 ++++++++++++++++--- 1 file changed, 74 insertions(+), 10 deletions(-) diff --git a/packages/react-bootstrap-table2-example/examples/remote/remote-all.js b/packages/react-bootstrap-table2-example/examples/remote/remote-all.js index 2720584..95107e1 100644 --- a/packages/react-bootstrap-table2-example/examples/remote/remote-all.js +++ b/packages/react-bootstrap-table2-example/examples/remote/remote-all.js @@ -12,15 +12,20 @@ const products = productsGenerator(87); const columns = [{ dataField: 'id', - text: 'Product ID' + text: 'Product ID', + sort: true }, { dataField: 'name', text: 'Product Name', - filter: textFilter() + filter: textFilter({ + defaultValue: '8' + }), + sort: true }, { dataField: 'price', text: 'Product Price', - filter: textFilter() + filter: textFilter(), + sort: true }]; const sourceCode = `\ @@ -31,15 +36,25 @@ import filterFactory, { textFilter, Comparator } from 'react-bootstrap-table2-fi const columns = [{ dataField: 'id', - text: 'Product ID' + text: 'Product ID', + sort: true }, { dataField: 'name', text: 'Product Name', - filter: textFilter() + filter: textFilter({ + defaultValue: '8' + }), + sort: true }, { dataField: 'price', text: 'Product Price', - filter: textFilter() + filter: textFilter(), + sort: true +}]; + +const defaultSorted = [{ + dataField: 'name', + order: 'desc' }]; const RemoteAll = ({ data, page, sizePerPage, onTableChange, totalSize }) => ( @@ -49,6 +64,7 @@ const RemoteAll = ({ data, page, sizePerPage, onTableChange, totalSize }) => ( keyField="id" data={ data } columns={ columns } + defaultSorted={ defaultSorted } filter={ filterFactory() } pagination={ paginationFactory({ page, sizePerPage, totalSize }) } onTableChange={ onTableChange } @@ -77,10 +93,11 @@ class Container extends React.Component { this.handleTableChange = this.handleTableChange.bind(this); } - handleTableChange = (type, { page, sizePerPage, filters }) => { + handleTableChange = (type, { page, sizePerPage, filters, sortField, sortOrder }) => { const currentIndex = (page - 1) * sizePerPage; setTimeout(() => { - const result = products.filter((row) => { + // Handle column filters + let result = products.filter((row) => { let valid = true; for (const dataField in filters) { const { filterVal, filterType, comparator } = filters[dataField]; @@ -96,6 +113,26 @@ class Container extends React.Component { } return valid; }); + // Handle column sort + if (sortOrder === 'asc') { + result = result.sort((a, b) => { + if (a[sortField] > b[sortField]) { + return 1; + } else if (b[sortField] > a[sortField]) { + return -1; + } + return 0; + }); + } else { + result = result.sort((a, b) => { + if (a[sortField] > b[sortField]) { + return -1; + } else if (b[sortField] > a[sortField]) { + return 1; + } + return 0; + }); + } this.setState(() => ({ page, data: result.slice(currentIndex, currentIndex + sizePerPage), @@ -120,6 +157,11 @@ class Container extends React.Component { } `; +const defaultSorted = [{ + dataField: 'name', + order: 'desc' +}]; + const RemoteAll = ({ data, page, sizePerPage, onTableChange, totalSize }) => (

When remote.pagination is enabled, the filtering, @@ -129,6 +171,7 @@ const RemoteAll = ({ data, page, sizePerPage, onTableChange, totalSize }) => ( keyField="id" data={ data } columns={ columns } + defaultSorted={ defaultSorted } filter={ filterFactory() } pagination={ paginationFactory({ page, sizePerPage, totalSize }) } onTableChange={ onTableChange } @@ -157,10 +200,11 @@ class Container extends React.Component { this.handleTableChange = this.handleTableChange.bind(this); } - handleTableChange = (type, { page, sizePerPage, filters }) => { + handleTableChange = (type, { page, sizePerPage, filters, sortField, sortOrder }) => { const currentIndex = (page - 1) * sizePerPage; setTimeout(() => { - const result = products.filter((row) => { + // Handle column filters + let result = products.filter((row) => { let valid = true; for (const dataField in filters) { const { filterVal, filterType, comparator } = filters[dataField]; @@ -176,6 +220,26 @@ class Container extends React.Component { } return valid; }); + // Handle column sort + if (sortOrder === 'asc') { + result = result.sort((a, b) => { + if (a[sortField] > b[sortField]) { + return 1; + } else if (b[sortField] > a[sortField]) { + return -1; + } + return 0; + }); + } else { + result = result.sort((a, b) => { + if (a[sortField] > b[sortField]) { + return -1; + } else if (b[sortField] > a[sortField]) { + return 1; + } + return 0; + }); + } this.setState(() => ({ page, data: result.slice(currentIndex, currentIndex + sizePerPage), From 78ea63074ec29fcc2782592f999fcb4a5c35b5ed Mon Sep 17 00:00:00 2001 From: AllenFang Date: Fri, 15 Jun 2018 14:56:18 +0800 Subject: [PATCH 22/55] fix bug for default sort and filter have potential issue when remote is enable --- .../src/components/date.js | 4 +- .../src/components/number.js | 2 +- .../src/components/select.js | 2 +- .../src/components/text.js | 2 +- .../src/context.js | 15 +++-- .../test/components/date.test.js | 4 +- .../test/components/number.test.js | 2 +- .../test/components/select.test.js | 2 +- .../test/components/text.test.js | 2 +- .../test/context.test.js | 62 +++++++++++++++++-- .../src/contexts/sort-context.js | 11 ++-- 11 files changed, 85 insertions(+), 23 deletions(-) diff --git a/packages/react-bootstrap-table2-filter/src/components/date.js b/packages/react-bootstrap-table2-filter/src/components/date.js index 8e31724..da751d2 100644 --- a/packages/react-bootstrap-table2-filter/src/components/date.js +++ b/packages/react-bootstrap-table2-filter/src/components/date.js @@ -36,7 +36,7 @@ class DateFilter extends Component { const comparator = this.dateFilterComparator.value; const date = this.inputDate.value; if (comparator && date) { - this.applyFilter(date, comparator); + this.applyFilter(date, comparator, true); } // export onFilter function to allow users to access @@ -103,7 +103,7 @@ class DateFilter extends Component { // instead of parsing an invalid Date. The filter function will interpret // null as an empty date field const date = value === '' ? null : new Date(value); - onFilter(column, FILTER_TYPE.DATE)({ date, comparator }); + onFilter(column, FILTER_TYPE.DATE, )({ date, comparator }); }; if (delay) { this.timeout = setTimeout(() => { execute(); }, delay); diff --git a/packages/react-bootstrap-table2-filter/src/components/number.js b/packages/react-bootstrap-table2-filter/src/components/number.js index 960aa5d..447eed2 100644 --- a/packages/react-bootstrap-table2-filter/src/components/number.js +++ b/packages/react-bootstrap-table2-filter/src/components/number.js @@ -36,7 +36,7 @@ class NumberFilter extends Component { const comparator = this.numberFilterComparator.value; const number = this.numberFilter.value; if (comparator && number) { - onFilter(column, FILTER_TYPE.NUMBER)({ number, comparator }); + onFilter(column, FILTER_TYPE.NUMBER, true)({ number, comparator }); } // export onFilter function to allow users to access diff --git a/packages/react-bootstrap-table2-filter/src/components/select.js b/packages/react-bootstrap-table2-filter/src/components/select.js index 7c4d285..e505138 100644 --- a/packages/react-bootstrap-table2-filter/src/components/select.js +++ b/packages/react-bootstrap-table2-filter/src/components/select.js @@ -29,7 +29,7 @@ class SelectFilter extends Component { const value = this.selectInput.value; if (value && value !== '') { - onFilter(column, FILTER_TYPE.SELECT)(value); + onFilter(column, FILTER_TYPE.SELECT, true)(value); } // export onFilter function to allow users to access diff --git a/packages/react-bootstrap-table2-filter/src/components/text.js b/packages/react-bootstrap-table2-filter/src/components/text.js index 3af8ae2..a17c3ea 100644 --- a/packages/react-bootstrap-table2-filter/src/components/text.js +++ b/packages/react-bootstrap-table2-filter/src/components/text.js @@ -23,7 +23,7 @@ class TextFilter extends Component { const defaultValue = this.input.value; if (defaultValue) { - onFilter(this.props.column, FILTER_TYPE.TEXT)(defaultValue); + onFilter(this.props.column, FILTER_TYPE.TEXT, true)(defaultValue); } // export onFilter function to allow users to access diff --git a/packages/react-bootstrap-table2-filter/src/context.js b/packages/react-bootstrap-table2-filter/src/context.js index 1459b3f..790ce00 100644 --- a/packages/react-bootstrap-table2-filter/src/context.js +++ b/packages/react-bootstrap-table2-filter/src/context.js @@ -27,7 +27,13 @@ export default ( this.onExternalFilter = this.onExternalFilter.bind(this); } - onFilter(column, filterType) { + componentDidMount() { + if (isRemoteFiltering() && Object.keys(this.currFilters).length > 0) { + handleFilterChange(this.currFilters); + } + } + + onFilter(column, filterType, initialize = false) { return (filterVal) => { // watch out here if migration to context API, #334 const currFilters = Object.assign({}, this.currFilters); @@ -52,10 +58,9 @@ export default ( 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 + if (!initialize) { + handleFilterChange(this.currFilters); + } return; } diff --git a/packages/react-bootstrap-table2-filter/test/components/date.test.js b/packages/react-bootstrap-table2-filter/test/components/date.test.js index 366a675..b421333 100644 --- a/packages/react-bootstrap-table2-filter/test/components/date.test.js +++ b/packages/react-bootstrap-table2-filter/test/components/date.test.js @@ -124,7 +124,7 @@ describe('Date Filter', () => { it('should do onFilter correctly when exported function was executed', () => { expect(onFilter).toHaveBeenCalledTimes(1); - expect(onFilter).toHaveBeenCalledWith(column, FILTER_TYPE.DATE); + expect(onFilter).toHaveBeenCalledWith(column, FILTER_TYPE.DATE, false); expect(onFilterFirstReturn).toHaveBeenCalledTimes(1); expect(onFilterFirstReturn).toHaveBeenCalledWith({ comparator, date }); }); @@ -148,7 +148,7 @@ describe('Date Filter', () => { it('should calling onFilter on componentDidMount', () => { expect(onFilter).toHaveBeenCalledTimes(1); - expect(onFilter).toHaveBeenCalledWith(column, FILTER_TYPE.DATE); + expect(onFilter).toHaveBeenCalledWith(column, FILTER_TYPE.DATE, true); expect(onFilterFirstReturn).toHaveBeenCalledTimes(1); // expect(onFilterFirstReturn).toHaveBeenCalledWith({ comparator, date }); }); diff --git a/packages/react-bootstrap-table2-filter/test/components/number.test.js b/packages/react-bootstrap-table2-filter/test/components/number.test.js index 3586ec4..1556dac 100644 --- a/packages/react-bootstrap-table2-filter/test/components/number.test.js +++ b/packages/react-bootstrap-table2-filter/test/components/number.test.js @@ -147,7 +147,7 @@ describe('Number Filter', () => { it('should calling onFilter on componentDidMount', () => { expect(onFilter.calledOnce).toBeTruthy(); - expect(onFilter.calledWith(column, FILTER_TYPE.NUMBER)).toBeTruthy(); + expect(onFilter.calledWith(column, FILTER_TYPE.NUMBER, true)).toBeTruthy(); expect(onFilterFirstReturn.calledOnce).toBeTruthy(); expect(onFilterFirstReturn.calledWith({ number: `${number}`, comparator })).toBeTruthy(); }); diff --git a/packages/react-bootstrap-table2-filter/test/components/select.test.js b/packages/react-bootstrap-table2-filter/test/components/select.test.js index 85f9686..80d30d7 100644 --- a/packages/react-bootstrap-table2-filter/test/components/select.test.js +++ b/packages/react-bootstrap-table2-filter/test/components/select.test.js @@ -91,7 +91,7 @@ describe('Select Filter', () => { it('should calling onFilter on componentDidMount', () => { expect(onFilter.calledOnce).toBeTruthy(); - expect(onFilter.calledWith(column, FILTER_TYPE.SELECT)).toBeTruthy(); + expect(onFilter.calledWith(column, FILTER_TYPE.SELECT, true)).toBeTruthy(); expect(onFilterFirstReturn.calledOnce).toBeTruthy(); expect(onFilterFirstReturn.calledWith(defaultValue)).toBeTruthy(); }); diff --git a/packages/react-bootstrap-table2-filter/test/components/text.test.js b/packages/react-bootstrap-table2-filter/test/components/text.test.js index 5a8d297..fbc849f 100644 --- a/packages/react-bootstrap-table2-filter/test/components/text.test.js +++ b/packages/react-bootstrap-table2-filter/test/components/text.test.js @@ -65,7 +65,7 @@ describe('Text Filter', () => { it('should calling onFilter on componentDidMount', () => { expect(onFilter.calledOnce).toBeTruthy(); - expect(onFilter.calledWith(column, FILTER_TYPE.TEXT)).toBeTruthy(); + expect(onFilter.calledWith(column, FILTER_TYPE.TEXT, true)).toBeTruthy(); expect(onFilterFirstReturn.calledOnce).toBeTruthy(); expect(onFilterFirstReturn.calledWith(defaultValue)).toBeTruthy(); }); diff --git a/packages/react-bootstrap-table2-filter/test/context.test.js b/packages/react-bootstrap-table2-filter/test/context.test.js index 4dd03d5..2dc2cb8 100644 --- a/packages/react-bootstrap-table2-filter/test/context.test.js +++ b/packages/react-bootstrap-table2-filter/test/context.test.js @@ -12,7 +12,6 @@ import { textFilter } from '../index'; describe('FilterContext', () => { let wrapper; - // let filter; let FilterContext; const data = [{ @@ -33,8 +32,6 @@ describe('FilterContext', () => { filter: textFilter() }]; - // const defaultFilter = {}; - const mockBase = jest.fn((props => ( { const handleFilterChange = jest.fn(); function shallowContext( - // customFilter = defaultFilter, enableRemote = false ) { mockBase.mockReset(); @@ -115,6 +111,45 @@ describe('FilterContext', () => { }); }); + describe('componentDidMount', () => { + describe('when remote filter is disabled', () => { + beforeEach(() => { + wrapper = shallow(shallowContext()); + wrapper.render(); + wrapper.instance().componentDidMount(); + }); + + it('should not call handleFilterChange', () => { + expect(handleFilterChange).toHaveBeenCalledTimes(0); + }); + }); + + describe('when remote filter is enable but currFilters is empty', () => { + beforeEach(() => { + wrapper = shallow(shallowContext(true)); + wrapper.render(); + wrapper.instance().componentDidMount(); + }); + + it('should not call handleFilterChange', () => { + expect(handleFilterChange).toHaveBeenCalledTimes(0); + }); + }); + + describe('when remote filter is enable and currFilters is not empty', () => { + beforeEach(() => { + wrapper = shallow(shallowContext(true)); + wrapper.instance().currFilters.price = { filterVal: 40, filterType: FILTER_TYPE.TEXT }; + }); + + it('should not call handleFilterChange', () => { + wrapper.instance().componentDidMount(); + expect(handleFilterChange).toHaveBeenCalledTimes(1); + expect(handleFilterChange).toHaveBeenCalledWith(wrapper.instance().currFilters); + }); + }); + }); + describe('onFilter', () => { let instance; describe('when filterVal is empty or undefined', () => { @@ -169,6 +204,25 @@ describe('FilterContext', () => { }); }); + describe('when remote filter is enabled but initialize argument is true', () => { + const filterVal = '3'; + + beforeEach(() => { + wrapper = shallow(shallowContext(true)); + wrapper.render(); + instance = wrapper.instance(); + instance.onFilter(columns[1], FILTER_TYPE.TEXT, true)(filterVal); + }); + + it('should correct currFilters', () => { + expect(Object.keys(instance.currFilters)).toHaveLength(1); + }); + + it('should not call handleFilterChange correctly', () => { + expect(handleFilterChange).toHaveBeenCalledTimes(0); + }); + }); + describe('combination', () => { beforeEach(() => { wrapper = shallow(shallowContext()); diff --git a/packages/react-bootstrap-table2/src/contexts/sort-context.js b/packages/react-bootstrap-table2/src/contexts/sort-context.js index d71dbf5..033d99a 100644 --- a/packages/react-bootstrap-table2/src/contexts/sort-context.js +++ b/packages/react-bootstrap-table2/src/contexts/sort-context.js @@ -38,15 +38,18 @@ export default ( if (sortColumn.onSort) { sortColumn.onSort(sortField, sortOrder); } - - if (isRemoteSort()) { - handleSortChange(sortField, sortOrder); - } } } this.state = { sortOrder, sortColumn }; } + componentDidMount() { + const { sortOrder, sortColumn } = this.state; + if (isRemoteSort() && sortOrder && sortColumn) { + handleSortChange(sortColumn.dataField, sortOrder); + } + } + handleSort = (column) => { const sortOrder = dataOperator.nextOrder(column, this.state, this.props.defaultSortDirection); From 77301c2cf1d0fc4437caf6275ce3e733c766ab69 Mon Sep 17 00:00:00 2001 From: AllenFang Date: Mon, 18 Jun 2018 13:02:43 +0800 Subject: [PATCH 23/55] implement table search --- .../react-bootstrap-table2-toolkit/context.js | 40 +++++++++ .../react-bootstrap-table2-toolkit/index.js | 4 + .../package.json | 40 +++++++++ .../src/search/SearchBar.js | 73 ++++++++++++++++ .../src/search/context.js | 83 +++++++++++++++++++ .../src/search/index.js | 10 +++ .../src/contexts/data-context.js | 3 +- .../src/contexts/index.js | 55 ++++++++++-- .../src/props-resolver/remote-resolver.js | 15 ++++ 9 files changed, 316 insertions(+), 7 deletions(-) create mode 100644 packages/react-bootstrap-table2-toolkit/context.js create mode 100644 packages/react-bootstrap-table2-toolkit/index.js create mode 100644 packages/react-bootstrap-table2-toolkit/package.json create mode 100644 packages/react-bootstrap-table2-toolkit/src/search/SearchBar.js create mode 100644 packages/react-bootstrap-table2-toolkit/src/search/context.js create mode 100644 packages/react-bootstrap-table2-toolkit/src/search/index.js diff --git a/packages/react-bootstrap-table2-toolkit/context.js b/packages/react-bootstrap-table2-toolkit/context.js new file mode 100644 index 0000000..31373fa --- /dev/null +++ b/packages/react-bootstrap-table2-toolkit/context.js @@ -0,0 +1,40 @@ +/* eslint react/prop-types: 0 */ +/* eslint react/require-default-props: 0 */ +import React from 'react'; + +const ToolkitContext = React.createContext(); + +class ToolkitProvider extends React.Component { + constructor(props) { + super(props); + this.test = false; + this.searchProps = { + onSearch: this.onSearch.bind(this), + searchText: '' + }; + } + + onSearch(searchText) { + this.searchProps = { + ...this.searchProps, + searchText + }; + this.forceUpdate(); + } + + render() { + return ( + + { this.props.children } + + ); + } +} + +export default { + Provider: ToolkitProvider, + Consumer: ToolkitContext.Consumer +}; diff --git a/packages/react-bootstrap-table2-toolkit/index.js b/packages/react-bootstrap-table2-toolkit/index.js new file mode 100644 index 0000000..03b2521 --- /dev/null +++ b/packages/react-bootstrap-table2-toolkit/index.js @@ -0,0 +1,4 @@ +import ToolkitContext from './context'; + +export default ToolkitContext; +export { default as Search } from './src/search'; diff --git a/packages/react-bootstrap-table2-toolkit/package.json b/packages/react-bootstrap-table2-toolkit/package.json new file mode 100644 index 0000000..e0b9ad9 --- /dev/null +++ b/packages/react-bootstrap-table2-toolkit/package.json @@ -0,0 +1,40 @@ +{ + "name": "react-bootstrap-table2-toolkit", + "version": "0.1.0", + "description": "The toolkit for react-bootstrap-table2", + "main": "./lib/index.js", + "repository": { + "type": "git", + "url": "git+https://github.com/react-bootstrap-table/react-bootstrap-table2.git" + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "AllenFang", + "license": "MIT", + "keywords": [ + "react", + "bootstrap", + "table", + "grid", + "react-bootstrap-table-addons", + "react-component" + ], + "contributors": [ + { + "name": "Allen Fang", + "email": "ayu780129@hotmail.com", + "url": "https://github.com/AllenFang" + }, + { + "name": "Chun-MingChen", + "email": "nick830314@gmail.com", + "url": "https://github.com/Chun-MingChen" + } + ], + "peerDependencies": { + "prop-types": "^15.0.0", + "react": "^16.0.0", + "react-dom": "^16.0.0" + } +} diff --git a/packages/react-bootstrap-table2-toolkit/src/search/SearchBar.js b/packages/react-bootstrap-table2-toolkit/src/search/SearchBar.js new file mode 100644 index 0000000..99ee8ee --- /dev/null +++ b/packages/react-bootstrap-table2-toolkit/src/search/SearchBar.js @@ -0,0 +1,73 @@ +/* eslint no-return-assign: 0 */ +import React from 'react'; +import PropTypes from 'prop-types'; + +const handleDebounce = (func, wait, immediate) => { + let timeout; + + return () => { + const later = () => { + timeout = null; + + if (!immediate) { + func.apply(this, arguments); + } + }; + + const callNow = immediate && !timeout; + + clearTimeout(timeout); + + timeout = setTimeout(later, wait || 0); + + if (callNow) { + func.appy(this, arguments); + } + }; +}; + +const SearchBar = ({ + delay, + onSearch, + className, + style, + placeholder, + searchText, + ...rest +}) => { + let input; + const debounceCallback = handleDebounce(() => { + onSearch(input.value); + }, delay); + + return ( + input = n } + type="text" + style={ style } + onKeyUp={ () => debounceCallback() } + className={ `form-control ${className}` } + placeholder={ placeholder || SearchBar.defaultProps.placeholder } + { ...rest } + /> + ); +}; + +SearchBar.propTypes = { + onSearch: PropTypes.func.isRequired, + className: PropTypes.string, + placeholder: PropTypes.string, + style: PropTypes.object, + delay: PropTypes.number, + searchText: PropTypes.string +}; + +SearchBar.defaultProps = { + className: '', + style: {}, + placeholder: 'Search', + delay: 250, + searchText: '' +}; + +export default SearchBar; diff --git a/packages/react-bootstrap-table2-toolkit/src/search/context.js b/packages/react-bootstrap-table2-toolkit/src/search/context.js new file mode 100644 index 0000000..150612b --- /dev/null +++ b/packages/react-bootstrap-table2-toolkit/src/search/context.js @@ -0,0 +1,83 @@ +/* eslint react/prop-types: 0 */ +/* eslint react/require-default-props: 0 */ +/* eslint no-continue: 0 */ +import React from 'react'; +import PropTypes from 'prop-types'; + +export default (options = { + searchFormatted: false +}) => ( + _, + isRemoteSearch, + handleRemoteSearchChange +) => { + const SearchContext = React.createContext(); + + class SearchProvider extends React.Component { + static propTypes = { + data: PropTypes.array.isRequired, + columns: PropTypes.array.isRequired, + searchText: PropTypes.string + } + + constructor(props) { + super(props); + this.needToSearch = true; + } + + componentWillReceiveProps(nextProps) { + if (nextProps.searchText !== this.props.searchText) { + this.needToSearch = true; + } else { + this.needToSearch = false; + } + } + + search() { + const { data, columns } = this.props; + let { searchText } = this.props; + + if (!this.needToSearch) return data; + + if (isRemoteSearch()) { + handleRemoteSearchChange(searchText); + return data; + } + + searchText = searchText.toLowerCase(); + return data.filter((row, ridx) => { + for (let cidx = 0; cidx < columns.length; cidx += 1) { + const column = columns[cidx]; + if (column.searchable === false) continue; + let targetValue = _.get(row, column.dataField); + if (column.formatter && options.searchFormatted) { + targetValue = column.formatter(targetValue, row, ridx, column.formatExtraData); + } else if (column.filterValue) { + targetValue = column.filterValue(targetValue, row); + } + if (targetValue !== null && typeof targetValue !== 'undefined') { + targetValue = targetValue.toString().toLowerCase(); + if (targetValue.indexOf(searchText) > -1) { + return true; + } + } + } + return false; + }); + } + + render() { + const data = this.search(); + return ( + + { this.props.children } + + ); + } + } + + return { + Provider: SearchProvider, + Consumer: SearchContext.Consumer + }; +}; diff --git a/packages/react-bootstrap-table2-toolkit/src/search/index.js b/packages/react-bootstrap-table2-toolkit/src/search/index.js new file mode 100644 index 0000000..06fc1d7 --- /dev/null +++ b/packages/react-bootstrap-table2-toolkit/src/search/index.js @@ -0,0 +1,10 @@ +import SearchBar from './SearchBar'; +import createContext from './context'; + + +const searchFactory = ({ searchText, onSearch, ...options }) => ({ + createContext: createContext(options), + searchText +}); + +export default { SearchBar, searchFactory }; diff --git a/packages/react-bootstrap-table2/src/contexts/data-context.js b/packages/react-bootstrap-table2/src/contexts/data-context.js index 8452509..3394c7a 100644 --- a/packages/react-bootstrap-table2/src/contexts/data-context.js +++ b/packages/react-bootstrap-table2/src/contexts/data-context.js @@ -16,9 +16,10 @@ export default () => { this.setState(() => ({ data: nextProps.data })); } - getData = (filterProps, sortProps, paginationProps) => { + getData = (filterProps, searchProps, sortProps, paginationProps) => { if (paginationProps) return paginationProps.data; else if (sortProps) return sortProps.data; + else if (searchProps) return searchProps.data; else if (filterProps) return filterProps.data; return this.props.data; } diff --git a/packages/react-bootstrap-table2/src/contexts/index.js b/packages/react-bootstrap-table2/src/contexts/index.js index a7e80f5..031d699 100644 --- a/packages/react-bootstrap-table2/src/contexts/index.js +++ b/packages/react-bootstrap-table2/src/contexts/index.js @@ -37,6 +37,11 @@ const withContext = Base => this.PaginationContext = props.pagination.createContext( this.isRemotePagination, this.handleRemotePageChange); } + + if (props.search) { + this.SearchContext = props.search.createContext( + _, this.isRemoteSearch, this.handleRemoteSearchChange); + } } renderBase() { @@ -44,6 +49,7 @@ const withContext = Base => rootProps, cellEditProps, filterProps, + searchProps, sortProps, paginationProps, selectionProps @@ -54,8 +60,9 @@ const withContext = Base => { ...sortProps } { ...cellEditProps } { ...filterProps } + { ...searchProps } { ...paginationProps } - data={ rootProps.getData(filterProps, sortProps, paginationProps) } + data={ rootProps.getData(filterProps, searchProps, sortProps, paginationProps) } /> ); } @@ -65,13 +72,14 @@ const withContext = Base => rootProps, cellEditProps, filterProps, + searchProps, sortProps, paginationProps ) => ( { @@ -79,6 +87,7 @@ const withContext = Base => rootProps, cellEditProps, filterProps, + searchProps, sortProps, paginationProps, selectionProps @@ -94,12 +103,13 @@ const withContext = Base => rootProps, cellEditProps, filterProps, + searchProps, sortProps ) => ( this.paginationContext = n } pagination={ this.props.pagination } - data={ rootProps.getData(filterProps, sortProps) } + data={ rootProps.getData(filterProps, searchProps, sortProps) } > { @@ -107,6 +117,7 @@ const withContext = Base => rootProps, cellEditProps, filterProps, + searchProps, sortProps, paginationProps ) @@ -120,14 +131,15 @@ const withContext = Base => return ( rootProps, cellEditProps, - filterProps + filterProps, + searchProps ) => ( this.sortContext = n } defaultSorted={ this.props.defaultSorted } defaultSortDirection={ this.props.defaultSortDirection } - data={ rootProps.getData(filterProps) } + data={ rootProps.getData(filterProps, searchProps) } > { @@ -135,7 +147,8 @@ const withContext = Base => rootProps, cellEditProps, filterProps, - sortProps + searchProps, + sortProps, ) } @@ -143,6 +156,32 @@ const withContext = Base => ); } + renderWithSearchCtx(base, baseProps) { + return ( + rootProps, + cellEditProps, + filterProps + ) => ( + this.searchContext = n } + data={ rootProps.getData(filterProps) } + searchText={ this.props.search.searchText } + > + + { + searchProps => base( + rootProps, + cellEditProps, + filterProps, + searchProps + ) + } + + + ); + } + renderWithFilterCtx(base, baseProps) { return ( rootProps, @@ -201,6 +240,10 @@ const withContext = Base => base = this.renderWithSortCtx(base, baseProps); } + if (this.SearchContext) { + base = this.renderWithSearchCtx(base, baseProps); + } + if (this.FilterContext) { base = this.renderWithFilterCtx(base, baseProps); } diff --git a/packages/react-bootstrap-table2/src/props-resolver/remote-resolver.js b/packages/react-bootstrap-table2/src/props-resolver/remote-resolver.js index 62cdb7a..e942ac5 100644 --- a/packages/react-bootstrap-table2/src/props-resolver/remote-resolver.js +++ b/packages/react-bootstrap-table2/src/props-resolver/remote-resolver.js @@ -7,6 +7,7 @@ export default ExtendBase => let sortField; let page; let sizePerPage; + let searchText; let filters = {}; if (this.sortContext) { @@ -25,17 +26,27 @@ export default ExtendBase => sizePerPage = this.paginationContext.currSizePerPage; } + if (this.searchContext) { + searchText = this.searchContext.props.searchText; + } + return { sortOrder, sortField, filters, page, sizePerPage, + searchText, ...state, data: this.props.data }; } + isRemoteSearch = () => { + const { remote } = this.props; + return remote === true || (_.isObject(remote) && remote.search); + } + isRemotePagination = () => { const { remote } = this.props; return remote === true || (_.isObject(remote) && remote.pagination); @@ -77,4 +88,8 @@ export default ExtendBase => const cellEdit = { rowId, dataField, newValue }; this.props.onTableChange('cellEdit', this.getNewestState({ cellEdit })); } + + handleRemoteSearchChange = (searchText) => { + this.props.onTableChange('search', this.getNewestState({ searchText })); + } }; From 760d459414e6ceb842c39a0b3ac5405a958d13de Mon Sep 17 00:00:00 2001 From: AllenFang Date: Mon, 18 Jun 2018 13:03:16 +0800 Subject: [PATCH 24/55] add example for tble search --- .../.storybook/webpack.config.js | 2 + .../examples/remote/remote-search.js | 168 ++++++++++++++++++ .../examples/search/custom-search-value.js | 106 +++++++++++ .../examples/search/default-custom-search.js | 99 +++++++++++ .../examples/search/fully-custom-search.js | 121 +++++++++++++ .../examples/search/index.js | 87 +++++++++ .../examples/search/search-formatted.js | 91 ++++++++++ .../stories/index.js | 16 ++ .../stories/stylesheet/search/_index.scss | 3 + .../stories/stylesheet/storybook.scss | 1 + 10 files changed, 694 insertions(+) create mode 100644 packages/react-bootstrap-table2-example/examples/remote/remote-search.js create mode 100644 packages/react-bootstrap-table2-example/examples/search/custom-search-value.js create mode 100644 packages/react-bootstrap-table2-example/examples/search/default-custom-search.js create mode 100644 packages/react-bootstrap-table2-example/examples/search/fully-custom-search.js create mode 100644 packages/react-bootstrap-table2-example/examples/search/index.js create mode 100644 packages/react-bootstrap-table2-example/examples/search/search-formatted.js create mode 100644 packages/react-bootstrap-table2-example/stories/stylesheet/search/_index.scss diff --git a/packages/react-bootstrap-table2-example/.storybook/webpack.config.js b/packages/react-bootstrap-table2-example/.storybook/webpack.config.js index a2c12e2..a3995c8 100644 --- a/packages/react-bootstrap-table2-example/.storybook/webpack.config.js +++ b/packages/react-bootstrap-table2-example/.storybook/webpack.config.js @@ -8,6 +8,7 @@ const editorSourcePath = path.join(__dirname, '../../react-bootstrap-table2-edit const sourceStylePath = path.join(__dirname, '../../react-bootstrap-table2/style'); const paginationStylePath = path.join(__dirname, '../../react-bootstrap-table2-paginator/style'); const filterStylePath = path.join(__dirname, '../../react-bootstrap-table2-filter/style'); +const toolkitSourcePath = path.join(__dirname, '../../react-bootstrap-table2-toolkit/index.js'); const storyPath = path.join(__dirname, '../stories'); const examplesPath = path.join(__dirname, '../examples'); const srcPath = path.join(__dirname, '../src'); @@ -23,6 +24,7 @@ const aliasPath = { 'react-bootstrap-table2-filter': filterSourcePath, 'react-bootstrap-table2-overlay': overlaySourcePath, 'react-bootstrap-table2-paginator': paginationSourcePath, + 'react-bootstrap-table2-toolkit': toolkitSourcePath }; const loaders = [{ diff --git a/packages/react-bootstrap-table2-example/examples/remote/remote-search.js b/packages/react-bootstrap-table2-example/examples/remote/remote-search.js new file mode 100644 index 0000000..78a2151 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/remote/remote-search.js @@ -0,0 +1,168 @@ +/* eslint guard-for-in: 0 */ +/* eslint no-restricted-syntax: 0 */ +import React from 'react'; +import PropTypes from 'prop-types'; +import BootstrapTable from 'react-bootstrap-table-next'; +import ToolkitContext, { Search } from 'react-bootstrap-table2-toolkit'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const { SearchBar, searchFactory } = Search; +const products = productsGenerator(17); + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; +import filterFactory, { textFilter } from 'react-bootstrap-table2-filter'; + +const columns = [{ + dataField: 'id', + text: 'Product ID', +}, { + dataField: 'name', + text: 'Product Name', + filter: textFilter() +}, { + dataField: 'price', + text: 'Product Price', + filter: textFilter() +}]; + +const RemoteFilter = props => ( +
+ + { sourceCode } +
+); + +class Container extends React.Component { + constructor(props) { + super(props); + this.state = { + data: products + }; + } + + handleTableChange = (type, { filters }) => { + setTimeout(() => { + const result = products.filter((row) => { + let valid = true; + for (const dataField in filters) { + const { filterVal, filterType, comparator } = filters[dataField]; + + if (filterType === 'TEXT') { + if (comparator === Comparator.LIKE) { + valid = row[dataField].toString().indexOf(filterVal) > -1; + } else { + valid = row[dataField] === filterVal; + } + } + if (!valid) break; + } + return valid; + }); + this.setState(() => ({ + data: result + })); + }, 2000); + } + + render() { + return ( + + ); + } +} +`; + +const RemoteFilter = props => ( +
+ + + { + toolkitprops => [ + , + + ] + } + + + { sourceCode } +
+); + +RemoteFilter.propTypes = { + data: PropTypes.array.isRequired, + onTableChange: PropTypes.func.isRequired +}; + +class Container extends React.Component { + constructor(props) { + super(props); + this.state = { + data: products + }; + } + + handleTableChange = (type, { searchText }) => { + console.log('table change'); + setTimeout(() => { + const result = products.filter((row) => { + for (let cidx = 0; cidx < columns.length; cidx += 1) { + const column = columns[cidx]; + let targetValue = row[column.dataField]; + if (targetValue !== null && typeof targetValue !== 'undefined') { + targetValue = targetValue.toString().toLowerCase(); + if (targetValue.indexOf(searchText) > -1) { + return true; + } + } + } + return false; + }); + this.setState(() => ({ + data: result + })); + }, 2000); + } + + render() { + return ( + + ); + } +} + +export default Container; diff --git a/packages/react-bootstrap-table2-example/examples/search/custom-search-value.js b/packages/react-bootstrap-table2-example/examples/search/custom-search-value.js new file mode 100644 index 0000000..06530a8 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/search/custom-search-value.js @@ -0,0 +1,106 @@ +/* eslint react/prop-types: 0 */ +/* eslint no-unused-vars: 0 */ +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table-next'; +import ToolkitContext, { Search } from 'react-bootstrap-table2-toolkit'; +import Code from 'components/common/code-block'; +import { jobsGenerator1 } from 'utils/common'; + +const { SearchBar, searchFactory } = Search; +const products = jobsGenerator1(5); + +const owners = ['Allen', 'Bob', 'Cat']; +const types = ['Cloud Service', 'Message Service', 'Add Service', 'Edit Service', 'Money']; + +const columns = [{ + dataField: 'id', + text: 'Job ID', + searchable: false, + hidden: true +}, { + dataField: 'owner', + text: 'Job Owner', + formatter: (cell, row) => owners[cell], + filterValue: (cell, row) => owners[cell] // we will search the value after filterValue called +}, { + dataField: 'type', + text: 'Job Type', + formatter: (cell, row) => types[cell], + filterValue: (cell, row) => types[cell] // we will search the value after filterValue called +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; +import ToolkitContext, { Search } from 'react-bootstrap-table2-toolkit'; + +const { SearchBar, searchFactory } = Search; +const owners = ['Allen', 'Bob', 'Cat']; +const types = ['Cloud Service', 'Message Service', 'Add Service', 'Edit Service', 'Money']; + +const columns = [{ + dataField: 'id', + text: 'Job ID', + searchable: false, + hidden: true +}, { + dataField: 'owner', + text: 'Job Owner', + formatter: (cell, row) => owners[cell], + filterValue: (cell, row) => owners[cell] // we will search the value after filterValue called +}, { + dataField: 'type', + text: 'Job Type', + formatter: (cell, row) => types[cell], + filterValue: (cell, row) => types[cell] // we will search the value after filterValue called +}]; + + + + { + props => ( +
+

Input something at below input field:

+ +
+ +
+ ) + } +
+
+`; + +export default () => ( +
+ + + { + props => ( +
+

Try to Search Bob, Cat or Allen instead of 0, 1 or 2

+ +
+ +
+ ) + } +
+
+ { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/search/default-custom-search.js b/packages/react-bootstrap-table2-example/examples/search/default-custom-search.js new file mode 100644 index 0000000..567110b --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/search/default-custom-search.js @@ -0,0 +1,99 @@ +/* eslint react/prop-types: 0 */ +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table-next'; +import ToolkitContext, { Search } from 'react-bootstrap-table2-toolkit'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const { SearchBar, searchFactory } = Search; +const products = productsGenerator(); + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; +import ToolkitContext, { Search } from 'react-bootstrap-table2-toolkit'; + +const { SearchBar, searchFactory } = Search; +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + + + + { + props => ( +
+

Input something at below input field:

+ +
+ +
+ ) + } +
+
+`; + +export default () => ( +
+ + + { + props => ( +
+

Input something at below input field:

+ +
+ +
+ ) + } +
+
+ { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/search/fully-custom-search.js b/packages/react-bootstrap-table2-example/examples/search/fully-custom-search.js new file mode 100644 index 0000000..eb96b47 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/search/fully-custom-search.js @@ -0,0 +1,121 @@ +/* eslint react/prop-types: 0 */ +/* eslint no-return-assign: 0 */ +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table-next'; +import ToolkitContext, { Search } from 'react-bootstrap-table2-toolkit'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(); +const { searchFactory } = Search; + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; +import ToolkitContext, { Search } from 'react-bootstrap-table2-toolkit'; + +const { searchFactory } = Search; +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const MySearch = (props) => { + let input; + const handleClick = () => { + props.onSearch(input.value); + }; + return ( +
+ input = n } + type="text" + /> + +
+ ); +}; + + + + { + props => ( +
+ + +
+ ) + } +
+
+`; + +const MySearch = (props) => { + let input; + const handleClick = () => { + props.onSearch(input.value); + }; + return ( +
+ input = n } + type="text" + /> + +
+ ); +}; + +export default () => ( +
+ + + { + props => ( +
+ + +
+
+ ) + } +
+
+ { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/search/index.js b/packages/react-bootstrap-table2-example/examples/search/index.js new file mode 100644 index 0000000..17ac96b --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/search/index.js @@ -0,0 +1,87 @@ +/* eslint react/prop-types: 0 */ +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table-next'; +import ToolkitContext, { Search } from 'react-bootstrap-table2-toolkit'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const { SearchBar, searchFactory } = Search; +const products = productsGenerator(); + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; +import ToolkitContext, { Search } from 'react-bootstrap-table2-toolkit'; + +const { SearchBar, searchFactory } = Search; +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + + + + { + props => ( +
+

Input something at below input field:

+ +
+ +
+ ) + } +
+
+`; + +export default () => ( +
+ + + { + props => ( +
+

Input something at below input field:

+ +
+ +
+ ) + } +
+
+ { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/search/search-formatted.js b/packages/react-bootstrap-table2-example/examples/search/search-formatted.js new file mode 100644 index 0000000..97ab4af --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/search/search-formatted.js @@ -0,0 +1,91 @@ +/* eslint react/prop-types: 0 */ +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table-next'; +import ToolkitContext, { Search } from 'react-bootstrap-table2-toolkit'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const { SearchBar, searchFactory } = Search; +const products = productsGenerator(); + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price', + formatter: cell => `USD ${cell}` +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; +import ToolkitContext, { Search } from 'react-bootstrap-table2-toolkit'; + +const { SearchBar, searchFactory } = Search; +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price', + formatter: cell => \`USD \${cell}\` // we will search the data after formatted +}]; + + + + { + props => ( +
+

Input something at below input field:

+ +
+ +
+ ) + } +
+
+`; + +export default () => ( +
+ + + { + props => ( +
+

Try to Search USD at below input field:

+ +
+ +
+ ) + } +
+
+ { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/stories/index.js b/packages/react-bootstrap-table2-example/stories/index.js index 8f319f5..f3572fe 100644 --- a/packages/react-bootstrap-table2-example/stories/index.js +++ b/packages/react-bootstrap-table2-example/stories/index.js @@ -118,6 +118,13 @@ import PaginationTable from 'examples/pagination'; import PaginationHooksTable from 'examples/pagination/pagination-hooks'; import CustomPaginationTable from 'examples/pagination/custom-pagination'; +// search +import SearchTable from 'examples/search'; +import DefaultCustomSearch from 'examples/search/default-custom-search'; +import FullyCustomSearch from 'examples/search/fully-custom-search'; +import SearchFormattedData from 'examples/search/search-formatted'; +import CustomSearchValue from 'examples/search/custom-search-value'; + // loading overlay import EmptyTableOverlay from 'examples/loading-overlay/empty-table-overlay'; import TableOverlay from 'examples/loading-overlay/table-overlay'; @@ -126,6 +133,7 @@ import TableOverlay from 'examples/loading-overlay/table-overlay'; import RemoteSort from 'examples/remote/remote-sort'; import RemoteFilter from 'examples/remote/remote-filter'; import RemotePaginationTable from 'examples/remote/remote-pagination'; +import RemoteSearch from 'examples/remote/remote-search'; import RemoteCellEdit from 'examples/remote/remote-celledit'; import RemoteAll from 'examples/remote/remote-all'; @@ -258,6 +266,13 @@ storiesOf('Pagination', module) .add('Pagination Hooks', () => ) .add('Custom Pagination', () => ); +storiesOf('Table Search', module) + .add('Basic Search Table', () => ) + .add('Default Custom Search', () => ) + .add('Fully Custom Search', () => ) + .add('Search Fromatted Value', () => ) + .add('Custom Search Value', () => ); + storiesOf('EmptyTableOverlay', module) .add('Empty Table Overlay', () => ) .add('Table Overlay', () => ); @@ -266,5 +281,6 @@ storiesOf('Remote', module) .add('Remote Sort', () => ) .add('Remote Filter', () => ) .add('Remote Pagination', () => ) + .add('Remote Search', () => ) .add('Remote Cell Editing', () => ) .add('Remote All', () => ); diff --git a/packages/react-bootstrap-table2-example/stories/stylesheet/search/_index.scss b/packages/react-bootstrap-table2-example/stories/stylesheet/search/_index.scss new file mode 100644 index 0000000..8c47ed2 --- /dev/null +++ b/packages/react-bootstrap-table2-example/stories/stylesheet/search/_index.scss @@ -0,0 +1,3 @@ +.custome-search-field { + background-color: #c8e6c9; +} \ No newline at end of file diff --git a/packages/react-bootstrap-table2-example/stories/stylesheet/storybook.scss b/packages/react-bootstrap-table2-example/stories/stylesheet/storybook.scss index c38e02c..14959a6 100644 --- a/packages/react-bootstrap-table2-example/stories/stylesheet/storybook.scss +++ b/packages/react-bootstrap-table2-example/stories/stylesheet/storybook.scss @@ -10,4 +10,5 @@ @import "row-selection/index"; @import "rows/index"; @import "sort/index"; +@import "search/index"; @import "loading-overlay/index"; From 0d4d32c6e45d92d60643c6c5f7e1416ae3600a8e Mon Sep 17 00:00:00 2001 From: AllenFang Date: Sat, 23 Jun 2018 12:57:41 +0800 Subject: [PATCH 25/55] patch for remote cell edit --- .../examples/remote/remote-all.js | 53 ++++++++++++++++--- 1 file changed, 46 insertions(+), 7 deletions(-) diff --git a/packages/react-bootstrap-table2-example/examples/remote/remote-all.js b/packages/react-bootstrap-table2-example/examples/remote/remote-all.js index 95107e1..1b479f5 100644 --- a/packages/react-bootstrap-table2-example/examples/remote/remote-all.js +++ b/packages/react-bootstrap-table2-example/examples/remote/remote-all.js @@ -4,11 +4,12 @@ import React from 'react'; import PropTypes from 'prop-types'; import BootstrapTable from 'react-bootstrap-table-next'; import paginationFactory from 'react-bootstrap-table2-paginator'; +import cellEditFactory from 'react-bootstrap-table2-editor'; import filterFactory, { textFilter, Comparator } from 'react-bootstrap-table2-filter'; import Code from 'components/common/code-block'; import { productsGenerator } from 'utils/common'; -const products = productsGenerator(87); +let products = productsGenerator(87); const columns = [{ dataField: 'id', @@ -31,6 +32,7 @@ const columns = [{ const sourceCode = `\ import BootstrapTable from 'react-bootstrap-table-next'; import paginationFactory from 'react-bootstrap-table2-paginator'; +import cellEditFactory from 'react-bootstrap-table2-editor'; import filterFactory, { textFilter, Comparator } from 'react-bootstrap-table2-filter'; // ... @@ -57,16 +59,21 @@ const defaultSorted = [{ order: 'desc' }]; +const cellEditProps = { + mode: 'click' +}; + const RemoteAll = ({ data, page, sizePerPage, onTableChange, totalSize }) => (
{ sourceCode } @@ -93,11 +100,25 @@ class Container extends React.Component { this.handleTableChange = this.handleTableChange.bind(this); } - handleTableChange = (type, { page, sizePerPage, filters, sortField, sortOrder }) => { + handleTableChange = (type, { page, sizePerPage, filters, sortField, sortOrder, cellEdit }) => { const currentIndex = (page - 1) * sizePerPage; setTimeout(() => { + // Handle cell editing + if (type === 'cellEdit') { + const { rowId, dataField, newValue } = cellEdit; + products = products.map((row) => { + if (row.id === rowId) { + const newRow = { ...row }; + newRow[dataField] = newValue; + return newRow; + } + return row; + }); + } + let result = products; + // Handle column filters - let result = products.filter((row) => { + result = result.filter((row) => { let valid = true; for (const dataField in filters) { const { filterVal, filterType, comparator } = filters[dataField]; @@ -162,12 +183,16 @@ const defaultSorted = [{ order: 'desc' }]; +const cellEditProps = { + mode: 'click' +}; + const RemoteAll = ({ data, page, sizePerPage, onTableChange, totalSize }) => (

When remote.pagination is enabled, the filtering, sorting and searching will also change to remote mode automatically

( filter={ filterFactory() } pagination={ paginationFactory({ page, sizePerPage, totalSize }) } onTableChange={ onTableChange } + cellEdit={ cellEditFactory(cellEditProps) } /> { sourceCode }
@@ -200,11 +226,24 @@ class Container extends React.Component { this.handleTableChange = this.handleTableChange.bind(this); } - handleTableChange = (type, { page, sizePerPage, filters, sortField, sortOrder }) => { + handleTableChange = (type, { page, sizePerPage, filters, sortField, sortOrder, cellEdit }) => { const currentIndex = (page - 1) * sizePerPage; setTimeout(() => { + // Handle cell editing + if (type === 'cellEdit') { + const { rowId, dataField, newValue } = cellEdit; + products = products.map((row) => { + if (row.id === rowId) { + const newRow = { ...row }; + newRow[dataField] = newValue; + return newRow; + } + return row; + }); + } + let result = products; // Handle column filters - let result = products.filter((row) => { + result = result.filter((row) => { let valid = true; for (const dataField in filters) { const { filterVal, filterType, comparator } = filters[dataField]; From 7b15bf45d9b34b2d1d02c4190ee56f39400bef37 Mon Sep 17 00:00:00 2001 From: AllenFang Date: Sat, 23 Jun 2018 14:24:15 +0800 Subject: [PATCH 26/55] patch tests for search --- .../examples/remote/remote-search.js | 1 - .../src/props-resolver/remote-resolver.js | 2 +- .../test/contexts/index.test.js | 32 ++++++++++ .../props-resolver/remote-resolver.test.js | 62 +++++++++++++++++++ 4 files changed, 95 insertions(+), 2 deletions(-) diff --git a/packages/react-bootstrap-table2-example/examples/remote/remote-search.js b/packages/react-bootstrap-table2-example/examples/remote/remote-search.js index 78a2151..7bad91a 100644 --- a/packages/react-bootstrap-table2-example/examples/remote/remote-search.js +++ b/packages/react-bootstrap-table2-example/examples/remote/remote-search.js @@ -134,7 +134,6 @@ class Container extends React.Component { } handleTableChange = (type, { searchText }) => { - console.log('table change'); setTimeout(() => { const result = products.filter((row) => { for (let cidx = 0; cidx < columns.length; cidx += 1) { diff --git a/packages/react-bootstrap-table2/src/props-resolver/remote-resolver.js b/packages/react-bootstrap-table2/src/props-resolver/remote-resolver.js index e942ac5..f0b1a59 100644 --- a/packages/react-bootstrap-table2/src/props-resolver/remote-resolver.js +++ b/packages/react-bootstrap-table2/src/props-resolver/remote-resolver.js @@ -44,7 +44,7 @@ export default ExtendBase => isRemoteSearch = () => { const { remote } = this.props; - return remote === true || (_.isObject(remote) && remote.search); + return remote === true || (_.isObject(remote) && remote.search) || this.isRemotePagination(); } isRemotePagination = () => { diff --git a/packages/react-bootstrap-table2/test/contexts/index.test.js b/packages/react-bootstrap-table2/test/contexts/index.test.js index 8b00719..1236b24 100644 --- a/packages/react-bootstrap-table2/test/contexts/index.test.js +++ b/packages/react-bootstrap-table2/test/contexts/index.test.js @@ -134,6 +134,38 @@ describe('Context', () => { }); }); + describe('if search is enable', () => { + beforeEach(() => { + const SearchContext = React.createContext(); + const search = { + createContext: jest.fn().mockReturnValue({ + Provider: SearchContext.Provider, + Consumer: SearchContext.Consumer + }), + searchText: '' + }; + wrapper = shallow( + + ); + wrapper.render(); + }); + + it('should create contexts correctly', () => { + expect(wrapper.instance().DataContext).toBeDefined(); + expect(wrapper.instance().SearchContext).toBeDefined(); + expect(wrapper.instance().SortContext).not.toBeDefined(); + expect(wrapper.instance().SelectionContext).not.toBeDefined(); + expect(wrapper.instance().CellEditContext).not.toBeDefined(); + expect(wrapper.instance().FilterContext).not.toBeDefined(); + expect(wrapper.instance().PaginationContext).not.toBeDefined(); + }); + }); + describe('if column filter is enable', () => { beforeEach(() => { const FilterContext = React.createContext(); diff --git a/packages/react-bootstrap-table2/test/props-resolver/remote-resolver.test.js b/packages/react-bootstrap-table2/test/props-resolver/remote-resolver.test.js index 31c8dfc..d3de234 100644 --- a/packages/react-bootstrap-table2/test/props-resolver/remote-resolver.test.js +++ b/packages/react-bootstrap-table2/test/props-resolver/remote-resolver.test.js @@ -186,6 +186,48 @@ describe('remoteResolver', () => { }); }); + describe('isRemoteSearch', () => { + describe('when remote is false', () => { + beforeEach(() => { + shallowContainer(); + }); + + it('should return false', () => { + expect(wrapper.instance().isRemoteSearch()).toBeFalsy(); + }); + }); + + describe('when remote is true', () => { + beforeEach(() => { + shallowContainer({ remote: true }); + }); + + it('should return true', () => { + expect(wrapper.instance().isRemoteSearch()).toBeTruthy(); + }); + }); + + describe('when remote.search is true', () => { + beforeEach(() => { + shallowContainer({ remote: { search: true } }); + }); + + it('should return true', () => { + expect(wrapper.instance().isRemoteSearch()).toBeTruthy(); + }); + }); + + describe('when this.isRemotePagination return true', () => { + beforeEach(() => { + shallowContainer({ remote: { pagination: true } }); + }); + + it('should return true', () => { + expect(wrapper.instance().isRemoteSearch()).toBeTruthy(); + }); + }); + }); + describe('handleRemoteCellChange', () => { const onTableChangeCB = sinon.stub(); const rowId = 1; @@ -244,6 +286,26 @@ describe('remoteResolver', () => { }); }); + describe('handleRemoteSearchChange', () => { + const onTableChangeCB = sinon.stub(); + const searchText = 'abc'; + + beforeEach(() => { + onTableChangeCB.reset(); + shallowContainer({ + onTableChange: onTableChangeCB + }); + wrapper.instance().handleRemoteSearchChange(searchText); + }); + + it('should calling props.onTableChange correctly', () => { + expect(onTableChangeCB.calledOnce).toBeTruthy(); + expect(onTableChangeCB.calledWith('search', wrapper.instance().getNewestState({ + searchText + }))).toBeTruthy(); + }); + }); + describe('handleRemoteFilterChange', () => { const onTableChangeCB = sinon.stub(); const filters = { price: { filterVal: 20, filterType: 'TEXT' } }; From 18b785d655a757c820dc8a376ec2386f17993bf4 Mon Sep 17 00:00:00 2001 From: AllenFang Date: Sat, 23 Jun 2018 14:27:23 +0800 Subject: [PATCH 27/55] update peer dependencies for react react-dom --- packages/react-bootstrap-table2-editor/package.json | 4 ++-- packages/react-bootstrap-table2-filter/package.json | 4 ++-- packages/react-bootstrap-table2-overlay/package.json | 4 ++-- packages/react-bootstrap-table2-paginator/package.json | 4 ++-- packages/react-bootstrap-table2-toolkit/package.json | 4 ++-- packages/react-bootstrap-table2/package.json | 4 ++-- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/react-bootstrap-table2-editor/package.json b/packages/react-bootstrap-table2-editor/package.json index f42e3df..ffdd996 100644 --- a/packages/react-bootstrap-table2-editor/package.json +++ b/packages/react-bootstrap-table2-editor/package.json @@ -41,7 +41,7 @@ ], "peerDependencies": { "prop-types": "^15.0.0", - "react": "^16.0.0", - "react-dom": "^16.0.0" + "react": "^16.3.0", + "react-dom": "^16.3.0" } } diff --git a/packages/react-bootstrap-table2-filter/package.json b/packages/react-bootstrap-table2-filter/package.json index e223d02..55bc235 100644 --- a/packages/react-bootstrap-table2-filter/package.json +++ b/packages/react-bootstrap-table2-filter/package.json @@ -38,7 +38,7 @@ ], "peerDependencies": { "prop-types": "^15.0.0", - "react": "^16.0.0", - "react-dom": "^16.0.0" + "react": "^16.3.0", + "react-dom": "^16.3.0" } } diff --git a/packages/react-bootstrap-table2-overlay/package.json b/packages/react-bootstrap-table2-overlay/package.json index cc8a25d..66fa40e 100644 --- a/packages/react-bootstrap-table2-overlay/package.json +++ b/packages/react-bootstrap-table2-overlay/package.json @@ -41,7 +41,7 @@ }, "peerDependencies": { "prop-types": "^15.0.0", - "react": "^16.0.0", - "react-dom": "^16.0.0" + "react": "^16.3.0", + "react-dom": "^16.3.0" } } diff --git a/packages/react-bootstrap-table2-paginator/package.json b/packages/react-bootstrap-table2-paginator/package.json index 6beb303..cf56c83 100644 --- a/packages/react-bootstrap-table2-paginator/package.json +++ b/packages/react-bootstrap-table2-paginator/package.json @@ -38,7 +38,7 @@ ], "peerDependencies": { "prop-types": "^15.0.0", - "react": "^16.0.0", - "react-dom": "^16.0.0" + "react": "^16.3.0", + "react-dom": "^16.3.0" } } diff --git a/packages/react-bootstrap-table2-toolkit/package.json b/packages/react-bootstrap-table2-toolkit/package.json index e0b9ad9..297d2c2 100644 --- a/packages/react-bootstrap-table2-toolkit/package.json +++ b/packages/react-bootstrap-table2-toolkit/package.json @@ -34,7 +34,7 @@ ], "peerDependencies": { "prop-types": "^15.0.0", - "react": "^16.0.0", - "react-dom": "^16.0.0" + "react": "^16.3.0", + "react-dom": "^16.3.0" } } diff --git a/packages/react-bootstrap-table2/package.json b/packages/react-bootstrap-table2/package.json index 802e04d..9b4e569 100644 --- a/packages/react-bootstrap-table2/package.json +++ b/packages/react-bootstrap-table2/package.json @@ -41,7 +41,7 @@ "peerDependencies": { "classnames": "^2.2.5", "prop-types": "^15.0.0", - "react": "^16.0.0", - "react-dom": "^16.0.0" + "react": "^16.3.0", + "react-dom": "^16.3.0" } } From 46f0ce493b77aa849e99ac5de982bff534c0a5f2 Mon Sep 17 00:00:00 2001 From: AllenFang Date: Sat, 23 Jun 2018 15:10:00 +0800 Subject: [PATCH 28/55] prepare builds for react-bootstrap-table2-toolkit --- gulpfile.babel.js | 6 ++++-- .../react-bootstrap-table2-toolkit/package.json | 7 +++++++ webpack/toolkit.umd.babel.js | 16 ++++++++++++++++ 3 files changed, 27 insertions(+), 2 deletions(-) create mode 100644 webpack/toolkit.umd.babel.js diff --git a/gulpfile.babel.js b/gulpfile.babel.js index f202fda..228bfa3 100644 --- a/gulpfile.babel.js +++ b/gulpfile.babel.js @@ -17,7 +17,8 @@ const JS_PKGS = [ 'react-bootstrap-table2-editor', 'react-bootstrap-table2-filter', 'react-bootstrap-table2-overlay', - 'react-bootstrap-table2-paginator' + 'react-bootstrap-table2-paginator', + 'react-bootstrap-table2-toolkit' ].reduce((pkg, curr) => `${curr}|${pkg}`, ''); const JS_SKIPS = `+(${TEST}|${LIB}|${DIST}|${NODE_MODULES})`; @@ -78,7 +79,8 @@ function umd(done) { () => gulp.src('./webpack/editor.umd.babel.js').pipe(shell(['webpack --config <%= file.path %>'])), () => gulp.src('./webpack/filter.umd.babel.js').pipe(shell(['webpack --config <%= file.path %>'])), () => gulp.src('./webpack/overlay.umd.babel.js').pipe(shell(['webpack --config <%= file.path %>'])), - () => gulp.src('./webpack/paginator.umd.babel.js').pipe(shell(['webpack --config <%= file.path %>'])) + () => gulp.src('./webpack/paginator.umd.babel.js').pipe(shell(['webpack --config <%= file.path %>'])), + () => gulp.src('./webpack/toolkit.umd.babel.js').pipe(shell(['webpack --config <%= file.path %>'])) )(); done(); } diff --git a/packages/react-bootstrap-table2-toolkit/package.json b/packages/react-bootstrap-table2-toolkit/package.json index 297d2c2..efdbc64 100644 --- a/packages/react-bootstrap-table2-toolkit/package.json +++ b/packages/react-bootstrap-table2-toolkit/package.json @@ -10,6 +10,13 @@ "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, + "files": [ + "lib/", + "dist/" + ], + "tags": [ + "react" + ], "author": "AllenFang", "license": "MIT", "keywords": [ diff --git a/webpack/toolkit.umd.babel.js b/webpack/toolkit.umd.babel.js new file mode 100644 index 0000000..677eacc --- /dev/null +++ b/webpack/toolkit.umd.babel.js @@ -0,0 +1,16 @@ +import * as path from 'path'; +import umdConfig from './webpack.umd.babel'; + +module.exports = { + ...umdConfig, + entry: { + 'react-bootstrap-table2-toolkit/dist/react-bootstrap-table2-toolkit': './packages/react-bootstrap-table2-toolkit/index.js', + 'react-bootstrap-table2-toolkit/dist/react-bootstrap-table2-toolkit.min': './packages/react-bootstrap-table2-toolkit/index.js' + }, + output: { + path: path.join(__dirname, '../packages'), + filename: '[name].js', + library: 'ReactBootstrapTable2Toolkit', + libraryTarget: 'umd' + } +}; From 6eaffe19933f852eca3136aec572933c2bcacc62 Mon Sep 17 00:00:00 2001 From: AllenFang Date: Sat, 23 Jun 2018 15:10:37 +0800 Subject: [PATCH 29/55] patch docs for table search --- docs/README.md | 41 +++++++++++ docs/migration.md | 24 ++++++- .../react-bootstrap-table2-toolkit/README.md | 71 +++++++++++++++++++ 3 files changed, 135 insertions(+), 1 deletion(-) create mode 100644 packages/react-bootstrap-table2-toolkit/README.md diff --git a/docs/README.md b/docs/README.md index 14782fe..6112dcd 100644 --- a/docs/README.md +++ b/docs/README.md @@ -28,6 +28,7 @@ * [defaultSortDirection](#defaultSortDirection) * [pagination](#pagination) * [filter](#filter) +* [search](#search) * [onTableChange](#onTableChange) ### keyField(**required**) - [String] @@ -260,6 +261,46 @@ const columns = [ { ``` +### search - [Object] +Enable the search functionality. + +`search` allow user to searhc all the table data. However, search functionality is separated from core of `react-bootstrap-table2` so that you are suppose to install `react-bootstrap-table2-toolkit` firstly. + +```sh +$ npm install react-bootstrap-table2-toolkit --save +``` + +After installation of `react-bootstrap-table2-toolkit`, you can render search field easily: + +```js +import ToolkitContext, { Search } from 'react-bootstrap-table2-toolkit'; + +const { SearchBar, searchFactory } = Search; +//... + + + + { + props => ( +
+

Input something at below input field:

+ +
+ +
+ ) + } +
+
+``` + ### onTableChange - [Function] This callback function will be called when [`remote`](#remote) enabled only. diff --git a/docs/migration.md b/docs/migration.md index 32d137d..c369afd 100644 --- a/docs/migration.md +++ b/docs/migration.md @@ -113,6 +113,28 @@ Remember to install [`react-bootstrap-table2-paginator`](https://www.npmjs.com/p No big changes for pagination, but still can't custom the pagination list, button and sizePerPage dropdown. +## Table Search +the usage of search functionality is a little bit different from legacy search. The mainly different thing is developer have to render the search input field, we do believe it will be very flexible for all the developers who want to custom the search position or search field itself. + +- [x] Custom search component and position +- [x] Custom search value +- [ ] Clear search +- [ ] Multiple search +- [ ] Strict search + ## Remote -> It's totally different in `react-bootstrap-table2`. Please [see](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/basic-remote.html). \ No newline at end of file +> It's totally different in `react-bootstrap-table2`. Please [see](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/basic-remote.html). + + +## Row insert/Delete +Not support yet + +## Expand row +Not support yet + +## Keyboard Navigation +Not support yet + +## Export CSV +Not support yet \ No newline at end of file diff --git a/packages/react-bootstrap-table2-toolkit/README.md b/packages/react-bootstrap-table2-toolkit/README.md new file mode 100644 index 0000000..f7dc601 --- /dev/null +++ b/packages/react-bootstrap-table2-toolkit/README.md @@ -0,0 +1,71 @@ +# react-bootstrap-table2-toolkit + +`react-bootstrap-table2` support some additional features in [`react-bootstrap-table2-toolkit`](https://github.com/react-bootstrap-table/react-bootstrap-table2/tree/develop/packages/react-bootstrap-table2-toolkit). + +In the future, this toolkit will support other feature like row delete, insert and export csv etc. Right now we only support Table Search. + +**[Live Demo For Table Search](https://react-bootstrap-table.github.io/react-bootstrap-table2/storybook/index.html?selectedKind=Table%20Search)** + +**[API&Props Definitation](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/pagination-props.html)** + +----- + +## Install + +```sh +$ npm install react-bootstrap-table2-toolkit --save +``` + +## Table Search + +```js +import ToolkitContext, { Search } from 'react-bootstrap-table2-toolkit'; + +const { SearchBar, searchFactory } = Search; +//... + + + + { + props => ( +
+

Input something at below input field:

+ +
+ +
+ ) + } +
+
+``` + +1. You need to enable the search functionality via `search` prop on `BootstrapTable` and pass the result of calling `searchFactory` with custom option and default `searchProps` provided by `ToolkitContext.Provider` + +2. `ToolkitContext` is a react context, you are supposed to wrap the `BootstrapTable` and `SearchBar` as the child of `ToolkitContext.Consumer` + +3. You should render `SearchBar` with `searchProps` as well. + +### Options + +# searchFormatted - [bool] +If you want to search on the formatted data, you are supposed to enable it. `react-bootstrap-table2` will check if you define the `column.formatter` when doing search. + +```js + +``` \ No newline at end of file From 35b1e3794073df52ebd90b71f14c3136b806422c Mon Sep 17 00:00:00 2001 From: AllenFang Date: Sun, 24 Jun 2018 13:12:23 +0800 Subject: [PATCH 30/55] implement expand row sketch --- packages/react-bootstrap-table2/src/body.js | 26 +++++++- .../src/bootstrap-table.js | 9 +++ .../src/contexts/index.js | 46 +++++++++++++- .../src/contexts/row-expand-context.js | 62 +++++++++++++++++++ .../src/props-resolver/column-resolver.js | 7 ++- .../src/props-resolver/expand-row-resolver.js | 15 +++++ .../src/props-resolver/index.js | 4 +- .../src/row-event-delegater.js | 23 ++++--- .../src/row-expand/expand-row.js | 18 ++++++ .../style/react-bootstrap-table2.scss | 4 ++ 10 files changed, 198 insertions(+), 16 deletions(-) create mode 100644 packages/react-bootstrap-table2/src/contexts/row-expand-context.js create mode 100644 packages/react-bootstrap-table2/src/props-resolver/expand-row-resolver.js create mode 100644 packages/react-bootstrap-table2/src/row-expand/expand-row.js diff --git a/packages/react-bootstrap-table2/src/body.js b/packages/react-bootstrap-table2/src/body.js index b4ca400..7b29989 100644 --- a/packages/react-bootstrap-table2/src/body.js +++ b/packages/react-bootstrap-table2/src/body.js @@ -7,6 +7,7 @@ import cs from 'classnames'; import _ from './utils'; import Row from './row'; +import ExpandRow from './row-expand/expand-row'; import RowSection from './row-section'; import Const from './const'; @@ -23,7 +24,8 @@ const Body = (props) => { selectedRowKeys, rowStyle, rowClasses, - rowEvents + rowEvents, + expandRow } = props; const { @@ -74,8 +76,10 @@ const Body = (props) => { } const selectable = !nonSelectable || !nonSelectable.includes(key); + const expandable = expandRow && !expandRow.nonExpandable.includes(key); + const expanded = expandRow && expandRow.expanded.includes(key); - return ( + const result = [ { cellEdit={ cellEdit } editable={ editable } selectable={ selectable } + expandable={ expandable } selected={ selected } + expanded={ expanded } selectRow={ selectRow } + expandRow={ expandRow } style={ style } className={ classes } attrs={ attrs } /> - ); + ]; + + if (expanded) { + result.push(( + + { expandRow.renderer(row) } + + )); + } + + return result; }); } diff --git a/packages/react-bootstrap-table2/src/bootstrap-table.js b/packages/react-bootstrap-table2/src/bootstrap-table.js index 5f8e5c4..7f3a16c 100644 --- a/packages/react-bootstrap-table2/src/bootstrap-table.js +++ b/packages/react-bootstrap-table2/src/bootstrap-table.js @@ -97,6 +97,7 @@ class BootstrapTable extends PropsBaseResolver(Component) { cellEdit={ this.props.cellEdit || {} } selectRow={ cellSelectionInfo } selectedRowKeys={ selected } + expandRow={ this.resolveExpandRowProps() } rowStyle={ rowStyle } rowClasses={ rowClasses } rowEvents={ rowEvents } @@ -145,6 +146,14 @@ BootstrapTable.propTypes = { }), onRowSelect: PropTypes.func, onAllRowsSelect: PropTypes.func, + expandRow: PropTypes.shape({ + renderer: PropTypes.func.isRequired, + expanded: PropTypes.array, + nonExpandable: PropTypes.array, + showExpandColumn: PropTypes.bool, + expandColumnRenderer: PropTypes.func + }), + onRowExpand: PropTypes.func, rowStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.func]), rowEvents: PropTypes.object, rowClasses: PropTypes.oneOfType([PropTypes.string, PropTypes.func]), diff --git a/packages/react-bootstrap-table2/src/contexts/index.js b/packages/react-bootstrap-table2/src/contexts/index.js index 031d699..d434478 100644 --- a/packages/react-bootstrap-table2/src/contexts/index.js +++ b/packages/react-bootstrap-table2/src/contexts/index.js @@ -5,6 +5,7 @@ import _ from '../utils'; import createDataContext from './data-context'; import createSortContext from './sort-context'; import createSelectionContext from './selection-context'; +import createRowExpandContext from './row-expand-context'; import remoteResolver from '../props-resolver/remote-resolver'; import dataOperator from '../store/operators'; @@ -23,6 +24,10 @@ const withContext = Base => this.SelectionContext = createSelectionContext(dataOperator); } + if (props.expandRow) { + this.RowExpandContext = createRowExpandContext(dataOperator); + } + if (props.cellEdit && props.cellEdit.createContext) { this.CellEditContext = props.cellEdit.createContext( _, dataOperator, this.isRemoteCellEdit, this.handleRemoteCellChange); @@ -52,6 +57,7 @@ const withContext = Base => searchProps, sortProps, paginationProps, + expandProps, selectionProps ) => ( { ...filterProps } { ...searchProps } { ...paginationProps } + { ...expandProps } data={ rootProps.getData(filterProps, searchProps, sortProps, paginationProps) } /> ); @@ -74,7 +81,8 @@ const withContext = Base => filterProps, searchProps, sortProps, - paginationProps + paginationProps, + expandProps ) => ( searchProps, sortProps, paginationProps, + expandProps, selectionProps ) } @@ -98,6 +107,37 @@ const withContext = Base => ); } + renderWithRowExpandCtx(base, baseProps) { + return ( + rootProps, + cellEditProps, + filterProps, + searchProps, + sortProps, + paginationProps + ) => ( + + + { + expandProps => base( + rootProps, + cellEditProps, + filterProps, + searchProps, + sortProps, + paginationProps, + expandProps + ) + } + + + ); + } + renderWithPaginationCtx(base) { return ( rootProps, @@ -232,6 +272,10 @@ const withContext = Base => base = this.renderWithSelectionCtx(base, baseProps); } + if (this.RowExpandContext) { + base = this.renderWithRowExpandCtx(base, baseProps); + } + if (this.PaginationContext) { base = this.renderWithPaginationCtx(base, baseProps); } diff --git a/packages/react-bootstrap-table2/src/contexts/row-expand-context.js b/packages/react-bootstrap-table2/src/contexts/row-expand-context.js new file mode 100644 index 0000000..d43d55a --- /dev/null +++ b/packages/react-bootstrap-table2/src/contexts/row-expand-context.js @@ -0,0 +1,62 @@ +/* eslint react/prop-types: 0 */ +import React from 'react'; +import PropTypes from 'prop-types'; + +export default ( + dataOperator +) => { + const RowExpandContext = React.createContext(); + + class RowExpandProvider extends React.Component { + static propTypes = { + children: PropTypes.node.isRequired, + data: PropTypes.array.isRequired, + keyField: PropTypes.string.isRequired + } + + state = { expanded: this.props.expandRow.expanded || [] }; + + componentWillReceiveProps(nextProps) { + if (nextProps.expandRow) { + this.setState(() => ({ + expanded: nextProps.expandRow.expanded || this.state.expanded + })); + } + } + + handleRowExpand = (rowKey, expanded, rowIndex, e) => { + const { data, keyField, expandRow: { onExpand } } = this.props; + + let currExpanded = [...this.state.expanded]; + + if (expanded) { + currExpanded.push(rowKey); + } else { + currExpanded = currExpanded.filter(value => value !== rowKey); + } + + if (onExpand) { + const row = dataOperator.getRowByRowId(data, keyField, rowKey); + onExpand(row, expanded, rowIndex, e); + } + this.setState(() => ({ expanded: currExpanded })); + } + + render() { + return ( + + { this.props.children } + + ); + } + } + return { + Provider: RowExpandProvider, + Consumer: RowExpandContext.Consumer + }; +}; diff --git a/packages/react-bootstrap-table2/src/props-resolver/column-resolver.js b/packages/react-bootstrap-table2/src/props-resolver/column-resolver.js index c330c7d..11fb652 100644 --- a/packages/react-bootstrap-table2/src/props-resolver/column-resolver.js +++ b/packages/react-bootstrap-table2/src/props-resolver/column-resolver.js @@ -1,10 +1,13 @@ export default ExtendBase => class ColumnResolver extends ExtendBase { visibleColumnSize(includeSelectColumn = true) { - const columnLen = this.props.columns.filter(c => !c.hidden).length; + let columnLen = this.props.columns.filter(c => !c.hidden).length; if (!includeSelectColumn) return columnLen; if (this.props.selectRow && !this.props.selectRow.hideSelectColumn) { - return columnLen + 1; + columnLen += 1; + } + if (this.props.expandRow && this.props.expandRow.showExpandColumn) { + columnLen += 1; } return columnLen; } diff --git a/packages/react-bootstrap-table2/src/props-resolver/expand-row-resolver.js b/packages/react-bootstrap-table2/src/props-resolver/expand-row-resolver.js new file mode 100644 index 0000000..c959776 --- /dev/null +++ b/packages/react-bootstrap-table2/src/props-resolver/expand-row-resolver.js @@ -0,0 +1,15 @@ +export default ExtendBase => + class ExpandRowResolver extends ExtendBase { + resolveExpandRowProps() { + const { expandRow, expanded, onRowExpand } = this.props; + if (expandRow) { + return { + ...expandRow, + expanded, + onRowExpand, + nonExpandable: expandRow.nonExpandable || [] + }; + } + return null; + } + }; diff --git a/packages/react-bootstrap-table2/src/props-resolver/index.js b/packages/react-bootstrap-table2/src/props-resolver/index.js index be81f50..2154ce3 100644 --- a/packages/react-bootstrap-table2/src/props-resolver/index.js +++ b/packages/react-bootstrap-table2/src/props-resolver/index.js @@ -1,9 +1,11 @@ import ColumnResolver from './column-resolver'; +import ExpandRowResolver from './expand-row-resolver'; import Const from '../const'; import _ from '../utils'; export default ExtendBase => - class TableResolver extends ColumnResolver(ExtendBase) { + class TableResolver extends + ExpandRowResolver(ColumnResolver(ExtendBase)) { validateProps() { const { keyField } = this.props; if (!keyField) { diff --git a/packages/react-bootstrap-table2/src/row-event-delegater.js b/packages/react-bootstrap-table2/src/row-event-delegater.js index 60d8a62..283ffd6 100644 --- a/packages/react-bootstrap-table2/src/row-event-delegater.js +++ b/packages/react-bootstrap-table2/src/row-event-delegater.js @@ -1,4 +1,5 @@ import _ from './utils'; +import Const from './const'; const events = [ 'onClick', @@ -30,11 +31,11 @@ export default ExtendBase => selected, keyField, selectable, + expandable, rowIndex, - selectRow: { - onRowSelect, - clickToEdit - }, + expanded, + expandRow, + selectRow, cellEdit: { mode, DBCLICK_TO_CELL_EDIT, @@ -46,13 +47,16 @@ export default ExtendBase => if (cb) { cb(e, row, rowIndex); } - if (selectable) { - const key = _.get(row, keyField); - onRowSelect(key, !selected, rowIndex, e); + const key = _.get(row, keyField); + if (expandRow && expandable) { + expandRow.onRowExpand(key, !expanded, rowIndex, e); + } + if (selectRow.mode !== Const.ROW_SELECT_DISABLED && selectable) { + selectRow.onRowSelect(key, !selected, rowIndex, e); } }; - if (mode === DBCLICK_TO_CELL_EDIT && clickToEdit) { + if (mode === DBCLICK_TO_CELL_EDIT && selectRow.clickToEdit) { this.clickNum += 1; _.debounce(() => { if (this.clickNum === 1) { @@ -68,7 +72,8 @@ export default ExtendBase => delegate(attrs = {}) { const newAttrs = {}; - if (this.props.selectRow && this.props.selectRow.clickToSelect) { + const { expandRow, selectRow } = this.props; + if (expandRow || (selectRow && selectRow.clickToSelect)) { newAttrs.onClick = this.createClickEventHandler(attrs.onClick); } Object.keys(attrs).forEach((attr) => { diff --git a/packages/react-bootstrap-table2/src/row-expand/expand-row.js b/packages/react-bootstrap-table2/src/row-expand/expand-row.js new file mode 100644 index 0000000..0a89628 --- /dev/null +++ b/packages/react-bootstrap-table2/src/row-expand/expand-row.js @@ -0,0 +1,18 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +const ExpandRow = ({ children, ...rest }) => ( + + { children } + +); + +ExpandRow.propTypes = { + children: PropTypes.node +}; + +ExpandRow.defaultProps = { + children: null +}; + +export default ExpandRow; diff --git a/packages/react-bootstrap-table2/style/react-bootstrap-table2.scss b/packages/react-bootstrap-table2/style/react-bootstrap-table2.scss index 2476781..55a1a09 100644 --- a/packages/react-bootstrap-table2/style/react-bootstrap-table2.scss +++ b/packages/react-bootstrap-table2/style/react-bootstrap-table2.scss @@ -30,6 +30,10 @@ text-align: center; } + tr.expanding-row { + padding: 5px; + } + td.react-bootstrap-table-editing-cell { .animated { animation-fill-mode: both; From dbd0f89a3d354665baded34e9fbc129f5c7ffd53 Mon Sep 17 00:00:00 2001 From: AllenFang Date: Sun, 24 Jun 2018 13:12:42 +0800 Subject: [PATCH 31/55] add stories for expand row --- .../examples/row-expand/expand-management.js | 138 ++++++++++++++++++ .../examples/row-expand/index.js | 72 +++++++++ .../row-expand/non-expandable-rows.js | 75 ++++++++++ .../src/utils/common.js | 14 ++ .../stories/index.js | 10 ++ 5 files changed, 309 insertions(+) create mode 100644 packages/react-bootstrap-table2-example/examples/row-expand/expand-management.js create mode 100644 packages/react-bootstrap-table2-example/examples/row-expand/index.js create mode 100644 packages/react-bootstrap-table2-example/examples/row-expand/non-expandable-rows.js diff --git a/packages/react-bootstrap-table2-example/examples/row-expand/expand-management.js b/packages/react-bootstrap-table2-example/examples/row-expand/expand-management.js new file mode 100644 index 0000000..83b1588 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/row-expand/expand-management.js @@ -0,0 +1,138 @@ +/* eslint no-unused-vars: 0 */ +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table-next'; +import Code from 'components/common/code-block'; +import { productsExpandRowsGenerator } from 'utils/common'; + +const products = productsExpandRowsGenerator(); + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +class RowExpandManagment extends React.Component { + constructor(props) { + super(props); + this.state = { expanded: [0, 1] }; + } + + handleBtnClick = () => { + if (!this.state.expanded.includes(2)) { + this.setState(() => ({ + expanded: [...this.state.expanded, 2] + })); + } else { + this.setState(() => ({ + expanded: this.state.expanded.filter(x => x !== 2) + })); + } + } + + handleOnExpand = (row, isExpand, rowIndex, e) => { + if (isExpand) { + this.setState(() => ({ + expanded: [...this.state.expanded, row.id] + })); + } else { + this.setState(() => ({ + expanded: this.state.expanded.filter(x => x !== row.id) + })); + } + } + + render() { + const expandRow = { + renderer: row => ( +
+

{ \`This Expand row is belong to rowKey $\{row.id}\` }

+

You can render anything here, also you can add additional data on every row object

+

expandRow.renderer callback will pass the origin row object to you

+
+ ), + expanded: this.state.expanded, + onExpand: this.handleOnExpand + }; + return ( +
+ + + { sourceCode } +
+ ); + } +} +`; + +export default class RowExpandManagment extends React.Component { + constructor(props) { + super(props); + this.state = { expanded: [0, 1] }; + } + + handleBtnClick = () => { + if (!this.state.expanded.includes(2)) { + this.setState(() => ({ + expanded: [...this.state.expanded, 2] + })); + } else { + this.setState(() => ({ + expanded: this.state.expanded.filter(x => x !== 2) + })); + } + } + + handleOnExpand = (row, isExpand, rowIndex, e) => { + if (isExpand) { + this.setState(() => ({ + expanded: [...this.state.expanded, row.id] + })); + } else { + this.setState(() => ({ + expanded: this.state.expanded.filter(x => x !== row.id) + })); + } + } + + render() { + const expandRow = { + renderer: row => ( +
+

{ `This Expand row is belong to rowKey ${row.id}` }

+

You can render anything here, also you can add additional data on every row object

+

expandRow.renderer callback will pass the origin row object to you

+
+ ), + expanded: this.state.expanded, + onExpand: this.handleOnExpand + }; + return ( +
+ + + { sourceCode } +
+ ); + } +} diff --git a/packages/react-bootstrap-table2-example/examples/row-expand/index.js b/packages/react-bootstrap-table2-example/examples/row-expand/index.js new file mode 100644 index 0000000..0a08904 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/row-expand/index.js @@ -0,0 +1,72 @@ +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table-next'; +import Code from 'components/common/code-block'; +import { productsExpandRowsGenerator } from 'utils/common'; + +const products = productsExpandRowsGenerator(); + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const expandRow = { + renderer: row => ( +
+

{ `This Expand row is belong to rowKey ${row.id}` }

+

You can render anything here, also you can add additional data on every row object

+

expandRow.renderer callback will pass the origin row object to you

+
+ ) +}; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const expandRow = { + renderer: row => ( +
+

{ \`This Expand row is belong to rowKey $\{row.id}\` }

+

You can render anything here, also you can add additional data on every row object

+

expandRow.renderer callback will pass the origin row object to you

+
+ ) +}; + + +`; + +export default () => ( +
+ + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/row-expand/non-expandable-rows.js b/packages/react-bootstrap-table2-example/examples/row-expand/non-expandable-rows.js new file mode 100644 index 0000000..6d1ef32 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/row-expand/non-expandable-rows.js @@ -0,0 +1,75 @@ +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table-next'; +import Code from 'components/common/code-block'; +import { productsExpandRowsGenerator } from 'utils/common'; + +const products = productsExpandRowsGenerator(); + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const expandRow = { + renderer: row => ( +
+

{ `This Expand row is belong to rowKey ${row.id}` }

+

You can render anything here, also you can add additional data on every row object

+

expandRow.renderer callback will pass the origin row object to you

+
+ ), + nonExpandable: [1, 3] +}; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const expandRow = { + renderer: row => ( +
+

{ \`This Expand row is belong to rowKey $\{row.id}\` }

+

You can render anything here, also you can add additional data on every row object

+

expandRow.renderer callback will pass the origin row object to you

+
+ ), + nonExpandable: [1, 3] +}; + + +`; + +export default () => ( +
+

The second and fourth row is not expandable

+ + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/src/utils/common.js b/packages/react-bootstrap-table2-example/src/utils/common.js index 037e639..4b7c325 100644 --- a/packages/react-bootstrap-table2-example/src/utils/common.js +++ b/packages/react-bootstrap-table2-example/src/utils/common.js @@ -68,3 +68,17 @@ export const stockGenerator = (quantity = 5) => })); export const sleep = ms => new Promise(resolve => setTimeout(resolve, ms)); + +export const productsExpandRowsGenerator = (quantity = 5, callback) => { + if (callback) return Array.from({ length: quantity }, callback); + + // if no given callback, retrun default product format. + return ( + Array.from({ length: quantity }, (value, index) => ({ + id: index, + name: `Item name ${index}`, + price: 2100 + index, + expand: productsQualityGenerator(index) + })) + ); +}; \ No newline at end of file diff --git a/packages/react-bootstrap-table2-example/stories/index.js b/packages/react-bootstrap-table2-example/stories/index.js index f3572fe..6d174ef 100644 --- a/packages/react-bootstrap-table2-example/stories/index.js +++ b/packages/react-bootstrap-table2-example/stories/index.js @@ -113,6 +113,11 @@ import SelectionBgColorTable from 'examples/row-selection/selection-bgcolor'; import SelectionHooks from 'examples/row-selection/selection-hooks'; import HideSelectionColumnTable from 'examples/row-selection/hide-selection-column'; +// work on row expand +import BasicRowExpand from 'examples/row-expand'; +import RowExpandManagement from 'examples/row-expand/expand-management'; +import NonExpandableRows from 'examples/row-expand/non-expandable-rows'; + // pagination import PaginationTable from 'examples/pagination'; import PaginationHooksTable from 'examples/pagination/pagination-hooks'; @@ -261,6 +266,11 @@ storiesOf('Row Selection', module) .add('Selection Hooks', () => ) .add('Hide Selection Column', () => ); +storiesOf('Row Expand', module) + .add('Basic Row Expand', () => ) + .add('Expand Management', () => ) + .add('Non Expandabled Rows', () => ); + storiesOf('Pagination', module) .add('Basic Pagination Table', () => ) .add('Pagination Hooks', () => ) From 4af5b4f6efefabcd0417337dd91546179f7f0bb6 Mon Sep 17 00:00:00 2001 From: AllenFang Date: Sun, 1 Jul 2018 15:20:07 +0800 Subject: [PATCH 32/55] implement expand indicator --- .../src/bootstrap-table.js | 9 +++- .../src/contexts/row-expand-context.js | 31 +++++++++++++- packages/react-bootstrap-table2/src/header.js | 15 ++++++- .../src/props-resolver/expand-row-resolver.js | 4 +- .../src/row-expand/expand-cell.js | 42 +++++++++++++++++++ .../src/row-expand/expand-header-cell.js | 40 ++++++++++++++++++ packages/react-bootstrap-table2/src/row.js | 14 +++++++ .../src/store/expand.js | 28 +++++++++++++ .../src/store/operators.js | 2 + 9 files changed, 179 insertions(+), 6 deletions(-) create mode 100644 packages/react-bootstrap-table2/src/row-expand/expand-cell.js create mode 100644 packages/react-bootstrap-table2/src/row-expand/expand-header-cell.js create mode 100644 packages/react-bootstrap-table2/src/store/expand.js diff --git a/packages/react-bootstrap-table2/src/bootstrap-table.js b/packages/react-bootstrap-table2/src/bootstrap-table.js index 7f3a16c..e3dd58d 100644 --- a/packages/react-bootstrap-table2/src/bootstrap-table.js +++ b/packages/react-bootstrap-table2/src/bootstrap-table.js @@ -72,6 +72,7 @@ class BootstrapTable extends PropsBaseResolver(Component) { }); const tableCaption = (caption && { caption }); + const expandRow = this.resolveExpandRowProps(); return (
@@ -86,6 +87,7 @@ class BootstrapTable extends PropsBaseResolver(Component) { onFilter={ this.props.onFilter } onExternalFilter={ this.props.onExternalFilter } selectRow={ headerCellSelectionInfo } + expandRow={ expandRow } /> ({ expanded: currExpanded })); } + handleAllRowExpand = (e, expandAll) => { + const { + data, + keyField, + expandRow: { + onExpandAll, + nonExpandable + } + } = this.props; + const { expanded } = this.state; + + let currExpanded; + + if (expandAll) { + currExpanded = expanded.concat(dataOperator.expandableKeys(data, keyField, nonExpandable)); + } else { + currExpanded = expanded.filter(s => typeof data.find(d => d[keyField] === s) === 'undefined'); + } + + if (onExpandAll) { + onExpandAll(expandAll, dataOperator.getExpandedRows(data, keyField, currExpanded), e); + } + + this.setState(() => ({ expanded: currExpanded })); + } + render() { + const { data, keyField } = this.props; return ( { this.props.children } diff --git a/packages/react-bootstrap-table2/src/header.js b/packages/react-bootstrap-table2/src/header.js index 0b8ee80..67568ea 100644 --- a/packages/react-bootstrap-table2/src/header.js +++ b/packages/react-bootstrap-table2/src/header.js @@ -5,6 +5,7 @@ import Const from './const'; import HeaderCell from './header-cell'; import SelectionHeaderCell from './row-selection/selection-header-cell'; +import ExpandHeaderCell from './row-expand/expand-header-cell'; const Header = (props) => { const { ROW_SELECT_DISABLED } = Const; @@ -17,12 +18,21 @@ const Header = (props) => { sortField, sortOrder, selectRow, - onExternalFilter + onExternalFilter, + expandRow } = props; return ( + { + (expandRow && expandRow.showExpandColumn) + ? : null + } { (selectRow.mode !== ROW_SELECT_DISABLED && !selectRow.hideSelectColumn) ? : null @@ -62,7 +72,8 @@ Header.propTypes = { sortOrder: PropTypes.string, selectRow: PropTypes.object, onExternalFilter: PropTypes.func, - className: PropTypes.string + className: PropTypes.string, + expandRow: PropTypes.object }; export default Header; diff --git a/packages/react-bootstrap-table2/src/props-resolver/expand-row-resolver.js b/packages/react-bootstrap-table2/src/props-resolver/expand-row-resolver.js index c959776..015e321 100644 --- a/packages/react-bootstrap-table2/src/props-resolver/expand-row-resolver.js +++ b/packages/react-bootstrap-table2/src/props-resolver/expand-row-resolver.js @@ -1,12 +1,14 @@ export default ExtendBase => class ExpandRowResolver extends ExtendBase { resolveExpandRowProps() { - const { expandRow, expanded, onRowExpand } = this.props; + const { expandRow, expanded, onRowExpand, onAllRowExpand, isAnyExpands } = this.props; if (expandRow) { return { ...expandRow, expanded, onRowExpand, + onAllRowExpand, + isAnyExpands, nonExpandable: expandRow.nonExpandable || [] }; } diff --git a/packages/react-bootstrap-table2/src/row-expand/expand-cell.js b/packages/react-bootstrap-table2/src/row-expand/expand-cell.js new file mode 100644 index 0000000..3df5b1c --- /dev/null +++ b/packages/react-bootstrap-table2/src/row-expand/expand-cell.js @@ -0,0 +1,42 @@ +/* eslint + react/require-default-props: 0 + jsx-a11y/no-noninteractive-element-interactions: 0 +*/ +/* eslint no-nested-ternary: 0 */ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; + +export default class ExpandCell extends Component { + static propTypes = { + rowKey: PropTypes.any, + expanded: PropTypes.bool.isRequired, + onRowExpand: PropTypes.func.isRequired, + expandColumnRenderer: PropTypes.func, + rowIndex: PropTypes.number + } + + constructor() { + super(); + this.handleClick = this.handleClick.bind(this); + } + + handleClick(e) { + const { rowKey, expanded, onRowExpand, rowIndex } = this.props; + + onRowExpand(rowKey, expanded, rowIndex, e); + } + + render() { + const { expanded, expandColumnRenderer } = this.props; + + return ( + + { + expandColumnRenderer ? expandColumnRenderer({ + expanded + }) : (expanded ? '(-)' : '(+)') + } + + ); + } +} diff --git a/packages/react-bootstrap-table2/src/row-expand/expand-header-cell.js b/packages/react-bootstrap-table2/src/row-expand/expand-header-cell.js new file mode 100644 index 0000000..65c099b --- /dev/null +++ b/packages/react-bootstrap-table2/src/row-expand/expand-header-cell.js @@ -0,0 +1,40 @@ +/* eslint react/require-default-props: 0 */ +/* eslint no-nested-ternary: 0 */ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; + +export default class SelectionHeaderCell extends Component { + static propTypes = { + anyExpands: PropTypes.bool.isRequired, + onAllRowExpand: PropTypes.func.isRequired, + renderer: PropTypes.func + } + + constructor() { + super(); + this.handleCheckBoxClick = this.handleCheckBoxClick.bind(this); + } + + handleCheckBoxClick(e) { + const { anyExpands, onAllRowExpand } = this.props; + + onAllRowExpand(e, !anyExpands); + } + + render() { + const { anyExpands, renderer } = this.props; + const attrs = { + onClick: this.handleCheckBoxClick + }; + + return ( + + { + renderer ? + renderer({ isAnyExpands: anyExpands }) : + (anyExpands ? '(-)' : '(+)') + } + + ); + } +} diff --git a/packages/react-bootstrap-table2/src/row.js b/packages/react-bootstrap-table2/src/row.js index b6222bb..1196716 100644 --- a/packages/react-bootstrap-table2/src/row.js +++ b/packages/react-bootstrap-table2/src/row.js @@ -6,6 +6,7 @@ import PropTypes from 'prop-types'; import _ from './utils'; import Cell from './cell'; import SelectionCell from './row-selection/selection-cell'; +import ExpandCell from './row-expand/expand-cell'; import eventDelegater from './row-event-delegater'; import Const from './const'; @@ -22,6 +23,8 @@ class Row extends eventDelegater(Component) { cellEdit, selected, selectRow, + expanded, + expandRow, selectable, editable: editableRow } = this.props; @@ -39,10 +42,21 @@ class Row extends eventDelegater(Component) { const key = _.get(row, keyField); const { hideSelectColumn } = selectRow; + const { showExpandColumn } = expandRow || {}; const trAttrs = this.delegate(attrs); return ( + { + showExpandColumn ? ( + + ) : null + } { (selectRow.mode !== Const.ROW_SELECT_DISABLED && !hideSelectColumn) ? ( diff --git a/packages/react-bootstrap-table2/src/store/expand.js b/packages/react-bootstrap-table2/src/store/expand.js new file mode 100644 index 0000000..31feb0a --- /dev/null +++ b/packages/react-bootstrap-table2/src/store/expand.js @@ -0,0 +1,28 @@ +import _ from '../utils'; +import { getRowByRowId } from './rows'; + +export const isAnyExpands = ( + data, + keyField, + expanded = [] +) => { + for (let i = 0; i < data.length; i += 1) { + const rowKey = _.get(data[i], keyField); + if (typeof expanded.find(x => x === rowKey) !== 'undefined') { + return true; + } + } + return false; +}; + +export const expandableKeys = (data, keyField, skips = []) => { + if (skips.length === 0) { + return data.map(row => _.get(row, keyField)); + } + return data + .filter(row => !skips.includes(_.get(row, keyField))) + .map(row => _.get(row, keyField)); +}; + +export const getExpandedRows = (data, keyField, expanded) => + expanded.map(k => getRowByRowId(data, keyField, k)); diff --git a/packages/react-bootstrap-table2/src/store/operators.js b/packages/react-bootstrap-table2/src/store/operators.js index c01ddad..de055ee 100644 --- a/packages/react-bootstrap-table2/src/store/operators.js +++ b/packages/react-bootstrap-table2/src/store/operators.js @@ -1,11 +1,13 @@ import * as rows from './rows'; import * as selection from './selection'; +import * as expand from './expand'; import * as mutate from './mutate'; import * as sort from './sort'; export default { ...rows, ...selection, + ...expand, ...mutate, ...sort }; From 81ddd2c25b091004c0a9298a20cc3d806a9584d5 Mon Sep 17 00:00:00 2001 From: AllenFang Date: Sun, 1 Jul 2018 15:21:08 +0800 Subject: [PATCH 33/55] add stories for expand indicator --- .../row-expand/custom-expand-column.js | 107 ++++++++++++++++++ .../examples/row-expand/expand-column.js | 74 ++++++++++++ .../examples/row-expand/expand-hooks.js | 97 ++++++++++++++++ .../src/utils/common.js | 2 +- .../stories/index.js | 8 +- 5 files changed, 286 insertions(+), 2 deletions(-) create mode 100644 packages/react-bootstrap-table2-example/examples/row-expand/custom-expand-column.js create mode 100644 packages/react-bootstrap-table2-example/examples/row-expand/expand-column.js create mode 100644 packages/react-bootstrap-table2-example/examples/row-expand/expand-hooks.js diff --git a/packages/react-bootstrap-table2-example/examples/row-expand/custom-expand-column.js b/packages/react-bootstrap-table2-example/examples/row-expand/custom-expand-column.js new file mode 100644 index 0000000..067046d --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/row-expand/custom-expand-column.js @@ -0,0 +1,107 @@ +/* eslint react/prop-types: 0 */ +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table-next'; +import Code from 'components/common/code-block'; +import { productsExpandRowsGenerator } from 'utils/common'; + +const products = productsExpandRowsGenerator(); + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const expandRow = { + renderer: row => ( +
+

{ `This Expand row is belong to rowKey ${row.id}` }

+

You can render anything here, also you can add additional data on every row object

+

expandRow.renderer callback will pass the origin row object to you

+
+ ), + showExpandColumn: true, + expandHeaderColumnRenderer: ({ isAnyExpands }) => { + if (isAnyExpands) { + return -; + } + return +; + }, + expandColumnRenderer: ({ expanded }) => { + if (expanded) { + return ( + - + ); + } + return ( + ... + ); + } +}; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const expandRow = { + renderer: row => ( +
+

{ \`This Expand row is belong to rowKey $\{row.id}\` }

+

You can render anything here, also you can add additional data on every row object

+

expandRow.renderer callback will pass the origin row object to you

+
+ ), + showExpandColumn: true, + expandHeaderColumnRenderer: ({ isAnyExpands }) => { + if (isAnyExpands) { + return -; + } + return +; + }, + expandColumnRenderer: ({ expanded }) => { + if (expanded) { + return ( + - + ); + } + return ( + ... + ); + } +}; + + +`; + +export default () => ( +
+ + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/row-expand/expand-column.js b/packages/react-bootstrap-table2-example/examples/row-expand/expand-column.js new file mode 100644 index 0000000..edfff16 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/row-expand/expand-column.js @@ -0,0 +1,74 @@ +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table-next'; +import Code from 'components/common/code-block'; +import { productsExpandRowsGenerator } from 'utils/common'; + +const products = productsExpandRowsGenerator(); + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const expandRow = { + renderer: row => ( +
+

{ `This Expand row is belong to rowKey ${row.id}` }

+

You can render anything here, also you can add additional data on every row object

+

expandRow.renderer callback will pass the origin row object to you

+
+ ), + showExpandColumn: true +}; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const expandRow = { + renderer: row => ( +
+

{ \`This Expand row is belong to rowKey $\{row.id}\` }

+

You can render anything here, also you can add additional data on every row object

+

expandRow.renderer callback will pass the origin row object to you

+
+ ), + showExpandColumn: true +}; + + +`; + +export default () => ( +
+ + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/row-expand/expand-hooks.js b/packages/react-bootstrap-table2-example/examples/row-expand/expand-hooks.js new file mode 100644 index 0000000..96fcbfe --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/row-expand/expand-hooks.js @@ -0,0 +1,97 @@ +/* eslint no-console: 0 */ +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table-next'; +import Code from 'components/common/code-block'; +import { productsExpandRowsGenerator } from 'utils/common'; + +const products = productsExpandRowsGenerator(); + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const expandRow = { + renderer: row => ( +
+

{ `This Expand row is belong to rowKey ${row.id}` }

+

You can render anything here, also you can add additional data on every row object

+

expandRow.renderer callback will pass the origin row object to you

+
+ ), + showExpandColumn: true, + onExpand: (row, isExpand, rowIndex, e) => { + console.log(row.id); + console.log(isExpand); + console.log(rowIndex); + console.log(e); + }, + onExpandAll: (isExpandAll, rows, e) => { + console.log(isExpandAll); + console.log(rows); + console.log(e); + } +}; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const expandRow = { + renderer: row => ( +
+

{ \`This Expand row is belong to rowKey $\{row.id}\` }

+

You can render anything here, also you can add additional data on every row object

+

expandRow.renderer callback will pass the origin row object to you

+
+ ), + showExpandColumn: true, + onExpand: (row, isExpand, rowIndex, e) => { + console.log(row.id); + console.log(isExpand); + console.log(rowIndex); + console.log(e); + }, + onExpandAll: (isExpandAll, rows, e) => { + console.log(isExpandAll); + console.log(rows); + console.log(e); + } +}; + + +`; + +export default () => ( +
+ + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/src/utils/common.js b/packages/react-bootstrap-table2-example/src/utils/common.js index 4b7c325..dbf7b7e 100644 --- a/packages/react-bootstrap-table2-example/src/utils/common.js +++ b/packages/react-bootstrap-table2-example/src/utils/common.js @@ -81,4 +81,4 @@ export const productsExpandRowsGenerator = (quantity = 5, callback) => { expand: productsQualityGenerator(index) })) ); -}; \ No newline at end of file +}; diff --git a/packages/react-bootstrap-table2-example/stories/index.js b/packages/react-bootstrap-table2-example/stories/index.js index 6d174ef..d556b1f 100644 --- a/packages/react-bootstrap-table2-example/stories/index.js +++ b/packages/react-bootstrap-table2-example/stories/index.js @@ -117,6 +117,9 @@ import HideSelectionColumnTable from 'examples/row-selection/hide-selection-colu import BasicRowExpand from 'examples/row-expand'; import RowExpandManagement from 'examples/row-expand/expand-management'; import NonExpandableRows from 'examples/row-expand/non-expandable-rows'; +import ExpandColumn from 'examples/row-expand/expand-column'; +import CustomExpandColumn from 'examples/row-expand/custom-expand-column'; +import ExpandHooks from 'examples/row-expand/expand-hooks'; // pagination import PaginationTable from 'examples/pagination'; @@ -269,7 +272,10 @@ storiesOf('Row Selection', module) storiesOf('Row Expand', module) .add('Basic Row Expand', () => ) .add('Expand Management', () => ) - .add('Non Expandabled Rows', () => ); + .add('Non Expandabled Rows', () => ) + .add('Expand Indicator', () => ) + .add('Custom Expand Indicator', () => ) + .add('Expand Hooks', () => ); storiesOf('Pagination', module) .add('Basic Pagination Table', () => ) From c36aa24c6531a29f0e34b4adcb9debf3acaf8a34 Mon Sep 17 00:00:00 2001 From: AllenFang Date: Sun, 1 Jul 2018 16:17:01 +0800 Subject: [PATCH 34/55] fix filter context tests broken due to missing onExternalFilter --- packages/react-bootstrap-table2-filter/test/context.test.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/react-bootstrap-table2-filter/test/context.test.js b/packages/react-bootstrap-table2-filter/test/context.test.js index 2dc2cb8..f4b97ca 100644 --- a/packages/react-bootstrap-table2-filter/test/context.test.js +++ b/packages/react-bootstrap-table2-filter/test/context.test.js @@ -90,7 +90,8 @@ describe('FilterContext', () => { expect(wrapper.length).toBe(1); expect(mockBase).toHaveBeenCalledWith({ data, - onFilter: wrapper.instance().onFilter + onFilter: wrapper.instance().onFilter, + onExternalFilter: wrapper.instance().onExternalFilter }); }); }); @@ -106,7 +107,8 @@ describe('FilterContext', () => { expect(wrapper.length).toBe(1); expect(mockBase).toHaveBeenCalledWith({ data, - onFilter: wrapper.instance().onFilter + onFilter: wrapper.instance().onFilter, + onExternalFilter: wrapper.instance().onExternalFilter }); }); }); From a18932e9eb85441d6b7df55e55079fdb21d6c9d1 Mon Sep 17 00:00:00 2001 From: AllenFang Date: Sun, 1 Jul 2018 16:36:41 +0800 Subject: [PATCH 35/55] patch docs for expand row --- docs/README.md | 4 ++ docs/migration.md | 14 +++-- docs/row-expand.md | 129 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 143 insertions(+), 4 deletions(-) create mode 100644 docs/row-expand.md diff --git a/docs/README.md b/docs/README.md index 6112dcd..07b8ad2 100644 --- a/docs/README.md +++ b/docs/README.md @@ -21,6 +21,7 @@ * [headerClasses](#headerClasses) * [cellEdit](#cellEdit) * [selectRow](#selectRow) +* [expandRow](#expandRow) * [rowStyle](#rowStyle) * [rowClasses](#rowClasses) * [rowEvents](#rowEvents) @@ -123,6 +124,9 @@ Makes table cells editable, please see [cellEdit definition](./cell-edit.md) for ### selectRow - [Object] Makes table rows selectable, please see [selectRow definition](./row-selection.md) for more detail. +### expandRow - [Object] +Makes table rows expandable, please see [expandRow definition](./row-expand.md) for more detail. + ### rowStyle = [Object | Function] Custom the style of table rows: diff --git a/docs/migration.md b/docs/migration.md index c369afd..a215952 100644 --- a/docs/migration.md +++ b/docs/migration.md @@ -114,7 +114,7 @@ Remember to install [`react-bootstrap-table2-paginator`](https://www.npmjs.com/p No big changes for pagination, but still can't custom the pagination list, button and sizePerPage dropdown. ## Table Search -the usage of search functionality is a little bit different from legacy search. The mainly different thing is developer have to render the search input field, we do believe it will be very flexible for all the developers who want to custom the search position or search field itself. +ï¼´he usage of search functionality is a little bit different from legacy search. The mainly different thing is developer have to render the search input field, we do believe it will be very flexible for all the developers who want to custom the search position or search field itself. - [x] Custom search component and position - [x] Custom search value @@ -122,6 +122,15 @@ the usage of search functionality is a little bit different from legacy search. - [ ] Multiple search - [ ] Strict search +## Row Expand +- [x] Expand Row Events +- [x] Expand Row Indicator +- [x] Expand Row Management +- [x] Custom Expand Row Indicators +- [ ] Compatiable with Row Selection +- [ ] Expand Column position +- [ ] Expand Column Style/Class + ## Remote > It's totally different in `react-bootstrap-table2`. Please [see](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/basic-remote.html). @@ -130,9 +139,6 @@ the usage of search functionality is a little bit different from legacy search. ## Row insert/Delete Not support yet -## Expand row -Not support yet - ## Keyboard Navigation Not support yet diff --git a/docs/row-expand.md b/docs/row-expand.md new file mode 100644 index 0000000..1aa958a --- /dev/null +++ b/docs/row-expand.md @@ -0,0 +1,129 @@ + +# Row expand +`react-bootstrap-table2` supports the row expand feature. By passing prop `expandRow` to enable this functionality. + +> Default is click to expand/collapse a row. In addition, we don't support any way to chagne this mechanism! + +## Required +* [renderer (**required**)](#renderer) + +## Optional +* [expanded](#expanded) +* [nonExpandable](#nonExpandable) +* [onExpand](#onExpand) +* [onExpandAll](#onExpandAll) +* [showExpandColumn](#showExpandColumn) +* [expandColumnRenderer](#expandColumnRenderer) +* [expandHeaderColumnRenderer](#expandHeaderColumnRenderer) + +### expandRow.renderer - [Function] + +Specify the content of expand row, `react-bootstrap-table2` will pass a row object as argument and expect return a react element. + +#### values +* **row** + +#### examples + +```js +const expandRow = { + renderer: row => ( +
+

{ `This Expand row is belong to rowKey ${row.id}` }

+

You can render anything here, also you can add additional data on every row object

+

expandRow.renderer callback will pass the origin row object to you

+
+ ) +}; + + +``` + +### expandRow.expanded - [Array] +`expandRow.expanded` allow you have default row expandations on table. + +```js +const expandRow = { + renderer: (row) => ... + expanded: [1, 3] // should be a row keys array +}; +``` + +### expandRow.nonExpandable - [Array] +This prop allow you to restrict some rows which can not be expanded by user. `expandRow.nonExpandable` accept an rowkeys array. + +```js +const expandRow = { + renderer: (row) => ... + nonExpandable: [1, 3 ,5] +}; +``` + +### expandRow.onExpand - [Function] +This callback function will be called when a row is expand/collapse and pass following four arguments: +`row`, `isExpand`, `rowIndex` and `e`. + +```js +const expandRow = { + renderer: (row) => ... + onExpand: (row, isExpand, rowIndex, e) => { + // ... + } +}; +``` + +### expandRow.onExpandAll - [Function] +This callback function will be called when expand/collapse all. It only work when you configure [`expandRow.showExpandColumn`](#showExpandColumn) as `true`. + +```js +const expandRow = { + renderer: (row) => ... + onExpandAll: (isExpandAll, results, e) => { + // ... + } +}; +``` + +### expandRow.expandColumnRenderer - [Function] +Provide a callback function which allow you to custom the expand indicator. This callback only have one argument which is an object and contain one property `expanded` which indicate if current row is expanded + + +```js +const expandRow = { + renderer: (row) => ... + expandColumnRenderer: ({ expanded }) => ( + // .... + ) +}; +``` + +> By default, `react-bootstrap-table2` will help you to handle the click event, it's not necessary to handle again by developer. + +### expandRow.expandHeaderColumnRenderer - [Function] +Provide a callback function which allow you to custom the expand indicator in the expand header column. This callback only have one argument which is an object and contain one property `isAnyExpands` which indicate if there's any rows are expanded: + +```js +const expandRow = { + renderer: (row) => ... + expandHeaderColumnRenderer: ({ isAnyExpands }) => ( + // .... + ) +}; +``` + +> By default, `react-bootstrap-table2` will help you to handle the click event, it's not necessary to handle again by developer. + +### expandRow.showExpandColumn - [Bool] +Default is `false`, if you want to have a expand indicator, give this prop as `true` + +```js +const expandRow = { + renderer: (row) => ... + showExpandColumn: true +}; +``` From 5a442bf7eda6ac9e1654d6d3cc88fc5607756b0d Mon Sep 17 00:00:00 2001 From: AllenFang Date: Sun, 1 Jul 2018 16:37:00 +0800 Subject: [PATCH 36/55] add missing expandRow props --- packages/react-bootstrap-table2/src/bootstrap-table.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/react-bootstrap-table2/src/bootstrap-table.js b/packages/react-bootstrap-table2/src/bootstrap-table.js index e3dd58d..20f64f4 100644 --- a/packages/react-bootstrap-table2/src/bootstrap-table.js +++ b/packages/react-bootstrap-table2/src/bootstrap-table.js @@ -151,6 +151,8 @@ BootstrapTable.propTypes = { expandRow: PropTypes.shape({ renderer: PropTypes.func.isRequired, expanded: PropTypes.array, + onExpand: PropTypes.func, + onExpandAll: PropTypes.func, nonExpandable: PropTypes.array, showExpandColumn: PropTypes.bool, expandColumnRenderer: PropTypes.func, From e6d4a9641bf7bf3c5542d5ecd3fb69b0337efb84 Mon Sep 17 00:00:00 2001 From: AllenFang Date: Tue, 3 Jul 2018 23:26:57 +0800 Subject: [PATCH 37/55] data, keyField and columns is necessary value for toolkits context --- .../examples/remote/remote-search.js | 34 +++++++++++++------ .../examples/search/custom-search-value.js | 22 +++++++----- .../examples/search/default-custom-search.js | 20 ++++++----- .../examples/search/fully-custom-search.js | 21 +++++++----- .../examples/search/index.js | 20 ++++++----- .../examples/search/search-formatted.js | 24 +++++++------ .../react-bootstrap-table2-toolkit/context.js | 12 ++++++- .../src/contexts/index.js | 2 +- 8 files changed, 100 insertions(+), 55 deletions(-) diff --git a/packages/react-bootstrap-table2-example/examples/remote/remote-search.js b/packages/react-bootstrap-table2-example/examples/remote/remote-search.js index 7bad91a..1056229 100644 --- a/packages/react-bootstrap-table2-example/examples/remote/remote-search.js +++ b/packages/react-bootstrap-table2-example/examples/remote/remote-search.js @@ -40,15 +40,27 @@ const columns = [{ const RemoteFilter = props => (
- - { sourceCode } + > + + { + toolkitprops => [ + , + + ] + } + +
); @@ -97,16 +109,18 @@ class Container extends React.Component { const RemoteFilter = props => (
- + { toolkitprops => [ , types[cell] // we will search the value after filterValue called }]; - + { props => (
-

Input something at below input field:

+

Try to Search Bob, Cat or Allen instead of 0, 1 or 2


(
- + { props => ( @@ -89,9 +95,7 @@ export default () => (
+ { props => ( @@ -51,9 +55,7 @@ const columns = [{ />
(
- + { props => ( @@ -82,9 +88,7 @@ export default () => ( />
{ ); }; - + { props => (
+
) } @@ -96,15 +99,17 @@ const MySearch = (props) => { export default () => (
- + { props => (
+ { props => ( @@ -45,9 +49,7 @@ const columns = [{
(
- + { props => ( @@ -70,9 +76,7 @@ export default () => (
\`USD \${cell}\` // we will search the data after formatted }]; - + { props => (
-

Input something at below input field:

+

Try to Search USD at below input field:


@@ -64,7 +66,11 @@ const columns = [{ export default () => (
- + { props => ( @@ -73,9 +79,7 @@ export default () => (
{ this.props.children } diff --git a/packages/react-bootstrap-table2/src/contexts/index.js b/packages/react-bootstrap-table2/src/contexts/index.js index d434478..e95ea27 100644 --- a/packages/react-bootstrap-table2/src/contexts/index.js +++ b/packages/react-bootstrap-table2/src/contexts/index.js @@ -13,7 +13,7 @@ const withContext = Base => class BootstrapTableContainer extends remoteResolver(Component) { constructor(props) { super(props); - this.DataContext = createDataContext(props.data); + this.DataContext = createDataContext(); if (props.columns.filter(col => col.sort).length > 0) { this.SortContext = createSortContext( From 0ec5b6cb9f5fab1eca69f9f9ce991e9e97045ab4 Mon Sep 17 00:00:00 2001 From: AllenFang Date: Wed, 11 Jul 2018 23:35:41 +0800 Subject: [PATCH 38/55] add simple toolkit context wrapper for user --- docs/README.md | 42 ++++---- .../examples/remote/remote-search.js | 67 ++++++------ .../examples/search/custom-search-value.js | 76 +++++++------ .../examples/search/default-custom-search.js | 100 +++++++++--------- .../examples/search/fully-custom-search.js | 72 ++++++------- .../examples/search/index.js | 76 +++++++------ .../examples/search/search-formatted.js | 80 +++++++------- .../react-bootstrap-table2-toolkit/README.md | 12 +-- .../react-bootstrap-table2-toolkit/index.js | 6 +- .../provider.js | 19 ++++ 10 files changed, 272 insertions(+), 278 deletions(-) create mode 100644 packages/react-bootstrap-table2-toolkit/provider.js diff --git a/docs/README.md b/docs/README.md index 07b8ad2..ba9ea6d 100644 --- a/docs/README.md +++ b/docs/README.md @@ -277,32 +277,30 @@ $ npm install react-bootstrap-table2-toolkit --save After installation of `react-bootstrap-table2-toolkit`, you can render search field easily: ```js -import ToolkitContext, { Search } from 'react-bootstrap-table2-toolkit'; +import ToolkitProvider, { Search } from 'react-bootstrap-table2-toolkit'; const { SearchBar, searchFactory } = Search; //... - - - { - props => ( -
-

Input something at below input field:

- -
- -
- ) - } -
-
+ + { + props => ( +
+

Input something at below input field:

+ +
+ +
+ ) + } +
``` ### onTableChange - [Function] diff --git a/packages/react-bootstrap-table2-example/examples/remote/remote-search.js b/packages/react-bootstrap-table2-example/examples/remote/remote-search.js index 1056229..95fbf4e 100644 --- a/packages/react-bootstrap-table2-example/examples/remote/remote-search.js +++ b/packages/react-bootstrap-table2-example/examples/remote/remote-search.js @@ -3,7 +3,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import BootstrapTable from 'react-bootstrap-table-next'; -import ToolkitContext, { Search } from 'react-bootstrap-table2-toolkit'; +import ToolkitProvider, { Search } from 'react-bootstrap-table2-toolkit'; import Code from 'components/common/code-block'; import { productsGenerator } from 'utils/common'; @@ -23,6 +23,7 @@ const columns = [{ const sourceCode = `\ import BootstrapTable from 'react-bootstrap-table-next'; +import ToolkitProvider, { Search } from 'react-bootstrap-table2-toolkit'; import filterFactory, { textFilter } from 'react-bootstrap-table2-filter'; const columns = [{ @@ -40,27 +41,25 @@ const columns = [{ const RemoteFilter = props => (
- - - { - toolkitprops => [ - , - - ] - } - - + { + toolkitprops => [ + , + + ] + } +
); @@ -109,27 +108,25 @@ class Container extends React.Component { const RemoteFilter = props => (
- - - { - toolkitprops => [ - , - - ] - } - - + { + toolkitprops => [ + , + + ] + } + { sourceCode }
); diff --git a/packages/react-bootstrap-table2-example/examples/search/custom-search-value.js b/packages/react-bootstrap-table2-example/examples/search/custom-search-value.js index 185d4ef..8df77fa 100644 --- a/packages/react-bootstrap-table2-example/examples/search/custom-search-value.js +++ b/packages/react-bootstrap-table2-example/examples/search/custom-search-value.js @@ -3,7 +3,7 @@ import React from 'react'; import BootstrapTable from 'react-bootstrap-table-next'; -import ToolkitContext, { Search } from 'react-bootstrap-table2-toolkit'; +import ToolkitProvider, { Search } from 'react-bootstrap-table2-toolkit'; import Code from 'components/common/code-block'; import { jobsGenerator1 } from 'utils/common'; @@ -32,7 +32,7 @@ const columns = [{ const sourceCode = `\ import BootstrapTable from 'react-bootstrap-table-next'; -import ToolkitContext, { Search } from 'react-bootstrap-table2-toolkit'; +import ToolkitProvider, { Search } from 'react-bootstrap-table2-toolkit'; const { SearchBar, searchFactory } = Search; const owners = ['Allen', 'Bob', 'Cat']; @@ -55,56 +55,52 @@ const columns = [{ filterValue: (cell, row) => types[cell] // we will search the value after filterValue called }]; - - - { - props => ( -
-

Try to Search Bob, Cat or Allen instead of 0, 1 or 2

- -
- -
- ) - } -
-
+ { + props => ( +
+

Try to Search Bob, Cat or Allen instead of 0, 1 or 2

+ +
+ +
+ ) + } + `; export default () => (
- - - { - props => ( -
-

Try to Search Bob, Cat or Allen instead of 0, 1 or 2

- -
- -
- ) - } -
-
+ { + props => ( +
+

Try to Search Bob, Cat or Allen instead of 0, 1 or 2

+ +
+ +
+ ) + } + { sourceCode }
); diff --git a/packages/react-bootstrap-table2-example/examples/search/default-custom-search.js b/packages/react-bootstrap-table2-example/examples/search/default-custom-search.js index 146a6ac..22938d1 100644 --- a/packages/react-bootstrap-table2-example/examples/search/default-custom-search.js +++ b/packages/react-bootstrap-table2-example/examples/search/default-custom-search.js @@ -2,7 +2,7 @@ import React from 'react'; import BootstrapTable from 'react-bootstrap-table-next'; -import ToolkitContext, { Search } from 'react-bootstrap-table2-toolkit'; +import ToolkitProvider, { Search } from 'react-bootstrap-table2-toolkit'; import Code from 'components/common/code-block'; import { productsGenerator } from 'utils/common'; @@ -22,7 +22,7 @@ const columns = [{ const sourceCode = `\ import BootstrapTable from 'react-bootstrap-table-next'; -import ToolkitContext, { Search } from 'react-bootstrap-table2-toolkit'; +import ToolkitProvider, { Search } from 'react-bootstrap-table2-toolkit'; const { SearchBar, searchFactory } = Search; const columns = [{ @@ -36,68 +36,64 @@ const columns = [{ text: 'Product Price' }]; - - - { - props => ( -
-

Input something at below input field:

- -
- -
- ) - } -
-
+ { + props => ( +
+

Input something at below input field:

+ +
+ +
+ ) + } + `; export default () => (
- - - { - props => ( -
-

Input something at below input field:

- -
- -
- ) - } -
-
+ { + props => ( +
+

Input something at below input field:

+ +
+ +
+ ) + } + { sourceCode }
); diff --git a/packages/react-bootstrap-table2-example/examples/search/fully-custom-search.js b/packages/react-bootstrap-table2-example/examples/search/fully-custom-search.js index 9b5149a..98720d6 100644 --- a/packages/react-bootstrap-table2-example/examples/search/fully-custom-search.js +++ b/packages/react-bootstrap-table2-example/examples/search/fully-custom-search.js @@ -3,7 +3,7 @@ import React from 'react'; import BootstrapTable from 'react-bootstrap-table-next'; -import ToolkitContext, { Search } from 'react-bootstrap-table2-toolkit'; +import ToolkitProvider, { Search } from 'react-bootstrap-table2-toolkit'; import Code from 'components/common/code-block'; import { productsGenerator } from 'utils/common'; @@ -23,7 +23,7 @@ const columns = [{ const sourceCode = `\ import BootstrapTable from 'react-bootstrap-table-next'; -import ToolkitContext, { Search } from 'react-bootstrap-table2-toolkit'; +import ToolkitProvider, { Search } from 'react-bootstrap-table2-toolkit'; const { searchFactory } = Search; const columns = [{ @@ -55,28 +55,26 @@ const MySearch = (props) => { ); }; - - - { - props => ( -
- - -
-
- ) - } -
-
+ { + props => ( +
+ + +
+
+ ) + } + `; const MySearch = (props) => { @@ -99,28 +97,26 @@ const MySearch = (props) => { export default () => (
- - - { - props => ( -
- - -
-
- ) - } -
-
+ { + props => ( +
+ + +
+
+ ) + } + { sourceCode }
); diff --git a/packages/react-bootstrap-table2-example/examples/search/index.js b/packages/react-bootstrap-table2-example/examples/search/index.js index 7435905..252cc68 100644 --- a/packages/react-bootstrap-table2-example/examples/search/index.js +++ b/packages/react-bootstrap-table2-example/examples/search/index.js @@ -2,7 +2,7 @@ import React from 'react'; import BootstrapTable from 'react-bootstrap-table-next'; -import ToolkitContext, { Search } from 'react-bootstrap-table2-toolkit'; +import ToolkitProvider, { Search } from 'react-bootstrap-table2-toolkit'; import Code from 'components/common/code-block'; import { productsGenerator } from 'utils/common'; @@ -22,7 +22,7 @@ const columns = [{ const sourceCode = `\ import BootstrapTable from 'react-bootstrap-table-next'; -import ToolkitContext, { Search } from 'react-bootstrap-table2-toolkit'; +import ToolkitProvider, { Search } from 'react-bootstrap-table2-toolkit'; const { SearchBar, searchFactory } = Search; const columns = [{ @@ -36,56 +36,52 @@ const columns = [{ text: 'Product Price' }]; - - - { - props => ( -
-

Input something at below input field:

- -
- -
- ) - } -
-
+ { + props => ( +
+

Input something at below input field:

+ +
+ +
+ ) + } + `; export default () => (
- - - { - props => ( -
-

Input something at below input field:

- -
- -
- ) - } -
-
+ { + props => ( +
+

Input something at below input field:

+ +
+ +
+ ) + } + { sourceCode }
); diff --git a/packages/react-bootstrap-table2-example/examples/search/search-formatted.js b/packages/react-bootstrap-table2-example/examples/search/search-formatted.js index c08f379..b6fd736 100644 --- a/packages/react-bootstrap-table2-example/examples/search/search-formatted.js +++ b/packages/react-bootstrap-table2-example/examples/search/search-formatted.js @@ -2,7 +2,7 @@ import React from 'react'; import BootstrapTable from 'react-bootstrap-table-next'; -import ToolkitContext, { Search } from 'react-bootstrap-table2-toolkit'; +import ToolkitProvider, { Search } from 'react-bootstrap-table2-toolkit'; import Code from 'components/common/code-block'; import { productsGenerator } from 'utils/common'; @@ -23,7 +23,7 @@ const columns = [{ const sourceCode = `\ import BootstrapTable from 'react-bootstrap-table-next'; -import ToolkitContext, { Search } from 'react-bootstrap-table2-toolkit'; +import ToolkitProvider, { Search } from 'react-bootstrap-table2-toolkit'; const { SearchBar, searchFactory } = Search; const columns = [{ @@ -38,58 +38,54 @@ const columns = [{ formatter: cell => \`USD \${cell}\` // we will search the data after formatted }]; - - - { - props => ( -
-

Try to Search USD at below input field:

- -
- -
- ) - } -
-
+ { + props => ( +
+

Try to Search USD at below input field:

+ +
+ +
+ ) + } + `; export default () => (
- - - { - props => ( -
-

Try to Search USD at below input field:

- -
- -
- ) - } -
-
+ { + props => ( +
+

Try to Search USD at below input field:

+ +
+ +
+ ) + } + { sourceCode }
); diff --git a/packages/react-bootstrap-table2-toolkit/README.md b/packages/react-bootstrap-table2-toolkit/README.md index f7dc601..538bc4a 100644 --- a/packages/react-bootstrap-table2-toolkit/README.md +++ b/packages/react-bootstrap-table2-toolkit/README.md @@ -19,13 +19,12 @@ $ npm install react-bootstrap-table2-toolkit --save ## Table Search ```js -import ToolkitContext, { Search } from 'react-bootstrap-table2-toolkit'; +import ToolkitProvider, { Search } from 'react-bootstrap-table2-toolkit'; const { SearchBar, searchFactory } = Search; //... - - + { props => (
@@ -43,13 +42,12 @@ const { SearchBar, searchFactory } = Search;
) } -
-
+ ``` -1. You need to enable the search functionality via `search` prop on `BootstrapTable` and pass the result of calling `searchFactory` with custom option and default `searchProps` provided by `ToolkitContext.Provider` +1. You need to enable the search functionality via `search` prop on `BootstrapTable` and pass the result of calling `searchFactory` with custom option and default `searchProps` provided by `ToolkitProvider` -2. `ToolkitContext` is a react context, you are supposed to wrap the `BootstrapTable` and `SearchBar` as the child of `ToolkitContext.Consumer` +2. `ToolkitProvider` is a wrapper of react context, you are supposed to wrap the `BootstrapTable` and `SearchBar` as the child of `ToolkitProvider` 3. You should render `SearchBar` with `searchProps` as well. diff --git a/packages/react-bootstrap-table2-toolkit/index.js b/packages/react-bootstrap-table2-toolkit/index.js index 03b2521..836f07d 100644 --- a/packages/react-bootstrap-table2-toolkit/index.js +++ b/packages/react-bootstrap-table2-toolkit/index.js @@ -1,4 +1,6 @@ -import ToolkitContext from './context'; +import Context from './context'; +import ToolkitProvider from './provider'; -export default ToolkitContext; +export default ToolkitProvider; +export const ToolkitContext = Context; export { default as Search } from './src/search'; diff --git a/packages/react-bootstrap-table2-toolkit/provider.js b/packages/react-bootstrap-table2-toolkit/provider.js new file mode 100644 index 0000000..ff8a323 --- /dev/null +++ b/packages/react-bootstrap-table2-toolkit/provider.js @@ -0,0 +1,19 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import ToolkitContext from './context'; + +const Toolkitprovider = props => ( + + + { + tookKitProps => props.children(tookKitProps) + } + + +); + +Toolkitprovider.propTypes = { + children: PropTypes.func.isRequired +}; + +export default Toolkitprovider; From 03ece4b1fc9dfa81deb261a3061f0f638445b0e0 Mon Sep 17 00:00:00 2001 From: AllenFang Date: Sat, 14 Jul 2018 14:09:51 +0800 Subject: [PATCH 39/55] refactoring search --- docs/README.md | 39 ---------------- .../examples/remote/remote-search.js | 11 ++--- .../examples/search/custom-search-value.js | 12 ++--- .../examples/search/default-custom-search.js | 12 ++--- .../examples/search/fully-custom-search.js | 14 ++---- .../examples/search/index.js | 12 ++--- .../examples/search/search-formatted.js | 14 ++---- .../react-bootstrap-table2-toolkit/README.md | 35 +++++++------- .../react-bootstrap-table2-toolkit/context.js | 46 +++++++++++++------ .../src/search/index.js | 9 +--- .../src/bootstrap-table.js | 7 ++- .../src/contexts/index.js | 4 +- .../src/props-resolver/remote-resolver.js | 2 +- .../test/contexts/index.test.js | 5 +- 14 files changed, 85 insertions(+), 137 deletions(-) diff --git a/docs/README.md b/docs/README.md index ba9ea6d..7a02af1 100644 --- a/docs/README.md +++ b/docs/README.md @@ -29,7 +29,6 @@ * [defaultSortDirection](#defaultSortDirection) * [pagination](#pagination) * [filter](#filter) -* [search](#search) * [onTableChange](#onTableChange) ### keyField(**required**) - [String] @@ -265,44 +264,6 @@ const columns = [ { ``` -### search - [Object] -Enable the search functionality. - -`search` allow user to searhc all the table data. However, search functionality is separated from core of `react-bootstrap-table2` so that you are suppose to install `react-bootstrap-table2-toolkit` firstly. - -```sh -$ npm install react-bootstrap-table2-toolkit --save -``` - -After installation of `react-bootstrap-table2-toolkit`, you can render search field easily: - -```js -import ToolkitProvider, { Search } from 'react-bootstrap-table2-toolkit'; - -const { SearchBar, searchFactory } = Search; -//... - - - { - props => ( -
-

Input something at below input field:

- -
- -
- ) - } -
-``` - ### onTableChange - [Function] This callback function will be called when [`remote`](#remote) enabled only. diff --git a/packages/react-bootstrap-table2-example/examples/remote/remote-search.js b/packages/react-bootstrap-table2-example/examples/remote/remote-search.js index 95fbf4e..ad4d5a1 100644 --- a/packages/react-bootstrap-table2-example/examples/remote/remote-search.js +++ b/packages/react-bootstrap-table2-example/examples/remote/remote-search.js @@ -7,7 +7,7 @@ import ToolkitProvider, { Search } from 'react-bootstrap-table2-toolkit'; import Code from 'components/common/code-block'; import { productsGenerator } from 'utils/common'; -const { SearchBar, searchFactory } = Search; +const { SearchBar } = Search; const products = productsGenerator(17); const columns = [{ @@ -45,6 +45,7 @@ const RemoteFilter = props => ( keyField="id" data={ props.data } columns={ columns } + search > { toolkitprops => [ @@ -53,13 +54,11 @@ const RemoteFilter = props => ( { ...toolkitprops.baseProps } remote={ { search: true } } onTableChange={ props.onTableChange } - search={ searchFactory({ - ...toolkitprops.searchProps - }) } /> ] } + { sourceCode }
); @@ -112,6 +111,7 @@ const RemoteFilter = props => ( keyField="id" data={ props.data } columns={ columns } + search > { toolkitprops => [ @@ -120,9 +120,6 @@ const RemoteFilter = props => ( { ...toolkitprops.baseProps } remote={ { search: true } } onTableChange={ props.onTableChange } - search={ searchFactory({ - ...toolkitprops.searchProps - }) } /> ] } diff --git a/packages/react-bootstrap-table2-example/examples/search/custom-search-value.js b/packages/react-bootstrap-table2-example/examples/search/custom-search-value.js index 8df77fa..fcb4d19 100644 --- a/packages/react-bootstrap-table2-example/examples/search/custom-search-value.js +++ b/packages/react-bootstrap-table2-example/examples/search/custom-search-value.js @@ -7,7 +7,7 @@ import ToolkitProvider, { Search } from 'react-bootstrap-table2-toolkit'; import Code from 'components/common/code-block'; import { jobsGenerator1 } from 'utils/common'; -const { SearchBar, searchFactory } = Search; +const { SearchBar } = Search; const products = jobsGenerator1(5); const owners = ['Allen', 'Bob', 'Cat']; @@ -34,7 +34,7 @@ const sourceCode = `\ import BootstrapTable from 'react-bootstrap-table-next'; import ToolkitProvider, { Search } from 'react-bootstrap-table2-toolkit'; -const { SearchBar, searchFactory } = Search; +const { SearchBar } = Search; const owners = ['Allen', 'Bob', 'Cat']; const types = ['Cloud Service', 'Message Service', 'Add Service', 'Edit Service', 'Money']; @@ -59,6 +59,7 @@ const columns = [{ keyField="id" data={ products } columns={ columns } + search > { props => ( @@ -68,9 +69,6 @@ const columns = [{
) @@ -84,6 +82,7 @@ export default () => ( keyField="id" data={ products } columns={ columns } + search > { props => ( @@ -93,9 +92,6 @@ export default () => (
) diff --git a/packages/react-bootstrap-table2-example/examples/search/default-custom-search.js b/packages/react-bootstrap-table2-example/examples/search/default-custom-search.js index 22938d1..3e146db 100644 --- a/packages/react-bootstrap-table2-example/examples/search/default-custom-search.js +++ b/packages/react-bootstrap-table2-example/examples/search/default-custom-search.js @@ -6,7 +6,7 @@ import ToolkitProvider, { Search } from 'react-bootstrap-table2-toolkit'; import Code from 'components/common/code-block'; import { productsGenerator } from 'utils/common'; -const { SearchBar, searchFactory } = Search; +const { SearchBar } = Search; const products = productsGenerator(); const columns = [{ @@ -24,7 +24,7 @@ const sourceCode = `\ import BootstrapTable from 'react-bootstrap-table-next'; import ToolkitProvider, { Search } from 'react-bootstrap-table2-toolkit'; -const { SearchBar, searchFactory } = Search; +const { SearchBar } = Search; const columns = [{ dataField: 'id', text: 'Product ID' @@ -40,6 +40,7 @@ const columns = [{ keyField="id" data={ products } columns={ columns } + search > { props => ( @@ -55,9 +56,6 @@ const columns = [{
) @@ -71,6 +69,7 @@ export default () => ( keyField="id" data={ products } columns={ columns } + search > { props => ( @@ -86,9 +85,6 @@ export default () => (
) diff --git a/packages/react-bootstrap-table2-example/examples/search/fully-custom-search.js b/packages/react-bootstrap-table2-example/examples/search/fully-custom-search.js index 98720d6..78bf504 100644 --- a/packages/react-bootstrap-table2-example/examples/search/fully-custom-search.js +++ b/packages/react-bootstrap-table2-example/examples/search/fully-custom-search.js @@ -3,12 +3,11 @@ import React from 'react'; import BootstrapTable from 'react-bootstrap-table-next'; -import ToolkitProvider, { Search } from 'react-bootstrap-table2-toolkit'; +import ToolkitProvider from 'react-bootstrap-table2-toolkit'; import Code from 'components/common/code-block'; import { productsGenerator } from 'utils/common'; const products = productsGenerator(); -const { searchFactory } = Search; const columns = [{ dataField: 'id', @@ -23,9 +22,8 @@ const columns = [{ const sourceCode = `\ import BootstrapTable from 'react-bootstrap-table-next'; -import ToolkitProvider, { Search } from 'react-bootstrap-table2-toolkit'; +import ToolkitProvider from 'react-bootstrap-table2-toolkit'; -const { searchFactory } = Search; const columns = [{ dataField: 'id', text: 'Product ID' @@ -59,15 +57,13 @@ const MySearch = (props) => { keyField="id" data={ products } columns={ columns } + search > { props => (

@@ -101,15 +97,13 @@ export default () => ( keyField="id" data={ products } columns={ columns } + search > { props => (

diff --git a/packages/react-bootstrap-table2-example/examples/search/index.js b/packages/react-bootstrap-table2-example/examples/search/index.js index 252cc68..3a8effc 100644 --- a/packages/react-bootstrap-table2-example/examples/search/index.js +++ b/packages/react-bootstrap-table2-example/examples/search/index.js @@ -6,7 +6,7 @@ import ToolkitProvider, { Search } from 'react-bootstrap-table2-toolkit'; import Code from 'components/common/code-block'; import { productsGenerator } from 'utils/common'; -const { SearchBar, searchFactory } = Search; +const { SearchBar } = Search; const products = productsGenerator(); const columns = [{ @@ -24,7 +24,7 @@ const sourceCode = `\ import BootstrapTable from 'react-bootstrap-table-next'; import ToolkitProvider, { Search } from 'react-bootstrap-table2-toolkit'; -const { SearchBar, searchFactory } = Search; +const { SearchBar } = Search; const columns = [{ dataField: 'id', text: 'Product ID' @@ -40,6 +40,7 @@ const columns = [{ keyField="id" data={ products } columns={ columns } + search > { props => ( @@ -49,9 +50,6 @@ const columns = [{
) @@ -65,6 +63,7 @@ export default () => ( keyField="id" data={ products } columns={ columns } + search > { props => ( @@ -74,9 +73,6 @@ export default () => (
) diff --git a/packages/react-bootstrap-table2-example/examples/search/search-formatted.js b/packages/react-bootstrap-table2-example/examples/search/search-formatted.js index b6fd736..71575ff 100644 --- a/packages/react-bootstrap-table2-example/examples/search/search-formatted.js +++ b/packages/react-bootstrap-table2-example/examples/search/search-formatted.js @@ -6,7 +6,7 @@ import ToolkitProvider, { Search } from 'react-bootstrap-table2-toolkit'; import Code from 'components/common/code-block'; import { productsGenerator } from 'utils/common'; -const { SearchBar, searchFactory } = Search; +const { SearchBar } = Search; const products = productsGenerator(); const columns = [{ @@ -25,7 +25,7 @@ const sourceCode = `\ import BootstrapTable from 'react-bootstrap-table-next'; import ToolkitProvider, { Search } from 'react-bootstrap-table2-toolkit'; -const { SearchBar, searchFactory } = Search; +const { SearchBar } = Search; const columns = [{ dataField: 'id', text: 'Product ID' @@ -42,6 +42,7 @@ const columns = [{ keyField="id" data={ products } columns={ columns } + search={ { searchFormatted: true } } > { props => ( @@ -51,10 +52,6 @@ const columns = [{
) @@ -68,6 +65,7 @@ export default () => ( keyField="id" data={ products } columns={ columns } + search={ { searchFormatted: true } } > { props => ( @@ -77,10 +75,6 @@ export default () => (
) diff --git a/packages/react-bootstrap-table2-toolkit/README.md b/packages/react-bootstrap-table2-toolkit/README.md index 538bc4a..5ddf507 100644 --- a/packages/react-bootstrap-table2-toolkit/README.md +++ b/packages/react-bootstrap-table2-toolkit/README.md @@ -21,10 +21,15 @@ $ npm install react-bootstrap-table2-toolkit --save ```js import ToolkitProvider, { Search } from 'react-bootstrap-table2-toolkit'; -const { SearchBar, searchFactory } = Search; +const { SearchBar } = Search; //... - + { props => (
@@ -32,12 +37,7 @@ const { SearchBar, searchFactory } = Search;
) @@ -45,25 +45,26 @@ const { SearchBar, searchFactory } = Search;
``` -1. You need to enable the search functionality via `search` prop on `BootstrapTable` and pass the result of calling `searchFactory` with custom option and default `searchProps` provided by `ToolkitProvider` +1. You have to enable the search functionality via `search` prop on `ToolkitProvider`. 2. `ToolkitProvider` is a wrapper of react context, you are supposed to wrap the `BootstrapTable` and `SearchBar` as the child of `ToolkitProvider` -3. You should render `SearchBar` with `searchProps` as well. +3. You should render `SearchBar` with `searchProps` as well. The position of `SearchBar` is depends on you. -### Options +### search pptions # searchFormatted - [bool] -If you want to search on the formatted data, you are supposed to enable it. `react-bootstrap-table2` will check if you define the `column.formatter` when doing search. +If you want to search on the formatted data, you are supposed to enable this props. `react-bootstrap-table2` will check if you define the `column.formatter` when doing search. ```js - + } } +> + // ... +
``` \ No newline at end of file diff --git a/packages/react-bootstrap-table2-toolkit/context.js b/packages/react-bootstrap-table2-toolkit/context.js index b86a4da..a561203 100644 --- a/packages/react-bootstrap-table2-toolkit/context.js +++ b/packages/react-bootstrap-table2-toolkit/context.js @@ -1,8 +1,9 @@ -/* eslint react/prop-types: 0 */ -/* eslint react/require-default-props: 0 */ + import React from 'react'; import PropTypes from 'prop-types'; +import createContext from './src/search/context'; + const ToolkitContext = React.createContext(); class ToolkitProvider extends React.Component { @@ -10,32 +11,49 @@ class ToolkitProvider extends React.Component { keyField: PropTypes.string.isRequired, data: PropTypes.array.isRequired, columns: PropTypes.array.isRequired, - children: PropTypes.node.isRequired + children: PropTypes.node.isRequired, + search: PropTypes.oneOfType([ + PropTypes.bool, + PropTypes.shape({ + searchFormatted: PropTypes.bool + }) + ]) + } + + static defaultProps = { + search: null } constructor(props) { super(props); - this.test = false; - this.searchProps = { - onSearch: this.onSearch.bind(this), + this.state = { searchText: '' }; + this.onSearch = this.onSearch.bind(this); } onSearch(searchText) { - this.searchProps = { - ...this.searchProps, - searchText - }; - this.forceUpdate(); + this.setState({ searchText }); } render() { - const { keyField, columns, data } = this.props; + const baseProps = { + keyField: this.props.keyField, + columns: this.props.columns, + data: this.props.data + }; + if (this.props.search) { + baseProps.search = { + searchContext: createContext(this.props.search), + searchText: this.state.searchText + }; + } return ( { this.props.children } diff --git a/packages/react-bootstrap-table2-toolkit/src/search/index.js b/packages/react-bootstrap-table2-toolkit/src/search/index.js index 06fc1d7..c905897 100644 --- a/packages/react-bootstrap-table2-toolkit/src/search/index.js +++ b/packages/react-bootstrap-table2-toolkit/src/search/index.js @@ -1,10 +1,3 @@ import SearchBar from './SearchBar'; -import createContext from './context'; - -const searchFactory = ({ searchText, onSearch, ...options }) => ({ - createContext: createContext(options), - searchText -}); - -export default { SearchBar, searchFactory }; +export default { SearchBar }; diff --git a/packages/react-bootstrap-table2/src/bootstrap-table.js b/packages/react-bootstrap-table2/src/bootstrap-table.js index 20f64f4..6be3c8e 100644 --- a/packages/react-bootstrap-table2/src/bootstrap-table.js +++ b/packages/react-bootstrap-table2/src/bootstrap-table.js @@ -174,7 +174,12 @@ BootstrapTable.propTypes = { onTableChange: PropTypes.func, onSort: PropTypes.func, onFilter: PropTypes.func, - onExternalFilter: PropTypes.func + onExternalFilter: PropTypes.func, + // Inject from toolkit + search: PropTypes.shape({ + searchText: PropTypes.string, + searchContext: PropTypes.func + }) }; BootstrapTable.defaultProps = { diff --git a/packages/react-bootstrap-table2/src/contexts/index.js b/packages/react-bootstrap-table2/src/contexts/index.js index e95ea27..bb5fe15 100644 --- a/packages/react-bootstrap-table2/src/contexts/index.js +++ b/packages/react-bootstrap-table2/src/contexts/index.js @@ -43,8 +43,8 @@ const withContext = Base => this.isRemotePagination, this.handleRemotePageChange); } - if (props.search) { - this.SearchContext = props.search.createContext( + if (props.search && props.search.searchContext) { + this.SearchContext = props.search.searchContext( _, this.isRemoteSearch, this.handleRemoteSearchChange); } } diff --git a/packages/react-bootstrap-table2/src/props-resolver/remote-resolver.js b/packages/react-bootstrap-table2/src/props-resolver/remote-resolver.js index f0b1a59..89af4c6 100644 --- a/packages/react-bootstrap-table2/src/props-resolver/remote-resolver.js +++ b/packages/react-bootstrap-table2/src/props-resolver/remote-resolver.js @@ -27,7 +27,7 @@ export default ExtendBase => } if (this.searchContext) { - searchText = this.searchContext.props.searchText; + searchText = this.props.search.searchText; } return { diff --git a/packages/react-bootstrap-table2/test/contexts/index.test.js b/packages/react-bootstrap-table2/test/contexts/index.test.js index 1236b24..4a2b1de 100644 --- a/packages/react-bootstrap-table2/test/contexts/index.test.js +++ b/packages/react-bootstrap-table2/test/contexts/index.test.js @@ -138,10 +138,7 @@ describe('Context', () => { beforeEach(() => { const SearchContext = React.createContext(); const search = { - createContext: jest.fn().mockReturnValue({ - Provider: SearchContext.Provider, - Consumer: SearchContext.Consumer - }), + searchContext: jest.fn().mockReturnValue(SearchContext), searchText: '' }; wrapper = shallow( From f0e37b130ccf150f337c1877f17d474634e6eac2 Mon Sep 17 00:00:00 2001 From: AllenFang Date: Sat, 14 Jul 2018 15:45:27 +0800 Subject: [PATCH 40/55] implement export csv --- .../examples/csv/index.js | 86 +++++++++++++++++++ .../stories/index.js | 6 ++ .../react-bootstrap-table2-toolkit/context.js | 18 +++- .../react-bootstrap-table2-toolkit/index.js | 1 + .../package.json | 3 + .../src/csv/button.js | 33 +++++++ .../src/csv/exporter.js | 64 ++++++++++++++ .../src/csv/index.js | 3 + .../src/op/csv.js | 24 ++++++ .../src/op/index.js | 5 ++ .../statelessOp.js | 4 + .../react-bootstrap-table2-toolkit/yarn.lock | 7 ++ 12 files changed, 252 insertions(+), 2 deletions(-) create mode 100644 packages/react-bootstrap-table2-example/examples/csv/index.js create mode 100644 packages/react-bootstrap-table2-toolkit/src/csv/button.js create mode 100644 packages/react-bootstrap-table2-toolkit/src/csv/exporter.js create mode 100644 packages/react-bootstrap-table2-toolkit/src/csv/index.js create mode 100644 packages/react-bootstrap-table2-toolkit/src/op/csv.js create mode 100644 packages/react-bootstrap-table2-toolkit/src/op/index.js create mode 100644 packages/react-bootstrap-table2-toolkit/statelessOp.js create mode 100644 packages/react-bootstrap-table2-toolkit/yarn.lock diff --git a/packages/react-bootstrap-table2-example/examples/csv/index.js b/packages/react-bootstrap-table2-example/examples/csv/index.js new file mode 100644 index 0000000..e8069c1 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/csv/index.js @@ -0,0 +1,86 @@ +/* eslint react/prop-types: 0 */ +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table-next'; +import ToolkitContext, { CSVExport } from 'react-bootstrap-table2-toolkit'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(); + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; +import ToolkitContext, { Search } from 'react-bootstrap-table2-toolkit'; + +const { SearchBar, searchFactory } = Search; +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + + + + { + props => ( +
+

Input something at below input field:

+ +
+ +
+ ) + } +
+
+`; + +export default () => ( +
+ + + { + props => ( +
+

Input something at below input field:

+ +
+ +
+ ) + } +
+
+ { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/stories/index.js b/packages/react-bootstrap-table2-example/stories/index.js index d556b1f..04d589e 100644 --- a/packages/react-bootstrap-table2-example/stories/index.js +++ b/packages/react-bootstrap-table2-example/stories/index.js @@ -133,6 +133,9 @@ import FullyCustomSearch from 'examples/search/fully-custom-search'; import SearchFormattedData from 'examples/search/search-formatted'; import CustomSearchValue from 'examples/search/custom-search-value'; +// CSV +import ExportCSV from 'examples/csv'; + // loading overlay import EmptyTableOverlay from 'examples/loading-overlay/empty-table-overlay'; import TableOverlay from 'examples/loading-overlay/table-overlay'; @@ -289,6 +292,9 @@ storiesOf('Table Search', module) .add('Search Fromatted Value', () => ) .add('Custom Search Value', () => ); +storiesOf('Export CSV', module) + .add('Basic Export CSV', () => ); + storiesOf('EmptyTableOverlay', module) .add('Empty Table Overlay', () => ) .add('Table Overlay', () => ); diff --git a/packages/react-bootstrap-table2-toolkit/context.js b/packages/react-bootstrap-table2-toolkit/context.js index a561203..e55be86 100644 --- a/packages/react-bootstrap-table2-toolkit/context.js +++ b/packages/react-bootstrap-table2-toolkit/context.js @@ -1,12 +1,13 @@ import React from 'react'; import PropTypes from 'prop-types'; +import statelessDrcorator from './statelessOp'; import createContext from './src/search/context'; const ToolkitContext = React.createContext(); -class ToolkitProvider extends React.Component { +class ToolkitProvider extends statelessDrcorator(React.Component) { static propTypes = { keyField: PropTypes.string.isRequired, data: PropTypes.array.isRequired, @@ -17,11 +18,21 @@ class ToolkitProvider extends React.Component { PropTypes.shape({ searchFormatted: PropTypes.bool }) + ]), + exportCSV: PropTypes.oneOfType([ + PropTypes.bool, + PropTypes.shape({ + fileName: PropTypes.string, + separator: PropTypes.string, + ignoreHeader: PropTypes.bool, + noAutoBOM: PropTypes.bool + }) ]) } static defaultProps = { - search: null + search: false, + exportCSV: false } constructor(props) { @@ -53,6 +64,9 @@ class ToolkitProvider extends React.Component { searchProps: { onSearch: this.onSearch }, + csvProps: { + onExport: this.handleExportCSV + }, baseProps } } > diff --git a/packages/react-bootstrap-table2-toolkit/index.js b/packages/react-bootstrap-table2-toolkit/index.js index 836f07d..f9553ac 100644 --- a/packages/react-bootstrap-table2-toolkit/index.js +++ b/packages/react-bootstrap-table2-toolkit/index.js @@ -4,3 +4,4 @@ import ToolkitProvider from './provider'; export default ToolkitProvider; export const ToolkitContext = Context; export { default as Search } from './src/search'; +export { default as CSVExport } from './src/csv'; diff --git a/packages/react-bootstrap-table2-toolkit/package.json b/packages/react-bootstrap-table2-toolkit/package.json index efdbc64..6338d8a 100644 --- a/packages/react-bootstrap-table2-toolkit/package.json +++ b/packages/react-bootstrap-table2-toolkit/package.json @@ -43,5 +43,8 @@ "prop-types": "^15.0.0", "react": "^16.3.0", "react-dom": "^16.3.0" + }, + "dependencies": { + "file-saver": "1.3.8" } } diff --git a/packages/react-bootstrap-table2-toolkit/src/csv/button.js b/packages/react-bootstrap-table2-toolkit/src/csv/button.js new file mode 100644 index 0000000..d0ee010 --- /dev/null +++ b/packages/react-bootstrap-table2-toolkit/src/csv/button.js @@ -0,0 +1,33 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +const ExportCSVButton = (props) => { + const { + onExport, + children, + ...rest + } = props; + + return ( + + ); +}; + +ExportCSVButton.propTypes = { + children: PropTypes.node.isRequired, + onExport: PropTypes.func.isRequired, + className: PropTypes.string, + style: PropTypes.object +}; +ExportCSVButton.defaultProps = { + className: 'react-bs-table-csv-btn btn btn-default', + style: {} +}; + +export default ExportCSVButton; diff --git a/packages/react-bootstrap-table2-toolkit/src/csv/exporter.js b/packages/react-bootstrap-table2-toolkit/src/csv/exporter.js new file mode 100644 index 0000000..0cb9252 --- /dev/null +++ b/packages/react-bootstrap-table2-toolkit/src/csv/exporter.js @@ -0,0 +1,64 @@ +/* eslint no-unneeded-ternary: 0 */ +import FileSaver from 'file-saver'; + +export const getMetaInfo = columns => + columns + .map(column => ({ + field: column.dataField, + type: column.csvType || String, + formatter: column.csvFormatter, + formatExtraData: column.formatExtraData, + header: column.csvText || column.text, + export: column.csvExport === false ? false : true, + row: Number(column.row) || 0, + rowSpan: Number(column.rowSpan) || 1, + colSpan: Number(column.colSpan) || 1 + })) + .filter(_ => _.export); + +export const transform = ( + data, + meta, + { + separator, + ignoreHeader + } +) => { + const visibleColumns = meta.filter(m => m.export); + let content = ''; + // extract csv header + if (!ignoreHeader) { + content += visibleColumns.map(m => `"${m.header}"`).join(separator); + content += '\n'; + } + // extract csv body + if (data.length === 0) return content; + content += data + .map((row, rowIndex) => + visibleColumns.map((m) => { + let cellContent = row[m.field]; + if (m.formatter) { + cellContent = m.formatter(cellContent, row, rowIndex, m.formatExtraData); + } + if (m.type === String) { + return `"${cellContent}"`; + } + return cellContent; + }).join(separator)).join('\n'); + + return content; +}; + +export const save = ( + content, + { + noAutoBOM, + fileName + } +) => { + FileSaver.saveAs( + new Blob(['\ufeff', content], { type: 'text/plain;charset=utf-8' }), + fileName, + noAutoBOM + ); +}; diff --git a/packages/react-bootstrap-table2-toolkit/src/csv/index.js b/packages/react-bootstrap-table2-toolkit/src/csv/index.js new file mode 100644 index 0000000..4c60453 --- /dev/null +++ b/packages/react-bootstrap-table2-toolkit/src/csv/index.js @@ -0,0 +1,3 @@ +import ExportCSVButton from './button'; + +export default { ExportCSVButton }; diff --git a/packages/react-bootstrap-table2-toolkit/src/op/csv.js b/packages/react-bootstrap-table2-toolkit/src/op/csv.js new file mode 100644 index 0000000..71cf3b2 --- /dev/null +++ b/packages/react-bootstrap-table2-toolkit/src/op/csv.js @@ -0,0 +1,24 @@ +import { getMetaInfo, transform, save } from '../csv/exporter'; + +const csvDefaultOptions = { + fileName: 'spreadsheet.csv', + separator: ',', + ignoreHeader: false, + noAutoBOM: true +}; + +export default Base => + class CSVOperation extends Base { + handleExportCSV = () => { + const { columns, data, exportCSV } = this.props; + const meta = getMetaInfo(columns); + const options = exportCSV === true ? + csvDefaultOptions : + { + ...csvDefaultOptions, + ...exportCSV + }; + const content = transform(data, meta, options); + save(content, options); + } + }; diff --git a/packages/react-bootstrap-table2-toolkit/src/op/index.js b/packages/react-bootstrap-table2-toolkit/src/op/index.js new file mode 100644 index 0000000..847e057 --- /dev/null +++ b/packages/react-bootstrap-table2-toolkit/src/op/index.js @@ -0,0 +1,5 @@ +import csvOperation from './csv'; + +export default { + csvOperation +}; diff --git a/packages/react-bootstrap-table2-toolkit/statelessOp.js b/packages/react-bootstrap-table2-toolkit/statelessOp.js new file mode 100644 index 0000000..b39287d --- /dev/null +++ b/packages/react-bootstrap-table2-toolkit/statelessOp.js @@ -0,0 +1,4 @@ +import Operation from './src/op'; + +export default Base => + class StatelessOperation extends Operation.csvOperation(Base) {}; diff --git a/packages/react-bootstrap-table2-toolkit/yarn.lock b/packages/react-bootstrap-table2-toolkit/yarn.lock new file mode 100644 index 0000000..bcdf61c --- /dev/null +++ b/packages/react-bootstrap-table2-toolkit/yarn.lock @@ -0,0 +1,7 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +file-saver@1.3.8: + version "1.3.8" + resolved "https://registry.yarnpkg.com/file-saver/-/file-saver-1.3.8.tgz#e68a30c7cb044e2fb362b428469feb291c2e09d8" From b792803974266ba8ce063db5867fd4666ce8cfde Mon Sep 17 00:00:00 2001 From: AllenFang Date: Sat, 14 Jul 2018 15:45:46 +0800 Subject: [PATCH 41/55] add export csv stories --- .../examples/csv/csv-column-formatter.js | 80 +++++++++++++++ .../examples/csv/csv-column-type.js | 79 +++++++++++++++ .../examples/csv/custom-csv-button.js | 97 +++++++++++++++++++ .../examples/csv/custom-csv-header.js | 83 ++++++++++++++++ .../examples/csv/custom-csv.js | 82 ++++++++++++++++ .../examples/csv/hide-column.js | 79 +++++++++++++++ .../examples/csv/index.js | 63 ++++++------ .../stories/index.js | 14 ++- 8 files changed, 540 insertions(+), 37 deletions(-) create mode 100644 packages/react-bootstrap-table2-example/examples/csv/csv-column-formatter.js create mode 100644 packages/react-bootstrap-table2-example/examples/csv/csv-column-type.js create mode 100644 packages/react-bootstrap-table2-example/examples/csv/custom-csv-button.js create mode 100644 packages/react-bootstrap-table2-example/examples/csv/custom-csv-header.js create mode 100644 packages/react-bootstrap-table2-example/examples/csv/custom-csv.js create mode 100644 packages/react-bootstrap-table2-example/examples/csv/hide-column.js diff --git a/packages/react-bootstrap-table2-example/examples/csv/csv-column-formatter.js b/packages/react-bootstrap-table2-example/examples/csv/csv-column-formatter.js new file mode 100644 index 0000000..3e4c018 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/csv/csv-column-formatter.js @@ -0,0 +1,80 @@ +/* eslint react/prop-types: 0 */ +import React from 'react'; + +/* eslint no-unused-vars: 0 */ +import BootstrapTable from 'react-bootstrap-table-next'; +import ToolkitProvider, { CSVExport } from 'react-bootstrap-table2-toolkit'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const { ExportCSVButton } = CSVExport; +const products = productsGenerator(); + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price', + csvFormatter: (cell, row, rowIndex) => `$ ${cell}NTD` +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; +import ToolkitProvider, { CSVExport } from 'react-bootstrap-table2-toolkit'; + +const { ExportCSVButton } = CSVExport; +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price', + csvFormatter: (cell, row, rowIndex) => \`$ \${cell}NTD\` +}]; + + + { + props => ( +
+ Export CSV!! +
+ +
+ ) + } +
+`; + +export default () => ( +
+ + { + props => ( +
+ Export CSV!! +
+ +
+ ) + } +
+ { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/csv/csv-column-type.js b/packages/react-bootstrap-table2-example/examples/csv/csv-column-type.js new file mode 100644 index 0000000..b487bd2 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/csv/csv-column-type.js @@ -0,0 +1,79 @@ +/* eslint react/prop-types: 0 */ +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table-next'; +import ToolkitProvider, { CSVExport } from 'react-bootstrap-table2-toolkit'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const { ExportCSVButton } = CSVExport; +const products = productsGenerator(); + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price', + csvType: Number +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; +import ToolkitProvider, { CSVExport } from 'react-bootstrap-table2-toolkit'; + +const { ExportCSVButton } = CSVExport; +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price', + csvType: Number +}]; + + + { + props => ( +
+ Export CSV!! +
+ +
+ ) + } +
+`; + +export default () => ( +
+ + { + props => ( +
+ Export CSV!! +
+ +
+ ) + } +
+ { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/csv/custom-csv-button.js b/packages/react-bootstrap-table2-example/examples/csv/custom-csv-button.js new file mode 100644 index 0000000..6977995 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/csv/custom-csv-button.js @@ -0,0 +1,97 @@ +/* eslint react/prop-types: 0 */ +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table-next'; +import ToolkitProvider from 'react-bootstrap-table2-toolkit'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(); + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; +import ToolkitProvider from 'react-bootstrap-table2-toolkit'; + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const MyExportCSV = (props) => { + const handleClick = () => { + props.onExport(); + }; + return ( +
+ +
+ ); +}; + + + { + props => ( +
+ +
+ +
+ ) + } +
+`; + +const MyExportCSV = (props) => { + const handleClick = () => { + props.onExport(); + }; + return ( +
+ +
+ ); +}; + +export default () => ( +
+ + { + props => ( +
+ +
+ +
+ ) + } +
+ { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/csv/custom-csv-header.js b/packages/react-bootstrap-table2-example/examples/csv/custom-csv-header.js new file mode 100644 index 0000000..284e7ac --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/csv/custom-csv-header.js @@ -0,0 +1,83 @@ +/* eslint react/prop-types: 0 */ +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table-next'; +import ToolkitProvider, { CSVExport } from 'react-bootstrap-table2-toolkit'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const { ExportCSVButton } = CSVExport; +const products = productsGenerator(); + +const columns = [{ + dataField: 'id', + text: 'Product ID', + csvText: 'CSV Product ID' +}, { + dataField: 'name', + text: 'Product Name', + csvText: 'CSV Product Name' +}, { + dataField: 'price', + text: 'Product Price', + csvText: 'CSV Product price' +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; +import ToolkitProvider, { CSVExport } from 'react-bootstrap-table2-toolkit'; + +const { ExportCSVButton } = CSVExport; +const columns = [{ + dataField: 'id', + text: 'Product ID', + csvText: 'CSV Product ID' +}, { + dataField: 'name', + text: 'Product Name', + csvText: 'CSV Product Name' +}, { + dataField: 'price', + text: 'Product Price', + csvText: 'CSV Product price' +}]; + + + { + props => ( +
+ Export CSV!! +
+ +
+ ) + } +
+`; + +export default () => ( +
+ + { + props => ( +
+ Export CSV!! +
+ +
+ ) + } +
+ { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/csv/custom-csv.js b/packages/react-bootstrap-table2-example/examples/csv/custom-csv.js new file mode 100644 index 0000000..a78d8b0 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/csv/custom-csv.js @@ -0,0 +1,82 @@ +/* eslint react/prop-types: 0 */ +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table-next'; +import ToolkitProvider, { CSVExport } from 'react-bootstrap-table2-toolkit'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const { ExportCSVButton } = CSVExport; +const products = productsGenerator(); + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; +import ToolkitProvider, { CSVExport } from 'react-bootstrap-table2-toolkit'; + +const { ExportCSVButton } = CSVExport; +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + + + { + props => ( +
+ Export CSV!! +
+ +
+ ) + } +
+`; + +export default () => ( +
+ + { + props => ( +
+ Export CSV!! +
+ +
+ ) + } +
+ { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/csv/hide-column.js b/packages/react-bootstrap-table2-example/examples/csv/hide-column.js new file mode 100644 index 0000000..4f7d05a --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/csv/hide-column.js @@ -0,0 +1,79 @@ +/* eslint react/prop-types: 0 */ +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table-next'; +import ToolkitProvider, { CSVExport } from 'react-bootstrap-table2-toolkit'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const { ExportCSVButton } = CSVExport; +const products = productsGenerator(); + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name', + csvExport: false +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; +import ToolkitProvider, { CSVExport } from 'react-bootstrap-table2-toolkit'; + +const { ExportCSVButton } = CSVExport; +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name', + csvExport: false +}, { + dataField: 'price', + text: 'Product Price' +}]; + + + { + props => ( +
+ Export CSV!! +
+ +
+ ) + } +
+`; + +export default () => ( +
+ + { + props => ( +
+ Export CSV!! +
+ +
+ ) + } +
+ { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/csv/index.js b/packages/react-bootstrap-table2-example/examples/csv/index.js index e8069c1..5c940cc 100644 --- a/packages/react-bootstrap-table2-example/examples/csv/index.js +++ b/packages/react-bootstrap-table2-example/examples/csv/index.js @@ -2,10 +2,11 @@ import React from 'react'; import BootstrapTable from 'react-bootstrap-table-next'; -import ToolkitContext, { CSVExport } from 'react-bootstrap-table2-toolkit'; +import ToolkitProvider, { CSVExport } from 'react-bootstrap-table2-toolkit'; import Code from 'components/common/code-block'; import { productsGenerator } from 'utils/common'; +const { ExportCSVButton } = CSVExport; const products = productsGenerator(); const columns = [{ @@ -21,9 +22,9 @@ const columns = [{ const sourceCode = `\ import BootstrapTable from 'react-bootstrap-table-next'; -import ToolkitContext, { Search } from 'react-bootstrap-table2-toolkit'; +import ToolkitProvider, { CSVExport } from 'react-bootstrap-table2-toolkit'; -const { SearchBar, searchFactory } = Search; +const { ExportCSVButton } = CSVExport; const columns = [{ dataField: 'id', text: 'Product ID' @@ -35,52 +36,42 @@ const columns = [{ text: 'Product Price' }]; - - - { - props => ( -
-

Input something at below input field:

- -
- -
- ) - } -
-
+ { + props => ( +
+ Export CSV!! +
+ +
+ ) + } + `; export default () => (
- - - { - props => ( -
-

Input something at below input field:

- -
- -
- ) - } -
-
+ { + props => ( +
+ Export CSV!! +
+ +
+ ) + } + { sourceCode }
); diff --git a/packages/react-bootstrap-table2-example/stories/index.js b/packages/react-bootstrap-table2-example/stories/index.js index 04d589e..eee2c87 100644 --- a/packages/react-bootstrap-table2-example/stories/index.js +++ b/packages/react-bootstrap-table2-example/stories/index.js @@ -135,6 +135,12 @@ import CustomSearchValue from 'examples/search/custom-search-value'; // CSV import ExportCSV from 'examples/csv'; +import CSVFormatter from 'examples/csv/csv-column-formatter'; +import CustomCSVHeader from 'examples/csv/custom-csv-header'; +import HideCSVColumn from 'examples/csv/hide-column'; +import CSVColumnType from 'examples/csv/csv-column-type'; +import CustomCSVButton from 'examples/csv/custom-csv-button'; +import CustomCSV from 'examples/csv/custom-csv'; // loading overlay import EmptyTableOverlay from 'examples/loading-overlay/empty-table-overlay'; @@ -293,7 +299,13 @@ storiesOf('Table Search', module) .add('Custom Search Value', () => ); storiesOf('Export CSV', module) - .add('Basic Export CSV', () => ); + .add('Basic Export CSV', () => ) + .add('Format CSV Column', () => ) + .add('Custom CSV Header', () => ) + .add('Hide CSV Column', () => ) + .add('CSV Column Type', () => ) + .add('Custom CSV Button', () => ) + .add('Custom CSV', () => ); storiesOf('EmptyTableOverlay', module) .add('Empty Table Overlay', () => ) From ec77a0539dc0d3261f7ed0af249a0e1f3d5af3c2 Mon Sep 17 00:00:00 2001 From: AllenFang Date: Sat, 14 Jul 2018 16:26:34 +0800 Subject: [PATCH 42/55] patch docs for export CSV --- docs/columns.md | 19 ++++++- docs/migration.md | 8 +-- .../react-bootstrap-table2-toolkit/README.md | 51 +++++++++++++++++-- 3 files changed, 70 insertions(+), 8 deletions(-) diff --git a/docs/columns.md b/docs/columns.md index 4b78e36..89c62cf 100644 --- a/docs/columns.md +++ b/docs/columns.md @@ -38,6 +38,10 @@ Available properties in a column object: * [editorRenderer](#editorRenderer) * [filter](#filter) * [filterValue](#filterValue) +* [csvType](#csvType) +* [csvFormatter](#csvFormatter) +* [csvText](#csvText) +* [csvExport](#csvExport) Following is a most simplest and basic usage: @@ -685,4 +689,17 @@ A final `String` value you want to be filtered. filter: textFilter(), filterValue: (cell, row) => owners[cell] } -``` \ No newline at end of file +``` + +## column.csvType - [Object] +Default is `String`. Currently, the available value is `String` and `Number`. If `Number` assigned, the cell value will not wrapped with double quote. + +## column.csvFormatter - [Function] + +This is same as [`column.formatter`](#formatter). But `csvFormatter` only for CSV export and called when export CSV. + +## column.csvText - [String] +Custom the CSV header cell, Default is [`column.text`](#text). + +## column.csvExport - [Bool] +Default is `true`, `false` will hide this column when export CSV. diff --git a/docs/migration.md b/docs/migration.md index a215952..a4b4c2c 100644 --- a/docs/migration.md +++ b/docs/migration.md @@ -22,6 +22,8 @@ Currently, **I still can't implement all the mainly features in legacy `react-bo * Pagination Addons * [`react-bootstrap-table2-overlay`](https://www.npmjs.com/package/react-bootstrap-table2-overlay) * Overlay/Loading Addons +* [`react-bootstrap-table2-toolkit`](https://www.npmjs.com/package/react-bootstrap-table2-toolkit) + * Table Toolkits, like search, csv etc. This can help your application with less bundled size and also help `react-bootstrap-table2` have clean design to avoid handling to much logic in kernel module(SRP). Hence, which means you probably need to install above addons when you need specific features. @@ -131,6 +133,9 @@ No big changes for pagination, but still can't custom the pagination list, butto - [ ] Expand Column position - [ ] Expand Column Style/Class +## Export CSV +Export CSV functionality is like search, which is one of functionality in the `react-bootstrap-table2-toolkit`. But all the legacy function we already implemented. + ## Remote > It's totally different in `react-bootstrap-table2`. Please [see](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/basic-remote.html). @@ -141,6 +146,3 @@ Not support yet ## Keyboard Navigation Not support yet - -## Export CSV -Not support yet \ No newline at end of file diff --git a/packages/react-bootstrap-table2-toolkit/README.md b/packages/react-bootstrap-table2-toolkit/README.md index 5ddf507..ace3fb4 100644 --- a/packages/react-bootstrap-table2-toolkit/README.md +++ b/packages/react-bootstrap-table2-toolkit/README.md @@ -2,7 +2,7 @@ `react-bootstrap-table2` support some additional features in [`react-bootstrap-table2-toolkit`](https://github.com/react-bootstrap-table/react-bootstrap-table2/tree/develop/packages/react-bootstrap-table2-toolkit). -In the future, this toolkit will support other feature like row delete, insert and export csv etc. Right now we only support Table Search. +In the future, this toolkit will support other feature like row delete, insert and export csv etc. Right now we only support Table Search and CSV export. **[Live Demo For Table Search](https://react-bootstrap-table.github.io/react-bootstrap-table2/storybook/index.html?selectedKind=Table%20Search)** @@ -51,9 +51,9 @@ const { SearchBar } = Search; 3. You should render `SearchBar` with `searchProps` as well. The position of `SearchBar` is depends on you. -### search pptions +### Search Options -# searchFormatted - [bool] +#### searchFormatted - [bool] If you want to search on the formatted data, you are supposed to enable this props. `react-bootstrap-table2` will check if you define the `column.formatter` when doing search. ```js @@ -67,4 +67,47 @@ If you want to search on the formatted data, you are supposed to enable this pro > // ... -``` \ No newline at end of file +``` + +## Export CSV +There are two step to enable the export CSV functionality: + +1. Give `exportCSV` prop as `true` on `ToolkitProvider`. +2. Render `ExportCSVButton` with `csvProps`. The position of `ExportCSVButton` is depends on you. + +```js +import ToolkitProvider, { CSVExport } from 'react-bootstrap-table2-toolkit'; + +const { ExportCSVButton } = CSVExport; + + + { + props => ( +
+ Export CSV!! +
+ +
+ ) + } +
+``` + +### Export CSV Options + +#### fileName - [String] +Custom the csv file name. + +#### separator - [String] +Custom the csv file separator. + +#### ignoreHeader - [bool] +Default is `false`. Give true to avoid to attach the csv header. + +#### noAutoBOM - [bool] +Default is `true`. \ No newline at end of file From fadbcdaa24a90781eb03ad1640f29f646005dff4 Mon Sep 17 00:00:00 2001 From: AllenFang Date: Sat, 14 Jul 2018 17:04:35 +0800 Subject: [PATCH 43/55] a workaround for fixing the _ module missing --- .../react-bootstrap-table2-toolkit/context.js | 17 ++++++++++++++++- .../src/csv/exporter.js | 3 ++- .../src/op/csv.js | 2 +- .../src/bootstrap-table.js | 3 ++- .../src/contexts/index.js | 4 ++++ 5 files changed, 25 insertions(+), 4 deletions(-) diff --git a/packages/react-bootstrap-table2-toolkit/context.js b/packages/react-bootstrap-table2-toolkit/context.js index e55be86..84027a3 100644 --- a/packages/react-bootstrap-table2-toolkit/context.js +++ b/packages/react-bootstrap-table2-toolkit/context.js @@ -40,18 +40,33 @@ class ToolkitProvider extends statelessDrcorator(React.Component) { this.state = { searchText: '' }; + this._ = null; this.onSearch = this.onSearch.bind(this); + this.setDependencyModules = this.setDependencyModules.bind(this); } onSearch(searchText) { this.setState({ searchText }); } + /** + * + * @param {*} _ + * this function will be called only one time when table render + * react-bootstrap-table-next/src/context/index.js will call this cb for passing the _ module + * Please consider to extract a common module to handle _ module. + * this is just a quick fix + */ + setDependencyModules(_) { + this._ = _; + } + render() { const baseProps = { keyField: this.props.keyField, columns: this.props.columns, - data: this.props.data + data: this.props.data, + setDependencyModules: this.setDependencyModules }; if (this.props.search) { baseProps.search = { diff --git a/packages/react-bootstrap-table2-toolkit/src/csv/exporter.js b/packages/react-bootstrap-table2-toolkit/src/csv/exporter.js index 0cb9252..e0e31f1 100644 --- a/packages/react-bootstrap-table2-toolkit/src/csv/exporter.js +++ b/packages/react-bootstrap-table2-toolkit/src/csv/exporter.js @@ -19,6 +19,7 @@ export const getMetaInfo = columns => export const transform = ( data, meta, + getValue, { separator, ignoreHeader @@ -36,7 +37,7 @@ export const transform = ( content += data .map((row, rowIndex) => visibleColumns.map((m) => { - let cellContent = row[m.field]; + let cellContent = getValue(row, m.field); if (m.formatter) { cellContent = m.formatter(cellContent, row, rowIndex, m.formatExtraData); } diff --git a/packages/react-bootstrap-table2-toolkit/src/op/csv.js b/packages/react-bootstrap-table2-toolkit/src/op/csv.js index 71cf3b2..b3803aa 100644 --- a/packages/react-bootstrap-table2-toolkit/src/op/csv.js +++ b/packages/react-bootstrap-table2-toolkit/src/op/csv.js @@ -18,7 +18,7 @@ export default Base => ...csvDefaultOptions, ...exportCSV }; - const content = transform(data, meta, options); + const content = transform(data, meta, this._.get, options); save(content, options); } }; diff --git a/packages/react-bootstrap-table2/src/bootstrap-table.js b/packages/react-bootstrap-table2/src/bootstrap-table.js index 6be3c8e..770cf4a 100644 --- a/packages/react-bootstrap-table2/src/bootstrap-table.js +++ b/packages/react-bootstrap-table2/src/bootstrap-table.js @@ -179,7 +179,8 @@ BootstrapTable.propTypes = { search: PropTypes.shape({ searchText: PropTypes.string, searchContext: PropTypes.func - }) + }), + setDependencyModules: PropTypes.func }; BootstrapTable.defaultProps = { diff --git a/packages/react-bootstrap-table2/src/contexts/index.js b/packages/react-bootstrap-table2/src/contexts/index.js index bb5fe15..bdc2402 100644 --- a/packages/react-bootstrap-table2/src/contexts/index.js +++ b/packages/react-bootstrap-table2/src/contexts/index.js @@ -47,6 +47,10 @@ const withContext = Base => this.SearchContext = props.search.searchContext( _, this.isRemoteSearch, this.handleRemoteSearchChange); } + + if (props.setDependencyModules) { + props.setDependencyModules(_); + } } renderBase() { From 7919a4001dfaf14eb340847132639c3786f92d7a Mon Sep 17 00:00:00 2001 From: AllenFang Date: Sun, 15 Jul 2018 17:28:27 +0800 Subject: [PATCH 44/55] enhance for #402 --- .../src/context.js | 12 +++++++++--- .../test/context.test.js | 14 +++++++++----- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/packages/react-bootstrap-table2-paginator/src/context.js b/packages/react-bootstrap-table2-paginator/src/context.js index 2a60d14..7f508a3 100644 --- a/packages/react-bootstrap-table2-paginator/src/context.js +++ b/packages/react-bootstrap-table2-paginator/src/context.js @@ -1,5 +1,6 @@ /* eslint react/prop-types: 0 */ /* eslint react/require-default-props: 0 */ +/* eslint no-lonely-if: 0 */ import React from 'react'; import PropTypes from 'prop-types'; @@ -62,8 +63,14 @@ export default ( currPage = page; needNewState = true; } else { - currPage = alignPage(nextProps.data, currPage, currSizePerPage, pageStartIndex); - needNewState = true; + // user should align the page when the page is not fit to the data size when remote enable + if (!isRemotePagination()) { + const newPage = alignPage(nextProps.data, currPage, currSizePerPage, pageStartIndex); + if (currPage !== newPage) { + currPage = newPage; + needNewState = true; + } + } } if (typeof sizePerPage !== 'undefined' && currSizePerPage !== sizePerPage) { @@ -75,7 +82,6 @@ export default ( if (onPageChange) { onPageChange(currPage, currSizePerPage); } - this.currPage = currPage; this.currSizePerPage = currSizePerPage; } diff --git a/packages/react-bootstrap-table2-paginator/test/context.test.js b/packages/react-bootstrap-table2-paginator/test/context.test.js index 10820b5..4b9c30d 100644 --- a/packages/react-bootstrap-table2-paginator/test/context.test.js +++ b/packages/react-bootstrap-table2-paginator/test/context.test.js @@ -138,6 +138,7 @@ describe('PaginationContext', () => { instance = wrapper.instance(); wrapper.render(); nextProps = { + data, pagination: { options: { page: 2, @@ -167,6 +168,7 @@ describe('PaginationContext', () => { instance = wrapper.instance(); wrapper.render(); nextProps = { + data, pagination: { options: { page: 1, @@ -178,7 +180,7 @@ describe('PaginationContext', () => { }); it('shouldn\'t call options.onPageChange', () => { - expect(onPageChange).not.toHaveBeenCalled(); + expect(onPageChange).toHaveBeenCalledTimes(0); }); it('should have correct currPage', () => { @@ -195,7 +197,7 @@ describe('PaginationContext', () => { })); instance = wrapper.instance(); wrapper.render(); - nextProps = { pagination: defaultPagination }; + nextProps = { data, pagination: defaultPagination }; instance.componentWillReceiveProps(nextProps); }); @@ -216,6 +218,7 @@ describe('PaginationContext', () => { instance = wrapper.instance(); wrapper.render(); nextProps = { + data, pagination: { options: { sizePerPage: Const.SIZE_PER_PAGE_LIST[2], @@ -245,6 +248,7 @@ describe('PaginationContext', () => { instance = wrapper.instance(); wrapper.render(); nextProps = { + data, pagination: { options: { sizePerPage: Const.SIZE_PER_PAGE_LIST[0], @@ -255,8 +259,8 @@ describe('PaginationContext', () => { instance.componentWillReceiveProps(nextProps); }); - it('shouldn\'t call options.onPageChange', () => { - expect(onPageChange).not.toHaveBeenCalled(); + it('shouldn\'t call options.onPageChange', () => { + expect(onPageChange).toHaveBeenCalledTimes(0); }); it('should have correct currSizePerPage', () => { @@ -273,7 +277,7 @@ describe('PaginationContext', () => { })); instance = wrapper.instance(); wrapper.render(); - nextProps = { pagination: defaultPagination }; + nextProps = { data, pagination: defaultPagination }; instance.componentWillReceiveProps(nextProps); }); From 0d64443b26249714dae16b5112e02f8a1817bf32 Mon Sep 17 00:00:00 2001 From: AllenFang Date: Fri, 20 Jul 2018 17:24:39 +0800 Subject: [PATCH 45/55] fix sort caret broken on bootstrap4 --- .../react-bootstrap-table2-toolkit/context.js | 5 +- .../src/bootstrap-table.js | 2 + .../src/contexts/bootstrap.js | 5 ++ .../src/contexts/index.js | 25 ++++--- packages/react-bootstrap-table2/src/header.js | 7 +- .../react-bootstrap-table2/src/sort/caret.js | 18 ++++- .../react-bootstrap-table2/src/sort/symbol.js | 26 ++++--- .../style/react-bootstrap-table2.scss | 33 +++++++++ .../test/sort/caret.test.js | 68 ++++++++++++++----- .../test/sort/symbol.test.js | 17 ++++- 10 files changed, 161 insertions(+), 45 deletions(-) create mode 100644 packages/react-bootstrap-table2/src/contexts/bootstrap.js diff --git a/packages/react-bootstrap-table2-toolkit/context.js b/packages/react-bootstrap-table2-toolkit/context.js index 84027a3..4ae5e19 100644 --- a/packages/react-bootstrap-table2-toolkit/context.js +++ b/packages/react-bootstrap-table2-toolkit/context.js @@ -13,6 +13,7 @@ class ToolkitProvider extends statelessDrcorator(React.Component) { data: PropTypes.array.isRequired, columns: PropTypes.array.isRequired, children: PropTypes.node.isRequired, + bootstrap4: PropTypes.bool, search: PropTypes.oneOfType([ PropTypes.bool, PropTypes.shape({ @@ -32,7 +33,8 @@ class ToolkitProvider extends statelessDrcorator(React.Component) { static defaultProps = { search: false, - exportCSV: false + exportCSV: false, + bootstrap4: false } constructor(props) { @@ -66,6 +68,7 @@ class ToolkitProvider extends statelessDrcorator(React.Component) { keyField: this.props.keyField, columns: this.props.columns, data: this.props.data, + bootstrap4: this.props.bootstrap4, setDependencyModules: this.setDependencyModules }; if (this.props.search) { diff --git a/packages/react-bootstrap-table2/src/bootstrap-table.js b/packages/react-bootstrap-table2/src/bootstrap-table.js index 770cf4a..9afaab0 100644 --- a/packages/react-bootstrap-table2/src/bootstrap-table.js +++ b/packages/react-bootstrap-table2/src/bootstrap-table.js @@ -114,6 +114,7 @@ BootstrapTable.propTypes = { keyField: PropTypes.string.isRequired, data: PropTypes.array.isRequired, columns: PropTypes.array.isRequired, + bootstrap4: PropTypes.bool, remote: PropTypes.oneOfType([PropTypes.bool, PropTypes.shape({ pagination: PropTypes.bool })]), @@ -184,6 +185,7 @@ BootstrapTable.propTypes = { }; BootstrapTable.defaultProps = { + bootstrap4: false, remote: false, striped: false, bordered: true, diff --git a/packages/react-bootstrap-table2/src/contexts/bootstrap.js b/packages/react-bootstrap-table2/src/contexts/bootstrap.js new file mode 100644 index 0000000..c719d9b --- /dev/null +++ b/packages/react-bootstrap-table2/src/contexts/bootstrap.js @@ -0,0 +1,5 @@ +import React from 'react'; + +export const BootstrapContext = React.createContext({ + bootstrap4: false +}); diff --git a/packages/react-bootstrap-table2/src/contexts/index.js b/packages/react-bootstrap-table2/src/contexts/index.js index bdc2402..63f959c 100644 --- a/packages/react-bootstrap-table2/src/contexts/index.js +++ b/packages/react-bootstrap-table2/src/contexts/index.js @@ -7,6 +7,7 @@ import createSortContext from './sort-context'; import createSelectionContext from './selection-context'; import createRowExpandContext from './row-expand-context'; import remoteResolver from '../props-resolver/remote-resolver'; +import { BootstrapContext } from './bootstrap'; import dataOperator from '../store/operators'; const withContext = Base => @@ -267,7 +268,7 @@ const withContext = Base => } render() { - const { keyField, columns } = this.props; + const { keyField, columns, bootstrap4 } = this.props; const baseProps = { keyField, columns }; let base = this.renderBase(); @@ -301,16 +302,18 @@ const withContext = Base => } return ( - - - { - base - } - - + + + + { + base + } + + + ); } }; diff --git a/packages/react-bootstrap-table2/src/header.js b/packages/react-bootstrap-table2/src/header.js index 67568ea..4549343 100644 --- a/packages/react-bootstrap-table2/src/header.js +++ b/packages/react-bootstrap-table2/src/header.js @@ -19,7 +19,8 @@ const Header = (props) => { sortOrder, selectRow, onExternalFilter, - expandRow + expandRow, + bootstrap4 } = props; return ( @@ -46,6 +47,7 @@ const Header = (props) => { return ( { const orderClass = cs('react-bootstrap-table-sort-order', { dropup: order === Const.SORT_ASC }); + return ( - - - + + { + ({ bootstrap4 }) => (bootstrap4 ? ( + + ) : ( + + + + )) + } + ); }; SortCaret.propTypes = { order: PropTypes.oneOf([Const.SORT_ASC, Const.SORT_DESC]).isRequired }; + export default SortCaret; diff --git a/packages/react-bootstrap-table2/src/sort/symbol.js b/packages/react-bootstrap-table2/src/sort/symbol.js index ecaf324..76c7e8f 100644 --- a/packages/react-bootstrap-table2/src/sort/symbol.js +++ b/packages/react-bootstrap-table2/src/sort/symbol.js @@ -1,13 +1,23 @@ import React from 'react'; +import { BootstrapContext } from '../contexts/bootstrap'; const SortSymbol = () => ( - - - - - - - - ); + + { + ({ bootstrap4 }) => (bootstrap4 ? ( + + ) : ( + + + + + + + + + )) + } + +); export default SortSymbol; diff --git a/packages/react-bootstrap-table2/style/react-bootstrap-table2.scss b/packages/react-bootstrap-table2/style/react-bootstrap-table2.scss index 55a1a09..498b012 100644 --- a/packages/react-bootstrap-table2/style/react-bootstrap-table2.scss +++ b/packages/react-bootstrap-table2/style/react-bootstrap-table2.scss @@ -8,6 +8,7 @@ cursor: pointer; } + // bootstrap 3 sort th .order > .dropdown > .caret { margin: 10px 0 10px 5px; color: #cccccc; @@ -22,6 +23,38 @@ margin: 10px 6.5px; } + // bootstrap 4 sort + th .order-4:before { + margin-left: 3.5px; + content: "\2191"; + opacity: 0.4; + } + + th .order-4:after { + content: "\2193"; + opacity: 0.4; + } + + th .caret-4-asc:before { + margin-left: 3.5px; + content: "\2191"; + } + + th .caret-4-asc:after { + content: "\2193"; + opacity: 0.4; + } + + th .caret-4-desc:before { + margin-left: 3.5px; + content: "\2191"; + opacity: 0.4; + } + + th .caret-4-desc:after { + content: "\2193"; + } + th[data-row-selection] { width: 30px; } diff --git a/packages/react-bootstrap-table2/test/sort/caret.test.js b/packages/react-bootstrap-table2/test/sort/caret.test.js index f2f5407..d524496 100644 --- a/packages/react-bootstrap-table2/test/sort/caret.test.js +++ b/packages/react-bootstrap-table2/test/sort/caret.test.js @@ -7,31 +7,63 @@ import SortCaret from '../../src/sort/caret'; describe('SortCaret', () => { let wrapper; - describe(`when order prop is ${Const.SORT_ASC}`, () => { - beforeEach(() => { - wrapper = shallow( - ); + describe('when bootstrap4 context is false', () => { + describe(`when order prop is ${Const.SORT_ASC}`, () => { + beforeEach(() => { + wrapper = shallow(); + const Children = wrapper.props().children({ bootstrap4: false }); + wrapper = shallow(Children); + }); + + it('should render caret correctly', () => { + expect(wrapper.length).toBe(1); + expect(wrapper.find('span').length).toBe(2); + expect(wrapper.find('.caret').length).toBe(1); + expect(wrapper.find('.dropup').length).toBe(1); + }); }); - it('should render caret correctly', () => { - expect(wrapper.length).toBe(1); - expect(wrapper.find('span').length).toBe(2); - expect(wrapper.find('.caret').length).toBe(1); - expect(wrapper.find('.dropup').length).toBe(1); + describe(`when order prop is ${Const.SORT_DESC}`, () => { + beforeEach(() => { + wrapper = shallow(); + const Children = wrapper.props().children({ bootstrap4: false }); + wrapper = shallow(Children); + }); + + it('should render caret correctly', () => { + expect(wrapper.length).toBe(1); + expect(wrapper.find('span').length).toBe(2); + expect(wrapper.find('.caret').length).toBe(1); + expect(wrapper.find('.dropup').length).toBe(0); + }); }); }); - describe(`when order prop is ${Const.SORT_DESC}`, () => { - beforeEach(() => { - wrapper = shallow( - ); + describe('when bootstrap4 context is true', () => { + describe(`when order prop is ${Const.SORT_ASC}`, () => { + beforeEach(() => { + wrapper = shallow(); + const Children = wrapper.props().children({ bootstrap4: true }); + wrapper = shallow(Children); + }); + + it('should render caret correctly', () => { + expect(wrapper.length).toBe(1); + expect(wrapper.find('.caret-4-asc').length).toBe(1); + }); }); - it('should render caret correctly', () => { - expect(wrapper.length).toBe(1); - expect(wrapper.find('span').length).toBe(2); - expect(wrapper.find('.caret').length).toBe(1); - expect(wrapper.find('.dropup').length).toBe(0); + describe(`when order prop is ${Const.SORT_DESC}`, () => { + beforeEach(() => { + wrapper = shallow(); + const Children = wrapper.props().children({ bootstrap4: true }); + wrapper = shallow(Children); + }); + + it('should render caret correctly', () => { + expect(wrapper.length).toBe(1); + expect(wrapper.find('.caret-4-desc').length).toBe(1); + }); }); }); }); diff --git a/packages/react-bootstrap-table2/test/sort/symbol.test.js b/packages/react-bootstrap-table2/test/sort/symbol.test.js index 4e7cd07..3aa88e1 100644 --- a/packages/react-bootstrap-table2/test/sort/symbol.test.js +++ b/packages/react-bootstrap-table2/test/sort/symbol.test.js @@ -6,8 +6,9 @@ import SortSymbol from '../../src/sort/symbol'; describe('SortSymbol', () => { let wrapper; beforeEach(() => { - wrapper = shallow( - ); + wrapper = shallow(); + const Children = wrapper.props().children({ bootstrap4: false }); + wrapper = shallow(Children); }); it('should render sort symbol correctly', () => { expect(wrapper.length).toBe(1); @@ -16,4 +17,16 @@ describe('SortSymbol', () => { expect(wrapper.find('.dropdown').length).toBe(1); expect(wrapper.find('.dropup').length).toBe(1); }); + + describe('if bootstrap4 prop is true', () => { + beforeEach(() => { + wrapper = shallow(); + const Children = wrapper.props().children({ bootstrap4: true }); + wrapper = shallow(Children); + }); + it('should render sort symbol correctly', () => { + expect(wrapper.length).toBe(1); + expect(wrapper.find('.order-4').length).toBe(1); + }); + }); }); From f7ba8e377d59122c357372dd04ee935589ef265a Mon Sep 17 00:00:00 2001 From: AllenFang Date: Sun, 22 Jul 2018 16:19:59 +0800 Subject: [PATCH 46/55] upgrade enzyme --- package.json | 4 ++-- yarn.lock | 67 ++++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 51 insertions(+), 20 deletions(-) diff --git a/package.json b/package.json index 15e5f9e..5cb8003 100644 --- a/package.json +++ b/package.json @@ -50,8 +50,8 @@ "babel-preset-stage-0": "6.24.1", "babel-register": "6.24.1", "css-loader": "0.28.1", - "enzyme": "3.1.1", - "enzyme-adapter-react-16": "1.0.4", + "enzyme": "3.3.0", + "enzyme-adapter-react-16": "1.1.1", "eslint": "4.5.0", "eslint-config-airbnb": "15.1.0", "eslint-loader": "1.9.0", diff --git a/yarn.lock b/yarn.lock index 5321b1a..ff2fc0d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2692,38 +2692,44 @@ entities@^1.1.1, entities@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0" -enzyme-adapter-react-16@1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.0.4.tgz#67f898cc053452f5c786424e395fe0c63a0607fe" +enzyme-adapter-react-16@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.1.1.tgz#a8f4278b47e082fbca14f5bfb1ee50ee650717b4" dependencies: - enzyme-adapter-utils "^1.1.0" + enzyme-adapter-utils "^1.3.0" lodash "^4.17.4" object.assign "^4.0.4" object.values "^1.0.4" - prop-types "^15.5.10" + prop-types "^15.6.0" + react-reconciler "^0.7.0" react-test-renderer "^16.0.0-0" -enzyme-adapter-utils@^1.1.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/enzyme-adapter-utils/-/enzyme-adapter-utils-1.3.0.tgz#d6c85756826c257a8544d362cc7a67e97ea698c7" +enzyme-adapter-utils@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/enzyme-adapter-utils/-/enzyme-adapter-utils-1.4.0.tgz#c403b81e8eb9953658569e539780964bdc98de62" dependencies: - lodash "^4.17.4" - object.assign "^4.0.4" + object.assign "^4.1.0" prop-types "^15.6.0" -enzyme@3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/enzyme/-/enzyme-3.1.1.tgz#c6948dfccd055d75fbd8627ad1c96a024d0e247b" +enzyme@3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/enzyme/-/enzyme-3.3.0.tgz#0971abd167f2d4bf3f5bd508229e1c4b6dc50479" dependencies: cheerio "^1.0.0-rc.2" function.prototype.name "^1.0.3" + has "^1.0.1" + is-boolean-object "^1.0.0" + is-callable "^1.1.3" + is-number-object "^1.0.3" + is-string "^1.0.4" is-subset "^0.1.1" lodash "^4.17.4" + object-inspect "^1.5.0" object-is "^1.0.1" - object.assign "^4.0.4" + object.assign "^4.1.0" object.entries "^1.0.4" object.values "^1.0.4" - raf "^3.3.2" + raf "^3.4.0" rst-selector-parser "^2.2.3" errno@^0.1.3, errno@^0.1.4: @@ -4293,6 +4299,10 @@ is-binary-path@^1.0.0: dependencies: binary-extensions "^1.0.0" +is-boolean-object@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.0.0.tgz#98f8b28030684219a95f375cfbd88ce3405dff93" + is-buffer@^1.0.2, is-buffer@^1.1.5: version "1.1.6" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" @@ -4420,6 +4430,10 @@ is-negated-glob@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-negated-glob/-/is-negated-glob-1.0.0.tgz#6910bca5da8c95e784b5751b976cf5a10fee36d2" +is-number-object@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.3.tgz#f265ab89a9f445034ef6aff15a8f00b00f551799" + is-number@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f" @@ -4516,6 +4530,10 @@ is-stream@^1.0.0, is-stream@^1.0.1, is-stream@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" +is-string@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.4.tgz#cc3a9b69857d621e963725a24caeec873b826e64" + is-subset@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/is-subset/-/is-subset-0.1.1.tgz#8a59117d932de1de00f245fcdd39ce43f1e939a6" @@ -5978,6 +5996,10 @@ object-hash@^1.1.4: version "1.2.0" resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-1.2.0.tgz#e96af0e96981996a1d47f88ead8f74f1ebc4422b" +object-inspect@^1.5.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.6.0.tgz#c70b6cbf72f274aab4c34c0c82f5167bf82cf15b" + object-is@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.0.1.tgz#0aa60ec9989a0b3ed795cf4d06f62cf1ad6539b6" @@ -5992,7 +6014,7 @@ object-visit@^1.0.0: dependencies: isobject "^3.0.0" -object.assign@^4.0.4: +object.assign@^4.0.4, object.assign@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da" dependencies: @@ -6725,7 +6747,7 @@ prop-types@15.5.10: fbjs "^0.8.9" loose-envify "^1.3.1" -prop-types@^15.5.10, prop-types@^15.6.0: +prop-types@^15.6.0: version "15.6.0" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.0.tgz#ceaf083022fc46b4a35f69e13ef75aed0d639856" dependencies: @@ -6824,7 +6846,7 @@ querystringify@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-1.0.0.tgz#6286242112c5b712fa654e526652bf6a13ff05cb" -raf@^3.3.2: +raf@^3.4.0: version "3.4.0" resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.0.tgz#a28876881b4bc2ca9117d4138163ddb80f781575" dependencies: @@ -6901,6 +6923,15 @@ react-dom@16.3.2: object-assign "^4.1.1" prop-types "^15.6.0" +react-reconciler@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/react-reconciler/-/react-reconciler-0.7.0.tgz#9614894103e5f138deeeb5eabaf3ee80eb1d026d" + dependencies: + fbjs "^0.8.16" + loose-envify "^1.1.0" + object-assign "^4.1.1" + prop-types "^15.6.0" + react-test-renderer@16.0.0: version "16.0.0" resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.0.0.tgz#9fe7b8308f2f71f29fc356d4102086f131c9cb15" From c0416fc307ee943910d79ad3273aac17df02a4e3 Mon Sep 17 00:00:00 2001 From: AllenFang Date: Sun, 22 Jul 2018 16:20:53 +0800 Subject: [PATCH 47/55] fix selection column broken when bootstrap4 --- .../src/row-selection/selection-cell.js | 32 +++++---- .../row-selection/selection-header-cell.js | 49 +++++++++----- .../style/react-bootstrap-table2.scss | 5 ++ .../test/row-selection/selection-cell.test.js | 65 +++++++++++-------- .../selection-header-cell.test.js | 54 +++++++++++---- 5 files changed, 136 insertions(+), 69 deletions(-) diff --git a/packages/react-bootstrap-table2/src/row-selection/selection-cell.js b/packages/react-bootstrap-table2/src/row-selection/selection-cell.js index bec0ef0..e7571a2 100644 --- a/packages/react-bootstrap-table2/src/row-selection/selection-cell.js +++ b/packages/react-bootstrap-table2/src/row-selection/selection-cell.js @@ -5,6 +5,7 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import Const from '../const'; +import { BootstrapContext } from '../contexts/bootstrap'; export default class SelectionCell extends Component { static propTypes = { @@ -59,21 +60,28 @@ export default class SelectionCell extends Component { } = this.props; return ( - + { - selectionRenderer ? selectionRenderer({ - mode: inputType, - checked: selected, - disabled - }) : ( - + ({ bootstrap4 }) => ( + + { + selectionRenderer ? selectionRenderer({ + mode: inputType, + checked: selected, + disabled + }) : ( + + ) + } + ) } - + ); } } diff --git a/packages/react-bootstrap-table2/src/row-selection/selection-header-cell.js b/packages/react-bootstrap-table2/src/row-selection/selection-header-cell.js index c031a72..169740f 100644 --- a/packages/react-bootstrap-table2/src/row-selection/selection-header-cell.js +++ b/packages/react-bootstrap-table2/src/row-selection/selection-header-cell.js @@ -2,11 +2,13 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import Const from '../const'; +import { BootstrapContext } from '../contexts/bootstrap'; -export const CheckBox = ({ checked, indeterminate }) => ( +export const CheckBox = ({ className, checked, indeterminate }) => ( { if (input) input.indeterminate = indeterminate; // eslint-disable-line no-param-reassign } } @@ -15,7 +17,8 @@ export const CheckBox = ({ checked, indeterminate }) => ( CheckBox.propTypes = { checked: PropTypes.bool.isRequired, - indeterminate: PropTypes.bool.isRequired + indeterminate: PropTypes.bool.isRequired, + className: PropTypes.string }; export default class SelectionHeaderCell extends Component { @@ -67,26 +70,36 @@ export default class SelectionHeaderCell extends Component { const attrs = {}; let content; - if (selectionHeaderRenderer) { - content = selectionHeaderRenderer({ - mode, - checked, - indeterminate - }); - attrs.onClick = this.handleCheckBoxClick; - } else if (mode === ROW_SELECT_MULTIPLE) { - content = ( - - ); + if (selectionHeaderRenderer || mode === ROW_SELECT_MULTIPLE) { attrs.onClick = this.handleCheckBoxClick; } return ( - { content } + + { + ({ bootstrap4 }) => { + if (selectionHeaderRenderer) { + content = selectionHeaderRenderer({ + mode, + checked, + indeterminate + }); + } else if (mode === ROW_SELECT_MULTIPLE) { + content = ( + + ); + } + return ( + { content } + ); + } + } + ); } } diff --git a/packages/react-bootstrap-table2/style/react-bootstrap-table2.scss b/packages/react-bootstrap-table2/style/react-bootstrap-table2.scss index 498b012..5974e45 100644 --- a/packages/react-bootstrap-table2/style/react-bootstrap-table2.scss +++ b/packages/react-bootstrap-table2/style/react-bootstrap-table2.scss @@ -59,6 +59,11 @@ width: 30px; } + th > .selection-input-4, + td > .selection-input-4 { + margin: -4px; + } + td.react-bs-table-no-data { text-align: center; } diff --git a/packages/react-bootstrap-table2/test/row-selection/selection-cell.test.js b/packages/react-bootstrap-table2/test/row-selection/selection-cell.test.js index 02e9d85..d3c9eac 100644 --- a/packages/react-bootstrap-table2/test/row-selection/selection-cell.test.js +++ b/packages/react-bootstrap-table2/test/row-selection/selection-cell.test.js @@ -1,7 +1,9 @@ +import 'jsdom-global/register'; import React from 'react'; import { shallow } from 'enzyme'; import sinon from 'sinon'; +import { shallowWithContext } from '../test-helpers/new-context'; import SelectionCell from '../../src/row-selection/selection-cell'; describe('', () => { @@ -52,14 +54,14 @@ describe('', () => { describe('when disabled prop is false', () => { beforeEach(() => { - wrapper = shallow( + wrapper = shallowWithContext( + />, { bootstrap4: false } ); wrapper.find('td').simulate('click'); }); @@ -78,7 +80,7 @@ describe('', () => { describe('when disabled prop is true', () => { beforeEach(() => { - wrapper = shallow( + wrapper = shallowWithContext( ', () => { rowIndex={ rowIndex } onRowSelect={ mockOnRowSelect } disabled - /> + />, { bootstrap4: false } ); wrapper.find('td').simulate('click'); }); @@ -102,14 +104,14 @@ describe('', () => { describe('if selectRow.mode is radio', () => { beforeEach(() => { - wrapper = shallow( + wrapper = shallowWithContext( + />, { bootstrap4: false } ); }); @@ -118,38 +120,27 @@ describe('', () => { wrapper.find('td').simulate('click'); expect(mockOnRowSelect.callCount).toBe(1); expect(mockOnRowSelect.calledWith(rowKey, true, rowIndex)).toBe(true); - - // second click - wrapper.find('td').simulate('click'); - expect(mockOnRowSelect.callCount).toBe(2); - expect(mockOnRowSelect.calledWith(rowKey, true, rowIndex)).toBe(true); }); }); describe('if selectRow.mode is checkbox', () => { beforeEach(() => { - wrapper = shallow( + wrapper = shallowWithContext( + />, { bootstrap4: false } ); }); it('should be called with correct paramters', () => { // first click - wrapper.setProps({ selected: true }); wrapper.find('td').simulate('click'); expect(mockOnRowSelect.callCount).toBe(1); - expect(mockOnRowSelect.calledWith(rowKey, false, rowIndex)).toBe(true); - - // second click - wrapper.setProps({ selected: false }); - wrapper.find('td').simulate('click'); - expect(mockOnRowSelect.callCount).toBe(2); - expect(mockOnRowSelect.calledWith(rowKey, true, rowIndex)).toBe(true); + expect(mockOnRowSelect.calledWith(rowKey, false, rowIndex, undefined)).toBe(true); }); }); }); @@ -159,33 +150,33 @@ describe('', () => { const selected = true; beforeEach(() => { - wrapper = shallow( + wrapper = shallowWithContext( + />, { bootstrap4: false } ); }); it('should render component correctly', () => { expect(wrapper.find('td').length).toBe(1); - expect(wrapper.find('input').length).toBe(1); + expect(wrapper.find('input')).toHaveLength(1); expect(wrapper.find('input').get(0).props.type).toBe(mode); expect(wrapper.find('input').get(0).props.checked).toBe(selected); }); describe('when disabled prop give as true', () => { beforeEach(() => { - wrapper = shallow( + wrapper = shallowWithContext( + />, { bootstrap4: false } ); }); @@ -200,14 +191,14 @@ describe('', () => { beforeEach(() => { selectionRenderer.mockClear(); - wrapper = shallow( + wrapper = shallowWithContext( + />, { bootstrap4: false } ); }); @@ -224,5 +215,23 @@ describe('', () => { }); }); }); + + describe('when bootstrap4 context is true', () => { + beforeEach(() => { + wrapper = shallowWithContext( + , { bootstrap4: true } + ); + }); + + it('should render component correctly', () => { + expect(wrapper.find('td').length).toBe(1); + expect(wrapper.find('.selection-input-4')).toHaveLength(1); + }); + }); }); }); diff --git a/packages/react-bootstrap-table2/test/row-selection/selection-header-cell.test.js b/packages/react-bootstrap-table2/test/row-selection/selection-header-cell.test.js index 73571d4..4423dbf 100644 --- a/packages/react-bootstrap-table2/test/row-selection/selection-header-cell.test.js +++ b/packages/react-bootstrap-table2/test/row-selection/selection-header-cell.test.js @@ -2,6 +2,7 @@ import React from 'react'; import { shallow } from 'enzyme'; import sinon from 'sinon'; +import { shallowWithContext } from '../test-helpers/new-context'; import Const from '../../src/const'; import SelectionHeaderCell, { CheckBox } from '../../src/row-selection/selection-header-cell'; @@ -11,7 +12,7 @@ describe('', () => { describe('shouldComponentUpdate', () => { describe('when props.mode is radio', () => { it('should not update component', () => { - wrapper = shallow(); + wrapper = shallow(, { bootstrap4: false }); expect(wrapper.instance().shouldComponentUpdate({})).toBe(false); }); @@ -24,7 +25,9 @@ describe('', () => { const nextProps = { checkedStatus }; wrapper = shallow( - ); + , + { bootstrap4: false } + ); expect(wrapper.instance().shouldComponentUpdate(nextProps)).toBe(false); }); @@ -37,7 +40,9 @@ describe('', () => { const nextProps = { checkedStatus }; wrapper = shallow( - ); + , + { bootstrap4: false } + ); expect(wrapper.instance().shouldComponentUpdate(nextProps)).toBe(true); }); @@ -57,12 +62,14 @@ describe('', () => { describe('if props.mode is radio', () => { beforeEach(() => { - wrapper = shallow( + wrapper = shallowWithContext( ); + />, + { bootstrap4: false } + ); }); it('should do nothing', () => { @@ -75,12 +82,14 @@ describe('', () => { describe('if props.mode is checkbox', () => { beforeEach(() => { - wrapper = shallow( + wrapper = shallowWithContext( ); + />, + { bootstrap4: false } + ); }); it('should call handleCheckBoxClick', () => { @@ -98,7 +107,10 @@ describe('', () => { beforeEach(() => { const checkedStatus = Const.CHECKBOX_STATUS_CHECKED; - wrapper = shallow(); + wrapper = shallowWithContext( + , + { bootstrap4: false } + ); }); it('should not render checkbox', () => { @@ -112,7 +124,10 @@ describe('', () => { const checkedStatus = Const.CHECKBOX_STATUS_CHECKED; beforeEach(() => { - wrapper = shallow(); + wrapper = shallowWithContext( + , + { bootstrap4: false } + ); }); it('should render checkbox', () => { @@ -134,12 +149,13 @@ describe('', () => { beforeEach(() => { selectionHeaderRenderer.mockClear(); - wrapper = shallow( + wrapper = shallowWithContext( + />, + { bootstrap4: false } ); }); @@ -156,6 +172,22 @@ describe('', () => { }); }); }); + + describe('when bootstrap4 context is true', () => { + beforeEach(() => { + const checkedStatus = Const.CHECKBOX_STATUS_CHECKED; + + wrapper = shallowWithContext( + , + { bootstrap4: true } + ); + }); + + it('should not render checkbox', () => { + expect(wrapper.find('th').length).toBe(1); + expect(wrapper.find('.selection-input-4').length).toBe(1); + }); + }); }); }); From 495875792f1a0bcb2591ae34e896a02599677e62 Mon Sep 17 00:00:00 2001 From: AllenFang Date: Sun, 22 Jul 2018 16:21:15 +0800 Subject: [PATCH 48/55] refine new context API tests --- .../test/sort/caret.test.js | 24 +++++++++---------- .../test/sort/symbol.test.js | 10 +++----- .../test/test-helpers/new-context.js | 7 ++++++ 3 files changed, 21 insertions(+), 20 deletions(-) create mode 100644 packages/react-bootstrap-table2/test/test-helpers/new-context.js diff --git a/packages/react-bootstrap-table2/test/sort/caret.test.js b/packages/react-bootstrap-table2/test/sort/caret.test.js index d524496..50c89da 100644 --- a/packages/react-bootstrap-table2/test/sort/caret.test.js +++ b/packages/react-bootstrap-table2/test/sort/caret.test.js @@ -1,8 +1,8 @@ import React from 'react'; -import { shallow } from 'enzyme'; import Const from '../../src/const'; import SortCaret from '../../src/sort/caret'; +import { shallowWithContext } from '../test-helpers/new-context'; describe('SortCaret', () => { let wrapper; @@ -10,9 +10,10 @@ describe('SortCaret', () => { describe('when bootstrap4 context is false', () => { describe(`when order prop is ${Const.SORT_ASC}`, () => { beforeEach(() => { - wrapper = shallow(); - const Children = wrapper.props().children({ bootstrap4: false }); - wrapper = shallow(Children); + wrapper = shallowWithContext( + , + { bootstrap4: false } + ); }); it('should render caret correctly', () => { @@ -25,9 +26,10 @@ describe('SortCaret', () => { describe(`when order prop is ${Const.SORT_DESC}`, () => { beforeEach(() => { - wrapper = shallow(); - const Children = wrapper.props().children({ bootstrap4: false }); - wrapper = shallow(Children); + wrapper = shallowWithContext( + , + { bootstrap4: false } + ); }); it('should render caret correctly', () => { @@ -42,9 +44,7 @@ describe('SortCaret', () => { describe('when bootstrap4 context is true', () => { describe(`when order prop is ${Const.SORT_ASC}`, () => { beforeEach(() => { - wrapper = shallow(); - const Children = wrapper.props().children({ bootstrap4: true }); - wrapper = shallow(Children); + wrapper = shallowWithContext(, { bootstrap4: true }); }); it('should render caret correctly', () => { @@ -55,9 +55,7 @@ describe('SortCaret', () => { describe(`when order prop is ${Const.SORT_DESC}`, () => { beforeEach(() => { - wrapper = shallow(); - const Children = wrapper.props().children({ bootstrap4: true }); - wrapper = shallow(Children); + wrapper = shallowWithContext(, { bootstrap4: true }); }); it('should render caret correctly', () => { diff --git a/packages/react-bootstrap-table2/test/sort/symbol.test.js b/packages/react-bootstrap-table2/test/sort/symbol.test.js index 3aa88e1..6302b73 100644 --- a/packages/react-bootstrap-table2/test/sort/symbol.test.js +++ b/packages/react-bootstrap-table2/test/sort/symbol.test.js @@ -1,14 +1,12 @@ import React from 'react'; -import { shallow } from 'enzyme'; +import { shallowWithContext } from '../test-helpers/new-context'; import SortSymbol from '../../src/sort/symbol'; describe('SortSymbol', () => { let wrapper; beforeEach(() => { - wrapper = shallow(); - const Children = wrapper.props().children({ bootstrap4: false }); - wrapper = shallow(Children); + wrapper = shallowWithContext(, { bootstrap4: false }); }); it('should render sort symbol correctly', () => { expect(wrapper.length).toBe(1); @@ -20,9 +18,7 @@ describe('SortSymbol', () => { describe('if bootstrap4 prop is true', () => { beforeEach(() => { - wrapper = shallow(); - const Children = wrapper.props().children({ bootstrap4: true }); - wrapper = shallow(Children); + wrapper = shallowWithContext(, { bootstrap4: true }); }); it('should render sort symbol correctly', () => { expect(wrapper.length).toBe(1); diff --git a/packages/react-bootstrap-table2/test/test-helpers/new-context.js b/packages/react-bootstrap-table2/test/test-helpers/new-context.js new file mode 100644 index 0000000..cfbcc58 --- /dev/null +++ b/packages/react-bootstrap-table2/test/test-helpers/new-context.js @@ -0,0 +1,7 @@ +import { shallow } from 'enzyme'; + +export const shallowWithContext = (elem, context = {}) => { + const wrapper = shallow(elem); + const Children = wrapper.props().children(context); + return shallow(Children); +}; From 3f957db56b5797dbcd61f17b0d80a64320b78a47 Mon Sep 17 00:00:00 2001 From: AllenFang Date: Sun, 22 Jul 2018 17:32:41 +0800 Subject: [PATCH 49/55] fix pagination broken when bootstrap4 --- .../src/bootstrap.js | 6 ++ .../src/context.js | 55 +++++++------ .../src/size-per-page-dropdown.js | 79 +++++++++++-------- .../src/size-per-page-option.js | 34 ++++++-- .../test/size-per-page-dropdown.test.js | 78 +++++++++++++++--- .../test/size-per-page-option.test.js | 71 ++++++++++++----- .../src/contexts/index.js | 1 + 7 files changed, 232 insertions(+), 92 deletions(-) create mode 100644 packages/react-bootstrap-table2-paginator/src/bootstrap.js diff --git a/packages/react-bootstrap-table2-paginator/src/bootstrap.js b/packages/react-bootstrap-table2-paginator/src/bootstrap.js new file mode 100644 index 0000000..1ee0db1 --- /dev/null +++ b/packages/react-bootstrap-table2-paginator/src/bootstrap.js @@ -0,0 +1,6 @@ +import React from 'react'; + +// consider to have a common lib?1 +export const BootstrapContext = React.createContext({ + bootstrap4: false +}); diff --git a/packages/react-bootstrap-table2-paginator/src/context.js b/packages/react-bootstrap-table2-paginator/src/context.js index 7f508a3..10d72ff 100644 --- a/packages/react-bootstrap-table2-paginator/src/context.js +++ b/packages/react-bootstrap-table2-paginator/src/context.js @@ -5,6 +5,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import Const from './const'; +import { BootstrapContext } from './bootstrap'; import Pagination from './pagination'; import { getByCurrPage, alignPage } from './page'; @@ -123,7 +124,7 @@ export default ( render() { let { data } = this.props; - const { pagination: { options } } = this.props; + const { pagination: { options }, bootstrap4 } = this.props; const { currPage, currSizePerPage } = this; const withFirstAndLast = typeof options.withFirstAndLast === 'undefined' ? Const.With_FIRST_AND_LAST : options.withFirstAndLast; @@ -148,31 +149,33 @@ export default ( return ( { this.props.children } - + + + ); } diff --git a/packages/react-bootstrap-table2-paginator/src/size-per-page-dropdown.js b/packages/react-bootstrap-table2-paginator/src/size-per-page-dropdown.js index 5ae4298..2d8fcc4 100644 --- a/packages/react-bootstrap-table2-paginator/src/size-per-page-dropdown.js +++ b/packages/react-bootstrap-table2-paginator/src/size-per-page-dropdown.js @@ -1,6 +1,7 @@ import React from 'react'; import cs from 'classnames'; import PropTypes from 'prop-types'; +import { BootstrapContext } from './bootstrap'; import SizePerPageOption from './size-per-page-option'; const sizePerPageDefaultClass = 'react-bs-table-sizePerPage-dropdown'; @@ -20,44 +21,60 @@ const SizePerPageDropDown = (props) => { } = props; const dropDownStyle = { visibility: hidden ? 'hidden' : 'visible' }; + const openClass = open ? 'open show' : ''; const dropdownClasses = cs( - open ? 'open show' : '', + openClass, sizePerPageDefaultClass, variation, className, ); return ( - - -
    - { - options.map(option => ( - - )) - } -
-
+ + { + ({ bootstrap4 }) => ( + + +
    + { + options.map(option => ( + + )) + } +
+
+ ) + } +
); }; diff --git a/packages/react-bootstrap-table2-paginator/src/size-per-page-option.js b/packages/react-bootstrap-table2-paginator/src/size-per-page-option.js index eba6ca6..a01a360 100644 --- a/packages/react-bootstrap-table2-paginator/src/size-per-page-option.js +++ b/packages/react-bootstrap-table2-paginator/src/size-per-page-option.js @@ -5,9 +5,28 @@ import PropTypes from 'prop-types'; const SizePerPageOption = ({ text, page, - onSizePerPageChange -}) => ( -
  • + onSizePerPageChange, + bootstrap4 +}) => (bootstrap4 ? ( + { + e.preventDefault(); + onSizePerPageChange(page); + } } + > + { text } + +) : ( +
  • -); +)); SizePerPageOption.propTypes = { text: PropTypes.string.isRequired, page: PropTypes.number.isRequired, - onSizePerPageChange: PropTypes.func.isRequired + onSizePerPageChange: PropTypes.func.isRequired, + bootstrap4: PropTypes.bool +}; + +SizePerPageOption.defaultProps = { + bootstrap4: false }; export default SizePerPageOption; diff --git a/packages/react-bootstrap-table2-paginator/test/size-per-page-dropdown.test.js b/packages/react-bootstrap-table2-paginator/test/size-per-page-dropdown.test.js index 21e11cc..fdf55bd 100644 --- a/packages/react-bootstrap-table2-paginator/test/size-per-page-dropdown.test.js +++ b/packages/react-bootstrap-table2-paginator/test/size-per-page-dropdown.test.js @@ -5,6 +5,12 @@ import { shallow } from 'enzyme'; import SizePerPageOption from '../src/size-per-page-option'; import SizePerPageDropDown from '../src/size-per-page-dropdown'; +const shallowWithContext = (elem, context = {}) => { + const wrapper = shallow(elem); + const Children = wrapper.props().children(context); + return shallow(Children); +}; + describe('SizePerPageDropDown', () => { let wrapper; const currSizePerPage = '25'; @@ -28,8 +34,9 @@ describe('SizePerPageDropDown', () => { describe('default SizePerPageDropDown component', () => { beforeEach(() => { - wrapper = shallow( - + wrapper = shallowWithContext( + , + { bootstrap4: false } ); }); @@ -47,6 +54,7 @@ describe('SizePerPageDropDown', () => { const option = options[i]; expect(sizePerPage.prop('text')).toEqual(option.text); expect(sizePerPage.prop('page')).toEqual(option.page); + expect(sizePerPage.prop('bootstrap4')).toBeFalsy(); expect(sizePerPage.prop('onSizePerPageChange')).toEqual(onSizePerPageChange); }); }); @@ -61,10 +69,52 @@ describe('SizePerPageDropDown', () => { }); }); + describe('when bootstrap4 context is true', () => { + beforeEach(() => { + wrapper = shallowWithContext( + , + { bootstrap4: true } + ); + }); + + it('should rendering SizePerPageDropDown correctly', () => { + expect(wrapper.length).toBe(1); + expect(wrapper.find('button').length).toBe(1); + expect(wrapper.find('button').text()).toEqual(`${currSizePerPage} `); + }); + + it('should rendering SizePerPageOption successfully', () => { + expect(wrapper.find('ul.dropdown-menu').length).toBe(1); + const sizePerPageOptions = wrapper.find(SizePerPageOption); + expect(sizePerPageOptions.length).toBe(options.length); + sizePerPageOptions.forEach((sizePerPage, i) => { + const option = options[i]; + expect(sizePerPage.prop('text')).toEqual(option.text); + expect(sizePerPage.prop('page')).toEqual(option.page); + expect(sizePerPage.prop('bootstrap4')).toBeTruthy(); + expect(sizePerPage.prop('onSizePerPageChange')).toEqual(onSizePerPageChange); + }); + }); + + it('no need to render caret', () => { + expect(wrapper.find('.caret')).toHaveLength(0); + }); + + it('default variation is dropdown', () => { + expect(wrapper.hasClass('dropdown')).toBeTruthy(); + }); + + it('default dropdown is not open', () => { + expect(wrapper.hasClass('open show')).toBeFalsy(); + expect(wrapper.find('[aria-expanded=false]').length).toBe(1); + }); + }); + describe('when open prop is true', () => { beforeEach(() => { - wrapper = shallow( - + wrapper = shallowWithContext( + , + { bootstrap4: false } ); }); @@ -76,8 +126,9 @@ describe('SizePerPageDropDown', () => { describe('when hidden prop is true', () => { beforeEach(() => { - wrapper = shallow( - bootstrap4 - [Bool] +`true` to indicate your bootstrap version is 4. Default version is 3. + ### loading - [Bool] Telling if table is loading or not, for example: waiting data loading, filtering etc. It's **only** valid when [`remote`](#remote) is enabled. When `loading` is `true`, `react-bootstrap-table2` will attend to render a overlay on table via [`overlay`](#overlay) prop, if [`overlay`](#overlay) prop is not given, `react-bootstrap-table2` will ignore the overlay rendering. diff --git a/docs/migration.md b/docs/migration.md index a4b4c2c..ffe7861 100644 --- a/docs/migration.md +++ b/docs/migration.md @@ -134,7 +134,7 @@ No big changes for pagination, but still can't custom the pagination list, butto - [ ] Expand Column Style/Class ## Export CSV -Export CSV functionality is like search, which is one of functionality in the `react-bootstrap-table2-toolkit`. But all the legacy function we already implemented. +Export CSV functionality is like search, which is one of functionality in the `react-bootstrap-table2-toolkit`. All of the legacy functions we already implemented. ## Remote diff --git a/packages/react-bootstrap-table2-toolkit/README.md b/packages/react-bootstrap-table2-toolkit/README.md index ace3fb4..2171da5 100644 --- a/packages/react-bootstrap-table2-toolkit/README.md +++ b/packages/react-bootstrap-table2-toolkit/README.md @@ -2,7 +2,7 @@ `react-bootstrap-table2` support some additional features in [`react-bootstrap-table2-toolkit`](https://github.com/react-bootstrap-table/react-bootstrap-table2/tree/develop/packages/react-bootstrap-table2-toolkit). -In the future, this toolkit will support other feature like row delete, insert and export csv etc. Right now we only support Table Search and CSV export. +In the future, this toolkit will support other feature like row delete, insert etc. Right now we only support Table Search and CSV export. **[Live Demo For Table Search](https://react-bootstrap-table.github.io/react-bootstrap-table2/storybook/index.html?selectedKind=Table%20Search)** From f7406bcafc8c19b5fec01796f664c201ba6a6950 Mon Sep 17 00:00:00 2001 From: AllenFang Date: Sat, 28 Jul 2018 14:49:12 +0800 Subject: [PATCH 52/55] add toolkits styles --- gulpfile.babel.js | 3 ++- packages/react-bootstrap-table2-toolkit/README.md | 10 ++++++++++ .../style/react-bootstrap-table2-toolkit.scss | 0 3 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 packages/react-bootstrap-table2-toolkit/style/react-bootstrap-table2-toolkit.scss diff --git a/gulpfile.babel.js b/gulpfile.babel.js index 228bfa3..fea8b18 100644 --- a/gulpfile.babel.js +++ b/gulpfile.babel.js @@ -26,7 +26,8 @@ const JS_SKIPS = `+(${TEST}|${LIB}|${DIST}|${NODE_MODULES})`; const STYLE_PKGS = [ 'react-bootstrap-table2', 'react-bootstrap-table2-filter', - 'react-bootstrap-table2-paginator' + 'react-bootstrap-table2-paginator', + 'react-bootstrap-table2-toolkit', ].reduce((pkg, curr) => `${curr}|${pkg}`, ''); const STYLE_SKIPS = `+(${NODE_MODULES})`; diff --git a/packages/react-bootstrap-table2-toolkit/README.md b/packages/react-bootstrap-table2-toolkit/README.md index 2171da5..5d502e1 100644 --- a/packages/react-bootstrap-table2-toolkit/README.md +++ b/packages/react-bootstrap-table2-toolkit/README.md @@ -16,6 +16,16 @@ In the future, this toolkit will support other feature like row delete, insert e $ npm install react-bootstrap-table2-toolkit --save ``` +## Add CSS + +```js +// es5 +require('react-bootstrap-table2-toolkit/dist/react-bootstrap-table2-toolkit.min.css'); + +// es6 +import 'react-bootstrap-table2-toolkit/dist/react-bootstrap-table2-toolkit.min.css'; +``` + ## Table Search ```js diff --git a/packages/react-bootstrap-table2-toolkit/style/react-bootstrap-table2-toolkit.scss b/packages/react-bootstrap-table2-toolkit/style/react-bootstrap-table2-toolkit.scss new file mode 100644 index 0000000..e69de29 From 2ec55f6de91a0ff1c3983f3ca4fc9be7cd373b8a Mon Sep 17 00:00:00 2001 From: AllenFang Date: Sat, 28 Jul 2018 15:47:53 +0800 Subject: [PATCH 53/55] patch for default sort and filter have potential issue when remote --- packages/react-bootstrap-table2-filter/src/components/date.js | 4 ++-- .../test/components/date.test.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/react-bootstrap-table2-filter/src/components/date.js b/packages/react-bootstrap-table2-filter/src/components/date.js index da751d2..be6ecd5 100644 --- a/packages/react-bootstrap-table2-filter/src/components/date.js +++ b/packages/react-bootstrap-table2-filter/src/components/date.js @@ -92,7 +92,7 @@ class DateFilter extends Component { return defaultDate; } - applyFilter(value, comparator) { + applyFilter(value, comparator, isInitial) { // if (!comparator || !value) { // return; // } @@ -103,7 +103,7 @@ class DateFilter extends Component { // instead of parsing an invalid Date. The filter function will interpret // null as an empty date field const date = value === '' ? null : new Date(value); - onFilter(column, FILTER_TYPE.DATE, )({ date, comparator }); + onFilter(column, FILTER_TYPE.DATE, isInitial)({ date, comparator }); }; if (delay) { this.timeout = setTimeout(() => { execute(); }, delay); diff --git a/packages/react-bootstrap-table2-filter/test/components/date.test.js b/packages/react-bootstrap-table2-filter/test/components/date.test.js index b421333..9fb3776 100644 --- a/packages/react-bootstrap-table2-filter/test/components/date.test.js +++ b/packages/react-bootstrap-table2-filter/test/components/date.test.js @@ -124,7 +124,7 @@ describe('Date Filter', () => { it('should do onFilter correctly when exported function was executed', () => { expect(onFilter).toHaveBeenCalledTimes(1); - expect(onFilter).toHaveBeenCalledWith(column, FILTER_TYPE.DATE, false); + expect(onFilter).toHaveBeenCalledWith(column, FILTER_TYPE.DATE, undefined); expect(onFilterFirstReturn).toHaveBeenCalledTimes(1); expect(onFilterFirstReturn).toHaveBeenCalledWith({ comparator, date }); }); From d5d8c54d98db8ed1373a30eb7716fe9ee8c78ca8 Mon Sep 17 00:00:00 2001 From: AllenFang Date: Sat, 28 Jul 2018 15:57:52 +0800 Subject: [PATCH 54/55] fix React doesn't allow Date Object as children --- .../examples/column-filter/custom-date-filter.js | 1 + .../examples/column-filter/date-filter-default-value.js | 1 + .../examples/column-filter/date-filter.js | 1 + .../examples/column-filter/programmatically-date-filter.js | 1 + packages/react-bootstrap-table2-filter/README.md | 2 +- 5 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/react-bootstrap-table2-example/examples/column-filter/custom-date-filter.js b/packages/react-bootstrap-table2-example/examples/column-filter/custom-date-filter.js index 226708d..bb04522 100644 --- a/packages/react-bootstrap-table2-example/examples/column-filter/custom-date-filter.js +++ b/packages/react-bootstrap-table2-example/examples/column-filter/custom-date-filter.js @@ -15,6 +15,7 @@ const columns = [{ }, { dataField: 'inStockDate', text: 'InStock Date', + formatter: cell => cell.toString(), filter: dateFilter({ delay: 400, placeholder: 'custom placeholder', diff --git a/packages/react-bootstrap-table2-example/examples/column-filter/date-filter-default-value.js b/packages/react-bootstrap-table2-example/examples/column-filter/date-filter-default-value.js index c292531..2fcba33 100644 --- a/packages/react-bootstrap-table2-example/examples/column-filter/date-filter-default-value.js +++ b/packages/react-bootstrap-table2-example/examples/column-filter/date-filter-default-value.js @@ -15,6 +15,7 @@ const columns = [{ }, { dataField: 'inStockDate', text: 'InStock Date', + formatter: cell => cell.toString(), filter: dateFilter({ defaultValue: { date: new Date(2018, 0, 1), comparator: Comparator.GT } }) diff --git a/packages/react-bootstrap-table2-example/examples/column-filter/date-filter.js b/packages/react-bootstrap-table2-example/examples/column-filter/date-filter.js index 24cd05b..85bcb89 100644 --- a/packages/react-bootstrap-table2-example/examples/column-filter/date-filter.js +++ b/packages/react-bootstrap-table2-example/examples/column-filter/date-filter.js @@ -15,6 +15,7 @@ const columns = [{ }, { dataField: 'inStockDate', text: 'InStock Date', + formatter: cell => cell.toString(), filter: dateFilter() }]; diff --git a/packages/react-bootstrap-table2-example/examples/column-filter/programmatically-date-filter.js b/packages/react-bootstrap-table2-example/examples/column-filter/programmatically-date-filter.js index d620120..7b25667 100644 --- a/packages/react-bootstrap-table2-example/examples/column-filter/programmatically-date-filter.js +++ b/packages/react-bootstrap-table2-example/examples/column-filter/programmatically-date-filter.js @@ -17,6 +17,7 @@ const columns = [{ }, { dataField: 'inStockDate', text: 'InStock Date', + formatter: cell => cell.toString(), filter: dateFilter({ getFilter: (filter) => { // inStockDateFilter was assigned once the component has been mounted. diff --git a/packages/react-bootstrap-table2-filter/README.md b/packages/react-bootstrap-table2-filter/README.md index 38c278e..05c7c9e 100644 --- a/packages/react-bootstrap-table2-filter/README.md +++ b/packages/react-bootstrap-table2-filter/README.md @@ -214,7 +214,7 @@ const columns = [..., { ``` -> **Notes:** date filter accept a Javascript Date object in your raw data. +> **Notes:** date filter accept a Javascript Date object in your raw data and you have to use `column.formatter` to make it as your prefer string result Date filter is same as other filter, you can custom the date filter via `dateFilter` factory function: From cb970cded5f2b201138aa2a445194d39bad08822 Mon Sep 17 00:00:00 2001 From: AllenFang Date: Sat, 4 Aug 2018 14:47:46 +0800 Subject: [PATCH 55/55] fix peerdep --- packages/react-bootstrap-table2-example/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react-bootstrap-table2-example/package.json b/packages/react-bootstrap-table2-example/package.json index 657a8cd..be6d702 100644 --- a/packages/react-bootstrap-table2-example/package.json +++ b/packages/react-bootstrap-table2-example/package.json @@ -14,8 +14,8 @@ "license": "ISC", "peerDependencies": { "prop-types": "^15.0.0", - "react": "^15.0.0", - "react-dom": "^15.0.0" + "react": "^16.3.0", + "react-dom": "^116.3.0" }, "dependencies": { "bootstrap": "^3.3.7"