UNPKG

react-query-builder-semantic

Version:
870 lines (784 loc) 31.3 kB
'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 _RuleGroupSemantic = require('../RuleGroupSemantic'); var _RuleGroupSemantic2 = _interopRequireDefault(_RuleGroupSemantic); var _OperatorSelectorSemantic = require('../OperatorSelectorSemantic'); var _OperatorSelectorSemantic2 = _interopRequireDefault(_OperatorSelectorSemantic); var _FieldSelectorSemantic = require('../FieldSelectorSemantic'); var _FieldSelectorSemantic2 = _interopRequireDefault(_FieldSelectorSemantic); var _ValueEditorSemantic = require('../ValueEditorSemantic'); var _ValueEditorSemantic2 = _interopRequireDefault(_ValueEditorSemantic); var _semanticUiReact = require('semantic-ui-react'); 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; } /** * QueryBuilderSemantic is QueryBuilder with react.semantic-ui components. * It outputs a structured JSON of rules which can be easily parsed to create SQL/NoSQL/whatever queries. */ var QueryBuilderSemantic = function (_React$Component) { _inherits(QueryBuilderSemantic, _React$Component); function QueryBuilderSemantic() { var _ref; _classCallCheck(this, QueryBuilderSemantic); for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } var _this = _possibleConstructorReturn(this, (_ref = QueryBuilderSemantic.__proto__ || Object.getPrototypeOf(QueryBuilderSemantic)).call.apply(_ref, [this].concat(args))); var _this$props = _this.props, fields = _this$props.fields, values = _this$props.values, operators = _this$props.operators, combinators = _this$props.combinators, controlElements = _this$props.controlElements; var controls = Object.assign({}, _this.mergeProperties(QueryBuilderSemantic.defaultControlElements, controlElements)); _this.state = { root: _this.getInitialQuery(), schema: { fields: fields, values: values, 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(QueryBuilderSemantic, [{ 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, combinatorColors = _props.combinatorColors, ruleSemanticProps = _props.ruleSemanticProps, ruleGroupSemanticProps = _props.ruleGroupSemanticProps, controlClassNames = _props.controlClassNames; var updatedClassNames = Object.assign({}, this.mergeProperties(QueryBuilderSemantic.defaultClassNames, controlClassNames)); var updatedTranslations = Object.assign({}, this.mergeProperties(QueryBuilderSemantic.defaultTranslations, translations)); return _react2.default.createElement( _semanticUiReact.Segment.Group, { fluid: 'true', raised: true, className: '' + updatedClassNames.queryBuilder }, _react2.default.createElement(_RuleGroupSemantic2.default, { classNames: updatedClassNames, ruleSemanticProps: ruleSemanticProps, ruleGroupSemanticProps: ruleGroupSemanticProps, translations: updatedTranslations, combinatorColors: combinatorColors, 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, parentCombinator: combinator }) ); } /** * 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, values = _state$schema.values; return { id: 'r-' + _shortid2.default.generate(), field: fields[0].value, type: 'rule', value: !_lodash2.default.isEmpty(values) ? values[0].value : '', operator: operators[0].value }; } }, { key: 'createRuleGroup', value: function createRuleGroup() { return { id: 'g-' + _shortid2.default.generate(), type: 'group', rules: [], combinator: this.props.combinators[0].value }; } }, { 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 { fieldSelector: _FieldSelectorSemantic2.default, operatorSelector: _OperatorSelectorSemantic2.default, valueEditor: _ValueEditorSemantic2.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: { title: "Remove rule" }, removeGroup: { title: "Remove group" }, addRule: { title: "Add rule" }, addGroup: { 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', combinators: 'group-or-rule__group-combinator', 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 QueryBuilderSemantic; }(_react2.default.Component); QueryBuilderSemantic.displayName = 'QueryBuilderSemantic'; QueryBuilderSemantic.defaultProps = { query: null, fields: [], values: [], operators: [{ value: 'null', text: 'Is Null' }, { value: 'notNull', text: 'Is Not Null' }, { value: 'in', text: 'In' }, { value: 'notIn', text: 'Not In' }, { value: '=', text: '=' }, { value: '!=', text: '!=' }, { value: '<', text: '<' }, { value: '>', text: '>' }, { value: '<=', text: '<=' }, { value: '>=', text: '>=' }], combinatorColors: [{ color: 'purple', combinator: 'and' }, { color: 'blue', combinator: 'or' }], combinators: [{ text: 'AND', value: 'and', label: { color: 'purple', empty: true, circular: true } }, { text: 'OR', value: 'or', label: { color: 'blue', empty: true, circular: true } }], translations: { fields: { title: "Fields" }, operators: { title: "Operators" }, value: { title: "Value" }, removeRule: { title: "Remove rule" }, removeGroup: { title: "Remove group" }, addRule: { title: "Add rule" }, addGroup: { title: "Add group" }, combinators: { title: "Combinators" } }, controlElements: { fieldSelector: _FieldSelectorSemantic2.default, operatorSelector: _OperatorSelectorSemantic2.default, valueEditor: _ValueEditorSemantic2.default }, getOperators: null, onQueryChange: null, ruleSemanticProps: { segment: { size: 'tiny', padded: true, compact: true }, valueEditor: { size: 'tiny', type: "text" }, fieldSelector: { scrolling: true, selection: true, search: true }, operatorSelector: { scrolling: true, selection: true, search: true }, deleteRuleButton: { size: 'tiny', compact: true, circular: true, floated: 'right', icon: 'remove' } }, ruleGroupSemanticProps: { dropDown: { attached: 'left', size: 'tiny', labeled: true, scrolling: true, selection: true }, segment: { size: 'tiny' }, addGroupButton: { attached: true, size: 'tiny', compact: true, icon: 'plus' }, removeGroupButton: { attached: 'right', size: 'tiny', compact: true, icon: 'minus' }, addRuleButton: { attached: 'right', size: 'tiny', compact: true, icon: 'plus' } }, 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', combinators: 'group-or-rule__group-combinator', 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' } }; QueryBuilderSemantic.propTypes = { /** * Semantic Props for valueEditor,fieldSelector,valueSelector,segment,deleteRuleButton on a rule */ ruleSemanticProps: _propTypes2.default.shape({ /** * Semantic Input props on a rule * https://react.semantic-ui.com/elements/input/ */ valueEditor: _propTypes2.default.any, /** * Semantic Dropdown props on a rule * https://react.semantic-ui.com/modules/dropdown/ */ fieldSelector: _propTypes2.default.any, /** * Semantic Dropdown props on a rule * https://react.semantic-ui.com/modules/dropdown/ */ operatorSelector: _propTypes2.default.any, /** * Semantic Segment props on a rule * https://react.semantic-ui.com/elements/segment/ */ segment: _propTypes2.default.any, /** * Semantic delete Button props on a rule * https://react.semantic-ui.com/elements/button/ */ deleteRuleButton: _propTypes2.default.any }), /** * Semantic Props for dropDown,addGroupButton,removeGroupButton,segment,addRuleButton on a group */ ruleGroupSemanticProps: _propTypes2.default.shape({ /** * Semantic combinator Dropdown props on a group * https://react.semantic-ui.com/modules/dropdown/ */ dropDown: _propTypes2.default.any, /** * Semantic Segment props on a group * https://react.semantic-ui.com/elements/segment/ */ segment: _propTypes2.default.any, /** * Semantic add group Button props on a group * https://react.semantic-ui.com/elements/button/ */ addGroupButton: _propTypes2.default.any, /** * Semantic remove group Button props on a group * https://react.semantic-ui.com/elements/button/ */ removeGroupButton: _propTypes2.default.any, /** * Semantic remove group Button props on a group * https://react.semantic-ui.com/elements/button/ */ addRuleButton: _propTypes2.default.any }), query: _propTypes2.default.object, /** * The array of fields that should be used. Each field should be an object with */ fields: _propTypes2.default.arrayOf(_propTypes2.default.shape({ value: _propTypes2.default.string.isRequired, text: _propTypes2.default.string.isRequired })).isRequired, /** * The array of values that should be used. Each value should be an object with */ values: _propTypes2.default.arrayOf(_propTypes2.default.shape({ value: _propTypes2.default.string.isRequired, text: _propTypes2.default.string.isRequired })), /** The array of operators that should be used. */ operators: _propTypes2.default.arrayOf(_propTypes2.default.shape({ value: _propTypes2.default.string, text: _propTypes2.default.string })), /** * The array of combinators that should be used for RuleGroups */ combinators: _propTypes2.default.arrayOf(_propTypes2.default.shape({ value: _propTypes2.default.string, content: _propTypes2.default.any, text: _propTypes2.default.string })), /** * The array of colors to use for the selected combinator * https://react.semantic-ui.com/elements/segment/#variations-colored */ combinatorColors: _propTypes2.default.arrayOf(_propTypes2.default.shape({ color: _propTypes2.default.string.isRequired, combinator: _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({ 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 QueryBuilderSemantic */ controlClassNames: _propTypes2.default.shape({ /** *Root <div> element */ queryBuilder: _propTypes2.default.string, /** *<div> containing the RuleGroup */ ruleGroup: _propTypes2.default.string, /** *<Dropdown> 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, /** *<Dropdown> control for fields */ fields: _propTypes2.default.string, /** *<Dropdown> 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 that are created by the <QueryBuilderSemantic /> */ 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({ title: _propTypes2.default.string }), removeGroup: _propTypes2.default.shape({ title: _propTypes2.default.string }), addRule: _propTypes2.default.shape({ title: _propTypes2.default.string }), addGroup: _propTypes2.default.shape({ title: _propTypes2.default.string }), combinators: _propTypes2.default.shape({ title: _propTypes2.default.string }) }) }; exports.default = QueryBuilderSemantic;