1.0.0 • Published 7 months ago

@happening-oss/expr2sql-editor v1.0.0

Weekly downloads
-
License
-
Repository
github
Last release
7 months ago

An auto-complete query builder for expr-lang expressions, designed for intuitive filtering and search. Compatible with expr2sql. 🚀 Try the live demo: Click here!

Features

  • 📝 Auto-complete with field and operator suggestions
  • 🔍 Syntax validation for expressions
  • 🎛️ Custom input components for specific data types

Getting Started

Install from npm:

npm i @happening-oss/expr2sql-editor

Define the target element:

<div id="editor"></div>

Initialize the ExprEditor component:

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);
  },
});

Configuration

ExprEditor accepts two parameters, one is the target element which the editor loads in. The other is the configuration object.

const editor = new ExprEditor(target, options);

Raw input vs expression

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 <search><separator><expression>. Examples when the separator is ::

Raw valueSearchExpression
test123:field1 == "Foo"Search + expressiontest123field1 == "Foo"
some stringJust searchsome string
:timestamp < '2025-01-01'Just expressiontimestamp < '2025-01-01'
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;
}

Field information

Autocomplete and suggestions require the doc option to be set. It contains all of the available fields and optionally the operators. Example:

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 fields

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:

{ [format: string]: ComponentCreator; }
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:

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();
      }
    }
  }
}