MVC
Intro About Overview Quickstart Get Involved Controllers About Basics Samples Views About Basics Other Template Libraries Routes Basics Hash Change Models About Basics Samples Adapters Other Model Libraries Configuration

Models

Models represent state in your application. Think of a user in your system, you would create a model that contains attributes about your user. We provide a basic model to assist in storing data.

Basics

Here we will start with a class called "car" and store attributes about it. When you think of a car, there are four properties everyone is familiar with.
  1. Make
  2. Model
  3. Year
  4. Color
By creating a car model, we can store multiple cars and then search/filter based on the properties.

Defaults

Each model has two default properties, id and modelName. The id is set by the storage adadpter. modelName is used for the storage adapter. With that, there are five functions implemented that are based on the storage adapters.
All callbacks are asynchronous, as storage adapters can be remote data stores
  1. get(id,callback)
  2. getAll(callback)
  3. remove(callback)
  4. save(callback)
  5. set(properties)

get(id,callback)

This takes in an id and loads the object, then passes it as a parameter to the callback function.

getAll(callback)

This loads all items of the given model and executes the callback.

remove(callback)

This removes an item from the storage adapter and executes the callback.

save(id,callback)

This saves an item from the storage adapter and executes the callback. This implements the insert/update commands.

set(properties)

This takes in a hash map of keys/values to set on the selected item.

Models Overview

Creating a model is simple. You simply call $.mvc.model.extend(name,properties,storageAdapter) and it will setup everything for you. The name parameter is name of the model, properties are a hash table, and storageAdapter is the adapter you want to use.
var  Car=$.mvc.model.extend("car",{});
The above would create a car model accessed by the variable Car. It would not have any properties and only have the default functions. IT would use the default local storage adapter.
var  Car=$.mvc.model.extend("car",{
    make:'',
    model:'',
    color:'',
    year:'1999'
});
The above would create a car model accessed by the variable Car. It has four properties, with the year being a default of "1999".

Custom Methods

You can create custom methods on the model that may do additional things. Let's create a model that has a function called getCarInfo that returns a string of all the data.
var  Car=$.mvc.model.extend("car",{
    make:'',
    model:'',
    color:'',
    year:'1999',
    getCarInfo:function(){
        return this.make+" "+this.model+" "+this.color+" "+this.year;
    }
});
With the above, when we call getCarInfo() on an object, it will return the details in a string.

Asychronous Loading

Because not all adapters can be syncronous, we require a callback function that acts on any data returned. The resulting data (object or array) is passed in as a parameter to the callback function.

Filtering/Searching

We do not provide a searching/filtering API. Why not? Because we return an array that you can then filter the results of.
var cars = new Car();
var blueCars=[];
var redCars=[];

var allCars=cars.getAll(function(results){
    redCars=results.filter(function(obj){return obj.color=='red';});
    blueCars=results.filter(function(obj){return obj.color=='blue';});
});

Sample Model

Below is a sample model for a "Todo" class. We will create the basic properties and methods first.
Todo = new $.mvc.model.extend("todo",{
    text: '', //Text of the todo
    isComplete:false, //Has it been completed
    isArchived:false, //Has it been archived
    /* Mark it as archived*/
    archiveItem:function(){
        this.isArchived=true;
        this.isComplete=false;
        this.save();
        return this;
    },
    /* Mark it as complete */
    finishItem:function(){
        this.isArchived=false;
        this.isComplete=true;
        this.save();
        return this;
    },
    /* Mark it as needing to be done */
    resetItem:function(){
        this.isArchived=false;
        this.isComplete=false;
        this.save();
        return this;
    }
});
We created three properties and three methods to help us with the todo models. Below are some sample calls a controller would use.
/* save a todo */
var todo = new Todo();
if(value.length==0)
   return alert("Please enter some text");
todo.text=value;
todo.set({text:value});
todo.save(callback);

/* Load a todo and call the method to update it */
var id=$(this).data("id");
var update=$("#update").val();
var todo = new Todo();
todo.get(id,function(el){
    if(that.value=="active")
    {
        el.finishItem();
        that.value="complete";
        that.checked=true;
     
    }
    else if(that.value=="complete")
    {
        el.resetItem();
        that.value="active";
        this.checked=false;
    }
    else {
        el.finishItem();
        that.value="complete"
        that.checked=true;
    }
});


