views:

1777

answers:

1

Hi,

I'm trying to use jquery's .ajax(), google visualisation annotated timeline and one of the google datatable helpers together. Ultimately what i'm after is having a link on a page and when the user clicks it data is loaded asynchronously via jquery.ajax(), returned as google visualisation compliant JSON and passed to the charting API to generated the annotated timeline.

Along the lines of the guidance on the datatable helper wiki page, the vanilla version works fine, i.e.

  1. no ajax loading of the data (instead json data is injected into page via a call to Page.ClientScript.RegisterStartupScript() in the Page_Load() code behind method) and
  2. no click event associated with a link on page

Here's the vanilla working code:

CodeBehind:

public partial class _Default : System.Web.UI.Page
{
 protected void Page_Load(object sender, EventArgs e)
 {
  DataTable dt = PopulateDatatable();
  ConvertToGoogleDatatable(dt);
 }

 private void ConvertToGoogleDatatable(DataTable dt)
 {
  // Use of Bortosky helper class to generated 
  // google visualisation compliant json
  GoogleDataTable gdt = new GoogleDataTable(dt);
  using (MemoryStream memoryStream = new MemoryStream())
  {
   gdt.WriteJson(memoryStream);
   memoryStream.Position = 0;
   StreamReader sr = new StreamReader(memoryStream);
   Page.ClientScript.RegisterStartupScript(this.GetType(), "vis", string.Format("var jsonData = {0}", sr.ReadToEnd()), true);
  }
 }

 private DataTable PopulateDatatable()
 {
  DataTable dt = new DataTable();
  dt.Columns.Add("Date", typeof(System.DateTime));
  dt.Columns.Add("High", typeof(System.Double));
  dt.Columns.Add("Low", typeof(System.Double));
  dt.Columns.Add("Closing Price", typeof(System.Double));

  using (StreamReader sr = new StreamReader(@"data.csv"))
  {
   string line;
   line = sr.ReadLine();

   while ((line = sr.ReadLine()) != null)
   {
    string[] lineParts = line.Split(',');
    dt.Rows.Add(new object[] { Convert.ToDateTime(lineParts[0]), lineParts[2], lineParts[3], lineParts[4] });
   }
  }
  return dt;
 }
}

XHTML:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="GoogleVis._Default" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"&gt;

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<script type='text/javascript' src='http://www.google.com/jsapi'&gt;&lt;/script&gt;
<script type='text/javascript'>

 google.load('visualization', '1', {'packages':['annotatedtimeline']});
 google.setOnLoadCallback(drawChart);

 function drawChart() {
 var data = new google.visualization.DataTable(jsonData, 0.5);
 var chart = new google.visualization.AnnotatedTimeLine(document.getElementById('chart_div'));
 chart.draw(data, {displayAnnotations: true});
 }

</script>
<title>Untitled Page</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
     <div id='chart_div' style='width: 90%; height: 500px;'></div>
    </div>
    </form>
</body>
</html>

When i then try to refactor in order to wrap the request for data in a PageMethod to be called via .ajax() and attach a click event things fall apart. In particular, debugging i can see that the .ajax() call is working fine and json data is being returned. The json data is also been passed correctly to the drawChart() method but execution doesn't get beyond this line:

var data = new google.visualization.DataTable(msg.d, 0.5);

The browser just stays at saying "Transferring data from google.com..."

Here's the non-working code:

CodeBehind:

public partial class _Default : System.Web.UI.Page
{
    [WebMethod]
    public static string AjaxMethod()
    {
        DataTable dt = PopulateDatatable();
        return ConvertToGoogleDatatable(dt);
    }

    private static string ConvertToGoogleDatatable(DataTable dt)
    {
        GoogleDataTable gdt = new GoogleDataTable(dt);
        using (MemoryStream memoryStream = new MemoryStream())
        {
            gdt.WriteJson(memoryStream);
            memoryStream.Position = 0;
            StreamReader sr = new StreamReader(memoryStream);
   // FOLLOWING 3 LINES DIFFERENT FROM VANILLA VERSION ABOVE!
            string returnValue = sr.ReadToEnd();
            sr.Close();
            return returnValue;
        }
    }

    private static DataTable PopulateDatatable()
    {
        DataTable dt = new DataTable();
        dt.Columns.Add("Date", typeof(System.DateTime));
        dt.Columns.Add("High", typeof(System.Double));
        dt.Columns.Add("Low", typeof(System.Double));
        dt.Columns.Add("Closing Price", typeof(System.Double));

        using (StreamReader sr = new StreamReader(@"data.csv"))
        {
            string line;
            line = sr.ReadLine();

            while ((line = sr.ReadLine()) != null)
            {
                string[] lineParts = line.Split(',');
                dt.Rows.Add(new object[] { Convert.ToDateTime(lineParts[0]), lineParts[2], lineParts[3], lineParts[4] });
            }
        }
        return dt;
    }
}

XHTML:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="GoogleVis._Default" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"&gt;

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<script type='text/javascript' src='http://www.google.com/jsapi'&gt;&lt;/script&gt;
<script type='text/javascript' src="jquery-1.3.2.min.js"></script>
<script type='text/javascript'>

 $(document).ready(function() {
  $("#Result").click(function() {
   $.ajax({
    type: "POST",
    url: "Default.aspx/AjaxMethod",
    data: "{}",
    contentType: "application/json; charset=utf-8",
    dataType: "json",
    success: function(msg) {
     google.load('visualization', '1', {'packages':['annotatedtimeline']});
     google.setOnLoadCallback(drawChart(msg));
    }
   });
  });
 });

    function drawChart(msg) {
        var data = new google.visualization.DataTable(msg.d, 0.5);
        var chart = new google.visualization.AnnotatedTimeLine(document.getElementById('chart_div'));
        chart.draw(data, {displayAnnotations: true});
    }

</script>
<title>Untitled Page</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
    <div id="Result"><a href="#">Load!</a></div>
     <div id='chart_div' style='width: 90%; height: 500px;'></div>
    </div>
    </form>
</body>
</html>

I've also found that stripping most of the functionality out and modifying the vanilla working version javascript by wrapping it in $(document).ready(function() {} (shown below) has the same damaging effect.

<script type='text/javascript'>
 $(document).ready(function() {

  google.load('visualization', '1', {'packages':['annotatedtimeline']});
  google.setOnLoadCallback(drawChart);

  function drawChart() {
  var data = new google.visualization.DataTable(jsonData, 0.5);
  var chart = new google.visualization.AnnotatedTimeLine(document.getElementById('chart_div'));
  chart.draw(data, {displayAnnotations: true});
  }
 }
</script>

So... any ideas about how i should be chaining these calls together to get it to work???

Thanks

EDIT:

The following rearranging of the code also doesn't seem to work - In particular the alert message in LoadData() is never displayed!

$(document).ready(function() {
  $("#Result").click(function() {
        google.load('visualization', '1', {'packages':['annotatedtimeline']});
        google.setOnLoadCallback(LoadData)
  });
});

function LoadData(){
    alert("breakpoint");
    $.ajax({
      type: "POST",
      url: "Default.aspx/AjaxMethod",
      data: "{}",
      contentType: "application/json; charset=utf-8",
      dataType: "json",
      success: function(msg) {
            var jsonData = msg.d;

            var data = new google.visualization.DataTable(jsonData, 0.5);
            var chart = new google.visualization.AnnotatedTimeLine(document.getElementById('chart_div'));
            chart.draw(data, {displayAnnotations: true});
      }
    });
};

EDIT 2:

Yet another variation that doesn't work!!!

The following results in the data being loaded (i.e. .ajax() call works) but i then get a this.t undefined error from the google javascript.

<script type='text/javascript' src="jquery-1.3.2.min.js"></script>
<script type='text/javascript'>

    google.load('visualization', '1', {'packages':['annotatedtimeline']});
    google.setOnLoadCallback(LoadData);

    function LoadData() {
        $.ajax({
            type: "POST",
            url: "Default.aspx/AjaxMethod",
            data: "{}",
            contentType: "application/json; charset=utf-8",
            dataType: "json",
            success: function(msg) {
             var data = new google.visualization.DataTable(msg.d, 0.5);
             var chart = new google.visualization.AnnotatedTimeLine(document.getElementById('chart_div'));
             chart.draw(data, {displayAnnotations: true});
            }
        });
    }

</script>

Wrapping the above in $(document).ready(function() {}); gets me back to the previous "Transferring data from google.com" and it just sits there. The ajax call is not made and when i hit stop to stop loading the page i get "$ is not defined, $.ajax({" in firebug.

Frustrating!

Any ideas?

+1  A: 

jQuery and Google Visualizations will play nicely together if you delay executing the jQuery ready function until google has loaded. This may not be the ideal approach, but it works around the problem of invoking code in either library before it is guaranteed to be loaded.

Example:

google.load('visualization','1',{packages:['piechart']);
function loaded() {
  if (MyLibrary.googleLoaded) {
    MyLibrary.googleLoaded();
  } else {
    setTimeout(loaded, 50);
  }
}
google.setOnLoadCallback(loaded);

and:

$(document).ready(function() {
  MyLibrary.googleLoaded = function() {
    // whatever you would have put in $(document).ready();
  };
});

...which allows both libraries to bind their onLoad handlers independently and delay execution of code depending on both libraries until they are both ready.