Merge pull request #607 from react-bootstrap-table/enhance/598

Enhance/598
This commit is contained in:
Allen 2018-10-14 14:49:43 +08:00 committed by GitHub
commit ae4d38cae6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 250 additions and 15 deletions

View File

@ -17,6 +17,7 @@
* [hover](#hover)
* [condensed](#condensed)
* [id](#id)
* [tabIndexCell](#tabIndexCell)
* [classes](#classes)
* [wrapperClasses](#wrapperClasses)
* [headerClasses](#headerClasses)
@ -112,6 +113,10 @@ Same as bootstrap `.table-condensed` class for making a table more compact by cu
### <a name='id'>id - [String]</a>
Customize id on `table` element.
### <a name='tabIndexCell'>tabIndexCell - [Bool]</a>
Enable the `tabIndex` attribute on `<td>` element.
### <a name='classes'>classes - [String]</a>
Customize class on `table` element.

View File

@ -0,0 +1,54 @@
import React from 'react';
import BootstrapTable from 'react-bootstrap-table-next';
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';
const columns = [{
dataField: 'id',
text: 'Product ID'
}, {
dataField: 'name',
text: 'Product Name'
}, {
dataField: 'price',
text: 'Product Price'
}];
<BootstrapTable
keyField='id'
data={ products }
columns={ columns }
selectRow={ { mode: 'checkbox' } }
tabIndexCell
/>
`;
export default () => (
<div>
<BootstrapTable
keyField="id"
data={ products }
columns={ columns }
selectRow={ { mode: 'checkbox' } }
tabIndexCell
/>
<Code>{ sourceCode }</Code>
</div>
);

View File

@ -14,6 +14,7 @@ import CustomizedIdClassesTable from 'examples/basic/customized-id-classes';
import CaptionTable from 'examples/basic/caption-table';
import LargeTable from 'examples/basic/large-table';
import ExposedAPITable from 'examples/basic/exposed-function';
import TabIndexCellTable from 'examples/basic/tabindex-column';
// bootstrap 4
import Bootstrap4DefaultSortTable from 'examples/bootstrap4/sort';
@ -195,7 +196,8 @@ storiesOf('Basic Table', module)
.add('Customized id and class table', () => <CustomizedIdClassesTable />)
.add('Table with caption', () => <CaptionTable />)
.add('Large Table', () => <LargeTable />)
.add('Exposed API', () => <ExposedAPITable />);
.add('Exposed API', () => <ExposedAPITable />)
.add('Enable tabIndex on Cell', () => <TabIndexCellTable />);
storiesOf('Bootstrap 4', module)
.addDecorator(bootstrapStyle(BOOTSTRAP_VERSION.FOUR))

View File

@ -5,7 +5,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import _ from './utils';
import Row from './row/simple-row';
import SimpleRow from './row/simple-row';
import RowAggregator from './row/aggregate-row';
import RowSection from './row/row-section';
import Const from './const';
@ -24,6 +24,7 @@ class Body extends React.Component {
const {
columns,
data,
tabIndexCell,
keyField,
isEmpty,
noDataIndication,
@ -45,7 +46,7 @@ class Body extends React.Component {
}
content = <RowSection content={ indication } colSpan={ visibleColumnSize } />;
} else {
let RowComponent = Row;
let RowComponent = SimpleRow;
const selectRowEnabled = selectRow.mode !== Const.ROW_SELECT_DISABLED;
const expandRowEnabled = !!expandRow.renderer;
@ -73,11 +74,13 @@ class Body extends React.Component {
const baseRowProps = {
key,
row,
tabIndexCell,
columns,
keyField,
cellEdit,
value: key,
rowIndex: index,
visibleColumnSize,
attrs: rowEvents || {},
...additionalRowProps
};

View File

@ -43,6 +43,7 @@ class BootstrapTable extends PropsBaseResolver(Component) {
data,
columns,
keyField,
tabIndexCell,
id,
classes,
striped,
@ -89,6 +90,7 @@ class BootstrapTable extends PropsBaseResolver(Component) {
<Body
data={ data }
keyField={ keyField }
tabIndexCell={ tabIndexCell }
columns={ columns }
isEmpty={ this.isEmpty() }
visibleColumnSize={ this.visibleColumnSize() }
@ -118,6 +120,7 @@ BootstrapTable.propTypes = {
striped: PropTypes.bool,
bordered: PropTypes.bool,
hover: PropTypes.bool,
tabIndexCell: PropTypes.bool,
id: PropTypes.string,
classes: PropTypes.string,
wrapperClasses: PropTypes.string,

View File

@ -34,7 +34,8 @@ class Cell extends Component {
!_.isEqual(this.props.style, nextProps.style) ||
!_.isEqual(this.props.column.formatExtraData, nextProps.column.formatExtraData) ||
!_.isEqual(this.props.column.events, nextProps.column.events) ||
!_.isEqual(this.props.column.attrs, nextProps.column.attrs);
!_.isEqual(this.props.column.attrs, nextProps.column.attrs) ||
this.props.tabIndex !== nextProps.tabIndex;
return shouldUpdate;
}

View File

@ -12,7 +12,8 @@ export default class ExpandCell extends Component {
expanded: PropTypes.bool.isRequired,
onRowExpand: PropTypes.func.isRequired,
expandColumnRenderer: PropTypes.func,
rowIndex: PropTypes.number
rowIndex: PropTypes.number,
tabIndex: PropTypes.number
}
constructor() {
@ -20,6 +21,16 @@ export default class ExpandCell extends Component {
this.handleClick = this.handleClick.bind(this);
}
shouldComponentUpdate(nextProps) {
const shouldUpdate =
this.props.rowIndex !== nextProps.rowIndex ||
this.props.expanded !== nextProps.expanded ||
this.props.rowKey !== nextProps.rowKey ||
this.props.tabIndex !== nextProps.tabIndex;
return shouldUpdate;
}
handleClick(e) {
const { rowKey, expanded, onRowExpand, rowIndex } = this.props;
@ -27,10 +38,12 @@ export default class ExpandCell extends Component {
}
render() {
const { expanded, expandColumnRenderer } = this.props;
const { expanded, expandColumnRenderer, tabIndex } = this.props;
const attrs = {};
if (tabIndex !== -1) attrs.tabIndex = tabIndex;
return (
<td onClick={ this.handleClick }>
<td onClick={ this.handleClick } { ...attrs }>
{
expandColumnRenderer ? expandColumnRenderer({
expanded

View File

@ -15,6 +15,7 @@ export default class SelectionCell extends Component {
onRowSelect: PropTypes.func,
disabled: PropTypes.bool,
rowIndex: PropTypes.number,
tabIndex: PropTypes.number,
clickToSelect: PropTypes.bool,
selectionRenderer: PropTypes.func
}
@ -29,7 +30,8 @@ export default class SelectionCell extends Component {
this.props.rowIndex !== nextProps.rowIndex ||
this.props.selected !== nextProps.selected ||
this.props.disabled !== nextProps.disabled ||
this.props.rowKey !== nextProps.rowKey;
this.props.rowKey !== nextProps.rowKey ||
this.props.tabIndex !== nextProps.tabIndex;
return shouldUpdate;
}
@ -60,14 +62,18 @@ export default class SelectionCell extends Component {
mode: inputType,
selected,
disabled,
tabIndex,
selectionRenderer
} = this.props;
const attrs = {};
if (tabIndex !== -1) attrs.tabIndex = tabIndex;
return (
<BootstrapContext.Consumer>
{
({ bootstrap4 }) => (
<td onClick={ this.handleClick }>
<td onClick={ this.handleClick } { ...attrs }>
{
selectionRenderer ? selectionRenderer({
mode: inputType,

View File

@ -1,4 +1,5 @@
/* eslint react/prop-types: 0 */
/* eslint no-plusplus: 0 */
import React from 'react';
import PropTypes from 'prop-types';
import _ from '../utils';
@ -55,6 +56,8 @@ export default class RowAggregator extends shouldUpdater(eventDelegater(React.Co
expanded,
selected,
selectable,
visibleColumnSize,
tabIndexCell,
...rest
} = this.props;
const key = _.get(row, keyField);
@ -66,6 +69,8 @@ export default class RowAggregator extends shouldUpdater(eventDelegater(React.Co
newAttrs.onClick = this.createClickEventHandler(newAttrs.onClick);
}
let tabIndexStart = (rowIndex * visibleColumnSize) + 1;
return (
<tr
style={ style }
@ -79,6 +84,7 @@ export default class RowAggregator extends shouldUpdater(eventDelegater(React.Co
rowKey={ key }
rowIndex={ rowIndex }
expanded={ expanded }
tabIndex={ tabIndexCell ? tabIndexStart++ : -1 }
/>
) : null
}
@ -91,6 +97,7 @@ export default class RowAggregator extends shouldUpdater(eventDelegater(React.Co
rowIndex={ rowIndex }
selected={ selected }
disabled={ !selectable }
tabIndex={ tabIndexCell ? tabIndexStart++ : -1 }
/>
)
: null
@ -101,6 +108,7 @@ export default class RowAggregator extends shouldUpdater(eventDelegater(React.Co
keyField={ keyField }
rowIndex={ rowIndex }
shouldUpdate={ this.shouldUpdateRowContent }
tabIndexStart={ tabIndexCell ? tabIndexStart : -1 }
{ ...rest }
/>
</tr>

View File

@ -1,5 +1,6 @@
/* eslint react/prop-types: 0 */
/* eslint react/no-array-index-key: 0 */
/* eslint no-plusplus: 0 */
import React from 'react';
import _ from '../utils';
@ -25,9 +26,12 @@ export default class RowPureContent extends React.Component {
onStart,
clickToEdit,
dbclickToEdit,
EditingCellComponent
EditingCellComponent,
tabIndexStart
} = this.props;
let tabIndex = tabIndexStart;
return columns.map((column, index) => {
if (!column.hidden) {
const { dataField } = column;
@ -87,6 +91,10 @@ export default class RowPureContent extends React.Component {
editableCell = column.editable(content, row, rowIndex, index);
}
if (tabIndexStart !== -1) {
cellAttrs.tabIndex = tabIndex++;
}
return (
<Cell
key={ `${content}-${index}` }

View File

@ -7,7 +7,7 @@ import RowPureContent from './row-pure-content';
import eventDelegater from './event-delegater';
import shouldUpdater from './should-updater';
class Row extends shouldUpdater(eventDelegater(Component)) {
class SimpleRow extends shouldUpdater(eventDelegater(Component)) {
constructor(props) {
super(props);
this.shouldUpdateRowContent = false;
@ -26,19 +26,26 @@ class Row extends shouldUpdater(eventDelegater(Component)) {
className,
style,
attrs,
visibleColumnSize,
tabIndexCell,
...rest
} = this.props;
const trAttrs = this.delegate(attrs);
const tabIndexStart = (this.props.rowIndex * visibleColumnSize) + 1;
return (
<tr style={ style } className={ className } { ...trAttrs }>
<RowPureContent shouldUpdate={ this.shouldUpdateRowContent } { ...rest } />
<RowPureContent
shouldUpdate={ this.shouldUpdateRowContent }
tabIndexStart={ tabIndexCell ? tabIndexStart : -1 }
{ ...rest }
/>
</tr>
);
}
}
Row.propTypes = {
SimpleRow.propTypes = {
row: PropTypes.object.isRequired,
rowIndex: PropTypes.number.isRequired,
columns: PropTypes.array.isRequired,
@ -47,11 +54,11 @@ Row.propTypes = {
attrs: PropTypes.object
};
Row.defaultProps = {
SimpleRow.defaultProps = {
editable: true,
style: {},
className: null,
attrs: {}
};
export default Row;
export default SimpleRow;

View File

@ -198,6 +198,26 @@ describe('Cell', () => {
});
});
describe('when props.tabIndex is change', () => {
const column = { dataField: 'name', text: 'Product Name' };
beforeEach(() => {
props = {
row,
columnIndex: 1,
rowIndex: 1,
tabIndex: 5,
column
};
wrapper = shallow(
<Cell { ...props } />);
});
it('should return true', () => {
nextProps = { ...props, tabIndex: 2 };
expect(wrapper.instance().shouldComponentUpdate(nextProps)).toBe(true);
});
});
describe('if column.isDummyField is true', () => {
describe('when content is change', () => {
const column = { dataField: '', text: 'Product Name', isDummyField: true };

View File

@ -57,6 +57,27 @@ describe('<SelectionCell />', () => {
});
});
describe('when tabIndex prop has been changed', () => {
beforeEach(() => {
props = {
selected: false,
mode,
rowIndex,
disabled: false,
tabIndex: 0,
rowKey: 1
};
wrapper = shallow(
<SelectionCell { ...props } />
);
});
it('should return true', () => {
nextProps = { ...props, tabIndex: 2 };
expect(wrapper.instance().shouldComponentUpdate(nextProps)).toBe(true);
});
});
describe('when disabled prop has been changed', () => {
beforeEach(() => {
props = {

View File

@ -104,6 +104,47 @@ describe('RowPureContent', () => {
});
});
describe('when tabIndexStart prop is -1', () => {
beforeEach(() => {
wrapper = shallow(
<RowPureContent
tabIndexStart={ -1 }
keyField={ keyField }
rowIndex={ rowIndex }
columns={ defaultColumns }
row={ row }
/>
);
});
it('should not render tabIndex prop on Cell', () => {
wrapper.find(Cell).forEach((cell) => {
expect(cell.prop('tabIndex')).toBeUndefined();
});
});
});
describe('when tabIndexStart prop is not -1', () => {
const tabIndexStart = 4;
beforeEach(() => {
wrapper = shallow(
<RowPureContent
tabIndexStart={ tabIndexStart }
keyField={ keyField }
rowIndex={ rowIndex }
columns={ defaultColumns }
row={ row }
/>
);
});
it('should render correct tabIndex prop on Cell', () => {
wrapper.find(Cell).forEach((cell, i) => {
expect(cell.prop('tabIndex')).toEqual(tabIndexStart + i);
});
});
});
describe('when editingRowIdx and editingColIdx prop is defined', () => {
const editingRowIdx = rowIndex;
const editingColIdx = 1;

View File

@ -57,6 +57,49 @@ describe('SimpleRow', () => {
expect(wrapper.length).toBe(1);
expect(wrapper.find(RowPureContent)).toHaveLength(1);
});
describe('when tabIndexCell prop is enable', () => {
const visibleColumnSize = 3;
beforeEach(() => {
wrapper = shallow(
<SimpleRow
keyField={ keyField }
rowIndex={ rowIndex }
columns={ defaultColumns }
row={ row }
tabIndexCell
visibleColumnSize={ visibleColumnSize }
/>
);
});
it('should render correct tabIndexStart', () => {
expect(wrapper.length).toBe(1);
expect(wrapper.find(RowPureContent)).toHaveLength(1);
expect(wrapper.find(RowPureContent).prop('tabIndexStart')).toBe((rowIndex * visibleColumnSize) + 1);
});
});
describe('when tabIndexCell prop is disable', () => {
const visibleColumnSize = 3;
beforeEach(() => {
wrapper = shallow(
<SimpleRow
keyField={ keyField }
rowIndex={ rowIndex }
columns={ defaultColumns }
row={ row }
visibleColumnSize={ visibleColumnSize }
/>
);
});
it('should always render tabIndexStart as -1', () => {
expect(wrapper.length).toBe(1);
expect(wrapper.find(RowPureContent)).toHaveLength(1);
expect(wrapper.find(RowPureContent).prop('tabIndexStart')).toBe(-1);
});
});
});
describe('shouldComponentUpdate', () => {