Storage Adapters

Storage adapters are the backend of the models that handle the data store. By default, we use the HTML5 local storage for data, but you can extend your own with any technology you write. Each adapter must implement four functions. When you create your model, there is a third parameter that is the storage adapter
var myRemoteAdapter={
    save:function(obj,callback){
    },
    get:function(id,callback){
    },
    getAll:function(id,callback){
    },
    remove:function(obj,callback){
    }
}
Below is the default local storage adapter with comments/description. We do not store everything in a single entry. Instead, we have a linker record and each is stored as it's own entry then.
We use the modelName property to help us look up and keep track in the linkerCache object.
var localAdapter = {
    linkerCache:{}, //We do not store all documents in a single record, we keep a lookup document to link them
  
    save: function(obj,callback) {
        if(!obj.id)
           obj.id=$.uuid(); //If it doesn't have an ID, create one
        try{
            window.localStorage.setItem(obj.id, JSON.stringify(obj)); //Store it in the local storage
            this.linkerCache[obj.name][obj.id]=1; //Add a record to the linker cache
            window.localStorage.setItem(obj.name+"_linker",JSON.stringify(this.linkerCache[obj.name]));
            $(document).trigger(obj.name + ":save", obj);
            if(callback)
               return callback(obj);
        }
        catch(e){console.log(e)}
    },
    get: function(id,callback) {
        var el = window.localStorage.getItem(id);
        try {
            el = JSON.parse(el);
        } 
        catch (e) {
            el = {}
        }
        return callback(el);
    },
    //Here we will get the linker cache record and then iterate through and load the other records
    getAll:function(type,callback){
        try{
            var data=JSON.parse(window.localStorage.getItem(type+"_linker"));
            var res=[];
            for(var j in data){
                if(localStorage[j]){
                    var item=JSON.parse(localStorage[j]);
                    item.name=type;
                    item.id=j;
                    res.push(item);
                }
                else {
                    delete data[j];
                }
            }
            this.linkerCache[type]=data?data:{};
            //Fix dangling references
            window.localStorage.setItem(type+"_linker",JSON.stringify(this.linkerCache[type])); 
            return callback(res);
        }
        catch(e){console.log(e)}
    },
    remove: function(obj,callback) {
        try{
            window.localStorage.removeItem(obj.id);
            delete this.linkerCache[obj.name][obj.id];
            window.localStorage.setItem(obj.name+"_linker",JSON.stringify(this.linkerCache[obj.name])); 
            $(document).trigger(obj.name + ":remove", obj.id);
            if(callback)
               return callback(obj);
        }
        catch(e){console.log(e)}
    }
}

Remote Adapter

Below is an adapter that works with a remote webservice. You need to implement the server side logic and security yourself.
var myRemoteAdapter={
    server:"http://localhost:9090/jqmws/",
    save:function(obj,callback){
        $.get(this.server+"todo.php?axt=save&data="+encodeURIComponent(JSON.stringify(obj)),
            function(id){
                obj.id=id;
                $(document).trigger(obj.name + ":save", obj);
                if(callback)
                    callback(obj);
            }
        );
    },
    get:function(id,callback){
        $.get(this.server+"todo.php?axt=get&data="+encodeURIComponent(id),
            function(obj){
                
                obj=JSON.parse(obj);
                if(callback)
                    callback(obj);
            }
        );
    },
    getAll:function(id,callback){
        $.get(this.server+"todo.php?axt=getAll",
            function(obj){
                obj=JSON.parse(obj);
                if(callback)
                    callback(obj);
            }
        );
    },
    remove:function(obj,callback){
        $.get(this.server+"todo.php?axt=delete&data="+encodeURIComponent(obj.id),
            function(obj){
                $(document).trigger(obj.name + ":remove", obj.id);
                obj=JSON.parse(obj);
                if(callback)
                    callback(obj);
            }
        );
    }
}

