From 745a4f3665f125480bb9db2936580cc543db7254 Mon Sep 17 00:00:00 2001 From: jos Date: Wed, 24 Jul 2019 13:30:44 +0200 Subject: [PATCH] Implement validation in mode `"preview"` --- examples/07_json_schema_validation.html | 2 +- examples/18_custom_validation.html | 2 +- examples/19_custom_validation_async.html | 2 +- src/css/jsoneditor.css | 6 ++++ src/js/ErrorTable.js | 13 ++++---- src/js/previewmode.js | 31 +++++++++++++---- src/js/textmode.js | 42 ++++++++++-------------- src/js/validationUtils.js | 6 ++-- 8 files changed, 61 insertions(+), 43 deletions(-) diff --git a/examples/07_json_schema_validation.html b/examples/07_json_schema_validation.html index f0cb1a3..b3bc8d5 100644 --- a/examples/07_json_schema_validation.html +++ b/examples/07_json_schema_validation.html @@ -125,7 +125,7 @@ schema: schema, schemaRefs: {"job": job}, mode: 'tree', - modes: ['code', 'text', 'tree'] + modes: ['code', 'text', 'tree', 'preview'] }; // create the editor diff --git a/examples/18_custom_validation.html b/examples/18_custom_validation.html index 9e9641e..37e496c 100644 --- a/examples/18_custom_validation.html +++ b/examples/18_custom_validation.html @@ -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 diff --git a/examples/19_custom_validation_async.html b/examples/19_custom_validation_async.html index 8cf4670..2fe82fb 100644 --- a/examples/19_custom_validation_async.html +++ b/examples/19_custom_validation_async.html @@ -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 diff --git a/src/css/jsoneditor.css b/src/css/jsoneditor.css index 46794d2..5a739b0 100644 --- a/src/css/jsoneditor.css +++ b/src/css/jsoneditor.css @@ -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 { diff --git a/src/js/ErrorTable.js b/src/js/ErrorTable.js index e9dbe3e..ba6d218 100644 --- a/src/js/ErrorTable.js +++ b/src/js/ErrorTable.js @@ -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 = '
'; 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; diff --git a/src/js/previewmode.js b/src/js/previewmode.js index b739e4a..5c277f8 100644 --- a/src/js/previewmode.js +++ b/src/js/previewmode.js @@ -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 = [ diff --git a/src/js/textmode.js b/src/js/textmode.js index e588872..01456e5 100644 --- a/src/js/textmode.js +++ b/src/js/textmode.js @@ -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, '
'), - 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, '
'), + line: line + }]; + } + + this._renderErrors(parseErrors); } }; diff --git a/src/js/validationUtils.js b/src/js/validationUtils.js index 7af7f73..df88abe 100644 --- a/src/js/validationUtils.js +++ b/src/js/validationUtils.js @@ -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 } });