Create tree in angular-grid (ag-grid) with async data loading
Asked Answered
V

3

6

I am trying to use angular-grid (ag-grid) to display a tree like in the example provided in the documentation:

http://www.angulargrid.com/example-file-browser/index.php

In the given example, all the data is already provided. How do I use async data loading when a row group is expanded? My guess is that i need to write my own group row renderer.

Valverde answered 27/8, 2015 at 6:36 Comment(0)
S
4

The grid doesn't support lazy loading of the tree data out of the box. So yes you would have to write your own cellRenderer to achieve this.

PS I'm the author of ag-Grid, so you can take this answer as Gospel!

Stine answered 2/9, 2015 at 20:35 Comment(6)
Hey Niall, if I'm not asking for too much, could you please elaborate a bit? Since writing your own groupRowRenderer will throw away so much standard and useful behaviour (expanding, collapsing and all that) I was thinking whether there's a less invasive way of doing it. For example, would it be possible to make the ajax calls onRowExpand? or the fact that the node will have no children (at the time of click / attempted expansion) will not allow "onRowExpand" to be called?Splotch
Also, wouldn't this have an impact on the virtualization feature?Splotch
don't see how it would impact row virtualisation. if the group is not expanded, the rows would not be considered for rendering so no reason for them to be included in the virtualisation.Stine
asking about the 'dont throw away for default', what you could do is copy it from github and use that as a starting point for yourself? or if you are asking me if i could extend what i have, yes i could, and i would like to, it's just not at the top of my list right now.Stine
I've played a bit with the tree demo. I'm not asking for a feature here, i'm just trying to gather some data to see whether or not ag-grid is the way to go for the product i'm working on. You're saying that if i'm to make my own group renderer, that would not impact the virtualisation (if I have like 2000 leaves on an expanded node)? And you're saying that writing my own groupRowRenderer is the way to go for this scenario?Splotch
Does Angular 2 has support for Tree data?Balikpapan
C
4

I came recently to the same problem in my React.js app and found solution. It's similar to what @leden posted but I found solution how to maintain current row expansions between table rows update.

The solution is as follow:

  1. Add dummy child row for each top-level row. Can be empty or can have loading... string for example in first column.

  2. On event getNodeChildDetails, which is called each time you update your table rowData, you can specify if a row should be expanded or not. So the idea is that we keep track of what is expanded and what is not.

    getNodeChildDetails = (rowItem) => {
      if (rowItem.children) {
        return {
          group: true,
          expanded: rowItem.id in this.expandedRows,
          children: rowItem.children,
        };
      }
      else {
        return null;
      }
    };
    
  3. On event rowGroupOpened we keep track which rows are expanded.

    rowGroupOpened = (param) => {
      const id= param.node.data.id;
    
      if(!param.node.expanded) {
        delete this.expandedRows[id];
        return;
      }
    
      this.expandedRows[id] = true;
    
      if (param.node.data.children.length !== 1) { // Here we need to check if only dummy row is present
          return;
        }
    
        this.api.showLoadingOverlay();
    
        // Here I simulate fetching data from server
        setTimeout(() => {
          this.rowData.forEach((e) => {
            if (e.id == id) {
              e.children = [
                // Add  fetch rows
              ]
            }
          });
    
          this.api.setRowData(this.rowData); // Setting data, will trigger getNodeChildDetails call on each row
          this.api.hideOverlay();
        }, 1000);
      };
    
Commination answered 16/2, 2017 at 12:2 Comment(2)
what object is this.expandedRows?Bulger
It can be any collection where you map id to boolean (object, map, dictionary).Commination
E
0

Just an idea, but I think that you could add a single placeholder child row to the group with "loading..." in the first cell, with the group's onRowGroupOpened event set to make the ajax call to get the data from the server, with the onreadystatechange then adding the new rows and replacing the placeholder one. The initial placeholder row can contain server-calculated total values to drive aggregation (total) values in the group row's cells, which would remain the same when real data replaces the placeholder.

I have come up with a basic test of the approach. It's not perfect, as the grid rebuilds after each expansion (I can't find an elegant way to just append the new rows), but it does work.

At the very top of the script is the AJAX call for detail. Although this happens later in the flow I put it at the top, so that if the server receives this request, it provides data and exits, without loading the page again. Alternatively you could just put it into another file.

