Drag & Drop between two Fancetrees - javascript

Is there a way to combine 2 Fancytrees in a way that Fancytree A is a fixed set of configuration items, Fancytree B is a configuration file and items from Fancytree A can be drag & dropped to Fancytree B without disappearing in Tree A. Within Fancytree B drag & drop should also be possible.
I've searched for a while now but haven't found exactly what i was looking for, so maybe somebody knows a way how to do that!

It is definitely possible using the standard functionality to drag/drop nodes from a different tree or even standard jQuery Draggables.
Basically you use the same API
$("#tree").fancytree({
extensions: ["dnd"],
...
dnd: {
...
dragStart: function(node, data) {
if( data.originalEvent.shiftKey ){
console.log("dragStart with SHIFT");
}
// allow dragging `node`:
return true;
},
dragEnter: function(node, data) {
// Prevent dropping a parent below another parent (only sort
// nodes under the same parent)
/* if(node.parent !== data.otherNode.parent){
return false;
}
// Don't allow dropping *over* a node (would create a child)
return ["before", "after"];
*/
return true;
},
dragDrop: function(node, data) {
if( !data.otherNode ){
// It's a non-tree draggable
var title = $(data.draggable.element).text() + " (" + (count)++ + ")";
node.addNode({title: title}, data.hitMode);
return;
}
data.otherNode.moveTo(node, data.hitMode);
}
}
});
The example browser contains a demo under Examples - Test - Drag'n'Drop

Related

vis.js hyperlink an edge

Is it possible to get a hyperlink to work on an edge?
Example, I have two nodes that are connected, And have a label on them
{ id: 1601, label: 'label', x: -1085, y: -429, url: 'link' },
So for the above, The url works for the node when using
this option
network.on("selectNode", function (params) {
if (params.nodes.length === 1) {
var node = nodes.get(params.nodes[0]);
window.open(node.url, '_blank');
}
});
Now I have a link between two nodes using the standard config
{ from: 1600, to: 1613, label: "label value" },
Is it possible to get a huperlink to work on the lines / edge that connections the two nodes?
Basically, changing "node" to "edge" in your code works to open the url when an edge is selected. But, selecting a node also results in selecting all the adjacent edges. You only get one selectEdge callback, but probably want to ignore it in any case. If you want to rule out the case where someone selected the node, where there happens to be just one connected edge, then add a check for that as follows:
network.on("selectEdge", function(params) {
if ((params.edges.length == 1) && (params.nodes.length == 0)) {
var edgeId = params.edges[0];
window.open(edges.get(edgeId).url, '_blank');
}
});
There's still a problem in using "select" for this. If you have previously selected a node, then the adjacent edges are selected, even if you don't see it because of the checking above. If you then click on one of those adjacent edges, you don't get the callback because that edge was already selected. It might be better to use the network.on("click" ... ) method instead:
network.on("click", function(params) {
if (params.nodes.length == 1) {
var nodeId = params.nodes[0];
if (nodes.get(nodeId).url != null) {
window.open(nodes.get(nodeId).url, '_blank');
}
} else if (params.edges.length==1) {
var edgeId = params.edges[0];
if (edges.get(edgeId).url != null) {
window.open(edges.get(edgeId).url, '_blank');
}
}
});

Disable the whole jsTree

