React-Plexus form v0.1.1: A dynamic form component for react using JSON-Schema.

icon
Latest Release: v0.1.1
  • support types other than string with enum (@martijnvermaat)
  • move react into peerdependencies (@DeaconDesperado)
  • make <Form> obey the className property
  • allow a user-defined button-rendering function
  • restructure the source code (@bebraw)
  • add demo site build and deploy scripts (@bebraw)
  • add some basic documentation
  • deprecate the built-in file upload component
Source code(tar.gz)
Source code(zip)

ATTENTION!

THIS PROJECT IS NO LONGER ACTIVE.

A maintained fork can be found at react-json-editor (h/t @ismaelga).

plexus-form

A dynamic form component for React using a specification format based on JSON-Schema.

Demo

The full code for the demo can be found at https://github.com/AppliedMathematicsANU/plexus-form/blob/master/demos/demo.jsx.

Plexus-form takes a JavaScript object describing the shape of the data we want a user to provide - a schema - and automatically creates a form based on that schema. It also validates user inputs by calling a pluggable validator which uses the same schema.

Minimal example:

var React    = require('react');
var validate = require('plexus-validate');
var Form     = require('plexus-form');

var schema = {
  title      : "My pretty form",
  description: "Declarative pure data DSLs are the best.",
  type       : "object",
  properties : {
    comment: {
      title      : "Your comment",
      description: "Type something here.",
      type       : "string",
      minLength  : 1
    }
  }
};

var onSubmit = function(data, buttonValue, errors) {
  alert('Data  : '+JSON.stringify(data)+'\n'+
        'Button: '+buttonValue+'\n'+
        'Errors: '+JSON.stringify(errors));
};

React.render(<Form
               schema   = {schema}
               validate = {validate}
               onSubmit = {onSubmit} />,
             document.body);

Differences between JSON-Schema and plexus-form schemas:

Plexus-form and plexus-validate take a plain JavaScript data object as input rather than a JSON-formatted string.

The following JSON-Schema properties are supported:

  • description
  • enum
  • enumNames
  • items
  • oneOf
  • properties
  • title
  • type
  • $ref

Additional properties relevant to data validation are implemented by plexus-validate.

JSON-Schema references can only point to elements within the schema object itself. URI references are not supported.

Plexus-form extends the JSON-Schema specification with two new properties x-hints and x-ordering. The latter, x-ordering, specifies a default order for the elements under the current object. The former, x-hints, can be used to annotate a schema with additional hints on how the data is to be handled or displayed. The relevant pieces of information for plexus-form are found under schema["x-hints"].form. We'll explore these extension in more detail in the following sections.

Enforced display order example:

var schema = {
  type      : "object",
  properties: {
    comment: { title: "Comment" },
    email  : { title: "Email" },
    name   : { title: "Name" }
  },
  "x-ordering": ["name", "email", "comment"]
};

Custom CSS classes example:

Plexus-form assigns the following CSS classes automatically:

  • form-section
  • form-subsection
  • form-section-title
  • form-element

Additional CSS classes can be specified via x-hints like so:

var schema = {
  type      : "object",
  properties: {
    name : {
      title: "Name",
      "x-hints": {
        form: {
          classes: [ "form-text-field", "form-name-field" ]
        }
      }
    },
    email: {
      title: "Email",
      "x-hints": {
        form: {
          classes: [ "form-text-field", "form-email-field" ]
        }
      }
    }
  },
  "x-hints": {
    form: {
      classes: [ "form-person-section" ]
    }
  }
};

Alternatives selection example:

