Skip to content
sv01a edited this page Apr 27, 2012 · 2 revisions

This code comes from Scott's app: commoncurriculum.com. Much of it has been simplified to make it easy to get the big picture.

Directory Structure

app/ -models/ -bindings/ application.js router.js fetchers.js

Application.js

var App = {
    VMs : {
        ,unit   : ko.observable()
        ,course : ko.observable()
        ,user   : ko.observable()
    } 
    ,template : ko.observable()
    ,fetchers : {}
    ,trackers :  {}
    ,init: function() {
        new App.Router();
        Backbone.history.start();
        addTrackers(App.VMs.trackers)
        ko.applyBindings(App.VMs)
    }
} 

Router.js

Download the latest backbone.js and include the JS in your app.

App.Router = Backbone.Router.extend({
    routes : {
        "" : "home"
        ,"!/:username" : "show_user"
        ,"!/:username/:course_slug": "show_course"
        ,"!/:username/:course_slug/:unit_id": "show_unit"
        // MORE ROUTES....
    }

    ,home : function(){
            Backbone.history.navigate("#!/" + cu.username, true) //cu is a javascript variable set on the server side when the page is served. It stands for current_user
    }


    ,show_user : function(username){
            App.fetchers.user(username, function(user){
                App.VMs.user(user)
                App.template('show_user')
            }
    }

    ,show_course : function(username, course_slug){
        App.fetchers.course(username, course_slug, function(course){
            App.VMs.course(course)
            App.template('show_course')
        })
    }

    ,show_unit = function(){ .... }

}

Fetchers.js

App.fetchers = {
    course : function(username, course_slug, cb){
        amplify.request('course#read',
            { username: username, course_slug : course_slug},
            function(data){
                if( typeof cb !== 'undefined') {cb(new Course(data))}
            }
        )
    }
    ,user :  function(username, cb){
        // ....
    }
    ,unit : function(data, cb){
        // ....
    }
}


amplify.request.define('user#read', "ajax", {
    url : "api/v1/users/{username}"
    ,dataType: "json"
    ,type: "GET"
})

amplify.request.define('user#update', "ajax", {
    url : "api/v1/users/{username}"
    ,dataType: "json"
    ,type: "PUT"
})

// MORE AMPLIFY.JS REQUESTS HERE....

Models....

var Unit = function(data){
    var _this = this

    // Here are a couple of the instance methods I use instance methods in the model
    this.older_sibling = function(){
        this.siblings()[this.position]
    }

    this.has_younger_sibling = function(){
        return this.position() == 1 ? false : true
    }

    this.younger_sibling = function(){
        return this.siblings()[this.position() -1]
    }

    // I LOVE the mappings plugin. I put the mappings in my model. 
    // The first mapping allows me to use recursion to create nested models!
    var mappings = {
        'units' : {
            key: function(item) {
                return ko.utils.unwrapObservable(item.id);
            }
            ,create: function(options){
                return new Unit(options.data) 
            }
        }

        ,'documents' : {
            create : function(options){
                return new Document(options.data)
            }
        }
        
    } 

    // this makes the mappings work
    ko.mapping.fromJS(data, mappings, this)

    // I also put my persistence methods in my model.
    this.save = function(course){
        amplify.request({
            resourceId: 'unit#update'
            ,data: data
            ,success: function(){show_save()}
            ,error: function(){lost_connection()}
        })
    }

    this.destroy = function(course, context){
        var _this = this
        if (confirm("Are you sure you want to delete this?")){ 
            amplify.request({
                resourceId: 'unit#delete'
                ,data: {  username : course().username()
                    ,course_slug : course().slug()
                    ,id : this.id()
                }
                ,success: function(){
                    context.remove(_this);
                    show_save();
                }
                ,error: function(){lost_connection()}
            })
        }    
    }

    // I let knockout track all the changes to my model and use the dirty flag that Ryan made
    // http://www.knockmeout.net/2011/05/creating-smart-dirty-flag-in-knockoutjs.html
    this.dirtyFlag = new ko.dirtyFlag(_this, false)

    // I track the changes to my nested models so that when they change, they're automatically saved.
    this.dirtyUnits = ko.dependentObservable(function() {
        return ko.utils.arrayFilter(this.units(), function(unit) {
            if (typeof unit.dirtyFlag !== 'undefined') {
                return unit.dirtyFlag.isDirty();
            }
        });
    }, this);

    this.dirtyDocuments = ko.dependentObservable(function() {
        return ko.utils.arrayFilter(this.documents(), function(document) {
            if (typeof document.dirtyFlag !== 'undefined') {
                return document.dirtyFlag.isDirty();
            }
        });
    }, this);

    this.tracked_units = ko.dependentObservable(function(){
        _.each(this.dirtyUnits(), function(unit){
            unit.save(App.VMs.course)
            unit.dirtyFlag.reset()
        })
    }, this)

    this.tracked_documents = ko.dependentObservable(function(){
        _.each(this.dirtyDocuments(), function(doc){
            doc.save(App.VMs.course())
            doc.dirtyFlag.reset()
        })
    }, this)

}

// I like to control what I serialize back to the server, so I change the toJSON. Read more here:
// http://www.knockmeout.net/2011/04/controlling-how-object-is-converted-to.html
Unit.prototype.toJSON = function(){
    var copy = ko.mapping.toJS(this); 
    copy.doc_ids = _.map(copy.documents, function(document){return document.id}); 
    copy.content_ids = _.map(copy.contents, function(content){return content.id}); 
    copy.children_ids = _.map(copy.units, function(unit){return unit.id});
    return {
        id : copy.id
        ,title : copy.title
        ,subtitle : copy.subtitle
        ,text : copy.text
        ,content_ids : copy.content_ids
        ,children_ids : copy.children_ids
        ,nested_children :copy.nested_children
        ,doc_ids : copy.doc_ids
    }
}