Fabian Kostadinov

Validation in JSONForm

Recently, I had created a user form with JSONForm. However, the form was embedded in another site with its own save button. JSONForm usually adds its own submit button to the site, but you easily can remove it. One of the problems left was how to trigger validation for the form manually once the site's save button was clicked. Be aware that this implies triggering validation from outside the HTML form element. Whereas JSONForm's submit button resides inside the form element as an <input type="button">, my site's save button does not. Unfortunately, the description in the JSONForm's Wiki pages was not really understandable to me. It took me a long time but I finally succeeded to trigger JSONForm validation manually. Here's what I did.

JSONForm's validation relies on its own adapted version of JSV.js. You have to make sure that this library is accessible to your JSONForm code. In the simplest case you can include the Javascript in your HTML site before the jsonform library. (I renamed the file to jsonform-jsv.js to make clear that this is a modified version of JSV.js.)

<html>
<head></head>
<body>
    <script type="text/javascript" src="deps/jquery.min.js"></script>
    <script type="text/javascript" src="deps/underscore.js"></script>
    <script type="text/javascript" src="deps/opt/jsonform-jsv.js"></script>
    <script type="text/javascript" src="lib/jsonform.js"></script>
</body>

In my case though all Javascript files were pre-compiled into a single, big, compressed file with Browserify and only this file was referenced from my HTML file. All Javascript libraries followed the Requirejs standard. Hence, the - theoretically - correct way to add another library such as JSV.js would be to use a require('JSV'); statement. In practice however this would not work, because doing so would actually imply loading the official version of JSV.js instead of JSONForm's own adapted version. I finally settled with the solution to add the JSONForm's version inside HTML together with the pre-compiled Javascript file:

<html>
<head></head>
<body>
    <script type="text/javascript" src="jsonform-jsv.js"></script>
    <script type="text/javascript" src="my-precompiled-js-lib.js"></script>
</body>

Maybe not the most elegant solution, but it worked. It is important to understand that if you do not provide a JSV library, then clicking JSONForm's submit button will actually trigger the HTML5 compliant browser-internal form validation. This is not really what you want to happen, as there are some important differences between JSONForm's own validation procedure and the one provided internally by most modern browsers. For instance, JSONForm renders input elements with type="number" actually as text inputs but validates them as number inputs, whereas the HTML5 compliant browser-internal validation validates them as text input.

So, I still had to trigger the validation manually. This involved several steps. The problem is that there exists code for validation inside the JSONForm Javascript library, but unfortunately it is tightly bound to the submit button click. I did not want to change any code inside the JSONForm library and try to expose the validation function to the outside world. There are however two other exposed functions that I could rely upon: myFormEl.jsonFormValue() and myFormEl.jsonFormErrors(errors, options). The first method returns all entered form values, the second one highlights invalid form input elements.

Thus, in my site's save function, added the following code:

var formEl = $('#myForm');
var env = JSONFormValidator.createEnvironment("json-schema-draft-03");
var schema = { 'properties': myJsonForm.schema };
var report = env.validate(formEl.jsonFormValue(), schema);
var options = {};
formEl.jsonFormErrors(report.errors, options);

There's quite a lot contained in this code snippet, so let's break it down.

  1. var formEl = $('#myForm');
    This line uses JQuery to get the HTML form element with the id of #myForm. You could achieve this without JQuery (e.g. document.getElementById('myForm'), but since JSONForm requires JQuery anyway, why not use it?

  2. var env = JSONFormValidator.createEnvironment("json-schema-draft-03");
    Here we create a new JSV validator environment. This assumes that the variable JSONFormValidator is globally accessible. Alternatively, JSV.createEnvironment("json-schema-draft-03") would achieve the same.

  3. var schema = { 'properties': myJsonForm.schema };
    This one took me long to figure out. myJsonForm.schema refers to the JSONForm's schema property you had to define for creating the HTML form. Be aware how I wrapped the schema inside another object as a value of a property called properties. This is important! If you simply use myJsonForm.schema directly, the validation will never indicate any errors.

  4. var report = env.validate(formEl.jsonFormValue(), schema);
    This code actually triggers the validation. We retrieve the user input with formEl.jsonFormValue() and use it together with the schema as an input to the validation function. Report will be an object containing an errors property, this is what we will need for further processing. If there are no errors, then report.errors.length will be 0. If it has length > 0 then there are errors. I used this information to abort the saving procedure.

  5. var options = {};
    formEl.jsonFormErrors(report.errors, options);

    options is simply an empty object. In the current JSONForm version, it does not serve any purpose, but it is still needed as a function parameter. Calling formEl.jsonFormErrors(...) will highlight invalid input elements on the page and also show a short error message to the user.

Some remarks. First, JSONForm's logic how to render certain specified input types to input elements is not always intuitive. For example, the schema specification "foo": { "type": "number" } results in an input element of type text: <input type="text">. JSONForm on the default mapping:

  • A number property generates a text input, i.e. an <input type="text"> element.
  • An integer property generates a text input as well, i.e. an <input type="text"> element.
What is even stranger is that specifying type="number" in the form section atually renders <input type="number"> elements.

Second, having an input fields set to readonly resulted in validation errors if I had specified this inside the schema section. It worked though if specified inside the form section.

Did not work Worked
{
  "schema": {
    "foo": {
      "type": "string",
      "readonly": "readonly"
    }
  },
  "form": [{
    "foo"
  }],
  "onSubmitValid": function(values) {}
}
{
  "schema": {
    "foo": {
      "type": "string
    }
  },
  "form": [{
    {
      "key": "foo",
      "readonly": "readonly"
    }
  }],
  "onSubmitValid": function(values) {}
}

Third, when I was using JSONForm's arrays of objects, setting an object property to required in the schema section resulted in validation errors. If however specified in the form section, this did not seem to have any effect whatsoever, that is apparently no check was performed whether the required input fields were filled out or not.

comments powered by Disqus