jQuery.Model  class     

tags: core
plugin: jquery/model
download: jQuery.Model
test: qunit.html

Models wrap an application's data layer. In large applications, a model is critical for:

  • Encapsulating services so controllers + views don't care where data comes from.

  • Providing helper functions that make manipulating and abstracting raw service data easier.

This is done in two ways:

  • Requesting data from and interacting with services

  • Converting or wrapping raw service data into a more useful form.

Basic Use

The jQuery.Model class provides a basic skeleton to organize pieces of your application's data layer. First, consider doing Ajax without a model. In our imaginary app, you:

  • retrieve a list of tasks
  • display the number of days remaining for each task
  • mark tasks as complete after users click them

Let's see how that might look without a model:

$.Controller("Tasks",
{
  // get tasks when the page is ready 
  init: function() {
    $.get('/tasks.json', this.callback('gotTasks'), 'json')
  },
 |* 
  * assume json is an array like [{name: "trash", due_date: 1247111409283}, ...]
  */
 gotTasks: function( json ) { 
    for(var i =0; i < json.length; i++){
      var taskJson = json[i];

      //calculate time remaining
      var remaininTime = new Date() - new Date(taskJson.due_date);

      //append some html
      $("#tasks").append("<div class='task' taskid='"+taskJson.id+"'>"+
                          "<label>"+taskJson.name+"</label>"+
                          "Due Date = "+remaininTime+"</div>")
    }
  },
  // when a task is complete, get the id, make a request, remove it
  ".task click" : function( el ) {
    $.post('/tasks/'+el.attr('data-taskid')+'.json',
         {complete: true}, 
      function(){
        el.remove();
      })
  }
})

This code might seem fine for right now, but what if:

  • The service changes?
  • Other parts of the app want to calculate remaininTime?
  • Other parts of the app want to get tasks?
  • The same task is represented multiple palces on the page?

The solution is of course a strong model layer. Lets look at what a a good model does for a controller before we learn how to make one:

$.Controller("Tasks",
{
  init: function() {
    Task.findAll({}, this.callback('tasks'));
  },
  list : function(todos){
    this.element.html("tasks.ejs", todos );
  },
  ".task click" : function( el ) {
    el.model().update({complete: true},function(){
      el.remove();
    });
  }
});

In tasks.ejs

<% for(var i =0; i < tasks.length; i++){ %>
<div <%= tasks[i] %>>
   <label><%= tasks[i].name %></label>
   <%= tasks[i].timeRemaining() %>
</div>
<% } %>

Isn't that better! Granted, some of the improvement comes because we used a view, but we've also made our controller completely understandable. Now lets take a look at the model:

$.Model("Task",
{
 findAll: "/tasks.json",
 update: "/tasks/{id}.json"
},
{
 timeRemaining: function() {
  return new Date() - new Date(this.due_date)
 }
})

Much better! Now you have a single place where you can organize Ajax functionality and wrap the data that it returned. Lets go through each bolded item in the controller and view.

Task.findAll

The findAll function requests data from "/tasks.json". When the data is returned, it converted by the models function before being passed to the success callback.

el.model

jQuery.fn.model is a jQuery helper that returns a model instance from an element. The list.ejs template assings tasks to elements with the following line:

<div <%= tasks[i] %>> ... </div>

timeRemaining

timeRemaining is an example of wrapping your model's raw data with more useful functionality.

Other Good Stuff

This is just a tiny taste of what models can do. Check out these other features:

Encapsulation

Learn how to connect to services.

$.Model("Task",{
  findAll : "/tasks.json",    
  findOne : "/tasks/{id}.json", 
  create : "/tasks.json",
  update : "/tasks/{id}.json"
},{})

Type Conversion

Convert data like "10-20-1982" into new Date(1982,9,20) auto-magically:

$.Model("Task",{
  attributes : {birthday : "date"}
  convert : {
    date : function(raw){ ... }
  }
},{})

jQuery.Model.List

Learn how to handle multiple instances with ease.

$.Model.List("Task.List",{
  destroyAll : function(){
    var ids = this.map(function(c){ return c.id });
    $.post("/destroy",
      ids,
      this.callback('destroyed'),
      'json')
  },
  destroyed : function(){
    this.each(function(){ this.destroyed() });
  }
});

".destroyAll click" : function(){
  this.find('.destroy:checked')
      .closest('.task')
      .models()
      .destroyAll();
}

Validations

Validate your model's attributes.

$.Model("Contact",{
init : function(){
    this.validate("birthday",function(){
        if(this.birthday > new Date){
            return "your birthday needs to be in the past"
        }
    })
}
,{});
© Jupiter IT - JavaScriptMVC Training and Support