Skip to content

Trying to convert to JSX #9

@rohan-deshpande

Description

@rohan-deshpande

Hi there, great component here!

I'm attempting to convert the lib to JSX for front end use as it's my preferred format to edit things in. I'm running into some issues though and I can't really figure out what's happening.

The tree loads, but no child nodes are getting rendered. I also don't get any errors...I'll attach the working files maybe you can easily see where I'm going wrong. FYI I'm pretty sure I am using React.addons.classSet incorrectly but I don't think that should cause this issue specifically.

Tree.js

var Tree = function (obj) {
  this.cnt = 1;
  this.obj = obj || {children:[]};
  this.indexes = {};
  this.build(this.obj);
}

var proto = Tree.prototype;

proto.build = function(obj) {
  var indexes = this.indexes;
  var startId = this.cnt;
  var self = this;
  var index = {id: startId, node: obj};

  indexes[this.cnt + ''] = index;
  this.cnt ++;

  if(obj.children && obj.children.length) walk(obj.children, index);

  function walk(objs, parent) {
    var children = [];

    objs.forEach(function(obj, i) {
      var index = {};

      index.id = self.cnt;
      index.node = obj;

      if(parent) index.parent = parent.id;

      indexes[self.cnt + ''] = index;
      children.push(self.cnt);
      self.cnt ++;

      if(obj.children && obj.children.length) walk(obj.children, index);
    });
    parent.children = children;

    children.forEach(function(id, i) {
      var index = indexes[id + ''];
      if(i > 0) index.prev = children[i - 1];
      if(i < children.length - 1) index.next = children[i + 1];
    });
  }

  return index;
};

proto.getIndex = function(id) {
  var index = this.indexes[id + ''];
  if(index) return index;
};

proto.removeIndex = function(index) {
  var self = this;

  del(index);

  function del(index) {
    delete self.indexes[index.id + ''];
    if(index.children && index.children.length) {
      index.children.forEach(function(child) {
        del(self.getIndex(child));
      });
    }
  }
};

proto.get = function(id) {
  var index = this.getIndex(id);

  if(index && index.node) return index.node;

  return null;
};

proto.remove = function(id) {
  var index = this.getIndex(id);
  var node = this.get(id);
  var parentIndex = this.getIndex(index.parent);
  var parentNode = this.get(index.parent);

  parentNode.children.splice(parentNode.children.indexOf(node), 1);
  parentIndex.children.splice(parentIndex.children.indexOf(id), 1);

  this.removeIndex(index);
  this.updateChildren(parentIndex.children);

  return node;
};

proto.updateChildren = function(children) {
  children.forEach(function(id, i) {
    var index = this.getIndex(id);

    index.prev = index.next = null;

    if(i > 0) index.prev = children[i - 1];
    if(i < children.length-1) index.next = children[i + 1];
  }.bind(this));
};

proto.insert = function(obj, parentId, i) {
  var parentIndex = this.getIndex(parentId);
  var parentNode = this.get(parentId);
  var index = this.build(obj);

  index.parent = parentId;

  parentNode.children = parentNode.children || [];
  parentIndex.children = parentIndex.children || [];

  parentNode.children.splice(i, 0, obj);
  parentIndex.children.splice(i, 0, index.id);

  this.updateChildren(parentIndex.children);

  if(parentIndex.parent) {
    this.updateChildren(this.getIndex(parentIndex.parent).children);
  }

  return index;
};

proto.insertBefore = function(obj, destId) {
  var destIndex = this.getIndex(destId);
  var parentId = destIndex.parent;
  var i = this.getIndex(parentId).children.indexOf(destId);

  return this.insert(obj, parentId, i);
};

proto.insertAfter = function(obj, destId) {
  var destIndex = this.getIndex(destId);
  var parentId = destIndex.parent;
  var i = this.getIndex(parentId).children.indexOf(destId);

  return this.insert(obj, parentId, i + 1);
};

proto.prepend = function(obj, destId) {
  return this.insert(obj, destId, 0);
};

proto.append = function(obj, destId) {
  var destIndex = this.getIndex(destId);

  destIndex.children = destIndex.children || [];

  return this.insert(obj, destId, destIndex.children.length);
};

proto.updateNodesPosition = function () {
  var top = 1;
  var left = 1;
  var root = this.getIndex(1);
  var self = this;

  root.top = top++;
  root.left = left++;

  walk(root.children, root, left, root.node.collapsed);

  function walk(children, parent, left, collapsed) {
    var height = 1;

    children.forEach(function (id) {
      var node = self.getIndex(id);

      if (collapsed) {
        node.top = null;
        node.left = null;
      } else {
        node.top = top++;
        node.left = left;
      }

      if (node.children && node.children.length) {
        height += walk(node.children, node, left + 1, collapsed || node.node.collapsed);
      } else {
        node.height = 1;
        height += 1;
      }
    });

    if (parent.node.collapsed) parent.height = 1;else parent.height = height;

    return parent.height;
  }
};

