Merge lp:~gary/juju-gui/fixDeltaFromChangeEdgeCase into lp:juju-gui/experimental

Proposed by Gary Poster
Status: Merged
Merged at revision: 1030
Proposed branch: lp:~gary/juju-gui/fixDeltaFromChangeEdgeCase
Merge into: lp:juju-gui/experimental
Diff against target: 169 lines (+69/-29)
2 files modified
app/views/databinding.js (+57/-29)
test/test_databinding.js (+12/-0)
To merge this branch: bzr merge lp:~gary/juju-gui/fixDeltaFromChangeEdgeCase
Reviewer Review Type Date Requested Status
Juju GUI Hackers Pending
Review via email: mp+185053@code.launchpad.net

Description of the change

optimize and fix databinding.deltaFromChange

This is a grabbag of three small changes. The biggest change is that I did some optimizations of databinding.deltaFromChange and fixed an edge case (with test). I also optimized _setupDependencies for that edge case, added some documentation about bindings (so I could refer to it myself), and optimized the bind/_bind pair a bit by removing redundant calls to code that iterates over all bindings (they are now done once in bind, rather than once per viewlet in _bind).

https://codereview.appspot.com/13348056/

To post a comment you must log in.
Revision history for this message
Gary Poster (gary) wrote :

Reviewers: mp+185053_code.launchpad.net,

Message:
Please take a look.

Description:
optimize and fix databinding.deltaFromChange

This is a grabbag of three small changes. The biggest change is that I
did some optimizations of databinding.deltaFromChange and fixed an edge
case (with test). I also optimized _setupDependencies for that edge
case, added some documentation about bindings (so I could refer to it
myself), and optimized the bind/_bind pair a bit by removing redundant
calls to code that iterates over all bindings (they are now done once in
bind, rather than once per viewlet in _bind).

https://code.launchpad.net/~gary/juju-gui/fixDeltaFromChangeEdgeCase/+merge/185053

(do not edit description out of merge proposal)

Please review this at https://codereview.appspot.com/13348056/

Affected files (+71, -29 lines):
   A [revision details]
   M app/views/databinding.js
   M test/test_databinding.js

Revision history for this message
Gary Poster (gary) wrote :

Line-by-line comments for reviewers.

https://codereview.appspot.com/13348056/diff/1/app/views/databinding.js
File app/views/databinding.js (right):

https://codereview.appspot.com/13348056/diff/1/app/views/databinding.js#newcode186
app/views/databinding.js:186: var index = indexBindings(bindings, null,
true);
The "true" argument and then the "result.bindings.push.apply" below is
the real fix. All the other changes streamline the logic, resulting in
much fewer duplicate results and hopefully clearer reading.

https://codereview.appspot.com/13348056/diff/1/app/views/databinding.js#newcode393
app/views/databinding.js:393: this._modelChangeHandler();
These were moved from _bind, mostly as an optimization for reading but
also to prevent unnecessary duplication of work.

https://codereview.appspot.com/13348056/diff/1/app/views/databinding.js#newcode533
app/views/databinding.js:533: index[dep] = source;
This is just an optimization, so that if we have more than one of these
implicit bindings on the same source, we don't create multiple copies of
the source. It wasn't causing a bug, but this just makes it easier to
debug further downstream.

https://codereview.appspot.com/13348056/

Revision history for this message
Gary Poster (gary) wrote :

*** Submitted:

optimize and fix databinding.deltaFromChange

This is a grabbag of three small changes. The biggest change is that I
did some optimizations of databinding.deltaFromChange and fixed an edge
case (with test). I also optimized _setupDependencies for that edge
case, added some documentation about bindings (so I could refer to it
myself), and optimized the bind/_bind pair a bit by removing redundant
calls to code that iterates over all bindings (they are now done once in
bind, rather than once per viewlet in _bind).

R=benji
CC=
https://codereview.appspot.com/13348056

