Giving Data to JavaScript

Thursday, August 30, 2012

One set of common questions in the world of ASP.NET MVC & JavaScript revolve around getting data from the server into JavaScript. This isn't about pulling data with SignalR or the WebAPI, but questions along the lines of:

- How do I generate an ActionLink in JavaScript and not hard code a URL?

- How do I get the URL for a WebApi?

- How do I get the ID value of something when the user clicks an item in a list?

If your JavaScript lives inside of your view, you can always use a Razor code expression to inject a value directly into the script. However, this doesn't solve all the scenarios listed above, and if your JavaScript is in an external .js file you don't have the ability to use code expressions. Here are a few techniques I've used to pass computed data from the server to the client.

Using <link>

Generating a <link> tag from a view is simple, and the value can represent an action link, a WebAPI endpoint, or a resource like a template. In the case of the WebAPI, for example, you could emit a link tag from a view using Url.RouteUrl (which requires an httproute member in route values to work properly).

<link id="cartApiUrl" 
href ="@Url.RouteUrl("DefaultApi",
new { httproute = "
", controller = "Cart"})"
rel="api"/>

With jQuery it is easy to find the value of the link, and use the value to call the web service.

var cartApiUrl = $("#cartApiUrl").attr("href");
$.post(cartApiUrl + "/" + itemId).then(updateCart);

Data Attributes

Data- (data dash) attributes are popular because you can embed nearly anything inside, and they are easy to consume from jQuery. Let's take the question of "What item did the user click on?".  If the view renders a data- attribute with an identifier inside, the task is trivial. The view code would look something like the following.

<div>                
<img src="@item.ImageUrl" data-itemid="@item.Id" />
</div>

The JavaScript to retrieve the Id of the clicked item can use jQuery.data.

$("img[data-itemid]").click(function () {
var itemId = $(this).data("itemid");
// ...
});

You can even embed JSON data into a data- attribute, and if jQuery detects JSON data (the value starts with { or [), it will try to parse the value inside and give you an object back. This is useful in cases when you need to build options for a widget, like jQuery UI autocomplete, and include a server generated value (like the URL of the action for autocomplete to use as a data source). The view would look like:

<input type="search" name="q" 
data-quicksearch ='{"source":"@Url.Action("Search")",
"minLength":4, "delay":250}'
/>

Then a few lines of JavaScript could wire up autocomplete throughout an application:

$("input[data-quicksearch]").each(function() {
var input = $(this);
var options = input.data("quicksearch");

// You can now use:
// options.source
// options.minLength
// options.delay

input.autocomplete(options);
});

Dumping Data

Yet another option is to dump a data structure into a view for script to consume. One framework to help with this approach is NGon (inspired by Gon in the Rails world). The NGon implementation takes information you put into a NGon property of the ViewBag and serializes the property into JSON. In a controller action you can throw anything into NGon.

public ActionResult Index()
{
ViewBag.NGon.Count = 3;
ViewBag.NGon.Name = "Home";
ViewBag.NGon.OtherStuff = new[]{2, 4, 6};
ViewBag.NGon.Thing = new Thing
{
Id = 5,
Name = "Home"
};

return View();
}

Then place the serialized data into your view using an HTML helper provided by NGon:

@Html.IncludeNGon()

And on the client, all the values are available to your script code.

var count = ngon.Count;
var thingName = ngon.Thing.Name;

NGon doesn't have a NuGet package and still uses the .NET JavaScriptSerializer (instead of Json.NET). Perhaps there is a pull request in Alex's future.


Comments
gravatar Anil Thursday, August 30, 2012
Why do you point out the use of JavaScriptSerializer over Json.Net? Is it cos of the performance difference or the fact that Json.Net ignores cyclic properties?
Scott Thursday, August 30, 2012
@Anil:

Both - plus with the WebAPI using Json.net as the default serializer right from the project start, it shouldn't create difficulties and will provide some consistency.
gravatar Oskar Austegard Thursday, August 30, 2012
Curious about the <link> usage. It's an option for sure, but seems like a violation of html - will the browser attempt to download? There really is no reason not to use a script block or use the data-attributes...

2cents
gravatar John Reilly Sunday, September 2, 2012
I've long been making heavy use of html data attributes. Absolutely love them!

By the way, I noticed your code here:

<img src="@item.ImageUrl" data-itemid="@item.Id" />

$("img[data-itemid]").click(function () {
var itemId = $(this).data("itemid");
// ...
});

Did you know that by using extra "-" characters in your data attributes jQuery will provide you with camel cased data attributes? eg:

<img src="@item.ImageUrl" data-item-id="@item.Id" />

$("img[data-item-id]").click(function () {
var itemId = $(this).data("itemId");
// ...
});

gravatar Alex Friedman Sunday, September 2, 2012
I was wondering why there was a sudden influx in "stars" on this project, then I found this :) If K. Scott Allen suggests some changes, you make it happen! I've just updated it to use JSON.Net and I added a NuGet package as well. (https://nuget.org/packages/NGon/1.0.0).

Thanks for the "plug"!
gravatar Alex Friedman Sunday, September 2, 2012
Err, wrong link in my previous post. Had to remove the 1.0.0 version of the NuGet package. Here's the correct link: https://nuget.org/packages/NGon
gravatar Scott Allen Monday, September 3, 2012
Sweet!
gravatar Scott Allen Monday, September 3, 2012
@John - thanks, I didn't know that, awesome.
gravatar Jamie Wednesday, September 5, 2012
I love data attributes too. Another useful technique for passing blocks of data is in script blocks, e.g.

http://jsfiddle.net/jamietre/WnthV/

<script id="mydata" type="application/json">
{ "key": "value" }
</script>

Using jQuery to read:

var data = $.parseJSON($('#mydata').text());
console.log(data);

For data that's purpose is related to an element I prefer attributes, but sometimes you just need to pass some config or a batch of data to the client when the page is loaded that's not related to a particular element.

This method seems more semantically correct than sticking it onto an hidden element that's otherwise not used because it also identifies the MIME type of information being passed to the client. And as long as the type isn't "text/javascript", it will never be rendered or parsed by the browser.

While (browsers/jQuery/nothing I know of now) has a feature to use "type" to automatically figure out how to parse data in script blocks, this seems like the "right" way to pass blocks of arbitrary data, and any library could easily be designed to use the type attribute to do the right conversions automatically.
Comments are now closed.
by K. Scott Allen K.Scott Allen
My Pluralsight Courses
The Podcast!