Autocomplete expression builder for expr-lang
npm install @happening-oss/expr2sql-editor

An auto-complete query builder for expr-lang expressions, designed for intuitive filtering and search. Compatible with expr2sql. 🚀 Try the live demo: Click here!
- 📝 Auto-complete with field and operator suggestions
- 🔍 Syntax validation for expressions
- 🎛️ Custom input components for specific data types
Install from npm:
``bash`
npm i @happening-oss/expr2sql-editor
Define the target element:
`html`
Initialize the ExprEditor component:
`js
import { ExprEditor } from '@happening-oss/expr2sql-editor';
import '@happening-oss/expr2sql-editor/style.css';
// define the doc
const doc = {
variables: {
intField: {
name: 'intField',
kind: 'int',
},
stringField: {
name: 'stringField',
kind: 'string',
},
},
};
// initialize the editor
const editorRef = document.querySelector('#editor');
const editor = new ExprEditor(editorRef, {
rawValue: 'intField > 1234',
doc,
onInput: (value) => {
console.log('value changed', value);
},
});
`
ExprEditor accepts two parameters, one is the target element which the editor loads in. The other is the configuration object.
`ts`
const editor = new ExprEditor(target, options);
The editor supports both free-text search and structured expressions. A separator (eg. :) defines where the structured expression begins. The whole input value is the "raw value" and its form is . Examples when the separator is ::
| Raw value | | Search | Expression
|--|--|--|--|
|test123:field1 == "Foo"| Search + expression | test123 | field1 == "Foo" |some string
||Just search| some string | |:timestamp < '2025-01-01'
||Just expression| | timestamp < '2025-01-01' |
`ts`
type Props = {
/* Separates search and expression, eg. with ':', 'text:prop == value' is possible /
separator?: string;
/* The raw input value /
rawValue?: string;
/* Triggers whenever input changes /
onInput?(value: string): void;
/* Same as above but with the parsed input /
onChange?(event: { search?: string; expression?: string; active?: boolean; }): void;
/* Triggers when 'Enter' key is pressed /
onEnter?(value: string): void;
/* See: Field information /
doc: Doc;
/* See: Custom inputs /
customInputs?: CustomInputs;
/* Input placeholder /
placeholder?: string;
/* Element classes /
class?: string;
}
Autocomplete and suggestions require the doc option to be set. It contains all of the available fields and optionally the operators. Example:
`ts`
const doc: Doc = {
variables: {
stringField: {
name: 'stringField',
kind: 'string',
},
/* String field but suggests a predefined list of values /
selectField: {
name: 'selectField',
kind: 'string',
values: [
'started',
'inprogress',
'finished',
'failed',
],
},
/* Field with a custom format defined, which is used for custom inputs /
timestampField: {
name: 'timestampField',
kind: 'string',
format: 'date',
},
boolField: {
name: 'boolField',
kind: 'bool',
},
intField: {
name: 'intField',
kind: 'int',
},
jsonField: {
name: 'jsonField',
kind: 'struct',
fields: {
stringProp: {
name: 'stringProp',
kind: 'string',
},
boolProp: {
name: 'boolProp',
kind: 'bool',
}
},
},
},
};
Custom components to input values can be rendered to easier input values for fields. Custom components can be defined with the customInputs option which contains the following object:
`ts`
{ [format: string]: ComponentCreator; }
`ts
export type ComponentCreator = (root: HTMLElement, args: ComponentParams) => Component;
export type ComponentParams = {
value: Token | null | undefined;
onInput(value: string, kind: string, close?: boolean): void;
onKeyDown(e: KeyboardEvent): void;
}
export type Component = {
update(value: Token): void;
destroy(): void;
focus(): void;
}
`
When a field doc contains a format field (eg. myField: { format: 'date' }), when entering this field (myField == ...) the exitor will then show this custom input in the dropdown.
Example:
`ts
import AirDatepicker from 'air-datepicker';
import 'air-datepicker/air-datepicker.css';
import localeEn from 'air-datepicker/locale/en';
const customInputs = {
date: (node, { value, onInput }) => {
const date = value ? value.value.replaceAll('"', '') : null;
const picker = new AirDatepicker(node, {
inline: true,
selectedDates: [date],
locale: localeEn,
onSelect({ date }) {
if (Array.isArray(date)) {
return;
}
onInput("${date.toISOString()}", '', false);
},
});
return {
update(selectedToken) {
const date = selectedToken ? selectedToken.value.replaceAll('"', '') : null;
picker.selectDate(date, { silent: true });
},
focus() {
},
destroy() {
picker.destroy();
}
}
}
}
``