Permalink
Browse files

Initial commit. Core idea is working. Backbone.Model regression tests…

… pass. Fiddle demonstrates concept.
  • Loading branch information...
0 parents commit e285c7bd0c485038024a8e7c147fd4b3dfa750c1 @hunterloftis committed May 3, 2012
@@ -0,0 +1,129 @@
+(function(Backbone) {
+
+ // description is an object with: node, viewModel, attribute
+ var TextBinding = function(description) {
+ _.bindAll(this);
+
+ console.log("New 'text' binding:", description);
+ _.extend(this, description);
+
+ this.bind();
+ this.onModelChange();
+ };
+
+ _.extend(TextBinding.prototype, {
+
+ bind: function() {
+ this.viewModel.on('change:' + this.attribute, this.onModelChange);
+ },
+
+ onModelChange: function() {
+ var val = this.viewModel.get(this.attribute);
+ $(this.node).text(val);
+ },
+
+ unbind: function() {
+ this.viewModel.removeListener('change:' + this.attribute, this.update);
+ }
+ });
+
+ var ValBinding = function(description) {
+ _.bindAll(this);
+
+ console.log("New 'val' binding:", description);
+ _.extend(this, description);
+
+ this.bind();
+ this.onModelChange();
+ };
+
+ _.extend(ValBinding.prototype, {
+
+ bind: function() {
+ this.viewModel.on('change:', this.attribute, this.onModelChange);
+ $(this.node).on('keyup change', this.onViewChange);
+ },
+
+ onModelChange: function() {
+ var val = this.viewModel.get(this.attribute);
+ $(this.node).val(val);
+ },
+
+ onViewChange: function() {
+ var val = $(this.node).val();
+ this.viewModel.set(this.attribute, val);
+ },
+
+ unbind: function() {
+
+ }
+ });
+
+ Backbone.ViewModel = Backbone.Model.extend({
+
+ initialize: function(attributes, options) {
+ this._bindings = [];
+ },
+
+ bindTo: function(attribute, container) {
+ container = container || 'body';
+ var nodes = $(container).find('*[' + attribute + ']');
+
+ _.each(nodes, this.bindToNode(attribute));
+ },
+
+ isBoundTo: function(node) {
+ return _.any(this._bindings, function(binding) {
+ return binding.node === node;
+ });
+ },
+
+ bindToNode: function(attribute) {
+ var self = this;
+ return function(node) {
+ if (self.isBoundTo(node)) return;
+
+ var bindingString = $(node).attr(attribute);
+ var bindingList = bindingString.split(',');
+ var descriptions = _.map(bindingList, self.parseBinding(node));
+
+ _.each(descriptions, self.createBinding, self);
+ };
+ },
+
+ parseBinding: function(node) {
+ var self = this;
+ return function(bindingPair) {
+ var bindingSplit = bindingPair.split(':');
+ var type = bindingSplit[0].trim();
+ var attribute = bindingSplit[1].trim();
+ return {
+ type: type,
+ node: node,
+ viewModel: self,
+ attribute: attribute
+ };
+ };
+ },
+
+ createBinding: function(description) {
+ var Binding = Backbone.ViewModel.bindings[description.type];
+ if (Binding) {
+ var binding = new Binding(description);
+ if (binding) {
+ this._bindings.push(binding);
+ return binding;
+ }
+ throw new Error("Unable to create '" + description.type + "' binding");
+ }
+ throw new Error("Trying to create a binding of unknown type '" + description.type + "'");
+ }
+
+ }, {
+ bindings: {
+ text: TextBinding,
+ val: ValBinding
+ }
+ });
+
+})(Backbone);
@@ -0,0 +1,57 @@
+describe('Attributes', function() {
+ describe('setting and getting', function() {
+ var vm = new Backbone.ViewModel({
+ num: 123,
+ str: 'abc',
+ bool: false
+ });
+ strictEqual(vm.)
+ });
+});
+
+$(document).ready(function() {
+
+ module("Bases");
+
+ test("setting and getting", function() {
+ var base, obj;
+ obj = {
+ num: 123,
+ str: 'abc',
+ bool: false
+ };
+ base = ok.base('abc');
+ strictEqual(base(), 'abc', 'can store a string in a base');
+ base = ok.base(123);
+ strictEqual(base(), 123, 'can store a number in a base');
+ base = ok.base(true);
+ strictEqual(base(), true, 'can store a boolean in a base');
+ base = ok.base(null);
+ strictEqual(base(), null, 'can store null in a base');
+ base = ok.base(undefined);
+ strictEqual(base(), undefined, 'can store undefined in a base');
+ base = ok.base(obj);
+ strictEqual(base(), obj, 'can store an object in a base');
+ base('replaced');
+ strictEqual(base(), 'replaced', 'can replace a stored base');
+ });
+
+ test("undefined bases", function() {
+ var base = ok.base();
+ strictEqual(typeof(base()), 'undefined', 'empty base returns undefined');
+ base('okay');
+ strictEqual(base(), 'okay', 'assigning a value to an empty base works');
+ });
+
+ test("subscribing to updates", function() {
+ var base = ok.base('abc'),
+ val = base();
+ base.subscribe(function(newVal) {
+ val = newVal;
+ });
+ strictEqual(val, 'abc', 'initial value is correct');
+ base(123);
+ strictEqual(val, 123, 'subscription updates on value change');
+ });
+
+});
@@ -0,0 +1,35 @@
+<!doctype html>
+<html>
+<head>
+ <meta charset='utf8'>
+ <title>Backbone Test Suite</title>
+
+ <!-- Test dependencies -->
+ <link rel="stylesheet" href="../vendor/qunit.css" type="text/css" media="screen">
+ <script src="../vendor/qunit.js"></script>
+ <script src="../vendor/jslitmus.js"></script>
+
+ <!-- Platform dependencies -->
+ <script src='../vendor/zepto-1.0rc1.js'></script>
+ <script src='../vendor/underscore-1.3.3.js'></script>
+ <script src='../vendor/backbone-0.9.2.js'></script>
+
+ <!-- Backbone.ViewModel -->
+ <script src='../backbone.viewmodel.js'></script>
+
+ <script src="model.regression.js"></script>
+ <script src="collection.regression.js"></script>
+</head>
+<body>
+ <div id="qunit"></div>
+ <div id="qunit-fixture">
+ <div id='testElement'>
+ <h1>Test</h1>
+ </div>
+ </div>
+ <br>
+ <br>
+ <h1 id="qunit-header"><a href="#">Backbone Speed Suite</a></h1>
+ <div id="jslitmus_container" style="margin: 20px 10px;"></div>
+</body>
+</html>
@@ -0,0 +1,77 @@
+$(document).ready(function() {
+
+ module("Bindings");
+
+ test("simple bindings", function() {
+
+ var viewModel = {
+ testVisible: ok.base(false)
+ };
+ viewModel.testHtml = ok.dependent(function() {
+ if (this.testVisible()) {
+ return 'visible';
+ }
+ return 'invisible';
+ }, viewModel);
+
+ strictEqual($('#testVisible').css('display'), 'block', 'no effect to visible before binding');
+ strictEqual($('#testHtml').html(), 'html', 'no effect to html before binding');
+
+ ok.bind(viewModel);
+
+ strictEqual($('#testVisible').css('display'), 'none', 'bind visible to a base value');
+ strictEqual($('#testHtml').html(), 'invisible', 'bind html to a dependent value');
+
+ viewModel.testVisible(true);
+
+ strictEqual($('#testVisible').css('display'), 'block', 'display property should update to "block" when bound base value updates');
+ strictEqual($('#testHtml').html(), 'visible', 'html property should be "visible" after bound dependent autoupdates');
+
+ ok.unbind();
+
+ viewModel.testVisible(false);
+
+ strictEqual(viewModel.testVisible(), false, 'viewmodel base is changed');
+ strictEqual(viewModel.testHtml(), 'invisible', 'viewmodel dependent is changed');
+ strictEqual($('#testVisible').css('display'), 'block', 'view bound to base is unchanged');
+ strictEqual($('#testHtml').html(), 'visible', 'view bound to dependent is unchanged');
+
+ });
+
+
+ test("simple namespaced bindings", function() {
+
+ var vm1 = {
+ visible: ok.base(false)
+ };
+
+ var vm2 = {
+ visible: ok.base(false)
+ }
+
+ strictEqual($('#namespace1').css('display'), 'block', 'no effect to namespaced nodes before binding');
+ strictEqual($('#namespace2').css('display'), 'block', 'no effect to namespaced nodes before binding');
+
+ ok.bind(vm1, 'namespace1');
+
+ strictEqual($('#namespace1').css('display'), 'none', 'namespaced nodes can bind to view model');
+ strictEqual($('#namespace2').css('display'), 'block', 'other namespaces are unaffected');
+
+ ok.bind(vm2, 'namespace2');
+
+ strictEqual($('#namespace2').css('display'), 'none', 'other namespaces can bind to different view models');
+
+ vm1.visible(true);
+
+ strictEqual($('#namespace1').css('display'), 'block', 'namespaced view models update nodes on change');
+ strictEqual($('#namespace2').css('display'), 'none', 'other namespaces are unaffected');
+
+ ok.unbind('namespace1');
+ vm1.visible(false);
+ vm2.visible(true);
+
+ strictEqual($('#namespace1').css('display'), 'block', 'namespaces can be unbound');
+ strictEqual($('#namespace2').css('display'), 'block', 'other namespaces are unaffected');
+ });
+
+});
@@ -0,0 +1,29 @@
+$(document).ready(function() {
+
+ module("Binding - Css");
+
+ test("setting two classes from a base", function() {
+ var div = $('#testCss');
+
+ var vm = {
+ first: ok.base(false),
+ second: ok.base(true)
+ };
+
+ strictEqual($(div).hasClass('default'), true, 'div starts with class "default"');
+
+ ok.bind(vm, 'css');
+
+ strictEqual($(div).hasClass('default'), true, 'div keeps original classes after binding');
+ strictEqual($(div).hasClass('first'), false, '"first" class is false after binding');
+ strictEqual($(div).hasClass('second'), true, '"second" class is true after binding');
+
+ vm.first(true);
+ vm.second(false);
+
+ strictEqual($(div).hasClass('first'), true, '"first" class is true after update');
+ strictEqual($(div).hasClass('second'), false, '"second" class is false after update');
+
+ });
+
+});
Oops, something went wrong.

0 comments on commit e285c7b

Please sign in to comment.