Skip to main content

Client side MVC: Using Publisher - Subscriber event model to build decoupled components

I've been really busy these days writing client side MVC code. A few months back I explored using Require and Backbone to build the frontend MVC structure. Since then I've been working on improving the structure of my client side code. One approach that I've used is to decouple the components. When I say decouple, the components that I build should not directly alter or affect the behavior of some other component in the page. By decoupling the components, we can ensure that these set of components can be reused and tested in isolation.

Usually it gets way to easy to get digressed while developing a website without following any code structure. You would end up writing tons of callback functions and it would become hard to maintain that code. Applying MVC structure to your client side code would ensure that the concerns are separated and if there is a bug in one of the components it would be easy to find and fix it. As mentioned in my earlier post, Backbone.js provides set of components that can be extended to build Models, Collections and Views. A Model or Collection can be considered as a Data Access Layer, which communicates with your server and maintains the data structure. The responsibility of the View would be to listen to the changes in the Model\Collection and render the page accordingly. On a page you would find components like the header, navbar, footer and various other pieces that make up the body of the page. Each of these components on the page can be considered as widgets on the page. This is how each widget would be structured:

widgets
    - widget_name
         - build
         - collections
         - models
         - templates
         - views

The widgets directory would contain definitions for various widgets. Each of these widgets contain the following directories:

  • Models - a set of Backbone models, here the data structure in each of the model definition is of a simple object containing few keys.
  • Collections - a set of Backbone collections. Collections would contain an array of objects.
  • Views - a set of Backbone views. These views would have a dependency on the collections, models and templates. It's sole responsibility is to listen to the changes in the Collections or Models and render the page accordingly.
  • Templates - a set client side templates. I have used Underscore.js templates since Backbone already has a dependency on Underscore.
  • Build - the build directory would contain the optimized code (created using Require.js optimizer - r.js).
Once you build these widgets, there would be many instances where the state of one widget would alter the state of another widget. For example, consider that you are building a 'Cart' and a 'Wishlist' widget. In a scenario where you want move an item from Cart to Wishlist or vice versa, these entities should have each other's reference to complete this action. Instead of these widgets storing a reference of another widget on the page, a central entity - 'Controller' would assist in sending messages between these widgets. The Controller would provide a Publisher - Subscriber event model, using which these widgets would publish and subscribe to events:


The Controller uses a 'events' object to store the list of events and its subscribers and it provides methods - 'subscribe' and 'publish' as an interface to other widgets:


The above code snippet defines a module which contains definitions for 'subscribe' and 'publish' methods. The widgets declare a dependency on the 'Controller' and would publish events by calling the publish method:

Controller.publish('movetowishlist', cartObject);

and subscribe to events using

Controller.subscribe(view, 'movetowishlist');

The 'subscribe' method first checks whether the specified 'eventname' already exist. If not, then it creates an entry in the events object. The value of this object is an array of subscribers. Here you also check whether the event was already published. If it has been published then the subscriber would get the published data as soon as it subscribes for the event. This is required when you have multiple widgets on the same page and these widgets have subscribed to the 'loadComplete' event of other widgets on the page. These widgets are loaded asynchronously and when the second widget loads it would not receive the 'loadComplete' event of the first widget. Therefore, we store the data published by widgets and provide it other widgets when they subscribe to it.

The subscriber's view object needs to implement a custom event (in this case 'movetowishlist'), so that when the event is published, the Controller would invoke this event on the subscriber's object. The publish method checks if the specified event already exists in the 'events' object and then iterates over the list of subscribers and publishes the event on the subscribers object. It then stores the published data in the 'publishedEvents' object, this is required for 'loadComplete' events.

The publisher - subscriber event model helps in decoupling the widgets on the page, allowing you to reuse them wherever required. The Controller here acts as a mediator between different widgets on the page and provides a communication channel for these widgets to interact with each other.

Comments

Popular posts from this blog

Adding beforeRender and afterRender functions to a Backbone View

I was working on a Backbone application that updated the DOM when a response was received from the server. In a Backbone View, the initialize method would perform some operations and then call the render method to update the view. This worked fine, however there was scenario where in I wanted to perform some tasks before and after rendering the view. This can be considered as firing an event before and after the function had completed its execution. I found a very simple way to do this with Underscore's wrap method.

De-obfuscating javascript code in Chrome Developer Tools

I had blogged about JavaScript debugging with Chrome Developer Tools  some time back, wherein I have explained how these developer tools can help in debugging javascript code. Today Google Chrome 12 was released and my Chrome browser was updated to this version. As with every release, there have been some improvements made on performance, usability etc,. One feature that stood out for me is the ability to De-obfuscate the javascript code. What is Minification? Minification is the process of removing unnecessary characters such as white spaces, comments, new lines from the source code. These otherwise would be added to make the code more readable. Minifying the source code helps in reducing the file size and thereby reducing the time taken to download the file. This is the reason why most of the popular javascript libraries such as jQuery are minified. A minified jQuery file is of 31 KB in size where as an uncompressed one is about 229 KB. Unfortunately, debugging minified javascript f

On GraphQL and building an application using React Apollo

When I visualize building an application, I would think of using React and Redux on the front-end which talks to a set of RESTful services built with Node and Hapi (or Express). However, over a period of time, I've realized that this approach does not scale well when you add new features to the front-end. For example, consider a page that displays user information along with courses that a user has enrolled in. At a later point, you decide to add a section that displays popular book titles that one can view and purchase. If every entity is considered as a microservice then to get data from three different microservices would require three http  requests to be sent by the front-end app. The performance of the app would degrade with the increase in the number of http requests. I read about GraphQL and knew that it is an ideal way of building an app and I need not look forward to anything else. The GraphQL layer can be viewed as a facade which sits on top of your RESTful services o