@coocoon/react-awesome-query-builder
Version:
User-friendly query builder for React. Demo: https://ukrbublik.github.io/react-awesome-query-builder
328 lines (289 loc) • 11 kB
JSX
import React, { PureComponent } from "react";
import PropTypes from "prop-types";
import RuleContainer from "../containers/RuleContainer";
import Draggable from "../containers/Draggable";
import OperatorWrapper from "../rule/OperatorWrapper";
import FieldWrapper from "../rule/FieldWrapper";
import Widget from "../rule/Widget";
import OperatorOptions from "../rule/OperatorOptions";
import {getFieldConfig, getOperatorConfig, getFieldWidgetConfig} from "../../utils/configUtils";
import {getFieldPathLabels, getValueSourcesForFieldOp} from "../../utils/ruleUtils";
import {useOnPropsChanged} from "../../utils/reactUtils";
import {Col, DragIcon, dummyFn, ConfirmFn} from "../utils";
const classNames = require("classnames");
class Rule extends PureComponent {
static propTypes = {
id: PropTypes.string.isRequired,
groupId: PropTypes.string,
selectedField: PropTypes.string,
selectedOperator: PropTypes.string,
operatorOptions: PropTypes.object,
config: PropTypes.object.isRequired,
value: PropTypes.any, //depends on widget
valueSrc: PropTypes.any,
asyncListValues: PropTypes.array,
isDraggingMe: PropTypes.bool,
isDraggingTempo: PropTypes.bool,
parentField: PropTypes.string, //from RuleGroup
valueError: PropTypes.any,
isLocked: PropTypes.bool,
isTrueLocked: PropTypes.bool,
//path: PropTypes.instanceOf(Immutable.List),
//actions
handleDraggerMouseDown: PropTypes.func,
setField: PropTypes.func,
setOperator: PropTypes.func,
setOperatorOption: PropTypes.func,
setLock: PropTypes.func,
removeSelf: PropTypes.func,
setValue: PropTypes.func,
setValueSrc: PropTypes.func,
reordableNodesCnt: PropTypes.number,
isFunc: PropTypes.bool,
args: PropTypes.array,
};
constructor(props) {
super(props);
useOnPropsChanged(this);
this.removeSelf = this.removeSelf.bind(this);
this.setLock = this.setLock.bind(this);
this.onPropsChanged(props);
}
onPropsChanged(nextProps) {
const prevProps = this.props;
const keysForMeta = ["selectedField", "selectedOperator", "config", "reordableNodesCnt", "isLocked"];
const needUpdateMeta = !this.meta || keysForMeta.map(k => (nextProps[k] !== prevProps[k])).filter(ch => ch).length > 0;
if (needUpdateMeta) {
this.meta = this.getMeta(nextProps);
}
}
getMeta({selectedField, selectedOperator, config, reordableNodesCnt, isLocked}) {
const selectedFieldPartsLabels = getFieldPathLabels(selectedField, config);
const selectedFieldConfig = getFieldConfig(config, selectedField);
const isSelectedGroup = selectedFieldConfig && selectedFieldConfig.type == "!struct";
const isFieldAndOpSelected = selectedField && selectedOperator && !isSelectedGroup;
const selectedOperatorConfig = getOperatorConfig(config, selectedOperator, selectedField);
const selectedOperatorHasOptions = selectedOperatorConfig && selectedOperatorConfig.options != null;
const selectedFieldWidgetConfig = getFieldWidgetConfig(config, selectedField, selectedOperator) || {};
const hideOperator = selectedFieldWidgetConfig.hideOperator;
const showDragIcon = config.settings.canReorder && reordableNodesCnt > 1 && !isLocked;
const showOperator = selectedField && !hideOperator;
const showOperatorLabel = selectedField && hideOperator && selectedFieldWidgetConfig.operatorInlineLabel;
const showWidget = isFieldAndOpSelected;
const showOperatorOptions = isFieldAndOpSelected && selectedOperatorHasOptions;
return {
selectedFieldPartsLabels, selectedFieldWidgetConfig,
showDragIcon, showOperator, showOperatorLabel, showWidget, showOperatorOptions
};
}
setLock(lock) {
this.props.setLock(lock);
}
removeSelf() {
const {confirmFn} = this.props;
const {renderConfirm, removeRuleConfirmOptions: confirmOptions} = this.props.config.settings;
const doRemove = () => {
this.props.removeSelf();
};
if (confirmOptions && !this.isEmptyCurrentRule()) {
renderConfirm({...confirmOptions,
onOk: doRemove,
onCancel: null,
confirmFn: confirmFn
});
} else {
doRemove();
}
}
isEmptyCurrentRule() {
return !(
this.props.selectedField !== null
&& this.props.selectedOperator !== null
&& this.props.value.filter((val) => val !== undefined).size > 0
);
}
renderField() {
const {config, isLocked} = this.props;
const { immutableFieldsMode } = config.settings;
return <FieldWrapper
key="field"
classname={"rule--field"}
config={config}
selectedField={this.props.selectedField}
setField={!immutableFieldsMode ? this.props.setField : dummyFn}
parentField={this.props.parentField}
readonly={immutableFieldsMode || isLocked}
id={this.props.id}
groupId={this.props.groupId}
isFunc={this.props.isFunc}
args={this.props.args}
/>;
}
renderOperator () {
const {config, isLocked} = this.props;
const {
selectedFieldPartsLabels, selectedFieldWidgetConfig, showOperator, showOperatorLabel
} = this.meta;
const { immutableOpsMode } = config.settings;
return <OperatorWrapper
key="operator"
config={config}
selectedField={this.props.selectedField}
selectedOperator={this.props.selectedOperator}
setOperator={!immutableOpsMode ? this.props.setOperator : dummyFn}
selectedFieldPartsLabels={selectedFieldPartsLabels}
showOperator={showOperator}
showOperatorLabel={showOperatorLabel}
selectedFieldWidgetConfig={selectedFieldWidgetConfig}
readonly={immutableOpsMode || isLocked}
id={this.props.id}
groupId={this.props.groupId}
isFunc={this.props.isFunc}
/>;
}
renderWidget() {
const {config, valueError, isLocked} = this.props;
const { showWidget } = this.meta;
const { immutableValuesMode } = config.settings;
if (!showWidget) return null;
const widget = <Widget
key="values"
field={this.props.selectedField}
parentField={this.props.parentField}
operator={this.props.selectedOperator}
value={this.props.value}
valueSrc={this.props.valueSrc}
asyncListValues={this.props.asyncListValues}
valueError={valueError}
config={config}
setValue={!immutableValuesMode ? this.props.setValue : dummyFn}
setValueSrc={!immutableValuesMode ? this.props.setValueSrc : dummyFn}
readonly={immutableValuesMode || isLocked}
id={this.props.id}
groupId={this.props.groupId}
isFunc={this.props.isFunc}
/>;
return (
<Col key={"widget-for-"+this.props.selectedOperator} className="rule--value">
{widget}
</Col>
);
}
renderOperatorOptions() {
const {config} = this.props;
const { showOperatorOptions } = this.meta;
const { immutableOpsMode, immutableValuesMode } = config.settings;
if (!showOperatorOptions) return null;
const opOpts = <OperatorOptions
key="operatorOptions"
selectedField={this.props.selectedField}
selectedOperator={this.props.selectedOperator}
operatorOptions={this.props.operatorOptions}
setOperatorOption={!immutableOpsMode ? this.props.setOperatorOption : dummyFn}
config={config}
readonly={immutableValuesMode}
/>;
return (
<Col key={"op-options-for-"+this.props.selectedOperator} className="rule--operator-options">
{opOpts}
</Col>
);
}
renderBeforeWidget() {
const {config} = this.props;
const { renderBeforeWidget } = config.settings;
return renderBeforeWidget
&& <Col key={"before-widget-for-" +this.props.selectedOperator} className="rule--before-widget">
{typeof renderBeforeWidget === "function" ? renderBeforeWidget(this.props) : renderBeforeWidget}
</Col>;
}
renderAfterWidget() {
const {config} = this.props;
const { renderAfterWidget } = config.settings;
return renderAfterWidget
&& <Col key={"after-widget-for-" +this.props.selectedOperator} className="rule--after-widget">
{typeof renderAfterWidget === "function" ? renderAfterWidget(this.props) : renderAfterWidget}
</Col>;
}
renderError() {
const {config, valueError} = this.props;
const { renderRuleError, showErrorMessage } = config.settings;
const oneValueError = valueError && valueError.toArray().filter(e => !!e).shift() || null;
return showErrorMessage && oneValueError
&& <div className="rule--error">
{renderRuleError ? renderRuleError({error: oneValueError}) : oneValueError}
</div>;
}
renderDrag() {
const { showDragIcon } = this.meta;
return showDragIcon
&& <span
key="rule-drag-icon"
className={"qb-drag-handler rule--drag-handler"}
onMouseDown={this.props.handleDraggerMouseDown}
><DragIcon /> </span>;
}
renderDel() {
const {config, isLocked} = this.props;
const {
deleteLabel,
immutableGroupsMode,
renderButton: Btn,
canDeleteLocked
} = config.settings;
return !immutableGroupsMode && (!isLocked || isLocked && canDeleteLocked) && (
<Btn
type="delRule" onClick={this.removeSelf} label={deleteLabel} config={config}
/>
);
}
renderLock() {
const {config, isLocked, isTrueLocked, id} = this.props;
const {
lockLabel, lockedLabel, showLock,
renderSwitch: Switch
} = config.settings;
return showLock && !(isLocked && !isTrueLocked) && (
<Switch
type="lock" id={id} value={isLocked} setValue={this.setLock} label={lockLabel} checkedLabel={lockedLabel} hideLabel={true} config={config}
/>
);
}
render () {
const { showOperatorOptions, selectedFieldWidgetConfig } = this.meta;
const { valueSrc, value, config } = this.props;
const canShrinkValue = valueSrc.first() == "value" && !showOperatorOptions && value.size == 1 && selectedFieldWidgetConfig.fullWidth;
const { renderButtonGroup: BtnGrp } = config.settings;
const parts = [
this.renderField(),
this.renderOperator(),
this.renderBeforeWidget(),
this.renderWidget(),
this.renderAfterWidget(),
this.renderOperatorOptions(),
];
const body = <div key="rule-body" className={classNames("rule--body", canShrinkValue && "can--shrink--value")}>{parts}</div>;
const error = this.renderError();
const drag = this.renderDrag();
const lock = this.renderLock();
const del = this.renderDel();
return (
<>
{drag}
<div className="rule--body--wrapper">
{body}{error}
</div>
<div className="rule--header">
<BtnGrp config={config}>
{lock}
{del}
</BtnGrp>
</div>
</>
);
}
}
export default Rule;