Refactoring Nerd Dinner – Adding Knockout JS

Ever wonder what Knockout JS is? Since it’s going to be a part of the default ASP.NET template in VS2012 I figured that I should take a look. AND what better project to try something new in than Nerd Dinner.

The Popular Dinners list in the Home/Index view is already using jQuery to get JSON data and dynamically building the list, this seems like the perfect candidate to refactor to use knockout.

dinners_thumb[2]

The first step in adding Knockout to NuGet (assuming that you already have the latest Nerd Dinner source) is to get the package from NuGet

knockout[9]

First, I need to add a script reference to knockout in the _Layout.cshtml

http://@Url.Content(

Next, the code in the Index.cshtml view needs to be changed from the empty container that gets dynamically populated to the use a knockout template. So I changed this line:


 
to:



Similarly, in the NerdDinner.js file _renderDinners needs to be changed to use knockout model binding. After some code cleanup _renderDinners now looks like this:
NerdDinner._renderDinners = function (dinners) {
var viewModel = {
dinners: ko.observableArray(dinners)
};
ko.applyBindings(viewModel);

NerdDinner.ClearMap();

$.each(dinners, function (i, dinner) {
var LL = new VELatLong(dinner.Latitude, dinner.Longitude, 0, null);

// Add Pin to Map
NerdDinner.LoadPin(LL, _getDinnerLinkHTML(dinner), _getDinnerDescriptionHTML(dinner), false);

// Display the event's pin-bubble on hover.
var dinnerPin = _getDinnerPin(dinner.DinnerID);
if (dinnerPin != null) {
$(dinner).hover(
function () { NerdDinner._map.ShowInfoBox(dinnerPin); },
function () { NerdDinner._map.HideInfoBox(dinnerPin); }
);
}
});

// Adjust zoom to display all the pins we just added.
if (NerdDinner._points.length > 1) {
NerdDinner._map.SetMapView(NerdDinner._points);
}
};

Next, there were some custom binders that I needed to add. One for displaying the EventDate properly and another for showing the RSVP Message. They look like this at the end of the NerdDinner.js file:
ko.bindingHandlers.dateString = {
update: function (element, valueAccessor, allBindingsAccessor) {
var value = valueAccessor();
var allBindings = allBindingsAccessor();
var valueUnwrapped = ko.utils.unwrapObservable(value);
var pattern = allBindings.datePattern || 'MM/dd/yyyy';
var v1 = eval('new' + valueUnwrapped.replace(/\//g, ' '));
var v2 = v1.format(pattern);
$(element).text(v2);
}
};
ko.bindingHandlers.rsvpMessage = {
update: function (element, valueAccessor) {
var value = valueAccessor();
var rsvpMessage = " with " + value + " RSVP";
if (value > 1)
rsvpMessage += "s";
$(element).text(rsvpMessage);
}
};

Finally, This sample has just barely scratched the surface of the interesting things that knockout allows you to do. If you’re interested in learning more go the Knockout JS site. Also, the Nerd Dinner source is on Codeplex. I’d encourage you to get it, fork it, and make it better!

Adding HTML5 Geolocation to Nerd Dinner with yepnope.js, and Modernizr

One of the new features of the recently updated Nerd Dinner project is the support for HTML5 Geolocation.

There are two JavaScript libraries that I used to accomplish this. The first is yepnope.js (http://yepnopejs.com/). Yepnope.js is an asynchronous resource loader. This means that you can load scripts based on the outcome of some condition. The second is Modernizr v1.7 (http://www.modernizr.com/). Modernizr detects a web browser’s ability to support various HTML5 and CSS3 features. Note: Modernizr v2 has yepnope.js built into the production version so you may want to consider using it in your project.

It’s important to remember that yepnope.js is an asynchronous resource loader. This means that the script you’re loading WON’T be available on page load or $(document).ready. What I did was create a common JavaScript function called getCurrentLocation in both javascript files and call that when the script load is completed.

The code in Nerd Dinner that kicks the Geolocation off is this:


So the whole thing works like this: First, the resource loader (yepnope.js) tests if the browser supports native geolocation by using Moderizr. If the browser does (yep) support geolocation natively, the geo.js gets loaded and the callback calls the geo.js version of getCurrentLocation.

geo.js

function getCurrentLocation() {
    try {
        // do native HTML5 geolocation
        navigator.geolocation.getCurrentPosition(showCurrentLocation, errorHandler)
    }
    catch (e) {
        // something happened, do it the old way
        NerdDinner.getCurrentLocationByIpAddress();
    }
}

// callback from geolocation
function showCurrentLocation(position) {
    // go update the location box
    NerdDinner.getCurrentLocationByLatLong(position.coords.latitude, position.coords.longitude);
}

Otherwise (nope), Nerd Dinner uses geo-polyfill.js and the callback calls his version of getCurrentLocation, which uses the browser’s IP address to query a geolocation service for the location data.

geo-polyfill.js

function getCurrentLocation() {
    // this is how we used to do it by calling a geolocation service using the client IP address
    NerdDinner.getCurrentLocationByIpAddress();
}

Finally both methods use the location data they got to populate the Location box. This behavior is in both the regular (Views/Home/Index.cshtml) and mobile (Views/Home/Index.Mobile.cshtml) versions.

If you want to learn more, I’d encourage you to go checkout the source for Nerd Dinner on CodePlex at http://nerddinner.codeplex.com/.