Telerik blogs

Architectural pattern names are making the rounds these days like a name dropper at a Hollywood party. "Model-View-Controller", "Model-View-Presenter", "Model-View-ViewModel" – the lines between the patterns, and the loose interpretations made by frameworks that implement them, often become so blurred that the catch-all-acronym "MV*" is gaining prominence in referring to any variant of client-side web application approaches. So - in the midst of this wonderful chaos of client-side framework approaches, what is MVVM?

Model-View-ViewModel -> MVVM

I'm not going to burden you with detailed academic treatises on MVVM's history - though I will list some links for further reading at the end of this post. MVVM is an attempt to better structure the relationships between what we see on a screen (the view), the data relevant to the scenario (the model) and any behavior and/or data that's important to the view as we interact with it (the ViewModel). I like diagrams, so let's look at this from a visual perspective.

Because, Diagrams

Let's pretend we have a customer model which, for the sake of discussion here, represents the true "domain" model for a customer in our application. We'd like to have an edit screen in our app that allows us to – big surprise, I know – edit the customer:

Our View – the Customer Edit Form - interacts with the ViewModel. The "Customer ViewModel" would likely contain properties that we'd also see on the "Customer Model": name, city, state, etc. It may also contain UI-specific properties. For example, if the Customer Edit Form could have sections of it collapsed/hidden, then our ViewModel might have properties like isContactVisible and isAddressVisible, which would be used to set the visibility/display values of the element(s) containing those sections.

The Customer ViewModel would also contain any methods (behavior) related to the UI. If our form had a "Save" button, then we'd expect to see a corresponding "save" method on the ViewModel. That "save" method would then take the properties relevant to a Customer Domain Model and pass them to a component in the app whose job is to communicate over HTTP or WebSockets with the back-end server and save the updated customer.

A purist might go as far as having an actual constructor function for a Customer Model - but it's JavaScript, and we can just as easily create an object literal that represents our Model using the appropriate properties (and transforming them where needed) from the ViewModel. You might not create that object literal until you make the actual HTTP request to update the customer.

Feeling like this, yet?

Almost immediately, we begin to venture into a gray area - one I'd like to think of as "You get lots of room to choose your own adventure". The "Customer Model" at the left of the above diagram is often transient in the client (i.e. - it's often only present in the HTTP response we get when we request the data, or the HTTP request we use to update/create the data), and that's OK. The important part is that we let the MVVM pattern do what it's here to do: separate the UI-specific state and behavior from our "Domain" Model. Your Model doesn't care about "isContactVisible" or "isAddressVisible", but the View does. MVVM helps every component get what they need, without leaking details everywhere.

One Pattern to Bind Them All

It's quite common for MVVM implementations to use "two-way-binding" (though it's not a requirement). This means that any change to the form inputs in our "Customer Edit Form" View will be propagated to the Customer Edit Form View Model and any changes to the ViewModel will be reflected on the view. This saves you from writing a ton of boilerplate code that would be necessary to keep the two in sync.

Getting Concrete with Kendo UI

What would it look like to represent our Customer Edit Form scenario using Kendo UI?

This jsFiddle gives you a basic example (we break down the HTML & JavaScript below):

The View

In our example, the "view" is a simple form:

<div id="custEdit">
<form role="form">
<div class="form-group">
<label>Customer Name: <input class="form-control" data-bind="value: name" /></label>
</div>
<div class="form-group">
<label>Address 1: <input class="form-control" data-bind="value: address1" /></label>
</div>
<div class="form-group">
<label>Address 2: <input class="form-control" data-bind="value: address2" /></label>
</div>
<div class="form-group">
<label>City: <input class="form-control" data-bind="value: city" /></label>
</div>
<div class="form-group">
<label>State: <input class="form-control" data-bind="value: state" /></label>
</div>
<div class="form-group">
<label>Zip: <input class="form-control" data-bind="value: zip" /></label>
</div>
<button class="k-button" data-bind="click: update">Update</button>
<button class="k-button" data-bind="click: reset">Reset to Defaults</button>
<div data-bind="visible: updated">
<h4>Customer Updated!</h4>
</div>
</form>
</div>

