mirror of
https://github.com/gosticks/react-bootstrap-table2.git
synced 2026-02-27 10:42:51 +00:00
Merge pull request #165 from react-bootstrap-table/enhance/cell-edit-as-package
react-bootstrap-table2-editor
This commit is contained in:
commit
55fe0075b1
@ -1,3 +1,10 @@
|
||||
# Cell Editing
|
||||
Before start to use cell edit, please remember to install `react-bootstrap-table2-editor`
|
||||
|
||||
```sh
|
||||
$ npm install react-bootstrap-table2-editor --save
|
||||
```
|
||||
|
||||
# Properties on cellEdit prop
|
||||
* [mode (**required**)](#mode)
|
||||
* [blurToSave](#blurToSave)
|
||||
|
||||
11
packages/react-bootstrap-table2-editor/package.json
Normal file
11
packages/react-bootstrap-table2-editor/package.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "react-bootstrap-table2-editor",
|
||||
"version": "0.0.1",
|
||||
"description": "it's the editor addon for react-bootstrap-table2",
|
||||
"main": "src/index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC"
|
||||
}
|
||||
4
packages/react-bootstrap-table2-editor/src/const.js
vendored
Normal file
4
packages/react-bootstrap-table2-editor/src/const.js
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
export const TIME_TO_CLOSE_MESSAGE = 3000;
|
||||
export const DELAY_FOR_DBCLICK = 200;
|
||||
export const CLICK_TO_CELL_EDIT = 'click';
|
||||
export const DBCLICK_TO_CELL_EDIT = 'dbclick';
|
||||
153
packages/react-bootstrap-table2-editor/src/editing-cell.js
vendored
Normal file
153
packages/react-bootstrap-table2-editor/src/editing-cell.js
vendored
Normal file
@ -0,0 +1,153 @@
|
||||
/* eslint react/prop-types: 0 */
|
||||
/* eslint no-return-assign: 0 */
|
||||
/* eslint class-methods-use-this: 0 */
|
||||
/* eslint jsx-a11y/no-noninteractive-element-interactions: 0 */
|
||||
import React, { Component } from 'react';
|
||||
import cs from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import TextEditor from './text-editor';
|
||||
import EditorIndicator from './editor-indicator';
|
||||
import { TIME_TO_CLOSE_MESSAGE } from './const';
|
||||
|
||||
export default _ =>
|
||||
class EditingCell extends Component {
|
||||
static propTypes = {
|
||||
row: PropTypes.object.isRequired,
|
||||
column: PropTypes.object.isRequired,
|
||||
onUpdate: PropTypes.func.isRequired,
|
||||
onEscape: PropTypes.func.isRequired,
|
||||
timeToCloseMessage: PropTypes.number,
|
||||
className: PropTypes.string,
|
||||
style: PropTypes.object
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
timeToCloseMessage: TIME_TO_CLOSE_MESSAGE,
|
||||
className: null,
|
||||
style: {}
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.indicatorTimer = null;
|
||||
this.clearTimer = this.clearTimer.bind(this);
|
||||
this.handleBlur = this.handleBlur.bind(this);
|
||||
this.handleClick = this.handleClick.bind(this);
|
||||
this.handleKeyDown = this.handleKeyDown.bind(this);
|
||||
this.beforeComplete = this.beforeComplete.bind(this);
|
||||
this.state = {
|
||||
invalidMessage: null
|
||||
};
|
||||
}
|
||||
|
||||
componentWillReceiveProps({ message }) {
|
||||
if (_.isDefined(message)) {
|
||||
this.createTimer();
|
||||
this.setState(() => ({
|
||||
invalidMessage: message
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.clearTimer();
|
||||
}
|
||||
|
||||
clearTimer() {
|
||||
if (this.indicatorTimer) {
|
||||
clearTimeout(this.indicatorTimer);
|
||||
}
|
||||
}
|
||||
|
||||
createTimer() {
|
||||
this.clearTimer();
|
||||
const { timeToCloseMessage, onErrorMessageDisappear } = this.props;
|
||||
this.indicatorTimer = _.sleep(() => {
|
||||
this.setState(() => ({
|
||||
invalidMessage: null
|
||||
}));
|
||||
if (_.isFunction(onErrorMessageDisappear)) onErrorMessageDisappear();
|
||||
}, timeToCloseMessage);
|
||||
}
|
||||
|
||||
beforeComplete(row, column, newValue) {
|
||||
const { onUpdate } = this.props;
|
||||
if (_.isFunction(column.validator)) {
|
||||
const validateForm = column.validator(newValue, row, column);
|
||||
if (_.isObject(validateForm) && !validateForm.valid) {
|
||||
this.setState(() => ({
|
||||
invalidMessage: validateForm.message
|
||||
}));
|
||||
this.createTimer();
|
||||
return;
|
||||
}
|
||||
}
|
||||
onUpdate(row, column, newValue);
|
||||
}
|
||||
|
||||
handleBlur() {
|
||||
const { onEscape, blurToSave, row, column } = this.props;
|
||||
if (blurToSave) {
|
||||
const value = this.editor.text.value;
|
||||
if (!_.isDefined(value)) {
|
||||
// TODO: for other custom or embed editor
|
||||
}
|
||||
this.beforeComplete(row, column, value);
|
||||
} else {
|
||||
onEscape();
|
||||
}
|
||||
}
|
||||
|
||||
handleKeyDown(e) {
|
||||
const { onEscape, row, column } = this.props;
|
||||
if (e.keyCode === 27) { // ESC
|
||||
onEscape();
|
||||
} else if (e.keyCode === 13) { // ENTER
|
||||
const value = e.currentTarget.value;
|
||||
if (!_.isDefined(value)) {
|
||||
// TODO: for other custom or embed editor
|
||||
}
|
||||
this.beforeComplete(row, column, value);
|
||||
}
|
||||
}
|
||||
|
||||
handleClick(e) {
|
||||
if (e.target.tagName !== 'TD') {
|
||||
// To avoid the row selection event be triggered,
|
||||
// When user define selectRow.clickToSelect and selectRow.clickToEdit
|
||||
// We shouldn't trigger selection event even if user click on the cell editor(input)
|
||||
e.stopPropagation();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { invalidMessage } = this.state;
|
||||
const { row, column, className, style } = this.props;
|
||||
const { dataField } = column;
|
||||
|
||||
const value = _.get(row, dataField);
|
||||
const editorAttrs = {
|
||||
onKeyDown: this.handleKeyDown,
|
||||
onBlur: this.handleBlur
|
||||
};
|
||||
|
||||
const hasError = _.isDefined(invalidMessage);
|
||||
const editorClass = hasError ? cs('animated', 'shake') : null;
|
||||
return (
|
||||
<td
|
||||
className={ cs('react-bootstrap-table-editing-cell', className) }
|
||||
style={ style }
|
||||
onClick={ this.handleClick }
|
||||
>
|
||||
<TextEditor
|
||||
ref={ node => this.editor = node }
|
||||
defaultValue={ value }
|
||||
className={ editorClass }
|
||||
{ ...editorAttrs }
|
||||
/>
|
||||
{ hasError ? <EditorIndicator invalidMessage={ invalidMessage } /> : null }
|
||||
</td>
|
||||
);
|
||||
}
|
||||
};
|
||||
16
packages/react-bootstrap-table2-editor/src/index.js
vendored
Normal file
16
packages/react-bootstrap-table2-editor/src/index.js
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
import wrapperFactory from './wrapper';
|
||||
import editingCellFactory from './editing-cell';
|
||||
import {
|
||||
CLICK_TO_CELL_EDIT,
|
||||
DBCLICK_TO_CELL_EDIT,
|
||||
DELAY_FOR_DBCLICK
|
||||
} from './const';
|
||||
|
||||
export default (options = {}) => ({
|
||||
wrapperFactory,
|
||||
editingCellFactory,
|
||||
CLICK_TO_CELL_EDIT,
|
||||
DBCLICK_TO_CELL_EDIT,
|
||||
DELAY_FOR_DBCLICK,
|
||||
options
|
||||
});
|
||||
@ -1,12 +1,31 @@
|
||||
/* eslint react/prop-types: 0 */
|
||||
import React, { Component } from 'react';
|
||||
import _ from '../utils';
|
||||
import remoteResolver from '../props-resolver/remote-resolver';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { CLICK_TO_CELL_EDIT, DBCLICK_TO_CELL_EDIT } from './const';
|
||||
|
||||
export default (
|
||||
Base,
|
||||
{ _, remoteResolver }
|
||||
) => {
|
||||
let EditingCell;
|
||||
return class CellEditWrapper extends remoteResolver(Component) {
|
||||
static propTypes = {
|
||||
options: PropTypes.shape({
|
||||
mode: PropTypes.oneOf([CLICK_TO_CELL_EDIT, DBCLICK_TO_CELL_EDIT]).isRequired,
|
||||
onErrorMessageDisappear: PropTypes.func,
|
||||
blurToSave: PropTypes.bool,
|
||||
beforeSaveCell: PropTypes.func,
|
||||
afterSaveCell: PropTypes.func,
|
||||
nonEditableRows: PropTypes.func,
|
||||
timeToCloseMessage: PropTypes.number,
|
||||
errorMessage: PropTypes.string
|
||||
})
|
||||
}
|
||||
|
||||
export default Base =>
|
||||
class CellEditWrapper extends remoteResolver(Component) {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
EditingCell = props.cellEdit.editingCellFactory(_);
|
||||
this.startEditing = this.startEditing.bind(this);
|
||||
this.escapeEditing = this.escapeEditing.bind(this);
|
||||
this.completeEditing = this.completeEditing.bind(this);
|
||||
@ -21,10 +40,10 @@ export default Base =>
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (nextProps.cellEdit && this.isRemoteCellEdit()) {
|
||||
if (nextProps.cellEdit.errorMessage) {
|
||||
if (nextProps.cellEdit.options.errorMessage) {
|
||||
this.setState(() => ({
|
||||
isDataChanged: false,
|
||||
message: nextProps.cellEdit.errorMessage
|
||||
message: nextProps.cellEdit.options.errorMessage
|
||||
}));
|
||||
} else {
|
||||
this.setState(() => ({
|
||||
@ -41,7 +60,7 @@ export default Base =>
|
||||
|
||||
handleCellUpdate(row, column, newValue) {
|
||||
const { keyField, cellEdit, store } = this.props;
|
||||
const { beforeSaveCell, afterSaveCell } = cellEdit;
|
||||
const { beforeSaveCell, afterSaveCell } = cellEdit.options;
|
||||
const oldValue = _.get(row, column.dataField);
|
||||
const rowId = _.get(row, keyField);
|
||||
if (_.isFunction(beforeSaveCell)) beforeSaveCell(oldValue, newValue, row, column);
|
||||
@ -84,17 +103,33 @@ export default Base =>
|
||||
}
|
||||
|
||||
render() {
|
||||
const { isDataChanged, ...rest } = this.state;
|
||||
const { isDataChanged, ...stateRest } = this.state;
|
||||
const {
|
||||
cellEdit: {
|
||||
options: { nonEditableRows, errorMessage, ...optionsRest },
|
||||
editingCellFactory,
|
||||
...cellEditRest
|
||||
}
|
||||
} = this.props;
|
||||
const newCellEdit = {
|
||||
...optionsRest,
|
||||
...cellEditRest,
|
||||
...stateRest,
|
||||
EditingCell,
|
||||
nonEditableRows: _.isDefined(nonEditableRows) ? nonEditableRows() : [],
|
||||
onStart: this.startEditing,
|
||||
onEscape: this.escapeEditing,
|
||||
onUpdate: this.handleCellUpdate
|
||||
};
|
||||
|
||||
return (
|
||||
<Base
|
||||
{ ...this.props }
|
||||
isDataChanged={ isDataChanged }
|
||||
data={ this.props.store.data }
|
||||
onCellUpdate={ this.handleCellUpdate }
|
||||
onStartEditing={ this.startEditing }
|
||||
onEscapeEditing={ this.escapeEditing }
|
||||
currEditCell={ { ...rest } }
|
||||
isDataChanged={ isDataChanged }
|
||||
cellEdit={ newCellEdit }
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
};
|
||||
@ -1,12 +1,23 @@
|
||||
/* eslint react/prop-types: 0 */
|
||||
import 'jsdom-global/register';
|
||||
import React from 'react';
|
||||
import sinon from 'sinon';
|
||||
import { shallow, mount } from 'enzyme';
|
||||
|
||||
import { TableRowWrapper } from '../test-helpers/table-wrapper';
|
||||
import EditingCell from '../../src/cell-edit/editing-cell';
|
||||
import TextEditor from '../../src/cell-edit/text-editor';
|
||||
import EditorIndicator from '../../src/cell-edit/editor-indicator';
|
||||
import _ from 'react-bootstrap-table2/src/utils';
|
||||
import editingCellFactory from '../src/editing-cell';
|
||||
import TextEditor from '../src/text-editor';
|
||||
import EditorIndicator from '../src/editor-indicator';
|
||||
|
||||
const EditingCell = editingCellFactory(_);
|
||||
const TableRowWrapper = props => (
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>{ props.children }</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
|
||||
|
||||
describe('EditingCell', () => {
|
||||
let wrapper;
|
||||
@ -2,7 +2,7 @@ import 'jsdom-global/register';
|
||||
import React from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
|
||||
import TextEditor from '../../src/cell-edit/text-editor';
|
||||
import TextEditor from '../src/text-editor';
|
||||
|
||||
describe('TextEditor', () => {
|
||||
let wrapper;
|
||||
330
packages/react-bootstrap-table2-editor/test/wrapper.test.js
Normal file
330
packages/react-bootstrap-table2-editor/test/wrapper.test.js
Normal file
@ -0,0 +1,330 @@
|
||||
import React from 'react';
|
||||
import sinon from 'sinon';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import _ from 'react-bootstrap-table2/src/utils';
|
||||
import remoteResolver from 'react-bootstrap-table2/src/props-resolver/remote-resolver';
|
||||
import Store from 'react-bootstrap-table2/src/store';
|
||||
import BootstrapTable from 'react-bootstrap-table2/src/bootstrap-table';
|
||||
import cellEditFactory from '../src';
|
||||
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(<CellEditWrapper { ...props } />);
|
||||
instance = wrapper.instance();
|
||||
if (renderFragment) {
|
||||
const fragment = instance.render();
|
||||
wrapper = shallow(<div>{ fragment }</div>);
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -4,6 +4,7 @@ const sourcePath = path.join(__dirname, '../../react-bootstrap-table2/src');
|
||||
const paginationSourcePath = path.join(__dirname, '../../react-bootstrap-table2-paginator/src');
|
||||
const overlaySourcePath = path.join(__dirname, '../../react-bootstrap-table2-overlay/src');
|
||||
const filterSourcePath = path.join(__dirname, '../../react-bootstrap-table2-filter/src');
|
||||
const editorSourcePath = path.join(__dirname, '../../react-bootstrap-table2-editor/src');
|
||||
const sourceStylePath = path.join(__dirname, '../../react-bootstrap-table2/style');
|
||||
const paginationStylePath = path.join(__dirname, '../../react-bootstrap-table2-paginator/style');
|
||||
const storyPath = path.join(__dirname, '../stories');
|
||||
@ -27,7 +28,7 @@ const loaders = [{
|
||||
test: /\.js?$/,
|
||||
use: ['babel-loader'],
|
||||
exclude: /node_modules/,
|
||||
include: [sourcePath, paginationSourcePath, overlaySourcePath, filterSourcePath, storyPath],
|
||||
include: [sourcePath, paginationSourcePath, overlaySourcePath, filterSourcePath, editorSourcePath, storyPath],
|
||||
}, {
|
||||
test: /\.css$/,
|
||||
use: ['style-loader', 'css-loader'],
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
|
||||
import BootstrapTable from 'react-bootstrap-table2';
|
||||
import cellEditFactory from 'react-bootstrap-table2-editor';
|
||||
import Code from 'components/common/code-block';
|
||||
import { productsGenerator } from 'utils/common';
|
||||
|
||||
@ -18,6 +19,8 @@ const columns = [{
|
||||
}];
|
||||
|
||||
const sourceCode = `\
|
||||
import cellEditFactory from 'react-bootstrap-table2-editor';
|
||||
// ...
|
||||
const columns = [{
|
||||
dataField: 'id',
|
||||
text: 'Product ID'
|
||||
@ -29,26 +32,28 @@ const columns = [{
|
||||
text: 'Product Price'
|
||||
}];
|
||||
|
||||
const cellEdit = {
|
||||
mode: 'click',
|
||||
blurToSave: true
|
||||
};
|
||||
|
||||
<BootstrapTable
|
||||
keyField='id'
|
||||
keyField="id"
|
||||
data={ products }
|
||||
columns={ columns }
|
||||
cellEdit={ cellEdit }
|
||||
cellEdit={ cellEditFactory({
|
||||
mode: 'click',
|
||||
blurToSave: true
|
||||
}) }
|
||||
/>
|
||||
`;
|
||||
|
||||
const cellEdit = {
|
||||
mode: 'click',
|
||||
blurToSave: true
|
||||
};
|
||||
export default () => (
|
||||
<div>
|
||||
<BootstrapTable keyField="id" data={ products } columns={ columns } cellEdit={ cellEdit } />
|
||||
<BootstrapTable
|
||||
keyField="id"
|
||||
data={ products }
|
||||
columns={ columns }
|
||||
cellEdit={ cellEditFactory({
|
||||
mode: 'click',
|
||||
blurToSave: true
|
||||
}) }
|
||||
/>
|
||||
<Code>{ sourceCode }</Code>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
import React from 'react';
|
||||
|
||||
import BootstrapTable from 'react-bootstrap-table2';
|
||||
import cellEditFactory from 'react-bootstrap-table2-editor';
|
||||
import Code from 'components/common/code-block';
|
||||
import { productsGenerator } from 'utils/common';
|
||||
|
||||
@ -22,6 +23,8 @@ const columns = [{
|
||||
}];
|
||||
|
||||
const sourceCode = `\
|
||||
import cellEditFactory from 'react-bootstrap-table2-editor';
|
||||
// ...
|
||||
const columns = [{
|
||||
dataField: 'id',
|
||||
text: 'Product ID'
|
||||
@ -36,24 +39,22 @@ const columns = [{
|
||||
(cell > 2101 ? 'editing-price-bigger-than-2101' : 'editing-price-small-than-2101')
|
||||
}];
|
||||
|
||||
const cellEdit = {
|
||||
mode: 'click'
|
||||
};
|
||||
|
||||
<BootstrapTable
|
||||
keyField='id'
|
||||
keyField="id"
|
||||
data={ products }
|
||||
columns={ columns }
|
||||
cellEdit={ cellEdit }
|
||||
cellEdit={ cellEditFactory({ mode: 'click' }) }
|
||||
/>
|
||||
`;
|
||||
|
||||
const cellEdit = {
|
||||
mode: 'click'
|
||||
};
|
||||
export default () => (
|
||||
<div>
|
||||
<BootstrapTable keyField="id" data={ products } columns={ columns } cellEdit={ cellEdit } />
|
||||
<BootstrapTable
|
||||
keyField="id"
|
||||
data={ products }
|
||||
columns={ columns }
|
||||
cellEdit={ cellEditFactory({ mode: 'click' }) }
|
||||
/>
|
||||
<Code>{ sourceCode }</Code>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
import React from 'react';
|
||||
|
||||
import BootstrapTable from 'react-bootstrap-table2';
|
||||
import cellEditFactory from 'react-bootstrap-table2-editor';
|
||||
import Code from 'components/common/code-block';
|
||||
import { productsGenerator } from 'utils/common';
|
||||
|
||||
@ -20,6 +21,8 @@ const columns = [{
|
||||
}];
|
||||
|
||||
const sourceCode = `\
|
||||
import cellEditFactory from 'react-bootstrap-table2-editor';
|
||||
// ...
|
||||
const columns = [{
|
||||
dataField: 'id',
|
||||
text: 'Product ID'
|
||||
@ -31,28 +34,30 @@ const columns = [{
|
||||
text: 'Product Price'
|
||||
}];
|
||||
|
||||
const cellEdit = {
|
||||
mode: 'click',
|
||||
beforeSaveCell: (oldValue, newValue, row, column) => { console.log('Before Saving Cell!!'); },
|
||||
afterSaveCell: (oldValue, newValue, row, column) => { console.log('After Saving Cell!!'); }
|
||||
};
|
||||
|
||||
<BootstrapTable
|
||||
keyField='id'
|
||||
keyField="id"
|
||||
data={ products }
|
||||
columns={ columns }
|
||||
cellEdit={ cellEdit }
|
||||
cellEdit={ cellEditFactory({
|
||||
mode: 'click',
|
||||
beforeSaveCell: (oldValue, newValue, row, column) => { console.log('Before Saving Cell!!'); },
|
||||
afterSaveCell: (oldValue, newValue, row, column) => { console.log('After Saving Cell!!'); }
|
||||
}) }
|
||||
/>
|
||||
`;
|
||||
|
||||
const cellEdit = {
|
||||
mode: 'click',
|
||||
beforeSaveCell: (oldValue, newValue, row, column) => { console.log('Before Saving Cell!!'); },
|
||||
afterSaveCell: (oldValue, newValue, row, column) => { console.log('After Saving Cell!!'); }
|
||||
};
|
||||
export default () => (
|
||||
<div>
|
||||
<BootstrapTable keyField="id" data={ products } columns={ columns } cellEdit={ cellEdit } />
|
||||
<BootstrapTable
|
||||
keyField="id"
|
||||
data={ products }
|
||||
columns={ columns }
|
||||
cellEdit={ cellEditFactory({
|
||||
mode: 'click',
|
||||
beforeSaveCell: (oldValue, newValue, row, column) => { console.log('Before Saving Cell!!'); },
|
||||
afterSaveCell: (oldValue, newValue, row, column) => { console.log('After Saving Cell!!'); }
|
||||
}) }
|
||||
/>
|
||||
<Code>{ sourceCode }</Code>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
import React from 'react';
|
||||
|
||||
import BootstrapTable from 'react-bootstrap-table2';
|
||||
import cellEditFactory from 'react-bootstrap-table2-editor';
|
||||
import Code from 'components/common/code-block';
|
||||
import { productsGenerator } from 'utils/common';
|
||||
|
||||
@ -26,6 +27,8 @@ const columns = [{
|
||||
}];
|
||||
|
||||
const sourceCode = `\
|
||||
import cellEditFactory from 'react-bootstrap-table2-editor';
|
||||
// ...
|
||||
const columns = [{
|
||||
dataField: 'id',
|
||||
text: 'Product ID'
|
||||
@ -44,24 +47,22 @@ const columns = [{
|
||||
}
|
||||
}];
|
||||
|
||||
const cellEdit = {
|
||||
mode: 'click'
|
||||
};
|
||||
|
||||
<BootstrapTable
|
||||
keyField='id'
|
||||
keyField="id"
|
||||
data={ products }
|
||||
columns={ columns }
|
||||
cellEdit={ cellEdit }
|
||||
cellEdit={ cellEditFactory({ mode: 'click' }) }
|
||||
/>
|
||||
`;
|
||||
|
||||
const cellEdit = {
|
||||
mode: 'click'
|
||||
};
|
||||
export default () => (
|
||||
<div>
|
||||
<BootstrapTable keyField="id" data={ products } columns={ columns } cellEdit={ cellEdit } />
|
||||
<BootstrapTable
|
||||
keyField="id"
|
||||
data={ products }
|
||||
columns={ columns }
|
||||
cellEdit={ cellEditFactory({ mode: 'click' }) }
|
||||
/>
|
||||
<Code>{ sourceCode }</Code>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
/* eslint no-unused-vars: 0 */
|
||||
import React from 'react';
|
||||
import BootstrapTable from 'react-bootstrap-table2';
|
||||
import cellEditFactory from 'react-bootstrap-table2-editor';
|
||||
import Code from 'components/common/code-block';
|
||||
import { productsGenerator } from 'utils/common';
|
||||
|
||||
@ -33,6 +34,8 @@ const columns = [{
|
||||
}];
|
||||
|
||||
const sourceCode = `\
|
||||
import cellEditFactory from 'react-bootstrap-table2-editor';
|
||||
// ...
|
||||
const columns = [{
|
||||
dataField: 'id',
|
||||
text: 'Product ID'
|
||||
@ -59,27 +62,29 @@ const columns = [{
|
||||
}
|
||||
}];
|
||||
|
||||
const cellEdit = {
|
||||
mode: 'click',
|
||||
blurToSave: true
|
||||
};
|
||||
|
||||
<BootstrapTable
|
||||
keyField='id'
|
||||
keyField="id"
|
||||
data={ products }
|
||||
columns={ columns }
|
||||
cellEdit={ cellEdit }
|
||||
cellEdit={ cellEditFactory({
|
||||
mode: 'click',
|
||||
blurToSave: true
|
||||
}) }
|
||||
/>
|
||||
`;
|
||||
|
||||
const cellEdit = {
|
||||
mode: 'click',
|
||||
blurToSave: true
|
||||
};
|
||||
export default () => (
|
||||
<div>
|
||||
<h3>Product Price should bigger than $2000</h3>
|
||||
<BootstrapTable keyField="id" data={ products } columns={ columns } cellEdit={ cellEdit } />
|
||||
<BootstrapTable
|
||||
keyField="id"
|
||||
data={ products }
|
||||
columns={ columns }
|
||||
cellEdit={ cellEditFactory({
|
||||
mode: 'click',
|
||||
blurToSave: true
|
||||
}) }
|
||||
/>
|
||||
<Code>{ sourceCode }</Code>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
import React from 'react';
|
||||
|
||||
import BootstrapTable from 'react-bootstrap-table2';
|
||||
import cellEditFactory from 'react-bootstrap-table2-editor';
|
||||
import Code from 'components/common/code-block';
|
||||
import { productsGenerator } from 'utils/common';
|
||||
|
||||
@ -20,6 +21,8 @@ const columns = [{
|
||||
}];
|
||||
|
||||
const sourceCode = `\
|
||||
import cellEditFactory from 'react-bootstrap-table2-editor';
|
||||
// ...
|
||||
const columns = [{
|
||||
dataField: 'id',
|
||||
text: 'Product ID'
|
||||
@ -32,24 +35,22 @@ const columns = [{
|
||||
editable: (content, row, rowIndex, columnIndex) => content > 2101
|
||||
}];
|
||||
|
||||
const cellEdit = {
|
||||
mode: 'click'
|
||||
};
|
||||
|
||||
<BootstrapTable
|
||||
keyField='id'
|
||||
keyField="id"
|
||||
data={ products }
|
||||
columns={ columns }
|
||||
cellEdit={ cellEdit }
|
||||
cellEdit={ cellEditFactory({ mode: 'click' }) }
|
||||
/>
|
||||
`;
|
||||
|
||||
const cellEdit = {
|
||||
mode: 'click'
|
||||
};
|
||||
export default () => (
|
||||
<div>
|
||||
<BootstrapTable keyField="id" data={ products } columns={ columns } cellEdit={ cellEdit } />
|
||||
<BootstrapTable
|
||||
keyField="id"
|
||||
data={ products }
|
||||
columns={ columns }
|
||||
cellEdit={ cellEditFactory({ mode: 'click' }) }
|
||||
/>
|
||||
<Code>{ sourceCode }</Code>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
/* eslint react/prefer-stateless-function: 0 */
|
||||
import React from 'react';
|
||||
|
||||
import BootstrapTable from 'react-bootstrap-table2';
|
||||
import cellEditFactory from 'react-bootstrap-table2-editor';
|
||||
import Code from 'components/common/code-block';
|
||||
import { productsGenerator } from 'utils/common';
|
||||
|
||||
@ -18,6 +20,8 @@ const columns = [{
|
||||
}];
|
||||
|
||||
const sourceCode = `\
|
||||
import cellEditFactory from 'react-bootstrap-table2-editor';
|
||||
// ...
|
||||
const columns = [{
|
||||
dataField: 'id',
|
||||
text: 'Product ID'
|
||||
@ -29,24 +33,22 @@ const columns = [{
|
||||
text: 'Product Price'
|
||||
}];
|
||||
|
||||
const cellEdit = {
|
||||
mode: 'click'
|
||||
};
|
||||
|
||||
<BootstrapTable
|
||||
keyField='id'
|
||||
keyField="id"
|
||||
data={ products }
|
||||
columns={ columns }
|
||||
cellEdit={ cellEdit }
|
||||
cellEdit={ cellEditFactory({ mode: 'click' }) }
|
||||
/>
|
||||
`;
|
||||
|
||||
const cellEdit = {
|
||||
mode: 'click'
|
||||
};
|
||||
export default () => (
|
||||
<div>
|
||||
<BootstrapTable keyField="id" data={ products } columns={ columns } cellEdit={ cellEdit } />
|
||||
<BootstrapTable
|
||||
keyField="id"
|
||||
data={ products }
|
||||
columns={ columns }
|
||||
cellEdit={ cellEditFactory({ mode: 'click' }) }
|
||||
/>
|
||||
<Code>{ sourceCode }</Code>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
|
||||
import BootstrapTable from 'react-bootstrap-table2';
|
||||
import cellEditFactory from 'react-bootstrap-table2-editor';
|
||||
import Code from 'components/common/code-block';
|
||||
import { productsGenerator } from 'utils/common';
|
||||
|
||||
@ -19,6 +20,8 @@ const columns = [{
|
||||
}];
|
||||
|
||||
const sourceCode = `\
|
||||
import cellEditFactory from 'react-bootstrap-table2-editor';
|
||||
// ...
|
||||
const columns = [{
|
||||
dataField: 'id',
|
||||
text: 'Product ID'
|
||||
@ -32,26 +35,28 @@ const columns = [{
|
||||
text: 'Product Price'
|
||||
}];
|
||||
|
||||
const cellEdit = {
|
||||
mode: 'click',
|
||||
blurToSave: true
|
||||
};
|
||||
|
||||
<BootstrapTable
|
||||
keyField='id'
|
||||
keyField="id"
|
||||
data={ products }
|
||||
columns={ columns }
|
||||
cellEdit={ cellEdit }
|
||||
cellEdit={ cellEditFactory({
|
||||
mode: 'click',
|
||||
blurToSave: true
|
||||
}) }
|
||||
/>
|
||||
`;
|
||||
|
||||
const cellEdit = {
|
||||
mode: 'click',
|
||||
blurToSave: true
|
||||
};
|
||||
export default () => (
|
||||
<div>
|
||||
<BootstrapTable keyField="id" data={ products } columns={ columns } cellEdit={ cellEdit } />
|
||||
<BootstrapTable
|
||||
keyField="id"
|
||||
data={ products }
|
||||
columns={ columns }
|
||||
cellEdit={ cellEditFactory({
|
||||
mode: 'click',
|
||||
blurToSave: true
|
||||
}) }
|
||||
/>
|
||||
<Code>{ sourceCode }</Code>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
|
||||
import BootstrapTable from 'react-bootstrap-table2';
|
||||
import cellEditFactory from 'react-bootstrap-table2-editor';
|
||||
import Code from 'components/common/code-block';
|
||||
import { productsGenerator } from 'utils/common';
|
||||
|
||||
@ -18,6 +19,8 @@ const columns = [{
|
||||
}];
|
||||
|
||||
const sourceCode = `\
|
||||
import cellEditFactory from 'react-bootstrap-table2-editor';
|
||||
// ...
|
||||
const columns = [{
|
||||
dataField: 'id',
|
||||
text: 'Product ID'
|
||||
@ -29,24 +32,22 @@ const columns = [{
|
||||
text: 'Product Price'
|
||||
}];
|
||||
|
||||
const cellEdit = {
|
||||
mode: 'dbclick'
|
||||
};
|
||||
|
||||
<BootstrapTable
|
||||
keyField='id'
|
||||
keyField="id"
|
||||
data={ products }
|
||||
columns={ columns }
|
||||
cellEdit={ cellEdit }
|
||||
cellEdit={ cellEditFactory({ mode: 'dbclick' }) }
|
||||
/>
|
||||
`;
|
||||
|
||||
const cellEdit = {
|
||||
mode: 'dbclick'
|
||||
};
|
||||
export default () => (
|
||||
<div>
|
||||
<BootstrapTable keyField="id" data={ products } columns={ columns } cellEdit={ cellEdit } />
|
||||
<BootstrapTable
|
||||
keyField="id"
|
||||
data={ products }
|
||||
columns={ columns }
|
||||
cellEdit={ cellEditFactory({ mode: 'dbclick' }) }
|
||||
/>
|
||||
<Code>{ sourceCode }</Code>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
|
||||
import BootstrapTable from 'react-bootstrap-table2';
|
||||
import cellEditFactory from 'react-bootstrap-table2-editor';
|
||||
import Code from 'components/common/code-block';
|
||||
import { productsGenerator } from 'utils/common';
|
||||
|
||||
@ -18,6 +19,8 @@ const columns = [{
|
||||
}];
|
||||
|
||||
const sourceCode = `\
|
||||
import cellEditFactory from 'react-bootstrap-table2-editor';
|
||||
// ...
|
||||
const columns = [{
|
||||
dataField: 'id',
|
||||
text: 'Product ID'
|
||||
@ -29,29 +32,29 @@ const columns = [{
|
||||
text: 'Product Price'
|
||||
}];
|
||||
|
||||
const cellEdit = {
|
||||
mode: 'click',
|
||||
blurToSave: true,
|
||||
// Product ID: 0, 3 will be non-editable
|
||||
nonEditableRows: () => [0, 3]
|
||||
};
|
||||
|
||||
<BootstrapTable
|
||||
keyField='id'
|
||||
keyField="id"
|
||||
data={ products }
|
||||
columns={ columns }
|
||||
cellEdit={ cellEdit }
|
||||
cellEdit={ cellEditFactory({
|
||||
mode: 'click',
|
||||
blurToSave: true,
|
||||
nonEditableRows: () => [0, 3]
|
||||
}) }
|
||||
/>
|
||||
`;
|
||||
|
||||
const cellEdit = {
|
||||
mode: 'click',
|
||||
blurToSave: true,
|
||||
nonEditableRows: () => [0, 3]
|
||||
};
|
||||
export default () => (
|
||||
<div>
|
||||
<BootstrapTable keyField="id" data={ products } columns={ columns } cellEdit={ cellEdit } />
|
||||
<BootstrapTable
|
||||
keyField="id"
|
||||
data={ products }
|
||||
columns={ columns }
|
||||
cellEdit={ cellEditFactory({
|
||||
mode: 'click',
|
||||
blurToSave: true,
|
||||
nonEditableRows: () => [0, 3]
|
||||
}) }
|
||||
/>
|
||||
<Code>{ sourceCode }</Code>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import BootstrapTable from 'react-bootstrap-table2';
|
||||
import cellEditFactory from 'react-bootstrap-table2-editor';
|
||||
import Code from 'components/common/code-block';
|
||||
import { productsGenerator } from 'utils/common';
|
||||
|
||||
@ -18,6 +19,8 @@ const columns = [{
|
||||
}];
|
||||
|
||||
const sourceCode = `\
|
||||
import cellEditFactory from 'react-bootstrap-table2-editor';
|
||||
// ...
|
||||
const RemoteCellEdit = (props) => {
|
||||
const cellEdit = {
|
||||
mode: 'click',
|
||||
@ -31,7 +34,7 @@ const RemoteCellEdit = (props) => {
|
||||
keyField="id"
|
||||
data={ props.data }
|
||||
columns={ columns }
|
||||
cellEdit={ cellEdit }
|
||||
cellEdit={ cellEditFactory(cellEdit) }
|
||||
onTableChange={ props.onTableChange }
|
||||
/>
|
||||
<Code>{ sourceCode }</Code>
|
||||
@ -104,7 +107,7 @@ const RemoteCellEdit = (props) => {
|
||||
keyField="id"
|
||||
data={ props.data }
|
||||
columns={ columns }
|
||||
cellEdit={ cellEdit }
|
||||
cellEdit={ cellEditFactory(cellEdit) }
|
||||
onTableChange={ props.onTableChange }
|
||||
/>
|
||||
<Code>{ sourceCode }</Code>
|
||||
|
||||
@ -18,6 +18,7 @@
|
||||
"dependencies": {
|
||||
"bootstrap": "^3.3.7",
|
||||
"react-bootstrap-table2": "0.0.1",
|
||||
"react-bootstrap-table2-editor": "0.0.1",
|
||||
"react-bootstrap-table2-paginator": "0.0.1",
|
||||
"react-bootstrap-table2-overlay": "0.0.1",
|
||||
"react-bootstrap-table2-filter": "0.0.1"
|
||||
|
||||
4
packages/react-bootstrap-table2/src/body.js
vendored
4
packages/react-bootstrap-table2/src/body.js
vendored
@ -37,10 +37,10 @@ const Body = (props) => {
|
||||
const indication = _.isFunction(noDataIndication) ? noDataIndication() : noDataIndication;
|
||||
content = <RowSection content={ indication } colSpan={ visibleColumnSize } />;
|
||||
} else {
|
||||
const nonEditableRows = cellEdit.nonEditableRows || [];
|
||||
content = data.map((row, index) => {
|
||||
const key = _.get(row, keyField);
|
||||
const editable = !(cellEdit.mode !== Const.UNABLE_TO_CELL_EDIT &&
|
||||
cellEdit.nonEditableRows.indexOf(key) > -1);
|
||||
const editable = !(nonEditableRows.length > 0 && nonEditableRows.indexOf(key) > -1);
|
||||
|
||||
const selected = selectRow.mode !== Const.ROW_SELECT_DISABLED
|
||||
? selectedRowKeys.includes(key)
|
||||
|
||||
@ -60,13 +60,6 @@ class BootstrapTable extends PropsBaseResolver(Component) {
|
||||
'table-condensed': condensed
|
||||
});
|
||||
|
||||
const cellEditInfo = this.resolveCellEditProps({
|
||||
onStart: this.props.onStartEditing,
|
||||
onEscape: this.props.onEscapeEditing,
|
||||
onUpdate: this.props.onCellUpdate,
|
||||
currEditCell: this.props.currEditCell
|
||||
});
|
||||
|
||||
const cellSelectionInfo = this.resolveSelectRowProps({
|
||||
onRowSelect: this.props.onRowSelect
|
||||
});
|
||||
@ -96,7 +89,7 @@ class BootstrapTable extends PropsBaseResolver(Component) {
|
||||
isEmpty={ this.isEmpty() }
|
||||
visibleColumnSize={ this.visibleColumnSize() }
|
||||
noDataIndication={ noDataIndication }
|
||||
cellEdit={ cellEditInfo }
|
||||
cellEdit={ this.props.cellEdit || {} }
|
||||
selectRow={ cellSelectionInfo }
|
||||
selectedRowKeys={ store.selected }
|
||||
rowStyle={ rowStyle }
|
||||
@ -128,24 +121,7 @@ BootstrapTable.propTypes = {
|
||||
]),
|
||||
pagination: PropTypes.object,
|
||||
filter: PropTypes.object,
|
||||
cellEdit: PropTypes.shape({
|
||||
mode: PropTypes.oneOf([Const.CLICK_TO_CELL_EDIT, Const.DBCLICK_TO_CELL_EDIT]).isRequired,
|
||||
onErrorMessageDisappear: PropTypes.func,
|
||||
blurToSave: PropTypes.bool,
|
||||
beforeSaveCell: PropTypes.func,
|
||||
afterSaveCell: PropTypes.func,
|
||||
nonEditableRows: PropTypes.func,
|
||||
timeToCloseMessage: PropTypes.number,
|
||||
errorMessage: PropTypes.string
|
||||
}),
|
||||
onCellUpdate: PropTypes.func,
|
||||
onStartEditing: PropTypes.func,
|
||||
onEscapeEditing: PropTypes.func,
|
||||
currEditCell: PropTypes.shape({
|
||||
ridx: PropTypes.number,
|
||||
cidx: PropTypes.number,
|
||||
message: PropTypes.string
|
||||
}),
|
||||
cellEdit: PropTypes.object,
|
||||
selectRow: PropTypes.shape({
|
||||
mode: PropTypes.oneOf([Const.ROW_SELECT_SINGLE, Const.ROW_SELECT_MULTIPLE]).isRequired,
|
||||
clickToSelect: PropTypes.bool,
|
||||
|
||||
@ -1,156 +0,0 @@
|
||||
/* eslint arrow-body-style: 0 */
|
||||
/* eslint react/prop-types: 0 */
|
||||
/* eslint no-return-assign: 0 */
|
||||
/* eslint class-methods-use-this: 0 */
|
||||
/* eslint jsx-a11y/no-noninteractive-element-interactions: 0 */
|
||||
import React, { Component } from 'react';
|
||||
import cs from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import _ from '../utils';
|
||||
import Const from '../const';
|
||||
import TextEditor from './text-editor';
|
||||
import EditorIndicator from './editor-indicator';
|
||||
|
||||
class EditingCell extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.indicatorTimer = null;
|
||||
this.clearTimer = this.clearTimer.bind(this);
|
||||
this.handleBlur = this.handleBlur.bind(this);
|
||||
this.handleClick = this.handleClick.bind(this);
|
||||
this.handleKeyDown = this.handleKeyDown.bind(this);
|
||||
this.beforeComplete = this.beforeComplete.bind(this);
|
||||
this.state = {
|
||||
invalidMessage: null
|
||||
};
|
||||
}
|
||||
|
||||
componentWillReceiveProps({ message }) {
|
||||
if (_.isDefined(message)) {
|
||||
this.createTimer();
|
||||
this.setState(() => {
|
||||
return { invalidMessage: message };
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.clearTimer();
|
||||
}
|
||||
|
||||
clearTimer() {
|
||||
if (this.indicatorTimer) {
|
||||
clearTimeout(this.indicatorTimer);
|
||||
}
|
||||
}
|
||||
|
||||
createTimer() {
|
||||
this.clearTimer();
|
||||
const { timeToCloseMessage, onErrorMessageDisappear } = this.props;
|
||||
this.indicatorTimer = _.sleep(() => {
|
||||
this.setState(() => {
|
||||
return { invalidMessage: null };
|
||||
});
|
||||
if (_.isFunction(onErrorMessageDisappear)) onErrorMessageDisappear();
|
||||
}, timeToCloseMessage);
|
||||
}
|
||||
|
||||
beforeComplete(row, column, newValue) {
|
||||
const { onUpdate } = this.props;
|
||||
if (_.isFunction(column.validator)) {
|
||||
const validateForm = column.validator(newValue, row, column);
|
||||
if (_.isObject(validateForm) && !validateForm.valid) {
|
||||
this.setState(() => {
|
||||
return { invalidMessage: validateForm.message };
|
||||
});
|
||||
this.createTimer();
|
||||
return;
|
||||
}
|
||||
}
|
||||
onUpdate(row, column, newValue);
|
||||
}
|
||||
|
||||
handleBlur() {
|
||||
const { onEscape, blurToSave, row, column } = this.props;
|
||||
if (blurToSave) {
|
||||
const value = this.editor.text.value;
|
||||
if (!_.isDefined(value)) {
|
||||
// TODO: for other custom or embed editor
|
||||
}
|
||||
this.beforeComplete(row, column, value);
|
||||
} else {
|
||||
onEscape();
|
||||
}
|
||||
}
|
||||
|
||||
handleKeyDown(e) {
|
||||
const { onEscape, row, column } = this.props;
|
||||
if (e.keyCode === 27) { // ESC
|
||||
onEscape();
|
||||
} else if (e.keyCode === 13) { // ENTER
|
||||
const value = e.currentTarget.value;
|
||||
if (!_.isDefined(value)) {
|
||||
// TODO: for other custom or embed editor
|
||||
}
|
||||
this.beforeComplete(row, column, value);
|
||||
}
|
||||
}
|
||||
|
||||
handleClick(e) {
|
||||
if (e.target.tagName !== 'TD') {
|
||||
// To avoid the row selection event be triggered,
|
||||
// When user define selectRow.clickToSelect and selectRow.clickToEdit
|
||||
// We shouldn't trigger selection event even if user click on the cell editor(input)
|
||||
e.stopPropagation();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { invalidMessage } = this.state;
|
||||
const { row, column, className, style } = this.props;
|
||||
const { dataField } = column;
|
||||
|
||||
const value = _.get(row, dataField);
|
||||
const editorAttrs = {
|
||||
onKeyDown: this.handleKeyDown,
|
||||
onBlur: this.handleBlur
|
||||
};
|
||||
|
||||
const hasError = _.isDefined(invalidMessage);
|
||||
const editorClass = hasError ? cs('animated', 'shake') : null;
|
||||
return (
|
||||
<td
|
||||
className={ cs('react-bootstrap-table-editing-cell', className) }
|
||||
style={ style }
|
||||
onClick={ this.handleClick }
|
||||
>
|
||||
<TextEditor
|
||||
ref={ node => this.editor = node }
|
||||
defaultValue={ value }
|
||||
className={ editorClass }
|
||||
{ ...editorAttrs }
|
||||
/>
|
||||
{ hasError ? <EditorIndicator invalidMessage={ invalidMessage } /> : null }
|
||||
</td>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
EditingCell.propTypes = {
|
||||
row: PropTypes.object.isRequired,
|
||||
column: PropTypes.object.isRequired,
|
||||
onUpdate: PropTypes.func.isRequired,
|
||||
onEscape: PropTypes.func.isRequired,
|
||||
timeToCloseMessage: PropTypes.number,
|
||||
className: PropTypes.string,
|
||||
style: PropTypes.object
|
||||
};
|
||||
|
||||
EditingCell.defaultProps = {
|
||||
timeToCloseMessage: Const.TIME_TO_CLOSE_MESSAGE,
|
||||
className: null,
|
||||
style: {}
|
||||
};
|
||||
|
||||
export default EditingCell;
|
||||
26
packages/react-bootstrap-table2/src/cell.js
vendored
26
packages/react-bootstrap-table2/src/cell.js
vendored
@ -2,7 +2,6 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import Const from './const';
|
||||
import _ from './utils';
|
||||
|
||||
class Cell extends Component {
|
||||
@ -12,18 +11,20 @@ class Cell extends Component {
|
||||
}
|
||||
|
||||
handleEditingCell(e) {
|
||||
const { editMode, column, onStart, rowIndex, columnIndex } = this.props;
|
||||
const { column, onStart, rowIndex, columnIndex, clickToEdit, dbclickToEdit } = this.props;
|
||||
const { events } = column;
|
||||
if (events) {
|
||||
if (editMode === Const.CLICK_TO_CELL_EDIT) {
|
||||
if (clickToEdit) {
|
||||
const customClick = events.onClick;
|
||||
if (_.isFunction(customClick)) customClick(e);
|
||||
} else {
|
||||
} else if (dbclickToEdit) {
|
||||
const customDbClick = events.onDoubleClick;
|
||||
if (_.isFunction(customDbClick)) customDbClick(e);
|
||||
}
|
||||
}
|
||||
onStart(rowIndex, columnIndex);
|
||||
if (onStart) {
|
||||
onStart(rowIndex, columnIndex);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
@ -32,8 +33,9 @@ class Cell extends Component {
|
||||
rowIndex,
|
||||
column,
|
||||
columnIndex,
|
||||
editMode,
|
||||
editable
|
||||
editable,
|
||||
clickToEdit,
|
||||
dbclickToEdit
|
||||
} = this.props;
|
||||
const {
|
||||
dataField,
|
||||
@ -85,12 +87,10 @@ class Cell extends Component {
|
||||
if (cellClasses) cellAttrs.className = cellClasses;
|
||||
|
||||
if (!_.isEmptyObject(cellStyle)) cellAttrs.style = cellStyle;
|
||||
if (editable && editMode !== Const.UNABLE_TO_CELL_EDIT) {
|
||||
if (editMode === Const.CLICK_TO_CELL_EDIT) {
|
||||
cellAttrs.onClick = this.handleEditingCell;
|
||||
} else {
|
||||
cellAttrs.onDoubleClick = this.handleEditingCell;
|
||||
}
|
||||
if (clickToEdit && editable) {
|
||||
cellAttrs.onClick = this.handleEditingCell;
|
||||
} else if (dbclickToEdit && editable) {
|
||||
cellAttrs.onDoubleClick = this.handleEditingCell;
|
||||
}
|
||||
return (
|
||||
<td { ...cellAttrs }>{ content }</td>
|
||||
|
||||
7
packages/react-bootstrap-table2/src/const.js
vendored
7
packages/react-bootstrap-table2/src/const.js
vendored
@ -1,15 +1,10 @@
|
||||
export default {
|
||||
SORT_ASC: 'asc',
|
||||
SORT_DESC: 'desc',
|
||||
UNABLE_TO_CELL_EDIT: 'none',
|
||||
CLICK_TO_CELL_EDIT: 'click',
|
||||
DBCLICK_TO_CELL_EDIT: 'dbclick',
|
||||
TIME_TO_CLOSE_MESSAGE: 3000,
|
||||
ROW_SELECT_SINGLE: 'radio',
|
||||
ROW_SELECT_MULTIPLE: 'checkbox',
|
||||
ROW_SELECT_DISABLED: 'ROW_SELECT_DISABLED',
|
||||
CHECKBOX_STATUS_CHECKED: 'checked',
|
||||
CHECKBOX_STATUS_INDETERMINATE: 'indeterminate',
|
||||
CHECKBOX_STATUS_UNCHECKED: 'unchecked',
|
||||
DELAY_FOR_DBCLICK: 200
|
||||
CHECKBOX_STATUS_UNCHECKED: 'unchecked'
|
||||
};
|
||||
|
||||
@ -3,7 +3,6 @@
|
||||
import React, { Component } from 'react';
|
||||
import Store from './store';
|
||||
import withSort from './sort/wrapper';
|
||||
import withCellEdit from './cell-edit/wrapper';
|
||||
import withSelection from './row-selection/wrapper';
|
||||
|
||||
import remoteResolver from './props-resolver/remote-resolver';
|
||||
@ -45,7 +44,11 @@ const withDataStore = Base =>
|
||||
}
|
||||
|
||||
if (cellEdit) {
|
||||
this.BaseComponent = withCellEdit(this.BaseComponent);
|
||||
const { wrapperFactory } = cellEdit;
|
||||
this.BaseComponent = wrapperFactory(this.BaseComponent, {
|
||||
_,
|
||||
remoteResolver
|
||||
});
|
||||
}
|
||||
|
||||
if (selectRow) {
|
||||
|
||||
@ -18,28 +18,6 @@ export default ExtendBase =>
|
||||
return this.props.data.length === 0;
|
||||
}
|
||||
|
||||
resolveCellEditProps(options = { currEditCell: null }) {
|
||||
const { cellEdit } = this.props;
|
||||
const nonEditableRows =
|
||||
(cellEdit && _.isFunction(cellEdit.nonEditableRows)) ? cellEdit.nonEditableRows() : [];
|
||||
const cellEditInfo = {
|
||||
...options.currEditCell,
|
||||
nonEditableRows
|
||||
};
|
||||
|
||||
if (_.isDefined(cellEdit)) {
|
||||
return {
|
||||
...cellEdit,
|
||||
...cellEditInfo,
|
||||
...options
|
||||
};
|
||||
}
|
||||
return {
|
||||
mode: Const.UNABLE_TO_CELL_EDIT,
|
||||
...cellEditInfo
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* props resolver for cell selection
|
||||
* @param {Object} options - addtional options like callback which are about to merge into props
|
||||
|
||||
19
packages/react-bootstrap-table2/src/row.js
vendored
19
packages/react-bootstrap-table2/src/row.js
vendored
@ -5,7 +5,6 @@ import PropTypes from 'prop-types';
|
||||
import _ from './utils';
|
||||
import Cell from './cell';
|
||||
import SelectionCell from './row-selection/selection-cell';
|
||||
import EditingCell from './cell-edit/editing-cell';
|
||||
import Const from './const';
|
||||
|
||||
class Row extends Component {
|
||||
@ -26,7 +25,11 @@ class Row extends Component {
|
||||
onRowSelect,
|
||||
clickToEdit
|
||||
},
|
||||
cellEdit: { mode },
|
||||
cellEdit: {
|
||||
mode,
|
||||
DBCLICK_TO_CELL_EDIT,
|
||||
DELAY_FOR_DBCLICK
|
||||
},
|
||||
attrs
|
||||
} = this.props;
|
||||
|
||||
@ -40,14 +43,14 @@ class Row extends Component {
|
||||
}
|
||||
};
|
||||
|
||||
if (mode === Const.DBCLICK_TO_CELL_EDIT && clickToEdit) {
|
||||
if (mode === DBCLICK_TO_CELL_EDIT && clickToEdit) {
|
||||
this.clickNum += 1;
|
||||
_.debounce(() => {
|
||||
if (this.clickNum === 1) {
|
||||
clickFn();
|
||||
}
|
||||
this.clickNum = 0;
|
||||
}, Const.DELAY_FOR_DBCLICK)();
|
||||
}, DELAY_FOR_DBCLICK)();
|
||||
} else {
|
||||
clickFn();
|
||||
}
|
||||
@ -72,8 +75,11 @@ class Row extends Component {
|
||||
const {
|
||||
mode,
|
||||
onStart,
|
||||
EditingCell,
|
||||
ridx: editingRowIdx,
|
||||
cidx: editingColIdx,
|
||||
CLICK_TO_CELL_EDIT,
|
||||
DBCLICK_TO_CELL_EDIT,
|
||||
...rest
|
||||
} = cellEdit;
|
||||
|
||||
@ -136,9 +142,10 @@ class Row extends Component {
|
||||
rowIndex={ rowIndex }
|
||||
columnIndex={ index }
|
||||
column={ column }
|
||||
editMode={ mode }
|
||||
editable={ editable }
|
||||
onStart={ onStart }
|
||||
editable={ editable }
|
||||
clickToEdit={ mode === CLICK_TO_CELL_EDIT }
|
||||
dbclickToEdit={ mode === DBCLICK_TO_CELL_EDIT }
|
||||
/>
|
||||
);
|
||||
})
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import React from 'react';
|
||||
import sinon from 'sinon';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import Caption from '../src/caption';
|
||||
@ -7,7 +6,6 @@ import Store from '../src/store';
|
||||
import Header from '../src/header';
|
||||
import Body from '../src/body';
|
||||
import BootstrapTable from '../src/bootstrap-table';
|
||||
import Const from '../src/const';
|
||||
|
||||
describe('BootstrapTable', () => {
|
||||
let wrapper;
|
||||
@ -116,49 +114,4 @@ describe('BootstrapTable', () => {
|
||||
expect(wrapper.find('.table-caption').length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when cellEdit props is defined', () => {
|
||||
const nonEditableRows = [data[1].id];
|
||||
const currEditCell = {
|
||||
ridx: 1,
|
||||
cidx: 2,
|
||||
message: null,
|
||||
editing: false
|
||||
};
|
||||
const cellEdit = {
|
||||
mode: Const.CLICK_TO_CELL_EDIT,
|
||||
onEditing: sinon.stub(),
|
||||
nonEditableRows: () => nonEditableRows
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = shallow(
|
||||
<BootstrapTable
|
||||
keyField="id"
|
||||
columns={ columns }
|
||||
data={ data }
|
||||
bordered={ false }
|
||||
store={ store }
|
||||
cellEdit={ cellEdit }
|
||||
onCellUpdate={ sinon.stub() }
|
||||
onStartEditing={ sinon.stub() }
|
||||
onEscapeEditing={ sinon.stub() }
|
||||
currEditCell={ currEditCell }
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
it('should resolve correct cellEdit object to Body component', () => {
|
||||
const body = wrapper.find(Body);
|
||||
expect(body.length).toBe(1);
|
||||
expect(body.props().cellEdit.nonEditableRows).toEqual(nonEditableRows);
|
||||
expect(body.props().cellEdit.ridx).toEqual(currEditCell.ridx);
|
||||
expect(body.props().cellEdit.cidx).toEqual(currEditCell.cidx);
|
||||
expect(body.props().cellEdit.message).toEqual(currEditCell.message);
|
||||
expect(body.props().cellEdit.editing).toEqual(currEditCell.editing);
|
||||
expect(body.props().cellEdit.onStart).toBeDefined();
|
||||
expect(body.props().cellEdit.onEscape).toBeDefined();
|
||||
expect(body.props().cellEdit.onUpdate).toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,346 +0,0 @@
|
||||
import React from 'react';
|
||||
import sinon from 'sinon';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import Store from '../../src/store';
|
||||
import Container from '../../src';
|
||||
import BootstrapTable from '../../src/bootstrap-table';
|
||||
import wrapperFactory from '../../src/cell-edit/wrapper';
|
||||
|
||||
describe('CellEditWrapper', () => {
|
||||
let wrapper;
|
||||
|
||||
const columns = [{
|
||||
dataField: 'id',
|
||||
text: 'ID'
|
||||
}, {
|
||||
dataField: 'name',
|
||||
text: 'Name'
|
||||
}];
|
||||
|
||||
const data = [{
|
||||
id: 1,
|
||||
name: 'A'
|
||||
}, {
|
||||
id: 2,
|
||||
name: 'B'
|
||||
}];
|
||||
|
||||
const cellEdit = {
|
||||
mode: 'click'
|
||||
};
|
||||
|
||||
const keyField = 'id';
|
||||
const store = new Store(keyField);
|
||||
store.data = data;
|
||||
|
||||
const CellEditWrapper = wrapperFactory(Container);
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = shallow(
|
||||
<CellEditWrapper
|
||||
keyField={ keyField }
|
||||
data={ data }
|
||||
columns={ columns }
|
||||
cellEdit={ cellEdit }
|
||||
store={ store }
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
it('should render CellEditWrapper correctly', () => {
|
||||
expect(wrapper.length).toBe(1);
|
||||
expect(wrapper.find(BootstrapTable)).toBeDefined();
|
||||
});
|
||||
|
||||
it('should have correct state', () => {
|
||||
expect(wrapper.state().ridx).toBeNull();
|
||||
expect(wrapper.state().cidx).toBeNull();
|
||||
expect(wrapper.state().message).toBeNull();
|
||||
expect(wrapper.state().isDataChanged).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should inject correct props to base component', () => {
|
||||
expect(wrapper.props().onCellUpdate).toBeDefined();
|
||||
expect(wrapper.props().onStartEditing).toBeDefined();
|
||||
expect(wrapper.props().onEscapeEditing).toBeDefined();
|
||||
expect(wrapper.props().isDataChanged).toBe(wrapper.state().isDataChanged);
|
||||
expect(wrapper.props().currEditCell).toBeDefined();
|
||||
expect(wrapper.props().currEditCell.ridx).toBeNull();
|
||||
expect(wrapper.props().currEditCell.cidx).toBeNull();
|
||||
expect(wrapper.props().currEditCell.message).toBeNull();
|
||||
});
|
||||
|
||||
describe('when receive new cellEdit prop', () => {
|
||||
const spy = jest.spyOn(CellEditWrapper.prototype, 'escapeEditing');
|
||||
|
||||
describe('and cellEdit is not work on remote', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = shallow(
|
||||
<CellEditWrapper
|
||||
keyField={ keyField }
|
||||
data={ data }
|
||||
columns={ columns }
|
||||
cellEdit={ cellEdit }
|
||||
store={ store }
|
||||
/>
|
||||
);
|
||||
wrapper.setProps({ cellEdit: { ...cellEdit } });
|
||||
});
|
||||
|
||||
it('should always setting state.isDataChanged as false', () => {
|
||||
expect(wrapper.state().isDataChanged).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('and cellEdit is work on remote', () => {
|
||||
let errorMessage;
|
||||
const ridx = 1;
|
||||
const cidx = 2;
|
||||
|
||||
describe('and cellEdit.errorMessage is defined', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = shallow(
|
||||
<CellEditWrapper
|
||||
remote={ { cellEdit: true } }
|
||||
keyField={ keyField }
|
||||
data={ data }
|
||||
columns={ columns }
|
||||
cellEdit={ cellEdit }
|
||||
store={ store }
|
||||
/>
|
||||
);
|
||||
errorMessage = 'test';
|
||||
wrapper.setState({ ridx, cidx });
|
||||
wrapper.setProps({ cellEdit: { ...cellEdit, errorMessage } });
|
||||
});
|
||||
|
||||
it('should setting correct state', () => {
|
||||
expect(wrapper.state().ridx).toEqual(ridx);
|
||||
expect(wrapper.state().cidx).toEqual(cidx);
|
||||
expect(wrapper.state().isDataChanged).toBeFalsy();
|
||||
expect(wrapper.state().message).toEqual(errorMessage);
|
||||
});
|
||||
});
|
||||
|
||||
describe('and cellEdit.errorMessage is undefined', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = shallow(
|
||||
<CellEditWrapper
|
||||
remote={ { cellEdit: true } }
|
||||
keyField={ keyField }
|
||||
data={ data }
|
||||
columns={ columns }
|
||||
cellEdit={ cellEdit }
|
||||
store={ store }
|
||||
/>
|
||||
);
|
||||
errorMessage = null;
|
||||
wrapper.setState({ ridx, cidx });
|
||||
wrapper.setProps({ cellEdit: { ...cellEdit, errorMessage } });
|
||||
});
|
||||
|
||||
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', () => {
|
||||
wrapper.instance().escapeEditing();
|
||||
expect(wrapper.state().ridx).toBeNull();
|
||||
expect(wrapper.state().cidx).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('call startEditing function', () => {
|
||||
const ridx = 1;
|
||||
const cidx = 3;
|
||||
it('should set state correctly', () => {
|
||||
wrapper.instance().startEditing(ridx, cidx);
|
||||
expect(wrapper.state().ridx).toEqual(ridx);
|
||||
expect(wrapper.state().cidx).toEqual(cidx);
|
||||
expect(wrapper.state().isDataChanged).toBeFalsy();
|
||||
});
|
||||
|
||||
describe('if selectRow.clickToSelect is defined', () => {
|
||||
beforeEach(() => {
|
||||
const selectRow = { mode: 'checkbox', clickToSelect: true };
|
||||
wrapper = shallow(
|
||||
<CellEditWrapper
|
||||
keyField={ keyField }
|
||||
data={ data }
|
||||
columns={ columns }
|
||||
cellEdit={ cellEdit }
|
||||
selectRow={ selectRow }
|
||||
store={ store }
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
it('should not set state', () => {
|
||||
wrapper.instance().startEditing(ridx, cidx);
|
||||
expect(wrapper.state().ridx).toBeNull();
|
||||
expect(wrapper.state().cidx).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('if selectRow.clickToSelect and selectRow.clickToEdit is defined', () => {
|
||||
beforeEach(() => {
|
||||
const selectRow = { mode: 'checkbox', clickToSelect: true, clickToEdit: true };
|
||||
wrapper = shallow(
|
||||
<CellEditWrapper
|
||||
keyField={ keyField }
|
||||
data={ data }
|
||||
columns={ columns }
|
||||
cellEdit={ cellEdit }
|
||||
selectRow={ selectRow }
|
||||
store={ store }
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
it('should set state correctly', () => {
|
||||
wrapper.instance().startEditing(ridx, cidx);
|
||||
expect(wrapper.state().ridx).toEqual(ridx);
|
||||
expect(wrapper.state().cidx).toEqual(cidx);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('call completeEditing function', () => {
|
||||
it('should set state correctly', () => {
|
||||
wrapper.instance().completeEditing();
|
||||
expect(wrapper.state().ridx).toBeNull();
|
||||
expect(wrapper.state().cidx).toBeNull();
|
||||
expect(wrapper.state().message).toBeNull();
|
||||
expect(wrapper.state().isDataChanged).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('call handleCellUpdate function', () => {
|
||||
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');
|
||||
const onTableChangeCB = jest.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = shallow(
|
||||
<CellEditWrapper
|
||||
remote={ { cellEdit: true } }
|
||||
keyField={ keyField }
|
||||
data={ data }
|
||||
columns={ columns }
|
||||
cellEdit={ cellEdit }
|
||||
onTableChange={ onTableChangeCB }
|
||||
store={ store }
|
||||
/>
|
||||
);
|
||||
wrapper.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[keyField]);
|
||||
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(() => {
|
||||
wrapper = shallow(
|
||||
<CellEditWrapper
|
||||
keyField={ keyField }
|
||||
data={ data }
|
||||
columns={ columns }
|
||||
cellEdit={ cellEdit }
|
||||
store={ store }
|
||||
/>
|
||||
);
|
||||
wrapper.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[keyField]);
|
||||
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(() => {
|
||||
cellEdit.afterSaveCell = aftereSaveCellCallBack;
|
||||
wrapper = shallow(
|
||||
<CellEditWrapper
|
||||
keyField={ keyField }
|
||||
data={ data }
|
||||
columns={ columns }
|
||||
cellEdit={ cellEdit }
|
||||
store={ store }
|
||||
/>
|
||||
);
|
||||
wrapper.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(() => {
|
||||
cellEdit.beforeSaveCell = beforeSaveCellCallBack;
|
||||
wrapper = shallow(
|
||||
<CellEditWrapper
|
||||
keyField={ keyField }
|
||||
data={ data }
|
||||
columns={ columns }
|
||||
cellEdit={ cellEdit }
|
||||
store={ store }
|
||||
/>
|
||||
);
|
||||
wrapper.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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -2,7 +2,6 @@ import React from 'react';
|
||||
import sinon from 'sinon';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import Const from '../src/const';
|
||||
import Cell from '../src/cell';
|
||||
|
||||
describe('Cell', () => {
|
||||
@ -462,7 +461,7 @@ describe('Cell', () => {
|
||||
onStartCallBack = sinon.stub().withArgs(rowIndex, columnIndex);
|
||||
});
|
||||
|
||||
describe(`and editMode is ${Const.CLICK_TO_CELL_EDIT}`, () => {
|
||||
describe('and clickToEdit is true', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = shallow(
|
||||
<Cell
|
||||
@ -471,7 +470,7 @@ describe('Cell', () => {
|
||||
column={ column }
|
||||
columnIndex={ columnIndex }
|
||||
editable
|
||||
editMode={ Const.CLICK_TO_CELL_EDIT }
|
||||
clickToEdit
|
||||
onStart={ onStartCallBack }
|
||||
/>
|
||||
);
|
||||
@ -502,7 +501,7 @@ describe('Cell', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe(`and editMode is ${Const.DBCLICK_TO_CELL_EDIT}`, () => {
|
||||
describe('and dbclickToEdit is true', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = shallow(
|
||||
<Cell
|
||||
@ -511,7 +510,7 @@ describe('Cell', () => {
|
||||
column={ column }
|
||||
columnIndex={ 1 }
|
||||
editable
|
||||
editMode={ Const.DBCLICK_TO_CELL_EDIT }
|
||||
dbclickToEdit
|
||||
onStart={ onStartCallBack }
|
||||
/>
|
||||
);
|
||||
|
||||
@ -51,8 +51,15 @@ describe('container', () => {
|
||||
});
|
||||
|
||||
describe('when cellEdit prop is defined', () => {
|
||||
const wrapperFactory = Base => class CellEditWrapper extends React.Component {
|
||||
render() { return <Base { ...this.props } />; }
|
||||
};
|
||||
|
||||
const cellEdit = {
|
||||
mode: 'click'
|
||||
wrapperFactory,
|
||||
options: {
|
||||
mode: 'click'
|
||||
}
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
@ -72,64 +72,6 @@ describe('TableResolver', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolveCellEditProps', () => {
|
||||
describe('if cellEdit prop not defined', () => {
|
||||
beforeEach(() => {
|
||||
const mockElement = React.createElement(BootstrapTableMock, {
|
||||
data, keyField, columns
|
||||
}, null);
|
||||
wrapper = shallow(mockElement);
|
||||
});
|
||||
|
||||
it('should resolve a default cellEdit instance', () => {
|
||||
const cellEdit = wrapper.instance().resolveCellEditProps();
|
||||
expect(cellEdit).toBeDefined();
|
||||
expect(cellEdit.mode).toEqual(Const.UNABLE_TO_CELL_EDIT);
|
||||
expect(cellEdit.nonEditableRows.length).toEqual(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('if cellEdit prop defined', () => {
|
||||
const expectNonEditableRows = [1, 2];
|
||||
const cellEdit = {
|
||||
mode: Const.DBCLICK_TO_CELL_EDIT,
|
||||
blurToSave: true,
|
||||
beforeSaveCell: sinon.stub(),
|
||||
afterSaveCell: sinon.stub(),
|
||||
nonEditableRows: sinon.stub().returns(expectNonEditableRows)
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
const mockElement = React.createElement(BootstrapTableMock, {
|
||||
data, keyField, columns, cellEdit
|
||||
}, null);
|
||||
wrapper = shallow(mockElement);
|
||||
});
|
||||
|
||||
it('should resolve a cellEdit correctly', () => {
|
||||
const cellEditInfo = wrapper.instance().resolveCellEditProps();
|
||||
expect(cellEditInfo).toBeDefined();
|
||||
expect(cellEditInfo.mode).toEqual(cellEdit.mode);
|
||||
expect(cellEditInfo.onUpdate).toEqual(cellEdit.onUpdate);
|
||||
expect(cellEditInfo.blurToSave).toEqual(cellEdit.blurToSave);
|
||||
expect(cellEditInfo.beforeSaveCell).toEqual(cellEdit.beforeSaveCell);
|
||||
expect(cellEditInfo.afterSaveCell).toEqual(cellEdit.afterSaveCell);
|
||||
expect(cellEditInfo.nonEditableRows).toEqual(expectNonEditableRows);
|
||||
});
|
||||
|
||||
it('should attach options to cellEdit props', () => {
|
||||
const something = {
|
||||
test: 1,
|
||||
cb: sinon.stub()
|
||||
};
|
||||
const cellEditInfo = wrapper.instance().resolveCellEditProps(something);
|
||||
expect(cellEditInfo).toBeDefined();
|
||||
expect(cellEditInfo.test).toEqual(something.test);
|
||||
expect(cellEditInfo.cb).toEqual(something.cb);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolveSelectRowProps', () => {
|
||||
let cellSelectionInfo;
|
||||
let selectRow;
|
||||
|
||||
@ -5,7 +5,6 @@ import { shallow } from 'enzyme';
|
||||
import Cell from '../src/cell';
|
||||
import Row from '../src/row';
|
||||
import Const from '../src/const';
|
||||
import EditingCell from '../src/cell-edit/editing-cell';
|
||||
import SelectionCell from '../src//row-selection/selection-cell';
|
||||
import mockBodyResolvedProps from './test-helpers/mock/body-resolved-props';
|
||||
|
||||
@ -97,7 +96,9 @@ describe('Row', () => {
|
||||
beforeEach(() => {
|
||||
columns = defaultColumns;
|
||||
cellEdit = {
|
||||
mode: Const.CLICK_TO_CELL_EDIT
|
||||
mode: 'click',
|
||||
CLICK_TO_CELL_EDIT: 'click',
|
||||
DBCLICK_TO_CELL_EDIT: 'dbclick'
|
||||
};
|
||||
wrapper = shallow(
|
||||
<Row
|
||||
@ -128,13 +129,50 @@ describe('Row', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('Cell component should receive correct editMode props', () => {
|
||||
it('Cell component should receive correct clickToEdit props', () => {
|
||||
expect(wrapper.length).toBe(1);
|
||||
for (let i = 0; i < columns.length; i += 1) {
|
||||
expect(wrapper.find(Cell).get(i).props.editMode).toEqual(cellEdit.mode);
|
||||
expect(wrapper.find(Cell).get(i).props.clickToEdit).toBeTruthy();
|
||||
}
|
||||
});
|
||||
|
||||
it('Cell component should receive correct dbclickToEdit props', () => {
|
||||
expect(wrapper.length).toBe(1);
|
||||
for (let i = 0; i < columns.length; i += 1) {
|
||||
expect(wrapper.find(Cell).get(i).props.dbclickToEdit).toBeFalsy();
|
||||
}
|
||||
});
|
||||
|
||||
describe('when props.cellEdit.mode is dbclick', () => {
|
||||
beforeEach(() => {
|
||||
cellEdit.mode = cellEdit.DBCLICK_TO_CELL_EDIT;
|
||||
wrapper = shallow(
|
||||
<Row
|
||||
{ ...mockBodyResolvedProps }
|
||||
row={ row }
|
||||
rowIndex={ rowIndex }
|
||||
columns={ columns }
|
||||
keyField={ keyField }
|
||||
cellEdit={ cellEdit }
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
it('Cell component should receive correct clickToEdit props', () => {
|
||||
expect(wrapper.length).toBe(1);
|
||||
for (let i = 0; i < columns.length; i += 1) {
|
||||
expect(wrapper.find(Cell).get(i).props.clickToEdit).toBeFalsy();
|
||||
}
|
||||
});
|
||||
|
||||
it('Cell component should receive correct dbclickToEdit props', () => {
|
||||
expect(wrapper.length).toBe(1);
|
||||
for (let i = 0; i < columns.length; i += 1) {
|
||||
expect(wrapper.find(Cell).get(i).props.dbclickToEdit).toBeTruthy();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('and column.editable defined false', () => {
|
||||
const nonEditableColIndex = 1;
|
||||
beforeEach(() => {
|
||||
@ -266,6 +304,7 @@ describe('Row', () => {
|
||||
|
||||
// Means a cell now is undering editing
|
||||
describe('when cellEdit.ridx and cellEdit.cidx is defined', () => {
|
||||
const EditingCell = () => null;
|
||||
describe('and cellEdit.ridx is match to current row index', () => {
|
||||
const editingColIndex = 1;
|
||||
beforeEach(() => {
|
||||
@ -273,6 +312,7 @@ describe('Row', () => {
|
||||
cellEdit.cidx = editingColIndex;
|
||||
cellEdit.onUpdate = sinon.stub();
|
||||
cellEdit.onEscape = sinon.stub();
|
||||
cellEdit.EditingCell = EditingCell;
|
||||
wrapper = shallow(
|
||||
<Row
|
||||
{ ...mockBodyResolvedProps }
|
||||
|
||||
@ -1,11 +0,0 @@
|
||||
/* eslint react/prop-types: 0 */
|
||||
import React from 'react';
|
||||
|
||||
export const TableRowWrapper = props => (
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>{ props.children }</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user