javascriptasp.netwebformsgoogle-visualizationrendercontrol

Asp.net render control -> javascript error


I have a usercontrol with some charts from Google chart api.

When the usercontrol is getting loaded and rendered by the server the usual way everything works as expected. But when I use a ajax webservice the render the control async and return the html with ajax and add it to the page, I get some script error in firebug(chrome calls it a ReferenceError):

"google is not defined"

IMAGE

My project is ASP.Net MVP Webforms with some javascript.
I have made a sample project where I have recreated the issue. Download it here.

Control renderer code:

public virtual string RenderControlToString(string path, bool htmlEncode)
{
    StringWriter stringWriter = new StringWriter();
    this.RenderControl(path, (TextWriter)stringWriter);
    string s = stringWriter.ToString();
    if (!htmlEncode)
        return s;
    else
        return HttpContext.Current.Server.HtmlEncode(s);
}

public virtual void RenderControl(string path, TextWriter writer)
{
    var page = new PageForRendering();
    page.Controls.Add(page.LoadControl(path));
    HttpContext.Current.Server.Execute((IHttpHandler)page, writer, true);
}

(To omit the "need to be in form" error - there is a form on the page the control is added to)

public class PageForRendering : Page
{
    public override void VerifyRenderingInServerForm(Control control)
    {}
}

Script on the usercontrol:

<script type="text/javascript" src="https://www.google.com/jsapi"></script>
<script type="text/javascript">
    console.log("Loading Google Charts...");
    google.load('visualization', '1.0', { 'packages': ['corechart'] });
    console.log("Google Charts loaded!");

    google.setOnLoadCallback(function () {
        ...some code...
    });
</script>

With firebug I found out that https://www.google.com/jsapi is loaded fine but only the first one of my console logs is printed.

I think it might be some kind of scope issue... The google variable will be defined in this script:

<script type="text/javascript" src="https://www.google.com/jsapi"></script>

But in this case it is inaccessible in the next script block. With normal rendering it works normally. Either the script isn't done loading before the google.load call or the google variable is now somehow defined in another scope because of the different rendering method.

If I look in firebug DOM the google variable is defined like here. But I found out that if I place a break point a the line: google.load('visualization', '1.0', { 'packages': ['corechart'] }); , I find that at this moment the google variable is not defined in the DOM. So it might also be somethink about loading the script. But the line <script type="text/javascript" src="https://www.google.com/jsapi"></script> is before it and it should be executed synchronzly right? When I check the Net in firebug I also find that the file is downloaded correctly.

I might also need to mention that the usercontrol loading ajax code looks like this:

<div id="statisticsContent">&nbsp;</div>

<script type="text/javascript">
    $(document).ready(function () {
        function getStatisticsView() {
            var id = $(".chooseStatisticsToShow input:radio:checked").val();
            var statisticsType = (id == $(".choosePlayingGroupDropDownList option:selected").val() ? 1 : 0);

            $.ajax({
                cache: false,
                global: false,
                type: "POST",
                contentType: "application/json; charset=utf-8",
                dataType: "json",
                url: '/Services/StatisticsService.svc/GetStatisticsView',
                data: JSON.stringify({
                    statisticsType: statisticsType,
                    id: id
                }),
                success: function (data) {
                        $("#statisticsContent").html(data.d.ViewHtml);
                }
            });
        }
        getStatisticsView();

        $(".chooseStatisticsToShow").change(function () {
            getStatisticsView();
        });
    });
</script>



I have made a sample project where I have recreated the issue. Download it here.


Solution

  • After your service sent response and you added it using $("#statisticsContent").html(data.d.ViewHtml); browser interprets html and starts loading and executing scripts in parallel. How do I know it? Just experience - in my projects every time I was trying to inject <script src='...'> browsers were not waiting for scripts to load and next script tags with custom code were executed immediately.

    This is your case and you need to wait for jsapi to load and only then execute code that will load package and perform chart rendering. Here is the code:

    <div id="chart_div"></div>
    
    <script type="text/javascript" src="https://www.google.com/jsapi"></script>
    <script type="text/javascript">
        waitForGoogleApiAndLoadPackages();
        function waitForGoogleApiAndLoadPackages() {
            if (typeof (google) === 'undefined')
                setTimeout(waitForGoogleApiAndLoadPackages, 100);
            else {
                google.load('visualization', '1.0', { 'callback': 'renderChart', 'packages': ['corechart'] });
            }
        }
    
        function renderChart() {
            var data = new google.visualization.DataTable();
            data.addColumn('string', 'Topping');
            data.addColumn('number', 'Slices');
            data.addRows([['Mushrooms', 3], ['Onions', 1], ['Olives', 1], ['Zucchini', 1], ['Pepperoni', 2]]);
    
            var options = { 'title': 'How Much Pizza I Ate Last Night', 'width': 400, 'height': 300 };
    
            var chart = new google.visualization.PieChart(document.getElementById('chart_div'));
            chart.draw(data, options);
        }
    </script>
    

    Another interesting part is else block in waitForGoogleApiAndLoadPackages function. This is a workaround for issue described in Why does google.load cause my page to go blank? question.


    Quote from Why does google.load cause my page to go blank?:

    Looks like google.load is adding the script to the page using a document.write(), which if used after the page loads, wipes out the html.