├── .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 |
66 | 67 |
68 | 69 |
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 |
114 |
115 |

116 | 117 |

118 |

119 | 120 |

121 |
122 | 123 |
124 | 125 | 126 |
127 |
128 |

129 | 137 |

138 | 139 |
140 |
141 |

142 | 149 |

150 | 188 |
189 | 190 |
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 | * 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 | --------------------------------------------------------------------------------