@coocoon/react-awesome-query-builder
Version:
User-friendly query builder for React. Demo: https://ukrbublik.github.io/react-awesome-query-builder
369 lines (330 loc) • 13.1 kB
JavaScript
import merge from "lodash/merge";
import mergeWith from "lodash/mergeWith";
import {settings as defaultSettings} from "../config/default";
import moment from "moment";
import {normalizeListValues, mergeArraysSmart} from "./stuff";
import {getWidgetForFieldOp} from "./ruleUtils";
import clone from "clone";
export const extendConfig = (config) => {
//operators, defaultOperator - merge
//widgetProps (including valueLabel, valuePlaceholder, hideOperator, operatorInlineLabel) - concrete by widget
if (config.__extended) {
return config;
}
config.settings = merge({}, defaultSettings, config.settings);
config._fieldsCntByType = {};
config._funcsCntByType = {};
config.types = clone(config.types);
_extendTypesConfig(config.types, config);
config.fields = clone(config.fields);
config.__fieldNames = {};
_extendFieldsConfig(config.fields, config);
config.funcs = clone(config.funcs);
_extendFuncArgsConfig(config.funcs, config);
moment.locale(config.settings.locale.moment);
Object.defineProperty(config, "__extended", {
enumerable: false,
writable: false,
value: true
});
return config;
};
function _extendTypesConfig(typesConfig, config) {
for (let type in typesConfig) {
let typeConfig = typesConfig[type];
_extendTypeConfig(type, typeConfig, config);
}
}
function _extendTypeConfig(type, typeConfig, config) {
let operators = null, defaultOperator = null;
typeConfig.mainWidget = typeConfig.mainWidget || Object.keys(typeConfig.widgets).filter(w => w != "field" && w != "func")[0];
for (let widget in typeConfig.widgets) {
let typeWidgetConfig = typeConfig.widgets[widget];
if (typeWidgetConfig.operators) {
let typeWidgetOperators = typeWidgetConfig.operators;
if (typeConfig.excludeOperators) {
typeWidgetOperators = typeWidgetOperators.filter(op => !typeConfig.excludeOperators.includes(op));
}
operators = mergeArraysSmart(operators, typeWidgetOperators);
}
if (typeWidgetConfig.defaultOperator)
defaultOperator = typeWidgetConfig.defaultOperator;
if (widget == typeConfig.mainWidget) {
typeWidgetConfig = merge({}, {widgetProps: typeConfig.mainWidgetProps || {}}, typeWidgetConfig);
}
typeConfig.widgets[widget] = typeWidgetConfig;
}
if (!typeConfig.valueSources)
typeConfig.valueSources = Object.keys(config.settings.valueSourcesInfo);
for (let valueSrc of typeConfig.valueSources) {
if (valueSrc != "value" && !typeConfig.widgets[valueSrc]) {
typeConfig.widgets[valueSrc] = {
};
}
}
if (!typeConfig.operators && operators)
typeConfig.operators = Array.from(new Set(operators)); //unique
if (!typeConfig.defaultOperator && defaultOperator)
typeConfig.defaultOperator = defaultOperator;
}
function _extendFieldsConfig(subconfig, config, path = []) {
for (let field in subconfig) {
_extendFieldConfig(subconfig[field], config, [...path, field]);
if (subconfig[field].subfields) {
_extendFieldsConfig(subconfig[field].subfields, config, [...path, field]);
}
}
}
function _extendFuncArgsConfig(subconfig, config) {
if (!subconfig) return;
for (let funcKey in subconfig) {
const funcDef = subconfig[funcKey];
if (funcDef.returnType) {
if (!config._funcsCntByType[funcDef.returnType])
config._funcsCntByType[funcDef.returnType] = 0;
config._funcsCntByType[funcDef.returnType]++;
}
for (let argKey in funcDef.args) {
_extendFieldConfig(funcDef.args[argKey], config, null, true);
}
// isOptional can be only in the end
if (funcDef.args) {
const argKeys = Object.keys(funcDef.args);
let tmpIsOptional = true;
for (const argKey of argKeys.reverse()) {
const argDef = funcDef.args[argKey];
if (!tmpIsOptional && argDef.isOptional) {
delete argDef.isOptional;
}
if (!argDef.isOptional)
tmpIsOptional = false;
}
}
if (funcDef.subfields) {
_extendFuncArgsConfig(funcDef.subfields, config);
}
}
}
function _extendFieldConfig(fieldConfig, config, path = null, isFuncArg = false) {
let operators = null, defaultOperator = null;
const typeConfig = config.types[fieldConfig.type];
const excludeOperatorsForField = fieldConfig.excludeOperators || [];
if (fieldConfig.type != "!struct" && fieldConfig.type != "!group") {
if (!typeConfig) {
//console.warn(`No type config for ${fieldConfig.type}`);
fieldConfig.disabled = true;
return;
}
if (!isFuncArg) {
if (!config._fieldsCntByType[fieldConfig.type])
config._fieldsCntByType[fieldConfig.type] = 0;
config._fieldsCntByType[fieldConfig.type]++;
}
if (!fieldConfig.widgets)
fieldConfig.widgets = {};
if (isFuncArg)
fieldConfig._isFuncArg = true;
fieldConfig.mainWidget = fieldConfig.mainWidget || typeConfig.mainWidget;
fieldConfig.valueSources = fieldConfig.valueSources || typeConfig.valueSources;
const excludeOperatorsForType = typeConfig.excludeOperators || [];
for (let widget in typeConfig.widgets) {
let fieldWidgetConfig = fieldConfig.widgets[widget] || {};
const typeWidgetConfig = typeConfig.widgets[widget] || {};
if (!isFuncArg) {
//todo: why I've excluded isFuncArg ?
const excludeOperators = [...excludeOperatorsForField, ...excludeOperatorsForType];
const shouldIncludeOperators = fieldConfig.preferWidgets
&& (widget == "field" || fieldConfig.preferWidgets.includes(widget))
|| excludeOperators.length > 0;
if (fieldWidgetConfig.operators) {
const addOperators = fieldWidgetConfig.operators.filter(o => !excludeOperators.includes(o));
operators = [...(operators || []), ...addOperators];
} else if (shouldIncludeOperators && typeWidgetConfig.operators) {
const addOperators = typeWidgetConfig.operators.filter(o => !excludeOperators.includes(o));
operators = [...(operators || []), ...addOperators];
}
if (fieldWidgetConfig.defaultOperator)
defaultOperator = fieldWidgetConfig.defaultOperator;
}
if (widget == fieldConfig.mainWidget) {
fieldWidgetConfig = merge({}, {widgetProps: fieldConfig.mainWidgetProps || {}}, fieldWidgetConfig);
}
fieldConfig.widgets[widget] = fieldWidgetConfig;
}
if (!isFuncArg) {
if (!fieldConfig.operators && operators)
fieldConfig.operators = Array.from(new Set(operators));
if (!fieldConfig.defaultOperator && defaultOperator)
fieldConfig.defaultOperator = defaultOperator;
}
const keysToPutInFieldSettings = ["listValues", "allowCustomValues", "validateValue"];
if (!fieldConfig.fieldSettings)
fieldConfig.fieldSettings = {};
for (const k of keysToPutInFieldSettings) {
if (fieldConfig[k]) {
fieldConfig.fieldSettings[k] = fieldConfig[k];
delete fieldConfig[k];
}
}
if (fieldConfig.fieldSettings.listValues) {
fieldConfig.fieldSettings.listValues = normalizeListValues(fieldConfig.fieldSettings.listValues, fieldConfig.type, fieldConfig.fieldSettings);
}
}
const computedFieldName = computeFieldName(config, path);
if (computedFieldName) {
fieldConfig.fieldName = computedFieldName;
}
if (path && fieldConfig.fieldName) {
config.__fieldNames[fieldConfig.fieldName] = path;
}
}
export const getFieldRawConfig = (config, field, fieldsKey = "fields", subfieldsKey = "subfields", isFunc=false) => {
if (!field)
return null;
if (field == "!case_value") {
return {
type: "case_value",
mainWidget: "case_value",
widgets: {
"case_value": config.widgets["case_value"]
}
};
}
if(isFunc){
const method = config?.funcs?.[field];
if(!method){
return null;
}
return {
label: method.label,
tooltip: method.tooltip,
type: method.type,
group: method.group,
};
}
const fieldSeparator = config.settings.fieldSeparator;
//field = normalizeField(config, field);
const parts = Array.isArray(field) ? field : field.split(fieldSeparator);
const targetFields = config[fieldsKey];
if (!targetFields)
return null;
let fields = targetFields;
let fieldConfig = null;
let path = [];
for (let i = 0 ; i < parts.length ; i++) {
const part = parts[i];
path.push(part);
const pathKey = path.join(fieldSeparator);
fieldConfig = fields[pathKey];
if(!fieldConfig && fields.name && fields.name.isSpelMap && fields.name.isFake){
//map name
fieldConfig = fields.name;
}
if (i < parts.length-1) {
if (fieldConfig && fieldConfig[subfieldsKey]) {
fields = fieldConfig[subfieldsKey];
path = [];
} else {
fieldConfig = null;
}
}
}
return fieldConfig;
};
const computeFieldName = (config, path) => {
if (!path)
return null;
const fieldSeparator = config.settings.fieldSeparator;
let l = [...path], r = [], f, fConfig;
while ((f = l.pop()) !== undefined && l.length > 0) {
r.unshift(f);
fConfig = getFieldRawConfig(config, l);
if (fConfig.fieldName) {
return [fConfig.fieldName, ...r].join(fieldSeparator);
}
}
return null;
};
export const normalizeField = (config, field) => {
const fieldSeparator = config.settings.fieldSeparator;
const fieldStr = Array.isArray(field) ? field.join(fieldSeparator) : field;
if (config.__fieldNames[fieldStr]) {
return config.__fieldNames[fieldStr].join(fieldSeparator);
}
return fieldStr;
};
export const getFuncConfig = (config, func) => {
if (!func)
return null;
const funcConfig = getFieldRawConfig(config, func, "funcs", "subfields");
if (!funcConfig)
return null; //throw new Error("Can't find func " + func + ", please check your config");
return funcConfig;
};
export const getFuncArgConfig = (config, funcKey, argKey) => {
const funcConfig = getFuncConfig(config, funcKey);
if (!funcConfig)
return null; //throw new Error(`Can't find func ${funcKey}, please check your config`);
const argConfig = funcConfig.args && funcConfig.args[argKey] || null;
if (!argConfig)
return null; //throw new Error(`Can't find arg ${argKey} for func ${funcKey}, please check your config`);
//merge, but don't merge operators (rewrite instead)
const typeConfig = config.types[argConfig.type] || {};
let ret = mergeWith({}, typeConfig, argConfig || {}, (objValue, srcValue, _key, _object, _source, _stack) => {
if (Array.isArray(objValue)) {
return srcValue;
}
});
return ret;
};
export const getFieldConfig = (config, field, isFunc=false) => {
if (!field)
return null;
if (typeof field == "object" && !field.func && !!field.type)
return field;
if (typeof field == "object" && field.func && field.arg)
return getFuncArgConfig(config, field.func, field.arg);
const fieldConfig = getFieldRawConfig(config, field, "fields", "subfields", isFunc);
if (!fieldConfig)
return null; //throw new Error("Can't find field " + field + ", please check your config");
//merge, but don't merge operators (rewrite instead)
const typeConfig = config.types[fieldConfig.type] || {};
let ret = mergeWith({}, typeConfig, fieldConfig || {}, (objValue, srcValue, _key, _object, _source, _stack) => {
if (Array.isArray(objValue)) {
return srcValue;
}
});
return ret;
};
export const getOperatorConfig = (config, operator, field = null,isFunc=false) => {
if (!operator)
return null;
const opConfig = config.operators[operator];
if (field) {
const fieldConfig = getFieldConfig(config, field,isFunc);
const widget = getWidgetForFieldOp(config, field, operator,isFunc);
const widgetConfig = config.widgets[widget] || {};
const fieldWidgetConfig = (fieldConfig && fieldConfig.widgets ? fieldConfig.widgets[widget] : {}) || {};
const widgetOpProps = (widgetConfig.opProps || {})[operator];
const fieldWidgetOpProps = (fieldWidgetConfig.opProps || {})[operator];
const mergedOpConfig = merge({}, opConfig, widgetOpProps, fieldWidgetOpProps);
return mergedOpConfig;
} else {
return opConfig;
}
};
export const getFieldWidgetConfig = (config, field, operator, widget = null, valueSrc = null, isFunc=false) => {
if (!field)
return null;
if (!(operator || widget) && valueSrc != "const" && field != "!case_value")
return null;
const fieldConfig = getFieldConfig(config, field, isFunc);
if (!widget)
widget = getWidgetForFieldOp(config, field, operator, valueSrc,isFunc);
const widgetConfig = config.widgets[widget] || {};
const fieldWidgetConfig = (fieldConfig && fieldConfig.widgets ? fieldConfig.widgets[widget] : {}) || {};
const fieldWidgetProps = (fieldWidgetConfig.widgetProps || {});
const valueFieldSettings = (valueSrc == "value" || !valueSrc) && fieldConfig && fieldConfig.fieldSettings || {}; // useful to take 'validateValue'
const mergedConfig = merge({}, widgetConfig, fieldWidgetProps, valueFieldSettings);
return mergedConfig;
};