JSFiddle - React, Tailwind, and code Playground
HTML
<div ng-app="app" ng-cloak>
<div ng-controller="TreeController">
<div class="new-item">
<a class="btn add icon" ng-click="addChild(data)">New Item</a>
</div>
<div class="navi-live">
<ol ui-nested-sortable="{
listType: 'ol',
items: 'li',
doNotClear: true,
placeholder: 'ui-state-highlight',
forcePlaceholderSize: true,
toleranceElement: '> div'
}" ui-nested-sortable-stop="update($event, $ui)" class="navi-item-container">
<li ya-tree="child in data.children at ol" ng-class="{minimized:child.minimized}" class="navi-item" ng-style="getStyle(child)">
<div class="nav-item-contents">
<div class="toggle-container">
<a ng-show="child.children.length" class="toggle btn small" ng-click="toggleMinimized(child)" ng-switch on="child.minimized"><span ng-switch-when="true">▶</span><span ng-switch-default>▼</span>
</a>
</div>
<span class="nav-item-main">
<input ng-model="child.title" />
<a class="btn small" ng-click="addChild(child)">add a child</a>
<a class="btn small" ng-click="remove(child)">delete</a>
</span>
</div>
<ol ng-class="{pregnant:child.children.length}" class="navi-item-container"></ol>
</li>
</ol>
</div>
CSS
.new-item {
margin:10px;
text-align: right;
}
.ui-state-highlight {
background-color:rgba(0, 0, 0, 0.1);
}
.nav-item-main {
padding-left:30px;
}
.navi-live {
-webkit-border-radius: 3px;
border-radius: 3px;
background-clip: padding-box;
-webkit-box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.05), 0 2px 10px -2px rgba(0, 0, 0, 0.1);
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.05), 0 2px 10px -2px rgba(0, 0, 0, 0.1);
}
.navi-live ol {
margin:0;
padding:0;
list-style-type: none;
}
.navi-live li {
margin:0;
padding:0px;
list-style-type: none;
}
.navi-live ol .nav-item-contents {
padding-left:20px;
}
.navi-live ol ol .nav-item-contents {
padding-left:40px;
}
.navi-live ol ol ol .nav-item-contents {
padding-left:60px;
}
.navi-live ol ol ol ol .nav-item-contents {
padding-left:80px;
}
.navi-live ol ol ol ol ol .nav-item-contents {
padding-left:100px;
}
.navi-live .nav-item-contents {
padding-top:7px;
padding-bottom:7px;
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
}
.navi-live .toggle-container {
border:none;
width:50px;
display:inline-block;
padding-left:10px;
}
.navi-live .nav-item > .toggle-container {
padding-left:20px;
}
.minimized > ol > li {
display:none;
}
.minimized > ol {
border: 0 none transparent;
}
JavaScript
// http://jsfiddle.net/michieljoris/VmtfR/
var app = angular.module('app', []);
app.directive('yaTree', function () {
return {
restrict: 'A',
transclude: 'element',
priority: 1000,
terminal: true,
compile: function (tElement, tAttrs, transclude) {
var repeatExpr, childExpr, rootExpr, childrenExpr;
repeatExpr = tAttrs.yaTree.match(/^(.*) in ((?:.*\.)?(.*)) at (.*)$/);
childExpr = repeatExpr[1];
rootExpr = repeatExpr[2];
childrenExpr = repeatExpr[3];
branchExpr = repeatExpr[4];
return function link(scope, element, attrs) {
var rootElement = element[0].parentNode,
cache = [];
// Reverse lookup object to avoid re-rendering elements
function lookup(child) {
var i = cache.length;
while (i--) {
if (cache[i].scope[childExpr] === child) {
return cache.splice(i, 1)[0];
}
}
}
scope.$watch(rootExpr, function (root) {
var currentCache = [];
// Recurse the data structure
(function walk(children, parentNode, parentScope, depth) {
var i = 0,
n = children.length,
last = n - 1,
cursor,
child,
cached,
childScope,
grandchildren;
// Iterate the children at the current level
for (; i < n; ++i) {
// We will compare the cached element to the element in
// at the destination index. If it does not match, then
...