Jeremy Likness' Blog

MVVM on MVC: HTML is not XAML

I have to admit that I may have rolled my eyes a bit when I first learned about the KnockoutJS library. It sounded too much like forcing a square peg into a round hole. Isn’t Model-View-Controller (MVC) already it’s own pattern? Does it make sense to apply something like Model-View-ViewModel (MVVM) to HTML? I already had enough issues dealing with MVVM on the platform it was designed for, XAML and C# (WPF and Silverlight). Some people simply didn’t get the pattern, others were pushing it without really understanding its benefits, and many applications completely and unnecessarily overcomplicated their implementation of the pattern. So before we talk about whether it makes sense in HTML-based applications, we first need to agree on what the benefits of the pattern are.

I’ve heard many opinions regarding this and have not only read lists, but also published my own. You might hear things like “decoupled code” or “best practices” or “modularity.” Many of the benefits often cited are really just good coding practices that should be implemented regardless of the user interface (UI) pattern being followed. After looking at all of the different benefits, coding using different patterns, and spending several years building enterprise Line of Business (LOB) Silverlight applications it really boils down to two specific benefits:

  1. Testability – MVVM improves the testability of your application. This is only a benefit if you feel that testing itself provides benefits.
  2. Designer-Developer Workflow – MVVM facilitates a separation of UI and presentation logic concerns from the business layer that makes it easy to streamline the development process by allowing design cycles to happen in parallel with development. A great example of this was the Microsoft Looking Glass project that had more than 6 designers working on design for over 3 months while we developed it. The total delivery time was around 4 months, instead of the typical 7 (3 + 3 + 1 month of testing) you get when the design process has to happen sequentially and not parallel to the development effort.

The design-time views even without the workflow are useful but not critical in my opinion. So how does this all translate to HTML-based development?

HTML is Not XAML

The first distinction that Silverlight and WPF developers need to make is between HTML and XAML. They are often compared, but are they really similar?

XAML is a declarative language for instantiating an object graph. While it is commonly used for UI elements, it can create special objects like styles that apply theming to elements, behaviors, triggers, even code that has no visual artifacts whatsoever. XAML by nature is extensible. You can create new elements that map to custom classes, and extend the attributes of existing elements through attachments and custom markup extensions. This strict relationship between the declaration and the object graph means that invalid XAML is a Bad Thing™ and causes the XAML parser to choke. If it cannot instantiate an object or set a property, the entire visual tree is invalid and that’s that.

HTML is declarative markup. It defines a structure. Most of that structure is simply containers for information. There is minimal behavior. Elements are pre-defined based on an accepted standard and arbitrary extension or customization is not supported. The containers provide some structure to information, but it is ultimately CSS that defines the UI and to an extent some of the presentation logic and behaviors. JavaScript handles the object graph for the page, but neither CSS nor HTML declaratively instantiate JavaScript objects. It’s the other way around – JavaScript in the context of a page is more like parsing an XML Document in C# code than it is like having XAML and code-behind. The closest thing to XAML in the web world is server-side code that through whatever mechanism emits the HTML and JavaScript code elements of a web page.

Because HTML is just structure, browsers can also be very forgiving in their parsing. They do not have to map an element to an actual class or type that is instantiated. Browsers will close your tags for you and even make substitutions for elements they don’t recognize. There are well-known algorithms for rendering HTML.

So how does this fit into MVC? First, let’s be clear on one important distinction:

MVC as a Framework, not a Pattern

Note I didn’t say MVC is a framework, but in the context of this conversation we’ll have to agree that I’m referring to MVC as a Microsoft Framework and not the pattern. When you talk about the pattern, it can get very confusing when introducing an entirely separate pattern like MVVM. When you talk about a framework based on the pattern, it’s a little easier to understand. While ASP. NET MVC is based on the MVC pattern, it doesn’t force a strict implementation or interpretation of the pattern and provides quite a bit of flexibility with how you structure your pages. You can choose different view engines and write your code in different languages.

So what about MVVM in the MVC framework? One of the key problems I’ve found when dealing with MVC is markup that looks like this:

<div id="logoDisplay">                
   <% Html.RenderPartial("LogoControl"); %>            
</div>                      
<div id="menuContainer">
   <ul id="menu">
      <li><%: Html.ActionLink("Home", "Index", "Home")%></li>
      <li><%: Html.ActionLink("About", "About", "Home")%></li>
   </ul>                       
