Implemented method `setSchema`

This commit is contained in:
jos 2016-01-12 13:16:13 +01:00
parent a2b84b2dd8
commit 1d9d9c2594
7 changed files with 145 additions and 74 deletions

View File

@ -6,6 +6,8 @@ https://github.com/josdejong/jsoneditor
## not yet released, version 5.1.0
- Implemented support for JSON schema validation, powered by `ajv`.
A JSON schema can be configured via the option `schema` or the method
`setSchema`.
- Added a minimalist bundle to the `dist` folder, excluding `ace` and `ajv`.
- Fixed an error throw when switching to mode "code" via the menu.

View File

@ -10,7 +10,7 @@ Constructs a new JSONEditor.
*Parameters:*
- `{Element} container`
- `{Element} container`
An HTML DIV element. The JSONEditor will be created inside this container element.
@ -20,11 +20,13 @@ Constructs a new JSONEditor.
[Configuration options](#configuration-options).
- `{JSON} json`
Initial JSON data to be loaded into the JSONEditor. Alternatively, the method `JSONEditor.set(json)` can be used to load JSON data into the editor.
*Returns:*
- `{JSONEditor} editor`
New instance of a JSONEditor.
### Configuration options
@ -33,7 +35,7 @@ Constructs a new JSONEditor.
Provide a custom version of the [Ace editor](http://ace.c9.io/) and use this instead of the version that comes embedded with JSONEditor. Only applicable when `mode` is `code`.
- `{function} ajv`
- `{Object} ajv`
Provide a custom instance of [ajv](https://github.com/epoberezkin/ajv), the
library used for JSON schema validation. Example:
@ -124,6 +126,7 @@ Set JSON data.
*Parameters:*
- `{JSON} json`
JSON data to be displayed in the JSONEditor.
#### `JSONEditor.setMode(mode)`
@ -132,7 +135,8 @@ Switch mode. Mode `code` requires the [Ace editor](http://ace.ajax.org/).
*Parameters:*
- `{String} mode`
- `{String} mode`
Available values: `tree`, `view`, `form`, `code`, `text`.
#### `JSONEditor.setName(name)`
@ -141,19 +145,32 @@ Set a field name for the root node.
*Parameters:*
- `{String | undefined} name`
- `{String | undefined} name`
Field name of the root node. If undefined, the current name will be removed.
#### `JSONEditor.setSchema(schema)`
Set a JSON schema for validation of the JSON object. See also option `schema`.
See [http://json-schema.org/](http://json-schema.org/) for more information on the JSON schema definition.
*Parameters:*
- `{Object} schema`
A JSON schema.
#### `JSONEditor.setText(jsonString)`
Set text data in the editor.
This method throws an exception when the provided jsonString does not contain
This method throws an exception when the provided jsonString does not contain
valid JSON and the editor is in mode `tree`, `view`, or `form`.
*Parameters:*
- `{String} jsonString`
- `{String} jsonString`
Contents of the editor as string.
#### `JSONEditor.get()`
@ -165,7 +182,8 @@ which can be the case when the editor is in mode `code` or `text`.
*Returns:*
- `{JSON} json`
- `{JSON} json`
JSON data from the JSONEditor.
#### `JSONEditor.getMode()`
@ -174,7 +192,8 @@ Retrieve the current mode of the editor.
*Returns:*
- `{String} mode`
- `{String} mode`
Current mode of the editor for example `tree` or `code`.
#### `JSONEditor.getName()`
@ -183,7 +202,8 @@ Retrieve the current field name of the root node.
*Returns:*
- `{String | undefined} name`
- `{String | undefined} name`
Current field name of the root node, or undefined if not set.
#### `JSONEditor.getText()`
@ -192,7 +212,8 @@ Get JSON data as string.
*Returns:*
- `{String} jsonString`
- `{String} jsonString`
Contents of the editor as string. When the editor is in code `text` or `code`,
the returned text is returned as-is. For the other modes, the returned text
is a compacted string. In order to get the JSON formatted with a certain

View File

@ -1,3 +1,11 @@
var Ajv;
try {
Ajv = require('ajv/dist/ajv.bundle.js');
}
catch (err) {
// no problem... when we need Ajv we will throw a neat exception
}
var treemode = require('./treemode');
var textmode = require('./textmode');
var util = require('./util');
@ -254,6 +262,51 @@ JSONEditor.prototype._onError = function(err) {
}
};
/**
* Set a JSON schema for validation of the JSON object.
* To remove the schema, call JSONEditor.setSchema(null)
* @param {Object | null} schema
*/
JSONEditor.prototype.setSchema = function (schema) {
// compile a JSON schema validator if a JSON schema is provided
if (schema) {
var ajv;
try {
// grab ajv from options if provided, else create a new instance
ajv = this.options.ajv || Ajv({ allErrors: true });
}
catch (err) {
console.warn('Failed to create an instance of Ajv, JSON Schema validation is not available. Please use a JSONEditor bundle including Ajv, or pass an instance of Ajv as via the configuration option `ajv`.');
}
if (ajv) {
this.validateSchema = ajv.compile(schema);
// add schema to the options, so that when switching to an other mode,
// the set schema is not lost
this.options.schema = schema;
// validate now
this.validate();
}
}
else {
// remove current schema
this.validateSchema = null;
this.options.schema = null;
this.validate(); // to clear current error messages
}
};
/**
* Validate current JSON object against the configured JSON schema
* Throws an exception when no JSON schema is configured
*/
JSONEditor.prototype.validate = function () {
// must be implemented by treemode and textmode
};
/**
* Register a plugin with one ore multiple modes for the JSON Editor.
*

View File

@ -342,6 +342,14 @@ textmode.setText = function(jsonText) {
}
};
/**
* Validate current JSON object against the configured JSON schema
* Throws an exception when no JSON schema is configured
*/
textmode.validate = function () {
// TODO: implement validate for textmode
};
// define modes
module.exports = [
{

View File

@ -1,10 +1,3 @@
var Ajv;
try {
Ajv = require('ajv/dist/ajv.bundle.js');
}
catch (err) {
// no problem... when we need Ajv we will throw a neat exception
}
var Highlighter = require('./Highlighter');
var History = require('./History');
var SearchBox = require('./SearchBox');
@ -47,6 +40,7 @@ treemode.create = function (container, options) {
this.multiselection = {
nodes: []
};
this.validateSchema = null; // will be set in .setSchema(schema)
this.errorNodes = [];
@ -95,20 +89,7 @@ treemode._setOptions = function (options) {
}
// compile a JSON schema validator if a JSON schema is provided
this._validate = null;
if (this.options.schema) {
var ajv;
try {
// grab ajv from options if provided, else create a new instance
ajv = options.ajv || Ajv({ allErrors: true });
}
catch (err) {
console.warn('Failed to create an instance of Ajv, JSON Schema validation is not available. Please use a JSONEditor bundle including Ajv, or pass an instance of Ajv as via the configuration option `ajv`.');
}
if (ajv) {
this._validate = ajv.compile(this.options.schema);
}
}
this.setSchema(this.options.schema);
// create a debounced validate function
var wait = this.options.debounceInterval;
@ -372,56 +353,62 @@ treemode._onChange = function () {
* Throws an exception when no JSON schema is configured
*/
treemode.validate = function () {
if (!this._validate) {
// clear all current errors
if (this.errorNodes) {
this.errorNodes.forEach(function (node) {
node.setError(null);
});
}
if (!this.validateSchema) {
// if no schema is configured or ajv was not loaded, skip validation
return;
}
var root = this.node;
if (!root) { // TODO: this should be redundant but is needed on mode switch
return;
}
//console.time('validate'); // TODO: clean up time measurement
var valid = this._validate(this.node.getValue());
var valid = this.validateSchema(root.getValue());
//console.timeEnd('validate');
// clear all current errors
this.errorNodes.forEach(function (node) {
node.setError(null);
});
// apply all new errors
var root = this.node;
if (!valid) {
this.errorNodes = this._validate.errors
.map(function findNode (error) {
return {
node: root.findNode(error.dataPath),
error: error
}
})
.filter(function hasNode (entry) {
return entry.node != null
})
.reduce(function expandParents (all, entry) {
// expand parents, then merge such that parents come first and
// original entries last
return entry.node
.findParents()
.map(function (parent) {
return {
node: parent,
child: entry.node,
error: {
message: parent.type === 'object'
? 'Contains invalid properties' // object
: 'Contains invalid items' // array
}
};
})
.concat(all, [entry]);
}, [])
// TODO: dedupe the parent nodes
.map(function setError (entry) {
entry.node.setError(entry.error, entry.child);
return entry.node;
});
this.errorNodes = this.validateSchema.errors
.map(function findNode (error) {
return {
node: root.findNode(error.dataPath),
error: error
}
})
.filter(function hasNode (entry) {
return entry.node != null
})
.reduce(function expandParents (all, entry) {
// expand parents, then merge such that parents come first and
// original entries last
return entry.node
.findParents()
.map(function (parent) {
return {
node: parent,
child: entry.node,
error: {
message: parent.type === 'object'
? 'Contains invalid properties' // object
: 'Contains invalid items' // array
}
};
})
.concat(all, [entry]);
}, [])
// TODO: dedupe the parent nodes
.map(function setError (entry) {
entry.node.setError(entry.error, entry.child);
return entry.node;
});
}
else {
this.errorNodes = [];

View File

@ -42,7 +42,7 @@
options = {
mode: 'tree',
modes: ['code', 'form', 'text', 'tree', 'view'], // allowed modes
error: function (err) {
onError: function (err) {
alert(err.toString());
}
};

View File

@ -65,7 +65,7 @@
var options = {
mode: 'text',
modes: ['text', 'form', 'tree', 'view'], // allowed modes
modes: ['code', 'form', 'text', 'tree', 'view'], // allowed modes
onError: function (err) {
console.error(err);
},