Implemented support for JSON schema in mode text and code

This commit is contained in:
jos 2016-01-12 14:34:31 +01:00
parent 6463d717cf
commit 5a6ca406da
3 changed files with 134 additions and 15 deletions

View File

@ -88,7 +88,7 @@ Constructs a new JSONEditor.
Validate the JSON object against a JSON schema. A JSON schema describes the
structure that a JSON object must have, like required properties or the type
that a value must have. Only applicable for modes `tree` and `form`.
that a value must have.
See [http://json-schema.org/](http://json-schema.org/) for more information.

View File

@ -405,3 +405,28 @@ div.jsoneditor-tree .jsoneditor-schema-error {
/*from { bottom: 24px; }*/
/*to { bottom: 32px; }*/
/*}*/
/* JSON schema errors displayed at the bottom of the editor in mode text and code */
.jsoneditor .jsoneditor-text-errors {
width: 100%;
border-collapse: collapse;
background-color: #ffef8b;
border-top: 1px solid gold;
}
.jsoneditor .jsoneditor-text-errors td {
padding: 3px 6px;
vertical-align: middle;
}
.jsoneditor-text-errors .jsoneditor-schema-error {
border: none;
width: 24px;
height: 24px;
padding: 0;
margin: 0 4px 0 0;
background: url('img/jsoneditor-icons.svg') -168px -48px;
}

View File

@ -12,6 +12,8 @@ var util = require('./util');
// create a mixin with the functions for text mode
var textmode = {};
var MAX_ERRORS = 3; // maximum number of displayed errors at the bottom
/**
* Create a text editor
* @param {Element} container
@ -66,6 +68,12 @@ textmode.create = function (container, options) {
this.dom = {};
this.aceEditor = undefined; // ace code editor
this.textarea = undefined; // plain text editor (fallback when Ace is not available)
this.validateSchema = null;
// create a debounced validate function
var wait = this.options.debounceInterval;
var immediate = true;
this._debouncedValidate = util.debounce(this.validate.bind(this), wait, immediate);
this.width = container.clientWidth;
this.height = container.clientHeight;
@ -172,10 +180,8 @@ textmode.create = function (container, options) {
};
this.menu.appendChild(poweredBy);
if (options.onChange) {
// register onchange event
aceEditor.on('change', options.onChange);
}
aceEditor.on('change', this._onChange.bind(this));
}
else {
// load a plain text textarea
@ -185,16 +191,37 @@ textmode.create = function (container, options) {
this.content.appendChild(textarea);
this.textarea = textarea;
if (options.onChange) {
// register onchange event
if (this.textarea.oninput === null) {
this.textarea.oninput = options.onChange();
this.textarea.oninput = this._onChange.bind(this);
}
else {
// oninput is undefined. For IE8-
this.textarea.onchange = options.onChange();
this.textarea.onchange = this._onChange.bind(this);
}
}
this.setSchema(this.options.schema);
};
/**
* Handle a change:
* - Validate JSON schema
* - Send a callback to the onChange listener if provided
* @private
*/
textmode._onChange = function () {
// validate JSON schema (if configured)
this._debouncedValidate();
// trigger the onChange callback
if (this.options.onChange) {
try {
this.options.onChange();
}
catch (err) {
console.error('Error in onChange callback: ', err);
}
}
};
@ -340,6 +367,9 @@ textmode.setText = function(jsonText) {
if (this.aceEditor) {
this.aceEditor.setValue(text, -1);
}
// validate JSON schema
this.validate();
};
/**
@ -347,7 +377,71 @@ textmode.setText = function(jsonText) {
* Throws an exception when no JSON schema is configured
*/
textmode.validate = function () {
// TODO: implement validate for textmode
// clear all current errors
if (this.dom.validationErrors) {
this.dom.validationErrors.parentNode.removeChild(this.dom.validationErrors);
this.dom.validationErrors = null;
this.content.style.marginBottom = '';
this.content.style.paddingBottom = '';
}
var doValidate = false;
var errors = [];
var json;
try {
json = this.get(); // this can fail when there is no valid json
doValidate = true;
}
catch (err) {
// no valid JSON, don't validate
}
// only validate the JSON when parsing the JSON succeeeded
if (doValidate && this.validateSchema) {
//console.time('validate'); // TODO: clean up time measurement
var valid = this.validateSchema(json);
//console.timeEnd('validate');
if (!valid) {
errors = this.validateSchema.errors;
}
}
if (errors.length > 0) {
// limit the number of displayed errors
var limit = errors.length > MAX_ERRORS;
if (limit) {
errors = errors.slice(0, MAX_ERRORS);
var hidden = this.validateSchema.errors.length - MAX_ERRORS;
errors.push('(' + hidden + ' more errors...)')
}
var validationErrors = document.createElement('div');
validationErrors.innerHTML = '<table class="jsoneditor-text-errors">' +
'<tbody>' +
errors.map(function (error) {
var message;
if (typeof error === 'string') {
message = '<td colspan="2"><pre>' + error + '</pre></td>';
}
else {
message = '<td>' + error.dataPath + '</td>' +
'<td>' + error.message + '</td>';
}
return '<tr><td><button class="jsoneditor-schema-error"></button></td>' + message + '</tr>'
}).join('') +
'</tbody>' +
'</table>';
this.dom.validationErrors = validationErrors;
this.frame.appendChild(validationErrors);
var height = validationErrors.clientHeight;
this.content.style.marginBottom = (-height) + 'px';
this.content.style.paddingBottom = height + 'px';
}
};
// define modes