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
1=== modified file 'app/views/databinding.js'
2--- app/views/databinding.js 2013-09-11 03:13:28 +0000
3+++ app/views/databinding.js 2013-09-11 13:37:02 +0000
4@@ -162,8 +162,7 @@
5 // Ignore 'possible strict violation'
6 var bindings = this._bindings;
7 var result = {bindings: [], wildcards: {}};
8- var index = indexBindings(bindings);
9- // Handle wildcards (before we filter down bindings)
10+ // Handle wildcards.
11 result.wildcards = indexBindings(bindings, function(binding) {
12 if (binding.name !== '*' && binding.name !== '+') {
13 return;
14@@ -172,30 +171,44 @@
15 }, true);
16
17 if (modelChangeKeys !== undefined && modelChangeKeys.length !== 0) {
18- bindings = bindings.filter(function(binding) {
19+ // In this branch of the the conditional, we only have a specific set
20+ // of keys that have changed, so we want to limit the resulting
21+ // bindings appropriately.
22+ // Find the bindings that match the modelChangeKeys.
23+ var filteredBindings = bindings.filter(function(binding) {
24 // Change events don't honor nested key paths. This means
25 // we may update bindings that impact multiple DOM nodes
26 // (our granularity is too low).
27 return (modelChangeKeys.indexOf(binding.name.split('.')[0]) > -1);
28 });
29+ // Add dependents.
30+ // We make an index of all bindings to help with this.
31+ var index = indexBindings(bindings, null, true);
32+ var added = {};
33+ filteredBindings.forEach(function(binding) {
34+ if (binding.name === '*' ||
35+ binding.name === '+') {
36+ return;
37+ }
38+ result.bindings.push(binding);
39+ if (binding.dependents) {
40+ binding.dependents.forEach(function(dep) {
41+ if (!added[dep]) {
42+ added[dep] = true;
43+ var depends = index[dep];
44+ if (depends) {
45+ result.bindings.push.apply(result.bindings, depends);
46+ }
47+ }
48+ });
49+ }
50+ });
51+ } else {
52+ // If we don't have modelChangeKeys, we simply want all of the
53+ // existing bindings.
54+ result.bindings.push.apply(result.bindings, bindings);
55 }
56
57- // Handle deps
58- bindings.forEach(function(binding) {
59- if (binding.name === '*' ||
60- binding.name === '+') {
61- return;
62- }
63- result.bindings.push(binding);
64- if (binding.dependents) {
65- binding.dependents.forEach(function(dep) {
66- var depends = index[dep];
67- if (depends) {
68- result.bindings.push(depends);
69- }
70- });
71- }
72- });
73
74 return result;
75 }
76@@ -281,10 +294,24 @@
77 };
78
79 /**
80- * @method addBinding
81- * @param {Object} config A bindings Object, see description in `bind`.
82- * @param {Object} viewlet A reference to the viewlet being bound.
83- * @return {Object} binding.
84+ Add a binding from a configuration and a viewlet.
85+
86+ A binding has the following attributes and methods.
87+
88+ * name: String
89+ * get(model):
90+ * target: (optional) Associated DOM node
91+ * field: (optional) Associated NodeHandler
92+ * dependents: (optional) Array of binding names that should be updated
93+ when this one is.
94+ * format(value): (optional)
95+ * update(node, value): (optional)
96+
97+
98+ @method addBinding
99+ @param {Object} config A bindings Object, see description in `bind`.
100+ @param {Object} viewlet A reference to the viewlet being bound.
101+ @return {Object} binding.
102 */
103 BindingEngine.prototype.addBinding = function(config, viewlet) {
104 var defaultBinding = {};
105@@ -321,8 +348,8 @@
106 From within the DOM, data-bind='model key' attributes can be used to
107 associate bindings from the model into the DOM. Nested keys are supported
108 by using '.' (dotted) paths. As an important development/debug tip when
109- trying to use YUI to select a dotted path name make sure to quote the
110- entire path. For example Y.one('[data-bind="a.b.c."]').
111+ trying to use YUI to select a dotted path name, make sure to quote the
112+ entire path. For example Y.one('[data-bind="a.b.c"]').
113
114 Viewlets can provide a number of configuration options
115 for use here:
116@@ -360,6 +387,10 @@
117 if (!Y.Lang.isArray(viewlets)) { viewlets = [viewlets]; }
118 Y.each(viewlets, function(v) {
119 this._bind(model, v);}, this);
120+ this._setupHeirarchicalBindings();
121+ this._setupDependencies();
122+ // Initialize viewlets with starting values.
123+ this._modelChangeHandler();
124 return this;
125 };
126
127@@ -440,11 +471,7 @@
128
129 }, this);
130
131- this._setupHeirarchicalBindings();
132- this._setupDependencies();
133 this._setupWildcarding(viewlet);
134- this._modelChangeHandler();
135-
136 return this;
137 };
138
139@@ -503,6 +530,7 @@
140 source = self.addBinding({
141 name: dep,
142 dependents: []}, binding.viewlet);
143+ index[dep] = source;
144 }
145 if (source.dependents === undefined) {
146 source.dependents = [];
147
148=== modified file 'test/test_databinding.js'
149--- test/test_databinding.js 2013-09-11 03:13:28 +0000
150+++ test/test_databinding.js 2013-09-11 13:37:02 +0000
151@@ -681,6 +681,18 @@
152 .get('value'), 'Sansa Lannister');
153 });
154
155+ it('should handle multiple dependents', function() {
156+ var model = new TestModel({first: 'Ned', last: 'Stark'});
157+ container.setHTML(
158+ '<input data-bind="full"><span data-bind="full"></span>');
159+ engine = new BindingEngine({interval: 0});
160+ engine.bind(model, viewlet);
161+ assert.equal(container.one('input[data-bind="full"]')
162+ .get('value'), 'Ned Stark');
163+ assert.equal(container.one('span[data-bind="full"]')
164+ .get('text'), 'Ned Stark');
165+ });
166+
167 });
168
169 describe('modellist tests', function() {

Subscribers

People subscribed via source and target branches