Motivation
"When working on a web application that involves a lot of JavaScript, one of the first things you learn is to stop tying your data to the DOM. It's all too easy to create JavaScript applications that end up as tangled piles of jQuery selectors and callbacks, all trying frantically to keep data in sync between the HTML UI, your JavaScript logic, and the database on your server. For rich client-side applications, a more structured approach is often helpful."
When designing User Interface is very easy to distribute behavior of application among objects. Such distribution can result in a structure with many connections between objects, in the worst case, every object ends up knowing about every other. Lots of interconnections make it less likely that an object can work without the support of others. Moreover it become harder and harder to change the system behavior in any significant way, since behavior is distributed among many objects. As the result, you may be forced to change many places in many files to customize system's behavior.
Ofter there are many dependencies between widgets on the website. For example, when a certain text area is empty, a button gets disabled. Selecting an option from a combo box might change contents of data in table. Typing a text into search field might change content of a list box. Different websites will have different dependencies between widgets. Customizing them will be tedious, since many classes are involved.
What is more, using AJAX request on the website to avoid refreshing a web page, hides changes from the user. Updating a view after such a request does not imply change in site's URL. Very often, after implementation of AJAX on the website, which makes the whole web applications looking more snappy and responsive, users asks for some way to make a bookmark to some specific state of application they can send and share with other users or save it for later. If this state is distributed among many UI controls, each of which cares only about its own internal state and data, implementing such a feature become very hard and tedious task.
Another consequence of using AJAX is that back button and browsing history becomes useless. User expects, every activity changing state of application can be undone by clicking back button in browser. Since AJAX calls makes no changes in URL, browser in not recording changes of application state.
To avoid this problems we have to encapsulate state of the application along with data in separate object. Also every collective behavior of UI controls should be directed to single object that will handle all the changes. Last but not least, all the visual widgets and methods that change visual appearance of the website should be data agnostic and kept in separate class.
This is where Model View Controller comes in handy. MVC is a software architecture, currently considered an architectural pattern used in software engineering. The pattern isolates the application logic from the user interface, permitting independent development, testing and maintenance of each.
Structure
Following image illustates the structure of Model View Controller:
The solid line on the image represents a direct association (on object have a handle to another), the dashed an indirect association (via events, or signals).
As you can conclude from the image:
- Model don't have ANY handles to View and Controller
- Controller is aware about BOTH - Model and View
- View don't know ANYTHING about Controller structure but it KNOWS Model interface.
The control flow in MVC architecture is generally as follows:
- The user interacts with the User Interface in some way (for example, by pressing a button).
- The view emits some event
- The controller handles the input event from the user interface, often via a registered handler or callback, and converts the event into an appropriate action, interacting with the model.
- The controller notifies the model of the user action, possibly resulting in a change in the model's state.
- After being changed, the model reacts, emitting some notification.
- The controller handles the change event and notifies the view.
- A view queries the model in order to generate an appropriate user interface. The view gets its own data from the model. In some implementations, the controller may issue a general instruction to the view to render itself without a view interacting with the model.
- The user interface waits for further user interactions, which restarts the control flow cycle.
Model
The Model manages data of the application, responds to requests for information about its state (usually from the View), and responds to instructions to change state (usually from the Controller). In event-driven systems, the Model notifies observers when the information changes so that they can react.
Models are the heart of any JavaScript application, containing the interactive data as well as a large part of the logic surrounding it: conversions, validations, computed properties, and access control.
In our concrete implementation the Model is a single class containing methods (getters, and setters) to read and modify data. Data is stored in a dictionary. Every change in the dictionary fires appropriate event. Model class knows also default values of data attributes and can restore them to the original state.
View
The View render the Model into a form suitable for interaction, typically a User Interface elements. Multiple Views can exist for a single model for different purposes. A viewport typically has a one to one correspondence with a display surface and knows how to render to it.
In our implementation View is a single class containing methods modifying Document Object Model. To make this methods more effective it uses some ready made UI components and libraries. Some of the methods requires knowledge about the state of the Model, that is why the View has a reference to the Model. As a rule View access Model only for reading.
Controller
The Controller receives input and initiates a response by making calls on Model objects. The Controller accepts input and instructs the Model and View to perform actions based on that input.
In our implementation the Controller is a single class that has references to both - Model and View. It listens for some specific events. Once the event is observed, the Controller interacts with Model or View according to some special functions that are defined in the Controller that are bind to this specific events.
Implementation
We try to present implementation useful for web developer working with dashboard framework. To keep things consistent it's strongly recommended to follow this example implementation as much as possible. The example was taken from working application and was prepared as a proof of concept. It uses well known, mature, robust and open source libraries. Using them will save developer's time.
Libraries and dependencies
Implementing a Model class we based it on
backbone.js
Model class.
Backbone.js
is a lightweight library that supplies structure to JavaScript-heavy applications. Under the hood it uses
underscore.js
so this is a next dependency of the example.
The View is using
jQuery
for DOM manipulation and
jQueryUI
for rendering custom controls. Also uses several libraries, mostly dependent on jQuery that can be treated as jQuery plugins.
The Controller reacts to every UI interaction by changing the URL hash. To do this
bbq history
is used.
Both, Controller and View are using jQuery to create and handle costumized events. Model signals changes using Backbone.js events. Although two event JavaScript implementations are used, they have exactly the same interface.
Events
Events solves the following problem: How can a class instance notify some other objects about it's internal change, not knowing what these objects are and not having any references to them? The answer is: After a change the object emits an event that is broadcasted. This event can be received only by objects that subscribed for this particular type of event.
This way senders do not program the messages to be sent directly to specific receivers. Rather, published messages are characterized into classes, without knowledge of what, if any, subscribers there may be. Subscribers express interest in one or more classes, and only receive messages that are of interest, without knowledge of what, if any, publishers there are.
Event-driven programming is widely used in graphical user interfaces because it enables rapid development of fexible interfaces composed of many independent widgets that can be easily bind toghether in many ways. This is important part of MVC architecture. In our example implementation all the dashed lines from the MVC diagram above are implemented using events.
Collaboration Mechanism
Knowing about MVC, its basic components and a concept of events we can consider now how different objects interact with each other in the concrete example. To depict that, UML collaboration diagaram should be used but to keep things simple we will used different approach.
By circles we will mark objects, dashed lines are events (jQuery custom events or Backbone.js events), solid lines are method calls.
First we will try to follow simple example: changing a state of application to fullscreen mode:
First the user clicks a "fullscreen" button. The button is a part of the View class. It was created by this class and the View has attached
click
callback that is fired when the button is clicked:
$('.fullscreenButton').button().click(function(){$(document).trigger('toggleFullscreen'); return false;});
There are several things to note here:
-
$('.fullscreenButton')
is jQuery selector that selects a DOM object with class atribute equal to "fullscreenButton"
-
button()
is jQuery UI method that decorates link in unobtrusive manner changing it to nice looking button.
-
click()
contains a function that will be executed when a button (decorated link) will be clicked
-
trigger
is a standard function of events interface. It just emits specified event, in our case the toggleFullscreen
event.
- because our button is really an anchor (link) we have to return false to prevent propagation of event which would cause standard anchor behavior which is pointing a browser to the address specified in
href
attribute of an anchor. Since we didn't specify href attribute for this dummy anchor default browser behavior would be erasing hash which will cause hashchange
event and complete disaster of our chain of execution.
After we triggered an event it is received by the controller which subscribed for it:
$(document).bind( 'toggleFullscreen', function( event ) {
var full = (that.model.get("fullscrenn") == "true") ? "false" : "true";
jQuery.bbq.pushState({"fullscrenn" : full}, 0);
});
This code says: if I (the Controller) observe toggleHighlight event I will take a
fullscreen
attribute value from model dictionary, toggle it, and push the toggled value to the URL.
In general every change of the View observed by controller causes a change in URL.
So after the URL hash is changed it will automatically fire the
hashchange
event. Lets take a look how the Controller handles this event:
$(window).bind( 'hashchange', function( event ) {
that.model.set(_.extend({}, that.model.defaults, jQuery.bbq.getState()));
});
Handling
hashchange
event is just one line of code but is pretty complicated so lets try to understand general idea:
Take default model data, take data from hash that was recently changed, put it together as one object that has all model attributes - these not mentioned in hash remain unchanged, the rest has new values taken from the hash. Now change the model internal state supplying this new data.
When
set
method is called on Model object it tries to figure out which attributes has changed. For each changed attribute if fires
change:attributeName
event.
In our case
change:fullsceen
attribute was fired. Now Controller listens for that event:
that.model.bind('change:fullscreen', function(model, full) {full == "true"? that.view.hideView(): that.view.restoreView(); });
And this is how finally the View was updated.
So why it is done this way which sounds a bit complicated...
Consider now the user pressed "back" button in the browser. There was no interaction with GUI, but pressing this button will change a hash. This will cause "hashchange" event fire and we are jumping in the middle of our chain:
But not all interactions with GUI causes changes in the Model. Model sores the data locally. This is a web application so we want to get data from server pretty often. So now lets follow a chain of events when we want to get remote data from server. Imagine that we have a button after pressing which we want to get information about the python version running on our server:
The beginning is pretty similar: we press the button with
click
callback attached that send customized callback received by Controller. But now we don't want to ineract with model so we don't push a state to the URL, instead Controller is calling ServerConnector. ServerConnector is a small class that executes customized AJAX requests to server and handles the answer, parses it and return formatted data. After answer from the server is processed, ServerConnector fires an event:
_versionCallback : function(json){ $(document).trigger('versionReady', [json.python.ver]); }
The Controller handles this event, updating a view:
$(document).bind('versionReady', function(event, version){ that.view.displayVersion(version);});
It's easy to find some similarities between both chains of events - when getting data from local model and remotely:
You can treat marked classes like a sort of
Data Access Object
. Since we have our application implementing two paradigms: Model-View-Controller and Client-Server the duality becomes obvious.
Summary
You may have many questions after reading this. There was some code provided in this article but for sure there are lots of gaps to be filled. The bast way to understand is to examine actual working code. This article was based on concrete example you can find in dashboard SVN. It's Site Status Board implementation of Expanded Table.
Why Java Script?
Robert Cailliau, computer scientist working at CERN who, together with Sir Tim Berners-Lee, developed the World Wide Web said:
"Anyway I know only one programming language worse than C and that is Javascript. [...] I was convinced that we needed to build-in a programming language, but the developers, Tim first, were very much opposed. It had to remain completely declarative. Maybe, but the net result is that the programming-vacuum filled itself with the most horrible kluge in the history of computing: Javascript."
Although dashboard framework is developed at CERN, the author of this article hopes that after studying presented example, the reader will become less reluctant in using Java Script as enabling technology for many solutions implemented in web applications developed at CERN.
Java Script proved to be great tool for rapid design of flexible User Interface, not only for dashboard team but also for many other companies. To give just a few examples: Nokia Qt team introduced recently
Qt Quick
- Qt UI creation kit, which includes as a critical part QML - CSS & JavaScript like language to speed up process of GUI development. Canonical is using Flash for rapid prototype development of user interface. Flash uses Action Script that is based on ECMA script specification, that means both Java Script and Action Script have common roots.
--
MmNowotka - 21-Sep-2011