Databinding Collections with Knockout

I’ve written a few articles about databinding using Knockout (here, here, and here), but I haven’t touched on collections yet. Collections have proved to be a little more challening and require a bit more work, but they still follow the same pattern.

In this post, I’ll work through a short example to demonstrate how to do two-way databinding with collection objects.

Create Your View Models

I’ve found that getting a collection to bind properly requires multiple view models. The applications that I’ve written have required a main, page-level view model and separate models for each item in the collection.

Let’s say we want to write an page that will manage a classroom roster. The primary model will be called rosterViewModel, and we’ll create another model to represent students, studentViewModel. When the page is initialized, we’ll create a new instance of rosterViewModel and bind it to the page using Knockout.

var rosterViewModel = function() {
    var self = this;
    self.students = ko.observableArray();
};

var studentViewModel = function() {
    var self = this;
    self.firstName = ko.observable();
    self.lastName = ko.observable();
};

ko.applyBindings(new rosterViewModel());

Allow Items to be Added

Adding new items to the databound collection is as easy as creating a new instance of the sub-item’s view model and adding it to the collection. This should be done in an event handler of the parent model. So, in our example, we’ll add an addStudent event that will add a new instance of studentViewModel to the students collection.

var rosterViewModel = function() {
    var self = this;
    self.students = ko.observableArray();
    self.addStudent = function() {
        self.students.push(new studentViewModel());
    };
};

Allow Items to be Removed

Removing items from the collection works the same way as adding. When the event is invoked from the item to be removed, you simply remove it from the collection. We create another event, removeStudent, that does this.

var rosterViewModel = function() {
    var self = this;
    self.students = ko.observableArray();
    self.addStudent = function() {
        self.students.push(new studentViewModel());
    };
    self.removeStudent = function(student) {
        self.students.remove(student);
    };
};

Populating the Collection

Populating the collection might seem like a no-brainer. You’ve got some data, just pass it to Knockout and let it do its job, right? That actually works okay if you’re not modifying the data. The problem is that the source data isn’t rigged for Knockout with ko.observables and ko.observableArrays. The result is that data populates, but changes aren’t detected.

What I’ve done to get around this is to loop through the source collection, instantiate the appropriate view model object, and add it to the parent model’s collection. To make things easier, I modify the view model to accept the source data as an input and populate properties when it’s provided.

var rosterViewModel = function(data) {
    var self = this;
    self.students = ko.observableArray();
    self.addStudent = function() {
        self.students.push(new studentViewModel());
    };
    self.removeStudent = function(student) {
        self.students.remove(student);
    };
    
    if (data != null) {
        var s = data.students;
        for (var i in s) {
            self.students.push(new studentViewModel(s[i]));
        }
    }
};

var studentViewModel = function(data) {
    var self = this;
    self.firstName = ko.observable();
    self.lastName = ko.observable();
    
    if (data != null) {
        self.firstName(data.firstName);
        self.lastName(data.lastName);
    }
};

var sourceData = {
    students: [{ firstName: "Adam", lastName: "Prescott" }]
};

ko.applyBindings(new rosterViewModel(sourceData));

Retrieving the Collection

If you need to retrieve your collection for processing, I suggest doing the opposite of what was done to populate the collection: loop through the view model and extract the data. What you do with the data is up to you. You can package it into another object to be sent to another service or method, or format it for display elsewhere on the page. To demonstrate, I created a computed property that creates an HTML list of students entered.

self.roster = ko.computed(function() {
    var result = "Students:<br/>";
    var s = self.students();
    for (var i in s) {
        var f = s[i].firstName() ? s[i].firstName() : "&lt;unknown&gt;";
        var l = s[i].lastName() ? s[i].lastName() : "&lt;unknown&gt;";
        result += l + ", " + f + "<br/>";
    }
    return result;
});

You can see the complete working example here.

Advertisements

Leave a comment

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s