proto.move = function (fromId, toId, placement) {
  if (fromId === toId || toId === 1) return;

  var obj = this.remove(fromId);
  var index = null;

  if (placement === 'before') index = this.insertBefore(obj, toId);else if (placement === 'after') index = this.insertAfter(obj, toId);else if (placement === 'prepend') index = this.prepend(obj, toId);else if (placement === 'append') index = this.append(obj, toId);

  // todo: perf
  this.updateNodesPosition();
  return index;
};

proto.getNodeByTop = function (top) {
  var indexes = this.indexes;

  for (var id in indexes) {
    if (indexes.hasOwnProperty(id)) {
      if (indexes[id].top === top) return indexes[id];
    }
  }
};

TreeNode.jsx

var cx = React.addons.classSet;
var TreeNode = React.createClass({
    displayName:'TreeUINode',

    renderCollapse:function () {
        var index = this.props.index;

        if (index.children && index.children.length) {
            var collapsed = index.node.collapsed;

            return (
                <span
                    className={cx('collapse', collapsed ? 'caret-right' : 'caret-down')}
                    onMouseDown={this.stopMouseDown}
                    onClick={this.handleCollapse}
                >
                </span>
            );
        }

        return null;
    },

    renderChildren:function () {
        var _this = this;
        var index = this.props.index;
        var tree = this.props.tree;
        var dragging = this.props.dragging;

        if (index.children && index.children.length) {
            var childrenStyles = {};

            if (index.node.collapsed) childrenStyles.display = 'none';

            childrenStyles.paddingLeft = this.props.paddingLeft + 'px';

            console.log(index);

            return (
                <div className='children' style={childrenStyles}>
                    {
                        index.children.map(function (child) {
                            var childIndex = tree.getIndex(child);
                            <TreeNode
                                tree={tree}
                                index={childIndex}
                                key={childIndex.id}
                                dragging={dragging}
                                paddingLeft={_this.props.paddingLeft}
                                onCollapse={_this.props.onCollapse}
                                onDragStart={_this.props.onDragStart}
                            />
                        })
                    }
                </div>
            );

            return null;
        }
    },

    handleCollapse:function (e) {
        e.stopPropagation();
        var nodeId = this.props.index.id;
        if (this.props.onCollapse) this.props.onCollapse(nodeId);
    },

    stopMouseDown:function (e) {
        e.stopPropagation();
    },

    handleMouseDown:function (e) {
        var nodeId = this.props.index.id;
        var dom = this.refs.inner.getDOMNode();

        if (this.props.onDragStart) {
            this.props.onDragStart(nodeId, dom, e);
        }
    },

    render:function () {
        var tree = this.props.tree;
        var index = this.props.index;
        var dragging = this.props.dragging;
        var node = index.node;
        var styles = {};

        return (
            <div
                className={cx('m-node', {placeholder: index.id === dragging})}
                styles={styles}
            >
                <div
                    className='inner'
                    ref='inner'
                    onMouseDown={this.handleMouseDown}
                >
                    {this.renderCollapse()}
                    {tree.renderNode(node)}
                </div>
                {this.renderChildren()}
            </div>
        );
    }
});

TreeUI.jsx

