This post is about developing just enough of an MVC based client on the browser to see how and where all the parts fit together, what design considerations might need to be taken into account and how the MVC pattern is applied. What is not implemented is the mechanism for communicating between the client and the server to remove a complexity that might otherwise obscure the client code. Client to server communications will be discussed without implementing code.
I’ll walk you through the code to exemplify the approach taken and how MVC comes into play. A goal is be to keep things minimal and clear in the hope you can use one or more of the techniques and tools outlined in your own projects. This post may even help you select alternative tools and frameworks.
* * The title of this blog is a play on the musical The Sound Of Music which contains the song “Maria“.
The code discussed in this post can be found here
My goal is to develop a small yet exemplary application and the canonical Todo application fits that bill quite nicely, with its simple data model and a user interface that needs little explanation. A Todo application can also be quite useful without server side functionality so conceptually its a whole application. The Todo Applications core model is essentially a list of entries outlining what is to be done, with each entry having a flag indicating if the entry has been completed and a description of what is to be done. To make the design and the use of MVC as clear as I can I have chosen to model the client side application as separate from the Todo model it manipulates. I find this helped me keep the separation of Client side and Server side clear and provide a more obvious ‘entry point’ for see where the Client Application begins and ends.
Tip - I strongly suggest that when Client Server data exchange is implemented that the data exchanged is not a direct mapping of the AppModel, rather it is an exchange of messages to manipulate each ’sides’ model indirectly. For example, when server data is processed it becomes a series of calls on the AppController rather than a direct replacement of the AppModel. A direct replacement or manipulation of the AppModel would be a tight coupling that would lead to possible change impact issues later.
MVC - Model View Controller
The Todo Client Application is embodied in the classes AppModel, AppView and AppController (see figure x), with the file src/js/initializer.js bringing them into existence when the browsers window load event is fired. The initializer.js code is the equivelant of a Java or ‘C’ programs ‘main’ function. Each class (AppModel, AppView & AppController) implements their part in the Model View Controller (MVC) pattern. There is a lot of material out there describing the MVC pattern in depth so I wont regurgitate it here, suffice to say I’m following the pattern as described by GOF and implemented in Smalltalk examples.
In a nutshell a View listens for events to happen on a HTML Element. When an event happens like the input field has changed the View is notified (by Maria). The View then extracts or builds whatever data is needed so it can communicate the event to the Controller by calling one of its methods. The Controller takes the notification and applies whatever Business logic it embodies (via other classes - don’t contain it within the controller) and calls a method on the associated Model to inform it of the change. The Model applies the appropriate changes to the model and then notifies all the observers of the model (via Maria) that the Model has changed. A View typically observes both a HTML Element and a Model meaning it will have a method called on it when either the HTML element or Model changes. When the HTML element changes the Controller is notified, when the Model changes the View is rendered. This is the circle of MVC life.
Looking at src/js/AppController.js you will notice little more than what is required to subclass the Maria Controller class and some may feel the controller is therefore unnecessary. Resist the urge to remove a Controller that is as simple as this because doing so removes a clear indication of where certain functionality belongs and tempts the lazy or uninformed developer to directly bind aspects of the Model to the View and that isn’t MVC. Not following MVC quickly couples and bridles the parts of an application with responsibilities that don’t belong, starting you on a road to despair.
* Maybe Maria is opinionated and I just happen to agree with the opinion.
A design decision I have touched on already is embodied in AppModel, a Class that represents the data of the Client Application as a whole. The AppModel contains a reference to the set of Todos that are being manipulated. This set of Todos could be seen as the core model but a higher level abstraction helps here as the Model in MVC is typically manipulated through an assortment of views and sub-views created on the same Model. When this model is not the ‘parent’ you eventually end up with a Controller needing to track a parent and a child model, or a model that keeps a parent relationship unnecessarily. Typically the top level model will expose the behavior that manipulates sub-models making it ideal as a parent and ‘documenting’ the behavior of the Application, ie: addTodo().
My Todo Application is minimal in that it only captures items to be done and doesn’t currently allow you to mark an item as done. This is enough to illustrate the points I wanted to make in code and to use the Maria framework in a ‘real’ way. The code for the Todo application is available in here (link) and what follows is a walk through of the code from the entry point through each class as it participates in the MVC pattern to enable the collection of Todos. It is hoped that following the code from the entry point down with a description of each line is the most appropriate way of describing what is going on and how the parts of the application come to interact with each other.
Running The Application
To run the Todo Application you need to make the todo-maria.js file by issuing the “make” command in the root folder. This results in a todo-maria.js file being created in a ‘build’ sub-folder. When this is done you can open the index.html file with your browser and you should see the Todo Application. Should you run into problems please check the README file in the distribution.
This file is packaged into the todo-maria.js file by the ‘Makefile’ where the initializer code is run as the full todo-maria.js is interpreted. The code in this initializer
creates the root triad of AppModel, AppView and AppController with their dependencies when the browser window has loaded. Since the AppView requires an existing HTML document there is no need to create the view before the document is ready. The initializer fragment of code uses the Maria event listener API to register our function to be called when the window loads. The initializer tests if the global ‘window’ object is defined before continuing because in my test scenarios the ‘window’ object is not present. There is probably a nicer approach than this but I have not looked into it.
The AppView is the main view of the application but it doesn’t attach itself to any HTML element, instead it binds other views to HTML elements using the DocumentToViewBinder class. The AppView requests the DocumentToViewBinder to bind the HTML elements ‘entry’ and ‘list’ to the associated View and Controllers, with each sharing the same AppModel. You can think of the AppView as an Abstract View. You should note that the AppView is an observer of the AppModel and when the AppModel is changed the update() method is called on the AppView. This update() method doesn’t perform any action as the sub-views themselves perform these actions as they are also observers of the AppModel.
The AppModel class represents the data model for the whole application and is composed of a set to contain the Todo’s. The ‘Set’ class is provided by Maria and it has some neat Collection methods making working with a collection more natural than when working with an array. When additional data is required for the application it would be added to the AppModel. When a Controller needs to manipulate data it calls a method on the AppModel to ask it to do the work. At the time of writing this the AppModel exposes the Todo set with a method and this is wrong, wrong, wrong. The AppModel should instead allow a function to be applied to each element of the Todo Set, keeping the implementation private and using the Tell Don’t Ask principle. I’ll be changing this as a priority.
The AppController coordinates requests from the View to update the model. Looking at the code right now you can see that the AppController does nothing more than subclass the Maria Controller and remember the associated Model. Not all the controllers in the Todo Application are this trivial but resist the urge to remove the ones that are. Controllers that do nothing provide two things, a place to add functionality when needed and a clear indication that the associated View doesn’t make requests to modify the associated model. A Controller is the ideal place to expose the behaviour needed to update the model, typically taking the notification from the View and then packaging that up in a new object before calling a method on the model. This flow is shown in the InputController which I’ll detail below.
The DocumentToViewBinder is a Factory for finding a HTML element from the DOM and creating an associated View and Controller. It is the element map passed to the DocumentToViewBinder that defines which Views are bound to which HTML elements, and these views will typically register to be notified when certain events are fired by the elements they are bound to. See InputView as an example of this. Technically the DocumentToViewBinder should be constructed with another class that handles the finding of the HTML element to be bind, as mixing this responsibility into the DocumentToViewBinder class violates the Single Responsibility Principle. I’ll add this as a TODO for me.
The namespace.js file ensures that a todo object exists into which all the todo application components can be bound. Using a namespace reduces the probability that class names and function names will collide with already defined classes and functions.
The InputView class listens for events on the associated HTML text input field, and for notifications of the AppModel being updated. Maria will call the ‘initialize’ method on a View to give it the opportunity to register for the events on the HTML element that is it interested in. In this case the InputView is interested in the ‘change’ event on the HTML element and when that event occurs it wants the ‘inputChanged’ method called on the InputView. When the ‘inputChanged’ method is called the View extracts the contents of the HTML text input field and tells the associated InputController that the view changed passing the input text along. The InputView also provides an ‘update’ method which is called (by Maria) when the AppModel has been updated. In this case the InputView does nothing with this notification and I could remove this method as Maria provides a no-operation implementation, however I left it defined as an explicit extension point for people less familiar with Maria or myself at a future date.
The InputController provides a method to be called when input has been given via the InputView. When input is received it is packaged up into a simple object and the method for adding todo entries is called on the AppModel. The View is also asked to clear its input ready for the next input.
The ListView class is slightly different to the InputView in that it doesn’t listen for event on any HTML elements, instead it listens only for notifications that the
AppModel has changed. When the AppModel has changed the ListView clears the associated HTML unordered list and adds each of the items in the Todo set as items in the unordered list.
I used the BusterJS framework for testing for two main reasons, firstly it provides behavior style testing that I am used to from my use of Ruby and RSpec, and secondly because it was being used by Maria for it’s testing. I found BusterJS to be fast and effective and its ability to automatically test on different browsers is very attractive.
At times I found that my tests would fail but running them two more times and they would pass. I have not looked into this and would not use BusterJS in a Continuous Integration environment until I could find out why this is happening.
You should note that some of the tests are not up to my usual standard as they don’t check all arguments passed to methods when they check a method call. This means it is possible to forget a parameter and tests continue to pass. I let the tests get this way as I was new to BusterJS and the underlying SinonJS library. I’ll be tightening up the tests.
Writing my Todo Application as a browser based client was a worth while experience and it helped me get a grasp on what it means to develop this style of application. Some important things I learned were:
- Follow MVC and always have a Controller even if it does nothing. Don’t communicate directly from View to Model, or Model to View. The MVC circle of life is all about being called back in response to events on objects being observed.
- Create a top level Model composed of other models to conceptualize the Client as a whole. I went so far as to have a top level Controller and View which helped.
- Use Maria or another MVC framework that is light and unobtrusive. I recommend you start with Maria even if you plan on moving onto more opinionated frameworks as it will provide a grounded understanding of MVC principles.
Your feedback is welcomed.