Ember - Tree View

POC WIP V1

by Nirvanachain

HTML

<script src="http://cdnjs.cloudflare.com/ajax/libs/handlebars.js/1.0.0-rc.3/handlebars.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/ember.js/1.0.0-rc.1/ember.js"></script>
<script type="text/x-handlebars" data-template-name="application">
  <h1>Tree view with Emberjs</h1>
    <p><a href="https://github.com/bobey/ember-treeview" target="_blank">Github project</a></p>
  {{view Ember.Tree.TreeContainer controllerBinding="treeController"}}
</script>

<script type="text/x-handlebars" data-template-name="tree-node">
    {{view view.treeNodeHeader}} <span class="node-content"><span {{bindAttr class=":icon view.node.isLeaf view.node.isBranch"}}></span>{{view.nodeContent}}</span>
</script>

CSS

body {
    font: 14px/1.5 "Helvetica Neue", Helvetica, Arial, sans-serif;
    color: #777;
    font-weight: 300;
}

.sortable-placeholder {
    border: 1px solid #A3BAE9;
    background-color: #DFE8F6;
}

.tree-container {
    margin: 20px;
    float: left;
    padding: 20px;
    padding-left: 40px;
    padding-right: 40px;
    border: 1px dotted #ccc;
}

.tree-node {
    display: block;
    /*height: 20px;*/
}

.tree-node.drag-over {
    background-color: #A3BAE9;
}

.tree-node.drag-over.drag-forbidden {
    background-color: #F2DEDE;
}

.tree-node.is-active {
    background-color: #f3f3f3;
}

.tree-node.is-selected {
    background-color: #227ECB;
    color: white;
}

.tree-node-sorting {
    border: 1px solid;
    height: 12px;
    width: 50px;
    display: block;
}

.tree-node-header {
    display: inline-block;
    height: 16px;
    width: 16px;
}

.is-branch.is-closed .tree-node-header {
    background-image: url(http://cdn2.iconfinder.com/data/icons/picol-vector/32/arrow_sans_right-16.png);
}

.is-branch.is-opened .tree-node-header {
    background-image: url(http://cdn2.iconfinder.com/data/icons/picol-vector/32/arrow_sans_down-16.png);
}

.drop-after-node {
    height: 3px;
    /*background-color: black;*/
}

.drop-after-node.drag-over {
    background-color: #A3BAE9;
}

.drop-after-node.drag-over.drag-forbidden {
    background-color: #F2DEDE;
}


/* Custom styles */

.icon {
    width: 16px;
    height: 16px;
    margin-right: 5px;
    display: inline-block;
}

.icon.is-leaf {
    width: 12px;
    height: 12px;
    background-image: url(https://raw.github.com/bobey/ember-treeview/master/img/table.png)
}

.icon.is-branch {
    background-image: url(https://raw.github.com/bobey/ember-treeview/master/img/folder_closed.png)
}

.is-opened .icon.is-branch {
    background-image: url(https://raw.github.com/bobey/ember-treeview/master/img/folder.png)
}



.dragging-node-tooltip {
    border: 1px dotted #ccc;
    background-repeat: no-repeat;
   ...

JavaScript

(function() {

    // Ember Tree
    Ember.Tree = Ember.Namespace.create();

    // Ember Tree Node
    Ember.Tree.Node = Ember.ObjectProxy.extend({
        label: null,
        isActive: false,
        isSelected: false,
        isOpened: false,
        parent: null,
        children: [],

        isVisible: function() {
            return this._hierarchyIsVisible(this);
        }.property('isRoot', 'parent', 'parent.isVisible', 'parent.isOpened'),

        isVisibleUnderRoot: function() {
            return this._hierarchyIsVisible(this, true);
        }.property('parent.isRoot', 'parent', 'parent.isVisible', 'parent.isOpened'),

        _hierarchyIsVisible: function(node, underRoot) {
            if (!underRoot && node.get('isRoot') || underRoot && node.get('parent.isRoot')) {
                return true;
            }

            return node.get('parent.isOpened') && this._hierarchyIsVisible(node.get('parent'), underRoot);
        },

        level: function() {
            return this._computeLevel(this.get('parent'), 0);
        }.property().volatile(),

        isRoot: function() {
            return this.get('parent') === null;
        }.property('parent'),

        isBranch: function() {
            return this.get('children').length > 0;
        }.property('children', 'children.@each'),

        isLeaf: function() {
            return !this.get('children').length;
        }.property('children', 'children.@each'),

        _computeLevel: function(node, level) {
            if (null !== node) {
                return this._computeLevel(node.get('parent'), level+1);
            }

            return level;
        }
    });

    // Mixins
    /**
     * Ember.StyleBindingsMixin
     * CREDITS for this one to Addepar EmberTable
     */
    Ember.StyleBindingsMixin = Ember.Mixin.create({
        concatenatedProperties: ['styleBindings'],
        attributeBindings: ['style'],
        unitType: 'px',
        createStyleString: function(styleName, property) {
...