Showing posts with label MVVM. Show all posts
Showing posts with label MVVM. Show all posts

Tuesday, 3 April 2012

WPF Metro Shell

Here is an example of a WPF application with pluggable components for core business functionality.

The solution features the following frameworks:

MVMMLight, MahApps.Metro, Ninject, ServiceLocator, Newtonsoft.Json, Rx Extensions

 

Projects

The main projects prefixed MetroWpf have a core shell window, login page and menu system.

MetroWpf-projects

Login

image

 

image

NOTE:

This is very much a work in progress but as I keep getting side tracked on other projects I thought I’d release what I have so far.

Source code:  http://stevenhollidge.com/blog-source-code/MetroWpf-CTP-v0.1.zip

HG repo:  http://hg.assembla.com/silverbladetech/file/4b8b38d17d24/MetroWpf

Friday, 17 February 2012

Rx Event Example

When subscribing to events Reactive Extensions (Rx) is a powerful way to filter, throttle and compose your .Net code. 

With this example I have a WPF application that listens to a FxRate pricing service, that’s pumping out 100 new prices a second via an event.

I’m going to make available the code to show how to hook up the front end Metro style application using MVVM with:

  • old skool event handlers
  • and then using Reactive Extensions (Rx)

Finally we update the Rx example to filter on just one of the exchange rates and also throttle back the rate of FxRates being received to one per second.

stockmarket-demo

The event signature

Old skool event handlers

Using MVVM, here we have my view model code stripped back just to show we have:

  • A pricing service
  • A subscribed command (data bound to the subscribed button on the UI) which toggles the subscribed property
  • A delegate (PriceUpdate) that handles the event when the subscribed button is clicked.  Click the button a second time to unsubscribe and remove the delegate from handling the event

Lines 25 and 30 show the adding and removal of the event handler, dependent on whether the user has clicked the subscribe button.

Rx Subscriptions

To do the same thing with Rx takes two lines of code (lines 18/19 and 21)

Filtering with Rx

Simply update our subscription to include a where filter that accepts the event coming in (e) and sets the predicate to Ccy (Currency) for the exchange to Euro to GBP.

Throttling with Rx

NOTE:  The code below was a workaround, please use Sample as shown here.

So we have filtered the events to only give us the Euro to GBP prices. Now let's enhance the solution just to give us the latest Euro to GBP price every two seconds.

Source code

Event handlers:  http://stevenhollidge.com/blog-source-code/standard-event-handler-wpf-metro-mvvm-stockmarket-pricing-app.zip

Reactive Extensions:  http://stevenhollidge.com/blog-source-code/Rx-wpf-metro-mvvm-stockmarket-pricing-app.zip

Wednesday, 15 February 2012

WPF Metro MVVM Application

This project is designed to get developers up and running on their WPF Metro MVVM projects.

Screenshots

image

 ScreenShot1 ScreenShot2 ScreenShot3 ScreenShot4

Frameworks

This project combines two excellent existing frameworks within the community:

Name Developer Link Comments
MahApps.Metro Paul Jenkins

http://www.theleagueofpaul.com/metro

Metro style and themes
MVVM Light Toolkit Laurent Bugnion http://mvvmlight.codeplex.com/ Originally based on ideas and source code from Microsoft PRISM

Concepts

The application contains working examples of the following:

  • MVVM with INPC (INotifyPropertyChanged), Messenger with messages (for event notifications), RelayCommands are available to use
  • Metro layout, styles and theming, easing and sliding from “outside” the visual area – all thanks to MapApps, great library!
  • Two way data binding for a combo box to a Enum list

image

ViewModel Example

Source code 

http://stevenhollidge.com/blog-source-code/wpf-metro-mvvm-app-stockmarket.zip

Wednesday, 18 January 2012

JavaScript MVVM: Knockout.js

Knockout.js is a rich JavaScript MVVM framework for data binding HTML elements to JavaScript view models, auto UI updates, dependency tracking and templating.

For a Silverlight or WPF developer this is just what we’ve wanted to be able to unit test our presentation layer (the View Model) outside of the UI.

MVVM

Overview

image

