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);
...