I'm using jsTree and have a form on the right of it based on the selected node that can be edited/saved. The goal is to prevent the user from clicking anywhere else on the tree while they are editing the form.
Is there any way to temp disable/enable the tree functionality while still keeping the tree visually available?
I tried using the disable_node(obj) method and apply it to the root of the tree but doesn't seem to be a solution.
Any suggestions? Or this is not a possible feature for the jsTree lib?
Thanks
To disable selected node do it this way:
var node = $("#tree").jstree().get_selected();
$("#tree").jstree().disable_node(node);
To disable all nodes use:
$('#tree li').each( function() {
$("#tree").jstree().disable_node(this.id);
})
UPDATED
I didn't find a way to prevent opening a disabled node so I'm just disabling all the children of a closed node too.
See demo: Fiddle
Edit As pointed out by #charles disabling the nodes doesn't disable the menu plugin (at least with a custom menu) or drag'n'drop - Added point 4 to take care of this
To disable the whole tree
Disable all rendered nodes - disable each node by id or get an array of ids to do a single call to "disable_node"
Forbid the opening of new nodes - intercept and block the click event on the open icon
Forbid the opening of new nodes with double click - modify the current tree settings
If the tree is user modifiable in any way, temporary disable all modifications setting core.check_callback = false
Note The point 2 is base on undocumented features and (given the history of the jstree plugin) will probably not work on future releases
See the snippet for a demo
var data1 = [{
"id": "W",
"text": "World",
"state": { "opened": true },
"children": [{"text": "Asia"},
{"text": "Africa"},
{"text": "Europe",
"state": { "opened": false },
"children": [ "France","Germany","UK" ]
}]
}];
$('#Tree').jstree({
core: {data: data1,
check_callback: true
},
plugins: ['dnd','contextmenu','checkbox']
})
function DisableFlawed() {
// this is not enough
$('#Tree li.jstree-node').each(function() {
$('#Tree').jstree("disable_node", this.id)
})
}
function Disable() {
// disable visible nodes
$('#Tree li.jstree-node').each(function() {
$('#Tree').jstree("disable_node", this.id)
})
// block open new nodes
$('#Tree i.jstree-ocl')
.off('click.block')
.on('click.block', function() {
return false;
});
// eventually... dbl click
$('#Tree').jstree().settings.core.dblclick_toggle = false;
// eventually... block all edits
$('#Tree').jstree().settings.core.check_callback = false;
}
function Enable() {
// enable again visible nodes
$('#Tree li.jstree-node').each(function() {
$('#Tree').jstree("enable_node", this.id)
});
// ublock open new nodes
$('#Tree i.jstree-ocl')
//
.off('click.block');
// eventually... dbl click
$('#Tree').jstree().settings.core.dblclick_toggle = true;
// eventually... unblock all edits
// set to true OR reset to whatever user defined function you are using
$('#Tree').jstree().settings.core.check_callback = true;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/jstree/3.2.1/themes/default/style.min.css" type="text/css" rel="stylesheet" />
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jstree/3.2.1/jstree.min.js"></script>
<button onclick="DisableFlawed()">Disable (bad)</button>
<button onclick="Disable()">Disable (ok)</button>
<button onclick="Enable()">Enable</button>
<div id="Tree"></div>
How about this?
// get an instance of jstree.
var tree = $.jstree.create('#tree', { ... });
// disable all nodes with one line.
tree.disable_node(tree.get_json(null, { flat: true }));
In addition to Nikolay Ermakov's answer, disabling the nodes doesn't disable the menu plugin (at least with a custom menu) or drag'n'drop. If you want to do so, you need to add an extra check in these functions (tested with JsTree 3.2.1)
$('#tree').jstree({
// ...
contextmenu: {
items: customMenu
},
dnd: {
is_draggable: function (node) {
return !node[0].state.disabled;
}
},
});
function customMenu(node)
{
if (node.state.disabled)
return false;
// usual menu generation code
}
Another way is to use something jQuery BlockUI plugin to do some general blocking outside of jsTree.

How to disable nth level node if any (n-1)th node is selected in jsTree

Hi am using jsTree and created the below shown Tree
Now i want to disable n th node if (n-1) th node is selected,ie user cant able to select different level of nodes.
eg:
if user selected Koramangala,then infosys,Accenture,TCS,IBM,Wipro and their child nodes should be disable
If Bangalore is selected,Koramangala,electronicCity,WhiteField,Marathahally and their child should be disabled and disable same level childs in US& UK
Is it possible to achieve this requirement ????
Thanks in advance
You can use an attribute added by jsTree to all li elements - the aria-level attribute. It starts from 1 for root element and spans whole tree showing level for every node.
You will have to do this:
add some events to jsTree object - changed event to disable visible nodes from next level and below and open_node to update status of to-be disabled nodes previously hidden (non-existent in the DOM till this moment to be exact)
add conditionalselect plugin to disallow node selection if node is disabled
I kept the currently selected level in var currentlevel. You should check that it is kept local. Also you can surely optimize the code so it wouldn't repeat enable/disable functionality.
Check demo - JS Fiddle
Solution for the above mentioned requiremnt will be available here in jsFfiddle
Here am listing the features
At a time,only same level of nodes can be selectable
If select nth level node,then all lower level nodes will be disabled
If select nth level node and after that if select any higher level node is selected,then all lower level nodes selection will be removed
Here am adding the jquery code
var data = [{"id":"1","text":"India","state":{"opened":false},"parent":"#", "state": { "opened":true } },{"id":"1-1","text":"Banglore", "state": { "opened":true }, "parent":"1"},{"id":"1-1-1","text":"Koramangala","state":{"opened":false},"parent":"1-1"},{"id":"1-1-1-1","text":"Infosys ","state":{"opened":false},"parent":"1-1-1"},{"id":"1-1-1-1-1","text":"D","state":{"opened":false},"parent":"1-1-1-1"},{"id":"1-1-1-1-2","text":"E","state":{"opened":false},"parent":"1-1-1-1"},{"id":"1-1-1-1-3","text":"G","state":{"opened":false},"parent":"1-1-1-1"},{"id":"1-1-1-3","text":"Accenture ","state":{"opened":false},"parent":"1-1-1"},{"id":"1-1-1-3-8","text":"C","state":{"opened":false},"parent":"1-1-1-3"},{"id":"1-1-1-3:9","text":"E","state":{"opened":false},"parent":"1-1-1-3"},{"id":"1-1-2","text":"Electronic City","state":{"opened":false},"parent":"1-1"},{"id":"1-1-2-2","text":"TCS ","state":{"opened":false},"parent":"1-1-2"},{"id":"1-1-2-2-4","text":"C","state":{"opened":false},"parent":"1-1-2-2"},{"id":"1-1-2-2-5","text":"E","state":{"opened":false},"parent":"1-1-2-2"},{"id":"1-1-2-2-6","text":"F","state":{"opened":false},"parent":"1-1-2-2"},{"id":"1-1-2-2-7","text":"G","state":{"opened":false},"parent":"1-1-2-2"},{"id":"1-1-3","text":"WhiteField","state":{"opened":false},"parent":"1-1"},{"id":"1-1-3-4","text":"IBM ","state":{"opened":false},"parent":"1-1-3"},{"id":"1-1-3-4-10","text":"F","state":{"opened":false},"parent":"1-1-3-4"},{"id":"1-1-4","text":"Marathahally","state":{"opened":false},"parent":"1-1"},{"id":"1-1-4-5","text":"Wipro ","state":{"opened":false},"parent":"1-1-4"},{"id":"1-1-4-5-11","text":"G","state":{"opened":false},"parent":"1-1-4-5"},{"id":"1-2","text":"Chennai","state":{"opened":false},"parent":"1"},{"id":"1-2-5","text":"sholinganallur","state":{"opened":false},"parent":"1-2"},{"id":"1-2-6","text":"Tiruvanmiyur","state":{"opened":false},"parent":"1-2"},{"id":"2","text":"UK","state":{"opened":false},"parent":"#"},{"id":"2-3","text":"London","state":{"opened":false},"parent":"2"},{"id":"3","text":"US","state":{"opened":false},"parent":"#"},{"id":"3-4","text":"Texas","state":{"opened":false},"parent":"3"},{"id":"3-5","text":"Washington","state":{"opened":false},"parent":"3"},{"id":"3-6","text":"California","state":{"opened":false},"parent":"3"}]
$.jstree.defaults.core = {};
var currentlevel;
$('#tree')
.on('changed.jstree', function (event, data) {
if( data.action == 'select_node'){
$('#tree').find('li').removeClass('disabled_node');
console.log('select '+ data.node.text);
currentlevel = parseInt( $('#'+data.node.id).attr('aria-level') );
$('#tree').find('li').each( function() {
if($(this).attr('aria-level') > currentlevel) {
$(this).addClass('disabled_node');
// remove checks from levels below
$('#tree').jstree('deselect_node', '#'+this.id);
} else if($(this).attr('aria-level') < currentlevel) {
// remove checks from levels above
$('#tree').jstree('deselect_node', '#'+this.id);
}
});
}
if( data.action == 'deselect_node' && data.event && data.event.type === 'click'){
// if have other checked nodes at same level - do not enable children
if ( $('#tree').find('li:not(#'+data.node.id+')[aria-level="'+currentlevel+'"][aria-selected="true"]').length>0 ) {
return;
}
$('#tree').find('li').each( function() {
if($(this).attr('aria-level') > currentlevel) {
$(this).removeClass('disabled_node');
}
});
}
})
.on('open_node.jstree', function(event, obj ) {
$('#'+obj.node.id).find('li').each( function() {
if($(this).attr('aria-level') > currentlevel) {
$(this).addClass('disabled_node');
}
});
})
.jstree({
"core" : {
"data" : data,
"multiple": true,
"themes": {
"url": true,
"icons": true
}
},
"checkbox" : {
"three_state" : false
},
"conditionalselect" : function (node, event) {
return !$('#'+node.id).hasClass('disabled_node');
},
"plugins" : [ "checkbox","conditionalselect" ]
});
Thanks to nikolay-ermakov
All the other answers\fiddles listed above works in most scenarios but one. All the above scenarios works fine as long as all the nodes are expanded (becuase once you collapse a node, it is removed from the dom).
Assume you select a node at level three and then you collapse the node at level three and then you select a node at level 1, it system does not un-select level 3 node (as it was removed from dom when node was collapsed) and nodes at level 1 and 3 remains selected.
To fix the issue, I am sending the node an additional field in the JSON call level which tells you about the current level of the node.
var myTree = $('#tree');
myTree .on('changed.jstree', function(event, data) {
mee.disableTreeNodesAtOtherLevels(event,data,myTree );
});
disableTreeNodesAtOtherLevels(event,data, tree){
var currentlevel = parseInt($('#' + data.node.id).attr('aria-level'));
var selectedNodes = tree.jstree(true).get_selected(true);
for (var i = 0; i < selectedNodes.length; i++) {
if(selectedNodes[i].original.level !== currentlevel){
tree.jstree(true).deselect_node(selectedNodes[i], true);
}
}
}
All this works based on assumption the data you are binding to tree is having a property called level

Sort(a,b) does not work in Dojo.dnd.source

I try to sort the data after user drop an element on target container, here is the sorting event
......
var elements_container= dojo.dnd.Source("elements_container");
dojo.byId("elements_container").innerHTML = '';
... // add elements into container...
function sortDnD(){
// actually full class name is ".element dojoDndItem" to query
dojo.query(".element", dojo.byId("elements_container")).sort(
function( a,b ) {
// fire bug debugging cursor skip this section
var divs_a = dojo.query('> div.sequence', a)
var diValue_a = divs_a[0].innerHTML;
var divs_b = dojo.query('> div.sequence', b)
var diValue_b = divs_b[0].innerHTML;
return (divs_a == divs_b ? 0 : (a.divs_a > b.divs_b ? 1 : -1));
}
).forEach(// fire bug debugging cursor move to this section
function(a, idx) {
dojo.byId("element_container").insertBefore(a, dojo.byId("elements_container").childNodes[idx]);
});
}
dojo.byId("elements_container") is the dojo dnd source. I can guarantee that there are several elements in the containers...
I am using dojo1.6, interestingly when I debug by firebug, it looks the body inside of
function( a,b ) {
....
}
never executed, nor get any error message; the debug cursor move to .forEach just after function( a,b ) but the body of .forEach method runs without any problem. It looks the sort function give no response at all.
UPDATE
here is the code to invoke above sorting function
dojo.connect( source_container, "onDndDrop", function( source, nodes, copy, target ) {
nodes.forEach(function(node) {
sortDnD();
});
});
UPDATE2
After I change
dojo.query(".element", dojo.byId("elements_container")).sort(
to
dojo.query(".element", elements_container).sort(
Dojo gives:
TypeError: root.getElementsByClassName is not a function
...ag){var ret=_201(0,arr),te,x=0;var tret=root.getElementsByClassName(_235);while(...
and here is the dom data for elements_container
node: div#elements_container.container.dnd-list.dojoDndContainer.dojoDndSource.dojoDndTarget
childrenNodes: NodeList[div#dojoUnique23.element.dojoDndItem, div#dojoUnique24.element.dojoDndItem, .....
The reason why the callback in the sort is not being called is because your query selector returns an empty array (therefore you have nothing to sort on).
Use the following instead :
dojo.query(".element.dojoDndItem", "elements_container").sort(
Note that initially your selector was ".element .dojoDndItem" which means "find all nodes with class dojoDndItem that are children of nodes with class element". Here, both classes are in the same nodes, so you need to remove the space and make the selector be ".element.dojoDndItem".

EXTjs 4 figuring out origin and destination for a drag/drop plugin events

I'm teaching myself EXTjs 4 by building a very simple application.
In EXTjs 4 I've got 4 grids that each have the Grid to Grid drag/drop plugin. (Example functionality here: http://dev.sencha.com/deploy/ext-4.0.2a/examples/dd/dnd_grid_to_grid.html )
In my view I have the plugin defined as such:
viewConfig: {
plugins: {
ptype: 'gridviewdragdrop',
dragGroup: 'ddzone',
dropGroup: 'ddzone'
}
},
Now in the example, they have different dragGroups and dropGroups, but because I want the items to drag/dropped between each other fluidly, I gave the groups the same name.
The way the information gets originally populated into the 4 different lists is by looking at an the state_id in the db. All state_ids 1 go into the backlog store, 2 In Progress store, etc, etc.
So what I need to do when the item is drag/dropped, is remove it from its old store and put it into the new store (updating the state_id at the same time, so I can sync it with the db afterwards).
My only problem is figuring out the origin grid and destination grid of the row that was moved over.
Thank you!
PS. If you're curious this is what my drop event handler looks like at the moment:
dropit: function (node, data, dropRec, dropPosition) {
console.log('this');
console.log(this);
console.log('node');
console.log(node);
console.log('data');
console.log(data);
console.log('droprec');
console.log(dropRec);
console.log('dropPosition');
console.log(dropPosition);
},
As you can see, I haven't gotten very far ^_^
Alright, I figured out a way of doing it that seems to be less then ideal... but it works so until someone provides a better solution I'll be stuck doing it like this:
dropit: function (node, data, dropRec, dropPosition) {
if (node.dragData.records[0].store.$className == "AM.store.BacklogCards")
{
data.records[0].set('state_id', 1);
this.getBacklogCardsStore().sync();
}
else if (node.dragData.records[0].store.$className == "AM.store.InprogressCards")
{
data.records[0].set('state_id', 2);
this.getInprogressCardsStore().sync();
}
else if (node.dragData.records[0].store.$className == "AM.store.ReviewCards")
{
data.records[0].set('state_id', 3);
this.getReviewCardsStore().sync();
}
else
{
data.records[0].set('state_id', 4);
this.getDoneCardsStore().sync();
}
I noticed that node.dragData.records[0].store.$className points to defined store that is what the grid bases itself on.
Using the data.records[0].set('state_id', 1); sets the state_id for the row that was moved over and then finally, I call the sync function to update the db with the new row information.

Categories