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

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

Issue 18162004: [package:mdv] port createInstance change (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: fix copyright line Created 7 years, 5 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/mdv.dart ('k') | pkg/mdv/lib/src/element.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 mdv; 5 part of mdv;
6 6
7 // This code is a port of Model-Driven-Views: 7 // This code is a port of Model-Driven-Views:
8 // https://github.com/polymer-project/mdv 8 // https://github.com/polymer-project/mdv
9 // The code mostly comes from src/template_element.js 9 // The code mostly comes from src/template_element.js
10 10
(...skipping 63 matching lines...) Expand 10 before | Expand all | Expand 10 after
74 74
75 void updateBinding(e) { 75 void updateBinding(e) {
76 binding.value = element.value; 76 binding.value = element.value;
77 } 77 }
78 } 78 }
79 79
80 class _CheckedBinding extends _InputBinding { 80 class _CheckedBinding extends _InputBinding {
81 _CheckedBinding(element, model, path) : super(element, model, path); 81 _CheckedBinding(element, model, path) : super(element, model, path);
82 82
83 void valueChanged(value) { 83 void valueChanged(value) {
84 element.checked = _Bindings._toBoolean(value); 84 element.checked = _toBoolean(value);
85 } 85 }
86 86
87 void updateBinding(e) { 87 void updateBinding(e) {
88 binding.value = element.checked; 88 binding.value = element.checked;
89 89
90 // Only the radio button that is getting checked gets an event. We 90 // Only the radio button that is getting checked gets an event. We
91 // therefore find all the associated radio buttons and update their 91 // therefore find all the associated radio buttons and update their
92 // CheckedBinding manually. 92 // CheckedBinding manually.
93 if (element is InputElement && element.type == 'radio') { 93 if (element is InputElement && element.type == 'radio') {
94 for (var r in _getAssociatedRadioButtons(element)) { 94 for (var r in _getAssociatedRadioButtons(element)) {
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after
129 // TODO(jmesserly): polyfill document.contains API instead of doing it here 129 // TODO(jmesserly): polyfill document.contains API instead of doing it here
130 static bool _isNodeInDocument(Node node) { 130 static bool _isNodeInDocument(Node node) {
131 // On non-IE this works: 131 // On non-IE this works:
132 // return node.document.contains(node); 132 // return node.document.contains(node);
133 var document = node.document; 133 var document = node.document;
134 if (node == document || node.parentNode == document) return true; 134 if (node == document || node.parentNode == document) return true;
135 return document.documentElement.contains(node); 135 return document.documentElement.contains(node);
136 } 136 }
137 } 137 }
138 138
139 class _Bindings { 139 // TODO(jmesserly): not sure what kind of boolean conversion rules to
140 // TODO(jmesserly): not sure what kind of boolean conversion rules to 140 // apply for template data-binding. HTML attributes are true if they're
141 // apply for template data-binding. HTML attributes are true if they're 141 // present. However Dart only treats "true" as true. Since this is HTML we'll
142 // present. However Dart only treats "true" as true. Since this is HTML we'll 142 // use something closer to the HTML rules: null (missing) and false are false,
143 // use something closer to the HTML rules: null (missing) and false are false, 143 // everything else is true. See: https://github.com/polymer-project/mdv/issues/5 9
144 // everything else is true. See: https://github.com/polymer-project/mdv/issues /59 144 bool _toBoolean(value) => null != value && false != value;
145 static bool _toBoolean(value) => null != value && false != value;
146 145
147 static Node _createDeepCloneAndDecorateTemplates(Node node, String syntax) { 146 Node _createDeepCloneAndDecorateTemplates(Node node, String syntax) {
Siggi Cherem (dart-lang) 2013/06/28 20:42:00 other than indentation, did anything else change f
Jennifer Messerly 2013/06/28 20:45:39 "syntax" was renamed to "delegate" if it's not a S
Siggi Cherem (dart-lang) 2013/06/28 20:47:28 I see- will the type change too? (CustomBindingSyn
Jennifer Messerly 2013/06/28 20:48:48 yeah I'll rename it once I catch up to the "bindin
148 var clone = node.clone(false); // Shallow clone. 147 var clone = node.clone(false); // Shallow clone.
149 if (clone is Element && clone.isTemplate) { 148 if (clone is Element && clone.isTemplate) {
150 TemplateElement.decorate(clone, node); 149 TemplateElement.decorate(clone, node);
151 if (syntax != null) { 150 if (syntax != null) {
152 clone.attributes.putIfAbsent('syntax', () => syntax); 151 clone.attributes.putIfAbsent('syntax', () => syntax);
152 }
153 }
154
155 for (var c = node.firstChild; c != null; c = c.nextNode) {
156 clone.append(_createDeepCloneAndDecorateTemplates(c, syntax));
157 }
158 return clone;
159 }
160
161 void _addBindings(Node node, model, [CustomBindingSyntax delegate]) {
162 if (node is Element) {
163 _addAttributeBindings(node, model, delegate);
164 } else if (node is Text) {
165 _parseAndBind(node, 'text', node.text, model, delegate);
166 }
167
168 for (var c = node.firstChild; c != null; c = c.nextNode) {
169 _addBindings(c, model, delegate);
170 }
171 }
172
173 void _addAttributeBindings(Element element, model, delegate) {
174 element.attributes.forEach((name, value) {
175 if (value == '' && (name == 'bind' || name == 'repeat')) {
176 value = '{{}}';
177 }
178 _parseAndBind(element, name, value, model, delegate);
179 });
180 }
181
182 void _parseAndBind(Node node, String name, String text, model,
183 CustomBindingSyntax delegate) {
184
185 var tokens = _parseMustacheTokens(text);
186 if (tokens.length == 0 || (tokens.length == 1 && tokens[0].isText)) {
187 return;
188 }
189
190 // If this is a custom element, give the .xtag a change to bind.
191 node = _nodeOrCustom(node);
192
193 if (tokens.length == 1 && tokens[0].isBinding) {
194 _bindOrDelegate(node, name, model, tokens[0].value, delegate);
195 return;
196 }
197
198 var replacementBinding = new CompoundBinding();
199 for (var i = 0; i < tokens.length; i++) {
200 var token = tokens[i];
201 if (token.isBinding) {
202 _bindOrDelegate(replacementBinding, i, model, token.value, delegate);
203 }
204 }
205
206 replacementBinding.combinator = (values) {
207 var newValue = new StringBuffer();
208
209 for (var i = 0; i < tokens.length; i++) {
210 var token = tokens[i];
211 if (token.isText) {
212 newValue.write(token.value);
213 } else {
214 var value = values[i];
215 if (value != null) {
216 newValue.write(value);
217 }
153 } 218 }
154 } 219 }
155 220
156 for (var c = node.firstChild; c != null; c = c.nextNode) { 221 return newValue.toString();
157 clone.append(_createDeepCloneAndDecorateTemplates(c, syntax)); 222 };
158 }
159 return clone;
160 }
161 223
162 static void _addBindings(Node node, model, [CustomBindingSyntax syntax]) { 224 node.bind(name, replacementBinding, 'value');
163 if (node is Element) { 225 }
164 _addAttributeBindings(node, model, syntax);
165 } else if (node is Text) {
166 _parseAndBind(node, 'text', node.text, model, syntax);
167 }
168 226
169 for (var c = node.firstChild; c != null; c = c.nextNode) { 227 void _bindOrDelegate(node, name, model, String path,
170 _addBindings(c, model, syntax); 228 CustomBindingSyntax delegate) {
229
230 if (delegate != null) {
231 var delegateBinding = delegate.getBinding(model, path, name, node);
232 if (delegateBinding != null) {
233 model = delegateBinding;
234 path = 'value';
171 } 235 }
172 } 236 }
173 237
174 static void _addAttributeBindings(Element element, model, syntax) { 238 node.bind(name, model, path);
175 element.attributes.forEach((name, value) { 239 }
176 if (value == '' && (name == 'bind' || name == 'repeat')) { 240
177 value = '{{}}'; 241 /**
242 * Gets the [node]'s custom [Element.xtag] if present, otherwise returns
243 * the node. This is used so nodes can override [Node.bind], [Node.unbind],
244 * and [Node.unbindAll] like InputElement does.
245 */
246 // TODO(jmesserly): remove this when we can extend Element for real.
247 _nodeOrCustom(node) => node is Element ? node.xtag : node;
248
249 List<_BindingToken> _parseMustacheTokens(String s) {
250 var result = [];
251 var length = s.length;
252 var index = 0, lastIndex = 0;
253 while (lastIndex < length) {
254 index = s.indexOf('{{', lastIndex);
255 if (index < 0) {
256 result.add(new _BindingToken(s.substring(lastIndex)));
257 break;
258 } else {
259 // There is a non-empty text run before the next path token.
260 if (index > 0 && lastIndex < index) {
261 result.add(new _BindingToken(s.substring(lastIndex, index)));
178 } 262 }
179 _parseAndBind(element, name, value, model, syntax); 263 lastIndex = index + 2;
180 }); 264 index = s.indexOf('}}', lastIndex);
265 if (index < 0) {
266 var text = s.substring(lastIndex - 2);
267 if (result.length > 0 && result.last.isText) {
268 result.last.value += text;
269 } else {
270 result.add(new _BindingToken(text));
271 }
272 break;
273 }
274
275 var value = s.substring(lastIndex, index).trim();
276 result.add(new _BindingToken(value, isBinding: true));
277 lastIndex = index + 2;
278 }
279 }
280 return result;
281 }
282
283 void _addTemplateInstanceRecord(fragment, model) {
284 if (fragment.firstChild == null) {
285 return;
181 } 286 }
182 287
183 static void _parseAndBind(Node node, String name, String text, model, 288 var instanceRecord = new TemplateInstance(
184 CustomBindingSyntax syntax) { 289 fragment.firstChild, fragment.lastChild, model);
185 290
186 var tokens = _parseMustacheTokens(text); 291 var node = instanceRecord.firstNode;
187 if (tokens.length == 0 || (tokens.length == 1 && tokens[0].isText)) { 292 while (node != null) {
188 return; 293 _mdv(node)._templateInstance = instanceRecord;
189 } 294 node = node.nextNode;
190
191 // If this is a custom element, give the .xtag a change to bind.
192 node = _nodeOrCustom(node);
193
194 if (tokens.length == 1 && tokens[0].isBinding) {
195 _bindOrDelegate(node, name, model, tokens[0].value, syntax);
196 return;
197 }
198
199 var replacementBinding = new CompoundBinding();
200 for (var i = 0; i < tokens.length; i++) {
201 var token = tokens[i];
202 if (token.isBinding) {
203 _bindOrDelegate(replacementBinding, i, model, token.value, syntax);
204 }
205 }
206
207 replacementBinding.combinator = (values) {
208 var newValue = new StringBuffer();
209
210 for (var i = 0; i < tokens.length; i++) {
211 var token = tokens[i];
212 if (token.isText) {
213 newValue.write(token.value);
214 } else {
215 var value = values[i];
216 if (value != null) {
217 newValue.write(value);
218 }
219 }
220 }
221
222 return newValue.toString();
223 };
224
225 node.bind(name, replacementBinding, 'value');
226 }
227
228 static void _bindOrDelegate(node, name, model, String path,
229 CustomBindingSyntax syntax) {
230
231 if (syntax != null) {
232 var delegateBinding = syntax.getBinding(model, path, name, node);
233 if (delegateBinding != null) {
234 model = delegateBinding;
235 path = 'value';
236 }
237 }
238
239 node.bind(name, model, path);
240 }
241
242 /**
243 * Gets the [node]'s custom [Element.xtag] if present, otherwise returns
244 * the node. This is used so nodes can override [Node.bind], [Node.unbind],
245 * and [Node.unbindAll] like InputElement does.
246 */
247 // TODO(jmesserly): remove this when we can extend Element for real.
248 static _nodeOrCustom(node) => node is Element ? node.xtag : node;
249
250 static List<_BindingToken> _parseMustacheTokens(String s) {
251 var result = [];
252 var length = s.length;
253 var index = 0, lastIndex = 0;
254 while (lastIndex < length) {
255 index = s.indexOf('{{', lastIndex);
256 if (index < 0) {
257 result.add(new _BindingToken(s.substring(lastIndex)));
258 break;
259 } else {
260 // There is a non-empty text run before the next path token.
261 if (index > 0 && lastIndex < index) {
262 result.add(new _BindingToken(s.substring(lastIndex, index)));
263 }
264 lastIndex = index + 2;
265 index = s.indexOf('}}', lastIndex);
266 if (index < 0) {
267 var text = s.substring(lastIndex - 2);
268 if (result.length > 0 && result.last.isText) {
269 result.last.value += text;
270 } else {
271 result.add(new _BindingToken(text));
272 }
273 break;
274 }
275
276 var value = s.substring(lastIndex, index).trim();
277 result.add(new _BindingToken(value, isBinding: true));
278 lastIndex = index + 2;
279 }
280 }
281 return result;
282 }
283
284 static void _addTemplateInstanceRecord(fragment, model) {
285 if (fragment.firstChild == null) {
286 return;
287 }
288
289 var instanceRecord = new TemplateInstance(
290 fragment.firstChild, fragment.lastChild, model);
291
292 var node = instanceRecord.firstNode;
293 while (node != null) {
294 _mdv(node)._templateInstance = instanceRecord;
295 node = node.nextNode;
296 }
297 } 295 }
298 } 296 }
299 297
300 class _BindingToken { 298 class _BindingToken {
301 final String value; 299 final String value;
302 final bool isBinding; 300 final bool isBinding;
303 301
304 _BindingToken(this.value, {this.isBinding: false}); 302 _BindingToken(this.value, {this.isBinding: false});
305 303
306 bool get isText => !isBinding; 304 bool get isText => !isBinding;
307 } 305 }
308 306
309 class _TemplateIterator { 307 class _TemplateIterator {
310 final Element _templateElement; 308 final Element _templateElement;
311 final List<Node> terminators = []; 309 final List<Node> terminators = [];
312 final CompoundBinding inputs; 310 final CompoundBinding inputs;
313 List iteratedValue; 311 List iteratedValue;
314 Object _lastValue; 312 Object _lastValue;
315 313
316 StreamSubscription _sub; 314 StreamSubscription _sub;
317 StreamSubscription _valueBinding; 315 StreamSubscription _valueBinding;
318 316
319 _TemplateIterator(this._templateElement) 317 _TemplateIterator(this._templateElement)
320 : inputs = new CompoundBinding(resolveInputs) { 318 : inputs = new CompoundBinding(resolveInputs) {
321 319
322 _valueBinding = new PathObserver(inputs, 'value').bindSync(valueChanged); 320 _valueBinding = new PathObserver(inputs, 'value').bindSync(valueChanged);
323 } 321 }
324 322
325 static Object resolveInputs(Map values) { 323 static Object resolveInputs(Map values) {
326 if (values.containsKey('if') && !_Bindings._toBoolean(values['if'])) { 324 if (values.containsKey('if') && !_toBoolean(values['if'])) {
327 return null; 325 return null;
328 } 326 }
329 327
330 if (values.containsKey('repeat')) { 328 if (values.containsKey('repeat')) {
331 return values['repeat']; 329 return values['repeat'];
332 } 330 }
333 331
334 if (values.containsKey('bind')) { 332 if (values.containsKey('bind')) {
335 return [values['bind']]; 333 return [values['bind']];
336 } 334 }
(...skipping 61 matching lines...) Expand 10 before | Expand all | Expand 10 after
398 var parent = _templateElement.parentNode; 396 var parent = _templateElement.parentNode;
399 while (terminator != previousTerminator) { 397 while (terminator != previousTerminator) {
400 var node = terminator; 398 var node = terminator;
401 terminator = node.previousNode; 399 terminator = node.previousNode;
402 node.remove(); 400 node.remove();
403 instanceNodes.add(node); 401 instanceNodes.add(node);
404 } 402 }
405 return instanceNodes; 403 return instanceNodes;
406 } 404 }
407 405
408 getInstanceModel(model, syntax) { 406 getInstanceModel(model, String syntax) {
409 if (syntax != null) { 407 var delegate = TemplateElement.syntax[syntax];
410 return syntax.getInstanceModel(_templateElement, model); 408 if (delegate != null) {
409 return delegate.getInstanceModel(_templateElement, model);
411 } 410 }
412 return model; 411 return model;
413 } 412 }
414 413
415 Node getInstanceFragment(syntax) { 414 List<Node> getInstanceNodes(model, String syntax, IdentityMap instanceCache) {
416 if (syntax != null) { 415 var instanceNodes = instanceCache.remove(model);
417 return syntax.getInstanceFragment(_templateElement); 416 if (instanceNodes != null) return instanceNodes;
418 }
419 return _templateElement.createInstance();
420 }
421 417
422 List<Node> getInstanceNodes(model, syntax) { 418 var fragment = _templateElement.createInstance(model, syntax);
423 // TODO(jmesserly): this line of code is jumping ahead of what MDV supports. 419 instanceNodes = fragment.nodes.toList();
424 // It doesn't let the custom syntax override createInstance().
425 var fragment = getInstanceFragment(syntax);
426
427 _Bindings._addBindings(fragment, model, syntax);
428 _Bindings._addTemplateInstanceRecord(fragment, model);
429
430 var instanceNodes = fragment.nodes.toList();
431 fragment.nodes.clear(); 420 fragment.nodes.clear();
432 return instanceNodes; 421 return instanceNodes;
433 } 422 }
434 423
435 void _handleChanges(Iterable<ChangeRecord> splices) { 424 void _handleChanges(Iterable<ChangeRecord> splices) {
436 splices = splices.where((s) => s is ListChangeRecord); 425 splices = splices.where((s) => s is ListChangeRecord);
437 426
438 var template = _templateElement; 427 var template = _templateElement;
439 var syntax = TemplateElement.syntax[template.attributes['syntax']]; 428 var syntax = template.attributes['syntax'];
440 429
441 if (template.parentNode == null || template.document.window == null) { 430 if (template.parentNode == null || template.document.window == null) {
442 abandon(); 431 abandon();
443 // TODO(jmesserly): MDV calls templateIteratorTable.delete(this) here, 432 // TODO(jmesserly): MDV calls templateIteratorTable.delete(this) here,
444 // but I think that's a no-op because only nodes are used as keys. 433 // but I think that's a no-op because only nodes are used as keys.
445 // See https://github.com/Polymer/mdv/pull/114. 434 // See https://github.com/Polymer/mdv/pull/114.
446 return; 435 return;
447 } 436 }
448 437
449 // TODO(jmesserly): IdentityMap matches JS semantics, but it's O(N) right 438 // TODO(jmesserly): IdentityMap matches JS semantics, but it's O(N) right
(...skipping 10 matching lines...) Expand all
460 removeDelta -= splice.addedCount; 449 removeDelta -= splice.addedCount;
461 } 450 }
462 451
463 for (var splice in splices) { 452 for (var splice in splices) {
464 for (var addIndex = splice.index; 453 for (var addIndex = splice.index;
465 addIndex < splice.index + splice.addedCount; 454 addIndex < splice.index + splice.addedCount;
466 addIndex++) { 455 addIndex++) {
467 456
468 var model = getInstanceModel(iteratedValue[addIndex], syntax); 457 var model = getInstanceModel(iteratedValue[addIndex], syntax);
469 458
470 var instanceNodes = instanceCache.remove(model); 459 var instanceNodes = getInstanceNodes(model, syntax, instanceCache);
471 if (instanceNodes == null) {
472 instanceNodes = getInstanceNodes(model, syntax);
473 }
474 insertInstanceAt(addIndex, instanceNodes); 460 insertInstanceAt(addIndex, instanceNodes);
475 } 461 }
476 } 462 }
477 463
478 for (var instanceNodes in instanceCache.values) { 464 for (var instanceNodes in instanceCache.values) {
479 instanceNodes.forEach(_unbindAllRecursively); 465 instanceNodes.forEach(_unbindAllRecursively);
480 } 466 }
481 } 467 }
482 468
483 void unobserve() { 469 void unobserve() {
(...skipping 14 matching lines...) Expand all
498 nodeExt._templateInstance = null; 484 nodeExt._templateInstance = null;
499 if (node is Element && (node as Element).isTemplate) { 485 if (node is Element && (node as Element).isTemplate) {
500 // Make sure we stop observing when we remove an element. 486 // Make sure we stop observing when we remove an element.
501 var templateIterator = nodeExt._templateIterator; 487 var templateIterator = nodeExt._templateIterator;
502 if (templateIterator != null) { 488 if (templateIterator != null) {
503 templateIterator.abandon(); 489 templateIterator.abandon();
504 nodeExt._templateIterator = null; 490 nodeExt._templateIterator = null;
505 } 491 }
506 } 492 }
507 493
508 _Bindings._nodeOrCustom(node).unbindAll(); 494 _nodeOrCustom(node).unbindAll();
509 for (var c = node.firstChild; c != null; c = c.nextNode) { 495 for (var c = node.firstChild; c != null; c = c.nextNode) {
510 _unbindAllRecursively(c); 496 _unbindAllRecursively(c);
511 } 497 }
512 } 498 }
513 } 499 }
OLDNEW
« no previous file with comments | « pkg/mdv/lib/mdv.dart ('k') | pkg/mdv/lib/src/element.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698