<?php
if (isset($_REQUEST['g'])) { // this is the AJAX request for child data (called later, but needed at the start of the script)
    // get connection to database
    require_once 'db_connection.php'; $dbh=getConnection();
    // query data to array
    $sql="SELECT accounts.description AS account, '' AS info, 
          tx.amnt AS amount, 1 AS transactions
          FROM tx 
          INNER JOIN accounts ON tx.account=accounts.account_id
          WHERE accounts.description='".$_REQUEST['g']."'";
    $data=array();
    $result = $dbh->query($sql);
    while ($row = $result->fetch_assoc()) {
        $data[]=$row;
    }
    $result->free();
    // return data as JSON
    print json_encode($data, JSON_NUMERIC_CHECK);
    exit;
}
?>

Then immediately after that comes a normal HTML page with a little bit more php within the javascript in the head:

<!DOCTYPE html>
<html>
<head>
<script src="lib/ag-grid-enterprise-master/dist/ag-grid-enterprise.js"></script>
<script>
// get JSON for initial group-level data from server with a little snippet of php which is called when the page is first loaded
var rowData =
<?php
    // get connection to the database
    require_once 'db_connection.php'; $dbh=getConnection();
    // query data to array
    $sql = "SELECT description AS account, 'loading...' AS info,
            SUM(tx.amnt) AS amount, COUNT(tx.tx_id) AS transactions
            FROM accounts 
            INNER JOIN tx ON accounts.account_id=tx.account
            GROUP BY accounts.account_id";
    $data=array();
    $result = $dbh->query($sql);
    while ($row = $result->fetch_assoc()) {
        $data[]=$row;
    }
    $result->free();
    // inject the JSON into the javascript assignment to rowData
    print json_encode($data, JSON_NUMERIC_CHECK);
?>;
// (back in javascript again)

// event function for when a group is expanded
function getChildRows(data) {
    if (data.node.allLeafChildren) {
        if (data.node.allLeafChildren.length > 0) {
            if (data.node.allLeafChildren[0].data.info==="loading...") {
                // data for this group has not yet been loaded, so make AJAX request for it
                var xmlHttp=new XMLHttpRequest();
                xmlHttp.onreadystatechange=function() {
                    if ((xmlHttp.readyState===4) && (xmlHttp.status === 200)) {
                        // call function to add the new rows to the grid
                        addRecords(JSON.parse(xmlHttp.responseText));
                    }
                };
                var requestParameters="g="+encodeURIComponent(data.node.key);
                xmlHttp.open("POST", "index.php", true);    // call to this same script
                xmlHttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
                xmlHttp.send(requestParameters);
            }
        }
    }
}
function addRecords(data) {
    var x; var d=new Array();
    var acc=data[0].account;
    for(x in gridOptions.api.inMemoryRowModel.rootNode.allLeafChildren) {
        if (gridOptions.api.inMemoryRowModel.rootNode.allLeafChildren[x].data.account===acc) {
            // this is group we are replacing with new data
            for (x in data) {
                d.push(data[x]);
            }
        } else {
            // this node is just the data as currently loaded to the grid (no change)
            d.push(gridOptions.api.inMemoryRowModel.rootNode.allLeafChildren[x].data);
        }
    }
    gridOptions.api.setRowData(d);
}
// set up the grid (standard stuff)
var columnDefs = [
    {headerName: "Account", field: "account", rowGroupIndex: 0, cellRenderer: "group", cellRendererParams : {suppressCount: true} },
    {headerName: "Info", field: "info"},
    {headerName: "Amount", field: "amount", aggFunc:"sum"},
    {headerName: "Transactions", field: "transactions", aggFunc:"sum"}
];
var gridOptions = {
    columnDefs: columnDefs,
    rowData: rowData,
    groupSuppressAutoColumn: true,
    onRowGroupOpened: getChildRows  /* event created above */
}
document.addEventListener("DOMContentLoaded", function() {
    var eGridDiv = document.querySelector('#myGrid');
    new agGrid.Grid(eGridDiv, gridOptions);
});
</script>
</head>
<body>
    <div id="myGrid" style="height: 100%;" class="ag-fresh"></div>
</body>
</html>

@Niall - any ideas on how to add the new rows more elegantly and retain status of group expansion?

Eire answered 29/5, 2016 at 19:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.