Example of an implementation of MVVM

MVVM_05

Knockout.js

Home page http://knockoutjs.com/
Source code: https://github.com/SteveSanderson/knockout
Resources: http://www.knockmeout.net/   
http://blog.stevesanderson.com
http://en.wikipedia.org/wiki/Model_View_ViewModel

 

A simple Knockout.js example

 image

Live demo:  http://c9.io/stevenh77/knockoutdemo/workspace/demo1.html

In this example we will be:

Diagram

Steps

image 1. Creating a view model in JavaScript called viewModel (see diagram).
2. Three of the properties will be ko.observables which enables data binding:
        firstName, familyName and fullNameVisible
3. firstname and familyName bound to textboxes.
4. Enforce the update of the viewModel properties after every key press by adding:
        valueUpdate: 'afterkeydown'  to the data-bind section of the textboxes
5. Binding the checked property of the checkbox to fullNameVisible along with the label.visible property.
6. Create a fourth dependentObservable property called fullName, which is dependent on firstName
and familyName and databound to “Hello {fullName}” label.

7. Bind the changeName function to the Change Name button, which updates firstName and familyName.

<!DOCTYPE html>
<html>
<head>
<script src="http://cloud.github.com/downloads/SteveSanderson/knockout/knockout-2.0.0rc.debug.js" type="text/javascript"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.js" type="text/javascript"></script>
<script type="text/javascript">
$(function () {
var viewModel = {
firstName: ko.observable("Bob"),
familyName: ko.observable("Smith"),
changeName: function () {
this.firstName("Joe");
this.familyName("Bloggs");
},
fullNameVisible: ko.observable(true),
};
viewModel.fullName = ko.dependentObservable(function() {
return this.firstName() + " " + this.familyName();
}, viewModel);
ko.applyBindings(viewModel);
});
</script>
</head>
<body>
<div>Stockbroker:
<input type="text" data-bind="value: firstName, valueUpdate: 'afterkeydown'" />
<input type="text" data-bind="value: familyName, valueUpdate: 'afterkeydown'" />
<input type="checkbox" data-bind="checked: fullNameVisible" />
<span>Hello </span><span data-bind="text: fullName, visible: fullNameVisible"></span>
<button data-bind="click: changeName">Change name</button>
</div>
</body>
</html>

Observable Arrays


Here is an example showing lists of data with validating input controls


Live demo:  http://c9.io/stevenh77/knockoutdemo/workspace/demo2.html


image

<!DOCTYPE html>
<html>
<head>
<script src="http://cloud.github.com/downloads/SteveSanderson/knockout/knockout-2.0.0rc.debug.js" type="text/javascript"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.js" type="text/javascript"></script>
<script src="http://ajax.microsoft.com/ajax/jQuery.Validate/1.6/jQuery.Validate.min.js" type="text/javascript"></script>
<script type="text/javascript">
$(function () {
var TradeModel = function(trades) {
var self = this;
self.trades = ko.observableArray(trades);

self.addTrade = function() {
self.trades.push({
ticker: "",
price: ""
});
};

self.removeTrade = function(trade) {
self.trades.remove(trade);
};

self.save = function(form) {
alert("Could now transmit to server: " + ko.utils.stringifyJson(self.trades));
// To actually transmit to server as a regular form post, write this: ko.utils.postJson($("form")[0], self.gifts);
};
};

var viewModel = new TradeModel([
{ ticker: "MSFT", price: "39.95"},
{ ticker: "AAPL", price: "120.00"}
]);

ko.applyBindings(viewModel);

// Activate jQuery Validation
$("form").validate({ submitHandler: viewModel.save });
});
</script>
</head>
<body>
<form action='/someServerSideHandler'>
<p>You have entered <span data-bind='text: trades().length'>&nbsp;</span> trade(s)</p>
<table data-bind='visible: trades().length > 0'>
<thead>
<tr>
<th>Ticker</th>
<th>Price</th>
<th />
</tr>
</thead>
<tbody data-bind='foreach: trades'>
<tr>
<td><input placeholder='Ticker' class='required' data-bind='value: ticker, uniqueName: true' /></td>
<td><input placeholder='Price' class='required number' data-bind='value: price, uniqueName: true' /></td>
<td><a href='#' data-bind='click: $root.removeTrade'>Delete</a></td>
</tr>
</tbody>
</table>