</div>

What’s wrong with this? It compiles and runs perfectly fine, right? Here’s my issue: I’ve just lost my designer-developer workflow. True, I might be able to hire an MVC-savvy designer who can work with this code, or I may be responsible for design myself. I’ve seen this workflow before:

  1. Designer creates Adobe Photoshop assets
  2. Same designer or someone else turns it into a beautiful HTML web page
  3. Developer has to rip out the HTML as best as they can and format it into the view and merge into CSS
  4. QA tester finds some strange nuance where a field wraps and unfortunately the designer doesn’t know how to fix it because their version works fine, it’s only in the runtime that it happens
  5. It turns out one of the embedded commands emitted some whitespace that the template didn’t account for and was causing the issue
  6. etc. etc. etc. .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

This also gives me no design-time experience. If HTML was real HTML without any embedded markup, I could just drag it into a browser and go to town. This is one place I start to see value with MVVM in MVC. I could do the same thing above, but make it look like this instead:

<div id="menuContainer">
   <ul id="menu">
      <li data-bind="foreach: menuItem">
         <span data-bind="text: name">Name</span>
      </li>     
   </ul>   
</div>

.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

Now I’ve got clean HTML that I can hand to a designer. I can even pull it up in a browser and view it to get a preview. Only … there’s still a problem. Can you see it?

The Problem with Data-Bind

Remember how I mentioned that HTML is not XAML? HTML is not supposed to be arbitrarily extended as in the previous example. In fact, most editors will complain about the data-bind attribute because it’s not a valid, defined attribute. Sure, you’ll get away with it: browsers are notoriously forgiving when it comes to bad mark-up, but this is what I would call an invasive or intrusive way to mark up HTML. What would really be nice is if we could do this more cleanly and provide pristine HTML with CSS, then provide our behaviors, validations, etc. in a way that is design-time friendly and testable.

A Clean Approach

For my “clean” approach I decided to model a simple form. It allows for first name and last name, shows the name dynamically as it is edited, and only allows submission when both fields are entered. First, let’s take a look at the HTML. I’m confident I could pass this off to a designer and they could modify it to their heart’s content. The only requirement is that they honor my “contract” which is the specific ids I’ve assigned. We agree there is something called a first name and last name and that they should be consistently referenced as firstName and lastName – whatever else they want to change is up to them.

The full HTML is here:

<!DOCTYPE html>
<html lang="en">
   <head>
      <title>Example MVVM Form</title>
      <link rel="stylesheet" href="style.css" type="text/css" media="screen" />
      <script type="text/javascript" language="javascript" src="jquery-1.7.1.js"></script>
      <script type="text/javascript" language="javascript" src="knockout-2.0.0.js"></script>
   </head>
   <body>
      <h1>Example MVVM Form</h1>
      <p>This is an example of an MVVM-based form. The HTML is "clean" markup - there are no custom attributes, tags, or embedded JavaScript. Everything is wired separately through code.</p>
      <form id="mainForm" action="#">
         <div class="form-label">Full Name:</div>
         <div class="form-field"><span id="fullName"></span>&nbsp;</div>
         <div class="form-label">First Name:</div>
            <div class="form-field">
               <input id="firstName" class="textField" type="text"/>
               <span id="firstNameValidation" class="field-validation"></span>
            </div>
         </div>
         <div class="form-label">Last Name:</div>
            <div class="form-field">
               <input id="lastName" class="textField" type="text"/>
               <span id="lastNameValidation" class="field-validation"></span>
            </div>
         </div>
         <input type="submit" class="formButton" value="Save">
      </form>
      <script type="text/javascript" language="javascript" src="./viewModel.js"></script>
      <script type="text/javascript" language="javascript" src="./mvvm.validations.js"></script>
      <script type="text/javascript" language="javascript" src="./bindings.js"></script>
   </body>
</html>

.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } Now that we have a page we can easily edit, let’s make it a little prettier with some simple CSS:

body {
    font-family: arial, helvetica, sans serif;
}

h1 {
   color: maroon; font-size: 20pt; font-weight: bold;
}
p {
   font-size: 12pt; 
}
.form-label {
   font-weight: bold;
   float: left;
   width: 200px;
   padding: 10px; 
}
.form-field {
   padding: 10px;
}
input.textField {
   width: 300px;
}
input.formButton {
   width: 100px;
   background-color: lightgreen;
   border: 2px solid green;
}
input.formButton:hover {
   background-color: lightred;
   border: 2px solid red;
}
.field-validation {
   font-weight: bold;
   padding-left: 10px;
   color: red;
}

