Implement validation in mode `"preview"`

This commit is contained in:
jos 2019-07-24 13:30:44 +02:00
parent 3ed0318624
commit 745a4f3665
8 changed files with 61 additions and 43 deletions

View File

@ -125,7 +125,7 @@
schema: schema,
schemaRefs: {"job": job},
mode: 'tree',
modes: ['code', 'text', 'tree']
modes: ['code', 'text', 'tree', 'preview']
};
// create the editor

View File

@ -46,7 +46,7 @@
var options = {
mode: 'tree',
modes: ['code', 'text', 'tree'],
modes: ['code', 'text', 'tree', 'preview'],
onValidate: function (json) {
// rules:
// - team, names, and ages must be filled in and be of correct type

View File

@ -39,7 +39,7 @@
var options = {
mode: 'tree',
modes: ['code', 'text', 'tree'],
modes: ['code', 'text', 'tree', 'preview'],
onValidate: function (json) {
// in this validation function we fake sending a request to a server
// to validate the existence of customers

View File

@ -549,6 +549,11 @@ div.jsoneditor-tree .jsoneditor-button.jsoneditor-schema-error {
overflow-y: auto;
}
.jsoneditor .jsoneditor-validation-errors {
width: 100%;
overflow: hidden;
}
.jsoneditor .jsoneditor-additional-errors {
position: absolute;
margin: auto;
@ -585,6 +590,7 @@ div.jsoneditor-tree .jsoneditor-button.jsoneditor-schema-error {
.jsoneditor .jsoneditor-text-errors td pre {
margin: 0;
white-space: normal;
}
.jsoneditor .jsoneditor-text-errors tr {

View File

@ -3,14 +3,14 @@
* @param {Object} config
* @property {boolean} errorTableVisible
* @property {function (boolean) : void} onToggleVisibility
* @property {function (number) } onFocusLine
* @property {function (number) } onChangeHeight
* @property {function (number)} [onFocusLine]
* @property {function (number)} onChangeHeight
* @constructor
*/
function ErrorTable (config) {
this.errorTableVisible = config.errorTableVisible;
this.onToggleVisibility = config.onToggleVisibility;
this.onFocusLine = config.onFocusLine;
this.onFocusLine = config.onFocusLine || function () {};
this.onChangeHeight = config.onChangeHeight;
this.dom = {};
@ -76,6 +76,7 @@ ErrorTable.prototype.setErrors = function (errors, errorLocations) {
// keep default behavior for parse errors
if (this.errorTableVisible && errors.length > 0) {
var validationErrors = document.createElement('div');
validationErrors.className = 'jsoneditor-validation-errors';
validationErrors.innerHTML = '<table class="jsoneditor-text-errors"><tbody></tbody></table>';
var tbody = validationErrors.getElementsByTagName('tbody')[0];
@ -140,9 +141,9 @@ ErrorTable.prototype.setErrors = function (errors, errorLocations) {
}
// update the status bar
var validationErrorsCount = errors.reduce(function (acc, curr) {
return (curr.type === 'validation' ? ++acc: acc)
}, 0);
var validationErrorsCount = errors.filter(function (error) {
return error.type !== 'error'
}).length;
if (validationErrorsCount > 0) {
this.dom.validationErrorCount.style.display = 'inline';
this.dom.validationErrorCount.innerText = validationErrorsCount;

View File

@ -3,6 +3,8 @@
var jmespath = require('jmespath');
var translate = require('./i18n').translate;
var ModeSwitcher = require('./ModeSwitcher');
var ErrorTable = require('./ErrorTable');
var textmode = require('./textmode')[0].mixin;
var showSortModal = require('./showSortModal');
var showTransformModal = require('./showTransformModal');
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.errorTable.getErrorTable());
this.container.appendChild(this.frame);
if (options.statusBar) {
@ -245,6 +262,10 @@ previewmode.create = function (container, options) {
this.dom.arrayInfo.className = 'jsoneditor-size-info';
this.dom.arrayInfo.innerText = '';
statusBar.appendChild(this.dom.arrayInfo);
statusBar.appendChild(this.errorTable.getErrorCounter());
statusBar.appendChild(this.errorTable.getWarningIcon());
statusBar.appendChild(this.errorTable.getErrorIcon());
}
this._renderPreview();
@ -656,13 +677,9 @@ previewmode.executeWithBusyMessage = function (fn, message) {
}
};
/**
* Validate current JSON object against the configured JSON schema
* Throws an exception when no JSON schema is configured
*/
previewmode.validate = function () {
// FIXME: implement validate (also support custom validation)
};
// TODO: refactor into composable functions instead of this shaky mixin-like structure
previewmode.validate = textmode.validate
previewmode._renderErrors = textmode._renderErrors
// define modes
module.exports = [

View File

@ -80,7 +80,6 @@ textmode.create = function (container, options) {
this.aceEditor = undefined; // ace code editor
this.textarea = undefined; // plain text editor (fallback when Ace is not available)
this.validateSchema = null;
this.validationSequence = 0;
this.annotations = [];
// create a debounced validate function
@ -287,7 +286,6 @@ textmode.create = function (container, options) {
this.errorTable = new ErrorTable({
errorTableVisible: this.mode === 'text',
onToggleVisibility: function () {
console.log('toggle')
me.validate();
},
onFocusLine: function (line) {
@ -787,32 +785,12 @@ textmode.updateText = function(jsonText) {
* Throws an exception when no JSON schema is configured
*/
textmode.validate = function () {
var doValidate = false;
var schemaErrors = [];
var parseErrors = [];
var json;
try {
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)
if (this.validateSchema) {
var valid = this.validateSchema(json);
@ -826,7 +804,7 @@ textmode.validate = function () {
// execute custom validation and after than merge and render all errors
// TODO: implement a better mechanism for only using the last validation action
this.validationSequence++;
this.validationSequence = (this.validationSequence || 0) + 1;
var me = this;
var seq = this.validationSequence;
validateCustom(json, this.options.onValidate)
@ -841,8 +819,22 @@ textmode.validate = function () {
console.error('Custom validation function did throw an error', err);
});
}
else {
this._renderErrors([]);
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 = [{
type: 'error',
message: err.message.replace(/\n/g, '<br>'),
line: line
}];
}
this._renderErrors(parseErrors);
}
};

View File

@ -1,4 +1,6 @@
var isPromise = require('./util').isPromise;
var isValidValidationError = require('./util').isValidValidationError;
var stringifyPath = require('./util').stringifyPath;
/**
* Execute custom validation if configured.
@ -21,7 +23,7 @@ function validateCustom (json, onValidate) {
if (Array.isArray(customValidationPathErrors)) {
return customValidationPathErrors
.filter(function (error) {
var valid = util.isValidValidationError(error);
var valid = isValidValidationError(error);
if (!valid) {
console.warn('Ignoring a custom validation error with invalid structure. ' +
@ -34,7 +36,7 @@ function validateCustom (json, onValidate) {
.map(function (error) {
// change data structure into the structure matching the JSON schema errors
return {
dataPath: util.stringifyPath(error.path),
dataPath: stringifyPath(error.path),
message: error.message
}
});