OLD | NEW |
(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> |
OLD | NEW |