var TreeUI = React.createClass({
    displayName: 'TreeUI',

    propTypes: {
        tree: React.PropTypes.object.isRequired,
        paddingLeft: React.PropTypes.number,
        renderNode: React.PropTypes.func.isRequired
    },

    getDefaultProps: function() {
        return {
            paddingLeft: 20
        };
    },

    getInitialState: function() {
        return this.init(this.props);
    },

    componentWillReceiveProps: function (nextProps) {
        if (!this._updated) this.setState(this.init(nextProps));else this._updated = false;
    },

    init:function (props) {
        var tree = new Tree(props.tree);

        tree.isNodeCollapsed = props.isNodeCollapsed;
        tree.renderNode = props.renderNode;
        tree.changeNodeCollapsed = props.changeNodeCollapsed;
        tree.updateNodesPosition();

        return {
            tree: tree,
            dragging: {
                id: null,
                x: null,
                y: null,
                w: null,
                h: null
           }
        };
    },

    getDraggingDom:function () {
        var tree = this.state.tree;
        var dragging = this.state.dragging;

        if (dragging && dragging.id) {
            var draggingIndex = tree.getIndex(dragging.id);
            var draggingStyles = {
                top: dragging.y,
                left: dragging.x,
                width: dragging.w
            };

            return (
                <div className='m-draggable' style={draggingStyles}>
                    <TreeNode
                        tree={tree}
                        index={draggingIndex}
                        paddingLeft={this.props.paddingLeft}
                    />
                 </div>
             );
         }

         return null;
     },

    dragStart:function (id, dom, e) {
        this.dragging = {
            id: id,
            w: dom.offsetWidth,
            h: dom.offsetHeight,
            x: dom.offsetLeft,
            y: dom.offsetTop
        };

        this._startX = dom.offsetLeft;
        this._startY = dom.offsetTop;
        this._offsetX = e.clientX;
        this._offsetY = e.clientY;
        this._start = true;

        window.addEventListener('mousemove', this.drag);
        window.addEventListener('mouseup', this.dragEnd);
     },

    drag:function (e) {
        if (this._start) {
            this.setState({
                dragging: this.dragging
            });

           this._start = false;
        }

        var tree = this.state.tree;
        var dragging = this.state.dragging;
        var paddingLeft = this.props.paddingLeft;
        var newIndex = null;
        var index = tree.getIndex(dragging.id);
        var collapsed = index.node.collapsed;

        var _startX = this._startX;
        var _startY = this._startY;
        var _offsetX = this._offsetX;
        var _offsetY = this._offsetY;

        var pos = {
            x: _startX + e.clientX - _offsetX,
            y: _startY + e.clientY - _offsetY
        };

        dragging.x = pos.x;
        dragging.y = pos.y;

        var diffX = dragging.x - paddingLeft / 2 - (index.left - 2) * paddingLeft;
        var diffY = dragging.y - dragging.h / 2 - (index.top - 2) * dragging.h;

        if (diffX < 0) {
           // left
            if (index.parent && !index.next) {
                newIndex = tree.move(index.id, index.parent, 'after');
            }
        } else if (diffX > paddingLeft) {
            // right
            if (index.prev && !tree.getIndex(index.prev).node.collapsed) {
                newIndex = tree.move(index.id, index.prev, 'append');
            }
        }

        if (newIndex) {
            index = newIndex;
            newIndex.node.collapsed = collapsed;
            dragging.id = newIndex.id;
        }

        if (diffY < 0) {
            // up
            var above = tree.getNodeByTop(index.top - 1);
            newIndex = tree.move(index.id, above.id, 'before');
        } else if (diffY > dragging.h) {
            // down
            if (index.next) {
                var below = tree.getIndex(index.next);

                if (below.children && below.children.length && !below.node.collapsed) {
                    newIndex = tree.move(index.id, index.next, 'prepend');
                } else {
                    newIndex = tree.move(index.id, index.next, 'after');
            }
        } else {
                var below = tree.getNodeByTop(index.top + index.height);
                if (below && below.parent !== index.id) {
                    if (below.children && below.children.length) {
                        newIndex = tree.move(index.id, below.id, 'prepend');
                    } else {
                        newIndex = tree.move(index.id, below.id, 'after');
                    }
                }
           }
        }

        if (newIndex) {
            newIndex.node.collapsed = collapsed;
            dragging.id = newIndex.id;
        }

        this.setState({
            tree: tree,
            dragging: dragging
        });
     },

    dragEnd: function () {
        this.setState({
            dragging: {
                id: null,
                x: null,
                y: null,
                w: null,
                h: null
            }
        });

        this.change(this.state.tree);

        window.removeEventListener('mousemove', this.drag);
        window.removeEventListener('mouseup', this.dragEnd);
    },

    change: function (tree) {
        this._updated = true;
        if (this.props.onChange) this.props.onChange(tree.obj);
    },

    toggleCollapse: function (nodeId) {
        var tree = this.state.tree;
        var index = tree.getIndex(nodeId);
        var node = index.node;

        node.collapsed = !node.collapsed;
        tree.updateNodesPosition();

        this.setState({
            tree: tree
        });

        this.change(tree);
    },

    render: function() {
        var tree = this.state.tree;
        var dragging = this.state.dragging;
        var draggingDom = this.getDraggingDom();

        return (
            <div className='m-tree'>
                {draggingDom}
                <TreeNode
                    tree={tree}
                    index={tree.getIndex(1)}
                    key={1}
                    paddingLeft={this.props.paddingLeft}
                    onDragStart={this.dragStart}
                    onCollapse={this.toggleCollapse}
                    dragging={dragging && dragging.id}
                />
            </div>
        );
    },
});

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions