1

We are trying to create a Visualforce page which will include a graph displaying budget data. For testing purposes, I want to display 3 columns as defined in the code below.

Running the query in the apex class from the editor provides the results I'm expecting. Only the graph is refusing to display the data.

Following error is provided in the component inspection console: BudgetGraph?core.apexpages.request.devconsole=1:34 Uncaught (in promise) TypeError: Cannot read property 'length' of undefined at drawChart (BudgetGraph?core.apexpages.request.devconsole=1:34)

I really cannot get my head around this simple one...

Visual Force Page (header section):

<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
<script type="text/javascript">
    Array.prototype.reduce = undefined;
   // Load Google Chart
  google.charts.load('current', {'packages':['line']});
  // Set a callback to run when the Google Visualization API is loaded.
  google.charts.setOnLoadCallback(drawChart);

  var bdgtData; //Variable to store data
  // Call remote action method
  BudgetGraphController.getBudgetData(function(result, event){
      bdgtData = result; //get data from Apex controller
      },{escape:true});

  // Draw Google chart
  function drawChart() {
      //Create the data table
  var data = new google.visualization.DataTable();
    data.addColumn('string', 'Timing');
    data.addColumn('string', 'Budget Engineering');   
    data.addColumn('string', 'Budget PM');
    //Add datatable rows
    for(i = 0; i< bdgtData.length; i++){
        data.addRow([bdgtData[i].Timing__c, bdgtData[i].Budget_Engineering__c, bdgtData[i].Budget_Project_Management__c]);
    }

    var options = {
      title: 'Project Budget',
      curveType: 'function',
      legend: { position: 'bottom' }
    };

    var chart = new google.visualization.LineChart(document.getElementById('curve_chart'));
    chart.draw(data, google.charts.Line.convertOptions(options));
  }
</script>

Apex Class:

global with sharing class BudgetGraphController {

@RemoteAction
global static List<Budget__c> getBudgetData(){
    List<Budget__c> bdgtGroupList = [Select Timing__c, Budget_Engineering__c, Budget_Project_Management__c From Budget__c ORDER BY Timing__c ASC];
    return bdgtGroupList;
}

}

1
  • Is the Id curve_chart on a raw HTML element e.g. a div? If it is not and is on an <apex:...> element, a $= selector is one way to ignore the prefixes that Visualforce applies to the Id that you supply.
    – Keith C
    Commented Feb 22, 2020 at 22:05

2 Answers 2

0

JavaScript Remoting function from apex controller returns a callback and hence you cannot assume the order of execution to be sequential.

Drawing of the chart should be executed upon callback response from the apex controller successful execution.

The below is working in my scratch org

<apex:page controller="ChartController" showHeader="false">
<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>

<body>
    <div id="curve_chart" style="width: 900px; height: 500px"></div>
</body>
<script type="text/javascript">
    // Load Google Chart
    google.charts.load('current', { 'packages': ['corechart', 'line'] });
    // Set a callback to run when the Google Visualization API is loaded.
    google.charts.setOnLoadCallback(drawChart);

    // Draw Google chart
    function drawChart() {
        //Call remote action method
        ChartController.getBudgetData(function (result, event) {
            dataTableArray = [];
            var datacolumn = ['Timing', 'Budget Engineering', 'Budget PM'] ;
            dataTableArray.push(datacolumn);
            //Create the data table
            for(i= 0 ; i < result.length ; i++) {
                var datatableRow = [result[i].timing, result[i].budgetEngineering, result[i].budgetPM];
                dataTableArray.push(datatableRow);
            }

            var data = google.visualization.arrayToDataTable(dataTableArray);
            var options = {
                title: 'Project Budget',
                curveType: 'function',
                legend: { position: 'bottom' },
            };
            var chart = new google.visualization.LineChart(document.getElementById('curve_chart'));
            chart.draw(data, google.charts.Line.convertOptions(options));
        }, { escape: true });

    }
  </script>
</apex:page>

The apex controller , note i had to mock the responses since i do not have your data model

global with sharing class ChartController {

@RemoteAction
global static List<BudgetData> getBudgetData(){
    List<BudgetData> bdgtGroupList = new List<BudgetData>();
    BudgetData bdgt1 = new BudgetData('2018',200, 400);
    BudgetData bdgt2 = new BudgetData('2019',400, 600);
    bdgtGroupList.add(bdgt1);
    bdgtGroupList.add(bdgt2);
    return bdgtGroupList;
}

global class BudgetData {

    global BudgetData(string timing,integer budgetEngineering, integer budgetPM) {
        this.timing = timing;
        this.budgetEngineering = budgetEngineering;
        this.budgetPM = budgetPM;
    }
    global String timing {get;set;}
    global integer budgetEngineering {get;set;}
    global integer budgetPM {get;set;}
  }
}

Also code improvement tips

  • Always put your Javascript file in salesforce static resource. Do not import it directly from the CDN. If your app is going through a security review this will be flagged.
  • You do not have to keep the remoting call global unless you want to expose this functionality in inline visualforce page or outside your org using managed package
  • If you are packaging this then use the namespace guidelines here.
2
  • Hi Mohith, got the hard coded data to work. Now I'm struggeling to get the query working as the data provided. Would it be possible that you can indicate where the query would be required to inserted? Copying my first created code over yours isn't working. My apologies for the stupid questions, but just trying to get my head around this one :). Commented Feb 25, 2020 at 20:48
  • looks like you got it working but essentially the one i have shows an example why your code was not working. The callbacks from remoting function is where you want to write the chart draw logic. Commented Feb 25, 2020 at 23:58
0

Got this bastard to work!

Visual force page

<apex:page Controller="BudgetGraphController">                                  
<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>                                                                            
<body>
<div id="curve_chart" style="width: 900px; height: 500px"></div></body><script type="text/javascript">// Load Google Chart
google.charts.load('current', { 'packages': ['corechart', 'line'] });
// Set a callback to run when the Google Visualization API is loaded.
google.charts.setOnLoadCallback(drawChart);

// Draw Google chart
function drawChart() {
    //Call remote action method
    BudgetGraphController.getBudgetData(function (result, event) {
        dataTableArray = [];
        var datacolumn = [{label:'Timing', type:'string'}, 
                          {label:'Budget Engineering', type:'number'},
                          {label:'Budget PM', type:'number'}];
        dataTableArray.push(datacolumn);
        //Create the data table
        for(i= 0 ; i < result.length ; i++) {
            var datatableRow = [result[i].timing__c, result[i].Budget_Engineering__c, result[i].Budget_Project_Management__c];
            dataTableArray.push(datatableRow);
        }

        var data = google.visualization.arrayToDataTable(dataTableArray);
        var options = {
            title: 'Project Budget',
            curveType: 'function',
            legend: { position: 'bottom' },
        };
        var chart = new google.visualization.LineChart(document.getElementById('curve_chart'));
        chart.draw(data, google.charts.Line.convertOptions(options));
    }, { escape: true });

}

Apex class:

lobal static List<Budget__c> getBudgetData(){
List<Budget__c> bdgtGroupList = [Select Timing__c, Budget_Engineering__c, Budget_Project_Management__c From Budget__c ORDER BY Timing__c ASC];
return bdgtGroupList;} global class BudgetData { global BudgetData(date timing,integer budgetEngineering, integer budgetPM) {
    this.timing = timing;
    this.budgetEngineering = budgetEngineering;
    this.budgetPM = budgetPM;
}
global Date timing {get;set;}
global integer budgetEngineering {get;set;}
global integer budgetPM {get;set;}}}

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .