From 3ef0fc275dfa5115c71fddb204bf10f752eb4f1d Mon Sep 17 00:00:00 2001 From: Roman Shtylman Date: Wed, 19 Mar 2014 11:17:01 -0400 Subject: [PATCH 1/2] Add option to specify bindings during construction. Without this, each bindings aren't able to receive the list of extra bindings in time to apply them. This commit makes custom bindings usable inside of each scope. --- History.md | 1 + lib/each.js | 3 +- lib/index.js | 35 ++-------------------- test/bindings.js | 76 ++++++++++++++++++++++++++++-------------------- 4 files changed, 49 insertions(+), 66 deletions(-) diff --git a/History.md b/History.md index 40d9ca7..4677a86 100644 --- a/History.md +++ b/History.md @@ -2,6 +2,7 @@ unreleased ========== * unpatch old array when changing arrays + * remove Reactive#bind in favor of passing `bindings` option 1.0.1 / 2014-03-06 ========== diff --git a/lib/each.js b/lib/each.js index 0ccdc95..54fb326 100644 --- a/lib/each.js +++ b/lib/each.js @@ -33,7 +33,8 @@ module.exports = function(el, val) { function childView(el, model) { return Reactive(el, model, { delegate: self.view, - adapter: self.reactive.opt.adapter + adapter: self.reactive.opt.adapter, + bindings: self.reactive.bindings }); } diff --git a/lib/index.js b/lib/index.js index 0fe1212..9399308 100644 --- a/lib/index.js +++ b/lib/index.js @@ -43,9 +43,9 @@ function Reactive(el, model, opt) { self.model = model || {}; self.adapter = (opt.adapter || Adapter)(self.model); self.el = el; - self.view = opt.delegate || {}; + self.view = opt.delegate || Object.create(null); - self.bindings = {}; + self.bindings = opt.bindings || Object.create(null); // TODO undo this crap and just export bindings regularly // not that binding order matters!! @@ -257,37 +257,6 @@ Reactive.prototype._bind = function() { }); }; -/** - * Bind `name` to `fn`. - * - * @param {String|Object} name or object - * @param {Function} fn - * @api public - */ - -Reactive.prototype.bind = function(name, fn) { - var self = this; - if ('object' == typeof name) { - for (var key in name) { - this.bind(key, name[key]); - } - return; - } - - var els = query.all('[' + name + ']', this.el); - if (this.el.hasAttribute && this.el.hasAttribute(name)) { - els = [].slice.call(els); - els.unshift(this.el); - } - if (!els.length) return; - - debug('bind [%s] (%d elements)', name, els.length); - for (var i = 0; i < els.length; i++) { - var binding = new Binding(name, this, els[i], fn); - binding.bind(); - } -}; - /** * Destroy the binding * - Removes the element from the dom (if inserted) diff --git a/test/bindings.js b/test/bindings.js index 05d53e8..f1c00aa 100644 --- a/test/bindings.js +++ b/test/bindings.js @@ -6,25 +6,47 @@ var reactive = require('../'); describe('reactive.bind(name, fn)', function(){ it('should define a new binding', function(done){ var el = domify('

Title

'); - var react = reactive(el, {}); - react.bind('data-editable', function(el, url){ - el.setAttribute('contenteditable', 'true'); - assert('/item/12' == url); + var react = reactive(el, {}, { + bindings: { + 'data-editable': function(el, url){ + el.setAttribute('contenteditable', 'true'); + assert('/item/12' == url); + } + } }); assert(el.children[0].getAttribute('contenteditable')); done(); }) + + it('should pass bindings onto each', function(done){ + var el = domify('

'); + var model = { + todos: [ { title: 'test title' } ] + }; + var react = reactive(el, model, { + bindings: { + 'lowercase': function(el, prop){ + var binding = this; + assert(prop === 'title'); + var val = binding.value(prop); + assert(val === 'test title'); + done(); + } + } + }); + }); }) describe('reactive.bind(obj)', function(){ it('should define several bindings', function(done){ var el = domify('

Title

'); - var react = reactive(el, {}); - react.bind({ - hello: function(el, val){ - assert('world' == val); - done(); + var react = reactive(el, {}, { + bindings: { + 'hello': function(el, val){ + assert('world' == val); + done(); + } } }); }) @@ -33,34 +55,24 @@ describe('reactive.bind(obj)', function(){ describe('Reactive#bind(name, fn)', function(){ it('should initialize a view-specific binding', function(done){ var el = domify(''); - var view = reactive(el, {}); - - view.bind('removable', function(el){ - assert('LI' == el.nodeName); - done(); + var view = reactive(el, {}, { + bindings: { + 'removable': function(el){ + assert('LI' == el.nodeName); + done(); + } + } }); }) it('should support root-level bindings', function(done){ var el = domify(''); - var view = reactive(el, {}); - - view.bind('removable', function(el){ - assert('UL' == el.nodeName); - done(); - }); - }) -}) - -describe('Reactive#bind(obj)', function(){ - it('should initialize several view-specific bindings', function(done){ - var el = domify('
'); - var view = reactive(el, {}); - - view.bind({ - autosubmit: function(el){ - assert('/login' == el.getAttribute('action')); - done(); + var view = reactive(el, {}, { + bindings: { + 'removable': function(el){ + assert('UL' == el.nodeName); + done(); + } } }); }) From 8442ba0cc20168cd2990795daeea2194fcb9a54b Mon Sep 17 00:00:00 2001 From: Roman Shtylman Date: Wed, 19 Mar 2014 11:57:28 -0400 Subject: [PATCH 2/2] subscribe to parent properties If using { foo.bar }, we should also subscribe to changes on "foo" so that when a user does `reactive.set("foo", {})` the binding is properly updated. --- lib/index.js | 9 +++++++++ test/text-interpolation.js | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/lib/index.js b/lib/index.js index 9399308..3db6ac3 100644 --- a/lib/index.js +++ b/lib/index.js @@ -72,6 +72,15 @@ Emitter(Reactive.prototype); Reactive.prototype.sub = function(prop, fn){ var self = this; + debug('subscribe %s', prop); + + // if we have parts, we need to subscribe to the parent as well + // TODO (defunctzombie) multiple levels of properties + var parts = prop.split('.'); + if (parts.length > 1) { + self.sub(parts[0], fn); + } + // for when reactive changes the property this.on('change ' + prop, fn); diff --git a/test/text-interpolation.js b/test/text-interpolation.js index ea2f673..a80f993 100644 --- a/test/text-interpolation.js +++ b/test/text-interpolation.js @@ -134,4 +134,13 @@ describe('text interpolation', function(){ var view = reactive('

Hello {name.slice(0,4)}

', model); assert('Hello Some' == view.el.textContent); }) + + it('should support setting base property', function(){ + var model = { name: { first: 'foobar' } }; + var view = reactive('

{name.first}

', model); + assert('foobar' == view.el.textContent); + + view.set('name', { first: 'baz' }); + assert('baz' == view.el.textContent); + }) })