How do you write clean code using Javascript and Razor

asp.net-mvc, javascript, razor

Solution

However, my nose says code smell.

Oh yeah it stinks, I can feel it.

Is there a better way to write this?

Of course. Never build JSON manually as you did by mixing the 2 languages and writing loops and stuff. Use a JSON serializer:

@model IEnumerable<Tuple<DateTime,int,int>>

<div id="chart_div_2" style="width: 900px; height: 500px;"></div>
<script type="text/javascript" src="https://www.google.com/jsapi"></script>
<script type="text/javascript">
    google.load("visualization", "1", { packages: ["corechart"] });
    google.setOnLoadCallback(drawChart);
    function drawChart() {
        var arr = @Html.Raw(
            Json.Encode(
                new object[] { new[] { "Year", "Sales", "Expenses" } }
                .Concat(
                    Model.Select(x => new object[] 
                    { 
                        x.Item1.ToString("MMM d"), 
                        x.Item2, 
                        x.Item3
                    })
                )
            )
        );

        var data = google.visualization.arrayToDataTable(arr);
        var chart = new google.visualization.LineChart(document.getElementById('chart_div_2'));
        chart.draw(data);
    }
</script>

This will generate an equivalent code markup as yours but the whole model manipulation and encoding is done on the server. You could also write a custom HTML helper in order to simplify your code to this:

public static class ChartExtensions
{
    public static IHtmlString ToChartData(
        this IEnumerable<Tuple<DateTime, int, int>> model, 
        params string[] titles
    )
    {
        return new HtmlString(
            Json.Encode(
                new object[] { titles }
                .Concat(
                    model.Select(x => new object[] 
                    { 
                        x.Item1.ToString("MMM d"), 
                        x.Item2, 
                        x.Item3 
                    })
                )
            )
        );
    }
}

and then in your view:

@model IEnumerable<Tuple<DateTime,int,int>>

<div id="chart_div_2" style="width: 900px; height: 500px;"></div>
<script type="text/javascript" src="https://www.google.com/jsapi"></script>
<script type="text/javascript">
    google.load("visualization", "1", { packages: ["corechart"] });
    google.setOnLoadCallback(drawChart);
    function drawChart() {
        var arr = @Model.ToChartData("Year", "Sales", "Expenses");
        var data = google.visualization.arrayToDataTable(arr);
        var chart = new google.visualization.LineChart(document.getElementById('chart_div_2'));
        chart.draw(data);
    }
</script>

Problem

I'm programming an ASP.Net MVC page and I'm using data from the server to create a Google chart. The x-axis is the date. The y-axis is the value. There are 2 lines of data being plotted to compare. Here is the relevant code: ``` @model IEnumerable<Tuple<DateTime,int,int>> <div id="chart_div_2" style="width: 900px; height: 500px;"></div> <script type="text/javascript" src="https://www.google.com/jsapi"></script> <script type="text/javascript"> google.load("visualization", "1", { packages: ["corechart"] }); google.setOnLoadCallback(drawChart); function drawChart() { var arr = [['Year', 'Sales', 'Expenses']]; //Using the Razor Model to create a Javascript array. var arr2 = [ @foreach(var row in Model) { @:["@row.Item1.ToString("MMM d")", @row.Item2, @row.Item3], } ]; for (var i = 0; i < arr2.length; i++) { arr.push(arr2[i]); } var data = google.visualization.arrayToDataTable(arr); var chart = new google.visualization.LineChart(document.getElementById('chart_div_2')); chart.draw(data); } </script> ``` First of all, this code does actually work. Creating arr2 this way does turn a Razor model into something that I can use. However, my nose says code smell. It says that throwing together two languages razor and Javascript, which have somewhat similar C-based programming flow syntax could be confusing to the next person that comes along and tries to read it. Is there a better way to write this?

Original source