react-query-builder-semantic
Version:
747 lines (662 loc) • 27.5 kB
JavaScript
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
var _shortid = require('shortid');
var _shortid2 = _interopRequireDefault(_shortid);
var _react = require('react');
var _react2 = _interopRequireDefault(_react);
var _propTypes = require('prop-types');
var _propTypes2 = _interopRequireDefault(_propTypes);
var _RuleGroup = require('../RuleGroup');
var _RuleGroup2 = _interopRequireDefault(_RuleGroup);
var _ActionElement = require('../ActionElement');
var _ActionElement2 = _interopRequireDefault(_ActionElement);
var _ValueSelector = require('../ValueSelector');
var _ValueSelector2 = _interopRequireDefault(_ValueSelector);
var _ValueEditor = require('../ValueEditor');
var _ValueEditor2 = _interopRequireDefault(_ValueEditor);
var _lodash = require('lodash');
var _lodash2 = _interopRequireDefault(_lodash);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
function queryToString(query) {
if (!query) {
return '';
}
var i = void 0,
length = void 0;
var result = void 0;
if (query.type === 'group') {
result = '(';
for (i = 0, length = query.rules.length; i < length; ++i) {
result += queryToString(query.rules[i]);
if (i + 1 < length) {
result += ' ' + query.combinator + ' ';
}
}
result += ')';
} else if (query.type === 'rule') {
result = query.field + ' ' + query.operator + ' ' + query.value;
} else {
console.error('invalid type: type must be Group or Rule');
return '';
}
return result;
}
/**
* QueryBuilder is an UI component to create queries and filters.
* It outputs a structured JSON of rules which can be easily parsed to create SQL/NoSQL/whatever queries.
*/
var QueryBuilder = function (_React$Component) {
_inherits(QueryBuilder, _React$Component);
function QueryBuilder() {
var _ref;
_classCallCheck(this, QueryBuilder);
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
var _this = _possibleConstructorReturn(this, (_ref = QueryBuilder.__proto__ || Object.getPrototypeOf(QueryBuilder)).call.apply(_ref, [this].concat(args)));
var _this$props = _this.props,
fields = _this$props.fields,
operators = _this$props.operators,
combinators = _this$props.combinators,
controlElements = _this$props.controlElements;
var controls = Object.assign({}, _this.mergeProperties(QueryBuilder.defaultControlElements, controlElements));
_this.state = {
root: _this.getInitialQuery(),
schema: {
fields: fields,
operators: operators,
combinators: combinators,
controls: controls
}
};
_this.createRule = _this.createRule.bind(_this);
_this.createRuleGroup = _this.createRuleGroup.bind(_this);
_this.onRuleAdd = _this._notifyQueryChange.bind(_this, _this.onRuleAdd);
_this.onGroupAdd = _this._notifyQueryChange.bind(_this, _this.onGroupAdd);
_this.onRuleRemove = _this._notifyQueryChange.bind(_this, _this.onRuleRemove);
_this.onGroupRemove = _this._notifyQueryChange.bind(_this, _this.onGroupRemove);
_this.onPropChange = _this._notifyQueryChange.bind(_this, _this.onPropChange);
_this.mergeProperties = _this.mergeProperties.bind(_this);
_this.getLevel = _this.getLevel.bind(_this);
_this.isRuleGroup = _this.isRuleGroup.bind(_this);
_this.getOperators = _this.getOperators.bind(_this);
return _this;
}
_createClass(QueryBuilder, [{
key: 'componentWillReceiveProps',
value: function componentWillReceiveProps(nextProps) {
var schema = Object.assign({}, Object.assign({}, this.state.schema));
if (_lodash2.default.isNull(this.props.query) && _lodash2.default.isNull(nextProps.query)) {
this.setState({ root: this.createRuleGroup() });
} else if (_lodash2.default.isNull(this.props.query) && !_lodash2.default.isNull(nextProps.query)) {
this.setState({ root: _lodash2.default.cloneDeep(nextProps.query) });
} else if (_lodash2.default.isNull(nextProps.query) && !_lodash2.default.isNull(this.props.query)) {
this.setState({ root: this.createRuleGroup() });
} else if (!_lodash2.default.isEqual(this.props.query, nextProps.query)) {
this.setState({ root: _lodash2.default.cloneDeep(nextProps.query) });
}
if (schema.fields !== nextProps.fields) {
schema.fields = nextProps.fields;
this.setState({ schema: schema });
}
}
/**
* Iterates through the query to return a human format string query
* @param query
* @returns {string|*}
*/
}, {
key: 'mergeProperties',
/**
* Checks the values passed as props to override the default values if specified
* @param defaultValues
* @param passedValues
* @returns {*}
*/
value: function mergeProperties(defaultValues, passedValues) {
return _lodash2.default.mergeWith(defaultValues, passedValues, function (objValue, srcValue) {
if (srcValue) {
return srcValue;
}
return objValue;
});
}
}, {
key: 'getInitialQuery',
value: function getInitialQuery() {
return this.props.query ? _lodash2.default.cloneDeep(this.props.query) : this.createRuleGroup();
}
}, {
key: 'componentDidMount',
value: function componentDidMount() {
this._notifyQueryChange(null);
}
}, {
key: 'render',
value: function render() {
var _this2 = this;
var _state = this.state,
_state$root = _state.root,
id = _state$root.id,
rules = _state$root.rules,
combinator = _state$root.combinator,
schema = _state.schema;
var _props = this.props,
translations = _props.translations,
controlClassNames = _props.controlClassNames;
var updatedClassNames = Object.assign({}, this.mergeProperties(QueryBuilder.defaultClassNames, controlClassNames));
var updatedTranslations = Object.assign({}, this.mergeProperties(QueryBuilder.defaultTranslations, translations));
return _react2.default.createElement(
'div',
{ className: '' + updatedClassNames.queryBuilder },
_react2.default.createElement(_RuleGroup2.default, {
classNames: updatedClassNames,
translations: updatedTranslations,
rules: rules,
createRule: this.createRule,
createRuleGroup: this.createRuleGroup,
onRuleAdd: this.onRuleAdd,
onGroupAdd: this.onGroupAdd,
onRuleRemove: this.onRuleRemove,
onGroupRemove: this.onGroupRemove,
isRuleGroup: this.isRuleGroup,
getLevel: this.getLevel,
onPropChange: this.onPropChange,
getOperators: function getOperators() {
return _this2.getOperators.apply(_this2, arguments);
},
combinator: combinator,
schema: schema,
id: id,
parentId: null
})
);
}
/**
* Returns true if the rule is a RuleGroup with rules
* @param rule
* @returns {boolean}
*/
}, {
key: 'isRuleGroup',
value: function isRuleGroup(rule) {
return rule.type === 'group';
}
}, {
key: 'createRule',
value: function createRule() {
var _state$schema = this.state.schema,
fields = _state$schema.fields,
operators = _state$schema.operators;
return {
id: 'r-' + _shortid2.default.generate(),
type: 'rule',
field: fields[0].name,
value: '',
operator: operators[0].name
};
}
}, {
key: 'createRuleGroup',
value: function createRuleGroup() {
return {
id: 'g-' + _shortid2.default.generate(),
type: 'group',
rules: [],
combinator: this.props.combinators[0].name
};
}
}, {
key: 'getOperators',
value: function getOperators(field) {
if (this.props.getOperators) {
var ops = this.props.getOperators(field);
if (ops) {
return ops;
}
}
return this.props.operators;
}
}, {
key: 'onRuleAdd',
value: function onRuleAdd(rule, parentId) {
var parent = this._findRule(parentId, this.state.root);
parent.rules.push(rule);
this.setState({ root: this.state.root });
}
}, {
key: 'onGroupAdd',
value: function onGroupAdd(group, parentId) {
var parent = this._findRule(parentId, this.state.root);
parent.rules.push(group);
this.setState({ root: this.state.root });
}
}, {
key: 'onPropChange',
value: function onPropChange(prop, value, ruleId) {
var rule = this._findRule(ruleId, this.state.root);
Object.assign(rule, _defineProperty({}, prop, value));
this.setState({ root: this.state.root });
}
/**
* Removes the given rule by id from the tree
* @param ruleId
* @param parentId
*/
}, {
key: 'onRuleRemove',
value: function onRuleRemove(ruleId, parentId) {
var parent = this._findRule(parentId, this.state.root);
var index = parent.rules.findIndex(function (x) {
return x.id === ruleId;
});
parent.rules.splice(index, 1);
this.setState({ root: this.state.root });
}
/**
* Removes the given group by id from the tree
* @param groupId
* @param parentId
*/
}, {
key: 'onGroupRemove',
value: function onGroupRemove(groupId, parentId) {
var parent = this._findRule(parentId, this.state.root);
var index = parent.rules.findIndex(function (x) {
return x.id === groupId;
});
parent.rules.splice(index, 1);
this.setState({ root: this.state.root });
}
}, {
key: 'getLevel',
value: function getLevel(id) {
return this._getLevel(id, 0, this.state.root);
}
/**
* Searches in the root tree for rules for the id specified and returns the index of the found rule
* @param id
* @param index
* @param root
* @returns {number}
* @private
*/
}, {
key: '_getLevel',
value: function _getLevel(id, index, root) {
var _this3 = this;
var foundAtIndex = -1;
if (root.id === id) {
foundAtIndex = index;
} else if (this.isRuleGroup(root)) {
root.rules.forEach(function (rule) {
if (foundAtIndex === -1) {
var indexForRule = index;
if (_this3.isRuleGroup(rule)) indexForRule++;
foundAtIndex = _this3._getLevel(id, indexForRule, rule);
}
});
}
return foundAtIndex;
}
/**
* Searches the rule group for the given rule id
* @param id
* @param parent
* @returns {*}
* @private
*/
}, {
key: '_findRule',
value: function _findRule(id, parent) {
if (parent.id === id) {
return parent;
}
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
for (var _iterator = parent.rules[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var rule = _step.value;
if (rule.id === id) {
return rule;
} else if (this.isRuleGroup(rule)) {
var subRule = this._findRule(id, rule);
if (subRule) {
return subRule;
}
}
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
}
/**
* Any callback change in the tree that is made, remove,add, change of rule or group the query is cloned
* and updated
* @param fn
* @param args
* @private
*/
}, {
key: '_notifyQueryChange',
value: function _notifyQueryChange(fn) {
if (fn) {
for (var _len2 = arguments.length, args = Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
args[_key2 - 1] = arguments[_key2];
}
fn.call.apply(fn, [this].concat(args));
}
var onQueryChange = this.props.onQueryChange;
if (onQueryChange) {
var query = _lodash2.default.cloneDeep(this.state.root);
onQueryChange(query);
}
}
/*
* default control elements to merge with due to cant use default props as duplication of this component will result in the
* others using the same control elements from other instantiations of this component
*/
}], [{
key: 'getQueryString',
value: function getQueryString(query) {
return queryToString(query);
}
}, {
key: 'defaultControlElements',
get: function get() {
return {
addGroupAction: _ActionElement2.default,
removeGroupAction: _ActionElement2.default,
addRuleAction: _ActionElement2.default,
removeRuleAction: _ActionElement2.default,
combinatorSelector: _ValueSelector2.default,
fieldSelector: _ValueSelector2.default,
operatorSelector: _ValueSelector2.default,
valueEditor: _ValueEditor2.default
};
}
/*
* default translations to merge with due to cant use default props as duplication of this component will result in the
* others using the same translations from other instantiations of this component
*/
}, {
key: 'defaultTranslations',
get: function get() {
return {
fields: {
title: "Fields"
},
operators: {
title: "Operators"
},
value: {
title: "Value"
},
removeRule: {
label: "x",
title: "Remove rule"
},
removeGroup: {
label: "x",
title: "Remove group"
},
addRule: {
label: "+Rule",
title: "Add rule"
},
addGroup: {
label: "+Group",
title: "Add group"
},
combinators: {
title: "Combinators"
}
};
}
/*
* default class names to merge with due to cant use default props as duplication of this component will result in the
* others using the same class names from other instantiations of this component
*/
}, {
key: 'defaultClassNames',
get: function get() {
return {
queryBuilder: 'query-builder',
removeRule: 'group-or-rule__rule-remove',
ruleGroup: 'group-or-rule-container__group-or-rule group-or-rule__group',
ruleGroupHeader: 'group-or-rule__group-header',
ruleGroupContainer: 'query-builder__group-or-rule-container group-or-rule-container__group',
ruleGroupCombinators: 'group-or-rule__group-combinators',
combinators: 'group-or-rule__group-combinator',
ruleGroupActions: 'group-or-rule__group-actions',
addRule: 'group-or-rule__ruleGroup-addRule',
addGroup: 'group-or-rule__ruleGroup-addGroup',
removeGroup: 'group-or-rule__ruleGroup-removeGroup',
rule: 'group-or-rule-container__group-or-rule group-or-rule__rule',
ruleHeader: 'group-or-rule__rule-header',
ruleContainer: 'query-builder__group-or-rule-container group-or-rule-container__rule',
fields: 'group-or-rule__rule-field',
operators: 'group-or-rule__rule-operator',
value: 'group-or-rule__rule-value'
};
}
}]);
return QueryBuilder;
}(_react2.default.Component);
QueryBuilder.displayName = 'QueryBuilder';
QueryBuilder.defaultProps = {
query: null,
fields: [],
operators: [{ name: 'null', label: 'Is Null' }, { name: 'notNull', label: 'Is Not Null' }, { name: 'in', label: 'In' }, { name: 'notIn', label: 'Not In' }, { name: '=', label: '=' }, { name: '!=', label: '!=' }, { name: '<', label: '<' }, { name: '>', label: '>' }, { name: '<=', label: '<=' }, { name: '>=', label: '>=' }],
combinators: [{ name: 'and', label: 'AND' }, { name: 'or', label: 'OR' }],
translations: {
fields: {
title: "Fields"
},
operators: {
title: "Operators"
},
value: {
title: "Value"
},
removeRule: {
label: "x",
title: "Remove rule"
},
removeGroup: {
label: "x",
title: "Remove group"
},
addRule: {
label: "+Rule",
title: "Add rule"
},
addGroup: {
label: "+Group",
title: "Add group"
},
combinators: {
title: "Combinators"
}
},
controlElements: {
addGroupAction: _ActionElement2.default,
removeGroupAction: _ActionElement2.default,
addRuleAction: _ActionElement2.default,
removeRuleAction: _ActionElement2.default,
combinatorSelector: _ValueSelector2.default,
fieldSelector: _ValueSelector2.default,
operatorSelector: _ValueSelector2.default,
valueEditor: _ValueEditor2.default
},
getOperators: null,
onQueryChange: null,
controlClassNames: {
queryBuilder: 'query-builder',
removeRule: 'group-or-rule__rule-remove',
ruleGroup: 'group-or-rule-container__group-or-rule group-or-rule__group',
ruleGroupHeader: 'group-or-rule__group-header',
ruleGroupContainer: 'query-builder__group-or-rule-container group-or-rule-container__group',
ruleGroupCombinators: 'group-or-rule__group-combinators',
combinators: 'group-or-rule__group-combinator',
ruleGroupActions: 'group-or-rule__group-actions',
addRule: 'group-or-rule__ruleGroup-addRule',
addGroup: 'group-or-rule__ruleGroup-addGroup',
removeGroup: 'group-or-rule__ruleGroup-removeGroup',
rule: 'group-or-rule-container__group-or-rule group-or-rule__rule',
ruleHeader: 'group-or-rule__rule-header',
ruleContainer: 'query-builder__group-or-rule-container group-or-rule-container__rule',
fields: 'group-or-rule__rule-field',
operators: 'group-or-rule__rule-operator',
value: 'group-or-rule__rule-value'
}
};
QueryBuilder.propTypes = {
query: _propTypes2.default.object,
/**
* The array of fields that should be used. Each field should be an object with
The Id is optional, if you do not provide an id for a field then the name will be used
*/
fields: _propTypes2.default.arrayOf(_propTypes2.default.shape({
name: _propTypes2.default.string.isRequired,
label: _propTypes2.default.string.isRequired,
id: _propTypes2.default.string
})).isRequired,
/**
The array of operators that should be used.
*/
operators: _propTypes2.default.arrayOf(_propTypes2.default.shape({
name: _propTypes2.default.string.isRequired,
label: _propTypes2.default.string.isRequired
})),
/**
* The array of combinators that should be used for RuleGroups
*/
combinators: _propTypes2.default.arrayOf(_propTypes2.default.shape({
name: _propTypes2.default.string.isRequired,
label: _propTypes2.default.string.isRequired
})),
/**
* This is a custom controls object that allows you to override the control elements used. The following control overrides are supported
*/
controlElements: _propTypes2.default.shape({
addGroupAction: _propTypes2.default.func, //returns ReactClass
removeGroupAction: _propTypes2.default.func, //returns ReactClass
addRuleAction: _propTypes2.default.func, //returns ReactClass
removeRuleAction: _propTypes2.default.func, //returns ReactClass
combinatorSelector: _propTypes2.default.func, //returns ReactClass
fieldSelector: _propTypes2.default.func, //returns ReactClass
operatorSelector: _propTypes2.default.func, //returns ReactClass
valueEditor: _propTypes2.default.func //returns ReactClass
}),
/**
* This is a callback function invoked to get the list of allowed operators for the given field
*/
getOperators: _propTypes2.default.func,
/**
* This is a notification that is invoked anytime the query configuration changes
*/
onQueryChange: _propTypes2.default.func,
/**
* This can be used to assign specific CSS classes to various controls that are created by the
*/
controlClassNames: _propTypes2.default.shape({
/**
*Root <div> element
*/
queryBuilder: _propTypes2.default.string,
/**
*<div> containing the RuleGroup
*/
ruleGroup: _propTypes2.default.string,
/**
*<select> control for combinators
*/
combinators: _propTypes2.default.string,
/**
*<button> to add a Rule
*/
addRule: _propTypes2.default.string,
/**
*<button> to add a RuleGroup
*/
addGroup: _propTypes2.default.string,
/**
*<button> to remove a RuleGroup
*/
removeGroup: _propTypes2.default.string,
/**
*<div> containing the Rule
*/
rule: _propTypes2.default.string,
/**
*<select> control for fields
*/
fields: _propTypes2.default.string,
/**
*<select> control for operators
*/
operators: _propTypes2.default.string,
/**
*<input> for the field value
*/
value: _propTypes2.default.string,
/**
*<button> to remove a Rule
*/
removeRule: _propTypes2.default.string
}),
/**
* This can be used to override translatable texts applied to various controls that are created by the <QueryBuilder />
*/
translations: _propTypes2.default.shape({
fields: _propTypes2.default.shape({
title: _propTypes2.default.string
}),
operators: _propTypes2.default.shape({
title: _propTypes2.default.string
}),
value: _propTypes2.default.shape({
title: _propTypes2.default.string
}),
removeRule: _propTypes2.default.shape({
label: _propTypes2.default.string,
title: _propTypes2.default.string
}),
removeGroup: _propTypes2.default.shape({
label: _propTypes2.default.string,
title: _propTypes2.default.string
}),
addRule: _propTypes2.default.shape({
label: _propTypes2.default.string,
title: _propTypes2.default.string
}),
addGroup: _propTypes2.default.shape({
label: _propTypes2.default.string,
title: _propTypes2.default.string
}),
combinators: _propTypes2.default.shape({
title: _propTypes2.default.string
})
})
};
exports.default = QueryBuilder;