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

Side by Side Diff: sky/framework/sky-element/polymer-expressions.sky

Issue 698653002: Add initial SkyElement & city-list example (Closed) Base URL: https://github.com/domokit/mojo.git@master
Patch Set: moar cleanup Created 6 years, 1 month 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
OLDNEW
(Empty)
1 <!--
2 // Copyright 2014 The Chromium Authors. All rights reserved.
3 // Use of this source code is governed by a BSD-style license that can be
4 // found in the LICENSE file.
5 -->
6 <link rel="import" href="third_party/esprima/esprima.sky" as="esprima" />
7
8 <script>
9 function prepareBinding(expressionText, name, node, filterRegistry) {
10 var expression;
11 try {
12 expression = getExpression(expressionText);
13 if (expression.scopeIdent &&
14 (node.nodeType !== Node.ELEMENT_NODE ||
15 node.tagName !== 'TEMPLATE' ||
16 (name !== 'bind' && name !== 'repeat'))) {
17 throw Error('as and in can only be used within <template bind/repeat>');
18 }
19 } catch (ex) {
20 console.error('Invalid expression syntax: ' + expressionText, ex);
21 return;
22 }
23
24 return function(model, node, oneTime) {
25 var binding = expression.getBinding(model, filterRegistry, oneTime);
26 if (expression.scopeIdent && binding) {
27 node.polymerExpressionScopeIdent_ = expression.scopeIdent;
28 if (expression.indexIdent)
29 node.polymerExpressionIndexIdent_ = expression.indexIdent;
30 }
31
32 return binding;
33 }
34 }
35
36 // TODO(rafaelw): Implement simple LRU.
37 var expressionParseCache = Object.create(null);
38
39 function getExpression(expressionText) {
40 var expression = expressionParseCache[expressionText];
41 if (!expression) {
42 var delegate = new ASTDelegate();
43 esprima.parse(expressionText, delegate);
44 expression = new Expression(delegate);
45 expressionParseCache[expressionText] = expression;
46 }
47 return expression;
48 }
49
50 function Literal(value) {
51 this.value = value;
52 this.valueFn_ = undefined;
53 }
54
55 Literal.prototype = {
56 valueFn: function() {
57 if (!this.valueFn_) {
58 var value = this.value;
59 this.valueFn_ = function() {
60 return value;
61 }
62 }
63
64 return this.valueFn_;
65 }
66 }
67
68 function IdentPath(name) {
69 this.name = name;
70 this.path = Path.get(name);
71 }
72
73 IdentPath.prototype = {
74 valueFn: function() {
75 if (!this.valueFn_) {
76 var name = this.name;
77 var path = this.path;
78 this.valueFn_ = function(model, observer) {
79 if (observer)
80 observer.addPath(model, path);
81
82 return path.getValueFrom(model);
83 }
84 }
85
86 return this.valueFn_;
87 },
88
89 setValue: function(model, newValue) {
90 if (this.path.length == 1);
91 model = findScope(model, this.path[0]);
92
93 return this.path.setValueFrom(model, newValue);
94 }
95 };
96
97 function MemberExpression(object, property, accessor) {
98 this.computed = accessor == '[';
99
100 this.dynamicDeps = typeof object == 'function' ||
101 object.dynamicDeps ||
102 (this.computed && !(property instanceof Literal));
103
104 this.simplePath =
105 !this.dynamicDeps &&
106 (property instanceof IdentPath || property instanceof Literal) &&
107 (object instanceof MemberExpression || object instanceof IdentPath);
108
109 this.object = this.simplePath ? object : getFn(object);
110 this.property = !this.computed || this.simplePath ?
111 property : getFn(property);
112 }
113
114 MemberExpression.prototype = {
115 get fullPath() {
116 if (!this.fullPath_) {
117
118 var parts = this.object instanceof MemberExpression ?
119 this.object.fullPath.slice() : [this.object.name];
120 parts.push(this.property instanceof IdentPath ?
121 this.property.name : this.property.value);
122 this.fullPath_ = Path.get(parts);
123 }
124
125 return this.fullPath_;
126 },
127
128 valueFn: function() {
129 if (!this.valueFn_) {
130 var object = this.object;
131
132 if (this.simplePath) {
133 var path = this.fullPath;
134
135 this.valueFn_ = function(model, observer) {
136 if (observer)
137 observer.addPath(model, path);
138
139 return path.getValueFrom(model);
140 };
141 } else if (!this.computed) {
142 var path = Path.get(this.property.name);
143
144 this.valueFn_ = function(model, observer, filterRegistry) {
145 var context = object(model, observer, filterRegistry);
146
147 if (observer)
148 observer.addPath(context, path);
149
150 return path.getValueFrom(context);
151 }
152 } else {
153 // Computed property.
154 var property = this.property;
155
156 this.valueFn_ = function(model, observer, filterRegistry) {
157 var context = object(model, observer, filterRegistry);
158 var propName = property(model, observer, filterRegistry);
159 if (observer)
160 observer.addPath(context, [propName]);
161
162 return context ? context[propName] : undefined;
163 };
164 }
165 }
166 return this.valueFn_;
167 },
168
169 setValue: function(model, newValue) {
170 if (this.simplePath) {
171 this.fullPath.setValueFrom(model, newValue);
172 return newValue;
173 }
174
175 var object = this.object(model);
176 var propName = this.property instanceof IdentPath ? this.property.name :
177 this.property(model);
178 return object[propName] = newValue;
179 }
180 };
181
182 function Filter(name, args) {
183 this.name = name;
184 this.args = [];
185 for (var i = 0; i < args.length; i++) {
186 this.args[i] = getFn(args[i]);
187 }
188 }
189
190 Filter.prototype = {
191 transform: function(model, observer, filterRegistry, toModelDirection,
192 initialArgs) {
193 var fn = filterRegistry[this.name];
194 var context = model;
195 if (fn) {
196 context = undefined;
197 } else {
198 fn = context[this.name];
199 if (!fn) {
200 console.error('Cannot find function or filter: ' + this.name);
201 return;
202 }
203 }
204
205 // If toModelDirection is falsey, then the "normal" (dom-bound) direction
206 // is used. Otherwise, it looks for a 'toModel' property function on the
207 // object.
208 if (toModelDirection) {
209 fn = fn.toModel;
210 } else if (typeof fn.toDOM == 'function') {
211 fn = fn.toDOM;
212 }
213
214 if (typeof fn != 'function') {
215 console.error('Cannot find function or filter: ' + this.name);
216 return;
217 }
218
219 var args = initialArgs || [];
220 for (var i = 0; i < this.args.length; i++) {
221 args.push(getFn(this.args[i])(model, observer, filterRegistry));
222 }
223
224 return fn.apply(context, args);
225 }
226 };
227
228 function notImplemented() { throw Error('Not Implemented'); }
229
230 var unaryOperators = {
231 '+': function(v) { return +v; },
232 '-': function(v) { return -v; },
233 '!': function(v) { return !v; }
234 };
235
236 var binaryOperators = {
237 '+': function(l, r) { return l+r; },
238 '-': function(l, r) { return l-r; },
239 '*': function(l, r) { return l*r; },
240 '/': function(l, r) { return l/r; },
241 '%': function(l, r) { return l%r; },
242 '<': function(l, r) { return l<r; },
243 '>': function(l, r) { return l>r; },
244 '<=': function(l, r) { return l<=r; },
245 '>=': function(l, r) { return l>=r; },
246 '==': function(l, r) { return l==r; },
247 '!=': function(l, r) { return l!=r; },
248 '===': function(l, r) { return l===r; },
249 '!==': function(l, r) { return l!==r; },
250 '&&': function(l, r) { return l&&r; },
251 '||': function(l, r) { return l||r; },
252 };
253
254 function getFn(arg) {
255 return typeof arg == 'function' ? arg : arg.valueFn();
256 }
257
258 function ASTDelegate() {
259 this.expression = null;
260 this.filters = [];
261 this.deps = {};
262 this.currentPath = undefined;
263 this.scopeIdent = undefined;
264 this.indexIdent = undefined;
265 this.dynamicDeps = false;
266 }
267
268 ASTDelegate.prototype = {
269 createUnaryExpression: function(op, argument) {
270 if (!unaryOperators[op])
271 throw Error('Disallowed operator: ' + op);
272
273 argument = getFn(argument);
274
275 return function(model, observer, filterRegistry) {
276 return unaryOperators[op](argument(model, observer, filterRegistry));
277 };
278 },
279
280 createBinaryExpression: function(op, left, right) {
281 if (!binaryOperators[op])
282 throw Error('Disallowed operator: ' + op);
283
284 left = getFn(left);
285 right = getFn(right);
286
287 switch (op) {
288 case '||':
289 this.dynamicDeps = true;
290 return function(model, observer, filterRegistry) {
291 return left(model, observer, filterRegistry) ||
292 right(model, observer, filterRegistry);
293 };
294 case '&&':
295 this.dynamicDeps = true;
296 return function(model, observer, filterRegistry) {
297 return left(model, observer, filterRegistry) &&
298 right(model, observer, filterRegistry);
299 };
300 }
301
302 return function(model, observer, filterRegistry) {
303 return binaryOperators[op](left(model, observer, filterRegistry),
304 right(model, observer, filterRegistry));
305 };
306 },
307
308 createConditionalExpression: function(test, consequent, alternate) {
309 test = getFn(test);
310 consequent = getFn(consequent);
311 alternate = getFn(alternate);
312
313 this.dynamicDeps = true;
314
315 return function(model, observer, filterRegistry) {
316 return test(model, observer, filterRegistry) ?
317 consequent(model, observer, filterRegistry) :
318 alternate(model, observer, filterRegistry);
319 }
320 },
321
322 createIdentifier: function(name) {
323 var ident = new IdentPath(name);
324 ident.type = 'Identifier';
325 return ident;
326 },
327
328 createMemberExpression: function(accessor, object, property) {
329 var ex = new MemberExpression(object, property, accessor);
330 if (ex.dynamicDeps)
331 this.dynamicDeps = true;
332 return ex;
333 },
334
335 createCallExpression: function(expression, args) {
336 if (!(expression instanceof IdentPath))
337 throw Error('Only identifier function invocations are allowed');
338
339 var filter = new Filter(expression.name, args);
340
341 return function(model, observer, filterRegistry) {
342 return filter.transform(model, observer, filterRegistry, false);
343 };
344 },
345
346 createLiteral: function(token) {
347 return new Literal(token.value);
348 },
349
350 createArrayExpression: function(elements) {
351 for (var i = 0; i < elements.length; i++)
352 elements[i] = getFn(elements[i]);
353
354 return function(model, observer, filterRegistry) {
355 var arr = []
356 for (var i = 0; i < elements.length; i++)
357 arr.push(elements[i](model, observer, filterRegistry));
358 return arr;
359 }
360 },
361
362 createProperty: function(kind, key, value) {
363 return {
364 key: key instanceof IdentPath ? key.name : key.value,
365 value: value
366 };
367 },
368
369 createObjectExpression: function(properties) {
370 for (var i = 0; i < properties.length; i++)
371 properties[i].value = getFn(properties[i].value);
372
373 return function(model, observer, filterRegistry) {
374 var obj = {};
375 for (var i = 0; i < properties.length; i++)
376 obj[properties[i].key] =
377 properties[i].value(model, observer, filterRegistry);
378 return obj;
379 }
380 },
381
382 createFilter: function(name, args) {
383 this.filters.push(new Filter(name, args));
384 },
385
386 createAsExpression: function(expression, scopeIdent) {
387 this.expression = expression;
388 this.scopeIdent = scopeIdent;
389 },
390
391 createInExpression: function(scopeIdent, indexIdent, expression) {
392 this.expression = expression;
393 this.scopeIdent = scopeIdent;
394 this.indexIdent = indexIdent;
395 },
396
397 createTopLevel: function(expression) {
398 this.expression = expression;
399 },
400
401 createThisExpression: notImplemented
402 }
403
404 function ConstantObservable(value) {
405 this.value_ = value;
406 }
407
408 ConstantObservable.prototype = {
409 open: function() { return this.value_; },
410 discardChanges: function() { return this.value_; },
411 deliver: function() {},
412 close: function() {},
413 }
414
415 function Expression(delegate) {
416 this.scopeIdent = delegate.scopeIdent;
417 this.indexIdent = delegate.indexIdent;
418
419 if (!delegate.expression)
420 throw Error('No expression found.');
421
422 this.expression = delegate.expression;
423 getFn(this.expression); // forces enumeration of path dependencies
424
425 this.filters = delegate.filters;
426 this.dynamicDeps = delegate.dynamicDeps;
427 }
428
429 Expression.prototype = {
430 getBinding: function(model, filterRegistry, oneTime) {
431 if (oneTime)
432 return this.getValue(model, undefined, filterRegistry);
433
434 var observer = new CompoundObserver();
435 // captures deps.
436 var firstValue = this.getValue(model, observer, filterRegistry);
437 var firstTime = true;
438 var self = this;
439
440 function valueFn() {
441 // deps cannot have changed on first value retrieval.
442 if (firstTime) {
443 firstTime = false;
444 return firstValue;
445 }
446
447 if (self.dynamicDeps)
448 observer.startReset();
449
450 var value = self.getValue(model,
451 self.dynamicDeps ? observer : undefined,
452 filterRegistry);
453 if (self.dynamicDeps)
454 observer.finishReset();
455
456 return value;
457 }
458
459 function setValueFn(newValue) {
460 self.setValue(model, newValue, filterRegistry);
461 return newValue;
462 }
463
464 return new ObserverTransform(observer, valueFn, setValueFn, true);
465 },
466
467 getValue: function(model, observer, filterRegistry) {
468 var value = getFn(this.expression)(model, observer, filterRegistry);
469 for (var i = 0; i < this.filters.length; i++) {
470 value = this.filters[i].transform(model, observer, filterRegistry,
471 false, [value]);
472 }
473
474 return value;
475 },
476
477 setValue: function(model, newValue, filterRegistry) {
478 var count = this.filters ? this.filters.length : 0;
479 while (count-- > 0) {
480 newValue = this.filters[count].transform(model, undefined,
481 filterRegistry, true, [newValue]);
482 }
483
484 if (this.expression.setValue)
485 return this.expression.setValue(model, newValue);
486 }
487 }
488
489 /**
490 * Converts a style property name to a css property name. For example:
491 * "WebkitUserSelect" to "-webkit-user-select"
492 */
493 function convertStylePropertyName(name) {
494 return String(name).replace(/[A-Z]/g, function(c) {
495 return '-' + c.toLowerCase();
496 });
497 }
498
499 var parentScopeName = '@' + Math.random().toString(36).slice(2);
500
501 // Single ident paths must bind directly to the appropriate scope object.
502 // I.e. Pushed values in two-bindings need to be assigned to the actual model
503 // object.
504 function findScope(model, prop) {
505 while (model[parentScopeName] &&
506 !Object.prototype.hasOwnProperty.call(model, prop)) {
507 model = model[parentScopeName];
508 }
509
510 return model;
511 }
512
513 function isLiteralExpression(pathString) {
514 switch (pathString) {
515 case '':
516 return false;
517
518 case 'false':
519 case 'null':
520 case 'true':
521 return true;
522 }
523
524 if (!isNaN(Number(pathString)))
525 return true;
526
527 return false;
528 };
529
530 function PolymerExpressions() {}
531
532 PolymerExpressions.prototype = {
533 // "built-in" filters
534 styleObject: function(value) {
535 var parts = [];
536 for (var key in value) {
537 parts.push(convertStylePropertyName(key) + ': ' + value[key]);
538 }
539 return parts.join('; ');
540 },
541
542 tokenList: function(value) {
543 var tokens = [];
544 for (var key in value) {
545 if (value[key])
546 tokens.push(key);
547 }
548 return tokens.join(' ');
549 },
550
551 // binding delegate API
552 prepareInstancePositionChanged: function(template) {
553 var indexIdent = template.polymerExpressionIndexIdent_;
554 if (!indexIdent)
555 return;
556
557 return function(templateInstance, index) {
558 templateInstance.model[indexIdent] = index;
559 };
560 },
561
562 prepareBinding: function(pathString, name, node) {
563 var path = Path.get(pathString);
564
565 if (!isLiteralExpression(pathString) && path.valid) {
566 if (path.length == 1) {
567 return function(model, node, oneTime) {
568 if (oneTime)
569 return path.getValueFrom(model);
570
571 var scope = findScope(model, path[0]);
572 return new PathObserver(scope, path);
573 };
574 }
575 return; // bail out early if pathString is simple path.
576 }
577
578 return prepareBinding(pathString, name, node, this);
579 },
580
581 prepareInstanceModel: function(template) {
582 var scopeName = template.polymerExpressionScopeIdent_;
583 if (!scopeName)
584 return;
585
586 var parentScope = template.templateInstance ?
587 template.templateInstance.model :
588 template.model;
589
590 var indexName = template.polymerExpressionIndexIdent_;
591
592 return function(model) {
593 return createScopeObject(parentScope, model, scopeName, indexName);
594 };
595 }
596 };
597
598 var createScopeObject = ('__proto__' in {}) ?
599 function(parentScope, model, scopeName, indexName) {
600 var scope = {};
601 scope[scopeName] = model;
602 scope[indexName] = undefined;
603 scope[parentScopeName] = parentScope;
604 scope.__proto__ = parentScope;
605 return scope;
606 } :
607 function(parentScope, model, scopeName, indexName) {
608 var scope = Object.create(parentScope);
609 Object.defineProperty(scope, scopeName,
610 { value: model, configurable: true, writable: true });
611 Object.defineProperty(scope, indexName,
612 { value: undefined, configurable: true, writable: true });
613 Object.defineProperty(scope, parentScopeName,
614 { value: parentScope, configurable: true, writable: true });
615 return scope;
616 };
617
618 PolymerExpressions.getExpression = getExpression;
619
620 module.exports = PolymerExpressions;
621
622 </script>
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698