├── LICENSE ├── README.md ├── demo ├── example.jpg └── index.html ├── tree.js ├── tree.min.js ├── treejs.css └── treejs.min.css /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Matthias Thalmann 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TreeJS 2 | 3 | TreeJS is a simple JavaScript library, to display a TreeView like in the windows filebrowser. 4 | It implements partially the Java Swing TreeModel etc. 5 | 6 | **Demo:** https://m-thalmann.github.io/treejs/demo/ 7 | 8 | ## Navigation 9 | - [Installation](#installation) 10 | - [Usage](#usage) 11 | - [Documentation](#documentation) 12 | - [TreeView](#treeview) 13 | - [TreeNode](#treenode) 14 | - [TreePath](#treepath) 15 | - [TreeUtil](#treeutil) 16 | - [TreeConfig](#treeconfig) 17 | - [Events](#events) 18 | - [Options](#options) 19 | - [Example](#example) 20 | 21 | ## Installation 22 | 1. Download the .zip-File and put it in your project-folder. 23 | 24 | 2. Add this script-tag to the head of the file 25 | ```html 26 | 27 | ``` 28 | 29 | 3. Add this link-tag to the head of the file, to include the styles 30 | ```html 31 | 32 | ``` 33 | 34 | 4. Start using the library! 35 | 36 | ## Usage 37 | ### Create new TreeView 38 | ```javascript 39 | var root = new TreeNode("root"); // Create the root-node 40 | var tree = new TreeView(root); // Create the tree 41 | ``` 42 | 43 | ### Set a container to display the tree 44 | ```javascript 45 | tree.setContainer("#container"); // Uses document.querySelector 46 | ``` 47 | or 48 | ```javascript 49 | tree.setContainer(document.getElementById("container")); 50 | ``` 51 | 52 | ### (Re-)load the tree 53 | ```javascript 54 | tree.reload(); // Always use this, when you change the TreeView or any of its nodes 55 | ``` 56 | ## Documentation 57 | ### TreeView 58 | It's the main object to display the Tree. 59 | #### Instanciating 60 | ```javascript 61 | new TreeView(root, container, options); 62 | ``` 63 | - **root** (TreeNode): The root-node of the tree. 64 | - **container** (DOM-Element/querySelector): The container for the tree to display **(optional)** 65 | - **options** (object): A object with options for the tree (see [below](#options)) **(optional)** 66 | 67 | After the instanciation, the TreeView is reloaded/rendered 68 | 69 | #### Methods 70 | ```javascript 71 | tree.setRoot(root); // Resets the root-node (TreeNode) 72 | tree.getRoot(); // Returns the root-node 73 | 74 | tree.expandAllNodes(); // Expands all nodes of the tree 75 | tree.expandPath(path); // Expands all nodes that are in the path (TreePath) 76 | tree.collapseAllNodes(); // Collapses all nodes of the tree 77 | 78 | tree.setContainer(container); // Resets the container (DOM-Element/querySelector) 79 | tree.getContainer(); // Returns the container 80 | 81 | tree.setOptions(options); // Resets the options (object) 82 | tree.changeOption(option, value); // Changes one option (string, object) 83 | tree.getOptions(); // Returns the options 84 | 85 | tree.getSelectedNodes(); // Returns all selected nodes in the tree 86 | tree.reload(); // Reloads/Renders the tree inside of the container 87 | ``` 88 | 89 | ### TreeNode 90 | It represents a node inside of a tree. You can append children to it and specify a userobject, which is used to display text on a node. This object can be a string but can also be a other object, where the toString() function is used to display the text. 91 | #### Instanciating 92 | ```javascript 93 | new TreeNode(userobject, options); 94 | ``` 95 | 96 | - **userobject** (object): This object is used to display text on the node (if not string, toString() is used) **(optional)** 97 | - **options** (object): A object with options for the node (see [below](#options)) **(optional)** 98 | 99 | #### Methods 100 | ```javascript 101 | node.addChild(node); // Adds a child to the current node and sets the parent of the node (TreeNode) 102 | node.removeChildPos(pos); // Removes the child at this position (integer) 103 | node.removeChild(node); // Removes the child from the current node, if contained (TreeNode) 104 | node.getChildren(); // Returns a array with the children of the current node 105 | node.getChildCount(); // Returns the number of children 106 | node.getIndexOfChild(node); // Returns the position of the child; -1 is returned if not found (TreeNode) 107 | 108 | node.getRoot(); // Tries to get the root node of this node 109 | 110 | node.setUserObject(userobject); // Resets the userobject (object) 111 | node.getUserObject(); // Returns the userobject 112 | 113 | node.setOptions(options); // Resets the options (object) 114 | node.changeOption(option, value); // Changes one option (string, object) 115 | node.getOptions(); // Returns the options 116 | 117 | node.isLeaf(); // Returns true, if the node doesn't have any children, else false 118 | 119 | node.setExpanded(true|false); // Sets the expanded-state of the node (if it shows its children) (boolean) 120 | node.toggleExpanded(); // Toggles the expanded-state of the node 121 | node.isExpanded(); // Returns, if the node is expanded or not 122 | 123 | node.setEnabled(true|false); // Sets the enabled-state of the node (if it is enabled) (boolean) 124 | node.toggleEnabled(); // Toggles the enabled-state of the node 125 | node.isEnabled(); // Returns, if the node is enabled or not 126 | 127 | node.setSelected(true|false); // Sets the selected-state of the node (if it is selected) (boolean) 128 | node.toggleSelected(); // Toggles the selected-state of the node 129 | node.isSelected(); // Returns, if the node is selected or not 130 | 131 | node.open(); // Triggers the "open"-event of the node 132 | 133 | node.on(event, callback); // Sets the eventlistener of the event, if the callback is specified; 134 | // if only the event is set, it returns the callback-function; if that is not 135 | // set, it returns a empty function (string, function) 136 | node.getListener(event); // Returns the callback-function for this event, if set (string) 137 | 138 | node.equals(node); // Returns if the node is equal to the parameter (TreeNode) 139 | 140 | node.toString(); // Returns the generated string from the userobject 141 | ``` 142 | 143 | ### TreePath 144 | It represents a path inside of a tree (containing all nodes that form the path). 145 | #### Instanciating 146 | ```javascript 147 | new TreePath(root, node); 148 | ``` 149 | 150 | - **root** & **node** (TreeNode): if they are both set, the method setPath(root, node) is executed **(optional)** 151 | 152 | #### Methods 153 | ```javascript 154 | path.setPath(root, node); // Generates the path between root and node (TreeNode, TreeNode) 155 | path.getPath(); // Returns the generated path as a array 156 | 157 | path.toString(); // Returns the path as a string (nodes joined with a ' - ') 158 | ``` 159 | 160 | ### TreeUtil 161 | A collection of default values and methods. Can't be instanciated. 162 | #### Variables 163 | ```javascript 164 | TreeUtil.default_leaf_icon // String, that represents the default icon for a leaf-node 165 | TreeUtil.default_parent_icon // String, that represents the default icon for a normal node (with children) 166 | TreeUtil.default_open_icon // String, that represents the default expanded-icon 167 | TreeUtil.default_close_icon // String, that represents the default collapsed-icon 168 | ``` 169 | #### Methods 170 | ```javascript 171 | TreeUtil.isDOM(object); // Returns true, if the object is a DOM-Element, else false (object) 172 | 173 | TreeUtil.getProperty(opt, o, def); // Returns the value of 'o' in the array/object opt, if it is set; 174 | // else it returns def (object, string, object) 175 | TreeUtil.expandNode(node); // Expands the node and all it's children and theirs etc. (TreeNode) 176 | TreeUtil.collapseNode(node); // Collapses the node and all it's children and theirs etc. (TreeNode) 177 | 178 | TreeUtil.getSelectedNodesForNode(n); // Returns all selected nodes inside of this node (and it's self, 179 | // if its selected) (TreeNode) 180 | ``` 181 | 182 | ### TreeConfig 183 | A collection of values, that you can change (directly inside of the file or with JavaScript for only one site). 184 | #### Variables 185 | ```javascript 186 | TreeConfig.leaf_icon // The icon for a leaf-node (default: TreeUtil.default_leaf_icon) 187 | TreeConfig.parent_icon // The icon for a normal node (default: TreeUtil.default_parent_icon) 188 | TreeConfig.open_icon // The expanded-icon (default: TreeUtil.default_open_icon) 189 | TreeConfig.close_icon // The collapsed-icon (default: TreeUtil.default_close_icon) 190 | TreeConfig.context_menu // A function that is executed when a contextmenu is opened (default: undefined) 191 | ``` 192 | 193 | ### Events 194 | It is possible to attach a event to a TreeNode: ``node.on(event, callback);`` 195 | 196 | | Event | Callback-Parameter(s) | Definition | Restriction | 197 | |-----------------|--------------------------------------|--------------------------------------------------------------------------|---------------| 198 | | click | e[click_event], node[TreeNode] | Is triggered when the node is clicked | - | 199 | | expand | node[TreeNode] | Is triggered when the node is expanded | Not-leaf only | 200 | | collapse | node[TreeNode] | Is triggered when the node is collapsed | Not-leaf only | 201 | | toggle_expanded | node[TreeNode] | Is triggered when the node is either expanded or collapsed | Not-leaf only | 202 | | open | node[TreeNode] | Is triggered when the open()-Function is executed or the leaf is clicked | Leaf only | 203 | | enable | node[TreeNode] | Is triggered when the node is enabled | - | 204 | | disable | node[TreeNode] | Is triggered when the node is disabled | - | 205 | | toggle_enabled | node[TreeNode] | Is triggered when the node is either enabled or disabled | - | 206 | | select | node[TreeNode] | Is triggered when the node is selected | - | 207 | | deselect | node[TreeNode] | Is triggered when the node is deselected | - | 208 | | toggle_selected | node[TreeNode] | Is triggered when the node is either selected or deselected | - | 209 | | contextmenu | e[contextmenu_event], node[TreeNode] | Is triggered when a contextmenu is opened on a node | - | 210 | 211 | ### Options 212 | #### for TreeView 213 | 214 | | Option | Values | Definition | 215 | |-------------|----------|-----------------------------------------------------------------------------| 216 | | leaf_icon | [string] | Sets the leaf-icon for this tree to the string (can be overwritten by node) | 217 | | parent_icon | [string] | Sets the node-icon for this tree to the string (can be overwritten by node) | 218 | | show_root | [boolean] | Sets whether the root node is shown or not | 219 | 220 | #### for TreeNode 221 | 222 | | Option | Values | Definition | 223 | |----------------|------------|----------------------------------------------------------------| 224 | | expanded | [boolean] | On creation, the node will have the expanded value set to this | 225 | | enabled | [boolean] | On creation, the node will have the enabled value set to this | 226 | | selected | [boolean] | On creation, the node will have the selected value set to this | 227 | | icon | [string] | Sets the icon for this node to the string | 228 | | allowsChildren | [boolean] | Sets if there can be added new children to this node | 229 | | forceParent | [boolean] | This node will be displayed as parent, even if it is empty | 230 | 231 | ## Example 232 | ### Code: 233 | ```javascript 234 | var root = new TreeNode("root"); 235 | var n1 = new TreeNode("1"); 236 | var n11 = new TreeNode("1.1"); 237 | var n2 = new TreeNode("2"); 238 | var n3 = new TreeNode("3"); 239 | var n31 = new TreeNode("3.1"); 240 | var n32 = new TreeNode("3.2"); 241 | var n321 = new TreeNode("3.2.1"); 242 | var n33 = new TreeNode("3.3"); 243 | 244 | root.addChild(n1); 245 | root.addChild(n2); 246 | root.addChild(n3); 247 | 248 | n1.addChild(n11); 249 | 250 | n3.addChild(n31); 251 | n3.addChild(n32); 252 | n3.addChild(n33); 253 | 254 | n32.addChild(n321); 255 | 256 | n3.setEnabled(false); 257 | 258 | var view = new TreeView(root, "#container"); 259 | ``` 260 | 261 | ### Output: 262 | 263 | ![treeJs example](demo/example.jpg) 264 | -------------------------------------------------------------------------------- /demo/example.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m-thalmann/treejs/1c8abf45078827eda2559c08582e2dd91905ce38/demo/example.jpg -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | TreeJS - Demo Page 7 | 8 | 9 | 10 | 11 | 12 | 41 | 42 | 115 | 116 | 117 |
118 |

