react-dynamic-forms
Version:
Dynamic forms library for React
289 lines (252 loc) • 9.22 kB
JavaScript
/**
* Copyright (c) 2015 - present, The Regents of the University of California,
* through Lawrence Berkeley National Laboratory (subject to receipt
* of any required approvals from the U.S. Dept. of Energy).
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
import React from "react";
import _ from "underscore";
import PropTypes from "prop-types";
import formGroup from "../js/formGroup";
import 'react-select/dist/react-select.css'
import 'react-virtualized/styles.css'
import 'react-virtualized-select/styles.css'
import VirtualizedSelect from "react-virtualized-select";
import "../css/chooser.css";
/**
* React Form control to select an item from a list.
*/
class Chooser extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
isEmpty(value) {
return _.isNull(value) || _.isUndefined(value) || value === "";
}
isMissing(value = this.props.value) {
return this.props.required && !this.props.disabled && this.isEmpty(value);
}
componentDidMount() {
const missing = this.props.required &&
!this.props.disabled &&
(_.isNull(this.props.value) ||
_.isUndefined(this.props.value) ||
this.props.value === "");
const missingCount = missing ? 1 : 0;
if (this.props.onMissingCountChange) {
this.props.onMissingCountChange(this.props.name, missingCount);
}
}
componentWillReceiveProps(nextProps) {
if (
this.props.value !== nextProps.value ||
(!this.props.value && nextProps.value) ||
(this.props.value && !nextProps.value)
) {
// The value might have been missing and is now set explicitly
// with a prop
const missing = this.props.required &&
!this.props.disabled &&
(_.isNull(nextProps.value) ||
_.isUndefined(nextProps.value) ||
nextProps.value === "");
const missingCount = missing ? 1 : 0;
if (this.props.onMissingCountChange) {
this.props.onMissingCountChange(this.props.name, missingCount);
}
}
}
handleChange(v) {
let { value } = v || {};
const missing = this.props.required && this.isEmpty(v);
// If the chosen id is a number, cast it to a number
if (!this.isEmpty(v) && !_.isNaN(Number(v))) {
value = +v;
}
// Callbacks
if (this.props.onChange) {
this.props.onChange(this.props.name, value);
}
if (this.props.onMissingCountChange) {
this.props.onMissingCountChange(this.props.name, missing ? 1 : 0);
}
if (this.props.onBlur) {
this.props.onBlur(this.props.name);
}
}
getOptionList() {
return this.props.choiceList
.map(item => {
let disabled = false;
const isDisabled = item.has("disabled") && item.get("disabled") === true;
if (_.contains(this.props.disableList, item.get("id")) || isDisabled) {
disabled = true;
}
return { value: item.get("id"), label: item.get("label"), disabled };
})
.toJS();
}
getFilteredOptionList(input) {
const items = this.props.choiceList;
const filteredItems = input
? items.filter(item => {
return item.label.toLowerCase().indexOf(`${input}`.toLowerCase()) !== -1;
})
: items;
const result = [];
filteredItems.forEach(item =>
result.push({
value: `${item.get("id")}`,
label: item.get("label"),
disabled: item.has("disabled") ? item["disabled"] : false
}));
return result;
}
getOptions(input, cb) {
const options = this.getFilteredOptionList(input);
if (options) {
cb(null, { options, complete: true });
}
}
getCurrentChoice() {
const choiceItem = this.props.choiceList.find(item => {
return item.get("id") === this.props.value;
});
return choiceItem ? choiceItem.get("id") : undefined;
}
getCurrentChoiceLabel() {
const choiceItem = this.props.choiceList.find(item => {
return item.get("id") === this.props.value;
});
return choiceItem ? choiceItem.get("label") : "";
}
render() {
const choice = this.getCurrentChoice();
const isMissing = this.isMissing(this.props.value);
if (this.props.edit) {
let className = "";
const chooserStyle = { marginBottom: 10 };
const clearable = this.props.allowSingleDeselect;
const searchable = !this.props.disableSearch;
const matchPos = this.props.searchContains ? "any" : "start";
if (searchable) {
const options = this.getFilteredOptionList(null);
const labelList = _.map(options, item => item.label);
const key = `${labelList}--${choice}`;
return (
<div className={className} style={chooserStyle}>
<VirtualizedSelect
className={isMissing ? "is-missing" : ""}
key={key}
value={choice}
options={options}
disabled={this.props.disabled}
searchable={true}
matchPos={matchPos}
placeholder={this.props.placeholder}
onChange={v => this.handleChange(v)}
/>
</div>
);
} else {
const options = this.getOptionList();
const labelList = _.map(options, item => item.label);
const key = `${labelList}--${choice}`;
return (
<div className={className} style={chooserStyle}>
<VirtualizedSelect
className={isMissing ? "is-missing" : ""}
key={key}
value={choice}
options={options}
disabled={this.props.disabled}
searchable={false}
matchPos={matchPos}
placeholder={this.props.placeholder}
clearable={clearable}
onChange={v => this.handleChange(v)}
/>
</div>
);
}
} else {
const view = this.props.view;
let text = this.getCurrentChoiceLabel();
let color = "";
let background = "";
if (isMissing) {
text = " ";
background = "floralwhite";
}
const viewStyle = {
color,
background,
// minHeight: 23,
height: "100%",
width: "100%",
paddingLeft: 3
};
const style = {
color,
background,
// height: 23,
height: "100%",
width: "100%",
paddingLeft: 3
};
if (!view) {
return <div style={style}>{text}</div>;
} else {
return <div style={viewStyle}>{view(text, choice)}</div>;
}
}
}
}
Chooser.propTypes = {
/**
* choiceList* - Pass in the available list of options as a list of
* objects. For example:
* ```
* [
* {id: 1: label: "cat"},
* {id: 2: label: "dog"},
* ...
* ]
* ```
*/
choiceList: PropTypes.object,
disabled: PropTypes.bool,
/**
* disableSearch* - If true the chooser becomes a simple pulldown menu
* rather than allowing the user to type into it.
*/
disableSearch: PropTypes.bool,
/**
* width - Customize the horizontal size of the Chooser
*/
width: PropTypes.number,
/**
* field - The identifier of the field being edited
*/
field: PropTypes.string,
/**
* allowSingleDeselect - Add a [x] icon to the chooser allowing the user to clear the selected value
*/
allowSingleDeselect: PropTypes.bool,
/**
* searchContains - Can be "any" or "start", indicating how the search is matched within the items (anywhere, or starting with)
*/
searchContains: PropTypes.oneOf(["any", "start"])
};
Chooser.defaultProps = {
disabled: false,
disableSearch: false,
searchContains: "any",
allowSingleDeselect: false,
width: 300
};
export default formGroup(Chooser);