var schema = {
  type      : "object",
  properties: {
    contact: {
      title      : "Contact details",
      description: "How would you like to be contacted?",
      type       : "object",
      properties : {
        contactType: {
          title      : "Contact medium",
          description: "Please pick your preferred medium"
        }
      },
      oneOf: [
        {
          properties: {
            contactType: { enum: [ "Email" ] },
            email      : { title: "Email address" }
          }
        },
        {
          properties: {
            contactType: { enum: [ "Telephone" ] },
            phoneNumber: { title: "Telephone number" }
          }
        },
        {
          properties: {
            contactType: { enum: [ "Physical mail" ] },
            address    : { title: "Street address" },
            postcode   : { title: "Post or area code" },
            state      : { title: "State or province" },
            country    : { title: "Country" }
          }
        }
      ],
      "x-hints": { form: { selector: "contactType" } }
    }
  }
};

User-defined input component example:

The following example shows how to associate a user-defined input handler with a data element. The association happens indirectly via a symbolic name and a handler object that assigns functions to names so that the schema itself remains easily serializable. We use a very simplistic file uploader component as a demonstration case. Other useful applications of these technique could be an autocompleting text field or a color picker.

The React component handling a data element (here Uploader) must call this.props.onChange whenever the data has changed. It should delegate low-level key press events it does not handle itself to this.props.onKeyPress, which enables the <Form> component to handle the "Enter" key consistently throughout the form.

var schema = {
  "x-hints" : {
    form: {
      inputComponent: "uploader"
    }
  }
};

var Uploader = React.createClass({
  componentDidMount: function() {
    var input = document.createElement('input');
    input.type = 'file';
    input.multiple = false;
    input.addEventListener('change', this.loadFile);
    this._input = input;
  },

  loadFile: function(event) {
    var files = event.target.files;
    var handleData = this.handleData;
    var file = files[0];
    var reader = new FileReader();

    reader.onload = function(event) {
      handleData(file, event.target.result);
    };

    reader.readAsText(file);
  },

  handleData: function(file, data) {
    this.props.onChange({
      name: file.name,
      type: file.type,
      size: file.size,
      data: data.slice(0, 1000) // truncate data in this demo
    });
  },

  openSelector: function() {
    this._input.click();
  },

  handleKeyPress: function(event) {
    this.props.onKeyPress(event);
  },

  render: function() {
    return (
      <button onClick = { this.openSelector }>
        Select a file
      </button>
    );
  }
});

var onSubmit = function(data, buttonValue, errors) {
  alert('Data  : '+JSON.stringify(data)+'\n'+
        'Button: '+buttonValue+'\n'+
        'Errors: '+JSON.stringify(errors));
};

var handlers = {
  uploader: Uploader
};

React.render(<Form
               buttons  = {[]}
               schema   = {schema}
               validate = {validate}
               onSubmit = {onSubmit}
               handlers = {handlers}
               submitOnChange = {true} />,
             document.body);

License

The MIT License (MIT)

Copyright (c) 2015 The Australian National University

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.

