KnockoutJS data-bind for a set of drop down lists

Standard

Heads up!

The blog has moved!
If you are interested in reading new posts, the new URL to bookmark is http://blog.valeriogheri.com/

 

In my current project I’m building a Single Page Application using Asp.Net MVC 3, jQuery and KnockoutJS.
As part of the UI, I had to build a part of it where the number of dropdown lists associated to a certain product are not known at design time, as the Model is based on an XML resource.
So I was facing the problem of keeping track of all the selected values (and the state of the rest of the UI as well) for each dropdown and without the possibility to use the MVC Model Binding feature because I had to use Ajax (no form submit and thus page re-loading).
After taking a look at what is currently out there to help the developer in this task, I decided to try knockout.js, a nice MVVM javascript framework, to help me keeping track of the state of the UI without writing lot of javascript code.
I’m not going into the concepts behind this framework, neither into details for beginners because they have a great tutorials on their website and nice documentation too, so I couldn’t do better even if I would.

Let’s see a simple example that shows how I solved this problem (I had to ask a question on stackoverflow to seek help as I was stuck. If you are interested, here is the link)

Controller:
Nothing special here, the controller is just injecting some dummy data into the ViewData to fill in the drop down lists and give the illusion that the number of menus is not fixed.

        public ActionResult Index()
        {
            var numberOfDropDownMenus = 5;
            for (var i = 0; i < numberOfDropDownMenus; i++)
            {
                var selectList = new SelectList(this.GetDummyItems(i), "Id", "Name", -1);
                ViewData["Options_" + i] = selectList;
            }
            ViewData["DropDownMenus"] = numberOfDropDownMenus;
            return View();
        }

View:

Let’s start with the knockout view model: for this specific page I’m just interested in the selected value for each ddl, nothing else, so my view model will be just as simple as this:

var initialValues = [];
    var n = @Html.Raw(Json.Encode(ViewData["DropDownMenus"]));
    for (var i = 0; i < n; i++) {
        initialValues.push(ko.observable());
    }

var viewModel = {
        selectedValues: ko.observableArray(initialValues)
};

The above code means that our view model has only one item, selectedValues, which is an observable array of observables (initialValues is in fact an array of observables).

Why not simply use an observableArray? Because we want each item of the array to reflect the currently selected value for a specific DDL, and therefore it must be an observable value so that knockout can automatically update its value whenever the user changes something.

It will be clearer if we look at how we data-bind each DDL to an observable value into the observable array:

@for (var i = 0; i < (int)ViewData["DropDownMenus"]; i++)
{
    @Html.DropDownList("DropDown_" + i, (SelectList)ViewData["Options_" + i],
                                new { data_bind = "value: selectedValues()[" + i + "]" })
}

The trick here is to use a positional databinding:

new { data_bind = “value: selectedValues()[” + i + “]” }

If i is 0, this will be evaluated for example as
new { data_bind = “value: selectedValues()[0]” }

The currently selected value for the first DDL will be stored in the first position of the observable array!

User interface and test output

User interface and test output

In the screenshot above we can see that the array is changing accordingly with the changes applied to the UI.
(To show the test output, I used the following syntax: <div data-bind=”text: ko.toJSON(selectedValues)”></div> It’s important to use ko.toJSON since selectedValues contains observables, not simple values)

Then, if we want to send data to the server (and we want), we can do like the following

$(document).ready(function () {
        $("#fakePost").click(function () {
            var viewModelToJSON = ko.toJSON(viewModel); //Serialize the viewmodel
           $.post("/Home/MyAction", { JSONviewModel: viewModelToJSON }, myCallback);
        });
        ko.applyBindings(viewModel);
    });

And on the server we can deserialize the string using our own favourite library.
For the sake of simplicity I used the built in javascript serializer:

public JsonResult MyAction(string JSONviewModel)
        {
            var jss = new JavaScriptSerializer();
            var selectedValues = jss.Deserialize(JSONviewModel);
            return Json("Ok");
        }

where ViewModel is a simple C# object useful for easy de-serialization and to communicate with an hypothetic Application Layer

public class ViewModel
    {
        public List selectedValues { get; set; }
    }

That’s all!
Obviously this is a super simplified example, but I hope it helps if someone has to face the same problem as me in the future.

Cheers,

Valerio