jquery.jsoneditor.js 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. // Simple yet flexible JSON editor plugin.
  2. // Turns any element into a stylable interactive JSON editor.
  3. // Copyright (c) 2013 David Durman
  4. // Licensed under the MIT license (http://www.opensource.org/licenses/mit-license.php).
  5. // Dependencies:
  6. // * jQuery
  7. // * JSON (use json2 library for browsers that do not support JSON natively)
  8. // Example:
  9. // var myjson = { any: { json: { value: 1 } } };
  10. // var opt = { change: function() { /* called on every change */ } };
  11. // /* opt.propertyElement = '<textarea>'; */ // element of the property field, <input> is default
  12. // /* opt.valueElement = '<textarea>'; */ // element of the value field, <input> is default
  13. // $('#mydiv').jsonEditor(myjson, opt);
  14. (function( $ ) {
  15. $.fn.jsonEditor = function(json, options) {
  16. options = options || {};
  17. // Make sure functions or other non-JSON data types are stripped down.
  18. json = parse(stringify(json));
  19. var K = function() {};
  20. var onchange = options.change || K;
  21. var onpropertyclick = options.propertyclick || K;
  22. return this.each(function() {
  23. JSONEditor($(this), json, onchange, onpropertyclick, options.propertyElement, options.valueElement);
  24. });
  25. };
  26. function JSONEditor(target, json, onchange, onpropertyclick, propertyElement, valueElement) {
  27. var opt = {
  28. target: target,
  29. onchange: onchange,
  30. onpropertyclick: onpropertyclick,
  31. original: json,
  32. propertyElement: propertyElement,
  33. valueElement: valueElement
  34. };
  35. construct(opt, json, opt.target);
  36. $(opt.target).on('blur focus', '.property, .value', function() {
  37. $(this).toggleClass('editing');
  38. });
  39. }
  40. function isObject(o) { return Object.prototype.toString.call(o) == '[object Object]'; }
  41. function isArray(o) { return Object.prototype.toString.call(o) == '[object Array]'; }
  42. function isBoolean(o) { return Object.prototype.toString.call(o) == '[object Boolean]'; }
  43. function isNumber(o) { return Object.prototype.toString.call(o) == '[object Number]'; }
  44. function isString(o) { return Object.prototype.toString.call(o) == '[object String]'; }
  45. var types = 'object array boolean number string null';
  46. // Feeds object `o` with `value` at `path`. If value argument is omitted,
  47. // object at `path` will be deleted from `o`.
  48. // Example:
  49. // feed({}, 'foo.bar.baz', 10); // returns { foo: { bar: { baz: 10 } } }
  50. function feed(o, path, value) {
  51. var del = arguments.length == 2;
  52. if (path.indexOf('.') > -1) {
  53. var diver = o,
  54. i = 0,
  55. parts = path.split('.');
  56. for (var len = parts.length; i < len - 1; i++) {
  57. diver = diver[parts[i]];
  58. }
  59. if (del) delete diver[parts[len - 1]];
  60. else diver[parts[len - 1]] = value;
  61. } else {
  62. if (del) delete o[path];
  63. else o[path] = value;
  64. }
  65. return o;
  66. }
  67. // Get a property by path from object o if it exists. If not, return defaultValue.
  68. // Example:
  69. // def({ foo: { bar: 5 } }, 'foo.bar', 100); // returns 5
  70. // def({ foo: { bar: 5 } }, 'foo.baz', 100); // returns 100
  71. function def(o, path, defaultValue) {
  72. path = path.split('.');
  73. var i = 0;
  74. while (i < path.length) {
  75. if ((o = o[path[i++]]) == undefined) return defaultValue;
  76. }
  77. return o;
  78. }
  79. function error(reason) { if (window.console) { console.error(reason); } }
  80. function parse(str) {
  81. var res;
  82. try { res = JSON.parse(str); }
  83. catch (e) { res = null; error('JSON parse failed.'); }
  84. return res;
  85. }
  86. function stringify(obj) {
  87. var res;
  88. try { res = JSON.stringify(obj); }
  89. catch (e) { res = 'null'; error('JSON stringify failed.'); }
  90. return res;
  91. }
  92. function addExpander(item) {
  93. if (item.children('.expander').length == 0) {
  94. var expander = $('<span>', { 'class': 'expander' });
  95. expander.bind('click', function() {
  96. var item = $(this).parent();
  97. item.toggleClass('expanded');
  98. });
  99. item.prepend(expander);
  100. }
  101. }
  102. function addListAppender(item, handler) {
  103. var appender = $('<div>', { 'class': 'item appender' }),
  104. btn = $('<button></button>', { 'class': 'property' });
  105. btn.text('Add New Value');
  106. appender.append(btn);
  107. item.append(appender);
  108. btn.click(handler);
  109. return appender;
  110. }
  111. function addNewValue(json) {
  112. if (isArray(json)) {
  113. json.push(null);
  114. return true;
  115. }
  116. if (isObject(json)) {
  117. var i = 1, newName = "newKey";
  118. while (json.hasOwnProperty(newName)) {
  119. newName = "newKey" + i;
  120. i++;
  121. }
  122. json[newName] = null;
  123. return true;
  124. }
  125. return false;
  126. }
  127. function construct(opt, json, root, path) {
  128. path = path || '';
  129. root.children('.item').remove();
  130. for (var key in json) {
  131. if (!json.hasOwnProperty(key)) continue;
  132. var item = $('<div>', { 'class': 'item', 'data-path': path }),
  133. property = $(opt.propertyElement || '<input>', { 'class': 'property' }),
  134. value = $(opt.valueElement || '<input>', { 'class': 'value' });
  135. if (isObject(json[key]) || isArray(json[key])) {
  136. addExpander(item);
  137. }
  138. item.append(property).append(value);
  139. root.append(item);
  140. property.val(key).attr('title', key);
  141. var val = stringify(json[key]);
  142. value.val(val).attr('title', val);
  143. assignType(item, json[key]);
  144. property.change(propertyChanged(opt));
  145. value.change(valueChanged(opt));
  146. property.click(propertyClicked(opt));
  147. if (isObject(json[key]) || isArray(json[key])) {
  148. construct(opt, json[key], item, (path ? path + '.' : '') + key);
  149. }
  150. }
  151. if (isObject(json) || isArray(json)) {
  152. addListAppender(root, function () {
  153. addNewValue(json);
  154. construct(opt, json, root, path);
  155. opt.onchange(parse(stringify(opt.original)));
  156. })
  157. }
  158. }
  159. function updateParents(el, opt) {
  160. $(el).parentsUntil(opt.target).each(function() {
  161. var path = $(this).data('path');
  162. path = (path ? path + '.' : path) + $(this).children('.property').val();
  163. var val = stringify(def(opt.original, path, null));
  164. $(this).children('.value').val(val).attr('title', val);
  165. });
  166. }
  167. function propertyClicked(opt) {
  168. return function() {
  169. var path = $(this).parent().data('path');
  170. var key = $(this).attr('title');
  171. var safePath = path ? path.split('.').concat([key]).join('\'][\'') : key;
  172. opt.onpropertyclick('[\'' + safePath + '\']');
  173. };
  174. }
  175. function propertyChanged(opt) {
  176. return function() {
  177. var path = $(this).parent().data('path'),
  178. val = parse($(this).next().val()),
  179. newKey = $(this).val(),
  180. oldKey = $(this).attr('title');
  181. $(this).attr('title', newKey);
  182. feed(opt.original, (path ? path + '.' : '') + oldKey);
  183. if (newKey) feed(opt.original, (path ? path + '.' : '') + newKey, val);
  184. updateParents(this, opt);
  185. if (!newKey) $(this).parent().remove();
  186. opt.onchange(parse(stringify(opt.original)));
  187. };
  188. }
  189. function valueChanged(opt) {
  190. return function() {
  191. var key = $(this).prev().val(),
  192. val = parse($(this).val() || 'null'),
  193. item = $(this).parent(),
  194. path = item.data('path');
  195. feed(opt.original, (path ? path + '.' : '') + key, val);
  196. if ((isObject(val) || isArray(val)) && !$.isEmptyObject(val)) {
  197. construct(opt, val, item, (path ? path + '.' : '') + key);
  198. addExpander(item);
  199. } else {
  200. item.find('.expander, .item').remove();
  201. }
  202. assignType(item, val);
  203. updateParents(this, opt);
  204. opt.onchange(parse(stringify(opt.original)));
  205. };
  206. }
  207. function assignType(item, val) {
  208. var className = 'null';
  209. if (isObject(val)) className = 'object';
  210. else if (isArray(val)) className = 'array';
  211. else if (isBoolean(val)) className = 'boolean';
  212. else if (isString(val)) className = 'string';
  213. else if (isNumber(val)) className = 'number';
  214. item.removeClass(types);
  215. item.addClass(className);
  216. }
  217. })( jQuery );