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.
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:
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:
remaininTime?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.
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.
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 is an example of wrapping your model's raw data with more useful functionality.
This is just a tiny taste of what models can do. Check out these other features:
Learn how to connect to services.
$.Model("Task",{
findAll : "/tasks.json",
findOne : "/tasks/{id}.json",
create : "/tasks.json",
update : "/tasks/{id}.json"
},{})
Convert data like "10-20-1982" into new Date(1982,9,20) auto-magically:
$.Model("Task",{
attributes : {birthday : "date"}
convert : {
date : function(raw){ ... }
}
},{})
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();
}
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"
}
})
}
,{});