Voodoo – A Todo list that demos the power of KnockoutJS

Voodoo - A todo list

This small demo app will demonstrate the usage and power of JavaScript MVC frameworks and in particular KnockoutJS. You can learn more about the framework through the tutorials on the KO site. I will gloss over some of the details but you can learn more in framework documentation. My goal here is to give you a high-level sense of what’s possible. The picture along side shows what we’re building. You can find the demo here and the full sourcecode here.

The HTML

Strictly speaking jQuery is not required for KO to work but it is likely that you will often include it as a helper for the framework. As alway you need to start with the static resource inclusions.

[code language=”html”]
<script type="text/javascript" src="js/jquery-1.7.1.min.js"></script>
<script type="text/javascript" src="js/knockout-2.0.0.js"></script>
[/code]

And you’ll need a form in order to create new todo items.

[code language=”html”]
<form data-bind="submit: addTask" id="create-todo">
<input class="new-todo" data-bind="value: newTaskText" placeholder="What needs to be done?" />
</form>
[/code]

For the first time you’ll notice the data-bind attribute. The framework recognises this attribute and parses the attribute value to determine what logic to apply. In this case the input element is bound to a JavaScript property called newTaskText.
Next up you need the markup that contains and displays each task. Some actions are available for each item too.

[code language=”html”]
<div class="todos">
<ul data-bind="foreach: tasks, visible: tasks().length > 0" id="todo-list">
<li>
<div class="todo" data-bind="css: { editing: isEditing }, event: { dblclick: startEdit }">
<div class="display" data-bind="css: { done: isDone }">
<input type="checkbox" class="check" data-bind="checked: isDone" />
<div class="todo-text" data-bind="text: title"></div>
<a href="#" class="todo-destroy" data-bind="click: $parent.removeTask">&times;</a>
</div>
<div class="edit">
<form data-bind="submit: updateTask">
<input data-bind="value: title" />
</form>
</div>
</div>
</li>
</ul>
</div>
[/code]

Again you’ll notice that each element that is to be used in someway by KO has an attribute of data-bind. Below I’ve picked out a few lines to demonstrate key functionality. The following line is an instruction to run through a collection of tasks and only display the ul element if there’s anything in the collection.

[code language=”html”]
<ul data-bind="foreach: tasks, visible: tasks().length > 0" id="todo-list">
[/code]

The line below is used to conditionally apply a style class and ensures that the doubleclick event is bound to the appropriate handler.

[code language=”html”]
<div class="todo" data-bind="css: { editing: isEditing }, event: { dblclick: startEdit }">
[/code]

And here we have an example of an input element being bound to a JavaScript object field isDone – the object structure will be shown later.

[code language=”html”]
<input class="check" type="checkbox" data-bind="checked: isDone" />
[/code]

Now here’s some of the magic of KO. Below are the some stats based on the number of tasks in the list. If you were using jQuery or just JavaScript you would have to track the number of elements in the list and update the stats appropriately.

[code language=”html”]
You have <b data-bind="text: incompleteTasks().length">&nbsp;</b> incomplete task(s)
<span data-bind="visible: incompleteTasks().length == 0"> – its beer time!</span>
[/code]

With KO the view is driven by the underlying object data. If the number of items in the list changes all related information is automatically updated in the view! In KO this is facilitated through concepts known as observables and dependency-tracking.

The JavaScript

KO is the first time I’ve used OOP within JavaScript for some time, and it’s pleasure to work with the concepts in such a paradigm! In this small app there are only 2 classes, one for tasks (fairly obvious) and another for the ViewModel which you can consider the application class.
The Task class contains the properties and methods applicable to Tasks. You’ll notice how the properties are initialised using using the ko.observable() method. This is a touch more magic and it means that the values of these properties will be “watched”. If they are changed either through the user interface or via JavaScript then all dependent views elements and JavaScript values will be changed too.

[code language=”javascript”]
function Task(data) {
this.title = ko.observable(data.title);
this.isDone = ko.observable(data.isDone);
this.isEditing = ko.observable(data.isEditing);

this.startEdit = function (event) {
this.isEditing(true);
}

this.updateTask = function (task) {
this.isEditing(false);
}
}
[/code]

The ViewModel class exposes the Tasks in a meaningful way and provides methods on that data. Types of data exposed here are observable arrays of tasks and properties that return the number of complete and incomplete tasks. The operations are simple add and remove functions. Right at the end of the class I’ve used jQuery to load JSON objects into the todo list.

[code language=”javascript”]
function TaskListViewModel() {
// Data
var self = this;
self.tasks = ko.observableArray([]);
self.newTaskText = ko.observable();
self.incompleteTasks = ko.computed(function() {
return ko.utils.arrayFilter(self.tasks(),
function(task) {
return !task.isDone() && !task._destroy;
});
});

self.completeTasks = ko.computed(function(){
return ko.utils.arrayFilter(self.tasks(),
function(task) {
return task.isDone() && !task._destroy;
});
});

// Operations
self.addTask = function() {
self.tasks.push(new Task({ title: this.newTaskText(), isEditing: false }));
self.newTaskText("");
};
self.removeTask = function(task) { self.tasks.destroy(task) };

self.removeCompleted = function(){
self.tasks.destroyAll(self.completeTasks());
};

/* Load the data */
var mappedTasks = $.map(data, function(item){
return new Task(item);
});

self.tasks(mappedTasks);
}
[/code]

The very last line in the JavaScript code tells KO to apply all it’s magic using the ViewModel and markup we’ve written.

Summary

To me it’s amazing how little code you need to write in order to build such a neat app. And you don’t even need to track the view state at all! Hopefully this gives you the confidence to start using JavaScript MVC/MVVM frameworks because in the end it helps save you heaps of time and effort.

6 thoughts on “Voodoo – A Todo list that demos the power of KnockoutJS”

  1. Wes, have you played with Backbone.js at all? Haven’t seen as much “stuff” on Knockout but can’t read a Ruby, Node.js or Javascript newsletter without articles about Backbone.js. We did have someone submit a CloudSpokes challenge using Knockout for data modeling. Any idea what the major differences are?

    Reply
    • I started with Backbone but my initial desire was to integrate with Force.com and Backbone only really works with RESTful webservices. KO gave me the freedom to use JavaScript Remoting without hacking the framework. The differences I’ve noticed are:

      – Backbone is MVC but a very weird form of it / KO is MVVM and is very pure in it’s implementation
      – Backbone is quite tough to get started with / KO is a piece of cake
      – Backbone is much more complex but allows finer customisation. For smaller apps you can write code MUCH quicker with KO e.g. this app is about 1/4 the code of the equivalent backbone version.

      I’m getting into Backbone now and it is beautiful. I would only use it for big apps like Do.com (which is Backbone). For smaller widget type apps I’d using KO everytime.

      Reply
      • I will answer to myself since I found this in release notes for newest winter release. New feature is called “Pass-Through HTML Attributes” page 190:

        “You can add arbitrary attributes to the component that will be “passed through” to the rendered HTML. This is useful, for example, when using Visualforce with JavaScript frameworks, such as jQuery Mobile and Knockout.js, that use data-* attributes as hooks to activate framework functions.”

        Reply
  2. Hi Wes,

    I read your article and think it’s really good. Currently working with Backbone and Force.com and found this as a nice alternative. However, did you find a way to really integrate KO with Force.com(Visualforce components)? I tried and seems that there is no way to use <apex:inputText instad of <input type="text" with KO since data-bind is not allowed attribute for VF components.

    Thanks

    Reply

Leave a Comment