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

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

Issue 34453003: Rename mdv -> template_binding, remove experiemntal apis from dart:html (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 7 years, 2 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/mdv/lib/src/template.dart ('k') | pkg/mdv/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
(Empty)
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
3 // BSD-style license that can be found in the LICENSE file.
4
5 part of mdv;
6
7 // This code is a port of Model-Driven-Views:
8 // https://github.com/polymer-project/mdv
9 // The code mostly comes from src/template_element.js
10
11 // TODO(jmesserly): not sure what kind of boolean conversion rules to
12 // apply for template data-binding. HTML attributes are true if they're
13 // present. However Dart only treats "true" as true. Since this is HTML we'll
14 // use something closer to the HTML rules: null (missing) and false are false,
15 // everything else is true. See: https://github.com/polymer-project/mdv/issues/5 9
16 bool _toBoolean(value) => null != value && false != value;
17
18 Node _createDeepCloneAndDecorateTemplates(Node node, BindingDelegate delegate) {
19 var clone = node.clone(false); // Shallow clone.
20 if (clone is Element && clone.isTemplate) {
21 TemplateElement.decorate(clone, node);
22 if (delegate != null) {
23 _mdv(clone)._bindingDelegate = delegate;
24 }
25 }
26
27 for (var c = node.firstChild; c != null; c = c.nextNode) {
28 clone.append(_createDeepCloneAndDecorateTemplates(c, delegate));
29 }
30 return clone;
31 }
32
33 void _addBindings(Node node, model, [BindingDelegate delegate]) {
34 List bindings = null;
35 if (node is Element) {
36 bindings = _parseAttributeBindings(node);
37 } else if (node is Text) {
38 var tokens = _parseMustacheTokens(node.text);
39 if (tokens != null) bindings = ['text', tokens];
40 }
41
42 if (bindings != null) {
43 _processBindings(bindings, node, model, delegate);
44 }
45
46 for (var c = node.firstChild; c != null; c = c.nextNode) {
47 _addBindings(c, model, delegate);
48 }
49 }
50
51 List _parseAttributeBindings(Element element) {
52 var bindings = null;
53 var ifFound = false;
54 var bindFound = false;
55 var isTemplateNode = element.isTemplate;
56
57 element.attributes.forEach((name, value) {
58 if (isTemplateNode) {
59 if (name == 'if') {
60 ifFound = true;
61 } else if (name == 'bind' || name == 'repeat') {
62 bindFound = true;
63 if (value == '') value = '{{}}';
64 }
65 }
66
67 var tokens = _parseMustacheTokens(value);
68 if (tokens != null) {
69 if (bindings == null) bindings = [];
70 bindings..add(name)..add(tokens);
71 }
72 });
73
74 // Treat <template if> as <template bind if>
75 if (ifFound && !bindFound) {
76 if (bindings == null) bindings = [];
77 bindings..add('bind')..add(_parseMustacheTokens('{{}}'));
78 }
79
80 return bindings;
81 }
82
83 void _processBindings(List bindings, Node node, model,
84 BindingDelegate delegate) {
85
86 for (var i = 0; i < bindings.length; i += 2) {
87 _setupBinding(node, bindings[i], bindings[i + 1], model, delegate);
88 }
89 }
90
91 void _setupBinding(Node node, String name, List tokens, model,
92 BindingDelegate delegate) {
93
94 // If this is a custom element, give the .xtag a change to bind.
95 node = _nodeOrCustom(node);
96
97 if (_isSimpleBinding(tokens)) {
98 _bindOrDelegate(node, name, model, tokens[1], delegate);
99 return;
100 }
101
102 // TODO(jmesserly): MDV caches the closure on the tokens, but I'm not sure
103 // why they do that instead of just caching the entire CompoundBinding object
104 // and unbindAll then bind to the new model.
105 var replacementBinding = new CompoundBinding()
106 ..scheduled = true
107 ..combinator = (values) {
108 var newValue = new StringBuffer();
109
110 for (var i = 0, text = true; i < tokens.length; i++, text = !text) {
111 if (text) {
112 newValue.write(tokens[i]);
113 } else {
114 var value = values[i];
115 if (value != null) {
116 newValue.write(value);
117 }
118 }
119 }
120
121 return newValue.toString();
122 };
123
124 for (var i = 1; i < tokens.length; i += 2) {
125 // TODO(jmesserly): not sure if this index is correct. See my comment here:
126 // https://github.com/Polymer/mdv/commit/f1af6fe683fd06eed2a7a7849f01c227db1 2cda3#L0L1035
127 _bindOrDelegate(replacementBinding, i, model, tokens[i], delegate);
128 }
129
130 replacementBinding.resolve();
131
132 node.bind(name, replacementBinding, 'value');
133 }
134
135 void _bindOrDelegate(node, name, model, String path,
136 BindingDelegate delegate) {
137
138 if (delegate != null) {
139 var delegateBinding = delegate.getBinding(model, path, name, node);
140 if (delegateBinding != null) {
141 model = delegateBinding;
142 path = 'value';
143 }
144 }
145
146 node.bind(name, model, path);
147 }
148
149 /**
150 * Gets the [node]'s custom [Element.xtag] if present, otherwise returns
151 * the node. This is used so nodes can override [Node.bind], [Node.unbind],
152 * and [Node.unbindAll] like InputElement does.
153 */
154 // TODO(jmesserly): remove this when we can extend Element for real.
155 _nodeOrCustom(node) => node is Element ? node.xtag : node;
156
157 /** True if and only if [tokens] is of the form `['', path, '']`. */
158 bool _isSimpleBinding(List<String> tokens) =>
159 tokens.length == 3 && tokens[0].isEmpty && tokens[2].isEmpty;
160
161 /**
162 * Parses {{ mustache }} bindings.
163 *
164 * Returns null if there are no matches. Otherwise returns
165 * [TEXT, (PATH, TEXT)+] if there is at least one mustache.
166 */
167 List<String> _parseMustacheTokens(String s) {
168 if (s.isEmpty) return null;
169
170 var tokens = null;
171 var length = s.length;
172 var startIndex = 0, lastIndex = 0, endIndex = 0;
173 while (lastIndex < length) {
174 startIndex = s.indexOf('{{', lastIndex);
175 endIndex = startIndex < 0 ? -1 : s.indexOf('}}', startIndex + 2);
176
177 if (endIndex < 0) {
178 if (tokens == null) return null;
179
180 tokens.add(s.substring(lastIndex));
181 break;
182 }
183
184 if (tokens == null) tokens = <String>[];
185 tokens.add(s.substring(lastIndex, startIndex)); // TEXT
186 tokens.add(s.substring(startIndex + 2, endIndex).trim()); // PATH
187 lastIndex = endIndex + 2;
188 }
189
190 if (lastIndex == length) tokens.add('');
191 return tokens;
192 }
193
194 void _addTemplateInstanceRecord(fragment, model) {
195 if (fragment.firstChild == null) {
196 return;
197 }
198
199 var instanceRecord = new TemplateInstance(
200 fragment.firstChild, fragment.lastChild, model);
201
202 var node = instanceRecord.firstNode;
203 while (node != null) {
204 _mdv(node)._templateInstance = instanceRecord;
205 node = node.nextNode;
206 }
207 }
208
209
210 class _TemplateIterator {
211 final Element _templateElement;
212 final List<Node> terminators = [];
213 CompoundBinding inputs;
214 List iteratedValue;
215 bool closed = false;
216
217 StreamSubscription _sub;
218
219 _TemplateIterator(this._templateElement) {
220 inputs = new CompoundBinding(resolveInputs);
221 }
222
223 resolveInputs(Map values) {
224 if (closed) return;
225
226 if (values.containsKey('if') && !_toBoolean(values['if'])) {
227 valueChanged(null);
228 } else if (values.containsKey('repeat')) {
229 valueChanged(values['repeat']);
230 } else if (values.containsKey('bind') || values.containsKey('if')) {
231 valueChanged([values['bind']]);
232 } else {
233 valueChanged(null);
234 }
235 // We don't return a value to the CompoundBinding; instead we skip a hop and
236 // call valueChanged directly.
237 return null;
238 }
239
240 void valueChanged(value) {
241 if (value is! List) value = null;
242
243 var oldValue = iteratedValue;
244 unobserve();
245 iteratedValue = value;
246
247 if (iteratedValue is Observable) {
248 _sub = (iteratedValue as Observable).changes.listen(_handleChanges);
249 }
250
251 var splices = calculateSplices(
252 iteratedValue != null ? iteratedValue : [],
253 oldValue != null ? oldValue : []);
254
255 if (splices.length > 0) _handleChanges(splices);
256
257 if (inputs.length == 0) {
258 close();
259 _mdv(_templateElement)._templateIterator = null;
260 }
261 }
262
263 Node getTerminatorAt(int index) {
264 if (index == -1) return _templateElement;
265 var terminator = terminators[index];
266 if (terminator is Element && (terminator as Element).isTemplate &&
267 !identical(terminator, _templateElement)) {
268 var subIterator = _mdv(terminator)._templateIterator;
269 if (subIterator != null) {
270 return subIterator.getTerminatorAt(subIterator.terminators.length - 1);
271 }
272 }
273
274 return terminator;
275 }
276
277 void insertInstanceAt(int index, DocumentFragment fragment,
278 List<Node> instanceNodes) {
279
280 var previousTerminator = getTerminatorAt(index - 1);
281 var terminator = null;
282 if (fragment != null) {
283 terminator = fragment.lastChild;
284 } else if (instanceNodes.length > 0) {
285 terminator = instanceNodes.last;
286 }
287 if (terminator == null) terminator = previousTerminator;
288
289 terminators.insert(index, terminator);
290
291 var parent = _templateElement.parentNode;
292 var insertBeforeNode = previousTerminator.nextNode;
293
294 if (fragment != null) {
295 parent.insertBefore(fragment, insertBeforeNode);
296 return;
297 }
298
299 for (var node in instanceNodes) {
300 parent.insertBefore(node, insertBeforeNode);
301 }
302 }
303
304 List<Node> extractInstanceAt(int index) {
305 var instanceNodes = <Node>[];
306 var previousTerminator = getTerminatorAt(index - 1);
307 var terminator = getTerminatorAt(index);
308 terminators.removeAt(index);
309
310 var parent = _templateElement.parentNode;
311 while (terminator != previousTerminator) {
312 var node = previousTerminator.nextNode;
313 if (node == terminator) terminator = previousTerminator;
314 node.remove();
315 instanceNodes.add(node);
316 }
317 return instanceNodes;
318 }
319
320 getInstanceModel(model, BindingDelegate delegate) {
321 if (delegate != null) {
322 return delegate.getInstanceModel(_templateElement, model);
323 }
324 return model;
325 }
326
327 DocumentFragment getInstanceFragment(model, BindingDelegate delegate) {
328 return _templateElement.createInstance(model, delegate);
329 }
330
331 void _handleChanges(Iterable<ChangeRecord> splices) {
332 if (closed) return;
333
334 splices = splices.where((s) => s is ListChangeRecord);
335
336 var template = _templateElement;
337 var delegate = template.bindingDelegate;
338
339 if (template.parentNode == null || template.ownerDocument.window == null) {
340 close();
341 // TODO(jmesserly): MDV calls templateIteratorTable.delete(this) here,
342 // but I think that's a no-op because only nodes are used as keys.
343 // See https://github.com/Polymer/mdv/pull/114.
344 return;
345 }
346
347 var instanceCache = new HashMap(equals: identical);
348 var removeDelta = 0;
349 for (var splice in splices) {
350 for (int i = 0; i < splice.removedCount; i++) {
351 var instanceNodes = extractInstanceAt(splice.index + removeDelta);
352 if (instanceNodes.length == 0) continue;
353 var model = _mdv(instanceNodes.first)._templateInstance.model;
354 instanceCache[model] = instanceNodes;
355 }
356
357 removeDelta -= splice.addedCount;
358 }
359
360 for (var splice in splices) {
361 for (var addIndex = splice.index;
362 addIndex < splice.index + splice.addedCount;
363 addIndex++) {
364
365 var model = iteratedValue[addIndex];
366 var fragment = null;
367 var instanceNodes = instanceCache.remove(model);
368 if (instanceNodes == null) {
369 var actualModel = getInstanceModel(model, delegate);
370 fragment = getInstanceFragment(actualModel, delegate);
371 }
372
373 insertInstanceAt(addIndex, fragment, instanceNodes);
374 }
375 }
376
377 for (var instanceNodes in instanceCache.values) {
378 instanceNodes.forEach(_unbindAllRecursively);
379 }
380 }
381
382 void unobserve() {
383 if (_sub == null) return;
384 _sub.cancel();
385 _sub = null;
386 }
387
388 void close() {
389 if (closed) return;
390
391 unobserve();
392 inputs.close();
393 terminators.clear();
394 closed = true;
395 }
396
397 static void _unbindAllRecursively(Node node) {
398 var nodeExt = _mdv(node);
399 nodeExt._templateInstance = null;
400 if (node is Element && (node as Element).isTemplate) {
401 // Make sure we stop observing when we remove an element.
402 var templateIterator = nodeExt._templateIterator;
403 if (templateIterator != null) {
404 templateIterator.close();
405 nodeExt._templateIterator = null;
406 }
407 }
408
409 _nodeOrCustom(node).unbindAll();
410 for (var c = node.firstChild; c != null; c = c.nextNode) {
411 _unbindAllRecursively(c);
412 }
413 }
414 }
OLDNEW
« no previous file with comments | « pkg/mdv/lib/src/template.dart ('k') | pkg/mdv/lib/src/text.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698