Merge branch 'develop'

Conflicts:
	README.md
This commit is contained in:
jos 2014-07-28 21:13:56 +02:00
commit e2192df650
18 changed files with 778 additions and 488 deletions

View File

@ -3,6 +3,16 @@
https://github.com/josdejong/jsoneditor
## 2014-07-28, version 3.1.0
- JSONEditor now accepts JavaScript objects as input, and can turn them into
valid JSON. For example `{a:2,b:'str'}` can be turned into `{"a":2,"b":"str"}`.
- Implemented an option `editable`, a callback function, which allows to set
individual nodes (their field and/or value) editable or read-only.
- Fixed: shortcut keys to manipulate the nodes are now disabled when mode
is `form` or `view`.
## 2014-05-31, version 3.0.0
- Large code reorganization.

View File

@ -1,9 +1,10 @@
# JSON Editor
https://github.com/josdejong/jsoneditor
http://jsoneditoronline.org/
Website: http://jsoneditoronline.org/
Github: https://github.com/josdejong/jsoneditor
### Description
@ -36,20 +37,12 @@ Supported browsers: Chrome, Firefox, Safari, Opera, Internet Explorer 9+.
- Format and compact JSON.
### Screenshots
#### Tree editor
<img alt="json editor" src="https://raw.github.com/josdejong/jsoneditor/master/misc/jsoneditor.png">
#### Code editor
<img alt="code editor" src="https://raw.github.com/josdejong/jsoneditor/master/misc/codeeditor.png">
### Documentation
- Documentation:
- [API](https://github.com/josdejong/jsoneditor/tree/master/docs/api.md)
- [Usage](https://github.com/josdejong/jsoneditor/tree/master/docs/usage.md)
- [Shortcut keys](https://github.com/josdejong/jsoneditor/tree/master/docs/shortcut_keys.md)
- [Examples](https://github.com/josdejong/jsoneditor/tree/master/examples)
- [Source](https://github.com/josdejong/jsoneditor)
- [History](https://github.com/josdejong/jsoneditor/blob/master/HISTORY.md)

View File

@ -1,6 +1,6 @@
{
"name": "jsoneditor",
"version": "3.0.0",
"version": "3.1.0",
"description": "A web-based tool to view, edit and format JSON",
"tags": [
"json",

View File

@ -11,41 +11,29 @@ Constructs a new JSONEditor.
*Parameters:*
- `{Element} container`
An HTML DIV element. The JSONEditor will be created inside this container
element.
An HTML DIV element. The JSONEditor will be created inside this container element.
- `{Object} options`
Optional object with options. Available options:
- `{function} change`.
Set a callback method triggered when the contents of the JSONEditor change.
Called without parameters.
- `{function} error`.
Set a callback method triggered when an error occurs.
Invoked with the error as first argument. The callback is only invoked
- `{function} change`
Set a callback method triggered when the contents of the JSONEditor change. Called without parameters.
- `{function} editable`
Set a callback method to determine whether individual nodes are editable or read-only. Only applicable when option `mode` is `tree`. The callback is invoked as `editable(node)`, where `node` is an object `{field: string, value: string, path: string[]}`. The function must either return a boolean value to set both the nodes field and value editable or read-only, or return an object `{field: boolean, value: boolean}`.
- `{function} error`
Set a callback method triggered when an error occurs. Invoked with the error as first argument. The callback is only invoked
for errors triggered by a users action.
- `{boolean} history`.
Enables history, adds a button Undo and Redo to the menu of the JSONEditor.
True by default. Only applicable when `mode` is 'tree' or 'form'.
- `{String} mode`.
Set the editor mode. Available values: 'tree' (default), 'view', 'form',
'code', 'text'. In 'view' mode, the data and datastructure is read-only.
In 'form' mode, only the value can be changed, the datastructure is read-only.
Mode 'code' requires the Ace editor to be loaded on the page.
Mode 'text' shows the data as plain text.
- `{String[]} modes`.
Create a box in the editor menu where the user can switch between the specified
modes. Available values: see option `mode`.
- `{String} name`.
Initial field name for the root node, is undefined by default.
Can also be set using `JSONEditor.setName(name)`.
Only applicable when `mode` is 'tree', 'view', or 'form'.
- `{boolean} search`.
Enables a search box in the upper right corner of the JSONEditor.
True by default.
Only applicable when `mode` is 'tree', 'view', or 'form'.
- `{Number} indentation`.
Number of indentation spaces. 2 by default.
Only applicable when `mode` is 'code' or 'text'.
- `{boolean} history`
Enables history, adds a button Undo and Redo to the menu of the JSONEditor. True by default. Only applicable when `mode` is 'tree' or 'form'.
- `{String} mode`
Set the editor mode. Available values: 'tree' (default), 'view', 'form', 'code', 'text'. In 'view' mode, the data and datastructure is read-only. In 'form' mode, only the value can be changed, the datastructure is read-only. Mode 'code' requires the Ace editor to be loaded on the page. Mode 'text' shows the data as plain text.
- `{String[]} modes`
Create a box in the editor menu where the user can switch between the specified modes. Available values: see option `mode`.
- `{String} name`
Initial field name for the root node, is undefined by default. Can also be set using `JSONEditor.setName(name)`. Only applicable when `mode` is 'tree', 'view', or 'form'.
- `{boolean} search`
Enables a search box in the upper right corner of the JSONEditor. True by default. Only applicable when `mode` is 'tree', 'view', or 'form'.
- `{Number} indentation`
Number of indentation spaces. 2 by default. Only applicable when `mode` is 'code' or 'text'.
- `{JSON} json`
Initial JSON data to be loaded into the JSONEditor. Alternatively, the method `JSONEditor.set(json)` can be used to load JSON data into the editor.
@ -98,14 +86,18 @@ Set a field name for the root node.
Set text data in the formatter.
*Parameters:*
- `{String} jsonString` Contents of the JSONformatter as string.
- `{String} jsonString`
Contents of the JSONformatter as string.
#### `JSONEditor.get()`
Get JSON data.
*Returns:*
- `{JSON} json` JSON data from the JSONEditor.
- `{JSON} json`
JSON data from the JSONEditor.
#### `JSONEditor.getName()`
@ -121,7 +113,9 @@ Retrieve the current field name of the root node.
Get JSON data as string.
*Returns:*
- `{String} jsonString` Contents of the JSONformatter as string.
- `{String} jsonString`
Contents of the JSONformatter as string.
### Examples
@ -171,8 +165,7 @@ var json = editor.get(json);
## JSON parsing and stringification
In general to parse or stringify JSON data, the browsers built in JSON parser can be used.
To create a formatted string from a JSON object, use:
In general to parse or stringify JSON data, the browsers built in JSON parser can be used. To create a formatted string from a JSON object, use:
```js
var formattedString = JSON.stringify(json, null, 2);

20
docs/shortcut_keys.md Normal file
View File

@ -0,0 +1,20 @@
# Shortcut keys
Key | Description
----------------------- | ------------------------------------------------
Alt+Arrows | Move the caret up/down/left/right between fields
Shift+Alt+Arrows | Move field up/down/left/right
Ctrl+D | Duplicate field
Ctrl+Del | Remove field
Ctrl+Enter | Open link when on a field containing an url
Ctrl+Ins | Insert a new field with type auto
Ctrl+Shift+Ins | Append a new field with type auto
Ctrl+E | Expand or collapse field
Alt+End | Move the caret to the last field
Ctrl+F | Find
F3, Ctrl+G | Find next
Shift+F3, Ctrl+Shift+G | Find previous
Alt+Home | Move the caret to the first field
Ctrl+M | Show actions menu
Ctrl+Z | Undo last action
Ctrl+Shift+Z | Redo

View File

@ -0,0 +1,61 @@
<!DOCTYPE HTML>
<html>
<head>
<title>JSONEditor | Load and save</title>
<link rel="stylesheet" type="text/css" href="../jsoneditor.css">
<script src="../jsoneditor.js"></script>
<script src="http://bgrins.github.io/filereader.js/filereader.js"></script>
<script src="http://eligrey.com/demos/FileSaver.js/FileSaver.js"></script>
<style>
html, body {
font: 11pt sans-serif;
}
#jsoneditor {
width: 500px;
height: 500px;
}
</style>
</head>
<body>
<h1>Load and save JSON documents</h1>
<p>
This examples uses HTML5 to load/save local files.
Powered by <a href="http://bgrins.github.io/filereader.js/">FileReader.js</a> and
<a href="https://github.com/eligrey/FileSaver.js">FileSaver.js</a>.<br>
Only supported on modern browsers (Chrome, FireFox, IE10+, Safari 6.1+, Opera 15+).
</p>
<p>
Load a JSON document: <input type="file" id="loadDocument" value="Load"/>
</p>
<p>
Save a JSON document: <input type="button" id="saveDocument" value="Save" />
</p>
<div id="jsoneditor"></div>
<script type="text/javascript" >
// create the editor
var editor = new JSONEditor(document.getElementById('jsoneditor'));
// Load a JSON document
FileReaderJS.setupInput(document.getElementById('loadDocument'), {
readAsDefault: 'Text',
on: {
load: function (event, file) {
editor.setText(event.target.result);
}
}
});
// Save a JSON document
document.getElementById('saveDocument').onclick = function () {
var blob = new Blob([editor.getText()], {type: 'application/json;charset=utf-8'});
saveAs(blob, 'document.json');
};
</script>
</body>
</html>

View File

@ -0,0 +1,61 @@
<!DOCTYPE HTML>
<html>
<head>
<title>JSONEditor | Basic usage</title>
<link rel="stylesheet" type="text/css" href="../jsoneditor.css">
<script type="text/javascript" src="../jsoneditor.js"></script>
<style type="text/css">
#jsoneditor {
width: 500px;
}
</style>
</head>
<body>
<p>
In this example:
</p>
<ul>
<li>the field <code>_id</code> and its value are read-only</li>
<li>the field <code>name</code> is read-only but has an editable value</li>
<li>the field <code>age</code> and its value are editable</li>
</ul>
<div id="jsoneditor"></div>
<script type="text/javascript" >
var container = document.getElementById('jsoneditor');
var options = {
editable: function (node) {
// node is an object like:
// {
// field: 'FIELD',
// value: 'VALUE',
// path: ['PATH', 'TO', 'NODE']
// }
switch (node.field) {
case '_id':
return false;
case 'name':
return {
field: false,
value: true
};
default:
return true;
}
}
};
var json = {
_id: 123456,
name: 'John',
age: 32
};
var editor = new JSONEditor(container, options, json);
</script>
</body>
</html>

View File

@ -23,8 +23,8 @@
* Copyright (c) 2011-2014 Jos de Jong, http://jsoneditoronline.org
*
* @author Jos de Jong, <wjosdejong@gmail.com>
* @version 3.0.0
* @date 2014-05-31
* @version 3.1.0
* @date 2014-07-28
*/
(function webpackUniversalModuleDefinition(root, factory) {
if(typeof exports === 'object' && typeof module === 'object')
@ -37,12 +37,12 @@
root["JSONEditor"] = factory();
})(this, function() {
return /******/ (function(modules) { // webpackBootstrap
/******/
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId])
/******/ return installedModules[moduleId].exports;
@ -74,7 +74,6 @@ return /******/ (function(modules) { // webpackBootstrap
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(0);
/******/ })
@ -381,7 +380,7 @@ return /******/ (function(modules) { // webpackBootstrap
this._setOptions(options);
if (this.options.history && !this.mode.view) {
if (this.options.history && this.options.mode !== 'view') {
this.history = new History(this);
}
@ -420,13 +419,6 @@ return /******/ (function(modules) { // webpackBootstrap
}
}
}
// interpret the mode options
this.mode = {
edit: (this.options.mode != 'view' && this.options.mode != 'form'),
view: (this.options.mode == 'view'),
form: (this.options.mode == 'form')
};
};
// node currently being edited
@ -1032,7 +1024,7 @@ return /******/ (function(modules) { // webpackBootstrap
// width, and the edit columns do have a fixed width
var col;
this.colgroupContent = document.createElement('colgroup');
if (this.mode.edit) {
if (this.options.mode === 'tree') {
col = document.createElement('col');
col.width = "24px";
this.colgroupContent.appendChild(col);
@ -1386,10 +1378,20 @@ return /******/ (function(modules) { // webpackBootstrap
return JSON.parse(jsonString);
}
catch (err) {
// try to load as JavaScript instead of JSON (like "{a: 2}" instead of "{"a": 2}"
try {
return eval('(' + jsonString + ')');
}
catch(err2) {
// ok no luck loading as JavaScript
// try to throw a more detailed error message using validate
util.validate(jsonString);
// rethrow the original error
throw err;
}
}
};
/**
@ -2513,6 +2515,56 @@ return /******/ (function(modules) { // webpackBootstrap
}
}
/**
* Determine whether the field and/or value of this node are editable
* @private
*/
Node.prototype._updateEditability = function () {
this.editable = {
field: true,
value: true
};
if (this.editor) {
this.editable.field = this.editor.options.mode === 'tree';
this.editable.value = this.editor.options.mode !== 'view';
if (this.editor.options.mode === 'tree' && (typeof this.editor.options.editable === 'function')) {
var editable = this.editor.options.editable({
field: this.field,
value: this.value,
path: this.path()
});
if (typeof editable === 'boolean') {
this.editable.field = editable;
this.editable.value = editable;
}
else {
if (typeof editable.field === 'boolean') this.editable.field = editable.field;
if (typeof editable.value === 'boolean') this.editable.value = editable.value;
}
}
}
};
/**
* Get the path of this node
* @return {String[]} Array containing the path to this node
*/
Node.prototype.path = function () {
var node = this;
var path = [];
while (node) {
var field = node.field || node.index;
if (field !== undefined) {
path.unshift(field);
}
node = node.parent;
}
return path;
};
/**
* Set parent node
* @param {Node} parent
@ -2584,7 +2636,7 @@ return /******/ (function(modules) { // webpackBootstrap
if (childValue !== undefined && !(childValue instanceof Function)) {
// ignore undefined and functions
child = new Node(this.editor, {
'value': childValue
value: childValue
});
this.appendChild(child);
}
@ -2600,8 +2652,8 @@ return /******/ (function(modules) { // webpackBootstrap
if (childValue !== undefined && !(childValue instanceof Function)) {
// ignore undefined and functions
child = new Node(this.editor, {
'field': childField,
'value': childValue
field: childField,
value: childValue
});
this.appendChild(child);
}
@ -3463,7 +3515,7 @@ return /******/ (function(modules) { // webpackBootstrap
var t = (this.type == 'auto') ? util.type(v) : this.type;
var isUrl = (t == 'string' && util.isUrl(v));
var color = '';
if (isUrl && !this.editor.mode.edit) {
if (isUrl && !this.editable.value) { // TODO: when to apply this?
color = '';
}
else if (t == 'string') {
@ -3510,7 +3562,7 @@ return /******/ (function(modules) { // webpackBootstrap
domValue.title = this.type + ' containing ' + count + ' items';
}
else if (t == 'string' && util.isUrl(v)) {
if (this.editor.mode.edit) {
if (this.editable.value) {
domValue.title = 'Ctrl+Click or Ctrl+Enter to open url in new window';
}
}
@ -3634,13 +3686,16 @@ return /******/ (function(modules) { // webpackBootstrap
return dom.tr;
}
this._updateEditability();
// create row
dom.tr = document.createElement('tr');
dom.tr.node = this;
if (this.editor.mode.edit) {
// create draggable area
if (this.editor.options.mode === 'tree') { // note: we take here the global setting
var tdDrag = document.createElement('td');
if (this.editable.field) {
// create draggable area
if (this.parent) {
var domDrag = document.createElement('button');
dom.drag = domDrag;
@ -3648,6 +3703,7 @@ return /******/ (function(modules) { // webpackBootstrap
domDrag.title = 'Drag to move this field (Alt+Shift+Arrows)';
tdDrag.appendChild(domDrag);
}
}
dom.tr.appendChild(tdDrag);
// create context menu
@ -3971,9 +4027,9 @@ return /******/ (function(modules) { // webpackBootstrap
// update field
var domField = this.dom.field;
if (domField) {
if (this.fieldEditable == true) {
if (this.fieldEditable) {
// parent is an object
domField.contentEditable = this.editor.mode.edit;
domField.contentEditable = this.editable.field;
domField.spellcheck = false;
domField.className = 'field';
}
@ -4089,7 +4145,7 @@ return /******/ (function(modules) { // webpackBootstrap
domValue.innerHTML = '{...}';
}
else {
if (!this.editor.mode.edit && util.isUrl(this.value)) {
if (!this.editable.value && util.isUrl(this.value)) {
// create a link in case of read-only editor and value containing an url
domValue = document.createElement('a');
domValue.className = 'value';
@ -4098,9 +4154,9 @@ return /******/ (function(modules) { // webpackBootstrap
domValue.innerHTML = this._escapeHTML(this.value);
}
else {
// create and editable or read-only div
// create an editable or read-only div
domValue = document.createElement('div');
domValue.contentEditable = !this.editor.mode.view;
domValue.contentEditable = this.editable.value;
domValue.spellcheck = false;
domValue.className = 'value';
domValue.innerHTML = this._escapeHTML(this.value);
@ -4263,7 +4319,7 @@ return /******/ (function(modules) { // webpackBootstrap
break;
case 'click':
if (event.ctrlKey && this.editor.mode.edit) {
if (event.ctrlKey || !this.editable.value) {
if (util.isUrl(this.value)) {
window.open(this.value, '_blank');
}
@ -4381,11 +4437,12 @@ return /******/ (function(modules) { // webpackBootstrap
var altKey = event.altKey;
var handled = false;
var prevNode, nextNode, nextDom, nextDom2;
var editable = this.editor.options.mode === 'tree';
// util.log(ctrlKey, keynum, event.charCode); // TODO: cleanup
if (keynum == 13) { // Enter
if (target == this.dom.value) {
if (!this.editor.mode.edit || event.ctrlKey) {
if (!this.editable.value || event.ctrlKey) {
if (util.isUrl(this.value)) {
window.open(this.value, '_blank');
handled = true;
@ -4403,7 +4460,7 @@ return /******/ (function(modules) { // webpackBootstrap
}
}
else if (keynum == 68) { // D
if (ctrlKey) { // Ctrl+D
if (ctrlKey && editable) { // Ctrl+D
this._onDuplicate();
handled = true;
}
@ -4415,19 +4472,19 @@ return /******/ (function(modules) { // webpackBootstrap
handled = true;
}
}
else if (keynum == 77) { // M
else if (keynum == 77 && editable) { // M
if (ctrlKey) { // Ctrl+M
this.showContextMenu(target);
handled = true;
}
}
else if (keynum == 46) { // Del
else if (keynum == 46 && editable) { // Del
if (ctrlKey) { // Ctrl+Del
this._onRemove();
handled = true;
}
}
else if (keynum == 45) { // Ins
else if (keynum == 45 && editable) { // Ins
if (ctrlKey && !shiftKey) { // Ctrl+Ins
this._onInsertBefore();
handled = true;
@ -4466,7 +4523,7 @@ return /******/ (function(modules) { // webpackBootstrap
}
handled = true;
}
else if (altKey && shiftKey) { // Alt + Shift Arrow left
else if (altKey && shiftKey && editable) { // Alt + Shift Arrow left
if (this.expanded) {
var appendDom = this.getAppend();
nextDom = appendDom ? appendDom.nextSibling : undefined;
@ -4539,7 +4596,7 @@ return /******/ (function(modules) { // webpackBootstrap
}
handled = true;
}
else if (altKey && shiftKey) { // Alt + Shift + Arrow Down
else if (altKey && shiftKey && editable) { // Alt + Shift + Arrow Down
// find the 2nd next node and move before that one
if (this.expanded) {
nextNode = this.append ? this.append._nextNode() : undefined;
@ -4624,11 +4681,11 @@ return /******/ (function(modules) { // webpackBootstrap
// store history action
this.editor._onAction('removeNode', {
'node': this,
'parent': this.parent,
'index': index,
'oldSelection': oldSelection,
'newSelection': newSelection
node: this,
parent: this.parent,
index: index,
oldSelection: oldSelection,
newSelection: newSelection
});
};
@ -4643,11 +4700,11 @@ return /******/ (function(modules) { // webpackBootstrap
var newSelection = this.editor.getSelection();
this.editor._onAction('duplicateNode', {
'node': this,
'clone': clone,
'parent': this.parent,
'oldSelection': oldSelection,
'newSelection': newSelection
node: this,
clone: clone,
parent: this.parent,
oldSelection: oldSelection,
newSelection: newSelection
});
};
@ -4662,9 +4719,9 @@ return /******/ (function(modules) { // webpackBootstrap
var oldSelection = this.editor.getSelection();
var newNode = new Node(this.editor, {
'field': (field != undefined) ? field : '',
'value': (value != undefined) ? value : '',
'type': type
field: (field != undefined) ? field : '',
value: (value != undefined) ? value : '',
type: type
});
newNode.expand(true);
this.parent.insertBefore(newNode, this);
@ -4673,11 +4730,11 @@ return /******/ (function(modules) { // webpackBootstrap
var newSelection = this.editor.getSelection();
this.editor._onAction('insertBeforeNode', {
'node': newNode,
'beforeNode': this,
'parent': this.parent,
'oldSelection': oldSelection,
'newSelection': newSelection
node: newNode,
beforeNode: this,
parent: this.parent,
oldSelection: oldSelection,
newSelection: newSelection
});
};
@ -4692,9 +4749,9 @@ return /******/ (function(modules) { // webpackBootstrap
var oldSelection = this.editor.getSelection();
var newNode = new Node(this.editor, {
'field': (field != undefined) ? field : '',
'value': (value != undefined) ? value : '',
'type': type
field: (field != undefined) ? field : '',
value: (value != undefined) ? value : '',
type: type
});
newNode.expand(true);
this.parent.insertAfter(newNode, this);
@ -4703,11 +4760,11 @@ return /******/ (function(modules) { // webpackBootstrap
var newSelection = this.editor.getSelection();
this.editor._onAction('insertAfterNode', {
'node': newNode,
'afterNode': this,
'parent': this.parent,
'oldSelection': oldSelection,
'newSelection': newSelection
node: newNode,
afterNode: this,
parent: this.parent,
oldSelection: oldSelection,
newSelection: newSelection
});
};
@ -4722,9 +4779,9 @@ return /******/ (function(modules) { // webpackBootstrap
var oldSelection = this.editor.getSelection();
var newNode = new Node(this.editor, {
'field': (field != undefined) ? field : '',
'value': (value != undefined) ? value : '',
'type': type
field: (field != undefined) ? field : '',
value: (value != undefined) ? value : '',
type: type
});
newNode.expand(true);
this.parent.appendChild(newNode);
@ -4733,10 +4790,10 @@ return /******/ (function(modules) { // webpackBootstrap
var newSelection = this.editor.getSelection();
this.editor._onAction('appendNode', {
'node': newNode,
'parent': this.parent,
'oldSelection': oldSelection,
'newSelection': newSelection
node: newNode,
parent: this.parent,
oldSelection: oldSelection,
newSelection: newSelection
});
};
@ -4753,11 +4810,11 @@ return /******/ (function(modules) { // webpackBootstrap
var newSelection = this.editor.getSelection();
this.editor._onAction('changeType', {
'node': this,
'oldType': oldType,
'newType': newType,
'oldSelection': oldSelection,
'newSelection': newSelection
node: this,
oldType: oldType,
newType: newType,
oldSelection: oldSelection,
newSelection: newSelection
});
}
};
@ -4789,11 +4846,11 @@ return /******/ (function(modules) { // webpackBootstrap
this.sort = (order == 1) ? 'asc' : 'desc';
this.editor._onAction('sort', {
'node': this,
'oldChilds': oldChilds,
'oldSort': oldSort,
'newChilds': this.childs,
'newSort': this.sort
node: this,
oldChilds: oldChilds,
oldSort: oldSort,
newChilds: this.childs,
newSort: this.sort
});
this.showChilds();
@ -5023,73 +5080,75 @@ return /******/ (function(modules) { // webpackBootstrap
var titles = Node.TYPE_TITLES;
var items = [];
if (this.editable.value) {
items.push({
'text': 'Type',
'title': 'Change the type of this field',
'className': 'type-' + this.type,
'submenu': [
text: 'Type',
title: 'Change the type of this field',
className: 'type-' + this.type,
submenu: [
{
'text': 'Auto',
'className': 'type-auto' +
text: 'Auto',
className: 'type-auto' +
(this.type == 'auto' ? ' selected' : ''),
'title': titles.auto,
'click': function () {
title: titles.auto,
click: function () {
node._onChangeType('auto');
}
},
{
'text': 'Array',
'className': 'type-array' +
text: 'Array',
className: 'type-array' +
(this.type == 'array' ? ' selected' : ''),
'title': titles.array,
'click': function () {
title: titles.array,
click: function () {
node._onChangeType('array');
}
},
{
'text': 'Object',
'className': 'type-object' +
text: 'Object',
className: 'type-object' +
(this.type == 'object' ? ' selected' : ''),
'title': titles.object,
'click': function () {
title: titles.object,
click: function () {
node._onChangeType('object');
}
},
{
'text': 'String',
'className': 'type-string' +
text: 'String',
className: 'type-string' +
(this.type == 'string' ? ' selected' : ''),
'title': titles.string,
'click': function () {
title: titles.string,
click: function () {
node._onChangeType('string');
}
}
]
});
}
if (this._hasChilds()) {
var direction = ((this.sort == 'asc') ? 'desc': 'asc');
items.push({
'text': 'Sort',
'title': 'Sort the childs of this ' + this.type,
'className': 'sort-' + direction,
'click': function () {
text: 'Sort',
title: 'Sort the childs of this ' + this.type,
className: 'sort-' + direction,
click: function () {
node._onSort(direction);
},
'submenu': [
submenu: [
{
'text': 'Ascending',
'className': 'sort-asc',
'title': 'Sort the childs of this ' + this.type + ' in ascending order',
'click': function () {
text: 'Ascending',
className: 'sort-asc',
title: 'Sort the childs of this ' + this.type + ' in ascending order',
click: function () {
node._onSort('asc');
}
},
{
'text': 'Descending',
'className': 'sort-desc',
'title': 'Sort the childs of this ' + this.type +' in descending order',
'click': function () {
text: 'Descending',
className: 'sort-desc',
title: 'Sort the childs of this ' + this.type +' in descending order',
click: function () {
node._onSort('desc');
}
}
@ -5098,52 +5157,54 @@ return /******/ (function(modules) { // webpackBootstrap
}
if (this.parent && this.parent._hasChilds()) {
if (items.length) {
// create a separator
items.push({
'type': 'separator'
});
}
// create append button (for last child node only)
var childs = node.parent.childs;
if (node == childs[childs.length - 1]) {
items.push({
'text': 'Append',
'title': 'Append a new field with type \'auto\' after this field (Ctrl+Shift+Ins)',
'submenuTitle': 'Select the type of the field to be appended',
'className': 'append',
'click': function () {
text: 'Append',
title: 'Append a new field with type \'auto\' after this field (Ctrl+Shift+Ins)',
submenuTitle: 'Select the type of the field to be appended',
className: 'append',
click: function () {
node._onAppend('', '', 'auto');
},
'submenu': [
submenu: [
{
'text': 'Auto',
'className': 'type-auto',
'title': titles.auto,
'click': function () {
text: 'Auto',
className: 'type-auto',
title: titles.auto,
click: function () {
node._onAppend('', '', 'auto');
}
},
{
'text': 'Array',
'className': 'type-array',
'title': titles.array,
'click': function () {
text: 'Array',
className: 'type-array',
title: titles.array,
click: function () {
node._onAppend('', []);
}
},
{
'text': 'Object',
'className': 'type-object',
'title': titles.object,
'click': function () {
text: 'Object',
className: 'type-object',
title: titles.object,
click: function () {
node._onAppend('', {});
}
},
{
'text': 'String',
'className': 'type-string',
'title': titles.string,
'click': function () {
text: 'String',
className: 'type-string',
title: titles.string,
click: function () {
node._onAppend('', '', 'string');
}
}
@ -5153,69 +5214,71 @@ return /******/ (function(modules) { // webpackBootstrap
// create insert button
items.push({
'text': 'Insert',
'title': 'Insert a new field with type \'auto\' before this field (Ctrl+Ins)',
'submenuTitle': 'Select the type of the field to be inserted',
'className': 'insert',
'click': function () {
text: 'Insert',
title: 'Insert a new field with type \'auto\' before this field (Ctrl+Ins)',
submenuTitle: 'Select the type of the field to be inserted',
className: 'insert',
click: function () {
node._onInsertBefore('', '', 'auto');
},
'submenu': [
submenu: [
{
'text': 'Auto',
'className': 'type-auto',
'title': titles.auto,
'click': function () {
text: 'Auto',
className: 'type-auto',
title: titles.auto,
click: function () {
node._onInsertBefore('', '', 'auto');
}
},
{
'text': 'Array',
'className': 'type-array',
'title': titles.array,
'click': function () {
text: 'Array',
className: 'type-array',
title: titles.array,
click: function () {
node._onInsertBefore('', []);
}
},
{
'text': 'Object',
'className': 'type-object',
'title': titles.object,
'click': function () {
text: 'Object',
className: 'type-object',
title: titles.object,
click: function () {
node._onInsertBefore('', {});
}
},
{
'text': 'String',
'className': 'type-string',
'title': titles.string,
'click': function () {
text: 'String',
className: 'type-string',
title: titles.string,
click: function () {
node._onInsertBefore('', '', 'string');
}
}
]
});
if (this.editable.field) {
// create duplicate button
items.push({
'text': 'Duplicate',
'title': 'Duplicate this field (Ctrl+D)',
'className': 'duplicate',
'click': function () {
text: 'Duplicate',
title: 'Duplicate this field (Ctrl+D)',
className: 'duplicate',
click: function () {
node._onDuplicate();
}
});
// create remove button
items.push({
'text': 'Remove',
'title': 'Remove this field (Ctrl+Del)',
'className': 'remove',
'click': function () {
text: 'Remove',
title: 'Remove this field (Ctrl+Del)',
className: 'remove',
click: function () {
node._onRemove();
}
});
}
}
var menu = new ContextMenu(items, {close: onClose});
menu.show(anchor);
@ -5949,6 +6012,8 @@ return /******/ (function(modules) { // webpackBootstrap
return dom.tr;
}
this._updateEditability();
// a row for the append button
var trAppend = document.createElement('tr');
trAppend.node = this;
@ -5956,7 +6021,7 @@ return /******/ (function(modules) { // webpackBootstrap
// TODO: consistent naming
if (this.editor.mode.edit) {
if (this.editable.field) {
// a cell for the dragarea column
dom.tdDrag = document.createElement('td');

File diff suppressed because one or more lines are too long

2
jsoneditor.min.css vendored

File diff suppressed because one or more lines are too long

9
jsoneditor.min.js vendored

File diff suppressed because one or more lines are too long

View File

@ -29,7 +29,7 @@ correct date and version number in the header.
Test whether the npm library is ok by installing it locally:
cd ../tmp-folder
npm install ../mathjs
npm install ./path/to/jsoneditor
Check whether the examples in the library work ok, and whether the necessary
files are included.
@ -57,13 +57,13 @@ Publish at cdnjs: test after 30 to 60 minutes whether the new version is
published at cdnjs (should auto update).
## Test published libraries
## Test published library
Install the libraries locally and test whether they work correctly:
cd tmp-folder
npm install mathjs
bower install mathjs
npm install jsoneditor
bower install jsoneditor
## Put zip file to website

View File

@ -1,6 +1,6 @@
{
"name": "jsoneditor",
"version": "3.0.0",
"version": "3.1.0",
"main": "jsoneditor.js",
"description": "A web-based tool to view, edit and format JSON",
"tags": [

View File

@ -27,6 +27,56 @@ define(['./ContextMenu', './appendNodeFactory', './util'], function (ContextMenu
}
}
/**
* Determine whether the field and/or value of this node are editable
* @private
*/
Node.prototype._updateEditability = function () {
this.editable = {
field: true,
value: true
};
if (this.editor) {
this.editable.field = this.editor.options.mode === 'tree';
this.editable.value = this.editor.options.mode !== 'view';
if (this.editor.options.mode === 'tree' && (typeof this.editor.options.editable === 'function')) {
var editable = this.editor.options.editable({
field: this.field,
value: this.value,
path: this.path()
});
if (typeof editable === 'boolean') {
this.editable.field = editable;
this.editable.value = editable;
}
else {
if (typeof editable.field === 'boolean') this.editable.field = editable.field;
if (typeof editable.value === 'boolean') this.editable.value = editable.value;
}
}
}
};
/**
* Get the path of this node
* @return {String[]} Array containing the path to this node
*/
Node.prototype.path = function () {
var node = this;
var path = [];
while (node) {
var field = node.field || node.index;
if (field !== undefined) {
path.unshift(field);
}
node = node.parent;
}
return path;
};
/**
* Set parent node
* @param {Node} parent
@ -98,7 +148,7 @@ define(['./ContextMenu', './appendNodeFactory', './util'], function (ContextMenu
if (childValue !== undefined && !(childValue instanceof Function)) {
// ignore undefined and functions
child = new Node(this.editor, {
'value': childValue
value: childValue
});
this.appendChild(child);
}
@ -114,8 +164,8 @@ define(['./ContextMenu', './appendNodeFactory', './util'], function (ContextMenu
if (childValue !== undefined && !(childValue instanceof Function)) {
// ignore undefined and functions
child = new Node(this.editor, {
'field': childField,
'value': childValue
field: childField,
value: childValue
});
this.appendChild(child);
}
@ -977,7 +1027,7 @@ define(['./ContextMenu', './appendNodeFactory', './util'], function (ContextMenu
var t = (this.type == 'auto') ? util.type(v) : this.type;
var isUrl = (t == 'string' && util.isUrl(v));
var color = '';
if (isUrl && !this.editor.mode.edit) {
if (isUrl && !this.editable.value) { // TODO: when to apply this?
color = '';
}
else if (t == 'string') {
@ -1024,7 +1074,7 @@ define(['./ContextMenu', './appendNodeFactory', './util'], function (ContextMenu
domValue.title = this.type + ' containing ' + count + ' items';
}
else if (t == 'string' && util.isUrl(v)) {
if (this.editor.mode.edit) {
if (this.editable.value) {
domValue.title = 'Ctrl+Click or Ctrl+Enter to open url in new window';
}
}
@ -1148,13 +1198,16 @@ define(['./ContextMenu', './appendNodeFactory', './util'], function (ContextMenu
return dom.tr;
}
this._updateEditability();
// create row
dom.tr = document.createElement('tr');
dom.tr.node = this;
if (this.editor.mode.edit) {
// create draggable area
if (this.editor.options.mode === 'tree') { // note: we take here the global setting
var tdDrag = document.createElement('td');
if (this.editable.field) {
// create draggable area
if (this.parent) {
var domDrag = document.createElement('button');
dom.drag = domDrag;
@ -1162,6 +1215,7 @@ define(['./ContextMenu', './appendNodeFactory', './util'], function (ContextMenu
domDrag.title = 'Drag to move this field (Alt+Shift+Arrows)';
tdDrag.appendChild(domDrag);
}
}
dom.tr.appendChild(tdDrag);
// create context menu
@ -1485,9 +1539,9 @@ define(['./ContextMenu', './appendNodeFactory', './util'], function (ContextMenu
// update field
var domField = this.dom.field;
if (domField) {
if (this.fieldEditable == true) {
if (this.fieldEditable) {
// parent is an object
domField.contentEditable = this.editor.mode.edit;
domField.contentEditable = this.editable.field;
domField.spellcheck = false;
domField.className = 'field';
}
@ -1603,7 +1657,7 @@ define(['./ContextMenu', './appendNodeFactory', './util'], function (ContextMenu
domValue.innerHTML = '{...}';
}
else {
if (!this.editor.mode.edit && util.isUrl(this.value)) {
if (!this.editable.value && util.isUrl(this.value)) {
// create a link in case of read-only editor and value containing an url
domValue = document.createElement('a');
domValue.className = 'value';
@ -1612,9 +1666,9 @@ define(['./ContextMenu', './appendNodeFactory', './util'], function (ContextMenu
domValue.innerHTML = this._escapeHTML(this.value);
}
else {
// create and editable or read-only div
// create an editable or read-only div
domValue = document.createElement('div');
domValue.contentEditable = !this.editor.mode.view;
domValue.contentEditable = this.editable.value;
domValue.spellcheck = false;
domValue.className = 'value';
domValue.innerHTML = this._escapeHTML(this.value);
@ -1777,7 +1831,7 @@ define(['./ContextMenu', './appendNodeFactory', './util'], function (ContextMenu
break;
case 'click':
if (event.ctrlKey && this.editor.mode.edit) {
if (event.ctrlKey || !this.editable.value) {
if (util.isUrl(this.value)) {
window.open(this.value, '_blank');
}
@ -1895,11 +1949,12 @@ define(['./ContextMenu', './appendNodeFactory', './util'], function (ContextMenu
var altKey = event.altKey;
var handled = false;
var prevNode, nextNode, nextDom, nextDom2;
var editable = this.editor.options.mode === 'tree';
// util.log(ctrlKey, keynum, event.charCode); // TODO: cleanup
if (keynum == 13) { // Enter
if (target == this.dom.value) {
if (!this.editor.mode.edit || event.ctrlKey) {
if (!this.editable.value || event.ctrlKey) {
if (util.isUrl(this.value)) {
window.open(this.value, '_blank');
handled = true;
@ -1917,7 +1972,7 @@ define(['./ContextMenu', './appendNodeFactory', './util'], function (ContextMenu
}
}
else if (keynum == 68) { // D
if (ctrlKey) { // Ctrl+D
if (ctrlKey && editable) { // Ctrl+D
this._onDuplicate();
handled = true;
}
@ -1929,19 +1984,19 @@ define(['./ContextMenu', './appendNodeFactory', './util'], function (ContextMenu
handled = true;
}
}
else if (keynum == 77) { // M
else if (keynum == 77 && editable) { // M
if (ctrlKey) { // Ctrl+M
this.showContextMenu(target);
handled = true;
}
}
else if (keynum == 46) { // Del
else if (keynum == 46 && editable) { // Del
if (ctrlKey) { // Ctrl+Del
this._onRemove();
handled = true;
}
}
else if (keynum == 45) { // Ins
else if (keynum == 45 && editable) { // Ins
if (ctrlKey && !shiftKey) { // Ctrl+Ins
this._onInsertBefore();
handled = true;
@ -1980,7 +2035,7 @@ define(['./ContextMenu', './appendNodeFactory', './util'], function (ContextMenu
}
handled = true;
}
else if (altKey && shiftKey) { // Alt + Shift Arrow left
else if (altKey && shiftKey && editable) { // Alt + Shift Arrow left
if (this.expanded) {
var appendDom = this.getAppend();
nextDom = appendDom ? appendDom.nextSibling : undefined;
@ -2053,7 +2108,7 @@ define(['./ContextMenu', './appendNodeFactory', './util'], function (ContextMenu
}
handled = true;
}
else if (altKey && shiftKey) { // Alt + Shift + Arrow Down
else if (altKey && shiftKey && editable) { // Alt + Shift + Arrow Down
// find the 2nd next node and move before that one
if (this.expanded) {
nextNode = this.append ? this.append._nextNode() : undefined;
@ -2138,11 +2193,11 @@ define(['./ContextMenu', './appendNodeFactory', './util'], function (ContextMenu
// store history action
this.editor._onAction('removeNode', {
'node': this,
'parent': this.parent,
'index': index,
'oldSelection': oldSelection,
'newSelection': newSelection
node: this,
parent: this.parent,
index: index,
oldSelection: oldSelection,
newSelection: newSelection
});
};
@ -2157,11 +2212,11 @@ define(['./ContextMenu', './appendNodeFactory', './util'], function (ContextMenu
var newSelection = this.editor.getSelection();
this.editor._onAction('duplicateNode', {
'node': this,
'clone': clone,
'parent': this.parent,
'oldSelection': oldSelection,
'newSelection': newSelection
node: this,
clone: clone,
parent: this.parent,
oldSelection: oldSelection,
newSelection: newSelection
});
};
@ -2176,9 +2231,9 @@ define(['./ContextMenu', './appendNodeFactory', './util'], function (ContextMenu
var oldSelection = this.editor.getSelection();
var newNode = new Node(this.editor, {
'field': (field != undefined) ? field : '',
'value': (value != undefined) ? value : '',
'type': type
field: (field != undefined) ? field : '',
value: (value != undefined) ? value : '',
type: type
});
newNode.expand(true);
this.parent.insertBefore(newNode, this);
@ -2187,11 +2242,11 @@ define(['./ContextMenu', './appendNodeFactory', './util'], function (ContextMenu
var newSelection = this.editor.getSelection();
this.editor._onAction('insertBeforeNode', {
'node': newNode,
'beforeNode': this,
'parent': this.parent,
'oldSelection': oldSelection,
'newSelection': newSelection
node: newNode,
beforeNode: this,
parent: this.parent,
oldSelection: oldSelection,
newSelection: newSelection
});
};
@ -2206,9 +2261,9 @@ define(['./ContextMenu', './appendNodeFactory', './util'], function (ContextMenu
var oldSelection = this.editor.getSelection();
var newNode = new Node(this.editor, {
'field': (field != undefined) ? field : '',
'value': (value != undefined) ? value : '',
'type': type
field: (field != undefined) ? field : '',
value: (value != undefined) ? value : '',
type: type
});
newNode.expand(true);
this.parent.insertAfter(newNode, this);
@ -2217,11 +2272,11 @@ define(['./ContextMenu', './appendNodeFactory', './util'], function (ContextMenu
var newSelection = this.editor.getSelection();
this.editor._onAction('insertAfterNode', {
'node': newNode,
'afterNode': this,
'parent': this.parent,
'oldSelection': oldSelection,
'newSelection': newSelection
node: newNode,
afterNode: this,
parent: this.parent,
oldSelection: oldSelection,
newSelection: newSelection
});
};
@ -2236,9 +2291,9 @@ define(['./ContextMenu', './appendNodeFactory', './util'], function (ContextMenu
var oldSelection = this.editor.getSelection();
var newNode = new Node(this.editor, {
'field': (field != undefined) ? field : '',
'value': (value != undefined) ? value : '',
'type': type
field: (field != undefined) ? field : '',
value: (value != undefined) ? value : '',
type: type
});
newNode.expand(true);
this.parent.appendChild(newNode);
@ -2247,10 +2302,10 @@ define(['./ContextMenu', './appendNodeFactory', './util'], function (ContextMenu
var newSelection = this.editor.getSelection();
this.editor._onAction('appendNode', {
'node': newNode,
'parent': this.parent,
'oldSelection': oldSelection,
'newSelection': newSelection
node: newNode,
parent: this.parent,
oldSelection: oldSelection,
newSelection: newSelection
});
};
@ -2267,11 +2322,11 @@ define(['./ContextMenu', './appendNodeFactory', './util'], function (ContextMenu
var newSelection = this.editor.getSelection();
this.editor._onAction('changeType', {
'node': this,
'oldType': oldType,
'newType': newType,
'oldSelection': oldSelection,
'newSelection': newSelection
node: this,
oldType: oldType,
newType: newType,
oldSelection: oldSelection,
newSelection: newSelection
});
}
};
@ -2303,11 +2358,11 @@ define(['./ContextMenu', './appendNodeFactory', './util'], function (ContextMenu
this.sort = (order == 1) ? 'asc' : 'desc';
this.editor._onAction('sort', {
'node': this,
'oldChilds': oldChilds,
'oldSort': oldSort,
'newChilds': this.childs,
'newSort': this.sort
node: this,
oldChilds: oldChilds,
oldSort: oldSort,
newChilds: this.childs,
newSort: this.sort
});
this.showChilds();
@ -2537,73 +2592,75 @@ define(['./ContextMenu', './appendNodeFactory', './util'], function (ContextMenu
var titles = Node.TYPE_TITLES;
var items = [];
if (this.editable.value) {
items.push({
'text': 'Type',
'title': 'Change the type of this field',
'className': 'type-' + this.type,
'submenu': [
text: 'Type',
title: 'Change the type of this field',
className: 'type-' + this.type,
submenu: [
{
'text': 'Auto',
'className': 'type-auto' +
text: 'Auto',
className: 'type-auto' +
(this.type == 'auto' ? ' selected' : ''),
'title': titles.auto,
'click': function () {
title: titles.auto,
click: function () {
node._onChangeType('auto');
}
},
{
'text': 'Array',
'className': 'type-array' +
text: 'Array',
className: 'type-array' +
(this.type == 'array' ? ' selected' : ''),
'title': titles.array,
'click': function () {
title: titles.array,
click: function () {
node._onChangeType('array');
}
},
{
'text': 'Object',
'className': 'type-object' +
text: 'Object',
className: 'type-object' +
(this.type == 'object' ? ' selected' : ''),
'title': titles.object,
'click': function () {
title: titles.object,
click: function () {
node._onChangeType('object');
}
},
{
'text': 'String',
'className': 'type-string' +
text: 'String',
className: 'type-string' +
(this.type == 'string' ? ' selected' : ''),
'title': titles.string,
'click': function () {
title: titles.string,
click: function () {
node._onChangeType('string');
}
}
]
});
}
if (this._hasChilds()) {
var direction = ((this.sort == 'asc') ? 'desc': 'asc');
items.push({
'text': 'Sort',
'title': 'Sort the childs of this ' + this.type,
'className': 'sort-' + direction,
'click': function () {
text: 'Sort',
title: 'Sort the childs of this ' + this.type,
className: 'sort-' + direction,
click: function () {
node._onSort(direction);
},
'submenu': [
submenu: [
{
'text': 'Ascending',
'className': 'sort-asc',
'title': 'Sort the childs of this ' + this.type + ' in ascending order',
'click': function () {
text: 'Ascending',
className: 'sort-asc',
title: 'Sort the childs of this ' + this.type + ' in ascending order',
click: function () {
node._onSort('asc');
}
},
{
'text': 'Descending',
'className': 'sort-desc',
'title': 'Sort the childs of this ' + this.type +' in descending order',
'click': function () {
text: 'Descending',
className: 'sort-desc',
title: 'Sort the childs of this ' + this.type +' in descending order',
click: function () {
node._onSort('desc');
}
}
@ -2612,52 +2669,54 @@ define(['./ContextMenu', './appendNodeFactory', './util'], function (ContextMenu
}
if (this.parent && this.parent._hasChilds()) {
if (items.length) {
// create a separator
items.push({
'type': 'separator'
});
}
// create append button (for last child node only)
var childs = node.parent.childs;
if (node == childs[childs.length - 1]) {
items.push({
'text': 'Append',
'title': 'Append a new field with type \'auto\' after this field (Ctrl+Shift+Ins)',
'submenuTitle': 'Select the type of the field to be appended',
'className': 'append',
'click': function () {
text: 'Append',
title: 'Append a new field with type \'auto\' after this field (Ctrl+Shift+Ins)',
submenuTitle: 'Select the type of the field to be appended',
className: 'append',
click: function () {
node._onAppend('', '', 'auto');
},
'submenu': [
submenu: [
{
'text': 'Auto',
'className': 'type-auto',
'title': titles.auto,
'click': function () {
text: 'Auto',
className: 'type-auto',
title: titles.auto,
click: function () {
node._onAppend('', '', 'auto');
}
},
{
'text': 'Array',
'className': 'type-array',
'title': titles.array,
'click': function () {
text: 'Array',
className: 'type-array',
title: titles.array,
click: function () {
node._onAppend('', []);
}
},
{
'text': 'Object',
'className': 'type-object',
'title': titles.object,
'click': function () {
text: 'Object',
className: 'type-object',
title: titles.object,
click: function () {
node._onAppend('', {});
}
},
{
'text': 'String',
'className': 'type-string',
'title': titles.string,
'click': function () {
text: 'String',
className: 'type-string',
title: titles.string,
click: function () {
node._onAppend('', '', 'string');
}
}
@ -2667,69 +2726,71 @@ define(['./ContextMenu', './appendNodeFactory', './util'], function (ContextMenu
// create insert button
items.push({
'text': 'Insert',
'title': 'Insert a new field with type \'auto\' before this field (Ctrl+Ins)',
'submenuTitle': 'Select the type of the field to be inserted',
'className': 'insert',
'click': function () {
text: 'Insert',
title: 'Insert a new field with type \'auto\' before this field (Ctrl+Ins)',
submenuTitle: 'Select the type of the field to be inserted',
className: 'insert',
click: function () {
node._onInsertBefore('', '', 'auto');
},
'submenu': [
submenu: [
{
'text': 'Auto',
'className': 'type-auto',
'title': titles.auto,
'click': function () {
text: 'Auto',
className: 'type-auto',
title: titles.auto,
click: function () {
node._onInsertBefore('', '', 'auto');
}
},
{
'text': 'Array',
'className': 'type-array',
'title': titles.array,
'click': function () {
text: 'Array',
className: 'type-array',
title: titles.array,
click: function () {
node._onInsertBefore('', []);
}
},
{
'text': 'Object',
'className': 'type-object',
'title': titles.object,
'click': function () {
text: 'Object',
className: 'type-object',
title: titles.object,
click: function () {
node._onInsertBefore('', {});
}
},
{
'text': 'String',
'className': 'type-string',
'title': titles.string,
'click': function () {
text: 'String',
className: 'type-string',
title: titles.string,
click: function () {
node._onInsertBefore('', '', 'string');
}
}
]
});
if (this.editable.field) {
// create duplicate button
items.push({
'text': 'Duplicate',
'title': 'Duplicate this field (Ctrl+D)',
'className': 'duplicate',
'click': function () {
text: 'Duplicate',
title: 'Duplicate this field (Ctrl+D)',
className: 'duplicate',
click: function () {
node._onDuplicate();
}
});
// create remove button
items.push({
'text': 'Remove',
'title': 'Remove this field (Ctrl+Del)',
'className': 'remove',
'click': function () {
text: 'Remove',
title: 'Remove this field (Ctrl+Del)',
className: 'remove',
click: function () {
node._onRemove();
}
});
}
}
var menu = new ContextMenu(items, {close: onClose});
menu.show(anchor);

View File

@ -32,6 +32,8 @@ define(['./ContextMenu', './util'], function (ContextMenu, util) {
return dom.tr;
}
this._updateEditability();
// a row for the append button
var trAppend = document.createElement('tr');
trAppend.node = this;
@ -39,7 +41,7 @@ define(['./ContextMenu', './util'], function (ContextMenu, util) {
// TODO: consistent naming
if (this.editor.mode.edit) {
if (this.editable.field) {
// a cell for the dragarea column
dom.tdDrag = document.createElement('td');

View File

@ -31,7 +31,7 @@ define(['./Highlighter', './History', './SearchBox', './Node', './modeswitcher',
this._setOptions(options);
if (this.options.history && !this.mode.view) {
if (this.options.history && this.options.mode !== 'view') {
this.history = new History(this);
}
@ -70,13 +70,6 @@ define(['./Highlighter', './History', './SearchBox', './Node', './modeswitcher',
}
}
}
// interpret the mode options
this.mode = {
edit: (this.options.mode != 'view' && this.options.mode != 'form'),
view: (this.options.mode == 'view'),
form: (this.options.mode == 'form')
};
};
// node currently being edited
@ -682,7 +675,7 @@ define(['./Highlighter', './History', './SearchBox', './Node', './modeswitcher',
// width, and the edit columns do have a fixed width
var col;
this.colgroupContent = document.createElement('colgroup');
if (this.mode.edit) {
if (this.options.mode === 'tree') {
col = document.createElement('col');
col.width = "24px";
this.colgroupContent.appendChild(col);

View File

@ -13,10 +13,20 @@ define(function () {
return JSON.parse(jsonString);
}
catch (err) {
// try to load as JavaScript instead of JSON (like "{a: 2}" instead of "{"a": 2}"
try {
return eval('(' + jsonString + ')');
}
catch(err2) {
// ok no luck loading as JavaScript
// try to throw a more detailed error message using validate
util.validate(jsonString);
// rethrow the original error
throw err;
}
}
};
/**

View File

@ -67,17 +67,37 @@
mode: 'tree',
modes: ['code', 'form', 'text', 'tree', 'view'], // allowed modes
error: function (err) {
console.trace();
alert(err.toString());
},
editable: function (node) {
console.log(node);
switch(node.field) {
case '_id':
return false;
case '_field':
return {
field: false,
value: true
};
default:
return true;
}
}
};
json = {
"_id": "1234",
"_field": "ABCD",
"array": [1, 2, 3],
"boolean": true,
"null": null,
"number": 123,
"object": {"a": "b", "c": "d"},
"string": "Hello World"
"object": {"a": "b", "c": {"d": "e", "f": "g"}},
"string": "Hello World",
"url": "http://jsoneditoronline.org"
};
editor = new JSONEditor(container, options, json);