0.6.50 • Published 4 years ago

vue-renderless-expression-builder v0.6.50

Weekly downloads
58
License
-
Repository
-
Last release
4 years ago

Renderless Expression Builder for Vue.js

codecov

This is something I needed for a project, where I had to build an expression (or query) builder, with a layout drastically different from the standard query builder interface (such as the jQuery QueryBuilder), but with very similar functionality. This library is a layout-agnostic implementaiton, meaning, that it aims to serve as a core for flexibly creating query builder components with different complexities and specifications.

Demo implementation (with Bootstrap Vue)
Demo code

What it does:

  • Creates and manages the nested structure (nodes and node groups)
  • Let's you define a set of available fields for filtering (First Name, Created At, etc.), field types (date, text, number, bool, etc.), and operators (equals, in, etc.).
  • Let's you define a max depth for the query generated by the builder
  • Based on the above parameters, creates the actual conditions for the individual expression nodes

What it does not do:

  • Provide any layout implementation

Installation

npm install vue-renderless-expression-builder

Available named exports are:

  • Core - the underlying model objects
  • Components - the actual renderless components
  • Conditions - the default conditions and conditionFactory class, used internally by the renderless components

Components

ExpressionNodeRenderless

Props: | Prop | Type | Description | |---|---|---| | node | Core.ExpressionNode | node represented by the component |

Available scopedSlots: | Slot | Type | Description | |---|---|---| | node | Core.ExpressionNode | node object, represented by the component | | index | number | index of the current node, in its parent's children array | | updateCondition | Function(fieldName: string, operatorName: string, value: any) | calls ConditionFactory.create with the params and updates the condition of the current node | | deleteSelf | Function | deletes the current node | | conditionFactory | ConditionFactory | condition factory instance |

ExpressionNodeGroupRenderless

Props: | Prop | Type | Description | |---|---|---| | node | Core.ExpressionNodeGroup | node group represented by the component |

Available scopedSlots: | Slot | Type | Description | |---|---|---| | node | Core.ExpressionNodeGroup | node group object, represented by the component | | index | number | index of the current node, in its parent's children array | | deleteSelf | Function | deletes the current node | | conditionFactory | ConditionFactory | condition factory instance | | toggleConnectionType | Function | toggles group's connection type between 'and' and 'or' | | addNode | Function(condition: Object?) | adds a node at the end of the childrens array of the group | | addGroup | Function | adds a node group at the end of the childrens array of the group | | insert | Function(node: Core.ExpressionNode(Group), index: number) | inserts a node or node group at a given index |

ExpressionBuilderRenderless

Props: | Prop | Type | Description | |---|---|---| | value | Core.ExpressionBuilder? | ExpressionBuilder instance to be represented by the component | | fields | ConditionFactoryField[] (below) | list of user defined fields to be used in the builder | | operators | ConditionFactoryOperator[] (below) | a list of operators available in the builder - default: Conditions.returnDefaultOperators | | fieldTypes | ConditionFactoryFieldTypeDefinition[] (below) | a list of field types available in the builder - default: Conditions.returnDefaultFieldTypes | | eventHub | Vue? | eventHub, that the builder and nodes use to communicate |

Provided keys (provide/inject API): | Key | Type | Description | |---|---|---| | $__qb_condition_factory__ | ConditionFactory | ConditionFactory, that's injected to all ExpressionNodeRenderless and ExpressionNodeGroupRenderless components, but you can inject in the implementations as well | | $__qb__event_hub__ | Vue | eventHub, that the builder and nodes use to communicate |

Core

Core.ExpressionNode

MemberTypeDescription
parentNodeCore.ExpressionNodeGroupThe node group containing this node
toJSONFunctionReturns a json representation of the current condition object
fromJSON (static)Function(json: Object)Creates a new Core.ExpressionNode instance from a json representation
conditionObjectCan be any object, however, the conditionFactory creates the condition objects with a specific schema - see below

Condition object schema:

{
  fieldName: "string",  // name of the field (fisrtName, dateOfBirth, etc.)
  operatorName: "string",  // name of the operator (equals, in, etc.)
  fieldTypeName: "string"  // name of the field type (text, date, etc.)
  value: any  // condition value
}

