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>
Related
I have been working with React Query and React Table recently to achieve a multi-level table structure with expandable rows, where each row should do lazy loading when clicked on. It's for product group hierarchy, which looks like this:
unit-1 (clicking fetches category data lazily)
category-1 (clicking fetches product data lazily)
product-1
product-2
category-2
product-3
unit-2
category-3
product-4
product-5
I have two options for the solution:
Sub components lazy load example for React Table (https://react-table.tanstack.com/docs/examples/sub-components-lazy)
This focuses on only one level of depth, so I am not sure how to make it multi-level due to the fact that I am still not very familiar with the React Table API and it seems like the example was not created with that purpose. I tried making it multi-level but I had difficulty in passing some row properties around like (.isExpanded, .id, etc)
A post about manipulating the data by each row click so that rest is handled automatically by the table (https://nafeu.medium.com/lazy-loading-expandable-rows-with-react-table-cd2fc86b0630 - GitHub ex: https://github.com/nafeu/react-query-table-sandbox/blob/main/src/ReactTableExpanding.jsx)
This seems to be a more declarative way to handle things. (We fetch new data, merge it with the existing data and the rest is handled) but I have some concerns like if updating the whole table data causes unnecessary renders. Another thing is since we don't render a sub-component here as in the first one, the API endpoint should be arranged to work with a click event. It's not like having a sub-component that is responsible for fetching its own data when it's mounted.
As a note for the table example above, I was planning to use a single API endpoint that takes params like:
?level=root&id=0 (initial units list data for the table on page load)
?level=unit&id=2 (returns categories of unit-2)
...
But this logic doesn't seem enough for supporting fetching on row click so I guess I'll need at least two endpoints if I follow the second approach.
Thanks for reading, and any ideas are welcome!
Have a nice day.
As an update, I could make things work mostly with the second option above. As a note on the API endpoints, I could use the same API endpoint and the same hook twice with different parameters. (this part is a bit project specific of course but maybe it gives an idea to the ones having similar scenarios)
To get the initial data for the table, I give hardcoded parameters to the hook, so that the table is rendered with the initialData on page load.
const { data: initialData, isLoading } = useGetLevels(
{
level: 'root',
id: [0],
...
}
);
In the second one, I use enabled: false, and when a row is clicked on I call refetch(), so additional data is fetched and merged with the initial data to update the table and the row is expanded accordingly:
const {
data: partialData,
refetch,
} = useGetLevels(
{
level,
id,
...
},
false
);
and the useGetLevels look like this:
function useGetLevels(
filterParams,
enabled
) {
return useQuery(
['levels', filterParams],
() => fetchLevels(filterParams),
{
enabled,
}
);
}
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.
I am using a window view in Enyo, which basically fetches data from database, and based on the no. items fetched, multiple buttons are created dyanamically. On click of any of the button, an another call to database is made to fetch other set of items. The fetched items need to be added dyanamically to a <ul> item as buttons. Which is done by the code -
testPOSView : function(inSender, inEvent) {
var data = inEvent.data;
console.log(data.tables);
enyo.forEach(data.tables, function(table) {
console.log(table);
this.$.sectiontablebar.createComponent({
kind : 'OB.OBPOSPointOfSale.UI.TablesButton',
button : {
kind : 'OB.UI.Section',
content: table.tableName,
id: table.tableId
}
});
}, this);
}
But when i click on the button, i am getting the results from DB, but they are not added to the sectiontablebar component.
The complete code of the file is available # https://gist.github.com/sangramanand/ad665db9cd438001254a
Any help would be appreciated. Thanks!!!
I'm not certain this is the way to do it, but when I create subcomponents dynamically, I add this.render() to the end of the function. This renders the component, thus showing the dynamically added content.
If I were to rewrite your code I would do it like this:
testPOSView : function(inSender, inEvent) {
var data = inEvent.data;
enyo.forEach(data.tables, function(table) {
// create the sub-component in "this"
this.createComponent({
// and assign the container
container: this.$.sectiontablebar,
kind : 'OB.OBPOSPointOfSale.UI.TablesButton',
button : {
kind : 'OB.UI.Section',
content: table.tableName,
id: table.tableId
}
});
}, this);
this.render();
}
More than likely you are losing the pointer to this when testPOSView is running after an async call to get the db results.
In your anythingTap function (see gist, readers) you might try binding the function you're sending to OB.DS.Process:
this.bindSafely(function(data) {me.doTestPOSView();}))
By the way, you can probably eliminate all that me = this tomfoolery if you bind your functions properly.
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
I have a page with a dijit.Tree, and a dojox.grid.EnhancedGrid both hooked up to some hierarchical data in an ItemFileWriteStore. When the user clicks on an item in the tree, I want to be able to show only the immediate children of that item in the grid, along with their attributes. This is a rather common pattern in database applications, but I can't find any examples of this, or perhaps I'm looking in the wrong place.
Looking at the grid docs, I see a setQuery method on the DataGrid. However, looking at the query syntax for ItemFileReadStore, I don't see anything that would let me specify to fetch only the children of a given item. Is there something I'm missing, is there another way to do this?
Using dojo 1.5.
(Edited for clarity)
Well, since no one answered, I came up with my own solution. I figured that this should be something that should have been built in to the DataStore framework, so I found an appropriate method in ItemFileReadStore to hook into, and extended it to add some query options to allow detail queries.
The following code adds two available QueryOptions arguments (parentItem, parentAttribute) which specify a parent item and a parent attribute for detail drill-down queries. They aren't compatible with the 'deep' option as the expected result of a combination of those two isn't clear.
dojo.extend(dojo.data.ItemFileReadStore, {
_getItemsArray: function(/*object?*/queryOptions) {
if (queryOptions) {
if (queryOptions.deep && queryOptions.parentItem) {
throw "Invalid query: a drill-down search can not be 'deep'"
}
if (queryOptions.deep) {
return this._arrayOfAllItems;
}
if (queryOptions.parentItem) {
if (!queryOptions.parentAttribute) {
throw "Invalid query: an attribute is required for drill-down searches.";
}
return this.getValues(queryOptions.parentItem,queryOptions.parentAttribute);
}
}
return this._arrayOfTopLevelItems;
}
});
The above code is available for anyone to use.