<button data-bind='click: addTrade'>Add Trade</button>
<button data-bind='enable: trades().length > 0' type='submit'>Submit</button>
</form>
</body>
</html>

Bindings

Built in binding

These bindings come out of the box











Text and Appearancevisible, text, html, css, style, attr
FormsClick, Event, Submit, Enable, Disable, Value, Checked, Options/Selected options (for dropdown or multi select)
Control Flowif, ifnot, foreach, with
TemplateOld:  Traditional JavaScript template (<script>)
New:  Inline, template less, comment based syntax

Custom bindings


Sometimes it’s preferred to use custom bindings over the built in bindings as it can keep your code more reusable and succinct.

ko.bindingHandlers.yourBindingName = {
init: function(element, valueAccessor, allBindingsAccessor, viewModel) {
// This will be called when the binding is first applied to an element
// Set up any initial state, event handlers, etc. here
},
update: function(element, valueAccessor, allBindingsAccessor, viewModel) {
// This will be called once when the binding is first applied to an element,
// and again whenever the associated observable changes value.
// Update the DOM element based on the supplied values here.
}
};

KO Utilities


Knockout protected observable:  Allows you to commit or undo changes to your observable data item, sweet!


Data features


You can use the built in Knockout functions to convert to |JavaScript and JSON:  ko.ToJS() and ko.ToJSON()

var viewModel = {
firstName : ko.observable("Bert"),
lastName : ko.observable("Smith"),
pets : ko.observableArray(["Cat", "Dog", "Fish"]),
type : "Customer"
};
viewModel.hasALotOfPets = ko.computed(function() {
return this.pets().length > 2
}, viewModel)


var jsonData = ko.toJSON(viewModel);

// Result: jsonData is now a string equal to the following value
// '{"firstName":"Bert","lastName":"Smith","pets":["Cat","Dog","Fish"],"type":"Customer","hasALotOfPets":true}'


var plainJs = ko.toJS(viewModel);

// Result: plainJS is now a plain JavaScript object in which nothing is observable. It's just data.
// The object is equivalent to the following:
// {
// firstName: "Bert",
// lastName: "Smith",
// pets: ["Cat","Dog","Fish"],
// type: "Customer",
// hasALotOfPets: true
// }

Loading and Saving JSON Data

$.getJSON("/some/url", function(data) { 
// Now use this data to update your view models,
// and Knockout will update your UI automatically
})

var data = /* Your data in JSON format - see below */;
$.post("/some/url", data, function(returnedData) {
// This callback is executed if the post was successful
})

See the knockout documentation for examples on loading and saving JSON data.


http://knockoutjs.com/documentation/json-data.html

Wednesday, 8 June 2011

Silverlight High Frequency Services

I’ve written a quick Silverlight application that listens to a WCF Service streaming a fire hose of the latest Fx prices at a rate of 1,200 per minute.

The application showcases the MVVM pattern, makes use of MVVMLight Toolkit and features the cosmopolitan/metro theme.

ScreenShot072_thumb[5]

The Service

The WCF service exposes a pub/sub model with a call back for each new FX Rate price:

using System;
using System.ServiceModel;
using System.Threading;
using System.Threading.Tasks;

namespace PricingServiceHost
{
[ServiceBehavior(InstanceContextMode=InstanceContextMode.Single)]
public class PricingService : IPricingService
{
static PricingService()
{
Task.Factory.StartNew(() =>
{
var factory = new PriceFactory();
while (true)
{
Thread.Sleep(50);
if (PriceUpdate == null) continue;
FxRate latestPrice = factory.GetNextPrice();

PriceUpdate(
null,
new PriceUpdateEventArgs
{
LatestPrice = latestPrice
});
}
});
}

static event EventHandler<PriceUpdateEventArgs> PriceUpdate;

IPricingServiceCallback _callback;

public void Subscribe()
{
_callback = OperationContext.Current.GetCallbackChannel<IPricingServiceCallback>();
PriceUpdate += PricingService_PriceUpdate;
}

public void UnSubscribe()
{
PriceUpdate -= PricingService_PriceUpdate;
}

void PricingService_PriceUpdate(object sender, PriceUpdateEventArgs e)
{
if (((ICommunicationObject)_callback).State == CommunicationState.Opened)
{
try
{
_callback.PriceUpdate(e.LatestPrice);
}
catch
{
UnSubscribe();
}
}
else
{
UnSubscribe();
}
}
}
}