Core.ExpressionNodeGroup

Core.ExpressionNodeGroup constructor params:
| Param | Type | Description | |---|---|---| | opts | Object | default: {maxDepth: 0, currentDepth: 0, children: [], connectionType: "and"} | | parentNode | Core.ExpressionNodeGroup | The node group containing this node |

Core.ExpressionNodeGroup members:
| Member | Type | Description | |---|---|---| | parentNode | Core.ExpressionNodeGroup | The node group containing this node | | maxDepth | number | the max depth of the nested structure, defaults to 0 (meaning infinite depth)| | currentDepth | number | the depth of the current node group in the current nested strucure | | toJSON | Function | Recursively creates a json representation of the current node group and its children | | fromJSON (static) | Function(json: Object) | reates a new Core.ExpressionNodeGroup object from a json representation | | flatten | Function | returns a 1 depth array of arrays, flattening the nested condition, where the elements of each array are conditions of ExpressionNodes and are connected by AND and the inner arrays are connected by OR, so the result can be used for client-side filtering like this: list.filter(elem => flattened.map(group => group.every(condition => validateCondition(condition, elem))).some(groupIsTrue => groupIsTrue)) |

Core.ExpressionBuilder

Core.ExpressionBuilder constructor params | Param | Type | Description | |---|---|---| | root | Core.ExpressionNodeGroup? or Object? (json representation) | if it's undefined, a new Core.ExpressionNodeGroup is created | | errorHandler | Function(key: string, msg: string) | called when an error occurs (like reaching max depth), can be used for notifying the user of errors |

Core.ExpressionBuilder members: | Member | Type | Description | |---|---|---| | root | Core.ExpressionNodeGroup | the root node group of the current expression/query | | context | Core.ExpressionNodeGroup | the current context, for which insertion and deletion method calls apply | | insert | Function(node: Core.ExpressionNode(Group), index: number) | Inserts a node or node group at a given index in the children array of the context node group. If a node group was inserted, sets it as the new context. Returns the context, for fluent editing | | set | Function(node: Core.ExpressionNode(Group), index: number) | Sets a node at a for a given index in the children array of the context node group. If a node group was inserted, sets it as the new context, Returns the context, for fluent editing | | delete | Function(index: number) | Deletes a node from a given index in the children array of the context node group. Returns the context, for fluent editing | | add | Function(node: Core.ExpressionNode(Group)) | Inserts a node or node group at the end of the children array of the context node group. If a node group was inserted, sets it as the new context, Returns the context, for fluent editing | | contextUp | Function | sets the parentNode of the current context as the new context | | contextToRoot | Function | sets the root as the new context | | contextTo | Function(path: number[]) | sets a node group by a path from the root, as the new context ie. the first child of the root node can be set as context by calling contextTo(0) | | flatten | Function | Calls the faltten method of the root Core.ExprssionNodeGroup | | toJSON | Function | Calls the toJSON method of the root Core.ExpressionNodeGroup |

Conditions

ConditionFactory

ConditionFactory constructor opts

interface ConditionFactoryOpts {
  fields: ConditionFactoryField[];
  operators?: ConditionFactoryOperator[];
  fieldTypes?: ConditionFactoryFieldTypeDefinition[];
}
ParamTypeDescription
fieldsConditionFactoryField[] (below)list of user defined fields to be used with the conditionFactory
operatorsConditionFactoryOperator[] (below)a list of operators available in the conditionFactory - default: returnDefaultOperators
fieldTypesConditionFactoryFieldTypeDefinition[] (below)a list of field types available in the conditionFactory - default: returnDefaultFieldTypes

ConditionFactory members: | Member | Type | Description | |---|---|---| | fields | ConditionFactoryField[] | fields provided in the constructor | | operators | ConditionFactoryOperator[] | operators provided in the constructor | | fieldTypes | ConditionFactoryFieldTypeDefinition[] | fieldTypes provided in the constructor | | create | Function(fieldName: string, operatorName: string, value: any) | returns: ConditionFactoryCondition (below) | | createAndUpdate | Function(node: Core.ExpressionNode, fieldName: string, operatorName: string, value: any) | returns: void - updates the condition key of the current node with a newly created condition |

