Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Knockback-Inspector initial commit

  • Loading branch information...
commit 4efd48148246e78176ba9525a71d5c7427090f8b 0 parents
@kmalakoff authored
Showing with 20,975 additions and 0 deletions.
  1. +1 −0  .gitignore
  2. +25 −0 Cakefile
  3. +22 −0 LICENSE
  4. +69 −0 README.md
  5. +32 −0 knockback-inspector.css
  6. +141 −0 knockback-inspector.js
  7. +9 −0 knockback-inspector.min.js
  8. +26 −0 package.json
  9. +10 −0 src/lib/kbi_core.coffee
  10. +15 −0 src/lib/kbi_string_template_engine.coffee
  11. +19 −0 src/models/kbi_fetched.coffee
  12. +17 −0 src/view_models/kbi_node_view_model.coffee
  13. +14 −0 src/views/kbi_collection_node_view.coffee
  14. +29 −0 src/views/kbi_model_node_view.coffee
  15. +110 −0 test/index.html
  16. +21 −0 tutorials/coffeescript/step1/Cakefile
  17. +37 −0 tutorials/coffeescript/step1/index.html
  18. +19 −0 tutorials/coffeescript/step1/javascripts/lib/kbi_core.js
  19. +10 −0 tutorials/coffeescript/step1/src/lib/kbi_core.coffee
  20. +21 −0 tutorials/coffeescript/step2/Cakefile
  21. +62 −0 tutorials/coffeescript/step2/index.html
  22. +19 −0 tutorials/coffeescript/step2/javascripts/lib/kbi_core.js
  23. +43 −0 tutorials/coffeescript/step2/javascripts/lib/kbi_string_template_engine.js
  24. +35 −0 tutorials/coffeescript/step2/javascripts/view_models/kbi_node_view_model.js
  25. +6 −0 tutorials/coffeescript/step2/javascripts/views/kbi_collection_node_view.js
  26. +6 −0 tutorials/coffeescript/step2/javascripts/views/kbi_model_node_view.js
  27. +10 −0 tutorials/coffeescript/step2/src/lib/kbi_core.coffee
  28. +15 −0 tutorials/coffeescript/step2/src/lib/kbi_string_template_engine.coffee
  29. +17 −0 tutorials/coffeescript/step2/src/view_models/kbi_node_view_model.coffee
  30. +14 −0 tutorials/coffeescript/step2/src/views/kbi_collection_node_view.coffee
  31. +29 −0 tutorials/coffeescript/step2/src/views/kbi_model_node_view.coffee
  32. +21 −0 tutorials/coffeescript/step3/Cakefile
  33. +74 −0 tutorials/coffeescript/step3/index.html
  34. +19 −0 tutorials/coffeescript/step3/javascripts/lib/kbi_core.js
  35. +43 −0 tutorials/coffeescript/step3/javascripts/lib/kbi_string_template_engine.js
  36. +35 −0 tutorials/coffeescript/step3/javascripts/view_models/kbi_node_view_model.js
  37. +6 −0 tutorials/coffeescript/step3/javascripts/views/kbi_collection_node_view.js
  38. +6 −0 tutorials/coffeescript/step3/javascripts/views/kbi_model_node_view.js
  39. +10 −0 tutorials/coffeescript/step3/src/lib/kbi_core.coffee
  40. +15 −0 tutorials/coffeescript/step3/src/lib/kbi_string_template_engine.coffee
  41. +17 −0 tutorials/coffeescript/step3/src/view_models/kbi_node_view_model.coffee
  42. +14 −0 tutorials/coffeescript/step3/src/views/kbi_collection_node_view.coffee
  43. +29 −0 tutorials/coffeescript/step3/src/views/kbi_model_node_view.coffee
  44. +21 −0 tutorials/coffeescript/step4/Cakefile
  45. +73 −0 tutorials/coffeescript/step4/index.html
  46. +19 −0 tutorials/coffeescript/step4/javascripts/lib/kbi_core.js
  47. +43 −0 tutorials/coffeescript/step4/javascripts/lib/kbi_string_template_engine.js
  48. +54 −0 tutorials/coffeescript/step4/javascripts/models/kbi_fetched.js
  49. +35 −0 tutorials/coffeescript/step4/javascripts/view_models/kbi_node_view_model.js
  50. +6 −0 tutorials/coffeescript/step4/javascripts/views/kbi_collection_node_view.js
  51. +6 −0 tutorials/coffeescript/step4/javascripts/views/kbi_model_node_view.js
  52. +10 −0 tutorials/coffeescript/step4/src/lib/kbi_core.coffee
  53. +15 −0 tutorials/coffeescript/step4/src/lib/kbi_string_template_engine.coffee
  54. +19 −0 tutorials/coffeescript/step4/src/models/kbi_fetched.coffee
  55. +17 −0 tutorials/coffeescript/step4/src/view_models/kbi_node_view_model.coffee
  56. +14 −0 tutorials/coffeescript/step4/src/views/kbi_collection_node_view.coffee
  57. +29 −0 tutorials/coffeescript/step4/src/views/kbi_model_node_view.coffee
  58. +39 −0 tutorials/javascript/step1/index.html
  59. +10 −0 tutorials/javascript/step1/javascripts/lib/kbi_core.js
  60. +63 −0 tutorials/javascript/step2/index.html
  61. +10 −0 tutorials/javascript/step2/javascripts/lib/kbi_core.js
  62. +17 −0 tutorials/javascript/step2/javascripts/lib/kbi_string_template_engine.js
  63. +21 −0 tutorials/javascript/step2/javascripts/view_models/kbi_node_view_model.js
  64. +13 −0 tutorials/javascript/step2/javascripts/views/kbi_collection_node_view.js
  65. +27 −0 tutorials/javascript/step2/javascripts/views/kbi_model_node_view.js
  66. +75 −0 tutorials/javascript/step3/index.html
  67. +10 −0 tutorials/javascript/step3/javascripts/lib/kbi_core.js
  68. +17 −0 tutorials/javascript/step3/javascripts/lib/kbi_string_template_engine.js
  69. +21 −0 tutorials/javascript/step3/javascripts/view_models/kbi_node_view_model.js
  70. +13 −0 tutorials/javascript/step3/javascripts/views/kbi_collection_node_view.js
  71. +27 −0 tutorials/javascript/step3/javascripts/views/kbi_model_node_view.js
  72. +74 −0 tutorials/javascript/step4/index.html
  73. +10 −0 tutorials/javascript/step4/javascripts/lib/kbi_core.js
  74. +17 −0 tutorials/javascript/step4/javascripts/lib/kbi_string_template_engine.js
  75. +26 −0 tutorials/javascript/step4/javascripts/models/kbi_fetched.js
  76. +21 −0 tutorials/javascript/step4/javascripts/view_models/kbi_node_view_model.js
  77. +13 −0 tutorials/javascript/step4/javascripts/views/kbi_collection_node_view.js
  78. +27 −0 tutorials/javascript/step4/javascripts/views/kbi_model_node_view.js
  79. +1,431 −0 vendor/backbone-0.9.2.js
  80. +1,425 −0 vendor/backbone-relational.js
  81. +9,404 −0 vendor/jquery-1.7.2.js
  82. +2,169 −0 vendor/knockback-0.15.1.js
  83. +3,443 −0 vendor/knockout-2.1.0.js
  84. +999 −0 vendor/underscore-1.3.1.js
