Skip to content

Commit

Permalink
Implemented support for JSON schema in mode text and code
Browse files Browse the repository at this point in the history
  • Loading branch information
josdejong committed Jan 12, 2016
1 parent 6463d71 commit 5a6ca40
Show file tree
Hide file tree
Showing 3 changed files with 134 additions and 15 deletions.
2 changes: 1 addition & 1 deletion docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
25 changes: 25 additions & 0 deletions src/css/jsoneditor.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

122 changes: 108 additions & 14 deletions src/js/textmode.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -172,10 +180,8 @@ textmode.create = function (container, options) {
};
this.menu.appendChild(poweredBy);

if (options.onChange) {
// register onchange event
aceEditor.on('change', options.onChange);
}
// register onchange event
aceEditor.on('change', this._onChange.bind(this));
}
else {
// load a plain text textarea
Expand All @@ -185,15 +191,36 @@ 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();
}
else {
// oninput is undefined. For IE8-
this.textarea.onchange = options.onChange();
}
// register onchange event
if (this.textarea.oninput === null) {
this.textarea.oninput = this._onChange.bind(this);
}
else {
// oninput is undefined. For IE8-
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);
}
}
};
Expand Down Expand Up @@ -340,14 +367,81 @@ textmode.setText = function(jsonText) {
if (this.aceEditor) {
this.aceEditor.setValue(text, -1);
}

// validate JSON schema
this.validate();
};

/**
* 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
// 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
Expand Down

0 comments on commit 5a6ca40

Please sign in to comment.