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

Side by Side Diff: pkg/polymer_expressions/lib/polymer_expressions.dart

Issue 141703024: Refactor of PolymerExpressions. Adds "as" expressions. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Reviewable state. More readable tests. 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
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 /** 5 /**
6 * A binding delegate used with Polymer elements that 6 * A binding delegate used with Polymer elements that
7 * allows for complex binding expressions, including 7 * allows for complex binding expressions, including
8 * property access, function invocation, 8 * property access, function invocation,
9 * list/map indexing, and two-way filtering. 9 * list/map indexing, and two-way filtering.
10 * 10 *
(...skipping 11 matching lines...) Expand all
22 * The 22 * The
23 * [Polymer expressions](http://pub.dartlang.org/packages/polymer_expressions) 23 * [Polymer expressions](http://pub.dartlang.org/packages/polymer_expressions)
24 * pub repository contains detailed documentation about using polymer 24 * pub repository contains detailed documentation about using polymer
25 * expressions. 25 * expressions.
26 */ 26 */
27 27
28 library polymer_expressions; 28 library polymer_expressions;
29 29
30 import 'dart:async'; 30 import 'dart:async';
31 import 'dart:html'; 31 import 'dart:html';
32 import 'dart:collection' show HashMap;
32 33
33 import 'package:logging/logging.dart'; 34 import 'package:logging/logging.dart';
34 import 'package:observe/observe.dart'; 35 import 'package:observe/observe.dart';
35 import 'package:template_binding/template_binding.dart'; 36 import 'package:template_binding/template_binding.dart';
36 37
37 import 'eval.dart'; 38 import 'eval.dart';
38 import 'expression.dart'; 39 import 'expression.dart';
39 import 'parser.dart'; 40 import 'parser.dart';
40 import 'src/globals.dart'; 41 import 'src/globals.dart';
41 42
42 final Logger _logger = new Logger('polymer_expressions'); 43 final Logger _logger = new Logger('polymer_expressions');
43 44
44 // TODO(justin): Investigate XSS protection
45 Object _classAttributeConverter(v) =>
46 (v is Map) ? v.keys.where((k) => v[k] == true).join(' ') :
47 (v is Iterable) ? v.join(' ') :
48 v;
49
50 Object _styleAttributeConverter(v) =>
51 (v is Map) ? v.keys.map((k) => '$k: ${v[k]}').join(';') :
52 (v is Iterable) ? v.join(';') :
53 v;
54
55 class PolymerExpressions extends BindingDelegate { 45 class PolymerExpressions extends BindingDelegate {
56 /** The default [globals] to use for Polymer expressions. */ 46 /** The default [globals] to use for Polymer expressions. */
57 static const Map DEFAULT_GLOBALS = const { 'enumerate': enumerate }; 47 static const Map DEFAULT_GLOBALS = const { 'enumerate': enumerate };
58 48
59 final Map<String, Object> globals; 49 final Map<String, Object> globals;
60 50
61 /** 51 /**
62 * Creates a new binding delegate for Polymer expressions, with the provided 52 * Creates a new binding delegate for Polymer expressions, with the provided
63 * variables used as [globals]. If no globals are supplied, a copy of the 53 * variables used as [globals]. If no globals are supplied, a copy of the
64 * [DEFAULT_GLOBALS] will be used. 54 * [DEFAULT_GLOBALS] will be used.
65 */ 55 */
66 PolymerExpressions({Map<String, Object> globals}) 56 PolymerExpressions({Map<String, Object> globals})
67 : globals = (globals == null) ? 57 : globals = (globals == null) ?
68 new Map<String, Object>.from(DEFAULT_GLOBALS) : globals; 58 new Map<String, Object>.from(DEFAULT_GLOBALS) : globals;
69 59
70 prepareBinding(String path, name, node) { 60 prepareBinding(String path, name, boundNode) {
71 if (path == null) return null; 61 if (path == null) return null;
72 var expr = new Parser(path).parse(); 62 var expr = new Parser(path).parse();
73 63
74 // For template bind/repeat to an empty path, just pass through the model. 64 if (isSemanticTemplate(boundNode)) {
75 // We don't want to unwrap the Scope. 65 // <template bind> expressions don't pass through prepareInstanceModel
Jennifer Messerly 2014/01/31 02:48:50 interesting. I wonder if it is intended, or happen
justinfagnani 2014/01/31 21:02:07 not sure, but I'm finding that I have to do this s
justinfagnani 2014/03/12 23:21:30 What's really happening here is that if you set th
76 // TODO(jmesserly): a custom element extending <template> could notice this 66 // first so we have to ensure the model is wrapped in a Scope.
77 // behavior. An alternative is to associate the Scope with the node via an 67 // prepareInstanceModel
78 // Expando, which is what the JavaScript PolymerExpressions does. 68 var wrapModel = prepareInstanceModel(boundNode);
79 if (isSemanticTemplate(node) && (name == 'bind' || name == 'repeat') && 69 if (name == 'bind') {
80 expr is EmptyExpression) { 70 if (expr is AsExpression) {
81 return null; 71 var identifier = expr.right.value;
72 var bindExpr = expr.left;
73 return (model, node) {
74 var scope = wrapModel(model);
Jennifer Messerly 2014/01/31 02:48:50 silly comment: i'd probably just fold this into th
justinfagnani 2014/03/12 23:21:30 obsolete
75 return new _AsBinding(bindExpr, identifier, scope);
76 };
77 }
78 return (model, node) {
79 var scope = wrapModel(model);
80 return new _Binding(expr, scope);
81 };
82 } else if (name == 'repeat' && expr is InExpression) {
83 var identifier = expr.left.value;
84 var iterableExpr = expr.right;
85 return (scope, node) => new _InBinding(iterableExpr, identifier, scope);
86 }
82 } 87 }
83 88
84 return (model, node) { 89 if ((boundNode is Element && name == 'class')) {
Jennifer Messerly 2014/01/31 02:48:50 nit: extra parens
justinfagnani 2014/03/12 23:21:30 Done.
85 if (model is! Scope) { 90 return (scope, node) => new _ClassBinding(expr, scope);
86 model = new Scope(model: model, variables: globals); 91 } else if ((boundNode is Element && name == 'style')) {
Jennifer Messerly 2014/01/31 02:48:50 here too
justinfagnani 2014/03/12 23:21:30 Done.
87 } 92 return (scope, node) => new _StyleBinding(expr, scope);
88 if (node is Element && name == "class") { 93 } else {
89 return new _Binding(expr, model, _classAttributeConverter); 94 return (scope, node) => new _Binding(expr, scope);
90 } 95 }
91 if (node is Element && name == "style") { 96 }
92 return new _Binding(expr, model, _styleAttributeConverter); 97
93 } 98 prepareInstanceModel(Element template) {
94 return new _Binding(expr, model); 99 var templateInstance = templateBind(template).templateInstance;
Jennifer Messerly 2014/01/31 02:48:50 maybe a shorter variable name here?
justinfagnani 2014/03/12 23:21:30 obsolete
100 Scope parentScope = (templateInstance != null) ? templateInstance.model : nu ll;
Jennifer Messerly 2014/01/31 02:48:50 long line
justinfagnani 2014/03/12 23:21:30 Done.
101 var variables = (parentScope == null) ? globals : null;
102 return (model) {
103 return model is Scope ? model : new Scope(parent: parentScope, model: mode l, variables: variables);
Jennifer Messerly 2014/01/31 02:48:50 long line
justinfagnani 2014/03/12 23:21:30 Done.
95 }; 104 };
96 } 105 }
97
98 prepareInstanceModel(Element template) => (model) =>
99 model is Scope ? model : new Scope(model: model, variables: globals);
100 } 106 }
101 107
102 class _Binding extends ChangeNotifier { 108 class _Binding extends ChangeNotifier {
103 final Scope _scope; 109 final Scope _scope;
104 final ExpressionObserver _expr; 110 final ExpressionObserver _expr;
105 final _converter;
106 var _value; 111 var _value;
107 112
108 _Binding(Expression expr, Scope scope, [this._converter]) 113 _Binding(Expression expr, Scope scope)
109 : _expr = observe(expr, scope), 114 : _expr = observe(expr, scope),
110 _scope = scope { 115 _scope = scope {
111 _expr.onUpdate.listen(_setValue).onError((e) { 116 _expr.onUpdate.listen(_updateValue).onError((e) {
112 _logger.warning("Error evaluating expression '$_expr': ${e.message}"); 117 _logger.warning("Error evaluating expression '$_expr': ${e.message}");
113 }); 118 });
114 try { 119 try {
115 update(_expr, _scope); 120 update(_expr, _scope);
116 _setValue(_expr.currentValue); 121 _updateValue(_expr.currentValue);
117 } on EvalException catch (e) { 122 } on EvalException catch (e) {
118 _logger.warning("Error evaluating expression '$_expr': ${e.message}"); 123 _logger.warning("Error evaluating expression '$_expr': ${e.message}");
119 } 124 }
120 } 125 }
121 126
122 _setValue(v) { 127 _updateValue(newValue) {
123 var oldValue = _value; 128 _value = notifyPropertyChange(#value, _value, newValue);
124 if (v is Comprehension) {
125 // convert the Comprehension into a list of scopes with the loop
126 // variable added to the scope
127 _value = v.iterable.map((i) {
128 var vars = new Map();
129 vars[v.identifier] = i;
130 Scope childScope = new Scope(parent: _scope, variables: vars);
131 return childScope;
132 }).toList(growable: false);
133 } else {
134 _value = (_converter == null) ? v : _converter(v);
135 }
136 notifyPropertyChange(#value, oldValue, _value);
137 } 129 }
138 130
139 @reflectable get value => _value; 131 @reflectable get value => _value;
140 132
141 @reflectable set value(v) { 133 @reflectable set value(v) {
142 try { 134 try {
143 assign(_expr, v, _scope); 135 assign(_expr, v, _scope);
144 } on EvalException catch (e) { 136 } on EvalException catch (e) {
145 _logger.warning("Error evaluating expression '$_expr': ${e.message}"); 137 _logger.warning("Error evaluating expression '$_expr': ${e.message}");
146 } 138 }
147 } 139 }
148 } 140 }
141
142 class _ClassBinding extends _Binding {
Jennifer Messerly 2014/01/31 02:48:50 hmmm. these will all change a bit after https://co
justinfagnani 2014/03/12 23:21:30 I ended up not needing AsBinding and the static on
143 _ClassBinding(Expression expr, Scope scope) : super(expr, scope);
144
145 _updateValue(v) {
146 // TODO(justinfagnani): Investigate XSS protection
147 // TODO(justinfagnani): observe collection changes
148 var newValue =
149 (v is Map) ? v.keys.where((k) => v[k] == true).join(' ') :
150 (v is Iterable) ? v.join(' ') :
151 v;
152 _value = notifyPropertyChange(#value, _value, newValue);
153 }
154 }
155
156 class _StyleBinding extends _Binding {
157 _StyleBinding(Expression expr, Scope scope) : super(expr, scope);
158
159 _updateValue(v) {
160 // TODO(justinfagnani): observe collection changes
161 var newValue =
162 (v is Map) ? v.keys.map((k) => '$k: ${v[k]}').join(';') :
163 (v is Iterable) ? v.join(';') :
164 v;
165 _value = notifyPropertyChange(#value, _value, newValue);
166 }
167 }
168
169 class _AsBinding extends _Binding {
170 final String _identifier;
171
172 _AsBinding(Expression expr, this._identifier, Scope scope)
173 : super(expr, scope);
174
175 _updateValue(v) {
176 // what does "this" in template bind="..as.." eval to?
177 _value = new Scope(parent: _scope, variables: {_identifier: v});
178 }
179 }
180
181 /*
182 * A binding for a repeat="a in b" expression. In addition to the right-side of
183 * the expression, we store the indentifier that should be introduced in child
184 * scopes, and we observe observable lists, modifying the list of child scopes
185 * according to the input data's changes.
186 */
187 class _InBinding extends _Binding {
188 final String _identifier;
189 StreamSubscription _subscription;
190 ObservableList<Scope> _scopes;
191
192 _InBinding(Expression expr, this._identifier, Scope scope)
193 : super(expr, scope);
194
195 _updateValue(v) {
196 assert(v is Iterable || v == null);
197
198 _makeScope(value) =>
199 new Scope(parent: _scope, variables: {_identifier: value});
200
201 if (_subscription != null) {
202 _subscription.cancel();
203 _subscription = null;
204 }
205
206 if (v is ObservableList) {
207 _subscription = v.listChanges.listen((List<ListChangeRecord> changes) {
208 for (var change in changes) {
209 var start = change.index;
210 var length = change.removed.length;
211 var newItems = change.object.sublist(start, start + length);
212 var newScopes = newItems.map(_makeScope);
213 _scopes.replaceRange(change.index, change.removed.length, newScopes);
214 }
215 });
216 }
217
218 var newValue = v == null ? null : new ObservableList.from(v.map(_makeScope)) ;
Jennifer Messerly 2014/01/31 02:48:50 long line. also, could this be just: var newValu
justinfagnani 2014/03/12 23:21:30 obsolete
219 _value = notifyPropertyChange(#value, _value, newValue);
220 }
221 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698