Todo = new $.mvc.model.extend("todo",{
    text: '',
    isComplete:false,
    isArchived:false,
    archiveItem:function(){
        this.isArchived=true;
        this.isComplete=false;
        this.save();
        return this;
    },
    finishItem:function(){
        this.isArchived=false;
        this.isComplete=true;
        this.save();
        return this;
    },
    resetItem:function(){
        this.isArchived=false;
        this.isComplete=false;
        this.save();
        return this;
    }
},myRemoteAdapter); //Notice the remote adapter as the third parameter
Please see the sample in the github repo for the server side component.

Other model libraries

You are free to use any model library you would like in your app. The only coupling between MVC and the built in models is auto loading. You are responsible for autoloading your own models.
Welcome to the
MVC Guide.

MVC is a lightweight MVC framework. You may ask "why another framework?". The target of MVC is to be lightweight and fast. We do not provide all the features of advanced frameworks, like Backbone.js, CanJS, Knockout.js and Ember.js. Instead, the goal is to help with separating the content out from your app, provide routing, basic models and views.

MVC was built to work alongside UI but can also be used as a standlone library. The main goal is to provide controllers and views to help segment out your application.

MVC Overview

MVC helps you segment out your application and manage your code. Controllers handle the logic side of your app, and are grouped by common functionality. Say you are writing a TODO app, you're controller may look something like

todo
    add
    edit
    save
    delete

You would then be able to call them by URL's that route to the "todo" controller like the following.

<a href="/todo/add">Add</a>

<a href="/todo/edit/1">Edit</a>

<a href="/todo/delete/1">Add</a>

You can also call a javascript function to invoke the controllers and actions using the following

$.mvc.route("/todo/delete");

Views

Views can use any template system you like, but we bundle jq.template.js in MVC. Views allow you to re-use HTML code to generate markup for the user. A few examples are

Please see the views section for more detailed information on how to use jq.template.js.

MVC Quickstart

Using MVC in your application is simple. Let's start with an empty folder that has an index.html file in it. Next, you will create three folders and an app.js file.

Now, open up your index.html file and include the jq.mvc.min.js file and app.js file.
jq.mvc.min.js includes jq.mvc.js, jq.mobi.js and jq.template.js

<script src="//cdn.app-framework-software.intel.com/jq.mvc.min.js" type="text/javascript"></script>
<script src="app.js" type="text/javascript"></script>
Next, open up the app.js file and let's create an instance of the MVC app.
var app = new $.mvc.app();

So our app this there, but it won't do anything. Let's create a controller and two views. Inside your controller folder create the file "hello.js".
Inside the views folder create the files "hello.js" and "world.js"

./
    controllers/
        hello.js
    views/
        hello.js
        world.js
    index.html
    app.js

"hello.js" controller

Open up the file with your favorite editor and let's set up a basic controller.
$.mvc.controller.create("hello", {
    views:["views/hello.js","views/world.js"], //These are the views we will use with the controller
    world:function(){
        //This is the action "world".  We will load the "world.js" view.
        //When loading views, you must reference the folder path and file name.
        $("#main").html($.template('views/world.js'));
    },
    init:function(){
        //Here we can run any initializing code for this controller/
    },
    default:function(){
        //Let's load the "hello.js" view as th default.
        $("#main").html($.template('views/hello.js'));
    },
});

Next, we will edit the two view files. First, we will edit "views/hello.js" and then "views/world.js"


Hello <br>
<a href="hello/world">Hello World</a>

Hello World <br>
<a href="hello/">Go Back</a>

app.js

Let's open up app.js again and edit it. We'll add an entry to load the hello.js controller and add a ready function to load the hello default action.

var app = new $.mvc.app();
app.loadControllers(["hello"]); //You can pass in array or a string.  You do not need to reference the .js extension.

//Now let's run code on app.ready and load the default action for the hello controller.
app.ready(function(){
    $.mvc.route("hello/");
});

Want to get involved?

First, head over to Github and fork the code. You can start playing around with the core, break things, fix things, and make enhancements. When you are ready, submit a pull request with the following

  1. Overview of the pull request
  2. Bugs fixed/Features added
  3. Test cases for the above
  4. Does this change affect current installs (will it break them)?
We will then review the pull request and either auto merge it or offer back feedback. If you are proposing something drastic, it's best to get in touch with us first to discuss the overall impact and go from there. If we find it diverts from our core goals or will affect users, we will deny it.

Controllers

Controllers drive the logic of your application. Routes are dispatched to functions in the controller to handle actions and execute code. This allows you to separate your logic and group functions into common areas.

Basics

The controller is an object that has functions attached to it. Using "routes", they will be dispatched to the controller, and then execute the function on that object.
Test
Clicking the above link would look at the "foo" controller and then execute the "bar" function if it exists. Anything passed after the "action" part of the URL comes through as parameters. You can specify exactly what parameters are available in your function, or you can access them via the "arguments" array.
Test
With the above, we would have three parameters available in the function. They would be
  1. 1
  2. 2
  3. 3

Controller Overview

Creating a controller is simple. You call $.mvc.controller.create(name,{functions}) and it will setup everything for you. The name parameter is the name of your controller and the first part of the URL that gets routed.
$.mvc.controller.create("foo",{});
The above would create an empty controller called "foo", which would be accessible via routes to /foo/*. Next let's create the basic controller.
$.mvc.controller.create("foo",
    {
        bar:function(param1,param2){
            console.log(param1,param2,arguments);
        }
    }
);
Now we have the controller "foo" with one function/route available called "bar". But there are three special configuration properties on each controller you should register.
  1. views - (optional) - array or object of views you want your controller to pre-load for use with App Framework template plugin
  2. init - (optional) function to execute when the controller has been loaded, including all views. This is usefull for binding global event listeners, etc.
  3. default - the default route for that controller
$.mvc.controller.create("foo",
    {
        views:{"main_tpl":"views/main.tpl"},
        init:function(){
            //do nothing
        },
        default:function(){
            //Load the main template
            $("#main").html($.template("main_tpl"));
        },
        bar:function(param1,param2){
            //Log the parameters and total arguments
            console.log(param1,param2,arguments);
        }
    }
);
We now have two routes that can be called, views that will be loaded by default, and the init function that gets created when the controller is loaded. Continue on to our sample to see a controller for a simple todo app.


Sample Controller

Below you will find a sample controller for a "Todo" app. It is basic and will only have a few routes, but you will get to see how a controller is registered and actions are handled.

Skeleton Controller

Here is the skeleton of the controller. We will declare the needed functions and views.
$.mvc.controller.create("foo",
    {
        views:{"main_tpl":"views/main.tpl","item_tpl":"views/item.tpl"},
        init:function(){
            //load the default view and wire events
        },
        default:function(){
            //get the saved todos
        },
        save:function(){
            //Get the value and save it
        },
        finish:function(id,active){
            //mark as finish
        },
        delete:function(id){
            //delete
        }
    }
);
The above gives us our basic stub for the controllers. Now we will implement each function (alone). First is the "init" function when the controller is created.
init:function(){
    $("#main").html($.template("main_tpl")); //Load the main template
    //Now we will wire events for save cancel
    $("#save_btn").bind("click",function(evt){
        $.mvc.route("/todo/save");
    });
    $("#cancel_btn").bind("click",function(evt){
        $("#todo_text").val("");
    });
}
Next is the default function. This is called when the app is loaded. We get all the previous items and save them.
default:function(){
    //todo_model is an instace of the Todo model.  We will get all of them and then list them
    var items=todo_model.getAll(function(items){
        //Filter active items
        var active=items.filter(function(obj){return obj.archive!==true;});
        //Filter archived items
        var archived=items.filter(function(obj){return obj.archive==true;});
        //Loop through the active items and put them in the DOM
        active.forEach(function(theItem){
            $("#active").append($.template("item_tpl",{item:theItem}));
        });
        //Loop through the archived items and put them in the DOM
        archived.forEach(function(theItem){
            $("#archived").append($.template("item_tpl",{item:theItem}));
        });
    });
}
Now we will handle saving a new todo.
save:function(){
    //create a new object
    var item = new Todo();
    //set active to true
    todo.active=true;
    //Call save.  it is asynchronous, so we execute a callback after it's finished.  We will add it to the dom
    todo.save(function(theItem){
        $("#active").append($.template("item_tpl",{item:theItem}));
    });
}
Let's handle toggling the state from active to inactive.
finish:function(id,active){
    var todo=todo_model.get(id,function(item){
        item.active=active;
        item.save(function(){
            //Let's move it to the correct list
            if(active)
                $("#active").append("#"+id+"_item");
            else
                $("#archived").append("#"+id+"_item");
        });
    });
}
We will finally implement a delete route.
delete:function(id){
    var todo=todo_model.remove(id,function(item){
        //Let's remove the item from the DOM now
        $("#"+id+"_item").remove();
    });
}

Put it all together

$.mvc.controller.create("foo",
    {
        views:{"main_tpl","views/main.tpl","item_tpl","views/item.tpl"},
        init:function(){
            $("#main").html($.template("main_tpl")); //Load the main template
            //Now we will wire events for save cancel
            $("#save_btn").bind("click",function(evt){
                $.mvc.route("/todo/save");
            });
            $("#cancel_btn").bind("click",function(evt){
                $("#todo_text").val("");
            });
        },
        default:function(){
            //todo_model is an instace of the Todo model.  We will get all of them and then list them
            var items=todo_model.getAll(function(items){
                //Filter active items
                var active=items.filter(function(obj){return obj.archive!==true;});
                //Filter archived items
                var archived=items.filter(function(obj){return obj.archive==true;});
                //Loop through the active items and put them in the DOM
                active.forEach(function(theItem){
                    $("#active").append($.template("item_tpl",{item:theItem}));
                });
                //Loop through the archived items and put them in the DOM
                archived.forEach(function(theItem){
                    $("#archived").append($.template("item_tpl",{item:theItem}));
                });
            });
        },
        save:function(){
             //create a new object
            var item = new Todo();
            //set active to true
            todo.active=true;
            //Call save.  it is asynchronous, so we execute a callback after it's finished.  We will add it to the dom
            todo.save(function(theItem){
                $("#active").append($.template("item_tpl",{item:theItem}));
            });
        },
        finish:function(id,active){
            var todo=todo_model.get(id,function(item){
                item.active=active;
                item.save(function(){
                //Let's move it to the correct list
                if(active)
                    $("#active").append("#"+id+"_item");
                else
                    $("#archived").append("#"+id+"_item");
                });
            });
        },
        delete:function(id){
            var todo=todo_model.remove(id,function(item){
                //Let's remove the item from the DOM now
                $("#"+id+"_item").remove();
            });
        }
    }
);

Views

Views render the UI for your client. MVC uses the Dot.js plugin to assist in rendering views, but you can use any library you want. Views help with simplifying the code of your application and displays any elements to the user.

Please visit https://github.com/olado/doT for support on Dot.js

Loading Templates

By adding a reference to your views object (or array) in the controller, we will load the content asynchronously and append the script tag to the DOM for later use.
$.mvc.controller.create("foo",{
   views:{"index_tpl":"views/index.tpl","bar_tpl":"views/foo/bar.tpl"},
   /* other code*/
});

Invoking templates

To invoke a template, you call the javascript function and pass in the template name. When including them using an object, the key is the name. When including them in an array, the path is the name of the template.
var content=$.template("index_tpl");
You can pass in data to the template with a second parameter that is an object with all the values. Each property on the data object is accessed in the template by name then. You do NOT act on the object that is passed in.
var content=$.template("index_tpl",{name:'MVC'});
$.template returns an HTML string that you can then set $.html() on.
$("#myDiv").html($.template("index_tpl",{name:'MVC'}));
You can also call the $.tmpl function, which is similar to $.template but returns a App Framework object with the result. The function parameters are the same between the two.
$("#myDiv").append($.tmpl("index_tpl",{name:'MVC'}));

Sample Views

Below are some sample views. We will show how the contents of the view and expected result.

Count 1 to 5


<% for(var i=1;i<=5;i++)%>
    Counter = <%=i%>
The expected output is
Counter = 1
Counter = 2
Counter = 3
Counter = 4
Counter = 5

Basic data

Here is a view that will display basic information about a person. We will create the person object and pass it in as a parameter, then render the data in the template.

Name: <%=data.name%>
Location: <%=data.location%>
Age: <%=data.age%>
Email: <%=data.email%>
Below is the call we would make passing the data in.
var info={
    name:"Joe Developer",
    location:"Lancaster, PA",
    age:"40",
    email:"nowhere@nowhere.com"
}

