JSFiddle - React, Tailwind, and code Playground
by michieljoris
HTML
<script src="http://cdnjs.cloudflare.com/ajax/libs/jqueryui-touch-punch/0.2.2/jquery.ui.touch-punch.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/angular.js/1.1.1/angular.min.js"></script>
<script src="https://rawgithub.com/mjsarfatti/nestedSortable/master/jquery.mjs.nestedSortable.js"></script>
<div ng-app="app" ng-cloak>
<div ng-controller="TreeController">
{{data|json}}
<div>
<button ng-click="addChild(data)">+ New</button>
</div>
<div class="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)">
<li ya-tree="child in data.children at ol" ng-class="{minimized:child.minimized}" item="{{child}}">
<div>
<button class="toggle" ng-click="toggleMinimized(child)" ng-switch on="child.minimized"><span ng-switch-when="true">▶</span><span ng-switch-default>▼</span>
</button>
<input ng-model="child.title" />
<button ng-click="addChild(child)">+</button>
<button ng-click="remove(child)">x</button>
</div>
<ol ng-class="{pregnant:child.children.length}"></ol>
</li>
</ol>
</div>
<div class="shadow">
<ol>
<li ya-tree="child in data.children at ol" class="bg{{$depth%6}}" ng-class="{minimized:child.minimized}">
<div>
<input disabled value="{{child.title}}" /> <em>({{$depth}})</em>
</div>
<ol ng-class="{pregnant:child.children.length}"></ol>
</li>
</ol>
</div>
</div>
</div>
CSS
[ng-cloak] {
display: none;
}
* {
box-sizing: border-box
}
body {
font: 12px/16px helvetica, arial, sans-serif;
background: #eee;
}
ol {
margin:4px 0;
border: 0 none transparent;
padding: 0;
background:#eee;
}
li {
list-style:none;
margin: 1px;
border: 1px solid #ddd;
padding: 8px 8px 4px 8px;
background: #fff;
}
.ui-sortable li {
cursor: move;
}
.live input, .shadow input {
margin: 0;
border: 1px solid #ddd;
padding: 1px;
font: inherit;
font-weight: bold;
height:2em;
}
.ui-state-highlight {
border: 1px solid #fc0;
background: #ffe;
}
.live, .shadow {
float: left;
width: 45%;
margin-right: 5%;
}
.shadow {
opacity: 0.7;
}
.debug {
clear: both;
}
.debug textarea {
width: 100%;
height: 40em;
font: 10px/13px monospace;
}
textarea, input {
outline: none;
}
.pregnant {
border: 1px solid #ddd;
}
.bg0 {
background:#fcc;
}
.bg1 {
background:#ffc;
}
.bg2 {
background:#cfc;
}
.bg3 {
background:#cff;
}
.bg4 {
background:#ccf;
}
.bg5 {
background:#fcf;
}
.minimized > ol > li {
display:none;
}
.minimized > ol {
border: 0 none transparent;
}
.toggle {
border: 0 none transparent;
background:transparent;
width:2em;
color:#aaa;
}
button {
cursor: pointer
}
JavaScript
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
// the cached element is being moved into this...