Search code examples
javascripthtmltabulator

Refresh group header to show sum of values in group


In Tabulator, I have a group header that shows calculated data, the grouping itself doesn't depend on this calculated data, I need to update a cell which is not used by the grouping and refresh the group.

Consider this example, In the group header I'm showing the sum of hours by group, update the cell "hours", and the header will not update because the hours column is not being used by the grouping function, how can I refresh the header?

   
var myTable = null;
$(() => {
    var tableDiv = document.querySelector("#tableDiv");
    console.log(tableDiv);
    var tableData = [
        { id: 1, name: "Oli Bob", age: "12", hours: 1 },
        { id: 2, name: "Mary May", age: "12", hours: 2 },
        { id: 3, name: "Christine Lobowski", age: "42", hours: 3 },
        { id: 4, name: "Brendon Philips", age: "125", hours: 5 },
        { id: 5, name: "Margret Marmajuke", age: "16", hours: 1 },
        { id: 6, name: "Oli Bob", age: "12", hours: 1 },
        { id: 7, name: "Mary May", age: "12", hours: 2 },
        { id: 8, name: "Christine Lobowski", age: "42", hours: 4 },
        { id: 9, name: "Brendon Philips", age: "125", hours: 5 },
        { id: 10, name: "Margret Marmajuke", age: "16", hours: 2 },

    ];

    for (i = 11; i <= 200; i++) {
        tableData.push(
            { id: i, name: window.crypto.randomUUID(), age: i, hours: i }
        );
    }

    var columnConfig = [
        { title: "Name", field: "name", width: 150 },
        { title: "Age", field: "age", hozAlign: "left", editor: "input" },
        { title: "Hours", field: "hours", width: 150, editor: "number" },
    ];

    myTable = new Tabulator(tableDiv, {
        data: tableData,
        columns: columnConfig,
        //height: "200px",
        groupBy: function (data) {
            //data - the data object for the row being grouped
            return data.age; //groups by age
        },
        groupHeader: function (value, count, data, group) {
            //value - the value all members of this group share
            //count - the number of rows in this group
            //data - an array of all the row data objects in this group
            //group - the group component for the group

            return `Age: ${value} - Hours: ${data.map(i => i.hours).reduce((a, b) => a + b)}`;
        },
        groupUpdateOnCellEdit: true,
        maxHeight: "100%"


    });


    myTable.on('cellEdited', (c) => {
      //what can I do here?
    });



})
div.example-table {
    height: 400px;  
  }
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <!-- <link href="https://unpkg.com/[email protected]/dist/css/tabulator.min.css" rel="stylesheet">
    <script type="text/javascript" src="https://unpkg.com/[email protected]/dist/js/tabulator.min.js"></script> -->

    <link href="https://unpkg.com/[email protected]/dist/css/tabulator.min.css" rel="stylesheet">
    <script type="text/javascript" src="https://unpkg.com/[email protected]/dist/js/tabulator.min.js"></script>

    <script src="https://code.jquery.com/jquery-3.7.1.min.js"
        integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo=" crossorigin="anonymous"></script>
    <script type="text/javascript" src="./index.js"></script>
    <link href="./index.css" rel="stylesheet">
    <title>Simple HTML Page</title>
    <style>

    </style>
</head>

<body>
    
        <div id="tableDiv" class="example-table"></div>
    
</body>

</html>

I can use the cellEdited callback to update the group, but how?

        myTable.on('cellEdited', (c) => {
          //what can I do here?
        });

Things I have tried:

Redraw the table

        myTable.on('cellEdited', (c) => {
          myTable.redraw(); //does nothing
        });

Redraw full

        myTable.on('cellEdited', (c) => {
          myTable.redraw(true); //works but the vertical scroll jumps to the top
        });

refreshActiveData

        myTable.on('cellEdited', (c) => {
          myTable.rowManager.refreshActiveData(); //works but the vertical scroll jumps to the top
        });

If I remove the height of the table the scroll problem goes away, but I lose the frozen column headers

What can be done?


Solution

  • If you can live without using the VirtualDOM, you can track the scrollTop position of the tabulator-holder div to get back to the same scroll position after cell is edited. First, you would move the groupHeader function out (gHeader in my example below). In the cellEditing event, you would get the initial scrollTop position and assign it to a variable, then in the cellEdited event, you would re-set the group header using myTable.setGroupHeader(gHeader) (myTable.redraw(true) also works but will be slower) and then restore the scrollTop position.

    I would be curious to see is there is a better way as well since this is a bit hacky but works. Here is the example:

    var myTable = null;
    $(() => {
        var tableDiv = document.querySelector("#tableDiv");
        var tableData = [
            { id: 1, name: "Oli Bob", age: "12", hours: 1 },
            { id: 2, name: "Mary May", age: "12", hours: 2 },
            { id: 3, name: "Christine Lobowski", age: "42", hours: 3 },
            { id: 4, name: "Brendon Philips", age: "125", hours: 5 },
            { id: 5, name: "Margret Marmajuke", age: "16", hours: 1 },
            { id: 6, name: "Oli Bob", age: "12", hours: 1 },
            { id: 7, name: "Mary May", age: "12", hours: 2 },
            { id: 8, name: "Christine Lobowski", age: "42", hours: 4 },
            { id: 9, name: "Brendon Philips", age: "125", hours: 5 },
            { id: 10, name: "Margret Marmajuke", age: "16", hours: 2 },
    
        ];
    
        for (i = 11; i <= 200; i++) {
            tableData.push(
                { id: i, name: window.crypto.randomUUID(), age: i, hours: i }
            );
        }
    
        var columnConfig = [
            { title: "Name", field: "name", width: 150 },
            { title: "Age", field: "age", hozAlign: "left", editor: "input" },
            { title: "Hours", field: "hours", width: 150, editor: "number" },
        ];
    
        const gHeader = (value, count, data, group) => {
          return `Age: ${value} - Hours: ${data.map((i) => i.hours).reduce((a, b) => a + b)}`
        }
    
        myTable = new Tabulator(tableDiv, {
          data: tableData,
          columns: columnConfig,
          height: '300px',
          groupBy: function (data) {
            //data - the data object for the row being grouped
            return data.age //groups by age
          },
          groupHeader: gHeader,
          groupUpdateOnCellEdit: true,
          renderVertical: 'basic'
        })
    
        let top
    
        myTable.on('cellEditing', (c) => {
          top = document.querySelector('.tabulator-tableholder').scrollTop
        })
    
        myTable.on('cellEdited', (c) => {
          myTable.setGroupHeader(gHeader)
    
          document.querySelector('.tabulator-tableholder').scrollTop = top
        })
    })
    <!DOCTYPE html>
    <html lang="en">
    
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <!-- <link href="https://unpkg.com/[email protected]/dist/css/tabulator.min.css" rel="stylesheet">
        <script type="text/javascript" src="https://unpkg.com/[email protected]/dist/js/tabulator.min.js"></script> -->
    
      <link href="https://unpkg.com/[email protected]/dist/css/tabulator.min.css" rel="stylesheet">
      <script type="text/javascript" src="https://unpkg.com/[email protected]/dist/js/tabulator.min.js"></script>
    
      <script src="https://code.jquery.com/jquery-3.7.1.min.js" integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo=" crossorigin="anonymous"></script>
      <script type="text/javascript" src="./index.js"></script>
      <link href="./index.css" rel="stylesheet">
      <title>Simple HTML Page</title>
      <style>
    
      </style>
    </head>
    
    <body>
    
      <div id="tableDiv" class="example-table"></div>
    
    </body>
    
    </html>