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.
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];
}
}
};
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>
);
}
});
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>
);
},
});