[TASK] Apply JS CGL on SvgTree files 72/54572/2
authorTymoteusz Motylewski <t.motylewski@gmail.com>
Mon, 6 Nov 2017 21:59:23 +0000 (22:59 +0100)
committerChristian Kuhn <lolli@schwarzbu.ch>
Tue, 7 Nov 2017 13:29:47 +0000 (14:29 +0100)
In order to make page tree smaller and easier to review,
this patch applies new JS CGLs on SvgTree related files.

Releases: master
Resolves: #82935
Change-Id: I5c9b8b889b21e77b4bcd7add2236eb849d204356
Reviewed-on: https://review.typo3.org/54572
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Wouter Wolters <typo3@wouterwolters.nl>
Reviewed-by: Markus Klein <markus.klein@typo3.org>
Tested-by: Markus Klein <markus.klein@typo3.org>
Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch>
Tested-by: Christian Kuhn <lolli@schwarzbu.ch>
typo3/sysext/backend/Resources/Public/JavaScript/FormEngine/Element/SelectTree.js
typo3/sysext/backend/Resources/Public/JavaScript/FormEngine/Element/SelectTreeElement.js
typo3/sysext/backend/Resources/Public/JavaScript/FormEngine/Element/SvgTree.js
typo3/sysext/backend/Resources/Public/JavaScript/FormEngine/Element/TreeToolbar.js

index 6ea3ef7..2d6fec2 100644 (file)
  * Module: TYPO3/CMS/Backend/FormEngine/Element/SelectTree
  * Logic for SelectTree
  */
  * Module: TYPO3/CMS/Backend/FormEngine/Element/SelectTree
  * Logic for SelectTree
  */
