Friday, August 21, 2009

Another Perspective on M-V-VM

I was tasked by a client to port a Windows Mobile application to run on many different platforms to include the iPhone, Palm Pre, Android, BlackBerry as well as desktop browsers.  To make this more interesting where the platform allows it, the application needed to run in an offline mode. 

They wanted me to look at building this on top of Google Gears to take advantage of its offline capabilities to include SQLite a local server to serve up web pages without an internet connection.  At first I was somewhat skeptical of the approach but once I started learning about the upcoming HTML 5.0 standards and the services it provided this approach seemed more and more practical.

So what does this have to do with Model View View Model?

This was a non-trivial application of approximately 120 screens and 80 or so tables so this wasn’t something I could just “hack” together. I wanted to implement this using some sort of pattern.  My basic requirement was that I wanted the simplest possible view (remember this needed to run on many different devices/platforms with different capabilities) so creating something where we can inject some HTML code that represents the view into the ViewModel and the ViewModel would take care of all the interactions and states of the control.  This way we can have a minimalist view for simplistic browsers such as Opera Mini that run on the BlackBerry, a decent good looking UI that will run on the iPhone, Palm Pre and Android and a full-featured UI that will run on the desktop.  The only thing that changes will be the HTML template.

There are some frameworks for M-V-VM that run on the desktop, some even run in Siliverlight.  My need was to build something that ran in the browser with JavaScript and HTML.  At this point I think the churn in my frameworks has pretty much died down and I can write what I learned and discuss my approach.

Before I get into the M-V-VM portion, let me list the 3rd party components that were used:

  1. JQuery – Used to manipulate the DOM
  2. Microsoft ASP.NET Ajax Library
  3. A modified version of Jay Kimble’s Class Compiler (simpler syntax for creating JavaScript classes)

Here’s a 50K ft level view of the interaction between the areas of the frameworksimage

Model

The model is used to encapsulate the idea of access to the data store.  My data access strategy is an interface that provides the mechanisms for CRUD.  This interface has been implemented on a Google Gears SQLite database, Safari/WebKit HTML 5.0 SQLite database as well as an AJAX connection that get’s it’s data from the server.  The models are fairly traditional in that they include all of the non-trivial business logic.

View

The views are built in HTML that rely heavily on CSS.  In addition we use attributes to describe how the View-Model should create and render the controls.  For example a drop down lists might be described as follows

<div id=’Module_View’>
  <div id=’ddlStatus’ TextField=’description’ ValueField=’id’
        DataValueField=’StatusId’ action=’statusChanged’>
    <label>Status</label>
  </div>
</div>

As you can see we are using HTML attributes to describe how the drop down should be populated as well as the action that should be invoked when the status changes.

Then for different templates we can create different views that our engine can pickup based upon browser type:

<div id=’Module_View_iPhone’>
  <div id=’ddlStatus’ TextField=’description’ ValueField=’id’
        DataValueField=’StatusId’ action=’statusChanged’>
    <label>Status</label>
  </div>
</div>

So far this has been working great, I haven’t had to include any JavaScript within my views.  All the JavaScript has been “attached” via JQuery in the View Models and primarily within the JavaScript controls.  I’ve also been able to do the vast majority of look-and-feel type of stuff in CSS.

View Model

When the html template as specified for the active skin is “injected” into the View-Model, the View-Model will create instances of JavaScript classes that encapsulate the idea of controls rendered properly on different platforms. 

