Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Drag and drop support #55

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion ipytree/tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,18 +86,20 @@ class Tree(DOMWidget):

nodes = Tuple().tag(trait=Instance(Node), sync=True, **widget_serialization)
multiple_selection = Bool(True, read_only=True).tag(sync=True)
drag_and_drop = Bool(True, read_only=True).tag(sync=True)
animation = Int(200, read_only=True).tag(sync=True)
selected_nodes = Tuple(read_only=True).tag(trait=Instance(Node),sync=True, **widget_serialization)

_id = Unicode('#', read_only=True).tag(sync=True)

def __init__(
self, nodes=[], multiple_selection=True, animation=200,
self, nodes=[], multiple_selection=True, animation=200, drag_and_drop=True,
**kwargs):
super(Tree, self).__init__(**kwargs)

self.nodes = nodes
self.set_trait('multiple_selection', multiple_selection)
self.set_trait('drag_and_drop', drag_and_drop)
self.set_trait('animation', animation)

def add_node(self, node, position=None):
Expand Down
75 changes: 72 additions & 3 deletions js/lib/tree.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,22 @@ var NodeModel = widgets.WidgetModel.extend({
NodeModel.__super__.initialize.apply(this, arguments);

nodesRegistry[this.get('_id')] = this;
},

contains: function(node) {
// Recursively search for node
// params:
// node <string>: id of the node to search for
// Returns true if found
if (node == this.get("_id")) return true;
var arr = this.get('nodes');
for (var i=0; i<arr.length; i++) {
var child = arr[i];
if (child.contains(node)) {
return true;
}
}
return false;
}
}, {
serializers: _.extend({
Expand Down Expand Up @@ -137,6 +153,7 @@ var NodeView = widgets.WidgetView.extend({
icon_element.removeClass(close_icon).addClass(open_icon);
}
},

onRendered: function() {
this.nodeViews = new widgets.ViewList(this.addNodeModel, this.removeNodeView, this);
this.nodeViews.update(this.model.get('nodes'));
Expand Down Expand Up @@ -235,6 +252,7 @@ var TreeModel = widgets.DOMWidgetModel.extend({
_view_module: 'ipytree',
nodes: [],
multiple_selection: true,
drag_and_drop: true,
animation: 200,
selected_nodes: [],
_id: '#'
Expand All @@ -249,15 +267,17 @@ var TreeModel = widgets.DOMWidgetModel.extend({
var TreeView = widgets.DOMWidgetView.extend({
render: function() {
this.waitTree = new Promise((resolve, reject) => {
var plugins = ['wholerow']
if (this.model.get('drag_and_drop')) {
plugins.push('dnd');
}
$(this.el).jstree({
'core': {
check_callback: true,
multiple: this.model.get('multiple_selection'),
animation: this.model.get('animation'),
},
'plugins': [
'wholerow'
]
'plugins': plugins
}).on('ready.jstree', () => {
this.tree = $(this.el).jstree(true);

Expand Down Expand Up @@ -320,6 +340,55 @@ var TreeView = widgets.DOMWidgetView.extend({
this.model.set('selected_nodes', selected_nodes);
this.model.save_changes();
}
).bind(
"move_node.jstree", (evt, data) => {

var new_parent = ((data.parent == "#") ? this.model : nodesRegistry[data.parent]);
var old_parent = ((data.old_parent == "#") ? this.model : nodesRegistry[data.old_parent]);

var np_children = []; // new children of new_parent
var op_children = []; // new children of old_parent

var update_np_quietly = false;
// If updating the old_parent's children changes the index of a new ancestor
Copy link
Author

@JoelStansbury JoelStansbury Dec 19, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is complete guess work. I don't know what is happening behind the scenes, but these are the assumptions that led to a working solution. There was definitely some funny duplication happening, though I cannot say for certain that it was due to the index changing, just that it happened when the index changed

// of the moved node, then the change event will propagate through the ancestor
// and eventually update the new parent.
// If we then explicitly update the new parent, then it's children will be duplicated
// on the front end, which is bad.
// This happens if the moved node was above a new ancestor. For example,
// | - node1 -> | - node2
// | - node2 | - node1
// root.indexof(node2) changes from 1 to 0, so the change event is propagated to node2


// Construct the new list of children for the old_parent
var i = -1;
data.instance._model.data[data.old_parent].children.slice().forEach((id) => {
i++;
op_children.push(nodesRegistry[id]);

// Once the old index of the moved node has been reached
// it is necessary to start searching for the new parent
// in order to catch the double change event issue.
if (i >= data.old_position) {
if (nodesRegistry[id].contains(data.parent)) {
update_np_quietly = true;
}
}
});

// Construct the new list of children for the new_parent
data.instance._model.data[data.parent].children.slice().forEach((id) => {
np_children.push(nodesRegistry[id]);
});

// Set and propagate the change events
old_parent.set('nodes', op_children);
old_parent.save_changes();

new_parent.set('nodes', np_children, {silent: update_np_quietly});
new_parent.save_changes();
}
);
},

Expand Down