About me

Michael L Perry

Improving Enterprises

Principal Consultant

@michaellperry

User login

Knockout JS and MVC Model Binding

Source code for this post is at https://github.com/MichaelLPerry/BuyMoreStuff.

Knockout is a great way to make interactive web pages using data binding and dependency tracking. Most of the uses of Knockout that I’ve seen use AJAX to send data to a RESTful JSON endpoint. But if you are using ASP.NET MVC, you don’t have to write another service if you don’t want to. You can take advantage of the powerful model binding that is built into the platform.

Interaction with observable arrays

Say you are building a shopping cart web page. You want your users to manage items in their cart all in the client, with no post-backs. Knockout is fantastic at this style of interaction. Just create a ko.observableArray in your view model, and add all of the cart lines to that array.

var ViewModel = function () {
    this.lines = ko.observableArray([
    @if (Model != null && Model.Cart != null && Model.Cart.Lines != null)
    {
        foreach (var line in Model.Cart.Lines)
        {
            var product = Model.GetProductById(line.ProductId);
            if (product != null)
            {
                @:new Line(this, @line.ProductId, "@product.Name", @product.UnitPrice, @line.Quantity),
            }
        }
    }
    ]);
};

The loop is generating JavaScript for all of the items already in the cart. So when the browser fetches the page, it is already fully populated. There is no second call to get the cart data.

Each of the lines in the array has an observable quantity, in addition to some immutable properties.

var Line = function (container, productId, productName, unitPrice, quantity) {
    this.productId = productId;
    this.productName = productName;
    this.unitPrice = unitPrice;

    this.quantity = ko.observable(quantity);

    this.remove = function() {
        container.lines.remove(this);
    }.bind(this);
}

To data bind these lines to a table, just use Knockout’s data-bind attribute.

        <table>

        <thead>
        <tr>
        <th>Product</th>
        <th>Quantity</th>
        <th>Price</th>
        <th>&nbsp;</th>
        </tr>
        </thead>

        <tbody data-bind="foreach: lines">
        <tr>
        <td>
            <input type="hidden" data-bind="value: productId" />
            <span data-bind="text: productName"></span>
        </td>
        <td><input type="text" data-bind="value: quantity" /></td>
        <td data-bind="text: unitPrice * quantity()"></td>
        <td><button type="button" data-bind="click: remove">-</button></td>
        </tr>
        </tbody>

        </table>

Notice the button data binding to the remove function so that the user can remove items from the cart.

Model binding

Now we need to get the modified cart data back on submit. Here’s where the model binder comes in. The MVC model binder looks for names on input fields to determine what properties to set. When the name refers to an element of a collection, the model binder creates an object and inserts it into the collection. We can use the Knockout “attr” syntax to set the name attribute of the inputs.

<input type="hidden" data-bind="value: productId, attr: {name: 'Cart.Lines[' + $index() + '].ProductId'}" />
<input type="text" data-bind="value: quantity, attr: {name: 'Cart.Lines[' + $index() + '].Quantity'}" />

Now when you submit this form, the MVC model binder will create a Cart object containing all of the items that the user managed on their client. This was all done through the standard MVC Razor view engine and model binder, with no additional AJAX calls.



Knockout MVC

Hello,

I'm a bit confused, when I have a Model that dont have sub properties like yours (Cart.Lines) but instead I have a Model that is the Lines it self..

In the:

I have to put Model on the attr: {name: 'Model[' + $index() + '].ProductId'}

or I have to put nothing.. like:

{name: '[' + $index() + '].ProductId'}

???

Thanks in advance.
Márcio