The Model (M)

using System;
using System.Collections.ObjectModel;
using GalaSoft.MvvmLight;

namespace MetroPricingSample.Models
{
public class DisplayFxRate : ObservableObject
{
public static ObservableCollection<DisplayFxRate> InitialRates = new ObservableCollection<DisplayFxRate>
{
new DisplayFxRate("AUD", (decimal) 0.93272),
new DisplayFxRate("BRL", (decimal) 1.58100),
new DisplayFxRate("CAD", (decimal) 0.97495),
new DisplayFxRate("CHF", (decimal) 0.83603),
new DisplayFxRate("CNY", (decimal) 0.15425),
new DisplayFxRate("EUR", (decimal) 0.68103),
new DisplayFxRate("GBP", (decimal) 0.60819),
new DisplayFxRate("INR", (decimal) 44.6300),
new DisplayFxRate("JPY", (decimal) 80.0032),
new DisplayFxRate("NZD", (decimal) 1.21847),
new DisplayFxRate("RUB", (decimal) 27.7411),
new DisplayFxRate("THB", (decimal) 0.03303),
new DisplayFxRate("ZAR", (decimal) 6.71610)
};

public DisplayFxRate() { }

public DisplayFxRate(string isoCode, decimal rate)
{
IsoCode = isoCode;
PreviousRate = rate;
CurrentRate = rate;
Updated = DateTime.Now;
}

public const string IsoCodePropertyName = "IsoCode";
private string _isoCode = string.Empty;
public string IsoCode
{
get { return _isoCode; }

set
{
if (_isoCode == value) return;
_isoCode = value;
RaisePropertyChanged(IsoCodePropertyName);
}
}

public const string PreviousRatePropertyName = "PreviousRate";
private decimal _previousRate = 0;
public decimal PreviousRate
{
get { return _previousRate; }

set
{
if (_previousRate == value) return;
_previousRate = value;
RaisePropertyChanged(PreviousRatePropertyName);
}
}

public const string CurrentRatePropertyName = "CurrentRate";
private decimal _currentRate = 0;
public decimal CurrentRate
{
get { return _currentRate; }

set
{
if (_currentRate == value) return;

_previousRate = _currentRate;
_currentRate = value;

RaisePropertyChanged(PreviousRatePropertyName);
RaisePropertyChanged(CurrentRatePropertyName);
RaisePropertyChanged(DeltaPropertyName);
RaisePropertyChanged(StatusPropertyName);
}
}


public const string DeltaPropertyName = "Delta";
public decimal Delta
{
get
{
decimal result;

if (PreviousRate == 0 || CurrentRate == 0)
result = 0;
else
result = Math.Round(((CurrentRate / PreviousRate) - 1), 2);

return result;
}
}

public const string StatusPropertyName = "Status";
public Status Status
{
get
{
Status status;
var delta = Delta;

if (delta > 0)
status = Status.Increase;
else if (delta < 0)
status = Status.Decrease;
else
status = Status.NoChange;

return status;
}
}

public const string UpdatedPropertyName = "Updated";
private DateTime _updated = DateTime.MinValue;
public DateTime Updated
{
get { return _updated; }

set
{
if (_updated == value) return;
_updated = value;
RaisePropertyChanged(UpdatedPropertyName);
}
}
}
}


The View (V)