interface ConditionFactoryField {
  type: string;
  name: string;
  label: string;
  operators?: string[];
  choices?: any[];  // used, if select-type type is used
}

interface ConditionFactoryOperator {
  name: string;
  label: string;  // operator display name
}

interface ConditionFactoryFieldTypeDefinition {
  name: string;
  availableOperators: string[];  // operators available by default for the field type
  label: string;  // display name for the field type
}

export interface ConditionFactoryCondition {
  fieldName: string;
  fieldTypeName: string;
  operatorName: string;
  value: any;
}

Defaults

defaultFieldTypes

const defaultFieldTypes = {
  TEXT: "text",
  DATE: "date",
  NUMBER: "number",
  BOOLEAN: "boolean",
  CHOICE: "radio",
  MULTIPLE_CHOICE: "multipleChoice",
  SELECT: "select"
};

defaultOperators

const defaultOperators = {
  EQUALS: "equals",
  NOT_EQUALS: "notEquals",
  GREATER_THAN: "graterThan",
  LOWER_THAN: "lowerThan",
  IN: "in",
  NOT_IN: "notIn",
  STARTS_WITH: "startsWith",
  NOT_STARTS_WITH: "notStartsWith",
  ENDS_WITH: "endsWith",
  NOT_ENDS_WITH: "notEndsWith",
  IS_EMPTY: "isEmpty",
  NOT_IS_EMPTY: "notIsEmpty",
  IS_NULL: "isNull",
  NOT_IS_NULL: "notIsNull",
  IS_ONE_OF: "isOneOf",
  NOT_IS_ONE_OF: "notIsOneOf"
};

returnDefaultFieldTypes

[{
	"name": "text",
	"label": "text",
	"availableOperators": ["equals", "notEquals", "isEmpty", "notIsEmpty", "endsWith", "notEndsWith", "startsWith", "notStartsWith", "isNull", "notIsNull", "in", "notIn"]
},
{
	"name": "date",
	"label": "date",
	"availableOperators": ["equals", "notEquals", "isNull", "notIsNull", "graterThan", "lowerThan"]
},
{
	"name": "number",
	"label": "number",
	"availableOperators": ["equals", "notEquals", "isNull", "notIsNull", "graterThan", "lowerThan"]
},
{
	"name": "boolean",
	"label": "boolean",
	"availableOperators": ["equals"]
},
{
	"name": "radio",
	"label": "radio",
	"availableOperators": ["equals", "notEquals"]
},
{
	"name": "multipleChoice",
	"label": "multiple choice",
	"availableOperators": ["in", "notIn"]
},
{
	"name": "select",
	"label": "select",
	"availableOperators": ["equals", "notEquals"]
}]

returnDefaultOperators

[{
    "name": "equals",
    "label": "equals"
}, {
    "name": "notEquals",
    "label": "not equals"
}, {
    "name": "graterThan",
    "label": "greater than"
}, {
    "name": "lowerThan",
    "label": "lower than"
}, {
    "name": "in",
    "label": "in"
}, {
    "name": "notIn",
    "label": "not in"
}, {
    "name": "startsWith",
    "label": "starts with"
}, {
    "name": "notStartsWith",
    "label": "doesn't start with"
}, {
    "name": "endsWith",
    "label": "ends with"
}, {
    "name": "notEndsWith",
    "label": "doesn't end with"
}, {
    "name": "isEmpty",
    "label": "is empty"
}, {
    "name": "notIsEmpty",
    "label": "is not empty"
}, {
    "name": "isNull",
    "label": "is null"
}, {
    "name": "notIsNull",
    "label": "is not null"
}]
0.6.43

4 years ago

0.6.42

4 years ago

0.6.50

4 years ago

0.6.41

4 years ago

0.6.40

4 years ago

0.6.35

4 years ago

0.6.32

4 years ago

0.6.34

4 years ago

0.6.33

4 years ago

0.6.31

4 years ago

0.6.30

4 years ago

0.6.22

4 years ago

0.6.21

4 years ago

0.6.20

4 years ago