Loading Data from the Server
This phase is the foundation for all the other MVC Tutorial phases. In this phase, we’ll create a lightweight Model, View, and Controller from scratch using JQuery. You can read more about the motivation for this tutorial from this post.
Demo
What type of functionality can you expect when you’re done? The demo below shows (1) loading in all or some of the data from the server, (2) getting data from the cache when available instead of AJAX, and (3) ability to clear the cache and/or console and start over.
Architecture
Like any good MVC pattern, our goal will be to keep the Model and View logically separate. All of our application logic will reside inside the Controller. So, where does the AJAX fit in? A simple data flow diagram: View <=> Controller <=> Model <=> Cache <=AJAX=> Server The View will only communicate with the Controller, where all of our application logic resides. The Controller will tell the Model when and what data to load, and then relay that data to the View. The Model will keep a local cache of data, as well as manage the AJAX to and from the server.
The Source Code
You can take a sneak peek at the source code here, or download it and follow along on your own machine. All applicable source code will also be displayed inline as it’s explained later in the tutorial.
First Steps
In the next few pages, I’ll walk you through the code for the Controller, Model, and View, and then lastly we’ll integrate it into a sample HTML page and debrief. So without further ado:
- Page 1: Loading Data from the Server
- Page 2: The View
- Page 3: The Model
- Page 4: The Controller
- Page 5: Summary and Source Code
jQuery.extend({
Model: function(){
/**
* our local cache of data
*/
var cache = new Array();
/**
* a reference to ourselves
*/
var that = this;
/**
* who is listening to us?
*/
var listeners = new Array();
/**
* get contents of cache into an array
*/
function toArray(){
var a = new Array();
for (var i in cache){
a.push(cache[i]);
}
return a;
}
/**
* load a json response from an
* ajax call
*/
function loadResponse(data){
$.each(data, function(item){
cache[data[item].id] = data[item];
that.notifyItemLoaded(data[item]);
});
}
/**
* look in the cache for a single item
* if it's there, get it, if not
* ask the server to load it
*/
this.getItem = function(id){
if(cache[id]) return cache[id];
that.notifyLoadBegin();
$.ajax({
url: 'ajax.php',
data : { load : true, id : id },
type: 'GET',
dataType: 'json',
timeout: 1000,
error: function(){
that.notifyLoadFail();
},
success: function(data){
loadResponse(data);
that.notifyLoadFinish();
}
});
}
/**
* load lots of data from the server
* or return data from cache if it's already
* loaded
*/
this.getAll = function(){
var outCache = toArray();
if(outCache.length) return outCache;
that.notifyLoadBegin();
$.ajax({
url: 'ajax.php',
data : { load : true },
type: 'GET',
dataType: 'json',
timeout: 1000,
error: function(){
that.notifyLoadFail();
},
success: function(data){
loadResponse(data);
that.notifyLoadFinish();
}
});
}
/**
* load lots of data from the server
*/
this.clearAll = function(){
cache = new Array();
}
/**
* add a listener to this model
*/
this.addListener = function(list){
listeners.push(list);
}
/**
* notify everone that we're starting
* to load some data
*/
this.notifyLoadBegin = function(){
$.each(listeners, function(i){
listeners[i].loadBegin();
});
}
/**
* we're done loading, tell everyone
*/
this.notifyLoadFinish = function(){
$.each(listeners, function(i){
listeners[i].loadFinish();
});
}
/**
* we're done loading, tell everyone
*/
this.notifyLoadFail = function(){
$.each(listeners, function(i){
listeners[i].loadFail();
});
}
/**
* tell everyone the item we've loaded
*/
this.notifyItemLoaded = function(item){
$.each(listeners, function(i){
listeners[i].loadItem(item);
});
}
},
/**
* let people create listeners easily
*/
ModelListener: function(list) {
if(!list) list = {};
return $.extend({
loadBegin : function() { },
loadFinish : function() { },
loadItem : function() { },
loadFail : function() { }
}, list);
}
});
jQuery.extend({
View: function($console){
/**
* keep a reference to ourselves
*/
var that = this;
/**
* who is listening to us?
*/
var listeners = new Array();
/**
* a box to put our incoming messages
*/
var $messages = $("<div style='height:130px; overflow: auto;'></div>");
/**
* show a simple text-only message
* in the view
*/
this.message = function(str){
$messages.append(str + "<br>");
}
/**
* set up the buttons to load data
*/
$console.append($("<input type='button' value='Load All'></input>").click(function(){
that.notifyAllClicked();
}));
$console.append($("<input type='button' value='Load One'></input>").click(function(){
that.notifyOneClicked();
}));
$console.append($("<input type='button' value='Clear Cache'></input>").click(function(){
that.notifyClearAllClicked();
}));
$console.append($("<input type='button' value='Clear Console'></input><br><br>").click(function(){
$messages.empty();
}));
$console.append($messages);
/**
* add a listener to this view
*/
this.addListener = function(list){
listeners.push(list);
}
/**
* notify everone that the user wants
* to load all the data
*/
this.notifyAllClicked = function(){
$.each(listeners, function(i){
listeners[i].loadAllClicked();
});
}
/**
* notify everone that the user
* wants to load just 1 item of data
*/
this.notifyOneClicked = function(){
$.each(listeners, function(i){
listeners[i].loadOneClicked();
});
}
/**
* notify everone that the user wants
* to clear the local cache
*/
this.notifyClearAllClicked = function(){
$.each(listeners, function(i){
listeners[i].clearAllClicked();
});
}
},
/**
* let people create listeners easily
*/
ViewListener: function(list) {
if(!list) list = {};
return $.extend({
loadAllClicked : function() { },
loadOneClicked : function() { },
clearAllClicked : function() { }
}, list);
}
});
jQuery.extend({
Controller: function(model, view){
/**
* listen to the view
*/
var vlist = $.ViewListener({
loadAllClicked : function(){
var all = model.getAll();
if(all) view.message("from cache: " + all.length + " items");
$.each(all, function(i){
view.message("from cache: " + all[i].name);
});
},
loadOneClicked : function(){
var d = model.getItem(1);
if(d) view.message("from cache: " + d.name);
},
clearAllClicked : function(){
view.message("clearing data");
model.clearAll();
}
});
view.addListener(vlist);
/**
* listen to the model
*/
var mlist = $.ModelListener({
loadBegin : function() {
view.message("Fetching Data...");
},
loadFail : function() {
view.message("ajax error");
},
loadFinish : function() {
view.message("Done.");
},
loadItem : function(item){
view.message("from ajax: " + item.name);
}
});
model.addListener(mlist);
}
});
<html>
<head>
<script src="jquery.js" type="text/javascript"></script>
<script src="model.js" type="text/javascript"></script>
<script src="view.js" type="text/javascript"></script>
<script src="controller.js" type="text/javascript"></script>
<script type="text/javascript">
$(function(){
var model = new $.Model();
var view = new $.View($("#console"));
var controller = new $.Controller(model, view);
});
</script>
</head>
<body>
<div id="console">
</div>
</body>
</html>
<?
if(isset($_REQUEST["id"])){
echo "[{ id : 1, name : 'item 1' }]";
}else{
echo "[{ id : 1, name : 'item 1' }, { id : 2, name : 'item 2' }]";
}
?>

2 responses so far ↓
1 pedant
// Aug 8, 2008 at 11:45 am
hi i like the tutorial.
one thing i noticed with the demo inside this page is if the result from “load one” is already cached, “load all” will only retrieve that cached item. should your button say “load cached” instead of “load all”, or do you perhaps have a bug in the code here? the way it’s named now I would expect it to load all (read: 2) items, regardless if one is already cached.
example:
[click clear cache]
clearing data
[click load one]
Fetching Data…
from ajax: item 1
Done.
[click load all]
from cache: 1 items
from cache: item 1
— here —
[click clear cache]
clearing data
[click load all]
Fetching Data…
from ajax: item 1
from ajax: item 2
Done.
2 adam
// Aug 9, 2008 at 11:32 am
true enough,
there are a few ways to write the getAll() function, one would be to just move line 69 to the end of the function. This would mean that the AJAX would always be called, and the cache would be returned from the function call.
Another option, if we knew that we’d only ever have 2 items, we could check if the cache were full and only AJAX if it weren’t.
Later phases will have a more meaningful data model (as opposed to the ever generic “item”), so that things like this will be more clear.
Leave a Comment