The view model is easy enough, it simply defines fields for the first and last names and a computed field that shows the full name. I’ve even been kind enough to humbly supply a default first name:

function NameViewModel() {

   var self = this;

   this.firstName = ko.observable('Jeremy');
   this.lastName = ko.observable('');

   this.fullName = ko.computed(function() {
      if (self.lastName()) {
         if (self.firstName()) {
            return self.lastName() + ', ' + self.firstName();
         }
         return self.lastName();
      }
      return self.firstName();
   });
}

var viewModel = new NameViewModel();

What’s nice about the MVVM approach is that I can easily extend the view model to provide validation. I will eventually use the JavaScript module pattern to create the validations so they can be easily attached and extended, but for this simple example I just used a simple object instead. In this case there is simply a required validation. In a full production system, I would have a library of these validations and also keep a collection on the target object to allow multiple validations to attach to the same attribute.

ko.extenders.required = function (target, overrideMessage) {
    
    target.hasError = ko.observable();
    target.validationMessage = ko.observable();

    function validate(newValue) {
        target.hasError(newValue ? false : true);
        target.validationMessage(newValue ? "" : 
             overrideMessage || "This field is required.");
    }

    validate(target());
    target.subscribe(validate);
    return target;
};

function MvvmValidations() {
    
    this.required = function(target, overrideMessage) {
        target.extend({ required: overrideMessage });
    };

}

.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

Now I have a view model specific to my view and a validation library that I can reuse across multiple views. I tie them all to the HTML like this – note that what is important is that the contracts were honored for the identifiers of the various elements, but everything else can be simply attached and declared.

$(document).ready(function() {

   $('#firstName').attr('data-bind','value: firstName, valueUpdate: "afterkeydown"');
   $('#lastName').attr('data-bind','value: lastName, valueUpdate: "afterkeydown"');
   $('#fullName').attr('data-bind', 'text: fullName');
 
   var validations = new MvvmValidations();

   validations.required(viewModel.firstName);
   $('#firstNameValidation')
   .attr('data-bind','visible: firstName.hasError, text: firstName.validationMessage');

   validations.required(viewModel.lastName, "Last name is required."); 
   $('#lastNameValidation')
   .attr('data-bind','visible: lastName.hasError, text: lastName.validationMessage');

   viewModel.saveName = function() {
      if (viewModel.firstName.hasError() || viewModel.lastName.hasError()) {
         alert('Errors exist.'); 
         return false;
      }
      alert('Looks good!');
      return true;
   };

   $('form').attr('data-bind', 'submit: saveName'); 
 
   ko.applyBindings(viewModel);

});

.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

You can view the end result and download the source here: http://apps.jeremylikness.com/samples/knockout-mvvm/mvvm.html

Quod Erat Demonstrandum

So what have we achieved? I think quite a bit. This format has allowed me to build out a website using data-binding. The source HTML is clean and can be edited easily by a designer. While I muck around with the DOM at runtime, this is done programmatically and doesn’t require that I invade the HTML or CSS. The JavaScript is separate, reusable, and testable.

The thing I haven’t shown is how this fits into the MVC framework. In the framework you’ll likely go ahead and use some of those helper methods to specify the path to the JavaScript but you should be able to maintain a nice, clean HTML core. And if the view models seem like a lot of work, you’re not thinking about the bigger picture. With a strongly-typed view (one that correlates to a typed model) you can easily create a view helper that emits the necessary JavaScript for the view model. You could probably even emit the code to generate the bindings, and the only step you would need to take as a developer would be to write the controller to return the model and apply any validates specific to the view.

At the end of the day, I’m a believer that MVVM can work in MVC if we remember that HTML is not XAML and approach the code the right way.

On Jan 24 2012 11:44 PMBy jlikness html5, Javascript, knockoutjsWith 3 Comments

Comments (3)

  1. Very nice article.

    But boy, is this MVVM in HTML madness ugly ;)

    Thanks for the insight.

    - ngm

  2. Why do people associate MVVM with MVC? Isn't MVP a better analog? http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93presenter

Leave a Comment