Select Child nodes when parent is also selected in JSTREE - jstree

I am having difficulties by selecting child nodes when parent is also selected , also would like to open subfolders and select all childs (not sure if im making it clear) so I have know how to get all the child nodes by:
selected_nodes = $("#demo").jstree("get_selected", null, true);
var tree = jQuery.jstree._reference('#demo');
var children = tree._get_children(selected_nodes);
but doest really select or open child folder and nodes

This might not be what OP wanted but on JSTree 3.3. I used this to open all nodes under a selected parent.
$(treeContainer).bind("select_node.jstree", function (e, data) {
return data.instance.open_all(data.node);
});

I gave up on this Idea.. now im just doing ajax requests to load the childs and receive a json object, and from there i can see if its a file or directory, if its a directory again another request and like this i have the whole structure

You have to turn on two_state checkboxes to allow parent and child nodes to be selected indepentently. Below I've configured the "two_state" parameter for the checkbox plugin:
$("#docTree").jstree({
"themes": {
"theme": "classic",
"url": "jstree/themes/classic/style.css"
},
"plugins": ["themes", "ui", "checkbox", "json_data"],
"checkbox": { "two_state" : true }
})
Check documentation here: http://www.jstree.com/documentation/checkbox

If I understand this correctly you want to select all of the lowest descendants in a jsTree (the files) when one of their parents (the folders) have been selected.
Unfortunately I haven't found a more direct approach than the one below. I'm using jsTree 3.1.1 and managed to solve this problem using the following:
var $demo = $("#demo");
var nodes = $demo.jstree("get_top_selected", true);
//Selects all of the children of each selected node including folders
if(nodes.length > 0){
nodes.forEach(function(node, i){
$demo.jstree("select_node", node.children_d, true, false);
});
}
var fileNodes = $demo.jstree("get_bottom_selected", false);
//We now need to deselect everything and only select the file nodes
$demo.jstree("deselect_all", true);
$demo.jstree("select_node", fileNodes, true, false);
The above code allows the user to "select" multiple folders and have those files selected.
The parameter with the value true for the "get_top_selected" function returns the full node instead of just ID's. This lets you get access to the node's children.
Function "select_node" takes three parameters. The first is the nodes to select (in this case the descendants of the current parent node). If the second parameter is set to true it prevents the changed.jstree event from firing for each selected node. The third parameter is set to false as setting to true prevents the children folders from opening.
The function "get_bottom_selected" only returns a node if it has both been selected and it itself does not have any children. As we are now only interested in the node ID's we can pass the parameter false (or omit it completely).
Passing a parameter of true to the function "deselect_all" again prevents the changed.jstree event from firing.
If you want to check out the jsTree API docs you can find them here. That list is filtered to only include the select functions and events. A full list of the API can be found here.
I hope this helps but let me know if you require further clarification on any of the code ^_^

select all child nodes when parent selected ,
$(data.rslt.obj).find("li").each( function( idx, listItem ) {
var child = $(listItem); // child object
$.jstree._reference("#associateRightHandTree").check_node(child);
});
unselect all child nodes when parent unselected ,
$(data.rslt.obj).find("li").each( function( idx, listItem ) {
var child = $(listItem); // child object
$.jstree._reference("#associateRightHandTree").uncheck_node(child);
});

Related

ag-grid - how to get parent node from child grid via context menu?

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.

Automatically load node children in fancytree