-define(['d3',
-        'TYPO3/CMS/Backend/FormEngine/Element/SvgTree',
-        'TYPO3/CMS/Backend/FormEngine'
-    ], function (d3, SvgTree) {
+define(['d3', 'TYPO3/CMS/Backend/FormEngine/Element/SvgTree', 'TYPO3/CMS/Backend/FormEngine'],
+  function (d3, SvgTree) {
     'use strict';
 
     /**
     'use strict';
 
     /**
@@ -26,8 +24,8 @@ define(['d3',
      * @exports TYPO3/CMS/Backend/FormEngine/Element/SelectTree
      */
     var SelectTree = function () {
      * @exports TYPO3/CMS/Backend/FormEngine/Element/SelectTree
      */
     var SelectTree = function () {
-        SvgTree.call(this);
-        this.settings.showCheckboxes = true;
+      SvgTree.call(this);
+      this.settings.showCheckboxes = true;
     };
 
     SelectTree.prototype = Object.create(SvgTree.prototype);
     };
 
     SelectTree.prototype = Object.create(SvgTree.prototype);
@@ -40,15 +38,16 @@ define(['d3',
      * @param {Object} settings
      */
     SelectTree.prototype.initialize = function (selector, settings) {
      * @param {Object} settings
      */
     SelectTree.prototype.initialize = function (selector, settings) {
-        if (!_super_.initialize.call(this, selector, settings)) {
-            return false;
-        }
-        this.addIcons();
-        this.dispatch.on('updateNodes.selectTree', this.updateNodes);
-        this.dispatch.on('loadDataAfter.selectTree', this.loadDataAfter);
-        this.dispatch.on('updateSvg.selectTree', this.renderCheckbox);
-        this.dispatch.on('nodeSelectedAfter.selectTree', this.nodeSelectedAfter);
-        return true;
+      if (!_super_.initialize.call(this, selector, settings)) {
+        return false;
+      }
+
+      this.addIcons();
+      this.dispatch.on('updateNodes.selectTree', this.updateNodes);
+      this.dispatch.on('loadDataAfter.selectTree', this.loadDataAfter);
+      this.dispatch.on('updateSvg.selectTree', this.renderCheckbox);
+      this.dispatch.on('nodeSelectedAfter.selectTree', this.nodeSelectedAfter);
+      return true;
     };
 
     /**
     };
 
     /**
@@ -57,23 +56,22 @@ define(['d3',
      * @param {Selection} nodeSelection
      */
     SelectTree.prototype.updateNodes = function (nodeSelection) {
      * @param {Selection} nodeSelection
      */
     SelectTree.prototype.updateNodes = function (nodeSelection) {
-        var me = this;
-        if (this.settings.showCheckboxes) {
-            nodeSelection
-                .selectAll('.tree-check use')
-                .attr('visibility', function (node) {
-                    var checked = Boolean(node.checked);
-                    if (d3.select(this).classed('icon-checked') && checked) {
-                        return 'visible';
-                    } else if (d3.select(this).classed('icon-indeterminate') && node.indeterminate && !checked) {
-                        return 'visible';
-                    } else if (d3.select(this).classed('icon-check') && !node.indeterminate && !checked) {
-                        return 'visible';
-                    } else {
-                        return 'hidden';
-                    }
-                });
-        }
+      if (this.settings.showCheckboxes) {
+        nodeSelection
+          .selectAll('.tree-check use')
+          .attr('visibility', function (node) {
+            var checked = Boolean(node.checked);
+            if (d3.select(this).classed('icon-checked') && checked) {
+              return 'visible';
+            } else if (d3.select(this).classed('icon-indeterminate') && node.indeterminate && !checked) {
+              return 'visible';
+            } else if (d3.select(this).classed('icon-check') && !node.indeterminate && !checked) {
+              return 'visible';
+            } else {
+              return 'hidden';
+            }
+          });
+      }
     };
 
     /**
     };
 
     /**
@@ -82,38 +80,41 @@ define(['d3',
      * @param {Selection} nodeSelection ENTER selection (only new DOM objects)
      */
     SelectTree.prototype.renderCheckbox = function (nodeSelection) {
      * @param {Selection} nodeSelection ENTER selection (only new DOM objects)
      */
     SelectTree.prototype.renderCheckbox = function (nodeSelection) {
-        var me = this;
-        if (this.settings.showCheckboxes) {
-            this.textPosition = 50;
-            //this can be simplified to single "use" element with changing href on click when we drop IE11 on WIN7 support
-            var g = nodeSelection.filter(function (node) {
-                    //do not render checkbox if node is not selectable
-                    return me.isNodeSelectable(node) || Boolean(node.checked);
-                })
-                .append('g')
-                .attr('class', 'tree-check')
-                .on('click', function (d) {
-                    me.selectNode(d);
-                });
-            g.append('use')
-                .attr('x', 28)
-                .attr('y', -8)
-                .attr('visibility', 'hidden')
-                .attr('class', 'icon-check')
-                .attr('xlink:href', '#icon-check');
-            g.append('use')
-                .attr('x', 28)
-                .attr('y', -8)
-                .attr('visibility', 'hidden')
-                .attr('class', 'icon-checked')
-                .attr('xlink:href', '#icon-checked');
-            g.append('use')
-                .attr('x', 28)
-                .attr('y', -8)
-                .attr('visibility', 'hidden')
-                .attr('class', 'icon-indeterminate')
-                .attr('xlink:href', '#icon-indeterminate');
-        }
+      var _this = this;
+      if (this.settings.showCheckboxes) {
+        this.textPosition = 50;
+
+        // this can be simplified to single "use" element with changing href on click
+        // when we drop IE11 on WIN7 support
+        var g = nodeSelection.filter(function (node) {
+          // do not render checkbox if node is not selectable
+          return _this.isNodeSelectable(node) || Boolean(node.checked);
+        })
+          .append('g')
+          .attr('class', 'tree-check')
+          .on('click', function (d) {
+            _this.selectNode(d);
+          });
+
+        g.append('use')
+          .attr('x', 28)
+          .attr('y', -8)
+          .attr('visibility', 'hidden')
+          .attr('class', 'icon-check')
+          .attr('xlink:href', '#icon-check');
+        g.append('use')
+          .attr('x', 28)
+          .attr('y', -8)
+          .attr('visibility', 'hidden')
+          .attr('class', 'icon-checked')
+          .attr('xlink:href', '#icon-checked');
+        g.append('use')
+          .attr('x', 28)
+          .attr('y', -8)
+          .attr('visibility', 'hidden')
+          .attr('class', 'icon-indeterminate')
+          .attr('xlink:href', '#icon-indeterminate');
+      }
     };
 
     /**
     };
 
     /**
@@ -121,48 +122,53 @@ define(['d3',
      *
      * @param {Node} node
      */
      *
      * @param {Node} node
      */
-    SelectTree.prototype.updateAncestorsIndetermineState = function (node) {
-        var me = this;
-        //foreach ancestor except node itself
-        var indeterminate = false;
-        node.parents.forEach(function (index) {
-            var n = me.nodes[index];
-            n.indeterminate = (node.checked || node.indeterminate || indeterminate);
-            // check state for the next level
-            indeterminate = (node.checked || node.indeterminate || n.checked || n.indeterminate)
-        });
-    };
+    SelectTree.prototype.updateAncestorsIndeterminateState = function (node) {
+      var _this = this;
 
 
+      //foreach ancestor except node itself
+      var indeterminate = false;
+      node.parents.forEach(function (index) {
+        var n = _this.nodes[index];
+        n.indeterminate = (node.checked || node.indeterminate || indeterminate);
+
+        // check state for the next level
+        indeterminate = (node.checked || node.indeterminate || n.checked || n.indeterminate);
+      });
+    };
 
     /**
 
     /**
-     * Resets the node.indeterminate for the whole tree
-     * It's done once after loading data. Later indeterminate state is updated just for the subset of nodes
+     * Resets the node.indeterminate for the whole tree.
+     * It's done once after loading data.
+     * Later indeterminate state is updated just for the subset of nodes
      */
     SelectTree.prototype.loadDataAfter = function () {
      */
     SelectTree.prototype.loadDataAfter = function () {
-        this.nodes.forEach(function (node) {
-            node.indeterminate = false;
-        });
-        this.calculateIndeterminate(this.nodes);
-        // Initialise "value" attribute of input field after load and revalidate form engine fields
-        this.saveCheckboxes(this.nodes);
-        if (typeof TYPO3.FormEngine.Validation !== 'undefined' && typeof TYPO3.FormEngine.Validation.validate === 'function') {
-            TYPO3.FormEngine.Validation.validate();
-        }
+      this.nodes.forEach(function (node) {
+        node.indeterminate = false;
+      });
+
+      this.calculateIndeterminate(this.nodes);
+
+      // Initialise "value" attribute of input field after load and revalidate form engine fields
+      this.saveCheckboxes(this.nodes);
+      if (typeof TYPO3.FormEngine.Validation !== 'undefined' && typeof TYPO3.FormEngine.Validation.validate === 'function') {
+        TYPO3.FormEngine.Validation.validate();
+      }
     };
 
     /**
     };
 
     /**
-     * Sets indeterminate state for a subtree. It relays on the tree to have indeterminate state reset beforehand.
+     * Sets indeterminate state for a subtree.
+     * It relays on the tree to have indeterminate state reset beforehand.
      *
      * @param {Array} nodes
      */
     SelectTree.prototype.calculateIndeterminate = function (nodes) {
      *
      * @param {Array} nodes
      */
     SelectTree.prototype.calculateIndeterminate = function (nodes) {
-        nodes.forEach(function(node) {
-            if ((node.checked || node.indeterminate) && node.parents && node.parents.length > 0) {
-                node.parents.forEach(function(parentNodeIndex) {
-                    nodes[parentNodeIndex].indeterminate = true;
-                })
-            }
-        })
+      nodes.forEach(function (node) {
+        if ((node.checked || node.indeterminate) && node.parents && node.parents.length > 0) {
+          node.parents.forEach(function (parentNodeIndex) {
+            nodes[parentNodeIndex].indeterminate = true;
+          });
+        }
+      });
     };
 
     /**
     };
 
     /**
@@ -171,24 +177,23 @@ define(['d3',
      * @param {Node} node
      */
     SelectTree.prototype.nodeSelectedAfter = function (node) {
      * @param {Node} node
      */
     SelectTree.prototype.nodeSelectedAfter = function (node) {
-        this.updateAncestorsIndetermineState(node);
-        // check all nodes again, to ensure correct display of indeterminate state
-        this.calculateIndeterminate(this.nodes);
-        this.saveCheckboxes(node);
+      this.updateAncestorsIndeterminateState(node);
+
+      // check all nodes again, to ensure correct display of indeterminate state
+      this.calculateIndeterminate(this.nodes);
+      this.saveCheckboxes();
     };
 
     /**
      * Sets a comma-separated list of selected nodes identifiers to configured input
     };
 
     /**
      * Sets a comma-separated list of selected nodes identifiers to configured input
-     *
-     * @param {Node} node
      */
      */
-    SelectTree.prototype.saveCheckboxes = function (node) {
-        if (typeof this.settings.input !== 'undefined') {
-            var selectedNodes = this.getSelectedNodes();
-            this.settings.input.val(selectedNodes.map(function (d) {
-                    return d.identifier
-            }));
-        }
+    SelectTree.prototype.saveCheckboxes = function () {
+      if (typeof this.settings.input !== 'undefined') {
+        var selectedNodes = this.getSelectedNodes();
+        this.settings.input.val(selectedNodes.map(function (d) {
+          return d.identifier;
+        }));
+      }
     };
 
     /**
     };
 
     /**
@@ -196,22 +201,22 @@ define(['d3',
      */
     SelectTree.prototype.addIcons = function () {
 
      */
     SelectTree.prototype.addIcons = function () {
 
-        this.data.icons = {
-            'check': {
-                identifier: 'check',
-                icon: '<g width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">' +
-                '<rect height="16" width="16" fill="transparent"></rect><path transform="scale(0.01)" d="M1312 256h-832q-66 0-113 47t-47 113v832q0 66 47 113t113 47h832q66 0 113-47t47-113v-832q0-66-47-113t-113-47zm288 160v832q0 119-84.5 203.5t-203.5 84.5h-832q-119 0-203.5-84.5t-84.5-203.5v-832q0-119 84.5-203.5t203.5-84.5h832q119 0 203.5 84.5t84.5 203.5z"></path></g>'
-            },
-            'checked': {
-                identifier: 'checked',
-                icon: '<g width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><rect height="16" width="16" fill="transparent"></rect><path transform="scale(0.01)" d="M813 1299l614-614q19-19 19-45t-19-45l-102-102q-19-19-45-19t-45 19l-467 467-211-211q-19-19-45-19t-45 19l-102 102q-19 19-19 45t19 45l358 358q19 19 45 19t45-19zm851-883v960q0 119-84.5 203.5t-203.5 84.5h-960q-119 0-203.5-84.5t-84.5-203.5v-960q0-119 84.5-203.5t203.5-84.5h960q119 0 203.5 84.5t84.5 203.5z"></path></g>'
-            },
-            'indeterminate': {
-                identifier: 'indeterminate',
-                icon: '<g width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><rect height="16" width="16" fill="transparent"></rect><path transform="scale(0.01)" d="M1344 800v64q0 14-9 23t-23 9h-832q-14 0-23-9t-9-23v-64q0-14 9-23t23-9h832q14 0 23 9t9 23zm128 448v-832q0-66-47-113t-113-47h-832q-66 0-113 47t-47 113v832q0 66 47 113t113 47h832q66 0 113-47t47-113zm128-832v832q0 119-84.5 203.5t-203.5 84.5h-832q-119 0-203.5-84.5t-84.5-203.5v-832q0-119 84.5-203.5t203.5-84.5h832q119 0 203.5 84.5t84.5 203.5z"></path></g>'
-            }
-        };
+      this.data.icons = {
+        check: {
+          identifier: 'check',
+          icon: '<g width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">' +
+          '<rect height="16" width="16" fill="transparent"></rect><path transform="scale(0.01)" d="M1312 256h-832q-66 0-113 47t-47 113v832q0 66 47 113t113 47h832q66 0 113-47t47-113v-832q0-66-47-113t-113-47zm288 160v832q0 119-84.5 203.5t-203.5 84.5h-832q-119 0-203.5-84.5t-84.5-203.5v-832q0-119 84.5-203.5t203.5-84.5h832q119 0 203.5 84.5t84.5 203.5z"></path></g>',
+        },
+        checked: {
+          identifier: 'checked',
+          icon: '<g width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><rect height="16" width="16" fill="transparent"></rect><path transform="scale(0.01)" d="M813 1299l614-614q19-19 19-45t-19-45l-102-102q-19-19-45-19t-45 19l-467 467-211-211q-19-19-45-19t-45 19l-102 102q-19 19-19 45t19 45l358 358q19 19 45 19t45-19zm851-883v960q0 119-84.5 203.5t-203.5 84.5h-960q-119 0-203.5-84.5t-84.5-203.5v-960q0-119 84.5-203.5t203.5-84.5h960q119 0 203.5 84.5t84.5 203.5z"></path></g>',
+        },
+        indeterminate: {
+          identifier: 'indeterminate',
+          icon: '<g width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><rect height="16" width="16" fill="transparent"></rect><path transform="scale(0.01)" d="M1344 800v64q0 14-9 23t-23 9h-832q-14 0-23-9t-9-23v-64q0-14 9-23t23-9h832q14 0 23 9t9 23zm128 448v-832q0-66-47-113t-113-47h-832q-66 0-113 47t-47 113v832q0 66 47 113t113 47h832q66 0 113-47t47-113zm128-832v832q0 119-84.5 203.5t-203.5 84.5h-832q-119 0-203.5-84.5t-84.5-203.5v-832q0-119 84.5-203.5t203.5-84.5h832q119 0 203.5 84.5t84.5 203.5z"></path></g>',
+        },
+      };
     };
 
     return SelectTree;
     };
 
     return SelectTree;
-});
+  });
index 267fced..d400937 100644 (file)
 /**
  * Initialization of the selectTree js component used e.g. for category tree rendering
  */
 /**
  * Initialization of the selectTree js component used e.g. for category tree rendering
  */
-define(['jquery', 'TYPO3/CMS/Backend/FormEngine/Element/SelectTree'], function ($, SelectTree) {
+define(['jquery', 'TYPO3/CMS/Backend/FormEngine/Element/SelectTree'],
+  function ($, SelectTree) {
     'use strict';
 
     var SelectTreeElement = {};
 
     SelectTreeElement.initialize = function () {
     'use strict';
 
     var SelectTreeElement = {};
 
     SelectTreeElement.initialize = function () {
-        $(document).ready(function() {
-            $('.typo3-tceforms-tree .treeRecord').each(function (i, element) {
-    
-                /**
-                 * Hidden input field storing selected elements.
-                 * Tree is initialized based on values stored in it's data attributes
-                 *
-                 * @type {*|jQuery|HTMLElement}
-                 */
-                var treeInput = $(element);
-                var dataParams = {
-                    tableName: treeInput.data('tablename'),
-                    fieldName: treeInput.data('fieldname'),
-                    uid: treeInput.data('uid'),
-                    recordTypeValue: treeInput.data('recordtypevalue'),
-                    dataStructureIdentifier: treeInput.data('datastructureidentifier'),
-                    flexFormSheetName: treeInput.data('flexformsheetname'),
-                    flexFormFieldName: treeInput.data('flexformfieldname'),
-                    flexFormContainerName: treeInput.data('flexformcontainername'),
-                    flexFormContainerIdentifier: treeInput.data('flexformcontaineridentifier'),
-                    flexFormContainerFieldName: treeInput.data('flexformcontainerfieldname'),
-                    flexFormSectionContainerIsNew: treeInput.data('flexformsectioncontainerisnew'),
-                    command: treeInput.data('command')
-                };
-                var $wrapper = treeInput.parent().siblings('.svg-tree-wrapper');
-                var dataUrl = TYPO3.settings.ajaxUrls['record_tree_data'] + '&' + $.param(dataParams);
-                var tree = new SelectTree();
-                var initialized = tree.initialize($wrapper, {
-                    'dataUrl': dataUrl,
-                    'showIcons': true,
-                    'showCheckboxes': true,
-                    'readOnlyMode': treeInput.data('read-only'),
-                    'input': treeInput,
-                    'exclusiveNodesIdentifiers': treeInput.data('tree-exclusive-keys'),
-                    'validation': treeInput.data('formengine-validation-rules')[0],
-                    'expandUpToLevel': treeInput.data('tree-expand-up-to-level')
-                });
-                if (!initialized) {
-                    return;
-                }
-                tree.dispatch.on('nodeSelectedAfter.requestUpdate', window[$wrapper.attr('id')]);
-    
-                if (treeInput.data('tree-show-toolbar')) {
-                    require(['TYPO3/CMS/Backend/FormEngine/Element/TreeToolbar'], function (TreeToolbar) {
-                        var selectTreeToolbar = new TreeToolbar();
-                        selectTreeToolbar.initialize($wrapper);
-                    });
-                }
+      $(document).ready(function () {
+        $('.typo3-tceforms-tree .treeRecord').each(function (i, element) {
+
+          /**
+           * Hidden input field storing selected elements.
+           * Tree is initialized based on values stored in it's data attributes
+           *
+           * @type {*|jQuery|HTMLElement}
+           */
+          var $treeInput = $(element);
+          var dataParams = {
+            tableName: $treeInput.data('tablename'),
+            fieldName: $treeInput.data('fieldname'),
+            uid: $treeInput.data('uid'),
+            recordTypeValue: $treeInput.data('recordtypevalue'),
+            dataStructureIdentifier: $treeInput.data('datastructureidentifier'),
+            flexFormSheetName: $treeInput.data('flexformsheetname'),
+            flexFormFieldName: $treeInput.data('flexformfieldname'),
+            flexFormContainerName: $treeInput.data('flexformcontainername'),
+            flexFormContainerIdentifier: $treeInput.data('flexformcontaineridentifier'),
+            flexFormContainerFieldName: $treeInput.data('flexformcontainerfieldname'),
+            flexFormSectionContainerIsNew: $treeInput.data('flexformsectioncontainerisnew'),
+            command: $treeInput.data('command'),
+          };
+          var $wrapper = $treeInput.parent().siblings('.svg-tree-wrapper');
+          var dataUrl = TYPO3.settings.ajaxUrls.record_tree_data + '&' + $.param(dataParams);
+          var tree = new SelectTree();
+          var initialized = tree.initialize($wrapper, {
+            dataUrl: dataUrl,
+            showIcons: true,
+            showCheckboxes: true,
+            readOnlyMode: $treeInput.data('read-only'),
+            input: $treeInput,
+            exclusiveNodesIdentifiers: $treeInput.data('tree-exclusive-keys'),
+            validation: $treeInput.data('formengine-validation-rules')[0],
+            expandUpToLevel: $treeInput.data('tree-expand-up-to-level'),
+          });
+          if (!initialized) {
+            return;
+          }
+
+          tree.dispatch.on('nodeSelectedAfter.requestUpdate', window[$wrapper.attr('id')]);
+
+          if ($treeInput.data('tree-show-toolbar')) {
+            require(['TYPO3/CMS/Backend/FormEngine/Element/TreeToolbar'], function (TreeToolbar) {
+              var selectTreeToolbar = new TreeToolbar();
+              selectTreeToolbar.initialize($wrapper);
             });
             });
+          }
         });
         });
+      });
     };
     };
+
     return SelectTreeElement;
     return SelectTreeElement;
-});
\ No newline at end of file
+  });
index 401710c..a22222a 100644 (file)
@@ -14,7 +14,8 @@
 /**
  * Module: TYPO3/CMS/Backend/FormEngine/Element/SvgTree
  */
 /**
  * Module: TYPO3/CMS/Backend/FormEngine/Element/SvgTree
  */
-define(['jquery', 'd3', 'TYPO3/CMS/Backend/Icons'], function ($, d3, Icons) {
+define(['jquery', 'd3', 'TYPO3/CMS/Backend/Icons'],
+  function ($, d3, Icons) {
     'use strict';
 
     /**
     'use strict';
 
     /**
@@ -22,659 +23,675 @@ define(['jquery', 'd3', 'TYPO3/CMS/Backend/Icons'], function ($, d3, Icons) {
      * @exports SvgTree
      */
     var SvgTree = function () {
      * @exports SvgTree
      */
     var SvgTree = function () {
-        this.settings = {
-            showCheckboxes: false,
-            showIcons: false,
-            nodeHeight: 20,
-            indentWidth: 16,
-            duration: 400,
-            dataUrl: 'tree-configuration.json',
-            validation: {
-                maxItems: Number.MAX_VALUE
-            },
-            unselectableElements: [],
-            expandUpToLevel: null,
-            readOnlyMode: false,
-            /**
-             * List node identifiers which can not be selected together with any other node
-             */
-            exclusiveNodesIdentifiers : ''
-        };
-
-        /**
-         * Root <svg> element
-         *
-         * @type {Selection}
-         */
-        this.svg = null;
-
-        /**
-         * SVG <g> container wrapping all .node elements
-         *
-         * @type {Selection}
-         */
-        this.nodesContainer = null;
-
-        /**
-         * SVG <defs> container wrapping all icon definitions
-         *
-         * @type {Selection}
-         */
-        this.iconsContainer = null;
-
-        /**
-         * SVG <g> container wrapping all links (lines between parent and child)
-         *
-         * @type {Selection}
-         */
-        this.linksContainer = null;
-
-        /**
-         *
-         * @type {{nodes: Node[], links: Object, icons: Object}}
-         */
-        this.data = {};
-
-        /**
-         * D3 event dispatcher
-         *
-         * @type {Object}
-         */
-        this.dispatch = null;
-
-        /**
-         * jQuery object of wrapper holding the SVG
-         * Height of this wrapper is important (we only render as many nodes as fit in the wrapper
-         *
-         * @type {jQuery}
-         */
-        this.wrapper = null;
-        this.viewportHeight = 0;
-        this.scrollTop = 0;
-        this.scrollBottom = 0;
-        this.position = 0;
-
-        /**
-         * Exclusive node which is currently selected
-         *
-         * @type {Node}
-         */
-        this.exclusiveSelectedNode = null;
+      this.settings = {
+        showCheckboxes: false,
+        showIcons: false,
+        nodeHeight: 20,
+        indentWidth: 16,
+        duration: 400,
+        dataUrl: 'tree-configuration.json',
+        validation: {
+          maxItems: Number.MAX_VALUE,
+        },
+        unselectableElements: [],
+        expandUpToLevel: null,
+        readOnlyMode: false,
+        /**
+         * List node identifiers which can not be selected together with any other node
+         */
+        exclusiveNodesIdentifiers: '',
+      };
+
+      /**
+       * Root <svg> element
+       *
+       * @type {Selection}
+       */
+      this.svg = null;
+
+      /**
+       * SVG <g> container wrapping all .node elements
+       *
+       * @type {Selection}
+       */
+      this.nodesContainer = null;
+
+      /**
+       * SVG <defs> container wrapping all icon definitions
+       *
+       * @type {Selection}
+       */
+      this.iconsContainer = null;
+
+      /**
+       * SVG <g> container wrapping all links (lines between parent and child)
+       *
+       * @type {Selection}
+       */
+      this.linksContainer = null;
+
+      /**
+       *
+       * @type {{nodes: Node[], links: Object, icons: Object}}
+       */
+      this.data = {};
+
+      /**
+       * D3 event dispatcher
+       *
+       * @type {Object}
+       */
+      this.dispatch = null;
+
+      /**
+       * jQuery object of wrapper holding the SVG
+       * Height of this wrapper is important (we only render as many nodes as fit in the wrapper
+       *
+       * @type {jQuery}
+       */
+      this.wrapper = null;
+      this.viewportHeight = 0;
+      this.scrollTop = 0;
+      this.scrollBottom = 0;
+      this.position = 0;
+
+      /**
+       * Exclusive node which is currently selected
+       *
+       * @type {Node}
+       */
+      this.exclusiveSelectedNode = null;
     };
 
     SvgTree.prototype = {
     };
 
     SvgTree.prototype = {
-        constructor: SvgTree,
-
-        /**
-         * Initializes the tree component - created basic markup, loads and renders data
-         *
-         * @param {String} selector
-         * @param {Object} settings
-         */
-        initialize: function (selector, settings) {
-            var $wrapper = $(selector);
-            // Do nothing if already initialized
-            if ($wrapper.data('svgtree-initialized')) {
-                return false;
-            }
-
-            $.extend(this.settings, settings);
-            var me = this;
-            this.wrapper = $wrapper;
-            this.dispatch = d3.dispatch('updateNodes', 'updateSvg', 'loadDataAfter', 'prepareLoadedNode', 'nodeSelectedAfter');
-            this.svg = d3
-                .select($wrapper[0])
-                .append('svg')
-                .attr('version', '1.1')
-                .attr('width', '100%');
-            var container = this.svg
-                .append('g')
-                .attr('transform', 'translate(' + (this.settings.indentWidth / 2) + ',' + (this.settings.nodeHeight / 2) + ')');
-            this.linksContainer = container.append('g')
-                .attr('class', 'links');
-            this.nodesContainer = container.append('g')
-                .attr('class', 'nodes');
-            if (this.settings.showIcons) {
-                this.iconsContainer = this.svg.append('defs');
-                this.data.icons = {};
-            }
-
-            this.updateScrollPosition();
-            this.loadData();
-
-            this.wrapper.on('resize scroll', function () {
-                me.updateScrollPosition();
-                me.update();
-            });
-            this.wrapper.data('svgtree', this);
-            this.wrapper.data('svgtree-initialized', true);
-            this.wrapper.trigger('svgTree.initialized');
-            return true;
-        },
-
-        /**
-         * Updates variables used for visible nodes calculation
-         */
-        updateScrollPosition: function () {
-            this.viewportHeight = this.wrapper.height();
-            this.scrollTop = this.wrapper.scrollTop();
-            this.scrollBottom = this.scrollTop + this.viewportHeight + (this.viewportHeight / 2);
-        },
-
-        /**
-         * Loads tree data (json) from configured url
-         */
-        loadData: function () {
-            var me = this;
-            d3.json(this.settings.dataUrl, function (error, json) {
-                if (error) throw error;
-                var nodes = Array.isArray(json) ? json : [];
-                nodes = nodes.map(function (node, index) {
-                    node.open = (me.settings.expandUpToLevel !== null) ? node.depth < me.settings.expandUpToLevel : Boolean(node.expanded);
-                    node.parents = [];
-                    node._isDragged = false;
-                    if (node.depth > 0) {
-                        var currentDepth = node.depth;
-                        for (var i = index; i >= 0; i--) {
-                            var currentNode = nodes[i];
-                            if (currentNode.depth < currentDepth) {
-                                node.parents.push(i);
-                                currentDepth = currentNode.depth;
-                            }
-                        }
-                    }
-                    if (typeof node.checked == 'undefined') {
-                        node.checked = false;
-                        me.settings.unselectableElements.push(node.identifier);
-                    }
-                    //dispatch event
-                    me.dispatch.call('prepareLoadedNode', me, node);
-                    return node;
-                });
-
-                me.nodes = nodes;
-                me.dispatch.call('loadDataAfter', me);
-                me.prepareDataForVisibleNodes();
-                me.update();
-            });
-        },
-
-        /**
-         * Filters out invisible nodes (collapsed) from the full dataset (this.rootNode)
-         * and enriches dataset with additional properties
-         * Visible dataset is stored in this.data
-         */
-        prepareDataForVisibleNodes: function () {
-            var me = this;
-
-            var blacklist = {};
-            this.nodes.map(function (node, index) {
-                if (!node.open) {
-                    blacklist[index] = true;
-                }
-            });
-
-            this.data.nodes = this.nodes.filter(function (node) {
-                return node.hidden != true && !node.parents.some(function (index) {
-                    return Boolean(blacklist[index]);
-                });
-            });
+      constructor: SvgTree,
+
+      /**
+       * Initializes the tree component - created basic markup, loads and renders data
+       *
+       * @param {String} selector
+       * @param {Object} settings
+       */
+      initialize: function (selector, settings) {
+        var $wrapper = $(selector);
+
+        // Do nothing if already initialized
+        if ($wrapper.data('svgtree-initialized')) {
+          return false;
+        }
 
 
-            this.data.links = [];
-            this.data.nodes.forEach(function (n, i) {
-                //delete n.children;
-                n.x = n.depth * me.settings.indentWidth;
-                n.y = i * me.settings.nodeHeight;
-                if (n.parents[0] !== undefined) {
-                    me.data.links.push({
-                        source: me.nodes[n.parents[0]],
-                        target: n
-                    });
-                }
+        $.extend(this.settings, settings);
+        var _this = this;
+        this.wrapper = $wrapper;
+        this.dispatch = d3.dispatch('updateNodes', 'updateSvg', 'loadDataAfter', 'prepareLoadedNode', 'nodeSelectedAfter');
+        this.svg = d3
+          .select($wrapper[0])
+          .append('svg')
+          .attr('version', '1.1')
+          .attr('width', '100%');
+        var container = this.svg
+          .append('g')
+          .attr('transform', 'translate(' + (this.settings.indentWidth / 2) + ',' + (this.settings.nodeHeight / 2) + ')');
+        this.linksContainer = container.append('g')
+          .attr('class', 'links');
+        this.nodesContainer = container.append('g')
+          .attr('class', 'nodes');
+        if (this.settings.showIcons) {
+          this.iconsContainer = this.svg.append('defs');
+          this.data.icons = {};
+        }
 
 
-                if (me.settings.showIcons) {
-                    me.fetchIcon(n.icon);
-                    me.fetchIcon(n.overlayIcon);
+        this.updateScrollPosition();
+        this.loadData();
+
+        this.wrapper.on('resize scroll', function () {
+          _this.updateScrollPosition();
+          _this.update();
+        });
+
+        this.wrapper.data('svgtree', this);
+        this.wrapper.data('svgtree-initialized', true);
+        this.wrapper.trigger('svgTree.initialized');
+        return true;
+      },
+
+      /**
+       * Updates variables used for visible nodes calculation
+       */
+      updateScrollPosition: function () {
+        this.viewportHeight = this.wrapper.height();
+        this.scrollTop = this.wrapper.scrollTop();
+        this.scrollBottom = this.scrollTop + this.viewportHeight + (this.viewportHeight / 2);
+      },
+
+      /**
+       * Loads tree data (json) from configured url
+       */
+      loadData: function () {
+        var _this = this;
+        d3.json(this.settings.dataUrl, function (error, json) {
+          if (error) throw error;
+          var nodes = Array.isArray(json) ? json : [];
+          nodes = nodes.map(function (node, index) {
+            node.open = (_this.settings.expandUpToLevel !== null) ? node.depth < _this.settings.expandUpToLevel : Boolean(node.expanded);
+            node.parents = [];
+            node._isDragged = false;
+            if (node.depth > 0) {
+              var currentDepth = node.depth;
+              for (var i = index; i >= 0; i--) {
+                var currentNode = nodes[i];
+                if (currentNode.depth < currentDepth) {
+                  node.parents.push(i);
+                  currentDepth = currentNode.depth;
                 }
                 }
-            });
-            this.svg.attr('height', this.data.nodes.length * this.settings.nodeHeight);
-        },
-
-        /**
-         * Fetch icon from Icon API and store it in data.icons
-         *
-         * @param {String} iconName
-         */
-        fetchIcon: function (iconName) {
-            if (!iconName) {
-                return;
-            }
-            var me = this;
-            if (!(iconName in this.data.icons)) {
-                this.data.icons[iconName] = {
-                    identifier: iconName,
-                    icon: ''
-                };
-                Icons.getIcon(iconName, Icons.sizes.small, null, null, 'inline').done(function(icon) {
-                    me.data.icons[iconName].icon = icon.match(/<svg.*<\/svg>/im)[0];
-                    me.update();
-                });
+              }
             }
             }
-        },
-
-        /**
-         * Renders the subset of the tree nodes fitting the viewport (adding, modifying and removing SVG nodes)
-         */
-        update: function () {
-            var me = this;
-            var visibleRows = Math.ceil(this.viewportHeight / this.settings.nodeHeight + 1);
-            var position = Math.floor(Math.max(this.scrollTop, 0) / this.settings.nodeHeight);
-
-            var visibleNodes = this.data.nodes.slice(position, position + visibleRows);
-            var nodes = this.nodesContainer.selectAll('.node').data(visibleNodes, function (d) {
-                return d.identifier;
-            });
 
 
-            // delete nodes without corresponding data
-            nodes
-                .exit()
-                .remove();
-
-            nodes = this.enterSvgElements(nodes);
-
-            this.updateLinks();
-            // update
-            nodes
-                .attr('transform', this.getNodeTransform)
-                .select('text')
-                .text(this.getNodeLabel.bind(me));
-
-            nodes
-                .select('.chevron')
-                .attr('transform', this.getChevronTransform)
-                .attr('visibility', this.getChevronVisibility);
-
-            if (this.settings.showIcons) {
-                nodes
-                    .select('use.node-icon')
-                    .attr('xlink:href', this.getIconId);
-                nodes
-                    .select('use.node-icon-overlay')
-                    .attr('xlink:href', this.getIconOverlayId);
+            if (typeof node.checked === 'undefined') {
+              node.checked = false;
+              _this.settings.unselectableElements.push(node.identifier);
             }
 
             //dispatch event
             }
 
             //dispatch event
-            this.dispatch.call('updateNodes', me, nodes);
-        },
-
-        /**
-         * Renders links(lines) between parent and child nodes
-         */
-        updateLinks: function () {
-            var me = this;
-            var visibleLinks = this.data.links.filter(function (linkData) {
-                return linkData.source.y <= me.scrollBottom && linkData.target.y >= me.scrollTop;
+            _this.dispatch.call('prepareLoadedNode', _this, node);
+            return node;
+          });
+
+          _this.nodes = nodes;
+          _this.dispatch.call('loadDataAfter', _this);
+          _this.prepareDataForVisibleNodes();
+          _this.update();
+        });
+      },
+
+      /**
+       * Filters out invisible nodes (collapsed) from the full dataset (this.rootNode)
+       * and enriches dataset with additional properties
+       * Visible dataset is stored in this.data
+       */
+      prepareDataForVisibleNodes: function () {
+        var _this = this;
+
+        var blacklist = {};
+        this.nodes.map(function (node, index) {
+          if (!node.open) {
+            blacklist[index] = true;
+          }
+        });
+
+        this.data.nodes = this.nodes.filter(function (node) {
+          return node.hidden !== true && !node.parents.some(function (index) {
+            return Boolean(blacklist[index]);
+          });
+        });
+
+        this.data.links = [];
+        this.data.nodes.forEach(function (n, i) {
+          //delete n.children;
+          n.x = n.depth * _this.settings.indentWidth;
+          n.y = i * _this.settings.nodeHeight;
+          if (n.parents[0] !== undefined) {
+            _this.data.links.push({
+              source: _this.nodes[n.parents[0]],
+              target: n,
             });
             });
+          }
+
+          if (_this.settings.showIcons) {
+            _this.fetchIcon(n.icon);
+            _this.fetchIcon(n.overlayIcon);
+          }
+        });
+
+        this.svg.attr('height', this.data.nodes.length * this.settings.nodeHeight);
+      },
+
+      /**
+       * Fetch icon from Icon API and store it in data.icons
+       *
+       * @param {String} iconName
+       */
+      fetchIcon: function (iconName) {
+        if (!iconName) {
+          return;
+        }
 
 
-            var links = this.linksContainer
-                .selectAll('.link')
-                .data(visibleLinks);
-            // delete
-            links
-                .exit()
-                .remove();
-
-            //create
-            links.enter().append('path')
-                .attr('class', 'link')
-                //create + update
-                .merge(links)
-                .attr('d', this.getLinkPath.bind(me));
-        },
-
-        /**
-         * Adds missing SVG nodes
-         *
-         * @param {Selection} nodes
-         * @returns {Selection}
-         */
-        enterSvgElements: function (nodes) {
-            var me = this;
-            me.textPosition = 10;
-
-            if (me.settings.showIcons) {
-                var iconsArray = $.map(me.data.icons, function(value) {if (value.icon !== '') return value});
-                var icons = this.iconsContainer
-                    .selectAll('.icon-def')
-                    .data(iconsArray, function (i) {
-                        return i.identifier;
-                    });
-                icons
-                    .enter()
-                    .append('g')
-                    .attr('class', 'icon-def')
-                    .attr('id', function (i) {
-                        return 'icon-' + i.identifier;
-                    })
-                    .append(function (i) {
-                        //workaround for IE11 where you can't simply call .html(content) on svg
-                        var parser = new DOMParser();
-                        var markupText = i.icon.replace('<svg', '<g').replace('/svg>', '/g>');
-                        markupText = "<svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'>" + markupText + "</svg>";
-                        var dom = parser.parseFromString(markupText, "image/svg+xml");
-                        return dom.documentElement.firstChild;
-                    });
-            }
-
-            // create the node elements
-            var nodeEnter = nodes
-                .enter()
-                .append('g')
-                .attr('class', this.getNodeClass)
-                .attr('transform', this.getNodeTransform);
-
-            // append the chevron element
-            var chevron = nodeEnter
-                .append('g')
-                .attr('class', 'toggle')
-                .on('click', this.chevronClick.bind(me));
-
-            // improve usability by making the click area a 16px square
-            chevron
-                .append('path')
-                .style('opacity', 0)
-                .attr('d', 'M 0 0 L 16 0 L 16 16 L 0 16 Z');
-            chevron
-                .append('path')
-                .attr('class', 'chevron')
-                .attr('d', 'M 4 3 L 13 8 L 4 13 Z');
-
-            // append the icon element
-            if (this.settings.showIcons) {
-                me.textPosition = 30;
-                nodeEnter
-                    .append('use')
-                    .attr('x', 8)
-                    .attr('y', -8)
-                    .attr('class', 'node-icon')
-                    .on('click', this.clickOnIcon.bind(me));
-                nodeEnter
-                    .append('use')
-                    .attr('x', 8)
-                    .attr('y', -3)
-                    .attr('class', 'node-icon-overlay')
-                    .on('click', this.clickOnIcon.bind(me));
-            }
-
-            this.dispatch.call('updateSvg', me, nodeEnter);
-
-            // append the text element
-            nodeEnter
-                .append('text')
-                .attr('dx', me.textPosition)
-                .attr('dy', 5)
-                .on('click', this.clickOnLabel.bind(me))
-                .on('dblclick', this.dblClickOnLabel.bind(me));
-
-            nodeEnter
-                .append('title')
-                .text(this.getNodeTitle.bind(me));
-
-            return nodes.merge(nodeEnter);
-        },
-
-        /**
-         * Computes the tree item label based on the data
-         *
-         * @param {Node} node
-         * @returns {String}
-         */
-        getNodeLabel: function (node) {
-            return node.name;
-        },
-
-        /**
-         * Computes the tree node class
-         *
-         * @param {Node} node
-         * @returns {String}
-         */
-        getNodeClass: function (node) {
-            return 'node identifier-' + node.identifier;
-        },
-
-        /**
-         * Computes the tree item label based on the data
-         *
-         * @param {Node} node
-         * @returns {String}
-         */
-        getNodeTitle: function (node) {
-            return 'uid=' + node.identifier;
-        },
-
-        /**
-         * Returns chevron 'transform' attribute value
-         *
-         * @param {Node} node
-         * @returns {String}
-         */
-        getChevronTransform: function (node) {
-            return node.open ? 'translate(8 -8) rotate(90)' : 'translate(-8 -8) rotate(0)';
-        },
-
-        /**
-         * Computes chevron 'visibility' attribute value
-         *
-         * @param {Node} node
-         * @returns {String}
-         */
-        getChevronVisibility: function (node) {
-            return node.hasChildren ? 'visible' : 'hidden';
-        },
-
-        /**
-         * Returns icon's href attribute value
-         *
-         * @param {Node} node
-         * @returns {String}
-         */
-        getIconId: function (node) {
-            return '#icon-' + node.icon;
-        },
-        /**
-         * Returns icon's href attribute value
-         *
-         * @param {Node} node
-         * @returns {String}
-         */
-        getIconOverlayId: function (node) {
-            return '#icon-' + node.overlayIcon;
-        },
-
-        /**
-         * Returns a SVG path's 'd' attribute value
-         *
-         * @param {Object} link
-         * @returns {String}
-         */
-        getLinkPath: function (link) {
-            var me = this;
-
-            var target = {
-                x: link.target._isDragged ? link.target._x : link.target.x,
-                y: link.target._isDragged ? link.target._y : link.target.y
-            };
-            var path = [];
-            path.push('M' + link.source.x + ' ' + link.source.y);
-            path.push('V' + target.y);
-            if (target.hasChildren) {
-                path.push('H' + target.x);
-            } else {
-                path.push('H' + (target.x + me.settings.indentWidth / 4));
-            }
-            return path.join(' ');
-        },
-
-        /**
-         * Returns a 'transform' attribute value for the tree element (absolute positioning)
-         *
-         * @param {Node} node
-         */
-        getNodeTransform: function (node) {
-            return 'translate(' + node.x + ',' + node.y + ')';
-        },
-
-        /**
-         * Node selection logic (triggered by different events)
-         *
-         * @param {Node} node
-         */
-        selectNode: function (node) {
-            if (!this.isNodeSelectable(node)) {
-                return;
-            }
-            var checked = node.checked;
-            this.handleExclusiveNodeSelection(node);
-
-            if (this.settings.validation && this.settings.validation.maxItems) {
-                    var selectedNodes = this.getSelectedNodes();
-                    if (!checked && selectedNodes.length >= this.settings.validation.maxItems) {
-                        return;
-                    }
-                }
-            node.checked = !checked;
-
-            this.dispatch.call('nodeSelectedAfter', this, node);
-            this.update();
-        },
-
-        /**
-         * Handle exclusive nodes functionality
-         * If a node is one of the exclusiveNodesIdentifiers list, all other nodes has to be unselected before selecting this node.
-         *
-         * @param {Node} node
-         */
-        handleExclusiveNodeSelection: function (node) {
-            var exclusiveKeys = this.settings.exclusiveNodesIdentifiers.split(','),
-                me = this;
-            if (this.settings.exclusiveNodesIdentifiers.length && node.checked === false) {
-                if (exclusiveKeys.indexOf('' + node.identifier) > -1) {
-                    // this key is exclusive, so uncheck all others
-                    this.nodes.forEach(function (node) {
-                        if (node.checked === true) {
-                            node.checked = false;
-                            me.dispatch.call('nodeSelectedAfter', me, node);
-                        }
-                    });
-                    this.exclusiveSelectedNode = node;
-                } else if (exclusiveKeys.indexOf('' + node.identifier) === -1 && this.exclusiveSelectedNode) {
-                    //current node is not exclusive, but other exclusive node is already selected
-                    this.exclusiveSelectedNode.checked = false;
-                    this.dispatch.call('nodeSelectedAfter', this, this.exclusiveSelectedNode);
-                    this.exclusiveSelectedNode = null;
-                }
-            }
-        },
-
-        /**
-         * Check whether node can be selected, in some cases like parent selector it should not be possible to select
-         * element as it's own parent
-         *
-         * @param {Node} node
-         * @returns {Boolean}
-         */
-        isNodeSelectable: function (node) {
-            return !this.settings.readOnlyMode && this.settings.unselectableElements.indexOf(node.identifier) == -1;
-        },
+        var _this = this;
+        if (!(iconName in this.data.icons)) {
+          this.data.icons[iconName] = {
+            identifier: iconName,
+            icon: '',
+          };
+          Icons.getIcon(iconName, Icons.sizes.small, null, null, 'inline').done(function (icon) {
+            _this.data.icons[iconName].icon = icon.match(/<svg.*<\/svg>/im)[0];
+            _this.update();
+          });
+        }
+      },
+
+      /**
+       * Renders the subset of the tree nodes fitting the viewport (adding, modifying and removing SVG nodes)
+       */
+      update: function () {
+        var visibleRows = Math.ceil(this.viewportHeight / this.settings.nodeHeight + 1);
+        var position = Math.floor(Math.max(this.scrollTop, 0) / this.settings.nodeHeight);
+
+        var visibleNodes = this.data.nodes.slice(position, position + visibleRows);
+        var nodes = this.nodesContainer.selectAll('.node').data(visibleNodes, function (d) {
+          return d.identifier;
+        });
+
+        // delete nodes without corresponding data
+        nodes
+          .exit()
+          .remove();
+
+        nodes = this.enterSvgElements(nodes);
+        this.updateLinks();
+
+        // update
+        nodes
+          .attr('transform', this.getNodeTransform)
+          .select('text')
+          .text(this.getNodeLabel.bind(this));
+
+        nodes
+          .select('.chevron')
+          .attr('transform', this.getChevronTransform)
+          .attr('visibility', this.getChevronVisibility);
+
+        if (this.settings.showIcons) {
+          nodes
+            .select('use.node-icon')
+            .attr('xlink:href', this.getIconId);
+          nodes
+            .select('use.node-icon-overlay')
+            .attr('xlink:href', this.getIconOverlayId);
+        }
 
 
-        /**
-         * Returns an array of selected nodes
-         *
-         * @returns {Node[]}
-         */
-        getSelectedNodes: function () {
-            return this.nodes.filter(function (node) {
-                return node.checked;
+        //dispatch event
+        this.dispatch.call('updateNodes', this, nodes);
+      },
+
+      /**
+       * Renders links(lines) between parent and child nodes
+       */
+      updateLinks: function () {
+        var _this = this;
+        var visibleLinks = this.data.links.filter(function (linkData) {
+          return linkData.source.y <= _this.scrollBottom && linkData.target.y >= _this.scrollTop;
+        });
+
+        var links = this.linksContainer
+          .selectAll('.link')
+          .data(visibleLinks);
+
+        // delete
+        links
+          .exit()
+          .remove();
+
+        //create
+        links.enter().append('path')
+          .attr('class', 'link')
+
+          //create + update
+          .merge(links)
+          .attr('d', this.getLinkPath.bind(_this));
+      },
+
+      /**
+       * Adds missing SVG nodes
+       *
+       * @param {Selection} nodes
+       * @returns {Selection}
+       */
+      enterSvgElements: function (nodes) {
+        this.textPosition = 10;
+
+        if (this.settings.showIcons) {
+          var iconsArray = $.map(this.data.icons, function (value) {
+            if (value.icon !== '') return value;
+          });
+
+          var icons = this.iconsContainer
+            .selectAll('.icon-def')
+            .data(iconsArray, function (i) {
+              return i.identifier;
             });
             });
-        },
-
-        /**
-         * Event handler for clicking on a node's icon
-         *
-         * @param {Node} node
-         */
-        clickOnIcon: function (node) {
-        },
 
 
-        /**
-         * Event handler for click on a node's label/text
-         *
-         * @param {Node} node
-         */
-        clickOnLabel: function (node) {
-            this.selectNode(node);
-        },
+          icons
+            .enter()
+            .append('g')
+            .attr('class', 'icon-def')
+            .attr('id', function (i) {
+              return 'icon-' + i.identifier;
+            })
+            .append(function (i) {
+              //workaround for IE11 where you can't simply call .html(content) on svg
+              var parser = new DOMParser();
+              var markupText = i.icon.replace('<svg', '<g').replace('/svg>', '/g>');
+              markupText = "<svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'>" + markupText + '</svg>';
+              var dom = parser.parseFromString(markupText, 'image/svg+xml');
+              return dom.documentElement.firstChild;
+            });
+        }
 
 
-        /**
-         * Event handler for double click on a node's label
-         *
-         * @param {Node} node
-         */
-        dblClickOnLabel: function (node) {
-        },
+        // create the node elements
+        var nodeEnter = nodes
+          .enter()
+          .append('g')
+          .attr('class', this.getNodeClass)
+          .attr('transform', this.getNodeTransform);
+
+        // append the chevron element
+        var chevron = nodeEnter
+          .append('g')
+          .attr('class', 'toggle')
+          .on('click', this.chevronClick.bind(this));
+
+        // improve usability by making the click area a 16px square
+        chevron
+          .append('path')
+          .style('opacity', 0)
+          .attr('d', 'M 0 0 L 16 0 L 16 16 L 0 16 Z');
+        chevron
+          .append('path')
+          .attr('class', 'chevron')
+          .attr('d', 'M 4 3 L 13 8 L 4 13 Z');
+
+        // append the icon element
+        if (this.settings.showIcons) {
+          this.textPosition = 30;
+          nodeEnter
+            .append('use')
+            .attr('x', 8)
+            .attr('y', -8)
+            .attr('class', 'node-icon')
+            .on('click', this.clickOnIcon.bind(this));
+          nodeEnter
+            .append('use')
+            .attr('x', 8)
+            .attr('y', -3)
+            .attr('class', 'node-icon-overlay')
+            .on('click', this.clickOnIcon.bind(this));
+        }
 
 
-        /**
-         * Event handler for click on a chevron
-         *
-         * @param {Node} node
-         */
-        chevronClick: function (node) {
-            if (node.open) {
-                this.hideChildren(node);
-            } else {
-                this.showChildren(node);
-            }
-            this.prepareDataForVisibleNodes();
-            this.update();
-        },
+        this.dispatch.call('updateSvg', this, nodeEnter);
+
+        // append the text element
+        nodeEnter
+          .append('text')
+          .attr('dx', this.textPosition)
+          .attr('dy', 5)
+          .on('click', this.clickOnLabel.bind(this))
+          .on('dblclick', this.dblClickOnLabel.bind(this));
+
+        nodeEnter
+          .append('title')
+          .text(this.getNodeTitle.bind(this));
+
+        return nodes.merge(nodeEnter);
+      },
+
+      /**
+       * Computes the tree item label based on the data
+       *
+       * @param {Node} node
+       * @returns {String}
+       */
+      getNodeLabel: function (node) {
+        return node.name;
+      },
+
+      /**
+       * Computes the tree node class
+       *
+       * @param {Node} node
+       * @returns {String}
+       */
+      getNodeClass: function (node) {
+        return 'node identifier-' + node.identifier;
+      },
+
+      /**
+       * Computes the tree item label based on the data
+       *
+       * @param {Node} node
+       * @returns {String}
+       */
+      getNodeTitle: function (node) {
+        return 'uid=' + node.identifier;
+      },
+
+      /**
+       * Returns chevron 'transform' attribute value
+       *
+       * @param {Node} node
+       * @returns {String}
+       */
+      getChevronTransform: function (node) {
+        return node.open ? 'translate(8 -8) rotate(90)' : 'translate(-8 -8) rotate(0)';
+      },
+
+      /**
+       * Computes chevron 'visibility' attribute value
+       *
+       * @param {Node} node
+       * @returns {String}
+       */
+      getChevronVisibility: function (node) {
+        return node.hasChildren ? 'visible' : 'hidden';
+      },
+
+      /**
+       * Returns icon's href attribute value
+       *
+       * @param {Node} node
+       * @returns {String}
+       */
+      getIconId: function (node) {
+        return '#icon-' + node.icon;
+      },
+
+      /**
+       * Returns icon's href attribute value
+       *
+       * @param {Node} node
+       * @returns {String}
+       */
+      getIconOverlayId: function (node) {
+        return '#icon-' + node.overlayIcon;
+      },
+
+      /**
+       * Returns a SVG path's 'd' attribute value
+       *
+       * @param {Object} link
+       * @returns {String}
+       */
+      getLinkPath: function (link) {
+        var target = {
+          x: link.target._isDragged ? link.target._x : link.target.x,
+          y: link.target._isDragged ? link.target._y : link.target.y,
+        };
+        var path = [];
+        path.push('M' + link.source.x + ' ' + link.source.y);
+        path.push('V' + target.y);
+        if (target.hasChildren) {
+          path.push('H' + target.x);
+        } else {
+          path.push('H' + (target.x + this.settings.indentWidth / 4));
+        }
 
 
-        /**
-         * Updates node's data to hide/collapse children
-         *
-         * @param {Node} node
-         */
-        hideChildren: function (node) {
-            node.open = false;
-        },
+        return path.join(' ');
+      },
+
+      /**
+       * Returns a 'transform' attribute value for the tree element (absolute positioning)
+       *
+       * @param {Node} node
+       */
+      getNodeTransform: function (node) {
+        return 'translate(' + node.x + ',' + node.y + ')';
+      },
+
+      /**
+       * Node selection logic (triggered by different events)
+       *
+       * @param {Node} node
+       */
+      selectNode: function (node) {
+        if (!this.isNodeSelectable(node)) {
+          return;
+        }
 
 
-        /**
-         * Updates node's data to show/expand children
-         *
-         * @param {Node} node
-         */
-        showChildren: function (node) {
-            node.open = true;
-        },
+        var checked = node.checked;
+        this.handleExclusiveNodeSelection(node);
 
 
-        /**
-         * Expand all nodes and refresh view
-         */
-        expandAll: function () {
-            this.nodes.forEach(this.showChildren.bind(this));
-            this.prepareDataForVisibleNodes();
-            this.update();
-        },
+        if (this.settings.validation && this.settings.validation.maxItems) {
+          var selectedNodes = this.getSelectedNodes();
+          if (!checked && selectedNodes.length >= this.settings.validation.maxItems) {
+            return;
+          }
+        }
 
 
-        /**
-         * Collapse all nodes recursively and refresh view
-         */
-        collapseAll: function () {
-            this.nodes.forEach(this.hideChildren.bind(this));
-            this.prepareDataForVisibleNodes();
-            this.update();
+        node.checked = !checked;
+
+        this.dispatch.call('nodeSelectedAfter', this, node);
+        this.update();
+      },
+
+      /**
+       * Handle exclusive nodes functionality
+       * If a node is one of the exclusiveNodesIdentifiers list,
+       * all other nodes has to be unselected before selecting this node.
+       *
+       * @param {Node} node
+       */
+      handleExclusiveNodeSelection: function (node) {
+        var exclusiveKeys = this.settings.exclusiveNodesIdentifiers.split(',');
+        var _this = this;
+        if (this.settings.exclusiveNodesIdentifiers.length && node.checked === false) {
+          if (exclusiveKeys.indexOf('' + node.identifier) > -1) {
+
+            // this key is exclusive, so uncheck all others
+            this.nodes.forEach(function (node) {
+              if (node.checked === true) {
+                node.checked = false;
+                _this.dispatch.call('nodeSelectedAfter', _this, node);
+              }
+            });
+            this.exclusiveSelectedNode = node;
+          } else if (exclusiveKeys.indexOf('' + node.identifier) === -1 && this.exclusiveSelectedNode) {
+            //current node is not exclusive, but other exclusive node is already selected
+            this.exclusiveSelectedNode.checked = false;
+            this.dispatch.call('nodeSelectedAfter', this, this.exclusiveSelectedNode);
+            this.exclusiveSelectedNode = null;
+          }
         }
         }
+      },
+
+      /**
+       * Check whether node can be selected.
+       * In some cases (e.g. selecting a parent) it should not be possible to select
+       * element (as it's own parent).
+       *
+       * @param {Node} node
+       * @returns {Boolean}
+       */
+      isNodeSelectable: function (node) {
+        return !this.settings.readOnlyMode && this.settings.unselectableElements.indexOf(node.identifier) === -1;
+      },
+
+      /**
+       * Returns an array of selected nodes
+       *
+       * @returns {Node[]}
+       */
+      getSelectedNodes: function () {
+        return this.nodes.filter(function (node) {
+          return node.checked;
+        });
+      },
+
+      /**
+       * Event handler for clicking on a node's icon
+       *
+       * @param {Node} node
+       */
+      clickOnIcon: function (node) {
+      },
+
+      /**
+       * Event handler for click on a node's label/text
+       *
+       * @param {Node} node
+       */
+      clickOnLabel: function (node) {
+        this.selectNode(node);
+      },
+
+      /**
+       * Event handler for double click on a node's label
+       *
+       * @param {Node} node
+       */
+      dblClickOnLabel: function (node) {
+      },
+
+      /**
+       * Event handler for click on a chevron
+       *
+       * @param {Node} node
+       */
+      chevronClick: function (node) {
+        if (node.open) {
+          this.hideChildren(node);
+        } else {
+          this.showChildren(node);
+        }
+
+        this.prepareDataForVisibleNodes();
+        this.update();
+      },
+
+      /**
+       * Updates node's data to hide/collapse children
+       *
+       * @param {Node} node
+       */
+      hideChildren: function (node) {
+        node.open = false;
+      },
+
+      /**
+       * Updates node's data to show/expand children
+       *
+       * @param {Node} node
+       */
+      showChildren: function (node) {
+        node.open = true;
+      },
+
+      /**
+       * Expand all nodes and refresh view
+       */
+      expandAll: function () {
+        this.nodes.forEach(this.showChildren.bind(this));
+        this.prepareDataForVisibleNodes();
+        this.update();
+      },
+
+      /**
+       * Collapse all nodes recursively and refresh view
+       */
+      collapseAll: function () {
+        this.nodes.forEach(this.hideChildren.bind(this));
+        this.prepareDataForVisibleNodes();
+        this.update();
+      },
     };
 
     return SvgTree;
     };
 
     return SvgTree;
-});
+  });
index f86d1a8..1bd0ebc 100644 (file)
 /**
  * Module: TYPO3/CMS/Backend/FormEngine/Element/TreeToolbar
  */
 /**
  * Module: TYPO3/CMS/Backend/FormEngine/Element/TreeToolbar
  */
-define(['jquery', 'TYPO3/CMS/Backend/Icons', 'TYPO3/CMS/Backend/Tooltip', 'TYPO3/CMS/Backend/FormEngine/Element/SvgTree'], function($, Icons) {
+define(['jquery',
+    'TYPO3/CMS/Backend/Icons',
+    'TYPO3/CMS/Backend/Tooltip',
+    'TYPO3/CMS/Backend/FormEngine/Element/SvgTree',
+  ],
+  function ($, Icons) {
     'use strict';
 
     /**
     'use strict';
 
     /**
@@ -24,54 +29,54 @@ define(['jquery', 'TYPO3/CMS/Backend/Icons', 'TYPO3/CMS/Backend/Tooltip', 'TYPO3
      * @exports TYPO3/CMS/Backend/FormEngine/Element/TreeToolbar
      */
     var TreeToolbar = function () {
      * @exports TYPO3/CMS/Backend/FormEngine/Element/TreeToolbar
      */
     var TreeToolbar = function () {
-        this.settings = {
-            toolbarSelector: '.tree-toolbar',
-            collapseAllBtn: '.collapse-all-btn',
-            expandAllBtn: '.expand-all-btn',
-            searchInput: '.search-input',
-            toggleHideUnchecked: '.hide-unchecked-btn'
-        };
-
-        /**
-         * jQuery object wrapping the SvgTree
-         *
-         * @type {jQuery}
-         */
-        this.treeWrapper = null;
-
-        /**
-         * SvgTree instance
-         *
-         * @type {SvgTree}
-         */
-        this.tree = null;
-
-        /**
-         * State of the hide unchecked toggle button
-         *
-         * @type {boolean}
-         * @private
-         */
-        this._hideUncheckedState = false;
-
-        /**
-         * Toolbar template
-         *
-         * @type {jQuery}
-         */
-        this.template = $(
-            '<div class="tree-toolbar btn-toolbar">'+
-                '<div class="input-group">' +
-                    '<span class="input-group-addon input-group-icon filter"></span>' +
-                    '<input type="text" class="form-control search-input" placeholder="' + TYPO3.lang['tcatree.findItem'] + '">' +
-                '</div>' +
-                '<div class="btn-group">' +
-                    '<button type="button" data-toggle="tooltip" class="btn btn-default expand-all-btn" title="' + TYPO3.lang['tcatree.expandAll'] + '"></button>' +
-                    '<button type="button" data-toggle="tooltip" class="btn btn-default collapse-all-btn" title="' + TYPO3.lang['tcatree.collapseAll'] + '"></button>' +
-                    '<button type="button" data-toggle="tooltip" class="btn btn-default hide-unchecked-btn" title="' + TYPO3.lang['tcatree.toggleHideUnchecked'] + '"></button>' +
-                '</div>' +
-            '</div>'
-        )
+      this.settings = {
+        toolbarSelector: '.tree-toolbar',
+        collapseAllBtn: '.collapse-all-btn',
+        expandAllBtn: '.expand-all-btn',
+        searchInput: '.search-input',
+        toggleHideUnchecked: '.hide-unchecked-btn',
+      };
+
+      /**
+       * jQuery object wrapping the SvgTree
+       *
+       * @type {jQuery}
+       */
+      this.$treeWrapper = null;
+
+      /**
+       * SvgTree instance
+       *
+       * @type {SvgTree}
+       */
+      this.tree = null;
+
+      /**
+       * State of the hide unchecked toggle button
+       *
+       * @type {boolean}
+       * @private
+       */
+      this._hideUncheckedState = false;
+
+      /**
+       * Toolbar template
+       *
+       * @type {jQuery}
+       */
+      this.$template = $(
+        '<div class="tree-toolbar btn-toolbar">' +
+          '<div class="input-group">' +
+            '<span class="input-group-addon input-group-icon filter"></span>' +
+            '<input type="text" class="form-control search-input" placeholder="' + TYPO3.lang['tcatree.findItem'] + '">' +
+          '</div>' +
+          '<div class="btn-group">' +
+            '<button type="button" data-toggle="tooltip" class="btn btn-default expand-all-btn" title="' + TYPO3.lang['tcatree.expandAll'] + '"></button>' +
+            '<button type="button" data-toggle="tooltip" class="btn btn-default collapse-all-btn" title="' + TYPO3.lang['tcatree.collapseAll'] + '"></button>' +
+            '<button type="button" data-toggle="tooltip" class="btn btn-default hide-unchecked-btn" title="' + TYPO3.lang['tcatree.toggleHideUnchecked'] + '"></button>' +
+          '</div>' +
+        '</div>'
+      );
     };
 
     /**
     };
 
     /**
@@ -81,60 +86,66 @@ define(['jquery', 'TYPO3/CMS/Backend/Icons', 'TYPO3/CMS/Backend/Tooltip', 'TYPO3
      * @param {Object} settings
      */
     TreeToolbar.prototype.initialize = function (treeSelector, settings) {
      * @param {Object} settings
      */
     TreeToolbar.prototype.initialize = function (treeSelector, settings) {
-        var me = this;
-        this.treeWrapper = $(treeSelector);
-        if (!this.treeWrapper.data('svgtree-initialized') || typeof this.treeWrapper.data('svgtree') !== 'object') {
-            //both toolbar and tree are loaded independently through require js, so we don't know which is loaded first
-            //in case of toolbar being loaded first, we wait for an event from svgTree
-            this.treeWrapper.on('svgTree.initialized', this.render.bind(me));
-            return;
-        }
-        $.extend(this.settings, settings);
-        this.render();
+      this.$treeWrapper = $(treeSelector);
+      if (!this.$treeWrapper.data('svgtree-initialized') || typeof this.$treeWrapper.data('svgtree') !== 'object') {
+
+        // both toolbar and tree are loaded independently through require js,
+        // so we don't know which is loaded first.
+        // In case of toolbar being loaded first, we wait for an event from svgTree
+        this.$treeWrapper.on('svgTree.initialized', this.render.bind(this));
+        return;
+      }
+
+      $.extend(this.settings, settings);
+      this.render();
     };
 
     /**
      * Renders toolbar
      */
     TreeToolbar.prototype.render = function () {
     };
 
     /**
      * Renders toolbar
      */
     TreeToolbar.prototype.render = function () {
-        var me = this;
-        this.tree = this.treeWrapper.data('svgtree');
-        var $toolbar = this.template.clone().insertBefore(this.treeWrapper);
-
-        Icons.getIcon('actions-filter', Icons.sizes.small).done(function(icon) {
-            $toolbar.find('.filter').append(icon);
-        });
-        Icons.getIcon('apps-pagetree-category-expand-all', Icons.sizes.small).done(function(icon) {
-            $toolbar.find('.expand-all-btn').append(icon);
-        });
-        Icons.getIcon('apps-pagetree-category-collapse-all', Icons.sizes.small).done(function(icon) {
-            $toolbar.find('.collapse-all-btn').append(icon);
-        });
-        Icons.getIcon('apps-pagetree-category-toggle-hide-checked', Icons.sizes.small).done(function(icon) {
-            $toolbar.find('.hide-unchecked-btn').append(icon);
-        });
-
-        $toolbar.find(this.settings.collapseAllBtn).on('click', this.collapseAll.bind(this));
-        $toolbar.find(this.settings.expandAllBtn).on('click', this.expandAll.bind(this));
-        $toolbar.find(this.settings.searchInput).on('input', function () {
-            me.search.call(me, this);
-        });
-        $toolbar.find(this.settings.toggleHideUnchecked).on('click', this.toggleHideUnchecked.bind(this));
-        $toolbar.find('[data-toggle="tooltip"]').tooltip();
+      var _this = this;
+      this.tree = this.$treeWrapper.data('svgtree');
+      var $toolbar = this.$template.clone().insertBefore(this.$treeWrapper);
+
+      Icons.getIcon('actions-filter', Icons.sizes.small).done(function (icon) {
+        $toolbar.find('.filter').append(icon);
+      });
+
+      Icons.getIcon('apps-pagetree-category-expand-all', Icons.sizes.small).done(function (icon) {
+        $toolbar.find('.expand-all-btn').append(icon);
+      });
+
+      Icons.getIcon('apps-pagetree-category-collapse-all', Icons.sizes.small).done(function (icon) {
+        $toolbar.find('.collapse-all-btn').append(icon);
+      });
+
+      Icons.getIcon('apps-pagetree-category-toggle-hide-checked', Icons.sizes.small).done(function (icon) {
+        $toolbar.find('.hide-unchecked-btn').append(icon);
+      });
+
+      $toolbar.find(this.settings.collapseAllBtn).on('click', this.collapseAll.bind(this));
+      $toolbar.find(this.settings.expandAllBtn).on('click', this.expandAll.bind(this));
+      $toolbar.find(this.settings.searchInput).on('input', function () {
+        _this.search.call(_this, this);
+      });
+
+      $toolbar.find(this.settings.toggleHideUnchecked).on('click', this.toggleHideUnchecked.bind(this));
+      $toolbar.find('[data-toggle="tooltip"]').tooltip();
     };
 
     /**
      * Collapse children of root node
      */
     TreeToolbar.prototype.collapseAll = function () {
     };
 
     /**
      * Collapse children of root node
      */
     TreeToolbar.prototype.collapseAll = function () {
-        this.tree.collapseAll();
+      this.tree.collapseAll();
     };
 
     /**
      * Expand all nodes
      */
     TreeToolbar.prototype.expandAll = function () {
     };
 
     /**
      * Expand all nodes
      */
     TreeToolbar.prototype.expandAll = function () {
-        this.tree.expandAll();
+      this.tree.expandAll();
     };
 
     /**
     };
 
     /**
@@ -143,53 +154,53 @@ define(['jquery', 'TYPO3/CMS/Backend/Icons', 'TYPO3/CMS/Backend/Tooltip', 'TYPO3
      * @param {HTMLElement} input
      */
     TreeToolbar.prototype.search = function (input) {
      * @param {HTMLElement} input
      */
     TreeToolbar.prototype.search = function (input) {
-        var me = this,
-            name = $(input).val();
+      var _this = this;
+      var name = $(input).val();
+
+      this.tree.nodes[0].open = false;
+      this.tree.nodes.forEach(function (node) {
+        var regex = new RegExp(name, 'i');
+        if (regex.test(node.name)) {
+          _this.showParents(node);
+          node.open = true;
+          node.hidden = false;
+        } else {
+          node.hidden = true;
+          node.open = false;
+        }
+      });
 
 
-        this.tree.nodes[0].open = false;
-        this.tree.nodes.forEach(function (node) {
-            var regex = new RegExp(name, 'i');
-            if (regex.test(node.name)) {
-                me.showParents(node);
-                node.open = true;
-                node.hidden = false;
-            } else {
-                node.hidden = true;
-                node.open = false;
-            }
-        });
-        this.tree.prepareDataForVisibleNodes();
-        this.tree.update();
+      this.tree.prepareDataForVisibleNodes();
+      this.tree.update();
     };
 
     /**
      * Show only checked items
     };
 
     /**
      * Show only checked items
-     *
-     * @param {HTMLElement} input
      */
      */
-    TreeToolbar.prototype.toggleHideUnchecked = function (input) {
-        var me = this;
-
-        this._hideUncheckedState = !this._hideUncheckedState;
-
-        if (this._hideUncheckedState) {
-            this.tree.nodes.forEach(function (node) {
-                if (node.checked) {
-                    me.showParents(node);
-                    node.open = true;
-                    node.hidden = false;
-                } else {
-                    node.hidden = true;
-                    node.open = false;
-                }
-            });
-        } else {
-            this.tree.nodes.forEach(function (node) {
-                node.hidden = false;
-            });
-        }
-        this.tree.prepareDataForVisibleNodes();
-        this.tree.update();
+    TreeToolbar.prototype.toggleHideUnchecked = function () {
+      var _this = this;
+
+      this._hideUncheckedState = !this._hideUncheckedState;
+
+      if (this._hideUncheckedState) {
+        this.tree.nodes.forEach(function (node) {
+          if (node.checked) {
+            _this.showParents(node);
+            node.open = true;
+            node.hidden = false;
+          } else {
+            node.hidden = true;
+            node.open = false;
+          }
+        });
+      } else {
+        this.tree.nodes.forEach(function (node) {
+          node.hidden = false;
+        });
+      }
+
+      this.tree.prepareDataForVisibleNodes();
+      this.tree.update();
     };
 
     /**
     };
 
     /**
@@ -199,16 +210,17 @@ define(['jquery', 'TYPO3/CMS/Backend/Icons', 'TYPO3/CMS/Backend/Tooltip', 'TYPO3
      * @returns {Boolean}
      */
     TreeToolbar.prototype.showParents = function (node) {
      * @returns {Boolean}
      */
     TreeToolbar.prototype.showParents = function (node) {
-        if (node.parents.length === 0) {
-            return true;
-        }
+      if (node.parents.length === 0) {
+        return true;
+      }
+
+      var parent = this.tree.nodes[node.parents[0]];
+      parent.hidden = false;
 
 
-        var parent = this.tree.nodes[node.parents[0]];
-        parent.hidden = false;
-        //expand parent node
-        parent.open = true;
-        this.showParents(parent);
+      //expand parent node
+      parent.open = true;
+      this.showParents(parent);
     };
 
     return TreeToolbar;
     };
 
     return TreeToolbar;
-});
+  });