angularjsangular-ui-treeangular-tree-component

how to use recursive function to select the parent in a tree array using angulat ui tree?


So, I'm trying to create a hierarchical tree. Whena node is selected that has children, then all the children of the node is selected, but when I select all the children I also want to select the parent.

here is a link to the plunker: [https://plnkr.co/plunk/iMBFfy6cf7urOHhZ][1]

I have created a directory to markup the tree

TreeController.js

 (function (ng) {
    var app = ng.module('tree', ['tree.service', 'tree.directives']);
    app.controller("TreeController", ["TreeService", "$scope", function (TreeService, $scope) {
        var tc = this;
        buildTree();
        function buildTree() {
            TreeService.getTree().then(function (result) {
                tc.tree = result.data;
            }, function (result) {
                alert("Tree no available, Error: " + result);
            });
        }
        
     
       $scope.selectedItems = [];
       
       $scope.getSelected = function(){
         $scope.selectedItems = [];
         function checkChildren(c) {
              angular.forEach(c.children, function (c) {
                 if (c.checked){
                    $scope.selectedItems.push({"selected":c.name});
                 }
                  checkChildren(c);
              });
         }
         
         
          angular.forEach(tc.tree, function(value, key) {
              if (value.checked){
                $scope.selectedItems.push({"selected":value.name});
              }
              
               checkChildren(value);
          });
       };
    }]);
})(angular);

index.html

 <div ng-controller="TreeController as tc">
    <ul class="tree">
        <node-tree children="tc.tree"></node-tree>
    </ul>
    
    <button ng-click="getSelected()">Get Selected</button>
    
    <br/>
    <br/>
    Selected: 
    <ul>
          <li ng-repeat="item in selectedItems">
            {{item.selected}}
          </li>
        </ul>
    </div>

TreeDirective.js

 (function (ng) {
        var app = ng.module('tree.directives', []);
        app.directive('nodeTree', function () {
            return {
                template: '<node ng-repeat="node in tree"></node>',
                replace: true,
                restrict: 'E',
                scope: {
                    tree: '=children'
                }
            };
        });
        app.directive('node', function ($compile) {
            return {
                restrict: 'E',
                replace: true,
                templateUrl: 'node.html', // HTML for a single node.
                link: function (scope, element) {
                    /*
                     * Here we are checking that if current node has children then compiling/rendering children.
                     * */
                    if (scope.node && scope.node.children && scope.node.children.length > 0) {
                        scope.node.childrenVisibility = true;
                        var childNode = $compile('<ul class="tree" ng-if="!node.childrenVisibility"><node-tree children="node.children"></node-tree></ul>')(scope);
                        element.append(childNode);
                    } else {
                        scope.node.childrenVisibility = false;
                    }
                },
                controller: ["$scope", function ($scope) {
                   
                    // This function is for just toggle the visibility of children
                    $scope.toggleVisibility = function (node) {
                        if (node.children) {
                            node.childrenVisibility = !node.childrenVisibility;
                        }
                    };
                    // Here We are marking check/un-check all the nodes.
                    $scope.checkNode = function (node) {
                        node.checked = !node.checked;
                        // if (node.checked){
                        //   alert("clicked");
                        // }
                        function checkChildren(c) {
                            angular.forEach(c.children, function (c) {
                                c.checked = node.checked;
                                checkChildren(c);
                            });
                        }
    
                        checkChildren(node);
                    };
                }]
            };
        });
    })(angular);
   

node.html

  <li>
    <span ng-click="toggleVisibility(node)"> {{ ( node.childrenVisibility && node.children.length ) ? '+' : '-' }}</span>
    <input ng-click="checkNode(node)" type="checkbox" ng-checked="node.checked">
    <span>
        {{ $index + 1 }}. {{ node.name }}
    </span>
</li>

Solution

  • The first step is to determine what each node's parent node is. We can do that by recursing right after the tree is loaded and setting a parent property on each node.

    TreeController.js

    ...
    function buildTree() {
        TreeService.getTree().then(function (result) {
            tc.tree = result.data;
            
            function setParentForChildren(n) {
                angular.forEach(n.children, function (c) {
                    c.parent = n;
                    setParentForChildren(c);
                })
            }
            angular.forEach(tc.tree, setParentForChildren);
        }, function (result) {
            alert("Tree no available, Error: " + result);
        });
    }
    ...
    

    Now, we can use that parent reference each time a box is checked to recurse up the tree and say "if all my children are checked, then I should be checked too" for each parent node.

    TreeDirective.js

    ...
    $scope.checkNode = function (node) {
        node.checked = !node.checked;
        function checkParent(n) {
            if (!n.parent)
                return;
            const p = n.parent;
            p.checked = p.children.every(function(c) { return c.checked });
            checkParent(p);
        }
        
        checkParent(node);
    
        function checkChildren(c) {
            angular.forEach(c.children, function (c) {
                c.checked = node.checked;
                checkChildren(c);
            });
        }
    
        checkChildren(node);
    };
    ...
    

    Link to modified plunker