@react-awesome-query-builder-dev/ui
Version:
User-friendly query builder for React. Core React UI
402 lines (362 loc) • 13.6 kB
JSX
import React, { Component } from "react";
import { Utils } from "@react-awesome-query-builder-dev/core";
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 {useOnPropsChanged} from "../../utils/reactUtils";
import {Col, dummyFn, WithConfirmFn, getRenderFromConfig} from "../utils";
import classNames from "classnames";
const {getFieldConfig, getOperatorConfig, getFieldWidgetConfig, getFieldId} = Utils.ConfigUtils;
const {isEmptyRuleProperties} = Utils.RuleUtils;
class Rule extends Component {
static propTypes = {
id: PropTypes.string.isRequired,
groupId: PropTypes.string,
selectedField: PropTypes.any,
selectedFieldSrc: PropTypes.string,
selectedFieldType: PropTypes.string,
selectedOperator: PropTypes.string,
operatorOptions: PropTypes.object,
config: PropTypes.object.isRequired,
value: PropTypes.any, //depends on widget
valueSrc: PropTypes.any,
valueType: PropTypes.any,
asyncListValues: PropTypes.array,
isDraggingMe: PropTypes.bool,
isDraggingTempo: PropTypes.bool,
parentField: PropTypes.string, //from RuleGroup
valueError: PropTypes.any,
fieldError: PropTypes.string,
isLocked: PropTypes.bool,
isTrueLocked: PropTypes.bool,
//path: PropTypes.instanceOf(Immutable.List),
//actions
handleDraggerMouseDown: PropTypes.func,
setField: PropTypes.func,
setFieldSrc: PropTypes.func,
setOperator: PropTypes.func,
setOperatorOption: PropTypes.func,
setLock: PropTypes.func,
removeSelf: PropTypes.func,
setValue: PropTypes.func,
setValueSrc: PropTypes.func,
setFuncValue: PropTypes.func,
reordableNodesCnt: PropTypes.number,
totalRulesCnt: PropTypes.number,
parentReordableNodesCnt: PropTypes.number,
parentFieldCanReorder: PropTypes.bool,
};
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 configChanged = !this.Icon || prevProps?.config !== nextProps?.config;
const keysForMeta = ["selectedField", "selectedFieldSrc", "selectedFieldType", "selectedOperator", "config", "reordableNodesCnt", "isLocked", "parentField", "parentFieldCanReorder"];
const needUpdateMeta = !this.meta || keysForMeta.map(k => (nextProps[k] !== prevProps[k])).filter(ch => ch).length > 0;
if (needUpdateMeta) {
this.meta = this.getMeta(nextProps);
}
if (configChanged) {
const { config } = nextProps;
const {
renderIcon, renderButton, renderButtonGroup, renderSwitch, renderBeforeWidget, renderAfterWidget, renderRuleError,
} = config.settings;
this.Icon = getRenderFromConfig(config, renderIcon);
this.Btn = getRenderFromConfig(config, renderButton);
this.BtnGrp = getRenderFromConfig(config, renderButtonGroup);
this.Switch = getRenderFromConfig(config, renderSwitch);
this.BeforeWidget = getRenderFromConfig(config, renderBeforeWidget);
this.AfterWidget = getRenderFromConfig(config, renderAfterWidget);
this.RuleError = getRenderFromConfig(config, renderRuleError);
}
this.doRemove = () => {
this.props.removeSelf();
};
}
getMeta({selectedField, selectedFieldType, selectedOperator, config, reordableNodesCnt, isLocked, parentField, parentFieldCanReorder}) {
const {keepInputOnChangeFieldSrc} = config.settings;
const selectedFieldId = getFieldId(selectedField, config);
const selectedFieldConfig = getFieldConfig(config, selectedField);
const isSelectedGroup = selectedFieldConfig && selectedFieldConfig.type === "!struct";
const isOkWithoutField = keepInputOnChangeFieldSrc && selectedFieldType;
const isFieldSelected = !!selectedField || isOkWithoutField;
const isFieldAndOpSelected = isFieldSelected && selectedOperator;
const selectedOperatorConfig = getOperatorConfig(config, selectedOperator, selectedField);
const selectedOperatorHasOptions = selectedOperatorConfig && selectedOperatorConfig.options != null;
const selectedFieldWidgetConfig = getFieldWidgetConfig(config, selectedField, selectedOperator, null, null) || {};
const hideOperator = selectedFieldWidgetConfig.hideOperator;
let showDragIcon = config.settings.canReorder && reordableNodesCnt > 1 && !isLocked;
if (parentField) {
showDragIcon = showDragIcon && parentFieldCanReorder;
}
const showOperator = isFieldSelected && !hideOperator;
const showOperatorLabel = isFieldSelected && hideOperator && selectedFieldWidgetConfig.operatorInlineLabel;
const showWidget = isFieldAndOpSelected && !isSelectedGroup;
const showOperatorOptions = isFieldAndOpSelected && selectedOperatorHasOptions;
return {
selectedFieldId, selectedFieldWidgetConfig,
showDragIcon, showOperator, showOperatorLabel, showWidget, showOperatorOptions
};
}
setLock(lock) {
this.props.setLock(lock);
}
removeSelf() {
const {confirmFn, config} = this.props;
const {renderConfirm, removeRuleConfirmOptions: confirmOptions} = config.settings;
if (confirmOptions && !this.isEmptyCurrentRule()) {
renderConfirm.call(config.ctx, {...confirmOptions,
onOk: this.doRemove,
onCancel: null,
confirmFn: confirmFn
}, config.ctx);
} else {
this.doRemove();
}
}
_buildWidgetProps({
selectedField, selectedFieldSrc, selectedFieldType,
selectedOperator, operatorOptions,
value, valueType, valueSrc, asyncListValues, valueError, fieldError,
parentField,
}, {
selectedFieldId
}) {
return {
field: selectedField,
fieldSrc: selectedFieldSrc,
fieldType: selectedFieldType,
fieldId: selectedFieldId,
operator: selectedOperator,
operatorOptions,
value,
valueType,
valueSrc,
asyncListValues,
valueError,
fieldError,
parentField,
};
}
isEmptyCurrentRule() {
const {config} = this.props;
const ruleData = this._buildWidgetProps(this.props, this.meta);
return isEmptyRuleProperties(ruleData, config);
}
renderField() {
const {
config, isLocked, parentField, groupId, id,
selectedFieldSrc, selectedField, selectedFieldType, setField, setFuncValue, setFieldSrc, fieldError,
} = this.props;
const { immutableFieldsMode } = config.settings;
const { selectedFieldId } = this.meta;
// tip: don't allow function inside !group (yet)
return <FieldWrapper
key="field"
classname={classNames(
selectedFieldSrc == "func" ? "rule--field--func" : "rule--field",
)}
config={config}
canSelectFieldSource={!parentField}
selectedField={selectedField}
selectedFieldSrc={selectedFieldSrc}
selectedFieldType={selectedFieldType}
selectedFieldId={selectedFieldId}
fieldError={fieldError}
setField={!immutableFieldsMode ? setField : dummyFn}
setFuncValue={!immutableFieldsMode ? setFuncValue : dummyFn}
setFieldSrc={!immutableFieldsMode ? setFieldSrc : dummyFn}
parentField={parentField}
readonly={immutableFieldsMode || isLocked}
id={id}
groupId={groupId}
/>;
}
renderOperator () {
const {config, isLocked} = this.props;
const {
selectedFieldId, selectedFieldWidgetConfig, showOperator, showOperatorLabel
} = this.meta;
const { immutableOpsMode } = config.settings;
return <OperatorWrapper
key="operator"
config={config}
selectedField={this.props.selectedField}
selectedFieldSrc={this.props.selectedFieldSrc}
selectedFieldType={this.props.selectedFieldType}
selectedFieldId={selectedFieldId}
selectedOperator={this.props.selectedOperator}
setOperator={!immutableOpsMode ? this.props.setOperator : dummyFn}
showOperator={showOperator}
showOperatorLabel={showOperatorLabel}
selectedFieldWidgetConfig={selectedFieldWidgetConfig}
readonly={immutableOpsMode || isLocked}
id={this.props.id}
groupId={this.props.groupId}
/>;
}
renderWidget() {
const {config, isLocked} = this.props;
const { showWidget } = this.meta;
const { immutableValuesMode } = config.settings;
if (!showWidget) return null;
const widget = <Widget
key="values"
{...this._buildWidgetProps(this.props, this.meta)}
config={config}
setValue={!immutableValuesMode ? this.props.setValue : dummyFn}
setValueSrc={!immutableValuesMode ? this.props.setValueSrc : dummyFn}
setFuncValue={!immutableValuesMode ? this.props.setFuncValue : dummyFn}
readonly={immutableValuesMode || isLocked}
id={this.props.id}
groupId={this.props.groupId}
/>;
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 BeforeWidget = this.BeforeWidget;
if (!BeforeWidget)
return null;
return <Col key={"before-widget-for-" +this.props.selectedOperator} className="rule--before-widget">
<BeforeWidget {...this.props} />
</Col>;
}
renderAfterWidget() {
const AfterWidget = this.AfterWidget;
if (!AfterWidget)
return null;
return <Col key={"after-widget-for-" +this.props.selectedOperator} className="rule--after-widget">
<AfterWidget {...this.props} />
</Col>;
}
renderError() {
const {config, valueError, fieldError} = this.props;
const { showErrorMessage } = config.settings;
const RuleError = this.RuleError;
const oneError = [fieldError, ...(valueError?.toArray() || [])].filter(e => !!e).shift() || null;
return showErrorMessage && oneError
&& <div className="rule--error">
{RuleError ? <RuleError error={oneError} /> : oneError}
</div>;
}
renderDrag() {
const { handleDraggerMouseDown } = this.props;
const { showDragIcon } = this.meta;
const Icon = this.Icon;
const icon = <Icon
type="drag"
/>;
return showDragIcon && (<div
key="rule-drag-icon"
onMouseDown={handleDraggerMouseDown}
className={"qb-drag-handler rule--drag-handler"}
>{icon}</div>);
}
renderDel() {
const {config, isLocked} = this.props;
const {
deleteLabel,
immutableGroupsMode,
canDeleteLocked
} = config.settings;
const Icon = this.Icon;
const Btn = this.Btn;
return !immutableGroupsMode && (!isLocked || isLocked && canDeleteLocked) && (
<Btn
key="rule-del"
type="delRule"
onClick={this.removeSelf}
label={deleteLabel}
config={config}
renderIcon={Icon}
/>
);
}
renderLock() {
const {config, isLocked, isTrueLocked, id} = this.props;
const {
lockLabel, lockedLabel, showLock,
} = config.settings;
const Switch = this.Switch;
return showLock && !(isLocked && !isTrueLocked) && (
<Switch
key="rule-lock"
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 BtnGrp = this.BtnGrp;
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 key="rule-body-wrapper" className="rule--body--wrapper">
{body}{error}
</div>
<div key="rule-header-wrapper" className="rule--header">
<BtnGrp key="rule-header-group" config={config}>
{lock}
{del}
</BtnGrp>
</div>
</>
);
}
}
export default RuleContainer(Draggable("rule")(WithConfirmFn(Rule)));