1  .gitignore
@@ -0,0 +1 @@
+node_modules/*
25 Cakefile
@@ -0,0 +1,25 @@
+{print} = require 'util'
+{spawn} = require 'child_process'
+path = require 'path'
+
+callback = ->
+ spawn 'uglifyjs', ['-o', 'knockback-inspector.min.js', 'knockback-inspector.js']
+ print "#{(new Date).toLocaleTimeString()} - generated knockback-inspector.min.js\n"
+
+task 'build', 'Build javascripts/ from src/', ->
+ coffee = spawn 'coffee', ['-c', '-j', 'knockback-inspector.js', 'src']
+ coffee.stderr.on 'data', (data) ->
+ process.stderr.write data.toString()
+ coffee.stdout.on 'data', (data) ->
+ print data.toString()
+ coffee.on 'exit', (code) ->
+ callback?() if code is 0
+
+task 'watch', 'Watch src/ for changes', ->
+ coffee = spawn 'coffee', ['-w', '-o', '.', '-j', 'knockback-inspector.js', 'src']
+ coffee.stderr.on 'data', (data) ->
+ print 'Error'
+ process.stderr.write data.toString()
+ coffee.stdout.on 'data', (data) ->
+ print data.toString()
+ callback?()
22 LICENSE
@@ -0,0 +1,22 @@
+Copyright (c) 2012 Kevin Malakoff
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
69 README.md
@@ -0,0 +1,69 @@
+````
+ _ __ _ _ _
+| |/ /_ __ ___ ___| | __| |__ __ _ ___| | __
+| ' /| '_ \ / _ \ / __| |/ /| '_ \ / _` |/ __| |/ /
+| . \| | | | (_) | (__| < | |_) | (_| | (__| <
+|_|\_\_| |_|\___/ \___|_|\_\|_.__/ \__,_|\___|_|\_\
+
+
+ ___ _
+|_ _|_ __ ___ _ __ ___ ___| |_ ___ _ __
+ | || '_ \/ __| '_ \ / _ \/ __| __|/ _ \| '__|
+ | || | | \__ \ |_) | __/ (__| |_| (_) | |
+|___|_| |_|___/ .__/ \___|\___|\__|\___/|_|
+ |_|
+````
+
+Knockback-Inspector.js provides an inspector tree view library for Backbone.Models and Backbone.Collections using Knockback.js
+
+### For more information on Knockback.js, please look at the website: http://kmalakoff.github.com/knockback/
+
+The Knockout-Inspector is designed as a small standalone library that you can integrate into your own application for debugging purposes. Just include the library file and optional styling into your HTML file:
+
+- <script src='knockback-inspector.min.js'></script>
+- <link rel='stylesheet' href='knockback-inspector.css'>
+
+along with Knockback.js and its dependencies:
+
+- [Knockout.js]:[https://github.com/SteveSanderson/knockout/downloads/]
+- [Underscore.js]:[http://documentcloud.github.com/underscore/]
+- [Backbone.js]:[http://documentcloud.github.com/backbone/]
+- [Knockback.js]:[http://kmalakoff.github.com/knockback/]
+
+If you want to inspect a model, set up the bindings like:
+
+````
+ <ul id='model' class='kbi' data-bind="template: {name: 'kbi_model_node', data: new kbi.NodeViewModel('root', true, $data)}"></ul>
+ <script type='text/javascript'>
+ var view_model = kb.viewModel(new Backbone.Model({name: 'Hello', place: 'World!'}));
+ ko.applyBindings(view_model, $('#model')[0]);
+ </script>
+````
+
+If you want to inspect a collection, set up the bindings like:
+
+````
+ <ul id='collection' class='kbi' data-bind="template: {name: 'kbi_collection_node', data: new kbi.NodeViewModel('root', true, $data)}"></ul>
+ <script type='text/javascript'>
+ var collection_observable = kb.collectionObservable(new Backbone.Collection([{name: 'Hello', place: 'World!'}, {name: 'Goodbye', place: 'Samsara!'}]));
+ ko.applyBindings(collection_observable, $('#collection')[0]);
+ </script>
+````
+
+For a step-by-step guide to creating and using Knockback-Inspector, please take a look at the full tutorial [here][http://kmalakoff.github.com/knockback/tutorial_inspector_library.html].
+
+
+Building the library
+-----------------------
+
+Installing:
+
+1. install node.js: http://nodejs.org
+2. install node packages: (sudo) 'npm install'
+
+Commands:
+
+1. 'cake build' - performs a single build
+2. 'cake watch' - automatically scans for and builds the project when changes are detected
+
+The library itself is in the root directory, but you can also build each tutorial individually using the Cakefile in their own directories.
32 knockback-inspector.css
@@ -0,0 +1,32 @@
+ul.kbi {
+ list-style-type: none;
+ margin: 0;
+ padding: 0;
+}
+
+li.kbi {
+ padding-left: 1px;
+ margin-left: 10px;
+ border-style: solid;
+ border-color: rgb(90, 0, 180); /* The Fallback */
+ border-color: rgba(90, 0, 180, 0.2);
+ border-width: 1px;
+ border-radius: 5px;
+}
+li.kbi.closed {
+ border-style: none; font-style:italic;
+}
+
+fieldset.kbi {border: none;}
+fieldset.kbi > label {
+ display: block;
+ float: left;
+ width: 25%;
+ text-align: right;
+ margin-right: 10px;
+}
+fieldset.kbi > input {
+ display: block;
+ float: left;
+ width: 70%;
+}
141 knockback-inspector.js
@@ -0,0 +1,141 @@
+// Generated by CoffeeScript 1.3.3
+
+/*
+knockback-inspector.js 0.1.0
+(c) 2012 Kevin Malakoff.
+Knockback-Inspector.js is freely distributable under the MIT license.
+See the following for full license details:
+ https://github.com/kmalakoff/knockback-inspector/blob/master/LICENSE
+Dependencies: Knockout.js, Underscore.js, Backbone.js, and Knockback.js.
+*/
+
+
+(function() {
+ var __hasProp = {}.hasOwnProperty,
+ __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+ this.kbi || (this.kbi = {});
+
+ this.kbi.VERSION = '0.1.0';
+
+ kbi.StringTemplateSource = (function() {
+
+ function StringTemplateSource(template_string) {
+ this.template_string = template_string;
+ }
+
+ StringTemplateSource.prototype.text = function(value) {
+ return this.template_string;
+ };
+
+ return StringTemplateSource;
+
+ })();
+
+ kbi.StringTemplateEngine = (function(_super) {
+
+ __extends(StringTemplateEngine, _super);
+
+ function StringTemplateEngine() {
+ this.allowTemplateRewriting = false;
+ }
+
+ StringTemplateEngine.prototype.makeTemplateSource = function(template) {
+ switch (template) {
+ case 'kbi_model_node':
+ return new kbi.StringTemplateSource(kbi.ModelNodeView);
+ case 'kbi_collection_node':
+ return new kbi.StringTemplateSource(kbi.CollectionNodeView);
+ default:
+ return ko.nativeTemplateEngine.prototype.makeTemplateSource.apply(this, arguments);
+ }
+ };
+
+ return StringTemplateEngine;
+
+ })(ko.nativeTemplateEngine);
+
+ kbi.FetchedModel = (function(_super) {
+
+ __extends(FetchedModel, _super);
+
+ function FetchedModel() {
+ return FetchedModel.__super__.constructor.apply(this, arguments);
+ }
+
+ FetchedModel.prototype.parse = function(response) {
+ var attributes, collection, key, model, value;
+ attributes = {};
+ for (key in response) {
+ value = response[key];
+ if (_.isObject(value)) {
+ model = new kbi.FetchedModel();
+ attributes[key] = model.set(model.parse(value));
+ } else if (_.isArray(value)) {
+ collection = new kbi.FetchedCollection();
+ attributes[key] = collection.reset(collection.parse(value));
+ } else {
+ attributes[key] = value;
+ }
+ }
+ return attributes;
+ };
+
+ return FetchedModel;
+
+ })(Backbone.Model);
+
+ kbi.FetchedCollection = (function(_super) {
+
+ __extends(FetchedCollection, _super);
+
+ function FetchedCollection() {
+ return FetchedCollection.__super__.constructor.apply(this, arguments);
+ }
+
+ FetchedCollection.prototype.model = kbi.FetchedModel;
+
+ FetchedCollection.prototype.parse = function(response) {
+ return response.results;
+ };
+
+ return FetchedCollection;
+
+ })(Backbone.Collection);
+
+ kbi.NodeViewModel = (function() {
+
+ function NodeViewModel(name, opened, node) {
+ var model;
+ this.name = name;
+ this.opened = ko.observable(opened);
+ this.node = ko.utils.unwrapObservable(node);
+ if (this.node instanceof kb.ViewModel) {
+ model = kb.utils.wrappedModel(this.node);
+ this.attribute_names = ko.observableArray(model ? _.keys(model.attributes) : []);
+ }
+ this;
+
+ }
+
+ NodeViewModel.prototype.attributeType = function(key) {
+ var attribute_connector;
+ attribute_connector = this.node[key];
+ if (ko.utils.unwrapObservable(attribute_connector) instanceof kb.ViewModel) {
+ return 'model';
+ }
+ if (kb.utils.observableInstanceOf(attribute_connector, kb.CollectionAttributeConnector)) {
+ return 'collection';
+ }
+ return 'simple';
+ };
+
+ return NodeViewModel;
+
+ })();
+
+ kbi.CollectionNodeView = "<li class='kbi' data-bind=\"css: {opened: opened, closed: !opened()}\">\n <div class='collection' data-bind=\"click: function(){ opened(!opened()) }\">\n <span data-bind=\"text: (opened() ? '- ' : '+ ' )\"></span>\n <span data-bind=\"text: name\"></span>\n </div>\n\n <!-- ko if: opened -->\n <!-- ko foreach: node -->\n <ul class='kbi' data-bind=\"template: {name: 'kbi_model_node', data: new kbi.NodeViewModel('['+$index()+']', false, $data)}\"></ul>\n <!-- /ko -->\n <!-- /ko -->\n</li>";
+
+ kbi.ModelNodeView = "<li class='kbi' data-bind=\"css: {opened: opened, closed: !opened()}\">\n <div class='kbi model' data-bind=\"click: function(){ opened(!opened()); }\">\n <span data-bind=\"text: (opened() ? '- ' : '+ ' )\"></span>\n <span data-bind=\"text: name\"></span>\n </div>\n\n <!-- ko if: opened -->\n <!-- ko foreach: attribute_names -->\n\n <!-- ko if: ($parent.attributeType($data) == 'simple') -->\n <fieldset class='kbi'>\n <label data-bind=\"text: $data\"> </label>\n <input type='text' data-bind=\"value: $parent.node[$data]\">\n </fieldset>\n <!-- /ko -->\n\n <!-- ko if: ($parent.attributeType($data) == 'model') -->\n <ul class='kbi' data-bind=\"template: {name: 'kbi_model_node', data: new kbi.NodeViewModel($data, false, $parent.node[$data])}\"></ul>\n <!-- /ko -->\n\n <!-- ko if: ($parent.attributeType($data) == 'collection') -->\n <ul class='kbi' data-bind=\"template: {name: 'kbi_collection_node', data: new kbi.NodeViewModel($data+'[]', true, $parent.node[$data])}\"></ul>\n <!-- /ko -->\n\n <!-- /ko -->\n <!-- /ko -->\n</li>";
+
+}).call(this);
9 knockback-inspector.min.js
@@ -0,0 +1,9 @@
+// Generated by CoffeeScript 1.3.3
+/*
+knockback-inspector.js 0.1.0
+(c) 2012 Kevin Malakoff.
+Knockback-Inspector.js is freely distributable under the MIT license.
+See the following for full license details:
+ https://github.com/kmalakoff/knockback-inspector/blob/master/LICENSE
+Dependencies: Knockout.js, Underscore.js, Backbone.js, and Knockback.js.
+*/(function(){var a={}.hasOwnProperty,b=function(b,c){function e(){this.constructor=b}for(var d in c)a.call(c,d)&&(b[d]=c[d]);return e.prototype=c.prototype,b.prototype=new e,b.__super__=c.prototype,b};this.kbi||(this.kbi={}),this.kbi.VERSION="0.1.0",kbi.StringTemplateSource=function(){function a(a){this.template_string=a}return a.prototype.text=function(a){return this.template_string},a}(),kbi.StringTemplateEngine=function(a){function c(){this.allowTemplateRewriting=!1}return b(c,a),c.prototype.makeTemplateSource=function(a){switch(a){case"kbi_model_node":return new kbi.StringTemplateSource(kbi.ModelNodeView);case"kbi_collection_node":return new kbi.StringTemplateSource(kbi.CollectionNodeView);default:return ko.nativeTemplateEngine.prototype.makeTemplateSource.apply(this,arguments)}},c}(ko.nativeTemplateEngine),kbi.FetchedModel=function(a){function c(){return c.__super__.constructor.apply(this,arguments)}return b(c,a),c.prototype.parse=function(a){var b,c,d,e,f;b={};for(d in a)f=a[d],_.isObject(f)?(e=new kbi.FetchedModel,b[d]=e.set(e.parse(f))):_.isArray(f)?(c=new kbi.FetchedCollection,b[d]=c.reset(c.parse(f))):b[d]=f;return b},c}(Backbone.Model),kbi.FetchedCollection=function(a){function c(){return c.__super__.constructor.apply(this,arguments)}return b(c,a),c.prototype.model=kbi.FetchedModel,c.prototype.parse=function(a){return a.results},c}(Backbone.Collection),kbi.NodeViewModel=function(){function a(a,b,c){var d;this.name=a,this.opened=ko.observable(b),this.node=ko.utils.unwrapObservable(c),this.node instanceof kb.ViewModel&&(d=kb.utils.wrappedModel(this.node),this.attribute_names=ko.observableArray(d?_.keys(d.attributes):[])),this}return a.prototype.attributeType=function(a){var b;return b=this.node[a],ko.utils.unwrapObservable(b)instanceof kb.ViewModel?"model":kb.utils.observableInstanceOf(b,kb.CollectionAttributeConnector)?"collection":"simple"},a}(),kbi.CollectionNodeView="<li class='kbi' data-bind=\"css: {opened: opened, closed: !opened()}\">\n <div class='collection' data-bind=\"click: function(){ opened(!opened()) }\">\n <span data-bind=\"text: (opened() ? '- ' : '+ ' )\"></span>\n <span data-bind=\"text: name\"></span>\n </div>\n\n <!-- ko if: opened -->\n <!-- ko foreach: node -->\n <ul class='kbi' data-bind=\"template: {name: 'kbi_model_node', data: new kbi.NodeViewModel('['+$index()+']', false, $data)}\"></ul>\n <!-- /ko -->\n <!-- /ko -->\n</li>",kbi.ModelNodeView="<li class='kbi' data-bind=\"css: {opened: opened, closed: !opened()}\">\n <div class='kbi model' data-bind=\"click: function(){ opened(!opened()); }\">\n <span data-bind=\"text: (opened() ? '- ' : '+ ' )\"></span>\n <span data-bind=\"text: name\"></span>\n </div>\n\n <!-- ko if: opened -->\n <!-- ko foreach: attribute_names -->\n\n <!-- ko if: ($parent.attributeType($data) == 'simple') -->\n <fieldset class='kbi'>\n <label data-bind=\"text: $data\"> </label>\n <input type='text' data-bind=\"value: $parent.node[$data]\">\n </fieldset>\n <!-- /ko -->\n\n <!-- ko if: ($parent.attributeType($data) == 'model') -->\n <ul class='kbi' data-bind=\"template: {name: 'kbi_model_node', data: new kbi.NodeViewModel($data, false, $parent.node[$data])}\"></ul>\n <!-- /ko -->\n\n <!-- ko if: ($parent.attributeType($data) == 'collection') -->\n <ul class='kbi' data-bind=\"template: {name: 'kbi_collection_node', data: new kbi.NodeViewModel($data+'[]', true, $parent.node[$data])}\"></ul>\n <!-- /ko -->\n\n <!-- /ko -->\n <!-- /ko -->\n</li>"}).call(this);
26 package.json
@@ -0,0 +1,26 @@
+{
+ "author": "Kevin Malakoff <https://github.com/kmalakoff>",
+ "name": "knockback-inspector",
+ "description": "Knockback-Inspector.js provides an inspector tree view library for Backbone.Models and Backbone.Collections using Knockback.js",
+
+ "url": "http://kmalakoff.github.com/knockback-inspector/",
+ "homepage": "http://kmalakoff.github.com/knockback-inspector/",
+ "repository": {
+ "type": "git",
+ "url": "http://github.com/kmalakoff/knockback-inspector.git"
+ },
+
+ "dependencies" : {
+ "underscore" : ">=1.3.1",
+ "backbone" : ">=0.9.1",
+ "knockout" : ">=1.2.1",
+ "knockback" : ">=0.15.1"
+ },
+ "devDependencies": {
+ "coffee-script": ">=1.3.0"
+ },
+
+ "lib" : ".",
+ "main" : "knockback-inspector.js",
+ "version": "0.1.0"
+}
10 src/lib/kbi_core.coffee
@@ -0,0 +1,10 @@
+###
+knockback-inspector.js 0.1.0
+(c) 2012 Kevin Malakoff.
+Knockback-Inspector.js is freely distributable under the MIT license.
+See the following for full license details:
+ https://github.com/kmalakoff/knockback-inspector/blob/master/LICENSE
+Dependencies: Knockout.js, Underscore.js, Backbone.js, and Knockback.js.
+###
+@kbi or={}
+@kbi.VERSION = '0.1.0'
15 src/lib/kbi_string_template_engine.coffee
@@ -0,0 +1,15 @@
+# template source
+class kbi.StringTemplateSource
+ constructor: (@template_string) ->
+ text: (value) -> return @template_string
+
+# template engine
+class kbi.StringTemplateEngine extends ko.nativeTemplateEngine
+ constructor: ->
+ @allowTemplateRewriting = false
+
+ makeTemplateSource: (template) ->
+ switch (template)
+ when 'kbi_model_node' then return new kbi.StringTemplateSource(kbi.ModelNodeView)
+ when 'kbi_collection_node' then return new kbi.StringTemplateSource(kbi.CollectionNodeView)
+ else return ko.nativeTemplateEngine.prototype.makeTemplateSource.apply(this, arguments)
19 src/models/kbi_fetched.coffee
@@ -0,0 +1,19 @@
+# General-purpose model for fetched JSON when you do not have a specialized implementation
+class kbi.FetchedModel extends Backbone.Model
+ parse: (response) ->
+ attributes = {}
+ for key, value of response
+ if _.isObject(value)
+ model = new kbi.FetchedModel()
+ attributes[key] = model.set(model.parse(value))
+ else if _.isArray(value)
+ collection = new kbi.FetchedCollection()
+ attributes[key] = collection.reset(collection.parse(value))
+ else
+ attributes[key] = value
+ return attributes
+
+# General-purpose collection for fetched JSON when you do not have a specialized implementation
+class kbi.FetchedCollection extends Backbone.Collection
+ model: kbi.FetchedModel
+ parse: (response) -> return response.results
17 src/view_models/kbi_node_view_model.coffee
@@ -0,0 +1,17 @@
+class kbi.NodeViewModel
+ constructor: (name, opened, node) ->
+ @name = name
+ @opened = ko.observable(opened)
+ @node = ko.utils.unwrapObservable(node)
+
+ # a kb.ViewModel indicates the node is a Backbone.Model
+ if (@node instanceof kb.ViewModel)
+ model = kb.utils.wrappedModel(@node)
+ @attribute_names = ko.observableArray(if model then _.keys(model.attributes) else [])
+ @
+
+ attributeType: (key) ->
+ attribute_connector = @node[key]
+ return 'model' if (ko.utils.unwrapObservable(attribute_connector) instanceof kb.ViewModel)
+ return 'collection' if kb.utils.observableInstanceOf(attribute_connector, kb.CollectionAttributeConnector)
+ return 'simple'
14 src/views/kbi_collection_node_view.coffee
@@ -0,0 +1,14 @@
+kbi.CollectionNodeView = """
+<li class='kbi' data-bind="css: {opened: opened, closed: !opened()}">
+ <div class='collection' data-bind="click: function(){ opened(!opened()) }">
+ <span data-bind="text: (opened() ? '- ' : '+ ' )"></span>
+ <span data-bind="text: name"></span>
+ </div>
+
+ <!-- ko if: opened -->
+ <!-- ko foreach: node -->
+ <ul class='kbi' data-bind="template: {name: 'kbi_model_node', data: new kbi.NodeViewModel('['+$index()+']', false, $data)}"></ul>
+ <!-- /ko -->
+ <!-- /ko -->
+</li>
+"""
29 src/views/kbi_model_node_view.coffee
@@ -0,0 +1,29 @@
+kbi.ModelNodeView = """
+<li class='kbi' data-bind="css: {opened: opened, closed: !opened()}">
+ <div class='kbi model' data-bind="click: function(){ opened(!opened()); }">
+ <span data-bind="text: (opened() ? '- ' : '+ ' )"></span>
+ <span data-bind="text: name"></span>
+ </div>
+
+ <!-- ko if: opened -->
+ <!-- ko foreach: attribute_names -->
+
+ <!-- ko if: ($parent.attributeType($data) == 'simple') -->
+ <fieldset class='kbi'>
+ <label data-bind="text: $data"> </label>
+ <input type='text' data-bind="value: $parent.node[$data]">
+ </fieldset>
+ <!-- /ko -->
+
+ <!-- ko if: ($parent.attributeType($data) == 'model') -->
+ <ul class='kbi' data-bind="template: {name: 'kbi_model_node', data: new kbi.NodeViewModel($data, false, $parent.node[$data])}"></ul>
+ <!-- /ko -->
+
+ <!-- ko if: ($parent.attributeType($data) == 'collection') -->
+ <ul class='kbi' data-bind="template: {name: 'kbi_collection_node', data: new kbi.NodeViewModel($data+'[]', true, $parent.node[$data])}"></ul>
+ <!-- /ko -->
+
+ <!-- /ko -->
+ <!-- /ko -->
+</li>
+"""
110 test/index.html
@@ -0,0 +1,110 @@
+<!doctype html>
+<head>
+ <meta charset='utf-8'>
+ <meta http-equiv='X-UA-Compatible' content='IE=edge,chrome=1'>
+ <title class='inspector_title' data-bind="text: title"></title>
+
+ <!-- DEPENDENCIES -->
+ <script src='../vendor/jquery-1.7.2.js'></script>
+ <script src='../vendor/knockout-2.1.0.js'></script>
+ <script src='../vendor/underscore-1.3.1.js'></script>
+ <script src='../vendor/backbone-0.9.2.js'></script>
+ <script src='../vendor/backbone-relational.js'></script>
+ <script src='../vendor/knockback-0.15.1.js'></script>
+
+ <!-- KNOCKBACK-INSPECTOR LIBRARY -->
+ <script src='../knockback-inspector.min.js'></script>
+
+ <!-- APPLICATION STYLING -->
+ <style type='text/css'>
+ #content {width: 800px; margin: 0 auto; background-color: #F5E0FF; border-radius: 5px;}
+ h1 {text-align: center; font-size: 2em; color: #fff; background-color: #CC66FF; border-radius: 5px;}
+ </style>
+ <link rel='stylesheet' href='../knockback-inspector.css'>
+
+ <!-- KNOCKOUT INITIALIZATION -->
+ <script type='text/javascript'>
+ // set the template engine so Knockout can find 'kbi_model_node' and 'kbi_collection_node' templates
+ ko.setTemplateEngine(new kbi.StringTemplateEngine());
+ </script>
+
+</head>
+<body>
+ <div id='content'>
+ <h1 class='inspector_title' data-bind="text: title"></h1>
+
+ <!-- Step 1: Setting up the Project -->
+ <script type='text/javascript'>
+ var app_model = new Backbone.Model({title: 'Knockback Inspector (' + kbi.VERSION + ')'});
+ $('.inspector_title').each(function(){ ko.applyBindings(kb.viewModel(app_model), this); });
+ </script>
+
+ <!-- Step 2: Rendering a Backbone.Model using kb.ViewModel -->
+ <ul id='backbone_model' class='kbi' data-bind="template: {name: 'kbi_model_node', data: new kbi.NodeViewModel('root', true, $data)}"></ul>
+ <script type='text/javascript'>
+ var model1 = new Backbone.Model({name: 'Model1', pet: 'frog', friends: new Backbone.Collection([])});
+ var model2 = new Backbone.Model({name: 'Model2', pet: 'dog', friends: new Backbone.Collection([])});
+ var model3 = new Backbone.Model({name: 'Model3', pet: '(none)', friends: new Backbone.Collection([])});
+ model1.get('friends').reset([model2, model3]);
+ model2.get('friends').reset([model1, model3]);
+ model3.get('friends').reset([model1, model2]);
+
+ ko.applyBindings(kb.viewModel(model1), $('#backbone_model')[0]);
+ </script>
+
+ </br>
+
+ <!-- Step 3: Rendering a BackboneRelational using kb.CollectionObservable -->
+ <ul id='backbone_relational' class='kbi' data-bind="template: {name: 'kbi_collection_node', data: new kbi.NodeViewModel('root', true, $data)}"></ul>
+ <script type='text/javascript'>
+ var Person = Backbone.RelationalModel.extend({});
+ var House = Backbone.RelationalModel.extend({
+ relations: [{
+ type: Backbone.HasMany,
+ key: 'occupants',
+ relatedModel: 'Person',
+ reverseRelation: {
+ key: 'livesIn'
+ }
+ }]
+ });
+
+ var bob = new Person({id: 'person-1', name: 'Bob'});
+ var fred = new Person({id: 'person-2', name: 'Fred'});
+ var house = new House({
+ location: 'In the middle of our street',
+ occupants: ['person-1', 'person-2']
+ });
+
+ ko.applyBindings(kb.collectionObservable(house.get('occupants'), {view_model: kb.ViewModel}), $('#backbone_relational')[0]);
+ </script>
+
+ </br>
+
+ <!-- Step 4: Rendering a Twitter Query using kb.CollectionObservable -->
+ <ul class='kbi' id='fetched_collection'>
+ <li class='kbi'>
+ <fieldset class='kbi'>
+ <label>URL</label>
+ <input type='text' data-bind="value: url">
+ </fieldset>
+ <ul class='kbi' data-bind="template: {name: 'kbi_collection_node', data: new kbi.NodeViewModel('root', true, collection)}"></ul>
+ </li>
+ </ul>
+
+ <script type='text/javascript'>
+ var custom_url_model = new Backbone.Model({url: '', collection: new kbi.FetchedCollection()});
+ var view_model = kb.viewModel(custom_url_model);
+ view_model.url.subscribe(function(url){
+ custom_url_model.get('collection').url = url;
+ custom_url_model.get('collection').fetch();
+ });
+
+ ko.applyBindings(view_model, $('#fetched_collection')[0]);
+ view_model.url('http://search.twitter.com/search.json?q=knockbackjs&callback=?');
+ </script>
+
+ </div>
+</body>
+
+</html>
21 tutorials/coffeescript/step1/Cakefile
@@ -0,0 +1,21 @@
+{print} = require 'util'
+{spawn} = require 'child_process'
+path = require 'path'
+
+task 'build', 'Build javascripts/ from src/', ->
+ coffee = spawn 'coffee', ['-c', '-o', 'javascripts', 'src']
+ coffee.stderr.on 'data', (data) ->
+ process.stderr.write data.toString()
+ coffee.stdout.on 'data', (data) ->
+ print data.toString()
+ coffee.on 'exit', (code) ->
+ callback?() if code is 0
+
+task 'watch', 'Watch src/ for changes', ->
+ coffee = spawn 'coffee', ['-w', '-o', 'javascripts', 'src']
+ coffee.stderr.on 'data', (data) ->
+ print 'Error'
+ process.stderr.write data.toString()
+ coffee.stdout.on 'data', (data) ->
+ print data.toString()
+ callback?()
37 tutorials/coffeescript/step1/index.html
@@ -0,0 +1,37 @@
+<!doctype html>
+<head>
+ <meta charset='utf-8'>
+ <meta http-equiv='X-UA-Compatible' content='IE=edge,chrome=1'>
+ <title class='inspector_title' data-bind="text: title"></title>
+
+ <!-- DEPENDENCIES -->
+ <script src='../../../vendor/jquery-1.7.2.js'></script>
+ <script src='../../../vendor/knockout-2.1.0.js'></script>
+ <script src='../../../vendor/underscore-1.3.1.js'></script>
+ <script src='../../../vendor/backbone-0.9.2.js'></script>
+ <script src='../../../vendor/backbone-relational.js'></script>
+ <script src='../../../vendor/knockback-0.15.1.js'></script>
+
+ <!-- KNOCKBACK-INSPECTOR LIBRARY -->
+ <script src='javascripts/lib/kbi_core.js'></script>
+
+ <!-- APPLICATION STYLING -->
+ <style type='text/css'>
+ #content {width: 800px; margin: 0 auto; background-color: #F5E0FF; border-radius: 5px;}
+ h1 {text-align: center; font-size: 2em; color: #fff; background-color: #CC66FF; border-radius: 5px;}
+ </style>
+
+</head>
+<body>
+ <div id='content'>
+ <h1 class='inspector_title' data-bind="text: title"></h1>
+
+ <!-- Step 1: Setting up the Project -->
+ <script type='text/javascript'>
+ var app_model = new Backbone.Model({title: 'Knockback Inspector (' + kbi.VERSION + ')'});
+ $('.inspector_title').each(function(){ ko.applyBindings(kb.viewModel(app_model), this); });
+ </script>
+
+ </div>
+</body>
+</html>
19 tutorials/coffeescript/step1/javascripts/lib/kbi_core.js
@@ -0,0 +1,19 @@
+// Generated by CoffeeScript 1.3.3
+
+/*
+knockback-inspector.js 0.1.0
+(c) 2012 Kevin Malakoff.
+Knockback-Inspector.js is freely distributable under the MIT license.
+See the following for full license details:
+ https://github.com/kmalakoff/knockback-inspector/blob/master/LICENSE
+Dependencies: Knockout.js, Underscore.js, Backbone.js, and Knockback.js.
+*/
+
+
+(function() {
+
+ this.kbi || (this.kbi = {});
+
+ this.kbi.VERSION = '0.1.0';
+
+}).call(this);
10 tutorials/coffeescript/step1/src/lib/kbi_core.coffee
@@ -0,0 +1,10 @@
+###
+knockback-inspector.js 0.1.0
+(c) 2012 Kevin Malakoff.
+Knockback-Inspector.js is freely distributable under the MIT license.
+See the following for full license details:
+ https://github.com/kmalakoff/knockback-inspector/blob/master/LICENSE
+Dependencies: Knockout.js, Underscore.js, Backbone.js, and Knockback.js.
+###
+@kbi or={}
+@kbi.VERSION = '0.1.0'
21 tutorials/coffeescript/step2/Cakefile
@@ -0,0 +1,21 @@
+{print} = require 'util'
+{spawn} = require 'child_process'
+path = require 'path'
+
+task 'build', 'Build javascripts/ from src/', ->
+ coffee = spawn 'coffee', ['-c', '-o', 'javascripts', 'src']
+ coffee.stderr.on 'data', (data) ->
+ process.stderr.write data.toString()
+ coffee.stdout.on 'data', (data) ->
+ print data.toString()
+ coffee.on 'exit', (code) ->
+ callback?() if code is 0
+
+task 'watch', 'Watch src/ for changes', ->
+ coffee = spawn 'coffee', ['-w', '-o', 'javascripts', 'src']
+ coffee.stderr.on 'data', (data) ->
+ print 'Error'
+ process.stderr.write data.toString()
+ coffee.stdout.on 'data', (data) ->
+ print data.toString()
+ callback?()
62 tutorials/coffeescript/step2/index.html
@@ -0,0 +1,62 @@
+<!doctype html>
+<head>
+ <meta charset='utf-8'>
+ <meta http-equiv='X-UA-Compatible' content='IE=edge,chrome=1'>
+ <title class='inspector_title' data-bind="text: title"></title>
+
+ <!-- DEPENDENCIES -->
+ <script src='../../../vendor/jquery-1.7.2.js'></script>
+ <script src='../../../vendor/knockout-2.1.0.js'></script>
+ <script src='../../../vendor/underscore-1.3.1.js'></script>
+ <script src='../../../vendor/backbone-0.9.2.js'></script>
+ <script src='../../../vendor/backbone-relational.js'></script>
+ <script src='../../../vendor/knockback-0.15.1.js'></script>
+
+ <!-- KNOCKBACK-INSPECTOR LIBRARY -->
+ <script src='javascripts/lib/kbi_core.js'></script>
+ <script src='javascripts/view_models/kbi_node_view_model.js'></script>
+ <script src='javascripts/views/kbi_collection_node_view.js'></script>
+ <script src='javascripts/views/kbi_model_node_view.js'></script>
+ <script src='javascripts/lib/kbi_string_template_engine.js'></script>
+
+ <!-- APPLICATION STYLING -->
+ <style type='text/css'>
+ #content {width: 800px; margin: 0 auto; background-color: #F5E0FF; border-radius: 5px;}
+ h1 {text-align: center; font-size: 2em; color: #fff; background-color: #CC66FF; border-radius: 5px;}
+ </style>
+ <link rel='stylesheet' href="../../../knockback-inspector.css">
+
+ <!-- KNOCKOUT INITIALIZATION -->
+ <script type='text/javascript'>
+ // set the template engine so Knockout can find 'kbi_model_node' and 'kbi_collection_node' templates
+ ko.setTemplateEngine(new kbi.StringTemplateEngine());
+ </script>
+
+</head>
+<body>
+ <div id='content'>
+ <h1 class='inspector_title' data-bind="text: title"></h1>
+
+ <!-- Step 1: Setting up the Project -->
+ <script type='text/javascript'>
+ var app_model = new Backbone.Model({title: 'Knockback Inspector (' + kbi.VERSION + ')'});
+ $('.inspector_title').each(function(){ ko.applyBindings(kb.viewModel(app_model), this); });
+ </script>
+
+ <!-- Step 2: Rendering a Backbone.Model using kb.ViewModel -->
+ <ul id='backbone_model' class='kbi' data-bind="template: {name: 'kbi_model_node', data: new kbi.NodeViewModel('root', true, $data)}"></ul>
+ <script type='text/javascript'>
+ var model1 = new Backbone.Model({name: 'Model1', pet: 'frog', friends: new Backbone.Collection([])});
+ var model2 = new Backbone.Model({name: 'Model2', pet: 'dog', friends: new Backbone.Collection([])});
+ var model3 = new Backbone.Model({name: 'Model3', pet: '(none)', friends: new Backbone.Collection([])});
+ model1.get('friends').reset([model2, model3]);
+ model2.get('friends').reset([model1, model3]);
+ model3.get('friends').reset([model1, model2]);
+
+ ko.applyBindings(kb.viewModel(model1), $('#backbone_model')[0]);
+ </script>
+
+ </div>
+</body>
+
+</html>
19 tutorials/coffeescript/step2/javascripts/lib/kbi_core.js
@@ -0,0 +1,19 @@
+// Generated by CoffeeScript 1.3.3
+
+/*
+knockback-inspector.js 0.1.0
+(c) 2012 Kevin Malakoff.
+Knockback-Inspector.js is freely distributable under the MIT license.
+See the following for full license details:
+ https://github.com/kmalakoff/knockback-inspector/blob/master/LICENSE
+Dependencies: Knockout.js, Underscore.js, Backbone.js, and Knockback.js.
+*/
+
+
+(function() {
+
+ this.kbi || (this.kbi = {});
+
+ this.kbi.VERSION = '0.1.0';
+
+}).call(this);
43 tutorials/coffeescript/step2/javascripts/lib/kbi_string_template_engine.js
@@ -0,0 +1,43 @@
+// Generated by CoffeeScript 1.3.3
+(function() {
+ var __hasProp = {}.hasOwnProperty,
+ __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+ kbi.StringTemplateSource = (function() {
+
+ function StringTemplateSource(template_string) {
+ this.template_string = template_string;
+ }
+
+ StringTemplateSource.prototype.text = function(value) {
+ return this.template_string;
+ };
+
+ return StringTemplateSource;
+
+ })();
+
+ kbi.StringTemplateEngine = (function(_super) {
+
+ __extends(StringTemplateEngine, _super);
+
+ function StringTemplateEngine() {
+ this.allowTemplateRewriting = false;
+ }
+
+ StringTemplateEngine.prototype.makeTemplateSource = function(template) {
+ switch (template) {
+ case 'kbi_model_node':
+ return new kbi.StringTemplateSource(kbi.ModelNodeView);
+ case 'kbi_collection_node':
+ return new kbi.StringTemplateSource(kbi.CollectionNodeView);
+ default:
+ return ko.nativeTemplateEngine.prototype.makeTemplateSource.apply(this, arguments);
+ }
+ };
+
+ return StringTemplateEngine;
+
+ })(ko.nativeTemplateEngine);
+
+}).call(this);
35 tutorials/coffeescript/step2/javascripts/view_models/kbi_node_view_model.js
@@ -0,0 +1,35 @@
+// Generated by CoffeeScript 1.3.3
+(function() {
+
+ kbi.NodeViewModel = (function() {
+
+ function NodeViewModel(name, opened, node) {
+ var model;
+ this.name = name;
+ this.opened = ko.observable(opened);
+ this.node = ko.utils.unwrapObservable(node);
+ if (this.node instanceof kb.ViewModel) {
+ model = kb.utils.wrappedModel(this.node);
+ this.attribute_names = ko.observableArray(model ? _.keys(model.attributes) : []);
+ }
+ this;
+
+ }
+
+ NodeViewModel.prototype.attributeType = function(key) {
+ var attribute_connector;
+ attribute_connector = this.node[key];
+ if (ko.utils.unwrapObservable(attribute_connector) instanceof kb.ViewModel) {
+ return 'model';
+ }
+ if (kb.utils.observableInstanceOf(attribute_connector, kb.CollectionAttributeConnector)) {
+ return 'collection';
+ }
+ return 'simple';
+ };
+
+ return NodeViewModel;
+
+ })();
+
+}).call(this);
6 tutorials/coffeescript/step2/javascripts/views/kbi_collection_node_view.js
@@ -0,0 +1,6 @@
+// Generated by CoffeeScript 1.3.3
+(function() {
+
+ kbi.CollectionNodeView = "<li class='kbi' data-bind=\"css: {opened: opened, closed: !opened()}\">\n <div class='collection' data-bind=\"click: function(){ opened(!opened()) }\">\n <span data-bind=\"text: (opened() ? '- ' : '+ ' )\"></span>\n <span data-bind=\"text: name\"></span>\n </div>\n\n <!-- ko if: opened -->\n <!-- ko foreach: node -->\n <ul class='kbi' data-bind=\"template: {name: 'kbi_model_node', data: new kbi.NodeViewModel('['+$index()+']', false, $data)}\"></ul>\n <!-- /ko -->\n <!-- /ko -->\n</li>";
+
+}).call(this);
6 tutorials/coffeescript/step2/javascripts/views/kbi_model_node_view.js
@@ -0,0 +1,6 @@
+// Generated by CoffeeScript 1.3.3
+(function() {
+
+ kbi.ModelNodeView = "<li class='kbi' data-bind=\"css: {opened: opened, closed: !opened()}\">\n <div class='kbi model' data-bind=\"click: function(){ opened(!opened()); }\">\n <span data-bind=\"text: (opened() ? '- ' : '+ ' )\"></span>\n <span data-bind=\"text: name\"></span>\n </div>\n\n <!-- ko if: opened -->\n <!-- ko foreach: attribute_names -->\n\n <!-- ko if: ($parent.attributeType($data) == 'simple') -->\n <fieldset class='kbi'>\n <label data-bind=\"text: $data\"> </label>\n <input type='text' data-bind=\"value: $parent.node[$data]\">\n </fieldset>\n <!-- /ko -->\n\n <!-- ko if: ($parent.attributeType($data) == 'model') -->\n <ul class='kbi' data-bind=\"template: {name: 'kbi_model_node', data: new kbi.NodeViewModel($data, false, $parent.node[$data])}\"></ul>\n <!-- /ko -->\n\n <!-- ko if: ($parent.attributeType($data) == 'collection') -->\n <ul class='kbi' data-bind=\"template: {name: 'kbi_collection_node', data: new kbi.NodeViewModel($data+'[]', true, $parent.node[$data])}\"></ul>\n <!-- /ko -->\n\n <!-- /ko -->\n <!-- /ko -->\n</li>";
+
+}).call(this);
10 tutorials/coffeescript/step2/src/lib/kbi_core.coffee
@@ -0,0 +1,10 @@
+###
+knockback-inspector.js 0.1.0
+(c) 2012 Kevin Malakoff.
+Knockback-Inspector.js is freely distributable under the MIT license.
+See the following for full license details:
+ https://github.com/kmalakoff/knockback-inspector/blob/master/LICENSE
+Dependencies: Knockout.js, Underscore.js, Backbone.js, and Knockback.js.
+###
+@kbi or={}
+@kbi.VERSION = '0.1.0'
15 tutorials/coffeescript/step2/src/lib/kbi_string_template_engine.coffee
@@ -0,0 +1,15 @@
+# template source
+class kbi.StringTemplateSource
+ constructor: (@template_string) ->
+ text: (value) -> return @template_string
+
+# template engine
+class kbi.StringTemplateEngine extends ko.nativeTemplateEngine
+ constructor: ->
+ @allowTemplateRewriting = false
+
+ makeTemplateSource: (template) ->
+ switch (template)
+ when 'kbi_model_node' then return new kbi.StringTemplateSource(kbi.ModelNodeView)
+ when 'kbi_collection_node' then return new kbi.StringTemplateSource(kbi.CollectionNodeView)
+ else return ko.nativeTemplateEngine.prototype.makeTemplateSource.apply(this, arguments)
17 tutorials/coffeescript/step2/src/view_models/kbi_node_view_model.coffee
@@ -0,0 +1,17 @@
+class kbi.NodeViewModel
+ constructor: (name, opened, node) ->
+ @name = name
+ @opened = ko.observable(opened)
+ @node = ko.utils.unwrapObservable(node)
+
+ # a kb.ViewModel indicates the node is a Backbone.Model
+ if (@node instanceof kb.ViewModel)
+ model = kb.utils.wrappedModel(@node)
+ @attribute_names = ko.observableArray(if model then _.keys(model.attributes) else [])
+ @
+
+ attributeType: (key) ->
+ attribute_connector = @node[key]
+ return 'model' if (ko.utils.unwrapObservable(attribute_connector) instanceof kb.ViewModel)
+ return 'collection' if kb.utils.observableInstanceOf(attribute_connector, kb.CollectionAttributeConnector)
+ return 'simple'
14 tutorials/coffeescript/step2/src/views/kbi_collection_node_view.coffee
@@ -0,0 +1,14 @@
+kbi.CollectionNodeView = """
+<li class='kbi' data-bind="css: {opened: opened, closed: !opened()}">
+ <div class='collection' data-bind="click: function(){ opened(!opened()) }">
+ <span data-bind="text: (opened() ? '- ' : '+ ' )"></span>
+ <span data-bind="text: name"></span>
+ </div>
+
+ <!-- ko if: opened -->
+ <!-- ko foreach: node -->
+ <ul class='kbi' data-bind="template: {name: 'kbi_model_node', data: new kbi.NodeViewModel('['+$index()+']', false, $data)}"></ul>
+ <!-- /ko -->
+ <!-- /ko -->
+</li>
+"""
29 tutorials/coffeescript/step2/src/views/kbi_model_node_view.coffee
@@ -0,0 +1,29 @@
+kbi.ModelNodeView = """
+<li class='kbi' data-bind="css: {opened: opened, closed: !opened()}">
+ <div class='kbi model' data-bind="click: function(){ opened(!opened()); }">
+ <span data-bind="text: (opened() ? '- ' : '+ ' )"></span>
+ <span data-bind="text: name"></span>
+ </div>
+
+ <!-- ko if: opened -->
+ <!-- ko foreach: attribute_names -->
+
+ <!-- ko if: ($parent.attributeType($data) == 'simple') -->
+ <fieldset class='kbi'>
+ <label data-bind="text: $data"> </label>
+ <input type='text' data-bind="value: $parent.node[$data]">
+ </fieldset>
+ <!-- /ko -->
+
+ <!-- ko if: ($parent.attributeType($data) == 'model') -->
+ <ul class='kbi' data-bind="template: {name: 'kbi_model_node', data: new kbi.NodeViewModel($data, false, $parent.node[$data])}"></ul>
+ <!-- /ko -->
+
+ <!-- ko if: ($parent.attributeType($data) == 'collection') -->
+ <ul class='kbi' data-bind="template: {name: 'kbi_collection_node', data: new kbi.NodeViewModel($data+'[]', true, $parent.node[$data])}"></ul>
+ <!-- /ko -->
+
+ <!-- /ko -->
+ <!-- /ko -->
+</li>
+"""
21 tutorials/coffeescript/step3/Cakefile
@@ -0,0 +1,21 @@
+{print} = require 'util'
+{spawn} = require 'child_process'
+path = require 'path'
+
+task 'build', 'Build javascripts/ from src/', ->
+ coffee = spawn 'coffee', ['-c', '-o', 'javascripts', 'src']
+ coffee.stderr.on 'data', (data) ->
+ process.stderr.write data.toString()
+ coffee.stdout.on 'data', (data) ->
+ print data.toString()
+ coffee.on 'exit', (code) ->
+ callback?() if code is 0
+
+task 'watch', 'Watch src/ for changes', ->
+ coffee = spawn 'coffee', ['-w', '-o', 'javascripts', 'src']
+ coffee.stderr.on 'data', (data) ->
+ print 'Error'
+ process.stderr.write data.toString()
+ coffee.stdout.on 'data', (data) ->
+ print data.toString()
+ callback?()
74 tutorials/coffeescript/step3/index.html
@@ -0,0 +1,74 @@
+<!doctype html>
+<head>
+ <meta charset='utf-8'>
+ <meta http-equiv='X-UA-Compatible' content='IE=edge,chrome=1'>
+ <title class='inspector_title' data-bind="text: title"></title>
+
+ <!-- DEPENDENCIES -->
+ <script src='../../../vendor/jquery-1.7.2.js'></script>
+ <script src='../../../vendor/knockout-2.1.0.js'></script>
+ <script src='../../../vendor/underscore-1.3.1.js'></script>
+ <script src='../../../vendor/backbone-0.9.2.js'></script>
+ <script src='../../../vendor/backbone-relational.js'></script>
+ <script src='../../../vendor/knockback-0.15.1.js'></script>
+
+ <!-- KNOCKBACK-INSPECTOR LIBRARY -->
+ <script src='javascripts/lib/kbi_core.js'></script>
+ <script src='javascripts/view_models/kbi_node_view_model.js'></script>
+ <script src='javascripts/views/kbi_collection_node_view.js'></script>
+ <script src='javascripts/views/kbi_model_node_view.js'></script>
+ <script src='javascripts/lib/kbi_string_template_engine.js'></script>
+
+ <!-- APPLICATION STYLING -->
+ <style type='text/css'>
+ #content {width: 800px; margin: 0 auto; background-color: #F5E0FF; border-radius: 5px;}
+ h1 {text-align: center; font-size: 2em; color: #fff; background-color: #CC66FF; border-radius: 5px;}
+ </style>
+ <link rel='stylesheet' href="../../../knockback-inspector.css">
+
+ <!-- KNOCKOUT INITIALIZATION -->
+ <script type='text/javascript'>
+ // set the template engine so Knockout can find 'kbi_model_node' and 'kbi_collection_node' templates
+ ko.setTemplateEngine(new kbi.StringTemplateEngine());
+ </script>
+
+</head>
+<body>
+ <div id='content'>
+ <h1 class='inspector_title' data-bind="text: title"></h1>
+
+ <!-- Step 1: Setting up the Project -->
+ <script type='text/javascript'>
+ var app_model = new Backbone.Model({title: 'Knockback Inspector (' + kbi.VERSION + ')'});
+ $('.inspector_title').each(function(){ ko.applyBindings(kb.viewModel(app_model), this); });
+ </script>
+
+ <!-- Step 3: Rendering a BackboneRelational using kb.CollectionObservable -->
+ <ul id='backbone_relational' class='kbi' data-bind="template: {name: 'kbi_collection_node', data: new kbi.NodeViewModel('root', true, $data)}"></ul>
+ <script type='text/javascript'>
+ var Person = Backbone.RelationalModel.extend({});
+ var House = Backbone.RelationalModel.extend({
+ relations: [{
+ type: Backbone.HasMany,
+ key: 'occupants',
+ relatedModel: 'Person',
+ reverseRelation: {
+ key: 'livesIn'
+ }
+ }]
+ });
+
+ var bob = new Person({id: 'person-1', name: 'Bob'});
+ var fred = new Person({id: 'person-2', name: 'Fred'});
+ var house = new House({
+ location: 'In the middle of our street',
+ occupants: ['person-1', 'person-2']
+ });
+
+ ko.applyBindings(kb.collectionObservable(house.get('occupants'), {view_model: kb.ViewModel}), $('#backbone_relational')[0]);
+ </script>
+
+ </div>
+</body>
+
+</html>
19 tutorials/coffeescript/step3/javascripts/lib/kbi_core.js
@@ -0,0 +1,19 @@
+// Generated by CoffeeScript 1.3.3
+
+/*
+knockback-inspector.js 0.1.0
+(c) 2012 Kevin Malakoff.
+Knockback-Inspector.js is freely distributable under the MIT license.
+See the following for full license details:
+ https://github.com/kmalakoff/knockback-inspector/blob/master/LICENSE
+Dependencies: Knockout.js, Underscore.js, Backbone.js, and Knockback.js.
+*/
+
+
+(function() {
+
+ this.kbi || (this.kbi = {});
+
+ this.kbi.VERSION = '0.1.0';
+
+}).call(this);
43 tutorials/coffeescript/step3/javascripts/lib/kbi_string_template_engine.js
@@ -0,0 +1,43 @@
+// Generated by CoffeeScript 1.3.3
+(function() {
+ var __hasProp = {}.hasOwnProperty,
+ __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+ kbi.StringTemplateSource = (function() {
+
+ function StringTemplateSource(template_string) {
+ this.template_string = template_string;
+ }
+
+ StringTemplateSource.prototype.text = function(value) {
+ return this.template_string;
+ };
+
+ return StringTemplateSource;
+
+ })();
+
+ kbi.StringTemplateEngine = (function(_super) {
+
+ __extends(StringTemplateEngine, _super);
+
+ function StringTemplateEngine() {
+ this.allowTemplateRewriting = false;
+ }
+
+ StringTemplateEngine.prototype.makeTemplateSource = function(template) {
+ switch (template) {
+ case 'kbi_model_node':
+ return new kbi.StringTemplateSource(kbi.ModelNodeView);
+ case 'kbi_collection_node':
+ return new kbi.StringTemplateSource(kbi.CollectionNodeView);
+ default:
+ return ko.nativeTemplateEngine.prototype.makeTemplateSource.apply(this, arguments);
+ }
+ };
+
+ return StringTemplateEngine;
+
+ })(ko.nativeTemplateEngine);
+
+}).call(this);
35 tutorials/coffeescript/step3/javascripts/view_models/kbi_node_view_model.js
@@ -0,0 +1,35 @@
+// Generated by CoffeeScript 1.3.3
+(function() {
+
+ kbi.NodeViewModel = (function() {
+
+ function NodeViewModel(name, opened, node) {
+ var model;
+ this.name = name;
+ this.opened = ko.observable(opened);
+ this.node = ko.utils.unwrapObservable(node);
+ if (this.node instanceof kb.ViewModel) {
+ model = kb.utils.wrappedModel(this.node);
+ this.attribute_names = ko.observableArray(model ? _.keys(model.attributes) : []);
+ }
+ this;
+
+ }
+
+ NodeViewModel.prototype.attributeType = function(key) {
+ var attribute_connector;
+ attribute_connector = this.node[key];
+ if (ko.utils.unwrapObservable(attribute_connector) instanceof kb.ViewModel) {
+ return 'model';
+ }
+ if (kb.utils.observableInstanceOf(attribute_connector, kb.CollectionAttributeConnector)) {
+ return 'collection';
+ }
+ return 'simple';
+ };
+
+ return NodeViewModel;
+
+ })();
+
+}).call(this);
6 tutorials/coffeescript/step3/javascripts/views/kbi_collection_node_view.js
@@ -0,0 +1,6 @@
+// Generated by CoffeeScript 1.3.3
+(function() {
+
+ kbi.CollectionNodeView = "<li class='kbi' data-bind=\"css: {opened: opened, closed: !opened()}\">\n <div class='collection' data-bind=\"click: function(){ opened(!opened()) }\">\n <span data-bind=\"text: (opened() ? '- ' : '+ ' )\"></span>\n <span data-bind=\"text: name\"></span>\n </div>\n\n <!-- ko if: opened -->\n <!-- ko foreach: node -->\n <ul class='kbi' data-bind=\"template: {name: 'kbi_model_node', data: new kbi.NodeViewModel('['+$index()+']', false, $data)}\"></ul>\n <!-- /ko -->\n <!-- /ko -->\n</li>";
+
+}).call(this);
6 tutorials/coffeescript/step3/javascripts/views/kbi_model_node_view.js
@@ -0,0 +1,6 @@
+// Generated by CoffeeScript 1.3.3
+(function() {
+
+ kbi.ModelNodeView = "<li class='kbi' data-bind=\"css: {opened: opened, closed: !opened()}\">\n <div class='kbi model' data-bind=\"click: function(){ opened(!opened()); }\">\n <span data-bind=\"text: (opened() ? '- ' : '+ ' )\"></span>\n <span data-bind=\"text: name\"></span>\n </div>\n\n <!-- ko if: opened -->\n <!-- ko foreach: attribute_names -->\n\n <!-- ko if: ($parent.attributeType($data) == 'simple') -->\n <fieldset class='kbi'>\n <label data-bind=\"text: $data\"> </label>\n <input type='text' data-bind=\"value: $parent.node[$data]\">\n </fieldset>\n <!-- /ko -->\n\n <!-- ko if: ($parent.attributeType($data) == 'model') -->\n <ul class='kbi' data-bind=\"template: {name: 'kbi_model_node', data: new kbi.NodeViewModel($data, false, $parent.node[$data])}\"></ul>\n <!-- /ko -->\n\n <!-- ko if: ($parent.attributeType($data) == 'collection') -->\n <ul class='kbi' data-bind=\"template: {name: 'kbi_collection_node', data: new kbi.NodeViewModel($data+'[]', true, $parent.node[$data])}\"></ul>\n <!-- /ko -->\n\n <!-- /ko -->\n <!-- /ko -->\n</li>";
+
+}).call(this);
10 tutorials/coffeescript/step3/src/lib/kbi_core.coffee
@@ -0,0 +1,10 @@
+###
+knockback-inspector.js 0.1.0
+(c) 2012 Kevin Malakoff.
+Knockback-Inspector.js is freely distributable under the MIT license.
+See the following for full license details:
+ https://github.com/kmalakoff/knockback-inspector/blob/master/LICENSE
+Dependencies: Knockout.js, Underscore.js, Backbone.js, and Knockback.js.
+###
+@kbi or={}
+@kbi.VERSION = '0.1.0'
15 tutorials/coffeescript/step3/src/lib/kbi_string_template_engine.coffee
@@ -0,0 +1,15 @@
+# template source
+class kbi.StringTemplateSource
+ constructor: (@template_string) ->
+ text: (value) -> return @template_string
+
+# template engine
+class kbi.StringTemplateEngine extends ko.nativeTemplateEngine
+ constructor: ->
+ @allowTemplateRewriting = false
+
+ makeTemplateSource: (template) ->
+ switch (template)
+ when 'kbi_model_node' then return new kbi.StringTemplateSource(kbi.ModelNodeView)
+ when 'kbi_collection_node' then return new kbi.StringTemplateSource(kbi.CollectionNodeView)
+ else return ko.nativeTemplateEngine.prototype.makeTemplateSource.apply(this, arguments)
17 tutorials/coffeescript/step3/src/view_models/kbi_node_view_model.coffee
@@ -0,0 +1,17 @@
+class kbi.NodeViewModel
+ constructor: (name, opened, node) ->
+ @name = name
+ @opened = ko.observable(opened)
+ @node = ko.utils.unwrapObservable(node)
+
+ # a kb.ViewModel indicates the node is a Backbone.Model
+ if (@node instanceof kb.ViewModel)
+ model = kb.utils.wrappedModel(@node)
+ @attribute_names = ko.observableArray(if model then _.keys(model.attributes) else [])
+ @
+
+ attributeType: (key) ->
+ attribute_connector = @node[key]
+ return 'model' if (ko.utils.unwrapObservable(attribute_connector) instanceof kb.ViewModel)
+ return 'collection' if kb.utils.observableInstanceOf(attribute_connector, kb.CollectionAttributeConnector)
+ return 'simple'
14 tutorials/coffeescript/step3/src/views/kbi_collection_node_view.coffee
@@ -0,0 +1,14 @@
+kbi.CollectionNodeView = """
+<li class='kbi' data-bind="css: {opened: opened, closed: !opened()}">
+ <div class='collection' data-bind="click: function(){ opened(!opened()) }">
+ <span data-bind="text: (opened() ? '- ' : '+ ' )"></span>
+ <span data-bind="text: name"></span>
+ </div>
+
+ <!-- ko if: opened -->
+ <!-- ko foreach: node -->
+ <ul class='kbi' data-bind="template: {name: 'kbi_model_node', data: new kbi.NodeViewModel('['+$index()+']', false, $data)}"></ul>
+ <!-- /ko -->
+ <!-- /ko -->
+</li>
+"""
29 tutorials/coffeescript/step3/src/views/kbi_model_node_view.coffee
@@ -0,0 +1,29 @@
+kbi.ModelNodeView = """
+<li class='kbi' data-bind="css: {opened: opened, closed: !opened()}">
+ <div class='kbi model' data-bind="click: function(){ opened(!opened()); }">
+ <span data-bind="text: (opened() ? '- ' : '+ ' )"></span>
+ <span data-bind="text: name"></span>
+ </div>
+
+ <!-- ko if: opened -->
+ <!-- ko foreach: attribute_names -->
+
+ <!-- ko if: ($parent.attributeType($data) == 'simple') -->
+ <fieldset class='kbi'>
+ <label data-bind="text: $data"> </label>
+ <input type='text' data-bind="value: $parent.node[$data]">
+ </fieldset>
+ <!-- /ko -->
+
+ <!-- ko if: ($parent.attributeType($data) == 'model') -->
+ <ul class='kbi' data-bind="template: {name: 'kbi_model_node', data: new kbi.NodeViewModel($data, false, $parent.node[$data])}"></ul>
+ <!-- /ko -->
+
+ <!-- ko if: ($parent.attributeType($data) == 'collection') -->
+ <ul class='kbi' data-bind="template: {name: 'kbi_collection_node', data: new kbi.NodeViewModel($data+'[]', true, $parent.node[$data])}"></ul>
+ <!-- /ko -->
+
+ <!-- /ko -->
+ <!-- /ko -->
+</li>
+"""
21 tutorials/coffeescript/step4/Cakefile
@@ -0,0 +1,21 @@
+{print} = require 'util'
+{spawn} = require 'child_process'
+path = require 'path'
+
+task 'build', 'Build javascripts/ from src/', ->
+ coffee = spawn 'coffee', ['-c', '-o', 'javascripts', 'src']
+ coffee.stderr.on 'data', (data) ->
+ process.stderr.write data.toString()
+ coffee.stdout.on 'data', (data) ->
+ print data.toString()
+ coffee.on 'exit', (code) ->
+ callback?() if code is 0
+
+task 'watch', 'Watch src/ for changes', ->
+ coffee = spawn 'coffee', ['-w', '-o', 'javascripts', 'src']
+ coffee.stderr.on 'data', (data) ->
+ print 'Error'
+ process.stderr.write data.toString()
+ coffee.stdout.on 'data', (data) ->
+ print data.toString()
+ callback?()
73 tutorials/coffeescript/step4/index.html
@@ -0,0 +1,73 @@
+<!doctype html>
+<head>
+ <meta charset='utf-8'>
+ <meta http-equiv='X-UA-Compatible' content='IE=edge,chrome=1'>
+ <title class='inspector_title' data-bind="text: title"></title>
+
+ <!-- DEPENDENCIES -->
+ <script src='../../../vendor/jquery-1.7.2.js'></script>
+ <script src='../../../vendor/knockout-2.1.0.js'></script>
+ <script src='../../../vendor/underscore-1.3.1.js'></script>
+ <script src='../../../vendor/backbone-0.9.2.js'></script>
+ <script src='../../../vendor/backbone-relational.js'></script>
+ <script src='../../../vendor/knockback-0.15.1.js'></script>
+
+ <!-- KNOCKBACK-INSPECTOR LIBRARY -->
+ <script src='javascripts/lib/kbi_core.js'></script>
+ <script src='javascripts/view_models/kbi_node_view_model.js'></script>
+ <script src='javascripts/views/kbi_collection_node_view.js'></script>
+ <script src='javascripts/views/kbi_model_node_view.js'></script>
+ <script src='javascripts/lib/kbi_string_template_engine.js'></script>
+ <script src='javascripts/models/kbi_fetched.js'></script>
+
+ <!-- APPLICATION STYLING -->
+ <style type='text/css'>
+ #content {width: 800px; margin: 0 auto; background-color: #F5E0FF; border-radius: 5px;}
+ h1 {text-align: center; font-size: 2em; color: #fff; background-color: #CC66FF; border-radius: 5px;}
+ </style>
+ <link rel='stylesheet' href="../../../knockback-inspector.css">
+
+ <!-- KNOCKOUT INITIALIZATION -->
+ <script type='text/javascript'>
+ // set the template engine so Knockout can find 'kbi_model_node' and 'kbi_collection_node' templates
+ ko.setTemplateEngine(new kbi.StringTemplateEngine());
+ </script>
+
+</head>
+<body>
+ <div id='content'>
+ <h1 class='inspector_title' data-bind="text: title"></h1>
+
+ <!-- Step 1: Setting up the Project -->
+ <script type='text/javascript'>
+ var app_model = new Backbone.Model({title: 'Knockback Inspector (' + kbi.VERSION + ')'});
+ $('.inspector_title').each(function(){ ko.applyBindings(kb.viewModel(app_model), this); });
+ </script>
+
+ <!-- Step 4: Rendering a Twitter Query using kb.CollectionObservable -->
+ <ul class='kbi' id='fetched_collection'>
+ <li class='kbi'>
+ <fieldset class='kbi'>
+ <label>URL</label>
+ <input type='text' data-bind="value: url">
+ </fieldset>
+ <ul class='kbi' data-bind="template: {name: 'kbi_collection_node', data: new kbi.NodeViewModel('root', true, collection)}"></ul>
+ </li>
+ </ul>
+
+ <script type='text/javascript'>
+ var custom_url_model = new Backbone.Model({url: '', collection: new kbi.FetchedCollection()});
+ var view_model = kb.viewModel(custom_url_model);
+ view_model.url.subscribe(function(url){
+ custom_url_model.get('collection').url = url;
+ custom_url_model.get('collection').fetch();
+ });
+
+ ko.applyBindings(view_model, $('#fetched_collection')[0]);
+ view_model.url('http://search.twitter.com/search.json?q=knockbackjs&callback=?');
+ </script>
+
+ </div>
+</body>
+
+</html>
19 tutorials/coffeescript/step4/javascripts/lib/kbi_core.js
@@ -0,0 +1,19 @@
+// Generated by CoffeeScript 1.3.3
+
+/*
+knockback-inspector.js 0.1.0
+(c) 2012 Kevin Malakoff.
+Knockback-Inspector.js is freely distributable under the MIT license.
+See the following for full license details:
+ https://github.com/kmalakoff/knockback-inspector/blob/master/LICENSE
+Dependencies: Knockout.js, Underscore.js, Backbone.js, and Knockback.js.
+*/
+
+
+(function() {
+
+ this.kbi || (this.kbi = {});
+
+ this.kbi.VERSION = '0.1.0';
+
+}).call(this);
43 tutorials/coffeescript/step4/javascripts/lib/kbi_string_template_engine.js
@@ -0,0 +1,43 @@
+// Generated by CoffeeScript 1.3.3
+(function() {
+ var __hasProp = {}.hasOwnProperty,
+ __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+ kbi.StringTemplateSource = (function() {
+
+ function StringTemplateSource(template_string) {
+ this.template_string = template_string;
+ }
+
+ StringTemplateSource.prototype.text = function(value) {
+ return this.template_string;
+ };
+
+ return StringTemplateSource;
+
+ })();
+
+ kbi.StringTemplateEngine = (function(_super) {
+
+ __extends(StringTemplateEngine, _super);
+
+ function StringTemplateEngine() {
+ this.allowTemplateRewriting = false;
+ }
+
+ StringTemplateEngine.prototype.makeTemplateSource = function(template) {
+ switch (template) {
+ case 'kbi_model_node':
+ return new kbi.StringTemplateSource(kbi.ModelNodeView);
+ case 'kbi_collection_node':
+ return new kbi.StringTemplateSource(kbi.CollectionNodeView);
+ default:
+ return ko.nativeTemplateEngine.prototype.makeTemplateSource.apply(this, arguments);
+ }
+ };
+
+ return StringTemplateEngine;
+
+ })(ko.nativeTemplateEngine);
+
+}).call(this);
54 tutorials/coffeescript/step4/javascripts/models/kbi_fetched.js
@@ -0,0 +1,54 @@
+// Generated by CoffeeScript 1.3.3
+(function() {
+ var __hasProp = {}.hasOwnProperty,
+ __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+ kbi.FetchedModel = (function(_super) {
+
+ __extends(FetchedModel, _super);
+
+ function FetchedModel() {
+ return FetchedModel.__super__.constructor.apply(this, arguments);
+ }
+
+ FetchedModel.prototype.parse = function(response) {
+ var attributes, collection, key, model, value;
+ attributes = {};
+ for (key in response) {
+ value = response[key];
+ if (_.isObject(value)) {
+ model = new kbi.FetchedModel();
+ attributes[key] = model.set(model.parse(value));
+ } else if (_.isArray(value)) {
+ collection = new kbi.FetchedCollection();
+ attributes[key] = collection.reset(collection.parse(value));
+ } else {
+ attributes[key] = value;
+ }
+ }
+ return attributes;
+ };
+
+ return FetchedModel;
+
+ })(Backbone.Model);
+
+ kbi.FetchedCollection = (function(_super) {
+
+ __extends(FetchedCollection, _super);
+
+ function FetchedCollection() {
+ return FetchedCollection.__super__.constructor.apply(this, arguments);
+ }
+
+ FetchedCollection.prototype.model = kbi.FetchedModel;
+
+ FetchedCollection.prototype.parse = function(response) {
+ return response.results;
+ };
+
+ return FetchedCollection;
+
+ })(Backbone.Collection);
+
+}).call(this);
35 tutorials/coffeescript/step4/javascripts/view_models/kbi_node_view_model.js
@@ -0,0 +1,35 @@
+// Generated by CoffeeScript 1.3.3
+(function() {
+
+ kbi.NodeViewModel = (function() {
+
+ function NodeViewModel(name, opened, node) {
+ var model;
+ this.name = name;
+ this.opened = ko.observable(opened);
+ this.node = ko.utils.unwrapObservable(node);
+ if (this.node instanceof kb.ViewModel) {
+ model = kb.utils.wrappedModel(this.node);
+ this.attribute_names = ko.observableArray(model ? _.keys(model.attributes) : []);
+ }
+ this;
+
+ }
+
+ NodeViewModel.prototype.attributeType = function(key) {
+ var attribute_connector;
+ attribute_connector = this.node[key];
+ if (ko.utils.unwrapObservable(attribute_connector) instanceof kb.ViewModel) {
+ return 'model';
+ }
+ if (kb.utils.observableInstanceOf(attribute_connector, kb.CollectionAttributeConnector)) {
+ return 'collection';
+ }
+ return 'simple';
+ };
+
+ return NodeViewModel;
+
+ })();
+
+}).call(this);
6 tutorials/coffeescript/step4/javascripts/views/kbi_collection_node_view.js
@@ -0,0 +1,6 @@
+// Generated by CoffeeScript 1.3.3
+(function() {
+
+ kbi.CollectionNodeView = "<li class='kbi' data-bind=\"css: {opened: opened, closed: !opened()}\">\n <div class='collection' data-bind=\"click: function(){ opened(!opened()) }\">\n <span data-bind=\"text: (opened() ? '- ' : '+ ' )\"></span>\n <span data-bind=\"text: name\"></span>\n </div>\n\n <!-- ko if: opened -->\n <!-- ko foreach: node -->\n <ul class='kbi' data-bind=\"template: {name: 'kbi_model_node', data: new kbi.NodeViewModel('['+$index()+']', false, $data)}\"></ul>\n <!-- /ko -->\n <!-- /ko -->\n</li>";
+
+}).call(this);
6 tutorials/coffeescript/step4/javascripts/views/kbi_model_node_view.js
@@ -0,0 +1,6 @@
+// Generated by CoffeeScript 1.3.3
+(function() {
+
+ kbi.ModelNodeView = "<li class='kbi' data-bind=\"css: {opened: opened, closed: !opened()}\">\n <div class='kbi model' data-bind=\"click: function(){ opened(!opened()); }\">\n <span data-bind=\"text: (opened() ? '- ' : '+ ' )\"></span>\n <span data-bind=\"text: name\"></span>\n </div>\n\n <!-- ko if: opened -->\n <!-- ko foreach: attribute_names -->\n\n <!-- ko if: ($parent.attributeType($data) == 'simple') -->\n <fieldset class='kbi'>\n <label data-bind=\"text: $data\"> </label>\n <input type='text' data-bind=\"value: $parent.node[$data]\">\n </fieldset>\n <!-- /ko -->\n\n <!-- ko if: ($parent.attributeType($data) == 'model') -->\n <ul class='kbi' data-bind=\"template: {name: 'kbi_model_node', data: new kbi.NodeViewModel($data, false, $parent.node[$data])}\"></ul>\n <!-- /ko -->\n\n <!-- ko if: ($parent.attributeType($data) == 'collection') -->\n <ul class='kbi' data-bind=\"template: {name: 'kbi_collection_node', data: new kbi.NodeViewModel($data+'[]', true, $parent.node[$data])}\"></ul>\n <!-- /ko -->\n\n <!-- /ko -->\n <!-- /ko -->\n</li>";
+
+}).call(this);
10 tutorials/coffeescript/step4/src/lib/kbi_core.coffee
@@ -0,0 +1,10 @@
+###
+knockback-inspector.js 0.1.0
+(c) 2012 Kevin Malakoff.
+Knockback-Inspector.js is freely distributable under the MIT license.
+See the following for full license details:
+ https://github.com/kmalakoff/knockback-inspector/blob/master/LICENSE
+Dependencies: Knockout.js, Underscore.js, Backbone.js, and Knockback.js.
+###
+@kbi or={}
+@kbi.VERSION = '0.1.0'
15 tutorials/coffeescript/step4/src/lib/kbi_string_template_engine.coffee
@@ -0,0 +1,15 @@
+# template source
+class kbi.StringTemplateSource
+ constructor: (@template_string) ->
+ text: (value) -> return @template_string
+
+# template engine
+class kbi.StringTemplateEngine extends ko.nativeTemplateEngine
+ constructor: ->
+ @allowTemplateRewriting = false
+
+ makeTemplateSource: (template) ->
+ switch (template)
+ when 'kbi_model_node' then return new kbi.StringTemplateSource(kbi.ModelNodeView)
+ when 'kbi_collection_node' then return new kbi.StringTemplateSource(kbi.CollectionNodeView)
+ else return ko.nativeTemplateEngine.prototype.makeTemplateSource.apply(this, arguments)
19 tutorials/coffeescript/step4/src/models/kbi_fetched.coffee
@@ -0,0 +1,19 @@
+# General-purpose model for fetched JSON when you do not have a specialized implementation
+class kbi.FetchedModel extends Backbone.Model
+ parse: (response) ->
+ attributes = {}
+ for key, value of response
+ if _.isObject(value)
+ model = new kbi.FetchedModel()
+ attributes[key] = model.set(model.parse(value))
+ else if _.isArray(value)
+ collection = new kbi.FetchedCollection()
+ attributes[key] = collection.reset(collection.parse(value))
+ else
+ attributes[key] = value
+ return attributes
+
+# General-purpose collection for fetched JSON when you do not have a specialized implementation
+class kbi.FetchedCollection extends Backbone.Collection
+ model: kbi.FetchedModel
+ parse: (response) -> return response.results
17 tutorials/coffeescript/step4/src/view_models/kbi_node_view_model.coffee
@@ -0,0 +1,17 @@
+class kbi.NodeViewModel
+ constructor: (name, opened, node) ->
+ @name = name
+ @opened = ko.observable(opened)
+ @node = ko.utils.unwrapObservable(node)
+
+ # a kb.ViewModel indicates the node is a Backbone.Model
+ if (@node instanceof kb.ViewModel)
+ model = kb.utils.wrappedModel(@node)
+ @attribute_names = ko.observableArray(if model then _.keys(model.attributes) else [])
+ @
+
+ attributeType: (key) ->
+ attribute_connector = @node[key]
+ return 'model' if (ko.utils.unwrapObservable(attribute_connector) instanceof kb.ViewModel)
+ return 'collection' if kb.utils.observableInstanceOf(attribute_connector, kb.CollectionAttributeConnector)
+ return 'simple'
14 tutorials/coffeescript/step4/src/views/kbi_collection_node_view.coffee
@@ -0,0 +1,14 @@
+kbi.CollectionNodeView = """
+<li class='kbi' data-bind="css: {opened: opened, closed: !opened()}">
+ <div class='collection' data-bind="click: function(){ opened(!opened()) }">
+ <span data-bind="text: (opened() ? '- ' : '+ ' )"></span>
+ <span data-bind="text: name"></span>
+ </div>
+
+ <!-- ko if: opened -->
+ <!-- ko foreach: node -->
+ <ul class='kbi' data-bind="template: {name: 'kbi_model_node', data: new kbi.NodeViewModel('['+$index()+']', false, $data)}"></ul>
+ <!-- /ko -->
+ <!-- /ko -->
+</li>
+"""
29 tutorials/coffeescript/step4/src/views/kbi_model_node_view.coffee
@@ -0,0 +1,29 @@
+kbi.ModelNodeView = """
+<li class='kbi' data-bind="css: {opened: opened, closed: !opened()}">
+ <div class='kbi model' data-bind="click: function(){ opened(!opened()); }">
+ <span data-bind="text: (opened() ? '- ' : '+ ' )"></span>
+ <span data-bind="text: name"></span>
+ </div>
+
+ <!-- ko if: opened -->
+ <!-- ko foreach: attribute_names -->
+
+ <!-- ko if: ($parent.attributeType($data) == 'simple') -->
+ <fieldset class='kbi'>
+ <label data-bind="text: $data"> </label>
+ <input type='text' data-bind="value: $parent.node[$data]">
+ </fieldset>
+ <!-- /ko -->
+
+ <!-- ko if: ($parent.attributeType($data) == 'model') -->
+ <ul class='kbi' data-bind="template: {name: 'kbi_model_node', data: new kbi.NodeViewModel($data, false, $parent.node[$data])}"></ul>
+ <!-- /ko -->
+
+ <!-- ko if: ($parent.attributeType($data) == 'collection') -->
+ <ul class='kbi' data-bind="template: {name: 'kbi_collection_node', data: new kbi.NodeViewModel($data+'[]', true, $parent.node[$data])}"></ul>
+ <!-- /ko -->
+
+ <!-- /ko -->
+ <!-- /ko -->
+</li>
+"""
39 tutorials/javascript/step1/index.html
@@ -0,0 +1,39 @@
+<!doctype html>
+<head>
+ <meta charset='utf-8'>
+ <meta http-equiv='X-UA-Compatible' content='IE=edge,chrome=1'>
+ <title class='inspector_title' data-bind="text: title"></title>
+
+ <!-- DEPENDENCIES -->
+ <script src='../../../vendor/jquery-1.7.2.js'></script>
+ <script src='../../../vendor/knockout-2.1.0.js'></script>
+ <script src='../../../vendor/underscore-1.3.1.js'></script>
+ <script src='../../../vendor/backbone-0.9.2.js'></script>
+ <script src='../../../vendor/backbone-relational.js'></script>
+ <script src='../../../vendor/knockback-0.15.1.js'></script>
+
+
+ <!-- KNOCKBACK-INSPECTOR LIBRARY -->
+ <script src='javascripts/lib/kbi_core.js'></script>
+
+ <!-- APPLICATION STYLING -->
+ <style type='text/css'>
+ #content {width: 800px; margin: 0 auto; background-color: #F5E0FF; border-radius: 5px;}
+ h1 {text-align: center; font-size: 2em; color: #fff; background-color: #CC66FF; border-radius: 5px;}
+ </style>
+
+</head>
+<body>
+ <div id='content'>
+ <h1 class='inspector_title' data-bind="text: title"></h1>
+
+ <!-- Step 1: Setting up the Project -->
+ <script type='text/javascript'>
+ var app_model = new Backbone.Model({title: 'Knockback Inspector (' + kbi.VERSION + ')'});
+ $('.inspector_title').each(function(){ ko.applyBindings(kb.viewModel(app_model), this); });
+ </script>
+
+ </div>
+</body>
+
+</html>
10 tutorials/javascript/step1/javascripts/lib/kbi_core.js
@@ -0,0 +1,10 @@
+/*
+knockback-inspector.js 0.1.0
+(c) 2012 Kevin Malakoff.
+Knockback-Inspector.js is freely distributable under the MIT license.
+See the following for full license details:
+ https://github.com/kmalakoff/knockback-inspector/blob/master/LICENSE
+Dependencies: Knockout.js, Backbone.js, Underscore.js, and Knockback.js.
+*/
+this.kbi || (this.kbi = {});
+this.kbi.VERSION = '0.1.0';
63 tutorials/javascript/step2/index.html
@@ -0,0 +1,63 @@
+<!doctype html>
+<head>
+ <meta charset='utf-8'>
+ <meta http-equiv='X-UA-Compatible' content='IE=edge,chrome=1'>
+ <title class='inspector_title' data-bind="text: title"></title>
+
+ <!-- DEPENDENCIES -->
+ <script src='../../../vendor/jquery-1.7.2.js'></script>
+ <script src='../../../vendor/knockout-2.1.0.js'></script>
+ <script src='../../../vendor/underscore-1.3.1.js'></script>
+ <script src='../../../vendor/backbone-0.9.2.js'></script>
+ <script src='../../../vendor/backbone-relational.js'></script>
+ <script src='../../../vendor/knockback-0.15.1.js'></script>
+
+
+ <!-- KNOCKBACK-INSPECTOR LIBRARY -->
+ <script src='javascripts/lib/kbi_core.js'></script>
+ <script src='javascripts/view_models/kbi_node_view_model.js'></script>
+ <script src='javascripts/views/kbi_collection_node_view.js'></script>
+ <script src='javascripts/views/kbi_model_node_view.js'></script>
+ <script src='javascripts/lib/kbi_string_template_engine.js'></script>
+
+ <!-- APPLICATION STYLING -->
+ <style type='text/css'>
+ #content {width: 800px; margin: 0 auto; background-color: #F5E0FF; border-radius: 5px;}
+ h1 {text-align: center; font-size: 2em; color: #fff; background-color: #CC66FF; border-radius: 5px;}
+ </style>
+ <link rel='stylesheet' href="../../../knockback-inspector.css">
+
+ <!-- KNOCKOUT INITIALIZATION -->
+ <script type='text/javascript'>
+ // set the template engine so Knockout can find 'kbi_model_node' and 'kbi_collection_node' templates
+ ko.setTemplateEngine(new kbi.StringTemplateEngine());
+ </script>
+
+</head>
+<body>
+ <div id='content'>
+ <h1 class='inspector_title' data-bind="text: title"></h1>
+
+ <!-- Step 1: Setting up the Project -->
+ <script type='text/javascript'>
+ var app_model = new Backbone.Model({title: 'Knockback Inspector (' + kbi.VERSION + ')'});
+ $('.inspector_title').each(function(){ ko.applyBindings(kb.viewModel(app_model), this); });
+ </script>
+
+ <!-- Step 2: Rendering a Backbone.Model using kb.ViewModel -->
+ <ul id='backbone_model' class='kbi' data-bind="template: {name: 'kbi_model_node', data: new kbi.NodeViewModel('root', true, $data)}"></ul>
+ <script type='text/javascript'>
+ var model1 = new Backbone.Model({name: 'Model1', pet: 'frog', friends: new Backbone.Collection([])});
+ var model2 = new Backbone.Model({name: 'Model2', pet: 'dog', friends: new Backbone.Collection([])});
+ var model3 = new Backbone.Model({name: 'Model3', pet: '(none)', friends: new Backbone.Collection([])});
+ model1.get('friends').reset([model2, model3]);
+ model2.get('friends').reset([model1, model3]);
+ model3.get('friends').reset([model1, model2]);
+
+ ko.applyBindings(kb.viewModel(model1), $('#backbone_model')[0]);
+ </script>
+
+ </div>
+</body>
+
+</html>
10 tutorials/javascript/step2/javascripts/lib/kbi_core.js
@@ -0,0 +1,10 @@
+/*
+knockback-inspector.js 0.1.0
+(c) 2012 Kevin Malakoff.
+Knockback-Inspector.js is freely distributable under the MIT license.
+See the following for full license details:
+ https://github.com/kmalakoff/knockback-inspector/blob/master/LICENSE
+Dependencies: Knockout.js, Backbone.js, Underscore.js, and Knockback.js.
+*/
+this.kbi || (this.kbi = {});
+this.kbi.VERSION = '0.1.0';
17 tutorials/javascript/step2/javascripts/lib/kbi_string_template_engine.js
@@ -0,0 +1,17 @@
+// template source
+kbi.StringTemplateSource = function(template_string) { this.template_string = template_string; };
+kbi.StringTemplateSource.prototype.text = function(value) { return this.template_string; };
+
+// template engine
+kbi.StringTemplateEngine = function() { this.allowTemplateRewriting = false; };
+kbi.StringTemplateEngine.prototype = new ko.nativeTemplateEngine();
+kbi.StringTemplateEngine.prototype.makeTemplateSource = function(template) {
+ switch (template) {
+ case 'kbi_model_node':
+ return new kbi.StringTemplateSource(kbi.ModelNodeView);
+ case 'kbi_collection_node':
+ return new kbi.StringTemplateSource(kbi.CollectionNodeView);
+ default:
+ return ko.nativeTemplateEngine.prototype.makeTemplateSource.apply(this, arguments);
+ }
+};
21 tutorials/javascript/step2/javascripts/view_models/kbi_node_view_model.js
@@ -0,0 +1,21 @@
+kbi.NodeViewModel = function(name, opened, node) {
+ this.name = name;
+ this.opened = ko.observable(opened);
+ this.node = ko.utils.unwrapObservable(node);
+
+ // a kb.ViewModel indicates the node is a Backbone.Model
+ if (this.node instanceof kb.ViewModel) {
+ var model = kb.utils.wrappedModel(this.node);
+ this.attribute_names = ko.observableArray(model ? _.keys(model.attributes) : []);
+ }
+};
+
+kbi.NodeViewModel.prototype.attributeType = function(key) {
+ var attribute_connector = this.node[key];
+ if (ko.utils.unwrapObservable(attribute_connector) instanceof kb.ViewModel)
+ return 'model';
+ else if (kb.utils.observableInstanceOf(attribute_connector, kb.CollectionAttributeConnector))
+ return 'collection';
+ else
+ return 'simple';
+};
13 tutorials/javascript/step2/javascripts/views/kbi_collection_node_view.js
@@ -0,0 +1,13 @@
+kbi.CollectionNodeView = " \
+<li class='kbi' data-bind=\"css: {opened: opened, closed: !opened()}\"> \
+ <div class='collection' data-bind=\"click: function(){ opened(!opened()) }\"> \
+ <span data-bind=\"text: (opened() ? '- ' : '+ ' )\"></span> \
+ <span data-bind=\"text: name\"></span> \
+ </div> \
+\
+ <!-- ko if: opened --> \
+ <!-- ko foreach: node --> \
+ <ul class='kbi' data-bind=\"template: {name: 'kbi_model_node', data: new kbi.NodeViewModel('['+$index()+']', false, $data)}\"></ul> \
+ <!-- /ko --> \
+ <!-- /ko --> \
+</li>";
27 tutorials/javascript/step2/javascripts/views/kbi_model_node_view.js
@@ -0,0 +1,27 @@
+kbi.ModelNodeView = "<li class='kbi' data-bind=\"css: {opened: opened, closed: !opened()}\"> \
+ <div class='kbi model' data-bind=\"click: function(){ opened(!opened()); }\"> \
+ <span data-bind=\"text: (opened() ? '- ' : '+ ' )\"></span> \
+ <span data-bind=\"text: name\"></span> \
+ </div> \
+\
+ <!-- ko if: opened --> \
+ <!-- ko foreach: attribute_names --> \
+\
+ <!-- ko if: ($parent.attributeType($data) == 'simple') --> \
+ <fieldset class='kbi'> \
+ <label data-bind=\"text: $data\"> </label> \
+ <input type=\"text\" data-bind=\"value: $parent.node[$data]\"> \
+ </fieldset> \
+ <!-- /ko --> \
+\
+ <!-- ko if: ($parent.attributeType($data) == 'model') --> \
+ <ul class='kbi' data-bind=\"template: {name: 'kbi_model_node', data: new kbi.NodeViewModel($data, false, $parent.node[$data])}\"></ul> \
+ <!-- /ko --> \
+\
+ <!-- ko if: ($parent.attributeType($data) == 'collection') --> \
+ <ul class='kbi' data-bind=\"template: {name: 'kbi_collection_node', data: new kbi.NodeViewModel($data+'[]', true, $parent.node[$data])}\"></ul> \
+ <!-- /ko --> \
+\
+ <!-- /ko --> \
+ <!-- /ko --> \
+</li>";
75 tutorials/javascript/step3/index.html
@@ -0,0 +1,75 @@
+<!doctype html>
+<head>
+ <meta charset='utf-8'>
+ <meta http-equiv='X-UA-Compatible' content='IE=edge,chrome=1'>
+ <title class='inspector_title' data-bind="text: title"></title>
+
+ <!-- DEPENDENCIES -->
+ <script src='../../../vendor/jquery-1.7.2.js'></script>
+ <script src='../../../vendor/knockout-2.1.0.js'></script>
+ <script src='../../../vendor/underscore-1.3.1.js'></script>
+ <script src='../../../vendor/backbone-0.9.2.js'></script>
+ <script src='../../../vendor/backbone-relational.js'></script>
+ <script src='../../../vendor/knockback-0.15.1.js'></script>
+
+
+ <!-- KNOCKBACK-INSPECTOR LIBRARY -->
+ <script src='javascripts/lib/kbi_core.js'></script>
+ <script src='javascripts/view_models/kbi_node_view_model.js'></script>
+ <script src='javascripts/views/kbi_collection_node_view.js'></script>
+ <script src='javascripts/views/kbi_model_node_view.js'></script>
+ <script src='javascripts/lib/kbi_string_template_engine.js'></script>
+
+ <!-- APPLICATION STYLING -->
+ <style type='text/css'>
+ #content {width: 800px; margin: 0 auto; background-color: #F5E0FF; border-radius: 5px;}
+ h1 {text-align: center; font-size: 2em; color: #fff; background-color: #CC66FF; border-radius: 5px;}
+ </style>
+ <link rel='stylesheet' href="../../../knockback-inspector.css">
+
+ <!-- KNOCKOUT INITIALIZATION -->
+ <script type='text/javascript'>
+ // set the template engine so Knockout can find 'kbi_model_node' and 'kbi_collection_node' templates
+ ko.setTemplateEngine(new kbi.StringTemplateEngine());
+ </script>
+
+</head>
+<body>
+ <div id='content'>
+ <h1 class='inspector_title' data-bind="text: title"></h1>
+
+ <!-- Step 1: Setting up the Project -->
+ <script type='text/javascript'>
+ var app_model = new Backbone.Model({title: 'Knockback Inspector (' + kbi.VERSION + ')'});
+ $('.inspector_title').each(function(){ ko.applyBindings(kb.viewModel(app_model), this); });
+ </script>
+
+ <!-- Step 3: Rendering a BackboneRelational using kb.CollectionObservable -->
+ <ul id='backbone_relational' class='kbi' data-bind="template: {name: 'kbi_collection_node', data: new kbi.NodeViewModel('root', true, $data)}"></ul>
+ <script type='text/javascript'>
+ var Person = Backbone.RelationalModel.extend({});
+ var House = Backbone.RelationalModel.extend({
+ relations: [{
+ type: Backbone.HasMany,
+ key: 'occupants',
+ relatedModel: 'Person',
+ reverseRelation: {
+ key: 'livesIn'
+ }
+ }]
+ });
+
+ var bob = new Person({id: 'person-1', name: 'Bob'});
+ var fred = new Person({id: 'person-2', name: 'Fred'});
+ var house = new House({
+ location: 'In the middle of our street',
+ occupants: ['person-1', 'person-2']
+ });
+
+ ko.applyBindings(kb.collectionObservable(house.get('occupants'), {view_model: kb.ViewModel}), $('#backbone_relational')[0]);
+ </script>
+
+ </div>
+</body>
+
+</html>
10 tutorials/javascript/step3/javascripts/lib/kbi_core.js
@@ -0,0 +1,10 @@
+/*
+knockback-inspector.js 0.1.0
+(c) 2012 Kevin Malakoff.
+Knockback-Inspector.js is freely distributable under the MIT license.
+See the following for full license details:
+ https://github.com/kmalakoff/knockback-inspector/blob/master/LICENSE