<pure-form> creates HTML forms with validation, UX & styling from JSON Schemas in 7k of pure JavaScript
Clone or download

README.MD

pure-form

Shippable branch Linked In Twitter Follow

<pure-form> is a 7k, pure JavaScript, drop-in replacement for the HTML <form> that allows forms to be created with validation, UX and styling using a JSON Schema.

Example:

<pure-form src="space-camp-application.json"></pure-form>

Produces:

Cucumber HTML report

Live Demo. Works in all modern web/mobile browsers from IE9+ - including Cordova/PhoneGap and iconic projects.

Features

  • Creates a schematic HTML FORM from a JSON Schema
  • Automatically handles client side validation
  • Provides visual validation on blur of form fields (optional)
  • Get/set the value of the entire form using JSON
  • Displays character limits (including characters remaining) when maxLength set
  • Preserves partially completed form values between refreshes (optional)
  • Renders a button for each REST endpoint included in the schema links collection
  • Automatically executes REST request when button clicked

Table of Contents

Install

Add the dist/pure-form.min.js and dist/pure-form.min.css files to your project:

Usage

HTML

<html>
  <head>
    <!-- add document.registerElement polyfill if IE/Safari support required -->
    <script src="document-register-element.js"></script>

    <!-- add pure-form script & css -->
    <link href="pure-form.min.css" rel="stylesheet" />
    <script src="pure-form.min.js"></script>
  </head>
  <body>
    <!-- create just like a regular HTML tag (can also be created using document.createElement) -->
    <pure-form id="contactForm" src="contact-form.json" validate-on-blur="true"></pure-form>
  </body>
</html>

JSON Schema

Pure form uses JSON Schema (v3) to define the forms input requirements and validation. The space-camp-application.json referenced above:

{
  "type": "object",
  "id": "space-camp-application",
  "$schema": "http://json-schema.org/draft-03/schema#",
  "title": "Space Camp Scholarship Application",
  "description": "Please provide as much detail as possible",
  "links": [
    { "rel": "self", "title": "Apply", "href": "http://localhost:8080/223423423342", "method": "PUT" }
  ],
  "additionalProperties": false,
  "properties": {
    "title": {
      "title": "Title",
      "id": "order1",
      "type": "string",
      "required": true,
      "enum": [
        "Dr",
        "Mr",
        "Mrs",
        "Ms"
      ]
    },
    "firstName": {
      "title": "First name",
      "id": "order2",
      "type": "string",
      "required": true,
      "description": "John"
    },
    "surname": {
      "title": "Surname",
      "id": "order3",
      "type": "string",
      "required": true,
      "description": "Appleseed"
    },
    "email": {
      "title": "Email",
      "id": "order4",
      "type": "string",
      "format": "email",
      "required": true,
      "description": "example@domain.com"
    },
    "phone": {
      "title": "Telephone",
      "id": "order5",
      "type": "string"
    },
    "message": {
      "title": "Experience",
      "id": "order6",
      "type": "string",
      "format": "textarea",
      "required": true,
      "maxLength": 100
    }
  }
}

A button is rendered for each link item, the title property is used as the button label. If a link contains a rel that starts with describedby:, it is assumed to be a link to another schema, clicking that button therefore simply loads that schema.

The markup created within the pure-form tag is just regular HTML and can be styled accordingly.

Attributes

The following attributes can be set in HTML or via .setAttribute(propertyName, value):