this._ddlStatus = new Ctrls.DropDownList($(‘#ddlStatus’),$(‘#Module_View’));

So the ViewModel will create a new DropDownList instance after we use JQuery to find this html template and specification for the control.  The instance will then be ready to be data-bound and rendered on the form.  Note this is all happening in JavaScript within the browser.

In addition to creating fields from the HTML control templates our ViewModel is responsible for binding the data to those controls.  Each ViewModel requires a Bind method that expects a Model of the data that corresponds to that form.  It is the responsibility of the view model to gather any additional data required to populate the view.  This includes things like data sets for populating drop down lists.  It is also the responsibility of the ViewModel to manage state of the data on the view and do two-way binding.  There are two additional methods defined in our interface for the view, Validate and Save.  I like to think of the workings of the ViewModel as the private interface and code used to interact with the View.  The rest of the application does not directly act upon the ViewModel.  The rest of the application acts on the view and ViewModel by going through a Controller.

Controller

Here is where my implementation differs slightly from the traditional M-V-VM implementation.  For the way I’m building the application a controller seems to solve a number of problems and provides and fairly high-level interface when interacting with the View and ViewModel.  I would consider the Controller as a public interface to the View.   Each Controller that is implemented defines the following methods (note: bold methods are defined in an interface and must be present):

  1. Show(args) – Shows the view, args are view dependent but usually contain the ID for the primary data to be displayed as well any secondary information.
  2. Restore(launchArgs, returnArgs) – If this controller launches a child view and we return back to the calling controller.  The original calling args and optionally restoring args can be provided.
  3. Save() – If the frameworks requests that the current view data be persisted to  storage.
  4. SaveAndClose(callback) – If the frameworks requests the current view data be persisted to storage and the view closed.  If the ViewModel does not have valid data, the callback will return false and the form must not be closed.
  5. Cancel(callback) – Abort any changes, if the ViewModel can not be closed then the callback will return false and the form must not be closed.
  6. xxxxxAction – These are what are considered additional actions that can be invoked for the controller and are controller dependent.  These can either be generated by the View’s controls as in the case for a drop down list selection changing or a button press or come from the outside world as in a change of status of connectivity.  This also supports a fairly robust model for doing automated testing.

Very little code exists within the controller, the controller basically either passes on data/calls to the ViewModel or invokes business logic in the model and tells the UI to re-bind.

So in summary I look at the Controller as a high-level public interface to interact with the View through the private interface on the ViewModel.

ViewDispatcher

Everything in the application get’s routed through the ViewDispatcher.  The ViewDispatcher also runs in the browser via JavaScript.

The ViewDispatcher has methods such as:

      • ShowView(“[MODULE_NAME]”,”[VIEW_NAME]”,args (optional) ) – Shows a specific view within a specific module.  The ViewDispatcher is responsible for finding the view template in the ViewRepository, creating or loading and instance of the controller, cleaning up the previous view template.
      • PopView(return args (optional)) – Take the calling view off of a “return-stack” and call the Restore() method on the restored controller.
      • HandleAction(“[ACTION_NAME]”,args (optional)) – On the active controller, invoke a method with the name [ACTION_NAME]Action();
      • Save() – Call the save method on the active controller
      • SaveAndClose(returnArgs (optional)) – Call the SaveAndClose method on the active controller, if the callback from the controller returns true, then call PopView() with returnArgs if present.
      • Cancel() – Calls the Cancel method on the active controller.  If the callback from the controller returns true, then call PopView without any return arguments.

ViewRepository

At any one time a view may be active in the UI.  The ViewRepository is the complete set of views that may be called upon to active via the ViewDispatcher.  Once the ViewDispatcher has decided that view is no longer needed (usually replaced by a different one) the view will be placed back in the ViewRepository so it can be called upon again to be active.

Summary

So in summary at a high-level here are the different components that make up my frameworks and their responsibilities:

Models
  1. Models are fairly traditional, getters and setters for fields that make up the data in our system.
  2. Additional methods to handle business logic (both class and instance methods)
  3. 1:1 relationship between the model and database table
  4. Data access goes through an implementation of an interface.  The implementations that we currently have defined are:
    1. Google Gears SQLite (synchronous)
    2. Safari/WebKit HTML 5.0 SQLite (asynchronous)
    3. AJAX Service for devices not supporting (asynchronous) 
  5. Due to the nature of Safari and AJAX all data access (and any calls that at some point need to access data) are done asynchronously via call backs.
View
  1. Defines the presentation of the forms and data to the user
  2. Contains zero behaviors or JavaScript
  3. Presentation attributes are defined in CSS
  4. Uses attributes to be extracted at run-time with JQuery to define additional behaviors
  5. Are stored in the ViewRepository
  6. Are populated into the Active content region by the ViewDispatcher
  7. Are stored back into the ViewRepository by the ViewDispatcher
  8. Use the id format of [MODULE]_[VIEW] and optionally [MODULE]_[VIEW]_[SKIN]
  9. Does not support WYSIWYG
ViewModel
  1. Think of the ViewModel as the private interface to the view.
  2. The View is injected into the ViewModel
  3. Instance variables that correspond to the controls on the form are created from html tags and attributes
  4. Exposes a method named Bind(model) that has as it’s argument an instance of the model used to populate the view.
  5. Handles two way data binding between the model and view.
  6. Is responsible for pulling any additional data required to populate the view.  This could be lists to populate drop down lists or similar
  7. Contains all logic for managing the relationship between the model and the view.
  8. Handles simple interaction of controls, based upon state another control should be enabled/disabled
  9. Provides validation services, confirms that both the content of the view and the model are valid
Controller
  1. This is where my approach is varies from the standard model
  2. Think of the controller as the public interface for the view and the view model
  3. Provides methods named Show, Restore Save, SaveAndClose and Cancel (see above for more details)
  4. Also has methods named [ACTION]Action where these are the actions that should be invoked on the controller.  This is how external forces act upon the controller.
  5. This makes testing very simple
ViewDispatcher
  1. ViewDispatcher is implemented as a singleton
  2. The ViewDispatcher launches and displays views ShowView([MODULE],[VIEW],args)All interactions with controllers and thus with the ViewModel and View go through
  3. The ViewDispatcher through a HandleAction([ACTIONNAME]) method
  4. There is exactly one active controller at any one time.
  5. The ViewDispatcher has access to the ViewModel and ultimately the View through the active controller.

-ec

No comments:

Post a Comment