You'll notice that our input controls have a data-bind attribute. Kendo UI gives you the option of declarative bindings – where we use our HTML to tell Kendo UI how an element should be bound to a ViewModel. For example, let's look at our Customer Name input:

<input class="form-control" data-bind="value: name" />

In our data-bind attribute, we're telling Kendo UI to bind the value property of the input element to the name property of our Customer ViewModel.

But wait - how does it know to use the Customer ViewModel?

I'm glad you asked - and we'll get to that in a moment when we look at the JavaScript.

Aside from binding values, our form also binds two button click events to methods on our ViewModel. For example, the "Update" button has an attribute/value of data-bind="click: update". This tells Kendo UI to invoke the update method on our ViewModel when a click event occurs on the button element.

In addition - the div at the bottom of the form has data-bind="visible: updated" attribute/value. This will cause this div to be hidden while updated is false, and shown if it's true.

Side Note: There has been plenty of argument as to whether or not declarative bindings are a good thing. Many JavaScript developers prefer an unobtrusive approach - where bindings like this would be set up imperatively, in JavaScript, rather than using markup attributes. I would normally consider myself in the unobtrusive camp. However - as long as you are not putting logic in your declarative bindings (some frameworks actually allow inline JavaScript in these attributes, yikes!), then I'd say declarative bindings are extremely powerful. Since a ViewModel is conceptually coupled to the View, using data-bind attributes like this is a terse and simple way to tell Kendo UI that the two things need to be wired up - sparing you from writing dreaded boilerplate code™.

The JavaScript

In our JavaScript code, you'll notice three main things going on:

  • We create a storage object to act as a wrapper around localStorage. We could have just as easily saved our customer via an HTTP request.
  • We create a custEditViewModel - more on that below.
  • We bind our custEditViewModel to the form (it's wrapped in a div with an id of "custEdit") via the kendo.bind call. This is how the Customer ViewModel gets associated with the View. Any references in the view's data-bind attributes will refer to properties on the Customer ViewModel.
var storage = {
    save: function(cust) {
        localStorage.setItem("cust", JSON.stringify(cust));
    }
};

// defaults is a placeholder for later :-)
var defaults = {}; 

var custEditViewModel = kendo.observable({
    updated  : false,
    update: function (e) {
        e.preventDefault();
        this.set("updated", true);
        storage.save({
            name     : this.get("name"),
            address1 : this.get("address1"),
            address2 : this.get("address2"),
            city     : this.get("city"),
            state    : this.get("state"),
            zip      : this.get("zip")
        });
    },
    reset: function (e) {
        e.preventDefault();
        this.set("name", defaults.name);
        this.set("address1", defaults.address1);
        this.set("address2", defaults.address2);
        this.set("city", defaults.city);
        this.set("state", defaults.state);
        this.set("zip", defaults.zip);
        this.set("updated", false);
    }
});

kendo.bind("#custEdit", custEditViewModel);

Making an Observable

So - about that custEditViewModel. The object literal we pass into kendo.observable is converted into an "observable". This means that it will be given all the necessary behavior to support change tracking. If we inspect the custEditViewModel instance in Chrome's console, we can see what's been added to it:

Our custEditViewModel has, among many things, been given set and get methods on its prototype. Now that we have an observable, we'll use those methods to set and get the values of properties:

//set(propertyName, value);
custEditViewModel.set("name", "ACME, Inc.")

...rather than directly referencing them (which you could still do) via custEditViewModel.name = "ACME, Inc."

WHY is this helpful?

Using set allows Kendo UI to know when the value has changed, enabling the instance to emit events. For example, we could set up a listener for change events on our ViewModel:

custEditViewModel.bind("change", function(e){
    console.log("What changed? The " + e.field + "field changed.");
});

You're probably getting the idea that this "observable" behavior is what makes two-way binding possible - and that's exactly right!

More on the ViewModel

Our custEditViewModel initially has an updated property, along with two methods: update and reset. You can see that we reference properties like name, address, etc in both methods - but where did we create those properties?

Actually, we didn't. When a user fills out a field in the form, they will be created. The property doesn't have to exist on the object literal we pass to kendo.observable in order to be used later. By calling custEditViewModel.set("name", "ACME, Inc."), the name property will be created if it doesn't already exist. Likewise, referencing the name property via custEditViewModel.get("name") before it has been set will return undefined, and not throw an exception.

In the update method, we're creating an object with only members that can be serialized (properties - no functions or circular references), and we're saving it to localStorage. We also set the updated property to true, which results in the div below the form in being shown (thus you see the "Customer Update" message).

In the reset method, we're setting the values of our ViewModel's properties to some "default" customer properties - which currently don't exist (we'll use this more later). This causes the form input fields to be blanked as well - since we've bound them. That's one of the beauties of MVVM with two-way bindings: updating your ViewModel in code causes the UI to reflect the changes without any additional work on your part.