https://codereview.appspot.com/13348056/

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'app/views/databinding.js'
--- app/views/databinding.js 2013-09-11 03:13:28 +0000
+++ app/views/databinding.js 2013-09-11 13:37:02 +0000
@@ -162,8 +162,7 @@
162 // Ignore 'possible strict violation'162 // Ignore 'possible strict violation'
163 var bindings = this._bindings;163 var bindings = this._bindings;
164 var result = {bindings: [], wildcards: {}};164 var result = {bindings: [], wildcards: {}};
165 var index = indexBindings(bindings);165 // Handle wildcards.
166 // Handle wildcards (before we filter down bindings)
167 result.wildcards = indexBindings(bindings, function(binding) {166 result.wildcards = indexBindings(bindings, function(binding) {
168 if (binding.name !== '*' && binding.name !== '+') {167 if (binding.name !== '*' && binding.name !== '+') {
169 return;168 return;
@@ -172,30 +171,44 @@
172 }, true);171 }, true);
173172
174 if (modelChangeKeys !== undefined && modelChangeKeys.length !== 0) {173 if (modelChangeKeys !== undefined && modelChangeKeys.length !== 0) {
175 bindings = bindings.filter(function(binding) {174 // In this branch of the the conditional, we only have a specific set
175 // of keys that have changed, so we want to limit the resulting
176 // bindings appropriately.
177 // Find the bindings that match the modelChangeKeys.
178 var filteredBindings = bindings.filter(function(binding) {
176 // Change events don't honor nested key paths. This means179 // Change events don't honor nested key paths. This means
177 // we may update bindings that impact multiple DOM nodes180 // we may update bindings that impact multiple DOM nodes
178 // (our granularity is too low).181 // (our granularity is too low).
179 return (modelChangeKeys.indexOf(binding.name.split('.')[0]) > -1);182 return (modelChangeKeys.indexOf(binding.name.split('.')[0]) > -1);
180 });183 });
184 // Add dependents.
185 // We make an index of all bindings to help with this.
186 var index = indexBindings(bindings, null, true);
187 var added = {};
188 filteredBindings.forEach(function(binding) {
189 if (binding.name === '*' ||
190 binding.name === '+') {
191 return;
192 }
193 result.bindings.push(binding);
194 if (binding.dependents) {
195 binding.dependents.forEach(function(dep) {
196 if (!added[dep]) {
197 added[dep] = true;
198 var depends = index[dep];
199 if (depends) {
200 result.bindings.push.apply(result.bindings, depends);
201 }
202 }
203 });
204 }
205 });
206 } else {
207 // If we don't have modelChangeKeys, we simply want all of the
208 // existing bindings.
209 result.bindings.push.apply(result.bindings, bindings);
181 }210 }
182211
183 // Handle deps
184 bindings.forEach(function(binding) {
185 if (binding.name === '*' ||
186 binding.name === '+') {
187 return;
188 }
189 result.bindings.push(binding);
190 if (binding.dependents) {
191 binding.dependents.forEach(function(dep) {
192 var depends = index[dep];
193 if (depends) {
194 result.bindings.push(depends);
195 }
196 });
197 }
198 });
199212
200 return result;213 return result;
201 }214 }
@@ -281,10 +294,24 @@
281 };294 };
282295
283 /**296 /**
284 * @method addBinding297 Add a binding from a configuration and a viewlet.
285 * @param {Object} config A bindings Object, see description in `bind`.298
286 * @param {Object} viewlet A reference to the viewlet being bound.299 A binding has the following attributes and methods.
287 * @return {Object} binding.300
301 * name: String
302 * get(model):
303 * target: (optional) Associated DOM node
304 * field: (optional) Associated NodeHandler
305 * dependents: (optional) Array of binding names that should be updated
306 when this one is.
307 * format(value): (optional)
308 * update(node, value): (optional)
309
310
311 @method addBinding
312 @param {Object} config A bindings Object, see description in `bind`.
313 @param {Object} viewlet A reference to the viewlet being bound.
314 @return {Object} binding.
288 */315 */
289 BindingEngine.prototype.addBinding = function(config, viewlet) {316 BindingEngine.prototype.addBinding = function(config, viewlet) {
290 var defaultBinding = {};317 var defaultBinding = {};
@@ -321,8 +348,8 @@
321 From within the DOM, data-bind='model key' attributes can be used to348 From within the DOM, data-bind='model key' attributes can be used to
322 associate bindings from the model into the DOM. Nested keys are supported349 associate bindings from the model into the DOM. Nested keys are supported
323 by using '.' (dotted) paths. As an important development/debug tip when350 by using '.' (dotted) paths. As an important development/debug tip when
324 trying to use YUI to select a dotted path name make sure to quote the351 trying to use YUI to select a dotted path name, make sure to quote the
325 entire path. For example Y.one('[data-bind="a.b.c."]').352 entire path. For example Y.one('[data-bind="a.b.c"]').
326353
327 Viewlets can provide a number of configuration options354 Viewlets can provide a number of configuration options
328 for use here:355 for use here:
@@ -360,6 +387,10 @@
360 if (!Y.Lang.isArray(viewlets)) { viewlets = [viewlets]; }387 if (!Y.Lang.isArray(viewlets)) { viewlets = [viewlets]; }
361 Y.each(viewlets, function(v) {388 Y.each(viewlets, function(v) {
362 this._bind(model, v);}, this);389 this._bind(model, v);}, this);
390 this._setupHeirarchicalBindings();
391 this._setupDependencies();
392 // Initialize viewlets with starting values.
393 this._modelChangeHandler();
363 return this;394 return this;
364 };395 };
365396
@@ -440,11 +471,7 @@
440471
441 }, this);472 }, this);
442473
443 this._setupHeirarchicalBindings();
444 this._setupDependencies();
445 this._setupWildcarding(viewlet);474 this._setupWildcarding(viewlet);
446 this._modelChangeHandler();
447
448 return this;475 return this;
449 };476 };
450477
@@ -503,6 +530,7 @@
503 source = self.addBinding({530 source = self.addBinding({
504 name: dep,531 name: dep,
505 dependents: []}, binding.viewlet);532 dependents: []}, binding.viewlet);
533 index[dep] = source;
506 }534 }
507 if (source.dependents === undefined) {535 if (source.dependents === undefined) {
508 source.dependents = [];536 source.dependents = [];
509537
=== modified file 'test/test_databinding.js'
--- test/test_databinding.js 2013-09-11 03:13:28 +0000
+++ test/test_databinding.js 2013-09-11 13:37:02 +0000
@@ -681,6 +681,18 @@
681 .get('value'), 'Sansa Lannister');681 .get('value'), 'Sansa Lannister');
682 });682 });
683683
684 it('should handle multiple dependents', function() {
685 var model = new TestModel({first: 'Ned', last: 'Stark'});
686 container.setHTML(
687 '<input data-bind="full"><span data-bind="full"></span>');
688 engine = new BindingEngine({interval: 0});
689 engine.bind(model, viewlet);
690 assert.equal(container.one('input[data-bind="full"]')
691 .get('value'), 'Ned Stark');
692 assert.equal(container.one('span[data-bind="full"]')
693 .get('text'), 'Ned Stark');
694 });
695
684 });696 });
685697
686 describe('modellist tests', function() {698 describe('modellist tests', function() {

Subscribers

People subscribed via source and target branches