728x90 AdSpace

Sunday, October 5, 2014

Understanding the basics of Backbone Models-1

Creating a simple backbone.js Model

To create a backbone model, we simply need to extend the backbone model class. Following code snippet shows how this can be done.
var Book = Backbone.Model.extend({
   
});
Furthermore, if we want to create a model that inherits from our model class then we just need to extend from our model class.
var ChildrensBook = Book.extend({
   
});
Instantiating a Model
Backbone models can simply be instantiated by using the new keyword.
var book = new Book();
Deleting a model
To delete a model, we just need to call the destroy function on the model.
book.destroy();
Sometimes deleting a model could take some time(depending on the size of the model). In such cases we can define a function that will be called when the model get successfully deleted.
book.destroy({
    success: function () {
        alert("The model has been destroyed successfully");
    }
});
Cloning a model
Often times we would want to have a deep copied object or clone of a model. To create clone of a backbone model we simply need to call the clone method.
function cloneModel() {
    var book = new Book();

    var book2 = book.clone();
}
How to specify the model attributes
Backbone models does not enforce defining the attributes in the model definition itself i.e. one can create a model and specify the attributes on the fly. Lets say we want to create 2 attributes in our Book model. lets try to create them on the fly.
var book = new Book({
    ID: 1,
    BookName: "Sample book"
});
Default values of model attributes
Now creating the attributes on the fly is supported by the backbone models and it is a very powerful feature. But this feature actually becomes proves to be a maintenance nightmare when it comes to working with large scale application. From a maintainable application perspective and also from a best practices perspective, I would like the possibility to define my models attributes in my model definition itself.
To accomplish this, the default function can be used. The default function is used to specify the default attributes of the model and their default values. Lets try to move the attributes in the model definition now.
var Book = Backbone.Model.extend({
    defaults: {
        ID: "",
        BookName: ""
    },    
});
This way just instantiating the model will be enough and the created models will have these attributes associated with them.

Setting and getting model attributes

Once we specify the model attributes, we need to be able to get and set their values too. To do this we can use the get and set functions on the model.
var book = new Book();

book.set("ID", 3);
book.set("BookName", "C# in a nutshell");

var bookId = book.get('ID');
var bookName = book.get('BookName');

How to check attribute existence

Since backbone allows us to add attributes on the fly, we need some way to identify whether a particular attribute exist in the model or not. To do this we can use the has function on model.
book.has('ID');     // true
book.has('author');  // false
Defining Functions in a Model
We can also define our functions in the model classes. Lets try to create a simple function in our model class.
var Book = Backbone.Model.extend({
    defaults: {
        ID: "",
        BookName: ""
    },

    showAlert: function () {
        alert('ID: ' + this.get('ID') + ', BookName: ' + this.get('BookName'));
    }
});
The initialize function
Whenever we create a model, the backbone will call its initialize function. We can override this function to provide custom behavior to it.
var Book = Backbone.Model.extend({
    defaults: {
        ID: "",
        BookName: ""
    },

    initialize: function(){
        console.log('Book has been intialized');
    },

    showAlert: function () {
        alert('ID: ' + this.get('ID') + ', BookName: ' + this.get('BookName'));
    }
});
Listening Model attribute changes
We can also use the events to listen to the model changes. This can be done by listening to the change event. backbone raises a change event whenever any model attribute is changed. For each attribute we can use hasChanged method to check if that attribute has been changed or not. Lets try to hook up the event handler to listen to the model change in our current model.
var Book = Backbone.Model.extend({
    defaults: {
        ID: "",
        BookName: ""
    },

    initialize: function(){
        console.log('Book has been intialized');

        // Lets hook up some event handers to listen to model change
        this.on('change',  function() {
            if(this.hasChanged('ID')){
                console.log('ID has been changed');
            }
            if(this.hasChanged('BookName')){
                console.log('BookName has been changed');
            }
        });
    },

    showAlert: function () {
        alert('ID: ' + this.get('ID') + ', BookName: ' + this.get('BookName'));
    }
});
If we have a lot of attributes and we are interested in listening to change for any specific attribute then perhaps we can specify that too in the change event binding. Lets try to listen to the BookName change only.
var Book = Backbone.Model.extend({
    defaults: {
        ID: "",
        BookName: ""
    },

    initialize: function () {
        console.log('Book has been intialized');

        // Lets hook up some event handers to listen to model change
        this.on('change:BookName', function () {
            console.log('Message from specific listener: BookName has been changed');
        });
    },

    showAlert: function () {
        alert('ID: ' + this.get('ID') + ', BookName: ' + this.get('BookName'));
    }
});