$("#userInfo").html($.template("index_tpl",{data:info}));
With the above data and output, this is the expected result.
Name: Joe Developer
Location: Lancaster, PA
Age: 40
Email: nowhere@nowhere.com

Advanced table list (loop)

Below is an advanced use where we will construct a table based off an array in two ways. The first is to pass in the full array and do a loop. The second we will loop outside and use $.tmpl and append the content as we build it.

<% for(var j=0;j
    
<%=currAlbum.artist%> - <%=currAlbum.title%>
} %>
Now we will show the albums array we pass in and get the data
var albums=[];
albums.push({
image:"path/to/image.png",
artist:"Cake",
title:"Fashion Nugget"
});

albums.push({
image:"path/to/image.png",
artist:"Slimfit",
title:"Poplockin"
});

albums.push({
image:"path/to/image.png",
artist:"The Killers",
title:"Hot Fuss"
});


$("#albums").html($.template("views/album_list_loop.tpl",{albums:albums}));
The expected output would be the following.

[image] Cake - Fashion Nugget
[image] Slimfit - Poplockin
[image] The Killers - Hot Fuss

Advanced table list (append)

Here we will append each track individually and loop at the top level. This is usefull to only update segmants of the page from the template.

    
<%=currAlbum.artist%> - <%=currAlbum.title%>
Now we will show the albums array we pass in and get the data
var albums=[];
albums.push({
image:"path/to/image.png",
artist:"Cake",
title:"Fashion Nugget"
});

albums.push({
image:"path/to/image.png",
artist:"Slimfit",
title:"Poplockin"
});

albums.push({
image:"path/to/image.png",
artist:"The Killers",
title:"Hot Fuss"
});


for(var i=0;i < albums.length;i++)
{
    $("#albums").append($.template("views/album_list.tpl",{currAlbum:albums[i]}));
}
The expected output would be the following.

[image] Cake - Fashion Nugget
[image] Slimfit - Poplockin
[image] The Killers - Hot Fuss

Other Template Libraries

You are free to use any other template library in MVC. You do need to register the special type attribute for your template system on the app object.
//Set the type for handlebars template system
app.setViewType("text/x-handlebars-template");


Please refer to the documentation for your selected template library on how to use them. There are numerous out there, so make sure it's lightweight and fast!

Hash Change Routes

You can listen for routing changes based off the hash change event by registering the listener on your app.
app.listenHashChange(); //Listen for hash change events
This will dispatch routes based off the URI structure of the hash. Take for example the hash you see above
docmvc.php#routes/cnt_hash //Full page

#routes/cnt_hash //hash
What we do is replace the hash symbol (#) with a forward slash to create our route
/routes/cnt_hash //Replace the # with a / to create the route
We now have the route being dispatched to the "routes" controller with actino of "cnt_hash"

Routes

You can setup functions based off routes instead of controllers. They look similar to REST routes, but you can not specify the type (GET/POST/DELETE). They work in the same pattern as the controllers, in that you have.
  1. controller
  2. action
  3. {parameters}
$.mvc.addRoute("/foo/bar",function(){
   var args=arguments;
   console.log("Foo bar",arguments);
});
The above would handle any routes going to "/foo/bar" and then passes in any arguments after via the arguments array.
$.mvc.addRoute("/foo/bar",function(){
   var args=arguments;
   console.log("Foo bar",arguments);
});
$.mvc.addRoute("/foo/foo",function(){
   var args=arguments;
   console.log("Foo foo",arguments);
});
$.mvc.addRoute("/foo",function(){
   var args=arguments;
   console.log("Foo",arguments);
});
With the above, we have three routes registered that can be called. Here are five calls and what the expected output would be from the console.log functions

MVC configuration

MVC a few configuration properties. We will go over those below, along with what they do. The configuration properties are set off the $.mvc.app object you create.

var app = new $.mvc.app();

app.controllersDir("path"); //Set the directory to your controllers folder if it is not named "controllers".
app.modelsDir("path"); //Set the directory to your models folder if it is not named "models";
app.ready(function); //Function that executes as soon as the MVC app has completed loading, or right away if it 
                     // has already loaded.