I have a fancytree implementation where each parent node has a child node that can be selected. After a user selects specific children, she is able to save her selections and settings so that she can come back to it later.
I'm able to do all of this, except for when I do an initial load of previously saved data. What I need to do is identify the nodes that need to be opened (and its children selected), and have Fancytree open those nodes and select the children.
I'm using lazy loading, and when the lazyloading event fires I'm able to check to see if the child needs to be selected and do so as needed. What I'd like to be able to do is programmatically do the same thing so that all the previously selected children are loaded and selected upon load. Currently, I'm attempting this in this way:
function getMultipleFieldsNoReset(element,selectedFieldsArray) {
var fieldCreator = element.value;
var tree = $("#multipleFields").fancytree("getTree");
var treeOptions = {
url: "urltogetdata",
type: "POST",
data: {
id: fieldCreator
},
dataType: "json"
};
tree.reload(treeOptions);
for (var i = 0, len = selectedFieldsArray.length; i < len; i++) {
var valueAry = selectedFieldsArray[i].split("-");
var growerNode = valueAry[0];
var farmNode = valueAry[1];
var fieldNode = valueAry[2];
var node = tree.getNodeByKey(growerNode);
console.log(node);
}
}
My problem is that tree.getNodeByKey(growerNode) never finds the node, even though I'm able to find the node in the console after this runs. It seems like the parent nodes aren't loaded yet, which can cause this issue, but I'm not certain where I can set a complete function. Where can I do this? Or even better, is there a cleaner way to handle this?
The OP / Fletchius has got solution on this issue and below is the answer of it. I achieved it in some different way but event loadChildren is the same. from which we both found solution. Just I'm loading those nodes which are selected nodes and lazy true meaning there are children down after this node. And only lazy=true can be useful for this.load(); explicit event.
loadChildren:function(event, data){
var node = data.node;
$(node.children).each(function(){
if(this.selected && this.lazy)
{
this.load();
}
});
},

Isolate reactivity in an ordered list

