I am trying to restrict nodes from dropping to some particular nodes using angularjs. For that am overriding some of the jstree events like the move_node, dnd_move.vakata. Nothing works.
You can't override events - just react to them. The config option you are looking for is core.check_callback. As for angular - it depends on which wrapper you are using - I do not use angular myself so I can't help you there - just make sure you use core.check_callback - it is a callback that gets executed as structure changes are about to happen:
core : {
check_callback : function (op, node, parent, position, more) {
if(more && more.dnd) {
// do checks here and return false to prevent the drop
// or return true to allow it
// depending on the node / parent / position arguments
}
return true;
},...
Related
As an example, I have a Master\Detail grid.
Master\Detail defined as key-relation model and on getDetailRowData method parent node data exists in params
but how to get parent node data from child view?
Tried via context-menu:
On right click - getContextMenuItems got executed which has an input params
On this sample, child-grid doesn't have any row and node in context-params is null, it's ok,
but anyway it should be possible to retrieve the parent details at least via grid API, isn't it ?
Then I've tried to get parent via node:
but instead of detail_173 as you can see its ROOT_NODE_ID, and here is a confusion for me.
So question is that how to get parent node data (RecordID 173 just in case) through child-view context menu or any other possible way (without storing temp value, cuz multiple children's could be opened at the same time)
Yes, I've read this part Accessing Detail Grid API, and still unclear how to get parent-node via child-grid.
For React Hook users without access to lifecycle methods, use Ag-Grid Context to pass the parent node (or parent data) to the detail grid via detailGridOptions. No need to traverse the DOM or use a detailCellRenderer unless you want to :)
https://www.ag-grid.com/javascript-grid-context/
detailCellRendererParams: (masterGridParams) => ({
detailGridOptions: {
...
context: {
masterGrid: {
node: masterGridParams.node.parent,
data: masterGridParams.data
}
},
onCellClicked: detailGridParams => {
console.log(detailGridParams.context.masterGrid.node);
console.log(detailGridParams.context.masterGrid.data);
}
}
})
Able to achieve it. Have a look at the plunk I've created: Get master record from child record - Editing Cells with Master / Detail
Right click on any child grid's cell, and check the console log. You'll be able to see parent record in the log for the child record on which you've click.
The implementation is somewhat tricky. We need to traverse the DOM inside our component code. Luckily ag-grid has provided us the access of it.
Get the child grid's wrapper HTML node from the params - Here in the code, I get it as the 6th element of the gridOptionsWrapper.layoutElements array.
Get it's 3rd level parent element - which is the actual row of the parent. Get it's row-id.
Use it to get the row of the parent grid using parent grid's gridApi.
getContextMenuItems: (params): void => {
var masterId = params.node.gridOptionsWrapper.layoutElements[6]
.parentElement.parentElement.parentElement.getAttribute('row-id');
// get the parent's id
var id = masterId.replace( /^\D+/g, '');
console.log(id);
var masterRecord = this.gridApi.getRowNode(id).data;
console.log(masterRecord);
},
defaultColDef: { editable: true },
onFirstDataRendered(params) {
params.api.sizeColumnsToFit();
}
Note: Master grid's rowIds are defined with [getRowNodeId]="getRowNodeId" assuming that account is the primary key of the parent grid.
A very reliable solution is to create your own detailCellRenderer.
on the init method:
this.masterNode = params.node.parent;
when creating the detail grid:
detailGridOptions = {
...
onCellClicked: params => console.log(this.masterNode.data)
}
Here is a plunker demonstrating this:
https://next.plnkr.co/edit/8EIHpxQnlxsqe7EO
I struggled a lot in finding a solution to this problem without creating custom details renderer but I could not find any viable solution. So the real good solution is already answered. I am just trying to share another way to avoid creating custom renderer.
So What I did is that I changed the details data on the fly and added the field I required from the parent.
getDetailRowData: function (params: any) {
params?.data?.children?.forEach((child:any) => {
//You can assign any other parameter.
child.parentId= params?.data?.id;
});
params.successCallback(params?.data?.children);
}
When you expand the row to render details the method getDetailRowData gets called, so it takes in params as the current row for which we are expanding the details and the details table is set by invoking params.successCallback. So before setting the row data I am iterating and updating the parentId.
I want to implement the move functionality for a node in jstree. Is it the move that needs to be implemented or the drag and drop? Also, it would be nice to have working code for binding the container to event and the event code.
You only need to use the dnd plugin if you don't need to enforce any move rules(don't allow certain nodes to be moved to other nodes etc)
If you need to enforce move rules, you can add the crrm plugin.
See the Reorder only demo of the dnd pluign documentation for an example of this. The documentation is very poor, so you will have to use the developer tool on your browser to see what the properties of the parameter for the check_move callback are. For the example in the documentation, m.o refers to your dragged node and m.r refers to your destination node.
You will also likely need to be notified when a node is moved, so bind to the move_node.jstree event when you initialize the tree:
$("#treeHost").jstree({
// ...
}).bind("move_node.jstree", function (e, data) {
// data.rslt.o is a list of objects that were moved
// Inspect data using your fav dev tools to see what the properties are
});
$("#demo1").jstree({
....
.bind("move_node.jstree", function (e, data) {
/*
requires crrm plugin
.o - the node being moved
.r - the reference node in the move
.ot - the origin tree instance
.rt - the reference tree instance
.p - the position to move to (may be a string - "last", "first", etc)
.cp - the calculated position to move to (always a number)
.np - the new parent
.oc - the original node (if there was a copy)
.cy - boolen indicating if the move was a copy
.cr - same as np, but if a root node is created this is -1
.op - the former parent
.or - the node that was previously in the position of the moved node */
var nodeType = $(data.rslt.o).attr("rel");
var parentType = $(data.rslt.np).attr("rel");
if (nodeType && parentType) {
// TODO!
}
})
});
The above approaches do not work with the latest versions of jstree (3.3.7 as of today).
The first line of Bojin's answer still holds true. To implement rules, you can use core.check_callback or possibly, the types plugin; the crrm plugin doesn't exist anymore. Bind to move_node.jstree to perform some action on completion of move (drop). By default, the dnd plugin allows re-ordering (dropping between two nodes) and copying (Ctrl + drag), in addition to moving a node. The code snippet below shows how to disable these additional behaviors.
$('#treeElement').jstree({
'core': {
'check_callback': CheckOperation,
...
},
'plugins': ['dnd']
})
.bind("move_node.jstree", function(e, data) {
//data.node was dragged and dropped on data.parent
});
function CheckOperation(operation, node, parent, position, more) {
if (operation == "move_node") {
if (more && more.dnd && more.pos !== "i") { // disallow re-ordering
return false;
}
... more rules if needed ...
else {
return true;
}
}
else if (operation == "copy_node") {
return false;
}
return true;
}
I have 2 trees using jsTree and dnd plugin.
I want that each drag operation to be a copy instead of a move.
There is a "copy_modifier" which works Ok when pressing a modifier key, but I want copy to be the default behavior without the modifier.
Any ideas?
Thanks,
Adrian
Found a solution on http://groups.google.com/group/jstree
I added the following section when configuring jsTree:
"crrm": {
"move": { "always_copy": "multitree" }
}
Hope this helps,
Adrian
another solution for the new version. it works, but not fully tested.
"core": {
"check_callback": function (operation, node, node_parent, node_position, more) {
if (more) {
if (more.is_multi) {
more.origin.settings.dnd.always_copy = true;
} else {
more.origin.settings.dnd.always_copy = false;
}
}
return true;
}
}
Adrian's solution won't work with the new versions.
There's that dnd plugins always copy flag
dnd.always_copy
Setting this flag will make all drag and drops copy operations instead of move. But if you are looking for a solution where you need internal tree elements to be moved on dnd but inter tree dnds to be copies than here's a hack:
Keep a global variable flag on your page
Handle copy_node.jstree events and update your global flag from
data.is_multi (data is the second arg of the event function)
Implement check_callback function and if operation is delete_node and your flag is set unset your flag and return false, preventing deletion from the dnd.
I need some combined functionality regarding drag'n'drop between two trees in ExtJS.
The first required feature is very simple and is just the builtin drag'n'drop features isolated to a single tree only.
The second required feature is that I wan't the user to be able to drag a node from the left tree and drop it at any node in the right tree.
The action should not remove the node from the left tree, thus creating the possibility of dragging the same node from the left tree to multiple places in the right tree.
My question is: Which approach should I take to combine these two functionalities, utilizing the existing possibilities in the TreePanel object without inventing the wheel again? I am not looking for a complete solution (it would be nice though ;-) ), but rather how to handle drag/drop-zones, events and so on.
Okay. I have been thinking about this some time now, and the following approach seems to work for me :)
I have configured the left tree as this:
listeners:
{
beforenodedrop: function (dropEvent) {
// Does this node come from the right tree?
if (dropEvent.source.tree.id !== dropEvent.tree.id) {
// The node should be discarded.
dropEvent.dropNode.parentNode.removeChild(dropEvent.dropNode, true);
// The node has been discarded, return drop succeeded.
dropEvent.dropStatus = true;
return false;
}
return true;
},
nodedragover: function (dragevent) {
// If the node comes from the right tree, it is allowed to be dropped here.
if (dragevent.source.tree.id !== dragevent.tree.id) {
return true;
}
// A node from this tree is not allowed to be dropped.
return false;
}
}
The right tree is configured like this:
listeners:
{
beforenodedrop: function (dropEvent) {
// Does this node come from the left tree?
if (dropEvent.source.tree.id !== dropEvent.tree.id) {
// The node should be cloned and inserted in the right tree.
// Copy the node.
var node = dropEvent.dropNode; // the node that was dropped
var nodeCopy = new Ext.tree.TreeNode( // copy it
Ext.apply({}, node.attributes)
);
// Match the id's.
nodeCopy.id = Ext.id(null,'newnode') + '_' + node.id;
// Find the right place to put it.
if (dropEvent.target.parentNode === dropEvent.tree.getRootNode()) {
// The node is placed on a folder, thus drop it there.
dropEvent.target.appendChild(nodeCopy);
} else {
// The node is placed inside a folder, thus place it in there.
dropEvent.target.parentNode.appendChild(nodeCopy);
}
// The node has been dropped, return okay and stop further process.
dropEvent.dropStatus = true;
return false;
}
// Just use the normal builtin drag and drop.
return true;
}
}
Both trees has been set to enable Drag'n'Drop:
enableDD: true
All leaf nodes have the following configuration:
allowDrop: true,
draggable: true
All folders are set to:
allowDrop: true,
draggable: false
The conclusion is, that I have chosen to override some of the builtin drag'n'drop methods in the treepanel while still maintaining the builtin functionality.
I'm using the jQuery Tools tooltip plugin, which is initialized with $('selector').tooltip(). I'd like to call this on any current or future .tooltipper element. I figured that the following would work:
$('.tooltipper').live('ready', function(){
$(this).tooltip()
}
But it was unsuccessful---the ready event did not fire. The same for load. I've read that livequery can produce the result of I'm looking for, but surely there is a way to use jQuery .live() to pull it off, considering the documentation says that it works for all jQuery events, of which I believe ready is one.
Quoted from the jQ API (http://api.jquery.com/live/):
In jQuery 1.3.x only the following JavaScript events (in addition to custom events) could be bound with .live(): click, dblclick, keydown, keypress, keyup, mousedown, mousemove, mouseout, mouseover, and mouseup.
As of jQuery 1.4 the .live() method supports custom events as well as all JavaScript events.
As of jQuery 1.4.1 even focus and blur work with live (mapping to the more appropriate, bubbling, events focusin and focusout).
As of jQuery 1.4.1 the hover event can be specified (mapping to "mouseenter mouseleave").
.live() does not appear to support the ready event.
To add to HurnsMobile's excellent answer; Looking at bindReady(), which is the internal call that jQuery makes to bind to the document load event every time you call $(some_function) or $(document).ready(some_function) we see why we cannot bind to "ready":
bindReady: function() {
if ( readyBound ) {
return;
}
readyBound = true;
// Catch cases where $(document).ready() is called after the
// browser event has already occurred.
if ( document.readyState === "complete" ) {
return jQuery.ready();
}
// Mozilla, Opera and webkit nightlies currently support this event
if ( document.addEventListener ) {
// Use the handy event callback
document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false );
// A fallback to window.onload, that will always work
window.addEventListener( "load", jQuery.ready, false );
// If IE event model is used
} else if ( document.attachEvent ) {
// ensure firing before onload,
// maybe late but safe also for iframes
document.attachEvent("onreadystatechange", DOMContentLoaded);
// A fallback to window.onload, that will always work
window.attachEvent( "onload", jQuery.ready );
// If IE and not a frame
// continually check to see if the document is ready
var toplevel = false;
try {
toplevel = window.frameElement == null;
} catch(e) { //and silently drop any errors
}
// If the document supports the scroll check and we're not in a frame:
if ( document.documentElement.doScroll && toplevel ) {
doScrollCheck();
}
}
}
To sum it up, $(some_function) calls a function which binds to:
DOMContentLoaded
onreadystatechange (DOMContentLoaded)
window.load / onload
Your best bet would be to bind to those actions that might create new .tooltipper elements, rather than trying to listen for the ready event (which happens only once).
HurnsMobile is right. JQuery live does not support the ready-event.
This is why I created a plugin that combines the two. You register your callback once, and then you will need to call the plugin once for content you add manually.
$.liveReady('.tooltipper', function(){
this.tooltip()
});
Then when creating new content:
element.html(somehtml);
element.liveReady();
or
$('<div class="tooltipper">...').appendTo($('body')).liveReady();
A demo is available here: http://cdn.bitbucket.org/larscorneliussen/jquery.liveready/downloads/demo.html
Check out the introductory post here: http://startbigthinksmall.wordpress.com/2011/04/20/announcing-jquery-live-ready-1-0-release/
Also have a look at http://docs.jquery.com/Plugins/livequery, which listenes for changes on the dom.