Wednesday, 8 May 2013

Simplest form of JavaScript MVC


A programmer in any platform will be familiar with the design patterns which are important to write maintainable and reusable code. A pattern is a reusable solution that can be applied to commonly occurring problems in software design. One of such popular pattern is MVC which separates the representation of information from user's interaction with it.

In the last few years, a series of JavaScript MVC frameworks have been developed, such as backbone.js, ember.js, AngularJS, Sencha, Kendo UI, and more. While they all have their unique advantages, each one of them follows some form of MVC pattern with the goal of encouraging developers to write more structured JavaScript code. In this blog I will help you to create an MVC pattern without the help of any frameworks.

Note: Readers require a minimum understanding of JavaScript prototype


Lets have a quick look at the basics of MVC pattern. As you know it is Model-View-Controller pattern.

Model : Holds data.  Handles change in state.
View: Generates output representation based on the data from Model.
Controller: Controls View to change it's representation. It can also send commands to Model to change its state.

From this diagram you can understand the behavior of this pattern.

Now lets consider a simple application. It allows the user to list/add/edit/delete records to the application.
When MVC pattern applies over it, it decouples the features as follows.

Model : It has an attribute called records. Methods for list/add/edit/delete are also present.
View :  Lets assume that screen has 4 buttons (list, add, edit, delete) and a record is represented as an input field. Methods for representing data to the user is also present. It has a reference to Model too.
Controller : It has references to Model and View. Methods for integrating Model and View are also present.

Model

constructor

 function Model(records){  
     this._records = records;  
     this.recordAddedSignal = new Signal(this);   
     this.recordDeletedSignal = new Signal(this);  
     this.recordEditedSignal = new Signal(this);  
 }  

prototype methods
 Model.prototype = {  
      listRecords : function(){  
        return this._records;  
      },  
      addRecord : function(record){  
        this._records.push(record);  
        this.recordAddedSignal.signal({data : record});  
      },  
      editRecord : function(record){  
        //find record from collection and update it  
        this.recordEditedSignal.signal({data : record});  
      },  
      deleteRecord : function(index){  
        //remove the record from collection, for example  
         this._records.splice(index,1);  
         this.recordDeletedSignal.signal({data : index});  
      }  
 };  



(We will see what a Signal is, shortly)

View

constructor
 function View(model, uicomponents){  
      this._model = model;  
      this._uicomponents = uicomponents;  
      this.listButtonClicked = new Signal(this);  
      this.addButtonClicked = new Signal(this);  
      this.editButtonClicked = new Signal(this);  
      this.deleteButtonClicked = new Signal(this);  
      var _self = this;  
      this._model.recordAddedSignal.listen(function(){  
         _self.refreshUicomponent();  
      });  
      this._model.recordDeletedSignal.listen(function(){  
         _self.refreshUicomponent();  
      });  
      this._model.recordEditedSignal.listen(function(){  
         _self.refreshUicomponent();  
      });  
      this._uicomponents.buttons.list.click(function(){  
         _self.listButtonClicked.signal();   
      });  
      this._uicomponents.buttons.add.click(function(){  
         _self.addButtonClicked.signal();   
      });  
      this._uicomponents.buttons.edit.click(function(){  
         _self.editButtonClicked.signal();   
      });  
      this._uicomponents.buttons.delete.click(function(){  
        _self.deleteButtonClicked.signal();   
     });  
 }  

prototype methods
 View.prototype = {  
          refreshUicomponent : function(){  
               var records=this._model.listRecords();  
               for(var record in records){  
                  if(records.hasOwnProperty(record)){  
                    //Populate screen with records on desired uicomponent  
                  }  
               }  
           }
 };  

Controller

constructor
 function Controller(model, view){  
            this._model = model;  
            this._view = view;  
            var _self = this;  
            this._view.listButtonClicked.listen(function(){  
                _self.listRecords();  
            });  
            this._view.addButtonClicked.listen(function(){  
                //fetch record data from uicomponent and pass to the method  
               _self.addRecord(record);  
             });  
             this._view.editButtonClicked.listen(function(){  
                //fetch record data from uicomponent and pass to the method  
                _self.editRecord(record);  
             });  
             this._view.deleteButtonClicked.listen(function(event){  
                //fetch index from event  
                _self.deleteRecord(index);  
             });   
 }  


prototype methods
 Controller.prototype = {  
             listRecords : function(){  
                this._model.listRecords();  
             },  
             addRecord : function(record){  
                this._model.addRecord(record);  
             },  
             editRecord : function(record){  
                this._model.editRecord(record);  
             },  
             deleteRecord : function(index){  
                this._model.deleteRecord(index);  
             }  
 };  

Signal

A signal is an event. It has a listener (attached using listen method) and an notifier (signal method).

constructor
 function Signal(transmitter) {  
     this._transmitter = transmitter;  
     this._listener;  
 }  


prototype methods
   Signal.prototype = {  
     listen : function (listener) {  
       this._listener = listener;  
     },  
     signal : function (args) {  
         this._listener(this._transmitter, args);  
         //You can have many listeners and invoke each of them  
     }  
   };  

That's all. You have created an MVC pattern. Now initialize each of them.

Add following code to your dom ready method.
 var sample_records = ['backbone.js', 'ember.js', 'AngularJS', 'Sencha', 'Kendo UI'];  
 var ui_components = {"buttons" : {   
                            "list" : $("#list"),  
                            "add" : $("#add"),  
                            "edit" : $("#edit"),  
                           "delete" : $("#delete")  
                    }};  
 var model = new Model(sample_records),  
     view = new View(model, ui_components),  
  controller = new Controller(model, view);  


Note: Please make sure you have proper ui components and refreshUicomponent method populates data in the required destination. I have not mentioned how to create a record object. That task is given to you. Simply create an object with some attributes that you want to display.

Hope you liked this session. Have a nice day!

No comments:

Post a Comment