From 2c0e2a743eef5975e985ec1ba7f54da9325130c5 Mon Sep 17 00:00:00 2001 From: Jake Boone Date: Sat, 20 Aug 2022 00:43:26 -0700 Subject: [PATCH 01/12] Freeze v4.5.2 documentation --- docs/api/querybuilder.mdx | 4 +- docusaurus.config.js | 4 + .../version-4.5.2/api/_ts_admonition.md | 5 + .../version-4.5.2/api/classnames.mdx | 155 ++++ versioned_docs/version-4.5.2/api/export.mdx | 514 +++++++++++ versioned_docs/version-4.5.2/api/import.mdx | 173 ++++ versioned_docs/version-4.5.2/api/misc.mdx | 92 ++ .../version-4.5.2/api/querybuilder.mdx | 799 ++++++++++++++++++ .../version-4.5.2/api/validation.mdx | 124 +++ .../version-4.5.2/api/valueeditor.mdx | 223 +++++ versioned_docs/version-4.5.2/compat.mdx | 98 +++ versioned_docs/version-4.5.2/intro.mdx | 167 ++++ .../tips/adding-removing-query-properties.mdx | 64 ++ .../version-4.5.2/tips/common-mistakes.mdx | 59 ++ .../tips/custom-bind-variables.mdx | 41 + .../tips/custom-with-fallback.mdx | 253 ++++++ .../version-4.5.2/tips/limit-groups.mdx | 100 +++ versioned_docs/version-4.5.2/tsconfig.json | 21 + versioned_docs/version-4.5.2/typescript.mdx | 242 ++++++ versioned_docs/version-4.5.2/umd.mdx | 62 ++ .../version-4.5.2-sidebars.json | 42 + versions.json | 3 + yarn.lock | 136 +-- 23 files changed, 3312 insertions(+), 69 deletions(-) create mode 100644 versioned_docs/version-4.5.2/api/_ts_admonition.md create mode 100644 versioned_docs/version-4.5.2/api/classnames.mdx create mode 100644 versioned_docs/version-4.5.2/api/export.mdx create mode 100644 versioned_docs/version-4.5.2/api/import.mdx create mode 100644 versioned_docs/version-4.5.2/api/misc.mdx create mode 100644 versioned_docs/version-4.5.2/api/querybuilder.mdx create mode 100644 versioned_docs/version-4.5.2/api/validation.mdx create mode 100644 versioned_docs/version-4.5.2/api/valueeditor.mdx create mode 100644 versioned_docs/version-4.5.2/compat.mdx create mode 100644 versioned_docs/version-4.5.2/intro.mdx create mode 100644 versioned_docs/version-4.5.2/tips/adding-removing-query-properties.mdx create mode 100644 versioned_docs/version-4.5.2/tips/common-mistakes.mdx create mode 100644 versioned_docs/version-4.5.2/tips/custom-bind-variables.mdx create mode 100644 versioned_docs/version-4.5.2/tips/custom-with-fallback.mdx create mode 100644 versioned_docs/version-4.5.2/tips/limit-groups.mdx create mode 100644 versioned_docs/version-4.5.2/tsconfig.json create mode 100644 versioned_docs/version-4.5.2/typescript.mdx create mode 100644 versioned_docs/version-4.5.2/umd.mdx create mode 100644 versioned_sidebars/version-4.5.2-sidebars.json create mode 100644 versions.json diff --git a/docs/api/querybuilder.mdx b/docs/api/querybuilder.mdx index 69330a2c6d..6c2e9147d0 100644 --- a/docs/api/querybuilder.mdx +++ b/docs/api/querybuilder.mdx @@ -770,7 +770,9 @@ Pass `true` to display a drag handle to the left of each group header and rule. :::caution -`react-querybuilder` uses [`react-dnd`](https://react-dnd.github.io/react-dnd/) to enable drag-and-drop features. If your application already uses `react-dnd`, you should import and render `` instead of ``. They are functionally the same, but the former will rely on your pre-existing `DndProvider` wrapper while the latter implements its own and will clash with a separate `DndProvider` higher up in the component tree (the error will typically look like this: "Cannot have two HTML5 backends at the same time."). +Drag-and-drop features are only enabled when `` is imported from the companion package [`@react-querybuilder/dnd`](https://www.npmjs.com/package/@react-querybuilder/dnd). [`react-dnd`](https://www.npmjs.com/package/react-dnd) and [`react-dnd-html5-backend`](https://www.npmjs.com/package/react-dnd-html5-backend) will also need to be installed. + +If your application already uses [`react-dnd`](https://react-dnd.github.io/react-dnd/), you should import and render `` instead of ``. They are functionally the same, but the former will rely on your pre-existing `` wrapper while the latter implements its own and will clash with a separate `` higher up in the component tree (the error will typically look like this: "Cannot have two HTML5 backends at the same time."). ::: diff --git a/docusaurus.config.js b/docusaurus.config.js index f559d77ded..8d5998c518 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -94,6 +94,10 @@ const config = { position: "right", }, // {to: '/blog', label: 'Blog', position: 'right'}, + { + type: "docsVersionDropdown", + position: "right", + }, { href: "https://github.com/react-querybuilder/react-querybuilder", "aria-label": "GitHub repository", diff --git a/versioned_docs/version-4.5.2/api/_ts_admonition.md b/versioned_docs/version-4.5.2/api/_ts_admonition.md new file mode 100644 index 0000000000..ffad60a58e --- /dev/null +++ b/versioned_docs/version-4.5.2/api/_ts_admonition.md @@ -0,0 +1,5 @@ +:::info + +Please refer to the [TypeScript](../typescript) page for information about the types and interfaces referenced below. + +::: diff --git a/versioned_docs/version-4.5.2/api/classnames.mdx b/versioned_docs/version-4.5.2/api/classnames.mdx new file mode 100644 index 0000000000..66bfc35dfa --- /dev/null +++ b/versioned_docs/version-4.5.2/api/classnames.mdx @@ -0,0 +1,155 @@ +--- +title: CSS classes +hide_table_of_contents: true +description: Visual guide to CSS classes for each component element +--- + +The `` component assigns [standard classes](./misc#defaults) to each element. In the (fully operational) query builder below, the `title` and `label` for each element have been set to the element's standard class. + +The following standard classnames are not visible below: + +- `.queryBuilder` (black outline; the outer-most `
`) +- `.ruleGroup` (maroon outline) +- `.ruleGroup-header` (purple outline) +- `.ruleGroup-body` (blue outline) +- `.rule.queryBuilder-disabled` (gray outline for locked/disabled rules) +- `.rule.queryBuilder-valid` (green outline for valid rules) +- `.rule.queryBuilder-invalid` (red outline for invalid rules) +- `.dndDragging` (applied to "preview" element while dragging) +- `.dndOver` (applied to "hovered over" element while dragging) + +import { QueryBuilderEmbed } from '@site/src/components/QueryBuilderEmbed'; +import { standardClassnames as sc } from 'react-querybuilder'; + + true, + }, + { + name: 'f2', + label: `.${sc.fields}`, + defaultValue: 'This rule is invalid', + validator: () => false, + }, + { + name: 'f3', + label: `.${sc.valueSource}`, + valueSources: ['value', 'field'], + }, + { + name: 'fb1', + label: `Value list`, + operators: [{ name: 'between', label: 'between' }], + valueSources: ['field', 'value'], + comparator: f => f.name === 'fb2', + }, + { + name: 'fb2', + label: `.${sc.valueListItem}`, + }, + ]} + combinators={[{ name: 'and', label: `.${sc.combinators}` }]} + getOperators={() => [{ name: '=', label: `.${sc.operators}` }]} + controlClassnames={{ + queryBuilder: 'rqb-structure', + }} + translations={{ + fields: { + title: `.${sc.fields}`, + }, + operators: { + title: `.${sc.operators}`, + }, + value: { + title: `.${sc.value}`, + }, + removeRule: { + label: `.${sc.removeRule}`, + title: `.${sc.removeRule}`, + }, + removeGroup: { + label: `.${sc.removeGroup}`, + title: `.${sc.removeGroup}`, + }, + addRule: { + label: `.${sc.addRule}`, + title: `.${sc.addRule}`, + }, + addGroup: { + label: `.${sc.addGroup}`, + title: `.${sc.addGroup}`, + }, + combinators: { + title: `.${sc.combinators}`, + }, + notToggle: { + label: `.${sc.notToggle}`, + title: `.${sc.notToggle}`, + }, + cloneRule: { + label: `.${sc.cloneRule}`, + title: `.${sc.cloneRule}`, + }, + cloneRuleGroup: { + label: `.${sc.cloneGroup}`, + title: `.${sc.cloneGroup}`, + }, + dragHandle: { + label: `.${sc.dragHandle}`, + title: `.${sc.dragHandle}`, + }, + lockRule: { + label: `.${sc.lockRule}`, + title: `.${sc.lockRule}`, + }, + lockGroup: { + label: `.${sc.lockGroup}`, + title: `.${sc.lockGroup}`, + }, + lockRuleDisabled: { + label: `.${sc.lockRuleDisabled}`, + title: `.${sc.lockRuleDisabled}`, + }, + lockGroupDisabled: { + label: `.${sc.lockGroupDisabled}`, + title: `.${sc.lockGroupDisabled}`, + }, + valueSourceSelector: { + title: `.${sc.valueSource}`, + }, + }} + defaultQuery={{ + combinator: 'and', + rules: [ + { field: 'f1', operator: '=', value: `.${sc.value}` }, + { + combinator: 'and', + rules: [ + { field: 'f1', operator: '=', value: 'This rule is valid' }, + { field: 'f2', operator: '=', value: 'This rule is invalid' }, + { + disabled: true, + combinator: 'and', + rules: [ + { + field: 'f1', + operator: '=', + value: `This rule's group is disabled, therefore this rule is also disabled`, + }, + ], + }, + ], + }, + { field: 'f3', operator: '=', value: 'f1' }, + { field: 'fb1', operator: 'between', value: 'fb2,fb2', valueSource: 'field' }, + ], + }} +/> diff --git a/versioned_docs/version-4.5.2/api/export.mdx b/versioned_docs/version-4.5.2/api/export.mdx new file mode 100644 index 0000000000..6a97bcb3c4 --- /dev/null +++ b/versioned_docs/version-4.5.2/api/export.mdx @@ -0,0 +1,514 @@ +--- +title: Export +description: Convert query builder objects to SQL, etc. +--- + +import TypeScriptAdmonition from './_ts_admonition.md'; + + + +Use the `formatQuery` function to export queries in various formats. The function signature is: + +```ts +function formatQuery( + query: RuleGroupTypeAny, + options?: ExportFormat | FormatQueryOptions +): string | ParameterizedSQL | ParameterizedNamedSQL | RQBJsonLogic; +``` + +`formatQuery` converts a given query into one of the following formats: + +- JSON (with or without `id`s) +- SQL `WHERE` clause +- Parameterized SQL (with anonymous or named parameters) +- MongoDB +- Common Expression Language (CEL) +- Spring Expression Language (SpEL) +- JsonLogic + +For the next few sections, assume the `query` variable has been defined as: + +```ts +const query: RuleGroupType = { + id: 'root', + combinator: 'and', + not: false, + rules: [ + { + id: 'rule1', + field: 'firstName', + value: 'Steve', + operator: '=', + }, + { + id: 'rule2', + field: 'lastName', + value: 'Vai', + operator: '=', + }, + ], +}; +``` + +:::tip + +_TL;DR: For best results, use the [default combinators and operators](./misc#defaults) or map custom combinators/operators to the defaults with [`transformQuery`](./misc#transformquery)._ + +While `formatQuery` technically accepts query objects of type `RuleGroupTypeAny` (i.e. `RuleGroupType` or `RuleGroupTypeIC`), it is not guaranteed to process a query correctly unless the query also conforms to the type `DefaultRuleGroupTypeAny` (i.e. `DefaultRuleGroupType` or `DefaultRuleGroupTypeIC`). + +In practice, this means that all `combinator` and `operator` properties in the query must match the `name` of an element in [`defaultCombinators` or `defaultOperators`](./misc#defaults), respectively. If you implement custom combinator/operator names, you can use the [`transformQuery` function](./misc#transformquery) to map your query properties to the defaults. + +For example, assume your implementation replaces the default "between" operator (`{ name: "between", label: "between" }`) with `{ name: "b/w", label: "b/w" }`. Any rules using this operator would have `operator: "b/w"` instead of `operator: "between"`. So if a query looked like this... + +```json +{ + "combinator": "and", + "rules": [ + { + "field": "someNumber", + "operator": "b/w", + "value": "12,14" + } + ] +} +``` + +...you could run it through `transformQuery` with the `operatorMap` option: + +```ts +const newQuery = transformQuery(query, { operatorMap: { 'b/w': 'between' } }); +// { +// "combinator": "and", +// "rules": [ +// { +// "field": "someNumber", +// "operator": "between", +// "value": "12,14" +// } +// ] +// } +``` + +The `newQuery` object would be ready for processing by `formatQuery`, including its special handling of the "between" operator. + +::: + +## Basic usage + +### JSON + +To export the internal query representation like what `react-querybuilder` passes to the `onQueryChange` callback, formatted by `JSON.stringify`, simply pass the query to `formatQuery`: + +```ts +formatQuery(query); +// or +formatQuery(query, 'json'); +``` + +The output will be a multi-line string representation of the query using 2 spaces for indentation. + +```ts +`{ + "id": "root", + "combinator": "and", + "not": false, + "rules": [ + { + "id": "rule1", + "field": "firstName", + "value": "Steve", + "operator": "=" + }, + { + "id": "rule2", + "field": "lastName", + "value": "Vai", + "operator": "=" + } + ] +}`; +``` + +### JSON without identifiers + +To export the internal query representation without formatting (single-line, no indentation) and without the `id` or `path` attributes on each object, use the "json_without_ids" format. This is useful if you need to serialize the query for storage. + +```ts +formatQuery(query, 'json_without_ids'); +``` + +Output: + +```ts +`{"combinator":"and","not":false,"rules":[{"field":"firstName","value":"Steve","operator":"="},{"field":"lastName","value":"Vai","operator":"="}]}`; +``` + +### SQL + +`formatQuery` can export SQL compatible with most RDBMS engines. To export a SQL `WHERE` clause, use the "sql" format. + +```ts +formatQuery(query, 'sql'); +``` + +Output: + +```ts +`(firstName = 'Steve' and lastName = 'Vai')`; +``` + +### Parameterized SQL + +To export a SQL `WHERE` clause with bind variables instead of explicit values, use the "parameterized" format. The output is an object with `sql` and `params` attributes. + +```ts +formatQuery(query, 'parameterized'); +``` + +Output: + +```json +{ + "sql": "(firstName = ? and lastName = ?)", + "params": ["Steve", "Vai"] +} +``` + +### Named parameters + +If anonymous parameters (aka bind variables) are not acceptable, `formatQuery` can output named parameters based on the field names. Use the "parameterized_named" format. The output object is similar to the "parameterized" format, but the `params` attribute is an object instead of an array. + +```ts +formatQuery(query, 'parameterized_named'); +``` + +Output: + +```json +{ + "sql": "(firstName = :firstName_1 and lastName = :lastName_1)", + "params": { + "firstName_1": "Steve", + "lastName_1": "Vai" + } +} +``` + +### MongoDB + +For MongoDB-compatible output, use the "mongodb" format. + +```ts +formatQuery(query, 'mongodb'); +``` + +Output: + +```ts +`{"$and":[{"firstName":{"$eq":"Steve"}},{"lastName":{"$eq":"Vai"}}]}`; +``` + +:::info + +The MongoDB export format does not support the inversion operator (setting `not: true` for a rule group), however rules _can_ be created using the `"!="` operator. + +::: + +### Common Expression Language + +For Common Expression Language (CEL) output, use the "cel" format. + +```ts +formatQuery(query, 'cel'); +``` + +Output: + +```ts +`firstName = "Steve" && lastName = "Vai"`; +``` + +### Spring Expression Language + +For Spring Expression Language (SpEL) output, use the "spel" format. + +```ts +formatQuery(query, 'spel'); +``` + +Output: + +```ts +`firstName == 'Steve' and lastName == 'Vai'`; +``` + +### JsonLogic + +The "jsonlogic" format produces an object that can be processed by the `jsonLogic.apply` function (see https://jsonlogic.com/). + +```ts +formatQuery(query, 'jsonlogic'); +``` + +Output: + +```json +{ "and": [{ "==": [{ "var": "firstName" }, "Steve"] }, { "==": [{ "var": "lastName" }, "Vai"] }] } +``` + +## Configuration + +An object can be passed as the second argument instead of a string to have more fine-grained control over the output. + +### Parse numbers + +Since HTML `` controls store values as strings (even when `type="number"`), exporting a query to various formats may produce a string representation of a value when a true numeric value is required or more appropriate. Set the `parseNumbers` option to `true` and `formatQuery` will attempt to convert all values to numbers, falling back to the original value if `parseFloat(value)` returns `NaN` (not a number). + +```ts +const query: RuleGroupType = { + combinator: 'and', + not: false, + rules: [ + { + field: 'digits', + operator: '=', + value: '20', + }, + { + field: 'age', + operator: 'between', + value: '26, 52', + }, + { + field: 'lastName', + operator: '=', + value: 'Vai', + }, + ], +}; + +// Default configuration - all values are strings: +formatQuery(query, { format: 'sql' }); +// Returns: "(digits = '20' and age between '26' and '52' and lastName = 'Vai')" + +// `parseNumbers: true` - numeric strings converted to actual numbers: +formatQuery(query, { format: 'sql', parseNumbers: true }); +// Returns: "(digits = 20 and age between 26 and 52 and lastName = 'Vai')" +``` + +:::info + +To avoid information loss, this option is more strict about what qualifies as "numeric" than [the standard `parseFloat` function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseFloat). To oversimplify a bit, `parseFloat` works with any string that _starts_ with a numeric sequence, ignoring the rest of the string beginning with the first non-numeric character. In contrast, when `parseNumbers` is `true`, `formatQuery` will only convert a `value` to a `number` if it appears to be numeric _in its entirety_ (after trimming whitespace). + +The following expressions all evaluate to `true`: + +```ts +parseFloat('000111abcdef') === 111; // everything from the 'a' on is ignored by `parseFloat` + +formatQuery( + { rules: [{ field: 'f', operator: '=', value: '000111abcdef' }] }, + { format: 'sql', parseNumbers: true } +) === "(f = '000111abcdef')"; // `value` contains non-numeric characters, so remains as-is + +formatQuery( + { rules: [{ field: 'f', operator: '=', value: ' 000111 ' }] }, + { format: 'sql', parseNumbers: true } +) === '(f = 111)'; // after trimming whitespace, `value` is wholly numeric +``` + +::: + +### Value processor + +If you need to control the way the value portion of the output is processed, you can specify a custom `valueProcessor` (only applicable for certain formats). + +```ts +const query: RuleGroupType = { + combinator: 'and', + not: false, + rules: [ + { + field: 'instrument', + operator: 'in', + value: ['Guitar', 'Vocals'], + }, + { + field: 'lastName', + operator: '=', + value: 'Vai', + }, + ], +}; + +const customValueProcessor = (field, operator, value) => { + if (operator === 'in') { + // Assuming `value` is an array, such as from a multi-select + return `(${value.map(v => `"${v.trim()}"`).join(', ')})`; + } + // Fall back to the default value processor for other operators + return defaultValueProcessor(field, operator, value); +}; + +formatQuery(query, { format: 'sql', valueProcessor: customValueProcessor }); +// Returns: "(instrument in ('Guitar', 'Vocals') and lastName = 'Vai')" +``` + +:::caution + +When using the "mongodb", "cel", or "spel" export formats, `valueProcessor` functions must produce the entire MongoDB rule object or CEL/SpEL rule string, not just the expression on the right-hand side of the operator like with SQL-based formats. + +::: + +#### Enhanced `valueProcessor` behavior + +`formatQuery` will invoke custom `valueProcessor` functions with different arguments based on the function's `length` property, which is the number of arguments a function accepts excluding those with default values. + +If the `valueProcessor` function accepts fewer than three (3) arguments, it will be called like this: + +```ts +valueProcessor(rule, { parseNumbers }); +``` + +The first argument is the `RuleType` object directly from the query. The second argument is of type `ValueProcessorOptions`, which is equivalent to `Pick`). + +Invoking `valueProcessor` with the full `RuleType` object provides access to much more information about each rule. Standard properties that were previously unavailable include `path`, `id`, and `disabled`, but any custom properties will also be accessible. + +The default value processors have not changed from the legacy function signature, but alternate functions using the new `fn(rule, options)` signature are now available: + +- `defaultValueProcessorByRule` (for SQL-based formats) +- `defaultValueProcessorCELByRule` +- `defaultValueProcessorMongoDBByRule` +- `defaultValueProcessorSpELByRule` + +To maintain the legacy signature (`valueProcessor(field, operator, value, valueSource)`), make sure your custom `valueProcessor` function accepts at least three arguments _with no default values_ (i.e. do not use `=` for the first three arguments). For example, the following code will log `length: 1` and the function would be called with the `(rule, options)` arguments: + +```ts +const valueProcessor = (field: string, operator = '=', value = '') => '...'; +console.log(`length: ${valueProcessor.length}`); +``` + +Removing `= ...` from the `operator` and `value` argument declarations would increase the function's `length` to 3. + +If you use TypeScript, these conditions will be enforced automatically. + +### Quote field names + +Some database engines wrap field names in backticks (`` ` ``). This can be configured with the `quoteFieldNamesWith` option. + +```ts +formatQuery(query, { format: 'sql', quoteFieldNamesWith: '`' }); +// Returns: "(`firstName` = 'Steve' and `lastName` = 'Vai')" +``` + +### Parameter prefix + +If the "parameterized_named" format is used, configure the parameter prefix used in the `sql` string with the `paramPrefix` option (should the default ":" be inappropriate). + +```ts +const p = formatQuery(query, { + format: 'parameterized_named', + paramPrefix: '$', +}); +// p.sql === "(firstName = $firstName_1 and lastName = $lastName_1)" +``` + +### Fallback expression + +The `fallbackExpression` is a string that will be part of the output when `formatQuery` can't quite figure out what to do for a particular rule or group. The intent is to maintain valid syntax while (hopefully) not detrimentally affecting the query criteria. If not provided, the default fallback expression for the given format will be used (see table below). + +| Format | Default `fallbackExpression` | +| --------------------- | ---------------------------- | +| "sql" | `"(1 = 1)"` | +| "parameterized" | `"(1 = 1)"` | +| "parameterized_named" | `"(1 = 1)"` | +| "mongodb" | `"{$and:[{$expr:true}]}"` | +| "cel" | `"1 == 1"` | +| "spel" | `"1 == 1"` | +| "jsonlogic" | `false` | + +### Value sources + +When the `valueSource` property for a rule is set to "field", `formatQuery` will place the bare, unquoted value (which should be a valid field name) in the result for the "sql", "parameterized", "parameterized_named", "mongodb", "cel", and "spel" formats. No parameters will be generated for such rules. + +```ts +const pf = formatQuery( + { + combinator: 'and', + rules: [ + { field: 'firstName', operator: '=', value: 'lastName', valueSource: 'field' }, + { field: 'firstName', operator: 'beginsWith', value: 'middleName', valueSource: 'field' }, + ], + }, + 'parameterized_named' +); +``` + +Output: + +```json +{ + "sql": "(firstName = lastName and firstName like middleName || '%')", + "params": {} +} +``` + +### Placeholder values + +Any rule where the `field` or `operator` matches the placeholder value (default `"~"`) will be excluded from the output for most export formats (see [Automatic validation](#automatic-validation)). To use a different string as the placeholder value, set the `placeholderFieldName` and/or `placeholderOperatorName` options. These correspond to the `fields.placeholderName` and `operators.placeholderName` properties on the main component's [`translations` prop](./querybuilder#translations) object. + +## Validation + +The validation options (`validator` and `fields` – see [Validation](./validation) for more information) only affect the output when `format` is not "json" or "json_without_ids". If the `validator` function returns `false`, the `fallbackExpression` will be returned. Otherwise, groups and rules marked as invalid (either by the validation map produced by the `validator` function or the result of the field-based `validator` function) will be ignored. + +Example: + +```ts +const query: RuleGroupType = { + id: 'root', + rules: [ + { + id: 'r1', + field: 'firstName', + value: '', + operator: '=', + }, + { + id: 'r2', + field: 'lastName', + value: 'Vai', + operator: '=', + }, + ], + combinator: 'and', + not: false, +}; + +// Query is invalid based on the validator function +formatQuery(query, { + format: 'sql', + validator: () => false, +}); +// Returns: "(1 = 1)" <-- see `fallbackExpression` option + +// Rule "r1" is invalid based on the validation map +formatQuery(query, { + format: 'sql', + validator: () => ({ r1: false }), +}); +// Returns: "(lastName = 'Vai')" <-- skipped `firstName` rule with `id === 'r1'` + +// Rule "r1" is invalid based on the field validator for `firstName` +formatQuery(query, { + format: 'sql', + fields: [{ name: 'firstName', validator: () => false }], +}); +// Returns: "(lastName = 'Vai')" <-- skipped `firstName` rule because field validator returned `false` +``` + +### Automatic validation + +A basic form of validation will be used by `formatQuery` for the "in", "notIn", "between", and "notBetween" operators when the output format is not "json" or "json_without_ids". This validation is used regardless of the presence of any `validator` options at either the query level or field level: + +- Rules that specify an "in" or "notIn" `operator` will be deemed invalid if the rule's `value` is neither an array with at least one element (i.e. `value.length > 0`) nor a non-empty string. +- Rules that specify a "between" or "notBetween" `operator` will be deemed invalid if the rule's `value` is neither an array with length of at least two (`value.length >= 2`) nor a string with at least one comma that isn't the first or last character (i.e. `value.split(',').length >= 2`, and neither element is an empty string). +- Rules where either the `field` or `operator` match their respective placeholder will be deemed invalid (`field === placeholderFieldName || operator === placeholderOperatorName`). diff --git a/versioned_docs/version-4.5.2/api/import.mdx b/versioned_docs/version-4.5.2/api/import.mdx new file mode 100644 index 0000000000..06fabdf2f7 --- /dev/null +++ b/versioned_docs/version-4.5.2/api/import.mdx @@ -0,0 +1,173 @@ +--- +title: Import +description: Convert SQL and other formats to query builder objects +--- + +import TypeScriptAdmonition from './_ts_admonition.md'; + + + +## SQL + +Use the `parseSQL` function to convert SQL `SELECT` statements into a format suitable for the `` component's `query` prop. The function signature is: + +```ts +function parseSQL(sql: string, options?: ParseSQLOptions): RuleGroupTypeAny; +``` + +`parseSQL` takes a SQL `SELECT` statement (either the full statement or the `WHERE` clause by itself). Try it out in the [demo](https://react-querybuilder.js.org/react-querybuilder/) by clicking the "Load from SQL" button. + +The optional second parameter to `parseSQL` is an options object that configures how the function handles named or anonymous bind variables within the SQL string. + +### Basic usage + +Running any of the following statements will produce the same result (see below): + +```ts +parseSQL(`SELECT * FROM t WHERE firstName = 'Steve' AND lastName = 'Vai'`); + +parseSQL(`SELECT * FROM t WHERE firstName = ? AND lastName = ?`, { + params: ['Steve', 'Vai'], +}); + +parseSQL(`SELECT * FROM t WHERE firstName = :p1 AND lastName = :p2`, { + params: { p1: 'Steve', p2: 'Vai' }, +}); + +parseSQL(`SELECT * FROM t WHERE firstName = $p1 AND lastName = $p2`, { + params: { p1: 'Steve', p2: 'Vai' }, + paramPrefix: '$', +}); +``` + +Output (`RuleGroupType`): + +```json +{ + "combinator": "and", + "rules": [ + { + "field": "firstName", + "operator": "=", + "value": "Steve" + }, + { + "field": "lastName", + "operator": "=", + "value": "Vai" + } + ] +} +``` + +## Common Expression Language (CEL) + +`parseCEL` takes a [CEL](https://github.com/google/cel-spec) string and converts it to `RuleGroupType`. + +Click the "Import from CEL" button in [the demo](https://react-querybuilder.js.org/react-querybuilder) to try it out. + +## JsonLogic + +`parseJsonLogic` takes a [JsonLogic](https://jsonlogic.com/) object and converts it to `RuleGroupType`. + +Click the "Import from JsonLogic" button in [the demo](https://react-querybuilder.js.org/react-querybuilder) to try it out. + +## Configuration + +### Lists as arrays + +To generate actual arrays instead of comma-separated strings for lists of values following `IN` and `BETWEEN` operators, use the `listsAsArrays` option. + +```ts +parseSQL(`SELECT * FROM t WHERE lastName IN ('Vai', 'Vaughan') AND age BETWEEN 20 AND 100`, { + listsAsArrays: true; +}); +``` + +Output: + +```json +{ + "combinator": "and", + "rules": [ + { + "field": "lastName", + "operator": "in", + "value": ["Vai", "Vaughan"] + }, + { + "field": "age", + "operator": "between", + "value": [20, 100] + } + ] +} +``` + +### Independent combinators + +When the `independentCombinators` option is `true`, `parse*` functions will output a query with combinator identifiers between sibling rules/groups. + +```ts +parseSQL(`SELECT * FROM t WHERE firstName = 'Steve' AND lastName = 'Vai'`, { + independentCombinators: true, +}); +``` + +Output (`RuleGroupTypeIC`): + +```json +{ + "rules": [ + { + "field": "firstName", + "operator": "=", + "value": "Steve" + }, + "and", + { + "field": "lastName", + "operator": "=", + "value": "Vai" + } + ] +} +``` + +### Fields as value source + +When the `fields` option (which accepts the same types as the [`fields` prop](./querybuilder#fields)) is provided, and _only_ if it is provided, then `parse*` functions will validate clauses that have a field identifier to the right of the operator instead of a primitive value. A `getValueSources` function (with the same signature as the [prop of the same name](./querybuilder#getvaluesources)) can also be provided to help validate rules. + +In order for such a rule to be considered valid, one of the following must be an array that includes the string "field": 1) the `getValueSources` return value, 2) the field's `valueSources` property return value, or 3) the field's `valueSources` property itself. + +```ts +parseSQL(`SELECT * FROM t WHERE firstName = lastName`, { + fields: [ + { name: 'firstName', label: 'First Name' }, + { name: 'lastName', label: 'Last Name' }, + ], + getValueSources: () => ['value', 'field'], +}); +``` + +Output: + +```json +{ + "combinator": "and", + "rules": [ + { + "field": "firstName", + "operator": "=", + "value": "lastName", + "valueSource": "field" + } + ] +} +``` + +:::note + +`parse*` functions will only validate clauses where "field" is the _only_ value source. Operators that take multiple values, like "between" and "in", must only have field names to the right of the operator, not a mix of field names and primitive values. + +::: diff --git a/versioned_docs/version-4.5.2/api/misc.mdx b/versioned_docs/version-4.5.2/api/misc.mdx new file mode 100644 index 0000000000..a46c26c9ff --- /dev/null +++ b/versioned_docs/version-4.5.2/api/misc.mdx @@ -0,0 +1,92 @@ +--- +title: Miscellaneous +hide_table_of_contents: true +description: Assorted utilities and other exports +--- + +import TypeScriptAdmonition from './_ts_admonition.md'; + + + +## Utilities + +### `defaultValidator` + +```ts +function defaultValidator(query: RuleGroupTypeAny): { + [id: string]: { valid: boolean; reasons?: string[] }; +}; +``` + +Pass `validator={defaultValidator}` to automatically validate groups (rules will be ignored). A group will be marked invalid if either 1) it has no child rules or groups (`query.rules.length === 0`), or 2) it has a missing/invalid `combinator` and more than one child rule or group (`rules.length >= 2`). + +You can see an example of the default validator in action in the [demo](https://react-querybuilder.js.org/react-querybuilder/) if you check the ["Use validation" option](https://react-querybuilder.js.org/react-querybuilder/#validateQuery=true). Empty groups will have bold text on their "+Rule" button and a description of the situation where the rules normally appear. + +### `findPath` + +```ts +function findPath(path: number[], query: RuleGroupTypeAny): RuleType | RuleGroupTypeAny; +``` + +`findPath` is a utility function for finding the rule or group within the query hierarchy that has a given `path`. Useful in custom [`onAddRule`](./querybuilder#onaddrule) and [`onAddGroup`](./querybuilder#onaddgroup) functions. + +### `convertQuery` + +```ts +function convertQuery(query: RuleGroupType): RuleGroupTypeIC; +// OR +function convertQuery(query: RuleGroupTypeIC): RuleGroupType; +``` + +`convertQuery` toggles a query between the conventional `RuleGroupType` structure (with combinators at the group level) and the "independent combinators" structure (`RuleGroupTypeIC`, used with the [`independentCombinators` prop](./querybuilder#independentcombinators)). + +`convertToIC` and `convertFromIC` do the same thing as `convertQuery`, but only in one direction. + +### `transformQuery` + +```ts +function transformQuery(query: RuleGroupTypeAny, options: QueryTransformerOptions): any; +``` + +This function recursively steps through nested `rules` arrays in a `RuleGroupType` or `RuleGroupTypeIC`, passing each `RuleType` object to a provided `ruleProcessor` function. Available options include: + +- `ruleProcessor`: Custom processing for each rule. +- `ruleGroupProcessor`: Custom processing for each rule group. (The `rules` property will be overwritten.) +- `propertyMap`: Keys in the rule or group objects that match keys in this object will be renamed to the corresponding value. +- `combinatorMap`: Best explained with an example: `{and: "&&", or: "||"}` would translate "and"/"or" combinators to "&&"/"||", respectively. +- `operatorMap`: Convert operators that match the keys in this object to the corresponding values, e.g. `{"=": "=="}`. +- `deleteRemappedProperties`: Defaults to `true`; pass `false` to leave the remapped properties _and_ the original properties in the resulting object. + +See the [test suite](https://github.com/react-querybuilder/react-querybuilder/blob/main/packages/react-querybuilder/src/utils/transformQuery.test.ts) for example usage. + +## Query tools + +Several methods are available to assist with manipulation of query objects outside the context of the `` component: + +- `add` - adds a rule or group (and an independent combinator if necessary to keep the query valid) +- `remove` - removes a rule or group (and the previous independent combinator if one exists) +- `update` - updates a property of a rule or group, or updates an independent combinator +- `move` - moves (or clones, with new `id` and `path`) a rule or group to a new location in the query tree + +These methods are used by the `` component itself, so they are guaranteed to achieve the same result as a corresponding UI-based update. + +## Defaults + +The following default configuration objects are exported for convenience. + +- `defaultCombinators` (see [`combinators` prop](./querybuilder#combinators)) +- `defaultOperators` (see [`operators` prop](./querybuilder#operators)) +- `defaultTranslations` (see [`translations` prop](./querybuilder#translations)) +- `defaultValueProcessor` and variants for non-SQL formats (see [Export](./export) > [Value processor](./export#value-processor)) +- `defaultFields` (see [`fields` prop](./querybuilder#fields)) +- `standardClassnames` (see [CSS classes](./classnames)) + +The default components are also exported: + +- `ActionElement` - used for action buttons (to add rules, remove groups, etc.) +- `DragHandle` - used for the drag handle on rules and group headers +- `NotToggle` - used for the "Invert this group" toggle switch +- `Rule` - the default rule component +- `RuleGroup` - the default rule group component +- `ValueEditor` - the default `valueEditor` component +- `ValueSelector` - used for drop-down lists (combinator, field, and operator selectors) diff --git a/versioned_docs/version-4.5.2/api/querybuilder.mdx b/versioned_docs/version-4.5.2/api/querybuilder.mdx new file mode 100644 index 0000000000..69330a2c6d --- /dev/null +++ b/versioned_docs/version-4.5.2/api/querybuilder.mdx @@ -0,0 +1,799 @@ +--- +title: +description: Props and other configuration for the main component +--- + +import TypeScriptAdmonition from './_ts_admonition.md'; + + + +The default export of `react-querybuilder` is the `` React component. + +All props are optional, but as stated in the [getting started guide](../intro), the query builder is really only useful when, at a minimum, the `fields` prop is defined. + +:::note + +When you see `RuleGroupTypeAny` below (e.g. for [query](#query), [defaultQuery](#defaultquery), and [onQueryChange](#onquerychange)), that means the type must either be `RuleGroupType` or `RuleGroupTypeIC`. However, if the type is `RuleGroupTypeIC`, then the [`independentCombinators` prop](#independentcombinators) must be set to `true`. Likewise, if the type is `RuleGroupType` then `independentCombinators` must be `false` or `undefined`. + +::: + +## Props + +### `fields` + +`Field[] | OptionGroup[] | Record` + +The array of [fields](../typescript#fields) that should be used or an array of [option groups](../typescript#miscellaneous) containing arrays of fields. (Alternatively, `fields` can be an object where the keys correspond to each field `name` and the values are the field definitions. If `fields` is an object, then the `options` array passed to the [`fieldSelector` component](#fieldselector) will be sorted alphabetically by the `label` property.) + +:::tip + +Field objects can also contain custom properties. Each field object will be passed in its entirety to the appropriate `OperatorSelector` and `ValueEditor` components as the `fieldData` prop (see the section on [`controlElements`](#controlelements)). + +::: + +### `onQueryChange` + +`(query: RuleGroupTypeAny) => void` + +This is a callback function that is invoked anytime the query configuration changes. The `query` is provided as an object of type `RuleGroupType` by default: + +```json +{ + "combinator": "and", + "not": false, + "rules": [ + { + "field": "firstName", + "operator": "=", + "value": "Steve" + }, + { + "field": "lastName", + "operator": "=", + "value": "Vai" + }, + { + "combinator": "and", + "rules": [ + { + "field": "age", + "operator": ">", + "value": "30" + } + ] + } + ] +} +``` + +If the `independentCombinators` prop is provided, then the `query` argument will be of type `RuleGroupTypeIC`, which looks like this: + +```json +{ + "not": false, + "rules": [ + { + "field": "firstName", + "operator": "=", + "value": "Steve" + }, + "and", + { + "field": "lastName", + "operator": "=", + "value": "Vai" + }, + "and", + { + "rules": [ + { + "field": "age", + "operator": ">", + "value": "30" + } + ] + } + ] +} +``` + +### `query` + +`RuleGroupTypeAny` + +The query is an object of type `RuleGroupType` (or `RuleGroupTypeIC`, if [`independentCombinators`](#independentcombinators) is `true`). If this prop is provided, `` will be a controlled component. + +The `query` prop follows the same format as the parameter passed to the [`onQueryChange`](#onquerychange) callback since they are meant to be used together to control the component. See [examples](https://github.com/react-querybuilder/react-querybuilder/blob/main/examples). + +### `defaultQuery` + +`RuleGroupTypeAny` + +The initial query when `` is uncontrolled. + +:::caution + +Do not provide both `query` and `defaultQuery` props. To use `` as a controlled component, provide and manage the `query` prop in combination with the `onQueryChange` callback. Use `defaultQuery` (or neither query prop) to render an uncontrolled component. + +If both props are provided, TypeScript will throw an error during compilation and an error will be logged to the console during runtime (in "development" mode only). Errors will also be logged to the console if the `query` prop is defined during one render and undefined in the next, or vice versa. + +::: + +### `context` + +`any` + +A "bucket" for passing arbitrary props down to custom components (default components will ignore this prop). The `context` prop is passed to each and every component, so it's accessible anywhere in the `QueryBuilder` component tree. + +### `operators` + +`NameLabelPair[] | OptionGroup[]` + +The array of operators that should be used. The default operators include: + +```ts +[ + { name: '=', label: '=' }, + { name: '!=', label: '!=' }, + { name: '<', label: '<' }, + { name: '>', label: '>' }, + { name: '<=', label: '<=' }, + { name: '>=', label: '>=' }, + { name: 'contains', label: 'contains' }, + { name: 'beginsWith', label: 'begins with' }, + { name: 'endsWith', label: 'ends with' }, + { name: 'doesNotContain', label: 'does not contain' }, + { name: 'doesNotBeginWith', label: 'does not begin with' }, + { name: 'doesNotEndWith', label: 'does not end with' }, + { name: 'null', label: 'is null' }, + { name: 'notNull', label: 'is not null' }, + { name: 'in', label: 'in' }, + { name: 'notIn', label: 'not in' }, + { name: 'between', label: 'between' }, + { name: 'notBetween', label: 'not between' }, +]; +``` + +### `combinators` + +`NameLabelPair[] | OptionGroup[]` + +The array of combinators that should be used for RuleGroups. The default set includes: + +```ts +[ + { name: 'and', label: 'AND' }, + { name: 'or', label: 'OR' }, +]; +``` + +### `controlElements` + +```ts +interface Controls { + addGroupAction?: React.ComponentType; + addRuleAction?: React.ComponentType; + cloneGroupAction?: React.ComponentType; + cloneRuleAction?: React.ComponentType; + combinatorSelector?: React.ComponentType; + dragHandle?: React.ForwardRefExoticComponent< + DragHandleProps & React.RefAttributes + >; + fieldSelector?: React.ComponentType; + notToggle?: React.ComponentType; + operatorSelector?: React.ComponentType; + removeGroupAction?: React.ComponentType; + removeRuleAction?: React.ComponentType; + rule?: React.ComponentType; + ruleGroup?: React.ComponentType; + valueEditor?: React.ComponentType; + valueSourceSelector?: React.ComponentType; +} +``` + +This is a custom controls object that allows you to override the default control elements. The following control overrides are supported: + +#### `addGroupAction` + +By default a `