Editor

The Editor “Query Box” comes as a fully pluggable <ComposeEditor /> Component that can be swapped with your current editor element or text-area.

See a short example in the demo app.

Overview

How the editor looks: Editor Component

Using the editor as query box only in an existing Editor:

“Editor”

Used the Editor as a SQL cell editor in a Notebook:

“Notebook”

Importing

Page

First install the NPM package and import the component into the page.

import React from 'react';
import './App.css';
import { ComposeEditor } from './components/ComposeEditor';

function App() {
  return (
    <div className="app">
      <header className="app-header">
        <p>Compose Editor</p>
      </header>

      <ComposeEditor
        dialect="dbsql"
        className="compose"
        catalogApi={catalogApi}
        // schemaCatalog={catalogSchema} // We can also provide the catalog json data directly
        // staticUrl="/" // Url prefix of the static file directory
      />

    </div>
  );
}

export default App;

Component

The non native objects (e.g. providing an API to go fetch the list of tables dynamically so those can show-up as suggestions) is done in the ComposeEditor.tsx file. They can not be provided via standard props in the static HTML page but can be bound to it post creation via the JavaScript framework of your choice. Here is an example with React.js:

import editor from 'queryflow/lib/components/ComposeEditorWebComponent';

export const ComposeEditor = React.forwardRef(
  (
    { className, initialValue, schemaCatalog, catalogApi, onValueChange, onSelectionChange }: ComposeEditorProps,
    ref,
  ) => {
    const [editorRef, setEditorRef] = useState<unknown>();
    const composeEditorRef = useRef<ComposeEditorElement>();

    useEffect(() => {
      if (composeEditorRef) {
        editor.setProperty<ComposeEditorElement>(composeEditorRef.current, 'schema-catalog', schemaCatalog);
        editor.setProperty<ComposeEditorElement>(composeEditorRef.current, 'catalog-api', catalogApi);
        editor.setProperty<ComposeEditorElement>(composeEditorRef.current, 'on-selection-change', (selectedValue: string) =>
          onSelectionChange?.(selectedValue || null),
        );
        editor.setProperty<ComposeEditorElement>(composeEditorRef.current, 'on-value-change', onValueChange);
        editor.setProperty<ComposeEditorElement>(composeEditorRef.current, 'on-ace-editor-created', setEditorRef);
      }
    }, [schemaCatalog, catalogApi, onSelectionChange, onValueChange, composeEditorRef]);

    useImperativeHandle(ref, () => editorRef, [editorRef]);

    return (
      <compose-editor ref={composeEditorRef} class={className} initial-value={initialValue} dialect="dbsql" />
    );
  },
);

Static files

The optional syntax and location highlighers require the JavaScript files of the Web Workers. Those should be served inside a workers directory just below the static url prefix.

These files are generated via the workers bundle command. The static directory will probably support other files like font ot styling in the future.

The default static url root is just “/” like on the demo app.

e.g. if we are serving the files via https://compose.app/workers:

staticUrl="/"

e.g. if we are serving the files via https://compose.app/static/bundles/workers:

staticUrl="/static/bundles/"

e.g. if we are serving the files from https://compose.app/static/workers and the editor is on another domain https://compose.com/editor:

staticUrl="https://compose.app/static/"

API

This section shows some basic recipes and examples to get started with editor. Many more examples are currently only documented in the demo app.

Listing tables, databases, columns

In order to suggest dynamic content like the list of tables, a catalogApi client following this interface is needed:

const catalogApi = {
  getDatabases() {
    return { schema: ['default', 'customers'] };
  },
  getDatabaseTables(databaseName: string) {
    return {
      schema: [
        { name: 'default.diamonds', columns: [], type: 'TABLE' },
        { name: 'default.airlines', columns: [], type: 'TABLE' },
      ],
    };
  },
  getTableColumns(databaseName: string, tableName: string) {
    if (tableName === 'diamonds') {
      return {
        schema: [
          { name: 'carat', type: 'STRING' },
          { name: 'cut', type: 'STRING' },
          { name: 'color', type: 'STRING' },
          { name: 'clarity', type: 'STRING' },
        ],
      };
    } else if (tableName === 'airlines') {
      return {
        schema: [
          { name: 'date', type: 'DATE' },
          { name: 'delay', type: 'INTEGER' },
          { name: 'distance', type: 'INTEGER' },
          { name: 'origin', type: 'STRING' },
          { name: 'destination', type: 'STRING' },
        ],
      };
    }
    return { schema: [{ name: 'accountId', type: 'STRING' }] };
  },
};

Inline Errors & Warnings

Errors and warnings can be provided from the outside through the messages property:

<ComposeEditor messages={[...]} />

Each message object needs to have the following properties:

interface Message {
  type: 'error' | 'warning';
  line: number;
  text: string;
}

When using the compose-editor web component directly instead of the ComposeEditor.tsx React component, a property with the same name will be set:

<compose-editor ref={editorRef} />
useEffect(() => {
  if (editorRef.current) {
    editorRef.current.setAttribute('messages', '');
    editorRef.current.messages = messages;
  }
}, [editorRef, messages]);

Note that since this is a complex data structure, you can’t set messages as an attribute in the HTML.

To document

  • Snippets
  • Autocomplete
  • Read only
  • Autocomplete off