Skip to content

Commit

Permalink
import from json logic (#135)
Browse files Browse the repository at this point in the history
* Import from json logic
* Added jsType
  • Loading branch information
ukrbublik authored Jan 8, 2020
1 parent 6f6208c commit 875e984
Show file tree
Hide file tree
Showing 26 changed files with 858 additions and 354 deletions.
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
# Changelog
- 1.1.2
- Added import from [JsonLogic](http://jsonlogic.com)
- 1.1.1
- Optimized `$eq` and `$and` in MongoDb query export
- Fixed error if query value is empty
- Added API in readme
- 1.1.0
- Added [JsonLogic](http://jsonlogic.com) support
- Added export to [JsonLogic](http://jsonlogic.com)
- 1.0.12
- Added `sqlFormatFunc`, `mongoFormatFunc`, `renderBrackets`, `renderSeps` (for func), `funcs` (for field)
- 1.0.11
Expand Down
2 changes: 0 additions & 2 deletions CONFIG.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -371,7 +371,6 @@ where `AND` and `OR` - available conjuctions (logical operators). You can add `N
reversedOp: 'not_equal',
labelForFormat: '==',
cardinality: 1,
isUnary: false,
formatOp: (field, _op, value, _valueSrc, _valueType, opDef) => `${field} ${opDef.labelForFormat} ${value}`,
mongoFormatOp: (field, op, value) => ({ [field]: { '$eq': value } }),
},
Expand All @@ -384,7 +383,6 @@ where `AND` and `OR` - available conjuctions (logical operators). You can add `N
|key |required |default |meaning
|label |+ | |Label to be displayed in operators select component
|reversedOp |+ | |Opposite operator.
|isUnary | |false |true for `is_empty` operator only
|cardinality | |1 |Number of right operands (1 for binary, 2 for `between`)
|formatOp |+ | |Function for formatting query string, used to join operands into rule. +
`(string field, string op, mixed value, string valueSrc, string valueType, Object opDef, Object operatorOptions, bool isForDisplay) => string` +
Expand Down
15 changes: 10 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ For [antd v2](https://2x.ant.design/docs/react/introduce) (which has more compac
- Reordering (drag-n-drop) support for rules and groups of rules
- Using awesome [Ant Design](https://ant.design/) (but using custom widgets of another framework is possible)
- Export to MongoDb, SQL, [JsonLogic](http://jsonlogic.com) or your custom format
- Import from [JsonLogic](http://jsonlogic.com)
- TypeScript support (see [types](https://github.com/ukrbublik/react-awesome-query-builder/tree/master/modules/index.d.ts) and [demo in TS](https://github.com/ukrbublik/react-awesome-query-builder/tree/master/examples/demo))


Expand Down Expand Up @@ -151,7 +152,7 @@ class DemoQueryBuilder extends Component {
### `<Query />`
Props:
- `{...config}` - destructured query [`CONFIG`](https://github.com/ukrbublik/react-awesome-query-builder/tree/master/CONFIG.adoc)
- `value` - query value in [Immutable](https://immutable-js.github.io/immutable-js/) format
- `value` - query value in internal [Immutable](https://immutable-js.github.io/immutable-js/) format
- `onChange` - callback when value changed. Params: `value` (in Immutable format), `config`.
- `renderBuilder` - function to render query builder itself. Takes 1 param `props` you need to pass into `<Builder {...props} />`.

Expand All @@ -169,12 +170,12 @@ Wrapping in `div.query-builder-container` in not necessary, but if you want to m
### Utils
- Save, load:
#### getTree(immutableValue) -> Object
Convert query value from Immutable format to JS format.
Convert query value from internal Immutable format to JS format.
You can use it to save value on backend in `onChange` callback of `<Query>`.
#### loadTree(jsValue, config) -> Immutable
Convert query value from Immutable format to JS format.
Convert query value from JS format to internal Immutable format.
You can use it to load saved value from backend and pass as `value` prop to `<Query>` (don't forget to also apply `checkTree()`).
#### checkTree(immutableValue, config) -> immutableValue
#### checkTree(immutableValue, config) -> Immutable
Validate query value corresponding to config.
Invalid parts of query (eg. if field was removed from config) will be deleted.
- Export:
Expand All @@ -186,7 +187,11 @@ Wrapping in `div.query-builder-container` in not necessary, but if you want to m
Convert query value to SQL where string.
#### jsonLogicFormat(immutableValue, config) -> {logic, data, errors}
Convert query value to [JsonLogic](http://jsonlogic.com) format.
If there are no `errors`, `logic` will be rule object and `data` will contain all used fields with empty (null) values.
If there are no `errors`, `logic` will be rule object and `data` will contain all used fields with null values ("template" data).
- Import:
#### loadFromJsonLogic(jsonLogicObject, config) -> Immutable
Convert query value from [JsonLogic](http://jsonlogic.com) format to internal Immutable format.



## Config format
Expand Down
3 changes: 2 additions & 1 deletion examples/demo/config.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,8 @@ const funcs: Funcs = {
LOWER: {
label: 'Lowercase',
mongoFunc: '$toLower',
jsonLogic: ({str}) => ({ "method": [ str, "toLowerCase" ] }),
jsonLogic: "toLowerCase",
jsonLogicIsMethod: true,
returnType: 'text',
args: {
str: {
Expand Down
15 changes: 10 additions & 5 deletions examples/demo/demo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,24 @@ import React, {Component} from 'react';
import {
Query, Builder, BasicConfig, Utils,
//types:
ImmutableTree, Config, BuilderProps, JsonTree
ImmutableTree, Config, BuilderProps, JsonTree, JsonLogicTree
} from 'react-awesome-query-builder';
import throttle from 'lodash/throttle';
import loadedConfig from './config';
import loadedInitValue from './init_value';
import loadedInitLogic from './init_logic';

const stringify = JSON.stringify;
const {queryBuilderFormat, jsonLogicFormat, queryString, mongodbFormat, sqlFormat, getTree, checkTree, loadTree, uuid} = Utils;
const {queryBuilderFormat, jsonLogicFormat, queryString, mongodbFormat, sqlFormat, getTree, checkTree, loadTree, uuid, loadFromJsonLogic} = Utils;
const preStyle = { backgroundColor: 'darkgrey', margin: '10px', padding: '10px' };
const preErrorStyle = { backgroundColor: 'lightpink', margin: '10px', padding: '10px' };

const emptyInitValue: JsonTree = {id: uuid(), type: "group"};
const initValue: JsonTree = loadedInitValue && Object.keys(loadedInitValue).length > 0 ? loadedInitValue as JsonTree : emptyInitValue;
const initLogic: JsonLogicTree = loadedInitLogic && Object.keys(loadedInitLogic).length > 0 ? loadedInitLogic as JsonLogicTree : undefined;

const initTree = checkTree(loadTree(initValue), loadedConfig);
//const initTree = checkTree(loadFromJsonLogic(initLogic, loadedConfig), loadedConfig); // <- this will work same


interface DemoQueryBuilderState {
Expand All @@ -28,7 +32,7 @@ export default class DemoQueryBuilder extends Component<{}, DemoQueryBuilderStat
private config: Config;

state = {
tree: checkTree(loadTree(initValue), loadedConfig),
tree: initTree,
config: loadedConfig
};

Expand Down Expand Up @@ -100,11 +104,12 @@ export default class DemoQueryBuilder extends Component<{}, DemoQueryBuilderStat
<hr/>
<div>
<a href="http://jsonlogic.com/play.html" target="_blank">jsonLogicFormat</a>:
{ errors ?
{ errors.length > 0 &&
<pre style={preErrorStyle}>
{stringify(errors, undefined, 2)}
</pre>
:
}
{ !!logic &&
<pre style={preStyle}>
// Rule:<br />
{stringify(logic, undefined, 2)}
Expand Down
36 changes: 36 additions & 0 deletions examples/demo/init_logic.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
export default
{
"and": [
{
"==": [
{
"var": "user.login"
},
{
"method": [
{
"var": "user.firstName"
},
"toLowerCase"
]
}
]
},
{
"==": [
{
"var": "stock"
},
false
]
},
{
"==": [
{
"var": "slider"
},
35
]
}
]
}
4 changes: 2 additions & 2 deletions modules/components/Query.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -147,8 +147,8 @@ export default class QueryContainer extends Component {

// compare trees
const storeValue = this.state.store.getState().tree;
const isTreeChnaged = !immutableEqual(nextProps.value, this.props.value) && !immutableEqual(nextProps.value, storeValue);
if (isTreeChnaged) {
const isTreeChanged = !immutableEqual(nextProps.value, this.props.value) && !immutableEqual(nextProps.value, storeValue);
if (isTreeChanged) {
const nextTree = nextProps.value || defaultRoot({ ...nextProps, tree: null });
const validatedTree = validateAndFixTree(nextTree, null, nextConfig, oldConfig);
this.state.store.dispatch(
Expand Down
2 changes: 1 addition & 1 deletion modules/components/widgets/antd/Date.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export default class DateWidget extends PureComponent {

handleChange = (_value) => {
const {setValue, valueFormat} = this.props;
const value = _value && _value.isValid() ? _value.format(valueFormat) : null;
const value = _value && _value.isValid() ? _value.format(valueFormat) : undefined;
if (value || _value === null)
setValue(value);
}
Expand Down
2 changes: 1 addition & 1 deletion modules/components/widgets/antd/DateTime.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export default class DateTimeWidget extends PureComponent {

handleChange = (_value) => {
const {setValue, valueFormat} = this.props;
const value = _value && _value.isValid() ? _value.format(valueFormat) : null;
const value = _value && _value.isValid() ? _value.format(valueFormat) : undefined;
if (value || _value === null)
setValue(value);
}
Expand Down
7 changes: 5 additions & 2 deletions modules/components/widgets/antd/Time.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,11 @@ export default class TimeWidget extends PureComponent {


handleChange = (_value) => {
const {setValue, valueFormat} = this.props;
const value = _value && _value.isValid() ? _value.format(valueFormat) : null;
const {setValue, valueFormat, timeFormat} = this.props;
if (_value && _value.isValid() && timeFormat == 'HH:mm') {
_value.set({second:0, millisecond:0});
}
const value = _value && _value.isValid() ? _value.format(valueFormat) : undefined;
if (value || _value === null)
setValue(value);
}
Expand Down
22 changes: 16 additions & 6 deletions modules/config/basic.js
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,9 @@ const operators = {
} 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: (field, op, val) => ({ "in": [val, field] }),
jsonLogic: "in",
_jsonLogicIsRevArgs: true,
valueSources: ['value'],
},
not_like: {
Expand Down Expand Up @@ -212,7 +214,7 @@ const operators = {
'and'
],
reversedOp: 'not_between',
jsonLogic: (field, op, vals) => ({ "<=": [vals[0], field, vals[1]] }),
jsonLogic: "<=",
},
not_between: {
label: 'Not between',
Expand Down Expand Up @@ -254,7 +256,7 @@ const operators = {
'and'
],
reversedOp: 'range_not_between',
jsonLogic: (field, op, vals) => ({ "<=": [vals[0], field, vals[1]] }),
jsonLogic: "<=",
},
range_not_between: {
label: 'Not between',
Expand All @@ -274,7 +276,6 @@ const operators = {
reversedOp: 'range_between',
},
is_empty: {
isUnary: true,
label: 'Is empty',
labelForFormat: 'IS EMPTY',
sqlOp: 'IS EMPTY',
Expand All @@ -287,7 +288,6 @@ const operators = {
jsonLogic: "!",
},
is_not_empty: {
isUnary: true,
label: 'Is not empty',
labelForFormat: 'IS NOT EMPTY',
sqlOp: 'IS NOT EMPTY',
Expand Down Expand Up @@ -423,7 +423,7 @@ const operators = {
const prox = operatorOptions.get('proximity');
return `CONTAINS(${field}, 'NEAR((${_val1}, ${_val2}), ${prox})')`;
},
mongoFormatOp: (field, op, values) => (undefined), // not supported
mongoFormatOp: undefined, // not supported
jsonLogic: undefined, // not supported
options: {
optionLabel: "Near", // label on top of "near" selectbox (for config.settings.showLabels==true)
Expand All @@ -444,6 +444,7 @@ const operators = {
const widgets = {
text: {
type: "text",
jsType: "string",
valueSrc: 'value',
valueLabel: "String",
valuePlaceholder: "Enter string",
Expand All @@ -457,6 +458,7 @@ const widgets = {
},
number: {
type: "number",
jsType: "number",
valueSrc: 'value',
factory: (props) => <NumberWidget {...props} />,
valueLabel: "Number",
Expand All @@ -474,6 +476,7 @@ const widgets = {
},
slider: {
type: "number",
jsType: "number",
valueSrc: 'value',
factory: (props) => <SliderWidget {...props} />,
valueLabel: "Number",
Expand All @@ -487,6 +490,7 @@ const widgets = {
},
rangeslider: {
type: "number",
jsType: "number",
valueSrc: 'value',
factory: (props) => <RangeWidget {...props} />,
valueLabel: "Range",
Expand All @@ -505,6 +509,7 @@ const widgets = {
},
select: {
type: "select",
jsType: "string",
valueSrc: 'value',
factory: (props) => <SelectWidget {...props} />,
valueLabel: "Value",
Expand All @@ -519,6 +524,7 @@ const widgets = {
},
multiselect: {
type: "multiselect",
jsType: "array",
valueSrc: 'value',
factory: (props) => <MultiSelectWidget {...props} />,
valueLabel: "Values",
Expand All @@ -533,6 +539,7 @@ const widgets = {
},
date: {
type: "date",
jsType: "string",
valueSrc: 'value',
factory: (props) => <DateWidget {...props} />,
dateFormat: 'DD.MM.YYYY',
Expand All @@ -555,6 +562,7 @@ const widgets = {
},
time: {
type: "time",
jsType: "string",
valueSrc: 'value',
factory: (props) => <TimeWidget {...props} />,
timeFormat: 'HH:mm',
Expand All @@ -581,6 +589,7 @@ const widgets = {
},
datetime: {
type: "datetime",
jsType: "string",
valueSrc: 'value',
factory: (props) => <DateTimeWidget {...props} />,
timeFormat: 'HH:mm',
Expand All @@ -604,6 +613,7 @@ const widgets = {
},
boolean: {
type: "boolean",
jsType: "boolean",
valueSrc: 'value',
factory: (props) => <BooleanWidget {...props} />,
labelYes: "Yes",
Expand Down
Loading

0 comments on commit 875e984

Please sign in to comment.