TodoMVC in C# with Saltarelle and Knockout





5.00/5 (5 votes)
a TodoMVC implementation written in C# and compiled to JavaScript
Introduction
In this article I present a TodoMVC implementation written in C# and compiled to JavaScript with the Saltarelle compiler. It's an example project meant to show that it's possible to write full featured single-page JavaScript applications directly in C# and Visual Studio, using a known JavaScript framework like Knockout.js.
C# + Saltarelle
Saltarelle is a C# to JavaScript compiler written by Erik Källén, derived from Script# by Nikhil Kothari. It can be used stand-alone (from command line) or from within Visual Studio. When called from Visual Studio, it compiles a C# project producing a single .js file containing the JavaScript executable code.
Saltarelle comes with a full HTML/DOM support library, as well as libraries for emulating basic .NET types, and libraries for widely known JavaScript packages like jQuery, Knockout and NodeJs.
Despite C# and JavaScript are very different languages, most C# features are translated seamlessly into JavaScript with the result that it's possible to code freely in C# without renouncing to classes, types, refactoring, intellisense and all the amazing features that make C# one of the best managed languages.
The code compiled by Saltarelle is also very efficient, compact and human readable, very close to writing JavaScript directly.
Knockout
Knockout.js is a JavaScript library written by Steve Sanderson, that allows to write MVVM (model-view-viewmodel) applications in JavaScript. And thanks to an import library, it integrates perfectly in C#/Saltarelle.
Explaining Knockout is beyond the scope of this article, so I'll assume a basic knowledge of it. Those who are not familiar with this library might want to check directly the Knockout website which is full of tutorials and documentations.
To describe it as briefly as possible, Knockout scans the HTML/DOM looking for data-bind
attributes (which are valid HTML) triggering UI refreshes depending on what's defined in the attribute itself, according to a specific syntax.
For example a
<p data-bind="text: mystring">
markup will cause the <p>
to be updated automatically every time mystring
changes in the ViewModel. It does that by using a Binding
named text
, and by having previously declared mystring
as an
Observable
property. By using bindings and observable properties it's possible to achieve the needed separation between UI and ViewModel, as per MVVM pattern.TodoMVC
TodMVC is an open source project written by Addy Osmani and Sindre Sorhus where a trivial web application (list of things "Todo") is coded using the most common JavaScript MVC frameworks. Its purpose is to compare such frameworks by using a common term (the Todo application) so to let the developer decide what's best suited for him. It's also a good learning exercise for people starting to write single page JavaScript applications, as it touches most of the aspects of an application of this kind.
In this article I have used the Knockout version of the TodoMVC application as a reference. My code tried to be as close as possible to its JavaScript counterpart so it's more easy to compare the machine generated code with the human-written one.
Before going deeply in the article I suggest you to play enough with the TodoMVC application itself, getting acquainted with its functionalities, so that you know what we will be talking about later in the article.
I suggest that you also inspect the index.html
(unchanged from to the original implementation) to see how the data-bind
attributes are plugged to HTML elements. The good thing of TodoMVC is that it's a small application so its code isn't overwhelming, and you can easily get a grasp of it.
MVC and MVVM
TodoMVC was born to feature MVC (model-view-controller) frameworks, but in reality Knockout is based on a MVVM (model-view-viewmodel) architecture (which can be thought as a special case of MVC). In our MVVM design,
- Model is the data stored on the
LocalStorage
(the list of "todo" items) - View is the presentation layer provided by the HTML page (
index.html
) - ViewModel is the C# class connected both to View (via Knockout bindings) and to Model (via LocalStorage and JSON parsing).
Getting Started
The first step is to get the TodoMVC template files as we don't want to rewrite everything from scratch. Since we want to write only the JavaScript part of the application, we copy entirely the TodoMVC sample application from the Knockout version. So get the files and organize them in folders as follows:TodoMVC
assets
base.css
base.js
director.min.js
js
lib
knockout.min.js
index.html
app.js
it's better to rename app.js
into something else like app.original.js
, as our C# compiled code will overwrite it. Keep it separate so you can compare the two versions and study them.
Configuring Saltarelle in Visual Studio
Everything will be organized in a Visual Studio solution. The solution will contain two projects: one is a minimalistic web site project (no ASP.NET involved at all) for the index.html file, assets and libs. Another project, in the same solution, will host the C# files that will be compiled by Saltarelle. This latter project will be a normal console application, for lack of a better project template in Visual Studio.
What really happens on the backstage, is that Visual Studio does a first compilation of the code, and if it succeeds, Saltarelle steps in doing its own compilation. So in reality the code it's compiled twice, the first one only needed for syntax checking (.exe produced will be ignored) and the second one being the real C# to JavaScript compilation.
So open Visual Studio and create a new "Console" C# project. Then open the management console (under tools) and type:
install-package Saltarelle.Compiler install-package Saltarelle.Runtime install-package Saltarelle.Web install-package Saltarelle.Knockout
this will download the Saltarelle compiler from the internet and will install it properly in your solution folder.
Now go to project property -> application, and change both application name and application namespace to "app". This will tell the compiler to produce a single file named "app.js", which is what is referenced by our index.html.
Go to project property -> compilation events -> post compilation command line and add the following line:
copy "$(TargetDir)$(TargetName).js" "$(SolutionDir)\TodoMVC\js"
this will instruct Visual Studio to automatically copy the compiled "app.js" to the website folder everytime you recompile the project. This is required because the output .js file normally is located in the bin folder of the C# project and not in the website folder as required by index.html.
Now add an "existing web site" to the solution, choosing the "TodoMVC" prepared before as root folder. So now you should have two projects in your solution: the website and the C# project.
Set your web site as startup project and mark index.html
as startup page so that you can easily run and debug in Visual Studio.
Copy the file mscorlib.js
from the "packages/Saltarelle.Runtime.1.6.3" folder to "js/lib"; this is the library that mimics .NET types and functionalities in JavaScript, and it's needed for the app to work. "mscorlib.min.js" is the minified version, but for debug purposes use the unminified one.
The mscorlib.js
library has also to be referenced in the html source, so open index.html
and add the following line, just before the "app.js" script reference:
<script src="js/lib/mscorlib.js"></script>
The last thing needed is to update the project dependencies in the solution. Since we have two projects, we have to tell Visual Studio that the website project depends on the C# projects, and that the C# project is to be compiled first.Let's start to code
Having done all the tedious preparatory steps, we can now start to code. OpenProgram.cs
created by the Saltarelle compiler and replace its content with:using System;
using System.Html;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using KnockoutApi;
this adds the basic references.
System
contains the .NET type emulations (e.g. int
is converted to JavaScript number
type, and so on).
System.HTML
contains the references for manipulating the DOM (e.g. the Window
, Document
, Element
objects).
KnockoutApi
contains the metadata for the Knockout.js library, so that we can use it directly from C#. And since it's all metadata, no real code is added by the compiler--using this library is as efficient as writing directly in JavaScript.
System.Runtime.CompilerServices
contains the [Attribute] decorators interpreted by Saltarelle that help to control the output JavaScript code. Some attribute used in the project are:
[IgnoreNameSpace]
tells the compiler to not add namespaces in the JavaScript output file, here for the sole reason of making it more human readable.[PreserveCase]
by default the compiler adopts camel-case names in the JavaScript output, for exampleDummy.ToString()
becomesDummy.toString()
. This design was made to let coders write in ".NET style" and have it output in "JavaScript style". To override this behavior, use[PreserveCase]
for members or[PreserveMemberCase]
for whole classes.
When using Knockout, it's generally advised to use case preservation for the ViewModel class, so to have corresponding names between HTMLdata-bind
attributes and C#. Anyway in this article, to make things simpler, I kept the ViewModel camel-cased, similar to the original JavaScript, so there is no risk of making mistakes.
The Todo Class
Our Todo app manages "things to do" entities, so we need a basic object that represents them:public class Todo
{
public Observable<string> title;
public Observable<bool> completed;
public Observable<bool> editing;
public Todo(string title, bool completed=false)
{
this.title = Knockout.Observable(title);
this.completed = Knockout.Observable(completed);
this.editing = Knockout.Observable(false);
}
}
title
, completed
and editing
are declared as Observable
, so when they change, Knockout automatically updates the UI. Being in a strongly typed environment, we are providing types in <> and we are also providing startup default values.
The ViewModel
Now let's code our ViewModel
:
public class ViewModel
{
public ObservableArray<todo> todos;
public Observable<string> current;
public Observable<string> showMode;
public Action add;
public Action<todo> remove;
public Action removeCompleted;
public Action<todo> editItem;
public Action<todo> stopEditing;
public ComputedObservable<todo[]> filteredTodos;
public ComputedObservable<int> completedCount;
public ComputedObservable<int> remainingCount;
public ComputedObservable<bool> allCompleted;
todos
holds the list of Todo
objects as an array. In Knockout they are called ObservableArray
and have a similar functionality as standard Arrays.
current
is the input box where the user can add new items.
showMode
is a string that can be "all", "active" or "completed" and allows to filter the results on the UI. By changing it, filteredTodos
will change accordingly. Note that showMode
is also referenced by the URL router so that the app can have bookmarkable links (more on this later).
All UI events (add
, remove
, removeCompleted
, editItem
and stopEditing
) are coded as Action
objects, that is the equivalent of the JavaScript function
type (but without any return parameter). Actions can be simply defined inline with lambda expressions as we'll see later.
ComputedObservables
are values that are calculated as the result of other properties, and as such they do not store a value, but calculate it when needed. For example remainingCount
is recalculated anytime todos
changes, and being remainingCount
an observable itself, any UI object referencing to it will automatically update.
So far we have only declared objects inside our ViewModel
, we now have to define them. We do this in the class constructor:
public ViewModel(dynamic[] initial_todos)
{
var self = this;
// map array of passed in todos to an observableArray of Todo objects
todos = Knockout.ObservableArray(KnockoutUtils.ArrayMap( initial_todos,
(todo)=> {return new Todo( todo.title, todo.completed );}
));
the constructor takes an initial array of objects and turns them into Observables. The objects come from the "model" part of the architecture, that is, from the LocalStorage
provided by the HTML5 browser.
ArrayMap()
converts simple objects into Todo
objects, and ObservableArray()
turns the resulting Todo[]
into an observable array. So in the end we have an ObservableArray containing Todo items which in turn contain Observable properties.
Continuing on the ViewModel constructor code, we initialize our Observables:
// store the new todo value being entered
current = Knockout.Observable("");
showMode = Knockout.Observable("all");
a bit more complex is the initialization of ComputedObservables:filteredTodos = Knockout.Computed( () =>
{
switch( self.showMode.Value )
{
case "active":
return self.todos.Value.Filter( (todo)=> {return !todo.completed.Value;} );
case "completed":
return self.todos.Value.Filter( (todo)=> {return todo.completed.Value;} );
default:
return self.todos.Value;
}
});
the parameter for Knockout.Computed()
is an inline function defined with the =>
lambda operator that returns an array of Todo objects according to what self.showMode
is set to ("all", "active" or "completed") by the help of Filter()
(which in turn takes another function as parameter!).
Notice that, differently from JavaScript, in C# we use the ".Value
" syntax to access the content of an Observable property. So, showMode
is the property object, and showMode.Value
is the string value stored within. In JavaScript, Observable properties are instead accessed writing them as methods, e.g. showMode()
.
We now code Actions:
// add a new todo, when enter key is pressed
add = () =>
{
var curr = self.current.Value.Trim();
if(curr!="")
{
self.todos.Push( new Todo( curr ) );
self.current.Value = "";
}
};
since add
is an Action
it can be defined inline with a lambda expression. The action body does nothing else than adding a new Todo
object via the ObservableArray.Push()
method.
Similarly, we define the remaining actions:
// remove a single todo
remove = (todo) =>
{
self.todos.Remove(todo);
};
// remove all completed todos
removeCompleted = () =>
{
self.todos.Remove( (todo) => {return todo.completed.Value;} );
};
// edit an item
editItem = (item) =>
{
item.editing.Value = true;
};
// stop editing an item. Remove the item, if it is now empty
stopEditing = (item) =>
{
item.editing.Value = false;
if (item.title.Value.Trim()=="")
{
self.remove(item);
}
};
and ComputableObeservables:// count of all completed todos
completedCount = Knockout.Computed( () =>
{
return self.todos.Value.Filter( (Todo todo) => { return todo.completed.Value; }).Length;
});
// count of todos that are not complete
remainingCount = Knockout.Computed( () =>
{
return self.todos.Value.Length - self.completedCount.Value;
});
The last remaining ComputableObeservable is a bit tricky, because it's a of particular kind. Instead of being read-only like the majority of ComputableObeservables, it's capable of being written too. The beaviour we want to implement here is: on read allCompleted
returns whether the list of items is empty or not (true/false). This is used to update the checkbox "All completed" on the UI. On write, that is when the checkbox is flagged by the user, allComplete
will change the boolean field .complete
in all the todos
, according to the checkbox value coming from the UI (parameter newValue
in the code below).
To implement this read/write ComputedObservable, in C# we need to pass a special object of type ComputedOptions
GetValueFunction
SetValueFunction
ComputedOptions
// writeable computed observable to handle marking all complete/incomplete
allCompleted = Knockout.Computed( new ComputedOptions<bool>()
{
GetValueFunction = () =>
{
return self.remainingCount.Value==0;
},
SetValueFunction = (newValue) =>
{
KnockoutUtils.ArrayForEach<todo>(self.todos.Value, (todo)=>
{
// set even if value is the same, as subscribers are not notified in that case
todo.completed.Value = newValue;
});
}
});
for those who don't remember, the object literal syntax allows to construct objects simply with the syntax
new ClassName() { property=value, [...] };
which is not to be confused with named parameters passed in constructors, e.g.
new ClassName(property: value, [...]);
The last thing we do in the ViewModel constructor is to make it save our data when a change is detected. This can be done in Knockout with the use of an advanced concept called extenders, in particular the throttle extender.
To make it brief, the throttle extender causes the ComputedObservable to delay its evaluation until all its dependencies have stopped changing for a specified period of time--which in our example is set to 500 ms.
So by using a throttle
extender, anytime the ViewModel is changed by the UI our data will be saved half second after.
First we create a dummy ComputedObservable that holds the behavior of saving to LocalStorage
:
// internal computed observable that fires whenever anything changes in our todos
ComputedObservable<string> th = Knockout.Computed( () =>
{
// store a clean copy to local storage, which also creates a dependency
// on the observableArray and all observables in each item
Window.LocalStorage.SetItem("todos-knockout", Knockout.ToJson( self.todos ) );
return "";
});
then we extend it with:KnockoutUtils.Extend<ComputedObservable<string>>( th, new { throttle = 500 });
here Extend()
takes an object declared inline containing a field named "throttle".
Our constructor is now complete and the ViewModel
class misses only this small function:
// helper function to keep expressions out of markup
public string getLabel( Observable<int> count )
{
return KnockoutUtils.UnwrapObservable<int>( count ) == 1 ? "item" : "items";
}
which is used by the index.html
page to display "1 item" or "2 items"..., getting the code out of the markup to make it more readable.
Passing the argument through the UnwrapObservable()
function is a Knockout technique that allows to reuse the getLabel
function even with parameters that are not Observables, but are normal variables.
Custom Bindings
Our ViewModel class is now ready, but there are still two missing pieces. The index.html page was built around the use of two custom Knockout bindings: enterKey
and selectAndFocus
, to implement to distinct functionalities.
enterKey
is used to trigger an event function when the enter key is pressed; in our application that happens when the user finishes his editing and the item has to be added to the list.
The second custom binding, selectAndFocus
, is used to manage the UI feature "double click to edit an item".
To write a custom binding, we write a new class deriving it from BindingHandler
and overriding the Init()
and Update()
methods.
public class EnterKeyBindingHandler : BindingHandler
{
public const int ENTER_KEY = 13;
public override void Init(Element element,
Func<object> valueAccessor,
Func<jsdictionary> allBindingsAccessor,
object viewModel)
{
Action<object,ElementEvent> wrappedHandler = (object data, ElementEvent ev) =>
{
if ( ev.KeyCode == ENTER_KEY )
{
((dynamic)valueAccessor)().call(this, data, ev );
}
};
Func<object> newValueAccessor = () =>
{
return new { keyup = wrappedHandler };
};
Knockout.BindingHandlers["event"].Init( element,
newValueAccessor,
allBindingsAccessor,
viewModel );
}
}
the EnterKeyBindingHandler
defined here, modifies the binding named event
so that when "keyup" is triggered, the function contained inside wrappedHandler
is called, producing the call to valueAccessor
if enter is pressed.
Similarly the SelectAndFocusBindingHandler
:
public class SelectAndFocusBindingHandler : BindingHandler
{
public override void Init(Element element,
Func<object> valueAccessor,
Func<jsdictionary> allBindingsAccessor,
object viewModel)
{
Knockout.BindingHandlers["hasfocus"].Init(element,
valueAccessor,
allBindingsAccessor,
viewModel);
KnockoutUtils.RegisterEventHandler(element, "focus", (el,ev) => {element.Focus();});
}
public override void Update(Element element,
Func<object> valueAccessor,
Func<jsdictionary> allBindingsAccessor,
object viewModel)
{
KnockoutUtils.UnwrapObservable<object>(valueAccessor()); // for dependency
// ensure that element is visible before trying to focus
Window.SetTimeout( ()=>
{
Knockout.BindingHandlers["hasfocus"].Update(element,
valueAccessor,
allBindingsAccessor,
viewModel );
}, 0);
}
}
The Main function
At this point the TodoMVC application is almost complete, we only need to plug in all the different parts together.
Similarly to a console application, our compiled JavaScript code will start from a Main()
function:
class Program
{
static void Main()
{
inside the Main()
we firstly register in Knockout the two custom bindings created before:// a custom binding to handle the enter key (could go in a separate library)
Knockout.BindingHandlers["enterKey"] = new EnterKeyBindingHandler();
// wrapper to hasfocus that also selects text and applies focus async
Knockout.BindingHandlers["selectAndFocus"] = new SelectAndFocusBindingHandler();
then we instantiate our ViewModel and initialize it with data coming from the LocalStorage
:// check local storage for todos
var todos = KnockoutUtils.ParseJson<dynamic[]>( (string) Window.LocalStorage.GetItem("todos-knockout") );
// bind a new instance of our view model to the page
var viewModel = new ViewModel(todos);
LocalStorage.GetItem()
returns a JSON string that we parse to produce an array of dynamic objects where our Todo items are stored. "dynamic
" in C# is a way to reference to a generic JavaScript object that doesn't have a C# class defined for it. Another possible way, is to have a JsDictionary
data-bind
attributes in the HTML file become meaningful and our application takes life:
Knockout.ApplyBindings(viewModel);
URL Routing
A routing library intercepts certain URLs in the browser, and instead of requesting a new page to the server, it routes them to a function call in the JavaScript code. Routing is how single-page applications simulate the behavior of old-style multi-page applications, allowing bookmarks, history buttons and stateless navigation.
In our application URL routing is used to write in the showMode
observable property, causing a filter on the todo items shown on the UI. For example the link
http://.../TodoMVC/completed
will write the string "completed" to showMode
, and, being showMode
an Observable property, it will trigger a UI refresh where required.
[IgnoreNamespace]
[Imported(IsRealType=true)]
public static class Director
{
[InlineCode("Router({route})")]
public static dynamic Router(JsDictionary<string,dynamic> route) {return null;}
}
which serves the only purpose of having the inline code put into the JavaScript output.
Having the small API defined, we can call the URL Router from Main()
:
// set up filter routing
Director.Router(new JsDictionary(new object[]{"/:filter",viewModel.showMode})).init();
Router()
accepts an object with a name-value pair, in our case name is the URL to intercept, and value is where in our ViewModel the router will write. Running the project
Now the application is complete and can be run in Visual Studio under your favorite browser. Unfortunately Saltarelle C# code can't be debugged natively (you can't place breakpoints) but, if using IE as browser, breakpoints can be put on the JavaScript compiled file (that's why it's so important to have it human-readable).
When debugging JavaScript, Visual Studio immediate mode (CTRL+ALT+I) gives access to all objects; remember that classes String
Object
) have been extended by the mscorlib.js
to introduce .NET-like methods and to support type checking.
Comparing the compiler output
Now that we have compiled our application, we can compare it with the original JavaScript code by Addy Osmani, evaluating the quality of the compiled code.
Firstly, we compare the two files by size:
Original Saltarelle % incr. ------------------------------------------------------- lines 169 181 +7 % bytes 4946 6518 +31 % minified bytes 2262 3893 +72 %
While the increment in line numbers is modest, the byte increment is consistent. This is mostly due to the verbose object names present in the Saltarelle output, which have no impact on the final execution speed of the code (apart of course from loading times).
If we do a line by line comparison, we can see that the two files are indeed very similar--even identical in some parts.
The only notable difference, as expected, is due to type checking introduced by the C# language and carried over in JavaScript. But this has to be seen as an additional feature rather than compiler code bloat. T
, introduced in mscorlib.js
. C# classes are first registered within the Type object (which can be seen as a Type manager proxy)
Type.registerClass(null, '$Program', $$Program, Object);
Type.registerClass(global, 'EnterKeyBindingHandler', $EnterKeyBindingHandler);
Type.registerClass(global, 'SelectAndFocusBindingHandler', $SelectAndFocusBindingHandler);
Type.registerClass(global, 'Todo', $Todo, Object);
Type.registerClass(global, 'ViewModel', $ViewModel, Object);
and then Type
is used when needed, for example to solve a type cast:return new $Todo(Type.cast(todo.title, String), !!todo.completed);
Using the attached file
To use the source files attached to this article, you have to install the Saltarelle compiler because it's not included in the .zip (for size reason). Just do the steps involving the install-package
commands. If you only want to see the TodoMVC running, without using the compiler, just open index.html
in your favourite browser. Switch back and forth the two versions by renaming app.js
.
Conclusion
By implementing a TodoMVC sample application, I tried to demostrate that C# + Saltarelle is an available option for the .NET developer who wants to develop single page web applications and maintain C# as his main language.
Credits
- TodoMVC by Addy Osmani and Sindre Sorhus
- Knockout by Steve Sanderson
- Saltarelle by Erik Källén
- Script# Nikhil Kothari
History
- Version 1.0, 27 dec 2012
based on Saltarelle 1.6.3 and Knockout version of TodoMVC as of 27 dec 2012