cmill
cmill

Reputation: 901

Google Charts Event listener - first click after filter fails

I have a table which behaves correctly until I use the Category Picker.

Correct Behavior:

  1. Filter for a "Gender"
  2. Click a row in the table - receive a result
  3. Sort any column like gender in the table
  4. Click on a row in the table - receive a result

Problematic Behavior: I would like to be able to select a row and receive a result without having to sort the gender column first.

  1. Filter for a "Gender"
  2. Click on a row in the table - FAIL - No results returned
  3. Sort any column like gender in the table
  4. Click on a row in the table - SUCCESS - you now get a result

How can I get success without having to sort a column first?

Grazie mille!

   google.charts.load('current', {
        'packages': ['corechart', 'table', 'gauge', 'controls', 'charteditor']
    });

    $(document).ready(function () {
        //console.log("ready!");
        renderChart_onPageLoad();
    });

    function renderChart_onPageLoad() {
        google.charts.setOnLoadCallback(function () {
            //console.log("renderChart_onPageLoad");
            drawDashboard();
        });
    }

    function drawDashboard() {
        //console.log("drawDashboard");

        var data = google.visualization.arrayToDataTable([
            ['Name', 'RoolNumber', 'Gender', 'Age', 'DonutsEaten'],
            ['Michael', 1, 'Male', 12, 5],
            ['Elisa', 2, 'Female', 20, 7],
            ['Robert', 3, 'Male', 7, 3],
            ['John', 4, 'Male', 54, 2],
            ['Jessica', 5, 'Female', 22, 6],
            ['Aaron', 6, 'Male', 3, 1],
            ['Margareth', 7, 'Female', 42, 8],
            ['Miranda', 8, 'Female', 33, 6]
        ]);

        var dashboard = new google.visualization.Dashboard(document.getElementById('dashboard'));

        var categoryPicker = new google.visualization.ControlWrapper({
            controlType: 'CategoryFilter',
            containerId: 'categoryPicker',
            options: {
                filterColumnLabel: 'Gender',
                ui: {
                    labelStacking: 'vertical',
                    allowTyping: false,
                    allowMultiple: false
                }
            }
        });

        var proxyTable = new google.visualization.ChartWrapper({
            chartType: 'Table',
            containerId: 'div_proxyTable',
            options: {
                width: '500px'
            }
        });

        var table = new google.visualization.ChartWrapper({
            chartType: 'Table',
            containerId: 'div_table',
            options: {
                sort: 'event', // <-- set sort to 'event' for table totaling
                width: '500px',
                allowHtml: true,
                page: 'enable',
                pageSize: '5',
            }
        });

        //This json contains my settings for later
        let json = {
            "tableChart": {
                "hasTable": true,
                "dataView": {
                    "columns": [
                        { "id": "Name" },
                        { "id": "RoolNumber" },
                        { "id": "Gender" },
                        { "id": "Age" },
                        { "id": "DonutsEaten" }
                    ]
                },
                "groupView": {
                    "hasGroupView": false                    
                },
                "totalRow": {
                    "hasTotalRow": true,
                    "labelCol": "Total",
                    "labelColIndex": 0,//This is the column where the words "Grand Total" are stored.  It must be text column.
                    "totalColumns": [
                        { "id": "RoolNumber", "type": "number", "function": "sum" }                       
                    ]
                },
                "conditionalFormat": {
                    "hasConditionalFormat": false
                },
                "options": {},
                "hasCSV": false,
                "clickGetFunc": null
            },
        };

        google.visualization.events.addOneTimeListener(proxyTable, 'ready', function () {
            console.log(".addOneTimeListener(proxyTable, 'ready' - sort");

            google.visualization.events.addOneTimeListener(table, 'ready', function () {
                console.log(".addOneTimeListener(table, 'ready' - sort");

                //#region table - sort: 'event'
                google.visualization.events.addListener(table.getChart(), 'sort', function (sender) {
                    console.log(".addListener(table.getChart(), 'sort' - sorted");

                    //// update table --> options must include (sort: 'event') for total row to work properly
                    //// update var grandTotal = tableData.getFilteredRows([{column: 0,
                    //// update .draw() 'table' references to new name if using for a different table variable

                    // sort data table according to sort properties
                    var tableData = table.getDataTable();
                    var sortIndexes = tableData.getSortedRows({
                        column: sender.column,
                        desc: !sender.ascending
                    });

                    //#region reposition total row - if required
                    let totalRow = (json.tableChart.totalRow !== undefined) ? json.tableChart.totalRow : [];
                    let hasTotalRow = totalRow.hasTotalRow;
                    if (hasTotalRow) {
                        // find grand total row
                        var grandTotal = tableData.getFilteredRows([{
                            column: json.tableChart.totalRow.labelColIndex, //must be placed in a column which is of type string.
                            value: json.tableChart.totalRow.labelCol
                        }]);
                        if (grandTotal.length > 0) {
                            // find grand total in sort
                            var grandTotalSort = sortIndexes.indexOf(grandTotal[0]);

                            // remove grand total from sort
                            sortIndexes.splice(grandTotalSort, 1);

                            // add grand total as first index
                            sortIndexes.unshift(grandTotal[0]);
                        }
                    }
                    //#endregion

                    // set table sort arrow
                    table.setOption('sortAscending', sender.ascending);
                    table.setOption('sortColumn', sender.column);

                    // set table view & re-draw table
                    table.setView({ rows: sortIndexes });
                    table.draw();

                    //Table ready then fires
                });
                //#endregion

            });
        });

        google.visualization.events.addOneTimeListener(proxyTable, 'ready', function () {
            console.log(".addOneTimeListener(proxyTable, 'ready' - select");

            google.visualization.events.addOneTimeListener(table, 'ready', function () {
                console.log(".addOneTimeListener(table, 'ready' - select");
                test_gcharts_selectedRowCol(table, json.tableChart.clickGetFunc);//(wrapperName, callback)
            });
        });

        google.visualization.events.addListener(proxyTable, 'ready', function () {
            console.log(".addListener(proxyTable, 'ready' - redrawTable()");
            redrawTable(json.tableChart);
        });

        dashboard.bind([categoryPicker], [proxyTable]);
        dashboard.draw(data);


        //This is a table builder which uses json from above.  This is working ok.  Contains no listeners.
        function redrawTable(tableChart) {
            console.log('redrawTable()');

            // set defaults for any undefined settings
            let dataView = (tableChart.dataView !== undefined) ? tableChart.dataView : [];
            let groupView = (tableChart.groupView !== undefined) ? tableChart.groupView : [];
            let totalRow = (tableChart.totalRow !== undefined) ? tableChart.totalRow : [];
            let conditionalFormat = (tableChart.conditionalFormat !== undefined) ? tableChart.conditionalFormat : [];

            // update .draw() 'table' or 'chart' references when using a different or additional chart name

            var sourceData = proxyTable.getDataTable().toDataTable().clone();
            //console.log('sourceData', sourceData);

            //#region create data view - this is used as basis for dataResults
            let view = new google.visualization.DataView(sourceData);

            //#region create group view - if required
            let dataResults_forTable;
            let hasGroupView = groupView.hasGroupView;
            if (hasGroupView) {

                // create keys for grouping
                const groupKeys = [];
                for (let i = 0; i < groupView.keys.length; i++) {
                    groupKeys.push(
                        groupKey_default(view, groupView.keys[i]),
                    );
                };
                // create columns for aggregating
                const groupColumns = [];
                for (let i = 0; i < groupView.columns.length; i++) {
                    groupColumns.push(
                        groupColumn_default(view, groupView.columns[i]),
                    );
                };

                // create data aggregation
                let group = google.visualization.data.group(view, groupKeys, groupColumns);
                //console.log('group'); console.log(group);

                dataResults_forTable = group.clone();
            }
            else {
                dataResults_forTable = view.toDataTable().clone();
            }//END if (hasGroupView) {
            //console.log('dataResults_forTable', dataResults_forTable);

            //#endregion

            //#region create total row - if required
            let hasTotalRow = totalRow.hasTotalRow;
            if (hasTotalRow) {

                let labelCol = totalRow.labelCol;
                let labelColIndex = totalRow.labelColIndex;
                let totalColumns = totalRow.totalColumns;

                //Create groupColumns for total row aggregation calculations
                const groupColumns = [];
                for (let i = 0; i < totalColumns.length; i++) {
                    const column = { column: dataResults_forTable.getColumnIndex(totalColumns[i].id), type: 'number' }
                    switch (totalColumns[i].function) {
                        case 'sum': column.aggregation = google.visualization.data.sum; break;
                        case 'count': column.aggregation = google.visualization.data.count; break;
                        case 'average': column.aggregation = google.visualization.data.avg; break;
                        case 'min': column.aggregation = google.visualization.data.min; break;
                        case 'max': column.aggregation = google.visualization.data.avg; break;
                        default: column.aggregation = google.visualization.data.sum;
                    }
                    groupColumns.push(column);
                }

                let groupTotal = google.visualization.data.group(dataResults_forTable,
                    // need key column to group on, so we want all rows grouped into 1, then it needs a constant value
                    [{ column: 0, type: "number", modifier: function () { return 1; } }], groupColumns);

                // this code block will run if the filter results in rows available to total.  Otherwise the table will present no rows.
                if (groupTotal.getNumberOfRows() !== 0) {

                    let formatDecimal = new google.visualization.NumberFormat({ pattern: '#,###.##' });
                    for (let i = 1; i < groupTotal.getNumberOfColumns(); i++) { formatDecimal.format(groupTotal, i); }

                    // create Grand Total row from colToTotal and groupTotal
                    const gtRow = [];
                    for (let i = 0; i < dataResults_forTable.getNumberOfColumns(); i++) {
                        //Build GT Row to match length of dataResults_forTable
                        gtRow.push(null);
                    }

                    //Push words "Grand Total" into it's set position in gtRow - It must go into a column of type string.
                    gtRow[labelColIndex] = labelCol;

                    for (let i = 0; i < totalColumns.length; i++) {
                        //Loop through groupColumns, test setting type for string.

                        if (totalColumns[i].type === 'string') {
                            //Convert to number from groupTotal result to string to match the column it's being pushed into
                            gtRow[dataResults_forTable.getColumnIndex(totalColumns[i].id)] = String(groupTotal.getValue(0, i + 1));
                        }
                        else {
                            //Otherwise push in the number value
                            gtRow[dataResults_forTable.getColumnIndex(totalColumns[i].id)] = groupTotal.getValue(0, i + 1);
                        }
                    }
                    //console.log('insertRow', insertRow);

                    // insert complete gtRow with values into row position 0
                    dataResults_forTable.insertRows(0, [gtRow]);

                    // add formatting for grand total row to highlight && justify to right if of type number
                    for (let j = 0; j < dataResults_forTable.getNumberOfColumns(); j++) {
                        //if statement on column type for left right justification
                        if (dataResults_forTable.getColumnType(j) === 'number') {
                            dataResults_forTable.setColumnProperty(j, 'className', 'googleTableTextRight');
                            dataResults_forTable.setProperty(0, j, 'className', 'googleTableTotalRow googleTableTextRight');//stored in css file
                        } else {
                            dataResults_forTable.setProperty(0, j, 'className', 'googleTableTotalRow');//stored in css file
                        }
                    }
                    //console.log('dataResults_forTable', dataResults_forTable);
                }//END (groupTotal.getNumberOfRows() !== 0) {
            }//END if (hasTotalRow) {
            //#endregion

            //#region conditional formatting - if required
            let hasConditionalFormat = conditionalFormat.hasConditionalFormat;
            if (hasConditionalFormat) {
                dataResults_forTable = conditionalFormatting_default(dataResults_forTable, conditionalFormat);
            }//END if (hasConditionalFormat) {
            //#endregion

            var finalView_forTable = new google.visualization.DataView(dataResults_forTable);
            //console.log('finalView_forTable', finalView_forTable);

            // set reset sorting, set dataTable & draw chart
            table.setView(null); // reset in case sorting has been used via user click
            table.setDataTable(finalView_forTable);//includes any total row
            table.draw();

        }//END redrawChart()

    }

    function test_gcharts_selectedRowCol(wrapperName, callback) {
        //console.log('wrapperName', wrapperName); console.log('callback', callback)

        //NEW - Works with paging active
        // initialize page number and size
        var page = 0;
        var pageSize = 10;
        if (wrapperName.getOption('page') === 'enable') {
            page = wrapperName.getOption('startPage');
            pageSize = wrapperName.getOption('pageSize');
        }
        test_enableCoordinates(callback);

        // page event
        google.visualization.events.addListener(wrapperName.getChart(), 'page', function (sender) {
            console.log(".addListener(wrapperName.getChart(), 'page' - paged - test_enableCoordinates called");
            page = sender.page; // save current page
            test_enableCoordinates(callback);
        });

        // sort event
        google.visualization.events.addListener(wrapperName.getChart(), 'sort', function () {
            console.log(".addListener(wrapperName.getChart(), 'sort' - sorted - test_enableCoordinates called ")
            page = 0; // reset back to first page
            test_enableCoordinates(callback);
        });

        function test_enableCoordinates(callback) {
            //console.log('callback enableCoordinates', callback);

            //remove event listeners
            var container = document.getElementById(wrapperName.getContainerId());
            Array.prototype.forEach.call(container.getElementsByTagName('td'), function (cell) {
                cell.removeEventListener("click", test_selectCell, false);
            });

            //add event listeners
            var container = document.getElementById(wrapperName.getContainerId());
            Array.prototype.forEach.call(container.getElementsByTagName('td'), function (cell) {
                cell.addEventListener('click', test_selectCell, false);
            });
        }

        function test_selectCell(sender) {

            var cell = sender.target;
            var row = cell.closest('tr');

            var wrapperDataTable = wrapperName.getDataTable();

            var selectedRow = row.rowIndex - 1; // adjust for header row (-1)
            selectedRow = (page * pageSize) + selectedRow;  // adjust for page number

            // Original from whitehat - save sorted info
            // This version does not work after a user clicks a column to sort
            //var sortInfo = wrapperName.getChart().getSortInfo();
            //if (sortInfo.sortedIndexes !== null) {
            //    selectedRow = sortInfo.sortedIndexes[selectedRow];
            //}

            // Replaced code to which finds the view row order by .getView then taking the selected row
            // and returning the number which is in the .getView result
            var sortInfo = wrapperName.getView();  // save sorted info
            if (sortInfo !== null) {
                selectedRow = sortInfo.rows[selectedRow];
            }
            var selectedCol = cell.cellIndex;

            //var result = "selectedRow: " + selectedRow + " selectedCol: " + selectedCol;
            //var ul = document.getElementById("demo");
            //var li = document.createElement("li");
            //li.innerHTML = result;
            //ul.appendChild(li);

            var selectedValue = wrapperDataTable.getValue(selectedRow, selectedCol);
            var colID = wrapperDataTable.getColumnId(selectedCol);
            var colLabel = wrapperDataTable.getColumnLabel(selectedCol);

            var result = //removed array brackets
            {
                "selectedRow": selectedRow,
                "selectedCol": selectedCol,
                "selectedValue": selectedValue,
                "colID": colID,
                "colLabel": colLabel
            };

            //callback(result, wrapperDataTable);
            console.log('test_selectCell', result);
            return result;
        }
    }
