react-awesome-query-builder

User-friendly React component to build queries.

Forked from https://github.com/fubhy/react-query-builder

Inspired by jQuery QueryBuilder

Using awesome Ant Design for widgets

Features

  • Highly configurable
  • Fields can be of type:
    • simple (string, number, bool, date/time/datetime, list)
    • structs (will be displayed in selectbox as tree of members)
    • custom type (dev should add its own widget component for this) (it's not complex, you can add slider for example)
  • Comparison operators can be:
    • binary (== != < > ..)
    • unary (is empty, is null)
    • 'between' (for numbers)
    • complex operators like 'proximity'
  • Values of fields can be compared with values -or- another fields (of same type)
  • Reordering support for rules and groups of rules
  • Using awesome Ant Design

Demo

Live Demo

Screenshot

Install & use

Install: npm i react-awesome-query-builder

See examples/demo as example of usage and configuration.

For full reordering support you need to add class query-builder-container for dom-element which is holding your querybuilder component AND has scrolling. If there is no such dom-element (only body) you can do nothing.

Use

import React, {Component} from 'react';
import {Query, Builder, Utils as QbUtils} from 'react-awesome-query-builder';
import config from './config'; //see below 'Config format'
import 'react-awesome-query-builder/css/styles.scss';
import 'react-awesome-query-builder/css/compact_styles.scss';
import 'react-awesome-query-builder/css/denormalize.scss';

class DemoQueryBuilder extends Component {
    render() {                
        return (
            <div>
                <Query 
                  {...config} 
                  //you can pass object here, see treeJSON at onChange
                  //value=transit.fromJSON(treeJSON)
                  get_children={this.getChildren}
                  onChange={this.onChange}
                ></Query>
            </div>
        );
    }
    
    getChildren(props) {
        return (
            <div>
                <div className="query-builder">
                    <Builder {...props} />
                </div>
                <div>Query string: {QbUtils.queryString(props.tree, props.config)}</div>
            </div>
        )
    }
    
    onChange(tree) {
      //here you can save tree object: 
      //var treeJSON = transit.toJSON(tree)
    }
}

Config format

import {Widgets, Operators} from 'react-awesome-query-builder';
const {
    TextWidget,
    NumberWidget,
    SelectWidget,
    MultiSelectWidget,
    DateWidget,
    BooleanWidget,
    TimeWidget,
    DateTimeWidget,
    ValueFieldWidget
} = Widgets;
import en_US from 'antd/lib/locale-provider/en_US';

export default {
  conjunctions: {
    'AND': {
      label: 'And', //label for conjunctions swicther
      //(for building query string) function to join rules into group
      // children - list of already formatted queries (strings) to be joined with conjuction
      // isForDisplay - false by default, for building query string for SQL/expression/etc., 
      //  true can be used to format query string displayed on collapsed query group 
      //  (not used for now, see Issue #2)
      formatConj: (Immultable.List children, string conj, bool not, bool isForDisplay) => string,
    },
    'OR': ...same as for 'AND'
  },
  
  fields: {
    //Example of atomic field:
    name: {
      label: 'Quantity',
      type: 'number', //one of types described below in section 'types'
      //Settings for widgets
      // Available settings for Number widget: min, max, step
      fieldSettings: {
          min: 2,
      },
      //List of values for Select widget
      listValues: {
        //<key>: <label to display at list of options>,
        yellow: 'Yellow',
        green: 'Green',
      },
      //(optional) You can override here some options of config of corresponding type:
      // 'operators', 'defaultOperator', 'widgets', 'valueSources' (see below at section 'types')
    },
    //Example of special struct field:
    members: { //key of field
      label: 'Members', //label to display at list of fields
      type: '!struct', //special type for struct
      subfields: { //only for type == '!struct'
        subname: { //key of subfield
          label: 'Subname', //label for list of fields
          //label for field menu's toggler (for config.renderFieldAndOpAsDropdown == true)
          label2: 'MemberName',
          type: 'text', //one of types described below in section 'types'
        },
      },
    },
    ...other fields
  },
  
  types: {
    number: { //type key
      //(optional) Values of fields can be compared with values or another fields
      // (see settings.valueSourcesInfo). If you want to compare values of this type 
      // only with values or other fields of this type, edit:
      valueSources: ['value'],
      //Available widgets for type and its configs:
      widgets: {
        number: { //widget key, see section 'widgets' below
          //List of operators can be applied to this type (see section 'operators' below)
          operators: ['greater', 'less'],
          defaultOperator: 'greater', //default operator to be selected for this type
          //Config for this widget (all optional):
          widgetProps: {
            //for example, here you can overwrire 'valueLabel', 'valuePlaceholder', 
            // for date/time: 'timeFormat', 'dateFormat', 'valueFormat'

            //also you can pass props directly to widget, for example enable search for Select widget:
            customProps: {
                showSearch: true
            }
          },
          //Config for operators for this widget (all optional):
          opProps: {
            between: { //operator key
              //for example, here you can overwrire 'valueLabels'
            },
            ...other ops
          },
        },
        //Most of types can have only 1 widget, but for list there can be 2: 
        // single-select widget (for op ==) and multi-select widget (for op 'in')
        ...other widgets if applicable
        //'field' is special widget to compare values of field of this type
        // with another fields (of this type)
        field: {
          ...you can overwrire 'operators' for example
        }
      }
    },
    ...other types
  },
  
  operators: {
    equal: { //operator key
      label: '==', //label for selectbox
      labelForFormat: '==', //string used for formatting query, only if 'formatOp' is not present
      reversedOp: 'not_equal', //operator opposite to current
      cardinality: 1, //number of right operands (1 for binary, 2 for 'between')
      isUnary: true,
      //(for building query string) function to format rule
      // value - string (already formatted value) for cardinality==1 
      // -or- Immutable.List of strings for cardinality!=1
      formatOp: (string field, string op, mixed value, string valueSrc, string valueType, 
        Object opDef, Object operatorOptions, bool isForDisplay) => string,
      //for cardinality==2 ('between')
      valueLabels: ['Value from', {label: 'Value to', placeholder: 'Enter value to'}],
      textSeparators: [null, 'and'],
      ...also see examples/demo for config of 'proximity' operator
    },
  },
  
  widgets: {
    text: {
      type: "text", //see 'types' section
      valueSrc: 'value', //'value' or 'field' (only for special 'field' widget)
      factory: (props) => <TextWidget {...props} />, //React component
      //(for building query string) function to format widget's value
      formatValue: (mixed val, Object fieldDef, Object wgtDef, bool isForDisplay) => string,
      //func to validate widget's value
      validateValue: (mixed val, Object fieldDef) => bool,
      //Options:
      // common:
      valueLabel: "Text",
      valuePlaceholder: "Enter text",
      // for date/time widgets:
      timeFormat: 'HH:mm',
      dateFormat: 'YYYY-MM-DD',
      valueFormat: 'YYYY-MM-DD HH:mm',
      // ...for your custom widgets you can add here your options
      // also you can pass customProps, for example to enable search for select widget:
      customProps: { showSearch: true }
    },
    ...other widgets (you can add your custom ones here)
    ...also there should be special 'field' widget, see examples/demo
  },
  
  settings: {
    //Locale used for AntDesign widgets
    locale: {
        short: 'en',
        full: 'en-US',
        antd: en_US,
    },
    //To shorten long labels of fields/values (by length, i.e. number of chars)
    maxLabelsLength: 50,
    //Placement of antdesign's dropdown pop-up menu (default: 'bottomLeft')
    dropdownPlacement: 'bottomRight',
    //Don't show conjunctions switcher for only 1 rule?
    hideConjForOne: true,
    //Size of AntDesign components
    renderSize: 'small',
    //How to render conjunctions switcher? true - use RadioGroup, false - use ButtonGroup
    renderConjsAsRadios: false,
    //How to render fields/ops list? true - use Dropdown/Menu, false - use Select
    renderFieldAndOpAsDropdown: false,
    //You can pass props to Select field widget
    customFieldSelectProps: {
        showSearch: true
    },
    // You can change the position of the group actions to the following:
    // oneOf [topLeft, topCenter, topRight (default), bottomLeft, bottomCenter, bottomRight]
    groupActionsPosition: 'topRight', 
    //Strategies for selecting operator for new field (used by order until success)
    // 'default' (default if present), 'keep' (keep prev from last field), 'first', 'none'
    setOpOnChangeField: ['keep', 'default'],
    //Clear value on field change? false - if prev & next fields have same type (widget), keep
    clearValueOnChangeField: false,
    //Clear value on operator change?
    clearValueOnChangeOp: false,
    //?
    setDefaultFieldAndOp: false,
    //Max nesting for rule groups
    maxNesting: 10,
    //Separaor for struct fields
    fieldSeparator: '.', //also used for formatting
    fieldSeparatorDisplay: '->', //used for toggler's text for renderFieldAndOpAsDropdown==true
    //Show labels under all ui fields?
    showLabels: false,
    //Show NOT together with AND/OR?
    showNot: true,
    //Next options are for localization:
    valueLabel: "Value",
    valuePlaceholder: "Value",
    fieldLabel: "Field",
    operatorLabel: "Operator",
    fieldPlaceholder: "Select field",
    operatorPlaceholder: "Select operator",
    deleteLabel: null,
    addGroupLabel: "Add group",
    addRuleLabel: "Add rule",
    readonlyMode: false,
    notLabel: "Not",
    delGroupLabel: null,
    valueSourcesPopupTitle: "Select value source",
    //Leave empty group after deletion or add 1 clean rule immediately?
    canLeaveEmptyGroup: true, //after deletion
    //(for building query string) function to format rule with reverse operator 
    // which haven't 'formatOp'
    // q - already formatted rule for opposite operator (which have 'formatOp')
    // return smth like "NOT(" + q + ")"
    formatReverse: (string q, string operator, string reversedOp, Object operatorDefinition, 
      Object revOperatorDefinition, bool isForDisplay) => string,
    //(for building query string) function to format field
    // parts - for struct field
    // label2 - with using of 'fieldSeparatorDisplay'
    //just return field (or label2 for isForDisplay==true)
    formatField: (string field, Array parts, string label2, Object fieldDefinition, Object config, 
      bool isForDisplay) => string,
    //Values of fields can be compared with values or another fields
    //If you want to disable this feature and leave only comparing with values, remove 'field'
    valueSourcesInfo: {
      value: {
        label: "Value"
      },
      field: {
        label: "Field",
        widget: "field",
      }
    },
    //Activate reordering support for rules and groups of rules?
    canReorder: true,
    //(For comparing field with field) Function for building right list of fields to compare
    canCompareFieldWithField: (string leftField, Object leftFieldConfig, string rightField, 
      Object rightFieldConfig) => {
        //for type == 'select'/'multiselect' you can check listValues
        return true;
    },
  },
}

Development

To build the component locally, clone this repo then run:

npm install npm run examples

Then open localhost:3001 in a browser.

Scripts:

  • npm run build-npm - Builds a npm module. Output path: build/npm
  • npm run build-global - Builds with webpack the self contained pack of the component. Output path: build/global
  • npm run build-examples - Builds with webpack the examples. Output path: examples
  • npm run examples - Builds with webpack the examples and runs a dev-server on localhost:3001.
  • sh ./scripts/gh-pages.sh - Update gh pages

The repo sticks in general to the Airbnb JavaScript Style Guide.

License

MIT. See also LICENSE.txt