UNPKG

react-dynamic-forms

Version:

Dynamic forms library for React

289 lines (252 loc) 9.22 kB
/** * 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);