« Back to Index

Cranium (Backbone API)

View original Gist on GitHub

cranium.js

/* Cranium MVC
 * A minimalist MVC implementation written for
 * demonstration purposes at my workshops
 * http://addyosmani.com
 * Copyright (c) 2012 Addy Osmani; Licensed MIT */


var Cranium = Cranium || {};

// Set DOM selection utility
var $ = document.querySelector.bind(document) || this.jQuery || this.Zepto;


// Mix in to any object in order to provide it with custom events.
var Events = Cranium.Events = {
        channels: {},
        eventNumber: 0,
        trigger: function (events, data) {
            for (var topic in Cranium.Events.channels){
                if (Cranium.Events.channels.hasOwnProperty(topic)) {
                    if (topic.split("-")[0] == events){
                        Cranium.Events.channels[topic](data) !== false || delete Cranium.Events.channels[topic];
                    }
                }
            }
        },
        on: function (events, callback) {
            Cranium.Events.channels[events + --Cranium.Events.eventNumber] = callback;
        },
        off: function(topic) {
            delete Cranium.Events.channels[topic];
        }            
};


// Domain-related data model
var Model = Cranium.Model = function (attributes) {
    this.id = _.uniqueId('model');
    this.attributes = attributes || {};    
};

Cranium.Model.prototype.get = function(attr) {
    return this.attributes[attr];
};
                 
Cranium.Model.prototype.set = function(attrs){
    if (_.isObject(attrs)) {
      _.extend(this.attributes, attrs);
      this.change(attrs);
    }
    return this;
};
            
Cranium.Model.prototype.toJSON = function(options) {
    return _.clone(this.attributes);
};

Cranium.Model.prototype.change = function(attrs){
    this.trigger(this.id + 'update', attrs);
}; 

_.extend(Cranium.Model.prototype, Cranium.Events);


// DOM View                                    
var View = Cranium.View = function (options) {
    _.extend(this, options); 
    this.id = _.uniqueId('view');
};

_.extend(Cranium.View.prototype, Cranium.Events);


// Controller tying together a model and view
var Controller = Cranium.Controller = function(options){
    _.extend(this, options); 
    this.id = _.uniqueId('controller');
    var parts, selector, eventType;
    if(this.events){
        _.each(this.events, function(method, eventName){
            parts = eventName.split('.');
            selector = parts[0];
            eventType = parts[1];
            $(selector)['on' + eventType] = this[method];
        }.bind(this));
    }    
};

markup.html

<div class="container">Foo</div>

<button id="inc">Increment</button>
<button id="alerter">Alert</button>

<script type="text/template" class="counter-template">
    <h1><%- counter %></h1>
</script>​

readme.md

Cranium is a minimalist MVC implementation I wrote to demonstrate how a developer might write their own MVC library within a short space of time. The purpose of this exercise is to better appreciate what solutions like Backbone.js provide you out of the box.

You may use jQuery and Underscore (or lo-dash) for your implementation.

Note: This code is released strictly for educational purposes.

usage.js

// Let's create a basic application

var myModel = new Cranium.Model({
    counter: 0,
    incr: function () {
        myModel.set({ counter: ++this.counter });
    }
});


var myView = new Cranium.View({

    el: '.container',
    template: _.template($('.counter-template').innerHTML),
    
    observe: function (model) {
        this.on(model.id + 'update', function (data) {
            
           $(this.el).innerHTML = this.template( model.toJSON() );
            
        }.bind(this));
    }   
});

var myController = new Cranium.Controller({

    // Specify the model to update
    model: myModel,

    // and the view to observe this model
    view:  myView,
    
    events: {
        "#inc.click" : "increment",
        "#alerter.click" : "alerter"
    },

    // Initialize everything
    initialize: function () {
        this.view.observe(this.model);
        return this;
    },
    increment: function () {
        myController.model.attributes.incr();
        return this;
    },
    alerter: function(){
       alert("Yo!"); 
    }
});


// Let's kick start things off
myController.initialize(myModel, myView).increment().increment();


// Some further experiments with Underscore utils
var myModel2 = new Cranium.Model({
    caption: 'hello!'
});
            
console.log(_.any([myModel, myModel2, null]));
console.log(_.pluck([myModel, myModel2], 'id'));
console.log(_.shuffle([myModel, myModel2]));