Improved error handling

This commit is contained in:
josdejong 2013-05-04 11:26:40 +02:00
parent 9d5aab339b
commit a8d26fd113
10 changed files with 272 additions and 194 deletions

View File

@ -6,6 +6,7 @@ http://jsoneditoronline.org
- Unified JSONFormatter and JSONEditor in one editor with a switchable mode.
- Urls are navigable now.
- Improved error and log handling.
- Added jsoneditor to npm and bower.

View File

@ -43,7 +43,7 @@ app.CodeToTree = function() {
treeEditor.set(codeEditor.get());
}
catch (err) {
app.notify.showError(err);
app.notify.showError(app.formatError(err));
}
};
@ -55,7 +55,7 @@ app.treeToCode = function () {
codeEditor.set(treeEditor.get());
}
catch (err) {
app.notify.showError(err);
app.notify.showError(app.formatError(err));
}
};
@ -103,12 +103,12 @@ app.load = function() {
mode: 'code',
change: function () {
app.lastChanged = codeEditor;
},
error: function (err) {
app.notify.showError(app.formatError(err));
}
});
codeEditor.set(json);
codeEditor.onError = function (err) {
app.notify.showError(err);
};
// tree editor
container = document.getElementById("treeEditor");
@ -116,6 +116,9 @@ app.load = function() {
mode: 'tree',
change: function () {
app.lastChanged = treeEditor;
},
error: function (err) {
app.notify.showError(app.formatError(err));
}
});
treeEditor.set(json);
@ -212,7 +215,7 @@ app.openCallback = function (err, data) {
}
catch (err) {
treeEditor.set({});
app.notify.showError(err);
app.notify.showError(app.formatError(err));
}
}
}
@ -267,6 +270,22 @@ app.saveFile = function () {
});
};
/**
* Format a JSON parse/stringify error as HTML
* @param {Error} err
* @returns {string}
*/
app.formatError = function (err) {
var message = '<pre class="error">' + err.toString() + '</pre>';
if (typeof(jsonlint) != 'undefined') {
message +=
'<a class="error" href="http://zaach.github.com/jsonlint/" target="_blank">' +
'validated by jsonlint' +
'</a>';
}
return message;
};
/**
* Clear the current file
*/

4
jsoneditor-min.js vendored

File diff suppressed because one or more lines are too long

View File

