Tree node structure

by Steve Jansen

HTML

<script src="http://getbootstrap.com/2.3.2/assets/js/jquery.js"></script>
<script src="http://getbootstrap.com/2.3.2/assets/js/bootstrap.min.js"></script>
<link rel="stylesheet" href="http://getbootstrap.com/2.3.2/assets/css/bootstrap.css">
<div ng-app ng-controller="GridCtrl">
    <script type="text/ng-template"  id="repeating_sub_tree.html">
	<div class="hideborderleft" ng-show="$index == 0"></div>
	<div class="hideborderright" ng-show="$last"></div>  
	<div>
		<svg width="100%" height="35"  ng-show="tree[0] != data">
		 <marker id="arrow" viewBox="0 0 10 10" refX="0" refY="5" markerUnits="strokeWidth" markerWidth="5" markerHeight="4" orient="auto" fill="#6FAFCE">
	  		<path d="M 0 0 L 10 5 L 0 10 z"/>
		 </marker>
		 <line fill="none" stroke="#6FAFCE" stroke-width="2"
		      x1="50%" y1="0" x2="50%" y2="25" marker-end="url(#arrow)"  />
		</svg>
	</div>
	<div class="label label-info">
    	<span ng-show="data.value != null">{{data.value}}</span>
    	<input id="{{data.name}}" name="{{data.name}}" ng-model="data.valueNew" ng-show="data.value == null" focus/>
    	<div class="btn-group">
		  	<a class="btn btn-mini dropdown-toggle" data-toggle="dropdown" href="#">
				<span class="caret"></span>
		  	</a>
			<ul class="dropdown-menu">
				<li class="btnli">
		    		<button ng-click="add(data)" class="btn btn-mini btn-success" title="Add region">+</button>
		    	</li>
				<li class="btnli">
		    		<button ng-click="deleteMeAndMyChildren(data)" ng-show="data.name != 'n'" class="btn btn-mini btn-danger" title="Delete region">x</button>
		    	</li>
				<li class="btnli">
		    		<button ng-click="deleteMyChildren(data)" ng-show="data.nodes.length > 0" class="btn btn-mini btn-warning" title="Delete children">-</button>
		    	</li>
				<li class="btnli">
		    		<button ng-click="editMe(data)" class="btn btn-mini btn-info" title="Edit region">..</button>
		    	</li>
			</ul>
		</div>
    </div>
	<div>
	    <svg width="85%" height="36"  ng-show="data.nodes.length...

CSS

ul{
    padding: 0;
    margin: 0;
    font-size: 1em;
}
ul.tree {
    margin: 2rem auto;
    text-align: center;
}
ul.tree.span12{
    float: none
}
ul.tree li:not(.btnli){
    display: inline-block;
}
ul.dropdown-menu{
    min-width: 7rem;
}
li.btnli{
    display: inline
}
ul.tree li:not(.btnli) span:not(.caret){
    padding: 0.14rem 1rem
}
ul.tree li:not(.btnli) input{
    width: 5rem;
}
ul.tree li:not(.btnli) ul:not(.dropdown-menu){
    display: table-cell;
    vertical-align: baseline;
}
ul.tree li:not(.btnli) ul:not(.dropdown-menu) li:not(.btnli){
    float: left;
    padding: 0 0.5rem;
    margin-bottom: 1rem;
}
ul.tree > li:nth-child(1) > div:not(.btn-group,.hideborder){
    display: inline-block;
    width: 25%;
}
ul[id='n']  li:not(.btnli) div:not(.btn-group, .hideborder),
ul[id^='n-'] li:not(.btnli) div:not(.btn-group, .hideborder){
    display: inline-block;
    clear: right;
}
ul[id='n']  li:not(.btnli),
ul[id^='n-'] li:not(.btnli){
    position: relative;
}
.connector{
    border-top: solid 0.14rem #9CB3D1;    
}
.hideborderleft,
.hideborderright{
    display: inline;
    position: absolute;
    top: -0.14rem;
    border-top: solid 0.14rem #fff;
    width: 49.5%;
}
.hideborderleft{
    left: 0;
}
.hideborderright{
    right: 0;
}
#regionForm{
    clear: both;
    text-align: center;
}

JavaScript

function GridCtrl($scope) {
    //$scope.tree = [{name: "n", nodes: []}];
    $scope.tree = [{"name": "n", "value": "Root", "nodes": [
    				{"name":"n-1","value": "A", "nodes":[{"name":"n-1-1", "value": "A1","nodes":[]}]},
    				{"name":"n-2", "value": "B","nodes":[{"name":"n-2-1", "value": "B1","nodes":[]}, {"name":"n-2-2","value": "B2", "nodes":[]}, {"name":"n-2-3", "value": "B3", "nodes":[]}]},
    				{"name":"n-3", "value": "C","nodes":[]},
    				{"name":"n-4", "value": "D","nodes":[]}]
    			}];

    $scope.findInChildNodes = function(currentNode, newNode, searchNode, operation){
    	var nodeFound = null;
    	var indexFound = null;
    	if(searchNode.name == currentNode.name){
    		nodeFound = searchNode;
    	}
    	else{
	    	for(var index = 0; index < searchNode.nodes.length; index++){
	    		if(searchNode.nodes[index].name == currentNode.name){
	    			nodeFound = searchNode.nodes[index];
	    			indexFound = index;
	    			break;
	    		}
	    		else{
	    			if(searchNode.nodes.length > 0){
	    				$scope.findInChildNodes(currentNode, newNode, searchNode.nodes[index], operation);
	    			}
	    		}
	    	}
	    }

    	if (nodeFound != null){
    		switch(operation){
    			case 0: nodeFound.nodes.push(newNode);
    					break;
    			case 1: nodeFound.nodes = [];
    					break;
    			case 2: nodeFound.nodes = [];
    					if(indexFound != null){
	    					searchNode.nodes.splice(indexFound, 1);
	    				}
    					break;
    		}
    	}
    }

    $scope.add = function(data) {
        var post = data.nodes.length + 1;
        var newName = data.name + '-' + post;
        var newNode = {name: newName, value: data.newValue, nodes: []};
        $scope.findInChildNodes(data, newNode, $scope.tree[0], 0);
    };

    $scope.deleteMyChildren = function(data) {
    	$scope.findInChildNodes(data, null, $scope.tree[0], 1);
    };

    $scope.deleteMeAndMyChildren = function(data) {
    	$scope.findInChildNodes(data, null, $scope.tree[0], 2);
   ...