Attribute Description
action Specifies where to send the form-data when a form is submitted
enctype The standard form enctype attribute specifies how the form-data should be encoded
method Specifies how to send form-data, options are get (send as URL variables or post (as HTTP post transaction)
src Path to the JSON Schema that defines the form
readonly If true, adds read only property to all form fields
data-title The title to be rendered before the form
description The description before the form
buttons Comma separated list of button labels (excludes buttons created by schema links)
show-schema-buttons If true, renders a button for each of the schema links
persist Partially completed forms survive refreshes (until browser is closed)
storage location to store presistant content, defaults to sessionStorage
disable-validation Disables all form validation
placeholder-max-length Max description length to be used as placeholder, values greater than are inlined
autofocus-error If set to true, sets focus to the first input containing an error after validation
validate-on-blur If set to true, validates each form field as it looses focus (disabled .autofocusError)
tab-on-enter If true, moves focus to the next field in the form when enter is pressed
submit-on-enter If true, clicks the first button in the form when the enter key is pressed
use-form-tag If false, uses a DIV rather than a HTML FORM tag (helps overcome iOS GO button issue)
enforce-max-length If true, does not allow the user to enter more characters that the value of schema maxLength property
schema-id the id of the current schema (readonly)
autofocus-id id of the field to set autofocus
auth-token HTTP Authorization Bearer token
http-headers TODO: document this!
number-range-selector If true, renders a HTML select for number ranges (default true)

Properties

Assuming contactForm = document.getElementById('contactForm'), the following properties can be set via JavaScript:

Property Type Default Description
action string "" Specifies where to send the form-data when a form is submitted (if empty posts to current page)
enctype string application/x-www-form-urlencoded The standard form enctype attribute specifies how the form-data should be encoded
method string get Specifies how to send form-data, options are get (send as URL variables or post (as HTTP post transaction)
src string "" Path to the JSON Schema that defines the form
schema object null Schema object to use
value object {} Object containing key/value pair of values
readonly boolean false If true, adds readonly property to all form fields
title string "" The title to be rendered before the form
description string "" The description before the form
buttons string "" Comma separated list of button labels (excludes buttons created by schema links)
showSchemaButtons string true If true, renders a button for each of the schema links
persist boolean false Partially completed forms survive refreshes (until browser is closed)
storage string sessionStorage Location to store partially completed content
disableValidation boolean false Disables all form validation
placeholderMaxLength boolean 75 Max description length to be used as placeholder, values greater than are inlined
autofocusError boolean false If set to true, sets focus to the first input containing an error after validation
validateOnBlur boolean false If set to true, validates each form field as it looses focus (disabled .autofocusError)
tabOnEnter boolean false If true, moves focus to the next field in the form when enter is pressed
submitOnEnter boolean false If true, clicks the first button in the form as would a normal HTML form
useFormTag boolean true If false, uses a DIV rather than a HTML FORM tag (helps overcome iOS GO button issue)
enforceMaxLength boolean false If true, does not allow the user to enter more characters that the value of schema maxLength property
schemaId string empty id valid of the currently rendered schema (readonly)
autoResize boolean false if true, expands the height of text and html inputs to display content - default false
isDirty boolean false true if the form value was changed by the user
autofocusId string `` id of the field to set autofocus
links array empty get/set array of HATEOAS that are rendered as buttons
authToken string empty HTTP Authorization Bearer token

action

// change the form post destination url
contactForm.action = 'http://www.example.com/example';

enctype

// change the format data is sent to the server
contactForm.enctype = 'multipart/form-data';

method

// post the form data to the server
contactForm.method = 'post';

src

// setting the src triggers loading of the JSON Schema:
contactForm.src = 'http://domain.com/schema-name.json';

schema

// get the schema retrieved from the server
var jsonSchema = contactForm.schema;

value

// get the value of the form as a JSON object
var formValue = contactForm.value;

// set the value of the form using a JSON object with keys matching fields
contactForm.value = {
  title: "Mr",
  firstName: "John",
  surname: "Doherty"
};

readonly

// set all form fields to readonly
contactForm.readonly = true;

title

// set the title rendered at the top of the form
contactForm.title = 'Contact Us';

description

// set the description rendered at the top of the form
contactForm.description = 'Got a question? Get in touch.';

buttons

// Add Ok and Cancel buttons
contactForm.buttons = 'Ok, Cancel';

contactForm.addEventListener('pure-form-button-clicked', function(e) {
  if (e.detail === 'Ok') {
    // Ok clicked, do something
  }
});

showSchemaButtons

// will cause a re-render and display a button for each schema .link
contactForm.showSchemaButtons = true;

persist

// preserve form data between page reloads
contactForm.persist = true;

storage

// store partially complete content in localStorage
contactForm.storage = 'localStorage';

disableValidation

// disable form validation
contactForm.disableValidation = true;

placeholderMaxLength

// the maximum number of characters to use a input placeholder before 
// rendering the item description under the field
contactForm.placeholderMaxLength = 25;

autofocusError

// automatically set focus to the first error found within a form
contactForm.autofocusError = true;

validateOnBlur

// automatically validate each field as it looses focus
contactForm.validateOnBlur = true;

tabOnEnter

// move to the next field when the user presses the enter key
contactForm.tabOnEnter = true;

submitOnEnter

// click the first button in the form when enter pressed similar to regular HTML form
contactForm.submitOnEnter = true;

useFormTag

// do not use a <form> tag to render form elements (helps fix iOS/Safari GO button issue)
contactForm.useFormTag = true;

enforceMaxLength

// stop the user from entering more character than the allowed amount (default is to inform user of overuse)
contactForm.enforceMaxLength = true;

schemaId

// get the currently rendered schema id
var schemaId = contactForm.schemaId;

autoResize

// allow textareas and contenteditable iframe (used for format=html) to auto expand height based on content
var schemaId = contactForm.autoResize = true;

isDirty

// use the .isDirty property to determine if the user have changed the values since the form was loaded or .value was set
var hasFormBeenUpdated = contactForm.isDirty;

autofocusId

// auto focus first name ready for input
contactForm.autofocusId = 'firstName';

links

// add a hateoas link object - renders a button that when clicked, sends form data to API link
contactForm.links = [
  // title is used as button text!
  { title: 'Save', rel: 'save', href: 'http://localhost:8080/api' }
]

authToken

// set authToken to be used as HTTP Authorization Bearer value in subsequent requests
contactForm.authToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9';

Methods

Each pure-form element exposes the following methods:

Method Description
submit(optionalRel) Submits the form to the server
clearError(fieldName) Clears error message on a form field
clearErrors() Clear all form error messages
setError(fieldName, errorMessage) Sets a custom error message on a form field
isValid(object, silent, ignoreRequired) Checks if the entire form is valid and updates UI
reset() Clears all form values and errors
checkValid() Checks if a value is valid against current schema

submit

// submit the form to the server just like a regular HTML form
document.getElementById('contactForm').submit();

// submit the form to the server using one of the schema link objects
document.getElementById('contactForm').submit('self');

clearError(fieldName)

// remove error message from email field
document.getElementById('contactForm').clearError('email');

clearErrors()

// remove all error messages
document.getElementById('contactForm').clearErrors();

setError(fieldName, errorMessage)

// set a custom error message on the phone field
document.getElementById('contactForm').setError('phone', 'Phone number is missing country code');

isValid(object, silent, ignoreRequired)

var contactForm = document.getElementById('contactForm');

if (contactForm.isValid()) {
  // form is valid, do some stuff
}
var contactForm = document.getElementById('contactForm');

// check if a value is valid against the loaded schema (pass true as second param to avoid updating the UI)
var isValidPhone = contactForm.isValid({ phone: '+441223 223 223' }, true);

if (isValidPhone) {
  // the phone number is valid according to form schema, do something
}

reset()

// Clear all form values and errors
document.getElementById('contactForm').reset();

checkValid()

// checks if a value is valid against the current schema
document.getElementById('contactForm').checkValid(propName, value);

Events

pure-form fires the following events (additional information is stored event.detail property):

Event Description
pure-form-submit Fired when the form is submitted to the server
pure-form-schema-loading Fires when a form schema is loading
pure-form-schema-loaded Fired when .src url is successfully loaded
pure-form-schema-error Fired when .src url fails to return a JSON Schema
pure-form-render-complete Fired when pure-form completes rendering
pure-form-button-clicked Fired when a pure-form button is clicked
pure-form-value-set Fired when the form value is set
pure-form-value-set-complete Fires when the value-set has finished updating the form
pure-form-validation-failed Validation failed, some field items are invalid
pure-form-validation-passed Validation passed, all field values are valid
pure-form-submit-complete Data posted to server and response received
pure-form-value-changed Change event fires whenever any item in the form changes

pure-form-submit

var contactForm = document.getElementById('contactForm');

contactForm.addEventListener('pure-form-submit', function(e) {
    console.log(this);            // pure form element
    console.log(e.target);        // pure form element
    console.log(e.detail.url);    // schema URL
    console.log(e.detail.status); // http status 200
    console.log(e.detail.body);   // JSON schema as a string
});

// trigger form submit
contactForm.submit();

pure-form-schema-loading

Fires when a JSON schema has started loading.

var contactForm = document.getElementById('contactForm');

contactForm.addEventListener('pure-form-schema-loading', function(e) {
    console.log(this);            // pure form element
    console.log(e.target);        // pure form element
    console.log(e.detail);    // schema URL
});

pure-form-schema-loaded

Fires when the JSON schema has loaded from .src url and assigned to .schema property.

var contactForm = document.getElementById('contactForm');

contactForm.addEventListener('pure-form-schema-loaded', function(e) {
    console.log(this);            // pure form element
    console.log(e.target);        // pure form element
    console.log(e.detail.url);    // schema URL
    console.log(e.detail.status); // http status 200
    console.log(e.detail.body);   // JSON schema as a string
});

pure-form-schema-error

Fires when JSON schema fails to load for whatever reason.

var contactForm = document.getElementById('contactForm');

contactForm.addEventListener('pure-form-schema-error', function(e) {
    console.log(this);            // pure form element
    console.log(e.target);        // pure form element
    console.log(e.detail.url);    // schema URL
    console.log(e.detail.status); // http status 404 (500 etc)
    console.log(e.detail.body);   // empty string
});

pure-form-render-complete

Fires when pure-form rendering has completed.

var contactForm = document.getElementById('contactForm');

contactForm.addEventListener('pure-form-render-complete', function(e) {
    console.log(this);      // pure form element
    console.log(e.target);  // pure form element
});

pure-form-button-clicked

<pure-form id="contact" src="path-to-json-schema.json" buttons="Delete,Disable"></pure-form>
var contactForm = document.getElementById('contactForm');

contactForm.addEventListener('pure-form-button-clicked', function(e) {
    console.log(e.target);        // pure form element
    console.log(e.detail.value);  // value of button clicked (Delete or Disable)
    console.log(e.detail.link);   // associated schema link object if present
});

pure-form-value-set

var contactForm = document.getElementById('contactForm');

contactForm.addEventListener('pure-form-value-set', function(e) {
    console.log(e.target);          // pure form element
    console.log(e.detail.oldValue); // OLD value of the form before the new value was set
    console.log(e.detail.newValue); // NEW value of the form
    // call e.preventDefault() to cancel value set
});

pure-form-value-set-complete

var contactForm = document.getElementById('contactForm');

contactForm.addEventListener('pure-form-value-set-complete', function(e) {
    console.log(e.target);          // pure form element
    console.log(e.detail.oldValue); // OLD value of the form before the new value was set
    console.log(e.detail.newValue); // NEW value of the form
});

pure-form-validation-failed

Fires when pure-form .isValid method returns false.

var contactForm = document.getElementById('contactForm');

contactForm.addEventListener('pure-form-validation-failed', function(e) {
    console.log(e.target);      // pure form element
    console.log(e.target.value) // form value
});

pure-form-validation-passed

Fires when pure-form .isValid method returns true.

var contactForm = document.getElementById('contactForm');

contactForm.addEventListener('pure-form-validation-passed', function(e) {
    console.log(e.target);      // pure form element
    console.log(e.target.value) // form value
});

pure-form-submit-complete

// data submitted and response received
document.addEventListener('pure-form-submit-complete', function(e) {
    console.log(e.type);            // event name
    console.log(e.target);          // actual HTML element clicked
    console.log(e.detail.url);      // requested URL
    console.log(e.detail.status);   // HTTP status code
    console.log(e.detail.body);     // HTTP response body
});

pure-form-submit-error

// the server rejected the submission for some reason
document.addEventListener('pure-form-submit-error', function(e) {
    console.log(e.type);            // event name
    console.log(e.target);          // actual HTML element clicked
    console.log(e.detail.url);      // requested URL
    console.log(e.detail.status);   // HTTP status code
    console.log(e.detail.body);     // HTTP response body
});

pure-form-value-changed

// the value of a field change within the form
document.addEventListener('pure-form-value-changed', function(e) {
    console.log('--------------------');
    console.log(e.type);        // event name
    console.log(e.detail);      // detail.srcElement contains element that triggered the change
    console.log(this);          // pure form element
    console.log(e.target);      // pure form element
    console.log(e.target.value) // form value
    console.log('--------------------');
});

Data Types

TODO: Add JSON Schema data type/format to html type documentation

Contribute

The Vision

My objective is to create a single script, drop in replacement for the HTML FORM tag that separates a forms requirements from the UI, UX, validation logic. The end result should be a more intuitive, user-friendly & consistent web form with a collection of predefined schemas to handle things such as user registration, online payment etc.

It's a big goal and one that I will be advancing daily, it would be great if you could join me!

Your contribution could be as simple as suggesting a feature, reporting a bug, adding a little more documentation or by forking the project and coding a new feature/fix/unit test.

Local Development

The project includes everything needed to continue development, including a node webserver. If you'd like to help out, run the following to get started:

git clone https://github.com/john-doherty/pure-form
cd pure-form
npm install
npm start

Then visit http://localhost:8080 in your browser.

Testing

The project includes Unit Test. To run tests, execute the following from within the pure-form folder:

npm install
npm test

Tests are written using jsdom, nock and jasmine-node and are broken into 4 files:

Filename Description
pure-form-event-spec.js Test pure-form events
pure-form-interface-spec.js Tests that check the correct properties and methods are exposed
pure-form-method-spec.js Tests that execute methods and verify their functionality
pure-form-rendering-spec.js Tests that tweak properties and verify the rendered output

Generate .min Files

The minified pure-form.min.js and pure-form.min.css files included in this project are the latest version. To generate .min files, execute the following:

npm run build

The version number is picked up from the package.json file.

Reporting Bugs

If you find a bug, please create an issue and provide as much detail as possible - include a jsfiddle if possible.

Pull Requests

Feel free to submit a pull requests, but please ensure your work is covered with unit tests and your code follows the current ESLint coding style defined in the package.json file.

License

Licensed under MIT License © John Doherty