Implemented support for JSON schema in mode text and code
This commit is contained in:
parent
6463d717cf
commit
5a6ca406da
|
@ -88,7 +88,7 @@ Constructs a new JSONEditor.
|
||||||
|
|
||||||
Validate the JSON object against a JSON schema. A JSON schema describes the
|
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
|
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.
|
See [http://json-schema.org/](http://json-schema.org/) for more information.
|
||||||
|
|
||||||
|
|
|
@ -405,3 +405,28 @@ div.jsoneditor-tree .jsoneditor-schema-error {
|
||||||
/*from { bottom: 24px; }*/
|
/*from { bottom: 24px; }*/
|
||||||
/*to { bottom: 32px; }*/
|
/*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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,8 @@ var util = require('./util');
|
||||||
// create a mixin with the functions for text mode
|
// create a mixin with the functions for text mode
|
||||||
var textmode = {};
|
var textmode = {};
|
||||||
|
|
||||||
|
var MAX_ERRORS = 3; // maximum number of displayed errors at the bottom
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a text editor
|
* Create a text editor
|
||||||
* @param {Element} container
|
* @param {Element} container
|
||||||
|
@ -66,6 +68,12 @@ textmode.create = function (container, options) {
|
||||||
this.dom = {};
|
this.dom = {};
|
||||||
this.aceEditor = undefined; // ace code editor
|
this.aceEditor = undefined; // ace code editor
|
||||||
this.textarea = undefined; // plain text editor (fallback when Ace is not available)
|
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.width = container.clientWidth;
|
||||||
this.height = container.clientHeight;
|
this.height = container.clientHeight;
|
||||||
|
@ -172,10 +180,8 @@ textmode.create = function (container, options) {
|
||||||
};
|
};
|
||||||
this.menu.appendChild(poweredBy);
|
this.menu.appendChild(poweredBy);
|
||||||
|
|
||||||
if (options.onChange) {
|
// register onchange event
|
||||||
// register onchange event
|
aceEditor.on('change', this._onChange.bind(this));
|
||||||
aceEditor.on('change', options.onChange);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// load a plain text textarea
|
// load a plain text textarea
|
||||||
|
@ -185,15 +191,36 @@ textmode.create = function (container, options) {
|
||||||
this.content.appendChild(textarea);
|
this.content.appendChild(textarea);
|
||||||
this.textarea = textarea;
|
this.textarea = textarea;
|
||||||
|
|
||||||
if (options.onChange) {
|
// register onchange event
|
||||||
// register onchange event
|
if (this.textarea.oninput === null) {
|
||||||
if (this.textarea.oninput === null) {
|
this.textarea.oninput = this._onChange.bind(this);
|
||||||
this.textarea.oninput = options.onChange();
|
}
|
||||||
}
|
else {
|
||||||
else {
|
// oninput is undefined. For IE8-
|
||||||
// oninput is undefined. For IE8-
|
this.textarea.onchange = this._onChange.bind(this);
|
||||||
this.textarea.onchange = options.onChange();
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
if (this.aceEditor) {
|
||||||
this.aceEditor.setValue(text, -1);
|
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
|
* Throws an exception when no JSON schema is configured
|
||||||
*/
|
*/
|
||||||
textmode.validate = function () {
|
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
|
// define modes
|
||||||
|
|
Loading…
Reference in New Issue