Monday, July 7, 2014

The base - Model

As client side codes began to pile up, it became need of the hour to structure and separate the views from their logics and data. Enter BackboneJS.

The true leverage of BackboneJS occur in SPAs or Single Page Applications, as we would see later on. 

BackboneJS is both a library and a framework but it is more of a library as the main purpose is to make the life of a developer easier. The framework like straits come into notice when understanding how the backbone events work.

Understanding BackboneJS from an OOP point of view

The general process in coding a BackboneJS app is to extend the classes it provides in its library. In doing so, we usually override some of the existing class properties and methods and include some of our own as needed.

There are 3 major classes in BackboneJS.

  • The Model Class
  • The View Class
  • The Router Class
There are ofcourse other classes but the above are of prime importance. We will cover the rest as this course blog unfolds.



Understanding the Model Class

The Model class holds the data in the application and the logics around it. BackboneJS provides the Backbone.Model class to be extended for use in our applications. 
For e.g.
var myModel = Backbone.Model.extend({     defaults: {       title: 'Default Title'     }  });


Existing Properties and Methods:

Lets take a look at some of the existing properties and methods of this class which would frequent our apps.

Property Name: defaults
Use: used to hold default values for attributes of the model class object. 
Example: see above

Method Name: get()
Use: convenient and OOP way of retrieving attribute values
Example: 
var myModel = Backbone.Model.extend({    defaults: {       title: 'Default Title',       author: 'Supi',       credits: 'Dimple'    }}); 
var model1 = new myModel({    title: 'Sailing away',    author: 'Chris DeBurgh'}); 
console.log(model1.get('title')); // logs 'Sailing away' 
console.log(model1.get('credits')); // logs default value 'Dimple'
Method Name: toJSON()
Use: read or clone all of a model's data attributes. Contrary to what it sounds, this method returns an object, instead of a JSON string.
Example(contd. from the above):
var model1Attributes = model1.toJSON(); 
console.log(model1Attributes); // logs "{title: 'Sailing away', author: 'Chris DeBurgh', credits: 'Dimple'}"

Important NOTE: JSON.stringify()

Brief: This method is usually used to stringify an object. Therefore when fed with an object, it returns the object as a series of strings. However when fed with an object that has the toJSON() method, this method would stringify the return value of the toJSON() method of that object.
Example: 
var obj1 = {title:'Single', girlfriend: false, status:'sucks'}; 
var obj2 = {title:'Couple', girlfriend: true, status:'rocks', toJSON: 
function(){ 
var me = this; 
return {title: me.title, girlfriend: me.girlfriend};   }}; 
console.log(JSON.stringify(obj1)); // logs '{title:'Single', girlfriend: false, status:'sucks'}' 
console.log(JSON.stringify(obj2)); // logs '{title:'Couple', girlfriend: true}'
Method Name: set()
Use: convenient, correct and OOP way of setting attribute values
Example(contd. from above)

   model1.set({title: 'Good Times', author: 'Supi', credits: 'Dimple'}); 


Property Name: attributes
Use: convenient way of retrieving or setting attribute values
Example(contd. from above)

   model1.attributes({title: 'Good life', author: 'Supi', credits: 'Dimple'}); 


   console.log(model1.attributes); 


Important NOTE: change events

Brief: Setting a model's properties via the set() method triggers a change event on the model as well as on the property. However setting the same via the attributes property bypasses these events. Conversely we can also silence the events for the set() method by passing in the {silence: true} property within the hash.
Example(contd. from above)

   model1.set({title: 'Good Times', author: 'Supi', credits: 'Dimple'}, {silence: true}); // doesn't trigger events

   model1.set({title: 'Bad Times'}, {silence: true}); // triggers events on model1 and on title

   model1.attributes({author: 'Nothing', credits: 'Dimple'}); // doesn't trigger events

Important NOTE: Do not access or set model states directly.
Example(contd. from above)

   model1.title = 'Good Times'; // wrong

