@mojule/schema-forms
v0.3.1
Published
Generate form elements from JSON Schema
Downloads
5
Readme
schema-forms
Generate HTML forms from JSON Schema
This module attempts to provide a toolkit which is small, simple and flexible enough to cover all use cases
todo: rationale, toc, example code, playground, full documentation, etc
npm install @mojule/schema-forms
Note, uses $id
from Schema 6+, not id
like in schema4
features
- supports all schema functionality required for the majority of data models, with remaining functionality planned in future releases
- modular and extensible - use the convenience functions provided to create forms with a few lines of code or easily override any of the functionality by following the established template/decorator/api patterns
- 100% test coverage
- typescript typings
- no strings, generates actual HTML elements for the browser or server (server via JSDOM or similar, easily serialized to a string to send as an HTTP response)
- no dependencies
- simple class-free coding style
quickstart
Write a schema:
{
"type": "object",
"title": "Contact Form",
"properties": {
"name": {
"type": "string",
"title": "Name"
},
"email": {
"type": "string",
"title": "Email",
"format": "email"
},
"subject": {
"type": "string",
"title": "Subject",
"default": "I have a query"
},
"message": {
"type": "string",
"title": "Message",
"format": "multiline"
}
},
"required": [ "name", "email", "message" ]
}
In the browser:
import { SchemaToFormElements } from '@mojule/schema-forms'
import * as schema from '/path/to/your/schema'
const createEl = SchemaToFormElements( document )
const el = createEl( schema )
console.log( el.outerHTML )
On the server:
import { JSDOM } from 'jsdom'
import { SchemaToFormElements } from '@mojule/schema-forms'
import * as schema from '/path/to/your/schema'
const { document } = new JSDOM( '<!doctype html>' ).window
const createEl = SchemaToFormElements( document )
const el = createEl( schema )
console.log( el.outerHTML )
Outputs:
<fieldset>
<legend>Contact Form</legend>
<div title="Contact Form">
<label>
<span>Name</span>
<input type="text" title="Name" required="" name="name">
</label>
<label>
<span>Email</span>
<input type="email" title="Email" required="" name="email">
</label>
<label>
<span>Subject</span>
<input type="text" title="Subject" name="subject" value="I have a query">
</label>
<label>
<span>Message</span>
<textarea title="Message" required="" name="message"></textarea>
</label>
</div>
</fieldset>
supported schema functionality
Some unsupported features are planned (particularly anyOf
/oneOf
, enum
)
PRs will be welcome after 1.0 release, until then please open an issue to discuss
todo: document which features won't be supported as they don't make sense for generating forms, additionally the lists below were written when it was using schema 4, currently at schema 7, so update them for that too
✔️ string
- ✔️ minLength
- ✔️ maxLength
- ✔️ pattern
- ✔️ format
✔️ number / ✔️ integer
- ✔️ multipleOf
- ✔️ minimum
- ✔️ maximum
✔️ object
- ✔️ properties
- ✔️ required
- ❌ additionalProperties
- ❌ minProperties
- ❌ maxProperties
- ❌ dependencies
- ❌ patternProperties
todo: custom api/decorator to support advanced keywords
✔️ array
- ✔️ items (list)
- ✔️ items (tuple)
- ✔️ minItems
- ✔️ maxItems
- ❌ additionalItems
- ❌ contains
- ❌ uniqueItems
todo: custom api/decorator to support advanced keywords
✔️ boolean
❌ null
todo: support via hidden element
✔️ generic keywords
- ✔️ title
- ✔️ default
- ✔️ type (string)
- ✔️ type (undefined)
- ❌ type (array)
- ❌ description
- ❌ enum
Where type is undefined, the type will be inferred according to which template was called, but note that type is required for child schema of arrays and objects, as these cannot be inferred - any child schema missing a type will be skipped
todo: enum is easy for primitive types, description could be added via a decorator in the same way that title is added with the label decorator, consider strict mode that throws instead of skipping, child types can be inferred in most cases if they have type-specific keywords
❌ combining keywords
- ❌ allOf
- ❌ anyOf
- ❌ oneOf
- ❌ not
todo: support anyOf
/oneOf
, these are easy via api/decorator similar to how
array
lists already work - possibly not
in some situations - allOf
is
not possible without making some opiniated decisions about how it should
work, see prior art in various npm modules that attempt this, they all do it
differently. An inelegant solution would be to treat allOf
the same way as
an array
tuple and let the consumer of the form data figure out what to do
with it, makes for an ugly and cluttered form for the user however
❌ $ref
todo: full resolution eg with http and etc is beyond the scope of this package and there are plenty of good packages to handle this. A decorator/api that works with this package could be published as a separate module.
Even so, $ref
can be circular but again an api and decorator can be written
that takes the required schema and allows adding them tree-style without
triggering an infinite loop
❌ definitions
Essentially the same as $ref
, pre-resolve them or create an external module
to handle it
Convenience Factories
SchemaToFormElements
Creates a single function for turning a schema into an element (schema must
have a type: string
keyword ):
import { SchemaToFormElements } from '@mojule/schema-forms'
const createEl = SchemaToFormElements( document )
const inputText = createEl( { type: 'string' } )
todo: document
TypeTemplates
Create an object containing basic templates for each schema type:
import { TypeTemplates } from '@mojule/schema-forms'
const templates = TypeTemplates( document )
templates
will be an instance of Templates
with no decorators applied:
export type SchemaTemplate =
( schema?: JSONSchema7, name?: string, value?: any, isRequired?: boolean ) => HTMLElement
export interface Templates {
array: SchemaTemplate
boolean: SchemaTemplate
number: SchemaTemplate
integer: SchemaTemplate
object: SchemaTemplate
string: SchemaTemplate
[ name: string ]: SchemaTemplate
}
todo: document
ServerFormTemplates
Create an object containing basic templates for each schema type, pre-decorated with labels and fieldsets:
import { ServerFormTemplates } from '@mojule/schema-forms'
const templates = TypeTemplates( document )
// <label><span>Foo</span><input type="text" name="foo" value="Bar"></label>
const el = templates.string( { title: 'Foo', default: 'Bar' }, 'foo' )
todo: document
ClientFormTemplates
Create an object containing basic templates for each schema type, pre-decorated with labels and fieldsets and using the mutable array list decorator so that the user can add and delete items from an array list:
import { ClientFormTemplates } from '@mojule/schema-forms'
const templates = ClientFormTemplates( document )
// <label><span>Foo</span><input type="text" name="foo" value="Bar"></label>
const el = templates.string( { title: 'Foo', default: 'Bar' }, 'foo' )
todo: document
Template Factories
Template Factory functions are exported for each schema type
The factory functions take an instance of document, allowing use in either the browser or on the server via JSDOM or any other module that provides an implementation of Document, and return a function that creates an HTML element
The signature for a schema template is:
( schema?: JSONSchema7, name?: string, value?: any, isRequired?: boolean ) => HTMLElement
StringTemplate
Get the template factory:
import { StringTemplate } from '@mojule/schema-forms'
Create a function that returns an input[type="text"]
element:
const inputText = StringTemplate( document )
No arguments:
const foo = inputText()
// <input type="text">
console.log( foo.outerHTML )
Empty schema and name:
const foo = inputText( {}, 'foo' )
// <input type="text" name="foo">
console.log( foo.outerHTML )
Schema provides default value:
const foo = inputText( { default: 'bar' } )
// <input type="text" value="bar">
console.log( foo.outerHTML )
Default value passed explictly, empty name:
const foo = inputText( {}, '', 'bar' )
// <input type="text" value="bar">
console.log( foo.outerHTML )
Default value and name provided:
const foo = inputText( { default: 'bar' }, 'foo' )
// <input type="text" name="foo" value="bar">
console.log( foo.outerHTML )
Empty schema, name and value:
const foo = inputText( {}, 'foo', 'bar' )
// <input type="text" name="foo" value="bar">
console.log( foo.outerHTML )
Empty schema, no name, no value, isRequired is true:
const foo = inputText( {}, '', '', true )
// <input type="text" required>
console.log( foo.outerHTML )
Empty schema, name, value, isRequired is true:
const foo = inputText( {}, 'foo', 'bar', true )
// <input type="text" name="foo" value="bar" required>
console.log( foo.outerHTML )
StringTemplate takes an optional extra argument, isMultiline
, to generate a
textarea
instead of an input element:
const textarea = StringTemplate( document, true )
const foo = textarea()
// <textarea name="foo"></textarea>
console.log( foo.outerHTML )
todo: document minLength, maxLength, pattern, how format is used (see format decorator below ) etc.
NumberTemplate
Get the template factory:
import { NumberTemplate } from '@mojule/schema-forms'
todo: document in full, basically same as string
ObjectTemplate
Get the template factory:
import { ObjectTemplate } from '@mojule/schema-forms'
todo: document
ArrayTemplate
Get the template factory:
import { ArrayTemplate } from '@mojule/schema-forms'
todo: document
BooleanTemplate
Get the template factory:
import { BooleanTemplate } from '@mojule/schema-forms'
decorators
Decorators modify the template output, allowing you to customize the generated
DOM, eg <label>text <input></label>
vs
<label for="foo">text</label> <input id="foo">
, or adding extra metadata
for client side interactivity and any number of other use cases
LabelDecorator
Get the decorator factory:
import { LabelDecorator } from '@mojule/schema-forms'
Wrap all string inputs with labels:
import { LabelDecorator, StringTemplate } from '@mojule/schema-forms'
const labelledString = LabelDecorator( document, StringTemplate( document ) )
// <label><span>Foo</span><input type="text"></label>
const el = labelledString( { title: 'Foo' } )
todo: document
FieldsetDecorator
Get the decorator factory:
import { FieldsetDecorator } from '@mojule/schema-forms'
todo: document
FormatDecorator
Get the decorator factory:
import { FormatDecorator } from '@mojule/schema-forms'
todo: document
MutableArrayListDecorator
Get the decorator factory:
import { MutableArrayListDecorator } from '@mojule/schema-forms'
todo: document
api
API provides interaction without having to create and dispatch synthetic events, for eg manipulating arrays etc
ArrayListApi
Get the decorator factory:
import { ArrayListApi } from '@mojule/schema-forms'
todo: document
utils
todo: document - existing utils are useful for getting the data from a form
post, or from the form element itself in the client - easy to turn into json
using eg @mojule/json-pointer
; also, consider if it is out of scope for this
package to populate an already generated form from json-compatible object, if so
consider creating an external module
validation
todo: not really in scope for this package but an external module that plays
nicely with this one using ajv
or similar would be good
architecture, extending, customising etc.
todo: overview
how templates and template factories work, creating your own
todo: document
how decorators and decorator factories work, creating your own
todo: document
how apis and api factories work, creating your own
todo: document
license
MIT License
Copyright (c) 2019 Nik Coughlin
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.