auto-views-with-ui-schema v0.6.0
AutoViews
AutoViews is a set of utilities and abstractions which provides functionality to automatically build UI on top of JSONSchema.
To build UI automatically AutoViews uses some abstractions:
- AutoView — React component, which gets
JSONSchema,data, optionalUISchemaas prop andComponentsRepoascomponentsproperty incontextand renders accordingly - ComponentsRepo — class that keeps all components, grouped by data types (
string,objectand others, even custom data types) and optionally with theirspredicate's, which are boolean returning functions that defines ability of this component to render thisJSONSchemanode. - UISchema — is
JSONthat describes specific rules to render that specificJSONSchemanode:- what component and its settings to use,
- which
ComponentsReposhould be chosen to get component, - what
UIHintsto apply
Each component which is depends on state or other variables could decide which UISchema to use for render or return custom render result.
To address specific JSONSchema node UISchema uses JSONPointer as key and ComponentOptions as value to tell which component and it's setting to use, or what UIHints have to be applied.
Autoview Component
AutoView is component that will automatically render data which is validated with given JSONSchema with components registered in ComponentsRepo and pathed through RepositoryProvider.
To choose what specific component to be used and what UIHints have to be considered AutoView uses UISchema object and utilities.
Events
Components in ComponentsRepo may have AutoViewProps props interface which has optional onChange?: AutoEventHandler and onClick?: AutoEventHandler.
AutoEventHandler is object with next type:
type AutoEventHandler = (
e: React.SyntheticEvent<HTMLElement>,
autoEvent: AutoEvent
) => void;where AutoEvent is something very special:
interface AutoEvent {
schemaPointer: string;
pointer: string;
patch?: Operation[];
}This events may have JSONPatch operations on given data, which should be handled by application that uses AutoView.
This library provides handy event handlers creators for each JSONPatch operation.
ComponentsRepo
ComponentsRepo is class which accepts string name and optional getType function as constructor parameter.
Once you did instance, you can assign components to data types:
const repo = new ComponentsRepo('editComponents');
repo.register('number', {name: 'input', component: NumberInput});Using getType
const repo = new ComponentsRepo('editComponents', node => node.customTypeField);
repo.register('number', {name: 'input', component: NumberInput});getMatched
You can have as many components assigned to certain data type as you want.
If you need more then one, you may want to add predicate function during registering:
const hasMinMax = node => node.hasOwnProperty('minimum') && node.hasOwnProperty('maximum');
repo.register('number', {name: 'slider', component: Slider, predicate: hasMinMax});
repo.getMatched({type: 'number', minimum: 0, maximum: 10});Will return array of available components in registration order
[
{name: 'number', component: NumberInput},
{name: 'slider', predicate: hasMinMax, component: SliderInput}
]By default AutoView will pick last component in getMatched array.
Unless there is other component specified in UISchema.
ComponentsRepo instance should be provided to the AutoView context.
<RepositoryProvider components={repo}>
<AutoView {...props}/>
</RepositoryProvider>clone
It is possible to clone repository with all records with clone method.
const edit = new ComponentsRepo('edit');
repo.register('number', {name: 'input', component: NumberInput});
const form = edit.clone('form');
repo.register('object', {name: 'form', component: Form});clone also allows to override getType
const edit = new ComponentsRepo('edit', node => node.type);
const form = edit.clone('form', node => node.customTypeField);addWrapper
You can wrap all/some repository components into wrapper
const edit = new ComponentsRepo('edit');
repo.register('number', {name: 'number', component: NumberInput});
repo.register('string', {name: 'string', component: TextInput});
repo.addWrapper((item, props) => (
<div data-automation-id={`${props.pointer}#TEST`}>
<h3>{props.schema.title}</h3>
{item}
</div>
));example above will wrapp any component in repository, however it is possible to specify which components you want to wrap with include and exclude options.
Both include and exclude are optional options and accepts array of components name from register function
This will wrap on number component
repo.addWrapper((item, props) => (
<div data-automation-id={`${props.pointer}#TEST`}>
<h3>{props.schema.title}</h3>
{item}
</div>
), {
include: ['number']
});This will wrap all component except number
repo.addWrapper((item, props) => (
<div data-automation-id={`${props.pointer}#TEST`}>
<h3>{props.schema.title}</h3>
{item}
</div>
), {
exclude: ['number']
});UISchema
UISchema is an object that contains information about how to render JSONSchema.
There is a corresponding type UISchema.
Here we create a new UISchema and assign our editComponents repository to the UISchema.
Example is valid for geo schema
repo.register('string', {name: 'input', component: TextInput}
repo.register('string', {name: 'coordinateInput', component: CoordinateInput}
const uiSchema = createUISchema({
editComponents: { // key is repository `name`.
'/properties/longitude', {name: 'coordinateInput'},
'/properties/latitude', {name: 'coordinateInput'},
}
});
// ...
<RepositoryProvider components={repo}>
<AutoView
{...props}
uiSchema={uiSchema}
/>
</RepositoryProvider>So with the appropriate JSONSchema and data properties the AutoView component will render assigned components from the UIHints at the given JSONPointer's
Options
Component may take some options.
When choosing specific component to render certain data type in UISchema you may also set it's options.
const overrides = createUISchema({
viewComponents: {
'': {name: 'uppercasable', options: {uppercase: true}}
}
});
const {select} = clientRenderer.render(
<RepositoryProvider components={repo}>
<AutoView schema={schema} data="foo" uiSchema={overrides}/>
</RepositoryProvider>
);In this case component that is registered in viewComponents components repository by string type with name uppercasable should get options and accordingly to value of option name uppercase make data: string = 'foo' prop — uppercased in render result.
UIHints
UISchema contains not only rules for one or multiple ComponentsRepo but also keeps list of UIHints which are specific rules for data type. Thus, components which are implementing certain type of data may consider that UIHints.
Same as for ComponentsRepo overrides, UIHints uses JSONPointers.
At the following example we have special UIHints for root JSONSchema which says what order should it have.
Each component that assigned to type object in repo may consider order hint and render result accordingly.
This repo contains AutoFields component which is consider order and will consider other object related UIHints.
const schema: CoreSchemaMetaSchema = {
type: 'object',
properties: {
first: {type: 'string'},
second: {type: 'string'},
third: {type: 'string'}
}
};
const ObjectFields = props => (
<fieldset>
<AutoFields {...props} />
</fieldset>
);
const repo = new ComponentsRepo('editComponents');
repo.register('string', {name: 'input', component: TextInput}
repo.register('object', {name: 'fields', component: ObjectFields}
const uiSchema = createUISchema({}, {
'': {order: ['second', 'third', 'first']}
});
// ...
<RepositoryProvider components={repo}>
<AutoView
schema={schema}
uiSchema={uiSchema}
data={{first: '', second: '', third: ''}}
/>
</RepositoryProvider>So this example will render data fields with input in order which is defined in UIHints.
Manipulating UISchema values
There are functions to change UISchema type values in an immutable way:
Adding/changing UIHints:
const emptyUISchema = createUISchema();
const uiSchemaWithHints = setUIHints(
'/path/in/data',
{order: ['second', 'third', 'first']},
emptyUISchema
);Retrieving UIHints:
const hints = getUIHints('/path/in/data', uiSchemaWithHints);Removing UIHints:
const uiSchemaWithoutHints = unsetUIHints('/path/in/data', uiSchemaWithHints);Adding/changing component options:
const emptyUISchema = createUISchema();
const uiSchemaWithComponent = setComponent(
'repositoryName',
'/path/in/data',
{name: 'uppercasable', options: {uppercase: true}},
original
);Retrieving component options;
const componentOptions = getComponent(
'repositoryName',
'/path/in/data',
uiSchemaWithComponent
);Removing component:
const uiSchemaWithoutComponent = unsetComponent(
'repositoryName',
'/path/in/data',
uiSchemaWithComponent
);