Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(4)

Side by Side Diff: pkg/template_binding/lib/src/template_iterator.dart

Issue 132403010: big update to observe, template_binding, polymer (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 6 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « pkg/template_binding/lib/src/template.dart ('k') | pkg/template_binding/lib/src/text.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a 2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 part of template_binding; 5 part of template_binding;
6 6
7 // This code is a port of what was formerly known as Model-Driven-Views, now 7 // This code is a port of what was formerly known as Model-Driven-Views, now
8 // located at: 8 // located at:
9 // https://github.com/polymer/TemplateBinding 9 // https://github.com/polymer/TemplateBinding
10 // https://github.com/polymer/NodeBind 10 // https://github.com/polymer/NodeBind
11 11
12 // TODO(jmesserly): not sure what kind of boolean conversion rules to 12 // TODO(jmesserly): not sure what kind of boolean conversion rules to
13 // apply for template data-binding. HTML attributes are true if they're 13 // apply for template data-binding. HTML attributes are true if they're
14 // present. However Dart only treats "true" as true. Since this is HTML we'll 14 // present. However Dart only treats "true" as true. Since this is HTML we'll
15 // use something closer to the HTML rules: null (missing) and false are false, 15 // use something closer to the HTML rules: null (missing) and false are false,
16 // everything else is true. 16 // everything else is true.
17 // See: https://github.com/polymer/TemplateBinding/issues/59 17 // See: https://github.com/polymer/TemplateBinding/issues/59
18 bool _toBoolean(value) => null != value && false != value; 18 bool _toBoolean(value) => null != value && false != value;
19 19
20 List _getBindings(Node node, BindingDelegate delegate) { 20 _InstanceBindingMap _getBindings(Node node, BindingDelegate delegate) {
21 if (node is Element) { 21 if (node is Element) {
22 return _parseAttributeBindings(node, delegate); 22 return _parseAttributeBindings(node, delegate);
23 } 23 }
24 24
25 if (node is Text) { 25 if (node is Text) {
26 var tokens = _parseMustaches(node.text, 'text', node, delegate); 26 var tokens = _parseMustaches(node.text, 'text', node, delegate);
27 if (tokens != null) return ['text', tokens]; 27 if (tokens != null) return new _InstanceBindingMap(['text', tokens]);
28 } 28 }
29 29
30 return null; 30 return null;
31 } 31 }
32 32
33 void _addBindings(Node node, model, [BindingDelegate delegate]) { 33 void _addBindings(Node node, model, [BindingDelegate delegate]) {
34 var bindings = _getBindings(node, delegate); 34 final bindings = _getBindings(node, delegate);
35 if (bindings != null) { 35 if (bindings != null) {
36 _processBindings(bindings, node, model); 36 _processBindings(node, bindings, model);
37 } 37 }
38 38
39 for (var c = node.firstChild; c != null; c = c.nextNode) { 39 for (var c = node.firstChild; c != null; c = c.nextNode) {
40 _addBindings(c, model, delegate); 40 _addBindings(c, model, delegate);
41 } 41 }
42 } 42 }
43 43
44 _MustacheTokens _parseWithDefault(Element element, String name,
45 BindingDelegate delegate) {
44 46
45 List _parseAttributeBindings(Element element, BindingDelegate delegate) { 47 var v = element.attributes[name];
48 if (v == '') v = '{{}}';
49 return _parseMustaches(v, name, element, delegate);
50 }
51
52 _InstanceBindingMap _parseAttributeBindings(Element element,
53 BindingDelegate delegate) {
54
46 var bindings = null; 55 var bindings = null;
47 var ifFound = false; 56 var ifFound = false;
48 var bindFound = false; 57 var bindFound = false;
49 var isTemplateNode = isSemanticTemplate(element); 58 var isTemplateNode = isSemanticTemplate(element);
50 59
51 element.attributes.forEach((name, value) { 60 element.attributes.forEach((name, value) {
52 // Allow bindings expressed in attributes to be prefixed with underbars. 61 // Allow bindings expressed in attributes to be prefixed with underbars.
53 // We do this to allow correct semantics for browsers that don't implement 62 // We do this to allow correct semantics for browsers that don't implement
54 // <template> where certain attributes might trigger side-effects -- and 63 // <template> where certain attributes might trigger side-effects -- and
55 // for IE which sanitizes certain attributes, disallowing mustache 64 // for IE which sanitizes certain attributes, disallowing mustache
56 // replacements in their text. 65 // replacements in their text.
57 while (name[0] == '_') { 66 while (name[0] == '_') {
58 name = name.substring(1); 67 name = name.substring(1);
59 } 68 }
60 69
61 if (isTemplateNode) { 70 if (isTemplateNode &&
62 if (name == 'if') { 71 (name == 'bind' || name == 'if' || name == 'repeat')) {
63 ifFound = true; 72 return;
64 if (value == '') value = '{{}}'; // Accept 'naked' if.
65 } else if (name == 'bind' || name == 'repeat') {
66 bindFound = true;
67 if (value == '') value = '{{}}'; // Accept 'naked' bind & repeat.
68 }
69 } 73 }
70 74
71 var tokens = _parseMustaches(value, name, element, delegate); 75 var tokens = _parseMustaches(value, name, element, delegate);
72 if (tokens != null) { 76 if (tokens != null) {
73 if (bindings == null) bindings = []; 77 if (bindings == null) bindings = [];
74 bindings..add(name)..add(tokens); 78 bindings..add(name)..add(tokens);
75 } 79 }
76 }); 80 });
77 81
78 // Treat <template if> as <template bind if> 82 if (isTemplateNode) {
79 if (ifFound && !bindFound) {
80 if (bindings == null) bindings = []; 83 if (bindings == null) bindings = [];
81 bindings..add('bind') 84 var result = new _TemplateBindingMap(bindings)
82 ..add(_parseMustaches('{{}}', 'bind', element, delegate)); 85 .._if = _parseWithDefault(element, 'if', delegate)
86 .._bind = _parseWithDefault(element, 'bind', delegate)
87 .._repeat = _parseWithDefault(element, 'repeat', delegate);
88
89 if (result._if != null && result._bind == null && result._repeat == null) {
justinfagnani 2014/02/04 20:16:08 why is this necessary?
Jennifer Messerly 2014/02/04 23:00:32 good point! This used to have a comment, not sure
90 result._bind = _parseMustaches('{{}}', 'bind', element, delegate);
91 }
92
93 return result;
83 } 94 }
84 95
85 return bindings; 96 return bindings == null ? null : new _InstanceBindingMap(bindings);
86 } 97 }
87 98
88 void _processBindings(List bindings, Node node, model, 99 _processOneTimeBinding(String name, _MustacheTokens tokens, Node node, model) {
89 [List<NodeBinding> bound]) {
90 100
101 if (tokens.hasOnePath) {
102 var delegateFn = tokens.tokens[3];
103 var value = delegateFn != null ? delegateFn(model, node, true) :
104 tokens.tokens[2].getValueFrom(model);
105 return tokens.isSimplePath ? value : tokens._combinator(value);
106 }
107
108 var values = new List(tokens.tokens.length ~/ 4);
justinfagnani 2014/02/04 20:16:08 oliver twist voice: more comments please?
Jennifer Messerly 2014/02/04 23:00:32 hah, you and me both want this :) I'll try and cl
109 for (int i = 1; i < tokens.tokens.length; i += 4) {
110 Function delegateFn = tokens.tokens[i + 2];
justinfagnani 2014/02/04 20:16:08 I'd love named constants for the offsets
Jennifer Messerly 2014/02/04 23:00:32 Excellent idea! I've attempted to encapsulate the
111 values[(i - 1) ~/ 4] = delegateFn != null ?
112 delegateFn(model, node, false) :
113 tokens.tokens[i + 1].getValueFrom(model);
114 }
115 return tokens._combinator(values);
116 }
117
118 _processSinglePathBinding(String name, _MustacheTokens tokens, Node node,
119 model) {
120 Function delegateFn = tokens.tokens[3];
121 var observer = delegateFn != null ?
122 delegateFn(model, node, false) :
123 new PathObserver(model, tokens.tokens[2]);
124
125 return tokens.isSimplePath ? observer :
126 new ObserverTransform(observer, tokens._combinator);
127 }
128
129 _processBinding(String name, _MustacheTokens tokens, Node node, model) {
130 if (tokens.onlyOneTime) {
131 return _processOneTimeBinding(name, tokens, node, model);
132 }
133 if (tokens.hasOnePath) {
134 return _processSinglePathBinding(name, tokens, node, model);
135 }
136
137 var observer = new CompoundObserver();
138
139 for (int i = 1; i < tokens.tokens.length; i += 4) {
140 bool oneTime = tokens.tokens[i];
141 Function delegateFn = tokens.tokens[i + 2];
142
143 if (delegateFn != null) {
144 var value = delegateFn(model, node, oneTime);
145 if (oneTime) {
146 observer.addPath(value);
147 } else {
148 observer.addObserver(value);
149 }
150 continue;
151 }
152
153 PropertyPath path = tokens.tokens[i + 1];
154 if (oneTime) {
155 observer.addPath(path.getValueFrom(model));
156 } else {
157 observer.addPath(model, path);
158 }
159 }
160
161 return new ObserverTransform(observer, tokens._combinator);
162 }
163
164 void _processBindings(Node node, _InstanceBindingMap map, model,
165 [List<Bindable> instanceBindings]) {
166
167 final bindings = map.bindings;
91 for (var i = 0; i < bindings.length; i += 2) { 168 for (var i = 0; i < bindings.length; i += 2) {
92 var name = bindings[i]; 169 var name = bindings[i];
93 var tokens = bindings[i + 1]; 170 var tokens = bindings[i + 1];
94 var bindingModel = model;
95 var bindingPath = tokens.tokens[1];
96 if (tokens.hasOnePath) {
97 var delegateFn = tokens.tokens[2];
98 if (delegateFn != null) {
99 var delegateBinding = delegateFn(model, node);
100 if (delegateBinding != null) {
101 bindingModel = delegateBinding;
102 bindingPath = 'value';
103 }
104 }
105 171
106 if (!tokens.isSimplePath) { 172 var value = _processBinding(name, tokens, node, model);
107 bindingModel = new PathObserver(bindingModel, bindingPath, 173 var binding = nodeBind(node).bind(name, value, oneTime: tokens.onlyOneTime);
108 computeValue: tokens.combinator); 174 if (binding != null && instanceBindings != null) {
109 bindingPath = 'value'; 175 instanceBindings.add(binding);
110 } 176 }
111 } else { 177 }
112 var observer = new CompoundPathObserver(computeValue: tokens.combinator);
113 for (var j = 1; j < tokens.tokens.length; j += 3) {
114 var subModel = model;
115 var subPath = tokens.tokens[j];
116 var delegateFn = tokens.tokens[j + 1];
117 var delegateBinding = delegateFn != null ?
118 delegateFn(subModel, node) : null;
119 178
120 if (delegateBinding != null) { 179 if (map is! _TemplateBindingMap) return;
121 subModel = delegateBinding;
122 subPath = 'value';
123 }
124 180
125 observer.addPath(subModel, subPath); 181 final templateExt = nodeBindFallback(node);
126 } 182 templateExt._model = model;
127 183
128 observer.start(); 184 var iter = templateExt._processBindingDirectives(map);
129 bindingModel = observer; 185 if (iter != null && instanceBindings != null) {
130 bindingPath = 'value'; 186 instanceBindings.add(iter);
131 }
132
133 var binding = nodeBind(node).bind(name, bindingModel, bindingPath);
134 if (bound != null) bound.add(binding);
135 } 187 }
136 } 188 }
137 189
138 /** 190 /**
139 * Parses {{ mustache }} bindings. 191 * Parses {{ mustache }} bindings.
140 * 192 *
141 * Returns null if there are no matches. Otherwise returns the parsed tokens. 193 * Returns null if there are no matches. Otherwise returns the parsed tokens.
142 */ 194 */
143 _MustacheTokens _parseMustaches(String s, String name, Node node, 195 _MustacheTokens _parseMustaches(String s, String name, Node node,
144 BindingDelegate delegate) { 196 BindingDelegate delegate) {
145 if (s.isEmpty) return null; 197 if (s == null || s.isEmpty) return null;
146 198
147 var tokens = null; 199 var tokens = null;
148 var length = s.length; 200 var length = s.length;
149 var startIndex = 0, lastIndex = 0, endIndex = 0; 201 var lastIndex = 0;
202 var onlyOneTime = true;
150 while (lastIndex < length) { 203 while (lastIndex < length) {
151 startIndex = s.indexOf('{{', lastIndex); 204 var startIndex = s.indexOf('{{', lastIndex);
152 endIndex = startIndex < 0 ? -1 : s.indexOf('}}', startIndex + 2); 205 var oneTimeStart = s.indexOf('[[', lastIndex);
206 var oneTime = false;
207 var terminator = '}}';
208
209 if (oneTimeStart >= 0 &&
210 (startIndex < 0 || oneTimeStart < startIndex)) {
211 startIndex = oneTimeStart;
212 oneTime = true;
213 terminator = ']]';
214 }
215
216 var endIndex = startIndex < 0 ? -1 : s.indexOf(terminator, startIndex + 2);
153 217
154 if (endIndex < 0) { 218 if (endIndex < 0) {
155 if (tokens == null) return null; 219 if (tokens == null) return null;
156 220
157 tokens.add(s.substring(lastIndex)); // TEXT 221 tokens.add(s.substring(lastIndex)); // TEXT
158 break; 222 break;
159 } 223 }
160 224
161 if (tokens == null) tokens = []; 225 if (tokens == null) tokens = [];
162 tokens.add(s.substring(lastIndex, startIndex)); // TEXT 226 tokens.add(s.substring(lastIndex, startIndex)); // TEXT
163 var pathString = s.substring(startIndex + 2, endIndex).trim(); 227 var pathString = s.substring(startIndex + 2, endIndex).trim();
164 tokens.add(pathString); // PATH 228 tokens.add(oneTime); // ONETIME?
229 onlyOneTime = onlyOneTime && oneTime;
230 tokens.add(new PropertyPath(pathString)); // PATH
165 var delegateFn = delegate == null ? null : 231 var delegateFn = delegate == null ? null :
166 delegate.prepareBinding(pathString, name, node); 232 delegate.prepareBinding(pathString, name, node);
167 tokens.add(delegateFn); 233 tokens.add(delegateFn);
168 234
169 lastIndex = endIndex + 2; 235 lastIndex = endIndex + 2;
170 } 236 }
171 237
172 if (lastIndex == length) tokens.add(''); 238 if (lastIndex == length) tokens.add('');
173 239
174 return new _MustacheTokens(tokens); 240 return new _MustacheTokens(tokens, onlyOneTime);
175 } 241 }
176 242
177 class _MustacheTokens { 243 class _MustacheTokens {
178 bool get hasOnePath => tokens.length == 4; 244 bool get hasOnePath => tokens.length == 5;
179 bool get isSimplePath => hasOnePath && tokens[0] == '' && tokens[3] == ''; 245 bool get isSimplePath => hasOnePath && tokens[0] == '' && tokens[4] == '';
180 246
181 /** [TEXT, (PATH, TEXT, DELEGATE_FN)+] if there is at least one mustache. */ 247 /**
248 * [TEXT, (ONE_TIME?, PATH, DELEGATE_FN, TEXT)+] if there is at least one
249 * mustache.
250 */
182 // TODO(jmesserly): clean up the type here? 251 // TODO(jmesserly): clean up the type here?
183 final List tokens; 252 final List tokens;
184 253
254 final bool onlyOneTime;
255
185 // Dart note: I think this is cached in JavaScript to avoid an extra 256 // Dart note: I think this is cached in JavaScript to avoid an extra
186 // allocation per template instance. Seems reasonable, so we do the same. 257 // allocation per template instance. Seems reasonable, so we do the same.
187 Function _combinator; 258 Function _combinator;
188 Function get combinator => _combinator; 259 Function get combinator => _combinator;
189 260
190 _MustacheTokens(this.tokens) { 261 _MustacheTokens(this.tokens, this.onlyOneTime) {
191 // Should be: [TEXT, (PATH, TEXT, DELEGATE_FN)+]. 262 // Should be: [TEXT, (ONE_TIME?, PATH, DELEGATE_FN, TEXT)+].
192 assert((tokens.length + 2) % 3 == 0); 263 assert((tokens.length - 1) % 4 == 0);
193 264
194 _combinator = hasOnePath ? _singleCombinator : _listCombinator; 265 _combinator = hasOnePath ? _singleCombinator : _listCombinator;
195 } 266 }
196 267
197 // Dart note: split "combinator" into the single/list variants, so the 268 // Dart note: split "combinator" into the single/list variants, so the
198 // argument can be typed. 269 // argument can be typed.
199 String _singleCombinator(Object value) { 270 String _singleCombinator(Object value) {
200 if (value == null) value = ''; 271 if (value == null) value = '';
201 return '${tokens[0]}$value${tokens[3]}'; 272 return '${tokens[0]}$value${tokens[4]}';
202 } 273 }
203 274
204 String _listCombinator(List<Object> values) { 275 String _listCombinator(List<Object> values) {
205 var newValue = new StringBuffer(tokens[0]); 276 var newValue = new StringBuffer(tokens[0]);
206 for (var i = 1; i < tokens.length; i += 3) { 277 for (var i = 1; i < tokens.length; i += 4) {
207 var value = values[(i - 1) ~/ 3]; 278 var value = values[(i - 1) ~/ 4];
208 if (value != null) newValue.write(value); 279 if (value != null) newValue.write(value);
209 newValue.write(tokens[i + 2]); 280 newValue.write(tokens[i + 3]);
210 } 281 }
211 282
212 return newValue.toString(); 283 return newValue.toString();
213 } 284 }
214 } 285 }
215 286
216 void _addTemplateInstanceRecord(fragment, model) {
217 if (fragment.firstChild == null) {
218 return;
219 }
220 287
221 var instanceRecord = new TemplateInstance( 288 // Note: this doesn't really implement most of Bindable. See:
222 fragment.firstChild, fragment.lastChild, model); 289 // https://github.com/Polymer/TemplateBinding/issues/147
223 290 class _TemplateIterator extends Bindable {
224 var node = instanceRecord.firstNode;
225 while (node != null) {
226 nodeBindFallback(node)._templateInstance = instanceRecord;
227 node = node.nextNode;
228 }
229 }
230
231 class _TemplateIterator {
232 final TemplateBindExtension _templateExt; 291 final TemplateBindExtension _templateExt;
233 292
234 /** 293 /**
235 * Flattened array of tuples: 294 * Flattened array of tuples:
236 * <instanceTerminatorNode, [bindingsSetupByInstance]> 295 * <instanceTerminatorNode, [bindingsSetupByInstance]>
237 */ 296 */
238 final List terminators = []; 297 final List _terminators = [];
239 List iteratedValue;
240 bool closed = false;
241 bool depsChanging = false;
242 298
243 bool hasRepeat = false, hasBind = false, hasIf = false; 299 /** A copy of the last rendered [_presentValue] list state. */
244 Object repeatModel, bindModel, ifModel; 300 final List _iteratedValue = [];
245 String repeatPath, bindPath, ifPath;
246 301
247 StreamSubscription _valueSub, _listSub; 302 List _presentValue;
303
304 bool _closed = false;
305
306 // Dart note: instead of storing these in a Map like JS, or using a separate
307 // object (extra memory overhead) we just inline the fields.
308 var _ifValue, _value;
309
310 // TODO(jmesserly): lots of booleans in this object. Bitmask?
311 bool _hasIf, _hasRepeat;
312 bool _ifOneTime, _oneTime;
313
314 StreamSubscription _listSub;
248 315
249 bool _initPrepareFunctions = false; 316 bool _initPrepareFunctions = false;
250 PrepareInstanceModelFunction _instanceModelFn; 317 PrepareInstanceModelFunction _instanceModelFn;
251 PrepareInstancePositionChangedFunction _instancePositionChangedFn; 318 PrepareInstancePositionChangedFunction _instancePositionChangedFn;
252 319
253 _TemplateIterator(this._templateExt); 320 _TemplateIterator(this._templateExt);
254 321
322 open(callback) => throw new StateError('binding already opened');
323 get value => _value;
324
255 Element get _templateElement => _templateExt._node; 325 Element get _templateElement => _templateExt._node;
256 326
257 resolve() { 327 void _closeDependencies() {
258 depsChanging = false; 328 if (_ifValue is Bindable) {
329 _ifValue.close();
330 _ifValue = null;
331 }
332 if (_value is Bindable) {
333 _value.close();
334 _value = null;
335 }
336 }
259 337
260 if (_valueSub != null) { 338 void _updateDependencies(_TemplateBindingMap directives, model) {
261 _valueSub.cancel(); 339 _closeDependencies();
262 _valueSub = null; 340
341 final template = _templateElement;
342
343 _hasIf = directives._if != null;
344 _hasRepeat = directives._repeat != null;
345
346 if (_hasIf) {
347 _ifOneTime = directives._if.onlyOneTime;
348 _ifValue = _processBinding('if', directives._if, template, model);
349
350 // oneTime if & predicate is false. nothing else to do.
351 if (_ifOneTime) {
352 if (!_toBoolean(_ifValue)) {
353 _updateIteratedValue(null);
354 return;
355 }
356 } else {
357 (_ifValue as Bindable).open(_updateIteratedValue);
358 }
263 } 359 }
264 360
265 if (!hasRepeat && !hasBind) { 361 if (_hasRepeat) {
266 _valueChanged(null); 362 _oneTime = directives._repeat.onlyOneTime;
267 return; 363 _value = _processBinding('repeat', directives._repeat, template, model);
364 } else {
365 _oneTime = directives._bind.onlyOneTime;
366 _value = _processBinding('bind', directives._bind, template, model);
268 } 367 }
269 368
270 final model = hasRepeat ? repeatModel : bindModel; 369 if (!_oneTime) _value.open(_updateIteratedValue);
271 final path = hasRepeat ? repeatPath : bindPath;
272 370
273 var valueObserver; 371 _updateIteratedValue(null);
274 if (!hasIf) { 372 }
275 valueObserver = new PathObserver(model, path,
276 computeValue: hasRepeat ? null : (x) => [x]);
277 } else {
278 // TODO(jmesserly): I'm not sure if closing over this is necessary for
279 // correctness. It does seem useful if the valueObserver gets fired after
280 // hasRepeat has changed, due to async nature of things.
281 final isRepeat = hasRepeat;
282 373
283 valueFn(List values) { 374 void _updateIteratedValue(_) {
284 var modelValue = values[0]; 375 if (_hasIf) {
285 var ifValue = values[1]; 376 var ifValue = _ifValue;
286 if (!_toBoolean(ifValue)) return null; 377 if (!_ifOneTime) ifValue = (ifValue as Bindable).value;
287 return isRepeat ? modelValue : [ modelValue ]; 378 if (!_toBoolean(ifValue)) {
379 _valueChanged([]);
380 return;
288 } 381 }
289
290 valueObserver = new CompoundPathObserver(computeValue: valueFn)
291 ..addPath(model, path)
292 ..addPath(ifModel, ifPath)
293 ..start();
294 } 382 }
295 383
296 _valueSub = valueObserver.changes.listen( 384 var value = _value;
297 (r) => _valueChanged(r.last.newValue)); 385 if (!_oneTime) value = (value as Bindable).value;
298 _valueChanged(valueObserver.value); 386 if (!_hasRepeat) value = [value];
387 _valueChanged(value);
299 } 388 }
300 389
301 void _valueChanged(newValue) { 390 void _valueChanged(Object value) {
302 var oldValue = iteratedValue; 391 if (value is! List) {
303 unobserve(); 392 if (value is Iterable) {
304 393 // Dart note: we support Iterable by calling toList.
305 if (newValue is List) { 394 // But we need to be careful to observe the original iterator if it
306 iteratedValue = newValue; 395 // supports that.
307 } else if (newValue is Iterable) { 396 value = (value as Iterable).toList();
308 // Dart note: we support Iterable by calling toList. 397 } else {
309 // But we need to be careful to observe the original iterator if it 398 value = [];
310 // supports that. 399 }
311 iteratedValue = (newValue as Iterable).toList();
312 } else {
313 iteratedValue = null;
314 } 400 }
315 401
316 if (iteratedValue != null && newValue is ObservableList) { 402 if (identical(value, _iteratedValue)) return;
317 _listSub = newValue.listChanges.listen(_handleSplices); 403
404 _unobserve();
405 _presentValue = value;
406
407 if (value is ObservableList && _hasRepeat && !_oneTime) {
408 // Make sure any pending changes aren't delivered, since we're getting
409 // a snapshot at this point in time.
410 value.discardListChages();
411 _listSub = value.listChanges.listen(_handleSplices);
318 } 412 }
319 413
320 var splices = ObservableList.calculateChangeRecords( 414 _handleSplices(ObservableList.calculateChangeRecords(
321 oldValue != null ? oldValue : [], 415 _iteratedValue != null ? _iteratedValue : [],
322 iteratedValue != null ? iteratedValue : []); 416 _presentValue != null ? _presentValue : []));
323
324 if (splices.isNotEmpty) _handleSplices(splices);
325 } 417 }
326 418
327 Node getTerminatorAt(int index) { 419 Node _getTerminatorAt(int index) {
328 if (index == -1) return _templateElement; 420 if (index == -1) return _templateElement;
329 var terminator = terminators[index * 2]; 421 var terminator = _terminators[index * 2];
330 if (!isSemanticTemplate(terminator) || 422 if (!isSemanticTemplate(terminator) ||
331 identical(terminator, _templateElement)) { 423 identical(terminator, _templateElement)) {
332 return terminator; 424 return terminator;
333 } 425 }
334 426
335 var subIter = templateBindFallback(terminator)._iterator; 427 var subIter = templateBindFallback(terminator)._iterator;
336 if (subIter == null) return terminator; 428 if (subIter == null) return terminator;
337 429
338 return subIter.getTerminatorAt(subIter.terminators.length ~/ 2 - 1); 430 return subIter._getTerminatorAt(subIter._terminators.length ~/ 2 - 1);
339 } 431 }
340 432
341 // TODO(rafaelw): If we inserting sequences of instances we can probably 433 // TODO(rafaelw): If we inserting sequences of instances we can probably
342 // avoid lots of calls to getTerminatorAt(), or cache its result. 434 // avoid lots of calls to _getTerminatorAt(), or cache its result.
343 void insertInstanceAt(int index, DocumentFragment fragment, 435 void _insertInstanceAt(int index, DocumentFragment fragment,
344 List<Node> instanceNodes, List<NodeBinding> bound) { 436 List<Node> instanceNodes, List<Bindable> instanceBindings) {
345 437
346 var previousTerminator = getTerminatorAt(index - 1); 438 var previousTerminator = _getTerminatorAt(index - 1);
347 var terminator = null; 439 var terminator = null;
348 if (fragment != null) { 440 if (fragment != null) {
349 terminator = fragment.lastChild; 441 terminator = fragment.lastChild;
350 } else if (instanceNodes != null && instanceNodes.isNotEmpty) { 442 } else if (instanceNodes != null && instanceNodes.isNotEmpty) {
351 terminator = instanceNodes.last; 443 terminator = instanceNodes.last;
352 } 444 }
353 if (terminator == null) terminator = previousTerminator; 445 if (terminator == null) terminator = previousTerminator;
354 446
355 terminators.insertAll(index * 2, [terminator, bound]); 447 _terminators.insertAll(index * 2, [terminator, instanceBindings]);
356 var parent = _templateElement.parentNode; 448 var parent = _templateElement.parentNode;
357 var insertBeforeNode = previousTerminator.nextNode; 449 var insertBeforeNode = previousTerminator.nextNode;
358 450
359 if (fragment != null) { 451 if (fragment != null) {
360 parent.insertBefore(fragment, insertBeforeNode); 452 parent.insertBefore(fragment, insertBeforeNode);
361 } else if (instanceNodes != null) { 453 } else if (instanceNodes != null) {
362 for (var node in instanceNodes) { 454 for (var node in instanceNodes) {
363 parent.insertBefore(node, insertBeforeNode); 455 parent.insertBefore(node, insertBeforeNode);
364 } 456 }
365 } 457 }
366 } 458 }
367 459
368 _BoundNodes extractInstanceAt(int index) { 460 _BoundNodes _extractInstanceAt(int index) {
369 var instanceNodes = <Node>[]; 461 var instanceNodes = <Node>[];
370 var previousTerminator = getTerminatorAt(index - 1); 462 var previousTerminator = _getTerminatorAt(index - 1);
371 var terminator = getTerminatorAt(index); 463 var terminator = _getTerminatorAt(index);
372 var bound = terminators[index * 2 + 1]; 464 var instanceBindings = _terminators[index * 2 + 1];
373 terminators.removeRange(index * 2, index * 2 + 2); 465 _terminators.removeRange(index * 2, index * 2 + 2);
374 466
375 var parent = _templateElement.parentNode; 467 var parent = _templateElement.parentNode;
376 while (terminator != previousTerminator) { 468 while (terminator != previousTerminator) {
377 var node = previousTerminator.nextNode; 469 var node = previousTerminator.nextNode;
378 if (node == terminator) terminator = previousTerminator; 470 if (node == terminator) terminator = previousTerminator;
379 node.remove(); 471 node.remove();
380 instanceNodes.add(node); 472 instanceNodes.add(node);
381 } 473 }
382 return new _BoundNodes(instanceNodes, bound); 474 return new _BoundNodes(instanceNodes, instanceBindings);
383 } 475 }
384 476
385 void _handleSplices(List<ListChangeRecord> splices) { 477 void _handleSplices(List<ListChangeRecord> splices) {
386 if (closed) return; 478 if (_closed || splices.isEmpty) return;
387 479
388 final template = _templateElement; 480 final template = _templateElement;
389 final delegate = _templateExt._self.bindingDelegate;
390 481
391 if (template.parentNode == null || template.ownerDocument.window == null) { 482 if (template.parentNode == null) {
392 close(); 483 close();
393 return; 484 return;
394 } 485 }
395 486
487 ObservableList.applyChangeRecords(_iteratedValue, _presentValue, splices);
488
489 final delegate = _templateExt.bindingDelegate;
490
396 // Dart note: the JavaScript code relies on the distinction between null 491 // Dart note: the JavaScript code relies on the distinction between null
397 // and undefined to track whether the functions are prepared. We use a bool. 492 // and undefined to track whether the functions are prepared. We use a bool.
398 if (!_initPrepareFunctions) { 493 if (!_initPrepareFunctions) {
399 _initPrepareFunctions = true; 494 _initPrepareFunctions = true;
495 final delegate = _templateExt._self.bindingDelegate;
400 if (delegate != null) { 496 if (delegate != null) {
401 _instanceModelFn = delegate.prepareInstanceModel(template); 497 _instanceModelFn = delegate.prepareInstanceModel(template);
402 _instancePositionChangedFn = 498 _instancePositionChangedFn =
403 delegate.prepareInstancePositionChanged(template); 499 delegate.prepareInstancePositionChanged(template);
404 } 500 }
405 } 501 }
406 502
407 var instanceCache = new HashMap<Object, _BoundNodes>(equals: identical); 503 var instanceCache = new HashMap<Object, _BoundNodes>(equals: identical);
408 var removeDelta = 0; 504 var removeDelta = 0;
409 for (var splice in splices) { 505 for (var splice in splices) {
410 for (var model in splice.removed) { 506 for (var model in splice.removed) {
411 instanceCache[model] = extractInstanceAt(splice.index + removeDelta); 507 instanceCache[model] = _extractInstanceAt(splice.index + removeDelta);
412 } 508 }
413 509
414 removeDelta -= splice.addedCount; 510 removeDelta -= splice.addedCount;
415 } 511 }
416 512
417 for (var splice in splices) { 513 for (var splice in splices) {
418 for (var addIndex = splice.index; 514 for (var addIndex = splice.index;
419 addIndex < splice.index + splice.addedCount; 515 addIndex < splice.index + splice.addedCount;
420 addIndex++) { 516 addIndex++) {
421 517
422 var model = iteratedValue[addIndex]; 518 var model = _iteratedValue[addIndex];
423 var fragment = null; 519 var fragment = null;
424 var instance = instanceCache.remove(model); 520 var instance = instanceCache.remove(model);
425 List bound; 521 List instanceBindings;
426 List instanceNodes = null; 522 List instanceNodes = null;
427 if (instance != null && instance.nodes.isNotEmpty) { 523 if (instance != null && instance.nodes.isNotEmpty) {
428 bound = instance.bound; 524 instanceBindings = instance.instanceBindings;
429 instanceNodes = instance.nodes; 525 instanceNodes = instance.nodes;
430 } else { 526 } else {
431 bound = []; 527 instanceBindings = [];
432 if (_instanceModelFn != null) { 528 if (_instanceModelFn != null) {
433 model = _instanceModelFn(model); 529 model = _instanceModelFn(model);
434 } 530 }
435 if (model != null) { 531 if (model != null) {
436 fragment = _templateExt.createInstance(model, delegate, bound); 532 fragment = _templateExt.createInstance(model, delegate,
533 instanceBindings);
437 } 534 }
438 } 535 }
439 536
440 insertInstanceAt(addIndex, fragment, instanceNodes, bound); 537 _insertInstanceAt(addIndex, fragment, instanceNodes, instanceBindings);
441 } 538 }
442 } 539 }
443 540
444 for (var instance in instanceCache.values) { 541 for (var instance in instanceCache.values) {
445 closeInstanceBindings(instance.bound); 542 _closeInstanceBindings(instance.instanceBindings);
446 } 543 }
447 544
448 if (_instancePositionChangedFn != null) reportInstancesMoved(splices); 545 if (_instancePositionChangedFn != null) _reportInstancesMoved(splices);
449 } 546 }
450 547
451 void reportInstanceMoved(int index) { 548 void _reportInstanceMoved(int index) {
452 var previousTerminator = getTerminatorAt(index - 1); 549 var previousTerminator = _getTerminatorAt(index - 1);
453 var terminator = getTerminatorAt(index); 550 var terminator = _getTerminatorAt(index);
454 if (identical(previousTerminator, terminator)) { 551 if (identical(previousTerminator, terminator)) {
455 return; // instance has zero nodes. 552 return; // instance has zero nodes.
456 } 553 }
457 554
458 // We must use the first node of the instance, because any subsequent 555 // We must use the first node of the instance, because any subsequent
459 // nodes may have been generated by sub-templates. 556 // nodes may have been generated by sub-templates.
460 // TODO(rafaelw): This is brittle WRT instance mutation -- e.g. if the 557 // TODO(rafaelw): This is brittle WRT instance mutation -- e.g. if the
461 // first node was removed by script. 558 // first node was removed by script.
462 var instance = nodeBind(previousTerminator.nextNode).templateInstance; 559 var instance = nodeBind(previousTerminator.nextNode).templateInstance;
463 _instancePositionChangedFn(instance, index); 560 _instancePositionChangedFn(instance, index);
464 } 561 }
465 562
466 void reportInstancesMoved(List<ListChangeRecord> splices) { 563 void _reportInstancesMoved(List<ListChangeRecord> splices) {
467 var index = 0; 564 var index = 0;
468 var offset = 0; 565 var offset = 0;
469 for (var splice in splices) { 566 for (var splice in splices) {
470 if (offset != 0) { 567 if (offset != 0) {
471 while (index < splice.index) { 568 while (index < splice.index) {
472 reportInstanceMoved(index); 569 _reportInstanceMoved(index);
473 index++; 570 index++;
474 } 571 }
475 } else { 572 } else {
476 index = splice.index; 573 index = splice.index;
477 } 574 }
478 575
479 while (index < splice.index + splice.addedCount) { 576 while (index < splice.index + splice.addedCount) {
480 reportInstanceMoved(index); 577 _reportInstanceMoved(index);
481 index++; 578 index++;
482 } 579 }
483 580
484 offset += splice.addedCount - splice.removed.length; 581 offset += splice.addedCount - splice.removed.length;
485 } 582 }
486 583
487 if (offset == 0) return; 584 if (offset == 0) return;
488 585
489 var length = terminators.length ~/ 2; 586 var length = _terminators.length ~/ 2;
490 while (index < length) { 587 while (index < length) {
491 reportInstanceMoved(index); 588 _reportInstanceMoved(index);
492 index++; 589 index++;
493 } 590 }
494 } 591 }
495 592
496 void closeInstanceBindings(List<NodeBinding> bound) { 593 void _closeInstanceBindings(List<Bindable> instanceBindings) {
497 for (var binding in bound) binding.close(); 594 for (var binding in instanceBindings) binding.close();
498 } 595 }
499 596
500 void unobserve() { 597 void _unobserve() {
501 if (_listSub == null) return; 598 if (_listSub == null) return;
502 _listSub.cancel(); 599 _listSub.cancel();
503 _listSub = null; 600 _listSub = null;
504 } 601 }
505 602
506 void close() { 603 void close() {
507 if (closed) return; 604 if (_closed) return;
508 605
509 unobserve(); 606 _unobserve();
510 for (var i = 1; i < terminators.length; i += 2) { 607 for (var i = 1; i < _terminators.length; i += 2) {
511 closeInstanceBindings(terminators[i]); 608 _closeInstanceBindings(_terminators[i]);
512 } 609 }
513 610
514 terminators.clear(); 611 _terminators.clear();
515 if (_valueSub != null) { 612 _closeDependencies();
516 _valueSub.cancel();
517 _valueSub = null;
518 }
519 _templateExt._iterator = null; 613 _templateExt._iterator = null;
520 closed = true; 614 _closed = true;
521 } 615 }
522 } 616 }
523 617
524 // Dart note: the JavaScript version just puts an expando on the array. 618 // Dart note: the JavaScript version just puts an expando on the array.
525 class _BoundNodes { 619 class _BoundNodes {
526 final List<Node> nodes; 620 final List<Node> nodes;
527 final List<NodeBinding> bound; 621 final List<Bindable> instanceBindings;
528 _BoundNodes(this.nodes, this.bound); 622 _BoundNodes(this.nodes, this.instanceBindings);
529 } 623 }
OLDNEW
« no previous file with comments | « pkg/template_binding/lib/src/template.dart ('k') | pkg/template_binding/lib/src/text.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698