Before reading about working with editron, make sure you have read the overview to docs/json-schema-and-editron-editors.
With a json-schema like
const jsonSchema = {
title: "My Data",
type: "object",
properties: {
title: {
title: "Title of introduction",
type: "string",
minLength: 1
},
wordCount: {
title: "Title of cord count",
type: "number"
}
}
}
we can create an instance of editron to generate data for this schema and perform data validation. Note, that creating an editron instance will not yet render anything.
import { Editron } from "editron";
const editronInstance = new Editron(jsonSchema);
editronInstance.getData(); // { title: "", wordCount: 0 }
You can also pass initial data to the editron constructor. This will merge the default data generated from your json-schema and the input data, e.g.:
import { Editron } from "editron";
const editronInstance = new Editron(jsonSchema, { title: "Custom Title" });
editronInstance.getData(); // { title: "Custom Title", wordCount: 0 }
Confirming to json-schema specifications, you can place default values directly in a schema:
{
"type": "object",
"properties": {
"wordCount": {
"type": "number",
"default": 100
}
}
}
import { Editron } from "editron";
const editronInstance = new Editron(jsonSchema);
editronInstance.getData(); // { wordCount: 100 }
Options Editron uses a set of options on initialization. See docs/doc-editron-options for editron configuration details.
With our editron instance ready, we can start to render input forms for our data. To render a form on an html-element with an id my-data
:
import { Editron } from "editron";
const editronInstance = new Editron(jsonSchema);
editronInstance.createEditor("#", document.querySelector("#my-data"));
This will create an input form on the given html element, in our example, an input for title and another input for number wordCount. The first parameter of createEditor is a json-pointer and specifies, which part of the data should be rendered. #
refers to the root json-pointer, which means all data. This also means, we can selectively render portions of the data to the ui.
The following example will render one the input for title to a html element <div id="my-title"> and the input for wordCount to a html element <div id="my-word-count">:
import { Editron } from "editron";
const editronInstance = new Editron(jsonSchema);
editronInstance.createEditor("#/title", document.querySelector("#my-title"));
editronInstance.createEditor("#/wordCount", document.querySelector("#my-word-count"));
Additionally, input forms can be rendered to multiple locations. Editron will sync updates between them.
Note rendering partial uis enables features like pagination or form details to be shown in a separate place of the application.
Created instances should be correctly removed, either within an editor or when using editron in an application. This step may be omitted, when the whole editron instance is destroyed. For a created editor in your application, use the destroy-method to completely remove the editor from editron and the dom:
import { Editron } from "editron";
const editronInstance = new Editron(jsonSchema);
const editor = editronInstance.createEditor("#", document.querySelector("#my-data"));
// ...
// when done, destroy the editor
editronInstance.destroyEditor(editor);
And when editron should be removed, simply call destroy
, which will also destroy all created editor instances
editronInstance.destroy();
Note To destroy an editor, you have to use the editron-method
editron.destroyEditor(myEditor)
. This will ensure all editor bootstrapping is removed from the editor. e.g. theupdate
-method is automatically registered and will continue to be called. So, do not use editor.destroy() directly or ensureeditron.destroyEditor(this)
is called on the editorsdestroy
-method.
An editron instance exposes basic functions to set and get data, json-schema and validation results as is listed in the following overview. For more advanced interactions and event-listeners, editron exposes several services that are described in the following section under editron-services.
So for the editron instance with
import { Editron } from "editron";
const editron = new Editron(jsonSchema);
the following methods are exposed:
method | description |
---|---|
getSchema(): JSONSchema |
returns the current json-schema |
setSchema(:JSONSchema) |
change the used json-schema to the passed schema |
setData(data) |
update initial data with passed data |
getData(): any |
returns whole date object |
getData(:JSONPointer): any |
returns the data from passed json-pointer |
validateAll() |
triggers async json-schema validation of whole data |
getErrors(): Error[] |
returns all current validation errors and warnings |
isActive(): boolean |
returns true, if the user form is in edit mode |
setActive(:boolean) |
if false is passed, will deactivate all user forms |
service(:ServiceID) |
Will return editron service matching serviceId |
proxy(): Foxy |
Will editrons proxy for configurable requests |
registerEditor(:Editor) |
adds a custom editron editor to available editors |
You can read about services in the next section editron-services. Refer to section proxy to learn how to expose custom requests to editron editors.
The order of the editors-list is relevant. Any json-schema will be resolved in order, starting at the first index, a matching editor-Constructor is searched. The first editor to return true (for Class.editorOf) will be used to represent the given json-schema and instantiated. Thus more specific editors should be at the start of list, where more generale editors, like object or default values should come last.
To completely reset the available list of editors, you can modify the editors property directly
editron.editors.length = 0;
The editron instance exposes all required methods and services for usage in an application, for custom editors and for editron plugins. Each editron service exposes its own api and manages a specific part of the json-editor. You can access a service by the method service(serviceId: string)
which may be called anytime on an editron instance. What follows is a quick overview of services, followed by a details description of their apis.
serviceId | serviceName | description |
---|---|---|
data | DataService | Manages data updates, change notifications and undo/redo stack |
validation | ValidationService | Performs data validation and emits error events |
schema | SchemaService | Manages retrieval and caching of json-schemas for each data |
location | LocationService | Manages global input selection and scroll to behaviour |
All services use the json-pointer specification to describe values within the managed data. Recap: a json-pointer describes a path into data, starting with a hash
#
, and each property is separated by a slash. e.g."#/todos/2"
describes a path to the third array element of a list on property todos. The root or whole object can be described as a root-pointer"#"
.
The DataService manages all input-data. Use this service to
- make changes to data
- get latest data
- watch for changes made to data
- watch for changes made on a specific json-pointer
- run undo and redo
You access the service through an editron instance by
const dataService = editron.service("data");
Working on data
You can set and get data as usual
const data = editron.service("data").get();
editron.service("data").set("#", data);
And as might be seen, data may also be directly selected:
const title = editron.service("data").get("#/title");
editron.service("data").set("#/title", "modified title");
Undo/Redo
The DataService also exposes undo/redo states
// get steps
const undoCount = editron.service("data").undoCount();
const redoCount = editron.service("data").redoCount();
// and performs undo actions
editron.service("data").undo();
editron.service("data").redo();
Listen to changes
The DataService emits events when something in data has changed. It supports two different event systems, a method watch to notify about all changes made on data and a method observe to listen for changes on a specific json-pointer.
Watch updates
The watch-method is usually used by an application, where you are interested in all changes of data, e.g. you want to sent the latest data when it has beend changed:
// This will add
editron.service("data").watch(event => {});
The callback in watch receives an event. For an update, multiple events will be sent to each watcher, like before update start, when a single change was notified and finally, when all watchers and observers have beend notified ("data:update:done"). You might use a switch statement, to select the event you need for your task, like:
const watcher => {
switch (event.type) {
case "data:update:done":
// respond to data change
console.log(event.value);
break;
}
}
editron.service("data").watch(watcher);
And remove the watch with
editron.service("data").removeWatcher(watcher);
For an up to date list of events, refer to the callback types in DataService or simple log to your console.
Note Each event has a property
type:string
and a propertyvalue
, where the type ofvalue
will vary based on the event-type.
Watch updates of a value
Observing data changes for a single value is usually used by an editron editor. Each editor updates and displays a single value and thus is interested in this value only. As always, the value is described by a json-pointer. To use the observer-api, pass the json-pointer you are interested and a callback-function receiving an updateEvent, with { type: "data:update", value }
:
const onUpdate = update => {};
editron.service("data").observe("#/title", onUpdate);
and remove the observer with
editron.service("data").removeObserver("#/title", onUpdate);
In same cases you might want updates from a certain point in data. For example, watch an object and all its children. For example: An editor that manages a whole object, like a point on a map with x and y coordinates:
{
"properties": {
"mapPoint": {
"type": "object",
"properties": {
"x": { "type": "number" },
"y": { "type": "number" }
}
}
}
}
Here, there are no individual forms listening to x and y. Some goes for validation errors. A map editor in this example may pass true
as a second parameter to observe(callback, true)
. With this, any observer will get notified of data changes on the json-pointer and also receives events if a child-value has been changed:
editron.service("data").removeObserver("#/mapPointer", onUpdate, true);
DataService API Overview
method | description |
---|---|
set(pointer: string, value) |
sets value at pointer-location to the passed value |
get(pointer?: string) |
returns the data at json-pointer. Defaults to all |
delete(pointer: string) |
deletes value at the given json-pointer |
undo() |
changes the state back to previous state |
redo() |
reverts the previous undo change |
getDataByReference(pointer) |
returns a direct reference to stored data |
undoCount(): number |
returns a number of available undo steps |
redoCount(): number |
returns a number of available redo steps |
resetUndoRedo() |
clears the undo/redo-stack |
watch(callback) |
adds a watcher to update events |
removeWatcher(callback) |
removes a registered watcher from update events |
observe(pointer, cb, bubble?) |
registers an observer to a json-pointer |
removeObserver(pointer, cb) |
removes a registered pointer-observer |
isValid(pointer) |
returns true when the passed location exists in data |
The ValidationService
manages the validation process, stores and notifies any input-errors within the data. Basic validation is usually performed sync, but custom validators may also introduce async validation. Thus, validation errors are notifies at once, when they resolve and the service will end with a validation:done event, when all errors have been omitted. As with the DataService, the ValidationService has two types of event-system. A simple watch
-method for general validation updates and an observe-method to watch specific values referenced by a json-pointer. You can access the ValidationService throuh any editron instance:
const validationService = editron.service("validation");
A new validation is started after each data change automatically. To manually start a validation use
editron.validateAll();
Errors and warning
Each ValidationError has a type of error e.g.
// example errorObject
code: "min-length-error"
data: { minLength: 1, length: 0, pointer: "#/title" }
message: "Value `#/title` should have a minimum length of `1`, but got `0`."
name: "MinLengthError"
severity: "error"
type: "error"
Each error can be differentiated by a severity. For custom validation methods a warning may be placed on a property severity. Editron exposes helpers to access the latest validation results, optionally differentiating by severty:
// most of the time, you will be interested in errors
const errors = editron.service("validation").getErrors();
// but warnings are also supported `{ type: 'warning' }`
const warnings = editron.service("validation").getWarnings();
// or get both
const problems = editron.service("validation").getErrorsAndWarnings();
Watch validation errors
The ValidationService will emit one event after validation containing all errors on property value
const onError = validationEvent => {};
editron.service("validation").watch(onError);
// ...
// and remove the watcher with
editron.service("validation").unwatch(onError);
with a validationEvent defined as { type: "validation:done", value: Array<ValidationError> }
Watch validation errors of a value
The ValidationService will notify each error individually
const onError = validationEvent => {};
editron.service("validation").observe("#/title", onError);
// ...
// and remove the observer with
editron.service("validation").removeObserver("#/title", onError);
ValidationService API Overview
method | description |
---|---|
getErrors() |
returns all errors with severity: error |
getErrorsAndWarnings() |
returns all errors with any severity |
getWarning() |
returns all errors with severity: warning |
observe(pointer, cb, bubble?) |
registers an observer to a json-pointer |
removeObserver(pointer, cb) |
removes a registered pointer-observer |
setErrorHandler(cb) |
set handler being called for each error (e.g. error translation) |
unwatch(callback) |
removes a registered watcher from validation events |
watch(callback) |
adds a watcher to validation:done events |
The SchemaService
manages schema retrieval based on the current data. Additionally, it caches resolved schemas for the current data object. You can access the SchemaService throuh any editron instance:
const schemaService = editron.service("schema");
As usual, you can get a schema based on a json-pointer, e.g.
const titleSchema = editron.service("schema").get("#/title");
// { title: "Title of introduction", type: "string", minLength: 1 }
SchemaService exposes two additional helpers methods for data generation based in a json-schema:
// generate data, confirming to the given json-schema
const templateData = editron.service("schema").getTemplate(jsonSchema);
// add any missing data, according to the json-schema
const validInputData = editron.service("schema").addDefaultData(inputData, jsonSchema);
Note Most json-schema can be resolved statically, but in case of e.g. oneOf statements the current data is required. Editron will ensure data is updated in SchemaService automatically
SchemaService API Overview
method | description |
---|---|
get(pointer):JSONSchema |
returns the json-schema of the given json-pointer |
getTemplate(:JSONSchema) |
returns generated data matching the passed json-schema |
addDefaultData(data, :JSONSchema) |
returns a merge of passed and generated data matching json-schema |
The LocationService
manages input selection, notifies of selection changes and exposes scroll-to helpers. Detailed usage of LocationService
within a custom editor is documented in docs/docs-editron-editor.md.
LocationService API Overview
method | description |
---|---|
goto(pointer,?:HTMLElement) |
focus the editor of the passed json-pointer |
getCurrent(): pointer |
returns the currently selected json-pointer |
watch(callback) |
add watcher for changes in current location |
removeWatcher(callback) |
remove a registered watcher |
setCurrent(pointer) |
set input for json-pointer as currently focused |
blur(pointer) |
unfocus an active and matching input element |