diff --git a/docs/columns.md b/docs/columns.md index d7a7bea..e7b477b 100644 --- a/docs/columns.md +++ b/docs/columns.md @@ -34,6 +34,8 @@ Available properties in a column object: * [editCellClasses](#editCellClasses) * [editorStyle](#editorStyle) * [editorClasses](#editorClasses) +* [editor](#editor) +* [editorRenderer](#editorRenderer) * [filter](#filter) * [filterValue](#filterValue) @@ -560,6 +562,87 @@ This is almost same as [`column.editCellStyle`](#editCellStyle), but `column.edi ## column.editorClasses - [Object | Function] This is almost same as [`column.editCellClasses`](#editCellClasses), but `column.editorClasses` is custom the class on editor instead of cell(`td`). +## column.editor - [Object] +`column.editor` allow you to custom the type of cell editor by following predefined type: + +* Text(Default) +* Dropdown +* Date +* Textarea +* Checkbox + +Following is a quite example: + +```js +import cellEditFactory, { Type } from 'react-bootstrap-table2-editor'; + +const columns = [ + //... + , { + dataField: 'done', + text: 'Done', + editor: { + type: Type.CHECKBOX, + value: 'Y:N' + } + } +]; +``` + +If you want more information, please check [here](https://github.com/react-bootstrap-table/react-bootstrap-table2/tree/master/packages/react-bootstrap-table2-editor). + +## column.editorRenderer - [Function] +If you feel above predefined editors are not satisfied to your requirement, you can totally custom the editor via `column.editorRenderer`: + +```js +import cellEditFactory, { Type } from 'react-bootstrap-table2-editor'; + +// Custom Editor +class QualityRanger extends React.Component { + static propTypes = { + value: PropTypes.number, + onUpdate: PropTypes.func.isRequired + } + static defaultProps = { + value: 0 + } + getValue() { + return parseInt(this.range.value, 10); + } + render() { + const { value, onUpdate, ...rest } = this.props; + return [ + this.range = node } + type="range" + min="0" + max="100" + />, + + ]; + } +} + + +const columns = [ + //... + , { + dataField: 'done', + text: 'Done', + editorRenderer: (editorProps, value, row, column, rowIndex, columnIndex) => + ; + } +]; +``` + ## column.filter - [Object] Configure `column.filter` will able to setup a column level filter on the header column. Currently, `react-bootstrap-table2` support following filters: diff --git a/package.json b/package.json index 45f4029..56c17c7 100644 --- a/package.json +++ b/package.json @@ -85,7 +85,8 @@ }, "jest": { "collectCoverageFrom": [ - "packages/**/*.js" + "packages/*/src/*.js", + "packages/*/index.js" ], "roots": [ "/packages" diff --git a/packages/react-bootstrap-table2-editor/README.md b/packages/react-bootstrap-table2-editor/README.md index 5c9719e..55d6c1d 100644 --- a/packages/react-bootstrap-table2-editor/README.md +++ b/packages/react-bootstrap-table2-editor/README.md @@ -48,6 +48,9 @@ How user save their new editings? We offer two ways: * Column Level (Configure [column.editable](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/column-props.html#columneditable-bool-function) as bool value) * Cell Level (Configure [column.editable](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/column-props.html#columneditable-bool-function) as a callback function) +## Validation + +[column.validator](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/column-props.html#columnvalidator-function) will help you to work on it! ## Customize Style/Class ### Editing Cell @@ -58,6 +61,169 @@ How user save their new editings? We offer two ways: * Customize the editor style via [column.editorStyle](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/column-props.html#columneditorstyle-object-function) * Customize the editor classname via [column.editoClasses](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/column-props.html#columneditorclasses-string-function) -## Validation +## Rich Editors +`react-bootstrap-table2` have following predefined editor: -[`column.validator`](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/column-props.html#columnvalidator-function) will help you to work on it! \ No newline at end of file +* Text(Default) +* Dropdown +* Date +* Textarea +* Checkbox + +In a nutshell, you just only give a [column.editor](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/column-props.html#columneditor-object) and define the `type`: + +```js +import { Type } from 'react-bootstrap-table2-editor'; +const columns = [ + ..., { + dataField: 'done', + text: 'Done', + editor: { + type: Type.SELECT | Type.TEXTAREA | Type.CHECKBOX | Type.DATE, + ... // The rest properties will be rendered into the editor's DOM element + } + } +] +``` + +In the following, we go though all the predefined editors: + +### Dropdown Editor +Dropdown editor give a select menu to choose a data from a list, the `editor.options` is required property for dropdown editor. + +```js +import { Type } from 'react-bootstrap-table2-editor'; +const columns = [ + ..., { + dataField: 'type', + text: 'Job Type', + editor: { + type: Type.SELECT, + options: [{ + value: 'A', + label: 'A' + }, { + value: 'B', + label: 'B' + }, { + value: 'C', + label: 'C' + }, { + value: 'D', + label: 'D' + }, { + value: 'E', + label: 'E' + }] + } +}]; +``` + +### Date Editor +Date editor is use ``, the configuration is very simple: + +```js +const columns = [ + ..., { + dataField: 'inStockDate', + text: 'Stock Date', + formatter: (cell) => { + let dateObj = cell; + if (typeof cell !== 'object') { + dateObj = new Date(cell); + } + return `${('0' + dateObj.getDate()).slice(-2)}/${('0' + (dateObj.getMonth() + 1)).slice(-2)}/${dateObj.getFullYear()}`; + }, + editor: { + type: Type.DATE + } +}]; +``` + +### Textarea Editor +Textarea editor is use ``, user can press `ENTER` to change line and in the `react-bootstrap-table2`, user allow to save result via press `SHIFT` + `ENTER`. + +```js +const columns = [ + ..., { + dataField: 'comment', + text: 'Product Comments', + editor: { + type: Type.TEXTAREA + } +}]; +``` +### Checkbox Editor +Checkbox editor allow you to have a pair value choice, the `editor.value` is required value to represent the actual value for check and uncheck. + +```js +const columns = [ + ..., { + dataField: 'comment', + text: 'Product Comments', + editor: { + type: Type.CHECKBOX, + value: 'Y:N' + } +}]; +``` + +## Customize Editor +If you feel above predefined editors are not satisfied to your requirement, you can certainly custom the editor via [column.editorRenderer](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/column-props.html#columneditorrenderer-function). It accept a function and pass following arguments when function called: + +* `editorProps`: Some useful attributes you can use on DOM editor, like class, style etc. +* `value`: Current cell value +* `row`: Current row data +* `column`: Current column definition +* `rowIndex`: Current row index +* `columnIndex`: Current column index + +> Note when implement a custom React editor component, this component should have a **getValue** function which return current value on editor + +> Note when you want to save value, you can call **editorProps.onUpdate** function + +Following is a short example: + +```js +class QualityRanger extends React.Component { + static propTypes = { + value: PropTypes.number, + onUpdate: PropTypes.func.isRequired + } + static defaultProps = { + value: 0 + } + getValue() { + return parseInt(this.range.value, 10); + } + render() { + const { value, onUpdate, ...rest } = this.props; + return [ + this.range = node } + type="range" + min="0" + max="100" + />, + + ]; + } +} + +const columns = [ + ..., { + dataField: 'quality', + text: 'Product Quality', + editorRenderer: (editorProps, value, row, column, rowIndex, columnIndex) => ( + + ) +}]; +``` diff --git a/packages/react-bootstrap-table2-editor/index.js b/packages/react-bootstrap-table2-editor/index.js index df715d7..3d07c2c 100644 --- a/packages/react-bootstrap-table2-editor/index.js +++ b/packages/react-bootstrap-table2-editor/index.js @@ -1,6 +1,7 @@ import wrapperFactory from './src/wrapper'; import editingCellFactory from './src/editing-cell'; import { + EDITTYPE, CLICK_TO_CELL_EDIT, DBCLICK_TO_CELL_EDIT, DELAY_FOR_DBCLICK @@ -14,3 +15,5 @@ export default (options = {}) => ({ DELAY_FOR_DBCLICK, options }); + +export const Type = EDITTYPE; diff --git a/packages/react-bootstrap-table2-editor/src/checkbox-editor.js b/packages/react-bootstrap-table2-editor/src/checkbox-editor.js new file mode 100644 index 0000000..561f25c --- /dev/null +++ b/packages/react-bootstrap-table2-editor/src/checkbox-editor.js @@ -0,0 +1,61 @@ +/* eslint no-return-assign: 0 */ +import React, { Component } from 'react'; +import cs from 'classnames'; +import PropTypes from 'prop-types'; + +class CheckBoxEditor extends Component { + constructor(props) { + super(props); + this.state = { + checked: props.defaultValue.toString() === props.value.split(':')[0] + }; + this.handleChange = this.handleChange.bind(this); + } + + componentDidMount() { + this.checkbox.focus(); + } + + getValue() { + const [positive, negative] = this.props.value.split(':'); + return this.checkbox.checked ? positive : negative; + } + + handleChange(e) { + if (this.props.onChange) this.props.onChange(e); + const { target } = e; + this.setState(() => ({ checked: target.checked })); + } + + render() { + const { defaultValue, className, ...rest } = this.props; + const editorClass = cs('editor edit-chseckbox checkbox', className); + return ( + this.checkbox = node } + type="checkbox" + className={ editorClass } + { ...rest } + checked={ this.state.checked } + onChange={ this.handleChange } + /> + ); + } +} + +CheckBoxEditor.propTypes = { + className: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.object + ]), + value: PropTypes.string, + defaultValue: PropTypes.any, + onChange: PropTypes.func +}; +CheckBoxEditor.defaultProps = { + className: '', + value: 'on:off', + defaultValue: false, + onChange: undefined +}; +export default CheckBoxEditor; diff --git a/packages/react-bootstrap-table2-editor/src/const.js b/packages/react-bootstrap-table2-editor/src/const.js index acbef9d..4455ad7 100644 --- a/packages/react-bootstrap-table2-editor/src/const.js +++ b/packages/react-bootstrap-table2-editor/src/const.js @@ -2,3 +2,11 @@ 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'; + +export const EDITTYPE = { + TEXT: 'text', + SELECT: 'select', + TEXTAREA: 'textarea', + CHECKBOX: 'checkbox', + DATE: 'date' +}; diff --git a/packages/react-bootstrap-table2-editor/src/date-editor.js b/packages/react-bootstrap-table2-editor/src/date-editor.js new file mode 100644 index 0000000..b712fe2 --- /dev/null +++ b/packages/react-bootstrap-table2-editor/src/date-editor.js @@ -0,0 +1,42 @@ +/* eslint no-return-assign: 0 */ +import React, { Component } from 'react'; +import cs from 'classnames'; +import PropTypes from 'prop-types'; + +class DateEditor extends Component { + componentDidMount() { + const { defaultValue } = this.props; + this.date.valueAsDate = new Date(defaultValue); + this.date.focus(); + } + + getValue() { + return this.date.value; + } + + render() { + const { defaultValue, className, ...rest } = this.props; + const editorClass = cs('form-control editor edit-date', className); + return ( + this.date = node } + type="date" + className={ editorClass } + { ...rest } + /> + ); + } +} + +DateEditor.propTypes = { + className: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.object + ]), + defaultValue: PropTypes.string +}; +DateEditor.defaultProps = { + className: '', + defaultValue: '' +}; +export default DateEditor; diff --git a/packages/react-bootstrap-table2-editor/src/dropdown-editor.js b/packages/react-bootstrap-table2-editor/src/dropdown-editor.js new file mode 100644 index 0000000..cf6a2a6 --- /dev/null +++ b/packages/react-bootstrap-table2-editor/src/dropdown-editor.js @@ -0,0 +1,61 @@ +/* eslint no-return-assign: 0 */ +import React, { Component } from 'react'; +import cs from 'classnames'; +import PropTypes from 'prop-types'; + +class DropDownEditor extends Component { + componentDidMount() { + const { defaultValue } = this.props; + this.select.value = defaultValue; + this.select.focus(); + } + + getValue() { + return this.select.value; + } + + render() { + const { defaultValue, className, options, ...rest } = this.props; + const editorClass = cs('form-control editor edit-select', className); + + const attr = { + ...rest, + className: editorClass + }; + + return ( + + ); + } +} + +DropDownEditor.propTypes = { + defaultValue: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.number + ]), + className: PropTypes.string, + style: PropTypes.object, + options: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.shape({ + label: PropTypes.string, + value: PropTypes.any + })) + ]).isRequired +}; +DropDownEditor.defaultProps = { + className: '', + defaultValue: '', + style: {} +}; +export default DropDownEditor; diff --git a/packages/react-bootstrap-table2-editor/src/editing-cell.js b/packages/react-bootstrap-table2-editor/src/editing-cell.js index 2c732de..2acda72 100644 --- a/packages/react-bootstrap-table2-editor/src/editing-cell.js +++ b/packages/react-bootstrap-table2-editor/src/editing-cell.js @@ -6,9 +6,13 @@ import React, { Component } from 'react'; import cs from 'classnames'; import PropTypes from 'prop-types'; +import DropdownEditor from './dropdown-editor'; +import TextAreaEditor from './textarea-editor'; +import CheckBoxEditor from './checkbox-editor'; +import DateEditor from './date-editor'; import TextEditor from './text-editor'; import EditorIndicator from './editor-indicator'; -import { TIME_TO_CLOSE_MESSAGE } from './const'; +import { TIME_TO_CLOSE_MESSAGE, EDITTYPE } from './const'; export default _ => class EditingCell extends Component { @@ -73,8 +77,8 @@ export default _ => }, timeToCloseMessage); } - beforeComplete(row, column, newValue) { - const { onUpdate } = this.props; + beforeComplete(newValue) { + const { onUpdate, row, column } = this.props; if (_.isFunction(column.validator)) { const validateForm = column.validator(newValue, row, column); if (_.isObject(validateForm) && !validateForm.valid) { @@ -89,28 +93,20 @@ export default _ => } handleBlur() { - const { onEscape, blurToSave, row, column } = this.props; + const { onEscape, blurToSave } = 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); + this.beforeComplete(this.editor.getValue()); } else { onEscape(); } } handleKeyDown(e) { - const { onEscape, row, column } = this.props; + const { onEscape } = 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); + this.beforeComplete(this.editor.getValue()); } } @@ -124,17 +120,13 @@ export default _ => } render() { - const { invalidMessage } = this.state; + let editor; const { row, column, className, style, rowIndex, columnIndex } = this.props; const { dataField } = column; const value = _.get(row, dataField); - const editorAttrs = { - onKeyDown: this.handleKeyDown, - onBlur: this.handleBlur - }; + const hasError = _.isDefined(this.state.invalidMessage); - const hasError = _.isDefined(invalidMessage); let customEditorClass = column.editorClasses || ''; if (_.isFunction(column.editorClasses)) { customEditorClass = column.editorClasses(value, row, rowIndex, columnIndex); @@ -150,20 +142,51 @@ export default _ => shake: hasError }, customEditorClass); + let editorProps = { + ref: node => this.editor = node, + defaultValue: value, + style: editorStyle, + className: editorClass, + onKeyDown: this.handleKeyDown, + onBlur: this.handleBlur + }; + + const isDefaultEditorDefined = _.isObject(column.editor); + + if (isDefaultEditorDefined) { + editorProps = { + ...editorProps, + ...column.editor + }; + } else if (_.isFunction(column.editorRenderer)) { + editorProps = { + ...editorProps, + onUpdate: this.beforeComplete + }; + } + + if (_.isFunction(column.editorRenderer)) { + editor = column.editorRenderer(editorProps, value, row, column, rowIndex, columnIndex); + } else if (isDefaultEditorDefined && column.editor.type === EDITTYPE.SELECT) { + editor = ; + } else if (isDefaultEditorDefined && column.editor.type === EDITTYPE.TEXTAREA) { + editor = ; + } else if (isDefaultEditorDefined && column.editor.type === EDITTYPE.CHECKBOX) { + editor = ; + } else if (isDefaultEditorDefined && column.editor.type === EDITTYPE.DATE) { + editor = ; + } else { + editor = ; + } + return ( - this.editor = node } - defaultValue={ value } - style={ editorStyle } - className={ editorClass } - { ...editorAttrs } - /> - { hasError ? : null } + { editor } + { hasError ? : null } ); } diff --git a/packages/react-bootstrap-table2-editor/src/text-editor.js b/packages/react-bootstrap-table2-editor/src/text-editor.js index 7937282..b44c2da 100644 --- a/packages/react-bootstrap-table2-editor/src/text-editor.js +++ b/packages/react-bootstrap-table2-editor/src/text-editor.js @@ -10,6 +10,10 @@ class TextEditor extends Component { this.text.focus(); } + getValue() { + return this.text.value; + } + render() { const { defaultValue, className, ...rest } = this.props; const editorClass = cs('form-control editor edit-text', className); diff --git a/packages/react-bootstrap-table2-editor/src/textarea-editor.js b/packages/react-bootstrap-table2-editor/src/textarea-editor.js new file mode 100644 index 0000000..e65b27e --- /dev/null +++ b/packages/react-bootstrap-table2-editor/src/textarea-editor.js @@ -0,0 +1,60 @@ +/* eslint no-return-assign: 0 */ +import React, { Component } from 'react'; +import cs from 'classnames'; +import PropTypes from 'prop-types'; + +class TextAreaEditor extends Component { + constructor(props) { + super(props); + this.handleKeyDown = this.handleKeyDown.bind(this); + } + + componentDidMount() { + const { defaultValue } = this.props; + this.text.value = defaultValue; + this.text.focus(); + } + + getValue() { + return this.text.value; + } + + handleKeyDown(e) { + if (e.keyCode === 13 && !e.shiftKey) return; + if (this.props.onKeyDown) { + this.props.onKeyDown(e); + } + } + + render() { + const { defaultValue, className, ...rest } = this.props; + const editorClass = cs('form-control editor edit-textarea', className); + return ( +