Implement validation in mode `"preview"`
This commit is contained in:
parent
3ed0318624
commit
745a4f3665
|
@ -125,7 +125,7 @@
|
||||||
schema: schema,
|
schema: schema,
|
||||||
schemaRefs: {"job": job},
|
schemaRefs: {"job": job},
|
||||||
mode: 'tree',
|
mode: 'tree',
|
||||||
modes: ['code', 'text', 'tree']
|
modes: ['code', 'text', 'tree', 'preview']
|
||||||
};
|
};
|
||||||
|
|
||||||
// create the editor
|
// create the editor
|
||||||
|
|
|
@ -46,7 +46,7 @@
|
||||||
|
|
||||||
var options = {
|
var options = {
|
||||||
mode: 'tree',
|
mode: 'tree',
|
||||||
modes: ['code', 'text', 'tree'],
|
modes: ['code', 'text', 'tree', 'preview'],
|
||||||
onValidate: function (json) {
|
onValidate: function (json) {
|
||||||
// rules:
|
// rules:
|
||||||
// - team, names, and ages must be filled in and be of correct type
|
// - team, names, and ages must be filled in and be of correct type
|
||||||
|
|
|
@ -39,7 +39,7 @@
|
||||||
|
|
||||||
var options = {
|
var options = {
|
||||||
mode: 'tree',
|
mode: 'tree',
|
||||||
modes: ['code', 'text', 'tree'],
|
modes: ['code', 'text', 'tree', 'preview'],
|
||||||
onValidate: function (json) {
|
onValidate: function (json) {
|
||||||
// in this validation function we fake sending a request to a server
|
// in this validation function we fake sending a request to a server
|
||||||
// to validate the existence of customers
|
// to validate the existence of customers
|
||||||
|
|
|
@ -549,6 +549,11 @@ div.jsoneditor-tree .jsoneditor-button.jsoneditor-schema-error {
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.jsoneditor .jsoneditor-validation-errors {
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
.jsoneditor .jsoneditor-additional-errors {
|
.jsoneditor .jsoneditor-additional-errors {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
|
@ -585,6 +590,7 @@ div.jsoneditor-tree .jsoneditor-button.jsoneditor-schema-error {
|
||||||
|
|
||||||
.jsoneditor .jsoneditor-text-errors td pre {
|
.jsoneditor .jsoneditor-text-errors td pre {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
white-space: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
.jsoneditor .jsoneditor-text-errors tr {
|
.jsoneditor .jsoneditor-text-errors tr {
|
||||||
|
|
|
@ -3,14 +3,14 @@
|
||||||
* @param {Object} config
|
* @param {Object} config
|
||||||
* @property {boolean} errorTableVisible
|
* @property {boolean} errorTableVisible
|
||||||
* @property {function (boolean) : void} onToggleVisibility
|
* @property {function (boolean) : void} onToggleVisibility
|
||||||
* @property {function (number) } onFocusLine
|
* @property {function (number)} [onFocusLine]
|
||||||
* @property {function (number) } onChangeHeight
|
* @property {function (number)} onChangeHeight
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
function ErrorTable (config) {
|
function ErrorTable (config) {
|
||||||
this.errorTableVisible = config.errorTableVisible;
|
this.errorTableVisible = config.errorTableVisible;
|
||||||
this.onToggleVisibility = config.onToggleVisibility;
|
this.onToggleVisibility = config.onToggleVisibility;
|
||||||
this.onFocusLine = config.onFocusLine;
|
this.onFocusLine = config.onFocusLine || function () {};
|
||||||
this.onChangeHeight = config.onChangeHeight;
|
this.onChangeHeight = config.onChangeHeight;
|
||||||
|
|
||||||
this.dom = {};
|
this.dom = {};
|
||||||
|
@ -76,6 +76,7 @@ ErrorTable.prototype.setErrors = function (errors, errorLocations) {
|
||||||
// keep default behavior for parse errors
|
// keep default behavior for parse errors
|
||||||
if (this.errorTableVisible && errors.length > 0) {
|
if (this.errorTableVisible && errors.length > 0) {
|
||||||
var validationErrors = document.createElement('div');
|
var validationErrors = document.createElement('div');
|
||||||
|
validationErrors.className = 'jsoneditor-validation-errors';
|
||||||
validationErrors.innerHTML = '<table class="jsoneditor-text-errors"><tbody></tbody></table>';
|
validationErrors.innerHTML = '<table class="jsoneditor-text-errors"><tbody></tbody></table>';
|
||||||
var tbody = validationErrors.getElementsByTagName('tbody')[0];
|
var tbody = validationErrors.getElementsByTagName('tbody')[0];
|
||||||
|
|
||||||
|
@ -140,9 +141,9 @@ ErrorTable.prototype.setErrors = function (errors, errorLocations) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// update the status bar
|
// update the status bar
|
||||||
var validationErrorsCount = errors.reduce(function (acc, curr) {
|
var validationErrorsCount = errors.filter(function (error) {
|
||||||
return (curr.type === 'validation' ? ++acc: acc)
|
return error.type !== 'error'
|
||||||
}, 0);
|
}).length;
|
||||||
if (validationErrorsCount > 0) {
|
if (validationErrorsCount > 0) {
|
||||||
this.dom.validationErrorCount.style.display = 'inline';
|
this.dom.validationErrorCount.style.display = 'inline';
|
||||||
this.dom.validationErrorCount.innerText = validationErrorsCount;
|
this.dom.validationErrorCount.innerText = validationErrorsCount;
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
var jmespath = require('jmespath');
|
var jmespath = require('jmespath');
|
||||||
var translate = require('./i18n').translate;
|
var translate = require('./i18n').translate;
|
||||||
var ModeSwitcher = require('./ModeSwitcher');
|
var ModeSwitcher = require('./ModeSwitcher');
|
||||||
|
var ErrorTable = require('./ErrorTable');
|
||||||
|
var textmode = require('./textmode')[0].mixin;
|
||||||
var showSortModal = require('./showSortModal');
|
var showSortModal = require('./showSortModal');
|
||||||
var showTransformModal = require('./showTransformModal');
|
var showTransformModal = require('./showTransformModal');
|
||||||
var MAX_PREVIEW_CHARACTERS = require('./constants').MAX_PREVIEW_CHARACTERS;
|
var MAX_PREVIEW_CHARACTERS = require('./constants').MAX_PREVIEW_CHARACTERS;
|
||||||
|
@ -225,7 +227,22 @@ previewmode.create = function (container, options) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.errorTable = new ErrorTable({
|
||||||
|
errorTableVisible: true,
|
||||||
|
onToggleVisibility: function () {
|
||||||
|
me.validate();
|
||||||
|
},
|
||||||
|
onFocusLine: null,
|
||||||
|
onChangeHeight: function (height) {
|
||||||
|
// TODO: change CSS to using flex box, remove setting height using JavaScript
|
||||||
|
var totalHeight = height + me.dom.statusBar.clientHeight + 1;
|
||||||
|
me.content.style.marginBottom = (-totalHeight) + 'px';
|
||||||
|
me.content.style.paddingBottom = totalHeight + 'px';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
this.frame.appendChild(this.content);
|
this.frame.appendChild(this.content);
|
||||||
|
this.frame.appendChild(this.errorTable.getErrorTable());
|
||||||
this.container.appendChild(this.frame);
|
this.container.appendChild(this.frame);
|
||||||
|
|
||||||
if (options.statusBar) {
|
if (options.statusBar) {
|
||||||
|
@ -245,6 +262,10 @@ previewmode.create = function (container, options) {
|
||||||
this.dom.arrayInfo.className = 'jsoneditor-size-info';
|
this.dom.arrayInfo.className = 'jsoneditor-size-info';
|
||||||
this.dom.arrayInfo.innerText = '';
|
this.dom.arrayInfo.innerText = '';
|
||||||
statusBar.appendChild(this.dom.arrayInfo);
|
statusBar.appendChild(this.dom.arrayInfo);
|
||||||
|
|
||||||
|
statusBar.appendChild(this.errorTable.getErrorCounter());
|
||||||
|
statusBar.appendChild(this.errorTable.getWarningIcon());
|
||||||
|
statusBar.appendChild(this.errorTable.getErrorIcon());
|
||||||
}
|
}
|
||||||
|
|
||||||
this._renderPreview();
|
this._renderPreview();
|
||||||
|
@ -656,13 +677,9 @@ previewmode.executeWithBusyMessage = function (fn, message) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
// TODO: refactor into composable functions instead of this shaky mixin-like structure
|
||||||
* Validate current JSON object against the configured JSON schema
|
previewmode.validate = textmode.validate
|
||||||
* Throws an exception when no JSON schema is configured
|
previewmode._renderErrors = textmode._renderErrors
|
||||||
*/
|
|
||||||
previewmode.validate = function () {
|
|
||||||
// FIXME: implement validate (also support custom validation)
|
|
||||||
};
|
|
||||||
|
|
||||||
// define modes
|
// define modes
|
||||||
module.exports = [
|
module.exports = [
|
||||||
|
|
|
@ -80,7 +80,6 @@ textmode.create = function (container, options) {
|
||||||
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;
|
this.validateSchema = null;
|
||||||
this.validationSequence = 0;
|
|
||||||
this.annotations = [];
|
this.annotations = [];
|
||||||
|
|
||||||
// create a debounced validate function
|
// create a debounced validate function
|
||||||
|
@ -287,7 +286,6 @@ textmode.create = function (container, options) {
|
||||||
this.errorTable = new ErrorTable({
|
this.errorTable = new ErrorTable({
|
||||||
errorTableVisible: this.mode === 'text',
|
errorTableVisible: this.mode === 'text',
|
||||||
onToggleVisibility: function () {
|
onToggleVisibility: function () {
|
||||||
console.log('toggle')
|
|
||||||
me.validate();
|
me.validate();
|
||||||
},
|
},
|
||||||
onFocusLine: function (line) {
|
onFocusLine: function (line) {
|
||||||
|
@ -787,32 +785,12 @@ textmode.updateText = 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 () {
|
||||||
var doValidate = false;
|
|
||||||
var schemaErrors = [];
|
var schemaErrors = [];
|
||||||
var parseErrors = [];
|
var parseErrors = [];
|
||||||
var json;
|
var json;
|
||||||
try {
|
try {
|
||||||
json = this.get(); // this can fail when there is no valid json
|
json = this.get(); // this can fail when there is no valid json
|
||||||
doValidate = true;
|
|
||||||
}
|
|
||||||
catch (err) {
|
|
||||||
if (this.getText()) {
|
|
||||||
// try to extract the line number from the jsonlint error message
|
|
||||||
var match = /\w*line\s*(\d+)\w*/g.exec(err.message);
|
|
||||||
var line;
|
|
||||||
if (match) {
|
|
||||||
line = +match[1];
|
|
||||||
}
|
|
||||||
parseErrors.push({
|
|
||||||
type: 'error',
|
|
||||||
message: err.message.replace(/\n/g, '<br>'),
|
|
||||||
line: line
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// only validate the JSON when parsing the JSON succeeded
|
|
||||||
if (doValidate) {
|
|
||||||
// execute JSON schema validation (ajv)
|
// execute JSON schema validation (ajv)
|
||||||
if (this.validateSchema) {
|
if (this.validateSchema) {
|
||||||
var valid = this.validateSchema(json);
|
var valid = this.validateSchema(json);
|
||||||
|
@ -826,7 +804,7 @@ textmode.validate = function () {
|
||||||
|
|
||||||
// execute custom validation and after than merge and render all errors
|
// execute custom validation and after than merge and render all errors
|
||||||
// TODO: implement a better mechanism for only using the last validation action
|
// TODO: implement a better mechanism for only using the last validation action
|
||||||
this.validationSequence++;
|
this.validationSequence = (this.validationSequence || 0) + 1;
|
||||||
var me = this;
|
var me = this;
|
||||||
var seq = this.validationSequence;
|
var seq = this.validationSequence;
|
||||||
validateCustom(json, this.options.onValidate)
|
validateCustom(json, this.options.onValidate)
|
||||||
|
@ -841,8 +819,22 @@ textmode.validate = function () {
|
||||||
console.error('Custom validation function did throw an error', err);
|
console.error('Custom validation function did throw an error', err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else {
|
catch (err) {
|
||||||
this._renderErrors([]);
|
if (this.getText()) {
|
||||||
|
// try to extract the line number from the jsonlint error message
|
||||||
|
var match = /\w*line\s*(\d+)\w*/g.exec(err.message);
|
||||||
|
var line;
|
||||||
|
if (match) {
|
||||||
|
line = +match[1];
|
||||||
|
}
|
||||||
|
parseErrors = [{
|
||||||
|
type: 'error',
|
||||||
|
message: err.message.replace(/\n/g, '<br>'),
|
||||||
|
line: line
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
this._renderErrors(parseErrors);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
var isPromise = require('./util').isPromise;
|
var isPromise = require('./util').isPromise;
|
||||||
|
var isValidValidationError = require('./util').isValidValidationError;
|
||||||
|
var stringifyPath = require('./util').stringifyPath;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute custom validation if configured.
|
* Execute custom validation if configured.
|
||||||
|
@ -21,7 +23,7 @@ function validateCustom (json, onValidate) {
|
||||||
if (Array.isArray(customValidationPathErrors)) {
|
if (Array.isArray(customValidationPathErrors)) {
|
||||||
return customValidationPathErrors
|
return customValidationPathErrors
|
||||||
.filter(function (error) {
|
.filter(function (error) {
|
||||||
var valid = util.isValidValidationError(error);
|
var valid = isValidValidationError(error);
|
||||||
|
|
||||||
if (!valid) {
|
if (!valid) {
|
||||||
console.warn('Ignoring a custom validation error with invalid structure. ' +
|
console.warn('Ignoring a custom validation error with invalid structure. ' +
|
||||||
|
@ -34,7 +36,7 @@ function validateCustom (json, onValidate) {
|
||||||
.map(function (error) {
|
.map(function (error) {
|
||||||
// change data structure into the structure matching the JSON schema errors
|
// change data structure into the structure matching the JSON schema errors
|
||||||
return {
|
return {
|
||||||
dataPath: util.stringifyPath(error.path),
|
dataPath: stringifyPath(error.path),
|
||||||
message: error.message
|
message: error.message
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue