Skip to content

Instantly share code, notes, and snippets.

@jupiterjs
Created March 12, 2011 05:43
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save jupiterjs/867069 to your computer and use it in GitHub Desktop.
Save jupiterjs/867069 to your computer and use it in GitHub Desktop.
JavaScriptMVC Overview

Introduction

JavaScriptMVC is an open-source jQuery-based JavaScript framework. It is nearly a comprehensive (holistic) front-end development framework; packaging utilities for:

  • testing
  • dependency management
  • error reporting
  • package management
  • code cleaning
  • custom events
  • jQuery extensions
  • documentation

The library is broken into 4 mostly independent sub-projects:

essentially everything but UI widgets. However, JMVC's MVC parts are only 7k gzipped.

Everything is a plugin

JavaScriptMVC is broken down into 4 independent sub-projects:

  • StealJS - Dependency Management, Code Generators, Production builds, Code cleaning.
  • FuncUnit - Web testing framework
  • DocumentJS - JS documentation framework
  • jQueryMX - jQuery MVC extentions

In the download, these are arranged into the following folders

funcunit
documentjs
jquery
steal

Within each of these folders, are numerous sub-plugins. For example, JavaScriptMVC's controller is found in jquery/controller/controller.js.

JavaScriptMVC encourages you to use a similar folder structure. We organized the todo app at the end of the application like:

funcunit
documentjs
jquery
steal
todo/
  todo.js
  todo.html

StealJS - Dependency Management

JavaScriptMVC uses StealJS for dependency management and production builds. To use steal, you just have to load the steal script in your page and point it to a script file that loads your other files. For example, putting the following in todo.html loads steal.js and tells it to load todo/todo.js.

<script type='text/javascript' 
        src='../steal/steal.js?todo/todo.js'>
</script>

The todo.js file can then use steal to load any dependencies it needs. JavaScriptMVC comes with the jQuery library in 'jquery/jquery.js'.
The following loads jQuery and uses it to write Todo List:

steal('../jquery/jquery').then(function(){
  $(document.body).append("<h1>Todo List</h1>")
})

Because loading from JavaScriptMVC's root folder is extremely common, steal provides a plugins helper method. We can write the above as:

steal.plugins('jquery').then(function(){
  $(document.body).append("<h1>Todo List</h1>")
})

Loading Non JavaScript resources

Steal can load and build other resource types as well: client side templates, css, LessCSS and CoffeeScript. The following uses an jQuery.tmpl template in todos/views/list.tmpl to write todo list:

steal.plugins('jquery','jquery/view/tmpl')
     .views("list.tmpl")
     .then(function(){

  $(document.body).append("//todo/views/list.tmpl",{message: "Todo List"} )
})

Compression

Having lots of files is very slow for page loading times. StealJS makes building your app into a single JS and CSS file very easy. To generate the files, run:

js steal/buildjs todo/todo.html

For our mini todo app, this produces:

todo/production.js

To use production.js, we just have to load the production version of steal. We do this by changing our steal script tag to:

<script type='text/javascript' 
        src='../steal/steal.js?todo/todo.js'>
</script>

Class

JavaScriptMVC's $.Controller and $.Model are based on it's Class helper - $.Class. $.Class is based on John Resig's simple class. It adds several important features, namely:

  • static methods and properties
  • introspection
  • namespaces
  • callback creation

Creating a $.Class and extending it with a new class is straightforward:

$.Class("Animal",{
  breath : function(){
     console.log('breath'); 
  }
});

Animal("Dog",{
  wag : function(){
    console.log('wag');
  }
})

var dog = new Dog;
dog.wag();
dog.breath();

When a new $.Class is created, it calls the class's init method with the arguments passed to the constructor function:

$.Class('Person',{
  init : function(name, age){
    this.name = name;
    this.age = age;
  },
  speak : function(){
    return "I am "+this.name+".";
  }
});

var justin = new Person("Justin",28);
justin.speak(); //-> 'I am Justin.'

$.Class lets you call base functions with this._super. Lets make a 'classier' person:

Person("ClassyPerson", {
  speak : function(){
    return "Salutations, "+this._super();
  }
});

var fancypants = new ClassyPerson("Mr. Fancy",42);
fancypants.speak(); //-> 'Salutations, I am Mr. Fancy.'

Class provides a callback method that can be used to return a function that has 'this' set appropriately (similar to proxy):

