├── .gitmodules
├── CHANGELOG.md
├── LICENSE
├── README.md
├── favicon.ico
├── index.htm
├── jsonTreeViewer.js
└── libs
└── jsonTree
├── icons.svg
├── jsonTree.css
└── jsonTree.js
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "libs/app"]
2 | path = libs/app
3 | url = https://github.com/summerstyle/app.js.git
4 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ### 0.6.0
2 | * Add displaying for JSONPath of each node
3 | * Add parent nodes expanding for finded and marked node
4 | * Add nodes search by some conditions
5 | * Replace images for expand/collapse buttons with new svg icons
6 | * Add toSourceJSON method for trees and source block for viewer
7 |
8 | ### 0.5.0
9 | * Show loading json form after loading page
10 | * Add marking/unmarking for node labels by click with pressed ALT key
11 | * Change modifier key for recursive expanding (CTRL, META instead SHIFT)
12 | * Add beautiful PT Mono monospace font from google.fonts
13 | * Add sorting for object keys
14 | * Refactor names of CSS-classes
15 |
16 | ### 0.4.0
17 | * Add filter function for expanding selected child nodes of root element
18 | * Disable expanding for empty nodes
19 | * Add recursive collapsing/expanding by SHIFT+click for single node
20 | * Add '…' ('show more') button for expanding of object or array nodes
21 |
22 | ### 0.3.0
23 | * Divide jsonTree library from jsonTreeViewer
24 | * Add documentation for jsonTree library to README.md
25 | * Use JSDoc for all jsonTree library methods
26 | * Change license from GPL3 to MIT
27 |
28 | ### 0.2.0
29 | * Add app.js framework as submodule
30 | * Delegate all interface tasks to app.js
31 |
32 | ### 0.1.1
33 | * Make text of json-tree selectable
34 |
35 | ### 0.1.0
36 | * Create base of jsonTreeViewer
37 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright © 2017 Vera Lobacheva (http://iamvera.com)
4 |
5 | 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:
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | 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.
10 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # jsonTreeViewer and jsonTree library
2 |
3 | The library and the viewer released under the MIT license (LICENSE.txt).
4 |
5 | ### jsonTreeViewer
6 |
7 | A simple json formatter/viewer based on [jsonTree library](#jsontree-library) and [app.js](https://github.com/summerstyle/app.js) framework.
8 |
9 | Clone with submodules (including App.js library):
10 |
11 | ```
12 | git clone --recursive https://github.com/summerstyle/jsonTreeViewer.git
13 | ```
14 |
15 | Online: http://summerstyle.github.io/jsonTreeViewer
16 |
17 | 1. Load json: click on "load" button and load a json-string to opened form
18 | 2. Expand/collapse single nodes by click on label (recursively - by click with pressed `CTRL`/`META` key)
19 | 3. Expand/collapse all tree nodes by click on "expand all" and "collapse all" buttons
20 | 4. Mark/unmark node labels by click on label with pressed `ALT` key
21 | 5. Show JSONPath of node by click on label with pressed `SHIFT` key
22 |
23 |
24 | ### jsonTree library (JSON pretty-printer)
25 |
26 | A simple lightweight pure-javascript library for drawing tree of json-nodes from json-object.
27 | You can get json-object from json-string by `JSON.parse(str)` method.
28 |
29 | Demo: http://summerstyle.github.io/jsonTreeViewer
30 |
31 | The library includes only 2 files - [`libs/jsonTree/jsonTree.js`](https://github.com/summerstyle/jsonTreeViewer/blob/master/libs/jsonTree/jsonTree.js) (18 KB)
32 | and [`libs/jsonTree/jsonTree.css`](https://github.com/summerstyle/jsonTreeViewer/blob/master/libs/jsonTree/jsonTree.css) (2 KB).
33 |
34 | ##### How to use:
35 |
36 | html:
37 | ```html
38 |
39 |
40 | ```
41 | javascript:
42 | ```javascript
43 | // Get DOM-element for inserting json-tree
44 | var wrapper = document.getElementById("wrapper");
45 |
46 | // Get json-data by javascript-object
47 | var data = {
48 | "firstName": "Jonh",
49 | "lastName": "Smith",
50 | "phones": [
51 | "123-45-67",
52 | "987-65-43"
53 | ]
54 | };
55 |
56 | // or from a string by JSON.parse(str) method
57 | var dataStr = '{ "firstName": "Jonh", "lastName": "Smith", "phones": ["123-45-67", "987-65-43"]}';
58 | try {
59 | var data = JSON.parse(dataStr);
60 | } catch (e) {}
61 |
62 | // Create json-tree
63 | var tree = jsonTree.create(data, wrapper);
64 |
65 | // Expand all (or selected) child nodes of root (optional)
66 | tree.expand(function(node) {
67 | return node.childNodes.length < 2 || node.label === 'phoneNumbers';
68 | });
69 | ```
70 | You can create many trees on one html-page.
71 |
72 | ##### The available methods of each json tree:
73 |
74 | * `loadData(jsonObj)` - Fill new data to current json tree
75 | * `appendTo(domEl)` - Appends tree to DOM-element (or move it to new place)
76 | * `expand()` - Expands all tree nodes (objects or arrays) recursively
77 | * `expand(filterFunc)` - Expands only selected (by filter function) child nodes of root element
78 | * `collapse()` - Collapses all tree nodes (objects or arrays) recursively
79 |
--------------------------------------------------------------------------------
/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/summerstyle/jsonTreeViewer/6abfcc1202396af12e2806d129bcae0e75b37365/favicon.ico
--------------------------------------------------------------------------------
/index.htm:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | jsonTreeViewer
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
20 |
21 |
22 |
27 |
28 |
29 |
63 |
64 |
65 |
70 |
71 |
72 |
73 |
74 |
75 | 1. Load json
76 |
77 | Click on "load" button and load a json-string to opened form
78 |
79 |
80 |
81 | 2. Expand/collapse single nodes
82 |
83 | By click on properties names (recursively - by CTRL/META + click)
84 |
85 |
86 |
87 | 3. Expand/collapse all tree nodes
88 |
89 | By click on "expand all" and "collapse all" buttons
90 |
91 |
92 |
93 | 4. Mark/unmark node labels
94 |
95 | By click on label with pressed ALT key
96 |
97 |
98 |
99 | 5. Show JSONPath of node
100 |
101 | By click on label with pressed SHIFT key
102 |
103 |
104 |
109 |
110 |
111 |
112 |
113 |
124 |
125 |
126 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
226 |
227 |
228 |
--------------------------------------------------------------------------------
/jsonTreeViewer.js:
--------------------------------------------------------------------------------
1 | /*
2 | * JSON Tree Viewer
3 | * http://github.com/summerstyle/jsonTreeViewer
4 | *
5 | * Copyright 2017 Vera Lobacheva (http://iamvera.com)
6 | * Released under the MIT license (LICENSE.txt)
7 | */
8 |
9 | 'use strict';
10 |
11 | var jsonTreeViewer = (function() {
12 |
13 | /* Utilities */
14 | var utils = App.utils;
15 |
16 | var treeWrapper = document.getElementById("tree");
17 | var tree = jsonTree.create({}, treeWrapper);
18 |
19 | // Menu
20 | var menu = new App.Menu(utils.dom.id('nav'), {
21 | 'load' : function() {
22 | load_json_form.show();
23 | },
24 | 'expand' : function() {
25 | tree.expand();
26 | },
27 | 'collapse' : function() {
28 | tree.collapse();
29 | },
30 | 'source' : function() {
31 | source_json_window.print(
32 | tree.toSourceJSON('isPrettyPrinted')
33 | );
34 | },
35 | 'find_and_mark' : function() {
36 | find_nodes_form.show();
37 | },
38 | 'unmark_all' : function() {
39 | tree.unmarkAll();
40 | },
41 | 'help' : function() {
42 | help.show();
43 | }
44 | });
45 |
46 |
47 | /* Load json form */
48 | var load_json_form = new App.Window({
49 | content_el : utils.dom.id('load_json_form'),
50 | overlay : true,
51 | js_module : function(self) {
52 | var form = self.content_el,
53 | code_input = document.getElementById('code_input'),
54 | load_button = document.getElementById('load_code_button');
55 |
56 | function load(e) {
57 | jsonTreeViewer.parse(code_input.value);
58 | self.hide();
59 | code_input.value = '';
60 |
61 | e.preventDefault();
62 | }
63 |
64 | load_button.addEventListener('click', load, false);
65 | }
66 | });
67 |
68 |
69 | /* Help block */
70 | var help = new App.Window({
71 | content_el : document.getElementById('help'),
72 | overlay : true
73 | });
74 |
75 | /* Block for source JSON */
76 | var source_json_window = new App.Window({
77 | content_el : utils.dom.id('source_json'),
78 | overlay : true,
79 | js_module : function(self) {
80 | return {
81 | print: function(str) {
82 | self.content_el.innerHTML = str;
83 | self.show();
84 | }
85 | };
86 | }
87 | });
88 |
89 | /* Find nodes form */
90 | var find_nodes_form = new App.Window({
91 | content_el : utils.dom.id('find_nodes_form'),
92 | overlay : true,
93 | js_module : function(self) {
94 | var form = self.content_el,
95 | search_type_radio = {
96 | label_name : document.getElementById('nodes_search_by_label'),
97 | node_type : document.getElementById('nodes_search_by_type')
98 | },
99 | label_name_input = document.getElementById('search_by_label_name'),
100 | node_types_checkboxes = document.getElementsByName('nodes_type'),
101 | find_button = document.getElementById('find_button'),
102 | MATCHERS = {
103 | BY_LABEL_NAME : function(labelName, node) {
104 | return node.label === labelName;
105 | },
106 | BY_NODE_TYPE : function(nodeTypesArray, node) {
107 | return nodeTypesArray.indexOf(node.type) >= 0;
108 | }
109 | };
110 |
111 | function find(e) {
112 | var matcher;
113 |
114 | e.preventDefault();
115 |
116 | if (search_type_radio.label_name.checked) {
117 | var label_name_value = label_name_input.value.trim();
118 |
119 | if (!label_name_value) {
120 | return;
121 | }
122 | matcher = MATCHERS.BY_LABEL_NAME.bind(null, label_name_value);
123 | } else if (search_type_radio.node_type.checked) {
124 | var node_type_values = [];
125 |
126 | for (var i = 0, c = node_types_checkboxes.length; i < c; i++) {
127 | if (node_types_checkboxes[i].checked) {
128 | node_type_values.push(node_types_checkboxes[i].value);
129 | }
130 | }
131 |
132 | if (!node_type_values.length) {
133 | return;
134 | }
135 |
136 | matcher = MATCHERS.BY_NODE_TYPE.bind(null, node_type_values);
137 | }
138 |
139 | if (!matcher) {
140 | return;
141 | }
142 |
143 | tree.findAndHandle(matcher, function(node) {
144 | node.mark();
145 | node.expandParent('isRecursive');
146 | });
147 |
148 | self.hide();
149 | }
150 |
151 | find_button.addEventListener('click', find, false);
152 | }
153 | });
154 |
155 | load_json_form.show();
156 |
157 | return {
158 | parse : function(json_str) {
159 | var temp;
160 |
161 | try {
162 | temp = JSON.parse(json_str);
163 | } catch(e) {
164 | alert(e);
165 | }
166 |
167 | tree.loadData(temp);
168 | }
169 | };
170 | })();
171 |
--------------------------------------------------------------------------------
/libs/jsonTree/icons.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/libs/jsonTree/jsonTree.css:
--------------------------------------------------------------------------------
1 | /*
2 | * JSON Tree Viewer
3 | * http://github.com/summerstyle/jsonTreeViewer
4 | *
5 | * Copyright 2017 Vera Lobacheva (http://iamvera.com)
6 | * Released under the MIT license (LICENSE.txt)
7 | */
8 |
9 | /* Background for the tree. May use for element */
10 | .jsontree_bg {
11 | background: #FFF;
12 | }
13 |
14 | /* Styles for the container of the tree (e.g. fonts, margins etc.) */
15 | .jsontree_tree {
16 | margin-left: 30px;
17 | font-family: 'PT Mono', monospace;
18 | font-size: 14px;
19 | }
20 |
21 | /* Styles for a list of child nodes */
22 | .jsontree_child-nodes {
23 | display: none;
24 | margin-left: 35px;
25 | margin-bottom: 5px;
26 | line-height: 2;
27 | }
28 | .jsontree_node_expanded > .jsontree_value-wrapper > .jsontree_value > .jsontree_child-nodes {
29 | display: block;
30 | }
31 |
32 | /* Styles for labels */
33 | .jsontree_label-wrapper {
34 | float: left;
35 | margin-right: 8px;
36 | }
37 | .jsontree_label {
38 | font-weight: normal;
39 | vertical-align: top;
40 | color: #000;
41 | position: relative;
42 | padding: 1px;
43 | border-radius: 4px;
44 | cursor: default;
45 | }
46 | .jsontree_node_marked > .jsontree_label-wrapper > .jsontree_label {
47 | background: #fff2aa;
48 | }
49 |
50 | /* Styles for values */
51 | .jsontree_value-wrapper {
52 | display: block;
53 | overflow: hidden;
54 | }
55 | .jsontree_node_complex > .jsontree_value-wrapper {
56 | overflow: inherit;
57 | }
58 | .jsontree_value {
59 | vertical-align: top;
60 | display: inline;
61 | }
62 | .jsontree_value_null {
63 | color: #777;
64 | font-weight: bold;
65 | }
66 | .jsontree_value_string {
67 | color: #025900;
68 | font-weight: bold;
69 | }
70 | .jsontree_value_number {
71 | color: #000E59;
72 | font-weight: bold;
73 | }
74 | .jsontree_value_boolean {
75 | color: #600100;
76 | font-weight: bold;
77 | }
78 |
79 | /* Styles for active elements */
80 | .jsontree_expand-button {
81 | position: absolute;
82 | top: 3px;
83 | left: -15px;
84 | display: block;
85 | width: 11px;
86 | height: 11px;
87 | background-image: url('icons.svg');
88 | }
89 | .jsontree_node_expanded > .jsontree_label-wrapper > .jsontree_label > .jsontree_expand-button {
90 | background-position: 0 -11px;
91 | }
92 | .jsontree_show-more {
93 | cursor: pointer;
94 | }
95 | .jsontree_node_expanded > .jsontree_value-wrapper > .jsontree_value > .jsontree_show-more {
96 | display: none;
97 | }
98 | .jsontree_node_empty > .jsontree_label-wrapper > .jsontree_label > .jsontree_expand-button,
99 | .jsontree_node_empty > .jsontree_value-wrapper > .jsontree_value > .jsontree_show-more {
100 | display: none !important;
101 | }
102 | .jsontree_node_complex > .jsontree_label-wrapper > .jsontree_label {
103 | cursor: pointer;
104 | }
105 | .jsontree_node_empty > .jsontree_label-wrapper > .jsontree_label {
106 | cursor: default !important;
107 | }
108 |
--------------------------------------------------------------------------------
/libs/jsonTree/jsonTree.js:
--------------------------------------------------------------------------------
1 | /**
2 | * JSON Tree library (a part of jsonTreeViewer)
3 | * http://github.com/summerstyle/jsonTreeViewer
4 | *
5 | * Copyright 2017 Vera Lobacheva (http://iamvera.com)
6 | * Released under the MIT license (LICENSE.txt)
7 | */
8 |
9 | var jsonTree = (function() {
10 |
11 | /* ---------- Utilities ---------- */
12 | var utils = {
13 |
14 | /*
15 | * Returns js-"class" of value
16 | *
17 | * @param val {any type} - value
18 | * @returns {string} - for example, "[object Function]"
19 | */
20 | getClass : function(val) {
21 | return Object.prototype.toString.call(val);
22 | },
23 |
24 | /**
25 | * Checks for a type of value (for valid JSON data types).
26 | * In other cases - throws an exception
27 | *
28 | * @param val {any type} - the value for new node
29 | * @returns {string} ("object" | "array" | "null" | "boolean" | "number" | "string")
30 | */
31 | getType : function(val) {
32 | if (val === null) {
33 | return 'null';
34 | }
35 |
36 | switch (typeof val) {
37 | case 'number':
38 | return 'number';
39 |
40 | case 'string':
41 | return 'string';
42 |
43 | case 'boolean':
44 | return 'boolean';
45 | }
46 |
47 | switch(utils.getClass(val)) {
48 | case '[object Array]':
49 | return 'array';
50 |
51 | case '[object Object]':
52 | return 'object';
53 | }
54 |
55 | throw new Error('Bad type: ' + utils.getClass(val));
56 | },
57 |
58 | /**
59 | * Applies for each item of list some function
60 | * and checks for last element of the list
61 | *
62 | * @param obj {Object | Array} - a list or a dict with child nodes
63 | * @param func {Function} - the function for each item
64 | */
65 | forEachNode : function(obj, func) {
66 | var type = utils.getType(obj),
67 | isLast;
68 |
69 | switch (type) {
70 | case 'array':
71 | isLast = obj.length - 1;
72 |
73 | obj.forEach(function(item, i) {
74 | func(i, item, i === isLast);
75 | });
76 |
77 | break;
78 |
79 | case 'object':
80 | var keys = Object.keys(obj).sort();
81 |
82 | isLast = keys.length - 1;
83 |
84 | keys.forEach(function(item, i) {
85 | func(item, obj[item], i === isLast);
86 | });
87 |
88 | break;
89 | }
90 |
91 | },
92 |
93 | /**
94 | * Implements the kind of an inheritance by
95 | * using parent prototype and
96 | * creating intermediate constructor
97 | *
98 | * @param Child {Function} - a child constructor
99 | * @param Parent {Function} - a parent constructor
100 | */
101 | inherits : (function() {
102 | var F = function() {};
103 |
104 | return function(Child, Parent) {
105 | F.prototype = Parent.prototype;
106 | Child.prototype = new F();
107 | Child.prototype.constructor = Child;
108 | };
109 | })(),
110 |
111 | /*
112 | * Checks for a valid type of root node*
113 | *
114 | * @param {any type} jsonObj - a value for root node
115 | * @returns {boolean} - true for an object or an array, false otherwise
116 | */
117 | isValidRoot : function(jsonObj) {
118 | switch (utils.getType(jsonObj)) {
119 | case 'object':
120 | case 'array':
121 | return true;
122 | default:
123 | return false;
124 | }
125 | },
126 |
127 | /**
128 | * Extends some object
129 | */
130 | extend : function(targetObj, sourceObj) {
131 | for (var prop in sourceObj) {
132 | if (sourceObj.hasOwnProperty(prop)) {
133 | targetObj[prop] = sourceObj[prop];
134 | }
135 | }
136 | }
137 | };
138 |
139 |
140 | /* ---------- Node constructors ---------- */
141 |
142 | /**
143 | * The factory for creating nodes of defined type.
144 | *
145 | * ~~~ Node ~~~ is a structure element of an onject or an array
146 | * with own label (a key of an object or an index of an array)
147 | * and value of any json data type. The root object or array
148 | * is a node without label.
149 | * {...
150 | * [+] "label": value,
151 | * ...}
152 | *
153 | * Markup:
154 | *
155 | *
156 | *
157 | *
158 | * "label"
159 | *
160 | * :
161 | *
162 | * <(div|span) class="jsontree_value jsontree_value_(object|array|boolean|null|number|string)">
163 | * ...
164 | * (div|span)>
165 | *
166 | *
167 | * @param label {string} - key name
168 | * @param val {Object | Array | string | number | boolean | null} - a value of node
169 | * @param isLast {boolean} - true if node is last in list of siblings
170 | *
171 | * @return {Node}
172 | */
173 | function Node(label, val, isLast) {
174 | var nodeType = utils.getType(val);
175 |
176 | if (nodeType in Node.CONSTRUCTORS) {
177 | return new Node.CONSTRUCTORS[nodeType](label, val, isLast);
178 | } else {
179 | throw new Error('Bad type: ' + utils.getClass(val));
180 | }
181 | }
182 |
183 | Node.CONSTRUCTORS = {
184 | 'boolean' : NodeBoolean,
185 | 'number' : NodeNumber,
186 | 'string' : NodeString,
187 | 'null' : NodeNull,
188 | 'object' : NodeObject,
189 | 'array' : NodeArray
190 | };
191 |
192 |
193 | /*
194 | * The constructor for simple types (string, number, boolean, null)
195 | * {...
196 | * [+] "label": value,
197 | * ...}
198 | * value = string || number || boolean || null
199 | *
200 | * Markup:
201 | *
202 | *
203 | * "age"
204 | * :
205 | *
206 | * 25
207 | * ,
208 | *
209 | *
210 | * @abstract
211 | * @param label {string} - key name
212 | * @param val {string | number | boolean | null} - a value of simple types
213 | * @param isLast {boolean} - true if node is last in list of parent childNodes
214 | */
215 | function _NodeSimple(label, val, isLast) {
216 | if (this.constructor === _NodeSimple) {
217 | throw new Error('This is abstract class');
218 | }
219 |
220 | var self = this,
221 | el = document.createElement('li'),
222 | labelEl,
223 | template = function(label, val) {
224 | var str = '\
225 | \
226 | "' +
227 | label +
228 | '" : \
229 | \
230 | \
231 | ' +
232 | val +
233 | '' +
234 | (!isLast ? ',' : '') +
235 | '';
236 |
237 | return str;
238 | };
239 |
240 | self.label = label;
241 | self.isComplex = false;
242 |
243 | el.classList.add('jsontree_node');
244 | el.innerHTML = template(label, val);
245 |
246 | self.el = el;
247 |
248 | labelEl = el.querySelector('.jsontree_label');
249 |
250 | labelEl.addEventListener('click', function(e) {
251 | if (e.altKey) {
252 | self.toggleMarked();
253 | return;
254 | }
255 |
256 | if (e.shiftKey) {
257 | document.getSelection().removeAllRanges();
258 | alert(self.getJSONPath());
259 | return;
260 | }
261 | }, false);
262 | }
263 |
264 | _NodeSimple.prototype = {
265 | constructor : _NodeSimple,
266 |
267 | /**
268 | * Mark node
269 | */
270 | mark : function() {
271 | this.el.classList.add('jsontree_node_marked');
272 | },
273 |
274 | /**
275 | * Unmark node
276 | */
277 | unmark : function() {
278 | this.el.classList.remove('jsontree_node_marked');
279 | },
280 |
281 | /**
282 | * Mark or unmark node
283 | */
284 | toggleMarked : function() {
285 | this.el.classList.toggle('jsontree_node_marked');
286 | },
287 |
288 | /**
289 | * Expands parent node of this node
290 | *
291 | * @param isRecursive {boolean} - if true, expands all parent nodes
292 | * (from node to root)
293 | */
294 | expandParent : function(isRecursive) {
295 | if (!this.parent) {
296 | return;
297 | }
298 |
299 | this.parent.expand();
300 | this.parent.expandParent(isRecursive);
301 | },
302 |
303 | /**
304 | * Returns JSON-path of this
305 | *
306 | * @param isInDotNotation {boolean} - kind of notation for returned json-path
307 | * (by default, in bracket notation)
308 | * @returns {string}
309 | */
310 | getJSONPath : function(isInDotNotation) {
311 | if (this.isRoot) {
312 | return "$";
313 | }
314 |
315 | var currentPath;
316 |
317 | if (this.parent.type === 'array') {
318 | currentPath = "[" + this.label + "]";
319 | } else {
320 | currentPath = isInDotNotation ? "." + this.label : "['" + this.label + "']";
321 | }
322 |
323 | return this.parent.getJSONPath(isInDotNotation) + currentPath;
324 | }
325 | };
326 |
327 |
328 | /*
329 | * The constructor for boolean values
330 | * {...
331 | * [+] "label": boolean,
332 | * ...}
333 | * boolean = true || false
334 | *
335 | * @constructor
336 | * @param label {string} - key name
337 | * @param val {boolean} - value of boolean type, true or false
338 | * @param isLast {boolean} - true if node is last in list of parent childNodes
339 | */
340 | function NodeBoolean(label, val, isLast) {
341 | this.type = "boolean";
342 |
343 | _NodeSimple.call(this, label, val, isLast);
344 | }
345 | utils.inherits(NodeBoolean,_NodeSimple);
346 |
347 |
348 | /*
349 | * The constructor for number values
350 | * {...
351 | * [+] "label": number,
352 | * ...}
353 | * number = 123
354 | *
355 | * @constructor
356 | * @param label {string} - key name
357 | * @param val {number} - value of number type, for example 123
358 | * @param isLast {boolean} - true if node is last in list of parent childNodes
359 | */
360 | function NodeNumber(label, val, isLast) {
361 | this.type = "number";
362 |
363 | _NodeSimple.call(this, label, val, isLast);
364 | }
365 | utils.inherits(NodeNumber,_NodeSimple);
366 |
367 |
368 | /*
369 | * The constructor for string values
370 | * {...
371 | * [+] "label": string,
372 | * ...}
373 | * string = "abc"
374 | *
375 | * @constructor
376 | * @param label {string} - key name
377 | * @param val {string} - value of string type, for example "abc"
378 | * @param isLast {boolean} - true if node is last in list of parent childNodes
379 | */
380 | function NodeString(label, val, isLast) {
381 | this.type = "string";
382 |
383 | _NodeSimple.call(this, label, '"' + val + '"', isLast);
384 | }
385 | utils.inherits(NodeString,_NodeSimple);
386 |
387 |
388 | /*
389 | * The constructor for null values
390 | * {...
391 | * [+] "label": null,
392 | * ...}
393 | *
394 | * @constructor
395 | * @param label {string} - key name
396 | * @param val {null} - value (only null)
397 | * @param isLast {boolean} - true if node is last in list of parent childNodes
398 | */
399 | function NodeNull(label, val, isLast) {
400 | this.type = "null";
401 |
402 | _NodeSimple.call(this, label, val, isLast);
403 | }
404 | utils.inherits(NodeNull,_NodeSimple);
405 |
406 |
407 | /*
408 | * The constructor for complex types (object, array)
409 | * {...
410 | * [+] "label": value,
411 | * ...}
412 | * value = object || array
413 | *
414 | * Markup:
415 | *
416 | *
417 | *
418 | *
419 | * "label"
420 | *
421 | * :
422 | *
423 | *
424 | *
{
425 | *
426 | *
}
427 | * ,
428 | *
429 | *
430 | *
431 | * @abstract
432 | * @param label {string} - key name
433 | * @param val {Object | Array} - a value of complex types, object or array
434 | * @param isLast {boolean} - true if node is last in list of parent childNodes
435 | */
436 | function _NodeComplex(label, val, isLast) {
437 | if (this.constructor === _NodeComplex) {
438 | throw new Error('This is abstract class');
439 | }
440 |
441 | var self = this,
442 | el = document.createElement('li'),
443 | template = function(label, sym) {
444 | var comma = (!isLast) ? ',' : '',
445 | str = '\
446 | \
447 |
\
448 |
' + sym[0] + '\
449 |
…\
450 |
\
451 |
' + sym[1] + '' +
452 | '
' + comma +
453 | '
';
454 |
455 | if (label !== null) {
456 | str = '\
457 | \
458 | ' +
459 | '' +
460 | '"' + label +
461 | '" : \
462 | ' + str;
463 | }
464 |
465 | return str;
466 | },
467 | childNodesUl,
468 | labelEl,
469 | moreContentEl,
470 | childNodes = [];
471 |
472 | self.label = label;
473 | self.isComplex = true;
474 |
475 | el.classList.add('jsontree_node');
476 | el.classList.add('jsontree_node_complex');
477 | el.innerHTML = template(label, self.sym);
478 |
479 | childNodesUl = el.querySelector('.jsontree_child-nodes');
480 |
481 | if (label !== null) {
482 | labelEl = el.querySelector('.jsontree_label');
483 | moreContentEl = el.querySelector('.jsontree_show-more');
484 |
485 | labelEl.addEventListener('click', function(e) {
486 | if (e.altKey) {
487 | self.toggleMarked();
488 | return;
489 | }
490 |
491 | if (e.shiftKey) {
492 | document.getSelection().removeAllRanges();
493 | alert(self.getJSONPath());
494 | return;
495 | }
496 |
497 | self.toggle(e.ctrlKey || e.metaKey);
498 | }, false);
499 |
500 | moreContentEl.addEventListener('click', function(e) {
501 | self.toggle(e.ctrlKey || e.metaKey);
502 | }, false);
503 |
504 | self.isRoot = false;
505 | } else {
506 | self.isRoot = true;
507 | self.parent = null;
508 |
509 | el.classList.add('jsontree_node_expanded');
510 | }
511 |
512 | self.el = el;
513 | self.childNodes = childNodes;
514 | self.childNodesUl = childNodesUl;
515 |
516 | utils.forEachNode(val, function(label, node, isLast) {
517 | self.addChild(new Node(label, node, isLast));
518 | });
519 |
520 | self.isEmpty = !Boolean(childNodes.length);
521 | if (self.isEmpty) {
522 | el.classList.add('jsontree_node_empty');
523 | }
524 | }
525 |
526 | utils.inherits(_NodeComplex, _NodeSimple);
527 |
528 | utils.extend(_NodeComplex.prototype, {
529 | constructor : _NodeComplex,
530 |
531 | /*
532 | * Add child node to list of child nodes
533 | *
534 | * @param child {Node} - child node
535 | */
536 | addChild : function(child) {
537 | this.childNodes.push(child);
538 | this.childNodesUl.appendChild(child.el);
539 | child.parent = this;
540 | },
541 |
542 | /*
543 | * Expands this list of node child nodes
544 | *
545 | * @param isRecursive {boolean} - if true, expands all child nodes
546 | */
547 | expand : function(isRecursive){
548 | if (this.isEmpty) {
549 | return;
550 | }
551 |
552 | if (!this.isRoot) {
553 | this.el.classList.add('jsontree_node_expanded');
554 | }
555 |
556 | if (isRecursive) {
557 | this.childNodes.forEach(function(item, i) {
558 | if (item.isComplex) {
559 | item.expand(isRecursive);
560 | }
561 | });
562 | }
563 | },
564 |
565 | /*
566 | * Collapses this list of node child nodes
567 | *
568 | * @param isRecursive {boolean} - if true, collapses all child nodes
569 | */
570 | collapse : function(isRecursive) {
571 | if (this.isEmpty) {
572 | return;
573 | }
574 |
575 | if (!this.isRoot) {
576 | this.el.classList.remove('jsontree_node_expanded');
577 | }
578 |
579 | if (isRecursive) {
580 | this.childNodes.forEach(function(item, i) {
581 | if (item.isComplex) {
582 | item.collapse(isRecursive);
583 | }
584 | });
585 | }
586 | },
587 |
588 | /*
589 | * Expands collapsed or collapses expanded node
590 | *
591 | * @param {boolean} isRecursive - Expand all child nodes if this node is expanded
592 | * and collapse it otherwise
593 | */
594 | toggle : function(isRecursive) {
595 | if (this.isEmpty) {
596 | return;
597 | }
598 |
599 | this.el.classList.toggle('jsontree_node_expanded');
600 |
601 | if (isRecursive) {
602 | var isExpanded = this.el.classList.contains('jsontree_node_expanded');
603 |
604 | this.childNodes.forEach(function(item, i) {
605 | if (item.isComplex) {
606 | item[isExpanded ? 'expand' : 'collapse'](isRecursive);
607 | }
608 | });
609 | }
610 | },
611 |
612 | /**
613 | * Find child nodes that match some conditions and handle it
614 | *
615 | * @param {Function} matcher
616 | * @param {Function} handler
617 | * @param {boolean} isRecursive
618 | */
619 | findChildren : function(matcher, handler, isRecursive) {
620 | if (this.isEmpty) {
621 | return;
622 | }
623 |
624 | this.childNodes.forEach(function(item, i) {
625 | if (matcher(item)) {
626 | handler(item);
627 | }
628 |
629 | if (item.isComplex && isRecursive) {
630 | item.findChildren(matcher, handler, isRecursive);
631 | }
632 | });
633 | }
634 | });
635 |
636 |
637 | /*
638 | * The constructor for object values
639 | * {...
640 | * [+] "label": object,
641 | * ...}
642 | * object = {"abc": "def"}
643 | *
644 | * @constructor
645 | * @param label {string} - key name
646 | * @param val {Object} - value of object type, {"abc": "def"}
647 | * @param isLast {boolean} - true if node is last in list of siblings
648 | */
649 | function NodeObject(label, val, isLast) {
650 | this.sym = ['{', '}'];
651 | this.type = "object";
652 |
653 | _NodeComplex.call(this, label, val, isLast);
654 | }
655 | utils.inherits(NodeObject,_NodeComplex);
656 |
657 |
658 | /*
659 | * The constructor for array values
660 | * {...
661 | * [+] "label": array,
662 | * ...}
663 | * array = [1,2,3]
664 | *
665 | * @constructor
666 | * @param label {string} - key name
667 | * @param val {Array} - value of array type, [1,2,3]
668 | * @param isLast {boolean} - true if node is last in list of siblings
669 | */
670 | function NodeArray(label, val, isLast) {
671 | this.sym = ['[', ']'];
672 | this.type = "array";
673 |
674 | _NodeComplex.call(this, label, val, isLast);
675 | }
676 | utils.inherits(NodeArray, _NodeComplex);
677 |
678 |
679 | /* ---------- The tree constructor ---------- */
680 |
681 | /*
682 | * The constructor for json tree.
683 | * It contains only one Node (Array or Object), without property name.
684 | * CSS-styles of .tree define main tree styles like font-family,
685 | * font-size and own margins.
686 | *
687 | * Markup:
688 | *
691 | *
692 | * @constructor
693 | * @param jsonObj {Object | Array} - data for tree
694 | * @param domEl {DOMElement} - DOM-element, wrapper for tree
695 | */
696 | function Tree(jsonObj, domEl) {
697 | this.wrapper = document.createElement('ul');
698 | this.wrapper.className = 'jsontree_tree clearfix';
699 |
700 | this.rootNode = null;
701 |
702 | this.sourceJSONObj = jsonObj;
703 |
704 | this.loadData(jsonObj);
705 | this.appendTo(domEl);
706 | }
707 |
708 | Tree.prototype = {
709 | constructor : Tree,
710 |
711 | /**
712 | * Fill new data in current json tree
713 | *
714 | * @param {Object | Array} jsonObj - json-data
715 | */
716 | loadData : function(jsonObj) {
717 | if (!utils.isValidRoot(jsonObj)) {
718 | alert('The root should be an object or an array');
719 | return;
720 | }
721 |
722 | this.sourceJSONObj = jsonObj;
723 |
724 | this.rootNode = new Node(null, jsonObj, 'last');
725 | this.wrapper.innerHTML = '';
726 | this.wrapper.appendChild(this.rootNode.el);
727 | },
728 |
729 | /**
730 | * Appends tree to DOM-element (or move it to new place)
731 | *
732 | * @param {DOMElement} domEl
733 | */
734 | appendTo : function(domEl) {
735 | domEl.appendChild(this.wrapper);
736 | },
737 |
738 | /**
739 | * Expands all tree nodes (objects or arrays) recursively
740 | *
741 | * @param {Function} filterFunc - 'true' if this node should be expanded
742 | */
743 | expand : function(filterFunc) {
744 | if (this.rootNode.isComplex) {
745 | if (typeof filterFunc == 'function') {
746 | this.rootNode.childNodes.forEach(function(item, i) {
747 | if (item.isComplex && filterFunc(item)) {
748 | item.expand();
749 | }
750 | });
751 | } else {
752 | this.rootNode.expand('recursive');
753 | }
754 | }
755 | },
756 |
757 | /**
758 | * Collapses all tree nodes (objects or arrays) recursively
759 | */
760 | collapse : function() {
761 | if (typeof this.rootNode.collapse === 'function') {
762 | this.rootNode.collapse('recursive');
763 | }
764 | },
765 |
766 | /**
767 | * Returns the source json-string (pretty-printed)
768 | *
769 | * @param {boolean} isPrettyPrinted - 'true' for pretty-printed string
770 | * @returns {string} - for exemple, '{"a":2,"b":3}'
771 | */
772 | toSourceJSON : function(isPrettyPrinted) {
773 | if (!isPrettyPrinted) {
774 | return JSON.stringify(this.sourceJSONObj);
775 | }
776 |
777 | var DELIMETER = "[%^$#$%^%]",
778 | jsonStr = JSON.stringify(this.sourceJSONObj, null, DELIMETER);
779 |
780 | jsonStr = jsonStr.split("\n").join("
");
781 | jsonStr = jsonStr.split(DELIMETER).join(" ");
782 |
783 | return jsonStr;
784 | },
785 |
786 | /**
787 | * Find all nodes that match some conditions and handle it
788 | */
789 | findAndHandle : function(matcher, handler) {
790 | this.rootNode.findChildren(matcher, handler, 'isRecursive');
791 | },
792 |
793 | /**
794 | * Unmark all nodes
795 | */
796 | unmarkAll : function() {
797 | this.rootNode.findChildren(function(node) {
798 | return true;
799 | }, function(node) {
800 | node.unmark();
801 | }, 'isRecursive');
802 | }
803 | };
804 |
805 |
806 | /* ---------- Public methods ---------- */
807 | return {
808 | /**
809 | * Creates new tree by data and appends it to the DOM-element
810 | *
811 | * @param jsonObj {Object | Array} - json-data
812 | * @param domEl {DOMElement} - the wrapper element
813 | * @returns {Tree}
814 | */
815 | create : function(jsonObj, domEl) {
816 | return new Tree(jsonObj, domEl);
817 | }
818 | };
819 | })();
820 |
--------------------------------------------------------------------------------