Skip to content

Latest commit

 

History

History
474 lines (333 loc) · 18.7 KB

howto-work-with-editron.md

File metadata and controls

474 lines (333 loc) · 18.7 KB

How To Work With Editron

Before reading about working with editron, make sure you have read the overview to docs/json-schema-and-editron-editors.

Editron

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.

Generate User Forms

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.

Remove User Forms

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. the update-method is automatically registered and will continue to be called. So, do not use editor.destroy() directly or ensure editron.destroyEditor(this) is called on the editors destroy-method.

Editron API

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.

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;

Editron Services

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 "#".

DataService

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 property value, where the type of value 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

ValidationService

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

SchemaService

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

LocationService

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

Editron Utils