). It offers a simple syntax for creating routes (and naming them):<\/span><\/p>\n[sourcecode language=”csharp”]
\n[GET("research\/{id}", RouteName = "research_details")]
\npublic ActionResult Details(string id)
\n{
\n var model = new ResearchDetailsViewModel();
\n[\/sourcecode]<\/p>\n
In RouteConfig.cs, code gathers the list of named routes, and builds a snippet of JavaScript which will be inserted on every page (yes, it would be nice if this were cached, see refactoring below):<\/p>\n
[sourcecode language=”csharp”]<\/p>\n
var named = new List();
\nforeach (var route in RouteTable.Routes)
\n{
\nAttributeRoute r = route as AttributeRoute;
\n \u00a0 \u00a0if (r != null)
\n \u00a0 \u00a0{
\n \u00a0 \u00a0\u00a0 \u00a0if (!string.IsNullOrWhiteSpace(r.RouteName))
\n \u00a0 \u00a0\u00a0 \u00a0{
\n \u00a0 \u00a0\u00a0 \u00a0\u00a0 \u00a0named.Add(r);
\n \u00a0 \u00a0\u00a0 \u00a0}
\n \u00a0 \u00a0}
\n}
\nif (named.Count > 0)
\n{
\n \u00a0 \u00a0StringBuilder js = new StringBuilder();
\n \u00a0 \u00a0foreach (var namedRoute in named)
\n \u00a0 \u00a0{
\n \u00a0 \u00a0\u00a0 \u00a0js.AppendFormat(@"function {0}_url(model){{ return buildUrl(""{1}"", model); }}",
\n \u00a0 \u00a0\u00a0 \u00a0namedRoute.RouteName, namedRoute.Url);
\n \u00a0 \u00a0\u00a0 \u00a0js.AppendLine();
\n \u00a0 \u00a0}
\n \u00a0 \u00a0RouteScript = js.ToString();
\n}
\n[\/sourcecode]<\/p>\n
The name of the function is based on the RouteName property of the attribute.<\/p>\n
I added a function called buildUrl\u00a0<\/strong>to a common JavaScript file:<\/p>\n[sourcecode language=”javascript”]<\/p>\n
function buildUrl(url, model) {
\n \/\/ unfixed if there’s not a thing
\n \u00a0 if (typeof model === ‘undefined’ || model === null) { return url; }<\/p>\n
var propValue;
\n \u00a0 for (var propName in model) {
\n \u00a0 \u00a0\u00a0 \u00a0 if (model.hasOwnProperty(propName)) {
\n propValue = model[propName];
\n \u00a0 \u00a0\u00a0 \u00a0\u00a0 if (ko) { propValue = ko.utils.unwrapObservable(propValue); }<\/p>\n
if (typeof propValue === ‘undefined’ || propValue === null) {
\n \u00a0 \u00a0\u00a0 \u00a0\u00a0 \u00a0\u00a0 propValue = "";
\n } else {
\n \u00a0 \u00a0\u00a0 \u00a0\u00a0 \u00a0 propValue = propValue.toString();
\n \u00a0 \u00a0\u00a0 \u00a0 }
\n \u00a0 \u00a0\u00a0 \u00a0 url = url.replace(‘{‘ + propName.toLowerCase() + ‘}’, propValue);
\n \u00a0 }
\n \u00a0 \u00a0}
\n \u00a0 \u00a0return url;
\n}
\n[\/sourcecode]<\/p>\n
The code above validates there is data and then proceeds to loop through every property of the model to see if there might be a replacement needed within the passed URL. If Knockout is available (if ko check), then it unwraps the value as the code needs the raw value from this point onward.<\/p>\n
It lowercases each property name and tries a replacement.<\/p>\n
So, if the string was \/research\/{id}\/{category}<\/strong>, and the model has properties named, Id<\/strong> and Category<\/strong>, the values of each will be substituted and returned as a full string. For example, it might be \/research\/123\/Coding<\/strong>.<\/p>\nNext, a simple example of using some JSON data:<\/p>\n
[sourcecode language=”javascript”]
\n$(function\u00a0()\u00a0{
\n var\u00a0vm\u00a0=\u00a0{
\n url:\u00a0function\u00a0($data)\u00a0{
\n return\u00a0app_url(research_details_url($data));
\n },
\n data\u00a0:\u00a0ko.observable()
\n };<\/p>\n
$.getJSON(app_url("api\/research\/")).success(function\u00a0(data)\u00a0{
\n vm.data\u00a0=\u00a0ko.mapping.fromJS(data);
\n ko.applyBindings(vm);
\n });
\n});
\n[\/sourcecode]<\/p>\n
Then, finally, the Knockout template:<\/p>\n
[sourcecode language=”html”]
\n<div data-bind="foreach: data.research">
\n <h3 class="title" data-bind="text: Title"><\/h3>
\n <div>
\n <a data-bind="attr: { href: $parent.url($data) }">Details<\/a>
\n <\/div>
\n<\/div>
\n[\/sourcecode]<\/p>\n
By using $parent in the template, Knockout points at the view model in this case (vm<\/strong>). On the view model, I added a function called url<\/strong>. Right now, I’ve hard coded it to point to a specific dynamically generated function called research_details_url. It’s passing the array item (which simply has an Id & Title property). So, the data-bind for the anchor (A) element assigns the value of the computation to the href attribute.<\/p>\napp_url:<\/p>\n
[sourcecode language=”javascript”]<\/p>\n
function app_url(url) {
\nreturn "@Request.ApplicationPath" + url;
\n}<\/p>\n
[\/sourcecode]<\/p>\n
As I said, it’s not super elegant, but it\u00a0achieved\u00a0my basic goals.<\/p>\n
Elegance rating: 59%<\/p>\n
Function rating: 100%<\/p>\n
Need of some refactoring rating: 99%<\/p>\n","protected":false},"excerpt":{"rendered":"
It’s hard to describe exactly what I’ve built here, but I’m just throwing these pieces out on the Internet in case someone: a) finds them useful, or b) has a better solution. Goals: Named routes, with a wee bit of a Rails feel A Knockout friendly syntax for binding to the values of a model […]<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"spay_email":"","jetpack_publicize_message":"","jetpack_is_tweetstorm":false,"jetpack_publicize_feature_enabled":true},"categories":[4],"tags":[],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"jetpack_shortlink":"https:\/\/wp.me\/pd5QIe-t2","jetpack_likes_enabled":true,"jetpack-related-posts":[{"id":1820,"url":"https:\/\/www.wiredprairie.us\/blog\/index.php\/archives\/1820","url_meta":{"origin":1800,"position":0},"title":"Knockout binding for JavaScript route fixup","date":"January 25, 2013","format":false,"excerpt":"Part one. After the first round, I felt compelled to KnockOut the code a bit more. I\u2019d mentioned I wasn\u2019t pleased with the code exactly. It needed some refactoring. So, I\u2019ve created a new Knockout binding handler. This binding handler replaces\u00a0 named parameters with a model\u2019s properties in a path.\u2026","rel":"","context":"In "Coding"","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":1565,"url":"https:\/\/www.wiredprairie.us\/blog\/index.php\/archives\/1565","url_meta":{"origin":1800,"position":1},"title":"Knockout.JS: Dictionary\/Index and ObservableArray, Part 2","date":"March 10, 2012","format":false,"excerpt":"Ryan suggested an alternative in a comment (and corresponding jsFiddle) to the technique that I\u2019d used in my previous Knockout.JS post. Following his suggestion, I made a few tweaks to my original function (and renamed it yet again): ko.observableArray.fn.withIndex = function (keyName) { var index = ko.computed(function () { var\u2026","rel":"","context":"In "Coding"","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":1563,"url":"https:\/\/www.wiredprairie.us\/blog\/index.php\/archives\/1563","url_meta":{"origin":1800,"position":2},"title":"Knockout.JS: AsDictionary","date":"March 9, 2012","format":false,"excerpt":"I frequently find that I have an array of objects in JavaScript that I want to display in a particular order and also have the ability to quickly locate an object by an ID or a key (and not use the indexOf function). As my recent project is using Knockout.JS,\u2026","rel":"","context":"In "Coding"","img":{"alt_text":"","src":"https:\/\/i0.wp.com\/www.wiredprairie.us\/blog\/wp-content\/uploads\/2012\/03\/image3.png?resize=350%2C200","width":350,"height":200},"classes":[]},{"id":1171,"url":"https:\/\/www.wiredprairie.us\/blog\/index.php\/archives\/1171","url_meta":{"origin":1800,"position":3},"title":"Translating Controller, Action, and Route Data to a JavaScript Object in ASP.NET MVC 3","date":"February 2, 2011","format":false,"excerpt":"To enable a more rich JavaScript\/Ajax experience on a web page, I had need of more detailed information regarding the route that resulted in the current View being displayed. I checked around a few sites, and nothing popped out as obviously awesome. As I was putting the JavaScript in the\u2026","rel":"","context":"In "Coding"","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":1345,"url":"https:\/\/www.wiredprairie.us\/blog\/index.php\/archives\/1345","url_meta":{"origin":1800,"position":4},"title":"Creating a simple Entity Reference system for Ember.js","date":"December 28, 2011","format":false,"excerpt":"I had some data stored in a structure similar to this: Nothing too fancy. A table of Gift(s) and a table of Person(s). Each gift had a foreign key relationship to a Person (the person who \u201cgave\u201d the gift). Since some people may give several gifts, I didn\u2019t want to\u2026","rel":"","context":"In "Coding"","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":1442,"url":"https:\/\/www.wiredprairie.us\/blog\/index.php\/archives\/1442","url_meta":{"origin":1800,"position":5},"title":"Nest Thermostat API\/Protocol","date":"January 8, 2012","format":false,"excerpt":"While Nest Labs hasn\u2019t released a formal (documented & supported) API, I thought I\u2019d do a bit of digging to see how they\u2019re using the network and what might be achievable. A few things are going on, the majority as you\u2019d probably expect. The web interface is using a long\u2026","rel":"","context":"In "Coding"","img":{"alt_text":"image","src":"https:\/\/i0.wp.com\/www.wiredprairie.us\/blog\/wp-content\/uploads\/2012\/01\/image_thumb7.png?resize=350%2C200","width":350,"height":200},"classes":[]}],"_links":{"self":[{"href":"https:\/\/www.wiredprairie.us\/blog\/index.php\/wpjson\/wp\/v2\/posts\/1800"}],"collection":[{"href":"https:\/\/www.wiredprairie.us\/blog\/index.php\/wpjson\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.wiredprairie.us\/blog\/index.php\/wpjson\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.wiredprairie.us\/blog\/index.php\/wpjson\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.wiredprairie.us\/blog\/index.php\/wpjson\/wp\/v2\/comments?post=1800"}],"version-history":[{"count":19,"href":"https:\/\/www.wiredprairie.us\/blog\/index.php\/wpjson\/wp\/v2\/posts\/1800\/revisions"}],"predecessor-version":[{"id":1819,"href":"https:\/\/www.wiredprairie.us\/blog\/index.php\/wpjson\/wp\/v2\/posts\/1800\/revisions\/1819"}],"wp:attachment":[{"href":"https:\/\/www.wiredprairie.us\/blog\/index.php\/wpjson\/wp\/v2\/media?parent=1800"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.wiredprairie.us\/blog\/index.php\/wpjson\/wp\/v2\/categories?post=1800"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.wiredprairie.us\/blog\/index.php\/wpjson\/wp\/v2\/tags?post=1800"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}