@ -173,35 +173,43 @@ JSONEditor.prototype.setMode = function (mode) {
options.mode = mode;
var config = JSONEditor.modes[mode];
if (config) {
if (config.data == 'text') {
// text
name = this.getName();
data = this.getText();
try {
if (config.data == 'text') {
// text
name = this.getName();
data = this.getText();
this._delete();
util.clear(this);
util.extend(this, config.editor.prototype);
this._create(container, options);
this._delete();
util.clear(this);
util.extend(this, config.editor.prototype);
this._create(container, options);
this.setName(name);
this.setText(data);
this.setName(name);
this.setText(data);
}
else {
// json
name = this.getName();
data = this.get();
this._delete();
util.clear(this);
util.extend(this, config.editor.prototype);
this._create(container, options);
this.setName(name);
this.set(data);
}
if (typeof config.load === 'function') {
try {
config.load.call(this);
}
catch (err) {}
}
}
else {
// json
name = this.getName();
data = this.get();
this._delete();
util.clear(this);
util.extend(this, config.editor.prototype);
this._create(container, options);
this.setName(name);
this.set(data);
}
if (typeof config.load === 'function') {
config.load.call(this);
catch (err) {
this._onError(err);
}
}
else {
@ -209,6 +217,28 @@ JSONEditor.prototype.setMode = function (mode) {
}
};
/**
* Throw an error. If an error callback is configured in options.error, this
* callback will be invoked. Else, a regular error is thrown.
* @param {Error} err
* @private
*/
JSONEditor.prototype._onError = function(err) {
// TODO: onError is deprecated since version 2.2.0. cleanup some day
if (typeof this.onError === 'function') {
util.log('WARNING: JSONEditor.onError is deprecated. ' +
'Use options.error instead.');
this.onError(err);
}
if (typeof this.options.error === 'function') {
this.options.error(err);
}
else {
throw err;
}
};
/**
* @constructor TreeEditor
* @param {Element} container Container element
@ -303,22 +333,22 @@ TreeEditor.prototype._setOptions = function (options) {
if (options['enableSearch']) {
// deprecated since version 1.6.0, 2012-11-03
this.options.search = options['enableSearch'];
console.log('WARNING: Option "enableSearch" is deprecated. Use "search" instead.');
util.log('WARNING: Option "enableSearch" is deprecated. Use "search" instead.');
}
if (options['enableHistory']) {
// deprecated since version 1.6.0, 2012-11-03
this.options.history = options['enableHistory'];
console.log('WARNING: Option "enableHistory" is deprecated. Use "history" instead.');
util.log('WARNING: Option "enableHistory" is deprecated. Use "history" instead.');
}
if (options['mode'] == 'editor') {
// deprecated since version 2.2.0, 2013-04-30
this.options.mode = 'tree';
console.log('WARNING: Mode "editor" is deprecated. Use "tree" instead.');
util.log('WARNING: Mode "editor" is deprecated. Use "tree" instead.');
}
if (options['mode'] == 'viewer') {
// deprecated since version 2.2.0, 2013-04-30
this.options.mode = 'view';
console.log('WARNING: Mode "viewer" is deprecated. Use "view" instead.');
util.log('WARNING: Mode "viewer" is deprecated. Use "view" instead.');
}
}
@ -343,7 +373,7 @@ TreeEditor.prototype.set = function (json, name) {
// adjust field name for root node
if (name) {
// TODO: deprecated since version 2.2.0. Cleanup some day.
console.log('Warning: second parameter "name" is deprecated. ' +
util.log('Warning: second parameter "name" is deprecated. ' +
'Use setName(name) instead.');
this.options.name = name;
}
@ -528,7 +558,7 @@ TreeEditor.prototype._onAction = function (action, params) {
this.options.change();
}
catch (err) {
console.log('Error in change callback: ', err);
util.log('Error in change callback: ', err);
}
}
};
@ -1023,17 +1053,18 @@ TextEditor.prototype._create = function (container, options, json) {
if (options.indentation) {
this.indentation = Number(options.indentation);
}
this.options = options;
this.mode = (options.mode == 'code') ? 'code' : 'text';
if (this.mode == 'code') {
// verify whether Ace editor is available and supported
if (typeof ace === 'undefined') {
this.mode = 'text';
console.log('WARNING: Cannot load code editor, Ace library not loaded. ' +
util.log('WARNING: Cannot load code editor, Ace library not loaded. ' +
'Falling back to plain text editor');
}
if (util.getInternetExplorerVersion() == 8) {
this.mode = 'text';
console.log('WARNING: Cannot load code editor, Ace is not supported on IE8. ' +
util.log('WARNING: Cannot load code editor, Ace is not supported on IE8. ' +
'Falling back to plain text editor');
}
}
@ -1067,7 +1098,12 @@ TextEditor.prototype._create = function (container, options, json) {
//buttonFormat.className = 'jsoneditor-button';
this.menu.appendChild(buttonFormat);
buttonFormat.onclick = function () {
me.format();
try {
me.format();
}
catch (err) {
me._onError(err);
}
};
// create compact button
@ -1078,7 +1114,13 @@ TextEditor.prototype._create = function (container, options, json) {
//buttonCompact.className = 'jsoneditor-button';
this.menu.appendChild(buttonCompact);
buttonCompact.onclick = function () {
me.compact();
try {
me.compact();
}
catch (err) {
me._onError(err);
}
};
this.content = document.createElement('div');
@ -1166,39 +1208,41 @@ TextEditor.prototype._delete = function () {
};
/**
* This method is executed on error.
* It can be overwritten for each instance of the TextEditor
* @param {String} err
* Throw an error. If an error callback is configured in options.error, this
* callback will be invoked. Else, a regular error is thrown.
* @param {Error} err
* @private
*/
// TODO: replace with an options.error
TextEditor.prototype.onError = function(err) {
// action should be implemented for the instance
TextEditor.prototype._onError = function(err) {
// TODO: onError is deprecated since version 2.2.0. cleanup some day
if (typeof this.onError === 'function') {
util.log('WARNING: JSONEditor.onError is deprecated. ' +
'Use options.error instead.');
this.onError(err);
}
if (typeof this.options.error === 'function') {
this.options.error(err);
}
else {
throw err;
}
};
/**
* Compact the code in the formatter
*/
TextEditor.prototype.compact = function () {
try {
var json = util.parse(this.getText());
this.setText(JSON.stringify(json));
}
catch (err) {
this.onError(err);
}
var json = util.parse(this.getText());
this.setText(JSON.stringify(json));
};
/**
* Format the code in the formatter
*/
TextEditor.prototype.format = function () {
try {
var json = util.parse(this.getText());
this.setText(JSON.stringify(json, null, this.indentation));
}
catch (err) {
this.onError(err);
}
var json = util.parse(this.getText());
this.setText(JSON.stringify(json, null, this.indentation));
};
/**
@ -1409,7 +1453,7 @@ Node.prototype.setValue = function(value, type) {
if (typeof(value) == 'string') {
var escValue = JSON.stringify(value);
this.value = escValue.substring(1, escValue.length - 1);
console.log('check', value, this.value);
util.log('check', value, this.value);
}
else {
this.value = value;
@ -3179,7 +3223,7 @@ Node.prototype.onKeyDown = function (event) {
var handled = false;
var prevNode, nextNode, nextDom, nextDom2;
// console.log(ctrlKey, keynum, event.charCode); // TODO: cleanup
// util.log(ctrlKey, keynum, event.charCode); // TODO: cleanup
if (keynum == 13) { // Enter
if (target == this.dom.value) {
if (!this.editor.mode.edit || event.ctrlKey) {
@ -4979,7 +5023,7 @@ History.prototype.undo = function () {
}
}
else {
console.log('Error: unknown action "' + obj.action + '"');
util.log('Error: unknown action "' + obj.action + '"');
}
}
this.index--;
@ -5006,7 +5050,7 @@ History.prototype.redo = function () {
}
}
else {
console.log('Error: unknown action "' + obj.action + '"');
util.log('Error: unknown action "' + obj.action + '"');
}
}
@ -5415,13 +5459,6 @@ if (!Array.prototype.forEach) {
}
}
// Old browsers do not have a console, so create a fake one in that case.
if (typeof console === 'undefined') {
console = {
log: function () {}
};
}
/**
* Parse JSON using the parser built-in in the browser.
* On exception, the jsonString is validated and a detailed error is thrown.
@ -5432,9 +5469,9 @@ util.parse = function (jsonString) {
return JSON.parse(jsonString);
}
catch (err) {
// get a detailed error message using validate
var message = util.validate(jsonString) || err;
throw new Error(message);
// try to throw a more detailed error message using validate
util.validate(jsonString);
throw err;
}
};
@ -5443,33 +5480,15 @@ util.parse = function (jsonString) {
* This method uses JSONLint to validate the String. If JSONLint is not
* available, the built-in JSON parser of the browser is used.
* @param {String} jsonString String with an (invalid) JSON object
* @return {String | undefined} Returns undefined when the string is valid JSON,
* returns a string with an error message when
* the data is invalid. This message is HTML
* formatted.
* @throws Error
*/
util.validate = function (jsonString) {
var message = undefined;
try {
if (typeof(jsonlint) != 'undefined') {
jsonlint.parse(jsonString);
}
else {
JSON.parse(jsonString);
}
if (typeof(jsonlint) != 'undefined') {
jsonlint.parse(jsonString);
}
catch (err) {
message = '<pre class="error">' + err.toString() + '</pre>';
if (typeof(jsonlint) != 'undefined') {
message +=
'<a class="error" href="http://zaach.github.com/jsonlint/" target="_blank">' +
'validated by jsonlint' +
'</a>';
}
else {
JSON.parse(jsonString);
}
return message;
};
/**
@ -5501,6 +5520,16 @@ util.clear = function (a) {
return a;
};
/**
* Output text to the console, if console is available
* @param {...*} args
*/
util.log = function(args) {
if (console && typeof console.log === 'function') {
console.log.apply(console, arguments);
}
};
/**
* Test whether a text contains a url (matches when a string starts
* with 'http://*' or 'https://*' and has no whitespace characters)

View File

@ -181,7 +181,7 @@ History.prototype.undo = function () {
}
}
else {
console.log('Error: unknown action "' + obj.action + '"');
util.log('Error: unknown action "' + obj.action + '"');
}
}
this.index--;
@ -208,7 +208,7 @@ History.prototype.redo = function () {
}
}
else {
console.log('Error: unknown action "' + obj.action + '"');
util.log('Error: unknown action "' + obj.action + '"');
}
}

View File

@ -139,38 +139,68 @@ JSONEditor.prototype.setMode = function (mode) {
options.mode = mode;
var config = JSONEditor.modes[mode];
if (config) {
if (config.data == 'text') {
// text
name = this.getName();
data = this.getText();
try {
if (config.data == 'text') {
// text
name = this.getName();
data = this.getText();
this._delete();
util.clear(this);
util.extend(this, config.editor.prototype);
this._create(container, options);
this._delete();
util.clear(this);
util.extend(this, config.editor.prototype);
this._create(container, options);
this.setName(name);
this.setText(data);
this.setName(name);
this.setText(data);
}
else {
// json
name = this.getName();
data = this.get();
this._delete();
util.clear(this);
util.extend(this, config.editor.prototype);
this._create(container, options);
this.setName(name);
this.set(data);
}
if (typeof config.load === 'function') {
try {
config.load.call(this);
}
catch (err) {}
}
}
else {
// json
name = this.getName();
data = this.get();
this._delete();
util.clear(this);
util.extend(this, config.editor.prototype);
this._create(container, options);
this.setName(name);
this.set(data);
}
if (typeof config.load === 'function') {
config.load.call(this);
catch (err) {
this._onError(err);
}
}
else {
throw new Error('Unknown mode "' + options.mode + '"');
}
};
/**
* Throw an error. If an error callback is configured in options.error, this
* callback will be invoked. Else, a regular error is thrown.
* @param {Error} err
* @private
*/
JSONEditor.prototype._onError = function(err) {
// TODO: onError is deprecated since version 2.2.0. cleanup some day
if (typeof this.onError === 'function') {
util.log('WARNING: JSONEditor.onError is deprecated. ' +
'Use options.error instead.');
this.onError(err);
}
if (typeof this.options.error === 'function') {
this.options.error(err);
}
else {
throw err;
}
};

View File

@ -129,7 +129,7 @@ Node.prototype.setValue = function(value, type) {
if (typeof(value) == 'string') {
var escValue = JSON.stringify(value);
this.value = escValue.substring(1, escValue.length - 1);
console.log('check', value, this.value);
util.log('check', value, this.value);
}
else {
this.value = value;
@ -1899,7 +1899,7 @@ Node.prototype.onKeyDown = function (event) {
var handled = false;
var prevNode, nextNode, nextDom, nextDom2;
// console.log(ctrlKey, keynum, event.charCode); // TODO: cleanup
// util.log(ctrlKey, keynum, event.charCode); // TODO: cleanup
if (keynum == 13) { // Enter
if (target == this.dom.value) {
if (!this.editor.mode.edit || event.ctrlKey) {

View File

@ -41,17 +41,18 @@ TextEditor.prototype._create = function (container, options, json) {
if (options.indentation) {
this.indentation = Number(options.indentation);
}
this.options = options;
this.mode = (options.mode == 'code') ? 'code' : 'text';
if (this.mode == 'code') {
// verify whether Ace editor is available and supported
if (typeof ace === 'undefined') {
this.mode = 'text';
console.log('WARNING: Cannot load code editor, Ace library not loaded. ' +
util.log('WARNING: Cannot load code editor, Ace library not loaded. ' +
'Falling back to plain text editor');
}
if (util.getInternetExplorerVersion() == 8) {
this.mode = 'text';
console.log('WARNING: Cannot load code editor, Ace is not supported on IE8. ' +
util.log('WARNING: Cannot load code editor, Ace is not supported on IE8. ' +
'Falling back to plain text editor');
}
}
@ -85,7 +86,12 @@ TextEditor.prototype._create = function (container, options, json) {
//buttonFormat.className = 'jsoneditor-button';
this.menu.appendChild(buttonFormat);
buttonFormat.onclick = function () {
me.format();
try {
me.format();
}
catch (err) {
me._onError(err);
}
};
// create compact button
@ -96,7 +102,13 @@ TextEditor.prototype._create = function (container, options, json) {
//buttonCompact.className = 'jsoneditor-button';
this.menu.appendChild(buttonCompact);
buttonCompact.onclick = function () {
me.compact();
try {
me.compact();
}
catch (err) {
me._onError(err);
}
};
this.content = document.createElement('div');
@ -184,39 +196,41 @@ TextEditor.prototype._delete = function () {
};
/**
* This method is executed on error.
* It can be overwritten for each instance of the TextEditor
* @param {String} err
* Throw an error. If an error callback is configured in options.error, this
* callback will be invoked. Else, a regular error is thrown.
* @param {Error} err
* @private
*/
// TODO: replace with an options.error
TextEditor.prototype.onError = function(err) {
// action should be implemented for the instance
TextEditor.prototype._onError = function(err) {
// TODO: onError is deprecated since version 2.2.0. cleanup some day
if (typeof this.onError === 'function') {
util.log('WARNING: JSONEditor.onError is deprecated. ' +
'Use options.error instead.');
this.onError(err);
}
if (typeof this.options.error === 'function') {
this.options.error(err);
}
else {
throw err;
}
};
/**
* Compact the code in the formatter
*/
TextEditor.prototype.compact = function () {
try {
var json = util.parse(this.getText());
this.setText(JSON.stringify(json));
}
catch (err) {
this.onError(err);
}
var json = util.parse(this.getText());
this.setText(JSON.stringify(json));
};
/**
* Format the code in the formatter
*/
TextEditor.prototype.format = function () {
try {
var json = util.parse(this.getText());
this.setText(JSON.stringify(json, null, this.indentation));
}
catch (err) {
this.onError(err);
}
var json = util.parse(this.getText());
this.setText(JSON.stringify(json, null, this.indentation));
};
/**

View File

@ -92,22 +92,22 @@ TreeEditor.prototype._setOptions = function (options) {
if (options['enableSearch']) {
// deprecated since version 1.6.0, 2012-11-03
this.options.search = options['enableSearch'];
console.log('WARNING: Option "enableSearch" is deprecated. Use "search" instead.');
util.log('WARNING: Option "enableSearch" is deprecated. Use "search" instead.');
}
if (options['enableHistory']) {
// deprecated since version 1.6.0, 2012-11-03
this.options.history = options['enableHistory'];
console.log('WARNING: Option "enableHistory" is deprecated. Use "history" instead.');
util.log('WARNING: Option "enableHistory" is deprecated. Use "history" instead.');
}
if (options['mode'] == 'editor') {
// deprecated since version 2.2.0, 2013-04-30
this.options.mode = 'tree';
console.log('WARNING: Mode "editor" is deprecated. Use "tree" instead.');
util.log('WARNING: Mode "editor" is deprecated. Use "tree" instead.');
}
if (options['mode'] == 'viewer') {
// deprecated since version 2.2.0, 2013-04-30
this.options.mode = 'view';
console.log('WARNING: Mode "viewer" is deprecated. Use "view" instead.');
util.log('WARNING: Mode "viewer" is deprecated. Use "view" instead.');
}
}
@ -132,7 +132,7 @@ TreeEditor.prototype.set = function (json, name) {
// adjust field name for root node
if (name) {
// TODO: deprecated since version 2.2.0. Cleanup some day.
console.log('Warning: second parameter "name" is deprecated. ' +
util.log('Warning: second parameter "name" is deprecated. ' +
'Use setName(name) instead.');
this.options.name = name;
}
@ -317,7 +317,7 @@ TreeEditor.prototype._onAction = function (action, params) {
this.options.change();
}
catch (err) {
console.log('Error in change callback: ', err);
util.log('Error in change callback: ', err);
}
}
};

View File

@ -26,13 +26,6 @@ if (!Array.prototype.forEach) {
}
}
// Old browsers do not have a console, so create a fake one in that case.
if (typeof console === 'undefined') {
console = {
log: function () {}
};
}
/**
* Parse JSON using the parser built-in in the browser.
* On exception, the jsonString is validated and a detailed error is thrown.
@ -43,9 +36,9 @@ util.parse = function (jsonString) {
return JSON.parse(jsonString);
}
catch (err) {
// get a detailed error message using validate
var message = util.validate(jsonString) || err;
throw new Error(message);
// try to throw a more detailed error message using validate
util.validate(jsonString);
throw err;
}
};
@ -54,33 +47,15 @@ util.parse = function (jsonString) {
* This method uses JSONLint to validate the String. If JSONLint is not
* available, the built-in JSON parser of the browser is used.
* @param {String} jsonString String with an (invalid) JSON object
* @return {String | undefined} Returns undefined when the string is valid JSON,
* returns a string with an error message when
* the data is invalid. This message is HTML
* formatted.
* @throws Error
*/
util.validate = function (jsonString) {
var message = undefined;
try {
if (typeof(jsonlint) != 'undefined') {
jsonlint.parse(jsonString);
}
else {
JSON.parse(jsonString);
}
if (typeof(jsonlint) != 'undefined') {
jsonlint.parse(jsonString);
}
catch (err) {
message = '<pre class="error">' + err.toString() + '</pre>';
if (typeof(jsonlint) != 'undefined') {
message +=
'<a class="error" href="http://zaach.github.com/jsonlint/" target="_blank">' +
'validated by jsonlint' +
'</a>';
}
else {
JSON.parse(jsonString);
}
return message;
};
/**
@ -112,6 +87,16 @@ util.clear = function (a) {
return a;
};
/**
* Output text to the console, if console is available
* @param {...*} args
*/
util.log = function(args) {
if (console && typeof console.log === 'function') {
console.log.apply(console, arguments);
}
};
/**
* Test whether a text contains a url (matches when a string starts
* with 'http://*' or 'https://*' and has no whitespace characters)