Method Name: initialize()
Use: is called when the model class object is instantiated
Example

   var myModel = Backbone.Model.extend({
     defaults: {title: 'Default Title'},
     initialize: function(){
       console.log('model instantiated');
     }
});
   var modelObj = new myModel({title: 'Dimple'}); // logs 'model instantiated'


Property Name: validationError
Use: is set with the value returned from the validate() method
Example

   var person = new Backbone.Model({credits: 'Dimple'}); 
   person.validate = function(attr){
       if(!attr.name) return 'please provide the name';
   }

   person.set({credits: 'Supi'}, {validate: true}); // logs false
   console.log(person.validationError); // logs 'please provide a name'



Model Class Events :

Model Class Object emits change events whenever any of its attributes are changed. The BackboneJS events class are mixed into the Model class so that these events can be listened for and handled accordingly. You can either listen for changes on the Model object as a whole or on a particular property / properties.

The event listeners are usually attached in the initialize method of the Model Class.

Example:

   var myModel1 = Backbone.Model.extend({
     defaults: {title: 'First Model', author: 'Dimple'},
     initialize: function(){
       this.on('change', function(){
         console.log('model state changed');
         // rest of the event listener code
       });
     }
   });

   var myModel2 = Backbone.Model.extend({
     defaults: {title: 'Second Model', credits: 'Supi'},
     initialize: function(){
       this.on('change: title', function(){
         console.log('model title changed');
         // rest of the event listener code
       });
     }
   });

   var modelObj1 = new myModel1({title: 'Dimple'}); 
   modelObj1.set({author: 'Supi'}); // logs 'model state changed'

   var modelObj2 = new myModel2({title: 'Dimple'}); 
   modelObj2.set({credits: 'Dimple'}); // logs nothing
   modelObj2.set({title: 'Supi'}); // logs 'model title changed'

Important Note: The events can be bypassed as shown below, using attributes property or the {silent: true} hash for the set method.

       modelObj2.set({title: 'Kids'}, {silent: true}); // logs nothing
   modelObj2.attributes({title: 'Parents'}); // logs nothing



Validations in Model Class:

Validations in Model class is implemented via the validate() method, which by the way is never really explicitly called. 
The validate method is automatically called when the model is persisted via the save() method or when the set() method is called with the {validate:true} passed in as parameter.
The validate method is set on the Model object. Example:

   var person = new Backbone.Model({credits: 'Dimple'}); 
   person.validate = function(attr){
       if(!attr.name) return 'please provide the name';
   }

   person.set({credits: 'Supi'}, {validate: true}); // logs false

Important Notes: 

  1. The value returned from the validate() method is stored in the ' validationError ' property of the model.
  2. Upon an unsuccessful validation to set the value of a Model's state, the old or the original state is preserved.
  3. The attributes passed into the validate() method are shallow copied. In detail:
    1. The attributes to be set are obviously different from the internal attributes hash of the model.

      model.attributes != model.set({x : 3, y : 4})
    2. Therefore the attributes to be set are copied to be validated within the validate() method.

      model.validate = function(attr){
    3. Any objects present in attributes are not deep copied while all other types are a copy of their original counterparts. So this would mean that if this object somehow gets manipulated in the validate() method, the manipulation will get reflected on the model attributes.
      var myModel = Backbone.Model.extend({
          default: {
              num: 22,
              str: 'Dimple'
          }
      });

      var model3 = new myModel();
      model3.validate = function(attr){
            if(!attr.num) attr.num = 33;
            if(!attr.str) attr.str = 'bingo';
            if(!attr.obj.test) attr.obj.test = 'changed';
            if(!attr.obj) return 'i need this object dude!';
      }
      model3.set({obj: {test: 'test'}}, {validate : true});
      console.log(model.toJSON());
      // does not log num and str values within the validate function. consequently num remains at 22 and str at 'Dimple'
      // logs the obj.test value as in the case of objects only reference is copied
  4. Upon validation failure, an ' invalid ' event will be triggered culminating in setting the validationError property.
  5. save() method will not continue and the attributes of the model will not be modified on the server.

This pretty much concludes the Model class. We shall now progress onto the View class but that would be in the next blog. Until then, think, code, play, fuck and sleep.