What if we wanted to load the values from localStorage to pre-populate our form the next time the user loads it?

Seeding Values from Local Storage

This fiddle gives you an idea of how this can be done:

You can see that our custEditViewModel has changed:

// snippet from above fiddle
var defaults = storage.load();

var custEditViewModel = kendo.observable({
    name     : defaults.name,
    address1 : defaults.address1,
    address2 : defaults.address2,
    city     : defaults.city,
    state    : defaults.state,
    zip      : defaults.zip,
    updated  : false,
    update: function (e) {
        e.preventDefault();
        this.set("updated", true);
        storage.save({
            name     : this.get("name"),
            address1 : this.get("address1"),
            address2 : this.get("address2"),
            city     : this.get("city"),
            state    : this.get("state"),
            zip      : this.get("zip")
        });
    },
    reset: function (e) {
        e.preventDefault();
        this.set("name", defaults.name);
        this.set("address1", defaults.address1);
        this.set("address2", defaults.address2);
        this.set("city", defaults.city);
        this.set("state", defaults.state);
        this.set("zip", defaults.zip);
        this.set("updated", false);
    }
});

We're loading our customer from localStorage and setting the values on the ViewModel. Sure - we can do it this way, but I still think that having to explicitly set each ViewModel property feels bloated, and I'd prefer to not create the observable instance until I really need it.

One More Try

This last fiddle is another variation - this time we're wrapping the creation of our custEditViewModel in a constructor function, that takes any "seed" values for the customer properties as a constructor argument:

I'm partial to this approach since I don't have to instantiate the ViewModel until I need it - and when I create the instance, I can pass in default values:

var storage = {
    save: function(cust) {
        localStorage.setItem("cust", JSON.stringify(cust));
    },
    load: function() {
        var cust = localStorage.getItem("cust");
        return cust ? JSON.parse(cust) : {}; 
    }
};

var CustomerEditViewModel = function(defaults) {
    return kendo.observable($.extend({
        updated  : false,
        update: function (e) {
            e.preventDefault();
            this.set("updated", true);
            // optional - add a line here overwriting the
            // original defaults value with what's being
            // saved so the Reset To Defaults only resets
            // to the most recently saved data.
            storage.save({
                name     : this.get("name"),
                address1 : this.get("address1"),
                address2 : this.get("address2"),
                city     : this.get("city"),
                state    : this.get("state"),
                zip      : this.get("zip")
            });
        },
        reset: function (e) {
            e.preventDefault();
            this.set("name", defaults.name);
            this.set("address1", defaults.address1);
            this.set("address2", defaults.address2);
            this.set("city", defaults.city);
            this.set("state", defaults.state);
            this.set("zip", defaults.zip);
            this.set("updated", false);
        }
    }, defaults)); 
};

kendo.bind("#custEdit", new CustomerEditViewModel(storage.load()));

Wrapping Up

That's all we have space to cover in this post - and we're only scratching the surface! In the future, we'll look at calculated fields as well as some of the other options we have at our disposal with the data-bind attribute.

Further Reading


About the Author

Jim Cowart

Jim Cowart is an architect, developer, open source author, and overall web/hybrid mobile development geek. He is an active speaker and writer, with a passion for elevating developer knowledge of patterns and helpful frameworks. 

Comments

Comments are disabled in preview mode.