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.
How the editor looks:
Using the editor as query box only in an existing Editor:
Used the Editor as a SQL cell editor in a Notebook:
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;
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" />
);
},
);
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/"
This section shows some basic recipes and examples to get started with editor. Many more examples are currently only documented in the demo app.
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' }] };
},
};
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.