The Initialize function and the Constructor

Whenever we create a model, the backbone will call its initialize function. We can override this function to provide custom behavior to it.
var Book = Backbone.Model.extend({
    defaults: {
        ID: "",
        BookName: ""
    },
    initialize: function () {
        console.log('Book has been intialized');       
    },
});
So when we create this object the output will be:
Internally what happens is that whenever a backbone model is created, its constructor gets called. The constructor will call the initialize function. It is also possible to provide out own constructor and provide the custom behaviour.
var Book = Backbone.Model.extend({
    defaults: {
        ID: "",
        BookName: ""
    },
    initialize: function () {
        console.log('Book has been intialized');       
    },
    constructor: function (attributes, options) {
        console.log('Book\'s constructor had been called');
    },
});
The output when creating this model:
Now the problem with this constructor is that whenever the backbone model is getting created our constructor will be called. But the default constructor also does a lot of other activities at the time of object construction like calling the initialize function. So to make sure that our custom constructor works in unison with all that default behaviour we need to let the backbone framework know that we still wants that default behaviour. This can be done by callingBackbone.Model.apply(this, arguments); at the end of our custom constructor. This will make sure that our custom constructor will be called and then all the other activities that the default constructor is supposed to do are also done.
var Book = Backbone.Model.extend({
    defaults: {
        ID: "",
        BookName: ""
    },
    initialize: function () {
        console.log('Book has been intialized');       
    },
    constructor: function (attributes, options) {
        console.log('Book\'s constructor had been called');
        Backbone.Model.apply(this, arguments);
    },
});
Now the output will be:
Note: For most of practical purposes overriding the initialize function will suffice. There is seldom a need to override the constructor but in case one decide to override the constructor, this should be the way to do it.

Model identifiers - id, cid and idAttribute

Every model needs to be uniquely identified. For this backbone gives us the model identifiers. The first one to look at is the cid. The cid or the client id is the auto-generated by backbone so that every model can be uniquely identified on the client.
var book1 = new Book();
var book2 = new Book();
Backbone also provides an identifier id to uniquely identify the model entity. This is the id that will be used to identify the model when the model data is actually being synced with server i.e. getting persisted. the cid is more useful for debugging purpose but the id attribute will determine the uniqueness of the model when it comes to CRUD operations on the model. Its fairly straight forward to set and get the id property.
var book2 = new Book();
book2.id = 3;
console.log(book2.id);
Output for the above code will be: 3.
Now it gets a little confusing at this point. Since most of our models will have an attribute that will correspond to the primary key/unique identifier of the entity. Do we need to explicitly set the id value to that attribute. The answer if yes and no. We have to somehow indicate the backbone model what attribute should be used as id but we don't have to set the id explicitly. we can use the idAttribute to accomplish this.
var Book = Backbone.Model.extend({
    defaults: {
        ID: "",
        BookName: ""
    },
    idAttribute: "ID",
    initialize: function () {
        console.log('Book has been intialized');
    },
    constructor: function (attributes, options) {
        console.log('Book\'s constructor had been called');
        Backbone.Model.apply(this, arguments);
    },
});
Now in the above code we have specified that the ID should be used as id by specifying the idAttribute. Lets try to create a new model with ID value now.
var book3 = new Book({ ID: 43 });
console.log(book1.id);
And we can see that the id value is taken from the specified attribute.
and thus this makes it very easier for the backbone models to work with server side entities and makes the model identification seamless.

Validating the model

