UNPKG

react-awesome-query-builder-pd

Version:

User-friendly query builder for React. Demo: https://ukrbublik.github.io/react-awesome-query-builder

1,337 lines (1,316 loc) 35.2 kB
import React from "react" import * as Widgets from "../components/widgets" import * as Operators from "../components/operators" import { SqlString } from "../utils/sql" import { escapeRegExp, getTitleInListValues } from "../utils/stuff" import moment from "moment" import { settings as defaultSettings } from "../config/default" const { //vanilla VanillaBooleanWidget, VanillaTextWidget, VanillaTextAreaWidget, VanillaDateWidget, VanillaTimeWidget, VanillaDateTimeWidget, VanillaMultiSelectWidget, VanillaSelectWidget, VanillaNumberWidget, VanillaSliderWidget, //common ValueFieldWidget, FuncWidget } = Widgets const { ProximityOperator } = Operators //---------------------------- conjunctions const conjunctions = { AND: { label: "And", mongoConj: "$and", reversedConj: "OR", formatConj: (children, conj, not, isForDisplay) => { return children.size > 1 ? (not ? "NOT " : "") + "(" + children.join(" " + (isForDisplay ? "AND" : "&&") + " ") + ")" : (not ? "NOT (" : "") + children.first() + (not ? ")" : "") }, sqlFormatConj: (children, conj, not) => { return children.size > 1 ? (not ? "NOT " : "") + "(" + children.join(" " + "AND" + " ") + ")" : (not ? "NOT (" : "") + children.first() + (not ? ")" : "") } }, OR: { label: "Or", mongoConj: "$or", reversedConj: "AND", formatConj: (children, conj, not, isForDisplay) => { return children.size > 1 ? (not ? "NOT " : "") + "(" + children.join(" " + (isForDisplay ? "OR" : "||") + " ") + ")" : (not ? "NOT (" : "") + children.first() + (not ? ")" : "") }, sqlFormatConj: (children, conj, not) => { return children.size > 1 ? (not ? "NOT " : "") + "(" + children.join(" " + "OR" + " ") + ")" : (not ? "NOT (" : "") + children.first() + (not ? ")" : "") } } } //---------------------------- operators // helpers for mongo format const mongoFormatOp1 = (mop, mc, not, field, _op, value, useExpr) => { const $field = typeof field == "string" && !field.startsWith("$") ? "$" + field : field const mv = mc(value) if (mv === undefined) return undefined if (not) { return !useExpr ? { [field]: { $not: { [mop]: mv } } } : { $not: { [mop]: [$field, mv] } } } else { if (!useExpr && mop == "$eq") return { [field]: mv } // short form return !useExpr ? { [field]: { [mop]: mv } } : { [mop]: [$field, mv] } } } const mongoFormatOp2 = (mops, not, field, _op, values, useExpr) => { const $field = typeof field == "string" && !field.startsWith("$") ? "$" + field : field if (not) { return !useExpr ? { [field]: { $not: { [mops[0]]: values[0], [mops[1]]: values[1] } } } : { $not: { $and: [ { [mops[0]]: [$field, values[0]] }, { [mops[1]]: [$field, values[1]] } ] } } } else { return !useExpr ? { [field]: { [mops[0]]: values[0], [mops[1]]: values[1] } } : { $and: [ { [mops[0]]: [$field, values[0]] }, { [mops[1]]: [$field, values[1]] } ] } } } const operators = { equal: { label: "==", labelForFormat: "==", sqlOp: "=", reversedOp: "not_equal", formatOp: ( field, op, value, valueSrcs, valueTypes, opDef, operatorOptions, isForDisplay, fieldDef ) => { if (valueTypes == "boolean" && isForDisplay) return value == "No" ? `NOT ${field}` : `${field}` else return `${field} ${opDef.label} ${value}` }, mongoFormatOp: mongoFormatOp1.bind(null, "$eq", (v) => v, false), jsonLogic: "==", elasticSearchQueryType: "term" }, not_equal: { isNotOp: true, label: "!=", labelForFormat: "!=", sqlOp: "<>", reversedOp: "equal", formatOp: ( field, op, value, valueSrcs, valueTypes, opDef, operatorOptions, isForDisplay, fieldDef ) => { if (valueTypes == "boolean" && isForDisplay) return value == "No" ? `${field}` : `NOT ${field}` else return `${field} ${opDef.label} ${value}` }, mongoFormatOp: mongoFormatOp1.bind(null, "$ne", (v) => v, false), jsonLogic: "!=" }, less: { label: "<", labelForFormat: "<", sqlOp: "<", reversedOp: "greater_or_equal", mongoFormatOp: mongoFormatOp1.bind(null, "$lt", (v) => v, false), jsonLogic: "<", elasticSearchQueryType: "range" }, less_or_equal: { label: "<=", labelForFormat: "<=", sqlOp: "<=", reversedOp: "greater", mongoFormatOp: mongoFormatOp1.bind(null, "$lte", (v) => v, false), jsonLogic: "<=", elasticSearchQueryType: "range" }, greater: { label: ">", labelForFormat: ">", sqlOp: ">", reversedOp: "less_or_equal", mongoFormatOp: mongoFormatOp1.bind(null, "$gt", (v) => v, false), jsonLogic: ">", elasticSearchQueryType: "range" }, greater_or_equal: { label: ">=", labelForFormat: ">=", sqlOp: ">=", reversedOp: "less", mongoFormatOp: mongoFormatOp1.bind(null, "$gte", (v) => v, false), jsonLogic: ">=", elasticSearchQueryType: "range" }, like: { label: "Like", labelForFormat: "Like", reversedOp: "not_like", sqlOp: "LIKE", sqlFormatOp: ( field, op, values, valueSrc, valueType, opDef, operatorOptions ) => { if (valueSrc == "value") { return `${field} LIKE ${values}` } else return undefined // not supported }, mongoFormatOp: mongoFormatOp1.bind( null, "$regex", (v) => (typeof v == "string" ? escapeRegExp(v) : undefined), false ), //jsonLogic: (field, op, val) => ({ "in": [val, field] }), jsonLogic: "in", _jsonLogicIsRevArgs: true, valueSources: ["value"], elasticSearchQueryType: "regexp" }, not_like: { isNotOp: true, label: "Not like", reversedOp: "like", labelForFormat: "Not Like", sqlOp: "NOT LIKE", sqlFormatOp: ( field, op, values, valueSrc, valueType, opDef, operatorOptions ) => { if (valueSrc == "value") { return `${field} NOT LIKE ${values}` } else return undefined // not supported }, mongoFormatOp: mongoFormatOp1.bind( null, "$regex", (v) => (typeof v == "string" ? escapeRegExp(v) : undefined), true ), valueSources: ["value"] }, starts_with: { label: "Starts with", labelForFormat: "Starts with", sqlOp: "LIKE", sqlFormatOp: ( field, op, values, valueSrc, valueType, opDef, operatorOptions ) => { if (valueSrc == "value") { return `${field} LIKE ${values}` } else return undefined // not supported }, mongoFormatOp: mongoFormatOp1.bind( null, "$regex", (v) => (typeof v == "string" ? "^" + escapeRegExp(v) : undefined), false ), jsonLogic: undefined, // not supported valueSources: ["value"] }, ends_with: { label: "Ends with", labelForFormat: "Ends with", sqlOp: "LIKE", sqlFormatOp: ( field, op, values, valueSrc, valueType, opDef, operatorOptions ) => { if (valueSrc == "value") { return `${field} LIKE ${values}` } else return undefined // not supported }, mongoFormatOp: mongoFormatOp1.bind( null, "$regex", (v) => (typeof v == "string" ? escapeRegExp(v) + "$" : undefined), false ), jsonLogic: undefined, // not supported valueSources: ["value"] }, between: { label: "Between", labelForFormat: "BETWEEN", sqlOp: "BETWEEN", cardinality: 2, formatOp: ( field, op, values, valueSrcs, valueTypes, opDef, operatorOptions, isForDisplay ) => { let valFrom = values.first() let valTo = values.get(1) if (isForDisplay) return `${field} BETWEEN ${valFrom} AND ${valTo}` else return `${field} >= ${valFrom} && ${field} <= ${valTo}` }, mongoFormatOp: mongoFormatOp2.bind(null, ["$gte", "$lte"], false), valueLabels: ["Value from", "Value to"], textSeparators: [null, "and"], reversedOp: "not_between", jsonLogic: "<=", validateValues: (values) => { if (values[0] != undefined && values[1] != undefined) { return values[0] <= values[1] ? null : "Invalid range" } return null }, elasticSearchQueryType: function elasticSearchQueryType(type) { return type === "time" ? "filter" : "range" } }, not_between: { isNotOp: true, label: "Not between", labelForFormat: "NOT BETWEEN", sqlOp: "NOT BETWEEN", cardinality: 2, mongoFormatOp: mongoFormatOp2.bind(null, ["$gte", "$lte"], true), valueLabels: ["Value from", "Value to"], textSeparators: [null, "and"], reversedOp: "between", validateValues: (values) => { if (values[0] != undefined && values[1] != undefined) { return values[0] <= values[1] ? null : "Invalid range" } return null } }, is_empty: { label: "Is empty", labelForFormat: "IS EMPTY", sqlOp: "IS EMPTY", cardinality: 0, reversedOp: "is_not_empty", formatOp: ( field, op, value, valueSrc, valueType, opDef, operatorOptions, isForDisplay ) => { return isForDisplay ? `${field} IS EMPTY` : `!${field}` }, mongoFormatOp: mongoFormatOp1.bind(null, "$exists", (v) => false, false), jsonLogic: "!" }, is_not_empty: { isNotOp: true, label: "Is not empty", labelForFormat: "IS NOT EMPTY", sqlOp: "IS NOT EMPTY", cardinality: 0, reversedOp: "is_empty", formatOp: ( field, op, value, valueSrc, valueType, opDef, operatorOptions, isForDisplay ) => { return isForDisplay ? `${field} IS NOT EMPTY` : `!!${field}` }, mongoFormatOp: mongoFormatOp1.bind(null, "$exists", (v) => true, false), jsonLogic: "!!", elasticSearchQueryType: "exists" }, select_equals: { label: "==", labelForFormat: "==", sqlOp: "=", // enum/set formatOp: ( field, op, value, valueSrc, valueType, opDef, operatorOptions, isForDisplay ) => { return `${field} == ${value}` }, mongoFormatOp: mongoFormatOp1.bind(null, "$eq", (v) => v, false), reversedOp: "select_not_equals", jsonLogic: "==", elasticSearchQueryType: "term" }, select_not_equals: { isNotOp: true, label: "!=", labelForFormat: "!=", sqlOp: "<>", // enum/set formatOp: ( field, op, value, valueSrc, valueType, opDef, operatorOptions, isForDisplay ) => { return `${field} != ${value}` }, mongoFormatOp: mongoFormatOp1.bind(null, "$ne", (v) => v, false), reversedOp: "select_equals", jsonLogic: "!=" }, select_any_in: { label: "Any in", labelForFormat: "IN", sqlOp: "IN", formatOp: ( field, op, values, valueSrc, valueType, opDef, operatorOptions, isForDisplay ) => { if (valueSrc == "value") return `${field} IN (${values.join(", ")})` else return `${field} IN (${values})` }, sqlFormatOp: ( field, op, values, valueSrc, valueType, opDef, operatorOptions ) => { return `${field} IN (${values.join(", ")})` }, mongoFormatOp: mongoFormatOp1.bind(null, "$in", (v) => v, false), reversedOp: "select_not_any_in", jsonLogic: "in", elasticSearchQueryType: "term" }, select_not_any_in: { isNotOp: true, label: "Not in", labelForFormat: "NOT IN", sqlOp: "NOT IN", formatOp: ( field, op, values, valueSrc, valueType, opDef, operatorOptions, isForDisplay ) => { if (valueSrc == "value") return `${field} NOT IN (${values.join(", ")})` else return `${field} NOT IN (${values})` }, sqlFormatOp: ( field, op, values, valueSrc, valueType, opDef, operatorOptions ) => { return `${field} NOT IN (${values.join(", ")})` }, mongoFormatOp: mongoFormatOp1.bind(null, "$nin", (v) => v, false), reversedOp: "select_any_in" }, multiselect_equals: { label: "Equals", labelForFormat: "==", sqlOp: "=", formatOp: ( field, op, values, valueSrc, valueType, opDef, operatorOptions, isForDisplay ) => { if (valueSrc == "value") return `${field} == [${values.join(", ")}]` else return `${field} == ${values}` }, sqlFormatOp: ( field, op, values, valueSrc, valueType, opDef, operatorOptions ) => { if (valueSrc == "value") // set return `${field} = '${values.map((v) => SqlString.trim(v)).join(",")}'` else return undefined //not supported }, mongoFormatOp: mongoFormatOp1.bind(null, "$eq", (v) => v, false), reversedOp: "multiselect_not_equals", jsonLogic2: "all-in", jsonLogic: (field, op, vals) => ({ // it's not "equals", but "includes" operator - just for example all: [field, { in: [{ var: "" }, vals] }] }), elasticSearchQueryType: "term" }, multiselect_not_equals: { isNotOp: true, label: "Not equals", labelForFormat: "!=", sqlOp: "<>", formatOp: ( field, op, values, valueSrc, valueType, opDef, operatorOptions, isForDisplay ) => { if (valueSrc == "value") return `${field} != [${values.join(", ")}]` else return `${field} != ${values}` }, sqlFormatOp: ( field, op, values, valueSrc, valueType, opDef, operatorOptions ) => { if (valueSrc == "value") // set return `${field} != '${values.map((v) => SqlString.trim(v)).join(",")}'` else return undefined //not supported }, mongoFormatOp: mongoFormatOp1.bind(null, "$ne", (v) => v, false), reversedOp: "multiselect_equals" }, proximity: { label: "Proximity search", cardinality: 2, valueLabels: [ { label: "Word 1", placeholder: "Enter first word" }, { label: "Word 2", placeholder: "Enter second word" } ], textSeparators: [ //'Word 1', //'Word 2' ], formatOp: ( field, op, values, valueSrc, valueType, opDef, operatorOptions, isForDisplay ) => { const val1 = values.first() const val2 = values.get(1) const prox = operatorOptions.get("proximity") return `${field} ${val1} NEAR/${prox} ${val2}` }, sqlFormatOp: ( field, op, values, valueSrc, valueType, opDef, operatorOptions ) => { const val1 = values.first() const val2 = values.get(1) const aVal1 = SqlString.trim(val1) const aVal2 = SqlString.trim(val2) const prox = operatorOptions.get("proximity") return `CONTAINS(${field}, 'NEAR((${aVal1}, ${aVal2}), ${prox})')` }, mongoFormatOp: undefined, // not supported jsonLogic: undefined, // not supported options: { optionLabel: "Near", // label on top of "near" selectbox (for config.settings.showLabels==true) optionTextBefore: "Near", // label before "near" selectbox (for config.settings.showLabels==false) optionPlaceholder: "Select words between", // placeholder for "near" selectbox factory: (props) => <ProximityOperator {...props} />, minProximity: 2, maxProximity: 10, defaults: { proximity: 2 } } }, some: { label: "Some", labelForFormat: "SOME", cardinality: 0, jsonLogic: "some", mongoFormatOp: mongoFormatOp1.bind(null, "$gt", (v) => 0, false) }, all: { label: "All", labelForFormat: "ALL", cardinality: 0, jsonLogic: "all", mongoFormatOp: mongoFormatOp1.bind(null, "$eq", (v) => v, false) }, none: { label: "None", labelForFormat: "NONE", cardinality: 0, jsonLogic: "none", mongoFormatOp: mongoFormatOp1.bind(null, "$eq", (v) => 0, false) } } //---------------------------- widgets const widgets = { text: { type: "text", jsType: "string", valueSrc: "value", valueLabel: "String", valuePlaceholder: "Enter string", factory: (props) => <VanillaTextWidget {...props} />, formatValue: (val, fieldDef, wgtDef, isForDisplay) => { return isForDisplay ? '"' + val + '"' : JSON.stringify(val) }, sqlFormatValue: (val, fieldDef, wgtDef, op, opDef) => { if (opDef.sqlOp == "LIKE" || opDef.sqlOp == "NOT LIKE") { return SqlString.escapeLike(val, op != "starts_with", op != "ends_with") } else { return SqlString.escape(val) } }, toJS: (val, fieldSettings) => val, mongoFormatValue: (val, fieldDef, wgtDef) => val }, textarea: { type: "text", jsType: "string", valueSrc: "value", valueLabel: "Text", valuePlaceholder: "Enter text", factory: (props) => <VanillaTextAreaWidget {...props} />, formatValue: (val, fieldDef, wgtDef, isForDisplay) => { return isForDisplay ? '"' + val + '"' : JSON.stringify(val) }, sqlFormatValue: (val, fieldDef, wgtDef, op, opDef) => { if (opDef.sqlOp == "LIKE" || opDef.sqlOp == "NOT LIKE") { return SqlString.escapeLike(val, op != "starts_with", op != "ends_with") } else { return SqlString.escape(val) } }, toJS: (val, fieldSettings) => val, mongoFormatValue: (val, fieldDef, wgtDef) => val, fullWidth: true }, number: { type: "number", jsType: "number", valueSrc: "value", factory: (props) => <VanillaNumberWidget {...props} />, valueLabel: "Number", valuePlaceholder: "Enter number", valueLabels: [ { label: "Number from", placeholder: "Enter number from" }, { label: "Number to", placeholder: "Enter number to" } ], formatValue: (val, fieldDef, wgtDef, isForDisplay) => { return isForDisplay ? val : JSON.stringify(val) }, sqlFormatValue: (val, fieldDef, wgtDef, op, opDef) => { return SqlString.escape(val) }, toJS: (val, fieldSettings) => val, mongoFormatValue: (val, fieldDef, wgtDef) => val }, slider: { type: "number", jsType: "number", valueSrc: "value", factory: (props) => <VanillaSliderWidget {...props} />, valueLabel: "Number", valuePlaceholder: "Enter number or move slider", formatValue: (val, fieldDef, wgtDef, isForDisplay) => { return isForDisplay ? val : JSON.stringify(val) }, sqlFormatValue: (val, fieldDef, wgtDef, op, opDef) => { return SqlString.escape(val) }, toJS: (val, fieldSettings) => val, mongoFormatValue: (val, fieldDef, wgtDef) => val }, select: { type: "select", jsType: "string", valueSrc: "value", factory: (props) => <VanillaSelectWidget {...props} />, valueLabel: "Value", valuePlaceholder: "Select value", formatValue: (val, fieldDef, wgtDef, isForDisplay) => { let valLabel = getTitleInListValues( fieldDef.fieldSettings.listValues || fieldDef.asyncListValues, val ) return isForDisplay ? '"' + valLabel + '"' : JSON.stringify(val) }, sqlFormatValue: (val, fieldDef, wgtDef, op, opDef) => { return SqlString.escape(val) }, toJS: (val, fieldSettings) => val, mongoFormatValue: (val, fieldDef, wgtDef) => val }, multiselect: { type: "multiselect", jsType: "array", valueSrc: "value", factory: (props) => <VanillaMultiSelectWidget {...props} />, valueLabel: "Values", valuePlaceholder: "Select values", formatValue: (vals, fieldDef, wgtDef, isForDisplay) => { let valsLabels = vals.map((v) => getTitleInListValues( fieldDef.fieldSettings.listValues || fieldDef.asyncListValues, v ) ) return isForDisplay ? valsLabels.map((v) => '"' + v + '"') : vals.map((v) => JSON.stringify(v)) }, sqlFormatValue: (vals, fieldDef, wgtDef, op, opDef) => { return vals.map((v) => SqlString.escape(v)) }, toJS: (val, fieldSettings) => val, mongoFormatValue: (val, fieldDef, wgtDef) => val }, date: { type: "date", jsType: "string", valueSrc: "value", factory: (props) => <VanillaDateWidget {...props} />, dateFormat: "dd/MM/yyyy", valueFormat: "YYYY-MM-DD", useKeyboard: true, valueLabel: "Date", valuePlaceholder: "Enter date", valueLabels: [ { label: "Date from", placeholder: "Enter date from" }, { label: "Date to", placeholder: "Enter date to" } ], formatValue: (val, fieldDef, wgtDef, isForDisplay) => { const dateVal = moment(val, wgtDef.valueFormat) return isForDisplay ? '"' + dateVal.format(wgtDef.dateFormat) + '"' : JSON.stringify(val) }, sqlFormatValue: (val, fieldDef, wgtDef, op, opDef) => { const dateVal = moment(val, wgtDef.valueFormat) console.log(dateVal, wgtDef, dateVal.format("YYYY-MM-DD")) return SqlString.escape(dateVal.format("YYYY-MM-DD")) }, jsonLogic: (val, fieldDef, wgtDef) => moment(val, wgtDef.valueFormat).toDate(), toJS: (val, fieldSettings) => { const dateVal = moment(val, fieldSettings.valueFormat) return dateVal.isValid() ? dateVal.toDate() : undefined }, mongoFormatValue: (val, fieldDef, wgtDef) => { const dateVal = moment(val, wgtDef.valueFormat) return dateVal.isValid() ? dateVal.toDate() : undefined } }, time: { type: "time", jsType: "string", valueSrc: "value", factory: (props) => <VanillaTimeWidget {...props} />, timeFormat: "HH:mm", valueFormat: "HH:mm:ss", use12Hours: false, useKeyboard: true, valueLabel: "Time", valuePlaceholder: "Enter time", valueLabels: [ { label: "Time from", placeholder: "Enter time from" }, { label: "Time to", placeholder: "Enter time to" } ], formatValue: (val, fieldDef, wgtDef, isForDisplay) => { const dateVal = moment(val, wgtDef.valueFormat) return isForDisplay ? '"' + dateVal.format(wgtDef.timeFormat) + '"' : JSON.stringify(val) }, sqlFormatValue: (val, fieldDef, wgtDef, op, opDef) => { const dateVal = moment(val, wgtDef.valueFormat) return SqlString.escape(dateVal.format("HH:mm:ss")) }, jsonLogic: (val, fieldDef, wgtDef) => { // return seconds of day const dateVal = moment(val, wgtDef.valueFormat) return ( dateVal.get("hour") * 60 * 60 + dateVal.get("minute") * 60 + dateVal.get("second") ) }, toJS: (val, fieldSettings) => { // return seconds of day const dateVal = moment(val, fieldSettings.valueFormat) return dateVal.isValid() ? dateVal.get("hour") * 60 * 60 + dateVal.get("minute") * 60 + dateVal.get("second") : undefined }, mongoFormatValue: (val, fieldDef, wgtDef) => { // return seconds of day const dateVal = moment(val, wgtDef.valueFormat) return ( dateVal.get("hour") * 60 * 60 + dateVal.get("minute") * 60 + dateVal.get("second") ) }, elasticSearchFormatValue: function elasticSearchFormatValue( queryType, value, operator, fieldName ) { return { script: { script: { source: "doc[" .concat(fieldName, "][0].getHour() >== params.min && doc[") .concat(fieldName, "][0].getHour() <== params.max"), params: { min: value[0], max: value[1] } } } } } }, datetime: { type: "datetime", jsType: "string", valueSrc: "value", factory: (props) => <VanillaDateTimeWidget {...props} />, timeFormat: "HH:mm", dateFormat: "dd.mm.yyyy", valueFormat: "yyyy-mm-dd HH:mm:ss", use12Hours: false, useKeyboard: true, valueLabel: "Datetime", valuePlaceholder: "Enter datetime", valueLabels: [ { label: "Datetime from", placeholder: "Enter datetime from" }, { label: "Datetime to", placeholder: "Enter datetime to" } ], formatValue: (val, fieldDef, wgtDef, isForDisplay) => { const dateVal = moment(val, wgtDef.valueFormat) return isForDisplay ? '"' + dateVal.format(wgtDef.dateFormat + " " + wgtDef.timeFormat) + '"' : JSON.stringify(val) }, sqlFormatValue: (val, fieldDef, wgtDef, op, opDef) => { const dateVal = moment(val, wgtDef.valueFormat) return SqlString.escape(dateVal.toDate()) }, jsonLogic: (val, fieldDef, wgtDef) => moment(val, wgtDef.valueFormat).toDate(), toJS: (val, fieldSettings) => { const dateVal = moment(val, fieldSettings.valueFormat) return dateVal.isValid() ? dateVal.toDate() : undefined }, mongoFormatValue: (val, fieldDef, wgtDef) => { const dateVal = moment(val, wgtDef.valueFormat) return dateVal.isValid() ? dateVal.toDate() : undefined } }, boolean: { type: "boolean", jsType: "boolean", valueSrc: "value", factory: (props) => <VanillaBooleanWidget {...props} />, labelYes: "Yes", labelNo: "No", formatValue: (val, fieldDef, wgtDef, isForDisplay) => { return isForDisplay ? (val ? "Yes" : "No") : JSON.stringify(!!val) }, sqlFormatValue: (val, fieldDef, wgtDef, op, opDef) => { return SqlString.escape(val) }, defaultValue: false, toJS: (val, fieldSettings) => val, mongoFormatValue: (val, fieldDef, wgtDef) => val }, field: { valueSrc: "field", factory: (props) => <ValueFieldWidget {...props} />, formatValue: ( val, fieldDef, wgtDef, isForDisplay, op, opDef, rightFieldDef ) => { return isForDisplay ? rightFieldDef.label || val : val }, sqlFormatValue: (val, fieldDef, wgtDef, op, opDef, rightFieldDef) => { return val }, valueLabel: "Field to compare", valuePlaceholder: "Select field to compare", customProps: { showSearch: true } }, func: { valueSrc: "func", factory: (props) => <FuncWidget {...props} />, valueLabel: "Function", valuePlaceholder: "Select function", customProps: { //showSearch: true } } } //---------------------------- types const types = { text: { defaultOperator: "equal", mainWidget: "text", widgets: { text: { operators: [ "equal", "not_equal", "is_empty", "is_not_empty", "like", "not_like", "starts_with", "ends_with", "proximity" ], widgetProps: {}, opProps: {} }, textarea: { operators: [ "equal", "not_equal", "is_empty", "is_not_empty", "like", "not_like", "starts_with", "ends_with" ], widgetProps: {}, opProps: {} }, field: { operators: [ //unary ops (like `is_empty`) will be excluded anyway, see getWidgetsForFieldOp() "equal", "not_equal", "proximity" //can exclude if you want ] } } }, number: { defaultOperator: "equal", mainWidget: "number", widgets: { number: { operators: [ "equal", "not_equal", "less", "less_or_equal", "greater", "greater_or_equal", "between", "not_between", "is_empty", "is_not_empty" ] }, slider: { operators: [ "equal", "not_equal", "less", "less_or_equal", "greater", "greater_or_equal", "is_empty", "is_not_empty" ] } } }, date: { defaultOperator: "equal", widgets: { date: { operators: [ "equal", "not_equal", "less", "less_or_equal", "greater", "greater_or_equal", "between", "not_between", "is_empty", "is_not_empty" ] } } }, time: { defaultOperator: "equal", widgets: { time: { operators: [ "equal", "not_equal", "less", "less_or_equal", "greater", "greater_or_equal", "between", "not_between", "is_empty", "is_not_empty" ] } } }, datetime: { defaultOperator: "equal", widgets: { datetime: { operators: [ "equal", "not_equal", "less", "less_or_equal", "greater", "greater_or_equal", "between", "not_between", "is_empty", "is_not_empty" ] } } }, select: { mainWidget: "select", defaultOperator: "select_equals", widgets: { select: { operators: [ "select_equals", "select_not_equals", "is_empty", "is_not_empty" ], widgetProps: { customProps: { showSearch: true } } }, multiselect: { operators: [ "select_any_in", "select_not_any_in", "is_empty", "is_not_empty" ] } } }, multiselect: { defaultOperator: "multiselect_equals", widgets: { multiselect: { operators: [ "multiselect_equals", "multiselect_not_equals", "is_empty", "is_not_empty" ] } } }, boolean: { defaultOperator: "equal", widgets: { boolean: { operators: ["equal", "not_equal"], widgetProps: { //you can enable this if you don't use fields as value sources // hideOperator: true, // operatorInlineLabel: "is", } }, field: { operators: ["equal", "not_equal"] } } }, "!group": { defaultOperator: "some", mainWidget: "number", widgets: { number: { widgetProps: { min: 0 }, operators: [ // w/o operand "some", "all", "none", // w/ operand - count "equal", "not_equal", "less", "less_or_equal", "greater", "greater_or_equal", "between", "not_between" ], opProps: { equal: { label: "Count ==" }, not_equal: { label: "Count !=" }, less: { label: "Count <" }, less_or_equal: { label: "Count <=" }, greater: { label: "Count >" }, greater_or_equal: { label: "Count >=" }, between: { label: "Count between" }, not_between: { label: "Count not between" } } } } } } //---------------------------- settings const settings = { ...defaultSettings, formatField: ( field, parts, label2, fieldDefinition, config, isForDisplay ) => { if (isForDisplay) return label2 else return field }, sqlFormatReverse: ( q, operator, reversedOp, operatorDefinition, revOperatorDefinition ) => { if (q == undefined) return undefined return "NOT(" + q + ")" }, formatReverse: ( q, operator, reversedOp, operatorDefinition, revOperatorDefinition, isForDisplay ) => { if (q == undefined) return undefined if (isForDisplay) return "NOT(" + q + ")" else return "!(" + q + ")" }, formatAggr: ( whereStr, aggrField, operator, value, valueSrc, valueType, opDef, operatorOptions, isForDisplay, aggrFieldDef ) => { const { labelForFormat, cardinality } = opDef if (cardinality == 0) { return `${labelForFormat} OF ${aggrField} HAVE ${whereStr}` } else if (cardinality == undefined || cardinality == 1) { return `COUNT OF ${aggrField} WHERE ${whereStr} ${labelForFormat} ${value}` } else if (cardinality == 2) { let valFrom = value.first() let valTo = value.get(1) return `COUNT OF ${aggrField} WHERE ${whereStr} ${labelForFormat} ${valFrom} AND ${valTo}` } }, canCompareFieldWithField: ( leftField, leftFieldConfig, rightField, rightFieldConfig ) => { //for type == 'select'/'multiselect' you can check listValues return true }, // enable compare fields valueSourcesInfo: { value: { label: "Value" }, field: { label: "Field", widget: "field" }, func: { label: "Function", widget: "func" } }, customFieldSelectProps: { showSearch: true }, defaultSliderWidth: "200px", defaultSelectWidth: "200px", defaultSearchWidth: "100px", defaultMaxRows: 5 } //---------------------------- export default { conjunctions, operators, widgets, types, settings }