<navigation:Page x:Class="MetroPricingSample.Views.Pricing"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
xmlns:Converters="clr-namespace:MetroPricingSample.Converters"
d:DesignHeight="300"
d:DesignWidth="640"
DataContext="{Binding PricingViewModel, Source={StaticResource Locator}}"
Style="{StaticResource PageStyle}"
mc:Ignorable="d">
<Grid x:Name="LayoutRoot">
<Grid.RowDefinitions>
<RowDefinition Height="60" />
<RowDefinition Height="30" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.Resources>
<Converters:BoolToSubscribedTextConverter x:Key="BoolToSubscribedTextConverter" />
<Converters:StatusToIconConverter x:Key="StatusToIconConverter" />
<Converters:IsoCodeToFlagConverter x:Key="IsoCodeToFlagConverter" />
<Converters:DateTimeToTimeConverter x:Key="DateTimeToTimeConverter" />
</Grid.Resources>

<StackPanel Grid.Row="0">
<Button x:Name="btnSubscribe"
Width="200"
Height="30"
HorizontalAlignment="Left"
Content="{Binding Subscribed, Converter={StaticResource BoolToSubscribedTextConverter}}"
Command="{Binding SubscriptionCommand, Mode=TwoWay}" />
<TextBlock x:Name="tbInfo" Height="30" Text="{Binding ErrorText}"/>

</StackPanel>
<StackPanel Grid.Row="1" Orientation="Horizontal">
<TextBlock Text="Currency" Width="85" Margin="10,0,0,0"/>
<TextBlock Text="Previous" Width="100"/>
<TextBlock Text="Current" Width="100" />
<TextBlock Text="Delta" Width="160" />
<TextBlock Text="Updated" />
</StackPanel>
<ListBox x:Name="lbFxRates"
ItemsSource="{Binding Path=Rates}"
Grid.Row="2">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image Source="{Binding IsoCode, Converter={StaticResource IsoCodeToFlagConverter}}" Width="20" Margin="10,0,0,0"/>
<TextBlock Text="{Binding IsoCode}" Width="50" Margin="10,0,0,0"/>
<TextBlock Text="{Binding PreviousRate}" Width="100" />
<TextBlock Text="{Binding CurrentRate}" Width="100" />
<TextBlock Text="{Binding Delta}" Width="100" HorizontalAlignment="Right" Margin="0,0,10,0" />
<Image Source="{Binding Status, Converter={StaticResource StatusToIconConverter}}" Width="20" />
<TextBlock Text="{Binding Updated, Converter={StaticResource DateTimeToTimeConverter}}" Width="200" Margin="30,0,0,0" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</navigation:Page>


The View Model (VM)



The view model in the client currently uses a generated proxy for the WCF service, we I really don’t like, this will be removed in the next refactoring iteration.

using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows.Input;
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using MetroPricingSample.Models;
using MetroPricingSample.ServiceReference1;

namespace MetroPricingSample.ViewModels
{
public class PricingViewModel : ViewModelBase
{
public ObservableCollection<DisplayFxRate> Rates { get; set; }

private const string SubscribedPropertyName = "Subscribed";
private bool _subscribed = false;

public bool Subscribed
{
get { return _subscribed; }

set
{
if (_subscribed == value)
{
return;
}

_subscribed = value;
RaisePropertyChanged(SubscribedPropertyName);
}
}

private const string ErrorTextPropertyName = "ErrorText";
private string _errorText = string.Empty;

public string ErrorText
{
get { return _errorText; }

set
{
if (_errorText == value)
{
return;
}

_errorText = value;
RaisePropertyChanged(ErrorTextPropertyName);
}
}


public ICommand SubscriptionCommand { get; set; }

private bool _subscriptionCommand_CanExecute = true;

private PricingServiceClient _client;

public PricingViewModel()
{
Rates = DisplayFxRate.InitialRates;

if (IsInDesignMode) return;

_client = new PricingServiceClient();
_client.SubscribeCompleted += _client_SubscribeCompleted;
_client.UnSubscribeCompleted += _client_UnSubscribeCompleted;
_client.PriceUpdateReceived += PriceUpdate;
SubscriptionCommand = new RelayCommand(SubscriptionCommand_Execute, () => _subscriptionCommand_CanExecute);
}

void SubscriptionCommand_Execute()
{
if (!Subscribed)
{
_client.SubscribeAsync();
}
else
{
_client.UnSubscribeAsync();
}

_subscriptionCommand_CanExecute = false;
}

void _client_UnSubscribeCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
{
if (e.Error == null)
{
Subscribed = false;
ErrorText = "";
}
else
{
ErrorText = "Unable to connect to service.";
}

_subscriptionCommand_CanExecute = true;
}

void _client_SubscribeCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
{
if (e.Error == null)
{
Subscribed = true;
ErrorText = "";
}
else
{
ErrorText = "Unable to connect to service.";
}

_subscriptionCommand_CanExecute = true;
}

public void PriceUpdate(object sender, PriceUpdateReceivedEventArgs e)
{
if (Subscribed)
{
PriceUpdate(e.fxRate);
}
}

public void PriceUpdate(FxRate fxRate)
{
try
{
foreach (var rate in Rates.Where(rate => rate.IsoCode == fxRate.IsoCode))
{
rate.CurrentRate = fxRate.Rate;
rate.Updated = fxRate.Updated;
}
}
catch (Exception e)
{
//log here
}
}
}
}


