I am using jsTree to display my database hierarchy categories like interactive tree. Initial load is done with JSON, and by default some categories are checked. jsTree knows which categories are selected because it shows them as checked (I am using checkbox plugin), but I want to be able to open that checked nodes (and all their parents) on tree load.
I need that because, when user open a page with tree he is not aware that there might be some categories selected without expanding whole tree, and I would like to expand only those nodes that are checked.
This is my code so far:
var tree = $('.tree').bind('loaded.jstree', function (e, data) {
// I assume that logic that expand checked nodes must be placed here, after tree is loaded
})
.jstree({
// Configure JSON data plugin
'json_data': {
'data': [<this is initial json data>]
},
'checkbox': {
'override_ui': true,
'two_state': true,
'real_checkboxes': true
},
// Specify which plugins to load
'plugins': ['themes', 'json_data', 'ui', 'checkbox']
});
Thanks!
You should look at the state plugin,
You will have to set the state at the checked nodes to open.
In the plugin array add "state".
In the json of the tree, after the data of each node, add an object like so:
state: {
opened : true
}
I don't think the state plugin is required. It saves the state of the tree and reopens the tree to that state when it is next encountered.
Importantly though, #alostr is correct that if you set the data of your node to state: { opened : true } it will open that node after it is displayed.
Related
I'm using ag-grid (free) with Angular 1 and I've already gotten my tree data to display as desired, where the children of a node are in the column to the right of it. However, what I want to do is collapse or expand nodes on double click. Right now just focusing on getting them to collapse since my default view is set to expand. here's my code for the double click event, given within $scope.gridOptions:
onCellDoubleClicked: function(event){
event.node.expanded = false;
$scope.gridOptions.api.refreshView();
};
My assumption was that changing the expanded property to false would cause the refreshView call to re-render the grid with child nodes collapsed, but the view is unchanged after the double click.
Also, my getChildNodeDetails within gridOptions:
getNodeChildDetails: function(obj){
if (obj.children){
var nodeType = obj.breakdownCol;
return {
group: true,
expanded: obj.expanded || true,
children: obj.children,
field: 'name',
key: obj[nodeType]
}
} else {
return null;
}
}
Any ideas on how I might fix this without buying enterprise? I know that in enterprise you can group the rows and this comes with build in expand/collapse functionality.
In my own application I created a work around that simulates the row grouping feature. What it really does is adds and removes the data from the grid.
One drawback that this option has is that since the rows aren't actually in the table any filtering or sorting on columns can't actually take place on data that isn't shown, unlike the actual enterprise feature that the grid offers. However if you have disabled filtering and sorting then this option is perfectly viable.
Something like this:
function toggleExpansion(index, data) {
if (insert) {
gridOptions.api.insertItemsAtIndex(index, data);
} else {
gridOptions.api.removeItems(data)
}
}
My specific code goes into more checks and other things unrelated to this question but that is the simple explanation of what I am doing as a work around.
I am using React, but you could probably do something similar with Angular:
function expandAll(expand) {
agGridRef.current.api.forEachNode((node) => {
node.setExpanded(expand);
});
}
where the agGridRef is a reference to the component:
<AgGridReact
ref={agGridRef}
.
.
.
</AgGridReact>
I'm trying to show a Kendo TreeView configured with remote data, but prefill the first two levels with data loaded directly from the html.
For that, I want to use the pushCreate method of the Kendo dataSource to add the initial elements to the tree:
homogeneous = new kendo.data.HierarchicalDataSource({
transport: { read: { url: serviceRoot + "/Employees", dataType: "jsonp" } },
schema: {
model: {
id: "EmployeeId",
hasChildren: "HasEmployees"
}
}
});
// Adding root
homogeneous.pushCreate({"EmployeeId":2,"FullName":"Andrew Fuller","HasEmployees":true,"ReportsTo":null});
// Adding children
homogeneous.pushCreate([
{"EmployeeId":1,"FullName":"Nancy Davolio","HasEmployees":false,"ReportsTo":2},
{"EmployeeId":3,"FullName":"Janet Leverling","HasEmployees":false,"ReportsTo":2},
{"EmployeeId":4,"FullName":"Margaret Peacock","HasEmployees":false,"ReportsTo":2},
{"EmployeeId":5,"FullName":"Steven Buchanan","HasEmployees":true,"ReportsTo":2},
{"EmployeeId":8,"FullName":"Laura Callahan","HasEmployees":false,"ReportsTo":2}
]);
$("#treeview").kendoTreeView({
dataSource: homogeneous,
dataTextField: "FullName"
});
However, the children elements are added to the root level (you can see this running here).
There is a schema.model.children configuration to set the property that holds the children inside the root element and I can add all in one go, but if I use that then the dataSource stops working with remote data (you can see that running here).
So the question is, is there a way to use pushCreate to add elements as children of another one on a HierarchicalDataSource?
I found the answer while writting the question :)
It seems like if I simply use items as the key for the children, I don't need to specify the schema.model.children option so the tree still works with remote data:
homogeneous.pushCreate(
{"EmployeeId":2,"FullName":"Andrew Fuller","HasEmployees":true,"ReportsTo":null,
"items": [
{"EmployeeId":1,"FullName":"Nancy Davolio","HasEmployees":false,"ReportsTo":2},
{"EmployeeId":3,"FullName":"Janet Leverling","HasEmployees":false,"ReportsTo":2},
{"EmployeeId":4,"FullName":"Margaret Peacock","HasEmployees":false,"ReportsTo":2},
{"EmployeeId":5,"FullName":"Steven Buchanan","HasEmployees":true,"ReportsTo":2},
{"EmployeeId":8,"FullName":"Laura Callahan","HasEmployees":false,"ReportsTo":2}
]
}
);
Here's the working example.
I don't know if that's and unintended feature but works for adding a whole subtree to the root (which solves my problem). However the question still stands, can I use pushCreate to add a child element to another one?
You can do this by appending the node element with append. See this example by telerik: http://dojo.telerik.com/AjIti/32
I have a jsTree which I am trying to bi-directionally "connect" to a Meteor collection. Right now I automatically trigger a jsTree.refresh() whenever the collection updates with the help of .observeChanges:
FileTree.find().observeChanges({
added: function() {
$.jstree.reference('#fileTree').refresh();
},
changed: function() {
$.jstree.reference('#fileTree').refresh();
},
removed: function() {
$.jstree.reference('#fileTree').refresh();
}
});
I want to allow editing of the database by dragging things around in jsTree. Here's how it would look:
User drags element to new location.
jsTree updates the location of the element in the tree.
jsTree calls event handler
Event handler updates database
My problem is, if I understand everything correctly, is that the database update would trigger the observeChanges event that I set up earlier. That would cause yet another refresh of the tree. That causes an annoying flicker which would interrupt users. (i.e. the file browser would be unusable for about 0.75s after every drag/drop operation.)
How can I prevent this unneeded update, or is there a better way to structure my interface with jsTree that would prevent this problem.
Have you seen this?
https://github.com/tobkle/meteor-jstree-example
https://github.com/tobkle/meteor-jstree
It looks like he/she is using autorun vs. observeChanges but is mostly the same concept as yours:
Template.showtree.rendered = function(){
var self = this;
var nodes = [];
Nodes1 = $('#tree1').tree();
Nodes2 = $('#tree2').tree();
if (!self.handle){
self.handle = Meteor.autorun(function(){
// refresh trees on new data
nodes = Nodes.find();
Nodes1.tree('option', 'data', 'Projects');
Nodes2.tree('option', 'data', 'Contexts');
}); //self.handle
} // if (!self.handle)
}
If this is something only a single user would be editing (at a time) then perhaps you simply don't refresh it based on DB updates, and leave the tree as an interface to author, only.
If it needs to refresh based on DB updates (from other users, or just to be in sync), you might consider changing your workflow/logic:
the changed.jstree event, updates the database locally, blocking
the autorun triggers the refresh in jstree
which should, in theory, only result in 1 refresh vs. 2 or 3
Or if you really want to limit re-draws... you could find the old parent node and the new parent node and only us refresh_node(obj) to refresh just those changes nodes:
refresh_node (obj)
refreshes a node in the tree (reload its children) all opened nodes
inside that node are reloaded with calls to load_node.
My php code creates a hierarchical json dataset for the hierarchicalDataSource used by a treeview.
In this php generating function, i set the very first leaf as selected=true... so when the treeview appears, the first leaf is automatically selected.
The problem is that when the user clicks any leaf, an event (onSelect) is triggered but it is not triggered for this automatic selection of the very first node that occurs when the treeview appears at UI creation time.
How can i fix this ?
UPDATE:
Made a Demo: http://jsbin.com/abapid/2/edit
If you want to programmatically trigger a select event, you should do:
$("#treeview").data("kendoTreeView").trigger("select");
For the benefits of others.... and thanks to OnaBai !... Here is the solution !
Kendo UI lacks many basic features other UI SDK always provide. One of them is an "onDisplay" kind of even that gets triggered once the widget gets painted. This would allow the application to react to specific case like in mine where the dataSource loaded a node containing a "selected = true" field.
The Kendo TreeView reacts by showing the node as selected but in most real world scenarios, the application would also need to react. That is why it would need to be called upon widget initialization to check if a node is selected and react accordingly.
The only hack we (OnaBai) found is to use the "DataBound" event as an "onDisplay" kind of event. The hic is that this event is fired each time a parent node as a child that got modified somehow. So it is called multiple times.
So here is the code to go around this limitation!
http://jsbin.com/ejabul/4/edit (Note that you must click the "Run with JS" to simulate a page reload)
$("#treeview").kendoTreeView({
dataSource:data,
dataTextField: "text",
select: onSelect,
dataBound: function (e) {
var uid = undefined;
var now = this.select();
if (now) {
var data = this.dataItem(now);
uid = data.uid;
if (uid && uid !== this.old_selected) {
alert("Bingo !");
}
console.log("data", data.uid);
}
this.old_selected = uid;
}
});
Explanation by OnaBai
Store in uid the Unique ID of the selected item. This ID is introduced by Kendo UI for most node, items, … that it creates. Initially I set it to undefined (just in case there is nothing selected)
"now" contains current selected node (if any).
If there is an element selected (now !== undefined) then I get the data item for the selected node and then get the UID for this node.
If there is a UID and if different than the UID of the previous selected node (that I store in a field that I just extended in the tree_view and I called old_selected) then I alert.
finally I save the UID of the selected node for the next time.
Basically what I try is to control that I do not alert two consecutive times for the same node and for controlling it I save the UID of the selected node from one iteration to the next.
I have a lazy loading dijit.tree which I want to reuse in many places after its data has been loaded. But if I just replace the store object in the other trees with the one which contains most data, the nodes come all expanded. I want to modify the store so that all the items are collapsed before setting it as a store in the new tree. can you tell me how to achieve this?
You cannot use the store for this, as it does not contain any information of the state of the tree nodes. This 'magic' is performed via TreeNode, see some examples here
The 'perfect solution' would be to figure out which paths you need expanded and then set the path of your tree to traverse into the wanted treenodes.
However, since your lazyloading, you need to check the state - while initializing a tree it should be UNCHECKED. However there is a cookie-functionality inbuilt which probably is kicking in, make sure to create new tree's with { persist:false }
You could also extend your tree, so that it will accept collapseChildren(TreeNode) as follows - and then call tree.set("path", [pathsArray]);
collapseChildren : function(top) {
var self = this;
if(!top || !self.model.mayHaveChildren(top.item)) return;
top.getChildren().forEach(function(n) {
if(n.item.children) {
//dojo.style(n.getParent().containerNode, { overflowY: 'hidden', overflowX: 'hidden'});
self._collapseNode(n);
self.collapseChildren(n);
}
});
},
EDIT:
If the autoExpand flag is passed to the constructor, the Tree is initially shown with all nodes expanded.
You can then call collapseAll() and expandAll() to collapse and expand the Tree, respectively.
http://livedocs.dojotoolkit.org/dijit/Tree-examples#id3