This isn't your typical backbone.js scenario but its fortunately easy to solve, and special thanks to Joey Beninghove for shaving several hours of hard work off my plate by helping me arrive at a solution.
My problem domain is this: we (by we I mean an intern at my company) are building a tool that displays this structure called a package. The package looks something like this...
package |- nodes [0..n] |- node |- assets [0-n] | |- asset |- actions [0-n] |- actionEssentially a tree of collections. Behind the scenes all of these models (package, node, asset, action) are full blown models. In this particular case though we wanted to display the structure of a package which means we get the entire package's JSON all at once, like a giant turd (snippet):
{ "CreateTime" : "/Date(1283319058700)/", "Id" : 10000, "ModifyTime" : "/Date(1283319060013)/", "Name" : "Some Package Name", "Nodes" : [ { "Actions" : [ { "FromNodeId" : 53730, "Name" : "Dismiss", "ToNodeId" : 0 }, { "FromNodeId" : 53730, "Name" : "View", "ToNodeId" : 53731 } ], "Assets" : [ { "AssetBinaryId" : null, "AssetClass" : { "Id" : 8, "Name" : "ImageURL" }, "AssetFormat" : { "Id" : 1, "Name" : "PNG" }, "Data" : "/SomeUrlHere?querystring", "Id" : 52616, "Name" : "Banner Image", "NodeId" : 53730, } ], "Name" : "Banner", "NodeClass" : { "Id" : 1, "Name" : "Banner" } }, ⋮ }
As I was working the solution at the time in a regular HTML file I was relying on console.log and Safari's excellent developer tools. (Note: to retrieve JSON data from a local file in Chrome you have to launch Chrome with a command line argument.) The solution looks something like this (without worrying about the Router or Views):
<html> ⋮ <head> <script type="text/javascript" src="js/vendor/backbone/underscore.js"></script> <script type="text/javascript" src="js/vendor/jquery/jquery-1.6.2.js"></script> <script type="text/javascript" src="js/vendor/backbone/backbone.js"></script> </head> <body> <script type="text/javascript"> window.Action = Backbone.Model.extend({ initialize : function() { this.fromNodeId = this.get('FromNodeId'); this.toNodeId = this.get('ToNodeId'); this.name = this.get('Name'); }, nextNode : function(nodes) { return nodes.detect(function(node) { return node.id == this.toNodeId}, this); }, previousNode : function(nodes) { return nodes.detect(function(node) { return node.id == this.fromNodeId}, this); } }); window.Actions = Backbone.Collection.extend({ model: Action }); window.Asset = Backbone.Model.extend({ initialize : function() { this.assetBinaryId = this.get('AssetBinaryId'); this.data = this.get('Data'); this.id = this.get('Id'); this.name = this.get('Name'); } }); window.Assets = Backbone.Collection.extend({ model: Asset }); window.Node = Backbone.Model.extend({ initialize: function() { this.assets = new Assets(this.get('Assets')); this.actions = new Actions(this.get('Actions')); }, }); window.Nodes = Backbone.Collection.extend({ model: Node }); window.Package = Backbone.Model.extend({ initialize : function() { // the following line forces 'this' to refer to the Package instance in the // function `fetch_success` _.bindAll(this, 'fetch_success'); this.bind('change', this.fetch_success); }, // specifying the URL as a function gives us a bit more flexibility url : function() { return "data/package/" + this.id + ".json" }, // invoked automatically when the change event is invoked which happens when fetch is successful fetch_success : function() { this.nodes = new Nodes(this.get('Nodes')); this.createTime = this.get('CreateTime'); this.modifyTime = this.get('ModifyTime'); this.name = this.get('Name'); } }); <!-- and... --> $(document).ready(function() { pkg = new Package({id:"10000"}); pkg.fetch({ failure: function(model, response) { console.error("ERROR"); console.log(response); }}); window.pkg = pkg; console.log(pkg); </script> ⋮ </body> </html>