KnockoutJS is a JavaScript dependency tracking library that enables MVVM in the browser. I took the syntax and simplicity of KnockoutJS and ported it to C#. Thus was born KnockoutCS.
Simple data binding
Like any pattern, MVVM is not appropriate in all situations. Sometimes an application is just too simple for an MVVM framework. But at what point do you make the switch? KnockoutCS fits into the gap between Hello World and Composite Enterprise Application.
Start by defining a data model. Use virtual properties to give KnockoutCS the ability to insert its dependency tracking hooks.
1: public class Model
2: {
3: public virtual string FirstName { get; set; }
4: public virtual string LastName { get; set; }
5: }
When you construct this model, use the KO.NewObservable method. Then use KO.ApplyBindings to bind it to your view.
1: private void MainPage_Loaded(object sender, RoutedEventArgs e)
2: {
3: Model model = KO.NewObservable<Model>();
4: DataContext = KO.ApplyBindings(model, new { });
5: }
You now have INotifyPropertyChanged behavior injected into your model. This is your basic Hello World starting point.
Computed properties
That little anonymous object “new { }” is your gateway to more interesting behavior. Insert a computed property using the KO.Computed method.
1: private void MainPage_Loaded(object sender, RoutedEventArgs e)
2: {
3: Model model = KO.NewObservable<Model>();
4: DataContext = KO.ApplyBindings(model, new
5: {
6: FullName = KO.Computed(() => model.FirstName + " " + model.LastName)
7: });
8: }
Now you can data bind to the computed property. It will automatically update (and fire PropertyChanged) when one of its observable properties changes.
Queries
Any data model is going to have a collection. Declare these as properties of type IList. There is no need to make these virtual.
1: public class Parent
2: {
3: public IList<Child> Children { get; set; }
4: }
Then when you bind the model, you can inject a computed property using a linq query.
1: private void MainPage_Loaded(object sender, RoutedEventArgs e)
2: {
3: Parent parent = KO.NewObservable<Parent>();
4: DataContext = KO.ApplyBindings(parent, new
5: {
6: Children = KO.Computed(() =>
7: from child in parent.Children
8: orderby child.Age
9: select new ChildSummary(child)
10: )
11: });
12: }
This query will depend not only upon the source collection, but also upon any observable properties used to order or filter it.
Commands
When you data bind to the Command property of a button, you provide a delegate that will be called when that button is clicked. And you can also provide a lambda that dictates when that button is enabled.
1: private void MainPage_Loaded(object sender, RoutedEventArgs e)
2: {
3: PhoneBook phoneBook = KO.NewObservable<PhoneBook>();
4: PhoneBookSelection selection = KO.NewObservable<PhoneBookSelection>();
5: DataContext = KO.ApplyBindings(phoneBook, new
6: {
7: DeletePerson = KO.Command(() =>
8: {
9: phoneBook.People.Remove(selection.SelectedPerson);
10: selection.SelectedPerson = null;
11: }, () => selection.SelectedPerson != null
12: )
13: });
14: }
Since this lambda references the property of an observable, the button will enable and disable as the user changes that property.
There is more to KnockoutCS. Try it out and see if you like it. Right now it only works for Silverlight 5, but I can easily port it to Silverlight 4 or the full WPF stack. I’m still working out how to port it to Windows Phone and WinRT.
Implementation Question
I'm curious why your computed and other KO-related things are only applied when binding to the DataContext.
Is the library currently not capable of declaring a reusable Observable ViewModel for use in multiple places?
If it is, you should probably point that out in your descriptions, since I would find that way more useful.
Tom
P.S. The "url is not valid" error when missing "http://" on my homepage url is kinda silly
Reusable view models
To create a reusable View Model, you can define it in a separate class rather than an anonymous type. I have an example on the Testing page.
The http:// requirement is just a Drupal thing. I agree that it's silly.