123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540 |
- Copyright (c) 2013, Andrew Cantino
- Copyright (c) 2009, Andrew Cantino & Kyle Maxwell
- Permission is hereby granted, free of charge, to any person obtaining a copy
- of this software and associated documentation files (the "Software"), to deal
- in the Software without restriction, including without limitation the rights
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- copies of the Software, and to permit persons to whom the Software is
- furnished to do so, subject to the following conditions:
- The above copyright notice and this permission notice shall be included in
- all copies or substantial portions of the Software.
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- THE SOFTWARE.
- You will probably need to tell the editor where to find its add and delete images. In your
- code before you make the editor, do something like this:
- JSONEditor.prototype.ADD_IMG = '/javascripts/jsoneditor/add.png';
- JSONEditor.prototype.DELETE_IMG = '/javascripts/jsoneditor/delete.png';
- You can enable or disable visual truncation in the structure editor with the following:
- myEditor.doTruncation(false);
- myEditor.doTruncation(true); // The default
- You can show a 'w'ipe button that does a more aggressive delete by calling showWipe(true|false) or by passing in 'showWipe: true'.
- */
- function JSONEditorBase(options) {
- if (!options) options = {};
- this.builderShowing = true;
- this.ADD_IMG = options.ADD_IMG || 'lib/images/add.png';
- this.DELETE_IMG = options.DELETE_IMG || 'lib/images/delete.png';
- this.functionButtonsEnabled = false;
- this._doTruncation = true;
- this._showWipe = options.showWipe;
- }
- function JSONEditor(wrapped, width, height) {
- this.history = [];
- this.historyPointer = -1;
- if (wrapped == null || (wrapped.get && wrapped.get(0) == null)) throw "Must provide an element to wrap.";
- var width = width || 600;
- var height = height || 300;
- this.wrapped = $(wrapped);
- this.wrapped.wrap('<div class="json-editor"></div>');
- this.container = $(this.wrapped.parent());
- this.container.width(width).height(height);
- this.wrapped.width(width).height(height);
- this.wrapped.hide();
- this.container.css("position", "relative");
- this.doAutoFocus = false;
- this.editingUnfocused();
- this.rebuild();
- var self = this;
- this.container.focus(function(){
- $(this).children('textarea').height(self.container.height() - self.functionButtons.height() - 5);
- $(this).children('.builder').height(self.container.height() - self.functionButtons.height() - 10);
- });
- return this;
- }
- JSONEditor.prototype = new JSONEditorBase();
- JSONEditor.prototype.braceUI = function(key, struct) {
- var self = this;
- return $('<a class="icon" href="#"><strong>{</strong></a>').click(function(e) {
- struct[key] = { "??": struct[key] };
- self.doAutoFocus = true;
- self.rebuild();
- return false;
- });
- };
- JSONEditor.prototype.bracketUI = function(key, struct) {
- var self = this;
- return $('<a class="icon" href="#"><strong>[</a>').click(function(e) {
- struct[key] = [ struct[key] ];
- self.doAutoFocus = true;
- self.rebuild();
- return false;
- });
- };
- JSONEditor.prototype.deleteUI = function(key, struct, fullDelete) {
- var self = this;
- return $('<a class="icon" href="#" title="delete"><img src="' + this.DELETE_IMG + '" border=0/></a>').click(function(e) {
- if (!fullDelete) {
- var didSomething = false;
- if (struct[key] instanceof Array) {
- if(struct[key].length > 0) {
- struct[key] = struct[key][0];
- didSomething = true;
- }
- } else if (struct[key] instanceof Object) {
- for (var i in struct[key]) {
- struct[key] = struct[key][i];
- didSomething = true;
- break;
- }
- }
- if (didSomething) {
- self.rebuild();
- return false;
- }
- }
- if (struct instanceof Array) {
- struct.splice(key, 1);
- } else {
- delete struct[key];
- }
- self.rebuild();
- return false;
- });
- };
- JSONEditor.prototype.wipeUI = function(key, struct) {
- var self = this;
- return $('<a class="icon" href="#" title="wipe"><strong>W</strong></a>').click(function(e) {
- if (struct instanceof Array) {
- struct.splice(key, 1);
- } else {
- delete struct[key];
- }
- self.rebuild();
- return false;
- });
- };
- JSONEditor.prototype.addUI = function(struct) {
- var self = this;
- return $('<a class="icon" href="#" title="add"><img src="' + this.ADD_IMG + '" border=0/></a>').click(function(e) {
- if (struct instanceof Array) {
- struct.push('??');
- } else {
- struct['??'] = '??';
- }
- self.doAutoFocus = true;
- self.rebuild();
- return false;
- });
- };
- JSONEditor.prototype.undo = function() {
- if (this.saveStateIfTextChanged()) {
- if (this.historyPointer > 0) this.historyPointer -= 1;
- this.restore();
- }
- };
- JSONEditor.prototype.redo = function() {
- if (this.historyPointer + 1 < this.history.length) {
- if (this.saveStateIfTextChanged()) {
- this.historyPointer += 1;
- this.restore();
- }
- }
- };
- JSONEditor.prototype.showBuilder = function() {
- if (this.checkJsonInText()) {
- this.setJsonFromText();
- this.rebuild();
- this.wrapped.hide();
- this.builder.show();
- return true;
- } else {
- alert("Sorry, there appears to be an error in your JSON input. Please fix it before continuing.");
- return false;
- }
- };
- JSONEditor.prototype.showText = function() {
- this.builder.hide();
- this.wrapped.show();
- };
- JSONEditor.prototype.toggleBuilder = function() {
- if(this.builderShowing){
- this.showText();
- this.builderShowing = !this.builderShowing;
- } else {
- if (this.showBuilder()) {
- this.builderShowing = !this.builderShowing;
- }
- }
- };
- JSONEditor.prototype.showFunctionButtons = function(insider) {
- if (!insider) this.functionButtonsEnabled = true;
- if (this.functionButtonsEnabled) if (!this.functionButtons) {
- this.functionButtons = $('<div class="function_buttons"></div>');
- var self = this;
- this.functionButtons.append($('<a href="#" style="padding-right: 10px;"></a>').click(function() {
- self.undo();
- return false;
- }).text('Undo')).append($('<a href="#" style="padding-right: 10px;"></a>').click(function() {
- self.redo();
- return false;
- }).text('Redo')).append($('<a id="toggle_view" href="#" style="padding-right: 10px;"></a>').click(function() {
- self.toggleBuilder();
- return false;
- }).text('Toggle View').css("float", "right"));
- this.container.prepend(this.functionButtons);
- this.container.height(this.container.height() + this.functionButtons.height() + 5);
- }
- if (this.functionButtons) {
- this.wrapped.css('top', this.functionButtons.height() + 5 + 'px');
- this.builder.css('top', this.functionButtons.height() + 5 + 'px');
- }
- };
- JSONEditor.prototype.saveStateIfTextChanged = function() {
- if (JSON.stringify(this.json, null, 2) != this.wrapped.get(0).value) {
- if (this.checkJsonInText()) {
- this.saveState(true);
- } else {
- if (confirm("The current JSON is malformed. If you continue, the current JSON will not be saved. Do you wish to continue?")) {
- this.historyPointer += 1;
- return true;
- } else {
- return false;
- }
- }
- }
- return true;
- };
- JSONEditor.prototype.restore = function() {
- if (this.history[this.historyPointer]) {
- this.wrapped.get(0).value = this.history[this.historyPointer];
- this.rebuild(true);
- }
- };
- JSONEditor.prototype.saveState = function(skipStoreText) {
- if (this.json) {
- if (!skipStoreText) this.storeToText();
- var text = this.wrapped.get(0).value;
- if (this.history[this.historyPointer] != text) {
- this.historyTruncate();
- this.history.push(text);
- this.historyPointer += 1;
- }
- }
- };
- JSONEditor.prototype.fireChange = function() {
- $(this.wrapped).trigger('change');
- };
- JSONEditor.prototype.historyTruncate = function() {
- if (this.historyPointer + 1 < this.history.length) {
- this.history.splice(this.historyPointer + 1, this.history.length - this.historyPointer);
- }
- };
- JSONEditor.prototype.storeToText = function() {
- this.wrapped.get(0).value = JSON.stringify(this.json, null, 2);
- };
- JSONEditor.prototype.getJSONText = function() {
- this.rebuild();
- return this.wrapped.get(0).value;
- };
- JSONEditor.prototype.getJSON = function() {
- this.rebuild();
- return this.json;
- };
- JSONEditor.prototype.rebuild = function(doNotRefreshText) {
- if (!this.json) this.setJsonFromText();
- var changed = this.haveThingsChanged();
- if (this.json && !doNotRefreshText) {
- this.saveState();
- }
- this.cleanBuilder();
- this.setJsonFromText();
- this.alreadyFocused = false;
- var elem = this.build(this.json, this.builder, null, null, this.json);
- this.recoverScrollPosition();
-
- if (elem) if (elem.text() == '??' && !this.alreadyFocused && this.doAutoFocus) {
- this.alreadyFocused = true;
- this.doAutoFocus = false;
- elem = elem.find('.editable');
- elem.click();
- elem.find('input').focus().select();
-
- }
- if (changed) this.fireChange();
- };
- JSONEditor.prototype.haveThingsChanged = function() {
- return (this.json && JSON.stringify(this.json, null, 2) != this.wrapped.get(0).value);
- }
- JSONEditor.prototype.saveScrollPosition = function() {
- this.oldScrollHeight = this.builder.scrollTop();
- };
- JSONEditor.prototype.recoverScrollPosition = function() {
- this.builder.scrollTop(this.oldScrollHeight);
- };
- JSONEditor.prototype.setJsonFromText = function() {
- if (this.wrapped.get(0).value.length == 0) this.wrapped.get(0).value = "{}";
- try {
- this.wrapped.get(0).value = this.wrapped.get(0).value.replace(/((^|[^\\])(\\\\)*)\\n/g, '$1\\\\n').replace(/((^|[^\\])(\\\\)*)\\t/g, '$1\\\\t');
- this.json = JSON.parse(this.wrapped.get(0).value);
- } catch(e) {
- alert("Got bad JSON from text.");
- }
- };
- JSONEditor.prototype.checkJsonInText = function() {
- try {
- JSON.parse(this.wrapped.get(0).value);
- return true;
- } catch(e) {
- return false;
- }
- };
- JSONEditor.prototype.logJSON = function() {
- console.log(JSON.stringify(this.json, null, 2));
- };
- JSONEditor.prototype.cleanBuilder = function() {
- if (!this.builder) {
- this.builder = $('<div class="builder"></div>');
- this.container.append(this.builder);
- }
- this.saveScrollPosition();
- this.builder.text('');
- this.builder.css("position", "absolute").css("top", 0).css("left", 0);
- this.builder.width(this.wrapped.width()).height(this.wrapped.height());
- this.wrapped.css("position", "absolute").css("top", 0).css("left", 0);
- this.showFunctionButtons("defined");
- };
- JSONEditor.prototype.updateStruct = function(struct, key, val, kind, selectionStart, selectionEnd) {
- if(kind == 'key') {
- if (selectionStart && selectionEnd) val = key.substring(0, selectionStart) + val + key.substring(selectionEnd, key.length);
- struct[val] = struct[key];
-
- var orderrest = 0;
- $.each(struct, function (index, value) {
-
- if(orderrest & index != val) {
- var tempval = struct[index];
- delete struct[index];
- struct[index] = tempval;
- }
- if(key == index) {
- orderrest = 1;
- }
- });
-
- if (key != val) delete struct[key];
- } else {
- if (selectionStart && selectionEnd) val = struct[key].substring(0, selectionStart) + val + struct[key].substring(selectionEnd, struct[key].length);
- struct[key] = val;
- }
- };
- JSONEditor.prototype.getValFromStruct = function(struct, key, kind) {
- if(kind == 'key') {
- return key;
- } else {
- return struct[key];
- }
- };
- JSONEditor.prototype.doTruncation = function(trueOrFalse) {
- if (this._doTruncation != trueOrFalse) {
- this._doTruncation = trueOrFalse;
- this.rebuild();
- }
- };
- JSONEditor.prototype.showWipe = function(trueOrFalse) {
- if (this._showWipe != trueOrFalse) {
- this._showWipe = trueOrFalse;
- this.rebuild();
- }
- };
- JSONEditor.prototype.truncate = function(text, length) {
- if (text.length == 0) return '-empty-';
- if(this._doTruncation && text.length > (length || 30)) return(text.substring(0, (length || 30)) + '...');
- return text;
- };
- JSONEditor.prototype.replaceLastSelectedFieldIfRecent = function(text) {
- if (this.lastEditingUnfocusedTime > (new Date()).getTime() - 200) {
- this.setLastEditingFocus(text);
- this.rebuild();
- }
- };
- JSONEditor.prototype.editingUnfocused = function(elem, struct, key, root, kind) {
- var self = this;
- var selectionStart = elem && elem.target.selectionStart;
- var selectionEnd = elem && elem.target.selectionEnd;
- this.setLastEditingFocus = function(text) {
- self.updateStruct(struct, key, text, kind, selectionStart, selectionEnd);
- self.json = root;
- };
- this.lastEditingUnfocusedTime = (new Date()).getTime();
- };
- JSONEditor.prototype.edit = function(e, key, struct, root, kind){
- var self = this;
- var form = $("<form></form>").css('display', 'inline');
- var input = document.createElement("INPUT");
- input.value = this.getValFromStruct(struct, key, kind);
-
- input.className = 'edit_field';
- var onblur = function(elem) {
- var val = input.value;
- self.updateStruct(struct, key, val, kind);
- self.editingUnfocused(elem, struct, (kind == 'key' ? val : key), root, kind);
- e.text(self.truncate(val));
- e.get(0).editing = false;
- if (key != val) self.rebuild();
- return false;
- };
- $(input).blur(onblur);
- $(input).keydown(function(e) {
- if (e.keyCode == 9 || e.keyCode == 13) {
- self.doAutoFocus = true;
- onblur(e);
- return false;
- }
- });
- $(form).submit(function(e) { self.doAutoFocus = true; onblur(e); return false;}).append(input);
- $(e).html(form);
- input.focus();
- };
- JSONEditor.prototype.editable = function(text, key, struct, root, kind) {
- var self = this;
- var elem = $('<span class="editable" href="#"></span>').text(this.truncate(text)).click(function(e) {
- if (!this.editing) {
- this.editing = true;
- self.edit($(this), key, struct, root, kind);
- }
- return true;
- });
- return elem;
- }
- JSONEditor.prototype.build = function(json, node, parent, key, root) {
- var elem = null;
- if(json instanceof Array){
- var bq = $(document.createElement("BLOCKQUOTE"));
- bq.append($('<div class="brackets">[</div>'));
- bq.prepend(this.addUI(json));
- if (parent) {
- if (this._showWipe) bq.prepend(this.wipeUI(key, parent));
- bq.prepend(this.deleteUI(key, parent));
- }
- for(var i = 0; i < json.length; i++) {
- var innerbq = $(document.createElement("BLOCKQUOTE"));
- var newElem = this.build(json[i], innerbq, json, i, root);
- if (newElem) if (newElem.text() == "??") elem = newElem;
- bq.append(innerbq);
- }
- bq.append($('<div class="brackets">]</div>'));
- node.append(bq);
- } else if (json instanceof Object) {
- var bq = $(document.createElement("BLOCKQUOTE"));
- bq.append($('<div class="bracers">{</div>'));
- for(var i in json){
- var innerbq = $(document.createElement("BLOCKQUOTE"));
- var newElem = this.editable(i.toString(), i.toString(), json, root, 'key').wrap('<span class="key"></b>').parent();
- innerbq.append(newElem);
- if (newElem) if (newElem.text() == "??") elem = newElem;
- if (typeof json[i] != 'string') {
- innerbq.prepend(this.braceUI(i, json));
- innerbq.prepend(this.bracketUI(i, json));
- if (this._showWipe) innerbq.prepend(this.wipeUI(i, json));
- innerbq.prepend(this.deleteUI(i, json, true));
- }
- innerbq.append($('<span class="colon">: </span>'));
- newElem = this.build(json[i], innerbq, json, i, root);
- if (newElem) if (newElem.text() == "??") elem = newElem;
- bq.append(innerbq);
- }
- bq.prepend(this.addUI(json));
- if (parent) {
- if (this._showWipe) bq.prepend(this.wipeUI(key, parent));
- bq.prepend(this.deleteUI(key, parent));
- }
- bq.append($('<div class="bracers">}</div>'));
- node.append(bq);
- } else {
- elem = this.editable(json.toString(), key, parent, root, 'value').wrap('<span class="val"></span>').parent();
- node.append(elem);
- node.prepend(this.braceUI(key, parent));
- node.prepend(this.bracketUI(key, parent));
- if (parent) {
- if (this._showWipe) node.prepend(this.wipeUI(key, parent));
- node.prepend(this.deleteUI(key, parent));
- }
- }
- return elem;
- };
|