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.
Before I get into the M-V-VM portion, let me list the 3rd party components that were used:
- JQuery – Used to manipulate the DOM
- Microsoft ASP.NET Ajax Library
Here’s a 50K ft level view of the interaction between the areas of the frameworks
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.
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=’ddlStatus’ TextField=’description’ ValueField=’id’
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=’ddlStatus’ TextField=’description’ ValueField=’id’
this._ddlStatus = new Ctrls.DropDownList($(‘#ddlStatus’),$(‘#Module_View’));
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.
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):
- 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.
- 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.
- Save() – If the frameworks requests that the current view data be persisted to storage.
- 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.
- 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.
- 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.
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.
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.
So in summary at a high-level here are the different components that make up my frameworks and their responsibilities:
- Models are fairly traditional, getters and setters for fields that make up the data in our system.
- Additional methods to handle business logic (both class and instance methods)
- 1:1 relationship between the model and database table
- Data access goes through an implementation of an interface. The implementations that we currently have defined are:
- Google Gears SQLite (synchronous)
- Safari/WebKit HTML 5.0 SQLite (asynchronous)
- AJAX Service for devices not supporting (asynchronous)
- 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.
- Defines the presentation of the forms and data to the user
- Presentation attributes are defined in CSS
- Uses attributes to be extracted at run-time with JQuery to define additional behaviors
- Are stored in the ViewRepository
- Are populated into the Active content region by the ViewDispatcher
- Are stored back into the ViewRepository by the ViewDispatcher
- Use the id format of [MODULE]_[VIEW] and optionally [MODULE]_[VIEW]_[SKIN]
- Does not support WYSIWYG
- Think of the ViewModel as the private interface to the view.
- The View is injected into the ViewModel
- Instance variables that correspond to the controls on the form are created from html tags and attributes
- Exposes a method named Bind(model) that has as it’s argument an instance of the model used to populate the view.
- Handles two way data binding between the model and view.
- Is responsible for pulling any additional data required to populate the view. This could be lists to populate drop down lists or similar
- Contains all logic for managing the relationship between the model and the view.
- Handles simple interaction of controls, based upon state another control should be enabled/disabled
- Provides validation services, confirms that both the content of the view and the model are valid
- This is where my approach is varies from the standard model
- Think of the controller as the public interface for the view and the view model
- Provides methods named Show, Restore Save, SaveAndClose and Cancel (see above for more details)
- 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.
- This makes testing very simple
- ViewDispatcher is implemented as a singleton
- The ViewDispatcher launches and displays views ShowView([MODULE],[VIEW],args)All interactions with controllers and thus with the ViewModel and View go through
- The ViewDispatcher through a HandleAction([ACTIONNAME]) method
- There is exactly one active controller at any one time.
- The ViewDispatcher has access to the ViewModel and ultimately the View through the active controller.
Post a Comment