Building JavaScript Apps using Modular and MVC Patterns

In this post, I would like to open your eyes to a new way of developing JavaScript applications. We will be building modular JavaScript code on an MVC architecture while also handling dependencies. With these techniques, you can join a new era of web development and stop coding like it's 1999!

Prince - 1999

Asynchronous Module Definition (AMD)

First, let us get some terminology out the way. The Asynchronous Module Definition, or AMD, is a mechanism for defining modules such that the module and its dependencies can be asynchronously loaded. In other words, JavaScript files and units of code can be loaded via AJAX. Think of the implication of this... it allows you to organize your code into self-contained files and load dependencies only when needed. This is where RequireJS comes in.

RequireJS is a JavaScript file and module loader. Using a modular script loader like RequireJS will improve the speed, quality and scalability of JavaScript code. Take a look at this small example:


~/scripts/ui/mygrid.js:

define([
     'lib/jquery.min',
     'lib/kendo.web.min',
     'helper/utils'
], function ($, kendo, utils) {
 
   var init = function () {
      $('.grid').kendoGrid();
      utils.notify('Kendo-fied grid!');
   }
 
   return {
      init: init
   };
}



Behold!
This is a JavaScript module. The function "define" is where the magic happens and is part of the AMD specification. The logic of "define" is declared in the RequireJS library or any AMD library you use. The first parameter is an array of dependencies. The array items represent the paths of the JavaScript files without the .js extension at the end. These JavaScript files can be libraries or other modules you define. This newly defined module can be used by other modules like this:


~/scripts/ui/mypage.js:

define([
  'ui/mygrid'
], function (mygrid) {
   mygrid.init();
}

Do you see what is happening here? The dependencies to Kendo-fy the grid is abstracted in the mygrid.js module. The module mypage.js does not care or know how the grid is being created or what dependencies it needs. All it does is load the mygrid.js module and call its public properties or functions. Each module is self-contained and declares its own dependencies. Who would have thought the handling JavaScript dependencies would also solve modular programming as well! Once this clicks in your mind, the way you write JavaScript will change forever!!

Model-View-Controller (MVC)

Now let us talk about delicious MVC. We will need a framework for this. I decided to go with the new kid on the block, CanJS, over the famous Backbone. I chose CanJS for the following reasons: it supports live-binding, seems more elegant, and is being built into the even more famous framework JavaScriptMVC (soon to be rebranded to DoneJS).

As with any MVC architecture, the folder structure is important and will be the foundation of our MVC pattern. This is how I decided to organize the structure:


Yes, that's Sublime Text 2 :)

The starting point to our site is of course index.html. The starting point to our JavaScript code is in the page head in this line:

~/index.html:

<script src="scripts/libs/require.js" data-main="scripts/main"></script>

This is RequireJS saying that my initial code is in ~/scripts/main.js. The module main.js defines some shortcuts to libraries and also initializes the router.

~/scripts/main.js:

//INITIALIZE APP
require([
  'router' //THIS SAYS LOCATION IS RELATIVE AND POINTS TO ~/scripts/router.js
], function () {
  //ROUTER DEPENDENCY INITIATED AND LISTENING
});


CanJS is used to create the router as so:

~/scripts/router.js:

define([
  'jquery',
  'can',
  'controllers/Conferences'
], function ($, can, Conferences) {
 
     var Router = can.Control({
          //HOME PAGE
          'route': function () {
               Conferences.list();
          },
 
          'conferences route': function () {
               Conferences.list();
          },
 
          'conferences/:id route': function (data) {
               Conferences.detail(data);
          }
     });
 
     return new Router(document);
});

What a beautiful sight. Routes are cleanly mapped to controller and actions. The controller is just as nice:

~/scripts/controllers/Conferences.js:

define([
  'jquery',
  'can',
  'moment',
  'models/Conference'
], function ($, can, moment, Conference) {
 
     var Conferences = can.Control({
          //EVENTS
          '.list > li click': function (element, event) {
               //GET THE MODEL DATA
               var model = element.data('conference');
 
               //NAVIGATE TO DETAIL BY GOING TO ROUTE
               window.location.hash = '!conferences/' + model.Id;
          },
 
          //ACTIONS
          list: function (data) {
               //GET ALL ITEMS FROM STORAGE
               Conference.findAll({}, function(response) {
                    //BUILD VIEW MODEL
                    var models = {
                         conferences: response.Conferences
                    };
 
                    //PASS RESULTS TO VIEW
                    var view = can.view('views/conferences/List.ejs', models);
 
                    //RENDER IN SPECIFIED CONTENT AREA
                    $('#main').html(view);
               });
          },
 
          detail: function (data) {
               //GET ITEM FROM STORAGE
               Conference.findOne({ id: data.id }, function(response) {
                    //BUILD VIEW MODEL
                    var models = {
                         conference: response.ConferenceInfo,
                         meta: response.Conference
                    };
 
                    //PASS RESULTS TO VIEW
                    var view = can.view('views/conferences/Detail.ejs', models);
 
                    //RENDER IN SPECIFIED CONTENT AREA
                    $('#main').html(view);
               });
          }
     });
 
     return new Conferences(document);
});

Notice that DOM events are declared here in the controller. No more polluting your code with jQuery binding. Also the model is brought in as a dependency. The REST services are called in the underlying model, then the result is passed into the view.


~/scripts/models/Conference.js:

define([
  'can'
], function (can) {
     var Conference = can.Model({
          findAll: 'GET /Services/conferences'),
          findOne: 'GET /Services/conferences/{id}'),
          create:  'POST /Services/conferences'),
          update:  'PUT /Services/conferences/{id}'),
          destroy: 'DELETE /Services/conferences/{id}')
     }, {});
 
     return Conference;
});

The view is a template that allows binding of data. This is how the view looks like:

~/scripts/views/conferences/List.ejs:

<h3>Conference List</h3>
<ul class="list">
     <% $.each(conferences, function (i, conference) { %>
         <li <%= (el) -> el.data('conference', conference) %>>
              <div class="thumb"><img src="<%= require.toUrl('../Content/images/logo.' + conference.BaseName + '.png') %>" /></div>
              <div class="title"><%= conference.Name %></div>
              <div class="date"><%= moment(conference.StartDate).format('MMM DD, YYYY') %> - <%= moment(conference.EndDate).format('MMM DD, YYYY') %></div>
              <div class="location"><%= conference.LocationFriendly %></div>
              <div class="description"><%= conference.Description %></div>
         </li>
     <% }) %>
</ul>

As you can see, the bindings are happening between the <% CODE %> magic tags. I am also using MomentJS to format the date. This dependency is declared in the controller which is why I can use it in the template. Otherwise, the view would not know what "moment" is. Another things worth mentioning in the view is that I am storing the model data in the "li" element with this line:

<li <%= (el) -> el.data('conference', conference) %>>

It is using the EMAScript 5 arrow function which CanJS supports. Essentially, this is storing the model data into the data storage. Most JavaScript libraries support reading this such as jQuery: $(selector).data(NAME) and will return the data stored at this element.

Conclusion

There you have it. Have you seen the light? There are many realizations here that will make coding JavaScript applications elegant, scalable and manageable. For the full source code of the sample, please visit our GitHub to see these concepts in action. As an added bonus, you will see how to use Kendo UI in modular programming to create rich-applications while having a solid client-side architecture.


HAPPY CODING!!

comments powered by Disqus