UNPKG

cspace-ui

Version:
436 lines (367 loc) 10.3 kB
import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { defineMessages, FormattedMessage } from 'react-intl'; import Immutable from 'immutable'; import { baseComponents as inputComponents } from 'cspace-input'; import OptionPickerInput from '../../record/OptionPickerInput'; import RemoveConditionButton from '../RemoveConditionButton'; import { OP_AND, OP_OR, OP_GROUP, } from '../../../constants/searchOperators'; import styles from '../../../../styles/cspace-ui/BooleanConditionInput.css'; const propTypes = { condition: PropTypes.instanceOf(Immutable.Map), config: PropTypes.shape({ recordTypes: PropTypes.object, }), hasChildGroups: PropTypes.bool, inline: PropTypes.bool, name: PropTypes.string, readOnly: PropTypes.bool, recordType: PropTypes.string, rootPath: PropTypes.string, showInlineParens: PropTypes.bool, showRemoveButton: PropTypes.bool, getSearchConditionInputComponent: PropTypes.func.isRequired, onCommit: PropTypes.func, onRemove: PropTypes.func, }; const defaultProps = { showInlineParens: true, showRemoveButton: true, }; const { MiniButton, } = inputComponents; const messages = { [OP_AND]: defineMessages({ label: { id: 'booleanConditionInput.and.label', defaultMessage: 'and', }, opSelectorLabel: { id: 'booleanConditionInput.and.opSelectorLabel', defaultMessage: 'All', }, }), [OP_OR]: defineMessages({ label: { id: 'booleanConditionInput.or.label', defaultMessage: 'or', }, opSelectorLabel: { id: 'booleanConditionInput.or.opSelectorLabel', defaultMessage: 'Any', }, }), opSelector: defineMessages({ label: { id: 'booleanConditionInput.opSelector.label', defaultMessage: `{opSelectorInput} of the following conditions { operator, select, and {must} or {may} } be satisfied:`, }, }), addBoolean: defineMessages({ label: { id: 'booleanConditionInput.addBoolean.label', description: 'Label of the button to add a new boolean constraint to a boolean search', defaultMessage: '+ Any/All', }, }), addField: defineMessages({ label: { id: 'booleanConditionInput.addField.label', description: 'Label of the button to add a new field constraint to a boolean search', defaultMessage: '+ Field', }, }), addGroup: defineMessages({ label: { id: 'booleanConditionInput.addGroup.label', description: 'Label of the button to add a new group constraint to a boolean search', defaultMessage: '+ Group', }, }), }; export default class BooleanConditionInput extends Component { constructor() { super(); this.handleAddBooleanButtonClick = this.handleAddBooleanButtonClick.bind(this); this.handleAddFieldButtonClick = this.handleAddFieldButtonClick.bind(this); this.handleAddGroupButtonClick = this.handleAddGroupButtonClick.bind(this); this.handleChildConditionCommit = this.handleChildConditionCommit.bind(this); this.handleChildConditionRemove = this.handleChildConditionRemove.bind(this); this.handleOpSelectorCommit = this.handleOpSelectorCommit.bind(this); this.handleRef = this.handleRef.bind(this); this.handleRemoveButtonClick = this.handleRemoveButtonClick.bind(this); } componentDidMount() { const { condition, } = this.props; const value = condition.get('value'); if (value === null) { // The condition was just added, and the operator needs to be selected. Focus it. if (this.domNode) { const input = this.domNode.querySelector('input[data-name="booleanSearchOp"]'); if (input) { input.focus(); } } } } handleAddBooleanButtonClick() { const { condition, name, onCommit, } = this.props; if (onCommit) { const op = condition.get('op'); const value = condition.get('value') || Immutable.List(); // For the new nested boolean, set the operator to the opposite of the current operator, // since this is likely what the user wants. const nestedOp = (op === OP_AND ? OP_OR : OP_AND); const newCondition = Immutable.Map({ op: nestedOp, path: null, value: null, }); onCommit(name, condition.set('value', value.push(newCondition))); } } handleAddFieldButtonClick() { const { condition, name, onCommit, } = this.props; if (onCommit) { const value = condition.get('value') || Immutable.List(); onCommit(name, condition.set('value', value.push(Immutable.Map({ path: null })))); } } handleAddGroupButtonClick() { const { condition, name, onCommit, } = this.props; if (onCommit) { const value = condition.get('value') || Immutable.List(); onCommit(name, condition.set('value', value.push(Immutable.Map({ op: OP_GROUP, path: null, })))); } } handleOpSelectorCommit(path, value) { const { condition, name, onCommit, } = this.props; if (onCommit) { onCommit(name, condition.set('op', value)); } } handleChildConditionCommit(childName, childCondition) { const { condition, name, onCommit, } = this.props; if (onCommit) { const index = parseInt(childName, 10); onCommit(name, condition.setIn(['value', index], childCondition)); } } handleChildConditionRemove(childName) { const { condition, name, onCommit, } = this.props; if (onCommit) { const index = parseInt(childName, 10); onCommit(name, condition.deleteIn(['value', index])); } } handleRef(ref) { this.domNode = ref; } handleRemoveButtonClick() { const { name, onRemove, } = this.props; if (onRemove) { onRemove(name); } } renderRemoveButton() { const { readOnly, showRemoveButton, } = this.props; if (readOnly || !showRemoveButton) { return null; } return ( <RemoveConditionButton onClick={this.handleRemoveButtonClick} /> ); } renderHeader() { const { condition, readOnly, } = this.props; if (readOnly) { return null; } const operator = condition.get('op'); const opSelectorInput = ( <OptionPickerInput blankable={false} name="booleanSearchOp" options={[ { value: OP_OR, message: messages[OP_OR].opSelectorLabel }, { value: OP_AND, message: messages[OP_AND].opSelectorLabel }, ]} value={operator} onCommit={this.handleOpSelectorCommit} /> ); return ( <header> <FormattedMessage {...messages.opSelector.label} tagName="div" values={{ opSelectorInput, operator }} /> {this.renderRemoveButton()} </header> ); } renderChildConditions() { const { condition, config, hasChildGroups, inline, readOnly, recordType, rootPath, getSearchConditionInputComponent, } = this.props; const operator = condition.get('op'); const childConditions = condition.get('value'); if (!childConditions || childConditions.size === 0) { return null; } const inputs = childConditions.map((childCondition, index) => { const SearchConditionInputComponent = getSearchConditionInputComponent(childCondition); const operatorLabel = (index > 0) ? <FormattedMessage {...messages[operator].label} /> : <span />; return ( // eslint-disable-next-line react/no-array-index-key <li key={index}> {operatorLabel} {inline ? ' ' : null} <SearchConditionInputComponent condition={childCondition} config={config} hasChildGroups={hasChildGroups} index={index} inline={inline} name={`${index}`} readOnly={readOnly} recordType={recordType} rootPath={rootPath} getSearchConditionInputComponent={getSearchConditionInputComponent} onCommit={this.handleChildConditionCommit} onRemove={this.handleChildConditionRemove} /> {inline ? ' ' : null} </li> ); }); return <ul>{inputs}</ul>; } renderFooter() { const { condition, hasChildGroups, readOnly, } = this.props; if (readOnly) { return null; } const operator = condition.get('op'); let addGroupButton; if (hasChildGroups) { addGroupButton = ( <MiniButton autoWidth name="addGroup" onClick={this.handleAddGroupButtonClick} > <FormattedMessage {...messages.addGroup.label} /> </MiniButton> ); } return ( <footer> <div> <FormattedMessage {...messages[operator].label} /> </div> <div> <MiniButton autoWidth name="addField" onClick={this.handleAddFieldButtonClick} > <FormattedMessage {...messages.addField.label} /> </MiniButton> {addGroupButton} <MiniButton autoWidth name="addBoolean" onClick={this.handleAddBooleanButtonClick} > <FormattedMessage {...messages.addBoolean.label} /> </MiniButton> </div> </footer> ); } render() { const { inline, showInlineParens, } = this.props; let openParen; let closeParen; if (inline && showInlineParens) { openParen = <div>(</div>; closeParen = <div>)</div>; } const className = inline ? styles.inline : styles.normal; return ( <div className={className} ref={this.handleRef}> {this.renderHeader()} {openParen} {this.renderChildConditions()} {closeParen} {this.renderFooter()} </div> ); } } BooleanConditionInput.propTypes = propTypes; BooleanConditionInput.defaultProps = defaultProps;