Comments

  • What is your favorite react form implementation?
  • rendering array as a root
    rendering array as a root

    May 22, 2015

    I am trying to use a schema that looks like this:

    { "type": "array", "items": { "type": "number" } }

    I am getting an input box rendered but it is not possible to type inside.

    Reply
  • Whitespace in string values is collapsed to single space character
    Whitespace in string values is collapsed to single space character

    May 28, 2015

    What's the rationale behind collapsing any stretch of whitespace to a single space character for values of type string?

    I can imagine some trimming might be useful (?) but I'm not sure why it should be impossible to enter multiple spaces?

    It becomes more problematic when I have a custom input component rendering a string value with textarea. The user cannot enter any newlines since they are replaced by a space character, but that's sort of the point of rendering the textarea.

    (There's also the replacement of the unicode soft hyphen which I don't really have a problem with, but I'm not sure why it's done.)

    Reply
  • (Not an issue) Is it possible to suppress the autogenerated section while rendering an array?
    (Not an issue) Is it possible to suppress the autogenerated section while rendering an array?

    May 31, 2015

    Guys, I have more of a question. I want to add a "+" sign to generate a new section for an array as opposed to auto-generate it when the previous item is filled. And in the same way I want to add "x" to remove a section. From what I see, I can add "+" and "-" in the section wrapper, but how would I suppress a new array element rendering? Thanks.

    Reply
  • Example in README.md throws error
    Example in README.md throws error

    Jun 15, 2015

    Hi,

    Running react 0.13.3, copy and paste from the "Minimal example" in README.md throws the following error...

    TypeError: Cannot read property 'find' of undefined
        at fullOrdering (plexus-form.js:923)
        at Object.types.object (plexus-form.js:904)
        at Object.module.exports [as make] (plexus-form.js:806)
        at module.exports.React.createClass.render (plexus-form.js:180)
        at ReactCompositeComponentMixin._renderValidatedComponentWithoutOwnerOrContext (ReactCompositeComponent.js:789)
        ...
    

    I'll investigate further...

    Reply
  • Tweaks to get minimal example running
    Tweaks to get minimal example running

    Jun 15, 2015

    See issue in #41 . Other errors were thrown regarding errors being undefined (where null was expected).

    Reply
  • Setting input value on render causes validation error
    Setting input value on render causes validation error

    Jul 2, 2015

    I have a field that I am setting the value on at render time. When I submit the form, without entering/changing the field, I get a validation error that says the field must be present.

    After reading through the code I noticed that the inputs value is indeed set, but since the input's handleChange function never gets called, the state never actually gets set on the form so the form doesn't think that field has a value.

    Any thoughts?

    Reply
  • [fix] react is accessible via root['React']
    [fix] react is accessible via root['React']

    Jul 31, 2015

    ClojureScript uses Google Closure and goog to require dependencies, and react is accessible via root['React'] there.

    Webpack preamble:

    (function webpackUniversalModuleDefinition(root, factory) {
            if(typeof exports === 'object' && typeof module === 'object')
                    module.exports = factory(require("react"));
            else if(typeof define === 'function' && define.amd)
                    define(["react"], factory);
            else if(typeof exports === 'object')
                    exports["PlexusForm"] = factory(require("react"));
            else
                    root["PlexusForm"] = factory(root["React"]);
    })(this, function(__WEBPACK_EXTERNAL_MODULE_2__) {
    
    Reply
  • Allow developers to customize input field class name
    Allow developers to customize input field class name

    Aug 10, 2015

    Hi, Would it be possible to allow developers to customize the input field class names by passing some props to the form?

    Thank you

    Reply
  • label filed for   that file  I'm don't to edit
    label filed for that file I'm don't to edit

    Nov 2, 2015

    label filed for that file I'm don't to edit can i set property for that filed not edit

    Thanks.

    Reply
  • Allow errorClass to survive undefined
    Allow errorClass to survive undefined

    Mar 26, 2015

    Just noticed errorClass generator can fail if props.errors is undefined. It's another question why that can happen in the first place of course.

    Reply
  • Allow select values to be defined to allow better i18n?
    Allow select values to be defined to allow better i18n?

    Sep 28, 2014

    I'm currently setting up a little system with i18n and some selects. Here's an example of one:

        companyTemplate: {
            title: 'Company',
            type: 'string',
            enum: [
                'Custom',
                'Koodilehto osk.'
            ]
        }
    

    I also have a onSubmit handler set up for the form. What I want to do is to detect which option is selected and then update the content of my form based on that selection. This is actually the easy part and I have no problem doing it (just need to set Form values at onSubmit literally).

    The problem is that I will need to allow the enum values to be translated. Unfortunately it seems the current implementation has fixed select option values to names. This is understandable given there is only enum to use.

    In order to allow i18n I would need some way to set the keys explicitly or through some implicit scheme (ie. default to index based value. This can break if ordering changes, though). Maybe the configuration can be extended somehow to support this use case? Ideally I would like to have a map like this somewhere in the definition:

    {
        custom: 'Custom',
        koodilehto: 'Koodilehto osk.'
    }
    

    Later on I would replace the values with something that gets injected through i18n but you get the idea. I would perform my custom logic based on these values at onSubmit as described above.

    Reply
  • Provide Form.onChange?
    Provide Form.onChange?

    Jun 26, 2014

    I am building a small editor. The idea is that I construct a form based on JSON schema and then render out an invoice based on the value as they change. I would like the changes to be instant.

    Would it make sense to provide an onChange method that gets triggered whenever the form changes somehow? For this to work you might need to attach onChange handler per input and then trigger the form method. How does this sound?

    Reply
  • Yields `Invariant Violation...` when installed through NPM
    Yields `Invariant Violation...` when installed through NPM

    Jun 25, 2014

    I'm getting Uncaught Error: Invariant Violation: EventPluginRegistry: Cannot inject event plugin ordering more than once. when using NPM version of the library. If I copy the library and import it then, everything works.

    I've set up a demo at require_issue. Please run gulp compile and gulp to see it in action. You can adjust src/js/app.js to point at the included version of plexus-form simply by doing var Form = require('./plexus-form');.

    Either I have configured the build wrong somehow or there's some glitch in the library. Just thought to bring this out.

    Besides this and #1 the library seems to do the job well.

    Reply
  • Rethink file uploads
    Rethink file uploads

    Jul 13, 2014

    I am not too happy with the way file uploads currently work, especially the fact that FileField instances need to call makeFieldset and the haphazard mix of user-defined and implied properties for upload elements. Maybe it would be cleaner to have a separate element type x-upload (prefixed because it is not part of json-schema) that is treated like a primitive type.

    enhancement 
    Reply
  • documentation?
    documentation?

    Mar 2, 2015

    Since "read the code" is usually not the best recommendation, could a rudimentary tutorial be added to the README.md? Just some simple "this is the boiler plate, here are the supported element types, here's a simple jsx with "complex" form that does some things"?

    Reply
  • Set up a basic demo site
    Set up a basic demo site

    Mar 3, 2015

    To start developing against it (hot loading) hit npm start. You can generate gh-pages using npm run gh-pages. It can be deployed to the gh-pages branch by hitting npm run deploy-gh-pages.

    The demo site has been split in two parts. The upper half is meant for a basic demo. I just copied the one that exists already. In addition you could render each field here in a separate section. The lower half just renders README.md (highlighting applied).

    Related to #23.

    Reply
  • Allow custom props to be passed to Form
    Allow custom props to be passed to Form

    Feb 9, 2015

    It would be handy if

    <Form className='pure-form' ...></Form>
    

    worked. Setting custom properties like this would make it easier to build on top of the library.

    Technically you would need to do something like this:

    var React = require('react/addons');
    var update = React.addons.update;
    
    ...
    
    props = update(this.props, {
        $merge: {
            valueToRemove: undefined,
            ...
        },
    });
    
    ...
    
    // pass props to <form>
    

    This way you won't end up passing custom properties to form.

    If you want, I can prepare a PR for you although this isn't a big change for you to make.

    Reply
  • Change main field in package.json
    Change main field in package.json

    Apr 9, 2015

    Now there is "main": "lib/index.js", Main field assume node env start point. There is no need to use packed version.

    This change will be help in debug time. Because we will be able to see separate files in debugger.

    If you confirm, I can send PR.

    Reply
  • plexus-form up for adoption
    plexus-form up for adoption

    Sep 9, 2015

    As I have mentioned several times before, I cannot really spare the time to maintain this code as a proper project, so as of now, I am officially declaring it abandoned. I may or may not be able to pick things up at some point in the future, but would then almost certainly do a complete rewrite from scratch.

    If anyone has a fork or otherwise related project that they are planning to maintain for some time and would like people to know about, please comment below and I will add a link to the readme. Also feel free to use this thread for coordinating future efforts.

    Since my employer holds the copyright to the code, I cannot pass this repository over to a new maintainer, and I will also have to hold on to the name plexus-form on npm.

    Cheers, Olaf

    Reply