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">&#x25B6;</span><span ng-switch-default>&#x25BC;</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...