$.Class("Clicky",{
  init : function(){
    this.clickCount = 0;
  },
  wasClicked : function(){
    this.clickCount++;
  },
  addListeners : function(el){
    el.click(this.callback('wasClicked');
  }
})

Callback also lets you curry arguments and chain methods together.

Class lets you define inheritable static properties and methods:

$.Class("Person",{
  findOne : function(id, success){
    $.get('/person/'+id, function(attrs){
      success( new Person( attrs ) );
    },'json')
  }
},{
  init : function(attrs){
    $.extend(this, attrs)
  },
  speak : function(){
    return "I am "+this.name+".";
  }
})

Person.findOne(5, function(person){
  alert( person.speak() );
})

Class also provides namespacing and access to the name of the class and namespace object:

$.Class("Jupiter.Person");

Jupiter.Person.shortName; //-> 'Person'
Jupiter.Person.fullName;  //-> 'Jupiter.Person'
Jupiter.Person.namespace; //-> Jupiter

Putting this all together, we can make a basic ORM-style model layer:

$.Class("ORM",{
  findOne : function(id, success){
    $.get('/'+this.fullName.toLowerCase()+'/'+id, 
      this.callback(function(attrs){
         success( new this( attrs ) );
      })
    },'json')
  }
},{
  init : function(attrs){
    $.extend(this, attrs)
  }
})

ORM("Person",{
  speak : function(){
    return "I am "+this.name+".";
  }
});

Person.findOne(5, function(person){
  alert( person.speak() );
});

ORM("Task")

Task.findOne(7,function(task){
  alert(task.name);
})

This is similar to how JavaScriptMVC's model layer works.

Models

JavaScriptMVC's model and it associated plugins provide lots of tools around organizing model data such as validations, associations, events, lists and more. But the core functionality is centered around service encapsulation and type conversion.

Service Encapsulation

Model makes it crazy easy to connect to JSON REST services and add helper methods to the resulting data. For example, take a todos service that allowed you to create, retrieve, update and delete todos like:

POST /todos name=laundry dueDate=1300001272986 -> {'id': 8}
GET /todos -> [{'id': 5, 'name': "take out trash", 'dueDate' : 1299916158482},
               {'id': 7, 'name': "wash dishes", 'dueDate' : 1299996222986},
               ... ]
GET /todos/5 -> {'id': 5, 'name': "take out trash", 'dueDate' : 1299916158482}
PUT /todos/5 name=take out recycling -> {}
DELETE  /todos/5 -> {}

Making a Model that can connect to these services and add helper functions is shockingly easy:

$.Model("Todo",{
  findAll : "GET /todos",
  findOne : "GET /todos/{id}",
  create  : "POST /todos",
  update  : "PUT  /todos/{id}",
  destroy : "DELETE  /todos/{id}"
},{
  daysRemaining : function(){
    return ( new Date(this.dueDate) - new Date() ) / 86400000
  }
});

This allows you to

// List all todos
Todo.findAll({}, function(todos){
  var html = [];
  for(var i =0; i < todos.length; i++){
    html.push(todos[i].name+" is due in "+
              todos[i].daysRemaining()+
              "days")
  }
  $('#todos').html("<li>"+todos.join("</li><li>")+"</li>")
})

//Create a todo
new Todo({
  name: "vacuum stairs", 
  dueDate: new Date()+86400000
}).save(function(todo){
  alert('you have to '+todo.name+".")
});

//update a todo
todo.update({name: "vacuum all carpets"}, function(todo){
  alert('updated todo to '+todo.name+'.')
});

//destroy a todo
todo.destroy(function(todo){
  alert('you no longer have to '+todo.name+'.')
});

Of course, you can supply your own functions.

Events

Although encapsulating ajax requests in a model is valuable, there's something even more important about models to an MVC architecture - events. $.Model lets you listen model events. You can listen to models being updated, destroyed, or even just having single attributes changed.

$.Model produces two types of events:

  • OpenAjax.hub events
  • jQuery events

Each has advantages and disadvantages for particular situations. For now we'll deal with jQuery events. Lets say we wanted to know when a todo is created and add it to the page. And after it's been added to the page, we'll listen for updates on that todo to make sure we are showing its name correctly. We can do that like:

$(Todo).bind('created', function(todo){
  var el = $('<li>').html(todo.name);
  el.appendTo($('#todos'));
  todo.bind('updated', function(todo){
    el.html(todo.name)
  })
})

Getter / Setters ?

Model.Lists

Often, in complex JS apps, you're dealing with discrete lists of items. For example, you might have two todo lists on the page at once.

Model has the model list plugin to help with this.

$.Model.List("Todo.List")

Controllers

JavaScriptMVC's controllers are really a jQuery plugin factory. They can be used as a traditional view, for example, making a slider widget, or a traditional controller, creating view-controllers and binding them to models.

  • jQuery helper
  • auto bind / unbind
  • parameterized actions
  • defaults
  • pub / sub

Views

JavaScriptMVC's views are really just client side templates.

Putting it all together ...

Events

Building a todo list

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment