Learning Knockout JS — Crazy Mom Baby Tracker Demo
I’m thrilled to be able to report my wife and I had our second daughter on May 8th, 2012. Vanessa was 7 pounds even and very healthy. Due to the birth, I took a few days off of work to help out (in many ways, I think I was more work for my wife being home). Most of my time has been spent getting acquainted with my new daughter, but occasionally, I’ve grabbed an hour here and there, usually in the middle of the night after a feeding, to read about knockout and even write a small demo as a learning exercise.
This little app is not meant to serve any commercial value and is very simplistic, but given the current situation I felt it was a fun and fitting topic.
To any mom’s out there, I mean the title to be lighthearted — no offense intended!
Ok, on with the article.
What’s It Made Out Of?
The Crazy Mom Baby Tracker is intended to exercise the following technologies:
At the Southwest Florida .NET User Group, we recently had a Battle of the UI Frameworks which highlighted a general movement towards JavaScript-centric applications in the .net community. I thought we were aggressive at work, perhaps even cutting edge, but alas — it turns out we’re about where everyone else appears to be right now.
HTML5 is, of course, the latest version of HTML (at the time of this blog post) and all the rage. Though I will display HTML5 semantics, there are not any earth shattering HTML5 snippets throughout this demo.
jQuery is found throughout, as it has become the de facto standard for working with JavaScript these days.
Bootstrap was shown to me by our non-.NET marketing development team and it has been a nice addition for standardizing the HTML structure, CSS (or LESS) classes, and general user experience.
Knockout is very recent for me and the main purpose of this educational demo. It is a responsive MVVM JavaScript implementation that binds the UI to the underlying view model.
jqPlot is a jQuery charting library that I added into the project to visualize the data more interestingly.
A Little Prep Work Before We Get Building
This application is pure HTML, CSS, and JavaScript. As such, there is no need for the .net framework or Visual Studio. However, it certainly is a nice IDE to work in and by using the Nuget Package Manager you can get up and running very quickly. Therefore, all screenshots will be provided using Visual Studio, but this is not a requirement and you can ignore these references if you like.
First, let’s create a new Web Site. In Visual Studio, click File > New Website. Then under the template selector, choose ASP.NET Empty Web Site. By selecting this, you get a pretty bare web site that does not need to contain any ASP-related tags or code. After selecting an appropriate location to store the files you should be ready to begin.
Next, right click the project and select Manage Nuget Packages. You will want the following packages: jQuery, Bootstrap from Twitter, Knockout JS, and jqPlot.
Let’s also add a few placeholder files that we’ll work with later. Please create the following:
- /Content/my.css
- /Scripts/my.js
- /index.html
Let’s Start Building
At this point, everything should be ready for us to start getting to the good stuff. Let’s open the index.html (our main application page) and add the css references for the selected Nuget packages, as well as our custom css file.
<head>
<title>Crazy Mom Demo</title>
<link rel="stylesheet" type="text/css" href="Content/bootstrap.min.css" />
<link rel="stylesheet" type="text/css" href="Content/bootstrap-responsive.min.css" />
<link rel="stylesheet" type="text/css" href="Scripts/jqPlot/jquery.jqplot.min.css" />
<link rel="stylesheet" type="text/css" href="Content/my.css" />
</head>
Next, let’s add our script tags to bring in the code for the Nuget packages, and our own placeholder js file which we will use to add all of our custom logic to drive the application.
<body>
<script type="text/javascript" src="Scripts/jquery-1.7.2.min.js"></script>
<script type="text/javascript" src="Scripts/bootstrap.min.js"></script>
<script type="text/javascript" src="Scripts/knockout-2.1.0.js"></script>
<script type="text/javascript" src="Scripts/jqPlot/jquery.jqplot.min.js"></script>
<script type="text/javascript" src="Scripts/my.js"></script>
</body>
Now that the references are in place, we need to build the structure of the html. Since we’re using bootstrap, we’re going to use the fixed grid layout they provide (hence the css classes “row” and “spanX”). The basics are below.
<div class="container">
<h1 data-bind="text: title">Title</h1>
<div class="row">
<!—-panel for data entry -->
<div class="span8">
</div><!—-panel for my cute kids picture -->
<div class="span4">
<!-—we won’t cover adding this in the blog post -->
</div>
</div>
</div>
We need a container wrapping the layout, and a few other layout related div’s to format the page. Notice the h1 tag which has our first knockout data-bind attribute. This is going to look for a property on the viewmodel called “title” and bind the innerText to it’s value.
Next, inside the data entry panel, let’s add two textboxes and a pair of buttons to control adding baby weight entries.
<form class="form-inline well" data-bind="submit: addItem">
<h3>Enter the babies weight below</h3>
<label>Pounds</label>
<input id="pounds" type="text" class="input-mini" data-bind="hasfocus: true" /><label>Remaining ounces</label>
<input id="ounces" type="text" class="input-mini" /><button type="submit" class="btn btn-primary"><i class="icon-ok icon-white"></i> Add Baby Weight</button>
<button type="reset" class="btn btn-danger" data-bind="click: clearItems"><i class="icon-remove icon-white"></i> Start Over</button>
</form>
The css classes for the form are also from bootstrap and help to stylize the form. You can view the bootstrap documentation for more details.
The form has a knockout binding for submit to call the function on the viewmodel “addItem”. There is also a binding for the click event of the reset button to clear all data in the viewmodel (not just the form fields as normal).
Directly below the form, let’s add a section for displaying notifications and data validation. We’ll again use knockout to bind the results of the messages based on what is happening in the viewmodel.
<div id="alert" class="alert"
data-bind="
visible: msg().length > 0,
css: {
'alert-success': msgType() == 'success',
'alert-error': msgType() == 'error',
'alert-info': msgType() == 'info' }"<
<a class="close" href="#" data-bind="click: hideAlert">×</a>
<p data-bind="text: msg"></p>
</div>
Let’s now add the final pieces to allow for a bit of data visualization. We’re going to use a chart control from jqPlot and a table displaying the individual entries.
<div id="resultsChart" data-bind="chart: items()"></div><table class="table table-striped" data-bind="visible: items().length > 0">
<thead>
<tr>
<td>Weight</td>
<td>Total Pounds</td>
<td>Total Ounces</td>
</tr>
</thead>
<tbody data-bind="foreach: items">
<tr>
<td data-bind="text: display()"></td>
<td data-bind="text: totalPounds()"></td>
<td data-bind="text: totalOunces()"></td>
<td><a href="#" data-bind="click: $parent.removeItem"><i class="icon-remove"></i></a></td>
</tr>
</tbody>
</table>
The chart is interesting, as it will be a custom binding we create for knockout to work with jqPlot. Data within the table are bound to an array of items and looped through using the foreach knockout binding. I’ve also added a remove button next to each entry to allow for the removal of entries added by mistake. Notice the scoping when specifying the knockout binding; while looping through the items, we’re at the individual item level — therefore, we must move up one level to access the viewmodel directly ($parent) and call the removeItem function.
Wiring Up the Logic with Knockout and jQuery
Now that we have a clear picture of what we want this application to look like, let’s wire up the viewmodel and make it actually perform.
Open up your my.js file and begin by creating a good old jQuery ready event:
$(function () {
});
We’ll put our code in here. Let’s also create our own namespace with the following code to avoid any collisions.
// global namespace
var my = my || {};
We now need a model to structure the baby weight entries. Let’s create it as follows:
// models
my.BabyWeight = function(pounds, ounces) {
var self = this;self.pounds = pounds;
self.remainingOunces = ounces;self.totalOunces = function () {
return (self.pounds * 16) + (self.remainingOunces * 1);
};
self.totalPounds = function () {
return (self.pounds * 1) + (self.remainingOunces / 16);
};
self.display = function () {
return self.pounds + 'lbs - ' + self.remainingOunces + 'oz';
};
};
This could contain knockout observables and computed values, but it’s not particularly necessary the way the demo is set up. The BabyWeight model has two properties: pounds and remainingOunces which together make up for the entire weight of the baby. I’ve also added a few calculated properties to add to the tabular data for each entry.
Let’s now create the viewmodel which will contain the bulk of our knockout observables.
// view model
my.vm = function(existingItems) {
var self = this;// properties
self.items = ko.observableArray(existingItems);
self.msg = ko.observable("");
self.msgType = ko.observable("info");
self.title = ko.observable("Crazy Mom Baby Tracker v.001");// methods
self.addItem = function () {
var pounds = $('#pounds').val();
var remainingOunces = $('#ounces').val();
var itemToAdd = new my.BabyWeight(pounds, remainingOunces);
// validate
if (itemToAdd.pounds == "" || itemToAdd.ounces == "") {
self.msgType("error");
self.msg("Oops, either the baby has become weightless or you didn't enter any data.");
return;
}
else {
self.msg("");
}// add to items array
self.items.push(itemToAdd);// update msg
self.msgType("success");
self.msg("You've successfully weighed the baby in at " + itemToAdd.display());
},
self.clearItems = function () {// clear items
self.items([]);// update msg
self.msgType("info");
self.msg("All weight entries have been cleared.");
},
self.hideAlert = function () {
self.msg(""); //clearing the message will auto-hide since it's bound
},
self.removeItem = function (item) {// remove item from items array
self.items.remove(item);// update msg
self.msgType("info");
self.msg("The weight entry has been successfully removed.");
}
};
Note the use of this line:
var self = this;
This helps to maintain reference to the proper this when inside callbacks from anonymous functions.
The observables ensure that changes to their values will be automatically reflected in the UI for any bindings. For example, as an item is added or removed from the items array, the UI for the chart and table will automatically be updated, well as soon as we add the custom binding to the chart that is. Let’s add that now:
// kick off knockout bindings
ko.applyBindings(new my.vm([]));
// add custom binding for charting
ko.bindingHandlers.chart = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
// empty - left as placeholder if needed later
},
update: function (element, valueAccessor, allBindingsAccessor, viewModel) {
// prepare chart values
var items = ko.utils.unwrapObservable(valueAccessor);
var chartValues = [[]];
for (var i = 0; i < items().length; i++) {
chartValues[0].push(items()[i].totalOunces());
}// clear previous chart
$(element).html("");// plot chart
$.jqplot(element.id, chartValues, {
title: 'Baby Weight'
});
}
};
The custom binding simply updates the chart on any change to the passed in valueAccessor, which we specified in the html as the items array in the viewmodel. jqPlot uses the element.id, in our case a div tag, to act as the placeholder container to drop the chart into. See the jqPlot documentation for much more detail on creating significantly more elaborate charting capabilities.
Some Odds and Ends
I didn’t cover the my.css file but I used this to add some very minor additional styling to the page. Most of the styles though do come “out of the box” from bootstrap. I hope someone finds this useful and please feel free to correct any mistakes I’ve made — this is certainly meant to reinforce my own pursuit in working with knockout and I welcome any advice.