When we are working on business applications it is often required that we validate the model before persisting the data. Backbone provides a very easy way of validating the model data. We just need to implement the models validate function.
var Book = Backbone.Model.extend({
    defaults: {
        ID: "",
        BookName: ""
    },
    idAttribute: "ID",
    initialize: function () {
        console.log('Book has been intialized');
    },
    constructor: function (attributes, options) {
        console.log('Book\'s constructor had been called');
        Backbone.Model.apply(this, arguments);
    },
    validate: function (attr) {
        if (attr.ID <= 0) {
            return "Invalid value for ID supplied."
        }
    }
});
What happens here is that whenever we try to save the model(which we will see in next section), the Validate function will get called. It will check the custom validation logic that we have put in place and validate the model. To test the validate method, we can use models isValid function.
var book4 = new Book({ ID: -4 });
var result = book4.isValid(); // false
Another way to prevent the invalid values in the model attributes is by passing the validate:true while setting the models attribute. This will also trigger the validate function
var book5 = new Book();
book5.set("ID", -1, {validate:true});
What this will do is that this will not even allow setting of invalid values if the value that we are trying to set is invalid as per our custom logic.
How this validation works is that whenever the user chooses to save the model, the validate function will be called. if there is any validation error then the model save will fail. alternatively, the user can choose to pass validate:true whenever he want to restrict the setting of invalid values in the model attributes. If we want to check the validity of the model at any particular instance, we can use the isValid function to test this. Having said that one important thing to know here is that whenever our validation function fails to validate the model an event invalid is raised by backbone. If we want to listen to this event, we can subscribe to this. Lets try to hook up to this event and see the validation errors. We will do this in the initialize function of the model.
var Book = Backbone.Model.extend({
    defaults: {
        ID: "",
        BookName: ""
    },
    idAttribute: "ID",
    initialize: function () {
        console.log('Book has been intialized');
        this.on("invalid", function (model, error) {
            console.log("Houston, we have a problem: " + error)
        });
    },
    constructor: function (attributes, options) {
        console.log('Book\'s constructor had been called');
        Backbone.Model.apply(this, arguments);
    },
    validate: function (attr) {
        if (attr.ID <= 0) {
            return "Invalid value for ID supplied."
        }
    }
});

Saving the model

The backbone models inherently supports saving on the server using a restful web api. To save the model using a HTTP REST service, we need to specify the urlRoot in the backbone model. To actually save the model, we can call the save on the backbone model. The save method will trigger the validations and if the validations are successful, it will try to identify the action to be performed i.e. create or update and based on that action, it will use urlRoot and call the appropriate REST API to perform the operation.
So if I have a service running on my local machine, i first need to specify the urlRoot for the service in my model.
var Book = Backbone.Model.extend({
    defaults: {
        ID: "",
        BookName: ""
    },
    idAttribute: "ID",
    initialize: function () {
        console.log('Book has been initialized');
        this.on("invalid", function (model, error) {
            console.log("Houston, we have a problem: " + error)
        });
    },
    constructor: function (attributes, options) {
        console.log('Book\'s constructor had been called');
        Backbone.Model.apply(this, arguments);
    },
    validate: function (attr) {
        if (attr.ID <= 0) {
            return "Invalid value for ID supplied."
        }
    },
    urlRoot: 'http://localhost:51377/api/Books'
});
and to save this model using this service, I could do something like:
var book = new Book({ BookName: "Backbone Book 43" });
    book.save({}, {
        success: function (model, response, options) {
            console.log("The model has been saved to the server");
        },
        error: function (model, xhr, options) {
            console.log("Something went wrong while saving the model");
        }
    });
The save function also accepts success and error callback functions so that appropriate action can be taken based on the response from the server.
Now if we want to save the model on local storage rather than on a server, we just need to keep in mind that save function actually calls sync function to actually save/retrieve the model information. So if we need to save the model on a local storage, we need to override the sync function and provide the custom code to save on local storage.
  • Blogger Comments
  • Facebook Comments

0 comments:

Item Reviewed: Understanding the basics of Backbone Models-1 Rating: 5 Reviewed By: Unknown