diff --git a/.travis.yml b/.travis.yml
index bfcdb77..086f638 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -5,7 +5,8 @@ node_js:
- "6"
cache:
- yarn: true
+ directories:
+ - node_modules
branches:
only:
@@ -13,8 +14,8 @@ branches:
# - master
- develop
-before_install:
- - curl -o- -L https://yarnpkg.com/install.sh | bash -s
- - export PATH="$HOME/.yarn/bin:$PATH"
+# before_install:
+# - curl -o- -L https://yarnpkg.com/install.sh | bash -s
+# - export PATH="$HOME/.yarn/bin:$PATH"
-install: yarn --frozen-lockfile
+# install: yarn --frozen-lockfile
diff --git a/docs/README.md b/docs/README.md
index fd43f93..19330f8 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -2,6 +2,18 @@
## Props on BootstrapTable
+#### Required
+* [keyField (**required**)](#keyField)
+* [data (**required**)](#data)
+* [columns (**required**)](#columns)
+
+#### Optional
+* [striped](#striped)
+* [bordered](#bordered)
+* [hover](#hover)
+* [condensed](#condensed)
+* [cellEdit](#cellEdit)
+
### keyField(**required**) - [String]
`keyField` is a prop to tell `react-bootstrap-table2` which column is unigue key.
@@ -18,4 +30,29 @@ Same as `.table-bordered` class for adding borders on all sides of the table and
### hover - [Bool]
Same as `.table-hover` class for adding a hover effect (grey background color) on table rows
### condensed - [Bool]
-Same as `.table-condensed` class for makeing a table more compact by cutting cell padding in half
\ No newline at end of file
+Same as `.table-condensed` class for makeing a table more compact by cutting cell padding in half
+
+### cellEdit - [Bool]
+Assign a valid `cellEdit` object can enable the cell editing on the cell. The default usage is click/dbclick to trigger cell editing and press `ENTER` to save cell or press `ESC` to cancel editing.
+
+> Note: The `keyField` column can't be edited
+
+Following is a `cellEdit` object:
+```js
+{
+ mode: 'click',
+ blurToSave: true,
+ onEditing: (rowId, dataField, newValue) => { ... },
+ beforeSaveCell: (oldValue, newValue, row, column) => { ... },
+ afterSaveCell: (oldValue, newValue, row, column) => { ... },
+ nonEditableRows: () => { ... }
+}
+```
+#### cellEdit.mode - [String]
+`cellEdit.mode` possible value is `click` and `dbclick`. It's required value that tell `react-bootstrap-table2` how to trigger the cell editing.
+
+#### cellEdit.blurToSave - [Bool]
+Default is `false`, enable it will be able to save the cell automatically when blur from the cell editor.
+
+#### cellEdit.nonEditableRows - [Function]
+`cellEdit.nonEditableRows` accept a callback function and expect return an array which used to restrict all the columns of some rows as non-editable. So the each item in return array should be rowkey(`keyField`)
diff --git a/docs/columns.md b/docs/columns.md
index a517532..8ac781e 100644
--- a/docs/columns.md
+++ b/docs/columns.md
@@ -25,6 +25,7 @@ Available properties in a column object:
* [headerEvents](#headerEvents)
* [headerAlign](#headerAlign)
* [headerAttrs](#headerAttrs)
+* [editable](#editable)
Following is a most simplest and basic usage:
@@ -411,5 +412,9 @@ Additionally, customize the header attributes by a `2-arguments` callback functi
A new `Object` will be the result of element headerAttrs.
-#### * Caution
-Same as [column.attrs](#attrs), it has lower priority and will be overwrited when other props related to HTML attributes were given.
+> Caution:
+> Same as [column.attrs](#attrs), it has lower priority and will be
+> overwrited when other props related to HTML attributes were given.
+
+## column.editable - [Bool]
+`column.editable` default is true, means every column is editable if you configure [`cellEdit`](./README.md#cellEdit). But you can disable some columns editable via setting `false`.
\ No newline at end of file
diff --git a/package.json b/package.json
index dd2cd5b..4e1840d 100644
--- a/package.json
+++ b/package.json
@@ -42,6 +42,8 @@
"eslint-plugin-react": "^7.2.1",
"html-webpack-plugin": "^2.30.1",
"jest": "^20.0.4",
+ "jsdom": "^11.2.0",
+ "jsdom-global": "^3.0.2",
"lerna": "^2.0.0",
"node-sass": "^4.5.3",
"react-test-renderer": "^15.6.1",
@@ -71,7 +73,7 @@
],
"testEnvironment": "node",
"testMatch": [
- "**/test/**/*.js"
+ "**/test/**/*.test.js"
]
}
}
diff --git a/packages/react-bootstrap-table2-example/examples/cell-edit/blur-to-save-table.js b/packages/react-bootstrap-table2-example/examples/cell-edit/blur-to-save-table.js
new file mode 100644
index 0000000..9452c8f
--- /dev/null
+++ b/packages/react-bootstrap-table2-example/examples/cell-edit/blur-to-save-table.js
@@ -0,0 +1,54 @@
+import React from 'react';
+
+import { BootstrapTableful } from 'react-bootstrap-table2';
+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 = `\
+const columns = [{
+ dataField: 'id',
+ text: 'Product ID'
+}, {
+ dataField: 'name',
+ text: 'Product Name'
+}, {
+ dataField: 'price',
+ text: 'Product Price'
+}];
+
+const cellEdit = {
+ mode: 'click',
+ blurToSave: true
+};
+
+
+`;
+
+const cellEdit = {
+ mode: 'click',
+ blurToSave: true
+};
+export default () => (
+
+
+ { sourceCode }
+
+);
diff --git a/packages/react-bootstrap-table2-example/examples/cell-edit/cell-edit-hooks-table.js b/packages/react-bootstrap-table2-example/examples/cell-edit/cell-edit-hooks-table.js
new file mode 100644
index 0000000..0238ec5
--- /dev/null
+++ b/packages/react-bootstrap-table2-example/examples/cell-edit/cell-edit-hooks-table.js
@@ -0,0 +1,58 @@
+/* eslint no-unused-vars: 0 */
+/* eslint no-console: 0 */
+import React from 'react';
+
+import { BootstrapTableful } from 'react-bootstrap-table2';
+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 = `\
+const columns = [{
+ dataField: 'id',
+ text: 'Product ID'
+}, {
+ dataField: 'name',
+ text: 'Product Name'
+}, {
+ dataField: 'price',
+ 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!!'); }
+};
+
+
+`;
+
+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 () => (
+
+
+ { sourceCode }
+
+);
diff --git a/packages/react-bootstrap-table2-example/examples/cell-edit/click-to-edit-table.js b/packages/react-bootstrap-table2-example/examples/cell-edit/click-to-edit-table.js
new file mode 100644
index 0000000..0030936
--- /dev/null
+++ b/packages/react-bootstrap-table2-example/examples/cell-edit/click-to-edit-table.js
@@ -0,0 +1,52 @@
+import React from 'react';
+
+import { BootstrapTableful } from 'react-bootstrap-table2';
+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 = `\
+const columns = [{
+ dataField: 'id',
+ text: 'Product ID'
+}, {
+ dataField: 'name',
+ text: 'Product Name'
+}, {
+ dataField: 'price',
+ text: 'Product Price'
+}];
+
+const cellEdit = {
+ mode: 'click'
+};
+
+
+`;
+
+const cellEdit = {
+ mode: 'click'
+};
+export default () => (
+
+
+ { sourceCode }
+
+);
diff --git a/packages/react-bootstrap-table2-example/examples/cell-edit/column-level-editable-table.js b/packages/react-bootstrap-table2-example/examples/cell-edit/column-level-editable-table.js
new file mode 100644
index 0000000..b777486
--- /dev/null
+++ b/packages/react-bootstrap-table2-example/examples/cell-edit/column-level-editable-table.js
@@ -0,0 +1,57 @@
+import React from 'react';
+
+import { BootstrapTableful } from 'react-bootstrap-table2';
+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',
+ editable: false
+}, {
+ dataField: 'price',
+ text: 'Product Price'
+}];
+
+const sourceCode = `\
+const columns = [{
+ dataField: 'id',
+ text: 'Product ID'
+}, {
+ dataField: 'name',
+ text: 'Product Name'
+ // Product Name column can't be edit anymore
+ editable: false
+}, {
+ dataField: 'price',
+ text: 'Product Price'
+}];
+
+const cellEdit = {
+ mode: 'click',
+ blurToSave: true
+};
+
+
+`;
+
+const cellEdit = {
+ mode: 'click',
+ blurToSave: true
+};
+export default () => (
+
+
+ { sourceCode }
+
+);
diff --git a/packages/react-bootstrap-table2-example/examples/cell-edit/dbclick-to-edit-table.js b/packages/react-bootstrap-table2-example/examples/cell-edit/dbclick-to-edit-table.js
new file mode 100644
index 0000000..37a1061
--- /dev/null
+++ b/packages/react-bootstrap-table2-example/examples/cell-edit/dbclick-to-edit-table.js
@@ -0,0 +1,52 @@
+import React from 'react';
+
+import { BootstrapTableful } from 'react-bootstrap-table2';
+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 = `\
+const columns = [{
+ dataField: 'id',
+ text: 'Product ID'
+}, {
+ dataField: 'name',
+ text: 'Product Name'
+}, {
+ dataField: 'price',
+ text: 'Product Price'
+}];
+
+const cellEdit = {
+ mode: 'dbclick'
+};
+
+
+`;
+
+const cellEdit = {
+ mode: 'dbclick'
+};
+export default () => (
+
+
+ { sourceCode }
+
+);
diff --git a/packages/react-bootstrap-table2-example/examples/cell-edit/row-level-editable-table.js b/packages/react-bootstrap-table2-example/examples/cell-edit/row-level-editable-table.js
new file mode 100644
index 0000000..4033f75
--- /dev/null
+++ b/packages/react-bootstrap-table2-example/examples/cell-edit/row-level-editable-table.js
@@ -0,0 +1,57 @@
+import React from 'react';
+
+import { BootstrapTableful } from 'react-bootstrap-table2';
+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 = `\
+const columns = [{
+ dataField: 'id',
+ text: 'Product ID'
+}, {
+ dataField: 'name',
+ text: 'Product Name'
+}, {
+ dataField: 'price',
+ text: 'Product Price'
+}];
+
+const cellEdit = {
+ mode: 'click',
+ blurToSave: true,
+ // Product ID: 0, 3 will be non-editable
+ nonEditableRows: () => [0, 3]
+};
+
+
+`;
+
+const cellEdit = {
+ mode: 'click',
+ blurToSave: true,
+ nonEditableRows: () => [0, 3]
+};
+export default () => (
+
+
+ { sourceCode }
+
+);
diff --git a/packages/react-bootstrap-table2-example/stories/index.js b/packages/react-bootstrap-table2-example/stories/index.js
index 8e40fcf..9ad48f2 100644
--- a/packages/react-bootstrap-table2-example/stories/index.js
+++ b/packages/react-bootstrap-table2-example/stories/index.js
@@ -35,6 +35,14 @@ import HeaderColumnAttrsTable from 'examples/header-columns/column-attrs-table';
import EnableSortTable from 'examples/sort/enable-sort-table';
import CustomSortTable from 'examples/sort/custom-sort-table';
+// cell editing
+import ClickToEditTable from 'examples/cell-edit/click-to-edit-table';
+import DoubleClickToEditTable from 'examples/cell-edit/dbclick-to-edit-table';
+import BlurToSaveTable from 'examples/cell-edit/blur-to-save-table';
+import RowLevelEditableTable from 'examples/cell-edit/row-level-editable-table';
+import ColumnLevelEditableTable from 'examples/cell-edit/column-level-editable-table';
+import CellEditHooks from 'examples/cell-edit/cell-edit-hooks-table';
+
// css style
import 'bootstrap/dist/css/bootstrap.min.css';
import 'stories/stylesheet/tomorrow.min.css';
@@ -77,3 +85,11 @@ storiesOf('Work on Header Columns', module)
storiesOf('Sort Table', module)
.add('Enable Sort', () => )
.add('Custom Sort Fuction', () => );
+
+storiesOf('Cell Editing', module)
+ .add('Click to Edit', () => )
+ .add('DoubleClick to Edit', () => )
+ .add('Blur to Save Cell', () => )
+ .add('Row Level Editable', () => )
+ .add('Column Level Editable', () => )
+ .add('Rich Hook Functions', () => );
diff --git a/packages/react-bootstrap-table2/src/body.js b/packages/react-bootstrap-table2/src/body.js
index a69af71..76cbd22 100644
--- a/packages/react-bootstrap-table2/src/body.js
+++ b/packages/react-bootstrap-table2/src/body.js
@@ -13,7 +13,8 @@ const Body = (props) => {
keyField,
isEmpty,
noDataIndication,
- visibleColumnSize
+ visibleColumnSize,
+ cellEdit
} = props;
let content;
@@ -22,14 +23,21 @@ const Body = (props) => {
const indication = _.isFunction(noDataIndication) ? noDataIndication() : noDataIndication;
content = ;
} else {
- content = data.map((row, index) => (
-
- ));
+ content = data.map((row, index) => {
+ const key = _.get(row, keyField);
+ const editable = !(cellEdit && cellEdit.nonEditableRows.indexOf(key) > -1);
+ return (
+
+ );
+ });
}
return (
diff --git a/packages/react-bootstrap-table2/src/bootstrap-table.js b/packages/react-bootstrap-table2/src/bootstrap-table.js
index 5acfb2f..d7ea363 100644
--- a/packages/react-bootstrap-table2/src/bootstrap-table.js
+++ b/packages/react-bootstrap-table2/src/bootstrap-table.js
@@ -7,6 +7,8 @@ import Header from './header';
import Body from './body';
import Store from './store/base';
import PropsBaseResolver from './props-resolver';
+import Const from './const';
+import _ from './utils';
class BootstrapTable extends PropsBaseResolver(Component) {
constructor(props) {
@@ -16,8 +18,15 @@ class BootstrapTable extends PropsBaseResolver(Component) {
this.store = !store ? new Store(props) : store;
this.handleSort = this.handleSort.bind(this);
+ this.startEditing = this.startEditing.bind(this);
+ this.escapeEditing = this.escapeEditing.bind(this);
+ this.completeEditing = this.completeEditing.bind(this);
this.state = {
- data: this.store.get()
+ data: this.store.get(),
+ currEditCell: {
+ ridx: null,
+ cidx: null
+ }
};
}
@@ -39,6 +48,12 @@ class BootstrapTable extends PropsBaseResolver(Component) {
'table-condensed': condensed
});
+ const cellEditInfo = this.resolveCellEditProps({
+ onStart: this.startEditing,
+ onEscape: this.escapeEditing,
+ onComplete: this.completeEditing
+ });
+
return (
@@ -55,6 +70,7 @@ class BootstrapTable extends PropsBaseResolver(Component) {
isEmpty={ this.isEmpty() }
visibleColumnSize={ this.visibleColumnSize() }
noDataIndication={ noDataIndication }
+ cellEdit={ cellEditInfo }
/>
@@ -70,6 +86,39 @@ class BootstrapTable extends PropsBaseResolver(Component) {
};
});
}
+
+ completeEditing(row, column, newValue) {
+ const { cellEdit, keyField } = this.props;
+ const { beforeSaveCell, onEditing, afterSaveCell } = cellEdit;
+ const oldValue = _.get(row, column.dataField);
+ const rowId = _.get(row, keyField);
+ if (_.isFunction(beforeSaveCell)) beforeSaveCell(oldValue, newValue, row, column);
+ onEditing(rowId, column.dataField, newValue);
+ if (_.isFunction(afterSaveCell)) afterSaveCell(oldValue, newValue, row, column);
+
+ this.setState(() => {
+ return {
+ data: this.store.get(),
+ currEditCell: { ridx: null, cidx: null }
+ };
+ });
+ }
+
+ startEditing(ridx, cidx) {
+ this.setState(() => {
+ return {
+ currEditCell: { ridx, cidx }
+ };
+ });
+ }
+
+ escapeEditing() {
+ this.setState(() => {
+ return {
+ currEditCell: { ridx: null, cidx: null }
+ };
+ });
+ }
}
BootstrapTable.propTypes = {
@@ -81,7 +130,15 @@ BootstrapTable.propTypes = {
striped: PropTypes.bool,
bordered: PropTypes.bool,
hover: PropTypes.bool,
- condensed: PropTypes.bool
+ condensed: PropTypes.bool,
+ cellEdit: PropTypes.shape({
+ mode: PropTypes.oneOf([Const.CLICK_TO_CELL_EDIT, Const.DBCLICK_TO_CELL_EDIT]).isRequired,
+ onEditing: PropTypes.func.isRequired,
+ blurToSave: PropTypes.bool,
+ beforeSaveCell: PropTypes.func,
+ afterSaveCell: PropTypes.func,
+ nonEditableRows: PropTypes.func
+ })
};
BootstrapTable.defaultProps = {
diff --git a/packages/react-bootstrap-table2/src/cell.js b/packages/react-bootstrap-table2/src/cell.js
index 2126c93..e4431e6 100644
--- a/packages/react-bootstrap-table2/src/cell.js
+++ b/packages/react-bootstrap-table2/src/cell.js
@@ -1,63 +1,103 @@
-import React from 'react';
+/* eslint react/prop-types: 0 */
+import React, { Component } from 'react';
import PropTypes from 'prop-types';
+import Const from './const';
import _ from './utils';
-const Cell = ({ row, rowIndex, column, columnIndex }) => {
- const {
- dataField,
- hidden,
- formatter,
- formatExtraData,
- style,
- classes,
- title,
- events,
- align,
- attrs
- } = column;
- let cellTitle;
- let cellStyle = {};
- let content = _.get(row, dataField);
-
- const cellAttrs = {
- ..._.isFunction(attrs) ? attrs(content, row, rowIndex, columnIndex) : attrs,
- ...events
- };
-
- const cellClasses = _.isFunction(classes)
- ? classes(content, row, rowIndex, columnIndex)
- : classes;
-
- if (style) {
- cellStyle = _.isFunction(style) ? style(content, row, rowIndex, columnIndex) : style;
+class Cell extends Component {
+ constructor(props) {
+ super(props);
+ this.handleEditingCell = this.handleEditingCell.bind(this);
}
- if (title) {
- cellTitle = _.isFunction(title) ? title(content, row, rowIndex, columnIndex) : content;
- cellAttrs.title = cellTitle;
+ handleEditingCell(e) {
+ const { editMode, column, onStart, rowIndex, columnIndex } = this.props;
+ const { events } = column;
+ if (events) {
+ if (editMode === Const.CLICK_TO_CELL_EDIT) {
+ const customClick = events.onClick;
+ if (_.isFunction(customClick)) customClick(e);
+ } else {
+ const customDbClick = events.onDoubleClick;
+ if (_.isFunction(customDbClick)) customDbClick(e);
+ }
+ }
+ onStart(rowIndex, columnIndex);
}
- if (formatter) {
- content = column.formatter(content, row, rowIndex, formatExtraData);
+ render() {
+ const {
+ row,
+ rowIndex,
+ column,
+ columnIndex,
+ editMode,
+ editable
+ } = this.props;
+ const {
+ dataField,
+ hidden,
+ formatter,
+ formatExtraData,
+ style,
+ classes,
+ title,
+ events,
+ align,
+ attrs
+ } = column;
+ let cellTitle;
+ let cellStyle = {};
+ let content = _.get(row, dataField);
+
+ const cellAttrs = {
+ ..._.isFunction(attrs) ? attrs(content, row, rowIndex, columnIndex) : attrs,
+ ...events
+ };
+
+ const cellClasses = _.isFunction(classes)
+ ? classes(content, row, rowIndex, columnIndex)
+ : classes;
+
+ if (style) {
+ cellStyle = _.isFunction(style) ? style(content, row, rowIndex, columnIndex) : style;
+ }
+
+ if (title) {
+ cellTitle = _.isFunction(title) ? title(content, row, rowIndex, columnIndex) : content;
+ cellAttrs.title = cellTitle;
+ }
+
+ if (formatter) {
+ content = column.formatter(content, row, rowIndex, formatExtraData);
+ }
+
+ if (align) {
+ cellStyle.textAlign =
+ _.isFunction(align) ? align(content, row, rowIndex, columnIndex) : align;
+ }
+
+ if (hidden) {
+ cellStyle.display = 'none';
+ }
+
+ 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) { // click to edit
+ cellAttrs.onClick = this.handleEditingCell;
+ } else { // dbclick to edit
+ cellAttrs.onDoubleClick = this.handleEditingCell;
+ }
+ }
+ return (
+ { content }
+ );
}
-
- if (align) {
- cellStyle.textAlign = _.isFunction(align) ? align(content, row, rowIndex, columnIndex) : align;
- }
-
- if (hidden) {
- cellStyle.display = 'none';
- }
-
- if (cellClasses) cellAttrs.className = cellClasses;
-
- if (!_.isEmptyObject(cellStyle)) cellAttrs.style = cellStyle;
-
- return (
- { content }
- );
-};
+}
Cell.propTypes = {
row: PropTypes.object.isRequired,
diff --git a/packages/react-bootstrap-table2/src/const.js b/packages/react-bootstrap-table2/src/const.js
index acc75d2..8a812ee 100644
--- a/packages/react-bootstrap-table2/src/const.js
+++ b/packages/react-bootstrap-table2/src/const.js
@@ -1,4 +1,7 @@
export default {
SORT_ASC: 'asc',
- SORT_DESC: 'desc'
+ SORT_DESC: 'desc',
+ UNABLE_TO_CELL_EDIT: 'none',
+ CLICK_TO_CELL_EDIT: 'click',
+ DBCLICK_TO_CELL_EDIT: 'dbclick'
};
diff --git a/packages/react-bootstrap-table2/src/editing-cell.js b/packages/react-bootstrap-table2/src/editing-cell.js
new file mode 100644
index 0000000..f32e1ad
--- /dev/null
+++ b/packages/react-bootstrap-table2/src/editing-cell.js
@@ -0,0 +1,66 @@
+/* eslint react/prop-types: 0 */
+/* eslint no-return-assign: 0 */
+import React, { Component } from 'react';
+import PropTypes from 'prop-types';
+
+import _ from './utils';
+import TextEditor from './text-editor';
+
+class EditingCell extends Component {
+ constructor(props) {
+ super(props);
+ this.handleBlur = this.handleBlur.bind(this);
+ this.handleKeyDown = this.handleKeyDown.bind(this);
+ }
+
+ handleBlur() {
+ const { onEscape, onComplete, blurToSave, row, column } = this.props;
+ if (blurToSave) {
+ const value = this.editor.text.value;
+ if (!_.isDefined(value)) {
+ // TODO: for other custom or embed editor
+ }
+ onComplete(row, column, value);
+ } else {
+ onEscape();
+ }
+ }
+
+ handleKeyDown(e) {
+ const { onEscape, onComplete, 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
+ }
+ onComplete(row, column, value);
+ }
+ }
+
+ render() {
+ const { row, column } = this.props;
+ const { dataField } = column;
+
+ const value = _.get(row, dataField);
+ const editorAttrs = {
+ onKeyDown: this.handleKeyDown,
+ onBlur: this.handleBlur
+ };
+ return (
+
+ this.editor = node } defaultValue={ value } { ...editorAttrs } />
+
+ );
+ }
+}
+
+EditingCell.propTypes = {
+ row: PropTypes.object.isRequired,
+ column: PropTypes.object.isRequired,
+ onComplete: PropTypes.func.isRequired,
+ onEscape: PropTypes.func.isRequired
+};
+
+export default EditingCell;
diff --git a/packages/react-bootstrap-table2/src/props-resolver/index.js b/packages/react-bootstrap-table2/src/props-resolver/index.js
index 627f796..d7ea8af 100644
--- a/packages/react-bootstrap-table2/src/props-resolver/index.js
+++ b/packages/react-bootstrap-table2/src/props-resolver/index.js
@@ -1,4 +1,6 @@
import ColumnResolver from './column-resolver';
+import Const from '../const';
+import _ from '../utils';
export default ExtendBase =>
class TableResolver extends ColumnResolver(ExtendBase) {
@@ -15,4 +17,27 @@ export default ExtendBase =>
isEmpty() {
return this.props.data.length === 0;
}
+
+ resolveCellEditProps(options) {
+ const { cellEdit } = this.props;
+ const { currEditCell } = this.state;
+ const nonEditableRows =
+ (cellEdit && _.isFunction(cellEdit.nonEditableRows)) ? cellEdit.nonEditableRows() : [];
+ const cellEditInfo = {
+ ...currEditCell,
+ nonEditableRows
+ };
+
+ if (_.isDefined(cellEdit)) {
+ return {
+ ...cellEdit,
+ ...cellEditInfo,
+ ...options
+ };
+ }
+ return {
+ mode: Const.UNABLE_TO_CELL_EDIT,
+ ...cellEditInfo
+ };
+ }
};
diff --git a/packages/react-bootstrap-table2/src/row.js b/packages/react-bootstrap-table2/src/row.js
index 842127c..c0e06b2 100644
--- a/packages/react-bootstrap-table2/src/row.js
+++ b/packages/react-bootstrap-table2/src/row.js
@@ -1,25 +1,64 @@
+/* eslint react/prop-types: 0 */
import React from 'react';
import PropTypes from 'prop-types';
import _ from './utils';
import Cell from './cell';
+import EditingCell from './editing-cell';
-const Row = ({ row, rowIndex, columns }) => (
-
- {
- columns.map((column, index) =>
- (
- |
- ))
- }
-
-);
+const Row = (props) => {
+ const {
+ row,
+ columns,
+ keyField,
+ rowIndex,
+ cellEdit,
+ editable: editableRow
+ } = props;
+ const {
+ ridx: editingRowIdx,
+ cidx: editingColIdx,
+ mode,
+ onStart,
+ onEscape,
+ onComplete,
+ blurToSave
+ } = cellEdit;
+ return (
+
+ {
+ columns.map((column, index) => {
+ let editable = _.isDefined(column.editable) ? column.editable : true;
+ if (column.dataField === keyField || !editableRow) editable = false;
+ if (rowIndex === editingRowIdx && index === editingColIdx) {
+ return (
+
+ );
+ }
+ return (
+ |
+ );
+ })
+ }
+
+ );
+};
Row.propTypes = {
row: PropTypes.object.isRequired,
@@ -27,4 +66,8 @@ Row.propTypes = {
columns: PropTypes.array.isRequired
};
+Row.defaultProps = {
+ editable: true
+};
+
export default Row;
diff --git a/packages/react-bootstrap-table2/src/stateful-layer.js b/packages/react-bootstrap-table2/src/stateful-layer.js
index ba0ad9d..c6a0133 100644
--- a/packages/react-bootstrap-table2/src/stateful-layer.js
+++ b/packages/react-bootstrap-table2/src/stateful-layer.js
@@ -7,11 +7,20 @@ const withStateful = (Base) => {
constructor(props) {
super(props);
this.store = new Store(props);
+ this.edit = this.edit.bind(this);
+ }
+
+ edit(rowId, dataField, newValue) {
+ this.store.edit(rowId, dataField, newValue);
}
render() {
const { props } = this;
- return ;
+ const newProps = { ...props };
+ if (newProps.cellEdit && !newProps.cellEdit.onEditing) {
+ newProps.cellEdit.onEditing = this.edit;
+ }
+ return ;
}
}
return StatefulComponent;
diff --git a/packages/react-bootstrap-table2/src/store/base.js b/packages/react-bootstrap-table2/src/store/base.js
index c44a5ef..2c92725 100644
--- a/packages/react-bootstrap-table2/src/store/base.js
+++ b/packages/react-bootstrap-table2/src/store/base.js
@@ -1,9 +1,11 @@
import { sort } from './sort';
import Const from '../const';
+import _ from '../utils';
export default class Store {
constructor(props) {
- const { data } = props;
+ const { data, keyField } = props;
+ this.keyField = keyField;
this.data = data ? data.slice() : [];
this.sortOrder = undefined;
@@ -25,7 +27,16 @@ export default class Store {
this.sortField = dataField;
}
+ edit(rowId, dataField, newValue) {
+ const row = this.getRowByRowId(rowId);
+ if (row) _.set(row, dataField, newValue);
+ }
+
get() {
return this.data;
}
+
+ getRowByRowId(rowId) {
+ return this.get().find(row => _.get(row, this.keyField) === rowId);
+ }
}
diff --git a/packages/react-bootstrap-table2/src/text-editor.js b/packages/react-bootstrap-table2/src/text-editor.js
new file mode 100644
index 0000000..06c9cfe
--- /dev/null
+++ b/packages/react-bootstrap-table2/src/text-editor.js
@@ -0,0 +1,21 @@
+/* eslint no-return-assign: 0 */
+import React, { Component } from 'react';
+
+class TextEditor extends Component {
+ componentDidMount() {
+ this.text.focus();
+ }
+
+ render() {
+ return (
+ this.text = node }
+ type="text"
+ className="form-control editor edit-text"
+ { ...this.props }
+ />
+ );
+ }
+}
+
+export default TextEditor;
diff --git a/packages/react-bootstrap-table2/src/utils.js b/packages/react-bootstrap-table2/src/utils.js
index 780be47..bc4febf 100644
--- a/packages/react-bootstrap-table2/src/utils.js
+++ b/packages/react-bootstrap-table2/src/utils.js
@@ -1,11 +1,16 @@
/* eslint no-empty: 0 */
+/* eslint no-param-reassign: 0 */
-function get(target, field) {
- const pathArray = [field]
+function splitNested(str) {
+ return [str]
.join('.')
.replace(/\[/g, '.')
.replace(/\]/g, '')
.split('.');
+}
+
+function get(target, field) {
+ const pathArray = splitNested(field);
let result;
try {
result = pathArray.reduce((curr, path) => curr[path], target);
@@ -13,6 +18,25 @@ function get(target, field) {
return result;
}
+function set(target, field, value, safe = false) {
+ const pathArray = splitNested(field);
+ let level = 0;
+ pathArray.reduce((a, b) => {
+ level += 1;
+ if (typeof a[b] === 'undefined') {
+ if (!safe) throw new Error(`${a}.${b} is undefined`);
+ a[b] = {};
+ return a[b];
+ }
+
+ if (level === pathArray.length) {
+ a[b] = value;
+ return value;
+ }
+ return a[b];
+ }, target);
+}
+
function isFunction(obj) {
return obj && (typeof obj === 'function');
}
@@ -46,6 +70,7 @@ function isDefined(value) {
export default {
get,
+ set,
isFunction,
isObject,
isEmptyObject,
diff --git a/packages/react-bootstrap-table2/style/react-bootstrap-table.scss b/packages/react-bootstrap-table2/style/react-bootstrap-table.scss
index 3a8a126..91cc609 100644
--- a/packages/react-bootstrap-table2/style/react-bootstrap-table.scss
+++ b/packages/react-bootstrap-table2/style/react-bootstrap-table.scss
@@ -1,5 +1,9 @@
.react-bootstrap-table-container {
+ table {
+ table-layout: fixed;
+ }
+
th.sortable {
cursor: pointer;
}
diff --git a/packages/react-bootstrap-table2/test/body.test.js b/packages/react-bootstrap-table2/test/body.test.js
index e1bb741..54ba506 100644
--- a/packages/react-bootstrap-table2/test/body.test.js
+++ b/packages/react-bootstrap-table2/test/body.test.js
@@ -4,6 +4,7 @@ import { shallow } from 'enzyme';
import Body from '../src/body';
import Row from '../src/row';
+import Const from '../src/const';
import RowSection from '../src/row-section';
describe('Body', () => {
@@ -111,4 +112,35 @@ describe('Body', () => {
});
});
});
+
+ describe('when cellEdit.nonEditableRows props is defined', () => {
+ const nonEditableRows = [data[1].id];
+ const keyField = 'id';
+ const cellEdit = {
+ mode: Const.CLICK_TO_CELL_EDIT,
+ nonEditableRows
+ };
+ beforeEach(() => {
+ wrapper = shallow(
+