1
0

jquery.json-editor.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565
  1. /*
  2. Copyright (c) 2014, Andrew Cantino
  3. Copyright (c) 2009, Andrew Cantino & Kyle Maxwell
  4. Permission is hereby granted, free of charge, to any person obtaining a copy
  5. of this software and associated documentation files (the "Software"), to deal
  6. in the Software without restriction, including without limitation the rights
  7. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  8. copies of the Software, and to permit persons to whom the Software is
  9. furnished to do so, subject to the following conditions:
  10. The above copyright notice and this permission notice shall be included in
  11. all copies or substantial portions of the Software.
  12. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  13. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  14. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  15. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  16. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  17. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  18. THE SOFTWARE.
  19. You will probably need to tell the editor where to find its 'add' and 'delete' images. In your
  20. code, before you make the editor, do something like this:
  21. JSONEditor.prototype.ADD_IMG = '/javascripts/jsoneditor/add.png';
  22. JSONEditor.prototype.DELETE_IMG = '/javascripts/jsoneditor/delete.png';
  23. You can enable or disable visual truncation in the structure editor with the following:
  24. myEditor.doTruncation(false);
  25. myEditor.doTruncation(true); // The default
  26. You can show a 'w'ipe button that does a more aggressive delete by calling showWipe(true|false) or by passing in 'showWipe: true'.
  27. */
  28. (function() {
  29. window.JSONEditor = (function() {
  30. function JSONEditor(wrapped, options) {
  31. if (options == null) {
  32. options = {};
  33. }
  34. this.builderShowing = true;
  35. this.ADD_IMG || (this.ADD_IMG = options.ADD_IMG || 'lib/images/add.png');
  36. this.DELETE_IMG || (this.DELETE_IMG = options.DELETE_IMG || 'lib/images/delete.png');
  37. this.functionButtonsEnabled = false;
  38. this._doTruncation = true;
  39. this._showWipe = options.showWipe;
  40. this.history = [];
  41. this.historyPointer = -1;
  42. if (wrapped === null || (wrapped.get && wrapped.get(0) === null)) {
  43. throw "Must provide an element to wrap.";
  44. }
  45. this.wrapped = $(wrapped);
  46. this.wrapped.wrap('<div class="json-editor"></div>');
  47. this.container = $(this.wrapped.parent());
  48. this.wrapped.hide();
  49. this.container.css("position", "relative");
  50. this.doAutoFocus = false;
  51. this.editingUnfocused();
  52. this.rebuild();
  53. }
  54. JSONEditor.prototype.braceUI = function(key, struct) {
  55. var _this = this;
  56. return $('<a class="icon" href="#"><strong>{</strong></a>').click(function(e) {
  57. e.preventDefault();
  58. struct[key] = {
  59. "??": struct[key]
  60. };
  61. _this.doAutoFocus = true;
  62. return _this.rebuild();
  63. });
  64. };
  65. JSONEditor.prototype.bracketUI = function(key, struct) {
  66. var _this = this;
  67. return $('<a class="icon" href="#"><strong>[</a>').click(function(e) {
  68. e.preventDefault();
  69. struct[key] = [struct[key]];
  70. _this.doAutoFocus = true;
  71. return _this.rebuild();
  72. });
  73. };
  74. JSONEditor.prototype.deleteUI = function(key, struct, fullDelete) {
  75. var _this = this;
  76. return $("<a class='icon' href='#' title='delete'><img src='" + this.DELETE_IMG + "' border=0 /></a>").click(function(e) {
  77. var didSomething, subkey, subval, _ref;
  78. e.preventDefault();
  79. if (!fullDelete) {
  80. didSomething = false;
  81. if (struct[key] instanceof Array) {
  82. if (struct[key].length > 0) {
  83. struct[key] = struct[key][0];
  84. didSomething = true;
  85. }
  86. } else if (struct[key] instanceof Object) {
  87. _ref = struct[key];
  88. for (subkey in _ref) {
  89. subval = _ref[subkey];
  90. struct[key] = struct[key][subkey];
  91. didSomething = true;
  92. break;
  93. }
  94. }
  95. if (didSomething) {
  96. _this.rebuild();
  97. return;
  98. }
  99. }
  100. if (struct instanceof Array) {
  101. struct.splice(key, 1);
  102. } else {
  103. delete struct[key];
  104. }
  105. return _this.rebuild();
  106. });
  107. };
  108. JSONEditor.prototype.wipeUI = function(key, struct) {
  109. var _this = this;
  110. return $('<a class="icon" href="#" title="wipe"><strong>W</strong></a>').click(function(e) {
  111. e.preventDefault();
  112. if (struct instanceof Array) {
  113. struct.splice(key, 1);
  114. } else {
  115. delete struct[key];
  116. }
  117. return _this.rebuild();
  118. });
  119. };
  120. JSONEditor.prototype.addUI = function(struct) {
  121. var _this = this;
  122. return $("<a class='icon' href='#' title='add'><img src='" + this.ADD_IMG + "' border=0/></a>").click(function(e) {
  123. e.preventDefault();
  124. if (struct instanceof Array) {
  125. struct.push('??');
  126. } else {
  127. struct['??'] = '??';
  128. }
  129. _this.doAutoFocus = true;
  130. return _this.rebuild();
  131. });
  132. };
  133. JSONEditor.prototype.undo = function() {
  134. if (this.saveStateIfTextChanged()) {
  135. if (this.historyPointer > 0) {
  136. this.historyPointer -= 1;
  137. }
  138. return this.restore();
  139. }
  140. };
  141. JSONEditor.prototype.redo = function() {
  142. if (this.historyPointer + 1 < this.history.length) {
  143. if (this.saveStateIfTextChanged()) {
  144. this.historyPointer += 1;
  145. return this.restore();
  146. }
  147. }
  148. };
  149. JSONEditor.prototype.showBuilder = function() {
  150. if (this.checkJsonInText()) {
  151. this.setJsonFromText();
  152. this.rebuild();
  153. this.wrapped.hide();
  154. this.builder.show();
  155. return true;
  156. } else {
  157. alert("Sorry, there appears to be an error in your JSON input. Please fix it before continuing.");
  158. return false;
  159. }
  160. };
  161. JSONEditor.prototype.showText = function() {
  162. this.builder.hide();
  163. return this.wrapped.show();
  164. };
  165. JSONEditor.prototype.toggleBuilder = function() {
  166. if (this.builderShowing) {
  167. this.showText();
  168. return this.builderShowing = !this.builderShowing;
  169. } else {
  170. if (this.showBuilder()) {
  171. return this.builderShowing = !this.builderShowing;
  172. }
  173. }
  174. };
  175. JSONEditor.prototype.showFunctionButtons = function(insider) {
  176. var _this = this;
  177. if (!insider) {
  178. this.functionButtonsEnabled = true;
  179. }
  180. if (this.functionButtonsEnabled && !this.functionButtons) {
  181. this.functionButtons = $('<div class="function_buttons"></div>');
  182. this.functionButtons.append($('<a href="#" style="padding-right: 10px;">Undo</a>').click(function(e) {
  183. e.preventDefault();
  184. return _this.undo();
  185. }));
  186. this.functionButtons.append($('<a href="#" style="padding-right: 10px;">Redo</a>').click(function(e) {
  187. e.preventDefault();
  188. return _this.redo();
  189. }));
  190. this.functionButtons.append($('<a id="toggle_view" href="#" style="padding-right: 10px; float: right;">Toggle View</a>').click(function(e) {
  191. e.preventDefault();
  192. return _this.toggleBuilder();
  193. }));
  194. return this.container.prepend(this.functionButtons);
  195. }
  196. };
  197. JSONEditor.prototype.saveStateIfTextChanged = function() {
  198. if (JSON.stringify(this.json, null, 2) !== this.wrapped.get(0).value) {
  199. if (this.checkJsonInText()) {
  200. this.saveState(true);
  201. } else {
  202. if (confirm("The current JSON is malformed. If you continue, the current JSON will not be saved. Do you wish to continue?")) {
  203. this.historyPointer += 1;
  204. true;
  205. } else {
  206. false;
  207. }
  208. }
  209. }
  210. return true;
  211. };
  212. JSONEditor.prototype.restore = function() {
  213. if (this.history[this.historyPointer]) {
  214. this.wrapped.get(0).value = this.history[this.historyPointer];
  215. return this.rebuild(true);
  216. }
  217. };
  218. JSONEditor.prototype.saveState = function(skipStoreText) {
  219. var text;
  220. if (this.json) {
  221. if (!skipStoreText) {
  222. this.storeToText();
  223. }
  224. text = this.wrapped.get(0).value;
  225. if (this.history[this.historyPointer] !== text) {
  226. this.historyTruncate();
  227. this.history.push(text);
  228. return this.historyPointer += 1;
  229. }
  230. }
  231. };
  232. JSONEditor.prototype.fireChange = function() {
  233. return $(this.wrapped).trigger('change');
  234. };
  235. JSONEditor.prototype.historyTruncate = function() {
  236. if (this.historyPointer + 1 < this.history.length) {
  237. return this.history.splice(this.historyPointer + 1, this.history.length - this.historyPointer);
  238. }
  239. };
  240. JSONEditor.prototype.storeToText = function() {
  241. return this.wrapped.get(0).value = JSON.stringify(this.json, null, 2);
  242. };
  243. JSONEditor.prototype.getJSONText = function() {
  244. this.rebuild();
  245. return this.wrapped.get(0).value;
  246. };
  247. JSONEditor.prototype.getJSON = function() {
  248. this.rebuild();
  249. return this.json;
  250. };
  251. JSONEditor.prototype.rebuild = function(doNotRefreshText) {
  252. var changed, elem;
  253. if (!this.json) {
  254. this.setJsonFromText();
  255. }
  256. changed = this.haveThingsChanged();
  257. if (this.json && !doNotRefreshText) {
  258. this.saveState();
  259. }
  260. this.cleanBuilder();
  261. this.setJsonFromText();
  262. this.alreadyFocused = false;
  263. elem = this.build(this.json, this.builder, null, null, this.json);
  264. this.recoverScrollPosition();
  265. if (elem && elem.text() === '??' && !this.alreadyFocused && this.doAutoFocus) {
  266. this.alreadyFocused = true;
  267. this.doAutoFocus = false;
  268. elem = elem.find('.editable');
  269. elem.click();
  270. elem.find('input').focus().select();
  271. }
  272. if (changed) {
  273. return this.fireChange();
  274. }
  275. };
  276. JSONEditor.prototype.haveThingsChanged = function() {
  277. return this.json && JSON.stringify(this.json, null, 2) !== this.wrapped.get(0).value;
  278. };
  279. JSONEditor.prototype.saveScrollPosition = function() {
  280. return this.oldScrollHeight = this.builder.scrollTop();
  281. };
  282. JSONEditor.prototype.recoverScrollPosition = function() {
  283. return this.builder.scrollTop(this.oldScrollHeight);
  284. };
  285. JSONEditor.prototype.setJsonFromText = function() {
  286. if (this.wrapped.get(0).value.length === 0) {
  287. this.wrapped.get(0).value = "{}";
  288. }
  289. try {
  290. this.wrapped.get(0).value = this.wrapped.get(0).value.replace(/((^|[^\\])(\\\\)*)\\n/g, '$1\\\\n').replace(/((^|[^\\])(\\\\)*)\\t/g, '$1\\\\t');
  291. return this.json = JSON.parse(this.wrapped.get(0).value);
  292. } catch (e) {
  293. return alert("Got bad JSON from text.");
  294. }
  295. };
  296. JSONEditor.prototype.checkJsonInText = function() {
  297. try {
  298. JSON.parse(this.wrapped.get(0).value);
  299. return true;
  300. } catch (e) {
  301. return false;
  302. }
  303. };
  304. JSONEditor.prototype.logJSON = function() {
  305. return console.log(JSON.stringify(this.json, null, 2));
  306. };
  307. JSONEditor.prototype.cleanBuilder = function() {
  308. if (!this.builder) {
  309. this.builder = $('<div class="builder"></div>');
  310. this.container.append(this.builder);
  311. }
  312. this.saveScrollPosition();
  313. this.builder.text('');
  314. return this.showFunctionButtons("defined");
  315. };
  316. JSONEditor.prototype.updateStruct = function(struct, key, val, kind, selectionStart, selectionEnd) {
  317. var orderrest;
  318. if (kind === 'key') {
  319. if (selectionStart && selectionEnd) {
  320. val = key.substring(0, selectionStart) + val + key.substring(selectionEnd, key.length);
  321. }
  322. struct[val] = struct[key];
  323. orderrest = 0;
  324. $.each(struct, function(index, value) {
  325. var tempval;
  326. if (orderrest & index !== val) {
  327. tempval = struct[index];
  328. delete struct[index];
  329. struct[index] = tempval;
  330. }
  331. if (key === index) {
  332. return orderrest = 1;
  333. }
  334. });
  335. if (key !== val) {
  336. return delete struct[key];
  337. }
  338. } else {
  339. if (selectionStart && selectionEnd) {
  340. val = struct[key].substring(0, selectionStart) + val + struct[key].substring(selectionEnd, struct[key].length);
  341. }
  342. return struct[key] = val;
  343. }
  344. };
  345. JSONEditor.prototype.getValFromStruct = function(struct, key, kind) {
  346. if (kind === 'key') {
  347. return key;
  348. } else {
  349. return struct[key];
  350. }
  351. };
  352. JSONEditor.prototype.doTruncation = function(trueOrFalse) {
  353. if (this._doTruncation !== trueOrFalse) {
  354. this._doTruncation = trueOrFalse;
  355. return this.rebuild();
  356. }
  357. };
  358. JSONEditor.prototype.showWipe = function(trueOrFalse) {
  359. if (this._showWipe !== trueOrFalse) {
  360. this._showWipe = trueOrFalse;
  361. return this.rebuild();
  362. }
  363. };
  364. JSONEditor.prototype.truncate = function(text, length) {
  365. if (text.length === 0) {
  366. return '-empty-';
  367. }
  368. if (this._doTruncation && text.length > (length || 30)) {
  369. return text.substring(0, length || 30) + '...';
  370. }
  371. return text;
  372. };
  373. JSONEditor.prototype.replaceLastSelectedFieldIfRecent = function(text) {
  374. if (this.lastEditingUnfocusedTime > (new Date()).getTime() - 200) {
  375. this.setLastEditingFocus(text);
  376. return this.rebuild();
  377. }
  378. };
  379. JSONEditor.prototype.editingUnfocused = function(elem, struct, key, root, kind) {
  380. var selectionEnd, selectionStart,
  381. _this = this;
  382. selectionStart = elem != null ? elem.selectionStart : void 0;
  383. selectionEnd = elem != null ? elem.selectionEnd : void 0;
  384. this.setLastEditingFocus = function(text) {
  385. _this.updateStruct(struct, key, text, kind, selectionStart, selectionEnd);
  386. return _this.json = root;
  387. };
  388. return this.lastEditingUnfocusedTime = (new Date()).getTime();
  389. };
  390. JSONEditor.prototype.edit = function($elem, key, struct, root, kind) {
  391. var $input, blurHandler, form,
  392. _this = this;
  393. form = $("<form></form>").css('display', 'inline');
  394. $input = $("<input />");
  395. $input.val(this.getValFromStruct(struct, key, kind));
  396. $input.addClass('edit_field');
  397. blurHandler = function() {
  398. var val, _ref;
  399. val = $input.val();
  400. _this.updateStruct(struct, key, val, kind);
  401. _this.editingUnfocused($elem, struct, (_ref = kind === 'key') != null ? _ref : {
  402. val: key
  403. }, root, kind);
  404. $elem.text(_this.truncate(val));
  405. $elem.get(0).editing = false;
  406. if (key !== val) {
  407. return _this.rebuild();
  408. }
  409. };
  410. $input.blur(blurHandler);
  411. $input.keydown(function(e) {
  412. if (e.keyCode === 9 || e.keyCode === 13) {
  413. _this.doAutoFocus = true;
  414. return blurHandler();
  415. }
  416. });
  417. $(form).append($input).submit(function(e) {
  418. e.preventDefault();
  419. _this.doAutoFocus = true;
  420. return blurHandler();
  421. });
  422. $elem.html(form);
  423. return $input.focus();
  424. };
  425. JSONEditor.prototype.editable = function(text, key, struct, root, kind) {
  426. var elem, self;
  427. self = this;
  428. elem = $('<span class="editable" href="#"></span>').text(this.truncate(text)).click(function(e) {
  429. if (!this.editing) {
  430. this.editing = true;
  431. self.edit($(this), key, struct, root, kind);
  432. }
  433. return true;
  434. });
  435. return elem;
  436. };
  437. JSONEditor.prototype.build = function(json, node, parent, key, root) {
  438. var bq, elem, i, innerbq, jsonkey, jsonvalue, newElem, _i, _ref;
  439. elem = null;
  440. if (json instanceof Array) {
  441. bq = $(document.createElement("BLOCKQUOTE"));
  442. bq.append($('<div class="brackets">[</div>'));
  443. bq.prepend(this.addUI(json));
  444. if (parent) {
  445. if (this._showWipe) {
  446. bq.prepend(this.wipeUI(key, parent));
  447. }
  448. bq.prepend(this.deleteUI(key, parent));
  449. }
  450. for (i = _i = 0, _ref = json.length; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) {
  451. innerbq = $(document.createElement("BLOCKQUOTE"));
  452. newElem = this.build(json[i], innerbq, json, i, root);
  453. if (newElem && newElem.text() === "??") {
  454. elem = newElem;
  455. }
  456. bq.append(innerbq);
  457. }
  458. bq.append($('<div class="brackets">]</div>'));
  459. node.append(bq);
  460. } else if (json instanceof Object) {
  461. bq = $(document.createElement("BLOCKQUOTE"));
  462. bq.append($('<div class="bracers">{</div>'));
  463. for (jsonkey in json) {
  464. jsonvalue = json[jsonkey];
  465. innerbq = $(document.createElement("BLOCKQUOTE"));
  466. newElem = this.editable(jsonkey.toString(), jsonkey.toString(), json, root, 'key').wrap('<span class="key"></b>').parent();
  467. innerbq.append(newElem);
  468. if (newElem && newElem.text() === "??") {
  469. elem = newElem;
  470. }
  471. if (typeof jsonvalue !== 'string') {
  472. innerbq.prepend(this.braceUI(jsonkey, json));
  473. innerbq.prepend(this.bracketUI(jsonkey, json));
  474. if (this._showWipe) {
  475. innerbq.prepend(this.wipeUI(jsonkey, json));
  476. }
  477. innerbq.prepend(this.deleteUI(jsonkey, json, true));
  478. }
  479. innerbq.append($('<span class="colon">: </span>'));
  480. newElem = this.build(jsonvalue, innerbq, json, jsonkey, root);
  481. if (!elem && newElem && newElem.text() === "??") {
  482. elem = newElem;
  483. }
  484. bq.append(innerbq);
  485. }
  486. bq.prepend(this.addUI(json));
  487. if (parent) {
  488. if (this._showWipe) {
  489. bq.prepend(this.wipeUI(key, parent));
  490. }
  491. bq.prepend(this.deleteUI(key, parent));
  492. }
  493. bq.append($('<div class="bracers">}</div>'));
  494. node.append(bq);
  495. } else {
  496. elem = this.editable(json.toString(), key, parent, root, 'value').wrap('<span class="val"></span>').parent();
  497. node.append(elem);
  498. node.prepend(this.braceUI(key, parent));
  499. node.prepend(this.bracketUI(key, parent));
  500. if (parent) {
  501. if (this._showWipe) {
  502. node.prepend(this.wipeUI(key, parent));
  503. }
  504. node.prepend(this.deleteUI(key, parent));
  505. }
  506. }
  507. return elem;
  508. };
  509. return JSONEditor;
  510. })();
  511. }).call(this);