Skip to content

Instantly share code, notes, and snippets.

@addyosmani
Last active April 5, 2025 08:12

Revisions

  1. addyosmani revised this gist Jul 18, 2014. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion cranium.js
    Original file line number Diff line number Diff line change
    @@ -8,7 +8,7 @@
    var Cranium = Cranium || {};

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


    // Mix in to any object in order to provide it with custom events.
  2. addyosmani revised this gist Feb 22, 2013. 2 changed files with 4 additions and 1 deletion.
    5 changes: 4 additions & 1 deletion markup.html → example.html
    Original file line number Diff line number Diff line change
    @@ -5,4 +5,7 @@

    <script type="text/template" class="counter-template">
    <h1><%- counter %></h1>
    </script>
    </script>
    <script src="underscore-min.js"></script>
    <script src="cranium.js"></script>
    <script src="example.js"></script>
    File renamed without changes.
  3. addyosmani revised this gist Feb 22, 2013. 2 changed files with 0 additions and 1 deletion.
    File renamed without changes.
    1 change: 0 additions & 1 deletion todo_example.js
    Original file line number Diff line number Diff line change
    @@ -1,4 +1,3 @@

    // And todo instance
    var todo1 = new Cranium.Model({
    title: "",
  4. addyosmani revised this gist Feb 22, 2013. 3 changed files with 84 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions readme.md
    Original file line number Diff line number Diff line change
    @@ -1,5 +1,5 @@
    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.
    Cranium is a minimalist MVC implementation I wrote to demonstrate how a developer might write their own MVC library within 20 minutes. 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.
    Note: This code is released strictly for educational purposes and should not be used in production. If you are searching for an MV* framework, TodoMVC.com may help with your selection process.
    21 changes: 21 additions & 0 deletions todo_example.html
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,21 @@
    <!doctype html>
    <html lang="en">
    <head>
    <meta charset="utf-8">
    <title></title>
    <meta name="description" content="">
    </head>
    <body>
    <div id="todo">
    </div>
    <script type="text/template" class="todo-template">
    <div>
    <input id="todo_complete" type="checkbox" <%= completed %>>
    <%= title %>
    </div>
    </script>
    <script src="underscore-min.js"></script>
    <script src="cranium.js"></script>
    <script src="todo_example.js"></script>
    </body>
    </html>
    61 changes: 61 additions & 0 deletions todo_example.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,61 @@

    // And todo instance
    var todo1 = new Cranium.Model({
    title: "",
    completed: ""
    });

    console.log("First todo title - nothing set: " + todo1.get('title'));
    todo1.set({title: "Do something"});
    console.log("Its changed now: " + todo1.get('title'));
    ''
    // View instance
    var todoView = new Cranium.View({
    // DOM element selector
    el: '#todo',

    // Todo template; Underscore temlating used
    template: _.template($('.todo-template').innerHTML),

    init: function (model) {
    this.render( model.toJSON() );

    this.on(model.id + 'update', this.render.bind(this));
    },
    render: function (data) {
    console.log("View about to render.");
    $(this.el).innerHTML = this.template( data );
    }
    });

    var todoController = new Cranium.Controller({
    // Specify the model to update
    model: todo1,

    // and the view to observe this model
    view: todoView,

    events: {
    "#todo.click" : "toggleComplete"
    },

    // Initialize everything
    initialize: function () {
    this.view.init(this.model);
    return this;
    },
    // Toggles the value of the todo in the Model
    toggleComplete: function () {
    var completed = todoController.model.get('completed');
    console.log("Todo old 'completed' value?", completed);
    todoController.model.set({ completed: (!completed) ? 'checked': '' });
    console.log("Todo new 'completed' value?", todoController.model.get('completed'));
    return this;
    }
    });


    // Let's kick start things off
    todoController.initialize();

    todo1.set({ title: "Due to this change Model will notify View and it will re-render"});
  5. addyosmani revised this gist Nov 27, 2012. 1 changed file with 3 additions and 1 deletion.
    4 changes: 3 additions & 1 deletion readme.md
    Original file line number Diff line number Diff line change
    @@ -1,3 +1,5 @@
    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.

    Note: This code is released strictly for educational purposes. There are almost certainly parts of this code that may change/be improved prior to my workshops.
    You may use jQuery and Underscore (or lo-dash) for your implementation.

    Note: This code is released strictly for educational purposes.
  6. addyosmani revised this gist Sep 23, 2012. 1 changed file with 4 additions and 1 deletion.
    5 changes: 4 additions & 1 deletion cranium.js
    Original file line number Diff line number Diff line change
    @@ -26,7 +26,10 @@ var Events = Cranium.Events = {
    },
    on: function (events, callback) {
    Cranium.Events.channels[events + --Cranium.Events.eventNumber] = callback;
    }
    },
    off: function(topic) {
    delete Cranium.Events.channels[topic];
    }
    };


  7. addyosmani revised this gist Sep 23, 2012. 1 changed file with 5 additions and 9 deletions.
    14 changes: 5 additions & 9 deletions cranium.js
    Original file line number Diff line number Diff line change
    @@ -2,7 +2,7 @@
    * A minimalist MVC implementation written for
    * demonstration purposes at my workshops
    * http://addyosmani.com
    * Copyright (c) 2012 Addy Osmani; Licensed MIT, GPL */
    * Copyright (c) 2012 Addy Osmani; Licensed MIT */


    var Cranium = Cranium || {};
    @@ -14,7 +14,7 @@ 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: {},
    b: 0,
    eventNumber: 0,
    trigger: function (events, data) {
    for (var topic in Cranium.Events.channels){
    if (Cranium.Events.channels.hasOwnProperty(topic)) {
    @@ -25,7 +25,7 @@ var Events = Cranium.Events = {
    }
    },
    on: function (events, callback) {
    Cranium.Events.channels[events + --Cranium.Events.b] = callback;
    Cranium.Events.channels[events + --Cranium.Events.eventNumber] = callback;
    }
    };

    @@ -73,16 +73,12 @@ var Controller = Cranium.Controller = function(options){
    _.extend(this, options);
    this.id = _.uniqueId('controller');
    var parts, selector, eventType;
    var self = this;
    if(this.events){
    _.each(this.events, function(method, eventName){
    parts = eventName.split('.');
    selector = parts[0];
    eventType = parts[1];
    $(selector)['on' + eventType] = self[method];
    });
    $(selector)['on' + eventType] = this[method];
    }.bind(this));
    }
    };


  8. addyosmani revised this gist Sep 23, 2012. 2 changed files with 55 additions and 57 deletions.
    55 changes: 0 additions & 55 deletions cranium.js
    Original file line number Diff line number Diff line change
    @@ -85,59 +85,4 @@ var Controller = Cranium.Controller = function(options){
    };



    ////////////////////////////////////////////////////////////////////////////////////////////////

    // Usage

    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!");
    }
    });


    57 changes: 55 additions & 2 deletions usage.js
    Original file line number Diff line number Diff line change
    @@ -1,11 +1,64 @@
    // Usage
    // 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!'
    });

    // Underscore utils
    console.log(_.any([myModel, myModel2, null]));
    console.log(_.pluck([myModel, myModel2], 'id'));
    console.log(_.shuffle([myModel, myModel2]));
  9. addyosmani renamed this gist Sep 23, 2012. 1 changed file with 0 additions and 0 deletions.
    File renamed without changes.
  10. addyosmani created this gist Sep 23, 2012.
    143 changes: 143 additions & 0 deletions cranium.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,143 @@
    /* Cranium MVC
    * A minimalist MVC implementation written for
    * demonstration purposes at my workshops
    * http://addyosmani.com
    * Copyright (c) 2012 Addy Osmani; Licensed MIT, GPL */


    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: {},
    b: 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.b] = callback;
    }
    };


    // 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;
    var self = this;
    if(this.events){
    _.each(this.events, function(method, eventName){
    parts = eventName.split('.');
    selector = parts[0];
    eventType = parts[1];
    $(selector)['on' + eventType] = self[method];
    });
    }
    };



    ////////////////////////////////////////////////////////////////////////////////////////////////

    // Usage

    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!");
    }
    });


    8 changes: 8 additions & 0 deletions markup.html
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,8 @@
    <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>
    3 changes: 3 additions & 0 deletions readme.txt
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,3 @@
    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.

    Note: This code is released strictly for educational purposes. There are almost certainly parts of this code that may change/be improved prior to my workshops.
    12 changes: 12 additions & 0 deletions usage.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,12 @@
    // Usage
    myController.initialize(myModel, myView).increment().increment();

    var myModel2 = new Cranium.Model({
    caption: 'hello!'
    });

    // Underscore utils
    console.log(_.any([myModel, myModel2, null]));
    console.log(_.pluck([myModel, myModel2], 'id'));
    console.log(_.shuffle([myModel, myModel2]));