<script src="https://www.gstatic.com/charts/loader.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.1/jquery.min.js"></script>

<div id="dashboard">
    <div id="categoryPicker"></div><br />
    Proxy Table<br />
    <div id="div_proxyTable" style="display:none;"></div><br />
    Table<br />
    <div id="div_table"></div><br /><br />
</div>

================MULTIPLE DASHBOARDS SCENARIO=====================

The solution provided works for a single drawDashboard() scenerio. However it breaks down in a multiple drawDashboard() scenerio which I would like to discuss next.

I will be adding additional drawDashboard() functions to this page.
They also require the same functionality and remain independent from each other.

Is there a way to accommodate multiple drawDashboard() function without global variables?

Do I move listenerPage and listenerSort inside a specific drawDashboard() function to self contain?

I wish to maintain only one library function test_gcharts_selectedRowCol() and not duplicate it for a additional drawDashboard() additions?

https://jsfiddle.net/cmill/pvj23nfk/

On run, drawDashboard_A() begins with listenerPage and listenerSort = null. It will then execute the .addListener routine for each.

Then as drawDashboard_B() runs listenerPage and listenerSort are no longer null but have been set by the pass made by drawDashboard_A().

The sorting and click events work perfectly and as needed and return results.

Bad Behavior:

  1. Click page 2 in drawDashboard_A()

  2. Click a row - FAIL - no result.

  3. click page 2 in drawDashboard_B()

  4. click a row - PASS - receive a result.

Upvotes: 2

Views: 785

Answers (1)

WhiteHat
WhiteHat

Reputation: 61230

to correct the issue with the table cell select event,
use addListener instead of addOneTimeListener, here...

    google.visualization.events.addOneTimeListener(proxyTable, 'ready', function () {
        console.log(".addOneTimeListener(proxyTable, 'ready' - select");

   // USE addListener HERE

        google.visualization.events.addListener(table, 'ready', function () {
            console.log(".addOneTimeListener(table, 'ready' - select");
            test_gcharts_selectedRowCol(table, json.tableChart.clickGetFunc);//(wrapperName, callback)
        });
    });

then to ensure we don't get multiple sort and page events,
save a reference to the listener handles when adding,
and remove before adding again.

we can use an object to store the handlers,
and the container id of the chart wrapper as the key...

// save handles to event listeners
var listenerPage = {};
var listenerSort = {};

function test_gcharts_selectedRowCol(wrapperName, callback) {
  //console.log('wrapperName', wrapperName); console.log('callback', callback)

  //NEW - Works with paging active
  // initialize page number and size
  var page = 0;
  var pageSize = 10;
  if (wrapperName.getOption('page') === 'enable') {
    page = wrapperName.getOption('startPage');
    pageSize = wrapperName.getOption('pageSize');
  }
  test_enableCoordinates(callback);

  // remove previous event handlers
  if (listenerPage.hasOwnProperty(wrapperName.getContainerId())) {
    google.visualization.events.removeListener(listenerPage[wrapperName.getContainerId()]);
    delete listenerPage[wrapperName.getContainerId()];
  }
  if (listenerSort.hasOwnProperty(wrapperName.getContainerId())) {
    google.visualization.events.removeListener(listenerSort[wrapperName.getContainerId()]);
    delete listenerSort[wrapperName.getContainerId()];
  }

  // page event
  listenerPage[wrapperName.getContainerId()] = google.visualization.events.addListener(wrapperName.getChart(), 'page', function(sender) {
    console.log(".addListener(wrapperName.getChart(), 'page' - paged - test_enableCoordinates called");
    page = sender.page; // save current page
    test_enableCoordinates(callback);
  });

  // sort event
  listenerSort[wrapperName.getContainerId()] = google.visualization.events.addListener(wrapperName.getChart(), 'sort', function() {
    console.log(".addListener(wrapperName.getChart(), 'sort' - sorted - test_enableCoordinates called ")
    page = 0; // reset back to first page
    test_enableCoordinates(callback);
  });

see following working snippet...

google.charts.load('current', {
  'packages': ['corechart', 'table', 'gauge', 'controls', 'charteditor']
}).then(drawDashboard);

function drawDashboard() {
  //console.log("drawDashboard");

  var data = google.visualization.arrayToDataTable([
    ['Name', 'RoolNumber', 'Gender', 'Age', 'DonutsEaten'],
    ['Michael', 1, 'Male', 12, 5],
    ['Elisa', 2, 'Female', 20, 7],
    ['Robert', 3, 'Male', 7, 3],
    ['John', 4, 'Male', 54, 2],
    ['Jessica', 5, 'Female', 22, 6],
    ['Aaron', 6, 'Male', 3, 1],
    ['Margareth', 7, 'Female', 42, 8],
    ['Miranda', 8, 'Female', 33, 6]
  ]);

  var dashboard = new google.visualization.Dashboard(document.getElementById('dashboard'));

  var categoryPicker = new google.visualization.ControlWrapper({
    controlType: 'CategoryFilter',
    containerId: 'categoryPicker',
    options: {
      filterColumnLabel: 'Gender',
      ui: {
        labelStacking: 'vertical',
        allowTyping: false,
        allowMultiple: false
      }
    }
  });

  var proxyTable = new google.visualization.ChartWrapper({
    chartType: 'Table',
    containerId: 'div_proxyTable',
    options: {
      width: '500px'
    }
  });

  var table = new google.visualization.ChartWrapper({
    chartType: 'Table',
    containerId: 'div_table',
    options: {
      sort: 'event', // <-- set sort to 'event' for table totaling
      width: '500px',
      allowHtml: true,
      page: 'enable',
      pageSize: '5',
    }
  });

  //This json contains my settings for later
  let json = {
    "tableChart": {
      "hasTable": true,
      "dataView": {
        "columns": [{
            "id": "Name"
          },
          {
            "id": "RoolNumber"
          },
          {
            "id": "Gender"
          },
          {
            "id": "Age"
          },
          {
            "id": "DonutsEaten"
          }
        ]
      },
      "groupView": {
        "hasGroupView": false
      },
      "totalRow": {
        "hasTotalRow": true,
        "labelCol": "Total",
        "labelColIndex": 0, //This is the column where the words "Grand Total" are stored.  It must be text column.
        "totalColumns": [{
          "id": "RoolNumber",
          "type": "number",
          "function": "sum"
        }]
      },
      "conditionalFormat": {
        "hasConditionalFormat": false
      },
      "options": {},
      "hasCSV": false,
      "clickGetFunc": null
    },
  };

  google.visualization.events.addOneTimeListener(proxyTable, 'ready', function() {
    console.log(".addOneTimeListener(proxyTable, 'ready' - sort");

    google.visualization.events.addOneTimeListener(table, 'ready', function() {
      console.log(".addOneTimeListener(table, 'ready' - sort");

      //#region table - sort: 'event'
      google.visualization.events.addListener(table.getChart(), 'sort', function(sender) {
        console.log(".addListener(table.getChart(), 'sort' - sorted");

        //// update table --> options must include (sort: 'event') for total row to work properly
        //// update var grandTotal = tableData.getFilteredRows([{column: 0,
        //// update .draw() 'table' references to new name if using for a different table variable

        // sort data table according to sort properties
        var tableData = table.getDataTable();
        var sortIndexes = tableData.getSortedRows({
          column: sender.column,
          desc: !sender.ascending
        });

        //#region reposition total row - if required
        let totalRow = (json.tableChart.totalRow !== undefined) ? json.tableChart.totalRow : [];
        let hasTotalRow = totalRow.hasTotalRow;
        if (hasTotalRow) {
          // find grand total row
          var grandTotal = tableData.getFilteredRows([{
            column: json.tableChart.totalRow.labelColIndex, //must be placed in a column which is of type string.
            value: json.tableChart.totalRow.labelCol
          }]);
          if (grandTotal.length > 0) {
            // find grand total in sort
            var grandTotalSort = sortIndexes.indexOf(grandTotal[0]);

            // remove grand total from sort
            sortIndexes.splice(grandTotalSort, 1);

            // add grand total as first index
            sortIndexes.unshift(grandTotal[0]);
          }
        }
        //#endregion

        // set table sort arrow
        table.setOption('sortAscending', sender.ascending);
        table.setOption('sortColumn', sender.column);

        // set table view & re-draw table
        table.setView({
          rows: sortIndexes
        });
        table.draw();

        //Table ready then fires
      });
      //#endregion

    });
  });

  google.visualization.events.addOneTimeListener(proxyTable, 'ready', function() {
    console.log(".addOneTimeListener(proxyTable, 'ready' - select");

    google.visualization.events.addListener(table, 'ready', function() {
      console.log(".addOneTimeListener(table, 'ready' - select");
      test_gcharts_selectedRowCol(table, json.tableChart.clickGetFunc); //(wrapperName, callback)
    });
  });

  google.visualization.events.addListener(proxyTable, 'ready', function() {
    console.log(".addListener(proxyTable, 'ready' - redrawTable()");
    redrawTable(json.tableChart);
  });

  dashboard.bind([categoryPicker], [proxyTable]);
  dashboard.draw(data);


  //This is a table builder which uses json from above.  This is working ok.  Contains no listeners.
  function redrawTable(tableChart) {
    console.log('redrawTable()');

    // set defaults for any undefined settings
    let dataView = (tableChart.dataView !== undefined) ? tableChart.dataView : [];
    let groupView = (tableChart.groupView !== undefined) ? tableChart.groupView : [];
    let totalRow = (tableChart.totalRow !== undefined) ? tableChart.totalRow : [];
    let conditionalFormat = (tableChart.conditionalFormat !== undefined) ? tableChart.conditionalFormat : [];

    // update .draw() 'table' or 'chart' references when using a different or additional chart name

    var sourceData = proxyTable.getDataTable().toDataTable().clone();
    //console.log('sourceData', sourceData);

    //#region create data view - this is used as basis for dataResults
    let view = new google.visualization.DataView(sourceData);

    //#region create group view - if required
    let dataResults_forTable;
    let hasGroupView = groupView.hasGroupView;
    if (hasGroupView) {

      // create keys for grouping
      const groupKeys = [];
      for (let i = 0; i < groupView.keys.length; i++) {
        groupKeys.push(
          groupKey_default(view, groupView.keys[i]),
        );
      };
      // create columns for aggregating
      const groupColumns = [];
      for (let i = 0; i < groupView.columns.length; i++) {
        groupColumns.push(
          groupColumn_default(view, groupView.columns[i]),
        );
      };

      // create data aggregation
      let group = google.visualization.data.group(view, groupKeys, groupColumns);
      //console.log('group'); console.log(group);

      dataResults_forTable = group.clone();
    } else {
      dataResults_forTable = view.toDataTable().clone();
    } //END if (hasGroupView) {
    //console.log('dataResults_forTable', dataResults_forTable);

    //#endregion

    //#region create total row - if required
    let hasTotalRow = totalRow.hasTotalRow;
    if (hasTotalRow) {

      let labelCol = totalRow.labelCol;
      let labelColIndex = totalRow.labelColIndex;
      let totalColumns = totalRow.totalColumns;

      //Create groupColumns for total row aggregation calculations
      const groupColumns = [];
      for (let i = 0; i < totalColumns.length; i++) {
        const column = {
          column: dataResults_forTable.getColumnIndex(totalColumns[i].id),
          type: 'number'
        }
        switch (totalColumns[i].function) {
          case 'sum':
            column.aggregation = google.visualization.data.sum;
            break;
          case 'count':
            column.aggregation = google.visualization.data.count;
            break;
          case 'average':
            column.aggregation = google.visualization.data.avg;
            break;
          case 'min':
            column.aggregation = google.visualization.data.min;
            break;
          case 'max':
            column.aggregation = google.visualization.data.avg;
            break;
          default:
            column.aggregation = google.visualization.data.sum;
        }
        groupColumns.push(column);
      }

      let groupTotal = google.visualization.data.group(dataResults_forTable,
        // need key column to group on, so we want all rows grouped into 1, then it needs a constant value
        [{
          column: 0,
          type: "number",
          modifier: function() {
            return 1;
          }
        }], groupColumns);

      // this code block will run if the filter results in rows available to total.  Otherwise the table will present no rows.
      if (groupTotal.getNumberOfRows() !== 0) {

        let formatDecimal = new google.visualization.NumberFormat({
          pattern: '#,###.##'
        });
        for (let i = 1; i < groupTotal.getNumberOfColumns(); i++) {
          formatDecimal.format(groupTotal, i);
        }

        // create Grand Total row from colToTotal and groupTotal
        const gtRow = [];
        for (let i = 0; i < dataResults_forTable.getNumberOfColumns(); i++) {
          //Build GT Row to match length of dataResults_forTable
          gtRow.push(null);
        }

        //Push words "Grand Total" into it's set position in gtRow - It must go into a column of type string.
        gtRow[labelColIndex] = labelCol;

        for (let i = 0; i < totalColumns.length; i++) {
          //Loop through groupColumns, test setting type for string.

          if (totalColumns[i].type === 'string') {
            //Convert to number from groupTotal result to string to match the column it's being pushed into
            gtRow[dataResults_forTable.getColumnIndex(totalColumns[i].id)] = String(groupTotal.getValue(0, i + 1));
          } else {
            //Otherwise push in the number value
            gtRow[dataResults_forTable.getColumnIndex(totalColumns[i].id)] = groupTotal.getValue(0, i + 1);
          }
        }
        //console.log('insertRow', insertRow);

        // insert complete gtRow with values into row position 0
        dataResults_forTable.insertRows(0, [gtRow]);

        // add formatting for grand total row to highlight && justify to right if of type number
        for (let j = 0; j < dataResults_forTable.getNumberOfColumns(); j++) {
          //if statement on column type for left right justification
          if (dataResults_forTable.getColumnType(j) === 'number') {
            dataResults_forTable.setColumnProperty(j, 'className', 'googleTableTextRight');
            dataResults_forTable.setProperty(0, j, 'className', 'googleTableTotalRow googleTableTextRight'); //stored in css file
          } else {
            dataResults_forTable.setProperty(0, j, 'className', 'googleTableTotalRow'); //stored in css file
          }
        }
        //console.log('dataResults_forTable', dataResults_forTable);
      } //END (groupTotal.getNumberOfRows() !== 0) {
    } //END if (hasTotalRow) {
    //#endregion

    //#region conditional formatting - if required
    let hasConditionalFormat = conditionalFormat.hasConditionalFormat;
    if (hasConditionalFormat) {
      dataResults_forTable = conditionalFormatting_default(dataResults_forTable, conditionalFormat);
    } //END if (hasConditionalFormat) {
    //#endregion

    var finalView_forTable = new google.visualization.DataView(dataResults_forTable);
    //console.log('finalView_forTable', finalView_forTable);

    // set reset sorting, set dataTable & draw chart
    table.setView(null); // reset in case sorting has been used via user click
    table.setDataTable(finalView_forTable); //includes any total row
    table.draw();

  } //END redrawChart()

}

// save handles to event listeners
var listenerPage = {};
var listenerSort = {};

function test_gcharts_selectedRowCol(wrapperName, callback) {
  //console.log('wrapperName', wrapperName); console.log('callback', callback)

  //NEW - Works with paging active
  // initialize page number and size
  var page = 0;
  var pageSize = 10;
  if (wrapperName.getOption('page') === 'enable') {
    page = wrapperName.getOption('startPage');
    pageSize = wrapperName.getOption('pageSize');
  }
  test_enableCoordinates(callback);

  // remove previous event handlers
  if (listenerPage.hasOwnProperty(wrapperName.getContainerId())) {
    google.visualization.events.removeListener(listenerPage[wrapperName.getContainerId()]);
    delete listenerPage[wrapperName.getContainerId()];
  }
  if (listenerSort.hasOwnProperty(wrapperName.getContainerId())) {
    google.visualization.events.removeListener(listenerSort[wrapperName.getContainerId()]);
    delete listenerSort[wrapperName.getContainerId()];
  }

  // page event
  listenerPage[wrapperName.getContainerId()] = google.visualization.events.addListener(wrapperName.getChart(), 'page', function(sender) {
    console.log(".addListener(wrapperName.getChart(), 'page' - paged - test_enableCoordinates called");
    page = sender.page; // save current page
    test_enableCoordinates(callback);
  });

  // sort event
  listenerSort[wrapperName.getContainerId()] = google.visualization.events.addListener(wrapperName.getChart(), 'sort', function() {
    console.log(".addListener(wrapperName.getChart(), 'sort' - sorted - test_enableCoordinates called ")
    page = 0; // reset back to first page
    test_enableCoordinates(callback);
  });

  function test_enableCoordinates(callback) {
    //console.log('callback enableCoordinates', callback);

    //remove event listeners
    var container = document.getElementById(wrapperName.getContainerId());
    Array.prototype.forEach.call(container.getElementsByTagName('td'), function(cell) {
      cell.removeEventListener("click", test_selectCell, false);
    });

    //add event listeners
    var container = document.getElementById(wrapperName.getContainerId());
    Array.prototype.forEach.call(container.getElementsByTagName('td'), function(cell) {
      cell.addEventListener('click', test_selectCell, false);
    });
  }

  function test_selectCell(sender) {

    var cell = sender.target;
    var row = cell.closest('tr');

    var wrapperDataTable = wrapperName.getDataTable();

    var selectedRow = row.rowIndex - 1; // adjust for header row (-1)
    selectedRow = (page * pageSize) + selectedRow; // adjust for page number

    // Original from whitehat - save sorted info
    // This version does not work after a user clicks a column to sort
    //var sortInfo = wrapperName.getChart().getSortInfo();
    //if (sortInfo.sortedIndexes !== null) {
    //    selectedRow = sortInfo.sortedIndexes[selectedRow];
    //}

    // Replaced code to which finds the view row order by .getView then taking the selected row
    // and returning the number which is in the .getView result
    var sortInfo = wrapperName.getView(); // save sorted info
    if (sortInfo !== null) {
      selectedRow = sortInfo.rows[selectedRow];
    }
    var selectedCol = cell.cellIndex;

    //var result = "selectedRow: " + selectedRow + " selectedCol: " + selectedCol;
    //var ul = document.getElementById("demo");
    //var li = document.createElement("li");
    //li.innerHTML = result;
    //ul.appendChild(li);

    var selectedValue = wrapperDataTable.getValue(selectedRow, selectedCol);
    var colID = wrapperDataTable.getColumnId(selectedCol);
    var colLabel = wrapperDataTable.getColumnLabel(selectedCol);

    var result = //removed array brackets
      {
        "selectedRow": selectedRow,
        "selectedCol": selectedCol,
        "selectedValue": selectedValue,
        "colID": colID,
        "colLabel": colLabel
      };

    //callback(result, wrapperDataTable);
    console.log('test_selectCell', result);
    return result;
  }
}
<script src="https://www.gstatic.com/charts/loader.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.1/jquery.min.js"></script>

<div id="dashboard">
    <div id="categoryPicker"></div><br />
    Proxy Table<br />
    <div id="div_proxyTable" style="display:none;"></div><br />
    Table<br />
    <div id="div_table"></div><br /><br />
</div>

Upvotes: 1

Related Questions