-
Notifications
You must be signed in to change notification settings - Fork 195
Closed
Description
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
Labels
No labels