react-x-editable
Version:
React X editable using react-bootstrap.
271 lines (253 loc) • 8.48 kB
JSX
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
import _ from 'lodash';
import {
Button,
Overlay,
Popover,
FormGroup,
ControlLabel,
FormControl,
HelpBlock
} from 'react-bootstrap';
import Text from './Text';
import Textarea from './Textarea';
import Select from './Select';
import Checklist from './Checklist';
import Date from './Date';
//import Radio from './Radio';
export default class Editable extends Component {
constructor(props){
super(props);
this.state = {
dataType : props.dataType ? props.dataType : "text",
name : props.name,
mode : props.mode ? props.mode : "inline",
disabled : props.disabled ? props.disabled : false,
showButtons : props.showButtons != undefined ? props.showButtons : true,
validate : props.validate ? props.validate : undefined,
display : props.display ? props.display : undefined,
// only used when mode is popup
title : props.title ? props.title : null,
placement : props.placement ? props.placement : "right",
//Input
bsInputClass :props.bsInputClass ? props.bsInputClass : "",
bsInputSize :props.bsInputSize ? props.bsInputSize : "sm",
//Select & checklist
options : props.options ? props.options : null,
//checklist
optionsInline : props.inline ? props.inline : false,
//Required for customize input
customComponent :props.customComponent ? props.customComponent : null,
onInputChange : props.onInputChange ? props.onInputChange : null,
//handle callback if provided
handleSubmit : props.handleSubmit ? props.handleSubmit : null,
//for internal use
editable: false,
valueUpdated : false,
};
this.setInitialValue(this.props);
}
componentWillReceiveProps(nextProps){
if(nextProps.value != this.value){
this.setInitialValue(nextProps);
}
}
setInitialValue = (newProps) => {
const { dataType, options, value } = newProps;
if(dataType == "select" || dataType == "checklist"){
if( options == null ) {
throw("Please specify options for "+dataType+" data type");
}
if(value && _.isEmpty(value)){
const option = _.find(options, {'value' : value});
if(option && option.text) {
this.value = option.text;
this.newValue = option.text;;
}else {
throw("No option found for specified value:"+ value)
}
}else{
this.value = value;
this.newValue = value;
}
}else {
this.value = value;
this.newValue = value;
}
this.validation = {};
}
setEditable = (editable) => {
if(!this.state.disabled) this.setState({editable});
}
onSubmit = () => {
this.validation = this.getValidationState();
if(this.validation.type === "error"){
this.setState({ valueUpdated : false});
}else {
this.value = this.newValue;
this.setEditable(false);
this.setState({ valueUpdated : true}, this.state.handleSubmit ? () => this.state.handleSubmit(this) : null);
}
}
onCancel = () => {
this.setEditable(false);
//reset validation
this.validation = {};
}
setValueToAnchor(value, event){
this.newValue = value;
//To trigger onInputChange event:user defined
if(this.props.onInputChange){
this.props.onInputChange(event);
}
}
getValueForAnchor(){
if(this.value){
if(this.props.display){
return this.props.display(this.value);
} else if(this.props.seperator && _.isArray(this.value)){
return _.join(this.value, this.props.seperator);
}else if(_.isArray(this.value)){
return _.join(this.value, ',');
}else if(_.isObject(this.value)){
let tmp = '';
_.forOwn(this.value, function(value, key) {
tmp += key +":"+ value +" ";
} );
return tmp;
}
}
return this.value;
}
getValidationState(){
if(this.props.validate){
const validate = this.props.validate(this.newValue)
if(validate){
return {type : 'error', msg : validate };
}
}
return {type : undefined, msg : undefined };
}
getButtons(){
if(this.state.showButtons){
return (
<div className="editable-btn" key={this.props.name+"editable-btn"}>
<Button bsStyle="success" bsSize="xsmall" onClick={this.onSubmit.bind(this)} key={"btn-success"+this.props.name}>
<i className="fa fa-check" key={"icon-fa-check"+this.props.name}></i></Button>
<Button bsStyle="danger" bsSize="xsmall" onClick={this.onCancel.bind(this)} key={"btn-danger"+this.props.name}>
<i className="fa fa-times" key={"icon-fa-times"+this.props.name}></i>
</Button>
</div>
)
}
return null;
}
getContent(){
const { editable, title, validate, showButtons,
defaultValue, dataType, placement, mode, name
} = this.state;
const componetProps = {
key: "editable-name-"+this.state.name,
setValueToAnchor: this.setValueToAnchor.bind(this),
value: this.value || defaultValue ,
onSubmit: this.onSubmit.bind(this),
setEditable: this.setEditable.bind(this),
validation: this.validation
};
const content = [];
if (editable) {
switch (dataType) {
case 'text':
content.push(<Text {...this.props} {...componetProps} {...this.state} />);
break;
case 'textarea':
content.push(<Textarea {...this.props} {...componetProps} {...this.state} />);
break;
case 'select':
content.push(<Select {...this.props} {...componetProps} {...this.state} />);
break;
case 'checklist':
content.push(<Checklist {...componetProps} {...this.state} />);
break;
case 'date':
content.push(<Date {...componetProps} {...this.state} />);
break;
// case 'radio':
// content.push(<Radio {...componetProps} {...this.state} />);
// break;
case 'custom':
const customComponentContent = this.state.customComponent(componetProps, this.state)
content.push(customComponentContent);
break;
default: throw('Please set valid dataType:'+dataType)
}
content.push(this.getButtons());
if(mode == 'popup'){
return (
<div>
<Overlay
rootClose={true}
onHide={() => { this.setEditable(false) } }
show={editable}
target={() => ReactDOM.findDOMNode(this.editableAnchor)}
{...this.props}>
<Popover id={"popover-positioned-"+placement} title={title} key={this.props.name}>
{content}
</Popover>
</Overlay>
</div>
);
}
return content;
}
}
render() {
const { editable, title, validate, showButtons,
defaultValue, dataType, mode, disabled } = this.state;
const editableContainerClass = (disabled) ? "editable-disabled" : "editable-container";
return (
<div className={editableContainerClass} key={this.props.name} >
{ !(mode == 'inline' && editable)
? (<a ref={ref => this.editableAnchor = ref}
onClick={this.setEditable.bind(this, true)}
href="javascript:;"
>
{ this.getValueForAnchor() || this.props.emptyValueText }
</a>
)
: null
}
{this.getContent()}
</div>
);
}
}
Editable.defaultProps = {
showButtons : true,
dataType : "text",
mode : "inline",
disabled : false,
emptyValueText : "empty",
//depend on mode
placement : "right",
};
Editable.propTypes = {
dataType : PropTypes.string.isRequired,
name : PropTypes.string.isRequired,
mode : PropTypes.string,
showButtons : PropTypes.bool,
disabled : PropTypes.bool,
validate : PropTypes.func,
display: PropTypes.func,
onInputChange : PropTypes.func,
//handle callback if provided
handleSubmit : PropTypes.func,
// only used when mode is popup
title : PropTypes.string,
placement : PropTypes.string,
// for input type text
bsInputClass :PropTypes.string,
bsInputSize :PropTypes.string,
};