treejs - Demo Page

119 |
120 | 121 |
122 |
123 | 124 |
125 | Add new element 126 | Remove element 127 | Expand nodes 128 | Collapse nodes 129 | Toggle show root 130 |
131 | Show selected nodes 132 | Toggle selected 133 | Toggle enabled 134 | Toggle force parent 135 |
136 | Font-Awesome icons 137 | Change custom icon 138 |
139 | 140 |
141 | All of the options used here are specified in the readme-File. 142 |
143 |
144 | 145 | 174 | 175 | 176 | -------------------------------------------------------------------------------- /tree.js: -------------------------------------------------------------------------------- 1 | /** 2 | * TreeJS is a JavaScript librarie for displaying TreeViews 3 | * on the web. 4 | * 5 | * @author Matthias Thalmann 6 | */ 7 | 8 | function TreeView(root, container, options){ 9 | var self = this; 10 | 11 | /* 12 | * Konstruktor 13 | */ 14 | if(typeof root === "undefined"){ 15 | throw new Error("Parameter 1 must be set (root)"); 16 | } 17 | 18 | if(!(root instanceof TreeNode)){ 19 | throw new Error("Parameter 1 must be of type TreeNode"); 20 | } 21 | 22 | if(container){ 23 | if(!TreeUtil.isDOM(container)){ 24 | container = document.querySelector(container); 25 | 26 | if(container instanceof Array){ 27 | container = container[0]; 28 | } 29 | 30 | if(!TreeUtil.isDOM(container)){ 31 | throw new Error("Parameter 2 must be either DOM-Object or CSS-QuerySelector (#, .)"); 32 | } 33 | } 34 | }else{ 35 | container = null; 36 | } 37 | 38 | if(!options || typeof options !== "object"){ 39 | options = {}; 40 | } 41 | 42 | /* 43 | * Methods 44 | */ 45 | this.setRoot = function(_root){ 46 | if(root instanceof TreeNode){ 47 | root = _root; 48 | } 49 | } 50 | 51 | this.getRoot = function(){ 52 | return root; 53 | } 54 | 55 | this.expandAllNodes = function(){ 56 | root.setExpanded(true); 57 | 58 | root.getChildren().forEach(function(child){ 59 | TreeUtil.expandNode(child); 60 | }); 61 | } 62 | 63 | this.expandPath = function(path){ 64 | if(!(path instanceof TreePath)){ 65 | throw new Error("Parameter 1 must be of type TreePath"); 66 | } 67 | 68 | path.getPath().forEach(function(node){ 69 | node.setExpanded(true); 70 | }); 71 | } 72 | 73 | this.collapseAllNodes = function(){ 74 | root.setExpanded(false); 75 | 76 | root.getChildren().forEach(function(child){ 77 | TreeUtil.collapseNode(child); 78 | }); 79 | } 80 | 81 | this.setContainer = function(_container){ 82 | if(TreeUtil.isDOM(_container)){ 83 | container = _container; 84 | }else{ 85 | _container = document.querySelector(_container); 86 | 87 | if(_container instanceof Array){ 88 | _container = _container[0]; 89 | } 90 | 91 | if(!TreeUtil.isDOM(_container)){ 92 | throw new Error("Parameter 1 must be either DOM-Object or CSS-QuerySelector (#, .)"); 93 | } 94 | } 95 | } 96 | 97 | this.getContainer = function(){ 98 | return container; 99 | } 100 | 101 | this.setOptions = function(_options){ 102 | if(typeof _options === "object"){ 103 | options = _options; 104 | } 105 | } 106 | 107 | this.changeOption = function(option, value){ 108 | options[option] = value; 109 | } 110 | 111 | this.getOptions = function(){ 112 | return options; 113 | } 114 | 115 | // TODO: set selected key: up down; expand right; collapse left; enter: open; 116 | this.getSelectedNodes = function(){ 117 | return TreeUtil.getSelectedNodesForNode(root); 118 | } 119 | 120 | this.reload = function(){ 121 | if(container == null){ 122 | console.warn("No container specified"); 123 | return; 124 | } 125 | 126 | container.classList.add("tj_container"); 127 | 128 | var cnt = document.createElement("ul"); 129 | 130 | if(TreeUtil.getProperty(options, "show_root", true)){ 131 | cnt.appendChild(renderNode(root)); 132 | }else{ 133 | root.getChildren().forEach(function(child){ 134 | cnt.appendChild(renderNode(child)); 135 | }); 136 | } 137 | 138 | container.innerHTML = ""; 139 | container.appendChild(cnt); 140 | } 141 | 142 | function renderNode(node){ 143 | var li_outer = document.createElement("li"); 144 | var span_desc = document.createElement("span"); 145 | span_desc.className = "tj_description"; 146 | span_desc.tj_node = node; 147 | 148 | if(!node.isEnabled()){ 149 | li_outer.setAttribute("disabled", ""); 150 | node.setExpanded(false); 151 | node.setSelected(false); 152 | } 153 | 154 | if(node.isSelected()){ 155 | span_desc.classList.add("selected"); 156 | } 157 | 158 | span_desc.addEventListener("click", function(e){ 159 | var cur_el = e.target; 160 | 161 | while(typeof cur_el.tj_node === "undefined" || cur_el.classList.contains("tj_container")){ 162 | cur_el = cur_el.parentElement; 163 | } 164 | 165 | var node_cur = cur_el.tj_node; 166 | 167 | if(typeof node_cur === "undefined"){ 168 | return; 169 | } 170 | 171 | if(node_cur.isEnabled()){ 172 | if(e.ctrlKey == false){ 173 | if(!node_cur.isLeaf()){ 174 | node_cur.toggleExpanded(); 175 | self.reload(); 176 | }else{ 177 | node_cur.open(); 178 | } 179 | 180 | node_cur.on("click")(e, node_cur); 181 | } 182 | 183 | 184 | if(e.ctrlKey == true){ 185 | node_cur.toggleSelected(); 186 | self.reload(); 187 | }else{ 188 | var rt = node_cur.getRoot(); 189 | 190 | if(rt instanceof TreeNode){ 191 | TreeUtil.getSelectedNodesForNode(rt).forEach(function(_nd){ 192 | _nd.setSelected(false); 193 | }); 194 | } 195 | node_cur.setSelected(true); 196 | 197 | self.reload(); 198 | } 199 | } 200 | }); 201 | 202 | span_desc.addEventListener("contextmenu", function(e){ 203 | var cur_el = e.target; 204 | 205 | while(typeof cur_el.tj_node === "undefined" || cur_el.classList.contains("tj_container")){ 206 | cur_el = cur_el.parentElement; 207 | } 208 | 209 | var node_cur = cur_el.tj_node; 210 | 211 | if(typeof node_cur === "undefined"){ 212 | return; 213 | } 214 | 215 | if(typeof node_cur.getListener("contextmenu") !== "undefined"){ 216 | node_cur.on("contextmenu")(e, node_cur); 217 | e.preventDefault(); 218 | }else if(typeof TreeConfig.context_menu === "function"){ 219 | TreeConfig.context_menu(e, node_cur); 220 | e.preventDefault(); 221 | } 222 | }); 223 | 224 | if(node.isLeaf() && !TreeUtil.getProperty(node.getOptions(), "forceParent", false)){ 225 | var ret = ''; 226 | var icon = TreeUtil.getProperty(node.getOptions(), "icon", ""); 227 | if(icon != ""){ 228 | ret += '' + icon + ''; 229 | }else if((icon = TreeUtil.getProperty(options, "leaf_icon", "")) != ""){ 230 | ret += '' + icon + ''; 231 | }else{ 232 | ret += '' + TreeConfig.leaf_icon + ''; 233 | } 234 | 235 | span_desc.innerHTML = ret + node.toString() + ""; 236 | span_desc.classList.add("tj_leaf"); 237 | 238 | li_outer.appendChild(span_desc); 239 | }else{ 240 | var ret = ''; 241 | if(node.isExpanded()){ 242 | ret += '' + TreeConfig.open_icon + ''; 243 | }else{ 244 | ret+= '' + TreeConfig.close_icon + ''; 245 | } 246 | 247 | var icon = TreeUtil.getProperty(node.getOptions(), "icon", ""); 248 | if(icon != ""){ 249 | ret += '' + icon + ''; 250 | }else if((icon = TreeUtil.getProperty(options, "parent_icon", "")) != ""){ 251 | ret += '' + icon + ''; 252 | }else{ 253 | ret += '' + TreeConfig.parent_icon + ''; 254 | } 255 | 256 | span_desc.innerHTML = ret + node.toString() + ''; 257 | 258 | li_outer.appendChild(span_desc); 259 | 260 | if(node.isExpanded()){ 261 | var ul_container = document.createElement("ul"); 262 | 263 | node.getChildren().forEach(function(child){ 264 | ul_container.appendChild(renderNode(child)); 265 | }); 266 | 267 | li_outer.appendChild(ul_container) 268 | } 269 | } 270 | 271 | return li_outer; 272 | } 273 | 274 | if(typeof container !== "undefined") 275 | this.reload(); 276 | } 277 | 278 | function TreeNode(userObject, options){ 279 | var children = new Array(); 280 | var self = this; 281 | var events = new Array(); 282 | 283 | var expanded = true; 284 | var enabled = true; 285 | var selected = false; 286 | 287 | /* 288 | * Konstruktor 289 | */ 290 | if(userObject){ 291 | if(typeof userObject !== "string" && typeof userObject.toString !== "function"){ 292 | throw new Error("Parameter 1 must be of type String or Object, where it must have the function toString()"); 293 | } 294 | }else{ 295 | userObject = ""; 296 | } 297 | 298 | if(!options || typeof options !== "object"){ 299 | options = {}; 300 | }else{ 301 | expanded = TreeUtil.getProperty(options, "expanded", true); 302 | enabled = TreeUtil.getProperty(options, "enabled", true); 303 | selected = TreeUtil.getProperty(options, "selected", false); 304 | } 305 | 306 | /* 307 | * Methods 308 | */ 309 | this.addChild = function(node){ 310 | if(!TreeUtil.getProperty(options, "allowsChildren", true)){ 311 | console.warn("Option allowsChildren is set to false, no child added"); 312 | return; 313 | } 314 | 315 | if(node instanceof TreeNode){ 316 | children.push(node); 317 | 318 | //Konstante hinzufügen (workaround) 319 | Object.defineProperty(node, "parent", { 320 | value: this, 321 | writable: false, 322 | enumerable: true, 323 | configurable: true 324 | }); 325 | }else{ 326 | throw new Error("Parameter 1 must be of type TreeNode"); 327 | } 328 | } 329 | 330 | this.removeChildPos = function(pos){ 331 | if(typeof children[pos] !== "undefined"){ 332 | if(typeof children[pos] !== "undefined"){ 333 | children.splice(pos, 1); 334 | } 335 | } 336 | } 337 | 338 | this.removeChild = function(node){ 339 | if(!(node instanceof TreeNode)){ 340 | throw new Error("Parameter 1 must be of type TreeNode"); 341 | } 342 | 343 | this.removeChildPos(this.getIndexOfChild(node)); 344 | } 345 | 346 | this.getChildren = function(){ 347 | return children; 348 | } 349 | 350 | this.getChildCount = function(){ 351 | return children.length; 352 | } 353 | 354 | this.getIndexOfChild = function(node){ 355 | for(var i = 0; i < children.length; i++){ 356 | if(children[i].equals(node)){ 357 | return i; 358 | } 359 | } 360 | 361 | return -1; 362 | } 363 | 364 | this.getRoot = function(){ 365 | var node = this; 366 | 367 | while(typeof node.parent !== "undefined"){ 368 | node = node.parent; 369 | } 370 | 371 | return node; 372 | } 373 | 374 | this.setUserObject = function(_userObject){ 375 | if(!(typeof _userObject === "string") || typeof _userObject.toString !== "function"){ 376 | throw new Error("Parameter 1 must be of type String or Object, where it must have the function toString()"); 377 | }else{ 378 | userObject = _userObject; 379 | } 380 | } 381 | 382 | this.getUserObject = function(){ 383 | return userObject; 384 | } 385 | 386 | this.setOptions = function(_options){ 387 | if(typeof _options === "object"){ 388 | options = _options; 389 | } 390 | } 391 | 392 | this.changeOption = function(option, value){ 393 | options[option] = value; 394 | } 395 | 396 | this.getOptions = function(){ 397 | return options; 398 | } 399 | 400 | this.isLeaf = function(){ 401 | return (children.length == 0); 402 | } 403 | 404 | this.setExpanded = function(_expanded){ 405 | if(this.isLeaf()){ 406 | return; 407 | } 408 | 409 | if(typeof _expanded === "boolean"){ 410 | if(expanded == _expanded){ 411 | return; 412 | } 413 | 414 | expanded = _expanded; 415 | 416 | if(_expanded){ 417 | this.on("expand")(this); 418 | }else{ 419 | this.on("collapse")(this); 420 | } 421 | 422 | this.on("toggle_expanded")(this); 423 | } 424 | } 425 | 426 | this.toggleExpanded = function(){ 427 | if(expanded){ 428 | this.setExpanded(false); 429 | }else{ 430 | this.setExpanded(true); 431 | } 432 | }; 433 | 434 | this.isExpanded = function(){ 435 | if(this.isLeaf()){ 436 | return true; 437 | }else{ 438 | return expanded; 439 | } 440 | } 441 | 442 | this.setEnabled = function(_enabled){ 443 | if(typeof _enabled === "boolean"){ 444 | if(enabled == _enabled){ 445 | return; 446 | } 447 | 448 | enabled = _enabled; 449 | 450 | if(_enabled){ 451 | this.on("enable")(this); 452 | }else{ 453 | this.on("disable")(this); 454 | } 455 | 456 | this.on("toggle_enabled")(this); 457 | } 458 | } 459 | 460 | this.toggleEnabled = function(){ 461 | if(enabled){ 462 | this.setEnabled(false); 463 | }else{ 464 | this.setEnabled(true); 465 | } 466 | } 467 | 468 | this.isEnabled = function(){ 469 | return enabled; 470 | } 471 | 472 | this.setSelected = function(_selected){ 473 | if(typeof _selected !== "boolean"){ 474 | return; 475 | } 476 | 477 | if(selected == _selected){ 478 | return; 479 | } 480 | 481 | selected = _selected; 482 | 483 | if(_selected){ 484 | this.on("select")(this); 485 | }else{ 486 | this.on("deselect")(this); 487 | } 488 | 489 | this.on("toggle_selected")(this); 490 | } 491 | 492 | this.toggleSelected = function(){ 493 | if(selected){ 494 | this.setSelected(false); 495 | }else{ 496 | this.setSelected(true); 497 | } 498 | } 499 | 500 | this.isSelected = function(){ 501 | return selected; 502 | } 503 | 504 | this.open = function(){ 505 | if(!this.isLeaf()){ 506 | this.on("open")(this); 507 | } 508 | } 509 | 510 | this.on = function(ev, callback){ 511 | if(typeof callback === "undefined"){ 512 | if(typeof events[ev] !== "function"){ 513 | return function(){}; 514 | }else{ 515 | return events[ev]; 516 | } 517 | } 518 | 519 | if(typeof callback !== 'function'){ 520 | throw new Error("Argument 2 must be of type function"); 521 | } 522 | 523 | events[ev] = callback; 524 | } 525 | 526 | this.getListener = function(ev){ 527 | return events[ev]; 528 | } 529 | 530 | this.equals = function(node){ 531 | if(node instanceof TreeNode){ 532 | if(node.getUserObject() == userObject){ 533 | return true; 534 | } 535 | } 536 | 537 | return false; 538 | } 539 | 540 | this.toString = function(){ 541 | if(typeof userObject === "string"){ 542 | return userObject; 543 | }else{ 544 | return userObject.toString(); 545 | } 546 | } 547 | } 548 | 549 | function TreePath(root, node){ 550 | var nodes = new Array(); 551 | 552 | this.setPath = function(root, node){ 553 | nodes = new Array(); 554 | 555 | while(typeof node !== "undefined" && !node.equals(root)){ 556 | nodes.push(node); 557 | node = node.parent; 558 | } 559 | 560 | if(node.equals(root)){ 561 | nodes.push(root); 562 | }else{ 563 | nodes = new Array(); 564 | throw new Error("Node is not contained in the tree of root"); 565 | } 566 | 567 | nodes = nodes.reverse(); 568 | 569 | return nodes; 570 | } 571 | 572 | this.getPath = function(){ 573 | return nodes; 574 | } 575 | 576 | this.toString = function(){ 577 | return nodes.join(" - "); 578 | } 579 | 580 | if(root instanceof TreeNode && node instanceof TreeNode){ 581 | this.setPath(root, node); 582 | } 583 | } 584 | 585 | /* 586 | * Util-Methods 587 | */ 588 | const TreeUtil = { 589 | default_leaf_icon: "🖹", 590 | default_parent_icon: "🗁", 591 | default_open_icon: "", 592 | default_close_icon: "", 593 | 594 | isDOM: function(obj){ 595 | try { 596 | return obj instanceof HTMLElement; 597 | } 598 | catch(e){ 599 | return (typeof obj==="object") && 600 | (obj.nodeType===1) && (typeof obj.style === "object") && 601 | (typeof obj.ownerDocument ==="object"); 602 | } 603 | }, 604 | 605 | getProperty: function(options, opt, def){ 606 | if(typeof options[opt] === "undefined"){ 607 | return def; 608 | } 609 | 610 | return options[opt]; 611 | }, 612 | 613 | expandNode: function(node){ 614 | node.setExpanded(true); 615 | 616 | if(!node.isLeaf()){ 617 | node.getChildren().forEach(function(child){ 618 | TreeUtil.expandNode(child); 619 | }); 620 | } 621 | }, 622 | 623 | collapseNode: function(node){ 624 | node.setExpanded(false); 625 | 626 | if(!node.isLeaf()){ 627 | node.getChildren().forEach(function(child){ 628 | TreeUtil.collapseNode(child); 629 | }); 630 | } 631 | }, 632 | 633 | getSelectedNodesForNode: function(node){ 634 | if(!(node instanceof TreeNode)){ 635 | throw new Error("Parameter 1 must be of type TreeNode"); 636 | } 637 | 638 | var ret = new Array(); 639 | 640 | if(node.isSelected()){ 641 | ret.push(node); 642 | } 643 | 644 | node.getChildren().forEach(function(child){ 645 | if(child.isSelected()){ 646 | if(ret.indexOf(child) == -1){ 647 | ret.push(child); 648 | } 649 | } 650 | 651 | if(!child.isLeaf()){ 652 | TreeUtil.getSelectedNodesForNode(child).forEach(function(_node){ 653 | if(ret.indexOf(_node) == -1){ 654 | ret.push(_node); 655 | } 656 | }); 657 | } 658 | }); 659 | 660 | return ret; 661 | } 662 | }; 663 | 664 | var TreeConfig = { 665 | leaf_icon: TreeUtil.default_leaf_icon, 666 | parent_icon: TreeUtil.default_parent_icon, 667 | open_icon: TreeUtil.default_open_icon, 668 | close_icon: TreeUtil.default_close_icon, 669 | context_menu: undefined 670 | }; 671 | -------------------------------------------------------------------------------- /tree.min.js: -------------------------------------------------------------------------------- 1 | function TreeView(a,b,c){function d(a){var b=document.createElement("li"),e=document.createElement("span");if(e.className="tj_description",e.tj_node=a,a.isEnabled()||(b.setAttribute("disabled",""),a.setExpanded(!1),a.setSelected(!1)),a.isSelected()&&e.classList.add("selected"),e.addEventListener("click",function(a){for(var b=a.target;"undefined"==typeof b.tj_node||b.classList.contains("tj_container");)b=b.parentElement;var c=b.tj_node;if("undefined"!=typeof c&&c.isEnabled())if(!1==a.ctrlKey&&(c.isLeaf()?c.open():(c.toggleExpanded(),f.reload()),c.on("click")(a,c)),!0==a.ctrlKey)c.toggleSelected(),f.reload();else{var d=c.getRoot();d instanceof TreeNode&&TreeUtil.getSelectedNodesForNode(d).forEach(function(a){a.setSelected(!1)}),c.setSelected(!0),f.reload()}}),e.addEventListener("contextmenu",function(a){for(var b=a.target;"undefined"==typeof b.tj_node||b.classList.contains("tj_container");)b=b.parentElement;var c=b.tj_node;"undefined"==typeof c||("undefined"==typeof c.getListener("contextmenu")?"function"==typeof TreeConfig.context_menu&&(TreeConfig.context_menu(a,c),a.preventDefault()):(c.on("contextmenu")(a,c),a.preventDefault()))}),a.isLeaf()&&!TreeUtil.getProperty(a.getOptions(),"forceParent",!1)){var g="",h=TreeUtil.getProperty(a.getOptions(),"icon","");g+=""==h?""==(h=TreeUtil.getProperty(c,"leaf_icon",""))?""+TreeConfig.leaf_icon+"":""+h+"":""+h+"",e.innerHTML=g+a.toString()+"",e.classList.add("tj_leaf"),b.appendChild(e)}else{var g="";g+=a.isExpanded()?""+TreeConfig.open_icon+"":""+TreeConfig.close_icon+"";var h=TreeUtil.getProperty(a.getOptions(),"icon","");if(g+=""==h?""==(h=TreeUtil.getProperty(c,"parent_icon",""))?""+TreeConfig.parent_icon+"":""+h+"":""+h+"",e.innerHTML=g+a.toString()+"",b.appendChild(e),a.isExpanded()){var i=document.createElement("ul");a.getChildren().forEach(function(a){i.appendChild(d(a))}),b.appendChild(i)}}return b}var f=this;if("undefined"==typeof a)throw new Error("Parameter 1 must be set (root)");if(!(a instanceof TreeNode))throw new Error("Parameter 1 must be of type TreeNode");if(!b)b=null;else if(!TreeUtil.isDOM(b)&&(b=document.querySelector(b),b instanceof Array&&(b=b[0]),!TreeUtil.isDOM(b)))throw new Error("Parameter 2 must be either DOM-Object or CSS-QuerySelector (#, .)");c&&"object"==typeof c||(c={}),this.setRoot=function(b){a instanceof TreeNode&&(a=b)},this.getRoot=function(){return a},this.expandAllNodes=function(){a.setExpanded(!0),a.getChildren().forEach(function(a){TreeUtil.expandNode(a)})},this.expandPath=function(a){if(!(a instanceof TreePath))throw new Error("Parameter 1 must be of type TreePath");a.getPath().forEach(function(a){a.setExpanded(!0)})},this.collapseAllNodes=function(){a.setExpanded(!1),a.getChildren().forEach(function(a){TreeUtil.collapseNode(a)})},this.setContainer=function(a){if(TreeUtil.isDOM(a))b=a;else if(a=document.querySelector(a),a instanceof Array&&(a=a[0]),!TreeUtil.isDOM(a))throw new Error("Parameter 1 must be either DOM-Object or CSS-QuerySelector (#, .)")},this.getContainer=function(){return b},this.setOptions=function(a){"object"==typeof a&&(c=a)},this.changeOption=function(a,b){c[a]=b},this.getOptions=function(){return c},this.getSelectedNodes=function(){return TreeUtil.getSelectedNodesForNode(a)},this.reload=function(){if(null==b)return void console.warn("No container specified");b.classList.add("tj_container");var e=document.createElement("ul");TreeUtil.getProperty(c,"show_root",!0)?e.appendChild(d(a)):a.getChildren().forEach(function(a){e.appendChild(d(a))}),b.innerHTML="",b.appendChild(e)},"undefined"!=typeof b&&this.reload()}function TreeNode(a,b){var c=[],d=this,e=[],f=!0,g=!0,h=!1;if(!a)a="";else if("string"!=typeof a&&"function"!=typeof a.toString)throw new Error("Parameter 1 must be of type String or Object, where it must have the function toString()");b&&"object"==typeof b?(f=TreeUtil.getProperty(b,"expanded",!0),g=TreeUtil.getProperty(b,"enabled",!0),h=TreeUtil.getProperty(b,"selected",!1)):b={},this.addChild=function(a){if(!TreeUtil.getProperty(b,"allowsChildren",!0))return void console.warn("Option allowsChildren is set to false, no child added");if(a instanceof TreeNode)c.push(a),Object.defineProperty(a,"parent",{value:this,writable:!1,enumerable:!0,configurable:!0});else throw new Error("Parameter 1 must be of type TreeNode")},this.removeChildPos=function(a){"undefined"!=typeof c[a]&&"undefined"!=typeof c[a]&&c.splice(a,1)},this.removeChild=function(a){if(!(a instanceof TreeNode))throw new Error("Parameter 1 must be of type TreeNode");this.removeChildPos(this.getIndexOfChild(a))},this.getChildren=function(){return c},this.getChildCount=function(){return c.length},this.getIndexOfChild=function(a){for(var b=0;b🖹",default_parent_icon:"🗁",default_open_icon:"",default_close_icon:"",isDOM:function(a){try{return a instanceof HTMLElement}catch(b){return"object"==typeof a&&1===a.nodeType&&"object"==typeof a.style&&"object"==typeof a.ownerDocument}},getProperty:function(a,b,c){return"undefined"==typeof a[b]?c:a[b]},expandNode:function(a){a.setExpanded(!0),a.isLeaf()||a.getChildren().forEach(function(a){TreeUtil.expandNode(a)})},collapseNode:function(a){a.setExpanded(!1),a.isLeaf()||a.getChildren().forEach(function(a){TreeUtil.collapseNode(a)})},getSelectedNodesForNode:function(a){if(!(a instanceof TreeNode))throw new Error("Parameter 1 must be of type TreeNode");var b=[];return a.isSelected()&&b.push(a),a.getChildren().forEach(function(a){a.isSelected()&&-1==b.indexOf(a)&&b.push(a),a.isLeaf()||TreeUtil.getSelectedNodesForNode(a).forEach(function(a){-1==b.indexOf(a)&&b.push(a)})}),b}};var TreeConfig={leaf_icon:TreeUtil.default_leaf_icon,parent_icon:TreeUtil.default_parent_icon,open_icon:TreeUtil.default_open_icon,close_icon:TreeUtil.default_close_icon,context_menu:void 0}; -------------------------------------------------------------------------------- /treejs.css: -------------------------------------------------------------------------------- 1 | .tj_container *{ 2 | position: relative; 3 | box-sizing: border-box; 4 | } 5 | 6 | .tj_container ul{ 7 | padding-left: 2em; 8 | list-style-type: none; 9 | } 10 | 11 | .tj_container > ul:first-of-type{ 12 | padding: 0; 13 | } 14 | 15 | .tj_container li span.tj_description{ 16 | cursor: pointer; 17 | padding: 2px 5px; 18 | display: block; 19 | border-radius: 2px; 20 | 21 | -webkit-touch-callout: none; 22 | -webkit-user-select: none; 23 | -khtml-user-select: none; 24 | -moz-user-select: none; 25 | -ms-user-select: none; 26 | user-select: none; 27 | 28 | text-align: left; 29 | } 30 | 31 | .tj_container li span.tj_description:hover{ 32 | background-color: #ccc; 33 | } 34 | 35 | .tj_container li span.tj_mod_icon, .tj_container li span.tj_icon{ 36 | margin-right: 0.5em; 37 | display: inline-block; 38 | } 39 | 40 | .tj_container li span.tj_mod_icon, .tj_container li span.tj_mod_icon *{ 41 | width: 1em; 42 | } 43 | 44 | .tj_container li span.tj_description.tj_leaf{ 45 | margin-left: 1.5em; 46 | } 47 | 48 | .tj_container li[disabled=""]{ 49 | color: #b5b5b5; 50 | } 51 | 52 | .tj_container li[disabled=""]:hover span.tj_description{ 53 | cursor: default; 54 | background-color: inherit; 55 | } 56 | 57 | .tj_container span.tj_description.selected{ 58 | background-color: #2b2b2b; 59 | color: #fff; 60 | } 61 | 62 | .tj_container span.tj_description.selected:hover{ 63 | background-color: #606060; 64 | } 65 | -------------------------------------------------------------------------------- /treejs.min.css: -------------------------------------------------------------------------------- 1 | .tj_container *{position:relative;box-sizing:border-box}.tj_container ul{padding-left:2em;list-style-type:none}.tj_container>ul:first-of-type{padding:0}.tj_container li span.tj_description{cursor:pointer;padding:2px 5px;display:block;border-radius:2px;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;text-align:left}.tj_container li span.tj_description:hover{background-color:#ccc}.tj_container li span.tj_mod_icon,.tj_container li span.tj_icon{margin-right:.5em;display:inline-block}.tj_container li span.tj_mod_icon,.tj_container li span.tj_mod_icon *{width:1em}.tj_container li span.tj_description.tj_leaf{margin-left:1.5em}.tj_container li[disabled=""]{color:#b5b5b5}.tj_container li[disabled=""]:hover span.tj_description{cursor:default;background-color:inherit}.tj_container span.tj_description.selected{background-color:#2b2b2b;color:#fff}.tj_container span.tj_description.selected:hover{background-color:#606060} --------------------------------------------------------------------------------