Conclusion



The application currently uses MVVM Light on the client side and WCF for the server side service.  The GUI is struggling to keep up with the service so I plan to introduce the Telerik Grid Control to see how it deals with the updates.  The functionality currently provided by value converters will be moved out of the view and into the view model and I’ll also refactor the application to use a REST interface with Service Stack (written by Demis Bellot).



You can download the source code here:



https://github.com/stevenh77/MetroPricingSample



When you run the application, be sure to set the Web host and Service to both start up at runtime (right click on solution > Properties > Multiple Startup Projects > START both MetroPricingSample.Web and PricingServiceHost).



Note: As I’m looking purely at performance I’ve deliberately omitted tests and some of the error handling and graceful dereferencing, I’ll add these as I refactor later on.

Wednesday, 18 May 2011

MVVMLight Messenger for Loosely Coupled Events

The Messenger class within MVVMLight gives developers the ability to publish an application wide event using messages that allow a subscriber to react without either the publisher or subscriber knowing anything about each other.

As always with MVVMLight it’s really simple to do.

In this example, I wanted a user to be able to click on the main menu of my application, shown on the left hand side of the screenshot below, which would update the content pane on the right hand side.

image

The main menu is a user control whilst the content pane is a navigation frame located within the MainPage.xaml.

To allow two pieces of code to communicate with each other, without knowing anything about each other, I will use the Messenger class in 3 simple steps.

1 Create a strongly typed message

 

2 Register a subscriber to the message type

Register the MainPage as the subscriber so that when the message is received the Uri within the message will be passed to the NavigateTo method to update the content pane.

 

3 Lastly, publish the message

 

When the TreeView menu is clicked the code behind checks for a Uri within the selected TreeViewItem then creates a message containing the Uri and publishes it to your application.

Summary

Using messages within your application can be a great way for your components to communicate whilst maintaining a loosely coupled architecture. This is important because you don’t have any nasty hard coded dependencies. Hard coded dependencies result in brittle, inflexible systems that can be hard to extend in the future.

The Messenger class in MVVMLight provides similar functionality to the EventAggregator class in PRISM.

You can download the WCF RIA sample application here:

http://www.stevenhollidge.com/blog-source-code/Silverblade5-WCF-RIA-MVVMLight-Messenger.zip

Please note that for demo purposes only menu options “Home > Search” and “Client > Details” have views that can be displayed within the content pane.  All other menu options will return an error.

Sunday, 17 April 2011

Knockout MVVM JavaScript UI framework

From Mix 2011, Steve Sanderson delivers KnockoutJS in this lightening talk introducing Knockout.JS.

Learn how the Knockout library builds on advanced jQuery and JavaScript techniques to make even the most complex data-filled HTML forms a breeze. We’ll see jQuery, jQuery templating, JSON and live data banding applied wto the MVVM pattern with Knockout, combined with ASP.NET to produce results that need to be seen to believed.

Highly recommended, within 5 minutes you’ll understand the power of Knockout.JS:

The view or download the video from the Microsoft website click here: 

http://channel9.msdn.com/Events/MIX/MIX11/FRM08