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

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 = MustacheTokens.parse(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 MustacheTokens.parse(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 = MustacheTokens.parse(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 // Treat <template if> as <template bind if>
90 if (result._if != null && result._bind == null && result._repeat == null) {
91 result._bind = MustacheTokens.parse('{{}}', 'bind', element, delegate);
92 }
93
94 return result;
83 } 95 }
84 96
85 return bindings; 97 return bindings == null ? null : new _InstanceBindingMap(bindings);
86 } 98 }
87 99
88 void _processBindings(List bindings, Node node, model, 100 _processOneTimeBinding(String name, MustacheTokens tokens, Node node, model) {
89 [List<NodeBinding> bound]) {
90 101
102 if (tokens.hasOnePath) {
103 var delegateFn = tokens.getPrepareBinding(0);
104 var value = delegateFn != null ? delegateFn(model, node, true) :
105 tokens.getPath(0).getValueFrom(model);
106 return tokens.isSimplePath ? value : tokens.combinator(value);
107 }
108
109 // Tokens uses a striding scheme to essentially store a sequence of structs in
110 // the list. See _MustacheTokens for more information.
111 var values = new List(tokens.length);
112 for (int i = 0; i < tokens.length; i++) {
113 Function delegateFn = tokens.getPrepareBinding(i);
114 values[i] = delegateFn != null ?
115 delegateFn(model, node, false) :
116 tokens.getPath(i).getValueFrom(model);
117 }
118 return tokens.combinator(values);
119 }
120
121 _processSinglePathBinding(String name, MustacheTokens tokens, Node node,
122 model) {
123 Function delegateFn = tokens.getPrepareBinding(0);
124 var observer = delegateFn != null ?
125 delegateFn(model, node, false) :
126 new PathObserver(model, tokens.getPath(0));
127
128 return tokens.isSimplePath ? observer :
129 new ObserverTransform(observer, tokens.combinator);
130 }
131
132 _processBinding(String name, MustacheTokens tokens, Node node, model) {
133 if (tokens.onlyOneTime) {
134 return _processOneTimeBinding(name, tokens, node, model);
135 }
136 if (tokens.hasOnePath) {
137 return _processSinglePathBinding(name, tokens, node, model);
138 }
139
140 var observer = new CompoundObserver();
141
142 for (int i = 0; i < tokens.length; i++) {
143 bool oneTime = tokens.getOneTime(i);
144 Function delegateFn = tokens.getPrepareBinding(i);
145
146 if (delegateFn != null) {
147 var value = delegateFn(model, node, oneTime);
148 if (oneTime) {
149 observer.addPath(value);
150 } else {
151 observer.addObserver(value);
152 }
153 continue;
154 }
155
156 PropertyPath path = tokens.getPath(i);
157 if (oneTime) {
158 observer.addPath(path.getValueFrom(model));
159 } else {
160 observer.addPath(model, path);
161 }
162 }
163
164 return new ObserverTransform(observer, tokens.combinator);
165 }
166
167 void _processBindings(Node node, _InstanceBindingMap map, model,
168 [List<Bindable> instanceBindings]) {
169
170 final bindings = map.bindings;
91 for (var i = 0; i < bindings.length; i += 2) { 171 for (var i = 0; i < bindings.length; i += 2) {
92 var name = bindings[i]; 172 var name = bindings[i];
93 var tokens = bindings[i + 1]; 173 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 174
106 if (!tokens.isSimplePath) { 175 var value = _processBinding(name, tokens, node, model);
107 bindingModel = new PathObserver(bindingModel, bindingPath, 176 var binding = nodeBind(node).bind(name, value, oneTime: tokens.onlyOneTime);
108 computeValue: tokens.combinator); 177 if (binding != null && instanceBindings != null) {
109 bindingPath = 'value'; 178 instanceBindings.add(binding);
110 } 179 }
111 } else { 180 }
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 181
120 if (delegateBinding != null) { 182 if (map is! _TemplateBindingMap) return;
121 subModel = delegateBinding;
122 subPath = 'value';
123 }
124 183
125 observer.addPath(subModel, subPath); 184 final templateExt = nodeBindFallback(node);
126 } 185 templateExt._model = model;
127 186
128 observer.start(); 187 var iter = templateExt._processBindingDirectives(map);
129 bindingModel = observer; 188 if (iter != null && instanceBindings != null) {
130 bindingPath = 'value'; 189 instanceBindings.add(iter);
131 }
132
133 var binding = nodeBind(node).bind(name, bindingModel, bindingPath);
134 if (bound != null) bound.add(binding);
135 } 190 }
136 } 191 }
137 192
138 /**
139 * Parses {{ mustache }} bindings.
140 *
141 * Returns null if there are no matches. Otherwise returns the parsed tokens.
142 */
143 _MustacheTokens _parseMustaches(String s, String name, Node node,
144 BindingDelegate delegate) {
145 if (s.isEmpty) return null;
146 193
147 var tokens = null; 194 // Note: this doesn't really implement most of Bindable. See:
148 var length = s.length; 195 // https://github.com/Polymer/TemplateBinding/issues/147
149 var startIndex = 0, lastIndex = 0, endIndex = 0; 196 class _TemplateIterator extends Bindable {
150 while (lastIndex < length) {
151 startIndex = s.indexOf('{{', lastIndex);
152 endIndex = startIndex < 0 ? -1 : s.indexOf('}}', startIndex + 2);
153
154 if (endIndex < 0) {
155 if (tokens == null) return null;
156
157 tokens.add(s.substring(lastIndex)); // TEXT
158 break;
159 }
160
161 if (tokens == null) tokens = [];
162 tokens.add(s.substring(lastIndex, startIndex)); // TEXT
163 var pathString = s.substring(startIndex + 2, endIndex).trim();
164 tokens.add(pathString); // PATH
165 var delegateFn = delegate == null ? null :
166 delegate.prepareBinding(pathString, name, node);
167 tokens.add(delegateFn);
168
169 lastIndex = endIndex + 2;
170 }
171
172 if (lastIndex == length) tokens.add('');
173
174 return new _MustacheTokens(tokens);
175 }
176
177 class _MustacheTokens {
178 bool get hasOnePath => tokens.length == 4;
179 bool get isSimplePath => hasOnePath && tokens[0] == '' && tokens[3] == '';
180
181 /** [TEXT, (PATH, TEXT, DELEGATE_FN)+] if there is at least one mustache. */
182 // TODO(jmesserly): clean up the type here?
183 final List tokens;
184
185 // 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.
187 Function _combinator;
188 Function get combinator => _combinator;
189
190 _MustacheTokens(this.tokens) {
191 // Should be: [TEXT, (PATH, TEXT, DELEGATE_FN)+].
192 assert((tokens.length + 2) % 3 == 0);
193
194 _combinator = hasOnePath ? _singleCombinator : _listCombinator;
195 }
196
197 // Dart note: split "combinator" into the single/list variants, so the
198 // argument can be typed.
199 String _singleCombinator(Object value) {
200 if (value == null) value = '';
201 return '${tokens[0]}$value${tokens[3]}';
202 }
203
204 String _listCombinator(List<Object> values) {
205 var newValue = new StringBuffer(tokens[0]);
206 for (var i = 1; i < tokens.length; i += 3) {
207 var value = values[(i - 1) ~/ 3];
208 if (value != null) newValue.write(value);
209 newValue.write(tokens[i + 2]);
210 }
211
212 return newValue.toString();
213 }
214 }
215
216 void _addTemplateInstanceRecord(fragment, model) {
217 if (fragment.firstChild == null) {
218 return;
219 }
220
221 var instanceRecord = new TemplateInstance(
222 fragment.firstChild, fragment.lastChild, model);
223
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; 197 final TemplateBindExtension _templateExt;
233 198
234 /** 199 /**
235 * Flattened array of tuples: 200 * Flattened array of tuples:
236 * <instanceTerminatorNode, [bindingsSetupByInstance]> 201 * <instanceTerminatorNode, [bindingsSetupByInstance]>
237 */ 202 */
238 final List terminators = []; 203 final List _terminators = [];
239 List iteratedValue;
240 bool closed = false;
241 bool depsChanging = false;
242 204
243 bool hasRepeat = false, hasBind = false, hasIf = false; 205 /** A copy of the last rendered [_presentValue] list state. */
244 Object repeatModel, bindModel, ifModel; 206 final List _iteratedValue = [];
245 String repeatPath, bindPath, ifPath;
246 207
247 StreamSubscription _valueSub, _listSub; 208 List _presentValue;
209
210 bool _closed = false;
211
212 // Dart note: instead of storing these in a Map like JS, or using a separate
213 // object (extra memory overhead) we just inline the fields.
214 var _ifValue, _value;
215
216 // TODO(jmesserly): lots of booleans in this object. Bitmask?
217 bool _hasIf, _hasRepeat;
218 bool _ifOneTime, _oneTime;
219
220 StreamSubscription _listSub;
248 221
249 bool _initPrepareFunctions = false; 222 bool _initPrepareFunctions = false;
250 PrepareInstanceModelFunction _instanceModelFn; 223 PrepareInstanceModelFunction _instanceModelFn;
251 PrepareInstancePositionChangedFunction _instancePositionChangedFn; 224 PrepareInstancePositionChangedFunction _instancePositionChangedFn;
252 225
253 _TemplateIterator(this._templateExt); 226 _TemplateIterator(this._templateExt);
254 227
228 open(callback) => throw new StateError('binding already opened');
229 get value => _value;
230
255 Element get _templateElement => _templateExt._node; 231 Element get _templateElement => _templateExt._node;
256 232
257 resolve() { 233 void _closeDependencies() {
258 depsChanging = false; 234 if (_ifValue is Bindable) {
235 _ifValue.close();
236 _ifValue = null;
237 }
238 if (_value is Bindable) {
239 _value.close();
240 _value = null;
241 }
242 }
259 243
260 if (_valueSub != null) { 244 void _updateDependencies(_TemplateBindingMap directives, model) {
261 _valueSub.cancel(); 245 _closeDependencies();
262 _valueSub = null; 246
247 final template = _templateElement;
248
249 _hasIf = directives._if != null;
250 _hasRepeat = directives._repeat != null;
251
252 if (_hasIf) {
253 _ifOneTime = directives._if.onlyOneTime;
254 _ifValue = _processBinding('if', directives._if, template, model);
255
256 // oneTime if & predicate is false. nothing else to do.
257 if (_ifOneTime) {
258 if (!_toBoolean(_ifValue)) {
259 _updateIteratedValue(null);
260 return;
261 }
262 } else {
263 (_ifValue as Bindable).open(_updateIteratedValue);
264 }
263 } 265 }
264 266
265 if (!hasRepeat && !hasBind) { 267 if (_hasRepeat) {
266 _valueChanged(null); 268 _oneTime = directives._repeat.onlyOneTime;
267 return; 269 _value = _processBinding('repeat', directives._repeat, template, model);
270 } else {
271 _oneTime = directives._bind.onlyOneTime;
272 _value = _processBinding('bind', directives._bind, template, model);
268 } 273 }
269 274
270 final model = hasRepeat ? repeatModel : bindModel; 275 if (!_oneTime) _value.open(_updateIteratedValue);
271 final path = hasRepeat ? repeatPath : bindPath;
272 276
273 var valueObserver; 277 _updateIteratedValue(null);
274 if (!hasIf) { 278 }
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 279
283 valueFn(List values) { 280 void _updateIteratedValue(_) {
284 var modelValue = values[0]; 281 if (_hasIf) {
285 var ifValue = values[1]; 282 var ifValue = _ifValue;
286 if (!_toBoolean(ifValue)) return null; 283 if (!_ifOneTime) ifValue = (ifValue as Bindable).value;
287 return isRepeat ? modelValue : [ modelValue ]; 284 if (!_toBoolean(ifValue)) {
285 _valueChanged([]);
286 return;
288 } 287 }
289
290 valueObserver = new CompoundPathObserver(computeValue: valueFn)
291 ..addPath(model, path)
292 ..addPath(ifModel, ifPath)
293 ..start();
294 } 288 }
295 289
296 _valueSub = valueObserver.changes.listen( 290 var value = _value;
297 (r) => _valueChanged(r.last.newValue)); 291 if (!_oneTime) value = (value as Bindable).value;
298 _valueChanged(valueObserver.value); 292 if (!_hasRepeat) value = [value];
293 _valueChanged(value);
299 } 294 }
300 295
301 void _valueChanged(newValue) { 296 void _valueChanged(Object value) {
302 var oldValue = iteratedValue; 297 if (value is! List) {
303 unobserve(); 298 if (value is Iterable) {
304 299 // Dart note: we support Iterable by calling toList.
305 if (newValue is List) { 300 // But we need to be careful to observe the original iterator if it
306 iteratedValue = newValue; 301 // supports that.
307 } else if (newValue is Iterable) { 302 value = (value as Iterable).toList();
308 // Dart note: we support Iterable by calling toList. 303 } else {
309 // But we need to be careful to observe the original iterator if it 304 value = [];
310 // supports that. 305 }
311 iteratedValue = (newValue as Iterable).toList();
312 } else {
313 iteratedValue = null;
314 } 306 }
315 307
316 if (iteratedValue != null && newValue is ObservableList) { 308 if (identical(value, _iteratedValue)) return;
317 _listSub = newValue.listChanges.listen(_handleSplices); 309
310 _unobserve();
311 _presentValue = value;
312
313 if (value is ObservableList && _hasRepeat && !_oneTime) {
314 // Make sure any pending changes aren't delivered, since we're getting
315 // a snapshot at this point in time.
316 value.discardListChages();
317 _listSub = value.listChanges.listen(_handleSplices);
318 } 318 }
319 319
320 var splices = ObservableList.calculateChangeRecords( 320 _handleSplices(ObservableList.calculateChangeRecords(
321 oldValue != null ? oldValue : [], 321 _iteratedValue != null ? _iteratedValue : [],
322 iteratedValue != null ? iteratedValue : []); 322 _presentValue != null ? _presentValue : []));
323
324 if (splices.isNotEmpty) _handleSplices(splices);
325 } 323 }
326 324
327 Node getTerminatorAt(int index) { 325 Node _getTerminatorAt(int index) {
328 if (index == -1) return _templateElement; 326 if (index == -1) return _templateElement;
329 var terminator = terminators[index * 2]; 327 var terminator = _terminators[index * 2];
330 if (!isSemanticTemplate(terminator) || 328 if (!isSemanticTemplate(terminator) ||
331 identical(terminator, _templateElement)) { 329 identical(terminator, _templateElement)) {
332 return terminator; 330 return terminator;
333 } 331 }
334 332
335 var subIter = templateBindFallback(terminator)._iterator; 333 var subIter = templateBindFallback(terminator)._iterator;
336 if (subIter == null) return terminator; 334 if (subIter == null) return terminator;
337 335
338 return subIter.getTerminatorAt(subIter.terminators.length ~/ 2 - 1); 336 return subIter._getTerminatorAt(subIter._terminators.length ~/ 2 - 1);
339 } 337 }
340 338
341 // TODO(rafaelw): If we inserting sequences of instances we can probably 339 // TODO(rafaelw): If we inserting sequences of instances we can probably
342 // avoid lots of calls to getTerminatorAt(), or cache its result. 340 // avoid lots of calls to _getTerminatorAt(), or cache its result.
343 void insertInstanceAt(int index, DocumentFragment fragment, 341 void _insertInstanceAt(int index, DocumentFragment fragment,
344 List<Node> instanceNodes, List<NodeBinding> bound) { 342 List<Node> instanceNodes, List<Bindable> instanceBindings) {
345 343
346 var previousTerminator = getTerminatorAt(index - 1); 344 var previousTerminator = _getTerminatorAt(index - 1);
347 var terminator = null; 345 var terminator = null;
348 if (fragment != null) { 346 if (fragment != null) {
349 terminator = fragment.lastChild; 347 terminator = fragment.lastChild;
350 } else if (instanceNodes != null && instanceNodes.isNotEmpty) { 348 } else if (instanceNodes != null && instanceNodes.isNotEmpty) {
351 terminator = instanceNodes.last; 349 terminator = instanceNodes.last;
352 } 350 }
353 if (terminator == null) terminator = previousTerminator; 351 if (terminator == null) terminator = previousTerminator;
354 352
355 terminators.insertAll(index * 2, [terminator, bound]); 353 _terminators.insertAll(index * 2, [terminator, instanceBindings]);
356 var parent = _templateElement.parentNode; 354 var parent = _templateElement.parentNode;
357 var insertBeforeNode = previousTerminator.nextNode; 355 var insertBeforeNode = previousTerminator.nextNode;
358 356
359 if (fragment != null) { 357 if (fragment != null) {
360 parent.insertBefore(fragment, insertBeforeNode); 358 parent.insertBefore(fragment, insertBeforeNode);
361 } else if (instanceNodes != null) { 359 } else if (instanceNodes != null) {
362 for (var node in instanceNodes) { 360 for (var node in instanceNodes) {
363 parent.insertBefore(node, insertBeforeNode); 361 parent.insertBefore(node, insertBeforeNode);
364 } 362 }
365 } 363 }
366 } 364 }
367 365
368 _BoundNodes extractInstanceAt(int index) { 366 _BoundNodes _extractInstanceAt(int index) {
369 var instanceNodes = <Node>[]; 367 var instanceNodes = <Node>[];
370 var previousTerminator = getTerminatorAt(index - 1); 368 var previousTerminator = _getTerminatorAt(index - 1);
371 var terminator = getTerminatorAt(index); 369 var terminator = _getTerminatorAt(index);
372 var bound = terminators[index * 2 + 1]; 370 var instanceBindings = _terminators[index * 2 + 1];
373 terminators.removeRange(index * 2, index * 2 + 2); 371 _terminators.removeRange(index * 2, index * 2 + 2);
374 372
375 var parent = _templateElement.parentNode; 373 var parent = _templateElement.parentNode;
376 while (terminator != previousTerminator) { 374 while (terminator != previousTerminator) {
377 var node = previousTerminator.nextNode; 375 var node = previousTerminator.nextNode;
378 if (node == terminator) terminator = previousTerminator; 376 if (node == terminator) terminator = previousTerminator;
379 node.remove(); 377 node.remove();
380 instanceNodes.add(node); 378 instanceNodes.add(node);
381 } 379 }
382 return new _BoundNodes(instanceNodes, bound); 380 return new _BoundNodes(instanceNodes, instanceBindings);
383 } 381 }
384 382
385 void _handleSplices(List<ListChangeRecord> splices) { 383 void _handleSplices(List<ListChangeRecord> splices) {
386 if (closed) return; 384 if (_closed || splices.isEmpty) return;
387 385
388 final template = _templateElement; 386 final template = _templateElement;
389 final delegate = _templateExt._self.bindingDelegate;
390 387
391 if (template.parentNode == null || template.ownerDocument.window == null) { 388 if (template.parentNode == null) {
392 close(); 389 close();
393 return; 390 return;
394 } 391 }
395 392
393 ObservableList.applyChangeRecords(_iteratedValue, _presentValue, splices);
394
395 final delegate = _templateExt.bindingDelegate;
396
396 // Dart note: the JavaScript code relies on the distinction between null 397 // 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. 398 // and undefined to track whether the functions are prepared. We use a bool.
398 if (!_initPrepareFunctions) { 399 if (!_initPrepareFunctions) {
399 _initPrepareFunctions = true; 400 _initPrepareFunctions = true;
401 final delegate = _templateExt._self.bindingDelegate;
400 if (delegate != null) { 402 if (delegate != null) {
401 _instanceModelFn = delegate.prepareInstanceModel(template); 403 _instanceModelFn = delegate.prepareInstanceModel(template);
402 _instancePositionChangedFn = 404 _instancePositionChangedFn =
403 delegate.prepareInstancePositionChanged(template); 405 delegate.prepareInstancePositionChanged(template);
404 } 406 }
405 } 407 }
406 408
407 var instanceCache = new HashMap<Object, _BoundNodes>(equals: identical); 409 var instanceCache = new HashMap<Object, _BoundNodes>(equals: identical);
408 var removeDelta = 0; 410 var removeDelta = 0;
409 for (var splice in splices) { 411 for (var splice in splices) {
410 for (var model in splice.removed) { 412 for (var model in splice.removed) {
411 instanceCache[model] = extractInstanceAt(splice.index + removeDelta); 413 instanceCache[model] = _extractInstanceAt(splice.index + removeDelta);
412 } 414 }
413 415
414 removeDelta -= splice.addedCount; 416 removeDelta -= splice.addedCount;
415 } 417 }
416 418
417 for (var splice in splices) { 419 for (var splice in splices) {
418 for (var addIndex = splice.index; 420 for (var addIndex = splice.index;
419 addIndex < splice.index + splice.addedCount; 421 addIndex < splice.index + splice.addedCount;
420 addIndex++) { 422 addIndex++) {
421 423
422 var model = iteratedValue[addIndex]; 424 var model = _iteratedValue[addIndex];
423 var fragment = null; 425 var fragment = null;
424 var instance = instanceCache.remove(model); 426 var instance = instanceCache.remove(model);
425 List bound; 427 List instanceBindings;
426 List instanceNodes = null; 428 List instanceNodes = null;
427 if (instance != null && instance.nodes.isNotEmpty) { 429 if (instance != null && instance.nodes.isNotEmpty) {
428 bound = instance.bound; 430 instanceBindings = instance.instanceBindings;
429 instanceNodes = instance.nodes; 431 instanceNodes = instance.nodes;
430 } else { 432 } else {
431 bound = []; 433 instanceBindings = [];
432 if (_instanceModelFn != null) { 434 if (_instanceModelFn != null) {
433 model = _instanceModelFn(model); 435 model = _instanceModelFn(model);
434 } 436 }
435 if (model != null) { 437 if (model != null) {
436 fragment = _templateExt.createInstance(model, delegate, bound); 438 fragment = _templateExt.createInstance(model, delegate,
439 instanceBindings);
437 } 440 }
438 } 441 }
439 442
440 insertInstanceAt(addIndex, fragment, instanceNodes, bound); 443 _insertInstanceAt(addIndex, fragment, instanceNodes, instanceBindings);
441 } 444 }
442 } 445 }
443 446
444 for (var instance in instanceCache.values) { 447 for (var instance in instanceCache.values) {
445 closeInstanceBindings(instance.bound); 448 _closeInstanceBindings(instance.instanceBindings);
446 } 449 }
447 450
448 if (_instancePositionChangedFn != null) reportInstancesMoved(splices); 451 if (_instancePositionChangedFn != null) _reportInstancesMoved(splices);
449 } 452 }
450 453
451 void reportInstanceMoved(int index) { 454 void _reportInstanceMoved(int index) {
452 var previousTerminator = getTerminatorAt(index - 1); 455 var previousTerminator = _getTerminatorAt(index - 1);
453 var terminator = getTerminatorAt(index); 456 var terminator = _getTerminatorAt(index);
454 if (identical(previousTerminator, terminator)) { 457 if (identical(previousTerminator, terminator)) {
455 return; // instance has zero nodes. 458 return; // instance has zero nodes.
456 } 459 }
457 460
458 // We must use the first node of the instance, because any subsequent 461 // We must use the first node of the instance, because any subsequent
459 // nodes may have been generated by sub-templates. 462 // nodes may have been generated by sub-templates.
460 // TODO(rafaelw): This is brittle WRT instance mutation -- e.g. if the 463 // TODO(rafaelw): This is brittle WRT instance mutation -- e.g. if the
461 // first node was removed by script. 464 // first node was removed by script.
462 var instance = nodeBind(previousTerminator.nextNode).templateInstance; 465 var instance = nodeBind(previousTerminator.nextNode).templateInstance;
463 _instancePositionChangedFn(instance, index); 466 _instancePositionChangedFn(instance, index);
464 } 467 }
465 468
466 void reportInstancesMoved(List<ListChangeRecord> splices) { 469 void _reportInstancesMoved(List<ListChangeRecord> splices) {
467 var index = 0; 470 var index = 0;
468 var offset = 0; 471 var offset = 0;
469 for (var splice in splices) { 472 for (var splice in splices) {
470 if (offset != 0) { 473 if (offset != 0) {
471 while (index < splice.index) { 474 while (index < splice.index) {
472 reportInstanceMoved(index); 475 _reportInstanceMoved(index);
473 index++; 476 index++;
474 } 477 }
475 } else { 478 } else {
476 index = splice.index; 479 index = splice.index;
477 } 480 }
478 481
479 while (index < splice.index + splice.addedCount) { 482 while (index < splice.index + splice.addedCount) {
480 reportInstanceMoved(index); 483 _reportInstanceMoved(index);
481 index++; 484 index++;
482 } 485 }
483 486
484 offset += splice.addedCount - splice.removed.length; 487 offset += splice.addedCount - splice.removed.length;
485 } 488 }
486 489
487 if (offset == 0) return; 490 if (offset == 0) return;
488 491
489 var length = terminators.length ~/ 2; 492 var length = _terminators.length ~/ 2;
490 while (index < length) { 493 while (index < length) {
491 reportInstanceMoved(index); 494 _reportInstanceMoved(index);
492 index++; 495 index++;
493 } 496 }
494 } 497 }
495 498
496 void closeInstanceBindings(List<NodeBinding> bound) { 499 void _closeInstanceBindings(List<Bindable> instanceBindings) {
497 for (var binding in bound) binding.close(); 500 for (var binding in instanceBindings) binding.close();
498 } 501 }
499 502
500 void unobserve() { 503 void _unobserve() {
501 if (_listSub == null) return; 504 if (_listSub == null) return;
502 _listSub.cancel(); 505 _listSub.cancel();
503 _listSub = null; 506 _listSub = null;
504 } 507 }
505 508
506 void close() { 509 void close() {
507 if (closed) return; 510 if (_closed) return;
508 511
509 unobserve(); 512 _unobserve();
510 for (var i = 1; i < terminators.length; i += 2) { 513 for (var i = 1; i < _terminators.length; i += 2) {
511 closeInstanceBindings(terminators[i]); 514 _closeInstanceBindings(_terminators[i]);
512 } 515 }
513 516
514 terminators.clear(); 517 _terminators.clear();
515 if (_valueSub != null) { 518 _closeDependencies();
516 _valueSub.cancel();
517 _valueSub = null;
518 }
519 _templateExt._iterator = null; 519 _templateExt._iterator = null;
520 closed = true; 520 _closed = true;
521 } 521 }
522 } 522 }
523 523
524 // Dart note: the JavaScript version just puts an expando on the array. 524 // Dart note: the JavaScript version just puts an expando on the array.
525 class _BoundNodes { 525 class _BoundNodes {
526 final List<Node> nodes; 526 final List<Node> nodes;
527 final List<NodeBinding> bound; 527 final List<Bindable> instanceBindings;
528 _BoundNodes(this.nodes, this.bound); 528 _BoundNodes(this.nodes, this.instanceBindings);
529 } 529 }
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