Scott's method

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
    }
}
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Press h to open a hovercard with more details.