Named routing with Knockout, ASP.NET MVC, and AttributeRouting, from JavaScript

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:

  1. Named routes, with a wee bit of a Rails feel
  2. A Knockout friendly syntax for binding to the values of a model to build an href
  3. Automatic replacement of values in the url with values from the model
  4. JavaScript generation (OK, not really a goal, but a necessary evil I suspected)

Solution:

I started by using AttributeRouting (from AttributeRouting.net). It offers a simple syntax for creating routes (and naming them):

[sourcecode language=”csharp”]
[GET("research/{id}", RouteName = "research_details")]
public ActionResult Details(string id)
{
var model = new ResearchDetailsViewModel();
[/sourcecode]

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):

[sourcecode language=”csharp”]

var named = new List();
foreach (var route in RouteTable.Routes)
{
AttributeRoute r = route as AttributeRoute;
   if (r != null)
   {
      if (!string.IsNullOrWhiteSpace(r.RouteName))
      {
         named.Add(r);
      }
   }
}
if (named.Count > 0)
{
   StringBuilder js = new StringBuilder();
   foreach (var namedRoute in named)
   {
      js.AppendFormat(@"function {0}_url(model){{ return buildUrl(""{1}"", model); }}",
      namedRoute.RouteName, namedRoute.Url);
      js.AppendLine();
   }
   RouteScript = js.ToString();
}
[/sourcecode]

The name of the function is based on the RouteName property of the attribute.

I added a function called buildUrl to a common JavaScript file:

[sourcecode language=”javascript”]

function buildUrl(url, model) {
// unfixed if there’s not a thing
  if (typeof model === ‘undefined’ || model === null) { return url; }

var propValue;
  for (var propName in model) {
       if (model.hasOwnProperty(propName)) {
propValue = model[propName];
        if (ko) { propValue = ko.utils.unwrapObservable(propValue); }

if (typeof propValue === ‘undefined’ || propValue === null) {
           propValue = "";
} else {
          propValue = propValue.toString();
       }
       url = url.replace(‘{‘ + propName.toLowerCase() + ‘}’, propValue);
  }
   }
   return url;
}
[/sourcecode]

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.

It lowercases each property name and tries a replacement.

So, if the string was /research/{id}/{category}, and the model has properties named, Id and Category, the values of each will be substituted and returned as a full string. For example, it might be /research/123/Coding.

Next, a simple example of using some JSON data:

[sourcecode language=”javascript”]
$(function () {
var vm = {
url: function ($data) {
return app_url(research_details_url($data));
},
data : ko.observable()
};

$.getJSON(app_url("api/research/")).success(function (data) {
vm.data = ko.mapping.fromJS(data);
ko.applyBindings(vm);
});
});
[/sourcecode]

Then, finally, the Knockout template:

[sourcecode language=”html”]
<div data-bind="foreach: data.research">
<h3 class="title" data-bind="text: Title"></h3>
<div>
<a data-bind="attr: { href: $parent.url($data) }">Details</a>
</div>
</div>
[/sourcecode]

By using $parent in the template, Knockout points at the view model in this case (vm). On the view model, I added a function called url. 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.

app_url:

[sourcecode language=”javascript”]

function app_url(url) {
return "@Request.ApplicationPath" + url;
}

[/sourcecode]

As I said, it’s not super elegant, but it achieved my basic goals.

Elegance rating: 59%

Function rating: 100%

Need of some refactoring rating: 99%

Nest Thermostat Wifi Connectivity Issues?

I’ve been seeing more Wifi issues recently with my Nest thermostats. I don’t see a pattern to the problem, and it seems to randomly affect all of them (sometimes at the same time, but often just one).

image

I’ve got a supported Wifi router (Apple Airport Express), and it’s within reasonable range to all of the thermostats in our house (no more than 20 foot from any thermostat). While it’s unfortunately overlapping with a similar band/frequency in our house, the router is dedicated to only the Thermostats right now (a unique SSID).

It doesn’t have any settings that seem necessary to change:

SNAGHTMLe2a23de

image

Any ideas?

Added Away/Home to unofficial-nest-api

I just finished adding a new simple feature to control the away status for a structure to my unofficial-nest-api published on GitHub and available as a node package (npm).

Usage is simple as calling setAway or setHome on the nest instance after authentication and a successful status has been returned (see commented calls below).

[javascript]
if (username && password) {
username = trimQuotes(username);
password = trimQuotes(password);
nest.login(username, password, function (data) {
if (!data) {
console.log(‘Login failed.’);
process.exit(1);
return;
}
console.log(‘Logged in.’);
nest.fetchStatus(function (data) {
for (var deviceId in data.device) {
if (data.device.hasOwnProperty(deviceId)) {
var device = data.shared[deviceId];
console.log(util.format("%s [%s], Current temperature = %d F target=%d",
device.name, deviceId,
nest.ctof(device.current_temperature),
nest.ctof(device.target_temperature)));
}
}
subscribe();
//nest.setAway();
//nest.setHome();
});
});
}
[/javascript]

[PSA] Discovery channel Velocity available on Dish Network

Public Service Announcement:

If you’re looking for Discovery’s Velocity on Dish Network – they do have it. It’s available only by subscribing to Blockbuster.

Blockbuster includes one DVD/Blu-ray at a time plus the following additional channels for $10 additional a month (and an extra $5 for 2 discs at a time).

image

(We looked around for quite a while to find this. There are many many pages that contain out of date information as they were written while it was transitioning from different programming, and also in a trial, so I hope others find this useful).

If you’re mildly into cars, it looks like a fun channel.