Recently I’ve been working with Knockout using an MVVM pattern and my viewmodels have been getting more and more bloated. Part of the elegance of the MVVM pattern is keeping the viewmodel “viewy.” When I see functions named “addAlbum” along with properties called “ajaxClient” on my viewmodel that raises a red flag. Here’s a likely viewmodel:
function AlbumViewModel () {
this.albums = ko.observableArray();
// my AJAX client
this.ajaxClient = new AlbumAjaxClient();
this.addAlbum = function (album) {
// validate the album and run it through some business logic
// make an AJAX call to add the album
// maybe make another AJAX call to repopulate your list if paging is involved
// oh, and you have to transform the server response message into your client-side album model
// maybe even parse date strings from JSON to JS Dates
};
this.pageNumber = ko.observable();
this.filterValues = ko.observableArray();
}
and maybe in a script block on your page you’d have the following initialization code (I’m using jQuery here, but it doesn’t really matter how you’re executing AJAX):
$(function () {
var viewModel = new AlbumViewModel();
ko.applyBindings(viewModel, getElementById('albums'));
$.ajax({
// form your ajax request to get a list of albums from the server
// ...
, success: function (data) {
// perform some transformation on the response message
viewModel.albums(transformedData);
}
});
});
Now imagine having a more complex viewmodel or multiple viewmodels and making multiple AJAX calls for multiple operations on the page. Or what if you want to wrap up your code into a distributable component? You get the picture: code soup.
Enter Model-View-ViewModel-Controller (MVVMC). The idea is that you delegate certain concerns to the controller that were previously owned by the viewmodel or code strung about anonymous functions or the global object. Prime candidates are searching the DOM for the elements to bind to, ko.applyBindings, AJAX calls, & deeplinking for a start. None of those belong in the viewmodel in my opinion. So let’s put them in the controller:
function AlbumController() {
var self = this;
this.viewModel = new AlbumViewModel();
this.getAlbums = function () {
updateAddress(this.viewModel.pageNumber(), this.viewModel.filterValues());
// call to your AJAX client; the last arg is a success callback for after the albums have been retrieved
client.getAlbums(pageNumber, filterValues, function (albums) {
self.viewModel.albums(transformAlbums(albums));
});
}
this.addAlbum = function (album) {
// call your AJAX client; the last arg is a success callback for after the album has been added
client.addAlbum(album, function () {
this.getAlbums();
});
};
this.initialize = function (elementId) {
ko.applyBindings(this.viewModel, document.getElementById(elementId));
};
function transformAlbums (albums) {
// transform here
}
function updateAddress (pageNumber, filterValues) {
// update the address bar for deeplinking
}
}
Now your initialization code looks like this:
$(function () {
var albumController = new AlbumController();
albumController.initialize('albums');
});
Or you could even make it fully unobtrusive pretty easily now. This illustrates how the MVVMC pattern will not only keep your code readable and maintainable, but will also let you wrap the code up into reusable components. Like when you need a different view of your albums, you already have a nice controller that does all of the heavy lifting for you.