I have got a template that shows tiles in a particular order:
<template name="container">
{{#each tiles}}{{>tile}}{{/each}}
</template>
Now the container is a list of tiles that is stored as an array in mongodb.
Since I want the tiles to be shown in the same order as they appear in that array, I'm using the following helper:
Template.container.tiles = function () {
return _.map(this.tiles || [], function(tileId) {
return _.extend({
container: this
}, Tiles.findOne({_id: tileId}));
}, this);
};
};
The problem is, that I:
Do not want the entire container to rerender when the any of it's contain tiles changes. (Only the relevent tile should be invalidated).
Do not want the entire container to rerender when a new tile is inserted. The rendered tile should be simply appended or insteted at the respective location.
Do not want the entire container to rerender when the order of the tiles is changed. Instead, when the order changes, the DOM objects that represent the tile should be rearranged without re-rendering the tile itself.
With the above approach I will not meet the requirements, because the each tiles data is marked as a dependency (when running Tiles.findOne({_id: tileId})) of the entire container and the entire array of tile-ids is part of the containers data and if that changes the entire container template is invalidated.
I'm thinking I should mark the cursor for the container as non-reactive. Something like this:
Containers.findOne({_id: containerId}, {reactive:false});
But I still need to find out when this container changes it's tiles array.
So something like
Deps.autorun(function() {
updateContainer(Containers.findOne({_id: containerId}));
});
But I want that container template to be highly reusable. So whatever solution there it should not require some preparations with dependencies.
Where do declare I run that autorun function? (surely i cannot do that in that helper, right?)
Is this the right approach?
Does anybody have better ideas on how to solve this problem?
Thanks in advance...
The way I usually approach this problem is by creating an auxiliary Collection object and populate it with a help of appropriate observer. In your case this might be something like:
// this one should be "global"
var tiles = new Meteor.Collection(null); // empty name
Now, depending on the current container, you can populate the tiles collection with corresponding data. Also, you'll probably need to remember each object's index:
Deps.autorun(function () {
var containerId = ... // get it somehow, e.g. from Session dictionary
var tilesIDs = Containers.findOne({_id:containerId}).tiles;
tiles.remove({}); // this will be performed any time
// the current container changes
Tiles.find({ _id : { $in : tilesIDs } }).observeChanges({
added: function (id, fields) {
tiles.insert(_.extend({
_index : _.indexOf(tilesIDs, id),
_id : id,
}, fields);
},
changed: ... // you'll also need to implement
removed: ... // these to guys
});
});
The helper code is now really simple:
Template.container.tiles = function () {
return tiles.find({}, {sort : {_index : 1}});
}
EDIT:
Please note, that in order to prevent the whole list being rerendered every time the container object changes (e.g. the order of tiles changes), you'll need to make a separate listOfTiles template that does not depend on the container object itself.

jstree get highlight node

Most of the questions in jstree are about getting the selected node (and related id, etc). My current code for getting the selected node is:
viewerObj.bind("select_node.jstree",
function (e, data) {
var nodeId = jQuery.data(data.rslt.obj[0], "jstree").id;
//alert(nodeId);
var date = new Date();
...
);
But what I really want is not the selected node necessarily, but the highlight node (and parent id) so that I could capture the "create_node" event when it is triggered. How do I do that? I can't seem to find any code that does this correctly.
Here's the image of what the UI looks like:
I want to know how to grab "models". The select_node event capture the node that is selected (or "admin") in this case.
Update:
This does it:
.bind("hover_node.jstree",
function(e, data){
var nodeId = jQuery.data(data.rslt.obj[0], "jstree").id;
}
);
It capture the hover event and grabs the id associated with the node that was last hovered.
Update:
This does it:
.bind("hover_node.jstree", function(e, data){
var nodeId = jQuery.data(data.rslt.obj[0], "jstree").id;
});
It capture the hover event and grabs the id associated with the node that was last hovered.

Can a dojo.dnd.Source object contains another dojo.dnd.Source object as one of the child nodes?

I have looked at the this link for a tutorial on dojo drag and drop feature. But one thing I have noticed is that in all cases of the examples, the items to be dragged around are always a simple item, just a string object...
I need to create something like an item group where you can drag an item into the item group to append into the group and to drag the item group around as a whole.
Hence my question, is it possible to drag and drop a dojo.dnd.Source item into another dojo.dnd.Source item?
Short answer: no. Many people tried to patch it, but found more and more non-working edge cases, so those patches never made the Dojo proper.
If you truly need to show and manipulate a hierarchical data, consider a Tree Dijit.
The problem is that when you start dragging and you drag over a Source of a child container, everything gets messed up. (Not exactly sure how). What you can do, is hide those child sources so that their overSource events never trigger:
1) Overrode the checkAcceptance function in Source.js. Just added the following for the if(!flag) return false;:
if(!flag){
/**
* Main Source
* - Group 1
* -- Child 1
* -- Child 2
* - Group 2
*/
var node = dojo.byId(this.node);
// If the node being moved is the source, skip, but don't hide from view.
if('#'+dojo.attr(source.node, 'id') != '#'+dojo.attr(node, 'id')){
// If the node being moved is an immediate child of the container, you can move it.
if(dojo.query('#'+dojo.attr(source.node, 'id') + '>#'+dojo.attr(node, 'id')).length) {
return true;
}
// If this source is not a parent of the element, hide it.
if(dojo.query('#'+dojo.attr(node, 'id') + ' #'+dojo.attr(source.node, 'id')).length == 0)
dojo.addClass(node, 'hiddenSource');
}
return false;
}
2) You need to also add the following as the first line under if(this.isDragging) in onMouseMove (important)
var node = dojo.byId(this.node);
// If this is immeditae child, drop it.
if(dojo.query('#'+dojo.attr(m.source.node, 'id') + '>#'+dojo.attr(node, 'id')).length){
m.canDrop(true);
return;
}
3) Extended onDndDrop to remove the added class to re display the hidden elements.
onDndDrop: function(source, nodes, copy, target)
{
this.inherited(arguments);
dojo.forEach(dojo.query('.hiddenSource'),
function(el){dojo.removeClass(el, 'hiddenSource');}
);
}
4) Extend onDndCancel to do the above
onDndCanel: function()
{
this.inherited(arguments);
dojo.forEach(dojo.query('.hiddenSource'),
function(el){dojo.removeClass(el, 'hiddenSource');}
);
}
This isn't the best solution since it hides the elements that can't be used with the current element that you are positioning, but it worked for me.