| OLD | NEW |
| (Empty) |
| 1 /** | |
| 2 * @license | |
| 3 * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. | |
| 4 * This code may only be used under the BSD style license found at http://polyme
r.github.io/LICENSE.txt | |
| 5 * The complete set of authors may be found at http://polymer.github.io/AUTHORS.
txt | |
| 6 * The complete set of contributors may be found at http://polymer.github.io/CON
TRIBUTORS.txt | |
| 7 * Code distributed by Google as part of the polymer project is also | |
| 8 * subject to an additional IP rights grant found at http://polymer.github.io/PA
TENTS.txt | |
| 9 */ | |
| 10 // @version 0.5.4 | |
| 11 window.PolymerGestures = {}; | |
| 12 | |
| 13 (function(scope) { | |
| 14 var hasFullPath = false; | |
| 15 | |
| 16 // test for full event path support | |
| 17 var pathTest = document.createElement('meta'); | |
| 18 if (pathTest.createShadowRoot) { | |
| 19 var sr = pathTest.createShadowRoot(); | |
| 20 var s = document.createElement('span'); | |
| 21 sr.appendChild(s); | |
| 22 pathTest.addEventListener('testpath', function(ev) { | |
| 23 if (ev.path) { | |
| 24 // if the span is in the event path, then path[0] is the real source for
all events | |
| 25 hasFullPath = ev.path[0] === s; | |
| 26 } | |
| 27 ev.stopPropagation(); | |
| 28 }); | |
| 29 var ev = new CustomEvent('testpath', {bubbles: true}); | |
| 30 // must add node to DOM to trigger event listener | |
| 31 document.head.appendChild(pathTest); | |
| 32 s.dispatchEvent(ev); | |
| 33 pathTest.parentNode.removeChild(pathTest); | |
| 34 sr = s = null; | |
| 35 } | |
| 36 pathTest = null; | |
| 37 | |
| 38 var target = { | |
| 39 shadow: function(inEl) { | |
| 40 if (inEl) { | |
| 41 return inEl.shadowRoot || inEl.webkitShadowRoot; | |
| 42 } | |
| 43 }, | |
| 44 canTarget: function(shadow) { | |
| 45 return shadow && Boolean(shadow.elementFromPoint); | |
| 46 }, | |
| 47 targetingShadow: function(inEl) { | |
| 48 var s = this.shadow(inEl); | |
| 49 if (this.canTarget(s)) { | |
| 50 return s; | |
| 51 } | |
| 52 }, | |
| 53 olderShadow: function(shadow) { | |
| 54 var os = shadow.olderShadowRoot; | |
| 55 if (!os) { | |
| 56 var se = shadow.querySelector('shadow'); | |
| 57 if (se) { | |
| 58 os = se.olderShadowRoot; | |
| 59 } | |
| 60 } | |
| 61 return os; | |
| 62 }, | |
| 63 allShadows: function(element) { | |
| 64 var shadows = [], s = this.shadow(element); | |
| 65 while(s) { | |
| 66 shadows.push(s); | |
| 67 s = this.olderShadow(s); | |
| 68 } | |
| 69 return shadows; | |
| 70 }, | |
| 71 searchRoot: function(inRoot, x, y) { | |
| 72 var t, st, sr, os; | |
| 73 if (inRoot) { | |
| 74 t = inRoot.elementFromPoint(x, y); | |
| 75 if (t) { | |
| 76 // found element, check if it has a ShadowRoot | |
| 77 sr = this.targetingShadow(t); | |
| 78 } else if (inRoot !== document) { | |
| 79 // check for sibling roots | |
| 80 sr = this.olderShadow(inRoot); | |
| 81 } | |
| 82 // search other roots, fall back to light dom element | |
| 83 return this.searchRoot(sr, x, y) || t; | |
| 84 } | |
| 85 }, | |
| 86 owner: function(element) { | |
| 87 if (!element) { | |
| 88 return document; | |
| 89 } | |
| 90 var s = element; | |
| 91 // walk up until you hit the shadow root or document | |
| 92 while (s.parentNode) { | |
| 93 s = s.parentNode; | |
| 94 } | |
| 95 // the owner element is expected to be a Document or ShadowRoot | |
| 96 if (s.nodeType != Node.DOCUMENT_NODE && s.nodeType != Node.DOCUMENT_FRAGME
NT_NODE) { | |
| 97 s = document; | |
| 98 } | |
| 99 return s; | |
| 100 }, | |
| 101 findTarget: function(inEvent) { | |
| 102 if (hasFullPath && inEvent.path && inEvent.path.length) { | |
| 103 return inEvent.path[0]; | |
| 104 } | |
| 105 var x = inEvent.clientX, y = inEvent.clientY; | |
| 106 // if the listener is in the shadow root, it is much faster to start there | |
| 107 var s = this.owner(inEvent.target); | |
| 108 // if x, y is not in this root, fall back to document search | |
| 109 if (!s.elementFromPoint(x, y)) { | |
| 110 s = document; | |
| 111 } | |
| 112 return this.searchRoot(s, x, y); | |
| 113 }, | |
| 114 findTouchAction: function(inEvent) { | |
| 115 var n; | |
| 116 if (hasFullPath && inEvent.path && inEvent.path.length) { | |
| 117 var path = inEvent.path; | |
| 118 for (var i = 0; i < path.length; i++) { | |
| 119 n = path[i]; | |
| 120 if (n.nodeType === Node.ELEMENT_NODE && n.hasAttribute('touch-action')
) { | |
| 121 return n.getAttribute('touch-action'); | |
| 122 } | |
| 123 } | |
| 124 } else { | |
| 125 n = inEvent.target; | |
| 126 while(n) { | |
| 127 if (n.nodeType === Node.ELEMENT_NODE && n.hasAttribute('touch-action')
) { | |
| 128 return n.getAttribute('touch-action'); | |
| 129 } | |
| 130 n = n.parentNode || n.host; | |
| 131 } | |
| 132 } | |
| 133 // auto is default | |
| 134 return "auto"; | |
| 135 }, | |
| 136 LCA: function(a, b) { | |
| 137 if (a === b) { | |
| 138 return a; | |
| 139 } | |
| 140 if (a && !b) { | |
| 141 return a; | |
| 142 } | |
| 143 if (b && !a) { | |
| 144 return b; | |
| 145 } | |
| 146 if (!b && !a) { | |
| 147 return document; | |
| 148 } | |
| 149 // fast case, a is a direct descendant of b or vice versa | |
| 150 if (a.contains && a.contains(b)) { | |
| 151 return a; | |
| 152 } | |
| 153 if (b.contains && b.contains(a)) { | |
| 154 return b; | |
| 155 } | |
| 156 var adepth = this.depth(a); | |
| 157 var bdepth = this.depth(b); | |
| 158 var d = adepth - bdepth; | |
| 159 if (d >= 0) { | |
| 160 a = this.walk(a, d); | |
| 161 } else { | |
| 162 b = this.walk(b, -d); | |
| 163 } | |
| 164 while (a && b && a !== b) { | |
| 165 a = a.parentNode || a.host; | |
| 166 b = b.parentNode || b.host; | |
| 167 } | |
| 168 return a; | |
| 169 }, | |
| 170 walk: function(n, u) { | |
| 171 for (var i = 0; n && (i < u); i++) { | |
| 172 n = n.parentNode || n.host; | |
| 173 } | |
| 174 return n; | |
| 175 }, | |
| 176 depth: function(n) { | |
| 177 var d = 0; | |
| 178 while(n) { | |
| 179 d++; | |
| 180 n = n.parentNode || n.host; | |
| 181 } | |
| 182 return d; | |
| 183 }, | |
| 184 deepContains: function(a, b) { | |
| 185 var common = this.LCA(a, b); | |
| 186 // if a is the common ancestor, it must "deeply" contain b | |
| 187 return common === a; | |
| 188 }, | |
| 189 insideNode: function(node, x, y) { | |
| 190 var rect = node.getBoundingClientRect(); | |
| 191 return (rect.left <= x) && (x <= rect.right) && (rect.top <= y) && (y <= r
ect.bottom); | |
| 192 }, | |
| 193 path: function(event) { | |
| 194 var p; | |
| 195 if (hasFullPath && event.path && event.path.length) { | |
| 196 p = event.path; | |
| 197 } else { | |
| 198 p = []; | |
| 199 var n = this.findTarget(event); | |
| 200 while (n) { | |
| 201 p.push(n); | |
| 202 n = n.parentNode || n.host; | |
| 203 } | |
| 204 } | |
| 205 return p; | |
| 206 } | |
| 207 }; | |
| 208 scope.targetFinding = target; | |
| 209 /** | |
| 210 * Given an event, finds the "deepest" node that could have been the original
target before ShadowDOM retargetting | |
| 211 * | |
| 212 * @param {Event} Event An event object with clientX and clientY properties | |
| 213 * @return {Element} The probable event origninator | |
| 214 */ | |
| 215 scope.findTarget = target.findTarget.bind(target); | |
| 216 /** | |
| 217 * Determines if the "container" node deeply contains the "containee" node, in
cluding situations where the "containee" is contained by one or more ShadowDOM | |
| 218 * roots. | |
| 219 * | |
| 220 * @param {Node} container | |
| 221 * @param {Node} containee | |
| 222 * @return {Boolean} | |
| 223 */ | |
| 224 scope.deepContains = target.deepContains.bind(target); | |
| 225 | |
| 226 /** | |
| 227 * Determines if the x/y position is inside the given node. | |
| 228 * | |
| 229 * Example: | |
| 230 * | |
| 231 * function upHandler(event) { | |
| 232 * var innode = PolymerGestures.insideNode(event.target, event.clientX,
event.clientY); | |
| 233 * if (innode) { | |
| 234 * // wait for tap? | |
| 235 * } else { | |
| 236 * // tap will never happen | |
| 237 * } | |
| 238 * } | |
| 239 * | |
| 240 * @param {Node} node | |
| 241 * @param {Number} x Screen X position | |
| 242 * @param {Number} y screen Y position | |
| 243 * @return {Boolean} | |
| 244 */ | |
| 245 scope.insideNode = target.insideNode; | |
| 246 | |
| 247 })(window.PolymerGestures); | |
| 248 | |
| 249 (function() { | |
| 250 function shadowSelector(v) { | |
| 251 return 'html /deep/ ' + selector(v); | |
| 252 } | |
| 253 function selector(v) { | |
| 254 return '[touch-action="' + v + '"]'; | |
| 255 } | |
| 256 function rule(v) { | |
| 257 return '{ -ms-touch-action: ' + v + '; touch-action: ' + v + ';}'; | |
| 258 } | |
| 259 var attrib2css = [ | |
| 260 'none', | |
| 261 'auto', | |
| 262 'pan-x', | |
| 263 'pan-y', | |
| 264 { | |
| 265 rule: 'pan-x pan-y', | |
| 266 selectors: [ | |
| 267 'pan-x pan-y', | |
| 268 'pan-y pan-x' | |
| 269 ] | |
| 270 }, | |
| 271 'manipulation' | |
| 272 ]; | |
| 273 var styles = ''; | |
| 274 // only install stylesheet if the browser has touch action support | |
| 275 var hasTouchAction = typeof document.head.style.touchAction === 'string'; | |
| 276 // only add shadow selectors if shadowdom is supported | |
| 277 var hasShadowRoot = !window.ShadowDOMPolyfill && document.head.createShadowRoo
t; | |
| 278 | |
| 279 if (hasTouchAction) { | |
| 280 attrib2css.forEach(function(r) { | |
| 281 if (String(r) === r) { | |
| 282 styles += selector(r) + rule(r) + '\n'; | |
| 283 if (hasShadowRoot) { | |
| 284 styles += shadowSelector(r) + rule(r) + '\n'; | |
| 285 } | |
| 286 } else { | |
| 287 styles += r.selectors.map(selector) + rule(r.rule) + '\n'; | |
| 288 if (hasShadowRoot) { | |
| 289 styles += r.selectors.map(shadowSelector) + rule(r.rule) + '\n'; | |
| 290 } | |
| 291 } | |
| 292 }); | |
| 293 | |
| 294 var el = document.createElement('style'); | |
| 295 el.textContent = styles; | |
| 296 document.head.appendChild(el); | |
| 297 } | |
| 298 })(); | |
| 299 | |
| 300 /** | |
| 301 * This is the constructor for new PointerEvents. | |
| 302 * | |
| 303 * New Pointer Events must be given a type, and an optional dictionary of | |
| 304 * initialization properties. | |
| 305 * | |
| 306 * Due to certain platform requirements, events returned from the constructor | |
| 307 * identify as MouseEvents. | |
| 308 * | |
| 309 * @constructor | |
| 310 * @param {String} inType The type of the event to create. | |
| 311 * @param {Object} [inDict] An optional dictionary of initial event properties. | |
| 312 * @return {Event} A new PointerEvent of type `inType` and initialized with prop
erties from `inDict`. | |
| 313 */ | |
| 314 (function(scope) { | |
| 315 | |
| 316 var MOUSE_PROPS = [ | |
| 317 'bubbles', | |
| 318 'cancelable', | |
| 319 'view', | |
| 320 'detail', | |
| 321 'screenX', | |
| 322 'screenY', | |
| 323 'clientX', | |
| 324 'clientY', | |
| 325 'ctrlKey', | |
| 326 'altKey', | |
| 327 'shiftKey', | |
| 328 'metaKey', | |
| 329 'button', | |
| 330 'relatedTarget', | |
| 331 'pageX', | |
| 332 'pageY' | |
| 333 ]; | |
| 334 | |
| 335 var MOUSE_DEFAULTS = [ | |
| 336 false, | |
| 337 false, | |
| 338 null, | |
| 339 null, | |
| 340 0, | |
| 341 0, | |
| 342 0, | |
| 343 0, | |
| 344 false, | |
| 345 false, | |
| 346 false, | |
| 347 false, | |
| 348 0, | |
| 349 null, | |
| 350 0, | |
| 351 0 | |
| 352 ]; | |
| 353 | |
| 354 var NOP_FACTORY = function(){ return function(){}; }; | |
| 355 | |
| 356 var eventFactory = { | |
| 357 // TODO(dfreedm): this is overridden by tap recognizer, needs review | |
| 358 preventTap: NOP_FACTORY, | |
| 359 makeBaseEvent: function(inType, inDict) { | |
| 360 var e = document.createEvent('Event'); | |
| 361 e.initEvent(inType, inDict.bubbles || false, inDict.cancelable || false); | |
| 362 e.preventTap = eventFactory.preventTap(e); | |
| 363 return e; | |
| 364 }, | |
| 365 makeGestureEvent: function(inType, inDict) { | |
| 366 inDict = inDict || Object.create(null); | |
| 367 | |
| 368 var e = this.makeBaseEvent(inType, inDict); | |
| 369 for (var i = 0, keys = Object.keys(inDict), k; i < keys.length; i++) { | |
| 370 k = keys[i]; | |
| 371 e[k] = inDict[k]; | |
| 372 } | |
| 373 return e; | |
| 374 }, | |
| 375 makePointerEvent: function(inType, inDict) { | |
| 376 inDict = inDict || Object.create(null); | |
| 377 | |
| 378 var e = this.makeBaseEvent(inType, inDict); | |
| 379 // define inherited MouseEvent properties | |
| 380 for(var i = 0, p; i < MOUSE_PROPS.length; i++) { | |
| 381 p = MOUSE_PROPS[i]; | |
| 382 e[p] = inDict[p] || MOUSE_DEFAULTS[i]; | |
| 383 } | |
| 384 e.buttons = inDict.buttons || 0; | |
| 385 | |
| 386 // Spec requires that pointers without pressure specified use 0.5 for down | |
| 387 // state and 0 for up state. | |
| 388 var pressure = 0; | |
| 389 if (inDict.pressure) { | |
| 390 pressure = inDict.pressure; | |
| 391 } else { | |
| 392 pressure = e.buttons ? 0.5 : 0; | |
| 393 } | |
| 394 | |
| 395 // add x/y properties aliased to clientX/Y | |
| 396 e.x = e.clientX; | |
| 397 e.y = e.clientY; | |
| 398 | |
| 399 // define the properties of the PointerEvent interface | |
| 400 e.pointerId = inDict.pointerId || 0; | |
| 401 e.width = inDict.width || 0; | |
| 402 e.height = inDict.height || 0; | |
| 403 e.pressure = pressure; | |
| 404 e.tiltX = inDict.tiltX || 0; | |
| 405 e.tiltY = inDict.tiltY || 0; | |
| 406 e.pointerType = inDict.pointerType || ''; | |
| 407 e.hwTimestamp = inDict.hwTimestamp || 0; | |
| 408 e.isPrimary = inDict.isPrimary || false; | |
| 409 e._source = inDict._source || ''; | |
| 410 return e; | |
| 411 } | |
| 412 }; | |
| 413 | |
| 414 scope.eventFactory = eventFactory; | |
| 415 })(window.PolymerGestures); | |
| 416 | |
| 417 /** | |
| 418 * This module implements an map of pointer states | |
| 419 */ | |
| 420 (function(scope) { | |
| 421 var USE_MAP = window.Map && window.Map.prototype.forEach; | |
| 422 var POINTERS_FN = function(){ return this.size; }; | |
| 423 function PointerMap() { | |
| 424 if (USE_MAP) { | |
| 425 var m = new Map(); | |
| 426 m.pointers = POINTERS_FN; | |
| 427 return m; | |
| 428 } else { | |
| 429 this.keys = []; | |
| 430 this.values = []; | |
| 431 } | |
| 432 } | |
| 433 | |
| 434 PointerMap.prototype = { | |
| 435 set: function(inId, inEvent) { | |
| 436 var i = this.keys.indexOf(inId); | |
| 437 if (i > -1) { | |
| 438 this.values[i] = inEvent; | |
| 439 } else { | |
| 440 this.keys.push(inId); | |
| 441 this.values.push(inEvent); | |
| 442 } | |
| 443 }, | |
| 444 has: function(inId) { | |
| 445 return this.keys.indexOf(inId) > -1; | |
| 446 }, | |
| 447 'delete': function(inId) { | |
| 448 var i = this.keys.indexOf(inId); | |
| 449 if (i > -1) { | |
| 450 this.keys.splice(i, 1); | |
| 451 this.values.splice(i, 1); | |
| 452 } | |
| 453 }, | |
| 454 get: function(inId) { | |
| 455 var i = this.keys.indexOf(inId); | |
| 456 return this.values[i]; | |
| 457 }, | |
| 458 clear: function() { | |
| 459 this.keys.length = 0; | |
| 460 this.values.length = 0; | |
| 461 }, | |
| 462 // return value, key, map | |
| 463 forEach: function(callback, thisArg) { | |
| 464 this.values.forEach(function(v, i) { | |
| 465 callback.call(thisArg, v, this.keys[i], this); | |
| 466 }, this); | |
| 467 }, | |
| 468 pointers: function() { | |
| 469 return this.keys.length; | |
| 470 } | |
| 471 }; | |
| 472 | |
| 473 scope.PointerMap = PointerMap; | |
| 474 })(window.PolymerGestures); | |
| 475 | |
| 476 (function(scope) { | |
| 477 var CLONE_PROPS = [ | |
| 478 // MouseEvent | |
| 479 'bubbles', | |
| 480 'cancelable', | |
| 481 'view', | |
| 482 'detail', | |
| 483 'screenX', | |
| 484 'screenY', | |
| 485 'clientX', | |
| 486 'clientY', | |
| 487 'ctrlKey', | |
| 488 'altKey', | |
| 489 'shiftKey', | |
| 490 'metaKey', | |
| 491 'button', | |
| 492 'relatedTarget', | |
| 493 // DOM Level 3 | |
| 494 'buttons', | |
| 495 // PointerEvent | |
| 496 'pointerId', | |
| 497 'width', | |
| 498 'height', | |
| 499 'pressure', | |
| 500 'tiltX', | |
| 501 'tiltY', | |
| 502 'pointerType', | |
| 503 'hwTimestamp', | |
| 504 'isPrimary', | |
| 505 // event instance | |
| 506 'type', | |
| 507 'target', | |
| 508 'currentTarget', | |
| 509 'which', | |
| 510 'pageX', | |
| 511 'pageY', | |
| 512 'timeStamp', | |
| 513 // gesture addons | |
| 514 'preventTap', | |
| 515 'tapPrevented', | |
| 516 '_source' | |
| 517 ]; | |
| 518 | |
| 519 var CLONE_DEFAULTS = [ | |
| 520 // MouseEvent | |
| 521 false, | |
| 522 false, | |
| 523 null, | |
| 524 null, | |
| 525 0, | |
| 526 0, | |
| 527 0, | |
| 528 0, | |
| 529 false, | |
| 530 false, | |
| 531 false, | |
| 532 false, | |
| 533 0, | |
| 534 null, | |
| 535 // DOM Level 3 | |
| 536 0, | |
| 537 // PointerEvent | |
| 538 0, | |
| 539 0, | |
| 540 0, | |
| 541 0, | |
| 542 0, | |
| 543 0, | |
| 544 '', | |
| 545 0, | |
| 546 false, | |
| 547 // event instance | |
| 548 '', | |
| 549 null, | |
| 550 null, | |
| 551 0, | |
| 552 0, | |
| 553 0, | |
| 554 0, | |
| 555 function(){}, | |
| 556 false | |
| 557 ]; | |
| 558 | |
| 559 var HAS_SVG_INSTANCE = (typeof SVGElementInstance !== 'undefined'); | |
| 560 | |
| 561 var eventFactory = scope.eventFactory; | |
| 562 | |
| 563 // set of recognizers to run for the currently handled event | |
| 564 var currentGestures; | |
| 565 | |
| 566 /** | |
| 567 * This module is for normalizing events. Mouse and Touch events will be | |
| 568 * collected here, and fire PointerEvents that have the same semantics, no | |
| 569 * matter the source. | |
| 570 * Events fired: | |
| 571 * - pointerdown: a pointing is added | |
| 572 * - pointerup: a pointer is removed | |
| 573 * - pointermove: a pointer is moved | |
| 574 * - pointerover: a pointer crosses into an element | |
| 575 * - pointerout: a pointer leaves an element | |
| 576 * - pointercancel: a pointer will no longer generate events | |
| 577 */ | |
| 578 var dispatcher = { | |
| 579 IS_IOS: false, | |
| 580 pointermap: new scope.PointerMap(), | |
| 581 requiredGestures: new scope.PointerMap(), | |
| 582 eventMap: Object.create(null), | |
| 583 // Scope objects for native events. | |
| 584 // This exists for ease of testing. | |
| 585 eventSources: Object.create(null), | |
| 586 eventSourceList: [], | |
| 587 gestures: [], | |
| 588 // map gesture event -> {listeners: int, index: gestures[int]} | |
| 589 dependencyMap: { | |
| 590 // make sure down and up are in the map to trigger "register" | |
| 591 down: {listeners: 0, index: -1}, | |
| 592 up: {listeners: 0, index: -1} | |
| 593 }, | |
| 594 gestureQueue: [], | |
| 595 /** | |
| 596 * Add a new event source that will generate pointer events. | |
| 597 * | |
| 598 * `inSource` must contain an array of event names named `events`, and | |
| 599 * functions with the names specified in the `events` array. | |
| 600 * @param {string} name A name for the event source | |
| 601 * @param {Object} source A new source of platform events. | |
| 602 */ | |
| 603 registerSource: function(name, source) { | |
| 604 var s = source; | |
| 605 var newEvents = s.events; | |
| 606 if (newEvents) { | |
| 607 newEvents.forEach(function(e) { | |
| 608 if (s[e]) { | |
| 609 this.eventMap[e] = s[e].bind(s); | |
| 610 } | |
| 611 }, this); | |
| 612 this.eventSources[name] = s; | |
| 613 this.eventSourceList.push(s); | |
| 614 } | |
| 615 }, | |
| 616 registerGesture: function(name, source) { | |
| 617 var obj = Object.create(null); | |
| 618 obj.listeners = 0; | |
| 619 obj.index = this.gestures.length; | |
| 620 for (var i = 0, g; i < source.exposes.length; i++) { | |
| 621 g = source.exposes[i].toLowerCase(); | |
| 622 this.dependencyMap[g] = obj; | |
| 623 } | |
| 624 this.gestures.push(source); | |
| 625 }, | |
| 626 register: function(element, initial) { | |
| 627 var l = this.eventSourceList.length; | |
| 628 for (var i = 0, es; (i < l) && (es = this.eventSourceList[i]); i++) { | |
| 629 // call eventsource register | |
| 630 es.register.call(es, element, initial); | |
| 631 } | |
| 632 }, | |
| 633 unregister: function(element) { | |
| 634 var l = this.eventSourceList.length; | |
| 635 for (var i = 0, es; (i < l) && (es = this.eventSourceList[i]); i++) { | |
| 636 // call eventsource register | |
| 637 es.unregister.call(es, element); | |
| 638 } | |
| 639 }, | |
| 640 // EVENTS | |
| 641 down: function(inEvent) { | |
| 642 this.requiredGestures.set(inEvent.pointerId, currentGestures); | |
| 643 this.fireEvent('down', inEvent); | |
| 644 }, | |
| 645 move: function(inEvent) { | |
| 646 // pipe move events into gesture queue directly | |
| 647 inEvent.type = 'move'; | |
| 648 this.fillGestureQueue(inEvent); | |
| 649 }, | |
| 650 up: function(inEvent) { | |
| 651 this.fireEvent('up', inEvent); | |
| 652 this.requiredGestures.delete(inEvent.pointerId); | |
| 653 }, | |
| 654 cancel: function(inEvent) { | |
| 655 inEvent.tapPrevented = true; | |
| 656 this.fireEvent('up', inEvent); | |
| 657 this.requiredGestures.delete(inEvent.pointerId); | |
| 658 }, | |
| 659 addGestureDependency: function(node, currentGestures) { | |
| 660 var gesturesWanted = node._pgEvents; | |
| 661 if (gesturesWanted && currentGestures) { | |
| 662 var gk = Object.keys(gesturesWanted); | |
| 663 for (var i = 0, r, ri, g; i < gk.length; i++) { | |
| 664 // gesture | |
| 665 g = gk[i]; | |
| 666 if (gesturesWanted[g] > 0) { | |
| 667 // lookup gesture recognizer | |
| 668 r = this.dependencyMap[g]; | |
| 669 // recognizer index | |
| 670 ri = r ? r.index : -1; | |
| 671 currentGestures[ri] = true; | |
| 672 } | |
| 673 } | |
| 674 } | |
| 675 }, | |
| 676 // LISTENER LOGIC | |
| 677 eventHandler: function(inEvent) { | |
| 678 // This is used to prevent multiple dispatch of events from | |
| 679 // platform events. This can happen when two elements in different scopes | |
| 680 // are set up to create pointer events, which is relevant to Shadow DOM. | |
| 681 | |
| 682 var type = inEvent.type; | |
| 683 | |
| 684 // only generate the list of desired events on "down" | |
| 685 if (type === 'touchstart' || type === 'mousedown' || type === 'pointerdown
' || type === 'MSPointerDown') { | |
| 686 if (!inEvent._handledByPG) { | |
| 687 currentGestures = {}; | |
| 688 } | |
| 689 | |
| 690 // in IOS mode, there is only a listener on the document, so this is not
re-entrant | |
| 691 if (this.IS_IOS) { | |
| 692 var ev = inEvent; | |
| 693 if (type === 'touchstart') { | |
| 694 var ct = inEvent.changedTouches[0]; | |
| 695 // set up a fake event to give to the path builder | |
| 696 ev = {target: inEvent.target, clientX: ct.clientX, clientY: ct.clien
tY, path: inEvent.path}; | |
| 697 } | |
| 698 // use event path if available, otherwise build a path from target fin
ding | |
| 699 var nodes = inEvent.path || scope.targetFinding.path(ev); | |
| 700 for (var i = 0, n; i < nodes.length; i++) { | |
| 701 n = nodes[i]; | |
| 702 this.addGestureDependency(n, currentGestures); | |
| 703 } | |
| 704 } else { | |
| 705 this.addGestureDependency(inEvent.currentTarget, currentGestures); | |
| 706 } | |
| 707 } | |
| 708 | |
| 709 if (inEvent._handledByPG) { | |
| 710 return; | |
| 711 } | |
| 712 var fn = this.eventMap && this.eventMap[type]; | |
| 713 if (fn) { | |
| 714 fn(inEvent); | |
| 715 } | |
| 716 inEvent._handledByPG = true; | |
| 717 }, | |
| 718 // set up event listeners | |
| 719 listen: function(target, events) { | |
| 720 for (var i = 0, l = events.length, e; (i < l) && (e = events[i]); i++) { | |
| 721 this.addEvent(target, e); | |
| 722 } | |
| 723 }, | |
| 724 // remove event listeners | |
| 725 unlisten: function(target, events) { | |
| 726 for (var i = 0, l = events.length, e; (i < l) && (e = events[i]); i++) { | |
| 727 this.removeEvent(target, e); | |
| 728 } | |
| 729 }, | |
| 730 addEvent: function(target, eventName) { | |
| 731 target.addEventListener(eventName, this.boundHandler); | |
| 732 }, | |
| 733 removeEvent: function(target, eventName) { | |
| 734 target.removeEventListener(eventName, this.boundHandler); | |
| 735 }, | |
| 736 // EVENT CREATION AND TRACKING | |
| 737 /** | |
| 738 * Creates a new Event of type `inType`, based on the information in | |
| 739 * `inEvent`. | |
| 740 * | |
| 741 * @param {string} inType A string representing the type of event to create | |
| 742 * @param {Event} inEvent A platform event with a target | |
| 743 * @return {Event} A PointerEvent of type `inType` | |
| 744 */ | |
| 745 makeEvent: function(inType, inEvent) { | |
| 746 var e = eventFactory.makePointerEvent(inType, inEvent); | |
| 747 e.preventDefault = inEvent.preventDefault; | |
| 748 e.tapPrevented = inEvent.tapPrevented; | |
| 749 e._target = e._target || inEvent.target; | |
| 750 return e; | |
| 751 }, | |
| 752 // make and dispatch an event in one call | |
| 753 fireEvent: function(inType, inEvent) { | |
| 754 var e = this.makeEvent(inType, inEvent); | |
| 755 return this.dispatchEvent(e); | |
| 756 }, | |
| 757 /** | |
| 758 * Returns a snapshot of inEvent, with writable properties. | |
| 759 * | |
| 760 * @param {Event} inEvent An event that contains properties to copy. | |
| 761 * @return {Object} An object containing shallow copies of `inEvent`'s | |
| 762 * properties. | |
| 763 */ | |
| 764 cloneEvent: function(inEvent) { | |
| 765 var eventCopy = Object.create(null), p; | |
| 766 for (var i = 0; i < CLONE_PROPS.length; i++) { | |
| 767 p = CLONE_PROPS[i]; | |
| 768 eventCopy[p] = inEvent[p] || CLONE_DEFAULTS[i]; | |
| 769 // Work around SVGInstanceElement shadow tree | |
| 770 // Return the <use> element that is represented by the instance for Safa
ri, Chrome, IE. | |
| 771 // This is the behavior implemented by Firefox. | |
| 772 if (p === 'target' || p === 'relatedTarget') { | |
| 773 if (HAS_SVG_INSTANCE && eventCopy[p] instanceof SVGElementInstance) { | |
| 774 eventCopy[p] = eventCopy[p].correspondingUseElement; | |
| 775 } | |
| 776 } | |
| 777 } | |
| 778 // keep the semantics of preventDefault | |
| 779 eventCopy.preventDefault = function() { | |
| 780 inEvent.preventDefault(); | |
| 781 }; | |
| 782 return eventCopy; | |
| 783 }, | |
| 784 /** | |
| 785 * Dispatches the event to its target. | |
| 786 * | |
| 787 * @param {Event} inEvent The event to be dispatched. | |
| 788 * @return {Boolean} True if an event handler returns true, false otherwise. | |
| 789 */ | |
| 790 dispatchEvent: function(inEvent) { | |
| 791 var t = inEvent._target; | |
| 792 if (t) { | |
| 793 t.dispatchEvent(inEvent); | |
| 794 // clone the event for the gesture system to process | |
| 795 // clone after dispatch to pick up gesture prevention code | |
| 796 var clone = this.cloneEvent(inEvent); | |
| 797 clone.target = t; | |
| 798 this.fillGestureQueue(clone); | |
| 799 } | |
| 800 }, | |
| 801 gestureTrigger: function() { | |
| 802 // process the gesture queue | |
| 803 for (var i = 0, e, rg; i < this.gestureQueue.length; i++) { | |
| 804 e = this.gestureQueue[i]; | |
| 805 rg = e._requiredGestures; | |
| 806 if (rg) { | |
| 807 for (var j = 0, g, fn; j < this.gestures.length; j++) { | |
| 808 // only run recognizer if an element in the source event's path is l
istening for those gestures | |
| 809 if (rg[j]) { | |
| 810 g = this.gestures[j]; | |
| 811 fn = g[e.type]; | |
| 812 if (fn) { | |
| 813 fn.call(g, e); | |
| 814 } | |
| 815 } | |
| 816 } | |
| 817 } | |
| 818 } | |
| 819 this.gestureQueue.length = 0; | |
| 820 }, | |
| 821 fillGestureQueue: function(ev) { | |
| 822 // only trigger the gesture queue once | |
| 823 if (!this.gestureQueue.length) { | |
| 824 requestAnimationFrame(this.boundGestureTrigger); | |
| 825 } | |
| 826 ev._requiredGestures = this.requiredGestures.get(ev.pointerId); | |
| 827 this.gestureQueue.push(ev); | |
| 828 } | |
| 829 }; | |
| 830 dispatcher.boundHandler = dispatcher.eventHandler.bind(dispatcher); | |
| 831 dispatcher.boundGestureTrigger = dispatcher.gestureTrigger.bind(dispatcher); | |
| 832 scope.dispatcher = dispatcher; | |
| 833 | |
| 834 /** | |
| 835 * Listen for `gesture` on `node` with the `handler` function | |
| 836 * | |
| 837 * If `handler` is the first listener for `gesture`, the underlying gesture re
cognizer is then enabled. | |
| 838 * | |
| 839 * @param {Element} node | |
| 840 * @param {string} gesture | |
| 841 * @return Boolean `gesture` is a valid gesture | |
| 842 */ | |
| 843 scope.activateGesture = function(node, gesture) { | |
| 844 var g = gesture.toLowerCase(); | |
| 845 var dep = dispatcher.dependencyMap[g]; | |
| 846 if (dep) { | |
| 847 var recognizer = dispatcher.gestures[dep.index]; | |
| 848 if (!node._pgListeners) { | |
| 849 dispatcher.register(node); | |
| 850 node._pgListeners = 0; | |
| 851 } | |
| 852 // TODO(dfreedm): re-evaluate bookkeeping to avoid using attributes | |
| 853 if (recognizer) { | |
| 854 var touchAction = recognizer.defaultActions && recognizer.defaultActions
[g]; | |
| 855 var actionNode; | |
| 856 switch(node.nodeType) { | |
| 857 case Node.ELEMENT_NODE: | |
| 858 actionNode = node; | |
| 859 break; | |
| 860 case Node.DOCUMENT_FRAGMENT_NODE: | |
| 861 actionNode = node.host; | |
| 862 break; | |
| 863 default: | |
| 864 actionNode = null; | |
| 865 break; | |
| 866 } | |
| 867 if (touchAction && actionNode && !actionNode.hasAttribute('touch-action'
)) { | |
| 868 actionNode.setAttribute('touch-action', touchAction); | |
| 869 } | |
| 870 } | |
| 871 if (!node._pgEvents) { | |
| 872 node._pgEvents = {}; | |
| 873 } | |
| 874 node._pgEvents[g] = (node._pgEvents[g] || 0) + 1; | |
| 875 node._pgListeners++; | |
| 876 } | |
| 877 return Boolean(dep); | |
| 878 }; | |
| 879 | |
| 880 /** | |
| 881 * | |
| 882 * Listen for `gesture` from `node` with `handler` function. | |
| 883 * | |
| 884 * @param {Element} node | |
| 885 * @param {string} gesture | |
| 886 * @param {Function} handler | |
| 887 * @param {Boolean} capture | |
| 888 */ | |
| 889 scope.addEventListener = function(node, gesture, handler, capture) { | |
| 890 if (handler) { | |
| 891 scope.activateGesture(node, gesture); | |
| 892 node.addEventListener(gesture, handler, capture); | |
| 893 } | |
| 894 }; | |
| 895 | |
| 896 /** | |
| 897 * Tears down the gesture configuration for `node` | |
| 898 * | |
| 899 * If `handler` is the last listener for `gesture`, the underlying gesture rec
ognizer is disabled. | |
| 900 * | |
| 901 * @param {Element} node | |
| 902 * @param {string} gesture | |
| 903 * @return Boolean `gesture` is a valid gesture | |
| 904 */ | |
| 905 scope.deactivateGesture = function(node, gesture) { | |
| 906 var g = gesture.toLowerCase(); | |
| 907 var dep = dispatcher.dependencyMap[g]; | |
| 908 if (dep) { | |
| 909 if (node._pgListeners > 0) { | |
| 910 node._pgListeners--; | |
| 911 } | |
| 912 if (node._pgListeners === 0) { | |
| 913 dispatcher.unregister(node); | |
| 914 } | |
| 915 if (node._pgEvents) { | |
| 916 if (node._pgEvents[g] > 0) { | |
| 917 node._pgEvents[g]--; | |
| 918 } else { | |
| 919 node._pgEvents[g] = 0; | |
| 920 } | |
| 921 } | |
| 922 } | |
| 923 return Boolean(dep); | |
| 924 }; | |
| 925 | |
| 926 /** | |
| 927 * Stop listening for `gesture` from `node` with `handler` function. | |
| 928 * | |
| 929 * @param {Element} node | |
| 930 * @param {string} gesture | |
| 931 * @param {Function} handler | |
| 932 * @param {Boolean} capture | |
| 933 */ | |
| 934 scope.removeEventListener = function(node, gesture, handler, capture) { | |
| 935 if (handler) { | |
| 936 scope.deactivateGesture(node, gesture); | |
| 937 node.removeEventListener(gesture, handler, capture); | |
| 938 } | |
| 939 }; | |
| 940 })(window.PolymerGestures); | |
| 941 | |
| 942 (function(scope) { | |
| 943 var dispatcher = scope.dispatcher; | |
| 944 var pointermap = dispatcher.pointermap; | |
| 945 // radius around touchend that swallows mouse events | |
| 946 var DEDUP_DIST = 25; | |
| 947 | |
| 948 var WHICH_TO_BUTTONS = [0, 1, 4, 2]; | |
| 949 | |
| 950 var currentButtons = 0; | |
| 951 | |
| 952 var FIREFOX_LINUX = /Linux.*Firefox\//i; | |
| 953 | |
| 954 var HAS_BUTTONS = (function() { | |
| 955 // firefox on linux returns spec-incorrect values for mouseup.buttons | |
| 956 // https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent.buttons#See_a
lso | |
| 957 // https://codereview.chromium.org/727593003/#msg16 | |
| 958 if (FIREFOX_LINUX.test(navigator.userAgent)) { | |
| 959 return false; | |
| 960 } | |
| 961 try { | |
| 962 return new MouseEvent('test', {buttons: 1}).buttons === 1; | |
| 963 } catch (e) { | |
| 964 return false; | |
| 965 } | |
| 966 })(); | |
| 967 | |
| 968 // handler block for native mouse events | |
| 969 var mouseEvents = { | |
| 970 POINTER_ID: 1, | |
| 971 POINTER_TYPE: 'mouse', | |
| 972 events: [ | |
| 973 'mousedown', | |
| 974 'mousemove', | |
| 975 'mouseup' | |
| 976 ], | |
| 977 exposes: [ | |
| 978 'down', | |
| 979 'up', | |
| 980 'move' | |
| 981 ], | |
| 982 register: function(target) { | |
| 983 dispatcher.listen(target, this.events); | |
| 984 }, | |
| 985 unregister: function(target) { | |
| 986 if (target === document) { | |
| 987 return; | |
| 988 } | |
| 989 dispatcher.unlisten(target, this.events); | |
| 990 }, | |
| 991 lastTouches: [], | |
| 992 // collide with the global mouse listener | |
| 993 isEventSimulatedFromTouch: function(inEvent) { | |
| 994 var lts = this.lastTouches; | |
| 995 var x = inEvent.clientX, y = inEvent.clientY; | |
| 996 for (var i = 0, l = lts.length, t; i < l && (t = lts[i]); i++) { | |
| 997 // simulated mouse events will be swallowed near a primary touchend | |
| 998 var dx = Math.abs(x - t.x), dy = Math.abs(y - t.y); | |
| 999 if (dx <= DEDUP_DIST && dy <= DEDUP_DIST) { | |
| 1000 return true; | |
| 1001 } | |
| 1002 } | |
| 1003 }, | |
| 1004 prepareEvent: function(inEvent) { | |
| 1005 var e = dispatcher.cloneEvent(inEvent); | |
| 1006 e.pointerId = this.POINTER_ID; | |
| 1007 e.isPrimary = true; | |
| 1008 e.pointerType = this.POINTER_TYPE; | |
| 1009 e._source = 'mouse'; | |
| 1010 if (!HAS_BUTTONS) { | |
| 1011 var type = inEvent.type; | |
| 1012 var bit = WHICH_TO_BUTTONS[inEvent.which] || 0; | |
| 1013 if (type === 'mousedown') { | |
| 1014 currentButtons |= bit; | |
| 1015 } else if (type === 'mouseup') { | |
| 1016 currentButtons &= ~bit; | |
| 1017 } | |
| 1018 e.buttons = currentButtons; | |
| 1019 } | |
| 1020 return e; | |
| 1021 }, | |
| 1022 mousedown: function(inEvent) { | |
| 1023 if (!this.isEventSimulatedFromTouch(inEvent)) { | |
| 1024 var p = pointermap.has(this.POINTER_ID); | |
| 1025 var e = this.prepareEvent(inEvent); | |
| 1026 e.target = scope.findTarget(inEvent); | |
| 1027 pointermap.set(this.POINTER_ID, e.target); | |
| 1028 dispatcher.down(e); | |
| 1029 } | |
| 1030 }, | |
| 1031 mousemove: function(inEvent) { | |
| 1032 if (!this.isEventSimulatedFromTouch(inEvent)) { | |
| 1033 var target = pointermap.get(this.POINTER_ID); | |
| 1034 if (target) { | |
| 1035 var e = this.prepareEvent(inEvent); | |
| 1036 e.target = target; | |
| 1037 // handle case where we missed a mouseup | |
| 1038 if ((HAS_BUTTONS ? e.buttons : e.which) === 0) { | |
| 1039 if (!HAS_BUTTONS) { | |
| 1040 currentButtons = e.buttons = 0; | |
| 1041 } | |
| 1042 dispatcher.cancel(e); | |
| 1043 this.cleanupMouse(e.buttons); | |
| 1044 } else { | |
| 1045 dispatcher.move(e); | |
| 1046 } | |
| 1047 } | |
| 1048 } | |
| 1049 }, | |
| 1050 mouseup: function(inEvent) { | |
| 1051 if (!this.isEventSimulatedFromTouch(inEvent)) { | |
| 1052 var e = this.prepareEvent(inEvent); | |
| 1053 e.relatedTarget = scope.findTarget(inEvent); | |
| 1054 e.target = pointermap.get(this.POINTER_ID); | |
| 1055 dispatcher.up(e); | |
| 1056 this.cleanupMouse(e.buttons); | |
| 1057 } | |
| 1058 }, | |
| 1059 cleanupMouse: function(buttons) { | |
| 1060 if (buttons === 0) { | |
| 1061 pointermap.delete(this.POINTER_ID); | |
| 1062 } | |
| 1063 } | |
| 1064 }; | |
| 1065 | |
| 1066 scope.mouseEvents = mouseEvents; | |
| 1067 })(window.PolymerGestures); | |
| 1068 | |
| 1069 (function(scope) { | |
| 1070 var dispatcher = scope.dispatcher; | |
| 1071 var allShadows = scope.targetFinding.allShadows.bind(scope.targetFinding); | |
| 1072 var pointermap = dispatcher.pointermap; | |
| 1073 var touchMap = Array.prototype.map.call.bind(Array.prototype.map); | |
| 1074 // This should be long enough to ignore compat mouse events made by touch | |
| 1075 var DEDUP_TIMEOUT = 2500; | |
| 1076 var DEDUP_DIST = 25; | |
| 1077 var CLICK_COUNT_TIMEOUT = 200; | |
| 1078 var HYSTERESIS = 20; | |
| 1079 var ATTRIB = 'touch-action'; | |
| 1080 // TODO(dfreedm): disable until http://crbug.com/399765 is resolved | |
| 1081 // var HAS_TOUCH_ACTION = ATTRIB in document.head.style; | |
| 1082 var HAS_TOUCH_ACTION = false; | |
| 1083 | |
| 1084 // handler block for native touch events | |
| 1085 var touchEvents = { | |
| 1086 IS_IOS: false, | |
| 1087 events: [ | |
| 1088 'touchstart', | |
| 1089 'touchmove', | |
| 1090 'touchend', | |
| 1091 'touchcancel' | |
| 1092 ], | |
| 1093 exposes: [ | |
| 1094 'down', | |
| 1095 'up', | |
| 1096 'move' | |
| 1097 ], | |
| 1098 register: function(target, initial) { | |
| 1099 if (this.IS_IOS ? initial : !initial) { | |
| 1100 dispatcher.listen(target, this.events); | |
| 1101 } | |
| 1102 }, | |
| 1103 unregister: function(target) { | |
| 1104 if (!this.IS_IOS) { | |
| 1105 dispatcher.unlisten(target, this.events); | |
| 1106 } | |
| 1107 }, | |
| 1108 scrollTypes: { | |
| 1109 EMITTER: 'none', | |
| 1110 XSCROLLER: 'pan-x', | |
| 1111 YSCROLLER: 'pan-y', | |
| 1112 }, | |
| 1113 touchActionToScrollType: function(touchAction) { | |
| 1114 var t = touchAction; | |
| 1115 var st = this.scrollTypes; | |
| 1116 if (t === st.EMITTER) { | |
| 1117 return 'none'; | |
| 1118 } else if (t === st.XSCROLLER) { | |
| 1119 return 'X'; | |
| 1120 } else if (t === st.YSCROLLER) { | |
| 1121 return 'Y'; | |
| 1122 } else { | |
| 1123 return 'XY'; | |
| 1124 } | |
| 1125 }, | |
| 1126 POINTER_TYPE: 'touch', | |
| 1127 firstTouch: null, | |
| 1128 isPrimaryTouch: function(inTouch) { | |
| 1129 return this.firstTouch === inTouch.identifier; | |
| 1130 }, | |
| 1131 setPrimaryTouch: function(inTouch) { | |
| 1132 // set primary touch if there no pointers, or the only pointer is the mous
e | |
| 1133 if (pointermap.pointers() === 0 || (pointermap.pointers() === 1 && pointer
map.has(1))) { | |
| 1134 this.firstTouch = inTouch.identifier; | |
| 1135 this.firstXY = {X: inTouch.clientX, Y: inTouch.clientY}; | |
| 1136 this.firstTarget = inTouch.target; | |
| 1137 this.scrolling = null; | |
| 1138 this.cancelResetClickCount(); | |
| 1139 } | |
| 1140 }, | |
| 1141 removePrimaryPointer: function(inPointer) { | |
| 1142 if (inPointer.isPrimary) { | |
| 1143 this.firstTouch = null; | |
| 1144 this.firstXY = null; | |
| 1145 this.resetClickCount(); | |
| 1146 } | |
| 1147 }, | |
| 1148 clickCount: 0, | |
| 1149 resetId: null, | |
| 1150 resetClickCount: function() { | |
| 1151 var fn = function() { | |
| 1152 this.clickCount = 0; | |
| 1153 this.resetId = null; | |
| 1154 }.bind(this); | |
| 1155 this.resetId = setTimeout(fn, CLICK_COUNT_TIMEOUT); | |
| 1156 }, | |
| 1157 cancelResetClickCount: function() { | |
| 1158 if (this.resetId) { | |
| 1159 clearTimeout(this.resetId); | |
| 1160 } | |
| 1161 }, | |
| 1162 typeToButtons: function(type) { | |
| 1163 var ret = 0; | |
| 1164 if (type === 'touchstart' || type === 'touchmove') { | |
| 1165 ret = 1; | |
| 1166 } | |
| 1167 return ret; | |
| 1168 }, | |
| 1169 findTarget: function(touch, id) { | |
| 1170 if (this.currentTouchEvent.type === 'touchstart') { | |
| 1171 if (this.isPrimaryTouch(touch)) { | |
| 1172 var fastPath = { | |
| 1173 clientX: touch.clientX, | |
| 1174 clientY: touch.clientY, | |
| 1175 path: this.currentTouchEvent.path, | |
| 1176 target: this.currentTouchEvent.target | |
| 1177 }; | |
| 1178 return scope.findTarget(fastPath); | |
| 1179 } else { | |
| 1180 return scope.findTarget(touch); | |
| 1181 } | |
| 1182 } | |
| 1183 // reuse target we found in touchstart | |
| 1184 return pointermap.get(id); | |
| 1185 }, | |
| 1186 touchToPointer: function(inTouch) { | |
| 1187 var cte = this.currentTouchEvent; | |
| 1188 var e = dispatcher.cloneEvent(inTouch); | |
| 1189 // Spec specifies that pointerId 1 is reserved for Mouse. | |
| 1190 // Touch identifiers can start at 0. | |
| 1191 // Add 2 to the touch identifier for compatibility. | |
| 1192 var id = e.pointerId = inTouch.identifier + 2; | |
| 1193 e.target = this.findTarget(inTouch, id); | |
| 1194 e.bubbles = true; | |
| 1195 e.cancelable = true; | |
| 1196 e.detail = this.clickCount; | |
| 1197 e.buttons = this.typeToButtons(cte.type); | |
| 1198 e.width = inTouch.webkitRadiusX || inTouch.radiusX || 0; | |
| 1199 e.height = inTouch.webkitRadiusY || inTouch.radiusY || 0; | |
| 1200 e.pressure = inTouch.webkitForce || inTouch.force || 0.5; | |
| 1201 e.isPrimary = this.isPrimaryTouch(inTouch); | |
| 1202 e.pointerType = this.POINTER_TYPE; | |
| 1203 e._source = 'touch'; | |
| 1204 // forward touch preventDefaults | |
| 1205 var self = this; | |
| 1206 e.preventDefault = function() { | |
| 1207 self.scrolling = false; | |
| 1208 self.firstXY = null; | |
| 1209 cte.preventDefault(); | |
| 1210 }; | |
| 1211 return e; | |
| 1212 }, | |
| 1213 processTouches: function(inEvent, inFunction) { | |
| 1214 var tl = inEvent.changedTouches; | |
| 1215 this.currentTouchEvent = inEvent; | |
| 1216 for (var i = 0, t, p; i < tl.length; i++) { | |
| 1217 t = tl[i]; | |
| 1218 p = this.touchToPointer(t); | |
| 1219 if (inEvent.type === 'touchstart') { | |
| 1220 pointermap.set(p.pointerId, p.target); | |
| 1221 } | |
| 1222 if (pointermap.has(p.pointerId)) { | |
| 1223 inFunction.call(this, p); | |
| 1224 } | |
| 1225 if (inEvent.type === 'touchend' || inEvent._cancel) { | |
| 1226 this.cleanUpPointer(p); | |
| 1227 } | |
| 1228 } | |
| 1229 }, | |
| 1230 // For single axis scrollers, determines whether the element should emit | |
| 1231 // pointer events or behave as a scroller | |
| 1232 shouldScroll: function(inEvent) { | |
| 1233 if (this.firstXY) { | |
| 1234 var ret; | |
| 1235 var touchAction = scope.targetFinding.findTouchAction(inEvent); | |
| 1236 var scrollAxis = this.touchActionToScrollType(touchAction); | |
| 1237 if (scrollAxis === 'none') { | |
| 1238 // this element is a touch-action: none, should never scroll | |
| 1239 ret = false; | |
| 1240 } else if (scrollAxis === 'XY') { | |
| 1241 // this element should always scroll | |
| 1242 ret = true; | |
| 1243 } else { | |
| 1244 var t = inEvent.changedTouches[0]; | |
| 1245 // check the intended scroll axis, and other axis | |
| 1246 var a = scrollAxis; | |
| 1247 var oa = scrollAxis === 'Y' ? 'X' : 'Y'; | |
| 1248 var da = Math.abs(t['client' + a] - this.firstXY[a]); | |
| 1249 var doa = Math.abs(t['client' + oa] - this.firstXY[oa]); | |
| 1250 // if delta in the scroll axis > delta other axis, scroll instead of | |
| 1251 // making events | |
| 1252 ret = da >= doa; | |
| 1253 } | |
| 1254 return ret; | |
| 1255 } | |
| 1256 }, | |
| 1257 findTouch: function(inTL, inId) { | |
| 1258 for (var i = 0, l = inTL.length, t; i < l && (t = inTL[i]); i++) { | |
| 1259 if (t.identifier === inId) { | |
| 1260 return true; | |
| 1261 } | |
| 1262 } | |
| 1263 }, | |
| 1264 // In some instances, a touchstart can happen without a touchend. This | |
| 1265 // leaves the pointermap in a broken state. | |
| 1266 // Therefore, on every touchstart, we remove the touches that did not fire a | |
| 1267 // touchend event. | |
| 1268 // To keep state globally consistent, we fire a | |
| 1269 // pointercancel for this "abandoned" touch | |
| 1270 vacuumTouches: function(inEvent) { | |
| 1271 var tl = inEvent.touches; | |
| 1272 // pointermap.pointers() should be < tl.length here, as the touchstart has
not | |
| 1273 // been processed yet. | |
| 1274 if (pointermap.pointers() >= tl.length) { | |
| 1275 var d = []; | |
| 1276 pointermap.forEach(function(value, key) { | |
| 1277 // Never remove pointerId == 1, which is mouse. | |
| 1278 // Touch identifiers are 2 smaller than their pointerId, which is the | |
| 1279 // index in pointermap. | |
| 1280 if (key !== 1 && !this.findTouch(tl, key - 2)) { | |
| 1281 var p = value; | |
| 1282 d.push(p); | |
| 1283 } | |
| 1284 }, this); | |
| 1285 d.forEach(function(p) { | |
| 1286 this.cancel(p); | |
| 1287 pointermap.delete(p.pointerId); | |
| 1288 }, this); | |
| 1289 } | |
| 1290 }, | |
| 1291 touchstart: function(inEvent) { | |
| 1292 this.vacuumTouches(inEvent); | |
| 1293 this.setPrimaryTouch(inEvent.changedTouches[0]); | |
| 1294 this.dedupSynthMouse(inEvent); | |
| 1295 if (!this.scrolling) { | |
| 1296 this.clickCount++; | |
| 1297 this.processTouches(inEvent, this.down); | |
| 1298 } | |
| 1299 }, | |
| 1300 down: function(inPointer) { | |
| 1301 dispatcher.down(inPointer); | |
| 1302 }, | |
| 1303 touchmove: function(inEvent) { | |
| 1304 if (HAS_TOUCH_ACTION) { | |
| 1305 // touchevent.cancelable == false is sent when the page is scrolling und
er native Touch Action in Chrome 36 | |
| 1306 // https://groups.google.com/a/chromium.org/d/msg/input-dev/wHnyukcYBcA/
b9kmtwM1jJQJ | |
| 1307 if (inEvent.cancelable) { | |
| 1308 this.processTouches(inEvent, this.move); | |
| 1309 } | |
| 1310 } else { | |
| 1311 if (!this.scrolling) { | |
| 1312 if (this.scrolling === null && this.shouldScroll(inEvent)) { | |
| 1313 this.scrolling = true; | |
| 1314 } else { | |
| 1315 this.scrolling = false; | |
| 1316 inEvent.preventDefault(); | |
| 1317 this.processTouches(inEvent, this.move); | |
| 1318 } | |
| 1319 } else if (this.firstXY) { | |
| 1320 var t = inEvent.changedTouches[0]; | |
| 1321 var dx = t.clientX - this.firstXY.X; | |
| 1322 var dy = t.clientY - this.firstXY.Y; | |
| 1323 var dd = Math.sqrt(dx * dx + dy * dy); | |
| 1324 if (dd >= HYSTERESIS) { | |
| 1325 this.touchcancel(inEvent); | |
| 1326 this.scrolling = true; | |
| 1327 this.firstXY = null; | |
| 1328 } | |
| 1329 } | |
| 1330 } | |
| 1331 }, | |
| 1332 move: function(inPointer) { | |
| 1333 dispatcher.move(inPointer); | |
| 1334 }, | |
| 1335 touchend: function(inEvent) { | |
| 1336 this.dedupSynthMouse(inEvent); | |
| 1337 this.processTouches(inEvent, this.up); | |
| 1338 }, | |
| 1339 up: function(inPointer) { | |
| 1340 inPointer.relatedTarget = scope.findTarget(inPointer); | |
| 1341 dispatcher.up(inPointer); | |
| 1342 }, | |
| 1343 cancel: function(inPointer) { | |
| 1344 dispatcher.cancel(inPointer); | |
| 1345 }, | |
| 1346 touchcancel: function(inEvent) { | |
| 1347 inEvent._cancel = true; | |
| 1348 this.processTouches(inEvent, this.cancel); | |
| 1349 }, | |
| 1350 cleanUpPointer: function(inPointer) { | |
| 1351 pointermap['delete'](inPointer.pointerId); | |
| 1352 this.removePrimaryPointer(inPointer); | |
| 1353 }, | |
| 1354 // prevent synth mouse events from creating pointer events | |
| 1355 dedupSynthMouse: function(inEvent) { | |
| 1356 var lts = scope.mouseEvents.lastTouches; | |
| 1357 var t = inEvent.changedTouches[0]; | |
| 1358 // only the primary finger will synth mouse events | |
| 1359 if (this.isPrimaryTouch(t)) { | |
| 1360 // remember x/y of last touch | |
| 1361 var lt = {x: t.clientX, y: t.clientY}; | |
| 1362 lts.push(lt); | |
| 1363 var fn = (function(lts, lt){ | |
| 1364 var i = lts.indexOf(lt); | |
| 1365 if (i > -1) { | |
| 1366 lts.splice(i, 1); | |
| 1367 } | |
| 1368 }).bind(null, lts, lt); | |
| 1369 setTimeout(fn, DEDUP_TIMEOUT); | |
| 1370 } | |
| 1371 } | |
| 1372 }; | |
| 1373 | |
| 1374 // prevent "ghost clicks" that come from elements that were removed in a touch
handler | |
| 1375 var STOP_PROP_FN = Event.prototype.stopImmediatePropagation || Event.prototype
.stopPropagation; | |
| 1376 document.addEventListener('click', function(ev) { | |
| 1377 var x = ev.clientX, y = ev.clientY; | |
| 1378 // check if a click is within DEDUP_DIST px radius of the touchstart | |
| 1379 var closeTo = function(touch) { | |
| 1380 var dx = Math.abs(x - touch.x), dy = Math.abs(y - touch.y); | |
| 1381 return (dx <= DEDUP_DIST && dy <= DEDUP_DIST); | |
| 1382 }; | |
| 1383 // if click coordinates are close to touch coordinates, assume the click cam
e from a touch | |
| 1384 var wasTouched = scope.mouseEvents.lastTouches.some(closeTo); | |
| 1385 // if the click came from touch, and the touchstart target is not in the pat
h of the click event, | |
| 1386 // then the touchstart target was probably removed, and the click should be
"busted" | |
| 1387 var path = scope.targetFinding.path(ev); | |
| 1388 if (wasTouched) { | |
| 1389 for (var i = 0; i < path.length; i++) { | |
| 1390 if (path[i] === touchEvents.firstTarget) { | |
| 1391 return; | |
| 1392 } | |
| 1393 } | |
| 1394 ev.preventDefault(); | |
| 1395 STOP_PROP_FN.call(ev); | |
| 1396 } | |
| 1397 }, true); | |
| 1398 | |
| 1399 scope.touchEvents = touchEvents; | |
| 1400 })(window.PolymerGestures); | |
| 1401 | |
| 1402 (function(scope) { | |
| 1403 var dispatcher = scope.dispatcher; | |
| 1404 var pointermap = dispatcher.pointermap; | |
| 1405 var HAS_BITMAP_TYPE = window.MSPointerEvent && typeof window.MSPointerEvent.MS
POINTER_TYPE_MOUSE === 'number'; | |
| 1406 var msEvents = { | |
| 1407 events: [ | |
| 1408 'MSPointerDown', | |
| 1409 'MSPointerMove', | |
| 1410 'MSPointerUp', | |
| 1411 'MSPointerCancel', | |
| 1412 ], | |
| 1413 register: function(target) { | |
| 1414 dispatcher.listen(target, this.events); | |
| 1415 }, | |
| 1416 unregister: function(target) { | |
| 1417 if (target === document) { | |
| 1418 return; | |
| 1419 } | |
| 1420 dispatcher.unlisten(target, this.events); | |
| 1421 }, | |
| 1422 POINTER_TYPES: [ | |
| 1423 '', | |
| 1424 'unavailable', | |
| 1425 'touch', | |
| 1426 'pen', | |
| 1427 'mouse' | |
| 1428 ], | |
| 1429 prepareEvent: function(inEvent) { | |
| 1430 var e = inEvent; | |
| 1431 e = dispatcher.cloneEvent(inEvent); | |
| 1432 if (HAS_BITMAP_TYPE) { | |
| 1433 e.pointerType = this.POINTER_TYPES[inEvent.pointerType]; | |
| 1434 } | |
| 1435 e._source = 'ms'; | |
| 1436 return e; | |
| 1437 }, | |
| 1438 cleanup: function(id) { | |
| 1439 pointermap['delete'](id); | |
| 1440 }, | |
| 1441 MSPointerDown: function(inEvent) { | |
| 1442 var e = this.prepareEvent(inEvent); | |
| 1443 e.target = scope.findTarget(inEvent); | |
| 1444 pointermap.set(inEvent.pointerId, e.target); | |
| 1445 dispatcher.down(e); | |
| 1446 }, | |
| 1447 MSPointerMove: function(inEvent) { | |
| 1448 var target = pointermap.get(inEvent.pointerId); | |
| 1449 if (target) { | |
| 1450 var e = this.prepareEvent(inEvent); | |
| 1451 e.target = target; | |
| 1452 dispatcher.move(e); | |
| 1453 } | |
| 1454 }, | |
| 1455 MSPointerUp: function(inEvent) { | |
| 1456 var e = this.prepareEvent(inEvent); | |
| 1457 e.relatedTarget = scope.findTarget(inEvent); | |
| 1458 e.target = pointermap.get(e.pointerId); | |
| 1459 dispatcher.up(e); | |
| 1460 this.cleanup(inEvent.pointerId); | |
| 1461 }, | |
| 1462 MSPointerCancel: function(inEvent) { | |
| 1463 var e = this.prepareEvent(inEvent); | |
| 1464 e.relatedTarget = scope.findTarget(inEvent); | |
| 1465 e.target = pointermap.get(e.pointerId); | |
| 1466 dispatcher.cancel(e); | |
| 1467 this.cleanup(inEvent.pointerId); | |
| 1468 } | |
| 1469 }; | |
| 1470 | |
| 1471 scope.msEvents = msEvents; | |
| 1472 })(window.PolymerGestures); | |
| 1473 | |
| 1474 (function(scope) { | |
| 1475 var dispatcher = scope.dispatcher; | |
| 1476 var pointermap = dispatcher.pointermap; | |
| 1477 var pointerEvents = { | |
| 1478 events: [ | |
| 1479 'pointerdown', | |
| 1480 'pointermove', | |
| 1481 'pointerup', | |
| 1482 'pointercancel' | |
| 1483 ], | |
| 1484 prepareEvent: function(inEvent) { | |
| 1485 var e = dispatcher.cloneEvent(inEvent); | |
| 1486 e._source = 'pointer'; | |
| 1487 return e; | |
| 1488 }, | |
| 1489 register: function(target) { | |
| 1490 dispatcher.listen(target, this.events); | |
| 1491 }, | |
| 1492 unregister: function(target) { | |
| 1493 if (target === document) { | |
| 1494 return; | |
| 1495 } | |
| 1496 dispatcher.unlisten(target, this.events); | |
| 1497 }, | |
| 1498 cleanup: function(id) { | |
| 1499 pointermap['delete'](id); | |
| 1500 }, | |
| 1501 pointerdown: function(inEvent) { | |
| 1502 var e = this.prepareEvent(inEvent); | |
| 1503 e.target = scope.findTarget(inEvent); | |
| 1504 pointermap.set(e.pointerId, e.target); | |
| 1505 dispatcher.down(e); | |
| 1506 }, | |
| 1507 pointermove: function(inEvent) { | |
| 1508 var target = pointermap.get(inEvent.pointerId); | |
| 1509 if (target) { | |
| 1510 var e = this.prepareEvent(inEvent); | |
| 1511 e.target = target; | |
| 1512 dispatcher.move(e); | |
| 1513 } | |
| 1514 }, | |
| 1515 pointerup: function(inEvent) { | |
| 1516 var e = this.prepareEvent(inEvent); | |
| 1517 e.relatedTarget = scope.findTarget(inEvent); | |
| 1518 e.target = pointermap.get(e.pointerId); | |
| 1519 dispatcher.up(e); | |
| 1520 this.cleanup(inEvent.pointerId); | |
| 1521 }, | |
| 1522 pointercancel: function(inEvent) { | |
| 1523 var e = this.prepareEvent(inEvent); | |
| 1524 e.relatedTarget = scope.findTarget(inEvent); | |
| 1525 e.target = pointermap.get(e.pointerId); | |
| 1526 dispatcher.cancel(e); | |
| 1527 this.cleanup(inEvent.pointerId); | |
| 1528 } | |
| 1529 }; | |
| 1530 | |
| 1531 scope.pointerEvents = pointerEvents; | |
| 1532 })(window.PolymerGestures); | |
| 1533 | |
| 1534 /** | |
| 1535 * This module contains the handlers for native platform events. | |
| 1536 * From here, the dispatcher is called to create unified pointer events. | |
| 1537 * Included are touch events (v1), mouse events, and MSPointerEvents. | |
| 1538 */ | |
| 1539 (function(scope) { | |
| 1540 | |
| 1541 var dispatcher = scope.dispatcher; | |
| 1542 var nav = window.navigator; | |
| 1543 | |
| 1544 if (window.PointerEvent) { | |
| 1545 dispatcher.registerSource('pointer', scope.pointerEvents); | |
| 1546 } else if (nav.msPointerEnabled) { | |
| 1547 dispatcher.registerSource('ms', scope.msEvents); | |
| 1548 } else { | |
| 1549 dispatcher.registerSource('mouse', scope.mouseEvents); | |
| 1550 if (window.ontouchstart !== undefined) { | |
| 1551 dispatcher.registerSource('touch', scope.touchEvents); | |
| 1552 } | |
| 1553 } | |
| 1554 | |
| 1555 // Work around iOS bugs https://bugs.webkit.org/show_bug.cgi?id=135628 and htt
ps://bugs.webkit.org/show_bug.cgi?id=136506 | |
| 1556 var ua = navigator.userAgent; | |
| 1557 var IS_IOS = ua.match(/iPad|iPhone|iPod/) && 'ontouchstart' in window; | |
| 1558 | |
| 1559 dispatcher.IS_IOS = IS_IOS; | |
| 1560 scope.touchEvents.IS_IOS = IS_IOS; | |
| 1561 | |
| 1562 dispatcher.register(document, true); | |
| 1563 })(window.PolymerGestures); | |
| 1564 | |
| 1565 /** | |
| 1566 * This event denotes the beginning of a series of tracking events. | |
| 1567 * | |
| 1568 * @module PointerGestures | |
| 1569 * @submodule Events | |
| 1570 * @class trackstart | |
| 1571 */ | |
| 1572 /** | |
| 1573 * Pixels moved in the x direction since trackstart. | |
| 1574 * @type Number | |
| 1575 * @property dx | |
| 1576 */ | |
| 1577 /** | |
| 1578 * Pixes moved in the y direction since trackstart. | |
| 1579 * @type Number | |
| 1580 * @property dy | |
| 1581 */ | |
| 1582 /** | |
| 1583 * Pixels moved in the x direction since the last track. | |
| 1584 * @type Number | |
| 1585 * @property ddx | |
| 1586 */ | |
| 1587 /** | |
| 1588 * Pixles moved in the y direction since the last track. | |
| 1589 * @type Number | |
| 1590 * @property ddy | |
| 1591 */ | |
| 1592 /** | |
| 1593 * The clientX position of the track gesture. | |
| 1594 * @type Number | |
| 1595 * @property clientX | |
| 1596 */ | |
| 1597 /** | |
| 1598 * The clientY position of the track gesture. | |
| 1599 * @type Number | |
| 1600 * @property clientY | |
| 1601 */ | |
| 1602 /** | |
| 1603 * The pageX position of the track gesture. | |
| 1604 * @type Number | |
| 1605 * @property pageX | |
| 1606 */ | |
| 1607 /** | |
| 1608 * The pageY position of the track gesture. | |
| 1609 * @type Number | |
| 1610 * @property pageY | |
| 1611 */ | |
| 1612 /** | |
| 1613 * The screenX position of the track gesture. | |
| 1614 * @type Number | |
| 1615 * @property screenX | |
| 1616 */ | |
| 1617 /** | |
| 1618 * The screenY position of the track gesture. | |
| 1619 * @type Number | |
| 1620 * @property screenY | |
| 1621 */ | |
| 1622 /** | |
| 1623 * The last x axis direction of the pointer. | |
| 1624 * @type Number | |
| 1625 * @property xDirection | |
| 1626 */ | |
| 1627 /** | |
| 1628 * The last y axis direction of the pointer. | |
| 1629 * @type Number | |
| 1630 * @property yDirection | |
| 1631 */ | |
| 1632 /** | |
| 1633 * A shared object between all tracking events. | |
| 1634 * @type Object | |
| 1635 * @property trackInfo | |
| 1636 */ | |
| 1637 /** | |
| 1638 * The element currently under the pointer. | |
| 1639 * @type Element | |
| 1640 * @property relatedTarget | |
| 1641 */ | |
| 1642 /** | |
| 1643 * The type of pointer that make the track gesture. | |
| 1644 * @type String | |
| 1645 * @property pointerType | |
| 1646 */ | |
| 1647 /** | |
| 1648 * | |
| 1649 * This event fires for all pointer movement being tracked. | |
| 1650 * | |
| 1651 * @class track | |
| 1652 * @extends trackstart | |
| 1653 */ | |
| 1654 /** | |
| 1655 * This event fires when the pointer is no longer being tracked. | |
| 1656 * | |
| 1657 * @class trackend | |
| 1658 * @extends trackstart | |
| 1659 */ | |
| 1660 | |
| 1661 (function(scope) { | |
| 1662 var dispatcher = scope.dispatcher; | |
| 1663 var eventFactory = scope.eventFactory; | |
| 1664 var pointermap = new scope.PointerMap(); | |
| 1665 var track = { | |
| 1666 events: [ | |
| 1667 'down', | |
| 1668 'move', | |
| 1669 'up', | |
| 1670 ], | |
| 1671 exposes: [ | |
| 1672 'trackstart', | |
| 1673 'track', | |
| 1674 'trackx', | |
| 1675 'tracky', | |
| 1676 'trackend' | |
| 1677 ], | |
| 1678 defaultActions: { | |
| 1679 'track': 'none', | |
| 1680 'trackx': 'pan-y', | |
| 1681 'tracky': 'pan-x' | |
| 1682 }, | |
| 1683 WIGGLE_THRESHOLD: 4, | |
| 1684 clampDir: function(inDelta) { | |
| 1685 return inDelta > 0 ? 1 : -1; | |
| 1686 }, | |
| 1687 calcPositionDelta: function(inA, inB) { | |
| 1688 var x = 0, y = 0; | |
| 1689 if (inA && inB) { | |
| 1690 x = inB.pageX - inA.pageX; | |
| 1691 y = inB.pageY - inA.pageY; | |
| 1692 } | |
| 1693 return {x: x, y: y}; | |
| 1694 }, | |
| 1695 fireTrack: function(inType, inEvent, inTrackingData) { | |
| 1696 var t = inTrackingData; | |
| 1697 var d = this.calcPositionDelta(t.downEvent, inEvent); | |
| 1698 var dd = this.calcPositionDelta(t.lastMoveEvent, inEvent); | |
| 1699 if (dd.x) { | |
| 1700 t.xDirection = this.clampDir(dd.x); | |
| 1701 } else if (inType === 'trackx') { | |
| 1702 return; | |
| 1703 } | |
| 1704 if (dd.y) { | |
| 1705 t.yDirection = this.clampDir(dd.y); | |
| 1706 } else if (inType === 'tracky') { | |
| 1707 return; | |
| 1708 } | |
| 1709 var gestureProto = { | |
| 1710 bubbles: true, | |
| 1711 cancelable: true, | |
| 1712 trackInfo: t.trackInfo, | |
| 1713 relatedTarget: inEvent.relatedTarget, | |
| 1714 pointerType: inEvent.pointerType, | |
| 1715 pointerId: inEvent.pointerId, | |
| 1716 _source: 'track' | |
| 1717 }; | |
| 1718 if (inType !== 'tracky') { | |
| 1719 gestureProto.x = inEvent.x; | |
| 1720 gestureProto.dx = d.x; | |
| 1721 gestureProto.ddx = dd.x; | |
| 1722 gestureProto.clientX = inEvent.clientX; | |
| 1723 gestureProto.pageX = inEvent.pageX; | |
| 1724 gestureProto.screenX = inEvent.screenX; | |
| 1725 gestureProto.xDirection = t.xDirection; | |
| 1726 } | |
| 1727 if (inType !== 'trackx') { | |
| 1728 gestureProto.dy = d.y; | |
| 1729 gestureProto.ddy = dd.y; | |
| 1730 gestureProto.y = inEvent.y; | |
| 1731 gestureProto.clientY = inEvent.clientY; | |
| 1732 gestureProto.pageY = inEvent.pageY; | |
| 1733 gestureProto.screenY = inEvent.screenY; | |
| 1734 gestureProto.yDirection = t.yDirection; | |
| 1735 } | |
| 1736 var e = eventFactory.makeGestureEvent(inType, gestureProto); | |
| 1737 t.downTarget.dispatchEvent(e); | |
| 1738 }, | |
| 1739 down: function(inEvent) { | |
| 1740 if (inEvent.isPrimary && (inEvent.pointerType === 'mouse' ? inEvent.butto
ns === 1 : true)) { | |
| 1741 var p = { | |
| 1742 downEvent: inEvent, | |
| 1743 downTarget: inEvent.target, | |
| 1744 trackInfo: {}, | |
| 1745 lastMoveEvent: null, | |
| 1746 xDirection: 0, | |
| 1747 yDirection: 0, | |
| 1748 tracking: false | |
| 1749 }; | |
| 1750 pointermap.set(inEvent.pointerId, p); | |
| 1751 } | |
| 1752 }, | |
| 1753 move: function(inEvent) { | |
| 1754 var p = pointermap.get(inEvent.pointerId); | |
| 1755 if (p) { | |
| 1756 if (!p.tracking) { | |
| 1757 var d = this.calcPositionDelta(p.downEvent, inEvent); | |
| 1758 var move = d.x * d.x + d.y * d.y; | |
| 1759 // start tracking only if finger moves more than WIGGLE_THRESHOLD | |
| 1760 if (move > this.WIGGLE_THRESHOLD) { | |
| 1761 p.tracking = true; | |
| 1762 p.lastMoveEvent = p.downEvent; | |
| 1763 this.fireTrack('trackstart', inEvent, p); | |
| 1764 } | |
| 1765 } | |
| 1766 if (p.tracking) { | |
| 1767 this.fireTrack('track', inEvent, p); | |
| 1768 this.fireTrack('trackx', inEvent, p); | |
| 1769 this.fireTrack('tracky', inEvent, p); | |
| 1770 } | |
| 1771 p.lastMoveEvent = inEvent; | |
| 1772 } | |
| 1773 }, | |
| 1774 up: function(inEvent) { | |
| 1775 var p = pointermap.get(inEvent.pointerId); | |
| 1776 if (p) { | |
| 1777 if (p.tracking) { | |
| 1778 this.fireTrack('trackend', inEvent, p); | |
| 1779 } | |
| 1780 pointermap.delete(inEvent.pointerId); | |
| 1781 } | |
| 1782 } | |
| 1783 }; | |
| 1784 dispatcher.registerGesture('track', track); | |
| 1785 })(window.PolymerGestures); | |
| 1786 | |
| 1787 /** | |
| 1788 * This event is fired when a pointer is held down for 200ms. | |
| 1789 * | |
| 1790 * @module PointerGestures | |
| 1791 * @submodule Events | |
| 1792 * @class hold | |
| 1793 */ | |
| 1794 /** | |
| 1795 * Type of pointer that made the holding event. | |
| 1796 * @type String | |
| 1797 * @property pointerType | |
| 1798 */ | |
| 1799 /** | |
| 1800 * Screen X axis position of the held pointer | |
| 1801 * @type Number | |
| 1802 * @property clientX | |
| 1803 */ | |
| 1804 /** | |
| 1805 * Screen Y axis position of the held pointer | |
| 1806 * @type Number | |
| 1807 * @property clientY | |
| 1808 */ | |
| 1809 /** | |
| 1810 * Type of pointer that made the holding event. | |
| 1811 * @type String | |
| 1812 * @property pointerType | |
| 1813 */ | |
| 1814 /** | |
| 1815 * This event is fired every 200ms while a pointer is held down. | |
| 1816 * | |
| 1817 * @class holdpulse | |
| 1818 * @extends hold | |
| 1819 */ | |
| 1820 /** | |
| 1821 * Milliseconds pointer has been held down. | |
| 1822 * @type Number | |
| 1823 * @property holdTime | |
| 1824 */ | |
| 1825 /** | |
| 1826 * This event is fired when a held pointer is released or moved. | |
| 1827 * | |
| 1828 * @class release | |
| 1829 */ | |
| 1830 | |
| 1831 (function(scope) { | |
| 1832 var dispatcher = scope.dispatcher; | |
| 1833 var eventFactory = scope.eventFactory; | |
| 1834 var hold = { | |
| 1835 // wait at least HOLD_DELAY ms between hold and pulse events | |
| 1836 HOLD_DELAY: 200, | |
| 1837 // pointer can move WIGGLE_THRESHOLD pixels before not counting as a hold | |
| 1838 WIGGLE_THRESHOLD: 16, | |
| 1839 events: [ | |
| 1840 'down', | |
| 1841 'move', | |
| 1842 'up', | |
| 1843 ], | |
| 1844 exposes: [ | |
| 1845 'hold', | |
| 1846 'holdpulse', | |
| 1847 'release' | |
| 1848 ], | |
| 1849 heldPointer: null, | |
| 1850 holdJob: null, | |
| 1851 pulse: function() { | |
| 1852 var hold = Date.now() - this.heldPointer.timeStamp; | |
| 1853 var type = this.held ? 'holdpulse' : 'hold'; | |
| 1854 this.fireHold(type, hold); | |
| 1855 this.held = true; | |
| 1856 }, | |
| 1857 cancel: function() { | |
| 1858 clearInterval(this.holdJob); | |
| 1859 if (this.held) { | |
| 1860 this.fireHold('release'); | |
| 1861 } | |
| 1862 this.held = false; | |
| 1863 this.heldPointer = null; | |
| 1864 this.target = null; | |
| 1865 this.holdJob = null; | |
| 1866 }, | |
| 1867 down: function(inEvent) { | |
| 1868 if (inEvent.isPrimary && !this.heldPointer) { | |
| 1869 this.heldPointer = inEvent; | |
| 1870 this.target = inEvent.target; | |
| 1871 this.holdJob = setInterval(this.pulse.bind(this), this.HOLD_DELAY); | |
| 1872 } | |
| 1873 }, | |
| 1874 up: function(inEvent) { | |
| 1875 if (this.heldPointer && this.heldPointer.pointerId === inEvent.pointerId)
{ | |
| 1876 this.cancel(); | |
| 1877 } | |
| 1878 }, | |
| 1879 move: function(inEvent) { | |
| 1880 if (this.heldPointer && this.heldPointer.pointerId === inEvent.pointerId)
{ | |
| 1881 var x = inEvent.clientX - this.heldPointer.clientX; | |
| 1882 var y = inEvent.clientY - this.heldPointer.clientY; | |
| 1883 if ((x * x + y * y) > this.WIGGLE_THRESHOLD) { | |
| 1884 this.cancel(); | |
| 1885 } | |
| 1886 } | |
| 1887 }, | |
| 1888 fireHold: function(inType, inHoldTime) { | |
| 1889 var p = { | |
| 1890 bubbles: true, | |
| 1891 cancelable: true, | |
| 1892 pointerType: this.heldPointer.pointerType, | |
| 1893 pointerId: this.heldPointer.pointerId, | |
| 1894 x: this.heldPointer.clientX, | |
| 1895 y: this.heldPointer.clientY, | |
| 1896 _source: 'hold' | |
| 1897 }; | |
| 1898 if (inHoldTime) { | |
| 1899 p.holdTime = inHoldTime; | |
| 1900 } | |
| 1901 var e = eventFactory.makeGestureEvent(inType, p); | |
| 1902 this.target.dispatchEvent(e); | |
| 1903 } | |
| 1904 }; | |
| 1905 dispatcher.registerGesture('hold', hold); | |
| 1906 })(window.PolymerGestures); | |
| 1907 | |
| 1908 /** | |
| 1909 * This event is fired when a pointer quickly goes down and up, and is used to | |
| 1910 * denote activation. | |
| 1911 * | |
| 1912 * Any gesture event can prevent the tap event from being created by calling | |
| 1913 * `event.preventTap`. | |
| 1914 * | |
| 1915 * Any pointer event can prevent the tap by setting the `tapPrevented` property | |
| 1916 * on itself. | |
| 1917 * | |
| 1918 * @module PointerGestures | |
| 1919 * @submodule Events | |
| 1920 * @class tap | |
| 1921 */ | |
| 1922 /** | |
| 1923 * X axis position of the tap. | |
| 1924 * @property x | |
| 1925 * @type Number | |
| 1926 */ | |
| 1927 /** | |
| 1928 * Y axis position of the tap. | |
| 1929 * @property y | |
| 1930 * @type Number | |
| 1931 */ | |
| 1932 /** | |
| 1933 * Type of the pointer that made the tap. | |
| 1934 * @property pointerType | |
| 1935 * @type String | |
| 1936 */ | |
| 1937 (function(scope) { | |
| 1938 var dispatcher = scope.dispatcher; | |
| 1939 var eventFactory = scope.eventFactory; | |
| 1940 var pointermap = new scope.PointerMap(); | |
| 1941 var tap = { | |
| 1942 events: [ | |
| 1943 'down', | |
| 1944 'up' | |
| 1945 ], | |
| 1946 exposes: [ | |
| 1947 'tap' | |
| 1948 ], | |
| 1949 down: function(inEvent) { | |
| 1950 if (inEvent.isPrimary && !inEvent.tapPrevented) { | |
| 1951 pointermap.set(inEvent.pointerId, { | |
| 1952 target: inEvent.target, | |
| 1953 buttons: inEvent.buttons, | |
| 1954 x: inEvent.clientX, | |
| 1955 y: inEvent.clientY | |
| 1956 }); | |
| 1957 } | |
| 1958 }, | |
| 1959 shouldTap: function(e, downState) { | |
| 1960 var tap = true; | |
| 1961 if (e.pointerType === 'mouse') { | |
| 1962 // only allow left click to tap for mouse | |
| 1963 tap = (e.buttons ^ 1) && (downState.buttons & 1); | |
| 1964 } | |
| 1965 return tap && !e.tapPrevented; | |
| 1966 }, | |
| 1967 up: function(inEvent) { | |
| 1968 var start = pointermap.get(inEvent.pointerId); | |
| 1969 if (start && this.shouldTap(inEvent, start)) { | |
| 1970 // up.relatedTarget is target currently under finger | |
| 1971 var t = scope.targetFinding.LCA(start.target, inEvent.relatedTarget); | |
| 1972 if (t) { | |
| 1973 var e = eventFactory.makeGestureEvent('tap', { | |
| 1974 bubbles: true, | |
| 1975 cancelable: true, | |
| 1976 x: inEvent.clientX, | |
| 1977 y: inEvent.clientY, | |
| 1978 detail: inEvent.detail, | |
| 1979 pointerType: inEvent.pointerType, | |
| 1980 pointerId: inEvent.pointerId, | |
| 1981 altKey: inEvent.altKey, | |
| 1982 ctrlKey: inEvent.ctrlKey, | |
| 1983 metaKey: inEvent.metaKey, | |
| 1984 shiftKey: inEvent.shiftKey, | |
| 1985 _source: 'tap' | |
| 1986 }); | |
| 1987 t.dispatchEvent(e); | |
| 1988 } | |
| 1989 } | |
| 1990 pointermap.delete(inEvent.pointerId); | |
| 1991 } | |
| 1992 }; | |
| 1993 // patch eventFactory to remove id from tap's pointermap for preventTap calls | |
| 1994 eventFactory.preventTap = function(e) { | |
| 1995 return function() { | |
| 1996 e.tapPrevented = true; | |
| 1997 pointermap.delete(e.pointerId); | |
| 1998 }; | |
| 1999 }; | |
| 2000 dispatcher.registerGesture('tap', tap); | |
| 2001 })(window.PolymerGestures); | |
| 2002 | |
| 2003 /* | |
| 2004 * Basic strategy: find the farthest apart points, use as diameter of circle | |
| 2005 * react to size change and rotation of the chord | |
| 2006 */ | |
| 2007 | |
| 2008 /** | |
| 2009 * @module pointer-gestures | |
| 2010 * @submodule Events | |
| 2011 * @class pinch | |
| 2012 */ | |
| 2013 /** | |
| 2014 * Scale of the pinch zoom gesture | |
| 2015 * @property scale | |
| 2016 * @type Number | |
| 2017 */ | |
| 2018 /** | |
| 2019 * Center X position of pointers causing pinch | |
| 2020 * @property centerX | |
| 2021 * @type Number | |
| 2022 */ | |
| 2023 /** | |
| 2024 * Center Y position of pointers causing pinch | |
| 2025 * @property centerY | |
| 2026 * @type Number | |
| 2027 */ | |
| 2028 | |
| 2029 /** | |
| 2030 * @module pointer-gestures | |
| 2031 * @submodule Events | |
| 2032 * @class rotate | |
| 2033 */ | |
| 2034 /** | |
| 2035 * Angle (in degrees) of rotation. Measured from starting positions of pointers. | |
| 2036 * @property angle | |
| 2037 * @type Number | |
| 2038 */ | |
| 2039 /** | |
| 2040 * Center X position of pointers causing rotation | |
| 2041 * @property centerX | |
| 2042 * @type Number | |
| 2043 */ | |
| 2044 /** | |
| 2045 * Center Y position of pointers causing rotation | |
| 2046 * @property centerY | |
| 2047 * @type Number | |
| 2048 */ | |
| 2049 (function(scope) { | |
| 2050 var dispatcher = scope.dispatcher; | |
| 2051 var eventFactory = scope.eventFactory; | |
| 2052 var pointermap = new scope.PointerMap(); | |
| 2053 var RAD_TO_DEG = 180 / Math.PI; | |
| 2054 var pinch = { | |
| 2055 events: [ | |
| 2056 'down', | |
| 2057 'up', | |
| 2058 'move', | |
| 2059 'cancel' | |
| 2060 ], | |
| 2061 exposes: [ | |
| 2062 'pinchstart', | |
| 2063 'pinch', | |
| 2064 'pinchend', | |
| 2065 'rotate' | |
| 2066 ], | |
| 2067 defaultActions: { | |
| 2068 'pinch': 'none', | |
| 2069 'rotate': 'none' | |
| 2070 }, | |
| 2071 reference: {}, | |
| 2072 down: function(inEvent) { | |
| 2073 pointermap.set(inEvent.pointerId, inEvent); | |
| 2074 if (pointermap.pointers() == 2) { | |
| 2075 var points = this.calcChord(); | |
| 2076 var angle = this.calcAngle(points); | |
| 2077 this.reference = { | |
| 2078 angle: angle, | |
| 2079 diameter: points.diameter, | |
| 2080 target: scope.targetFinding.LCA(points.a.target, points.b.target) | |
| 2081 }; | |
| 2082 | |
| 2083 this.firePinch('pinchstart', points.diameter, points); | |
| 2084 } | |
| 2085 }, | |
| 2086 up: function(inEvent) { | |
| 2087 var p = pointermap.get(inEvent.pointerId); | |
| 2088 var num = pointermap.pointers(); | |
| 2089 if (p) { | |
| 2090 if (num === 2) { | |
| 2091 // fire 'pinchend' before deleting pointer | |
| 2092 var points = this.calcChord(); | |
| 2093 this.firePinch('pinchend', points.diameter, points); | |
| 2094 } | |
| 2095 pointermap.delete(inEvent.pointerId); | |
| 2096 } | |
| 2097 }, | |
| 2098 move: function(inEvent) { | |
| 2099 if (pointermap.has(inEvent.pointerId)) { | |
| 2100 pointermap.set(inEvent.pointerId, inEvent); | |
| 2101 if (pointermap.pointers() > 1) { | |
| 2102 this.calcPinchRotate(); | |
| 2103 } | |
| 2104 } | |
| 2105 }, | |
| 2106 cancel: function(inEvent) { | |
| 2107 this.up(inEvent); | |
| 2108 }, | |
| 2109 firePinch: function(type, diameter, points) { | |
| 2110 var zoom = diameter / this.reference.diameter; | |
| 2111 var e = eventFactory.makeGestureEvent(type, { | |
| 2112 bubbles: true, | |
| 2113 cancelable: true, | |
| 2114 scale: zoom, | |
| 2115 centerX: points.center.x, | |
| 2116 centerY: points.center.y, | |
| 2117 _source: 'pinch' | |
| 2118 }); | |
| 2119 this.reference.target.dispatchEvent(e); | |
| 2120 }, | |
| 2121 fireRotate: function(angle, points) { | |
| 2122 var diff = Math.round((angle - this.reference.angle) % 360); | |
| 2123 var e = eventFactory.makeGestureEvent('rotate', { | |
| 2124 bubbles: true, | |
| 2125 cancelable: true, | |
| 2126 angle: diff, | |
| 2127 centerX: points.center.x, | |
| 2128 centerY: points.center.y, | |
| 2129 _source: 'pinch' | |
| 2130 }); | |
| 2131 this.reference.target.dispatchEvent(e); | |
| 2132 }, | |
| 2133 calcPinchRotate: function() { | |
| 2134 var points = this.calcChord(); | |
| 2135 var diameter = points.diameter; | |
| 2136 var angle = this.calcAngle(points); | |
| 2137 if (diameter != this.reference.diameter) { | |
| 2138 this.firePinch('pinch', diameter, points); | |
| 2139 } | |
| 2140 if (angle != this.reference.angle) { | |
| 2141 this.fireRotate(angle, points); | |
| 2142 } | |
| 2143 }, | |
| 2144 calcChord: function() { | |
| 2145 var pointers = []; | |
| 2146 pointermap.forEach(function(p) { | |
| 2147 pointers.push(p); | |
| 2148 }); | |
| 2149 var dist = 0; | |
| 2150 // start with at least two pointers | |
| 2151 var points = {a: pointers[0], b: pointers[1]}; | |
| 2152 var x, y, d; | |
| 2153 for (var i = 0; i < pointers.length; i++) { | |
| 2154 var a = pointers[i]; | |
| 2155 for (var j = i + 1; j < pointers.length; j++) { | |
| 2156 var b = pointers[j]; | |
| 2157 x = Math.abs(a.clientX - b.clientX); | |
| 2158 y = Math.abs(a.clientY - b.clientY); | |
| 2159 d = x + y; | |
| 2160 if (d > dist) { | |
| 2161 dist = d; | |
| 2162 points = {a: a, b: b}; | |
| 2163 } | |
| 2164 } | |
| 2165 } | |
| 2166 x = Math.abs(points.a.clientX + points.b.clientX) / 2; | |
| 2167 y = Math.abs(points.a.clientY + points.b.clientY) / 2; | |
| 2168 points.center = { x: x, y: y }; | |
| 2169 points.diameter = dist; | |
| 2170 return points; | |
| 2171 }, | |
| 2172 calcAngle: function(points) { | |
| 2173 var x = points.a.clientX - points.b.clientX; | |
| 2174 var y = points.a.clientY - points.b.clientY; | |
| 2175 return (360 + Math.atan2(y, x) * RAD_TO_DEG) % 360; | |
| 2176 } | |
| 2177 }; | |
| 2178 dispatcher.registerGesture('pinch', pinch); | |
| 2179 })(window.PolymerGestures); | |
| 2180 | |
| 2181 (function (global) { | |
| 2182 'use strict'; | |
| 2183 | |
| 2184 var Token, | |
| 2185 TokenName, | |
| 2186 Syntax, | |
| 2187 Messages, | |
| 2188 source, | |
| 2189 index, | |
| 2190 length, | |
| 2191 delegate, | |
| 2192 lookahead, | |
| 2193 state; | |
| 2194 | |
| 2195 Token = { | |
| 2196 BooleanLiteral: 1, | |
| 2197 EOF: 2, | |
| 2198 Identifier: 3, | |
| 2199 Keyword: 4, | |
| 2200 NullLiteral: 5, | |
| 2201 NumericLiteral: 6, | |
| 2202 Punctuator: 7, | |
| 2203 StringLiteral: 8 | |
| 2204 }; | |
| 2205 | |
| 2206 TokenName = {}; | |
| 2207 TokenName[Token.BooleanLiteral] = 'Boolean'; | |
| 2208 TokenName[Token.EOF] = '<end>'; | |
| 2209 TokenName[Token.Identifier] = 'Identifier'; | |
| 2210 TokenName[Token.Keyword] = 'Keyword'; | |
| 2211 TokenName[Token.NullLiteral] = 'Null'; | |
| 2212 TokenName[Token.NumericLiteral] = 'Numeric'; | |
| 2213 TokenName[Token.Punctuator] = 'Punctuator'; | |
| 2214 TokenName[Token.StringLiteral] = 'String'; | |
| 2215 | |
| 2216 Syntax = { | |
| 2217 ArrayExpression: 'ArrayExpression', | |
| 2218 BinaryExpression: 'BinaryExpression', | |
| 2219 CallExpression: 'CallExpression', | |
| 2220 ConditionalExpression: 'ConditionalExpression', | |
| 2221 EmptyStatement: 'EmptyStatement', | |
| 2222 ExpressionStatement: 'ExpressionStatement', | |
| 2223 Identifier: 'Identifier', | |
| 2224 Literal: 'Literal', | |
| 2225 LabeledStatement: 'LabeledStatement', | |
| 2226 LogicalExpression: 'LogicalExpression', | |
| 2227 MemberExpression: 'MemberExpression', | |
| 2228 ObjectExpression: 'ObjectExpression', | |
| 2229 Program: 'Program', | |
| 2230 Property: 'Property', | |
| 2231 ThisExpression: 'ThisExpression', | |
| 2232 UnaryExpression: 'UnaryExpression' | |
| 2233 }; | |
| 2234 | |
| 2235 // Error messages should be identical to V8. | |
| 2236 Messages = { | |
| 2237 UnexpectedToken: 'Unexpected token %0', | |
| 2238 UnknownLabel: 'Undefined label \'%0\'', | |
| 2239 Redeclaration: '%0 \'%1\' has already been declared' | |
| 2240 }; | |
| 2241 | |
| 2242 // Ensure the condition is true, otherwise throw an error. | |
| 2243 // This is only to have a better contract semantic, i.e. another safety net | |
| 2244 // to catch a logic error. The condition shall be fulfilled in normal case. | |
| 2245 // Do NOT use this to enforce a certain condition on any user input. | |
| 2246 | |
| 2247 function assert(condition, message) { | |
| 2248 if (!condition) { | |
| 2249 throw new Error('ASSERT: ' + message); | |
| 2250 } | |
| 2251 } | |
| 2252 | |
| 2253 function isDecimalDigit(ch) { | |
| 2254 return (ch >= 48 && ch <= 57); // 0..9 | |
| 2255 } | |
| 2256 | |
| 2257 | |
| 2258 // 7.2 White Space | |
| 2259 | |
| 2260 function isWhiteSpace(ch) { | |
| 2261 return (ch === 32) || // space | |
| 2262 (ch === 9) || // tab | |
| 2263 (ch === 0xB) || | |
| 2264 (ch === 0xC) || | |
| 2265 (ch === 0xA0) || | |
| 2266 (ch >= 0x1680 && '\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u
2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\uFEFF'.indexOf(String.fromCharCod
e(ch)) > 0); | |
| 2267 } | |
| 2268 | |
| 2269 // 7.3 Line Terminators | |
| 2270 | |
| 2271 function isLineTerminator(ch) { | |
| 2272 return (ch === 10) || (ch === 13) || (ch === 0x2028) || (ch === 0x2029); | |
| 2273 } | |
| 2274 | |
| 2275 // 7.6 Identifier Names and Identifiers | |
| 2276 | |
| 2277 function isIdentifierStart(ch) { | |
| 2278 return (ch === 36) || (ch === 95) || // $ (dollar) and _ (underscore) | |
| 2279 (ch >= 65 && ch <= 90) || // A..Z | |
| 2280 (ch >= 97 && ch <= 122); // a..z | |
| 2281 } | |
| 2282 | |
| 2283 function isIdentifierPart(ch) { | |
| 2284 return (ch === 36) || (ch === 95) || // $ (dollar) and _ (underscore) | |
| 2285 (ch >= 65 && ch <= 90) || // A..Z | |
| 2286 (ch >= 97 && ch <= 122) || // a..z | |
| 2287 (ch >= 48 && ch <= 57); // 0..9 | |
| 2288 } | |
| 2289 | |
| 2290 // 7.6.1.1 Keywords | |
| 2291 | |
| 2292 function isKeyword(id) { | |
| 2293 return (id === 'this') | |
| 2294 } | |
| 2295 | |
| 2296 // 7.4 Comments | |
| 2297 | |
| 2298 function skipWhitespace() { | |
| 2299 while (index < length && isWhiteSpace(source.charCodeAt(index))) { | |
| 2300 ++index; | |
| 2301 } | |
| 2302 } | |
| 2303 | |
| 2304 function getIdentifier() { | |
| 2305 var start, ch; | |
| 2306 | |
| 2307 start = index++; | |
| 2308 while (index < length) { | |
| 2309 ch = source.charCodeAt(index); | |
| 2310 if (isIdentifierPart(ch)) { | |
| 2311 ++index; | |
| 2312 } else { | |
| 2313 break; | |
| 2314 } | |
| 2315 } | |
| 2316 | |
| 2317 return source.slice(start, index); | |
| 2318 } | |
| 2319 | |
| 2320 function scanIdentifier() { | |
| 2321 var start, id, type; | |
| 2322 | |
| 2323 start = index; | |
| 2324 | |
| 2325 id = getIdentifier(); | |
| 2326 | |
| 2327 // There is no keyword or literal with only one character. | |
| 2328 // Thus, it must be an identifier. | |
| 2329 if (id.length === 1) { | |
| 2330 type = Token.Identifier; | |
| 2331 } else if (isKeyword(id)) { | |
| 2332 type = Token.Keyword; | |
| 2333 } else if (id === 'null') { | |
| 2334 type = Token.NullLiteral; | |
| 2335 } else if (id === 'true' || id === 'false') { | |
| 2336 type = Token.BooleanLiteral; | |
| 2337 } else { | |
| 2338 type = Token.Identifier; | |
| 2339 } | |
| 2340 | |
| 2341 return { | |
| 2342 type: type, | |
| 2343 value: id, | |
| 2344 range: [start, index] | |
| 2345 }; | |
| 2346 } | |
| 2347 | |
| 2348 | |
| 2349 // 7.7 Punctuators | |
| 2350 | |
| 2351 function scanPunctuator() { | |
| 2352 var start = index, | |
| 2353 code = source.charCodeAt(index), | |
| 2354 code2, | |
| 2355 ch1 = source[index], | |
| 2356 ch2; | |
| 2357 | |
| 2358 switch (code) { | |
| 2359 | |
| 2360 // Check for most common single-character punctuators. | |
| 2361 case 46: // . dot | |
| 2362 case 40: // ( open bracket | |
| 2363 case 41: // ) close bracket | |
| 2364 case 59: // ; semicolon | |
| 2365 case 44: // , comma | |
| 2366 case 123: // { open curly brace | |
| 2367 case 125: // } close curly brace | |
| 2368 case 91: // [ | |
| 2369 case 93: // ] | |
| 2370 case 58: // : | |
| 2371 case 63: // ? | |
| 2372 ++index; | |
| 2373 return { | |
| 2374 type: Token.Punctuator, | |
| 2375 value: String.fromCharCode(code), | |
| 2376 range: [start, index] | |
| 2377 }; | |
| 2378 | |
| 2379 default: | |
| 2380 code2 = source.charCodeAt(index + 1); | |
| 2381 | |
| 2382 // '=' (char #61) marks an assignment or comparison operator. | |
| 2383 if (code2 === 61) { | |
| 2384 switch (code) { | |
| 2385 case 37: // % | |
| 2386 case 38: // & | |
| 2387 case 42: // *: | |
| 2388 case 43: // + | |
| 2389 case 45: // - | |
| 2390 case 47: // / | |
| 2391 case 60: // < | |
| 2392 case 62: // > | |
| 2393 case 124: // | | |
| 2394 index += 2; | |
| 2395 return { | |
| 2396 type: Token.Punctuator, | |
| 2397 value: String.fromCharCode(code) + String.fromCharCode(c
ode2), | |
| 2398 range: [start, index] | |
| 2399 }; | |
| 2400 | |
| 2401 case 33: // ! | |
| 2402 case 61: // = | |
| 2403 index += 2; | |
| 2404 | |
| 2405 // !== and === | |
| 2406 if (source.charCodeAt(index) === 61) { | |
| 2407 ++index; | |
| 2408 } | |
| 2409 return { | |
| 2410 type: Token.Punctuator, | |
| 2411 value: source.slice(start, index), | |
| 2412 range: [start, index] | |
| 2413 }; | |
| 2414 default: | |
| 2415 break; | |
| 2416 } | |
| 2417 } | |
| 2418 break; | |
| 2419 } | |
| 2420 | |
| 2421 // Peek more characters. | |
| 2422 | |
| 2423 ch2 = source[index + 1]; | |
| 2424 | |
| 2425 // Other 2-character punctuators: && || | |
| 2426 | |
| 2427 if (ch1 === ch2 && ('&|'.indexOf(ch1) >= 0)) { | |
| 2428 index += 2; | |
| 2429 return { | |
| 2430 type: Token.Punctuator, | |
| 2431 value: ch1 + ch2, | |
| 2432 range: [start, index] | |
| 2433 }; | |
| 2434 } | |
| 2435 | |
| 2436 if ('<>=!+-*%&|^/'.indexOf(ch1) >= 0) { | |
| 2437 ++index; | |
| 2438 return { | |
| 2439 type: Token.Punctuator, | |
| 2440 value: ch1, | |
| 2441 range: [start, index] | |
| 2442 }; | |
| 2443 } | |
| 2444 | |
| 2445 throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); | |
| 2446 } | |
| 2447 | |
| 2448 // 7.8.3 Numeric Literals | |
| 2449 function scanNumericLiteral() { | |
| 2450 var number, start, ch; | |
| 2451 | |
| 2452 ch = source[index]; | |
| 2453 assert(isDecimalDigit(ch.charCodeAt(0)) || (ch === '.'), | |
| 2454 'Numeric literal must start with a decimal digit or a decimal point'
); | |
| 2455 | |
| 2456 start = index; | |
| 2457 number = ''; | |
| 2458 if (ch !== '.') { | |
| 2459 number = source[index++]; | |
| 2460 ch = source[index]; | |
| 2461 | |
| 2462 // Hex number starts with '0x'. | |
| 2463 // Octal number starts with '0'. | |
| 2464 if (number === '0') { | |
| 2465 // decimal number starts with '0' such as '09' is illegal. | |
| 2466 if (ch && isDecimalDigit(ch.charCodeAt(0))) { | |
| 2467 throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); | |
| 2468 } | |
| 2469 } | |
| 2470 | |
| 2471 while (isDecimalDigit(source.charCodeAt(index))) { | |
| 2472 number += source[index++]; | |
| 2473 } | |
| 2474 ch = source[index]; | |
| 2475 } | |
| 2476 | |
| 2477 if (ch === '.') { | |
| 2478 number += source[index++]; | |
| 2479 while (isDecimalDigit(source.charCodeAt(index))) { | |
| 2480 number += source[index++]; | |
| 2481 } | |
| 2482 ch = source[index]; | |
| 2483 } | |
| 2484 | |
| 2485 if (ch === 'e' || ch === 'E') { | |
| 2486 number += source[index++]; | |
| 2487 | |
| 2488 ch = source[index]; | |
| 2489 if (ch === '+' || ch === '-') { | |
| 2490 number += source[index++]; | |
| 2491 } | |
| 2492 if (isDecimalDigit(source.charCodeAt(index))) { | |
| 2493 while (isDecimalDigit(source.charCodeAt(index))) { | |
| 2494 number += source[index++]; | |
| 2495 } | |
| 2496 } else { | |
| 2497 throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); | |
| 2498 } | |
| 2499 } | |
| 2500 | |
| 2501 if (isIdentifierStart(source.charCodeAt(index))) { | |
| 2502 throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); | |
| 2503 } | |
| 2504 | |
| 2505 return { | |
| 2506 type: Token.NumericLiteral, | |
| 2507 value: parseFloat(number), | |
| 2508 range: [start, index] | |
| 2509 }; | |
| 2510 } | |
| 2511 | |
| 2512 // 7.8.4 String Literals | |
| 2513 | |
| 2514 function scanStringLiteral() { | |
| 2515 var str = '', quote, start, ch, octal = false; | |
| 2516 | |
| 2517 quote = source[index]; | |
| 2518 assert((quote === '\'' || quote === '"'), | |
| 2519 'String literal must starts with a quote'); | |
| 2520 | |
| 2521 start = index; | |
| 2522 ++index; | |
| 2523 | |
| 2524 while (index < length) { | |
| 2525 ch = source[index++]; | |
| 2526 | |
| 2527 if (ch === quote) { | |
| 2528 quote = ''; | |
| 2529 break; | |
| 2530 } else if (ch === '\\') { | |
| 2531 ch = source[index++]; | |
| 2532 if (!ch || !isLineTerminator(ch.charCodeAt(0))) { | |
| 2533 switch (ch) { | |
| 2534 case 'n': | |
| 2535 str += '\n'; | |
| 2536 break; | |
| 2537 case 'r': | |
| 2538 str += '\r'; | |
| 2539 break; | |
| 2540 case 't': | |
| 2541 str += '\t'; | |
| 2542 break; | |
| 2543 case 'b': | |
| 2544 str += '\b'; | |
| 2545 break; | |
| 2546 case 'f': | |
| 2547 str += '\f'; | |
| 2548 break; | |
| 2549 case 'v': | |
| 2550 str += '\x0B'; | |
| 2551 break; | |
| 2552 | |
| 2553 default: | |
| 2554 str += ch; | |
| 2555 break; | |
| 2556 } | |
| 2557 } else { | |
| 2558 if (ch === '\r' && source[index] === '\n') { | |
| 2559 ++index; | |
| 2560 } | |
| 2561 } | |
| 2562 } else if (isLineTerminator(ch.charCodeAt(0))) { | |
| 2563 break; | |
| 2564 } else { | |
| 2565 str += ch; | |
| 2566 } | |
| 2567 } | |
| 2568 | |
| 2569 if (quote !== '') { | |
| 2570 throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); | |
| 2571 } | |
| 2572 | |
| 2573 return { | |
| 2574 type: Token.StringLiteral, | |
| 2575 value: str, | |
| 2576 octal: octal, | |
| 2577 range: [start, index] | |
| 2578 }; | |
| 2579 } | |
| 2580 | |
| 2581 function isIdentifierName(token) { | |
| 2582 return token.type === Token.Identifier || | |
| 2583 token.type === Token.Keyword || | |
| 2584 token.type === Token.BooleanLiteral || | |
| 2585 token.type === Token.NullLiteral; | |
| 2586 } | |
| 2587 | |
| 2588 function advance() { | |
| 2589 var ch; | |
| 2590 | |
| 2591 skipWhitespace(); | |
| 2592 | |
| 2593 if (index >= length) { | |
| 2594 return { | |
| 2595 type: Token.EOF, | |
| 2596 range: [index, index] | |
| 2597 }; | |
| 2598 } | |
| 2599 | |
| 2600 ch = source.charCodeAt(index); | |
| 2601 | |
| 2602 // Very common: ( and ) and ; | |
| 2603 if (ch === 40 || ch === 41 || ch === 58) { | |
| 2604 return scanPunctuator(); | |
| 2605 } | |
| 2606 | |
| 2607 // String literal starts with single quote (#39) or double quote (#34). | |
| 2608 if (ch === 39 || ch === 34) { | |
| 2609 return scanStringLiteral(); | |
| 2610 } | |
| 2611 | |
| 2612 if (isIdentifierStart(ch)) { | |
| 2613 return scanIdentifier(); | |
| 2614 } | |
| 2615 | |
| 2616 // Dot (.) char #46 can also start a floating-point number, hence the ne
ed | |
| 2617 // to check the next character. | |
| 2618 if (ch === 46) { | |
| 2619 if (isDecimalDigit(source.charCodeAt(index + 1))) { | |
| 2620 return scanNumericLiteral(); | |
| 2621 } | |
| 2622 return scanPunctuator(); | |
| 2623 } | |
| 2624 | |
| 2625 if (isDecimalDigit(ch)) { | |
| 2626 return scanNumericLiteral(); | |
| 2627 } | |
| 2628 | |
| 2629 return scanPunctuator(); | |
| 2630 } | |
| 2631 | |
| 2632 function lex() { | |
| 2633 var token; | |
| 2634 | |
| 2635 token = lookahead; | |
| 2636 index = token.range[1]; | |
| 2637 | |
| 2638 lookahead = advance(); | |
| 2639 | |
| 2640 index = token.range[1]; | |
| 2641 | |
| 2642 return token; | |
| 2643 } | |
| 2644 | |
| 2645 function peek() { | |
| 2646 var pos; | |
| 2647 | |
| 2648 pos = index; | |
| 2649 lookahead = advance(); | |
| 2650 index = pos; | |
| 2651 } | |
| 2652 | |
| 2653 // Throw an exception | |
| 2654 | |
| 2655 function throwError(token, messageFormat) { | |
| 2656 var error, | |
| 2657 args = Array.prototype.slice.call(arguments, 2), | |
| 2658 msg = messageFormat.replace( | |
| 2659 /%(\d)/g, | |
| 2660 function (whole, index) { | |
| 2661 assert(index < args.length, 'Message reference must be in ra
nge'); | |
| 2662 return args[index]; | |
| 2663 } | |
| 2664 ); | |
| 2665 | |
| 2666 error = new Error(msg); | |
| 2667 error.index = index; | |
| 2668 error.description = msg; | |
| 2669 throw error; | |
| 2670 } | |
| 2671 | |
| 2672 // Throw an exception because of the token. | |
| 2673 | |
| 2674 function throwUnexpected(token) { | |
| 2675 throwError(token, Messages.UnexpectedToken, token.value); | |
| 2676 } | |
| 2677 | |
| 2678 // Expect the next token to match the specified punctuator. | |
| 2679 // If not, an exception will be thrown. | |
| 2680 | |
| 2681 function expect(value) { | |
| 2682 var token = lex(); | |
| 2683 if (token.type !== Token.Punctuator || token.value !== value) { | |
| 2684 throwUnexpected(token); | |
| 2685 } | |
| 2686 } | |
| 2687 | |
| 2688 // Return true if the next token matches the specified punctuator. | |
| 2689 | |
| 2690 function match(value) { | |
| 2691 return lookahead.type === Token.Punctuator && lookahead.value === value; | |
| 2692 } | |
| 2693 | |
| 2694 // Return true if the next token matches the specified keyword | |
| 2695 | |
| 2696 function matchKeyword(keyword) { | |
| 2697 return lookahead.type === Token.Keyword && lookahead.value === keyword; | |
| 2698 } | |
| 2699 | |
| 2700 function consumeSemicolon() { | |
| 2701 // Catch the very common case first: immediately a semicolon (char #59). | |
| 2702 if (source.charCodeAt(index) === 59) { | |
| 2703 lex(); | |
| 2704 return; | |
| 2705 } | |
| 2706 | |
| 2707 skipWhitespace(); | |
| 2708 | |
| 2709 if (match(';')) { | |
| 2710 lex(); | |
| 2711 return; | |
| 2712 } | |
| 2713 | |
| 2714 if (lookahead.type !== Token.EOF && !match('}')) { | |
| 2715 throwUnexpected(lookahead); | |
| 2716 } | |
| 2717 } | |
| 2718 | |
| 2719 // 11.1.4 Array Initialiser | |
| 2720 | |
| 2721 function parseArrayInitialiser() { | |
| 2722 var elements = []; | |
| 2723 | |
| 2724 expect('['); | |
| 2725 | |
| 2726 while (!match(']')) { | |
| 2727 if (match(',')) { | |
| 2728 lex(); | |
| 2729 elements.push(null); | |
| 2730 } else { | |
| 2731 elements.push(parseExpression()); | |
| 2732 | |
| 2733 if (!match(']')) { | |
| 2734 expect(','); | |
| 2735 } | |
| 2736 } | |
| 2737 } | |
| 2738 | |
| 2739 expect(']'); | |
| 2740 | |
| 2741 return delegate.createArrayExpression(elements); | |
| 2742 } | |
| 2743 | |
| 2744 // 11.1.5 Object Initialiser | |
| 2745 | |
| 2746 function parseObjectPropertyKey() { | |
| 2747 var token; | |
| 2748 | |
| 2749 skipWhitespace(); | |
| 2750 token = lex(); | |
| 2751 | |
| 2752 // Note: This function is called only from parseObjectProperty(), where | |
| 2753 // EOF and Punctuator tokens are already filtered out. | |
| 2754 if (token.type === Token.StringLiteral || token.type === Token.NumericLi
teral) { | |
| 2755 return delegate.createLiteral(token); | |
| 2756 } | |
| 2757 | |
| 2758 return delegate.createIdentifier(token.value); | |
| 2759 } | |
| 2760 | |
| 2761 function parseObjectProperty() { | |
| 2762 var token, key; | |
| 2763 | |
| 2764 token = lookahead; | |
| 2765 skipWhitespace(); | |
| 2766 | |
| 2767 if (token.type === Token.EOF || token.type === Token.Punctuator) { | |
| 2768 throwUnexpected(token); | |
| 2769 } | |
| 2770 | |
| 2771 key = parseObjectPropertyKey(); | |
| 2772 expect(':'); | |
| 2773 return delegate.createProperty('init', key, parseExpression()); | |
| 2774 } | |
| 2775 | |
| 2776 function parseObjectInitialiser() { | |
| 2777 var properties = []; | |
| 2778 | |
| 2779 expect('{'); | |
| 2780 | |
| 2781 while (!match('}')) { | |
| 2782 properties.push(parseObjectProperty()); | |
| 2783 | |
| 2784 if (!match('}')) { | |
| 2785 expect(','); | |
| 2786 } | |
| 2787 } | |
| 2788 | |
| 2789 expect('}'); | |
| 2790 | |
| 2791 return delegate.createObjectExpression(properties); | |
| 2792 } | |
| 2793 | |
| 2794 // 11.1.6 The Grouping Operator | |
| 2795 | |
| 2796 function parseGroupExpression() { | |
| 2797 var expr; | |
| 2798 | |
| 2799 expect('('); | |
| 2800 | |
| 2801 expr = parseExpression(); | |
| 2802 | |
| 2803 expect(')'); | |
| 2804 | |
| 2805 return expr; | |
| 2806 } | |
| 2807 | |
| 2808 | |
| 2809 // 11.1 Primary Expressions | |
| 2810 | |
| 2811 function parsePrimaryExpression() { | |
| 2812 var type, token, expr; | |
| 2813 | |
| 2814 if (match('(')) { | |
| 2815 return parseGroupExpression(); | |
| 2816 } | |
| 2817 | |
| 2818 type = lookahead.type; | |
| 2819 | |
| 2820 if (type === Token.Identifier) { | |
| 2821 expr = delegate.createIdentifier(lex().value); | |
| 2822 } else if (type === Token.StringLiteral || type === Token.NumericLiteral
) { | |
| 2823 expr = delegate.createLiteral(lex()); | |
| 2824 } else if (type === Token.Keyword) { | |
| 2825 if (matchKeyword('this')) { | |
| 2826 lex(); | |
| 2827 expr = delegate.createThisExpression(); | |
| 2828 } | |
| 2829 } else if (type === Token.BooleanLiteral) { | |
| 2830 token = lex(); | |
| 2831 token.value = (token.value === 'true'); | |
| 2832 expr = delegate.createLiteral(token); | |
| 2833 } else if (type === Token.NullLiteral) { | |
| 2834 token = lex(); | |
| 2835 token.value = null; | |
| 2836 expr = delegate.createLiteral(token); | |
| 2837 } else if (match('[')) { | |
| 2838 expr = parseArrayInitialiser(); | |
| 2839 } else if (match('{')) { | |
| 2840 expr = parseObjectInitialiser(); | |
| 2841 } | |
| 2842 | |
| 2843 if (expr) { | |
| 2844 return expr; | |
| 2845 } | |
| 2846 | |
| 2847 throwUnexpected(lex()); | |
| 2848 } | |
| 2849 | |
| 2850 // 11.2 Left-Hand-Side Expressions | |
| 2851 | |
| 2852 function parseArguments() { | |
| 2853 var args = []; | |
| 2854 | |
| 2855 expect('('); | |
| 2856 | |
| 2857 if (!match(')')) { | |
| 2858 while (index < length) { | |
| 2859 args.push(parseExpression()); | |
| 2860 if (match(')')) { | |
| 2861 break; | |
| 2862 } | |
| 2863 expect(','); | |
| 2864 } | |
| 2865 } | |
| 2866 | |
| 2867 expect(')'); | |
| 2868 | |
| 2869 return args; | |
| 2870 } | |
| 2871 | |
| 2872 function parseNonComputedProperty() { | |
| 2873 var token; | |
| 2874 | |
| 2875 token = lex(); | |
| 2876 | |
| 2877 if (!isIdentifierName(token)) { | |
| 2878 throwUnexpected(token); | |
| 2879 } | |
| 2880 | |
| 2881 return delegate.createIdentifier(token.value); | |
| 2882 } | |
| 2883 | |
| 2884 function parseNonComputedMember() { | |
| 2885 expect('.'); | |
| 2886 | |
| 2887 return parseNonComputedProperty(); | |
| 2888 } | |
| 2889 | |
| 2890 function parseComputedMember() { | |
| 2891 var expr; | |
| 2892 | |
| 2893 expect('['); | |
| 2894 | |
| 2895 expr = parseExpression(); | |
| 2896 | |
| 2897 expect(']'); | |
| 2898 | |
| 2899 return expr; | |
| 2900 } | |
| 2901 | |
| 2902 function parseLeftHandSideExpression() { | |
| 2903 var expr, args, property; | |
| 2904 | |
| 2905 expr = parsePrimaryExpression(); | |
| 2906 | |
| 2907 while (true) { | |
| 2908 if (match('[')) { | |
| 2909 property = parseComputedMember(); | |
| 2910 expr = delegate.createMemberExpression('[', expr, property); | |
| 2911 } else if (match('.')) { | |
| 2912 property = parseNonComputedMember(); | |
| 2913 expr = delegate.createMemberExpression('.', expr, property); | |
| 2914 } else if (match('(')) { | |
| 2915 args = parseArguments(); | |
| 2916 expr = delegate.createCallExpression(expr, args); | |
| 2917 } else { | |
| 2918 break; | |
| 2919 } | |
| 2920 } | |
| 2921 | |
| 2922 return expr; | |
| 2923 } | |
| 2924 | |
| 2925 // 11.3 Postfix Expressions | |
| 2926 | |
| 2927 var parsePostfixExpression = parseLeftHandSideExpression; | |
| 2928 | |
| 2929 // 11.4 Unary Operators | |
| 2930 | |
| 2931 function parseUnaryExpression() { | |
| 2932 var token, expr; | |
| 2933 | |
| 2934 if (lookahead.type !== Token.Punctuator && lookahead.type !== Token.Keyw
ord) { | |
| 2935 expr = parsePostfixExpression(); | |
| 2936 } else if (match('+') || match('-') || match('!')) { | |
| 2937 token = lex(); | |
| 2938 expr = parseUnaryExpression(); | |
| 2939 expr = delegate.createUnaryExpression(token.value, expr); | |
| 2940 } else if (matchKeyword('delete') || matchKeyword('void') || matchKeywor
d('typeof')) { | |
| 2941 throwError({}, Messages.UnexpectedToken); | |
| 2942 } else { | |
| 2943 expr = parsePostfixExpression(); | |
| 2944 } | |
| 2945 | |
| 2946 return expr; | |
| 2947 } | |
| 2948 | |
| 2949 function binaryPrecedence(token) { | |
| 2950 var prec = 0; | |
| 2951 | |
| 2952 if (token.type !== Token.Punctuator && token.type !== Token.Keyword) { | |
| 2953 return 0; | |
| 2954 } | |
| 2955 | |
| 2956 switch (token.value) { | |
| 2957 case '||': | |
| 2958 prec = 1; | |
| 2959 break; | |
| 2960 | |
| 2961 case '&&': | |
| 2962 prec = 2; | |
| 2963 break; | |
| 2964 | |
| 2965 case '==': | |
| 2966 case '!=': | |
| 2967 case '===': | |
| 2968 case '!==': | |
| 2969 prec = 6; | |
| 2970 break; | |
| 2971 | |
| 2972 case '<': | |
| 2973 case '>': | |
| 2974 case '<=': | |
| 2975 case '>=': | |
| 2976 case 'instanceof': | |
| 2977 prec = 7; | |
| 2978 break; | |
| 2979 | |
| 2980 case 'in': | |
| 2981 prec = 7; | |
| 2982 break; | |
| 2983 | |
| 2984 case '+': | |
| 2985 case '-': | |
| 2986 prec = 9; | |
| 2987 break; | |
| 2988 | |
| 2989 case '*': | |
| 2990 case '/': | |
| 2991 case '%': | |
| 2992 prec = 11; | |
| 2993 break; | |
| 2994 | |
| 2995 default: | |
| 2996 break; | |
| 2997 } | |
| 2998 | |
| 2999 return prec; | |
| 3000 } | |
| 3001 | |
| 3002 // 11.5 Multiplicative Operators | |
| 3003 // 11.6 Additive Operators | |
| 3004 // 11.7 Bitwise Shift Operators | |
| 3005 // 11.8 Relational Operators | |
| 3006 // 11.9 Equality Operators | |
| 3007 // 11.10 Binary Bitwise Operators | |
| 3008 // 11.11 Binary Logical Operators | |
| 3009 | |
| 3010 function parseBinaryExpression() { | |
| 3011 var expr, token, prec, stack, right, operator, left, i; | |
| 3012 | |
| 3013 left = parseUnaryExpression(); | |
| 3014 | |
| 3015 token = lookahead; | |
| 3016 prec = binaryPrecedence(token); | |
| 3017 if (prec === 0) { | |
| 3018 return left; | |
| 3019 } | |
| 3020 token.prec = prec; | |
| 3021 lex(); | |
| 3022 | |
| 3023 right = parseUnaryExpression(); | |
| 3024 | |
| 3025 stack = [left, token, right]; | |
| 3026 | |
| 3027 while ((prec = binaryPrecedence(lookahead)) > 0) { | |
| 3028 | |
| 3029 // Reduce: make a binary expression from the three topmost entries. | |
| 3030 while ((stack.length > 2) && (prec <= stack[stack.length - 2].prec))
{ | |
| 3031 right = stack.pop(); | |
| 3032 operator = stack.pop().value; | |
| 3033 left = stack.pop(); | |
| 3034 expr = delegate.createBinaryExpression(operator, left, right); | |
| 3035 stack.push(expr); | |
| 3036 } | |
| 3037 | |
| 3038 // Shift. | |
| 3039 token = lex(); | |
| 3040 token.prec = prec; | |
| 3041 stack.push(token); | |
| 3042 expr = parseUnaryExpression(); | |
| 3043 stack.push(expr); | |
| 3044 } | |
| 3045 | |
| 3046 // Final reduce to clean-up the stack. | |
| 3047 i = stack.length - 1; | |
| 3048 expr = stack[i]; | |
| 3049 while (i > 1) { | |
| 3050 expr = delegate.createBinaryExpression(stack[i - 1].value, stack[i -
2], expr); | |
| 3051 i -= 2; | |
| 3052 } | |
| 3053 | |
| 3054 return expr; | |
| 3055 } | |
| 3056 | |
| 3057 | |
| 3058 // 11.12 Conditional Operator | |
| 3059 | |
| 3060 function parseConditionalExpression() { | |
| 3061 var expr, consequent, alternate; | |
| 3062 | |
| 3063 expr = parseBinaryExpression(); | |
| 3064 | |
| 3065 if (match('?')) { | |
| 3066 lex(); | |
| 3067 consequent = parseConditionalExpression(); | |
| 3068 expect(':'); | |
| 3069 alternate = parseConditionalExpression(); | |
| 3070 | |
| 3071 expr = delegate.createConditionalExpression(expr, consequent, altern
ate); | |
| 3072 } | |
| 3073 | |
| 3074 return expr; | |
| 3075 } | |
| 3076 | |
| 3077 // Simplification since we do not support AssignmentExpression. | |
| 3078 var parseExpression = parseConditionalExpression; | |
| 3079 | |
| 3080 // Polymer Syntax extensions | |
| 3081 | |
| 3082 // Filter :: | |
| 3083 // Identifier | |
| 3084 // Identifier "(" ")" | |
| 3085 // Identifier "(" FilterArguments ")" | |
| 3086 | |
| 3087 function parseFilter() { | |
| 3088 var identifier, args; | |
| 3089 | |
| 3090 identifier = lex(); | |
| 3091 | |
| 3092 if (identifier.type !== Token.Identifier) { | |
| 3093 throwUnexpected(identifier); | |
| 3094 } | |
| 3095 | |
| 3096 args = match('(') ? parseArguments() : []; | |
| 3097 | |
| 3098 return delegate.createFilter(identifier.value, args); | |
| 3099 } | |
| 3100 | |
| 3101 // Filters :: | |
| 3102 // "|" Filter | |
| 3103 // Filters "|" Filter | |
| 3104 | |
| 3105 function parseFilters() { | |
| 3106 while (match('|')) { | |
| 3107 lex(); | |
| 3108 parseFilter(); | |
| 3109 } | |
| 3110 } | |
| 3111 | |
| 3112 // TopLevel :: | |
| 3113 // LabelledExpressions | |
| 3114 // AsExpression | |
| 3115 // InExpression | |
| 3116 // FilterExpression | |
| 3117 | |
| 3118 // AsExpression :: | |
| 3119 // FilterExpression as Identifier | |
| 3120 | |
| 3121 // InExpression :: | |
| 3122 // Identifier, Identifier in FilterExpression | |
| 3123 // Identifier in FilterExpression | |
| 3124 | |
| 3125 // FilterExpression :: | |
| 3126 // Expression | |
| 3127 // Expression Filters | |
| 3128 | |
| 3129 function parseTopLevel() { | |
| 3130 skipWhitespace(); | |
| 3131 peek(); | |
| 3132 | |
| 3133 var expr = parseExpression(); | |
| 3134 if (expr) { | |
| 3135 if (lookahead.value === ',' || lookahead.value == 'in' && | |
| 3136 expr.type === Syntax.Identifier) { | |
| 3137 parseInExpression(expr); | |
| 3138 } else { | |
| 3139 parseFilters(); | |
| 3140 if (lookahead.value === 'as') { | |
| 3141 parseAsExpression(expr); | |
| 3142 } else { | |
| 3143 delegate.createTopLevel(expr); | |
| 3144 } | |
| 3145 } | |
| 3146 } | |
| 3147 | |
| 3148 if (lookahead.type !== Token.EOF) { | |
| 3149 throwUnexpected(lookahead); | |
| 3150 } | |
| 3151 } | |
| 3152 | |
| 3153 function parseAsExpression(expr) { | |
| 3154 lex(); // as | |
| 3155 var identifier = lex().value; | |
| 3156 delegate.createAsExpression(expr, identifier); | |
| 3157 } | |
| 3158 | |
| 3159 function parseInExpression(identifier) { | |
| 3160 var indexName; | |
| 3161 if (lookahead.value === ',') { | |
| 3162 lex(); | |
| 3163 if (lookahead.type !== Token.Identifier) | |
| 3164 throwUnexpected(lookahead); | |
| 3165 indexName = lex().value; | |
| 3166 } | |
| 3167 | |
| 3168 lex(); // in | |
| 3169 var expr = parseExpression(); | |
| 3170 parseFilters(); | |
| 3171 delegate.createInExpression(identifier.name, indexName, expr); | |
| 3172 } | |
| 3173 | |
| 3174 function parse(code, inDelegate) { | |
| 3175 delegate = inDelegate; | |
| 3176 source = code; | |
| 3177 index = 0; | |
| 3178 length = source.length; | |
| 3179 lookahead = null; | |
| 3180 state = { | |
| 3181 labelSet: {} | |
| 3182 }; | |
| 3183 | |
| 3184 return parseTopLevel(); | |
| 3185 } | |
| 3186 | |
| 3187 global.esprima = { | |
| 3188 parse: parse | |
| 3189 }; | |
| 3190 })(this); | |
| 3191 | |
| 3192 // Copyright (c) 2014 The Polymer Project Authors. All rights reserved. | |
| 3193 // This code may only be used under the BSD style license found at http://polyme
r.github.io/LICENSE.txt | |
| 3194 // The complete set of authors may be found at http://polymer.github.io/AUTHORS.
txt | |
| 3195 // The complete set of contributors may be found at http://polymer.github.io/CON
TRIBUTORS.txt | |
| 3196 // Code distributed by Google as part of the polymer project is also | |
| 3197 // subject to an additional IP rights grant found at http://polymer.github.io/PA
TENTS.txt | |
| 3198 | |
| 3199 (function (global) { | |
| 3200 'use strict'; | |
| 3201 | |
| 3202 function prepareBinding(expressionText, name, node, filterRegistry) { | |
| 3203 var expression; | |
| 3204 try { | |
| 3205 expression = getExpression(expressionText); | |
| 3206 if (expression.scopeIdent && | |
| 3207 (node.nodeType !== Node.ELEMENT_NODE || | |
| 3208 node.tagName !== 'TEMPLATE' || | |
| 3209 (name !== 'bind' && name !== 'repeat'))) { | |
| 3210 throw Error('as and in can only be used within <template bind/repeat>'); | |
| 3211 } | |
| 3212 } catch (ex) { | |
| 3213 console.error('Invalid expression syntax: ' + expressionText, ex); | |
| 3214 return; | |
| 3215 } | |
| 3216 | |
| 3217 return function(model, node, oneTime) { | |
| 3218 var binding = expression.getBinding(model, filterRegistry, oneTime); | |
| 3219 if (expression.scopeIdent && binding) { | |
| 3220 node.polymerExpressionScopeIdent_ = expression.scopeIdent; | |
| 3221 if (expression.indexIdent) | |
| 3222 node.polymerExpressionIndexIdent_ = expression.indexIdent; | |
| 3223 } | |
| 3224 | |
| 3225 return binding; | |
| 3226 } | |
| 3227 } | |
| 3228 | |
| 3229 // TODO(rafaelw): Implement simple LRU. | |
| 3230 var expressionParseCache = Object.create(null); | |
| 3231 | |
| 3232 function getExpression(expressionText) { | |
| 3233 var expression = expressionParseCache[expressionText]; | |
| 3234 if (!expression) { | |
| 3235 var delegate = new ASTDelegate(); | |
| 3236 esprima.parse(expressionText, delegate); | |
| 3237 expression = new Expression(delegate); | |
| 3238 expressionParseCache[expressionText] = expression; | |
| 3239 } | |
| 3240 return expression; | |
| 3241 } | |
| 3242 | |
| 3243 function Literal(value) { | |
| 3244 this.value = value; | |
| 3245 this.valueFn_ = undefined; | |
| 3246 } | |
| 3247 | |
| 3248 Literal.prototype = { | |
| 3249 valueFn: function() { | |
| 3250 if (!this.valueFn_) { | |
| 3251 var value = this.value; | |
| 3252 this.valueFn_ = function() { | |
| 3253 return value; | |
| 3254 } | |
| 3255 } | |
| 3256 | |
| 3257 return this.valueFn_; | |
| 3258 } | |
| 3259 } | |
| 3260 | |
| 3261 function IdentPath(name) { | |
| 3262 this.name = name; | |
| 3263 this.path = Path.get(name); | |
| 3264 } | |
| 3265 | |
| 3266 IdentPath.prototype = { | |
| 3267 valueFn: function() { | |
| 3268 if (!this.valueFn_) { | |
| 3269 var name = this.name; | |
| 3270 var path = this.path; | |
| 3271 this.valueFn_ = function(model, observer) { | |
| 3272 if (observer) | |
| 3273 observer.addPath(model, path); | |
| 3274 | |
| 3275 return path.getValueFrom(model); | |
| 3276 } | |
| 3277 } | |
| 3278 | |
| 3279 return this.valueFn_; | |
| 3280 }, | |
| 3281 | |
| 3282 setValue: function(model, newValue) { | |
| 3283 if (this.path.length == 1) | |
| 3284 model = findScope(model, this.path[0]); | |
| 3285 | |
| 3286 return this.path.setValueFrom(model, newValue); | |
| 3287 } | |
| 3288 }; | |
| 3289 | |
| 3290 function MemberExpression(object, property, accessor) { | |
| 3291 this.computed = accessor == '['; | |
| 3292 | |
| 3293 this.dynamicDeps = typeof object == 'function' || | |
| 3294 object.dynamicDeps || | |
| 3295 (this.computed && !(property instanceof Literal)); | |
| 3296 | |
| 3297 this.simplePath = | |
| 3298 !this.dynamicDeps && | |
| 3299 (property instanceof IdentPath || property instanceof Literal) && | |
| 3300 (object instanceof MemberExpression || object instanceof IdentPath); | |
| 3301 | |
| 3302 this.object = this.simplePath ? object : getFn(object); | |
| 3303 this.property = !this.computed || this.simplePath ? | |
| 3304 property : getFn(property); | |
| 3305 } | |
| 3306 | |
| 3307 MemberExpression.prototype = { | |
| 3308 get fullPath() { | |
| 3309 if (!this.fullPath_) { | |
| 3310 | |
| 3311 var parts = this.object instanceof MemberExpression ? | |
| 3312 this.object.fullPath.slice() : [this.object.name]; | |
| 3313 parts.push(this.property instanceof IdentPath ? | |
| 3314 this.property.name : this.property.value); | |
| 3315 this.fullPath_ = Path.get(parts); | |
| 3316 } | |
| 3317 | |
| 3318 return this.fullPath_; | |
| 3319 }, | |
| 3320 | |
| 3321 valueFn: function() { | |
| 3322 if (!this.valueFn_) { | |
| 3323 var object = this.object; | |
| 3324 | |
| 3325 if (this.simplePath) { | |
| 3326 var path = this.fullPath; | |
| 3327 | |
| 3328 this.valueFn_ = function(model, observer) { | |
| 3329 if (observer) | |
| 3330 observer.addPath(model, path); | |
| 3331 | |
| 3332 return path.getValueFrom(model); | |
| 3333 }; | |
| 3334 } else if (!this.computed) { | |
| 3335 var path = Path.get(this.property.name); | |
| 3336 | |
| 3337 this.valueFn_ = function(model, observer, filterRegistry) { | |
| 3338 var context = object(model, observer, filterRegistry); | |
| 3339 | |
| 3340 if (observer) | |
| 3341 observer.addPath(context, path); | |
| 3342 | |
| 3343 return path.getValueFrom(context); | |
| 3344 } | |
| 3345 } else { | |
| 3346 // Computed property. | |
| 3347 var property = this.property; | |
| 3348 | |
| 3349 this.valueFn_ = function(model, observer, filterRegistry) { | |
| 3350 var context = object(model, observer, filterRegistry); | |
| 3351 var propName = property(model, observer, filterRegistry); | |
| 3352 if (observer) | |
| 3353 observer.addPath(context, [propName]); | |
| 3354 | |
| 3355 return context ? context[propName] : undefined; | |
| 3356 }; | |
| 3357 } | |
| 3358 } | |
| 3359 return this.valueFn_; | |
| 3360 }, | |
| 3361 | |
| 3362 setValue: function(model, newValue) { | |
| 3363 if (this.simplePath) { | |
| 3364 this.fullPath.setValueFrom(model, newValue); | |
| 3365 return newValue; | |
| 3366 } | |
| 3367 | |
| 3368 var object = this.object(model); | |
| 3369 var propName = this.property instanceof IdentPath ? this.property.name : | |
| 3370 this.property(model); | |
| 3371 return object[propName] = newValue; | |
| 3372 } | |
| 3373 }; | |
| 3374 | |
| 3375 function Filter(name, args) { | |
| 3376 this.name = name; | |
| 3377 this.args = []; | |
| 3378 for (var i = 0; i < args.length; i++) { | |
| 3379 this.args[i] = getFn(args[i]); | |
| 3380 } | |
| 3381 } | |
| 3382 | |
| 3383 Filter.prototype = { | |
| 3384 transform: function(model, observer, filterRegistry, toModelDirection, | |
| 3385 initialArgs) { | |
| 3386 var fn = filterRegistry[this.name]; | |
| 3387 var context = model; | |
| 3388 if (fn) { | |
| 3389 context = undefined; | |
| 3390 } else { | |
| 3391 fn = context[this.name]; | |
| 3392 if (!fn) { | |
| 3393 console.error('Cannot find function or filter: ' + this.name); | |
| 3394 return; | |
| 3395 } | |
| 3396 } | |
| 3397 | |
| 3398 // If toModelDirection is falsey, then the "normal" (dom-bound) direction | |
| 3399 // is used. Otherwise, it looks for a 'toModel' property function on the | |
| 3400 // object. | |
| 3401 if (toModelDirection) { | |
| 3402 fn = fn.toModel; | |
| 3403 } else if (typeof fn.toDOM == 'function') { | |
| 3404 fn = fn.toDOM; | |
| 3405 } | |
| 3406 | |
| 3407 if (typeof fn != 'function') { | |
| 3408 console.error('Cannot find function or filter: ' + this.name); | |
| 3409 return; | |
| 3410 } | |
| 3411 | |
| 3412 var args = initialArgs || []; | |
| 3413 for (var i = 0; i < this.args.length; i++) { | |
| 3414 args.push(getFn(this.args[i])(model, observer, filterRegistry)); | |
| 3415 } | |
| 3416 | |
| 3417 return fn.apply(context, args); | |
| 3418 } | |
| 3419 }; | |
| 3420 | |
| 3421 function notImplemented() { throw Error('Not Implemented'); } | |
| 3422 | |
| 3423 var unaryOperators = { | |
| 3424 '+': function(v) { return +v; }, | |
| 3425 '-': function(v) { return -v; }, | |
| 3426 '!': function(v) { return !v; } | |
| 3427 }; | |
| 3428 | |
| 3429 var binaryOperators = { | |
| 3430 '+': function(l, r) { return l+r; }, | |
| 3431 '-': function(l, r) { return l-r; }, | |
| 3432 '*': function(l, r) { return l*r; }, | |
| 3433 '/': function(l, r) { return l/r; }, | |
| 3434 '%': function(l, r) { return l%r; }, | |
| 3435 '<': function(l, r) { return l<r; }, | |
| 3436 '>': function(l, r) { return l>r; }, | |
| 3437 '<=': function(l, r) { return l<=r; }, | |
| 3438 '>=': function(l, r) { return l>=r; }, | |
| 3439 '==': function(l, r) { return l==r; }, | |
| 3440 '!=': function(l, r) { return l!=r; }, | |
| 3441 '===': function(l, r) { return l===r; }, | |
| 3442 '!==': function(l, r) { return l!==r; }, | |
| 3443 '&&': function(l, r) { return l&&r; }, | |
| 3444 '||': function(l, r) { return l||r; }, | |
| 3445 }; | |
| 3446 | |
| 3447 function getFn(arg) { | |
| 3448 return typeof arg == 'function' ? arg : arg.valueFn(); | |
| 3449 } | |
| 3450 | |
| 3451 function ASTDelegate() { | |
| 3452 this.expression = null; | |
| 3453 this.filters = []; | |
| 3454 this.deps = {}; | |
| 3455 this.currentPath = undefined; | |
| 3456 this.scopeIdent = undefined; | |
| 3457 this.indexIdent = undefined; | |
| 3458 this.dynamicDeps = false; | |
| 3459 } | |
| 3460 | |
| 3461 ASTDelegate.prototype = { | |
| 3462 createUnaryExpression: function(op, argument) { | |
| 3463 if (!unaryOperators[op]) | |
| 3464 throw Error('Disallowed operator: ' + op); | |
| 3465 | |
| 3466 argument = getFn(argument); | |
| 3467 | |
| 3468 return function(model, observer, filterRegistry) { | |
| 3469 return unaryOperators[op](argument(model, observer, filterRegistry)); | |
| 3470 }; | |
| 3471 }, | |
| 3472 | |
| 3473 createBinaryExpression: function(op, left, right) { | |
| 3474 if (!binaryOperators[op]) | |
| 3475 throw Error('Disallowed operator: ' + op); | |
| 3476 | |
| 3477 left = getFn(left); | |
| 3478 right = getFn(right); | |
| 3479 | |
| 3480 switch (op) { | |
| 3481 case '||': | |
| 3482 this.dynamicDeps = true; | |
| 3483 return function(model, observer, filterRegistry) { | |
| 3484 return left(model, observer, filterRegistry) || | |
| 3485 right(model, observer, filterRegistry); | |
| 3486 }; | |
| 3487 case '&&': | |
| 3488 this.dynamicDeps = true; | |
| 3489 return function(model, observer, filterRegistry) { | |
| 3490 return left(model, observer, filterRegistry) && | |
| 3491 right(model, observer, filterRegistry); | |
| 3492 }; | |
| 3493 } | |
| 3494 | |
| 3495 return function(model, observer, filterRegistry) { | |
| 3496 return binaryOperators[op](left(model, observer, filterRegistry), | |
| 3497 right(model, observer, filterRegistry)); | |
| 3498 }; | |
| 3499 }, | |
| 3500 | |
| 3501 createConditionalExpression: function(test, consequent, alternate) { | |
| 3502 test = getFn(test); | |
| 3503 consequent = getFn(consequent); | |
| 3504 alternate = getFn(alternate); | |
| 3505 | |
| 3506 this.dynamicDeps = true; | |
| 3507 | |
| 3508 return function(model, observer, filterRegistry) { | |
| 3509 return test(model, observer, filterRegistry) ? | |
| 3510 consequent(model, observer, filterRegistry) : | |
| 3511 alternate(model, observer, filterRegistry); | |
| 3512 } | |
| 3513 }, | |
| 3514 | |
| 3515 createIdentifier: function(name) { | |
| 3516 var ident = new IdentPath(name); | |
| 3517 ident.type = 'Identifier'; | |
| 3518 return ident; | |
| 3519 }, | |
| 3520 | |
| 3521 createMemberExpression: function(accessor, object, property) { | |
| 3522 var ex = new MemberExpression(object, property, accessor); | |
| 3523 if (ex.dynamicDeps) | |
| 3524 this.dynamicDeps = true; | |
| 3525 return ex; | |
| 3526 }, | |
| 3527 | |
| 3528 createCallExpression: function(expression, args) { | |
| 3529 if (!(expression instanceof IdentPath)) | |
| 3530 throw Error('Only identifier function invocations are allowed'); | |
| 3531 | |
| 3532 var filter = new Filter(expression.name, args); | |
| 3533 | |
| 3534 return function(model, observer, filterRegistry) { | |
| 3535 return filter.transform(model, observer, filterRegistry, false); | |
| 3536 }; | |
| 3537 }, | |
| 3538 | |
| 3539 createLiteral: function(token) { | |
| 3540 return new Literal(token.value); | |
| 3541 }, | |
| 3542 | |
| 3543 createArrayExpression: function(elements) { | |
| 3544 for (var i = 0; i < elements.length; i++) | |
| 3545 elements[i] = getFn(elements[i]); | |
| 3546 | |
| 3547 return function(model, observer, filterRegistry) { | |
| 3548 var arr = [] | |
| 3549 for (var i = 0; i < elements.length; i++) | |
| 3550 arr.push(elements[i](model, observer, filterRegistry)); | |
| 3551 return arr; | |
| 3552 } | |
| 3553 }, | |
| 3554 | |
| 3555 createProperty: function(kind, key, value) { | |
| 3556 return { | |
| 3557 key: key instanceof IdentPath ? key.name : key.value, | |
| 3558 value: value | |
| 3559 }; | |
| 3560 }, | |
| 3561 | |
| 3562 createObjectExpression: function(properties) { | |
| 3563 for (var i = 0; i < properties.length; i++) | |
| 3564 properties[i].value = getFn(properties[i].value); | |
| 3565 | |
| 3566 return function(model, observer, filterRegistry) { | |
| 3567 var obj = {}; | |
| 3568 for (var i = 0; i < properties.length; i++) | |
| 3569 obj[properties[i].key] = | |
| 3570 properties[i].value(model, observer, filterRegistry); | |
| 3571 return obj; | |
| 3572 } | |
| 3573 }, | |
| 3574 | |
| 3575 createFilter: function(name, args) { | |
| 3576 this.filters.push(new Filter(name, args)); | |
| 3577 }, | |
| 3578 | |
| 3579 createAsExpression: function(expression, scopeIdent) { | |
| 3580 this.expression = expression; | |
| 3581 this.scopeIdent = scopeIdent; | |
| 3582 }, | |
| 3583 | |
| 3584 createInExpression: function(scopeIdent, indexIdent, expression) { | |
| 3585 this.expression = expression; | |
| 3586 this.scopeIdent = scopeIdent; | |
| 3587 this.indexIdent = indexIdent; | |
| 3588 }, | |
| 3589 | |
| 3590 createTopLevel: function(expression) { | |
| 3591 this.expression = expression; | |
| 3592 }, | |
| 3593 | |
| 3594 createThisExpression: notImplemented | |
| 3595 } | |
| 3596 | |
| 3597 function ConstantObservable(value) { | |
| 3598 this.value_ = value; | |
| 3599 } | |
| 3600 | |
| 3601 ConstantObservable.prototype = { | |
| 3602 open: function() { return this.value_; }, | |
| 3603 discardChanges: function() { return this.value_; }, | |
| 3604 deliver: function() {}, | |
| 3605 close: function() {}, | |
| 3606 } | |
| 3607 | |
| 3608 function Expression(delegate) { | |
| 3609 this.scopeIdent = delegate.scopeIdent; | |
| 3610 this.indexIdent = delegate.indexIdent; | |
| 3611 | |
| 3612 if (!delegate.expression) | |
| 3613 throw Error('No expression found.'); | |
| 3614 | |
| 3615 this.expression = delegate.expression; | |
| 3616 getFn(this.expression); // forces enumeration of path dependencies | |
| 3617 | |
| 3618 this.filters = delegate.filters; | |
| 3619 this.dynamicDeps = delegate.dynamicDeps; | |
| 3620 } | |
| 3621 | |
| 3622 Expression.prototype = { | |
| 3623 getBinding: function(model, filterRegistry, oneTime) { | |
| 3624 if (oneTime) | |
| 3625 return this.getValue(model, undefined, filterRegistry); | |
| 3626 | |
| 3627 var observer = new CompoundObserver(); | |
| 3628 // captures deps. | |
| 3629 var firstValue = this.getValue(model, observer, filterRegistry); | |
| 3630 var firstTime = true; | |
| 3631 var self = this; | |
| 3632 | |
| 3633 function valueFn() { | |
| 3634 // deps cannot have changed on first value retrieval. | |
| 3635 if (firstTime) { | |
| 3636 firstTime = false; | |
| 3637 return firstValue; | |
| 3638 } | |
| 3639 | |
| 3640 if (self.dynamicDeps) | |
| 3641 observer.startReset(); | |
| 3642 | |
| 3643 var value = self.getValue(model, | |
| 3644 self.dynamicDeps ? observer : undefined, | |
| 3645 filterRegistry); | |
| 3646 if (self.dynamicDeps) | |
| 3647 observer.finishReset(); | |
| 3648 | |
| 3649 return value; | |
| 3650 } | |
| 3651 | |
| 3652 function setValueFn(newValue) { | |
| 3653 self.setValue(model, newValue, filterRegistry); | |
| 3654 return newValue; | |
| 3655 } | |
| 3656 | |
| 3657 return new ObserverTransform(observer, valueFn, setValueFn, true); | |
| 3658 }, | |
| 3659 | |
| 3660 getValue: function(model, observer, filterRegistry) { | |
| 3661 var value = getFn(this.expression)(model, observer, filterRegistry); | |
| 3662 for (var i = 0; i < this.filters.length; i++) { | |
| 3663 value = this.filters[i].transform(model, observer, filterRegistry, | |
| 3664 false, [value]); | |
| 3665 } | |
| 3666 | |
| 3667 return value; | |
| 3668 }, | |
| 3669 | |
| 3670 setValue: function(model, newValue, filterRegistry) { | |
| 3671 var count = this.filters ? this.filters.length : 0; | |
| 3672 while (count-- > 0) { | |
| 3673 newValue = this.filters[count].transform(model, undefined, | |
| 3674 filterRegistry, true, [newValue]); | |
| 3675 } | |
| 3676 | |
| 3677 if (this.expression.setValue) | |
| 3678 return this.expression.setValue(model, newValue); | |
| 3679 } | |
| 3680 } | |
| 3681 | |
| 3682 /** | |
| 3683 * Converts a style property name to a css property name. For example: | |
| 3684 * "WebkitUserSelect" to "-webkit-user-select" | |
| 3685 */ | |
| 3686 function convertStylePropertyName(name) { | |
| 3687 return String(name).replace(/[A-Z]/g, function(c) { | |
| 3688 return '-' + c.toLowerCase(); | |
| 3689 }); | |
| 3690 } | |
| 3691 | |
| 3692 var parentScopeName = '@' + Math.random().toString(36).slice(2); | |
| 3693 | |
| 3694 // Single ident paths must bind directly to the appropriate scope object. | |
| 3695 // I.e. Pushed values in two-bindings need to be assigned to the actual model | |
| 3696 // object. | |
| 3697 function findScope(model, prop) { | |
| 3698 while (model[parentScopeName] && | |
| 3699 !Object.prototype.hasOwnProperty.call(model, prop)) { | |
| 3700 model = model[parentScopeName]; | |
| 3701 } | |
| 3702 | |
| 3703 return model; | |
| 3704 } | |
| 3705 | |
| 3706 function isLiteralExpression(pathString) { | |
| 3707 switch (pathString) { | |
| 3708 case '': | |
| 3709 return false; | |
| 3710 | |
| 3711 case 'false': | |
| 3712 case 'null': | |
| 3713 case 'true': | |
| 3714 return true; | |
| 3715 } | |
| 3716 | |
| 3717 if (!isNaN(Number(pathString))) | |
| 3718 return true; | |
| 3719 | |
| 3720 return false; | |
| 3721 }; | |
| 3722 | |
| 3723 function PolymerExpressions() {} | |
| 3724 | |
| 3725 PolymerExpressions.prototype = { | |
| 3726 // "built-in" filters | |
| 3727 styleObject: function(value) { | |
| 3728 var parts = []; | |
| 3729 for (var key in value) { | |
| 3730 parts.push(convertStylePropertyName(key) + ': ' + value[key]); | |
| 3731 } | |
| 3732 return parts.join('; '); | |
| 3733 }, | |
| 3734 | |
| 3735 tokenList: function(value) { | |
| 3736 var tokens = []; | |
| 3737 for (var key in value) { | |
| 3738 if (value[key]) | |
| 3739 tokens.push(key); | |
| 3740 } | |
| 3741 return tokens.join(' '); | |
| 3742 }, | |
| 3743 | |
| 3744 // binding delegate API | |
| 3745 prepareInstancePositionChanged: function(template) { | |
| 3746 var indexIdent = template.polymerExpressionIndexIdent_; | |
| 3747 if (!indexIdent) | |
| 3748 return; | |
| 3749 | |
| 3750 return function(templateInstance, index) { | |
| 3751 templateInstance.model[indexIdent] = index; | |
| 3752 }; | |
| 3753 }, | |
| 3754 | |
| 3755 prepareBinding: function(pathString, name, node) { | |
| 3756 var path = Path.get(pathString); | |
| 3757 | |
| 3758 if (!isLiteralExpression(pathString) && path.valid) { | |
| 3759 if (path.length == 1) { | |
| 3760 return function(model, node, oneTime) { | |
| 3761 if (oneTime) | |
| 3762 return path.getValueFrom(model); | |
| 3763 | |
| 3764 var scope = findScope(model, path[0]); | |
| 3765 return new PathObserver(scope, path); | |
| 3766 }; | |
| 3767 } | |
| 3768 return; // bail out early if pathString is simple path. | |
| 3769 } | |
| 3770 | |
| 3771 return prepareBinding(pathString, name, node, this); | |
| 3772 }, | |
| 3773 | |
| 3774 prepareInstanceModel: function(template) { | |
| 3775 var scopeName = template.polymerExpressionScopeIdent_; | |
| 3776 if (!scopeName) | |
| 3777 return; | |
| 3778 | |
| 3779 var parentScope = template.templateInstance ? | |
| 3780 template.templateInstance.model : | |
| 3781 template.model; | |
| 3782 | |
| 3783 var indexName = template.polymerExpressionIndexIdent_; | |
| 3784 | |
| 3785 return function(model) { | |
| 3786 return createScopeObject(parentScope, model, scopeName, indexName); | |
| 3787 }; | |
| 3788 } | |
| 3789 }; | |
| 3790 | |
| 3791 var createScopeObject = ('__proto__' in {}) ? | |
| 3792 function(parentScope, model, scopeName, indexName) { | |
| 3793 var scope = {}; | |
| 3794 scope[scopeName] = model; | |
| 3795 scope[indexName] = undefined; | |
| 3796 scope[parentScopeName] = parentScope; | |
| 3797 scope.__proto__ = parentScope; | |
| 3798 return scope; | |
| 3799 } : | |
| 3800 function(parentScope, model, scopeName, indexName) { | |
| 3801 var scope = Object.create(parentScope); | |
| 3802 Object.defineProperty(scope, scopeName, | |
| 3803 { value: model, configurable: true, writable: true }); | |
| 3804 Object.defineProperty(scope, indexName, | |
| 3805 { value: undefined, configurable: true, writable: true }); | |
| 3806 Object.defineProperty(scope, parentScopeName, | |
| 3807 { value: parentScope, configurable: true, writable: true }); | |
| 3808 return scope; | |
| 3809 }; | |
| 3810 | |
| 3811 global.PolymerExpressions = PolymerExpressions; | |
| 3812 PolymerExpressions.getExpression = getExpression; | |
| 3813 })(this); | |
| 3814 | |
| 3815 Polymer = { | |
| 3816 version: '0.5.4' | |
| 3817 }; | |
| 3818 | |
| 3819 // TODO(sorvell): this ensures Polymer is an object and not a function | |
| 3820 // Platform is currently defining it as a function to allow for async loading | |
| 3821 // of polymer; once we refine the loading process this likely goes away. | |
| 3822 if (typeof window.Polymer === 'function') { | |
| 3823 Polymer = {}; | |
| 3824 } | |
| 3825 | |
| 3826 | |
| 3827 (function(scope) { | |
| 3828 | |
| 3829 function withDependencies(task, depends) { | |
| 3830 depends = depends || []; | |
| 3831 if (!depends.map) { | |
| 3832 depends = [depends]; | |
| 3833 } | |
| 3834 return task.apply(this, depends.map(marshal)); | |
| 3835 } | |
| 3836 | |
| 3837 function module(name, dependsOrFactory, moduleFactory) { | |
| 3838 var module; | |
| 3839 switch (arguments.length) { | |
| 3840 case 0: | |
| 3841 return; | |
| 3842 case 1: | |
| 3843 module = null; | |
| 3844 break; | |
| 3845 case 2: | |
| 3846 // dependsOrFactory is `factory` in this case | |
| 3847 module = dependsOrFactory.apply(this); | |
| 3848 break; | |
| 3849 default: | |
| 3850 // dependsOrFactory is `depends` in this case | |
| 3851 module = withDependencies(moduleFactory, dependsOrFactory); | |
| 3852 break; | |
| 3853 } | |
| 3854 modules[name] = module; | |
| 3855 }; | |
| 3856 | |
| 3857 function marshal(name) { | |
| 3858 return modules[name]; | |
| 3859 } | |
| 3860 | |
| 3861 var modules = {}; | |
| 3862 | |
| 3863 function using(depends, task) { | |
| 3864 HTMLImports.whenImportsReady(function() { | |
| 3865 withDependencies(task, depends); | |
| 3866 }); | |
| 3867 }; | |
| 3868 | |
| 3869 // exports | |
| 3870 | |
| 3871 scope.marshal = marshal; | |
| 3872 // `module` confuses commonjs detectors | |
| 3873 scope.modularize = module; | |
| 3874 scope.using = using; | |
| 3875 | |
| 3876 })(window); | |
| 3877 | |
| 3878 /* | |
| 3879 Build only script. | |
| 3880 | |
| 3881 Ensures scripts needed for basic x-platform compatibility | |
| 3882 will be run when platform.js is not loaded. | |
| 3883 */ | |
| 3884 if (!window.WebComponents) { | |
| 3885 | |
| 3886 /* | |
| 3887 On supported platforms, platform.js is not needed. To retain compatibili
ty | |
| 3888 with the polyfills, we stub out minimal functionality. | |
| 3889 */ | |
| 3890 if (!window.WebComponents) { | |
| 3891 | |
| 3892 WebComponents = { | |
| 3893 flush: function() {}, | |
| 3894 flags: {log: {}} | |
| 3895 }; | |
| 3896 | |
| 3897 Platform = WebComponents; | |
| 3898 | |
| 3899 CustomElements = { | |
| 3900 useNative: true, | |
| 3901 ready: true, | |
| 3902 takeRecords: function() {}, | |
| 3903 instanceof: function(obj, base) { | |
| 3904 return obj instanceof base; | |
| 3905 } | |
| 3906 }; | |
| 3907 | |
| 3908 HTMLImports = { | |
| 3909 useNative: true | |
| 3910 }; | |
| 3911 | |
| 3912 | |
| 3913 addEventListener('HTMLImportsLoaded', function() { | |
| 3914 document.dispatchEvent( | |
| 3915 new CustomEvent('WebComponentsReady', {bubbles: true}) | |
| 3916 ); | |
| 3917 }); | |
| 3918 | |
| 3919 | |
| 3920 // ShadowDOM | |
| 3921 ShadowDOMPolyfill = null; | |
| 3922 wrap = unwrap = function(n){ | |
| 3923 return n; | |
| 3924 }; | |
| 3925 | |
| 3926 } | |
| 3927 | |
| 3928 /* | |
| 3929 Create polyfill scope and feature detect native support. | |
| 3930 */ | |
| 3931 window.HTMLImports = window.HTMLImports || {flags:{}}; | |
| 3932 | |
| 3933 (function(scope) { | |
| 3934 | |
| 3935 /** | |
| 3936 Basic setup and simple module executer. We collect modules and then execute | |
| 3937 the code later, only if it's necessary for polyfilling. | |
| 3938 */ | |
| 3939 var IMPORT_LINK_TYPE = 'import'; | |
| 3940 var useNative = Boolean(IMPORT_LINK_TYPE in document.createElement('link')); | |
| 3941 | |
| 3942 /** | |
| 3943 Support `currentScript` on all browsers as `document._currentScript.` | |
| 3944 | |
| 3945 NOTE: We cannot polyfill `document.currentScript` because it's not possible | |
| 3946 both to override and maintain the ability to capture the native value. | |
| 3947 Therefore we choose to expose `_currentScript` both when native imports | |
| 3948 and the polyfill are in use. | |
| 3949 */ | |
| 3950 // NOTE: ShadowDOMPolyfill intrusion. | |
| 3951 var hasShadowDOMPolyfill = Boolean(window.ShadowDOMPolyfill); | |
| 3952 var wrap = function(node) { | |
| 3953 return hasShadowDOMPolyfill ? ShadowDOMPolyfill.wrapIfNeeded(node) : node; | |
| 3954 }; | |
| 3955 var rootDocument = wrap(document); | |
| 3956 | |
| 3957 var currentScriptDescriptor = { | |
| 3958 get: function() { | |
| 3959 var script = HTMLImports.currentScript || document.currentScript || | |
| 3960 // NOTE: only works when called in synchronously executing code. | |
| 3961 // readyState should check if `loading` but IE10 is | |
| 3962 // interactive when scripts run so we cheat. | |
| 3963 (document.readyState !== 'complete' ? | |
| 3964 document.scripts[document.scripts.length - 1] : null); | |
| 3965 return wrap(script); | |
| 3966 }, | |
| 3967 configurable: true | |
| 3968 }; | |
| 3969 | |
| 3970 Object.defineProperty(document, '_currentScript', currentScriptDescriptor); | |
| 3971 Object.defineProperty(rootDocument, '_currentScript', currentScriptDescriptor); | |
| 3972 | |
| 3973 /** | |
| 3974 Add support for the `HTMLImportsLoaded` event and the `HTMLImports.whenReady` | |
| 3975 method. This api is necessary because unlike the native implementation, | |
| 3976 script elements do not force imports to resolve. Instead, users should wrap | |
| 3977 code in either an `HTMLImportsLoaded` hander or after load time in an | |
| 3978 `HTMLImports.whenReady(callback)` call. | |
| 3979 | |
| 3980 NOTE: This module also supports these apis under the native implementation. | |
| 3981 Therefore, if this file is loaded, the same code can be used under both | |
| 3982 the polyfill and native implementation. | |
| 3983 */ | |
| 3984 | |
| 3985 var isIE = /Trident/.test(navigator.userAgent); | |
| 3986 | |
| 3987 // call a callback when all HTMLImports in the document at call time | |
| 3988 // (or at least document ready) have loaded. | |
| 3989 // 1. ensure the document is in a ready state (has dom), then | |
| 3990 // 2. watch for loading of imports and call callback when done | |
| 3991 function whenReady(callback, doc) { | |
| 3992 doc = doc || rootDocument; | |
| 3993 // if document is loading, wait and try again | |
| 3994 whenDocumentReady(function() { | |
| 3995 watchImportsLoad(callback, doc); | |
| 3996 }, doc); | |
| 3997 } | |
| 3998 | |
| 3999 // call the callback when the document is in a ready state (has dom) | |
| 4000 var requiredReadyState = isIE ? 'complete' : 'interactive'; | |
| 4001 var READY_EVENT = 'readystatechange'; | |
| 4002 function isDocumentReady(doc) { | |
| 4003 return (doc.readyState === 'complete' || | |
| 4004 doc.readyState === requiredReadyState); | |
| 4005 } | |
| 4006 | |
| 4007 // call <callback> when we ensure the document is in a ready state | |
| 4008 function whenDocumentReady(callback, doc) { | |
| 4009 if (!isDocumentReady(doc)) { | |
| 4010 var checkReady = function() { | |
| 4011 if (doc.readyState === 'complete' || | |
| 4012 doc.readyState === requiredReadyState) { | |
| 4013 doc.removeEventListener(READY_EVENT, checkReady); | |
| 4014 whenDocumentReady(callback, doc); | |
| 4015 } | |
| 4016 }; | |
| 4017 doc.addEventListener(READY_EVENT, checkReady); | |
| 4018 } else if (callback) { | |
| 4019 callback(); | |
| 4020 } | |
| 4021 } | |
| 4022 | |
| 4023 function markTargetLoaded(event) { | |
| 4024 event.target.__loaded = true; | |
| 4025 } | |
| 4026 | |
| 4027 // call <callback> when we ensure all imports have loaded | |
| 4028 function watchImportsLoad(callback, doc) { | |
| 4029 var imports = doc.querySelectorAll('link[rel=import]'); | |
| 4030 var loaded = 0, l = imports.length; | |
| 4031 function checkDone(d) { | |
| 4032 if ((loaded == l) && callback) { | |
| 4033 callback(); | |
| 4034 } | |
| 4035 } | |
| 4036 function loadedImport(e) { | |
| 4037 markTargetLoaded(e); | |
| 4038 loaded++; | |
| 4039 checkDone(); | |
| 4040 } | |
| 4041 if (l) { | |
| 4042 for (var i=0, imp; (i<l) && (imp=imports[i]); i++) { | |
| 4043 if (isImportLoaded(imp)) { | |
| 4044 loadedImport.call(imp, {target: imp}); | |
| 4045 } else { | |
| 4046 imp.addEventListener('load', loadedImport); | |
| 4047 imp.addEventListener('error', loadedImport); | |
| 4048 } | |
| 4049 } | |
| 4050 } else { | |
| 4051 checkDone(); | |
| 4052 } | |
| 4053 } | |
| 4054 | |
| 4055 // NOTE: test for native imports loading is based on explicitly watching | |
| 4056 // all imports (see below). | |
| 4057 // However, we cannot rely on this entirely without watching the entire document | |
| 4058 // for import links. For perf reasons, currently only head is watched. | |
| 4059 // Instead, we fallback to checking if the import property is available | |
| 4060 // and the document is not itself loading. | |
| 4061 function isImportLoaded(link) { | |
| 4062 return useNative ? link.__loaded || | |
| 4063 (link.import && link.import.readyState !== 'loading') : | |
| 4064 link.__importParsed; | |
| 4065 } | |
| 4066 | |
| 4067 // TODO(sorvell): Workaround for | |
| 4068 // https://www.w3.org/Bugs/Public/show_bug.cgi?id=25007, should be removed when | |
| 4069 // this bug is addressed. | |
| 4070 // (1) Install a mutation observer to see when HTMLImports have loaded | |
| 4071 // (2) if this script is run during document load it will watch any existing | |
| 4072 // imports for loading. | |
| 4073 // | |
| 4074 // NOTE: The workaround has restricted functionality: (1) it's only compatible | |
| 4075 // with imports that are added to document.head since the mutation observer | |
| 4076 // watches only head for perf reasons, (2) it requires this script | |
| 4077 // to run before any imports have completed loading. | |
| 4078 if (useNative) { | |
| 4079 new MutationObserver(function(mxns) { | |
| 4080 for (var i=0, l=mxns.length, m; (i < l) && (m=mxns[i]); i++) { | |
| 4081 if (m.addedNodes) { | |
| 4082 handleImports(m.addedNodes); | |
| 4083 } | |
| 4084 } | |
| 4085 }).observe(document.head, {childList: true}); | |
| 4086 | |
| 4087 function handleImports(nodes) { | |
| 4088 for (var i=0, l=nodes.length, n; (i<l) && (n=nodes[i]); i++) { | |
| 4089 if (isImport(n)) { | |
| 4090 handleImport(n); | |
| 4091 } | |
| 4092 } | |
| 4093 } | |
| 4094 | |
| 4095 function isImport(element) { | |
| 4096 return element.localName === 'link' && element.rel === 'import'; | |
| 4097 } | |
| 4098 | |
| 4099 function handleImport(element) { | |
| 4100 var loaded = element.import; | |
| 4101 if (loaded) { | |
| 4102 markTargetLoaded({target: element}); | |
| 4103 } else { | |
| 4104 element.addEventListener('load', markTargetLoaded); | |
| 4105 element.addEventListener('error', markTargetLoaded); | |
| 4106 } | |
| 4107 } | |
| 4108 | |
| 4109 // make sure to catch any imports that are in the process of loading | |
| 4110 // when this script is run. | |
| 4111 (function() { | |
| 4112 if (document.readyState === 'loading') { | |
| 4113 var imports = document.querySelectorAll('link[rel=import]'); | |
| 4114 for (var i=0, l=imports.length, imp; (i<l) && (imp=imports[i]); i++) { | |
| 4115 handleImport(imp); | |
| 4116 } | |
| 4117 } | |
| 4118 })(); | |
| 4119 | |
| 4120 } | |
| 4121 | |
| 4122 // Fire the 'HTMLImportsLoaded' event when imports in document at load time | |
| 4123 // have loaded. This event is required to simulate the script blocking | |
| 4124 // behavior of native imports. A main document script that needs to be sure | |
| 4125 // imports have loaded should wait for this event. | |
| 4126 whenReady(function() { | |
| 4127 HTMLImports.ready = true; | |
| 4128 HTMLImports.readyTime = new Date().getTime(); | |
| 4129 rootDocument.dispatchEvent( | |
| 4130 new CustomEvent('HTMLImportsLoaded', {bubbles: true}) | |
| 4131 ); | |
| 4132 }); | |
| 4133 | |
| 4134 // exports | |
| 4135 scope.IMPORT_LINK_TYPE = IMPORT_LINK_TYPE; | |
| 4136 scope.useNative = useNative; | |
| 4137 scope.rootDocument = rootDocument; | |
| 4138 scope.whenReady = whenReady; | |
| 4139 scope.isIE = isIE; | |
| 4140 | |
| 4141 })(HTMLImports); | |
| 4142 | |
| 4143 (function(scope) { | |
| 4144 | |
| 4145 // TODO(sorvell): It's desireable to provide a default stylesheet | |
| 4146 // that's convenient for styling unresolved elements, but | |
| 4147 // it's cumbersome to have to include this manually in every page. | |
| 4148 // It would make sense to put inside some HTMLImport but | |
| 4149 // the HTMLImports polyfill does not allow loading of stylesheets | |
| 4150 // that block rendering. Therefore this injection is tolerated here. | |
| 4151 var style = document.createElement('style'); | |
| 4152 style.textContent = '' | |
| 4153 + 'body {' | |
| 4154 + 'transition: opacity ease-in 0.2s;' | |
| 4155 + ' } \n' | |
| 4156 + 'body[unresolved] {' | |
| 4157 + 'opacity: 0; display: block; overflow: hidden;' | |
| 4158 + ' } \n' | |
| 4159 ; | |
| 4160 var head = document.querySelector('head'); | |
| 4161 head.insertBefore(style, head.firstChild); | |
| 4162 | |
| 4163 })(Platform); | |
| 4164 | |
| 4165 /* | |
| 4166 Build only script. | |
| 4167 | |
| 4168 Ensures scripts needed for basic x-platform compatibility | |
| 4169 will be run when platform.js is not loaded. | |
| 4170 */ | |
| 4171 } | |
| 4172 (function(global) { | |
| 4173 'use strict'; | |
| 4174 | |
| 4175 var testingExposeCycleCount = global.testingExposeCycleCount; | |
| 4176 | |
| 4177 // Detect and do basic sanity checking on Object/Array.observe. | |
| 4178 function detectObjectObserve() { | |
| 4179 if (typeof Object.observe !== 'function' || | |
| 4180 typeof Array.observe !== 'function') { | |
| 4181 return false; | |
| 4182 } | |
| 4183 | |
| 4184 var records = []; | |
| 4185 | |
| 4186 function callback(recs) { | |
| 4187 records = recs; | |
| 4188 } | |
| 4189 | |
| 4190 var test = {}; | |
| 4191 var arr = []; | |
| 4192 Object.observe(test, callback); | |
| 4193 Array.observe(arr, callback); | |
| 4194 test.id = 1; | |
| 4195 test.id = 2; | |
| 4196 delete test.id; | |
| 4197 arr.push(1, 2); | |
| 4198 arr.length = 0; | |
| 4199 | |
| 4200 Object.deliverChangeRecords(callback); | |
| 4201 if (records.length !== 5) | |
| 4202 return false; | |
| 4203 | |
| 4204 if (records[0].type != 'add' || | |
| 4205 records[1].type != 'update' || | |
| 4206 records[2].type != 'delete' || | |
| 4207 records[3].type != 'splice' || | |
| 4208 records[4].type != 'splice') { | |
| 4209 return false; | |
| 4210 } | |
| 4211 | |
| 4212 Object.unobserve(test, callback); | |
| 4213 Array.unobserve(arr, callback); | |
| 4214 | |
| 4215 return true; | |
| 4216 } | |
| 4217 | |
| 4218 var hasObserve = detectObjectObserve(); | |
| 4219 | |
| 4220 function detectEval() { | |
| 4221 // Don't test for eval if we're running in a Chrome App environment. | |
| 4222 // We check for APIs set that only exist in a Chrome App context. | |
| 4223 if (typeof chrome !== 'undefined' && chrome.app && chrome.app.runtime) { | |
| 4224 return false; | |
| 4225 } | |
| 4226 | |
| 4227 // Firefox OS Apps do not allow eval. This feature detection is very hacky | |
| 4228 // but even if some other platform adds support for this function this code | |
| 4229 // will continue to work. | |
| 4230 if (typeof navigator != 'undefined' && navigator.getDeviceStorage) { | |
| 4231 return false; | |
| 4232 } | |
| 4233 | |
| 4234 try { | |
| 4235 var f = new Function('', 'return true;'); | |
| 4236 return f(); | |
| 4237 } catch (ex) { | |
| 4238 return false; | |
| 4239 } | |
| 4240 } | |
| 4241 | |
| 4242 var hasEval = detectEval(); | |
| 4243 | |
| 4244 function isIndex(s) { | |
| 4245 return +s === s >>> 0 && s !== ''; | |
| 4246 } | |
| 4247 | |
| 4248 function toNumber(s) { | |
| 4249 return +s; | |
| 4250 } | |
| 4251 | |
| 4252 function isObject(obj) { | |
| 4253 return obj === Object(obj); | |
| 4254 } | |
| 4255 | |
| 4256 var numberIsNaN = global.Number.isNaN || function(value) { | |
| 4257 return typeof value === 'number' && global.isNaN(value); | |
| 4258 } | |
| 4259 | |
| 4260 function areSameValue(left, right) { | |
| 4261 if (left === right) | |
| 4262 return left !== 0 || 1 / left === 1 / right; | |
| 4263 if (numberIsNaN(left) && numberIsNaN(right)) | |
| 4264 return true; | |
| 4265 | |
| 4266 return left !== left && right !== right; | |
| 4267 } | |
| 4268 | |
| 4269 var createObject = ('__proto__' in {}) ? | |
| 4270 function(obj) { return obj; } : | |
| 4271 function(obj) { | |
| 4272 var proto = obj.__proto__; | |
| 4273 if (!proto) | |
| 4274 return obj; | |
| 4275 var newObject = Object.create(proto); | |
| 4276 Object.getOwnPropertyNames(obj).forEach(function(name) { | |
| 4277 Object.defineProperty(newObject, name, | |
| 4278 Object.getOwnPropertyDescriptor(obj, name)); | |
| 4279 }); | |
| 4280 return newObject; | |
| 4281 }; | |
| 4282 | |
| 4283 var identStart = '[\$_a-zA-Z]'; | |
| 4284 var identPart = '[\$_a-zA-Z0-9]'; | |
| 4285 var identRegExp = new RegExp('^' + identStart + '+' + identPart + '*' + '$'); | |
| 4286 | |
| 4287 function getPathCharType(char) { | |
| 4288 if (char === undefined) | |
| 4289 return 'eof'; | |
| 4290 | |
| 4291 var code = char.charCodeAt(0); | |
| 4292 | |
| 4293 switch(code) { | |
| 4294 case 0x5B: // [ | |
| 4295 case 0x5D: // ] | |
| 4296 case 0x2E: // . | |
| 4297 case 0x22: // " | |
| 4298 case 0x27: // ' | |
| 4299 case 0x30: // 0 | |
| 4300 return char; | |
| 4301 | |
| 4302 case 0x5F: // _ | |
| 4303 case 0x24: // $ | |
| 4304 return 'ident'; | |
| 4305 | |
| 4306 case 0x20: // Space | |
| 4307 case 0x09: // Tab | |
| 4308 case 0x0A: // Newline | |
| 4309 case 0x0D: // Return | |
| 4310 case 0xA0: // No-break space | |
| 4311 case 0xFEFF: // Byte Order Mark | |
| 4312 case 0x2028: // Line Separator | |
| 4313 case 0x2029: // Paragraph Separator | |
| 4314 return 'ws'; | |
| 4315 } | |
| 4316 | |
| 4317 // a-z, A-Z | |
| 4318 if ((0x61 <= code && code <= 0x7A) || (0x41 <= code && code <= 0x5A)) | |
| 4319 return 'ident'; | |
| 4320 | |
| 4321 // 1-9 | |
| 4322 if (0x31 <= code && code <= 0x39) | |
| 4323 return 'number'; | |
| 4324 | |
| 4325 return 'else'; | |
| 4326 } | |
| 4327 | |
| 4328 var pathStateMachine = { | |
| 4329 'beforePath': { | |
| 4330 'ws': ['beforePath'], | |
| 4331 'ident': ['inIdent', 'append'], | |
| 4332 '[': ['beforeElement'], | |
| 4333 'eof': ['afterPath'] | |
| 4334 }, | |
| 4335 | |
| 4336 'inPath': { | |
| 4337 'ws': ['inPath'], | |
| 4338 '.': ['beforeIdent'], | |
| 4339 '[': ['beforeElement'], | |
| 4340 'eof': ['afterPath'] | |
| 4341 }, | |
| 4342 | |
| 4343 'beforeIdent': { | |
| 4344 'ws': ['beforeIdent'], | |
| 4345 'ident': ['inIdent', 'append'] | |
| 4346 }, | |
| 4347 | |
| 4348 'inIdent': { | |
| 4349 'ident': ['inIdent', 'append'], | |
| 4350 '0': ['inIdent', 'append'], | |
| 4351 'number': ['inIdent', 'append'], | |
| 4352 'ws': ['inPath', 'push'], | |
| 4353 '.': ['beforeIdent', 'push'], | |
| 4354 '[': ['beforeElement', 'push'], | |
| 4355 'eof': ['afterPath', 'push'] | |
| 4356 }, | |
| 4357 | |
| 4358 'beforeElement': { | |
| 4359 'ws': ['beforeElement'], | |
| 4360 '0': ['afterZero', 'append'], | |
| 4361 'number': ['inIndex', 'append'], | |
| 4362 "'": ['inSingleQuote', 'append', ''], | |
| 4363 '"': ['inDoubleQuote', 'append', ''] | |
| 4364 }, | |
| 4365 | |
| 4366 'afterZero': { | |
| 4367 'ws': ['afterElement', 'push'], | |
| 4368 ']': ['inPath', 'push'] | |
| 4369 }, | |
| 4370 | |
| 4371 'inIndex': { | |
| 4372 '0': ['inIndex', 'append'], | |
| 4373 'number': ['inIndex', 'append'], | |
| 4374 'ws': ['afterElement'], | |
| 4375 ']': ['inPath', 'push'] | |
| 4376 }, | |
| 4377 | |
| 4378 'inSingleQuote': { | |
| 4379 "'": ['afterElement'], | |
| 4380 'eof': ['error'], | |
| 4381 'else': ['inSingleQuote', 'append'] | |
| 4382 }, | |
| 4383 | |
| 4384 'inDoubleQuote': { | |
| 4385 '"': ['afterElement'], | |
| 4386 'eof': ['error'], | |
| 4387 'else': ['inDoubleQuote', 'append'] | |
| 4388 }, | |
| 4389 | |
| 4390 'afterElement': { | |
| 4391 'ws': ['afterElement'], | |
| 4392 ']': ['inPath', 'push'] | |
| 4393 } | |
| 4394 } | |
| 4395 | |
| 4396 function noop() {} | |
| 4397 | |
| 4398 function parsePath(path) { | |
| 4399 var keys = []; | |
| 4400 var index = -1; | |
| 4401 var c, newChar, key, type, transition, action, typeMap, mode = 'beforePath'; | |
| 4402 | |
| 4403 var actions = { | |
| 4404 push: function() { | |
| 4405 if (key === undefined) | |
| 4406 return; | |
| 4407 | |
| 4408 keys.push(key); | |
| 4409 key = undefined; | |
| 4410 }, | |
| 4411 | |
| 4412 append: function() { | |
| 4413 if (key === undefined) | |
| 4414 key = newChar | |
| 4415 else | |
| 4416 key += newChar; | |
| 4417 } | |
| 4418 }; | |
| 4419 | |
| 4420 function maybeUnescapeQuote() { | |
| 4421 if (index >= path.length) | |
| 4422 return; | |
| 4423 | |
| 4424 var nextChar = path[index + 1]; | |
| 4425 if ((mode == 'inSingleQuote' && nextChar == "'") || | |
| 4426 (mode == 'inDoubleQuote' && nextChar == '"')) { | |
| 4427 index++; | |
| 4428 newChar = nextChar; | |
| 4429 actions.append(); | |
| 4430 return true; | |
| 4431 } | |
| 4432 } | |
| 4433 | |
| 4434 while (mode) { | |
| 4435 index++; | |
| 4436 c = path[index]; | |
| 4437 | |
| 4438 if (c == '\\' && maybeUnescapeQuote(mode)) | |
| 4439 continue; | |
| 4440 | |
| 4441 type = getPathCharType(c); | |
| 4442 typeMap = pathStateMachine[mode]; | |
| 4443 transition = typeMap[type] || typeMap['else'] || 'error'; | |
| 4444 | |
| 4445 if (transition == 'error') | |
| 4446 return; // parse error; | |
| 4447 | |
| 4448 mode = transition[0]; | |
| 4449 action = actions[transition[1]] || noop; | |
| 4450 newChar = transition[2] === undefined ? c : transition[2]; | |
| 4451 action(); | |
| 4452 | |
| 4453 if (mode === 'afterPath') { | |
| 4454 return keys; | |
| 4455 } | |
| 4456 } | |
| 4457 | |
| 4458 return; // parse error | |
| 4459 } | |
| 4460 | |
| 4461 function isIdent(s) { | |
| 4462 return identRegExp.test(s); | |
| 4463 } | |
| 4464 | |
| 4465 var constructorIsPrivate = {}; | |
| 4466 | |
| 4467 function Path(parts, privateToken) { | |
| 4468 if (privateToken !== constructorIsPrivate) | |
| 4469 throw Error('Use Path.get to retrieve path objects'); | |
| 4470 | |
| 4471 for (var i = 0; i < parts.length; i++) { | |
| 4472 this.push(String(parts[i])); | |
| 4473 } | |
| 4474 | |
| 4475 if (hasEval && this.length) { | |
| 4476 this.getValueFrom = this.compiledGetValueFromFn(); | |
| 4477 } | |
| 4478 } | |
| 4479 | |
| 4480 // TODO(rafaelw): Make simple LRU cache | |
| 4481 var pathCache = {}; | |
| 4482 | |
| 4483 function getPath(pathString) { | |
| 4484 if (pathString instanceof Path) | |
| 4485 return pathString; | |
| 4486 | |
| 4487 if (pathString == null || pathString.length == 0) | |
| 4488 pathString = ''; | |
| 4489 | |
| 4490 if (typeof pathString != 'string') { | |
| 4491 if (isIndex(pathString.length)) { | |
| 4492 // Constructed with array-like (pre-parsed) keys | |
| 4493 return new Path(pathString, constructorIsPrivate); | |
| 4494 } | |
| 4495 | |
| 4496 pathString = String(pathString); | |
| 4497 } | |
| 4498 | |
| 4499 var path = pathCache[pathString]; | |
| 4500 if (path) | |
| 4501 return path; | |
| 4502 | |
| 4503 var parts = parsePath(pathString); | |
| 4504 if (!parts) | |
| 4505 return invalidPath; | |
| 4506 | |
| 4507 var path = new Path(parts, constructorIsPrivate); | |
| 4508 pathCache[pathString] = path; | |
| 4509 return path; | |
| 4510 } | |
| 4511 | |
| 4512 Path.get = getPath; | |
| 4513 | |
| 4514 function formatAccessor(key) { | |
| 4515 if (isIndex(key)) { | |
| 4516 return '[' + key + ']'; | |
| 4517 } else { | |
| 4518 return '["' + key.replace(/"/g, '\\"') + '"]'; | |
| 4519 } | |
| 4520 } | |
| 4521 | |
| 4522 Path.prototype = createObject({ | |
| 4523 __proto__: [], | |
| 4524 valid: true, | |
| 4525 | |
| 4526 toString: function() { | |
| 4527 var pathString = ''; | |
| 4528 for (var i = 0; i < this.length; i++) { | |
| 4529 var key = this[i]; | |
| 4530 if (isIdent(key)) { | |
| 4531 pathString += i ? '.' + key : key; | |
| 4532 } else { | |
| 4533 pathString += formatAccessor(key); | |
| 4534 } | |
| 4535 } | |
| 4536 | |
| 4537 return pathString; | |
| 4538 }, | |
| 4539 | |
| 4540 getValueFrom: function(obj, directObserver) { | |
| 4541 for (var i = 0; i < this.length; i++) { | |
| 4542 if (obj == null) | |
| 4543 return; | |
| 4544 obj = obj[this[i]]; | |
| 4545 } | |
| 4546 return obj; | |
| 4547 }, | |
| 4548 | |
| 4549 iterateObjects: function(obj, observe) { | |
| 4550 for (var i = 0; i < this.length; i++) { | |
| 4551 if (i) | |
| 4552 obj = obj[this[i - 1]]; | |
| 4553 if (!isObject(obj)) | |
| 4554 return; | |
| 4555 observe(obj, this[i]); | |
| 4556 } | |
| 4557 }, | |
| 4558 | |
| 4559 compiledGetValueFromFn: function() { | |
| 4560 var str = ''; | |
| 4561 var pathString = 'obj'; | |
| 4562 str += 'if (obj != null'; | |
| 4563 var i = 0; | |
| 4564 var key; | |
| 4565 for (; i < (this.length - 1); i++) { | |
| 4566 key = this[i]; | |
| 4567 pathString += isIdent(key) ? '.' + key : formatAccessor(key); | |
| 4568 str += ' &&\n ' + pathString + ' != null'; | |
| 4569 } | |
| 4570 str += ')\n'; | |
| 4571 | |
| 4572 var key = this[i]; | |
| 4573 pathString += isIdent(key) ? '.' + key : formatAccessor(key); | |
| 4574 | |
| 4575 str += ' return ' + pathString + ';\nelse\n return undefined;'; | |
| 4576 return new Function('obj', str); | |
| 4577 }, | |
| 4578 | |
| 4579 setValueFrom: function(obj, value) { | |
| 4580 if (!this.length) | |
| 4581 return false; | |
| 4582 | |
| 4583 for (var i = 0; i < this.length - 1; i++) { | |
| 4584 if (!isObject(obj)) | |
| 4585 return false; | |
| 4586 obj = obj[this[i]]; | |
| 4587 } | |
| 4588 | |
| 4589 if (!isObject(obj)) | |
| 4590 return false; | |
| 4591 | |
| 4592 obj[this[i]] = value; | |
| 4593 return true; | |
| 4594 } | |
| 4595 }); | |
| 4596 | |
| 4597 var invalidPath = new Path('', constructorIsPrivate); | |
| 4598 invalidPath.valid = false; | |
| 4599 invalidPath.getValueFrom = invalidPath.setValueFrom = function() {}; | |
| 4600 | |
| 4601 var MAX_DIRTY_CHECK_CYCLES = 1000; | |
| 4602 | |
| 4603 function dirtyCheck(observer) { | |
| 4604 var cycles = 0; | |
| 4605 while (cycles < MAX_DIRTY_CHECK_CYCLES && observer.check_()) { | |
| 4606 cycles++; | |
| 4607 } | |
| 4608 if (testingExposeCycleCount) | |
| 4609 global.dirtyCheckCycleCount = cycles; | |
| 4610 | |
| 4611 return cycles > 0; | |
| 4612 } | |
| 4613 | |
| 4614 function objectIsEmpty(object) { | |
| 4615 for (var prop in object) | |
| 4616 return false; | |
| 4617 return true; | |
| 4618 } | |
| 4619 | |
| 4620 function diffIsEmpty(diff) { | |
| 4621 return objectIsEmpty(diff.added) && | |
| 4622 objectIsEmpty(diff.removed) && | |
| 4623 objectIsEmpty(diff.changed); | |
| 4624 } | |
| 4625 | |
| 4626 function diffObjectFromOldObject(object, oldObject) { | |
| 4627 var added = {}; | |
| 4628 var removed = {}; | |
| 4629 var changed = {}; | |
| 4630 | |
| 4631 for (var prop in oldObject) { | |
| 4632 var newValue = object[prop]; | |
| 4633 | |
| 4634 if (newValue !== undefined && newValue === oldObject[prop]) | |
| 4635 continue; | |
| 4636 | |
| 4637 if (!(prop in object)) { | |
| 4638 removed[prop] = undefined; | |
| 4639 continue; | |
| 4640 } | |
| 4641 | |
| 4642 if (newValue !== oldObject[prop]) | |
| 4643 changed[prop] = newValue; | |
| 4644 } | |
| 4645 | |
| 4646 for (var prop in object) { | |
| 4647 if (prop in oldObject) | |
| 4648 continue; | |
| 4649 | |
| 4650 added[prop] = object[prop]; | |
| 4651 } | |
| 4652 | |
| 4653 if (Array.isArray(object) && object.length !== oldObject.length) | |
| 4654 changed.length = object.length; | |
| 4655 | |
| 4656 return { | |
| 4657 added: added, | |
| 4658 removed: removed, | |
| 4659 changed: changed | |
| 4660 }; | |
| 4661 } | |
| 4662 | |
| 4663 var eomTasks = []; | |
| 4664 function runEOMTasks() { | |
| 4665 if (!eomTasks.length) | |
| 4666 return false; | |
| 4667 | |
| 4668 for (var i = 0; i < eomTasks.length; i++) { | |
| 4669 eomTasks[i](); | |
| 4670 } | |
| 4671 eomTasks.length = 0; | |
| 4672 return true; | |
| 4673 } | |
| 4674 | |
| 4675 var runEOM = hasObserve ? (function(){ | |
| 4676 return function(fn) { | |
| 4677 return Promise.resolve().then(fn); | |
| 4678 } | |
| 4679 })() : | |
| 4680 (function() { | |
| 4681 return function(fn) { | |
| 4682 eomTasks.push(fn); | |
| 4683 }; | |
| 4684 })(); | |
| 4685 | |
| 4686 var observedObjectCache = []; | |
| 4687 | |
| 4688 function newObservedObject() { | |
| 4689 var observer; | |
| 4690 var object; | |
| 4691 var discardRecords = false; | |
| 4692 var first = true; | |
| 4693 | |
| 4694 function callback(records) { | |
| 4695 if (observer && observer.state_ === OPENED && !discardRecords) | |
| 4696 observer.check_(records); | |
| 4697 } | |
| 4698 | |
| 4699 return { | |
| 4700 open: function(obs) { | |
| 4701 if (observer) | |
| 4702 throw Error('ObservedObject in use'); | |
| 4703 | |
| 4704 if (!first) | |
| 4705 Object.deliverChangeRecords(callback); | |
| 4706 | |
| 4707 observer = obs; | |
| 4708 first = false; | |
| 4709 }, | |
| 4710 observe: function(obj, arrayObserve) { | |
| 4711 object = obj; | |
| 4712 if (arrayObserve) | |
| 4713 Array.observe(object, callback); | |
| 4714 else | |
| 4715 Object.observe(object, callback); | |
| 4716 }, | |
| 4717 deliver: function(discard) { | |
| 4718 discardRecords = discard; | |
| 4719 Object.deliverChangeRecords(callback); | |
| 4720 discardRecords = false; | |
| 4721 }, | |
| 4722 close: function() { | |
| 4723 observer = undefined; | |
| 4724 Object.unobserve(object, callback); | |
| 4725 observedObjectCache.push(this); | |
| 4726 } | |
| 4727 }; | |
| 4728 } | |
| 4729 | |
| 4730 /* | |
| 4731 * The observedSet abstraction is a perf optimization which reduces the total | |
| 4732 * number of Object.observe observations of a set of objects. The idea is that | |
| 4733 * groups of Observers will have some object dependencies in common and this | |
| 4734 * observed set ensures that each object in the transitive closure of | |
| 4735 * dependencies is only observed once. The observedSet acts as a write barrier | |
| 4736 * such that whenever any change comes through, all Observers are checked for | |
| 4737 * changed values. | |
| 4738 * | |
| 4739 * Note that this optimization is explicitly moving work from setup-time to | |
| 4740 * change-time. | |
| 4741 * | |
| 4742 * TODO(rafaelw): Implement "garbage collection". In order to move work off | |
| 4743 * the critical path, when Observers are closed, their observed objects are | |
| 4744 * not Object.unobserve(d). As a result, it's possible that if the observedSet | |
| 4745 * is kept open, but some Observers have been closed, it could cause "leaks" | |
| 4746 * (prevent otherwise collectable objects from being collected). At some | |
| 4747 * point, we should implement incremental "gc" which keeps a list of | |
| 4748 * observedSets which may need clean-up and does small amounts of cleanup on a | |
| 4749 * timeout until all is clean. | |
| 4750 */ | |
| 4751 | |
| 4752 function getObservedObject(observer, object, arrayObserve) { | |
| 4753 var dir = observedObjectCache.pop() || newObservedObject(); | |
| 4754 dir.open(observer); | |
| 4755 dir.observe(object, arrayObserve); | |
| 4756 return dir; | |
| 4757 } | |
| 4758 | |
| 4759 var observedSetCache = []; | |
| 4760 | |
| 4761 function newObservedSet() { | |
| 4762 var observerCount = 0; | |
| 4763 var observers = []; | |
| 4764 var objects = []; | |
| 4765 var rootObj; | |
| 4766 var rootObjProps; | |
| 4767 | |
| 4768 function observe(obj, prop) { | |
| 4769 if (!obj) | |
| 4770 return; | |
| 4771 | |
| 4772 if (obj === rootObj) | |
| 4773 rootObjProps[prop] = true; | |
| 4774 | |
| 4775 if (objects.indexOf(obj) < 0) { | |
| 4776 objects.push(obj); | |
| 4777 Object.observe(obj, callback); | |
| 4778 } | |
| 4779 | |
| 4780 observe(Object.getPrototypeOf(obj), prop); | |
| 4781 } | |
| 4782 | |
| 4783 function allRootObjNonObservedProps(recs) { | |
| 4784 for (var i = 0; i < recs.length; i++) { | |
| 4785 var rec = recs[i]; | |
| 4786 if (rec.object !== rootObj || | |
| 4787 rootObjProps[rec.name] || | |
| 4788 rec.type === 'setPrototype') { | |
| 4789 return false; | |
| 4790 } | |
| 4791 } | |
| 4792 return true; | |
| 4793 } | |
| 4794 | |
| 4795 function callback(recs) { | |
| 4796 if (allRootObjNonObservedProps(recs)) | |
| 4797 return; | |
| 4798 | |
| 4799 var observer; | |
| 4800 for (var i = 0; i < observers.length; i++) { | |
| 4801 observer = observers[i]; | |
| 4802 if (observer.state_ == OPENED) { | |
| 4803 observer.iterateObjects_(observe); | |
| 4804 } | |
| 4805 } | |
| 4806 | |
| 4807 for (var i = 0; i < observers.length; i++) { | |
| 4808 observer = observers[i]; | |
| 4809 if (observer.state_ == OPENED) { | |
| 4810 observer.check_(); | |
| 4811 } | |
| 4812 } | |
| 4813 } | |
| 4814 | |
| 4815 var record = { | |
| 4816 objects: objects, | |
| 4817 get rootObject() { return rootObj; }, | |
| 4818 set rootObject(value) { | |
| 4819 rootObj = value; | |
| 4820 rootObjProps = {}; | |
| 4821 }, | |
| 4822 open: function(obs, object) { | |
| 4823 observers.push(obs); | |
| 4824 observerCount++; | |
| 4825 obs.iterateObjects_(observe); | |
| 4826 }, | |
| 4827 close: function(obs) { | |
| 4828 observerCount--; | |
| 4829 if (observerCount > 0) { | |
| 4830 return; | |
| 4831 } | |
| 4832 | |
| 4833 for (var i = 0; i < objects.length; i++) { | |
| 4834 Object.unobserve(objects[i], callback); | |
| 4835 Observer.unobservedCount++; | |
| 4836 } | |
| 4837 | |
| 4838 observers.length = 0; | |
| 4839 objects.length = 0; | |
| 4840 rootObj = undefined; | |
| 4841 rootObjProps = undefined; | |
| 4842 observedSetCache.push(this); | |
| 4843 if (lastObservedSet === this) | |
| 4844 lastObservedSet = null; | |
| 4845 }, | |
| 4846 }; | |
| 4847 | |
| 4848 return record; | |
| 4849 } | |
| 4850 | |
| 4851 var lastObservedSet; | |
| 4852 | |
| 4853 function getObservedSet(observer, obj) { | |
| 4854 if (!lastObservedSet || lastObservedSet.rootObject !== obj) { | |
| 4855 lastObservedSet = observedSetCache.pop() || newObservedSet(); | |
| 4856 lastObservedSet.rootObject = obj; | |
| 4857 } | |
| 4858 lastObservedSet.open(observer, obj); | |
| 4859 return lastObservedSet; | |
| 4860 } | |
| 4861 | |
| 4862 var UNOPENED = 0; | |
| 4863 var OPENED = 1; | |
| 4864 var CLOSED = 2; | |
| 4865 var RESETTING = 3; | |
| 4866 | |
| 4867 var nextObserverId = 1; | |
| 4868 | |
| 4869 function Observer() { | |
| 4870 this.state_ = UNOPENED; | |
| 4871 this.callback_ = undefined; | |
| 4872 this.target_ = undefined; // TODO(rafaelw): Should be WeakRef | |
| 4873 this.directObserver_ = undefined; | |
| 4874 this.value_ = undefined; | |
| 4875 this.id_ = nextObserverId++; | |
| 4876 } | |
| 4877 | |
| 4878 Observer.prototype = { | |
| 4879 open: function(callback, target) { | |
| 4880 if (this.state_ != UNOPENED) | |
| 4881 throw Error('Observer has already been opened.'); | |
| 4882 | |
| 4883 addToAll(this); | |
| 4884 this.callback_ = callback; | |
| 4885 this.target_ = target; | |
| 4886 this.connect_(); | |
| 4887 this.state_ = OPENED; | |
| 4888 return this.value_; | |
| 4889 }, | |
| 4890 | |
| 4891 close: function() { | |
| 4892 if (this.state_ != OPENED) | |
| 4893 return; | |
| 4894 | |
| 4895 removeFromAll(this); | |
| 4896 this.disconnect_(); | |
| 4897 this.value_ = undefined; | |
| 4898 this.callback_ = undefined; | |
| 4899 this.target_ = undefined; | |
| 4900 this.state_ = CLOSED; | |
| 4901 }, | |
| 4902 | |
| 4903 deliver: function() { | |
| 4904 if (this.state_ != OPENED) | |
| 4905 return; | |
| 4906 | |
| 4907 dirtyCheck(this); | |
| 4908 }, | |
| 4909 | |
| 4910 report_: function(changes) { | |
| 4911 try { | |
| 4912 this.callback_.apply(this.target_, changes); | |
| 4913 } catch (ex) { | |
| 4914 Observer._errorThrownDuringCallback = true; | |
| 4915 console.error('Exception caught during observer callback: ' + | |
| 4916 (ex.stack || ex)); | |
| 4917 } | |
| 4918 }, | |
| 4919 | |
| 4920 discardChanges: function() { | |
| 4921 this.check_(undefined, true); | |
| 4922 return this.value_; | |
| 4923 } | |
| 4924 } | |
| 4925 | |
| 4926 var collectObservers = !hasObserve; | |
| 4927 var allObservers; | |
| 4928 Observer._allObserversCount = 0; | |
| 4929 | |
| 4930 if (collectObservers) { | |
| 4931 allObservers = []; | |
| 4932 } | |
| 4933 | |
| 4934 function addToAll(observer) { | |
| 4935 Observer._allObserversCount++; | |
| 4936 if (!collectObservers) | |
| 4937 return; | |
| 4938 | |
| 4939 allObservers.push(observer); | |
| 4940 } | |
| 4941 | |
| 4942 function removeFromAll(observer) { | |
| 4943 Observer._allObserversCount--; | |
| 4944 } | |
| 4945 | |
| 4946 var runningMicrotaskCheckpoint = false; | |
| 4947 | |
| 4948 global.Platform = global.Platform || {}; | |
| 4949 | |
| 4950 global.Platform.performMicrotaskCheckpoint = function() { | |
| 4951 if (runningMicrotaskCheckpoint) | |
| 4952 return; | |
| 4953 | |
| 4954 if (!collectObservers) | |
| 4955 return; | |
| 4956 | |
| 4957 runningMicrotaskCheckpoint = true; | |
| 4958 | |
| 4959 var cycles = 0; | |
| 4960 var anyChanged, toCheck; | |
| 4961 | |
| 4962 do { | |
| 4963 cycles++; | |
| 4964 toCheck = allObservers; | |
| 4965 allObservers = []; | |
| 4966 anyChanged = false; | |
| 4967 | |
| 4968 for (var i = 0; i < toCheck.length; i++) { | |
| 4969 var observer = toCheck[i]; | |
| 4970 if (observer.state_ != OPENED) | |
| 4971 continue; | |
| 4972 | |
| 4973 if (observer.check_()) | |
| 4974 anyChanged = true; | |
| 4975 | |
| 4976 allObservers.push(observer); | |
| 4977 } | |
| 4978 if (runEOMTasks()) | |
| 4979 anyChanged = true; | |
| 4980 } while (cycles < MAX_DIRTY_CHECK_CYCLES && anyChanged); | |
| 4981 | |
| 4982 if (testingExposeCycleCount) | |
| 4983 global.dirtyCheckCycleCount = cycles; | |
| 4984 | |
| 4985 runningMicrotaskCheckpoint = false; | |
| 4986 }; | |
| 4987 | |
| 4988 if (collectObservers) { | |
| 4989 global.Platform.clearObservers = function() { | |
| 4990 allObservers = []; | |
| 4991 }; | |
| 4992 } | |
| 4993 | |
| 4994 function ObjectObserver(object) { | |
| 4995 Observer.call(this); | |
| 4996 this.value_ = object; | |
| 4997 this.oldObject_ = undefined; | |
| 4998 } | |
| 4999 | |
| 5000 ObjectObserver.prototype = createObject({ | |
| 5001 __proto__: Observer.prototype, | |
| 5002 | |
| 5003 arrayObserve: false, | |
| 5004 | |
| 5005 connect_: function(callback, target) { | |
| 5006 if (hasObserve) { | |
| 5007 this.directObserver_ = getObservedObject(this, this.value_, | |
| 5008 this.arrayObserve); | |
| 5009 } else { | |
| 5010 this.oldObject_ = this.copyObject(this.value_); | |
| 5011 } | |
| 5012 | |
| 5013 }, | |
| 5014 | |
| 5015 copyObject: function(object) { | |
| 5016 var copy = Array.isArray(object) ? [] : {}; | |
| 5017 for (var prop in object) { | |
| 5018 copy[prop] = object[prop]; | |
| 5019 }; | |
| 5020 if (Array.isArray(object)) | |
| 5021 copy.length = object.length; | |
| 5022 return copy; | |
| 5023 }, | |
| 5024 | |
| 5025 check_: function(changeRecords, skipChanges) { | |
| 5026 var diff; | |
| 5027 var oldValues; | |
| 5028 if (hasObserve) { | |
| 5029 if (!changeRecords) | |
| 5030 return false; | |
| 5031 | |
| 5032 oldValues = {}; | |
| 5033 diff = diffObjectFromChangeRecords(this.value_, changeRecords, | |
| 5034 oldValues); | |
| 5035 } else { | |
| 5036 oldValues = this.oldObject_; | |
| 5037 diff = diffObjectFromOldObject(this.value_, this.oldObject_); | |
| 5038 } | |
| 5039 | |
| 5040 if (diffIsEmpty(diff)) | |
| 5041 return false; | |
| 5042 | |
| 5043 if (!hasObserve) | |
| 5044 this.oldObject_ = this.copyObject(this.value_); | |
| 5045 | |
| 5046 this.report_([ | |
| 5047 diff.added || {}, | |
| 5048 diff.removed || {}, | |
| 5049 diff.changed || {}, | |
| 5050 function(property) { | |
| 5051 return oldValues[property]; | |
| 5052 } | |
| 5053 ]); | |
| 5054 | |
| 5055 return true; | |
| 5056 }, | |
| 5057 | |
| 5058 disconnect_: function() { | |
| 5059 if (hasObserve) { | |
| 5060 this.directObserver_.close(); | |
| 5061 this.directObserver_ = undefined; | |
| 5062 } else { | |
| 5063 this.oldObject_ = undefined; | |
| 5064 } | |
| 5065 }, | |
| 5066 | |
| 5067 deliver: function() { | |
| 5068 if (this.state_ != OPENED) | |
| 5069 return; | |
| 5070 | |
| 5071 if (hasObserve) | |
| 5072 this.directObserver_.deliver(false); | |
| 5073 else | |
| 5074 dirtyCheck(this); | |
| 5075 }, | |
| 5076 | |
| 5077 discardChanges: function() { | |
| 5078 if (this.directObserver_) | |
| 5079 this.directObserver_.deliver(true); | |
| 5080 else | |
| 5081 this.oldObject_ = this.copyObject(this.value_); | |
| 5082 | |
| 5083 return this.value_; | |
| 5084 } | |
| 5085 }); | |
| 5086 | |
| 5087 function ArrayObserver(array) { | |
| 5088 if (!Array.isArray(array)) | |
| 5089 throw Error('Provided object is not an Array'); | |
| 5090 ObjectObserver.call(this, array); | |
| 5091 } | |
| 5092 | |
| 5093 ArrayObserver.prototype = createObject({ | |
| 5094 | |
| 5095 __proto__: ObjectObserver.prototype, | |
| 5096 | |
| 5097 arrayObserve: true, | |
| 5098 | |
| 5099 copyObject: function(arr) { | |
| 5100 return arr.slice(); | |
| 5101 }, | |
| 5102 | |
| 5103 check_: function(changeRecords) { | |
| 5104 var splices; | |
| 5105 if (hasObserve) { | |
| 5106 if (!changeRecords) | |
| 5107 return false; | |
| 5108 splices = projectArraySplices(this.value_, changeRecords); | |
| 5109 } else { | |
| 5110 splices = calcSplices(this.value_, 0, this.value_.length, | |
| 5111 this.oldObject_, 0, this.oldObject_.length); | |
| 5112 } | |
| 5113 | |
| 5114 if (!splices || !splices.length) | |
| 5115 return false; | |
| 5116 | |
| 5117 if (!hasObserve) | |
| 5118 this.oldObject_ = this.copyObject(this.value_); | |
| 5119 | |
| 5120 this.report_([splices]); | |
| 5121 return true; | |
| 5122 } | |
| 5123 }); | |
| 5124 | |
| 5125 ArrayObserver.applySplices = function(previous, current, splices) { | |
| 5126 splices.forEach(function(splice) { | |
| 5127 var spliceArgs = [splice.index, splice.removed.length]; | |
| 5128 var addIndex = splice.index; | |
| 5129 while (addIndex < splice.index + splice.addedCount) { | |
| 5130 spliceArgs.push(current[addIndex]); | |
| 5131 addIndex++; | |
| 5132 } | |
| 5133 | |
| 5134 Array.prototype.splice.apply(previous, spliceArgs); | |
| 5135 }); | |
| 5136 }; | |
| 5137 | |
| 5138 function PathObserver(object, path) { | |
| 5139 Observer.call(this); | |
| 5140 | |
| 5141 this.object_ = object; | |
| 5142 this.path_ = getPath(path); | |
| 5143 this.directObserver_ = undefined; | |
| 5144 } | |
| 5145 | |
| 5146 PathObserver.prototype = createObject({ | |
| 5147 __proto__: Observer.prototype, | |
| 5148 | |
| 5149 get path() { | |
| 5150 return this.path_; | |
| 5151 }, | |
| 5152 | |
| 5153 connect_: function() { | |
| 5154 if (hasObserve) | |
| 5155 this.directObserver_ = getObservedSet(this, this.object_); | |
| 5156 | |
| 5157 this.check_(undefined, true); | |
| 5158 }, | |
| 5159 | |
| 5160 disconnect_: function() { | |
| 5161 this.value_ = undefined; | |
| 5162 | |
| 5163 if (this.directObserver_) { | |
| 5164 this.directObserver_.close(this); | |
| 5165 this.directObserver_ = undefined; | |
| 5166 } | |
| 5167 }, | |
| 5168 | |
| 5169 iterateObjects_: function(observe) { | |
| 5170 this.path_.iterateObjects(this.object_, observe); | |
| 5171 }, | |
| 5172 | |
| 5173 check_: function(changeRecords, skipChanges) { | |
| 5174 var oldValue = this.value_; | |
| 5175 this.value_ = this.path_.getValueFrom(this.object_); | |
| 5176 if (skipChanges || areSameValue(this.value_, oldValue)) | |
| 5177 return false; | |
| 5178 | |
| 5179 this.report_([this.value_, oldValue, this]); | |
| 5180 return true; | |
| 5181 }, | |
| 5182 | |
| 5183 setValue: function(newValue) { | |
| 5184 if (this.path_) | |
| 5185 this.path_.setValueFrom(this.object_, newValue); | |
| 5186 } | |
| 5187 }); | |
| 5188 | |
| 5189 function CompoundObserver(reportChangesOnOpen) { | |
| 5190 Observer.call(this); | |
| 5191 | |
| 5192 this.reportChangesOnOpen_ = reportChangesOnOpen; | |
| 5193 this.value_ = []; | |
| 5194 this.directObserver_ = undefined; | |
| 5195 this.observed_ = []; | |
| 5196 } | |
| 5197 | |
| 5198 var observerSentinel = {}; | |
| 5199 | |
| 5200 CompoundObserver.prototype = createObject({ | |
| 5201 __proto__: Observer.prototype, | |
| 5202 | |
| 5203 connect_: function() { | |
| 5204 if (hasObserve) { | |
| 5205 var object; | |
| 5206 var needsDirectObserver = false; | |
| 5207 for (var i = 0; i < this.observed_.length; i += 2) { | |
| 5208 object = this.observed_[i] | |
| 5209 if (object !== observerSentinel) { | |
| 5210 needsDirectObserver = true; | |
| 5211 break; | |
| 5212 } | |
| 5213 } | |
| 5214 | |
| 5215 if (needsDirectObserver) | |
| 5216 this.directObserver_ = getObservedSet(this, object); | |
| 5217 } | |
| 5218 | |
| 5219 this.check_(undefined, !this.reportChangesOnOpen_); | |
| 5220 }, | |
| 5221 | |
| 5222 disconnect_: function() { | |
| 5223 for (var i = 0; i < this.observed_.length; i += 2) { | |
| 5224 if (this.observed_[i] === observerSentinel) | |
| 5225 this.observed_[i + 1].close(); | |
| 5226 } | |
| 5227 this.observed_.length = 0; | |
| 5228 this.value_.length = 0; | |
| 5229 | |
| 5230 if (this.directObserver_) { | |
| 5231 this.directObserver_.close(this); | |
| 5232 this.directObserver_ = undefined; | |
| 5233 } | |
| 5234 }, | |
| 5235 | |
| 5236 addPath: function(object, path) { | |
| 5237 if (this.state_ != UNOPENED && this.state_ != RESETTING) | |
| 5238 throw Error('Cannot add paths once started.'); | |
| 5239 | |
| 5240 var path = getPath(path); | |
| 5241 this.observed_.push(object, path); | |
| 5242 if (!this.reportChangesOnOpen_) | |
| 5243 return; | |
| 5244 var index = this.observed_.length / 2 - 1; | |
| 5245 this.value_[index] = path.getValueFrom(object); | |
| 5246 }, | |
| 5247 | |
| 5248 addObserver: function(observer) { | |
| 5249 if (this.state_ != UNOPENED && this.state_ != RESETTING) | |
| 5250 throw Error('Cannot add observers once started.'); | |
| 5251 | |
| 5252 this.observed_.push(observerSentinel, observer); | |
| 5253 if (!this.reportChangesOnOpen_) | |
| 5254 return; | |
| 5255 var index = this.observed_.length / 2 - 1; | |
| 5256 this.value_[index] = observer.open(this.deliver, this); | |
| 5257 }, | |
| 5258 | |
| 5259 startReset: function() { | |
| 5260 if (this.state_ != OPENED) | |
| 5261 throw Error('Can only reset while open'); | |
| 5262 | |
| 5263 this.state_ = RESETTING; | |
| 5264 this.disconnect_(); | |
| 5265 }, | |
| 5266 | |
| 5267 finishReset: function() { | |
| 5268 if (this.state_ != RESETTING) | |
| 5269 throw Error('Can only finishReset after startReset'); | |
| 5270 this.state_ = OPENED; | |
| 5271 this.connect_(); | |
| 5272 | |
| 5273 return this.value_; | |
| 5274 }, | |
| 5275 | |
| 5276 iterateObjects_: function(observe) { | |
| 5277 var object; | |
| 5278 for (var i = 0; i < this.observed_.length; i += 2) { | |
| 5279 object = this.observed_[i] | |
| 5280 if (object !== observerSentinel) | |
| 5281 this.observed_[i + 1].iterateObjects(object, observe) | |
| 5282 } | |
| 5283 }, | |
| 5284 | |
| 5285 check_: function(changeRecords, skipChanges) { | |
| 5286 var oldValues; | |
| 5287 for (var i = 0; i < this.observed_.length; i += 2) { | |
| 5288 var object = this.observed_[i]; | |
| 5289 var path = this.observed_[i+1]; | |
| 5290 var value; | |
| 5291 if (object === observerSentinel) { | |
| 5292 var observable = path; | |
| 5293 value = this.state_ === UNOPENED ? | |
| 5294 observable.open(this.deliver, this) : | |
| 5295 observable.discardChanges(); | |
| 5296 } else { | |
| 5297 value = path.getValueFrom(object); | |
| 5298 } | |
| 5299 | |
| 5300 if (skipChanges) { | |
| 5301 this.value_[i / 2] = value; | |
| 5302 continue; | |
| 5303 } | |
| 5304 | |
| 5305 if (areSameValue(value, this.value_[i / 2])) | |
| 5306 continue; | |
| 5307 | |
| 5308 oldValues = oldValues || []; | |
| 5309 oldValues[i / 2] = this.value_[i / 2]; | |
| 5310 this.value_[i / 2] = value; | |
| 5311 } | |
| 5312 | |
| 5313 if (!oldValues) | |
| 5314 return false; | |
| 5315 | |
| 5316 // TODO(rafaelw): Having observed_ as the third callback arg here is | |
| 5317 // pretty lame API. Fix. | |
| 5318 this.report_([this.value_, oldValues, this.observed_]); | |
| 5319 return true; | |
| 5320 } | |
| 5321 }); | |
| 5322 | |
| 5323 function identFn(value) { return value; } | |
| 5324 | |
| 5325 function ObserverTransform(observable, getValueFn, setValueFn, | |
| 5326 dontPassThroughSet) { | |
| 5327 this.callback_ = undefined; | |
| 5328 this.target_ = undefined; | |
| 5329 this.value_ = undefined; | |
| 5330 this.observable_ = observable; | |
| 5331 this.getValueFn_ = getValueFn || identFn; | |
| 5332 this.setValueFn_ = setValueFn || identFn; | |
| 5333 // TODO(rafaelw): This is a temporary hack. PolymerExpressions needs this | |
| 5334 // at the moment because of a bug in it's dependency tracking. | |
| 5335 this.dontPassThroughSet_ = dontPassThroughSet; | |
| 5336 } | |
| 5337 | |
| 5338 ObserverTransform.prototype = { | |
| 5339 open: function(callback, target) { | |
| 5340 this.callback_ = callback; | |
| 5341 this.target_ = target; | |
| 5342 this.value_ = | |
| 5343 this.getValueFn_(this.observable_.open(this.observedCallback_, this)); | |
| 5344 return this.value_; | |
| 5345 }, | |
| 5346 | |
| 5347 observedCallback_: function(value) { | |
| 5348 value = this.getValueFn_(value); | |
| 5349 if (areSameValue(value, this.value_)) | |
| 5350 return; | |
| 5351 var oldValue = this.value_; | |
| 5352 this.value_ = value; | |
| 5353 this.callback_.call(this.target_, this.value_, oldValue); | |
| 5354 }, | |
| 5355 | |
| 5356 discardChanges: function() { | |
| 5357 this.value_ = this.getValueFn_(this.observable_.discardChanges()); | |
| 5358 return this.value_; | |
| 5359 }, | |
| 5360 | |
| 5361 deliver: function() { | |
| 5362 return this.observable_.deliver(); | |
| 5363 }, | |
| 5364 | |
| 5365 setValue: function(value) { | |
| 5366 value = this.setValueFn_(value); | |
| 5367 if (!this.dontPassThroughSet_ && this.observable_.setValue) | |
| 5368 return this.observable_.setValue(value); | |
| 5369 }, | |
| 5370 | |
| 5371 close: function() { | |
| 5372 if (this.observable_) | |
| 5373 this.observable_.close(); | |
| 5374 this.callback_ = undefined; | |
| 5375 this.target_ = undefined; | |
| 5376 this.observable_ = undefined; | |
| 5377 this.value_ = undefined; | |
| 5378 this.getValueFn_ = undefined; | |
| 5379 this.setValueFn_ = undefined; | |
| 5380 } | |
| 5381 } | |
| 5382 | |
| 5383 var expectedRecordTypes = { | |
| 5384 add: true, | |
| 5385 update: true, | |
| 5386 delete: true | |
| 5387 }; | |
| 5388 | |
| 5389 function diffObjectFromChangeRecords(object, changeRecords, oldValues) { | |
| 5390 var added = {}; | |
| 5391 var removed = {}; | |
| 5392 | |
| 5393 for (var i = 0; i < changeRecords.length; i++) { | |
| 5394 var record = changeRecords[i]; | |
| 5395 if (!expectedRecordTypes[record.type]) { | |
| 5396 console.error('Unknown changeRecord type: ' + record.type); | |
| 5397 console.error(record); | |
| 5398 continue; | |
| 5399 } | |
| 5400 | |
| 5401 if (!(record.name in oldValues)) | |
| 5402 oldValues[record.name] = record.oldValue; | |
| 5403 | |
| 5404 if (record.type == 'update') | |
| 5405 continue; | |
| 5406 | |
| 5407 if (record.type == 'add') { | |
| 5408 if (record.name in removed) | |
| 5409 delete removed[record.name]; | |
| 5410 else | |
| 5411 added[record.name] = true; | |
| 5412 | |
| 5413 continue; | |
| 5414 } | |
| 5415 | |
| 5416 // type = 'delete' | |
| 5417 if (record.name in added) { | |
| 5418 delete added[record.name]; | |
| 5419 delete oldValues[record.name]; | |
| 5420 } else { | |
| 5421 removed[record.name] = true; | |
| 5422 } | |
| 5423 } | |
| 5424 | |
| 5425 for (var prop in added) | |
| 5426 added[prop] = object[prop]; | |
| 5427 | |
| 5428 for (var prop in removed) | |
| 5429 removed[prop] = undefined; | |
| 5430 | |
| 5431 var changed = {}; | |
| 5432 for (var prop in oldValues) { | |
| 5433 if (prop in added || prop in removed) | |
| 5434 continue; | |
| 5435 | |
| 5436 var newValue = object[prop]; | |
| 5437 if (oldValues[prop] !== newValue) | |
| 5438 changed[prop] = newValue; | |
| 5439 } | |
| 5440 | |
| 5441 return { | |
| 5442 added: added, | |
| 5443 removed: removed, | |
| 5444 changed: changed | |
| 5445 }; | |
| 5446 } | |
| 5447 | |
| 5448 function newSplice(index, removed, addedCount) { | |
| 5449 return { | |
| 5450 index: index, | |
| 5451 removed: removed, | |
| 5452 addedCount: addedCount | |
| 5453 }; | |
| 5454 } | |
| 5455 | |
| 5456 var EDIT_LEAVE = 0; | |
| 5457 var EDIT_UPDATE = 1; | |
| 5458 var EDIT_ADD = 2; | |
| 5459 var EDIT_DELETE = 3; | |
| 5460 | |
| 5461 function ArraySplice() {} | |
| 5462 | |
| 5463 ArraySplice.prototype = { | |
| 5464 | |
| 5465 // Note: This function is *based* on the computation of the Levenshtein | |
| 5466 // "edit" distance. The one change is that "updates" are treated as two | |
| 5467 // edits - not one. With Array splices, an update is really a delete | |
| 5468 // followed by an add. By retaining this, we optimize for "keeping" the | |
| 5469 // maximum array items in the original array. For example: | |
| 5470 // | |
| 5471 // 'xxxx123' -> '123yyyy' | |
| 5472 // | |
| 5473 // With 1-edit updates, the shortest path would be just to update all seven | |
| 5474 // characters. With 2-edit updates, we delete 4, leave 3, and add 4. This | |
| 5475 // leaves the substring '123' intact. | |
| 5476 calcEditDistances: function(current, currentStart, currentEnd, | |
| 5477 old, oldStart, oldEnd) { | |
| 5478 // "Deletion" columns | |
| 5479 var rowCount = oldEnd - oldStart + 1; | |
| 5480 var columnCount = currentEnd - currentStart + 1; | |
| 5481 var distances = new Array(rowCount); | |
| 5482 | |
| 5483 // "Addition" rows. Initialize null column. | |
| 5484 for (var i = 0; i < rowCount; i++) { | |
| 5485 distances[i] = new Array(columnCount); | |
| 5486 distances[i][0] = i; | |
| 5487 } | |
| 5488 | |
| 5489 // Initialize null row | |
| 5490 for (var j = 0; j < columnCount; j++) | |
| 5491 distances[0][j] = j; | |
| 5492 | |
| 5493 for (var i = 1; i < rowCount; i++) { | |
| 5494 for (var j = 1; j < columnCount; j++) { | |
| 5495 if (this.equals(current[currentStart + j - 1], old[oldStart + i - 1])) | |
| 5496 distances[i][j] = distances[i - 1][j - 1]; | |
| 5497 else { | |
| 5498 var north = distances[i - 1][j] + 1; | |
| 5499 var west = distances[i][j - 1] + 1; | |
| 5500 distances[i][j] = north < west ? north : west; | |
| 5501 } | |
| 5502 } | |
| 5503 } | |
| 5504 | |
| 5505 return distances; | |
| 5506 }, | |
| 5507 | |
| 5508 // This starts at the final weight, and walks "backward" by finding | |
| 5509 // the minimum previous weight recursively until the origin of the weight | |
| 5510 // matrix. | |
| 5511 spliceOperationsFromEditDistances: function(distances) { | |
| 5512 var i = distances.length - 1; | |
| 5513 var j = distances[0].length - 1; | |
| 5514 var current = distances[i][j]; | |
| 5515 var edits = []; | |
| 5516 while (i > 0 || j > 0) { | |
| 5517 if (i == 0) { | |
| 5518 edits.push(EDIT_ADD); | |
| 5519 j--; | |
| 5520 continue; | |
| 5521 } | |
| 5522 if (j == 0) { | |
| 5523 edits.push(EDIT_DELETE); | |
| 5524 i--; | |
| 5525 continue; | |
| 5526 } | |
| 5527 var northWest = distances[i - 1][j - 1]; | |
| 5528 var west = distances[i - 1][j]; | |
| 5529 var north = distances[i][j - 1]; | |
| 5530 | |
| 5531 var min; | |
| 5532 if (west < north) | |
| 5533 min = west < northWest ? west : northWest; | |
| 5534 else | |
| 5535 min = north < northWest ? north : northWest; | |
| 5536 | |
| 5537 if (min == northWest) { | |
| 5538 if (northWest == current) { | |
| 5539 edits.push(EDIT_LEAVE); | |
| 5540 } else { | |
| 5541 edits.push(EDIT_UPDATE); | |
| 5542 current = northWest; | |
| 5543 } | |
| 5544 i--; | |
| 5545 j--; | |
| 5546 } else if (min == west) { | |
| 5547 edits.push(EDIT_DELETE); | |
| 5548 i--; | |
| 5549 current = west; | |
| 5550 } else { | |
| 5551 edits.push(EDIT_ADD); | |
| 5552 j--; | |
| 5553 current = north; | |
| 5554 } | |
| 5555 } | |
| 5556 | |
| 5557 edits.reverse(); | |
| 5558 return edits; | |
| 5559 }, | |
| 5560 | |
| 5561 /** | |
| 5562 * Splice Projection functions: | |
| 5563 * | |
| 5564 * A splice map is a representation of how a previous array of items | |
| 5565 * was transformed into a new array of items. Conceptually it is a list of | |
| 5566 * tuples of | |
| 5567 * | |
| 5568 * <index, removed, addedCount> | |
| 5569 * | |
| 5570 * which are kept in ascending index order of. The tuple represents that at | |
| 5571 * the |index|, |removed| sequence of items were removed, and counting forwa
rd | |
| 5572 * from |index|, |addedCount| items were added. | |
| 5573 */ | |
| 5574 | |
| 5575 /** | |
| 5576 * Lacking individual splice mutation information, the minimal set of | |
| 5577 * splices can be synthesized given the previous state and final state of an | |
| 5578 * array. The basic approach is to calculate the edit distance matrix and | |
| 5579 * choose the shortest path through it. | |
| 5580 * | |
| 5581 * Complexity: O(l * p) | |
| 5582 * l: The length of the current array | |
| 5583 * p: The length of the old array | |
| 5584 */ | |
| 5585 calcSplices: function(current, currentStart, currentEnd, | |
| 5586 old, oldStart, oldEnd) { | |
| 5587 var prefixCount = 0; | |
| 5588 var suffixCount = 0; | |
| 5589 | |
| 5590 var minLength = Math.min(currentEnd - currentStart, oldEnd - oldStart); | |
| 5591 if (currentStart == 0 && oldStart == 0) | |
| 5592 prefixCount = this.sharedPrefix(current, old, minLength); | |
| 5593 | |
| 5594 if (currentEnd == current.length && oldEnd == old.length) | |
| 5595 suffixCount = this.sharedSuffix(current, old, minLength - prefixCount); | |
| 5596 | |
| 5597 currentStart += prefixCount; | |
| 5598 oldStart += prefixCount; | |
| 5599 currentEnd -= suffixCount; | |
| 5600 oldEnd -= suffixCount; | |
| 5601 | |
| 5602 if (currentEnd - currentStart == 0 && oldEnd - oldStart == 0) | |
| 5603 return []; | |
| 5604 | |
| 5605 if (currentStart == currentEnd) { | |
| 5606 var splice = newSplice(currentStart, [], 0); | |
| 5607 while (oldStart < oldEnd) | |
| 5608 splice.removed.push(old[oldStart++]); | |
| 5609 | |
| 5610 return [ splice ]; | |
| 5611 } else if (oldStart == oldEnd) | |
| 5612 return [ newSplice(currentStart, [], currentEnd - currentStart) ]; | |
| 5613 | |
| 5614 var ops = this.spliceOperationsFromEditDistances( | |
| 5615 this.calcEditDistances(current, currentStart, currentEnd, | |
| 5616 old, oldStart, oldEnd)); | |
| 5617 | |
| 5618 var splice = undefined; | |
| 5619 var splices = []; | |
| 5620 var index = currentStart; | |
| 5621 var oldIndex = oldStart; | |
| 5622 for (var i = 0; i < ops.length; i++) { | |
| 5623 switch(ops[i]) { | |
| 5624 case EDIT_LEAVE: | |
| 5625 if (splice) { | |
| 5626 splices.push(splice); | |
| 5627 splice = undefined; | |
| 5628 } | |
| 5629 | |
| 5630 index++; | |
| 5631 oldIndex++; | |
| 5632 break; | |
| 5633 case EDIT_UPDATE: | |
| 5634 if (!splice) | |
| 5635 splice = newSplice(index, [], 0); | |
| 5636 | |
| 5637 splice.addedCount++; | |
| 5638 index++; | |
| 5639 | |
| 5640 splice.removed.push(old[oldIndex]); | |
| 5641 oldIndex++; | |
| 5642 break; | |
| 5643 case EDIT_ADD: | |
| 5644 if (!splice) | |
| 5645 splice = newSplice(index, [], 0); | |
| 5646 | |
| 5647 splice.addedCount++; | |
| 5648 index++; | |
| 5649 break; | |
| 5650 case EDIT_DELETE: | |
| 5651 if (!splice) | |
| 5652 splice = newSplice(index, [], 0); | |
| 5653 | |
| 5654 splice.removed.push(old[oldIndex]); | |
| 5655 oldIndex++; | |
| 5656 break; | |
| 5657 } | |
| 5658 } | |
| 5659 | |
| 5660 if (splice) { | |
| 5661 splices.push(splice); | |
| 5662 } | |
| 5663 return splices; | |
| 5664 }, | |
| 5665 | |
| 5666 sharedPrefix: function(current, old, searchLength) { | |
| 5667 for (var i = 0; i < searchLength; i++) | |
| 5668 if (!this.equals(current[i], old[i])) | |
| 5669 return i; | |
| 5670 return searchLength; | |
| 5671 }, | |
| 5672 | |
| 5673 sharedSuffix: function(current, old, searchLength) { | |
| 5674 var index1 = current.length; | |
| 5675 var index2 = old.length; | |
| 5676 var count = 0; | |
| 5677 while (count < searchLength && this.equals(current[--index1], old[--index2
])) | |
| 5678 count++; | |
| 5679 | |
| 5680 return count; | |
| 5681 }, | |
| 5682 | |
| 5683 calculateSplices: function(current, previous) { | |
| 5684 return this.calcSplices(current, 0, current.length, previous, 0, | |
| 5685 previous.length); | |
| 5686 }, | |
| 5687 | |
| 5688 equals: function(currentValue, previousValue) { | |
| 5689 return currentValue === previousValue; | |
| 5690 } | |
| 5691 }; | |
| 5692 | |
| 5693 var arraySplice = new ArraySplice(); | |
| 5694 | |
| 5695 function calcSplices(current, currentStart, currentEnd, | |
| 5696 old, oldStart, oldEnd) { | |
| 5697 return arraySplice.calcSplices(current, currentStart, currentEnd, | |
| 5698 old, oldStart, oldEnd); | |
| 5699 } | |
| 5700 | |
| 5701 function intersect(start1, end1, start2, end2) { | |
| 5702 // Disjoint | |
| 5703 if (end1 < start2 || end2 < start1) | |
| 5704 return -1; | |
| 5705 | |
| 5706 // Adjacent | |
| 5707 if (end1 == start2 || end2 == start1) | |
| 5708 return 0; | |
| 5709 | |
| 5710 // Non-zero intersect, span1 first | |
| 5711 if (start1 < start2) { | |
| 5712 if (end1 < end2) | |
| 5713 return end1 - start2; // Overlap | |
| 5714 else | |
| 5715 return end2 - start2; // Contained | |
| 5716 } else { | |
| 5717 // Non-zero intersect, span2 first | |
| 5718 if (end2 < end1) | |
| 5719 return end2 - start1; // Overlap | |
| 5720 else | |
| 5721 return end1 - start1; // Contained | |
| 5722 } | |
| 5723 } | |
| 5724 | |
| 5725 function mergeSplice(splices, index, removed, addedCount) { | |
| 5726 | |
| 5727 var splice = newSplice(index, removed, addedCount); | |
| 5728 | |
| 5729 var inserted = false; | |
| 5730 var insertionOffset = 0; | |
| 5731 | |
| 5732 for (var i = 0; i < splices.length; i++) { | |
| 5733 var current = splices[i]; | |
| 5734 current.index += insertionOffset; | |
| 5735 | |
| 5736 if (inserted) | |
| 5737 continue; | |
| 5738 | |
| 5739 var intersectCount = intersect(splice.index, | |
| 5740 splice.index + splice.removed.length, | |
| 5741 current.index, | |
| 5742 current.index + current.addedCount); | |
| 5743 | |
| 5744 if (intersectCount >= 0) { | |
| 5745 // Merge the two splices | |
| 5746 | |
| 5747 splices.splice(i, 1); | |
| 5748 i--; | |
| 5749 | |
| 5750 insertionOffset -= current.addedCount - current.removed.length; | |
| 5751 | |
| 5752 splice.addedCount += current.addedCount - intersectCount; | |
| 5753 var deleteCount = splice.removed.length + | |
| 5754 current.removed.length - intersectCount; | |
| 5755 | |
| 5756 if (!splice.addedCount && !deleteCount) { | |
| 5757 // merged splice is a noop. discard. | |
| 5758 inserted = true; | |
| 5759 } else { | |
| 5760 var removed = current.removed; | |
| 5761 | |
| 5762 if (splice.index < current.index) { | |
| 5763 // some prefix of splice.removed is prepended to current.removed. | |
| 5764 var prepend = splice.removed.slice(0, current.index - splice.index); | |
| 5765 Array.prototype.push.apply(prepend, removed); | |
| 5766 removed = prepend; | |
| 5767 } | |
| 5768 | |
| 5769 if (splice.index + splice.removed.length > current.index + current.add
edCount) { | |
| 5770 // some suffix of splice.removed is appended to current.removed. | |
| 5771 var append = splice.removed.slice(current.index + current.addedCount
- splice.index); | |
| 5772 Array.prototype.push.apply(removed, append); | |
| 5773 } | |
| 5774 | |
| 5775 splice.removed = removed; | |
| 5776 if (current.index < splice.index) { | |
| 5777 splice.index = current.index; | |
| 5778 } | |
| 5779 } | |
| 5780 } else if (splice.index < current.index) { | |
| 5781 // Insert splice here. | |
| 5782 | |
| 5783 inserted = true; | |
| 5784 | |
| 5785 splices.splice(i, 0, splice); | |
| 5786 i++; | |
| 5787 | |
| 5788 var offset = splice.addedCount - splice.removed.length | |
| 5789 current.index += offset; | |
| 5790 insertionOffset += offset; | |
| 5791 } | |
| 5792 } | |
| 5793 | |
| 5794 if (!inserted) | |
| 5795 splices.push(splice); | |
| 5796 } | |
| 5797 | |
| 5798 function createInitialSplices(array, changeRecords) { | |
| 5799 var splices = []; | |
| 5800 | |
| 5801 for (var i = 0; i < changeRecords.length; i++) { | |
| 5802 var record = changeRecords[i]; | |
| 5803 switch(record.type) { | |
| 5804 case 'splice': | |
| 5805 mergeSplice(splices, record.index, record.removed.slice(), record.adde
dCount); | |
| 5806 break; | |
| 5807 case 'add': | |
| 5808 case 'update': | |
| 5809 case 'delete': | |
| 5810 if (!isIndex(record.name)) | |
| 5811 continue; | |
| 5812 var index = toNumber(record.name); | |
| 5813 if (index < 0) | |
| 5814 continue; | |
| 5815 mergeSplice(splices, index, [record.oldValue], 1); | |
| 5816 break; | |
| 5817 default: | |
| 5818 console.error('Unexpected record type: ' + JSON.stringify(record)); | |
| 5819 break; | |
| 5820 } | |
| 5821 } | |
| 5822 | |
| 5823 return splices; | |
| 5824 } | |
| 5825 | |
| 5826 function projectArraySplices(array, changeRecords) { | |
| 5827 var splices = []; | |
| 5828 | |
| 5829 createInitialSplices(array, changeRecords).forEach(function(splice) { | |
| 5830 if (splice.addedCount == 1 && splice.removed.length == 1) { | |
| 5831 if (splice.removed[0] !== array[splice.index]) | |
| 5832 splices.push(splice); | |
| 5833 | |
| 5834 return | |
| 5835 }; | |
| 5836 | |
| 5837 splices = splices.concat(calcSplices(array, splice.index, splice.index + s
plice.addedCount, | |
| 5838 splice.removed, 0, splice.removed.len
gth)); | |
| 5839 }); | |
| 5840 | |
| 5841 return splices; | |
| 5842 } | |
| 5843 | |
| 5844 // Export the observe-js object for **Node.js**, with | |
| 5845 // backwards-compatibility for the old `require()` API. If we're in | |
| 5846 // the browser, export as a global object. | |
| 5847 | |
| 5848 var expose = global; | |
| 5849 | |
| 5850 if (typeof exports !== 'undefined') { | |
| 5851 if (typeof module !== 'undefined' && module.exports) { | |
| 5852 expose = exports = module.exports; | |
| 5853 } | |
| 5854 expose = exports; | |
| 5855 } | |
| 5856 | |
| 5857 expose.Observer = Observer; | |
| 5858 expose.Observer.runEOM_ = runEOM; | |
| 5859 expose.Observer.observerSentinel_ = observerSentinel; // for testing. | |
| 5860 expose.Observer.hasObjectObserve = hasObserve; | |
| 5861 expose.ArrayObserver = ArrayObserver; | |
| 5862 expose.ArrayObserver.calculateSplices = function(current, previous) { | |
| 5863 return arraySplice.calculateSplices(current, previous); | |
| 5864 }; | |
| 5865 | |
| 5866 expose.ArraySplice = ArraySplice; | |
| 5867 expose.ObjectObserver = ObjectObserver; | |
| 5868 expose.PathObserver = PathObserver; | |
| 5869 expose.CompoundObserver = CompoundObserver; | |
| 5870 expose.Path = Path; | |
| 5871 expose.ObserverTransform = ObserverTransform; | |
| 5872 | |
| 5873 })(typeof global !== 'undefined' && global && typeof module !== 'undefined' && m
odule ? global : this || window); | |
| 5874 | |
| 5875 // Copyright (c) 2014 The Polymer Project Authors. All rights reserved. | |
| 5876 // This code may only be used under the BSD style license found at http://polyme
r.github.io/LICENSE.txt | |
| 5877 // The complete set of authors may be found at http://polymer.github.io/AUTHORS.
txt | |
| 5878 // The complete set of contributors may be found at http://polymer.github.io/CON
TRIBUTORS.txt | |
| 5879 // Code distributed by Google as part of the polymer project is also | |
| 5880 // subject to an additional IP rights grant found at http://polymer.github.io/PA
TENTS.txt | |
| 5881 | |
| 5882 (function(global) { | |
| 5883 'use strict'; | |
| 5884 | |
| 5885 var filter = Array.prototype.filter.call.bind(Array.prototype.filter); | |
| 5886 | |
| 5887 function getTreeScope(node) { | |
| 5888 while (node.parentNode) { | |
| 5889 node = node.parentNode; | |
| 5890 } | |
| 5891 | |
| 5892 return typeof node.getElementById === 'function' ? node : null; | |
| 5893 } | |
| 5894 | |
| 5895 Node.prototype.bind = function(name, observable) { | |
| 5896 console.error('Unhandled binding to Node: ', this, name, observable); | |
| 5897 }; | |
| 5898 | |
| 5899 Node.prototype.bindFinished = function() {}; | |
| 5900 | |
| 5901 function updateBindings(node, name, binding) { | |
| 5902 var bindings = node.bindings_; | |
| 5903 if (!bindings) | |
| 5904 bindings = node.bindings_ = {}; | |
| 5905 | |
| 5906 if (bindings[name]) | |
| 5907 binding[name].close(); | |
| 5908 | |
| 5909 return bindings[name] = binding; | |
| 5910 } | |
| 5911 | |
| 5912 function returnBinding(node, name, binding) { | |
| 5913 return binding; | |
| 5914 } | |
| 5915 | |
| 5916 function sanitizeValue(value) { | |
| 5917 return value == null ? '' : value; | |
| 5918 } | |
| 5919 | |
| 5920 function updateText(node, value) { | |
| 5921 node.data = sanitizeValue(value); | |
| 5922 } | |
| 5923 | |
| 5924 function textBinding(node) { | |
| 5925 return function(value) { | |
| 5926 return updateText(node, value); | |
| 5927 }; | |
| 5928 } | |
| 5929 | |
| 5930 var maybeUpdateBindings = returnBinding; | |
| 5931 | |
| 5932 Object.defineProperty(Platform, 'enableBindingsReflection', { | |
| 5933 get: function() { | |
| 5934 return maybeUpdateBindings === updateBindings; | |
| 5935 }, | |
| 5936 set: function(enable) { | |
| 5937 maybeUpdateBindings = enable ? updateBindings : returnBinding; | |
| 5938 return enable; | |
| 5939 }, | |
| 5940 configurable: true | |
| 5941 }); | |
| 5942 | |
| 5943 Text.prototype.bind = function(name, value, oneTime) { | |
| 5944 if (name !== 'textContent') | |
| 5945 return Node.prototype.bind.call(this, name, value, oneTime); | |
| 5946 | |
| 5947 if (oneTime) | |
| 5948 return updateText(this, value); | |
| 5949 | |
| 5950 var observable = value; | |
| 5951 updateText(this, observable.open(textBinding(this))); | |
| 5952 return maybeUpdateBindings(this, name, observable); | |
| 5953 } | |
| 5954 | |
| 5955 function updateAttribute(el, name, conditional, value) { | |
| 5956 if (conditional) { | |
| 5957 if (value) | |
| 5958 el.setAttribute(name, ''); | |
| 5959 else | |
| 5960 el.removeAttribute(name); | |
| 5961 return; | |
| 5962 } | |
| 5963 | |
| 5964 el.setAttribute(name, sanitizeValue(value)); | |
| 5965 } | |
| 5966 | |
| 5967 function attributeBinding(el, name, conditional) { | |
| 5968 return function(value) { | |
| 5969 updateAttribute(el, name, conditional, value); | |
| 5970 }; | |
| 5971 } | |
| 5972 | |
| 5973 Element.prototype.bind = function(name, value, oneTime) { | |
| 5974 var conditional = name[name.length - 1] == '?'; | |
| 5975 if (conditional) { | |
| 5976 this.removeAttribute(name); | |
| 5977 name = name.slice(0, -1); | |
| 5978 } | |
| 5979 | |
| 5980 if (oneTime) | |
| 5981 return updateAttribute(this, name, conditional, value); | |
| 5982 | |
| 5983 | |
| 5984 var observable = value; | |
| 5985 updateAttribute(this, name, conditional, | |
| 5986 observable.open(attributeBinding(this, name, conditional))); | |
| 5987 | |
| 5988 return maybeUpdateBindings(this, name, observable); | |
| 5989 }; | |
| 5990 | |
| 5991 var checkboxEventType; | |
| 5992 (function() { | |
| 5993 // Attempt to feature-detect which event (change or click) is fired first | |
| 5994 // for checkboxes. | |
| 5995 var div = document.createElement('div'); | |
| 5996 var checkbox = div.appendChild(document.createElement('input')); | |
| 5997 checkbox.setAttribute('type', 'checkbox'); | |
| 5998 var first; | |
| 5999 var count = 0; | |
| 6000 checkbox.addEventListener('click', function(e) { | |
| 6001 count++; | |
| 6002 first = first || 'click'; | |
| 6003 }); | |
| 6004 checkbox.addEventListener('change', function() { | |
| 6005 count++; | |
| 6006 first = first || 'change'; | |
| 6007 }); | |
| 6008 | |
| 6009 var event = document.createEvent('MouseEvent'); | |
| 6010 event.initMouseEvent("click", true, true, window, 0, 0, 0, 0, 0, false, | |
| 6011 false, false, false, 0, null); | |
| 6012 checkbox.dispatchEvent(event); | |
| 6013 // WebKit/Blink don't fire the change event if the element is outside the | |
| 6014 // document, so assume 'change' for that case. | |
| 6015 checkboxEventType = count == 1 ? 'change' : first; | |
| 6016 })(); | |
| 6017 | |
| 6018 function getEventForInputType(element) { | |
| 6019 switch (element.type) { | |
| 6020 case 'checkbox': | |
| 6021 return checkboxEventType; | |
| 6022 case 'radio': | |
| 6023 case 'select-multiple': | |
| 6024 case 'select-one': | |
| 6025 return 'change'; | |
| 6026 case 'range': | |
| 6027 if (/Trident|MSIE/.test(navigator.userAgent)) | |
| 6028 return 'change'; | |
| 6029 default: | |
| 6030 return 'input'; | |
| 6031 } | |
| 6032 } | |
| 6033 | |
| 6034 function updateInput(input, property, value, santizeFn) { | |
| 6035 input[property] = (santizeFn || sanitizeValue)(value); | |
| 6036 } | |
| 6037 | |
| 6038 function inputBinding(input, property, santizeFn) { | |
| 6039 return function(value) { | |
| 6040 return updateInput(input, property, value, santizeFn); | |
| 6041 } | |
| 6042 } | |
| 6043 | |
| 6044 function noop() {} | |
| 6045 | |
| 6046 function bindInputEvent(input, property, observable, postEventFn) { | |
| 6047 var eventType = getEventForInputType(input); | |
| 6048 | |
| 6049 function eventHandler() { | |
| 6050 observable.setValue(input[property]); | |
| 6051 observable.discardChanges(); | |
| 6052 (postEventFn || noop)(input); | |
| 6053 Platform.performMicrotaskCheckpoint(); | |
| 6054 } | |
| 6055 input.addEventListener(eventType, eventHandler); | |
| 6056 | |
| 6057 return { | |
| 6058 close: function() { | |
| 6059 input.removeEventListener(eventType, eventHandler); | |
| 6060 observable.close(); | |
| 6061 }, | |
| 6062 | |
| 6063 observable_: observable | |
| 6064 } | |
| 6065 } | |
| 6066 | |
| 6067 function booleanSanitize(value) { | |
| 6068 return Boolean(value); | |
| 6069 } | |
| 6070 | |
| 6071 // |element| is assumed to be an HTMLInputElement with |type| == 'radio'. | |
| 6072 // Returns an array containing all radio buttons other than |element| that | |
| 6073 // have the same |name|, either in the form that |element| belongs to or, | |
| 6074 // if no form, in the document tree to which |element| belongs. | |
| 6075 // | |
| 6076 // This implementation is based upon the HTML spec definition of a | |
| 6077 // "radio button group": | |
| 6078 // http://www.whatwg.org/specs/web-apps/current-work/multipage/number-state.
html#radio-button-group | |
| 6079 // | |
| 6080 function getAssociatedRadioButtons(element) { | |
| 6081 if (element.form) { | |
| 6082 return filter(element.form.elements, function(el) { | |
| 6083 return el != element && | |
| 6084 el.tagName == 'INPUT' && | |
| 6085 el.type == 'radio' && | |
| 6086 el.name == element.name; | |
| 6087 }); | |
| 6088 } else { | |
| 6089 var treeScope = getTreeScope(element); | |
| 6090 if (!treeScope) | |
| 6091 return []; | |
| 6092 var radios = treeScope.querySelectorAll( | |
| 6093 'input[type="radio"][name="' + element.name + '"]'); | |
| 6094 return filter(radios, function(el) { | |
| 6095 return el != element && !el.form; | |
| 6096 }); | |
| 6097 } | |
| 6098 } | |
| 6099 | |
| 6100 function checkedPostEvent(input) { | |
| 6101 // Only the radio button that is getting checked gets an event. We | |
| 6102 // therefore find all the associated radio buttons and update their | |
| 6103 // check binding manually. | |
| 6104 if (input.tagName === 'INPUT' && | |
| 6105 input.type === 'radio') { | |
| 6106 getAssociatedRadioButtons(input).forEach(function(radio) { | |
| 6107 var checkedBinding = radio.bindings_.checked; | |
| 6108 if (checkedBinding) { | |
| 6109 // Set the value directly to avoid an infinite call stack. | |
| 6110 checkedBinding.observable_.setValue(false); | |
| 6111 } | |
| 6112 }); | |
| 6113 } | |
| 6114 } | |
| 6115 | |
| 6116 HTMLInputElement.prototype.bind = function(name, value, oneTime) { | |
| 6117 if (name !== 'value' && name !== 'checked') | |
| 6118 return HTMLElement.prototype.bind.call(this, name, value, oneTime); | |
| 6119 | |
| 6120 this.removeAttribute(name); | |
| 6121 var sanitizeFn = name == 'checked' ? booleanSanitize : sanitizeValue; | |
| 6122 var postEventFn = name == 'checked' ? checkedPostEvent : noop; | |
| 6123 | |
| 6124 if (oneTime) | |
| 6125 return updateInput(this, name, value, sanitizeFn); | |
| 6126 | |
| 6127 | |
| 6128 var observable = value; | |
| 6129 var binding = bindInputEvent(this, name, observable, postEventFn); | |
| 6130 updateInput(this, name, | |
| 6131 observable.open(inputBinding(this, name, sanitizeFn)), | |
| 6132 sanitizeFn); | |
| 6133 | |
| 6134 // Checkboxes may need to update bindings of other checkboxes. | |
| 6135 return updateBindings(this, name, binding); | |
| 6136 } | |
| 6137 | |
| 6138 HTMLTextAreaElement.prototype.bind = function(name, value, oneTime) { | |
| 6139 if (name !== 'value') | |
| 6140 return HTMLElement.prototype.bind.call(this, name, value, oneTime); | |
| 6141 | |
| 6142 this.removeAttribute('value'); | |
| 6143 | |
| 6144 if (oneTime) | |
| 6145 return updateInput(this, 'value', value); | |
| 6146 | |
| 6147 var observable = value; | |
| 6148 var binding = bindInputEvent(this, 'value', observable); | |
| 6149 updateInput(this, 'value', | |
| 6150 observable.open(inputBinding(this, 'value', sanitizeValue))); | |
| 6151 return maybeUpdateBindings(this, name, binding); | |
| 6152 } | |
| 6153 | |
| 6154 function updateOption(option, value) { | |
| 6155 var parentNode = option.parentNode;; | |
| 6156 var select; | |
| 6157 var selectBinding; | |
| 6158 var oldValue; | |
| 6159 if (parentNode instanceof HTMLSelectElement && | |
| 6160 parentNode.bindings_ && | |
| 6161 parentNode.bindings_.value) { | |
| 6162 select = parentNode; | |
| 6163 selectBinding = select.bindings_.value; | |
| 6164 oldValue = select.value; | |
| 6165 } | |
| 6166 | |
| 6167 option.value = sanitizeValue(value); | |
| 6168 | |
| 6169 if (select && select.value != oldValue) { | |
| 6170 selectBinding.observable_.setValue(select.value); | |
| 6171 selectBinding.observable_.discardChanges(); | |
| 6172 Platform.performMicrotaskCheckpoint(); | |
| 6173 } | |
| 6174 } | |
| 6175 | |
| 6176 function optionBinding(option) { | |
| 6177 return function(value) { | |
| 6178 updateOption(option, value); | |
| 6179 } | |
| 6180 } | |
| 6181 | |
| 6182 HTMLOptionElement.prototype.bind = function(name, value, oneTime) { | |
| 6183 if (name !== 'value') | |
| 6184 return HTMLElement.prototype.bind.call(this, name, value, oneTime); | |
| 6185 | |
| 6186 this.removeAttribute('value'); | |
| 6187 | |
| 6188 if (oneTime) | |
| 6189 return updateOption(this, value); | |
| 6190 | |
| 6191 var observable = value; | |
| 6192 var binding = bindInputEvent(this, 'value', observable); | |
| 6193 updateOption(this, observable.open(optionBinding(this))); | |
| 6194 return maybeUpdateBindings(this, name, binding); | |
| 6195 } | |
| 6196 | |
| 6197 HTMLSelectElement.prototype.bind = function(name, value, oneTime) { | |
| 6198 if (name === 'selectedindex') | |
| 6199 name = 'selectedIndex'; | |
| 6200 | |
| 6201 if (name !== 'selectedIndex' && name !== 'value') | |
| 6202 return HTMLElement.prototype.bind.call(this, name, value, oneTime); | |
| 6203 | |
| 6204 this.removeAttribute(name); | |
| 6205 | |
| 6206 if (oneTime) | |
| 6207 return updateInput(this, name, value); | |
| 6208 | |
| 6209 var observable = value; | |
| 6210 var binding = bindInputEvent(this, name, observable); | |
| 6211 updateInput(this, name, | |
| 6212 observable.open(inputBinding(this, name))); | |
| 6213 | |
| 6214 // Option update events may need to access select bindings. | |
| 6215 return updateBindings(this, name, binding); | |
| 6216 } | |
| 6217 })(this); | |
| 6218 | |
| 6219 // Copyright (c) 2014 The Polymer Project Authors. All rights reserved. | |
| 6220 // This code may only be used under the BSD style license found at http://polyme
r.github.io/LICENSE.txt | |
| 6221 // The complete set of authors may be found at http://polymer.github.io/AUTHORS.
txt | |
| 6222 // The complete set of contributors may be found at http://polymer.github.io/CON
TRIBUTORS.txt | |
| 6223 // Code distributed by Google as part of the polymer project is also | |
| 6224 // subject to an additional IP rights grant found at http://polymer.github.io/PA
TENTS.txt | |
| 6225 | |
| 6226 (function(global) { | |
| 6227 'use strict'; | |
| 6228 | |
| 6229 function assert(v) { | |
| 6230 if (!v) | |
| 6231 throw new Error('Assertion failed'); | |
| 6232 } | |
| 6233 | |
| 6234 var forEach = Array.prototype.forEach.call.bind(Array.prototype.forEach); | |
| 6235 | |
| 6236 function getFragmentRoot(node) { | |
| 6237 var p; | |
| 6238 while (p = node.parentNode) { | |
| 6239 node = p; | |
| 6240 } | |
| 6241 | |
| 6242 return node; | |
| 6243 } | |
| 6244 | |
| 6245 function searchRefId(node, id) { | |
| 6246 if (!id) | |
| 6247 return; | |
| 6248 | |
| 6249 var ref; | |
| 6250 var selector = '#' + id; | |
| 6251 while (!ref) { | |
| 6252 node = getFragmentRoot(node); | |
| 6253 | |
| 6254 if (node.protoContent_) | |
| 6255 ref = node.protoContent_.querySelector(selector); | |
| 6256 else if (node.getElementById) | |
| 6257 ref = node.getElementById(id); | |
| 6258 | |
| 6259 if (ref || !node.templateCreator_) | |
| 6260 break | |
| 6261 | |
| 6262 node = node.templateCreator_; | |
| 6263 } | |
| 6264 | |
| 6265 return ref; | |
| 6266 } | |
| 6267 | |
| 6268 function getInstanceRoot(node) { | |
| 6269 while (node.parentNode) { | |
| 6270 node = node.parentNode; | |
| 6271 } | |
| 6272 return node.templateCreator_ ? node : null; | |
| 6273 } | |
| 6274 | |
| 6275 var Map; | |
| 6276 if (global.Map && typeof global.Map.prototype.forEach === 'function') { | |
| 6277 Map = global.Map; | |
| 6278 } else { | |
| 6279 Map = function() { | |
| 6280 this.keys = []; | |
| 6281 this.values = []; | |
| 6282 }; | |
| 6283 | |
| 6284 Map.prototype = { | |
| 6285 set: function(key, value) { | |
| 6286 var index = this.keys.indexOf(key); | |
| 6287 if (index < 0) { | |
| 6288 this.keys.push(key); | |
| 6289 this.values.push(value); | |
| 6290 } else { | |
| 6291 this.values[index] = value; | |
| 6292 } | |
| 6293 }, | |
| 6294 | |
| 6295 get: function(key) { | |
| 6296 var index = this.keys.indexOf(key); | |
| 6297 if (index < 0) | |
| 6298 return; | |
| 6299 | |
| 6300 return this.values[index]; | |
| 6301 }, | |
| 6302 | |
| 6303 delete: function(key, value) { | |
| 6304 var index = this.keys.indexOf(key); | |
| 6305 if (index < 0) | |
| 6306 return false; | |
| 6307 | |
| 6308 this.keys.splice(index, 1); | |
| 6309 this.values.splice(index, 1); | |
| 6310 return true; | |
| 6311 }, | |
| 6312 | |
| 6313 forEach: function(f, opt_this) { | |
| 6314 for (var i = 0; i < this.keys.length; i++) | |
| 6315 f.call(opt_this || this, this.values[i], this.keys[i], this); | |
| 6316 } | |
| 6317 }; | |
| 6318 } | |
| 6319 | |
| 6320 // JScript does not have __proto__. We wrap all object literals with | |
| 6321 // createObject which uses Object.create, Object.defineProperty and | |
| 6322 // Object.getOwnPropertyDescriptor to create a new object that does the exact | |
| 6323 // same thing. The main downside to this solution is that we have to extract | |
| 6324 // all those property descriptors for IE. | |
| 6325 var createObject = ('__proto__' in {}) ? | |
| 6326 function(obj) { return obj; } : | |
| 6327 function(obj) { | |
| 6328 var proto = obj.__proto__; | |
| 6329 if (!proto) | |
| 6330 return obj; | |
| 6331 var newObject = Object.create(proto); | |
| 6332 Object.getOwnPropertyNames(obj).forEach(function(name) { | |
| 6333 Object.defineProperty(newObject, name, | |
| 6334 Object.getOwnPropertyDescriptor(obj, name)); | |
| 6335 }); | |
| 6336 return newObject; | |
| 6337 }; | |
| 6338 | |
| 6339 // IE does not support have Document.prototype.contains. | |
| 6340 if (typeof document.contains != 'function') { | |
| 6341 Document.prototype.contains = function(node) { | |
| 6342 if (node === this || node.parentNode === this) | |
| 6343 return true; | |
| 6344 return this.documentElement.contains(node); | |
| 6345 } | |
| 6346 } | |
| 6347 | |
| 6348 var BIND = 'bind'; | |
| 6349 var REPEAT = 'repeat'; | |
| 6350 var IF = 'if'; | |
| 6351 | |
| 6352 var templateAttributeDirectives = { | |
| 6353 'template': true, | |
| 6354 'repeat': true, | |
| 6355 'bind': true, | |
| 6356 'ref': true, | |
| 6357 'if': true | |
| 6358 }; | |
| 6359 | |
| 6360 var semanticTemplateElements = { | |
| 6361 'THEAD': true, | |
| 6362 'TBODY': true, | |
| 6363 'TFOOT': true, | |
| 6364 'TH': true, | |
| 6365 'TR': true, | |
| 6366 'TD': true, | |
| 6367 'COLGROUP': true, | |
| 6368 'COL': true, | |
| 6369 'CAPTION': true, | |
| 6370 'OPTION': true, | |
| 6371 'OPTGROUP': true | |
| 6372 }; | |
| 6373 | |
| 6374 var hasTemplateElement = typeof HTMLTemplateElement !== 'undefined'; | |
| 6375 if (hasTemplateElement) { | |
| 6376 // TODO(rafaelw): Remove when fix for | |
| 6377 // https://codereview.chromium.org/164803002/ | |
| 6378 // makes it to Chrome release. | |
| 6379 (function() { | |
| 6380 var t = document.createElement('template'); | |
| 6381 var d = t.content.ownerDocument; | |
| 6382 var html = d.appendChild(d.createElement('html')); | |
| 6383 var head = html.appendChild(d.createElement('head')); | |
| 6384 var base = d.createElement('base'); | |
| 6385 base.href = document.baseURI; | |
| 6386 head.appendChild(base); | |
| 6387 })(); | |
| 6388 } | |
| 6389 | |
| 6390 var allTemplatesSelectors = 'template, ' + | |
| 6391 Object.keys(semanticTemplateElements).map(function(tagName) { | |
| 6392 return tagName.toLowerCase() + '[template]'; | |
| 6393 }).join(', '); | |
| 6394 | |
| 6395 function isSVGTemplate(el) { | |
| 6396 return el.tagName == 'template' && | |
| 6397 el.namespaceURI == 'http://www.w3.org/2000/svg'; | |
| 6398 } | |
| 6399 | |
| 6400 function isHTMLTemplate(el) { | |
| 6401 return el.tagName == 'TEMPLATE' && | |
| 6402 el.namespaceURI == 'http://www.w3.org/1999/xhtml'; | |
| 6403 } | |
| 6404 | |
| 6405 function isAttributeTemplate(el) { | |
| 6406 return Boolean(semanticTemplateElements[el.tagName] && | |
| 6407 el.hasAttribute('template')); | |
| 6408 } | |
| 6409 | |
| 6410 function isTemplate(el) { | |
| 6411 if (el.isTemplate_ === undefined) | |
| 6412 el.isTemplate_ = el.tagName == 'TEMPLATE' || isAttributeTemplate(el); | |
| 6413 | |
| 6414 return el.isTemplate_; | |
| 6415 } | |
| 6416 | |
| 6417 // FIXME: Observe templates being added/removed from documents | |
| 6418 // FIXME: Expose imperative API to decorate and observe templates in | |
| 6419 // "disconnected tress" (e.g. ShadowRoot) | |
| 6420 document.addEventListener('DOMContentLoaded', function(e) { | |
| 6421 bootstrapTemplatesRecursivelyFrom(document); | |
| 6422 // FIXME: Is this needed? Seems like it shouldn't be. | |
| 6423 Platform.performMicrotaskCheckpoint(); | |
| 6424 }, false); | |
| 6425 | |
| 6426 function forAllTemplatesFrom(node, fn) { | |
| 6427 var subTemplates = node.querySelectorAll(allTemplatesSelectors); | |
| 6428 | |
| 6429 if (isTemplate(node)) | |
| 6430 fn(node) | |
| 6431 forEach(subTemplates, fn); | |
| 6432 } | |
| 6433 | |
| 6434 function bootstrapTemplatesRecursivelyFrom(node) { | |
| 6435 function bootstrap(template) { | |
| 6436 if (!HTMLTemplateElement.decorate(template)) | |
| 6437 bootstrapTemplatesRecursivelyFrom(template.content); | |
| 6438 } | |
| 6439 | |
| 6440 forAllTemplatesFrom(node, bootstrap); | |
| 6441 } | |
| 6442 | |
| 6443 if (!hasTemplateElement) { | |
| 6444 /** | |
| 6445 * This represents a <template> element. | |
| 6446 * @constructor | |
| 6447 * @extends {HTMLElement} | |
| 6448 */ | |
| 6449 global.HTMLTemplateElement = function() { | |
| 6450 throw TypeError('Illegal constructor'); | |
| 6451 }; | |
| 6452 } | |
| 6453 | |
| 6454 var hasProto = '__proto__' in {}; | |
| 6455 | |
| 6456 function mixin(to, from) { | |
| 6457 Object.getOwnPropertyNames(from).forEach(function(name) { | |
| 6458 Object.defineProperty(to, name, | |
| 6459 Object.getOwnPropertyDescriptor(from, name)); | |
| 6460 }); | |
| 6461 } | |
| 6462 | |
| 6463 // http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/templates/index.html#
dfn-template-contents-owner | |
| 6464 function getOrCreateTemplateContentsOwner(template) { | |
| 6465 var doc = template.ownerDocument | |
| 6466 if (!doc.defaultView) | |
| 6467 return doc; | |
| 6468 var d = doc.templateContentsOwner_; | |
| 6469 if (!d) { | |
| 6470 // TODO(arv): This should either be a Document or HTMLDocument depending | |
| 6471 // on doc. | |
| 6472 d = doc.implementation.createHTMLDocument(''); | |
| 6473 while (d.lastChild) { | |
| 6474 d.removeChild(d.lastChild); | |
| 6475 } | |
| 6476 doc.templateContentsOwner_ = d; | |
| 6477 } | |
| 6478 return d; | |
| 6479 } | |
| 6480 | |
| 6481 function getTemplateStagingDocument(template) { | |
| 6482 if (!template.stagingDocument_) { | |
| 6483 var owner = template.ownerDocument; | |
| 6484 if (!owner.stagingDocument_) { | |
| 6485 owner.stagingDocument_ = owner.implementation.createHTMLDocument(''); | |
| 6486 owner.stagingDocument_.isStagingDocument = true; | |
| 6487 // TODO(rafaelw): Remove when fix for | |
| 6488 // https://codereview.chromium.org/164803002/ | |
| 6489 // makes it to Chrome release. | |
| 6490 var base = owner.stagingDocument_.createElement('base'); | |
| 6491 base.href = document.baseURI; | |
| 6492 owner.stagingDocument_.head.appendChild(base); | |
| 6493 | |
| 6494 owner.stagingDocument_.stagingDocument_ = owner.stagingDocument_; | |
| 6495 } | |
| 6496 | |
| 6497 template.stagingDocument_ = owner.stagingDocument_; | |
| 6498 } | |
| 6499 | |
| 6500 return template.stagingDocument_; | |
| 6501 } | |
| 6502 | |
| 6503 // For non-template browsers, the parser will disallow <template> in certain | |
| 6504 // locations, so we allow "attribute templates" which combine the template | |
| 6505 // element with the top-level container node of the content, e.g. | |
| 6506 // | |
| 6507 // <tr template repeat="{{ foo }}"" class="bar"><td>Bar</td></tr> | |
| 6508 // | |
| 6509 // becomes | |
| 6510 // | |
| 6511 // <template repeat="{{ foo }}"> | |
| 6512 // + #document-fragment | |
| 6513 // + <tr class="bar"> | |
| 6514 // + <td>Bar</td> | |
| 6515 // | |
| 6516 function extractTemplateFromAttributeTemplate(el) { | |
| 6517 var template = el.ownerDocument.createElement('template'); | |
| 6518 el.parentNode.insertBefore(template, el); | |
| 6519 | |
| 6520 var attribs = el.attributes; | |
| 6521 var count = attribs.length; | |
| 6522 while (count-- > 0) { | |
| 6523 var attrib = attribs[count]; | |
| 6524 if (templateAttributeDirectives[attrib.name]) { | |
| 6525 if (attrib.name !== 'template') | |
| 6526 template.setAttribute(attrib.name, attrib.value); | |
| 6527 el.removeAttribute(attrib.name); | |
| 6528 } | |
| 6529 } | |
| 6530 | |
| 6531 return template; | |
| 6532 } | |
| 6533 | |
| 6534 function extractTemplateFromSVGTemplate(el) { | |
| 6535 var template = el.ownerDocument.createElement('template'); | |
| 6536 el.parentNode.insertBefore(template, el); | |
| 6537 | |
| 6538 var attribs = el.attributes; | |
| 6539 var count = attribs.length; | |
| 6540 while (count-- > 0) { | |
| 6541 var attrib = attribs[count]; | |
| 6542 template.setAttribute(attrib.name, attrib.value); | |
| 6543 el.removeAttribute(attrib.name); | |
| 6544 } | |
| 6545 | |
| 6546 el.parentNode.removeChild(el); | |
| 6547 return template; | |
| 6548 } | |
| 6549 | |
| 6550 function liftNonNativeTemplateChildrenIntoContent(template, el, useRoot) { | |
| 6551 var content = template.content; | |
| 6552 if (useRoot) { | |
| 6553 content.appendChild(el); | |
| 6554 return; | |
| 6555 } | |
| 6556 | |
| 6557 var child; | |
| 6558 while (child = el.firstChild) { | |
| 6559 content.appendChild(child); | |
| 6560 } | |
| 6561 } | |
| 6562 | |
| 6563 var templateObserver; | |
| 6564 if (typeof MutationObserver == 'function') { | |
| 6565 templateObserver = new MutationObserver(function(records) { | |
| 6566 for (var i = 0; i < records.length; i++) { | |
| 6567 records[i].target.refChanged_(); | |
| 6568 } | |
| 6569 }); | |
| 6570 } | |
| 6571 | |
| 6572 /** | |
| 6573 * Ensures proper API and content model for template elements. | |
| 6574 * @param {HTMLTemplateElement} opt_instanceRef The template element which | |
| 6575 * |el| template element will return as the value of its ref(), and whose | |
| 6576 * content will be used as source when createInstance() is invoked. | |
| 6577 */ | |
| 6578 HTMLTemplateElement.decorate = function(el, opt_instanceRef) { | |
| 6579 if (el.templateIsDecorated_) | |
| 6580 return false; | |
| 6581 | |
| 6582 var templateElement = el; | |
| 6583 templateElement.templateIsDecorated_ = true; | |
| 6584 | |
| 6585 var isNativeHTMLTemplate = isHTMLTemplate(templateElement) && | |
| 6586 hasTemplateElement; | |
| 6587 var bootstrapContents = isNativeHTMLTemplate; | |
| 6588 var liftContents = !isNativeHTMLTemplate; | |
| 6589 var liftRoot = false; | |
| 6590 | |
| 6591 if (!isNativeHTMLTemplate) { | |
| 6592 if (isAttributeTemplate(templateElement)) { | |
| 6593 assert(!opt_instanceRef); | |
| 6594 templateElement = extractTemplateFromAttributeTemplate(el); | |
| 6595 templateElement.templateIsDecorated_ = true; | |
| 6596 isNativeHTMLTemplate = hasTemplateElement; | |
| 6597 liftRoot = true; | |
| 6598 } else if (isSVGTemplate(templateElement)) { | |
| 6599 templateElement = extractTemplateFromSVGTemplate(el); | |
| 6600 templateElement.templateIsDecorated_ = true; | |
| 6601 isNativeHTMLTemplate = hasTemplateElement; | |
| 6602 } | |
| 6603 } | |
| 6604 | |
| 6605 if (!isNativeHTMLTemplate) { | |
| 6606 fixTemplateElementPrototype(templateElement); | |
| 6607 var doc = getOrCreateTemplateContentsOwner(templateElement); | |
| 6608 templateElement.content_ = doc.createDocumentFragment(); | |
| 6609 } | |
| 6610 | |
| 6611 if (opt_instanceRef) { | |
| 6612 // template is contained within an instance, its direct content must be | |
| 6613 // empty | |
| 6614 templateElement.instanceRef_ = opt_instanceRef; | |
| 6615 } else if (liftContents) { | |
| 6616 liftNonNativeTemplateChildrenIntoContent(templateElement, | |
| 6617 el, | |
| 6618 liftRoot); | |
| 6619 } else if (bootstrapContents) { | |
| 6620 bootstrapTemplatesRecursivelyFrom(templateElement.content); | |
| 6621 } | |
| 6622 | |
| 6623 return true; | |
| 6624 }; | |
| 6625 | |
| 6626 // TODO(rafaelw): This used to decorate recursively all templates from a given | |
| 6627 // node. This happens by default on 'DOMContentLoaded', but may be needed | |
| 6628 // in subtrees not descendent from document (e.g. ShadowRoot). | |
| 6629 // Review whether this is the right public API. | |
| 6630 HTMLTemplateElement.bootstrap = bootstrapTemplatesRecursivelyFrom; | |
| 6631 | |
| 6632 var htmlElement = global.HTMLUnknownElement || HTMLElement; | |
| 6633 | |
| 6634 var contentDescriptor = { | |
| 6635 get: function() { | |
| 6636 return this.content_; | |
| 6637 }, | |
| 6638 enumerable: true, | |
| 6639 configurable: true | |
| 6640 }; | |
| 6641 | |
| 6642 if (!hasTemplateElement) { | |
| 6643 // Gecko is more picky with the prototype than WebKit. Make sure to use the | |
| 6644 // same prototype as created in the constructor. | |
| 6645 HTMLTemplateElement.prototype = Object.create(htmlElement.prototype); | |
| 6646 | |
| 6647 Object.defineProperty(HTMLTemplateElement.prototype, 'content', | |
| 6648 contentDescriptor); | |
| 6649 } | |
| 6650 | |
| 6651 function fixTemplateElementPrototype(el) { | |
| 6652 if (hasProto) | |
| 6653 el.__proto__ = HTMLTemplateElement.prototype; | |
| 6654 else | |
| 6655 mixin(el, HTMLTemplateElement.prototype); | |
| 6656 } | |
| 6657 | |
| 6658 function ensureSetModelScheduled(template) { | |
| 6659 if (!template.setModelFn_) { | |
| 6660 template.setModelFn_ = function() { | |
| 6661 template.setModelFnScheduled_ = false; | |
| 6662 var map = getBindings(template, | |
| 6663 template.delegate_ && template.delegate_.prepareBinding); | |
| 6664 processBindings(template, map, template.model_); | |
| 6665 }; | |
| 6666 } | |
| 6667 | |
| 6668 if (!template.setModelFnScheduled_) { | |
| 6669 template.setModelFnScheduled_ = true; | |
| 6670 Observer.runEOM_(template.setModelFn_); | |
| 6671 } | |
| 6672 } | |
| 6673 | |
| 6674 mixin(HTMLTemplateElement.prototype, { | |
| 6675 bind: function(name, value, oneTime) { | |
| 6676 if (name != 'ref') | |
| 6677 return Element.prototype.bind.call(this, name, value, oneTime); | |
| 6678 | |
| 6679 var self = this; | |
| 6680 var ref = oneTime ? value : value.open(function(ref) { | |
| 6681 self.setAttribute('ref', ref); | |
| 6682 self.refChanged_(); | |
| 6683 }); | |
| 6684 | |
| 6685 this.setAttribute('ref', ref); | |
| 6686 this.refChanged_(); | |
| 6687 if (oneTime) | |
| 6688 return; | |
| 6689 | |
| 6690 if (!this.bindings_) { | |
| 6691 this.bindings_ = { ref: value }; | |
| 6692 } else { | |
| 6693 this.bindings_.ref = value; | |
| 6694 } | |
| 6695 | |
| 6696 return value; | |
| 6697 }, | |
| 6698 | |
| 6699 processBindingDirectives_: function(directives) { | |
| 6700 if (this.iterator_) | |
| 6701 this.iterator_.closeDeps(); | |
| 6702 | |
| 6703 if (!directives.if && !directives.bind && !directives.repeat) { | |
| 6704 if (this.iterator_) { | |
| 6705 this.iterator_.close(); | |
| 6706 this.iterator_ = undefined; | |
| 6707 } | |
| 6708 | |
| 6709 return; | |
| 6710 } | |
| 6711 | |
| 6712 if (!this.iterator_) { | |
| 6713 this.iterator_ = new TemplateIterator(this); | |
| 6714 } | |
| 6715 | |
| 6716 this.iterator_.updateDependencies(directives, this.model_); | |
| 6717 | |
| 6718 if (templateObserver) { | |
| 6719 templateObserver.observe(this, { attributes: true, | |
| 6720 attributeFilter: ['ref'] }); | |
| 6721 } | |
| 6722 | |
| 6723 return this.iterator_; | |
| 6724 }, | |
| 6725 | |
| 6726 createInstance: function(model, bindingDelegate, delegate_) { | |
| 6727 if (bindingDelegate) | |
| 6728 delegate_ = this.newDelegate_(bindingDelegate); | |
| 6729 else if (!delegate_) | |
| 6730 delegate_ = this.delegate_; | |
| 6731 | |
| 6732 if (!this.refContent_) | |
| 6733 this.refContent_ = this.ref_.content; | |
| 6734 var content = this.refContent_; | |
| 6735 if (content.firstChild === null) | |
| 6736 return emptyInstance; | |
| 6737 | |
| 6738 var map = getInstanceBindingMap(content, delegate_); | |
| 6739 var stagingDocument = getTemplateStagingDocument(this); | |
| 6740 var instance = stagingDocument.createDocumentFragment(); | |
| 6741 instance.templateCreator_ = this; | |
| 6742 instance.protoContent_ = content; | |
| 6743 instance.bindings_ = []; | |
| 6744 instance.terminator_ = null; | |
| 6745 var instanceRecord = instance.templateInstance_ = { | |
| 6746 firstNode: null, | |
| 6747 lastNode: null, | |
| 6748 model: model | |
| 6749 }; | |
| 6750 | |
| 6751 var i = 0; | |
| 6752 var collectTerminator = false; | |
| 6753 for (var child = content.firstChild; child; child = child.nextSibling) { | |
| 6754 // The terminator of the instance is the clone of the last child of the | |
| 6755 // content. If the last child is an active template, it may produce | |
| 6756 // instances as a result of production, so simply collecting the last | |
| 6757 // child of the instance after it has finished producing may be wrong. | |
| 6758 if (child.nextSibling === null) | |
| 6759 collectTerminator = true; | |
| 6760 | |
| 6761 var clone = cloneAndBindInstance(child, instance, stagingDocument, | |
| 6762 map.children[i++], | |
| 6763 model, | |
| 6764 delegate_, | |
| 6765 instance.bindings_); | |
| 6766 clone.templateInstance_ = instanceRecord; | |
| 6767 if (collectTerminator) | |
| 6768 instance.terminator_ = clone; | |
| 6769 } | |
| 6770 | |
| 6771 instanceRecord.firstNode = instance.firstChild; | |
| 6772 instanceRecord.lastNode = instance.lastChild; | |
| 6773 instance.templateCreator_ = undefined; | |
| 6774 instance.protoContent_ = undefined; | |
| 6775 return instance; | |
| 6776 }, | |
| 6777 | |
| 6778 get model() { | |
| 6779 return this.model_; | |
| 6780 }, | |
| 6781 | |
| 6782 set model(model) { | |
| 6783 this.model_ = model; | |
| 6784 ensureSetModelScheduled(this); | |
| 6785 }, | |
| 6786 | |
| 6787 get bindingDelegate() { | |
| 6788 return this.delegate_ && this.delegate_.raw; | |
| 6789 }, | |
| 6790 | |
| 6791 refChanged_: function() { | |
| 6792 if (!this.iterator_ || this.refContent_ === this.ref_.content) | |
| 6793 return; | |
| 6794 | |
| 6795 this.refContent_ = undefined; | |
| 6796 this.iterator_.valueChanged(); | |
| 6797 this.iterator_.updateIteratedValue(this.iterator_.getUpdatedValue()); | |
| 6798 }, | |
| 6799 | |
| 6800 clear: function() { | |
| 6801 this.model_ = undefined; | |
| 6802 this.delegate_ = undefined; | |
| 6803 if (this.bindings_ && this.bindings_.ref) | |
| 6804 this.bindings_.ref.close() | |
| 6805 this.refContent_ = undefined; | |
| 6806 if (!this.iterator_) | |
| 6807 return; | |
| 6808 this.iterator_.valueChanged(); | |
| 6809 this.iterator_.close() | |
| 6810 this.iterator_ = undefined; | |
| 6811 }, | |
| 6812 | |
| 6813 setDelegate_: function(delegate) { | |
| 6814 this.delegate_ = delegate; | |
| 6815 this.bindingMap_ = undefined; | |
| 6816 if (this.iterator_) { | |
| 6817 this.iterator_.instancePositionChangedFn_ = undefined; | |
| 6818 this.iterator_.instanceModelFn_ = undefined; | |
| 6819 } | |
| 6820 }, | |
| 6821 | |
| 6822 newDelegate_: function(bindingDelegate) { | |
| 6823 if (!bindingDelegate) | |
| 6824 return; | |
| 6825 | |
| 6826 function delegateFn(name) { | |
| 6827 var fn = bindingDelegate && bindingDelegate[name]; | |
| 6828 if (typeof fn != 'function') | |
| 6829 return; | |
| 6830 | |
| 6831 return function() { | |
| 6832 return fn.apply(bindingDelegate, arguments); | |
| 6833 }; | |
| 6834 } | |
| 6835 | |
| 6836 return { | |
| 6837 bindingMaps: {}, | |
| 6838 raw: bindingDelegate, | |
| 6839 prepareBinding: delegateFn('prepareBinding'), | |
| 6840 prepareInstanceModel: delegateFn('prepareInstanceModel'), | |
| 6841 prepareInstancePositionChanged: | |
| 6842 delegateFn('prepareInstancePositionChanged') | |
| 6843 }; | |
| 6844 }, | |
| 6845 | |
| 6846 set bindingDelegate(bindingDelegate) { | |
| 6847 if (this.delegate_) { | |
| 6848 throw Error('Template must be cleared before a new bindingDelegate ' + | |
| 6849 'can be assigned'); | |
| 6850 } | |
| 6851 | |
| 6852 this.setDelegate_(this.newDelegate_(bindingDelegate)); | |
| 6853 }, | |
| 6854 | |
| 6855 get ref_() { | |
| 6856 var ref = searchRefId(this, this.getAttribute('ref')); | |
| 6857 if (!ref) | |
| 6858 ref = this.instanceRef_; | |
| 6859 | |
| 6860 if (!ref) | |
| 6861 return this; | |
| 6862 | |
| 6863 var nextRef = ref.ref_; | |
| 6864 return nextRef ? nextRef : ref; | |
| 6865 } | |
| 6866 }); | |
| 6867 | |
| 6868 // Returns | |
| 6869 // a) undefined if there are no mustaches. | |
| 6870 // b) [TEXT, (ONE_TIME?, PATH, DELEGATE_FN, TEXT)+] if there is at least one
mustache. | |
| 6871 function parseMustaches(s, name, node, prepareBindingFn) { | |
| 6872 if (!s || !s.length) | |
| 6873 return; | |
| 6874 | |
| 6875 var tokens; | |
| 6876 var length = s.length; | |
| 6877 var startIndex = 0, lastIndex = 0, endIndex = 0; | |
| 6878 var onlyOneTime = true; | |
| 6879 while (lastIndex < length) { | |
| 6880 var startIndex = s.indexOf('{{', lastIndex); | |
| 6881 var oneTimeStart = s.indexOf('[[', lastIndex); | |
| 6882 var oneTime = false; | |
| 6883 var terminator = '}}'; | |
| 6884 | |
| 6885 if (oneTimeStart >= 0 && | |
| 6886 (startIndex < 0 || oneTimeStart < startIndex)) { | |
| 6887 startIndex = oneTimeStart; | |
| 6888 oneTime = true; | |
| 6889 terminator = ']]'; | |
| 6890 } | |
| 6891 | |
| 6892 endIndex = startIndex < 0 ? -1 : s.indexOf(terminator, startIndex + 2); | |
| 6893 | |
| 6894 if (endIndex < 0) { | |
| 6895 if (!tokens) | |
| 6896 return; | |
| 6897 | |
| 6898 tokens.push(s.slice(lastIndex)); // TEXT | |
| 6899 break; | |
| 6900 } | |
| 6901 | |
| 6902 tokens = tokens || []; | |
| 6903 tokens.push(s.slice(lastIndex, startIndex)); // TEXT | |
| 6904 var pathString = s.slice(startIndex + 2, endIndex).trim(); | |
| 6905 tokens.push(oneTime); // ONE_TIME? | |
| 6906 onlyOneTime = onlyOneTime && oneTime; | |
| 6907 var delegateFn = prepareBindingFn && | |
| 6908 prepareBindingFn(pathString, name, node); | |
| 6909 // Don't try to parse the expression if there's a prepareBinding function | |
| 6910 if (delegateFn == null) { | |
| 6911 tokens.push(Path.get(pathString)); // PATH | |
| 6912 } else { | |
| 6913 tokens.push(null); | |
| 6914 } | |
| 6915 tokens.push(delegateFn); // DELEGATE_FN | |
| 6916 lastIndex = endIndex + 2; | |
| 6917 } | |
| 6918 | |
| 6919 if (lastIndex === length) | |
| 6920 tokens.push(''); // TEXT | |
| 6921 | |
| 6922 tokens.hasOnePath = tokens.length === 5; | |
| 6923 tokens.isSimplePath = tokens.hasOnePath && | |
| 6924 tokens[0] == '' && | |
| 6925 tokens[4] == ''; | |
| 6926 tokens.onlyOneTime = onlyOneTime; | |
| 6927 | |
| 6928 tokens.combinator = function(values) { | |
| 6929 var newValue = tokens[0]; | |
| 6930 | |
| 6931 for (var i = 1; i < tokens.length; i += 4) { | |
| 6932 var value = tokens.hasOnePath ? values : values[(i - 1) / 4]; | |
| 6933 if (value !== undefined) | |
| 6934 newValue += value; | |
| 6935 newValue += tokens[i + 3]; | |
| 6936 } | |
| 6937 | |
| 6938 return newValue; | |
| 6939 } | |
| 6940 | |
| 6941 return tokens; | |
| 6942 }; | |
| 6943 | |
| 6944 function processOneTimeBinding(name, tokens, node, model) { | |
| 6945 if (tokens.hasOnePath) { | |
| 6946 var delegateFn = tokens[3]; | |
| 6947 var value = delegateFn ? delegateFn(model, node, true) : | |
| 6948 tokens[2].getValueFrom(model); | |
| 6949 return tokens.isSimplePath ? value : tokens.combinator(value); | |
| 6950 } | |
| 6951 | |
| 6952 var values = []; | |
| 6953 for (var i = 1; i < tokens.length; i += 4) { | |
| 6954 var delegateFn = tokens[i + 2]; | |
| 6955 values[(i - 1) / 4] = delegateFn ? delegateFn(model, node) : | |
| 6956 tokens[i + 1].getValueFrom(model); | |
| 6957 } | |
| 6958 | |
| 6959 return tokens.combinator(values); | |
| 6960 } | |
| 6961 | |
| 6962 function processSinglePathBinding(name, tokens, node, model) { | |
| 6963 var delegateFn = tokens[3]; | |
| 6964 var observer = delegateFn ? delegateFn(model, node, false) : | |
| 6965 new PathObserver(model, tokens[2]); | |
| 6966 | |
| 6967 return tokens.isSimplePath ? observer : | |
| 6968 new ObserverTransform(observer, tokens.combinator); | |
| 6969 } | |
| 6970 | |
| 6971 function processBinding(name, tokens, node, model) { | |
| 6972 if (tokens.onlyOneTime) | |
| 6973 return processOneTimeBinding(name, tokens, node, model); | |
| 6974 | |
| 6975 if (tokens.hasOnePath) | |
| 6976 return processSinglePathBinding(name, tokens, node, model); | |
| 6977 | |
| 6978 var observer = new CompoundObserver(); | |
| 6979 | |
| 6980 for (var i = 1; i < tokens.length; i += 4) { | |
| 6981 var oneTime = tokens[i]; | |
| 6982 var delegateFn = tokens[i + 2]; | |
| 6983 | |
| 6984 if (delegateFn) { | |
| 6985 var value = delegateFn(model, node, oneTime); | |
| 6986 if (oneTime) | |
| 6987 observer.addPath(value) | |
| 6988 else | |
| 6989 observer.addObserver(value); | |
| 6990 continue; | |
| 6991 } | |
| 6992 | |
| 6993 var path = tokens[i + 1]; | |
| 6994 if (oneTime) | |
| 6995 observer.addPath(path.getValueFrom(model)) | |
| 6996 else | |
| 6997 observer.addPath(model, path); | |
| 6998 } | |
| 6999 | |
| 7000 return new ObserverTransform(observer, tokens.combinator); | |
| 7001 } | |
| 7002 | |
| 7003 function processBindings(node, bindings, model, instanceBindings) { | |
| 7004 for (var i = 0; i < bindings.length; i += 2) { | |
| 7005 var name = bindings[i] | |
| 7006 var tokens = bindings[i + 1]; | |
| 7007 var value = processBinding(name, tokens, node, model); | |
| 7008 var binding = node.bind(name, value, tokens.onlyOneTime); | |
| 7009 if (binding && instanceBindings) | |
| 7010 instanceBindings.push(binding); | |
| 7011 } | |
| 7012 | |
| 7013 node.bindFinished(); | |
| 7014 if (!bindings.isTemplate) | |
| 7015 return; | |
| 7016 | |
| 7017 node.model_ = model; | |
| 7018 var iter = node.processBindingDirectives_(bindings); | |
| 7019 if (instanceBindings && iter) | |
| 7020 instanceBindings.push(iter); | |
| 7021 } | |
| 7022 | |
| 7023 function parseWithDefault(el, name, prepareBindingFn) { | |
| 7024 var v = el.getAttribute(name); | |
| 7025 return parseMustaches(v == '' ? '{{}}' : v, name, el, prepareBindingFn); | |
| 7026 } | |
| 7027 | |
| 7028 function parseAttributeBindings(element, prepareBindingFn) { | |
| 7029 assert(element); | |
| 7030 | |
| 7031 var bindings = []; | |
| 7032 var ifFound = false; | |
| 7033 var bindFound = false; | |
| 7034 | |
| 7035 for (var i = 0; i < element.attributes.length; i++) { | |
| 7036 var attr = element.attributes[i]; | |
| 7037 var name = attr.name; | |
| 7038 var value = attr.value; | |
| 7039 | |
| 7040 // Allow bindings expressed in attributes to be prefixed with underbars. | |
| 7041 // We do this to allow correct semantics for browsers that don't implement | |
| 7042 // <template> where certain attributes might trigger side-effects -- and | |
| 7043 // for IE which sanitizes certain attributes, disallowing mustache | |
| 7044 // replacements in their text. | |
| 7045 while (name[0] === '_') { | |
| 7046 name = name.substring(1); | |
| 7047 } | |
| 7048 | |
| 7049 if (isTemplate(element) && | |
| 7050 (name === IF || name === BIND || name === REPEAT)) { | |
| 7051 continue; | |
| 7052 } | |
| 7053 | |
| 7054 var tokens = parseMustaches(value, name, element, | |
| 7055 prepareBindingFn); | |
| 7056 if (!tokens) | |
| 7057 continue; | |
| 7058 | |
| 7059 bindings.push(name, tokens); | |
| 7060 } | |
| 7061 | |
| 7062 if (isTemplate(element)) { | |
| 7063 bindings.isTemplate = true; | |
| 7064 bindings.if = parseWithDefault(element, IF, prepareBindingFn); | |
| 7065 bindings.bind = parseWithDefault(element, BIND, prepareBindingFn); | |
| 7066 bindings.repeat = parseWithDefault(element, REPEAT, prepareBindingFn); | |
| 7067 | |
| 7068 if (bindings.if && !bindings.bind && !bindings.repeat) | |
| 7069 bindings.bind = parseMustaches('{{}}', BIND, element, prepareBindingFn); | |
| 7070 } | |
| 7071 | |
| 7072 return bindings; | |
| 7073 } | |
| 7074 | |
| 7075 function getBindings(node, prepareBindingFn) { | |
| 7076 if (node.nodeType === Node.ELEMENT_NODE) | |
| 7077 return parseAttributeBindings(node, prepareBindingFn); | |
| 7078 | |
| 7079 if (node.nodeType === Node.TEXT_NODE) { | |
| 7080 var tokens = parseMustaches(node.data, 'textContent', node, | |
| 7081 prepareBindingFn); | |
| 7082 if (tokens) | |
| 7083 return ['textContent', tokens]; | |
| 7084 } | |
| 7085 | |
| 7086 return []; | |
| 7087 } | |
| 7088 | |
| 7089 function cloneAndBindInstance(node, parent, stagingDocument, bindings, model, | |
| 7090 delegate, | |
| 7091 instanceBindings, | |
| 7092 instanceRecord) { | |
| 7093 var clone = parent.appendChild(stagingDocument.importNode(node, false)); | |
| 7094 | |
| 7095 var i = 0; | |
| 7096 for (var child = node.firstChild; child; child = child.nextSibling) { | |
| 7097 cloneAndBindInstance(child, clone, stagingDocument, | |
| 7098 bindings.children[i++], | |
| 7099 model, | |
| 7100 delegate, | |
| 7101 instanceBindings); | |
| 7102 } | |
| 7103 | |
| 7104 if (bindings.isTemplate) { | |
| 7105 HTMLTemplateElement.decorate(clone, node); | |
| 7106 if (delegate) | |
| 7107 clone.setDelegate_(delegate); | |
| 7108 } | |
| 7109 | |
| 7110 processBindings(clone, bindings, model, instanceBindings); | |
| 7111 return clone; | |
| 7112 } | |
| 7113 | |
| 7114 function createInstanceBindingMap(node, prepareBindingFn) { | |
| 7115 var map = getBindings(node, prepareBindingFn); | |
| 7116 map.children = {}; | |
| 7117 var index = 0; | |
| 7118 for (var child = node.firstChild; child; child = child.nextSibling) { | |
| 7119 map.children[index++] = createInstanceBindingMap(child, prepareBindingFn); | |
| 7120 } | |
| 7121 | |
| 7122 return map; | |
| 7123 } | |
| 7124 | |
| 7125 var contentUidCounter = 1; | |
| 7126 | |
| 7127 // TODO(rafaelw): Setup a MutationObserver on content which clears the id | |
| 7128 // so that bindingMaps regenerate when the template.content changes. | |
| 7129 function getContentUid(content) { | |
| 7130 var id = content.id_; | |
| 7131 if (!id) | |
| 7132 id = content.id_ = contentUidCounter++; | |
| 7133 return id; | |
| 7134 } | |
| 7135 | |
| 7136 // Each delegate is associated with a set of bindingMaps, one for each | |
| 7137 // content which may be used by a template. The intent is that each binding | |
| 7138 // delegate gets the opportunity to prepare the instance (via the prepare* | |
| 7139 // delegate calls) once across all uses. | |
| 7140 // TODO(rafaelw): Separate out the parse map from the binding map. In the | |
| 7141 // current implementation, if two delegates need a binding map for the same | |
| 7142 // content, the second will have to reparse. | |
| 7143 function getInstanceBindingMap(content, delegate_) { | |
| 7144 var contentId = getContentUid(content); | |
| 7145 if (delegate_) { | |
| 7146 var map = delegate_.bindingMaps[contentId]; | |
| 7147 if (!map) { | |
| 7148 map = delegate_.bindingMaps[contentId] = | |
| 7149 createInstanceBindingMap(content, delegate_.prepareBinding) || []; | |
| 7150 } | |
| 7151 return map; | |
| 7152 } | |
| 7153 | |
| 7154 var map = content.bindingMap_; | |
| 7155 if (!map) { | |
| 7156 map = content.bindingMap_ = | |
| 7157 createInstanceBindingMap(content, undefined) || []; | |
| 7158 } | |
| 7159 return map; | |
| 7160 } | |
| 7161 | |
| 7162 Object.defineProperty(Node.prototype, 'templateInstance', { | |
| 7163 get: function() { | |
| 7164 var instance = this.templateInstance_; | |
| 7165 return instance ? instance : | |
| 7166 (this.parentNode ? this.parentNode.templateInstance : undefined); | |
| 7167 } | |
| 7168 }); | |
| 7169 | |
| 7170 var emptyInstance = document.createDocumentFragment(); | |
| 7171 emptyInstance.bindings_ = []; | |
| 7172 emptyInstance.terminator_ = null; | |
| 7173 | |
| 7174 function TemplateIterator(templateElement) { | |
| 7175 this.closed = false; | |
| 7176 this.templateElement_ = templateElement; | |
| 7177 this.instances = []; | |
| 7178 this.deps = undefined; | |
| 7179 this.iteratedValue = []; | |
| 7180 this.presentValue = undefined; | |
| 7181 this.arrayObserver = undefined; | |
| 7182 } | |
| 7183 | |
| 7184 TemplateIterator.prototype = { | |
| 7185 closeDeps: function() { | |
| 7186 var deps = this.deps; | |
| 7187 if (deps) { | |
| 7188 if (deps.ifOneTime === false) | |
| 7189 deps.ifValue.close(); | |
| 7190 if (deps.oneTime === false) | |
| 7191 deps.value.close(); | |
| 7192 } | |
| 7193 }, | |
| 7194 | |
| 7195 updateDependencies: function(directives, model) { | |
| 7196 this.closeDeps(); | |
| 7197 | |
| 7198 var deps = this.deps = {}; | |
| 7199 var template = this.templateElement_; | |
| 7200 | |
| 7201 var ifValue = true; | |
| 7202 if (directives.if) { | |
| 7203 deps.hasIf = true; | |
| 7204 deps.ifOneTime = directives.if.onlyOneTime; | |
| 7205 deps.ifValue = processBinding(IF, directives.if, template, model); | |
| 7206 | |
| 7207 ifValue = deps.ifValue; | |
| 7208 | |
| 7209 // oneTime if & predicate is false. nothing else to do. | |
| 7210 if (deps.ifOneTime && !ifValue) { | |
| 7211 this.valueChanged(); | |
| 7212 return; | |
| 7213 } | |
| 7214 | |
| 7215 if (!deps.ifOneTime) | |
| 7216 ifValue = ifValue.open(this.updateIfValue, this); | |
| 7217 } | |
| 7218 | |
| 7219 if (directives.repeat) { | |
| 7220 deps.repeat = true; | |
| 7221 deps.oneTime = directives.repeat.onlyOneTime; | |
| 7222 deps.value = processBinding(REPEAT, directives.repeat, template, model); | |
| 7223 } else { | |
| 7224 deps.repeat = false; | |
| 7225 deps.oneTime = directives.bind.onlyOneTime; | |
| 7226 deps.value = processBinding(BIND, directives.bind, template, model); | |
| 7227 } | |
| 7228 | |
| 7229 var value = deps.value; | |
| 7230 if (!deps.oneTime) | |
| 7231 value = value.open(this.updateIteratedValue, this); | |
| 7232 | |
| 7233 if (!ifValue) { | |
| 7234 this.valueChanged(); | |
| 7235 return; | |
| 7236 } | |
| 7237 | |
| 7238 this.updateValue(value); | |
| 7239 }, | |
| 7240 | |
| 7241 /** | |
| 7242 * Gets the updated value of the bind/repeat. This can potentially call | |
| 7243 * user code (if a bindingDelegate is set up) so we try to avoid it if we | |
| 7244 * already have the value in hand (from Observer.open). | |
| 7245 */ | |
| 7246 getUpdatedValue: function() { | |
| 7247 var value = this.deps.value; | |
| 7248 if (!this.deps.oneTime) | |
| 7249 value = value.discardChanges(); | |
| 7250 return value; | |
| 7251 }, | |
| 7252 | |
| 7253 updateIfValue: function(ifValue) { | |
| 7254 if (!ifValue) { | |
| 7255 this.valueChanged(); | |
| 7256 return; | |
| 7257 } | |
| 7258 | |
| 7259 this.updateValue(this.getUpdatedValue()); | |
| 7260 }, | |
| 7261 | |
| 7262 updateIteratedValue: function(value) { | |
| 7263 if (this.deps.hasIf) { | |
| 7264 var ifValue = this.deps.ifValue; | |
| 7265 if (!this.deps.ifOneTime) | |
| 7266 ifValue = ifValue.discardChanges(); | |
| 7267 if (!ifValue) { | |
| 7268 this.valueChanged(); | |
| 7269 return; | |
| 7270 } | |
| 7271 } | |
| 7272 | |
| 7273 this.updateValue(value); | |
| 7274 }, | |
| 7275 | |
| 7276 updateValue: function(value) { | |
| 7277 if (!this.deps.repeat) | |
| 7278 value = [value]; | |
| 7279 var observe = this.deps.repeat && | |
| 7280 !this.deps.oneTime && | |
| 7281 Array.isArray(value); | |
| 7282 this.valueChanged(value, observe); | |
| 7283 }, | |
| 7284 | |
| 7285 valueChanged: function(value, observeValue) { | |
| 7286 if (!Array.isArray(value)) | |
| 7287 value = []; | |
| 7288 | |
| 7289 if (value === this.iteratedValue) | |
| 7290 return; | |
| 7291 | |
| 7292 this.unobserve(); | |
| 7293 this.presentValue = value; | |
| 7294 if (observeValue) { | |
| 7295 this.arrayObserver = new ArrayObserver(this.presentValue); | |
| 7296 this.arrayObserver.open(this.handleSplices, this); | |
| 7297 } | |
| 7298 | |
| 7299 this.handleSplices(ArrayObserver.calculateSplices(this.presentValue, | |
| 7300 this.iteratedValue)); | |
| 7301 }, | |
| 7302 | |
| 7303 getLastInstanceNode: function(index) { | |
| 7304 if (index == -1) | |
| 7305 return this.templateElement_; | |
| 7306 var instance = this.instances[index]; | |
| 7307 var terminator = instance.terminator_; | |
| 7308 if (!terminator) | |
| 7309 return this.getLastInstanceNode(index - 1); | |
| 7310 | |
| 7311 if (terminator.nodeType !== Node.ELEMENT_NODE || | |
| 7312 this.templateElement_ === terminator) { | |
| 7313 return terminator; | |
| 7314 } | |
| 7315 | |
| 7316 var subtemplateIterator = terminator.iterator_; | |
| 7317 if (!subtemplateIterator) | |
| 7318 return terminator; | |
| 7319 | |
| 7320 return subtemplateIterator.getLastTemplateNode(); | |
| 7321 }, | |
| 7322 | |
| 7323 getLastTemplateNode: function() { | |
| 7324 return this.getLastInstanceNode(this.instances.length - 1); | |
| 7325 }, | |
| 7326 | |
| 7327 insertInstanceAt: function(index, fragment) { | |
| 7328 var previousInstanceLast = this.getLastInstanceNode(index - 1); | |
| 7329 var parent = this.templateElement_.parentNode; | |
| 7330 this.instances.splice(index, 0, fragment); | |
| 7331 | |
| 7332 parent.insertBefore(fragment, previousInstanceLast.nextSibling); | |
| 7333 }, | |
| 7334 | |
| 7335 extractInstanceAt: function(index) { | |
| 7336 var previousInstanceLast = this.getLastInstanceNode(index - 1); | |
| 7337 var lastNode = this.getLastInstanceNode(index); | |
| 7338 var parent = this.templateElement_.parentNode; | |
| 7339 var instance = this.instances.splice(index, 1)[0]; | |
| 7340 | |
| 7341 while (lastNode !== previousInstanceLast) { | |
| 7342 var node = previousInstanceLast.nextSibling; | |
| 7343 if (node == lastNode) | |
| 7344 lastNode = previousInstanceLast; | |
| 7345 | |
| 7346 instance.appendChild(parent.removeChild(node)); | |
| 7347 } | |
| 7348 | |
| 7349 return instance; | |
| 7350 }, | |
| 7351 | |
| 7352 getDelegateFn: function(fn) { | |
| 7353 fn = fn && fn(this.templateElement_); | |
| 7354 return typeof fn === 'function' ? fn : null; | |
| 7355 }, | |
| 7356 | |
| 7357 handleSplices: function(splices) { | |
| 7358 if (this.closed || !splices.length) | |
| 7359 return; | |
| 7360 | |
| 7361 var template = this.templateElement_; | |
| 7362 | |
| 7363 if (!template.parentNode) { | |
| 7364 this.close(); | |
| 7365 return; | |
| 7366 } | |
| 7367 | |
| 7368 ArrayObserver.applySplices(this.iteratedValue, this.presentValue, | |
| 7369 splices); | |
| 7370 | |
| 7371 var delegate = template.delegate_; | |
| 7372 if (this.instanceModelFn_ === undefined) { | |
| 7373 this.instanceModelFn_ = | |
| 7374 this.getDelegateFn(delegate && delegate.prepareInstanceModel); | |
| 7375 } | |
| 7376 | |
| 7377 if (this.instancePositionChangedFn_ === undefined) { | |
| 7378 this.instancePositionChangedFn_ = | |
| 7379 this.getDelegateFn(delegate && | |
| 7380 delegate.prepareInstancePositionChanged); | |
| 7381 } | |
| 7382 | |
| 7383 // Instance Removals | |
| 7384 var instanceCache = new Map; | |
| 7385 var removeDelta = 0; | |
| 7386 for (var i = 0; i < splices.length; i++) { | |
| 7387 var splice = splices[i]; | |
| 7388 var removed = splice.removed; | |
| 7389 for (var j = 0; j < removed.length; j++) { | |
| 7390 var model = removed[j]; | |
| 7391 var instance = this.extractInstanceAt(splice.index + removeDelta); | |
| 7392 if (instance !== emptyInstance) { | |
| 7393 instanceCache.set(model, instance); | |
| 7394 } | |
| 7395 } | |
| 7396 | |
| 7397 removeDelta -= splice.addedCount; | |
| 7398 } | |
| 7399 | |
| 7400 // Instance Insertions | |
| 7401 for (var i = 0; i < splices.length; i++) { | |
| 7402 var splice = splices[i]; | |
| 7403 var addIndex = splice.index; | |
| 7404 for (; addIndex < splice.index + splice.addedCount; addIndex++) { | |
| 7405 var model = this.iteratedValue[addIndex]; | |
| 7406 var instance = instanceCache.get(model); | |
| 7407 if (instance) { | |
| 7408 instanceCache.delete(model); | |
| 7409 } else { | |
| 7410 if (this.instanceModelFn_) { | |
| 7411 model = this.instanceModelFn_(model); | |
| 7412 } | |
| 7413 | |
| 7414 if (model === undefined) { | |
| 7415 instance = emptyInstance; | |
| 7416 } else { | |
| 7417 instance = template.createInstance(model, undefined, delegate); | |
| 7418 } | |
| 7419 } | |
| 7420 | |
| 7421 this.insertInstanceAt(addIndex, instance); | |
| 7422 } | |
| 7423 } | |
| 7424 | |
| 7425 instanceCache.forEach(function(instance) { | |
| 7426 this.closeInstanceBindings(instance); | |
| 7427 }, this); | |
| 7428 | |
| 7429 if (this.instancePositionChangedFn_) | |
| 7430 this.reportInstancesMoved(splices); | |
| 7431 }, | |
| 7432 | |
| 7433 reportInstanceMoved: function(index) { | |
| 7434 var instance = this.instances[index]; | |
| 7435 if (instance === emptyInstance) | |
| 7436 return; | |
| 7437 | |
| 7438 this.instancePositionChangedFn_(instance.templateInstance_, index); | |
| 7439 }, | |
| 7440 | |
| 7441 reportInstancesMoved: function(splices) { | |
| 7442 var index = 0; | |
| 7443 var offset = 0; | |
| 7444 for (var i = 0; i < splices.length; i++) { | |
| 7445 var splice = splices[i]; | |
| 7446 if (offset != 0) { | |
| 7447 while (index < splice.index) { | |
| 7448 this.reportInstanceMoved(index); | |
| 7449 index++; | |
| 7450 } | |
| 7451 } else { | |
| 7452 index = splice.index; | |
| 7453 } | |
| 7454 | |
| 7455 while (index < splice.index + splice.addedCount) { | |
| 7456 this.reportInstanceMoved(index); | |
| 7457 index++; | |
| 7458 } | |
| 7459 | |
| 7460 offset += splice.addedCount - splice.removed.length; | |
| 7461 } | |
| 7462 | |
| 7463 if (offset == 0) | |
| 7464 return; | |
| 7465 | |
| 7466 var length = this.instances.length; | |
| 7467 while (index < length) { | |
| 7468 this.reportInstanceMoved(index); | |
| 7469 index++; | |
| 7470 } | |
| 7471 }, | |
| 7472 | |
| 7473 closeInstanceBindings: function(instance) { | |
| 7474 var bindings = instance.bindings_; | |
| 7475 for (var i = 0; i < bindings.length; i++) { | |
| 7476 bindings[i].close(); | |
| 7477 } | |
| 7478 }, | |
| 7479 | |
| 7480 unobserve: function() { | |
| 7481 if (!this.arrayObserver) | |
| 7482 return; | |
| 7483 | |
| 7484 this.arrayObserver.close(); | |
| 7485 this.arrayObserver = undefined; | |
| 7486 }, | |
| 7487 | |
| 7488 close: function() { | |
| 7489 if (this.closed) | |
| 7490 return; | |
| 7491 this.unobserve(); | |
| 7492 for (var i = 0; i < this.instances.length; i++) { | |
| 7493 this.closeInstanceBindings(this.instances[i]); | |
| 7494 } | |
| 7495 | |
| 7496 this.instances.length = 0; | |
| 7497 this.closeDeps(); | |
| 7498 this.templateElement_.iterator_ = undefined; | |
| 7499 this.closed = true; | |
| 7500 } | |
| 7501 }; | |
| 7502 | |
| 7503 // Polyfill-specific API. | |
| 7504 HTMLTemplateElement.forAllTemplatesFrom_ = forAllTemplatesFrom; | |
| 7505 })(this); | |
| 7506 | |
| 7507 (function(scope) { | |
| 7508 'use strict'; | |
| 7509 | |
| 7510 // feature detect for URL constructor | |
| 7511 var hasWorkingUrl = false; | |
| 7512 if (!scope.forceJURL) { | |
| 7513 try { | |
| 7514 var u = new URL('b', 'http://a'); | |
| 7515 u.pathname = 'c%20d'; | |
| 7516 hasWorkingUrl = u.href === 'http://a/c%20d'; | |
| 7517 } catch(e) {} | |
| 7518 } | |
| 7519 | |
| 7520 if (hasWorkingUrl) | |
| 7521 return; | |
| 7522 | |
| 7523 var relative = Object.create(null); | |
| 7524 relative['ftp'] = 21; | |
| 7525 relative['file'] = 0; | |
| 7526 relative['gopher'] = 70; | |
| 7527 relative['http'] = 80; | |
| 7528 relative['https'] = 443; | |
| 7529 relative['ws'] = 80; | |
| 7530 relative['wss'] = 443; | |
| 7531 | |
| 7532 var relativePathDotMapping = Object.create(null); | |
| 7533 relativePathDotMapping['%2e'] = '.'; | |
| 7534 relativePathDotMapping['.%2e'] = '..'; | |
| 7535 relativePathDotMapping['%2e.'] = '..'; | |
| 7536 relativePathDotMapping['%2e%2e'] = '..'; | |
| 7537 | |
| 7538 function isRelativeScheme(scheme) { | |
| 7539 return relative[scheme] !== undefined; | |
| 7540 } | |
| 7541 | |
| 7542 function invalid() { | |
| 7543 clear.call(this); | |
| 7544 this._isInvalid = true; | |
| 7545 } | |
| 7546 | |
| 7547 function IDNAToASCII(h) { | |
| 7548 if ('' == h) { | |
| 7549 invalid.call(this) | |
| 7550 } | |
| 7551 // XXX | |
| 7552 return h.toLowerCase() | |
| 7553 } | |
| 7554 | |
| 7555 function percentEscape(c) { | |
| 7556 var unicode = c.charCodeAt(0); | |
| 7557 if (unicode > 0x20 && | |
| 7558 unicode < 0x7F && | |
| 7559 // " # < > ? ` | |
| 7560 [0x22, 0x23, 0x3C, 0x3E, 0x3F, 0x60].indexOf(unicode) == -1 | |
| 7561 ) { | |
| 7562 return c; | |
| 7563 } | |
| 7564 return encodeURIComponent(c); | |
| 7565 } | |
| 7566 | |
| 7567 function percentEscapeQuery(c) { | |
| 7568 // XXX This actually needs to encode c using encoding and then | |
| 7569 // convert the bytes one-by-one. | |
| 7570 | |
| 7571 var unicode = c.charCodeAt(0); | |
| 7572 if (unicode > 0x20 && | |
| 7573 unicode < 0x7F && | |
| 7574 // " # < > ` (do not escape '?') | |
| 7575 [0x22, 0x23, 0x3C, 0x3E, 0x60].indexOf(unicode) == -1 | |
| 7576 ) { | |
| 7577 return c; | |
| 7578 } | |
| 7579 return encodeURIComponent(c); | |
| 7580 } | |
| 7581 | |
| 7582 var EOF = undefined, | |
| 7583 ALPHA = /[a-zA-Z]/, | |
| 7584 ALPHANUMERIC = /[a-zA-Z0-9\+\-\.]/; | |
| 7585 | |
| 7586 function parse(input, stateOverride, base) { | |
| 7587 function err(message) { | |
| 7588 errors.push(message) | |
| 7589 } | |
| 7590 | |
| 7591 var state = stateOverride || 'scheme start', | |
| 7592 cursor = 0, | |
| 7593 buffer = '', | |
| 7594 seenAt = false, | |
| 7595 seenBracket = false, | |
| 7596 errors = []; | |
| 7597 | |
| 7598 loop: while ((input[cursor - 1] != EOF || cursor == 0) && !this._isInvalid)
{ | |
| 7599 var c = input[cursor]; | |
| 7600 switch (state) { | |
| 7601 case 'scheme start': | |
| 7602 if (c && ALPHA.test(c)) { | |
| 7603 buffer += c.toLowerCase(); // ASCII-safe | |
| 7604 state = 'scheme'; | |
| 7605 } else if (!stateOverride) { | |
| 7606 buffer = ''; | |
| 7607 state = 'no scheme'; | |
| 7608 continue; | |
| 7609 } else { | |
| 7610 err('Invalid scheme.'); | |
| 7611 break loop; | |
| 7612 } | |
| 7613 break; | |
| 7614 | |
| 7615 case 'scheme': | |
| 7616 if (c && ALPHANUMERIC.test(c)) { | |
| 7617 buffer += c.toLowerCase(); // ASCII-safe | |
| 7618 } else if (':' == c) { | |
| 7619 this._scheme = buffer; | |
| 7620 buffer = ''; | |
| 7621 if (stateOverride) { | |
| 7622 break loop; | |
| 7623 } | |
| 7624 if (isRelativeScheme(this._scheme)) { | |
| 7625 this._isRelative = true; | |
| 7626 } | |
| 7627 if ('file' == this._scheme) { | |
| 7628 state = 'relative'; | |
| 7629 } else if (this._isRelative && base && base._scheme == this._scheme)
{ | |
| 7630 state = 'relative or authority'; | |
| 7631 } else if (this._isRelative) { | |
| 7632 state = 'authority first slash'; | |
| 7633 } else { | |
| 7634 state = 'scheme data'; | |
| 7635 } | |
| 7636 } else if (!stateOverride) { | |
| 7637 buffer = ''; | |
| 7638 cursor = 0; | |
| 7639 state = 'no scheme'; | |
| 7640 continue; | |
| 7641 } else if (EOF == c) { | |
| 7642 break loop; | |
| 7643 } else { | |
| 7644 err('Code point not allowed in scheme: ' + c) | |
| 7645 break loop; | |
| 7646 } | |
| 7647 break; | |
| 7648 | |
| 7649 case 'scheme data': | |
| 7650 if ('?' == c) { | |
| 7651 query = '?'; | |
| 7652 state = 'query'; | |
| 7653 } else if ('#' == c) { | |
| 7654 this._fragment = '#'; | |
| 7655 state = 'fragment'; | |
| 7656 } else { | |
| 7657 // XXX error handling | |
| 7658 if (EOF != c && '\t' != c && '\n' != c && '\r' != c) { | |
| 7659 this._schemeData += percentEscape(c); | |
| 7660 } | |
| 7661 } | |
| 7662 break; | |
| 7663 | |
| 7664 case 'no scheme': | |
| 7665 if (!base || !(isRelativeScheme(base._scheme))) { | |
| 7666 err('Missing scheme.'); | |
| 7667 invalid.call(this); | |
| 7668 } else { | |
| 7669 state = 'relative'; | |
| 7670 continue; | |
| 7671 } | |
| 7672 break; | |
| 7673 | |
| 7674 case 'relative or authority': | |
| 7675 if ('/' == c && '/' == input[cursor+1]) { | |
| 7676 state = 'authority ignore slashes'; | |
| 7677 } else { | |
| 7678 err('Expected /, got: ' + c); | |
| 7679 state = 'relative'; | |
| 7680 continue | |
| 7681 } | |
| 7682 break; | |
| 7683 | |
| 7684 case 'relative': | |
| 7685 this._isRelative = true; | |
| 7686 if ('file' != this._scheme) | |
| 7687 this._scheme = base._scheme; | |
| 7688 if (EOF == c) { | |
| 7689 this._host = base._host; | |
| 7690 this._port = base._port; | |
| 7691 this._path = base._path.slice(); | |
| 7692 this._query = base._query; | |
| 7693 break loop; | |
| 7694 } else if ('/' == c || '\\' == c) { | |
| 7695 if ('\\' == c) | |
| 7696 err('\\ is an invalid code point.'); | |
| 7697 state = 'relative slash'; | |
| 7698 } else if ('?' == c) { | |
| 7699 this._host = base._host; | |
| 7700 this._port = base._port; | |
| 7701 this._path = base._path.slice(); | |
| 7702 this._query = '?'; | |
| 7703 state = 'query'; | |
| 7704 } else if ('#' == c) { | |
| 7705 this._host = base._host; | |
| 7706 this._port = base._port; | |
| 7707 this._path = base._path.slice(); | |
| 7708 this._query = base._query; | |
| 7709 this._fragment = '#'; | |
| 7710 state = 'fragment'; | |
| 7711 } else { | |
| 7712 var nextC = input[cursor+1] | |
| 7713 var nextNextC = input[cursor+2] | |
| 7714 if ( | |
| 7715 'file' != this._scheme || !ALPHA.test(c) || | |
| 7716 (nextC != ':' && nextC != '|') || | |
| 7717 (EOF != nextNextC && '/' != nextNextC && '\\' != nextNextC && '?'
!= nextNextC && '#' != nextNextC)) { | |
| 7718 this._host = base._host; | |
| 7719 this._port = base._port; | |
| 7720 this._path = base._path.slice(); | |
| 7721 this._path.pop(); | |
| 7722 } | |
| 7723 state = 'relative path'; | |
| 7724 continue; | |
| 7725 } | |
| 7726 break; | |
| 7727 | |
| 7728 case 'relative slash': | |
| 7729 if ('/' == c || '\\' == c) { | |
| 7730 if ('\\' == c) { | |
| 7731 err('\\ is an invalid code point.'); | |
| 7732 } | |
| 7733 if ('file' == this._scheme) { | |
| 7734 state = 'file host'; | |
| 7735 } else { | |
| 7736 state = 'authority ignore slashes'; | |
| 7737 } | |
| 7738 } else { | |
| 7739 if ('file' != this._scheme) { | |
| 7740 this._host = base._host; | |
| 7741 this._port = base._port; | |
| 7742 } | |
| 7743 state = 'relative path'; | |
| 7744 continue; | |
| 7745 } | |
| 7746 break; | |
| 7747 | |
| 7748 case 'authority first slash': | |
| 7749 if ('/' == c) { | |
| 7750 state = 'authority second slash'; | |
| 7751 } else { | |
| 7752 err("Expected '/', got: " + c); | |
| 7753 state = 'authority ignore slashes'; | |
| 7754 continue; | |
| 7755 } | |
| 7756 break; | |
| 7757 | |
| 7758 case 'authority second slash': | |
| 7759 state = 'authority ignore slashes'; | |
| 7760 if ('/' != c) { | |
| 7761 err("Expected '/', got: " + c); | |
| 7762 continue; | |
| 7763 } | |
| 7764 break; | |
| 7765 | |
| 7766 case 'authority ignore slashes': | |
| 7767 if ('/' != c && '\\' != c) { | |
| 7768 state = 'authority'; | |
| 7769 continue; | |
| 7770 } else { | |
| 7771 err('Expected authority, got: ' + c); | |
| 7772 } | |
| 7773 break; | |
| 7774 | |
| 7775 case 'authority': | |
| 7776 if ('@' == c) { | |
| 7777 if (seenAt) { | |
| 7778 err('@ already seen.'); | |
| 7779 buffer += '%40'; | |
| 7780 } | |
| 7781 seenAt = true; | |
| 7782 for (var i = 0; i < buffer.length; i++) { | |
| 7783 var cp = buffer[i]; | |
| 7784 if ('\t' == cp || '\n' == cp || '\r' == cp) { | |
| 7785 err('Invalid whitespace in authority.'); | |
| 7786 continue; | |
| 7787 } | |
| 7788 // XXX check URL code points | |
| 7789 if (':' == cp && null === this._password) { | |
| 7790 this._password = ''; | |
| 7791 continue; | |
| 7792 } | |
| 7793 var tempC = percentEscape(cp); | |
| 7794 (null !== this._password) ? this._password += tempC : this._userna
me += tempC; | |
| 7795 } | |
| 7796 buffer = ''; | |
| 7797 } else if (EOF == c || '/' == c || '\\' == c || '?' == c || '#' == c)
{ | |
| 7798 cursor -= buffer.length; | |
| 7799 buffer = ''; | |
| 7800 state = 'host'; | |
| 7801 continue; | |
| 7802 } else { | |
| 7803 buffer += c; | |
| 7804 } | |
| 7805 break; | |
| 7806 | |
| 7807 case 'file host': | |
| 7808 if (EOF == c || '/' == c || '\\' == c || '?' == c || '#' == c) { | |
| 7809 if (buffer.length == 2 && ALPHA.test(buffer[0]) && (buffer[1] == ':'
|| buffer[1] == '|')) { | |
| 7810 state = 'relative path'; | |
| 7811 } else if (buffer.length == 0) { | |
| 7812 state = 'relative path start'; | |
| 7813 } else { | |
| 7814 this._host = IDNAToASCII.call(this, buffer); | |
| 7815 buffer = ''; | |
| 7816 state = 'relative path start'; | |
| 7817 } | |
| 7818 continue; | |
| 7819 } else if ('\t' == c || '\n' == c || '\r' == c) { | |
| 7820 err('Invalid whitespace in file host.'); | |
| 7821 } else { | |
| 7822 buffer += c; | |
| 7823 } | |
| 7824 break; | |
| 7825 | |
| 7826 case 'host': | |
| 7827 case 'hostname': | |
| 7828 if (':' == c && !seenBracket) { | |
| 7829 // XXX host parsing | |
| 7830 this._host = IDNAToASCII.call(this, buffer); | |
| 7831 buffer = ''; | |
| 7832 state = 'port'; | |
| 7833 if ('hostname' == stateOverride) { | |
| 7834 break loop; | |
| 7835 } | |
| 7836 } else if (EOF == c || '/' == c || '\\' == c || '?' == c || '#' == c)
{ | |
| 7837 this._host = IDNAToASCII.call(this, buffer); | |
| 7838 buffer = ''; | |
| 7839 state = 'relative path start'; | |
| 7840 if (stateOverride) { | |
| 7841 break loop; | |
| 7842 } | |
| 7843 continue; | |
| 7844 } else if ('\t' != c && '\n' != c && '\r' != c) { | |
| 7845 if ('[' == c) { | |
| 7846 seenBracket = true; | |
| 7847 } else if (']' == c) { | |
| 7848 seenBracket = false; | |
| 7849 } | |
| 7850 buffer += c; | |
| 7851 } else { | |
| 7852 err('Invalid code point in host/hostname: ' + c); | |
| 7853 } | |
| 7854 break; | |
| 7855 | |
| 7856 case 'port': | |
| 7857 if (/[0-9]/.test(c)) { | |
| 7858 buffer += c; | |
| 7859 } else if (EOF == c || '/' == c || '\\' == c || '?' == c || '#' == c |
| stateOverride) { | |
| 7860 if ('' != buffer) { | |
| 7861 var temp = parseInt(buffer, 10); | |
| 7862 if (temp != relative[this._scheme]) { | |
| 7863 this._port = temp + ''; | |
| 7864 } | |
| 7865 buffer = ''; | |
| 7866 } | |
| 7867 if (stateOverride) { | |
| 7868 break loop; | |
| 7869 } | |
| 7870 state = 'relative path start'; | |
| 7871 continue; | |
| 7872 } else if ('\t' == c || '\n' == c || '\r' == c) { | |
| 7873 err('Invalid code point in port: ' + c); | |
| 7874 } else { | |
| 7875 invalid.call(this); | |
| 7876 } | |
| 7877 break; | |
| 7878 | |
| 7879 case 'relative path start': | |
| 7880 if ('\\' == c) | |
| 7881 err("'\\' not allowed in path."); | |
| 7882 state = 'relative path'; | |
| 7883 if ('/' != c && '\\' != c) { | |
| 7884 continue; | |
| 7885 } | |
| 7886 break; | |
| 7887 | |
| 7888 case 'relative path': | |
| 7889 if (EOF == c || '/' == c || '\\' == c || (!stateOverride && ('?' == c
|| '#' == c))) { | |
| 7890 if ('\\' == c) { | |
| 7891 err('\\ not allowed in relative path.'); | |
| 7892 } | |
| 7893 var tmp; | |
| 7894 if (tmp = relativePathDotMapping[buffer.toLowerCase()]) { | |
| 7895 buffer = tmp; | |
| 7896 } | |
| 7897 if ('..' == buffer) { | |
| 7898 this._path.pop(); | |
| 7899 if ('/' != c && '\\' != c) { | |
| 7900 this._path.push(''); | |
| 7901 } | |
| 7902 } else if ('.' == buffer && '/' != c && '\\' != c) { | |
| 7903 this._path.push(''); | |
| 7904 } else if ('.' != buffer) { | |
| 7905 if ('file' == this._scheme && this._path.length == 0 && buffer.len
gth == 2 && ALPHA.test(buffer[0]) && buffer[1] == '|') { | |
| 7906 buffer = buffer[0] + ':'; | |
| 7907 } | |
| 7908 this._path.push(buffer); | |
| 7909 } | |
| 7910 buffer = ''; | |
| 7911 if ('?' == c) { | |
| 7912 this._query = '?'; | |
| 7913 state = 'query'; | |
| 7914 } else if ('#' == c) { | |
| 7915 this._fragment = '#'; | |
| 7916 state = 'fragment'; | |
| 7917 } | |
| 7918 } else if ('\t' != c && '\n' != c && '\r' != c) { | |
| 7919 buffer += percentEscape(c); | |
| 7920 } | |
| 7921 break; | |
| 7922 | |
| 7923 case 'query': | |
| 7924 if (!stateOverride && '#' == c) { | |
| 7925 this._fragment = '#'; | |
| 7926 state = 'fragment'; | |
| 7927 } else if (EOF != c && '\t' != c && '\n' != c && '\r' != c) { | |
| 7928 this._query += percentEscapeQuery(c); | |
| 7929 } | |
| 7930 break; | |
| 7931 | |
| 7932 case 'fragment': | |
| 7933 if (EOF != c && '\t' != c && '\n' != c && '\r' != c) { | |
| 7934 this._fragment += c; | |
| 7935 } | |
| 7936 break; | |
| 7937 } | |
| 7938 | |
| 7939 cursor++; | |
| 7940 } | |
| 7941 } | |
| 7942 | |
| 7943 function clear() { | |
| 7944 this._scheme = ''; | |
| 7945 this._schemeData = ''; | |
| 7946 this._username = ''; | |
| 7947 this._password = null; | |
| 7948 this._host = ''; | |
| 7949 this._port = ''; | |
| 7950 this._path = []; | |
| 7951 this._query = ''; | |
| 7952 this._fragment = ''; | |
| 7953 this._isInvalid = false; | |
| 7954 this._isRelative = false; | |
| 7955 } | |
| 7956 | |
| 7957 // Does not process domain names or IP addresses. | |
| 7958 // Does not handle encoding for the query parameter. | |
| 7959 function jURL(url, base /* , encoding */) { | |
| 7960 if (base !== undefined && !(base instanceof jURL)) | |
| 7961 base = new jURL(String(base)); | |
| 7962 | |
| 7963 this._url = url; | |
| 7964 clear.call(this); | |
| 7965 | |
| 7966 var input = url.replace(/^[ \t\r\n\f]+|[ \t\r\n\f]+$/g, ''); | |
| 7967 // encoding = encoding || 'utf-8' | |
| 7968 | |
| 7969 parse.call(this, input, null, base); | |
| 7970 } | |
| 7971 | |
| 7972 jURL.prototype = { | |
| 7973 get href() { | |
| 7974 if (this._isInvalid) | |
| 7975 return this._url; | |
| 7976 | |
| 7977 var authority = ''; | |
| 7978 if ('' != this._username || null != this._password) { | |
| 7979 authority = this._username + | |
| 7980 (null != this._password ? ':' + this._password : '') + '@'; | |
| 7981 } | |
| 7982 | |
| 7983 return this.protocol + | |
| 7984 (this._isRelative ? '//' + authority + this.host : '') + | |
| 7985 this.pathname + this._query + this._fragment; | |
| 7986 }, | |
| 7987 set href(href) { | |
| 7988 clear.call(this); | |
| 7989 parse.call(this, href); | |
| 7990 }, | |
| 7991 | |
| 7992 get protocol() { | |
| 7993 return this._scheme + ':'; | |
| 7994 }, | |
| 7995 set protocol(protocol) { | |
| 7996 if (this._isInvalid) | |
| 7997 return; | |
| 7998 parse.call(this, protocol + ':', 'scheme start'); | |
| 7999 }, | |
| 8000 | |
| 8001 get host() { | |
| 8002 return this._isInvalid ? '' : this._port ? | |
| 8003 this._host + ':' + this._port : this._host; | |
| 8004 }, | |
| 8005 set host(host) { | |
| 8006 if (this._isInvalid || !this._isRelative) | |
| 8007 return; | |
| 8008 parse.call(this, host, 'host'); | |
| 8009 }, | |
| 8010 | |
| 8011 get hostname() { | |
| 8012 return this._host; | |
| 8013 }, | |
| 8014 set hostname(hostname) { | |
| 8015 if (this._isInvalid || !this._isRelative) | |
| 8016 return; | |
| 8017 parse.call(this, hostname, 'hostname'); | |
| 8018 }, | |
| 8019 | |
| 8020 get port() { | |
| 8021 return this._port; | |
| 8022 }, | |
| 8023 set port(port) { | |
| 8024 if (this._isInvalid || !this._isRelative) | |
| 8025 return; | |
| 8026 parse.call(this, port, 'port'); | |
| 8027 }, | |
| 8028 | |
| 8029 get pathname() { | |
| 8030 return this._isInvalid ? '' : this._isRelative ? | |
| 8031 '/' + this._path.join('/') : this._schemeData; | |
| 8032 }, | |
| 8033 set pathname(pathname) { | |
| 8034 if (this._isInvalid || !this._isRelative) | |
| 8035 return; | |
| 8036 this._path = []; | |
| 8037 parse.call(this, pathname, 'relative path start'); | |
| 8038 }, | |
| 8039 | |
| 8040 get search() { | |
| 8041 return this._isInvalid || !this._query || '?' == this._query ? | |
| 8042 '' : this._query; | |
| 8043 }, | |
| 8044 set search(search) { | |
| 8045 if (this._isInvalid || !this._isRelative) | |
| 8046 return; | |
| 8047 this._query = '?'; | |
| 8048 if ('?' == search[0]) | |
| 8049 search = search.slice(1); | |
| 8050 parse.call(this, search, 'query'); | |
| 8051 }, | |
| 8052 | |
| 8053 get hash() { | |
| 8054 return this._isInvalid || !this._fragment || '#' == this._fragment ? | |
| 8055 '' : this._fragment; | |
| 8056 }, | |
| 8057 set hash(hash) { | |
| 8058 if (this._isInvalid) | |
| 8059 return; | |
| 8060 this._fragment = '#'; | |
| 8061 if ('#' == hash[0]) | |
| 8062 hash = hash.slice(1); | |
| 8063 parse.call(this, hash, 'fragment'); | |
| 8064 }, | |
| 8065 | |
| 8066 get origin() { | |
| 8067 var host; | |
| 8068 if (this._isInvalid || !this._scheme) { | |
| 8069 return ''; | |
| 8070 } | |
| 8071 // javascript: Gecko returns String(""), WebKit/Blink String("null") | |
| 8072 // Gecko throws error for "data://" | |
| 8073 // data: Gecko returns "", Blink returns "data://", WebKit returns "null" | |
| 8074 // Gecko returns String("") for file: mailto: | |
| 8075 // WebKit/Blink returns String("SCHEME://") for file: mailto: | |
| 8076 switch (this._scheme) { | |
| 8077 case 'data': | |
| 8078 case 'file': | |
| 8079 case 'javascript': | |
| 8080 case 'mailto': | |
| 8081 return 'null'; | |
| 8082 } | |
| 8083 host = this.host; | |
| 8084 if (!host) { | |
| 8085 return ''; | |
| 8086 } | |
| 8087 return this._scheme + '://' + host; | |
| 8088 } | |
| 8089 }; | |
| 8090 | |
| 8091 // Copy over the static methods | |
| 8092 var OriginalURL = scope.URL; | |
| 8093 if (OriginalURL) { | |
| 8094 jURL.createObjectURL = function(blob) { | |
| 8095 // IE extension allows a second optional options argument. | |
| 8096 // http://msdn.microsoft.com/en-us/library/ie/hh772302(v=vs.85).aspx | |
| 8097 return OriginalURL.createObjectURL.apply(OriginalURL, arguments); | |
| 8098 }; | |
| 8099 jURL.revokeObjectURL = function(url) { | |
| 8100 OriginalURL.revokeObjectURL(url); | |
| 8101 }; | |
| 8102 } | |
| 8103 | |
| 8104 scope.URL = jURL; | |
| 8105 | |
| 8106 })(this); | |
| 8107 | |
| 8108 (function(scope) { | |
| 8109 | |
| 8110 var iterations = 0; | |
| 8111 var callbacks = []; | |
| 8112 var twiddle = document.createTextNode(''); | |
| 8113 | |
| 8114 function endOfMicrotask(callback) { | |
| 8115 twiddle.textContent = iterations++; | |
| 8116 callbacks.push(callback); | |
| 8117 } | |
| 8118 | |
| 8119 function atEndOfMicrotask() { | |
| 8120 while (callbacks.length) { | |
| 8121 callbacks.shift()(); | |
| 8122 } | |
| 8123 } | |
| 8124 | |
| 8125 new (window.MutationObserver || JsMutationObserver)(atEndOfMicrotask) | |
| 8126 .observe(twiddle, {characterData: true}) | |
| 8127 ; | |
| 8128 | |
| 8129 // exports | |
| 8130 scope.endOfMicrotask = endOfMicrotask; | |
| 8131 // bc | |
| 8132 Platform.endOfMicrotask = endOfMicrotask; | |
| 8133 | |
| 8134 })(Polymer); | |
| 8135 | |
| 8136 | |
| 8137 (function(scope) { | |
| 8138 | |
| 8139 /** | |
| 8140 * @class Polymer | |
| 8141 */ | |
| 8142 | |
| 8143 // imports | |
| 8144 var endOfMicrotask = scope.endOfMicrotask; | |
| 8145 | |
| 8146 // logging | |
| 8147 var log = window.WebComponents ? WebComponents.flags.log : {}; | |
| 8148 | |
| 8149 // inject style sheet | |
| 8150 var style = document.createElement('style'); | |
| 8151 style.textContent = 'template {display: none !important;} /* injected by platfor
m.js */'; | |
| 8152 var head = document.querySelector('head'); | |
| 8153 head.insertBefore(style, head.firstChild); | |
| 8154 | |
| 8155 | |
| 8156 /** | |
| 8157 * Force any pending data changes to be observed before | |
| 8158 * the next task. Data changes are processed asynchronously but are guaranteed | |
| 8159 * to be processed, for example, before painting. This method should rarely be | |
| 8160 * needed. It does nothing when Object.observe is available; | |
| 8161 * when Object.observe is not available, Polymer automatically flushes data | |
| 8162 * changes approximately every 1/10 second. | |
| 8163 * Therefore, `flush` should only be used when a data mutation should be | |
| 8164 * observed sooner than this. | |
| 8165 * | |
| 8166 * @method flush | |
| 8167 */ | |
| 8168 // flush (with logging) | |
| 8169 var flushing; | |
| 8170 function flush() { | |
| 8171 if (!flushing) { | |
| 8172 flushing = true; | |
| 8173 endOfMicrotask(function() { | |
| 8174 flushing = false; | |
| 8175 log.data && console.group('flush'); | |
| 8176 Platform.performMicrotaskCheckpoint(); | |
| 8177 log.data && console.groupEnd(); | |
| 8178 }); | |
| 8179 } | |
| 8180 }; | |
| 8181 | |
| 8182 // polling dirty checker | |
| 8183 // flush periodically if platform does not have object observe. | |
| 8184 if (!Observer.hasObjectObserve) { | |
| 8185 var FLUSH_POLL_INTERVAL = 125; | |
| 8186 window.addEventListener('WebComponentsReady', function() { | |
| 8187 flush(); | |
| 8188 // watch document visiblity to toggle dirty-checking | |
| 8189 var visibilityHandler = function() { | |
| 8190 // only flush if the page is visibile | |
| 8191 if (document.visibilityState === 'hidden') { | |
| 8192 if (scope.flushPoll) { | |
| 8193 clearInterval(scope.flushPoll); | |
| 8194 } | |
| 8195 } else { | |
| 8196 scope.flushPoll = setInterval(flush, FLUSH_POLL_INTERVAL); | |
| 8197 } | |
| 8198 }; | |
| 8199 if (typeof document.visibilityState === 'string') { | |
| 8200 document.addEventListener('visibilitychange', visibilityHandler); | |
| 8201 } | |
| 8202 visibilityHandler(); | |
| 8203 }); | |
| 8204 } else { | |
| 8205 // make flush a no-op when we have Object.observe | |
| 8206 flush = function() {}; | |
| 8207 } | |
| 8208 | |
| 8209 if (window.CustomElements && !CustomElements.useNative) { | |
| 8210 var originalImportNode = Document.prototype.importNode; | |
| 8211 Document.prototype.importNode = function(node, deep) { | |
| 8212 var imported = originalImportNode.call(this, node, deep); | |
| 8213 CustomElements.upgradeAll(imported); | |
| 8214 return imported; | |
| 8215 }; | |
| 8216 } | |
| 8217 | |
| 8218 // exports | |
| 8219 scope.flush = flush; | |
| 8220 // bc | |
| 8221 Platform.flush = flush; | |
| 8222 | |
| 8223 })(window.Polymer); | |
| 8224 | |
| 8225 | |
| 8226 (function(scope) { | |
| 8227 | |
| 8228 var urlResolver = { | |
| 8229 resolveDom: function(root, url) { | |
| 8230 url = url || baseUrl(root); | |
| 8231 this.resolveAttributes(root, url); | |
| 8232 this.resolveStyles(root, url); | |
| 8233 // handle template.content | |
| 8234 var templates = root.querySelectorAll('template'); | |
| 8235 if (templates) { | |
| 8236 for (var i = 0, l = templates.length, t; (i < l) && (t = templates[i]); i+
+) { | |
| 8237 if (t.content) { | |
| 8238 this.resolveDom(t.content, url); | |
| 8239 } | |
| 8240 } | |
| 8241 } | |
| 8242 }, | |
| 8243 resolveTemplate: function(template) { | |
| 8244 this.resolveDom(template.content, baseUrl(template)); | |
| 8245 }, | |
| 8246 resolveStyles: function(root, url) { | |
| 8247 var styles = root.querySelectorAll('style'); | |
| 8248 if (styles) { | |
| 8249 for (var i = 0, l = styles.length, s; (i < l) && (s = styles[i]); i++) { | |
| 8250 this.resolveStyle(s, url); | |
| 8251 } | |
| 8252 } | |
| 8253 }, | |
| 8254 resolveStyle: function(style, url) { | |
| 8255 url = url || baseUrl(style); | |
| 8256 style.textContent = this.resolveCssText(style.textContent, url); | |
| 8257 }, | |
| 8258 resolveCssText: function(cssText, baseUrl, keepAbsolute) { | |
| 8259 cssText = replaceUrlsInCssText(cssText, baseUrl, keepAbsolute, CSS_URL_REGEX
P); | |
| 8260 return replaceUrlsInCssText(cssText, baseUrl, keepAbsolute, CSS_IMPORT_REGEX
P); | |
| 8261 }, | |
| 8262 resolveAttributes: function(root, url) { | |
| 8263 if (root.hasAttributes && root.hasAttributes()) { | |
| 8264 this.resolveElementAttributes(root, url); | |
| 8265 } | |
| 8266 // search for attributes that host urls | |
| 8267 var nodes = root && root.querySelectorAll(URL_ATTRS_SELECTOR); | |
| 8268 if (nodes) { | |
| 8269 for (var i = 0, l = nodes.length, n; (i < l) && (n = nodes[i]); i++) { | |
| 8270 this.resolveElementAttributes(n, url); | |
| 8271 } | |
| 8272 } | |
| 8273 }, | |
| 8274 resolveElementAttributes: function(node, url) { | |
| 8275 url = url || baseUrl(node); | |
| 8276 URL_ATTRS.forEach(function(v) { | |
| 8277 var attr = node.attributes[v]; | |
| 8278 var value = attr && attr.value; | |
| 8279 var replacement; | |
| 8280 if (value && value.search(URL_TEMPLATE_SEARCH) < 0) { | |
| 8281 if (v === 'style') { | |
| 8282 replacement = replaceUrlsInCssText(value, url, false, CSS_URL_REGEXP); | |
| 8283 } else { | |
| 8284 replacement = resolveRelativeUrl(url, value); | |
| 8285 } | |
| 8286 attr.value = replacement; | |
| 8287 } | |
| 8288 }); | |
| 8289 } | |
| 8290 }; | |
| 8291 | |
| 8292 var CSS_URL_REGEXP = /(url\()([^)]*)(\))/g; | |
| 8293 var CSS_IMPORT_REGEXP = /(@import[\s]+(?!url\())([^;]*)(;)/g; | |
| 8294 var URL_ATTRS = ['href', 'src', 'action', 'style', 'url']; | |
| 8295 var URL_ATTRS_SELECTOR = '[' + URL_ATTRS.join('],[') + ']'; | |
| 8296 var URL_TEMPLATE_SEARCH = '{{.*}}'; | |
| 8297 var URL_HASH = '#'; | |
| 8298 | |
| 8299 function baseUrl(node) { | |
| 8300 var u = new URL(node.ownerDocument.baseURI); | |
| 8301 u.search = ''; | |
| 8302 u.hash = ''; | |
| 8303 return u; | |
| 8304 } | |
| 8305 | |
| 8306 function replaceUrlsInCssText(cssText, baseUrl, keepAbsolute, regexp) { | |
| 8307 return cssText.replace(regexp, function(m, pre, url, post) { | |
| 8308 var urlPath = url.replace(/["']/g, ''); | |
| 8309 urlPath = resolveRelativeUrl(baseUrl, urlPath, keepAbsolute); | |
| 8310 return pre + '\'' + urlPath + '\'' + post; | |
| 8311 }); | |
| 8312 } | |
| 8313 | |
| 8314 function resolveRelativeUrl(baseUrl, url, keepAbsolute) { | |
| 8315 // do not resolve '/' absolute urls | |
| 8316 if (url && url[0] === '/') { | |
| 8317 return url; | |
| 8318 } | |
| 8319 // do not resolve '#' links, they are used for routing | |
| 8320 if (url && url[0] === '#') { | |
| 8321 return url; | |
| 8322 } | |
| 8323 var u = new URL(url, baseUrl); | |
| 8324 return keepAbsolute ? u.href : makeDocumentRelPath(u.href); | |
| 8325 } | |
| 8326 | |
| 8327 function makeDocumentRelPath(url) { | |
| 8328 var root = baseUrl(document.documentElement); | |
| 8329 var u = new URL(url, root); | |
| 8330 if (u.host === root.host && u.port === root.port && | |
| 8331 u.protocol === root.protocol) { | |
| 8332 return makeRelPath(root, u); | |
| 8333 } else { | |
| 8334 return url; | |
| 8335 } | |
| 8336 } | |
| 8337 | |
| 8338 // make a relative path from source to target | |
| 8339 function makeRelPath(sourceUrl, targetUrl) { | |
| 8340 var source = sourceUrl.pathname; | |
| 8341 var target = targetUrl.pathname; | |
| 8342 var s = source.split('/'); | |
| 8343 var t = target.split('/'); | |
| 8344 while (s.length && s[0] === t[0]){ | |
| 8345 s.shift(); | |
| 8346 t.shift(); | |
| 8347 } | |
| 8348 for (var i = 0, l = s.length - 1; i < l; i++) { | |
| 8349 t.unshift('..'); | |
| 8350 } | |
| 8351 // empty '#' is discarded but we need to preserve it. | |
| 8352 var hash = (targetUrl.href.slice(-1) === URL_HASH) ? URL_HASH : targetUrl.hash
; | |
| 8353 return t.join('/') + targetUrl.search + hash; | |
| 8354 } | |
| 8355 | |
| 8356 // exports | |
| 8357 scope.urlResolver = urlResolver; | |
| 8358 | |
| 8359 })(Polymer); | |
| 8360 | |
| 8361 (function(scope) { | |
| 8362 var endOfMicrotask = Polymer.endOfMicrotask; | |
| 8363 | |
| 8364 // Generic url loader | |
| 8365 function Loader(regex) { | |
| 8366 this.cache = Object.create(null); | |
| 8367 this.map = Object.create(null); | |
| 8368 this.requests = 0; | |
| 8369 this.regex = regex; | |
| 8370 } | |
| 8371 Loader.prototype = { | |
| 8372 | |
| 8373 // TODO(dfreedm): there may be a better factoring here | |
| 8374 // extract absolute urls from the text (full of relative urls) | |
| 8375 extractUrls: function(text, base) { | |
| 8376 var matches = []; | |
| 8377 var matched, u; | |
| 8378 while ((matched = this.regex.exec(text))) { | |
| 8379 u = new URL(matched[1], base); | |
| 8380 matches.push({matched: matched[0], url: u.href}); | |
| 8381 } | |
| 8382 return matches; | |
| 8383 }, | |
| 8384 // take a text blob, a root url, and a callback and load all the urls found
within the text | |
| 8385 // returns a map of absolute url to text | |
| 8386 process: function(text, root, callback) { | |
| 8387 var matches = this.extractUrls(text, root); | |
| 8388 | |
| 8389 // every call to process returns all the text this loader has ever receive
d | |
| 8390 var done = callback.bind(null, this.map); | |
| 8391 this.fetch(matches, done); | |
| 8392 }, | |
| 8393 // build a mapping of url -> text from matches | |
| 8394 fetch: function(matches, callback) { | |
| 8395 var inflight = matches.length; | |
| 8396 | |
| 8397 // return early if there is no fetching to be done | |
| 8398 if (!inflight) { | |
| 8399 return callback(); | |
| 8400 } | |
| 8401 | |
| 8402 // wait for all subrequests to return | |
| 8403 var done = function() { | |
| 8404 if (--inflight === 0) { | |
| 8405 callback(); | |
| 8406 } | |
| 8407 }; | |
| 8408 | |
| 8409 // start fetching all subrequests | |
| 8410 var m, req, url; | |
| 8411 for (var i = 0; i < inflight; i++) { | |
| 8412 m = matches[i]; | |
| 8413 url = m.url; | |
| 8414 req = this.cache[url]; | |
| 8415 // if this url has already been requested, skip requesting it again | |
| 8416 if (!req) { | |
| 8417 req = this.xhr(url); | |
| 8418 req.match = m; | |
| 8419 this.cache[url] = req; | |
| 8420 } | |
| 8421 // wait for the request to process its subrequests | |
| 8422 req.wait(done); | |
| 8423 } | |
| 8424 }, | |
| 8425 handleXhr: function(request) { | |
| 8426 var match = request.match; | |
| 8427 var url = match.url; | |
| 8428 | |
| 8429 // handle errors with an empty string | |
| 8430 var response = request.response || request.responseText || ''; | |
| 8431 this.map[url] = response; | |
| 8432 this.fetch(this.extractUrls(response, url), request.resolve); | |
| 8433 }, | |
| 8434 xhr: function(url) { | |
| 8435 this.requests++; | |
| 8436 var request = new XMLHttpRequest(); | |
| 8437 request.open('GET', url, true); | |
| 8438 request.send(); | |
| 8439 request.onerror = request.onload = this.handleXhr.bind(this, request); | |
| 8440 | |
| 8441 // queue of tasks to run after XHR returns | |
| 8442 request.pending = []; | |
| 8443 request.resolve = function() { | |
| 8444 var pending = request.pending; | |
| 8445 for(var i = 0; i < pending.length; i++) { | |
| 8446 pending[i](); | |
| 8447 } | |
| 8448 request.pending = null; | |
| 8449 }; | |
| 8450 | |
| 8451 // if we have already resolved, pending is null, async call the callback | |
| 8452 request.wait = function(fn) { | |
| 8453 if (request.pending) { | |
| 8454 request.pending.push(fn); | |
| 8455 } else { | |
| 8456 endOfMicrotask(fn); | |
| 8457 } | |
| 8458 }; | |
| 8459 | |
| 8460 return request; | |
| 8461 } | |
| 8462 }; | |
| 8463 | |
| 8464 scope.Loader = Loader; | |
| 8465 })(Polymer); | |
| 8466 | |
| 8467 (function(scope) { | |
| 8468 | |
| 8469 var urlResolver = scope.urlResolver; | |
| 8470 var Loader = scope.Loader; | |
| 8471 | |
| 8472 function StyleResolver() { | |
| 8473 this.loader = new Loader(this.regex); | |
| 8474 } | |
| 8475 StyleResolver.prototype = { | |
| 8476 regex: /@import\s+(?:url)?["'\(]*([^'"\)]*)['"\)]*;/g, | |
| 8477 // Recursively replace @imports with the text at that url | |
| 8478 resolve: function(text, url, callback) { | |
| 8479 var done = function(map) { | |
| 8480 callback(this.flatten(text, url, map)); | |
| 8481 }.bind(this); | |
| 8482 this.loader.process(text, url, done); | |
| 8483 }, | |
| 8484 // resolve the textContent of a style node | |
| 8485 resolveNode: function(style, url, callback) { | |
| 8486 var text = style.textContent; | |
| 8487 var done = function(text) { | |
| 8488 style.textContent = text; | |
| 8489 callback(style); | |
| 8490 }; | |
| 8491 this.resolve(text, url, done); | |
| 8492 }, | |
| 8493 // flatten all the @imports to text | |
| 8494 flatten: function(text, base, map) { | |
| 8495 var matches = this.loader.extractUrls(text, base); | |
| 8496 var match, url, intermediate; | |
| 8497 for (var i = 0; i < matches.length; i++) { | |
| 8498 match = matches[i]; | |
| 8499 url = match.url; | |
| 8500 // resolve any css text to be relative to the importer, keep absolute url | |
| 8501 intermediate = urlResolver.resolveCssText(map[url], url, true); | |
| 8502 // flatten intermediate @imports | |
| 8503 intermediate = this.flatten(intermediate, base, map); | |
| 8504 text = text.replace(match.matched, intermediate); | |
| 8505 } | |
| 8506 return text; | |
| 8507 }, | |
| 8508 loadStyles: function(styles, base, callback) { | |
| 8509 var loaded=0, l = styles.length; | |
| 8510 // called in the context of the style | |
| 8511 function loadedStyle(style) { | |
| 8512 loaded++; | |
| 8513 if (loaded === l && callback) { | |
| 8514 callback(); | |
| 8515 } | |
| 8516 } | |
| 8517 for (var i=0, s; (i<l) && (s=styles[i]); i++) { | |
| 8518 this.resolveNode(s, base, loadedStyle); | |
| 8519 } | |
| 8520 } | |
| 8521 }; | |
| 8522 | |
| 8523 var styleResolver = new StyleResolver(); | |
| 8524 | |
| 8525 // exports | |
| 8526 scope.styleResolver = styleResolver; | |
| 8527 | |
| 8528 })(Polymer); | |
| 8529 | |
| 8530 (function(scope) { | |
| 8531 | |
| 8532 // copy own properties from 'api' to 'prototype, with name hinting for 'super' | |
| 8533 function extend(prototype, api) { | |
| 8534 if (prototype && api) { | |
| 8535 // use only own properties of 'api' | |
| 8536 Object.getOwnPropertyNames(api).forEach(function(n) { | |
| 8537 // acquire property descriptor | |
| 8538 var pd = Object.getOwnPropertyDescriptor(api, n); | |
| 8539 if (pd) { | |
| 8540 // clone property via descriptor | |
| 8541 Object.defineProperty(prototype, n, pd); | |
| 8542 // cache name-of-method for 'super' engine | |
| 8543 if (typeof pd.value == 'function') { | |
| 8544 // hint the 'super' engine | |
| 8545 pd.value.nom = n; | |
| 8546 } | |
| 8547 } | |
| 8548 }); | |
| 8549 } | |
| 8550 return prototype; | |
| 8551 } | |
| 8552 | |
| 8553 | |
| 8554 // mixin | |
| 8555 | |
| 8556 // copy all properties from inProps (et al) to inObj | |
| 8557 function mixin(inObj/*, inProps, inMoreProps, ...*/) { | |
| 8558 var obj = inObj || {}; | |
| 8559 for (var i = 1; i < arguments.length; i++) { | |
| 8560 var p = arguments[i]; | |
| 8561 try { | |
| 8562 for (var n in p) { | |
| 8563 copyProperty(n, p, obj); | |
| 8564 } | |
| 8565 } catch(x) { | |
| 8566 } | |
| 8567 } | |
| 8568 return obj; | |
| 8569 } | |
| 8570 | |
| 8571 // copy property inName from inSource object to inTarget object | |
| 8572 function copyProperty(inName, inSource, inTarget) { | |
| 8573 var pd = getPropertyDescriptor(inSource, inName); | |
| 8574 Object.defineProperty(inTarget, inName, pd); | |
| 8575 } | |
| 8576 | |
| 8577 // get property descriptor for inName on inObject, even if | |
| 8578 // inName exists on some link in inObject's prototype chain | |
| 8579 function getPropertyDescriptor(inObject, inName) { | |
| 8580 if (inObject) { | |
| 8581 var pd = Object.getOwnPropertyDescriptor(inObject, inName); | |
| 8582 return pd || getPropertyDescriptor(Object.getPrototypeOf(inObject), inName
); | |
| 8583 } | |
| 8584 } | |
| 8585 | |
| 8586 // exports | |
| 8587 | |
| 8588 scope.extend = extend; | |
| 8589 scope.mixin = mixin; | |
| 8590 | |
| 8591 // for bc | |
| 8592 Platform.mixin = mixin; | |
| 8593 | |
| 8594 })(Polymer); | |
| 8595 | |
| 8596 (function(scope) { | |
| 8597 | |
| 8598 // usage | |
| 8599 | |
| 8600 // invoke cb.call(this) in 100ms, unless the job is re-registered, | |
| 8601 // which resets the timer | |
| 8602 // | |
| 8603 // this.myJob = this.job(this.myJob, cb, 100) | |
| 8604 // | |
| 8605 // returns a job handle which can be used to re-register a job | |
| 8606 | |
| 8607 var Job = function(inContext) { | |
| 8608 this.context = inContext; | |
| 8609 this.boundComplete = this.complete.bind(this) | |
| 8610 }; | |
| 8611 Job.prototype = { | |
| 8612 go: function(callback, wait) { | |
| 8613 this.callback = callback; | |
| 8614 var h; | |
| 8615 if (!wait) { | |
| 8616 h = requestAnimationFrame(this.boundComplete); | |
| 8617 this.handle = function() { | |
| 8618 cancelAnimationFrame(h); | |
| 8619 } | |
| 8620 } else { | |
| 8621 h = setTimeout(this.boundComplete, wait); | |
| 8622 this.handle = function() { | |
| 8623 clearTimeout(h); | |
| 8624 } | |
| 8625 } | |
| 8626 }, | |
| 8627 stop: function() { | |
| 8628 if (this.handle) { | |
| 8629 this.handle(); | |
| 8630 this.handle = null; | |
| 8631 } | |
| 8632 }, | |
| 8633 complete: function() { | |
| 8634 if (this.handle) { | |
| 8635 this.stop(); | |
| 8636 this.callback.call(this.context); | |
| 8637 } | |
| 8638 } | |
| 8639 }; | |
| 8640 | |
| 8641 function job(job, callback, wait) { | |
| 8642 if (job) { | |
| 8643 job.stop(); | |
| 8644 } else { | |
| 8645 job = new Job(this); | |
| 8646 } | |
| 8647 job.go(callback, wait); | |
| 8648 return job; | |
| 8649 } | |
| 8650 | |
| 8651 // exports | |
| 8652 | |
| 8653 scope.job = job; | |
| 8654 | |
| 8655 })(Polymer); | |
| 8656 | |
| 8657 (function(scope) { | |
| 8658 | |
| 8659 // dom polyfill, additions, and utility methods | |
| 8660 | |
| 8661 var registry = {}; | |
| 8662 | |
| 8663 HTMLElement.register = function(tag, prototype) { | |
| 8664 registry[tag] = prototype; | |
| 8665 }; | |
| 8666 | |
| 8667 // get prototype mapped to node <tag> | |
| 8668 HTMLElement.getPrototypeForTag = function(tag) { | |
| 8669 var prototype = !tag ? HTMLElement.prototype : registry[tag]; | |
| 8670 // TODO(sjmiles): creating <tag> is likely to have wasteful side-effects | |
| 8671 return prototype || Object.getPrototypeOf(document.createElement(tag)); | |
| 8672 }; | |
| 8673 | |
| 8674 // we have to flag propagation stoppage for the event dispatcher | |
| 8675 var originalStopPropagation = Event.prototype.stopPropagation; | |
| 8676 Event.prototype.stopPropagation = function() { | |
| 8677 this.cancelBubble = true; | |
| 8678 originalStopPropagation.apply(this, arguments); | |
| 8679 }; | |
| 8680 | |
| 8681 | |
| 8682 // polyfill DOMTokenList | |
| 8683 // * add/remove: allow these methods to take multiple classNames | |
| 8684 // * toggle: add a 2nd argument which forces the given state rather | |
| 8685 // than toggling. | |
| 8686 | |
| 8687 var add = DOMTokenList.prototype.add; | |
| 8688 var remove = DOMTokenList.prototype.remove; | |
| 8689 DOMTokenList.prototype.add = function() { | |
| 8690 for (var i = 0; i < arguments.length; i++) { | |
| 8691 add.call(this, arguments[i]); | |
| 8692 } | |
| 8693 }; | |
| 8694 DOMTokenList.prototype.remove = function() { | |
| 8695 for (var i = 0; i < arguments.length; i++) { | |
| 8696 remove.call(this, arguments[i]); | |
| 8697 } | |
| 8698 }; | |
| 8699 DOMTokenList.prototype.toggle = function(name, bool) { | |
| 8700 if (arguments.length == 1) { | |
| 8701 bool = !this.contains(name); | |
| 8702 } | |
| 8703 bool ? this.add(name) : this.remove(name); | |
| 8704 }; | |
| 8705 DOMTokenList.prototype.switch = function(oldName, newName) { | |
| 8706 oldName && this.remove(oldName); | |
| 8707 newName && this.add(newName); | |
| 8708 }; | |
| 8709 | |
| 8710 // add array() to NodeList, NamedNodeMap, HTMLCollection | |
| 8711 | |
| 8712 var ArraySlice = function() { | |
| 8713 return Array.prototype.slice.call(this); | |
| 8714 }; | |
| 8715 | |
| 8716 var namedNodeMap = (window.NamedNodeMap || window.MozNamedAttrMap || {}); | |
| 8717 | |
| 8718 NodeList.prototype.array = ArraySlice; | |
| 8719 namedNodeMap.prototype.array = ArraySlice; | |
| 8720 HTMLCollection.prototype.array = ArraySlice; | |
| 8721 | |
| 8722 // utility | |
| 8723 | |
| 8724 function createDOM(inTagOrNode, inHTML, inAttrs) { | |
| 8725 var dom = typeof inTagOrNode == 'string' ? | |
| 8726 document.createElement(inTagOrNode) : inTagOrNode.cloneNode(true); | |
| 8727 dom.innerHTML = inHTML; | |
| 8728 if (inAttrs) { | |
| 8729 for (var n in inAttrs) { | |
| 8730 dom.setAttribute(n, inAttrs[n]); | |
| 8731 } | |
| 8732 } | |
| 8733 return dom; | |
| 8734 } | |
| 8735 | |
| 8736 // exports | |
| 8737 | |
| 8738 scope.createDOM = createDOM; | |
| 8739 | |
| 8740 })(Polymer); | |
| 8741 | |
| 8742 (function(scope) { | |
| 8743 // super | |
| 8744 | |
| 8745 // `arrayOfArgs` is an optional array of args like one might pass | |
| 8746 // to `Function.apply` | |
| 8747 | |
| 8748 // TODO(sjmiles): | |
| 8749 // $super must be installed on an instance or prototype chain | |
| 8750 // as `super`, and invoked via `this`, e.g. | |
| 8751 // `this.super();` | |
| 8752 | |
| 8753 // will not work if function objects are not unique, for example, | |
| 8754 // when using mixins. | |
| 8755 // The memoization strategy assumes each function exists on only one | |
| 8756 // prototype chain i.e. we use the function object for memoizing) | |
| 8757 // perhaps we can bookkeep on the prototype itself instead | |
| 8758 function $super(arrayOfArgs) { | |
| 8759 // since we are thunking a method call, performance is important here: | |
| 8760 // memoize all lookups, once memoized the fast path calls no other | |
| 8761 // functions | |
| 8762 // | |
| 8763 // find the caller (cannot be `strict` because of 'caller') | |
| 8764 var caller = $super.caller; | |
| 8765 // memoized 'name of method' | |
| 8766 var nom = caller.nom; | |
| 8767 // memoized next implementation prototype | |
| 8768 var _super = caller._super; | |
| 8769 if (!_super) { | |
| 8770 if (!nom) { | |
| 8771 nom = caller.nom = nameInThis.call(this, caller); | |
| 8772 } | |
| 8773 if (!nom) { | |
| 8774 console.warn('called super() on a method not installed declaratively (
has no .nom property)'); | |
| 8775 } | |
| 8776 // super prototype is either cached or we have to find it | |
| 8777 // by searching __proto__ (at the 'top') | |
| 8778 // invariant: because we cache _super on fn below, we never reach | |
| 8779 // here from inside a series of calls to super(), so it's ok to | |
| 8780 // start searching from the prototype of 'this' (at the 'top') | |
| 8781 // we must never memoize a null super for this reason | |
| 8782 _super = memoizeSuper(caller, nom, getPrototypeOf(this)); | |
| 8783 } | |
| 8784 // our super function | |
| 8785 var fn = _super[nom]; | |
| 8786 if (fn) { | |
| 8787 // memoize information so 'fn' can call 'super' | |
| 8788 if (!fn._super) { | |
| 8789 // must not memoize null, or we lose our invariant above | |
| 8790 memoizeSuper(fn, nom, _super); | |
| 8791 } | |
| 8792 // invoke the inherited method | |
| 8793 // if 'fn' is not function valued, this will throw | |
| 8794 return fn.apply(this, arrayOfArgs || []); | |
| 8795 } | |
| 8796 } | |
| 8797 | |
| 8798 function nameInThis(value) { | |
| 8799 var p = this.__proto__; | |
| 8800 while (p && p !== HTMLElement.prototype) { | |
| 8801 // TODO(sjmiles): getOwnPropertyNames is absurdly expensive | |
| 8802 var n$ = Object.getOwnPropertyNames(p); | |
| 8803 for (var i=0, l=n$.length, n; i<l && (n=n$[i]); i++) { | |
| 8804 var d = Object.getOwnPropertyDescriptor(p, n); | |
| 8805 if (typeof d.value === 'function' && d.value === value) { | |
| 8806 return n; | |
| 8807 } | |
| 8808 } | |
| 8809 p = p.__proto__; | |
| 8810 } | |
| 8811 } | |
| 8812 | |
| 8813 function memoizeSuper(method, name, proto) { | |
| 8814 // find and cache next prototype containing `name` | |
| 8815 // we need the prototype so we can do another lookup | |
| 8816 // from here | |
| 8817 var s = nextSuper(proto, name, method); | |
| 8818 if (s[name]) { | |
| 8819 // `s` is a prototype, the actual method is `s[name]` | |
| 8820 // tag super method with it's name for quicker lookups | |
| 8821 s[name].nom = name; | |
| 8822 } | |
| 8823 return method._super = s; | |
| 8824 } | |
| 8825 | |
| 8826 function nextSuper(proto, name, caller) { | |
| 8827 // look for an inherited prototype that implements name | |
| 8828 while (proto) { | |
| 8829 if ((proto[name] !== caller) && proto[name]) { | |
| 8830 return proto; | |
| 8831 } | |
| 8832 proto = getPrototypeOf(proto); | |
| 8833 } | |
| 8834 // must not return null, or we lose our invariant above | |
| 8835 // in this case, a super() call was invoked where no superclass | |
| 8836 // method exists | |
| 8837 // TODO(sjmiles): thow an exception? | |
| 8838 return Object; | |
| 8839 } | |
| 8840 | |
| 8841 // NOTE: In some platforms (IE10) the prototype chain is faked via | |
| 8842 // __proto__. Therefore, always get prototype via __proto__ instead of | |
| 8843 // the more standard Object.getPrototypeOf. | |
| 8844 function getPrototypeOf(prototype) { | |
| 8845 return prototype.__proto__; | |
| 8846 } | |
| 8847 | |
| 8848 // utility function to precompute name tags for functions | |
| 8849 // in a (unchained) prototype | |
| 8850 function hintSuper(prototype) { | |
| 8851 // tag functions with their prototype name to optimize | |
| 8852 // super call invocations | |
| 8853 for (var n in prototype) { | |
| 8854 var pd = Object.getOwnPropertyDescriptor(prototype, n); | |
| 8855 if (pd && typeof pd.value === 'function') { | |
| 8856 pd.value.nom = n; | |
| 8857 } | |
| 8858 } | |
| 8859 } | |
| 8860 | |
| 8861 // exports | |
| 8862 | |
| 8863 scope.super = $super; | |
| 8864 | |
| 8865 })(Polymer); | |
| 8866 | |
| 8867 (function(scope) { | |
| 8868 | |
| 8869 function noopHandler(value) { | |
| 8870 return value; | |
| 8871 } | |
| 8872 | |
| 8873 // helper for deserializing properties of various types to strings | |
| 8874 var typeHandlers = { | |
| 8875 string: noopHandler, | |
| 8876 'undefined': noopHandler, | |
| 8877 date: function(value) { | |
| 8878 return new Date(Date.parse(value) || Date.now()); | |
| 8879 }, | |
| 8880 boolean: function(value) { | |
| 8881 if (value === '') { | |
| 8882 return true; | |
| 8883 } | |
| 8884 return value === 'false' ? false : !!value; | |
| 8885 }, | |
| 8886 number: function(value) { | |
| 8887 var n = parseFloat(value); | |
| 8888 // hex values like "0xFFFF" parseFloat as 0 | |
| 8889 if (n === 0) { | |
| 8890 n = parseInt(value); | |
| 8891 } | |
| 8892 return isNaN(n) ? value : n; | |
| 8893 // this code disabled because encoded values (like "0xFFFF") | |
| 8894 // do not round trip to their original format | |
| 8895 //return (String(floatVal) === value) ? floatVal : value; | |
| 8896 }, | |
| 8897 object: function(value, currentValue) { | |
| 8898 if (currentValue === null) { | |
| 8899 return value; | |
| 8900 } | |
| 8901 try { | |
| 8902 // If the string is an object, we can parse is with the JSON library. | |
| 8903 // include convenience replace for single-quotes. If the author omits | |
| 8904 // quotes altogether, parse will fail. | |
| 8905 return JSON.parse(value.replace(/'/g, '"')); | |
| 8906 } catch(e) { | |
| 8907 // The object isn't valid JSON, return the raw value | |
| 8908 return value; | |
| 8909 } | |
| 8910 }, | |
| 8911 // avoid deserialization of functions | |
| 8912 'function': function(value, currentValue) { | |
| 8913 return currentValue; | |
| 8914 } | |
| 8915 }; | |
| 8916 | |
| 8917 function deserializeValue(value, currentValue) { | |
| 8918 // attempt to infer type from default value | |
| 8919 var inferredType = typeof currentValue; | |
| 8920 // invent 'date' type value for Date | |
| 8921 if (currentValue instanceof Date) { | |
| 8922 inferredType = 'date'; | |
| 8923 } | |
| 8924 // delegate deserialization via type string | |
| 8925 return typeHandlers[inferredType](value, currentValue); | |
| 8926 } | |
| 8927 | |
| 8928 // exports | |
| 8929 | |
| 8930 scope.deserializeValue = deserializeValue; | |
| 8931 | |
| 8932 })(Polymer); | |
| 8933 | |
| 8934 (function(scope) { | |
| 8935 | |
| 8936 // imports | |
| 8937 | |
| 8938 var extend = scope.extend; | |
| 8939 | |
| 8940 // module | |
| 8941 | |
| 8942 var api = {}; | |
| 8943 | |
| 8944 api.declaration = {}; | |
| 8945 api.instance = {}; | |
| 8946 | |
| 8947 api.publish = function(apis, prototype) { | |
| 8948 for (var n in apis) { | |
| 8949 extend(prototype, apis[n]); | |
| 8950 } | |
| 8951 }; | |
| 8952 | |
| 8953 // exports | |
| 8954 | |
| 8955 scope.api = api; | |
| 8956 | |
| 8957 })(Polymer); | |
| 8958 | |
| 8959 (function(scope) { | |
| 8960 | |
| 8961 /** | |
| 8962 * @class polymer-base | |
| 8963 */ | |
| 8964 | |
| 8965 var utils = { | |
| 8966 | |
| 8967 /** | |
| 8968 * Invokes a function asynchronously. The context of the callback | |
| 8969 * function is bound to 'this' automatically. Returns a handle which may | |
| 8970 * be passed to <a href="#cancelAsync">cancelAsync</a> to cancel the | |
| 8971 * asynchronous call. | |
| 8972 * | |
| 8973 * @method async | |
| 8974 * @param {Function|String} method | |
| 8975 * @param {any|Array} args | |
| 8976 * @param {number} timeout | |
| 8977 */ | |
| 8978 async: function(method, args, timeout) { | |
| 8979 // when polyfilling Object.observe, ensure changes | |
| 8980 // propagate before executing the async method | |
| 8981 Polymer.flush(); | |
| 8982 // second argument to `apply` must be an array | |
| 8983 args = (args && args.length) ? args : [args]; | |
| 8984 // function to invoke | |
| 8985 var fn = function() { | |
| 8986 (this[method] || method).apply(this, args); | |
| 8987 }.bind(this); | |
| 8988 // execute `fn` sooner or later | |
| 8989 var handle = timeout ? setTimeout(fn, timeout) : | |
| 8990 requestAnimationFrame(fn); | |
| 8991 // NOTE: switch on inverting handle to determine which time is used. | |
| 8992 return timeout ? handle : ~handle; | |
| 8993 }, | |
| 8994 | |
| 8995 /** | |
| 8996 * Cancels a pending callback that was scheduled via | |
| 8997 * <a href="#async">async</a>. | |
| 8998 * | |
| 8999 * @method cancelAsync | |
| 9000 * @param {handle} handle Handle of the `async` to cancel. | |
| 9001 */ | |
| 9002 cancelAsync: function(handle) { | |
| 9003 if (handle < 0) { | |
| 9004 cancelAnimationFrame(~handle); | |
| 9005 } else { | |
| 9006 clearTimeout(handle); | |
| 9007 } | |
| 9008 }, | |
| 9009 | |
| 9010 /** | |
| 9011 * Fire an event. | |
| 9012 * | |
| 9013 * @method fire | |
| 9014 * @returns {Object} event | |
| 9015 * @param {string} type An event name. | |
| 9016 * @param {any} detail | |
| 9017 * @param {Node} onNode Target node. | |
| 9018 * @param {Boolean} bubbles Set false to prevent bubbling, defaults to true | |
| 9019 * @param {Boolean} cancelable Set false to prevent cancellation, defaults
to true | |
| 9020 */ | |
| 9021 fire: function(type, detail, onNode, bubbles, cancelable) { | |
| 9022 var node = onNode || this; | |
| 9023 var detail = detail === null || detail === undefined ? {} : detail; | |
| 9024 var event = new CustomEvent(type, { | |
| 9025 bubbles: bubbles !== undefined ? bubbles : true, | |
| 9026 cancelable: cancelable !== undefined ? cancelable : true, | |
| 9027 detail: detail | |
| 9028 }); | |
| 9029 node.dispatchEvent(event); | |
| 9030 return event; | |
| 9031 }, | |
| 9032 | |
| 9033 /** | |
| 9034 * Fire an event asynchronously. | |
| 9035 * | |
| 9036 * @method asyncFire | |
| 9037 * @param {string} type An event name. | |
| 9038 * @param detail | |
| 9039 * @param {Node} toNode Target node. | |
| 9040 */ | |
| 9041 asyncFire: function(/*inType, inDetail*/) { | |
| 9042 this.async("fire", arguments); | |
| 9043 }, | |
| 9044 | |
| 9045 /** | |
| 9046 * Remove class from old, add class to anew, if they exist. | |
| 9047 * | |
| 9048 * @param classFollows | |
| 9049 * @param anew A node. | |
| 9050 * @param old A node | |
| 9051 * @param className | |
| 9052 */ | |
| 9053 classFollows: function(anew, old, className) { | |
| 9054 if (old) { | |
| 9055 old.classList.remove(className); | |
| 9056 } | |
| 9057 if (anew) { | |
| 9058 anew.classList.add(className); | |
| 9059 } | |
| 9060 }, | |
| 9061 | |
| 9062 /** | |
| 9063 * Inject HTML which contains markup bound to this element into | |
| 9064 * a target element (replacing target element content). | |
| 9065 * | |
| 9066 * @param String html to inject | |
| 9067 * @param Element target element | |
| 9068 */ | |
| 9069 injectBoundHTML: function(html, element) { | |
| 9070 var template = document.createElement('template'); | |
| 9071 template.innerHTML = html; | |
| 9072 var fragment = this.instanceTemplate(template); | |
| 9073 if (element) { | |
| 9074 element.textContent = ''; | |
| 9075 element.appendChild(fragment); | |
| 9076 } | |
| 9077 return fragment; | |
| 9078 } | |
| 9079 }; | |
| 9080 | |
| 9081 // no-operation function for handy stubs | |
| 9082 var nop = function() {}; | |
| 9083 | |
| 9084 // null-object for handy stubs | |
| 9085 var nob = {}; | |
| 9086 | |
| 9087 // deprecated | |
| 9088 | |
| 9089 utils.asyncMethod = utils.async; | |
| 9090 | |
| 9091 // exports | |
| 9092 | |
| 9093 scope.api.instance.utils = utils; | |
| 9094 scope.nop = nop; | |
| 9095 scope.nob = nob; | |
| 9096 | |
| 9097 })(Polymer); | |
| 9098 | |
| 9099 (function(scope) { | |
| 9100 | |
| 9101 // imports | |
| 9102 | |
| 9103 var log = window.WebComponents ? WebComponents.flags.log : {}; | |
| 9104 var EVENT_PREFIX = 'on-'; | |
| 9105 | |
| 9106 // instance events api | |
| 9107 var events = { | |
| 9108 // read-only | |
| 9109 EVENT_PREFIX: EVENT_PREFIX, | |
| 9110 // event listeners on host | |
| 9111 addHostListeners: function() { | |
| 9112 var events = this.eventDelegates; | |
| 9113 log.events && (Object.keys(events).length > 0) && console.log('[%s] addHos
tListeners:', this.localName, events); | |
| 9114 // NOTE: host events look like bindings but really are not; | |
| 9115 // (1) we don't want the attribute to be set and (2) we want to support | |
| 9116 // multiple event listeners ('host' and 'instance') and Node.bind | |
| 9117 // by default supports 1 thing being bound. | |
| 9118 for (var type in events) { | |
| 9119 var methodName = events[type]; | |
| 9120 PolymerGestures.addEventListener(this, type, this.element.getEventHandle
r(this, this, methodName)); | |
| 9121 } | |
| 9122 }, | |
| 9123 // call 'method' or function method on 'obj' with 'args', if the method exis
ts | |
| 9124 dispatchMethod: function(obj, method, args) { | |
| 9125 if (obj) { | |
| 9126 log.events && console.group('[%s] dispatch [%s]', obj.localName, method)
; | |
| 9127 var fn = typeof method === 'function' ? method : obj[method]; | |
| 9128 if (fn) { | |
| 9129 fn[args ? 'apply' : 'call'](obj, args); | |
| 9130 } | |
| 9131 log.events && console.groupEnd(); | |
| 9132 // NOTE: dirty check right after calling method to ensure | |
| 9133 // changes apply quickly; in a very complicated app using high | |
| 9134 // frequency events, this can be a perf concern; in this case, | |
| 9135 // imperative handlers can be used to avoid flushing. | |
| 9136 Polymer.flush(); | |
| 9137 } | |
| 9138 } | |
| 9139 }; | |
| 9140 | |
| 9141 // exports | |
| 9142 | |
| 9143 scope.api.instance.events = events; | |
| 9144 | |
| 9145 /** | |
| 9146 * @class Polymer | |
| 9147 */ | |
| 9148 | |
| 9149 /** | |
| 9150 * Add a gesture aware event handler to the given `node`. Can be used | |
| 9151 * in place of `element.addEventListener` and ensures gestures will function | |
| 9152 * as expected on mobile platforms. Please note that Polymer's declarative | |
| 9153 * event handlers include this functionality by default. | |
| 9154 * | |
| 9155 * @method addEventListener | |
| 9156 * @param {Node} node node on which to listen | |
| 9157 * @param {String} eventType name of the event | |
| 9158 * @param {Function} handlerFn event handler function | |
| 9159 * @param {Boolean} capture set to true to invoke event capturing | |
| 9160 * @type Function | |
| 9161 */ | |
| 9162 // alias PolymerGestures event listener logic | |
| 9163 scope.addEventListener = function(node, eventType, handlerFn, capture) { | |
| 9164 PolymerGestures.addEventListener(wrap(node), eventType, handlerFn, capture); | |
| 9165 }; | |
| 9166 | |
| 9167 /** | |
| 9168 * Remove a gesture aware event handler on the given `node`. To remove an | |
| 9169 * event listener, the exact same arguments are required that were passed | |
| 9170 * to `Polymer.addEventListener`. | |
| 9171 * | |
| 9172 * @method removeEventListener | |
| 9173 * @param {Node} node node on which to listen | |
| 9174 * @param {String} eventType name of the event | |
| 9175 * @param {Function} handlerFn event handler function | |
| 9176 * @param {Boolean} capture set to true to invoke event capturing | |
| 9177 * @type Function | |
| 9178 */ | |
| 9179 scope.removeEventListener = function(node, eventType, handlerFn, capture) { | |
| 9180 PolymerGestures.removeEventListener(wrap(node), eventType, handlerFn, captur
e); | |
| 9181 }; | |
| 9182 | |
| 9183 })(Polymer); | |
| 9184 | |
| 9185 (function(scope) { | |
| 9186 | |
| 9187 // instance api for attributes | |
| 9188 | |
| 9189 var attributes = { | |
| 9190 // copy attributes defined in the element declaration to the instance | |
| 9191 // e.g. <polymer-element name="x-foo" tabIndex="0"> tabIndex is copied | |
| 9192 // to the element instance here. | |
| 9193 copyInstanceAttributes: function () { | |
| 9194 var a$ = this._instanceAttributes; | |
| 9195 for (var k in a$) { | |
| 9196 if (!this.hasAttribute(k)) { | |
| 9197 this.setAttribute(k, a$[k]); | |
| 9198 } | |
| 9199 } | |
| 9200 }, | |
| 9201 // for each attribute on this, deserialize value to property as needed | |
| 9202 takeAttributes: function() { | |
| 9203 // if we have no publish lookup table, we have no attributes to take | |
| 9204 // TODO(sjmiles): ad hoc | |
| 9205 if (this._publishLC) { | |
| 9206 for (var i=0, a$=this.attributes, l=a$.length, a; (a=a$[i]) && i<l; i++)
{ | |
| 9207 this.attributeToProperty(a.name, a.value); | |
| 9208 } | |
| 9209 } | |
| 9210 }, | |
| 9211 // if attribute 'name' is mapped to a property, deserialize | |
| 9212 // 'value' into that property | |
| 9213 attributeToProperty: function(name, value) { | |
| 9214 // try to match this attribute to a property (attributes are | |
| 9215 // all lower-case, so this is case-insensitive search) | |
| 9216 var name = this.propertyForAttribute(name); | |
| 9217 if (name) { | |
| 9218 // filter out 'mustached' values, these are to be | |
| 9219 // replaced with bound-data and are not yet values | |
| 9220 // themselves | |
| 9221 if (value && value.search(scope.bindPattern) >= 0) { | |
| 9222 return; | |
| 9223 } | |
| 9224 // get original value | |
| 9225 var currentValue = this[name]; | |
| 9226 // deserialize Boolean or Number values from attribute | |
| 9227 var value = this.deserializeValue(value, currentValue); | |
| 9228 // only act if the value has changed | |
| 9229 if (value !== currentValue) { | |
| 9230 // install new value (has side-effects) | |
| 9231 this[name] = value; | |
| 9232 } | |
| 9233 } | |
| 9234 }, | |
| 9235 // return the published property matching name, or undefined | |
| 9236 propertyForAttribute: function(name) { | |
| 9237 var match = this._publishLC && this._publishLC[name]; | |
| 9238 return match; | |
| 9239 }, | |
| 9240 // convert representation of `stringValue` based on type of `currentValue` | |
| 9241 deserializeValue: function(stringValue, currentValue) { | |
| 9242 return scope.deserializeValue(stringValue, currentValue); | |
| 9243 }, | |
| 9244 // convert to a string value based on the type of `inferredType` | |
| 9245 serializeValue: function(value, inferredType) { | |
| 9246 if (inferredType === 'boolean') { | |
| 9247 return value ? '' : undefined; | |
| 9248 } else if (inferredType !== 'object' && inferredType !== 'function' | |
| 9249 && value !== undefined) { | |
| 9250 return value; | |
| 9251 } | |
| 9252 }, | |
| 9253 // serializes `name` property value and updates the corresponding attribute | |
| 9254 // note that reflection is opt-in. | |
| 9255 reflectPropertyToAttribute: function(name) { | |
| 9256 var inferredType = typeof this[name]; | |
| 9257 // try to intelligently serialize property value | |
| 9258 var serializedValue = this.serializeValue(this[name], inferredType); | |
| 9259 // boolean properties must reflect as boolean attributes | |
| 9260 if (serializedValue !== undefined) { | |
| 9261 this.setAttribute(name, serializedValue); | |
| 9262 // TODO(sorvell): we should remove attr for all properties | |
| 9263 // that have undefined serialization; however, we will need to | |
| 9264 // refine the attr reflection system to achieve this; pica, for example, | |
| 9265 // relies on having inferredType object properties not removed as | |
| 9266 // attrs. | |
| 9267 } else if (inferredType === 'boolean') { | |
| 9268 this.removeAttribute(name); | |
| 9269 } | |
| 9270 } | |
| 9271 }; | |
| 9272 | |
| 9273 // exports | |
| 9274 | |
| 9275 scope.api.instance.attributes = attributes; | |
| 9276 | |
| 9277 })(Polymer); | |
| 9278 | |
| 9279 (function(scope) { | |
| 9280 | |
| 9281 /** | |
| 9282 * @class polymer-base | |
| 9283 */ | |
| 9284 | |
| 9285 // imports | |
| 9286 | |
| 9287 var log = window.WebComponents ? WebComponents.flags.log : {}; | |
| 9288 | |
| 9289 // magic words | |
| 9290 | |
| 9291 var OBSERVE_SUFFIX = 'Changed'; | |
| 9292 | |
| 9293 // element api | |
| 9294 | |
| 9295 var empty = []; | |
| 9296 | |
| 9297 var updateRecord = { | |
| 9298 object: undefined, | |
| 9299 type: 'update', | |
| 9300 name: undefined, | |
| 9301 oldValue: undefined | |
| 9302 }; | |
| 9303 | |
| 9304 var numberIsNaN = Number.isNaN || function(value) { | |
| 9305 return typeof value === 'number' && isNaN(value); | |
| 9306 }; | |
| 9307 | |
| 9308 function areSameValue(left, right) { | |
| 9309 if (left === right) | |
| 9310 return left !== 0 || 1 / left === 1 / right; | |
| 9311 if (numberIsNaN(left) && numberIsNaN(right)) | |
| 9312 return true; | |
| 9313 return left !== left && right !== right; | |
| 9314 } | |
| 9315 | |
| 9316 // capture A's value if B's value is null or undefined, | |
| 9317 // otherwise use B's value | |
| 9318 function resolveBindingValue(oldValue, value) { | |
| 9319 if (value === undefined && oldValue === null) { | |
| 9320 return value; | |
| 9321 } | |
| 9322 return (value === null || value === undefined) ? oldValue : value; | |
| 9323 } | |
| 9324 | |
| 9325 var properties = { | |
| 9326 | |
| 9327 // creates a CompoundObserver to observe property changes | |
| 9328 // NOTE, this is only done there are any properties in the `observe` object | |
| 9329 createPropertyObserver: function() { | |
| 9330 var n$ = this._observeNames; | |
| 9331 if (n$ && n$.length) { | |
| 9332 var o = this._propertyObserver = new CompoundObserver(true); | |
| 9333 this.registerObserver(o); | |
| 9334 // TODO(sorvell): may not be kosher to access the value here (this[n]); | |
| 9335 // previously we looked at the descriptor on the prototype | |
| 9336 // this doesn't work for inheritance and not for accessors without | |
| 9337 // a value property | |
| 9338 for (var i=0, l=n$.length, n; (i<l) && (n=n$[i]); i++) { | |
| 9339 o.addPath(this, n); | |
| 9340 this.observeArrayValue(n, this[n], null); | |
| 9341 } | |
| 9342 } | |
| 9343 }, | |
| 9344 | |
| 9345 // start observing property changes | |
| 9346 openPropertyObserver: function() { | |
| 9347 if (this._propertyObserver) { | |
| 9348 this._propertyObserver.open(this.notifyPropertyChanges, this); | |
| 9349 } | |
| 9350 }, | |
| 9351 | |
| 9352 // handler for property changes; routes changes to observing methods | |
| 9353 // note: array valued properties are observed for array splices | |
| 9354 notifyPropertyChanges: function(newValues, oldValues, paths) { | |
| 9355 var name, method, called = {}; | |
| 9356 for (var i in oldValues) { | |
| 9357 // note: paths is of form [object, path, object, path] | |
| 9358 name = paths[2 * i + 1]; | |
| 9359 method = this.observe[name]; | |
| 9360 if (method) { | |
| 9361 var ov = oldValues[i], nv = newValues[i]; | |
| 9362 // observes the value if it is an array | |
| 9363 this.observeArrayValue(name, nv, ov); | |
| 9364 if (!called[method]) { | |
| 9365 // only invoke change method if one of ov or nv is not (undefined |
null) | |
| 9366 if ((ov !== undefined && ov !== null) || (nv !== undefined && nv !==
null)) { | |
| 9367 called[method] = true; | |
| 9368 // TODO(sorvell): call method with the set of values it's expectin
g; | |
| 9369 // e.g. 'foo bar': 'invalidate' expects the new and old values for | |
| 9370 // foo and bar. Currently we give only one of these and then | |
| 9371 // deliver all the arguments. | |
| 9372 this.invokeMethod(method, [ov, nv, arguments]); | |
| 9373 } | |
| 9374 } | |
| 9375 } | |
| 9376 } | |
| 9377 }, | |
| 9378 | |
| 9379 // call method iff it exists. | |
| 9380 invokeMethod: function(method, args) { | |
| 9381 var fn = this[method] || method; | |
| 9382 if (typeof fn === 'function') { | |
| 9383 fn.apply(this, args); | |
| 9384 } | |
| 9385 }, | |
| 9386 | |
| 9387 /** | |
| 9388 * Force any pending property changes to synchronously deliver to | |
| 9389 * handlers specified in the `observe` object. | |
| 9390 * Note, normally changes are processed at microtask time. | |
| 9391 * | |
| 9392 * @method deliverChanges | |
| 9393 */ | |
| 9394 deliverChanges: function() { | |
| 9395 if (this._propertyObserver) { | |
| 9396 this._propertyObserver.deliver(); | |
| 9397 } | |
| 9398 }, | |
| 9399 | |
| 9400 observeArrayValue: function(name, value, old) { | |
| 9401 // we only care if there are registered side-effects | |
| 9402 var callbackName = this.observe[name]; | |
| 9403 if (callbackName) { | |
| 9404 // if we are observing the previous value, stop | |
| 9405 if (Array.isArray(old)) { | |
| 9406 log.observe && console.log('[%s] observeArrayValue: unregister observe
r [%s]', this.localName, name); | |
| 9407 this.closeNamedObserver(name + '__array'); | |
| 9408 } | |
| 9409 // if the new value is an array, being observing it | |
| 9410 if (Array.isArray(value)) { | |
| 9411 log.observe && console.log('[%s] observeArrayValue: register observer
[%s]', this.localName, name, value); | |
| 9412 var observer = new ArrayObserver(value); | |
| 9413 observer.open(function(splices) { | |
| 9414 this.invokeMethod(callbackName, [splices]); | |
| 9415 }, this); | |
| 9416 this.registerNamedObserver(name + '__array', observer); | |
| 9417 } | |
| 9418 } | |
| 9419 }, | |
| 9420 | |
| 9421 emitPropertyChangeRecord: function(name, value, oldValue) { | |
| 9422 var object = this; | |
| 9423 if (areSameValue(value, oldValue)) { | |
| 9424 return; | |
| 9425 } | |
| 9426 // invoke property change side effects | |
| 9427 this._propertyChanged(name, value, oldValue); | |
| 9428 // emit change record | |
| 9429 if (!Observer.hasObjectObserve) { | |
| 9430 return; | |
| 9431 } | |
| 9432 var notifier = this._objectNotifier; | |
| 9433 if (!notifier) { | |
| 9434 notifier = this._objectNotifier = Object.getNotifier(this); | |
| 9435 } | |
| 9436 updateRecord.object = this; | |
| 9437 updateRecord.name = name; | |
| 9438 updateRecord.oldValue = oldValue; | |
| 9439 notifier.notify(updateRecord); | |
| 9440 }, | |
| 9441 | |
| 9442 _propertyChanged: function(name, value, oldValue) { | |
| 9443 if (this.reflect[name]) { | |
| 9444 this.reflectPropertyToAttribute(name); | |
| 9445 } | |
| 9446 }, | |
| 9447 | |
| 9448 // creates a property binding (called via bind) to a published property. | |
| 9449 bindProperty: function(property, observable, oneTime) { | |
| 9450 if (oneTime) { | |
| 9451 this[property] = observable; | |
| 9452 return; | |
| 9453 } | |
| 9454 var computed = this.element.prototype.computed; | |
| 9455 // Binding an "out-only" value to a computed property. Note that | |
| 9456 // since this observer isn't opened, it doesn't need to be closed on | |
| 9457 // cleanup. | |
| 9458 if (computed && computed[property]) { | |
| 9459 var privateComputedBoundValue = property + 'ComputedBoundObservable_'; | |
| 9460 this[privateComputedBoundValue] = observable; | |
| 9461 return; | |
| 9462 } | |
| 9463 return this.bindToAccessor(property, observable, resolveBindingValue); | |
| 9464 }, | |
| 9465 | |
| 9466 // NOTE property `name` must be published. This makes it an accessor. | |
| 9467 bindToAccessor: function(name, observable, resolveFn) { | |
| 9468 var privateName = name + '_'; | |
| 9469 var privateObservable = name + 'Observable_'; | |
| 9470 // Present for properties which are computed and published and have a | |
| 9471 // bound value. | |
| 9472 var privateComputedBoundValue = name + 'ComputedBoundObservable_'; | |
| 9473 this[privateObservable] = observable; | |
| 9474 var oldValue = this[privateName]; | |
| 9475 // observable callback | |
| 9476 var self = this; | |
| 9477 function updateValue(value, oldValue) { | |
| 9478 self[privateName] = value; | |
| 9479 var setObserveable = self[privateComputedBoundValue]; | |
| 9480 if (setObserveable && typeof setObserveable.setValue == 'function') { | |
| 9481 setObserveable.setValue(value); | |
| 9482 } | |
| 9483 self.emitPropertyChangeRecord(name, value, oldValue); | |
| 9484 } | |
| 9485 // resolve initial value | |
| 9486 var value = observable.open(updateValue); | |
| 9487 if (resolveFn && !areSameValue(oldValue, value)) { | |
| 9488 var resolvedValue = resolveFn(oldValue, value); | |
| 9489 if (!areSameValue(value, resolvedValue)) { | |
| 9490 value = resolvedValue; | |
| 9491 if (observable.setValue) { | |
| 9492 observable.setValue(value); | |
| 9493 } | |
| 9494 } | |
| 9495 } | |
| 9496 updateValue(value, oldValue); | |
| 9497 // register and return observable | |
| 9498 var observer = { | |
| 9499 close: function() { | |
| 9500 observable.close(); | |
| 9501 self[privateObservable] = undefined; | |
| 9502 self[privateComputedBoundValue] = undefined; | |
| 9503 } | |
| 9504 }; | |
| 9505 this.registerObserver(observer); | |
| 9506 return observer; | |
| 9507 }, | |
| 9508 | |
| 9509 createComputedProperties: function() { | |
| 9510 if (!this._computedNames) { | |
| 9511 return; | |
| 9512 } | |
| 9513 for (var i = 0; i < this._computedNames.length; i++) { | |
| 9514 var name = this._computedNames[i]; | |
| 9515 var expressionText = this.computed[name]; | |
| 9516 try { | |
| 9517 var expression = PolymerExpressions.getExpression(expressionText); | |
| 9518 var observable = expression.getBinding(this, this.element.syntax); | |
| 9519 this.bindToAccessor(name, observable); | |
| 9520 } catch (ex) { | |
| 9521 console.error('Failed to create computed property', ex); | |
| 9522 } | |
| 9523 } | |
| 9524 }, | |
| 9525 | |
| 9526 // property bookkeeping | |
| 9527 registerObserver: function(observer) { | |
| 9528 if (!this._observers) { | |
| 9529 this._observers = [observer]; | |
| 9530 return; | |
| 9531 } | |
| 9532 this._observers.push(observer); | |
| 9533 }, | |
| 9534 | |
| 9535 closeObservers: function() { | |
| 9536 if (!this._observers) { | |
| 9537 return; | |
| 9538 } | |
| 9539 // observer array items are arrays of observers. | |
| 9540 var observers = this._observers; | |
| 9541 for (var i = 0; i < observers.length; i++) { | |
| 9542 var observer = observers[i]; | |
| 9543 if (observer && typeof observer.close == 'function') { | |
| 9544 observer.close(); | |
| 9545 } | |
| 9546 } | |
| 9547 this._observers = []; | |
| 9548 }, | |
| 9549 | |
| 9550 // bookkeeping observers for memory management | |
| 9551 registerNamedObserver: function(name, observer) { | |
| 9552 var o$ = this._namedObservers || (this._namedObservers = {}); | |
| 9553 o$[name] = observer; | |
| 9554 }, | |
| 9555 | |
| 9556 closeNamedObserver: function(name) { | |
| 9557 var o$ = this._namedObservers; | |
| 9558 if (o$ && o$[name]) { | |
| 9559 o$[name].close(); | |
| 9560 o$[name] = null; | |
| 9561 return true; | |
| 9562 } | |
| 9563 }, | |
| 9564 | |
| 9565 closeNamedObservers: function() { | |
| 9566 if (this._namedObservers) { | |
| 9567 for (var i in this._namedObservers) { | |
| 9568 this.closeNamedObserver(i); | |
| 9569 } | |
| 9570 this._namedObservers = {}; | |
| 9571 } | |
| 9572 } | |
| 9573 | |
| 9574 }; | |
| 9575 | |
| 9576 // logging | |
| 9577 var LOG_OBSERVE = '[%s] watching [%s]'; | |
| 9578 var LOG_OBSERVED = '[%s#%s] watch: [%s] now [%s] was [%s]'; | |
| 9579 var LOG_CHANGED = '[%s#%s] propertyChanged: [%s] now [%s] was [%s]'; | |
| 9580 | |
| 9581 // exports | |
| 9582 | |
| 9583 scope.api.instance.properties = properties; | |
| 9584 | |
| 9585 })(Polymer); | |
| 9586 | |
| 9587 (function(scope) { | |
| 9588 | |
| 9589 /** | |
| 9590 * @class polymer-base | |
| 9591 */ | |
| 9592 | |
| 9593 // imports | |
| 9594 | |
| 9595 var log = window.WebComponents ? WebComponents.flags.log : {}; | |
| 9596 | |
| 9597 // element api supporting mdv | |
| 9598 var mdv = { | |
| 9599 | |
| 9600 /** | |
| 9601 * Creates dom cloned from the given template, instantiating bindings | |
| 9602 * with this element as the template model and `PolymerExpressions` as the | |
| 9603 * binding delegate. | |
| 9604 * | |
| 9605 * @method instanceTemplate | |
| 9606 * @param {Template} template source template from which to create dom. | |
| 9607 */ | |
| 9608 instanceTemplate: function(template) { | |
| 9609 // ensure template is decorated (lets' things like <tr template ...> work) | |
| 9610 HTMLTemplateElement.decorate(template); | |
| 9611 // ensure a default bindingDelegate | |
| 9612 var syntax = this.syntax || (!template.bindingDelegate && | |
| 9613 this.element.syntax); | |
| 9614 var dom = template.createInstance(this, syntax); | |
| 9615 var observers = dom.bindings_; | |
| 9616 for (var i = 0; i < observers.length; i++) { | |
| 9617 this.registerObserver(observers[i]); | |
| 9618 } | |
| 9619 return dom; | |
| 9620 }, | |
| 9621 | |
| 9622 // Called by TemplateBinding/NodeBind to setup a binding to the given | |
| 9623 // property. It's overridden here to support property bindings | |
| 9624 // in addition to attribute bindings that are supported by default. | |
| 9625 bind: function(name, observable, oneTime) { | |
| 9626 var property = this.propertyForAttribute(name); | |
| 9627 if (!property) { | |
| 9628 // TODO(sjmiles): this mixin method must use the special form | |
| 9629 // of `super` installed by `mixinMethod` in declaration/prototype.js | |
| 9630 return this.mixinSuper(arguments); | |
| 9631 } else { | |
| 9632 // use n-way Polymer binding | |
| 9633 var observer = this.bindProperty(property, observable, oneTime); | |
| 9634 // NOTE: reflecting binding information is typically required only for | |
| 9635 // tooling. It has a performance cost so it's opt-in in Node.bind. | |
| 9636 if (Platform.enableBindingsReflection && observer) { | |
| 9637 observer.path = observable.path_; | |
| 9638 this._recordBinding(property, observer); | |
| 9639 } | |
| 9640 if (this.reflect[property]) { | |
| 9641 this.reflectPropertyToAttribute(property); | |
| 9642 } | |
| 9643 return observer; | |
| 9644 } | |
| 9645 }, | |
| 9646 | |
| 9647 _recordBinding: function(name, observer) { | |
| 9648 this.bindings_ = this.bindings_ || {}; | |
| 9649 this.bindings_[name] = observer; | |
| 9650 }, | |
| 9651 | |
| 9652 // Called by TemplateBinding when all bindings on an element have been | |
| 9653 // executed. This signals that all element inputs have been gathered | |
| 9654 // and it's safe to ready the element, create shadow-root and start | |
| 9655 // data-observation. | |
| 9656 bindFinished: function() { | |
| 9657 this.makeElementReady(); | |
| 9658 }, | |
| 9659 | |
| 9660 // called at detached time to signal that an element's bindings should be | |
| 9661 // cleaned up. This is done asynchronously so that users have the chance | |
| 9662 // to call `cancelUnbindAll` to prevent unbinding. | |
| 9663 asyncUnbindAll: function() { | |
| 9664 if (!this._unbound) { | |
| 9665 log.unbind && console.log('[%s] asyncUnbindAll', this.localName); | |
| 9666 this._unbindAllJob = this.job(this._unbindAllJob, this.unbindAll, 0); | |
| 9667 } | |
| 9668 }, | |
| 9669 | |
| 9670 /** | |
| 9671 * This method should rarely be used and only if | |
| 9672 * <a href="#cancelUnbindAll">`cancelUnbindAll`</a> has been called to | |
| 9673 * prevent element unbinding. In this case, the element's bindings will | |
| 9674 * not be automatically cleaned up and it cannot be garbage collected | |
| 9675 * by the system. If memory pressure is a concern or a | |
| 9676 * large amount of elements need to be managed in this way, `unbindAll` | |
| 9677 * can be called to deactivate the element's bindings and allow its | |
| 9678 * memory to be reclaimed. | |
| 9679 * | |
| 9680 * @method unbindAll | |
| 9681 */ | |
| 9682 unbindAll: function() { | |
| 9683 if (!this._unbound) { | |
| 9684 this.closeObservers(); | |
| 9685 this.closeNamedObservers(); | |
| 9686 this._unbound = true; | |
| 9687 } | |
| 9688 }, | |
| 9689 | |
| 9690 /** | |
| 9691 * Call in `detached` to prevent the element from unbinding when it is | |
| 9692 * detached from the dom. The element is unbound as a cleanup step that | |
| 9693 * allows its memory to be reclaimed. | |
| 9694 * If `cancelUnbindAll` is used, consider calling | |
| 9695 * <a href="#unbindAll">`unbindAll`</a> when the element is no longer | |
| 9696 * needed. This will allow its memory to be reclaimed. | |
| 9697 * | |
| 9698 * @method cancelUnbindAll | |
| 9699 */ | |
| 9700 cancelUnbindAll: function() { | |
| 9701 if (this._unbound) { | |
| 9702 log.unbind && console.warn('[%s] already unbound, cannot cancel unbindAl
l', this.localName); | |
| 9703 return; | |
| 9704 } | |
| 9705 log.unbind && console.log('[%s] cancelUnbindAll', this.localName); | |
| 9706 if (this._unbindAllJob) { | |
| 9707 this._unbindAllJob = this._unbindAllJob.stop(); | |
| 9708 } | |
| 9709 } | |
| 9710 | |
| 9711 }; | |
| 9712 | |
| 9713 function unbindNodeTree(node) { | |
| 9714 forNodeTree(node, _nodeUnbindAll); | |
| 9715 } | |
| 9716 | |
| 9717 function _nodeUnbindAll(node) { | |
| 9718 node.unbindAll(); | |
| 9719 } | |
| 9720 | |
| 9721 function forNodeTree(node, callback) { | |
| 9722 if (node) { | |
| 9723 callback(node); | |
| 9724 for (var child = node.firstChild; child; child = child.nextSibling) { | |
| 9725 forNodeTree(child, callback); | |
| 9726 } | |
| 9727 } | |
| 9728 } | |
| 9729 | |
| 9730 var mustachePattern = /\{\{([^{}]*)}}/; | |
| 9731 | |
| 9732 // exports | |
| 9733 | |
| 9734 scope.bindPattern = mustachePattern; | |
| 9735 scope.api.instance.mdv = mdv; | |
| 9736 | |
| 9737 })(Polymer); | |
| 9738 | |
| 9739 (function(scope) { | |
| 9740 | |
| 9741 /** | |
| 9742 * Common prototype for all Polymer Elements. | |
| 9743 * | |
| 9744 * @class polymer-base | |
| 9745 * @homepage polymer.github.io | |
| 9746 */ | |
| 9747 var base = { | |
| 9748 /** | |
| 9749 * Tags this object as the canonical Base prototype. | |
| 9750 * | |
| 9751 * @property PolymerBase | |
| 9752 * @type boolean | |
| 9753 * @default true | |
| 9754 */ | |
| 9755 PolymerBase: true, | |
| 9756 | |
| 9757 /** | |
| 9758 * Debounce signals. | |
| 9759 * | |
| 9760 * Call `job` to defer a named signal, and all subsequent matching signals, | |
| 9761 * until a wait time has elapsed with no new signal. | |
| 9762 * | |
| 9763 * debouncedClickAction: function(e) { | |
| 9764 * // processClick only when it's been 100ms since the last click | |
| 9765 * this.job('click', function() { | |
| 9766 * this.processClick; | |
| 9767 * }, 100); | |
| 9768 * } | |
| 9769 * | |
| 9770 * @method job | |
| 9771 * @param String {String} job A string identifier for the job to debounce. | |
| 9772 * @param Function {Function} callback A function that is called (with `this
` context) when the wait time elapses. | |
| 9773 * @param Number {Number} wait Time in milliseconds (ms) after the last sign
al that must elapse before invoking `callback` | |
| 9774 * @type Handle | |
| 9775 */ | |
| 9776 job: function(job, callback, wait) { | |
| 9777 if (typeof job === 'string') { | |
| 9778 var n = '___' + job; | |
| 9779 this[n] = Polymer.job.call(this, this[n], callback, wait); | |
| 9780 } else { | |
| 9781 // TODO(sjmiles): suggest we deprecate this call signature | |
| 9782 return Polymer.job.call(this, job, callback, wait); | |
| 9783 } | |
| 9784 }, | |
| 9785 | |
| 9786 /** | |
| 9787 * Invoke a superclass method. | |
| 9788 * | |
| 9789 * Use `super()` to invoke the most recently overridden call to the | |
| 9790 * currently executing function. | |
| 9791 * | |
| 9792 * To pass arguments through, use the literal `arguments` as the parameter | |
| 9793 * to `super()`. | |
| 9794 * | |
| 9795 * nextPageAction: function(e) { | |
| 9796 * // invoke the superclass version of `nextPageAction` | |
| 9797 * this.super(arguments); | |
| 9798 * } | |
| 9799 * | |
| 9800 * To pass custom arguments, arrange them in an array. | |
| 9801 * | |
| 9802 * appendSerialNo: function(value, serial) { | |
| 9803 * // prefix the superclass serial number with our lot # before | |
| 9804 * // invoking the superlcass | |
| 9805 * return this.super([value, this.lotNo + serial]) | |
| 9806 * } | |
| 9807 * | |
| 9808 * @method super | |
| 9809 * @type Any | |
| 9810 * @param {args) An array of arguments to use when calling the superclass me
thod, or null. | |
| 9811 */ | |
| 9812 super: Polymer.super, | |
| 9813 | |
| 9814 /** | |
| 9815 * Lifecycle method called when the element is instantiated. | |
| 9816 * | |
| 9817 * Override `created` to perform custom create-time tasks. No need to call | |
| 9818 * super-class `created` unless you are extending another Polymer element. | |
| 9819 * Created is called before the element creates `shadowRoot` or prepares | |
| 9820 * data-observation. | |
| 9821 * | |
| 9822 * @method created | |
| 9823 * @type void | |
| 9824 */ | |
| 9825 created: function() { | |
| 9826 }, | |
| 9827 | |
| 9828 /** | |
| 9829 * Lifecycle method called when the element has populated it's `shadowRoot`, | |
| 9830 * prepared data-observation, and made itself ready for API interaction. | |
| 9831 * | |
| 9832 * @method ready | |
| 9833 * @type void | |
| 9834 */ | |
| 9835 ready: function() { | |
| 9836 }, | |
| 9837 | |
| 9838 /** | |
| 9839 * Low-level lifecycle method called as part of standard Custom Elements | |
| 9840 * operation. Polymer implements this method to provide basic default | |
| 9841 * functionality. For custom create-time tasks, implement `created` | |
| 9842 * instead, which is called immediately after `createdCallback`. | |
| 9843 * | |
| 9844 * @method createdCallback | |
| 9845 */ | |
| 9846 createdCallback: function() { | |
| 9847 if (this.templateInstance && this.templateInstance.model) { | |
| 9848 console.warn('Attributes on ' + this.localName + ' were data bound ' + | |
| 9849 'prior to Polymer upgrading the element. This may result in ' + | |
| 9850 'incorrect binding types.'); | |
| 9851 } | |
| 9852 this.created(); | |
| 9853 this.prepareElement(); | |
| 9854 if (!this.ownerDocument.isStagingDocument) { | |
| 9855 this.makeElementReady(); | |
| 9856 } | |
| 9857 }, | |
| 9858 | |
| 9859 // system entry point, do not override | |
| 9860 prepareElement: function() { | |
| 9861 if (this._elementPrepared) { | |
| 9862 console.warn('Element already prepared', this.localName); | |
| 9863 return; | |
| 9864 } | |
| 9865 this._elementPrepared = true; | |
| 9866 // storage for shadowRoots info | |
| 9867 this.shadowRoots = {}; | |
| 9868 // install property observers | |
| 9869 this.createPropertyObserver(); | |
| 9870 this.openPropertyObserver(); | |
| 9871 // install boilerplate attributes | |
| 9872 this.copyInstanceAttributes(); | |
| 9873 // process input attributes | |
| 9874 this.takeAttributes(); | |
| 9875 // add event listeners | |
| 9876 this.addHostListeners(); | |
| 9877 }, | |
| 9878 | |
| 9879 // system entry point, do not override | |
| 9880 makeElementReady: function() { | |
| 9881 if (this._readied) { | |
| 9882 return; | |
| 9883 } | |
| 9884 this._readied = true; | |
| 9885 this.createComputedProperties(); | |
| 9886 this.parseDeclarations(this.__proto__); | |
| 9887 // NOTE: Support use of the `unresolved` attribute to help polyfill | |
| 9888 // custom elements' `:unresolved` feature. | |
| 9889 this.removeAttribute('unresolved'); | |
| 9890 // user entry point | |
| 9891 this.ready(); | |
| 9892 }, | |
| 9893 | |
| 9894 /** | |
| 9895 * Low-level lifecycle method called as part of standard Custom Elements | |
| 9896 * operation. Polymer implements this method to provide basic default | |
| 9897 * functionality. For custom tasks in your element, implement `attributeChan
ged` | |
| 9898 * instead, which is called immediately after `attributeChangedCallback`. | |
| 9899 * | |
| 9900 * @method attributeChangedCallback | |
| 9901 */ | |
| 9902 attributeChangedCallback: function(name, oldValue) { | |
| 9903 // TODO(sjmiles): adhoc filter | |
| 9904 if (name !== 'class' && name !== 'style') { | |
| 9905 this.attributeToProperty(name, this.getAttribute(name)); | |
| 9906 } | |
| 9907 if (this.attributeChanged) { | |
| 9908 this.attributeChanged.apply(this, arguments); | |
| 9909 } | |
| 9910 }, | |
| 9911 | |
| 9912 /** | |
| 9913 * Low-level lifecycle method called as part of standard Custom Elements | |
| 9914 * operation. Polymer implements this method to provide basic default | |
| 9915 * functionality. For custom create-time tasks, implement `attached` | |
| 9916 * instead, which is called immediately after `attachedCallback`. | |
| 9917 * | |
| 9918 * @method attachedCallback | |
| 9919 */ | |
| 9920 attachedCallback: function() { | |
| 9921 // when the element is attached, prevent it from unbinding. | |
| 9922 this.cancelUnbindAll(); | |
| 9923 // invoke user action | |
| 9924 if (this.attached) { | |
| 9925 this.attached(); | |
| 9926 } | |
| 9927 if (!this.hasBeenAttached) { | |
| 9928 this.hasBeenAttached = true; | |
| 9929 if (this.domReady) { | |
| 9930 this.async('domReady'); | |
| 9931 } | |
| 9932 } | |
| 9933 }, | |
| 9934 | |
| 9935 /** | |
| 9936 * Implement to access custom elements in dom descendants, ancestors, | |
| 9937 * or siblings. Because custom elements upgrade in document order, | |
| 9938 * elements accessed in `ready` or `attached` may not be upgraded. When | |
| 9939 * `domReady` is called, all registered custom elements are guaranteed | |
| 9940 * to have been upgraded. | |
| 9941 * | |
| 9942 * @method domReady | |
| 9943 */ | |
| 9944 | |
| 9945 /** | |
| 9946 * Low-level lifecycle method called as part of standard Custom Elements | |
| 9947 * operation. Polymer implements this method to provide basic default | |
| 9948 * functionality. For custom create-time tasks, implement `detached` | |
| 9949 * instead, which is called immediately after `detachedCallback`. | |
| 9950 * | |
| 9951 * @method detachedCallback | |
| 9952 */ | |
| 9953 detachedCallback: function() { | |
| 9954 if (!this.preventDispose) { | |
| 9955 this.asyncUnbindAll(); | |
| 9956 } | |
| 9957 // invoke user action | |
| 9958 if (this.detached) { | |
| 9959 this.detached(); | |
| 9960 } | |
| 9961 // TODO(sorvell): bc | |
| 9962 if (this.leftView) { | |
| 9963 this.leftView(); | |
| 9964 } | |
| 9965 }, | |
| 9966 | |
| 9967 /** | |
| 9968 * Walks the prototype-chain of this element and allows specific | |
| 9969 * classes a chance to process static declarations. | |
| 9970 * | |
| 9971 * In particular, each polymer-element has it's own `template`. | |
| 9972 * `parseDeclarations` is used to accumulate all element `template`s | |
| 9973 * from an inheritance chain. | |
| 9974 * | |
| 9975 * `parseDeclaration` static methods implemented in the chain are called | |
| 9976 * recursively, oldest first, with the `<polymer-element>` associated | |
| 9977 * with the current prototype passed as an argument. | |
| 9978 * | |
| 9979 * An element may override this method to customize shadow-root generation. | |
| 9980 * | |
| 9981 * @method parseDeclarations | |
| 9982 */ | |
| 9983 parseDeclarations: function(p) { | |
| 9984 if (p && p.element) { | |
| 9985 this.parseDeclarations(p.__proto__); | |
| 9986 p.parseDeclaration.call(this, p.element); | |
| 9987 } | |
| 9988 }, | |
| 9989 | |
| 9990 /** | |
| 9991 * Perform init-time actions based on static information in the | |
| 9992 * `<polymer-element>` instance argument. | |
| 9993 * | |
| 9994 * For example, the standard implementation locates the template associated | |
| 9995 * with the given `<polymer-element>` and stamps it into a shadow-root to | |
| 9996 * implement shadow inheritance. | |
| 9997 * | |
| 9998 * An element may override this method for custom behavior. | |
| 9999 * | |
| 10000 * @method parseDeclaration | |
| 10001 */ | |
| 10002 parseDeclaration: function(elementElement) { | |
| 10003 var template = this.fetchTemplate(elementElement); | |
| 10004 if (template) { | |
| 10005 var root = this.shadowFromTemplate(template); | |
| 10006 this.shadowRoots[elementElement.name] = root; | |
| 10007 } | |
| 10008 }, | |
| 10009 | |
| 10010 /** | |
| 10011 * Given a `<polymer-element>`, find an associated template (if any) to be | |
| 10012 * used for shadow-root generation. | |
| 10013 * | |
| 10014 * An element may override this method for custom behavior. | |
| 10015 * | |
| 10016 * @method fetchTemplate | |
| 10017 */ | |
| 10018 fetchTemplate: function(elementElement) { | |
| 10019 return elementElement.querySelector('template'); | |
| 10020 }, | |
| 10021 | |
| 10022 /** | |
| 10023 * Create a shadow-root in this host and stamp `template` as it's | |
| 10024 * content. | |
| 10025 * | |
| 10026 * An element may override this method for custom behavior. | |
| 10027 * | |
| 10028 * @method shadowFromTemplate | |
| 10029 */ | |
| 10030 shadowFromTemplate: function(template) { | |
| 10031 if (template) { | |
| 10032 // make a shadow root | |
| 10033 var root = this.createShadowRoot(); | |
| 10034 // stamp template | |
| 10035 // which includes parsing and applying MDV bindings before being | |
| 10036 // inserted (to avoid {{}} in attribute values) | |
| 10037 // e.g. to prevent <img src="images/{{icon}}"> from generating a 404. | |
| 10038 var dom = this.instanceTemplate(template); | |
| 10039 // append to shadow dom | |
| 10040 root.appendChild(dom); | |
| 10041 // perform post-construction initialization tasks on shadow root | |
| 10042 this.shadowRootReady(root, template); | |
| 10043 // return the created shadow root | |
| 10044 return root; | |
| 10045 } | |
| 10046 }, | |
| 10047 | |
| 10048 // utility function that stamps a <template> into light-dom | |
| 10049 lightFromTemplate: function(template, refNode) { | |
| 10050 if (template) { | |
| 10051 // TODO(sorvell): mark this element as an eventController so that | |
| 10052 // event listeners on bound nodes inside it will be called on it. | |
| 10053 // Note, the expectation here is that events on all descendants | |
| 10054 // should be handled by this element. | |
| 10055 this.eventController = this; | |
| 10056 // stamp template | |
| 10057 // which includes parsing and applying MDV bindings before being | |
| 10058 // inserted (to avoid {{}} in attribute values) | |
| 10059 // e.g. to prevent <img src="images/{{icon}}"> from generating a 404. | |
| 10060 var dom = this.instanceTemplate(template); | |
| 10061 // append to shadow dom | |
| 10062 if (refNode) { | |
| 10063 this.insertBefore(dom, refNode); | |
| 10064 } else { | |
| 10065 this.appendChild(dom); | |
| 10066 } | |
| 10067 // perform post-construction initialization tasks on ahem, light root | |
| 10068 this.shadowRootReady(this); | |
| 10069 // return the created shadow root | |
| 10070 return dom; | |
| 10071 } | |
| 10072 }, | |
| 10073 | |
| 10074 shadowRootReady: function(root) { | |
| 10075 // locate nodes with id and store references to them in this.$ hash | |
| 10076 this.marshalNodeReferences(root); | |
| 10077 }, | |
| 10078 | |
| 10079 // locate nodes with id and store references to them in this.$ hash | |
| 10080 marshalNodeReferences: function(root) { | |
| 10081 // establish $ instance variable | |
| 10082 var $ = this.$ = this.$ || {}; | |
| 10083 // populate $ from nodes with ID from the LOCAL tree | |
| 10084 if (root) { | |
| 10085 var n$ = root.querySelectorAll("[id]"); | |
| 10086 for (var i=0, l=n$.length, n; (i<l) && (n=n$[i]); i++) { | |
| 10087 $[n.id] = n; | |
| 10088 }; | |
| 10089 } | |
| 10090 }, | |
| 10091 | |
| 10092 /** | |
| 10093 * Register a one-time callback when a child-list or sub-tree mutation | |
| 10094 * occurs on node. | |
| 10095 * | |
| 10096 * For persistent callbacks, call onMutation from your listener. | |
| 10097 * | |
| 10098 * @method onMutation | |
| 10099 * @param Node {Node} node Node to watch for mutations. | |
| 10100 * @param Function {Function} listener Function to call on mutation. The fun
ction is invoked as `listener.call(this, observer, mutations);` where `observer`
is the MutationObserver that triggered the notification, and `mutations` is the
native mutation list. | |
| 10101 */ | |
| 10102 onMutation: function(node, listener) { | |
| 10103 var observer = new MutationObserver(function(mutations) { | |
| 10104 listener.call(this, observer, mutations); | |
| 10105 observer.disconnect(); | |
| 10106 }.bind(this)); | |
| 10107 observer.observe(node, {childList: true, subtree: true}); | |
| 10108 } | |
| 10109 }; | |
| 10110 | |
| 10111 /** | |
| 10112 * @class Polymer | |
| 10113 */ | |
| 10114 | |
| 10115 /** | |
| 10116 * Returns true if the object includes <a href="#polymer-base">polymer-base</a
> in it's prototype chain. | |
| 10117 * | |
| 10118 * @method isBase | |
| 10119 * @param Object {Object} object Object to test. | |
| 10120 * @type Boolean | |
| 10121 */ | |
| 10122 function isBase(object) { | |
| 10123 return object.hasOwnProperty('PolymerBase') | |
| 10124 } | |
| 10125 | |
| 10126 // name a base constructor for dev tools | |
| 10127 | |
| 10128 /** | |
| 10129 * The Polymer base-class constructor. | |
| 10130 * | |
| 10131 * @property Base | |
| 10132 * @type Function | |
| 10133 */ | |
| 10134 function PolymerBase() {}; | |
| 10135 PolymerBase.prototype = base; | |
| 10136 base.constructor = PolymerBase; | |
| 10137 | |
| 10138 // exports | |
| 10139 | |
| 10140 scope.Base = PolymerBase; | |
| 10141 scope.isBase = isBase; | |
| 10142 scope.api.instance.base = base; | |
| 10143 | |
| 10144 })(Polymer); | |
| 10145 | |
| 10146 (function(scope) { | |
| 10147 | |
| 10148 // imports | |
| 10149 | |
| 10150 var log = window.WebComponents ? WebComponents.flags.log : {}; | |
| 10151 var hasShadowDOMPolyfill = window.ShadowDOMPolyfill; | |
| 10152 | |
| 10153 // magic words | |
| 10154 | |
| 10155 var STYLE_SCOPE_ATTRIBUTE = 'element'; | |
| 10156 var STYLE_CONTROLLER_SCOPE = 'controller'; | |
| 10157 | |
| 10158 var styles = { | |
| 10159 STYLE_SCOPE_ATTRIBUTE: STYLE_SCOPE_ATTRIBUTE, | |
| 10160 /** | |
| 10161 * Installs external stylesheets and <style> elements with the attribute | |
| 10162 * polymer-scope='controller' into the scope of element. This is intended | |
| 10163 * to be a called during custom element construction. | |
| 10164 */ | |
| 10165 installControllerStyles: function() { | |
| 10166 // apply controller styles, but only if they are not yet applied | |
| 10167 var scope = this.findStyleScope(); | |
| 10168 if (scope && !this.scopeHasNamedStyle(scope, this.localName)) { | |
| 10169 // allow inherited controller styles | |
| 10170 var proto = getPrototypeOf(this), cssText = ''; | |
| 10171 while (proto && proto.element) { | |
| 10172 cssText += proto.element.cssTextForScope(STYLE_CONTROLLER_SCOPE); | |
| 10173 proto = getPrototypeOf(proto); | |
| 10174 } | |
| 10175 if (cssText) { | |
| 10176 this.installScopeCssText(cssText, scope); | |
| 10177 } | |
| 10178 } | |
| 10179 }, | |
| 10180 installScopeStyle: function(style, name, scope) { | |
| 10181 var scope = scope || this.findStyleScope(), name = name || ''; | |
| 10182 if (scope && !this.scopeHasNamedStyle(scope, this.localName + name)) { | |
| 10183 var cssText = ''; | |
| 10184 if (style instanceof Array) { | |
| 10185 for (var i=0, l=style.length, s; (i<l) && (s=style[i]); i++) { | |
| 10186 cssText += s.textContent + '\n\n'; | |
| 10187 } | |
| 10188 } else { | |
| 10189 cssText = style.textContent; | |
| 10190 } | |
| 10191 this.installScopeCssText(cssText, scope, name); | |
| 10192 } | |
| 10193 }, | |
| 10194 installScopeCssText: function(cssText, scope, name) { | |
| 10195 scope = scope || this.findStyleScope(); | |
| 10196 name = name || ''; | |
| 10197 if (!scope) { | |
| 10198 return; | |
| 10199 } | |
| 10200 if (hasShadowDOMPolyfill) { | |
| 10201 cssText = shimCssText(cssText, scope.host); | |
| 10202 } | |
| 10203 var style = this.element.cssTextToScopeStyle(cssText, | |
| 10204 STYLE_CONTROLLER_SCOPE); | |
| 10205 Polymer.applyStyleToScope(style, scope); | |
| 10206 // cache that this style has been applied | |
| 10207 this.styleCacheForScope(scope)[this.localName + name] = true; | |
| 10208 }, | |
| 10209 findStyleScope: function(node) { | |
| 10210 // find the shadow root that contains this element | |
| 10211 var n = node || this; | |
| 10212 while (n.parentNode) { | |
| 10213 n = n.parentNode; | |
| 10214 } | |
| 10215 return n; | |
| 10216 }, | |
| 10217 scopeHasNamedStyle: function(scope, name) { | |
| 10218 var cache = this.styleCacheForScope(scope); | |
| 10219 return cache[name]; | |
| 10220 }, | |
| 10221 styleCacheForScope: function(scope) { | |
| 10222 if (hasShadowDOMPolyfill) { | |
| 10223 var scopeName = scope.host ? scope.host.localName : scope.localName; | |
| 10224 return polyfillScopeStyleCache[scopeName] || (polyfillScopeStyleCache[sc
opeName] = {}); | |
| 10225 } else { | |
| 10226 return scope._scopeStyles = (scope._scopeStyles || {}); | |
| 10227 } | |
| 10228 } | |
| 10229 }; | |
| 10230 | |
| 10231 var polyfillScopeStyleCache = {}; | |
| 10232 | |
| 10233 // NOTE: use raw prototype traversal so that we ensure correct traversal | |
| 10234 // on platforms where the protoype chain is simulated via __proto__ (IE10) | |
| 10235 function getPrototypeOf(prototype) { | |
| 10236 return prototype.__proto__; | |
| 10237 } | |
| 10238 | |
| 10239 function shimCssText(cssText, host) { | |
| 10240 var name = '', is = false; | |
| 10241 if (host) { | |
| 10242 name = host.localName; | |
| 10243 is = host.hasAttribute('is'); | |
| 10244 } | |
| 10245 var selector = WebComponents.ShadowCSS.makeScopeSelector(name, is); | |
| 10246 return WebComponents.ShadowCSS.shimCssText(cssText, selector); | |
| 10247 } | |
| 10248 | |
| 10249 // exports | |
| 10250 | |
| 10251 scope.api.instance.styles = styles; | |
| 10252 | |
| 10253 })(Polymer); | |
| 10254 | |
| 10255 (function(scope) { | |
| 10256 | |
| 10257 // imports | |
| 10258 | |
| 10259 var extend = scope.extend; | |
| 10260 var api = scope.api; | |
| 10261 | |
| 10262 // imperative implementation: Polymer() | |
| 10263 | |
| 10264 // specify an 'own' prototype for tag `name` | |
| 10265 function element(name, prototype) { | |
| 10266 if (typeof name !== 'string') { | |
| 10267 var script = prototype || document._currentScript; | |
| 10268 prototype = name; | |
| 10269 name = script && script.parentNode && script.parentNode.getAttribute ? | |
| 10270 script.parentNode.getAttribute('name') : ''; | |
| 10271 if (!name) { | |
| 10272 throw 'Element name could not be inferred.'; | |
| 10273 } | |
| 10274 } | |
| 10275 if (getRegisteredPrototype(name)) { | |
| 10276 throw 'Already registered (Polymer) prototype for element ' + name; | |
| 10277 } | |
| 10278 // cache the prototype | |
| 10279 registerPrototype(name, prototype); | |
| 10280 // notify the registrar waiting for 'name', if any | |
| 10281 notifyPrototype(name); | |
| 10282 } | |
| 10283 | |
| 10284 // async prototype source | |
| 10285 | |
| 10286 function waitingForPrototype(name, client) { | |
| 10287 waitPrototype[name] = client; | |
| 10288 } | |
| 10289 | |
| 10290 var waitPrototype = {}; | |
| 10291 | |
| 10292 function notifyPrototype(name) { | |
| 10293 if (waitPrototype[name]) { | |
| 10294 waitPrototype[name].registerWhenReady(); | |
| 10295 delete waitPrototype[name]; | |
| 10296 } | |
| 10297 } | |
| 10298 | |
| 10299 // utility and bookkeeping | |
| 10300 | |
| 10301 // maps tag names to prototypes, as registered with | |
| 10302 // Polymer. Prototypes associated with a tag name | |
| 10303 // using document.registerElement are available from | |
| 10304 // HTMLElement.getPrototypeForTag(). | |
| 10305 // If an element was fully registered by Polymer, then | |
| 10306 // Polymer.getRegisteredPrototype(name) === | |
| 10307 // HTMLElement.getPrototypeForTag(name) | |
| 10308 | |
| 10309 var prototypesByName = {}; | |
| 10310 | |
| 10311 function registerPrototype(name, prototype) { | |
| 10312 return prototypesByName[name] = prototype || {}; | |
| 10313 } | |
| 10314 | |
| 10315 function getRegisteredPrototype(name) { | |
| 10316 return prototypesByName[name]; | |
| 10317 } | |
| 10318 | |
| 10319 function instanceOfType(element, type) { | |
| 10320 if (typeof type !== 'string') { | |
| 10321 return false; | |
| 10322 } | |
| 10323 var proto = HTMLElement.getPrototypeForTag(type); | |
| 10324 var ctor = proto && proto.constructor; | |
| 10325 if (!ctor) { | |
| 10326 return false; | |
| 10327 } | |
| 10328 if (CustomElements.instanceof) { | |
| 10329 return CustomElements.instanceof(element, ctor); | |
| 10330 } | |
| 10331 return element instanceof ctor; | |
| 10332 } | |
| 10333 | |
| 10334 // exports | |
| 10335 | |
| 10336 scope.getRegisteredPrototype = getRegisteredPrototype; | |
| 10337 scope.waitingForPrototype = waitingForPrototype; | |
| 10338 scope.instanceOfType = instanceOfType; | |
| 10339 | |
| 10340 // namespace shenanigans so we can expose our scope on the registration | |
| 10341 // function | |
| 10342 | |
| 10343 // make window.Polymer reference `element()` | |
| 10344 | |
| 10345 window.Polymer = element; | |
| 10346 | |
| 10347 // TODO(sjmiles): find a way to do this that is less terrible | |
| 10348 // copy window.Polymer properties onto `element()` | |
| 10349 | |
| 10350 extend(Polymer, scope); | |
| 10351 | |
| 10352 // Under the HTMLImports polyfill, scripts in the main document | |
| 10353 // do not block on imports; we want to allow calls to Polymer in the main | |
| 10354 // document. WebComponents collects those calls until we can process them, whi
ch | |
| 10355 // we do here. | |
| 10356 | |
| 10357 if (WebComponents.consumeDeclarations) { | |
| 10358 WebComponents.consumeDeclarations(function(declarations) { | |
| 10359 if (declarations) { | |
| 10360 for (var i=0, l=declarations.length, d; (i<l) && (d=declarations[i]); i+
+) { | |
| 10361 element.apply(null, d); | |
| 10362 } | |
| 10363 } | |
| 10364 }); | |
| 10365 } | |
| 10366 | |
| 10367 })(Polymer); | |
| 10368 | |
| 10369 (function(scope) { | |
| 10370 | |
| 10371 /** | |
| 10372 * @class polymer-base | |
| 10373 */ | |
| 10374 | |
| 10375 /** | |
| 10376 * Resolve a url path to be relative to a `base` url. If unspecified, `base` | |
| 10377 * defaults to the element's ownerDocument url. Can be used to resolve | |
| 10378 * paths from element's in templates loaded in HTMLImports to be relative | |
| 10379 * to the document containing the element. Polymer automatically does this for | |
| 10380 * url attributes in element templates; however, if a url, for | |
| 10381 * example, contains a binding, then `resolvePath` can be used to ensure it is | |
| 10382 * relative to the element document. For example, in an element's template, | |
| 10383 * | |
| 10384 * <a href="{{resolvePath(path)}}">Resolved</a> | |
| 10385 * | |
| 10386 * @method resolvePath | |
| 10387 * @param {String} url Url path to resolve. | |
| 10388 * @param {String} base Optional base url against which to resolve, defaults | |
| 10389 * to the element's ownerDocument url. | |
| 10390 * returns {String} resolved url. | |
| 10391 */ | |
| 10392 | |
| 10393 var path = { | |
| 10394 resolveElementPaths: function(node) { | |
| 10395 Polymer.urlResolver.resolveDom(node); | |
| 10396 }, | |
| 10397 addResolvePathApi: function() { | |
| 10398 // let assetpath attribute modify the resolve path | |
| 10399 var assetPath = this.getAttribute('assetpath') || ''; | |
| 10400 var root = new URL(assetPath, this.ownerDocument.baseURI); | |
| 10401 this.prototype.resolvePath = function(urlPath, base) { | |
| 10402 var u = new URL(urlPath, base || root); | |
| 10403 return u.href; | |
| 10404 }; | |
| 10405 } | |
| 10406 }; | |
| 10407 | |
| 10408 // exports | |
| 10409 scope.api.declaration.path = path; | |
| 10410 | |
| 10411 })(Polymer); | |
| 10412 | |
| 10413 (function(scope) { | |
| 10414 | |
| 10415 // imports | |
| 10416 | |
| 10417 var log = window.WebComponents ? WebComponents.flags.log : {}; | |
| 10418 var api = scope.api.instance.styles; | |
| 10419 var STYLE_SCOPE_ATTRIBUTE = api.STYLE_SCOPE_ATTRIBUTE; | |
| 10420 | |
| 10421 var hasShadowDOMPolyfill = window.ShadowDOMPolyfill; | |
| 10422 | |
| 10423 // magic words | |
| 10424 | |
| 10425 var STYLE_SELECTOR = 'style'; | |
| 10426 var STYLE_LOADABLE_MATCH = '@import'; | |
| 10427 var SHEET_SELECTOR = 'link[rel=stylesheet]'; | |
| 10428 var STYLE_GLOBAL_SCOPE = 'global'; | |
| 10429 var SCOPE_ATTR = 'polymer-scope'; | |
| 10430 | |
| 10431 var styles = { | |
| 10432 // returns true if resources are loading | |
| 10433 loadStyles: function(callback) { | |
| 10434 var template = this.fetchTemplate(); | |
| 10435 var content = template && this.templateContent(); | |
| 10436 if (content) { | |
| 10437 this.convertSheetsToStyles(content); | |
| 10438 var styles = this.findLoadableStyles(content); | |
| 10439 if (styles.length) { | |
| 10440 var templateUrl = template.ownerDocument.baseURI; | |
| 10441 return Polymer.styleResolver.loadStyles(styles, templateUrl, callback)
; | |
| 10442 } | |
| 10443 } | |
| 10444 if (callback) { | |
| 10445 callback(); | |
| 10446 } | |
| 10447 }, | |
| 10448 convertSheetsToStyles: function(root) { | |
| 10449 var s$ = root.querySelectorAll(SHEET_SELECTOR); | |
| 10450 for (var i=0, l=s$.length, s, c; (i<l) && (s=s$[i]); i++) { | |
| 10451 c = createStyleElement(importRuleForSheet(s, this.ownerDocument.baseURI)
, | |
| 10452 this.ownerDocument); | |
| 10453 this.copySheetAttributes(c, s); | |
| 10454 s.parentNode.replaceChild(c, s); | |
| 10455 } | |
| 10456 }, | |
| 10457 copySheetAttributes: function(style, link) { | |
| 10458 for (var i=0, a$=link.attributes, l=a$.length, a; (a=a$[i]) && i<l; i++) { | |
| 10459 if (a.name !== 'rel' && a.name !== 'href') { | |
| 10460 style.setAttribute(a.name, a.value); | |
| 10461 } | |
| 10462 } | |
| 10463 }, | |
| 10464 findLoadableStyles: function(root) { | |
| 10465 var loadables = []; | |
| 10466 if (root) { | |
| 10467 var s$ = root.querySelectorAll(STYLE_SELECTOR); | |
| 10468 for (var i=0, l=s$.length, s; (i<l) && (s=s$[i]); i++) { | |
| 10469 if (s.textContent.match(STYLE_LOADABLE_MATCH)) { | |
| 10470 loadables.push(s); | |
| 10471 } | |
| 10472 } | |
| 10473 } | |
| 10474 return loadables; | |
| 10475 }, | |
| 10476 /** | |
| 10477 * Install external stylesheets loaded in <polymer-element> elements into th
e | |
| 10478 * element's template. | |
| 10479 * @param elementElement The <element> element to style. | |
| 10480 */ | |
| 10481 installSheets: function() { | |
| 10482 this.cacheSheets(); | |
| 10483 this.cacheStyles(); | |
| 10484 this.installLocalSheets(); | |
| 10485 this.installGlobalStyles(); | |
| 10486 }, | |
| 10487 /** | |
| 10488 * Remove all sheets from element and store for later use. | |
| 10489 */ | |
| 10490 cacheSheets: function() { | |
| 10491 this.sheets = this.findNodes(SHEET_SELECTOR); | |
| 10492 this.sheets.forEach(function(s) { | |
| 10493 if (s.parentNode) { | |
| 10494 s.parentNode.removeChild(s); | |
| 10495 } | |
| 10496 }); | |
| 10497 }, | |
| 10498 cacheStyles: function() { | |
| 10499 this.styles = this.findNodes(STYLE_SELECTOR + '[' + SCOPE_ATTR + ']'); | |
| 10500 this.styles.forEach(function(s) { | |
| 10501 if (s.parentNode) { | |
| 10502 s.parentNode.removeChild(s); | |
| 10503 } | |
| 10504 }); | |
| 10505 }, | |
| 10506 /** | |
| 10507 * Takes external stylesheets loaded in an <element> element and moves | |
| 10508 * their content into a <style> element inside the <element>'s template. | |
| 10509 * The sheet is then removed from the <element>. This is done only so | |
| 10510 * that if the element is loaded in the main document, the sheet does | |
| 10511 * not become active. | |
| 10512 * Note, ignores sheets with the attribute 'polymer-scope'. | |
| 10513 * @param elementElement The <element> element to style. | |
| 10514 */ | |
| 10515 installLocalSheets: function () { | |
| 10516 var sheets = this.sheets.filter(function(s) { | |
| 10517 return !s.hasAttribute(SCOPE_ATTR); | |
| 10518 }); | |
| 10519 var content = this.templateContent(); | |
| 10520 if (content) { | |
| 10521 var cssText = ''; | |
| 10522 sheets.forEach(function(sheet) { | |
| 10523 cssText += cssTextFromSheet(sheet) + '\n'; | |
| 10524 }); | |
| 10525 if (cssText) { | |
| 10526 var style = createStyleElement(cssText, this.ownerDocument); | |
| 10527 content.insertBefore(style, content.firstChild); | |
| 10528 } | |
| 10529 } | |
| 10530 }, | |
| 10531 findNodes: function(selector, matcher) { | |
| 10532 var nodes = this.querySelectorAll(selector).array(); | |
| 10533 var content = this.templateContent(); | |
| 10534 if (content) { | |
| 10535 var templateNodes = content.querySelectorAll(selector).array(); | |
| 10536 nodes = nodes.concat(templateNodes); | |
| 10537 } | |
| 10538 return matcher ? nodes.filter(matcher) : nodes; | |
| 10539 }, | |
| 10540 /** | |
| 10541 * Promotes external stylesheets and <style> elements with the attribute | |
| 10542 * polymer-scope='global' into global scope. | |
| 10543 * This is particularly useful for defining @keyframe rules which | |
| 10544 * currently do not function in scoped or shadow style elements. | |
| 10545 * (See wkb.ug/72462) | |
| 10546 * @param elementElement The <element> element to style. | |
| 10547 */ | |
| 10548 // TODO(sorvell): remove when wkb.ug/72462 is addressed. | |
| 10549 installGlobalStyles: function() { | |
| 10550 var style = this.styleForScope(STYLE_GLOBAL_SCOPE); | |
| 10551 applyStyleToScope(style, document.head); | |
| 10552 }, | |
| 10553 cssTextForScope: function(scopeDescriptor) { | |
| 10554 var cssText = ''; | |
| 10555 // handle stylesheets | |
| 10556 var selector = '[' + SCOPE_ATTR + '=' + scopeDescriptor + ']'; | |
| 10557 var matcher = function(s) { | |
| 10558 return matchesSelector(s, selector); | |
| 10559 }; | |
| 10560 var sheets = this.sheets.filter(matcher); | |
| 10561 sheets.forEach(function(sheet) { | |
| 10562 cssText += cssTextFromSheet(sheet) + '\n\n'; | |
| 10563 }); | |
| 10564 // handle cached style elements | |
| 10565 var styles = this.styles.filter(matcher); | |
| 10566 styles.forEach(function(style) { | |
| 10567 cssText += style.textContent + '\n\n'; | |
| 10568 }); | |
| 10569 return cssText; | |
| 10570 }, | |
| 10571 styleForScope: function(scopeDescriptor) { | |
| 10572 var cssText = this.cssTextForScope(scopeDescriptor); | |
| 10573 return this.cssTextToScopeStyle(cssText, scopeDescriptor); | |
| 10574 }, | |
| 10575 cssTextToScopeStyle: function(cssText, scopeDescriptor) { | |
| 10576 if (cssText) { | |
| 10577 var style = createStyleElement(cssText); | |
| 10578 style.setAttribute(STYLE_SCOPE_ATTRIBUTE, this.getAttribute('name') + | |
| 10579 '-' + scopeDescriptor); | |
| 10580 return style; | |
| 10581 } | |
| 10582 } | |
| 10583 }; | |
| 10584 | |
| 10585 function importRuleForSheet(sheet, baseUrl) { | |
| 10586 var href = new URL(sheet.getAttribute('href'), baseUrl).href; | |
| 10587 return '@import \'' + href + '\';'; | |
| 10588 } | |
| 10589 | |
| 10590 function applyStyleToScope(style, scope) { | |
| 10591 if (style) { | |
| 10592 if (scope === document) { | |
| 10593 scope = document.head; | |
| 10594 } | |
| 10595 if (hasShadowDOMPolyfill) { | |
| 10596 scope = document.head; | |
| 10597 } | |
| 10598 // TODO(sorvell): necessary for IE | |
| 10599 // see https://connect.microsoft.com/IE/feedback/details/790212/ | |
| 10600 // cloning-a-style-element-and-adding-to-document-produces | |
| 10601 // -unexpected-result#details | |
| 10602 // var clone = style.cloneNode(true); | |
| 10603 var clone = createStyleElement(style.textContent); | |
| 10604 var attr = style.getAttribute(STYLE_SCOPE_ATTRIBUTE); | |
| 10605 if (attr) { | |
| 10606 clone.setAttribute(STYLE_SCOPE_ATTRIBUTE, attr); | |
| 10607 } | |
| 10608 // TODO(sorvell): probably too brittle; try to figure out | |
| 10609 // where to put the element. | |
| 10610 var refNode = scope.firstElementChild; | |
| 10611 if (scope === document.head) { | |
| 10612 var selector = 'style[' + STYLE_SCOPE_ATTRIBUTE + ']'; | |
| 10613 var s$ = document.head.querySelectorAll(selector); | |
| 10614 if (s$.length) { | |
| 10615 refNode = s$[s$.length-1].nextElementSibling; | |
| 10616 } | |
| 10617 } | |
| 10618 scope.insertBefore(clone, refNode); | |
| 10619 } | |
| 10620 } | |
| 10621 | |
| 10622 function createStyleElement(cssText, scope) { | |
| 10623 scope = scope || document; | |
| 10624 scope = scope.createElement ? scope : scope.ownerDocument; | |
| 10625 var style = scope.createElement('style'); | |
| 10626 style.textContent = cssText; | |
| 10627 return style; | |
| 10628 } | |
| 10629 | |
| 10630 function cssTextFromSheet(sheet) { | |
| 10631 return (sheet && sheet.__resource) || ''; | |
| 10632 } | |
| 10633 | |
| 10634 function matchesSelector(node, inSelector) { | |
| 10635 if (matches) { | |
| 10636 return matches.call(node, inSelector); | |
| 10637 } | |
| 10638 } | |
| 10639 var p = HTMLElement.prototype; | |
| 10640 var matches = p.matches || p.matchesSelector || p.webkitMatchesSelector | |
| 10641 || p.mozMatchesSelector; | |
| 10642 | |
| 10643 // exports | |
| 10644 | |
| 10645 scope.api.declaration.styles = styles; | |
| 10646 scope.applyStyleToScope = applyStyleToScope; | |
| 10647 | |
| 10648 })(Polymer); | |
| 10649 | |
| 10650 (function(scope) { | |
| 10651 | |
| 10652 // imports | |
| 10653 | |
| 10654 var log = window.WebComponents ? WebComponents.flags.log : {}; | |
| 10655 var api = scope.api.instance.events; | |
| 10656 var EVENT_PREFIX = api.EVENT_PREFIX; | |
| 10657 | |
| 10658 var mixedCaseEventTypes = {}; | |
| 10659 [ | |
| 10660 'webkitAnimationStart', | |
| 10661 'webkitAnimationEnd', | |
| 10662 'webkitTransitionEnd', | |
| 10663 'DOMFocusOut', | |
| 10664 'DOMFocusIn', | |
| 10665 'DOMMouseScroll' | |
| 10666 ].forEach(function(e) { | |
| 10667 mixedCaseEventTypes[e.toLowerCase()] = e; | |
| 10668 }); | |
| 10669 | |
| 10670 // polymer-element declarative api: events feature | |
| 10671 var events = { | |
| 10672 parseHostEvents: function() { | |
| 10673 // our delegates map | |
| 10674 var delegates = this.prototype.eventDelegates; | |
| 10675 // extract data from attributes into delegates | |
| 10676 this.addAttributeDelegates(delegates); | |
| 10677 }, | |
| 10678 addAttributeDelegates: function(delegates) { | |
| 10679 // for each attribute | |
| 10680 for (var i=0, a; a=this.attributes[i]; i++) { | |
| 10681 // does it have magic marker identifying it as an event delegate? | |
| 10682 if (this.hasEventPrefix(a.name)) { | |
| 10683 // if so, add the info to delegates | |
| 10684 delegates[this.removeEventPrefix(a.name)] = a.value.replace('{{', '') | |
| 10685 .replace('}}', '').trim(); | |
| 10686 } | |
| 10687 } | |
| 10688 }, | |
| 10689 // starts with 'on-' | |
| 10690 hasEventPrefix: function (n) { | |
| 10691 return n && (n[0] === 'o') && (n[1] === 'n') && (n[2] === '-'); | |
| 10692 }, | |
| 10693 removeEventPrefix: function(n) { | |
| 10694 return n.slice(prefixLength); | |
| 10695 }, | |
| 10696 findController: function(node) { | |
| 10697 while (node.parentNode) { | |
| 10698 if (node.eventController) { | |
| 10699 return node.eventController; | |
| 10700 } | |
| 10701 node = node.parentNode; | |
| 10702 } | |
| 10703 return node.host; | |
| 10704 }, | |
| 10705 getEventHandler: function(controller, target, method) { | |
| 10706 var events = this; | |
| 10707 return function(e) { | |
| 10708 if (!controller || !controller.PolymerBase) { | |
| 10709 controller = events.findController(target); | |
| 10710 } | |
| 10711 | |
| 10712 var args = [e, e.detail, e.currentTarget]; | |
| 10713 controller.dispatchMethod(controller, method, args); | |
| 10714 }; | |
| 10715 }, | |
| 10716 prepareEventBinding: function(pathString, name, node) { | |
| 10717 if (!this.hasEventPrefix(name)) | |
| 10718 return; | |
| 10719 | |
| 10720 var eventType = this.removeEventPrefix(name); | |
| 10721 eventType = mixedCaseEventTypes[eventType] || eventType; | |
| 10722 | |
| 10723 var events = this; | |
| 10724 | |
| 10725 return function(model, node, oneTime) { | |
| 10726 var handler = events.getEventHandler(undefined, node, pathString); | |
| 10727 PolymerGestures.addEventListener(node, eventType, handler); | |
| 10728 | |
| 10729 if (oneTime) | |
| 10730 return; | |
| 10731 | |
| 10732 // TODO(rafaelw): This is really pointless work. Aside from the cost | |
| 10733 // of these allocations, NodeBind is going to setAttribute back to its | |
| 10734 // current value. Fixing this would mean changing the TemplateBinding | |
| 10735 // binding delegate API. | |
| 10736 function bindingValue() { | |
| 10737 return '{{ ' + pathString + ' }}'; | |
| 10738 } | |
| 10739 | |
| 10740 return { | |
| 10741 open: bindingValue, | |
| 10742 discardChanges: bindingValue, | |
| 10743 close: function() { | |
| 10744 PolymerGestures.removeEventListener(node, eventType, handler); | |
| 10745 } | |
| 10746 }; | |
| 10747 }; | |
| 10748 } | |
| 10749 }; | |
| 10750 | |
| 10751 var prefixLength = EVENT_PREFIX.length; | |
| 10752 | |
| 10753 // exports | |
| 10754 scope.api.declaration.events = events; | |
| 10755 | |
| 10756 })(Polymer); | |
| 10757 | |
| 10758 (function(scope) { | |
| 10759 | |
| 10760 // element api | |
| 10761 | |
| 10762 var observationBlacklist = ['attribute']; | |
| 10763 | |
| 10764 var properties = { | |
| 10765 inferObservers: function(prototype) { | |
| 10766 // called before prototype.observe is chained to inherited object | |
| 10767 var observe = prototype.observe, property; | |
| 10768 for (var n in prototype) { | |
| 10769 if (n.slice(-7) === 'Changed') { | |
| 10770 property = n.slice(0, -7); | |
| 10771 if (this.canObserveProperty(property)) { | |
| 10772 if (!observe) { | |
| 10773 observe = (prototype.observe = {}); | |
| 10774 } | |
| 10775 observe[property] = observe[property] || n; | |
| 10776 } | |
| 10777 } | |
| 10778 } | |
| 10779 }, | |
| 10780 canObserveProperty: function(property) { | |
| 10781 return (observationBlacklist.indexOf(property) < 0); | |
| 10782 }, | |
| 10783 explodeObservers: function(prototype) { | |
| 10784 // called before prototype.observe is chained to inherited object | |
| 10785 var o = prototype.observe; | |
| 10786 if (o) { | |
| 10787 var exploded = {}; | |
| 10788 for (var n in o) { | |
| 10789 var names = n.split(' '); | |
| 10790 for (var i=0, ni; ni=names[i]; i++) { | |
| 10791 exploded[ni] = o[n]; | |
| 10792 } | |
| 10793 } | |
| 10794 prototype.observe = exploded; | |
| 10795 } | |
| 10796 }, | |
| 10797 optimizePropertyMaps: function(prototype) { | |
| 10798 if (prototype.observe) { | |
| 10799 // construct name list | |
| 10800 var a = prototype._observeNames = []; | |
| 10801 for (var n in prototype.observe) { | |
| 10802 var names = n.split(' '); | |
| 10803 for (var i=0, ni; ni=names[i]; i++) { | |
| 10804 a.push(ni); | |
| 10805 } | |
| 10806 } | |
| 10807 } | |
| 10808 if (prototype.publish) { | |
| 10809 // construct name list | |
| 10810 var a = prototype._publishNames = []; | |
| 10811 for (var n in prototype.publish) { | |
| 10812 a.push(n); | |
| 10813 } | |
| 10814 } | |
| 10815 if (prototype.computed) { | |
| 10816 // construct name list | |
| 10817 var a = prototype._computedNames = []; | |
| 10818 for (var n in prototype.computed) { | |
| 10819 a.push(n); | |
| 10820 } | |
| 10821 } | |
| 10822 }, | |
| 10823 publishProperties: function(prototype, base) { | |
| 10824 // if we have any properties to publish | |
| 10825 var publish = prototype.publish; | |
| 10826 if (publish) { | |
| 10827 // transcribe `publish` entries onto own prototype | |
| 10828 this.requireProperties(publish, prototype, base); | |
| 10829 // warn and remove accessor names that are broken on some browsers | |
| 10830 this.filterInvalidAccessorNames(publish); | |
| 10831 // construct map of lower-cased property names | |
| 10832 prototype._publishLC = this.lowerCaseMap(publish); | |
| 10833 } | |
| 10834 var computed = prototype.computed; | |
| 10835 if (computed) { | |
| 10836 // warn and remove accessor names that are broken on some browsers | |
| 10837 this.filterInvalidAccessorNames(computed); | |
| 10838 } | |
| 10839 }, | |
| 10840 // Publishing/computing a property where the name might conflict with a | |
| 10841 // browser property is not currently supported to help users of Polymer | |
| 10842 // avoid browser bugs: | |
| 10843 // | |
| 10844 // https://code.google.com/p/chromium/issues/detail?id=43394 | |
| 10845 // https://bugs.webkit.org/show_bug.cgi?id=49739 | |
| 10846 // | |
| 10847 // We can lift this restriction when those bugs are fixed. | |
| 10848 filterInvalidAccessorNames: function(propertyNames) { | |
| 10849 for (var name in propertyNames) { | |
| 10850 // Check if the name is in our blacklist. | |
| 10851 if (this.propertyNameBlacklist[name]) { | |
| 10852 console.warn('Cannot define property "' + name + '" for element "' + | |
| 10853 this.name + '" because it has the same name as an HTMLElement ' + | |
| 10854 'property, and not all browsers support overriding that. ' + | |
| 10855 'Consider giving it a different name.'); | |
| 10856 // Remove the invalid accessor from the list. | |
| 10857 delete propertyNames[name]; | |
| 10858 } | |
| 10859 } | |
| 10860 }, | |
| 10861 // | |
| 10862 // `name: value` entries in the `publish` object may need to generate | |
| 10863 // matching properties on the prototype. | |
| 10864 // | |
| 10865 // Values that are objects may have a `reflect` property, which | |
| 10866 // signals that the value describes property control metadata. | |
| 10867 // In metadata objects, the prototype default value (if any) | |
| 10868 // is encoded in the `value` property. | |
| 10869 // | |
| 10870 // publish: { | |
| 10871 // foo: 5, | |
| 10872 // bar: {value: true, reflect: true}, | |
| 10873 // zot: {} | |
| 10874 // } | |
| 10875 // | |
| 10876 // `reflect` metadata property controls whether changes to the property | |
| 10877 // are reflected back to the attribute (default false). | |
| 10878 // | |
| 10879 // A value is stored on the prototype unless it's === `undefined`, | |
| 10880 // in which case the base chain is checked for a value. | |
| 10881 // If the basal value is also undefined, `null` is stored on the prototype. | |
| 10882 // | |
| 10883 // The reflection data is stored on another prototype object, `reflect` | |
| 10884 // which also can be specified directly. | |
| 10885 // | |
| 10886 // reflect: { | |
| 10887 // foo: true | |
| 10888 // } | |
| 10889 // | |
| 10890 requireProperties: function(propertyInfos, prototype, base) { | |
| 10891 // per-prototype storage for reflected properties | |
| 10892 prototype.reflect = prototype.reflect || {}; | |
| 10893 // ensure a prototype value for each property | |
| 10894 // and update the property's reflect to attribute status | |
| 10895 for (var n in propertyInfos) { | |
| 10896 var value = propertyInfos[n]; | |
| 10897 // value has metadata if it has a `reflect` property | |
| 10898 if (value && value.reflect !== undefined) { | |
| 10899 prototype.reflect[n] = Boolean(value.reflect); | |
| 10900 value = value.value; | |
| 10901 } | |
| 10902 // only set a value if one is specified | |
| 10903 if (value !== undefined) { | |
| 10904 prototype[n] = value; | |
| 10905 } | |
| 10906 } | |
| 10907 }, | |
| 10908 lowerCaseMap: function(properties) { | |
| 10909 var map = {}; | |
| 10910 for (var n in properties) { | |
| 10911 map[n.toLowerCase()] = n; | |
| 10912 } | |
| 10913 return map; | |
| 10914 }, | |
| 10915 createPropertyAccessor: function(name, ignoreWrites) { | |
| 10916 var proto = this.prototype; | |
| 10917 | |
| 10918 var privateName = name + '_'; | |
| 10919 var privateObservable = name + 'Observable_'; | |
| 10920 proto[privateName] = proto[name]; | |
| 10921 | |
| 10922 Object.defineProperty(proto, name, { | |
| 10923 get: function() { | |
| 10924 var observable = this[privateObservable]; | |
| 10925 if (observable) | |
| 10926 observable.deliver(); | |
| 10927 | |
| 10928 return this[privateName]; | |
| 10929 }, | |
| 10930 set: function(value) { | |
| 10931 if (ignoreWrites) { | |
| 10932 return this[privateName]; | |
| 10933 } | |
| 10934 | |
| 10935 var observable = this[privateObservable]; | |
| 10936 if (observable) { | |
| 10937 observable.setValue(value); | |
| 10938 return; | |
| 10939 } | |
| 10940 | |
| 10941 var oldValue = this[privateName]; | |
| 10942 this[privateName] = value; | |
| 10943 this.emitPropertyChangeRecord(name, value, oldValue); | |
| 10944 | |
| 10945 return value; | |
| 10946 }, | |
| 10947 configurable: true | |
| 10948 }); | |
| 10949 }, | |
| 10950 createPropertyAccessors: function(prototype) { | |
| 10951 var n$ = prototype._computedNames; | |
| 10952 if (n$ && n$.length) { | |
| 10953 for (var i=0, l=n$.length, n, fn; (i<l) && (n=n$[i]); i++) { | |
| 10954 this.createPropertyAccessor(n, true); | |
| 10955 } | |
| 10956 } | |
| 10957 var n$ = prototype._publishNames; | |
| 10958 if (n$ && n$.length) { | |
| 10959 for (var i=0, l=n$.length, n, fn; (i<l) && (n=n$[i]); i++) { | |
| 10960 // If the property is computed and published, the accessor is created | |
| 10961 // above. | |
| 10962 if (!prototype.computed || !prototype.computed[n]) { | |
| 10963 this.createPropertyAccessor(n); | |
| 10964 } | |
| 10965 } | |
| 10966 } | |
| 10967 }, | |
| 10968 // This list contains some property names that people commonly want to use, | |
| 10969 // but won't work because of Chrome/Safari bugs. It isn't an exhaustive | |
| 10970 // list. In particular it doesn't contain any property names found on | |
| 10971 // subtypes of HTMLElement (e.g. name, value). Rather it attempts to catch | |
| 10972 // some common cases. | |
| 10973 propertyNameBlacklist: { | |
| 10974 children: 1, | |
| 10975 'class': 1, | |
| 10976 id: 1, | |
| 10977 hidden: 1, | |
| 10978 style: 1, | |
| 10979 title: 1, | |
| 10980 } | |
| 10981 }; | |
| 10982 | |
| 10983 // exports | |
| 10984 | |
| 10985 scope.api.declaration.properties = properties; | |
| 10986 | |
| 10987 })(Polymer); | |
| 10988 | |
| 10989 (function(scope) { | |
| 10990 | |
| 10991 // magic words | |
| 10992 | |
| 10993 var ATTRIBUTES_ATTRIBUTE = 'attributes'; | |
| 10994 var ATTRIBUTES_REGEX = /\s|,/; | |
| 10995 | |
| 10996 // attributes api | |
| 10997 | |
| 10998 var attributes = { | |
| 10999 | |
| 11000 inheritAttributesObjects: function(prototype) { | |
| 11001 // chain our lower-cased publish map to the inherited version | |
| 11002 this.inheritObject(prototype, 'publishLC'); | |
| 11003 // chain our instance attributes map to the inherited version | |
| 11004 this.inheritObject(prototype, '_instanceAttributes'); | |
| 11005 }, | |
| 11006 | |
| 11007 publishAttributes: function(prototype, base) { | |
| 11008 // merge names from 'attributes' attribute into the 'publish' object | |
| 11009 var attributes = this.getAttribute(ATTRIBUTES_ATTRIBUTE); | |
| 11010 if (attributes) { | |
| 11011 // create a `publish` object if needed. | |
| 11012 // the `publish` object is only relevant to this prototype, the | |
| 11013 // publishing logic in `declaration/properties.js` is responsible for | |
| 11014 // managing property values on the prototype chain. | |
| 11015 // TODO(sjmiles): the `publish` object is later chained to it's | |
| 11016 // ancestor object, presumably this is only for | |
| 11017 // reflection or other non-library uses. | |
| 11018 var publish = prototype.publish || (prototype.publish = {}); | |
| 11019 // names='a b c' or names='a,b,c' | |
| 11020 var names = attributes.split(ATTRIBUTES_REGEX); | |
| 11021 // record each name for publishing | |
| 11022 for (var i=0, l=names.length, n; i<l; i++) { | |
| 11023 // remove excess ws | |
| 11024 n = names[i].trim(); | |
| 11025 // looks weird, but causes n to exist on `publish` if it does not; | |
| 11026 // a more careful test would need expensive `in` operator | |
| 11027 if (n && publish[n] === undefined) { | |
| 11028 publish[n] = undefined; | |
| 11029 } | |
| 11030 } | |
| 11031 } | |
| 11032 }, | |
| 11033 | |
| 11034 // record clonable attributes from <element> | |
| 11035 accumulateInstanceAttributes: function() { | |
| 11036 // inherit instance attributes | |
| 11037 var clonable = this.prototype._instanceAttributes; | |
| 11038 // merge attributes from element | |
| 11039 var a$ = this.attributes; | |
| 11040 for (var i=0, l=a$.length, a; (i<l) && (a=a$[i]); i++) { | |
| 11041 if (this.isInstanceAttribute(a.name)) { | |
| 11042 clonable[a.name] = a.value; | |
| 11043 } | |
| 11044 } | |
| 11045 }, | |
| 11046 | |
| 11047 isInstanceAttribute: function(name) { | |
| 11048 return !this.blackList[name] && name.slice(0,3) !== 'on-'; | |
| 11049 }, | |
| 11050 | |
| 11051 // do not clone these attributes onto instances | |
| 11052 blackList: { | |
| 11053 name: 1, | |
| 11054 'extends': 1, | |
| 11055 constructor: 1, | |
| 11056 noscript: 1, | |
| 11057 assetpath: 1, | |
| 11058 'cache-csstext': 1 | |
| 11059 } | |
| 11060 | |
| 11061 }; | |
| 11062 | |
| 11063 // add ATTRIBUTES_ATTRIBUTE to the blacklist | |
| 11064 attributes.blackList[ATTRIBUTES_ATTRIBUTE] = 1; | |
| 11065 | |
| 11066 // exports | |
| 11067 | |
| 11068 scope.api.declaration.attributes = attributes; | |
| 11069 | |
| 11070 })(Polymer); | |
| 11071 | |
| 11072 (function(scope) { | |
| 11073 | |
| 11074 // imports | |
| 11075 var events = scope.api.declaration.events; | |
| 11076 | |
| 11077 var syntax = new PolymerExpressions(); | |
| 11078 var prepareBinding = syntax.prepareBinding; | |
| 11079 | |
| 11080 // Polymer takes a first crack at the binding to see if it's a declarative | |
| 11081 // event handler. | |
| 11082 syntax.prepareBinding = function(pathString, name, node) { | |
| 11083 return events.prepareEventBinding(pathString, name, node) || | |
| 11084 prepareBinding.call(syntax, pathString, name, node); | |
| 11085 }; | |
| 11086 | |
| 11087 // declaration api supporting mdv | |
| 11088 var mdv = { | |
| 11089 syntax: syntax, | |
| 11090 fetchTemplate: function() { | |
| 11091 return this.querySelector('template'); | |
| 11092 }, | |
| 11093 templateContent: function() { | |
| 11094 var template = this.fetchTemplate(); | |
| 11095 return template && template.content; | |
| 11096 }, | |
| 11097 installBindingDelegate: function(template) { | |
| 11098 if (template) { | |
| 11099 template.bindingDelegate = this.syntax; | |
| 11100 } | |
| 11101 } | |
| 11102 }; | |
| 11103 | |
| 11104 // exports | |
| 11105 scope.api.declaration.mdv = mdv; | |
| 11106 | |
| 11107 })(Polymer); | |
| 11108 | |
| 11109 (function(scope) { | |
| 11110 | |
| 11111 // imports | |
| 11112 | |
| 11113 var api = scope.api; | |
| 11114 var isBase = scope.isBase; | |
| 11115 var extend = scope.extend; | |
| 11116 | |
| 11117 var hasShadowDOMPolyfill = window.ShadowDOMPolyfill; | |
| 11118 | |
| 11119 // prototype api | |
| 11120 | |
| 11121 var prototype = { | |
| 11122 | |
| 11123 register: function(name, extendeeName) { | |
| 11124 // build prototype combining extendee, Polymer base, and named api | |
| 11125 this.buildPrototype(name, extendeeName); | |
| 11126 // register our custom element with the platform | |
| 11127 this.registerPrototype(name, extendeeName); | |
| 11128 // reference constructor in a global named by 'constructor' attribute | |
| 11129 this.publishConstructor(); | |
| 11130 }, | |
| 11131 | |
| 11132 buildPrototype: function(name, extendeeName) { | |
| 11133 // get our custom prototype (before chaining) | |
| 11134 var extension = scope.getRegisteredPrototype(name); | |
| 11135 // get basal prototype | |
| 11136 var base = this.generateBasePrototype(extendeeName); | |
| 11137 // implement declarative features | |
| 11138 this.desugarBeforeChaining(extension, base); | |
| 11139 // join prototypes | |
| 11140 this.prototype = this.chainPrototypes(extension, base); | |
| 11141 // more declarative features | |
| 11142 this.desugarAfterChaining(name, extendeeName); | |
| 11143 }, | |
| 11144 | |
| 11145 desugarBeforeChaining: function(prototype, base) { | |
| 11146 // back reference declaration element | |
| 11147 // TODO(sjmiles): replace `element` with `elementElement` or `declaration` | |
| 11148 prototype.element = this; | |
| 11149 // transcribe `attributes` declarations onto own prototype's `publish` | |
| 11150 this.publishAttributes(prototype, base); | |
| 11151 // `publish` properties to the prototype and to attribute watch | |
| 11152 this.publishProperties(prototype, base); | |
| 11153 // infer observers for `observe` list based on method names | |
| 11154 this.inferObservers(prototype); | |
| 11155 // desugar compound observer syntax, e.g. 'a b c' | |
| 11156 this.explodeObservers(prototype); | |
| 11157 }, | |
| 11158 | |
| 11159 chainPrototypes: function(prototype, base) { | |
| 11160 // chain various meta-data objects to inherited versions | |
| 11161 this.inheritMetaData(prototype, base); | |
| 11162 // chain custom api to inherited | |
| 11163 var chained = this.chainObject(prototype, base); | |
| 11164 // x-platform fixup | |
| 11165 ensurePrototypeTraversal(chained); | |
| 11166 return chained; | |
| 11167 }, | |
| 11168 | |
| 11169 inheritMetaData: function(prototype, base) { | |
| 11170 // chain observe object to inherited | |
| 11171 this.inheritObject('observe', prototype, base); | |
| 11172 // chain publish object to inherited | |
| 11173 this.inheritObject('publish', prototype, base); | |
| 11174 // chain reflect object to inherited | |
| 11175 this.inheritObject('reflect', prototype, base); | |
| 11176 // chain our lower-cased publish map to the inherited version | |
| 11177 this.inheritObject('_publishLC', prototype, base); | |
| 11178 // chain our instance attributes map to the inherited version | |
| 11179 this.inheritObject('_instanceAttributes', prototype, base); | |
| 11180 // chain our event delegates map to the inherited version | |
| 11181 this.inheritObject('eventDelegates', prototype, base); | |
| 11182 }, | |
| 11183 | |
| 11184 // implement various declarative features | |
| 11185 desugarAfterChaining: function(name, extendee) { | |
| 11186 // build side-chained lists to optimize iterations | |
| 11187 this.optimizePropertyMaps(this.prototype); | |
| 11188 this.createPropertyAccessors(this.prototype); | |
| 11189 // install mdv delegate on template | |
| 11190 this.installBindingDelegate(this.fetchTemplate()); | |
| 11191 // install external stylesheets as if they are inline | |
| 11192 this.installSheets(); | |
| 11193 // adjust any paths in dom from imports | |
| 11194 this.resolveElementPaths(this); | |
| 11195 // compile list of attributes to copy to instances | |
| 11196 this.accumulateInstanceAttributes(); | |
| 11197 // parse on-* delegates declared on `this` element | |
| 11198 this.parseHostEvents(); | |
| 11199 // | |
| 11200 // install a helper method this.resolvePath to aid in | |
| 11201 // setting resource urls. e.g. | |
| 11202 // this.$.image.src = this.resolvePath('images/foo.png') | |
| 11203 this.addResolvePathApi(); | |
| 11204 // under ShadowDOMPolyfill, transforms to approximate missing CSS features | |
| 11205 if (hasShadowDOMPolyfill) { | |
| 11206 WebComponents.ShadowCSS.shimStyling(this.templateContent(), name, | |
| 11207 extendee); | |
| 11208 } | |
| 11209 // allow custom element access to the declarative context | |
| 11210 if (this.prototype.registerCallback) { | |
| 11211 this.prototype.registerCallback(this); | |
| 11212 } | |
| 11213 }, | |
| 11214 | |
| 11215 // if a named constructor is requested in element, map a reference | |
| 11216 // to the constructor to the given symbol | |
| 11217 publishConstructor: function() { | |
| 11218 var symbol = this.getAttribute('constructor'); | |
| 11219 if (symbol) { | |
| 11220 window[symbol] = this.ctor; | |
| 11221 } | |
| 11222 }, | |
| 11223 | |
| 11224 // build prototype combining extendee, Polymer base, and named api | |
| 11225 generateBasePrototype: function(extnds) { | |
| 11226 var prototype = this.findBasePrototype(extnds); | |
| 11227 if (!prototype) { | |
| 11228 // create a prototype based on tag-name extension | |
| 11229 var prototype = HTMLElement.getPrototypeForTag(extnds); | |
| 11230 // insert base api in inheritance chain (if needed) | |
| 11231 prototype = this.ensureBaseApi(prototype); | |
| 11232 // memoize this base | |
| 11233 memoizedBases[extnds] = prototype; | |
| 11234 } | |
| 11235 return prototype; | |
| 11236 }, | |
| 11237 | |
| 11238 findBasePrototype: function(name) { | |
| 11239 return memoizedBases[name]; | |
| 11240 }, | |
| 11241 | |
| 11242 // install Polymer instance api into prototype chain, as needed | |
| 11243 ensureBaseApi: function(prototype) { | |
| 11244 if (prototype.PolymerBase) { | |
| 11245 return prototype; | |
| 11246 } | |
| 11247 var extended = Object.create(prototype); | |
| 11248 // we need a unique copy of base api for each base prototype | |
| 11249 // therefore we 'extend' here instead of simply chaining | |
| 11250 api.publish(api.instance, extended); | |
| 11251 // TODO(sjmiles): sharing methods across prototype chains is | |
| 11252 // not supported by 'super' implementation which optimizes | |
| 11253 // by memoizing prototype relationships. | |
| 11254 // Probably we should have a version of 'extend' that is | |
| 11255 // share-aware: it could study the text of each function, | |
| 11256 // look for usage of 'super', and wrap those functions in | |
| 11257 // closures. | |
| 11258 // As of now, there is only one problematic method, so | |
| 11259 // we just patch it manually. | |
| 11260 // To avoid re-entrancy problems, the special super method | |
| 11261 // installed is called `mixinSuper` and the mixin method | |
| 11262 // must use this method instead of the default `super`. | |
| 11263 this.mixinMethod(extended, prototype, api.instance.mdv, 'bind'); | |
| 11264 // return buffed-up prototype | |
| 11265 return extended; | |
| 11266 }, | |
| 11267 | |
| 11268 mixinMethod: function(extended, prototype, api, name) { | |
| 11269 var $super = function(args) { | |
| 11270 return prototype[name].apply(this, args); | |
| 11271 }; | |
| 11272 extended[name] = function() { | |
| 11273 this.mixinSuper = $super; | |
| 11274 return api[name].apply(this, arguments); | |
| 11275 } | |
| 11276 }, | |
| 11277 | |
| 11278 // ensure prototype[name] inherits from a prototype.prototype[name] | |
| 11279 inheritObject: function(name, prototype, base) { | |
| 11280 // require an object | |
| 11281 var source = prototype[name] || {}; | |
| 11282 // chain inherited properties onto a new object | |
| 11283 prototype[name] = this.chainObject(source, base[name]); | |
| 11284 }, | |
| 11285 | |
| 11286 // register 'prototype' to custom element 'name', store constructor | |
| 11287 registerPrototype: function(name, extendee) { | |
| 11288 var info = { | |
| 11289 prototype: this.prototype | |
| 11290 } | |
| 11291 // native element must be specified in extends | |
| 11292 var typeExtension = this.findTypeExtension(extendee); | |
| 11293 if (typeExtension) { | |
| 11294 info.extends = typeExtension; | |
| 11295 } | |
| 11296 // register the prototype with HTMLElement for name lookup | |
| 11297 HTMLElement.register(name, this.prototype); | |
| 11298 // register the custom type | |
| 11299 this.ctor = document.registerElement(name, info); | |
| 11300 }, | |
| 11301 | |
| 11302 findTypeExtension: function(name) { | |
| 11303 if (name && name.indexOf('-') < 0) { | |
| 11304 return name; | |
| 11305 } else { | |
| 11306 var p = this.findBasePrototype(name); | |
| 11307 if (p.element) { | |
| 11308 return this.findTypeExtension(p.element.extends); | |
| 11309 } | |
| 11310 } | |
| 11311 } | |
| 11312 | |
| 11313 }; | |
| 11314 | |
| 11315 // memoize base prototypes | |
| 11316 var memoizedBases = {}; | |
| 11317 | |
| 11318 // implementation of 'chainObject' depends on support for __proto__ | |
| 11319 if (Object.__proto__) { | |
| 11320 prototype.chainObject = function(object, inherited) { | |
| 11321 if (object && inherited && object !== inherited) { | |
| 11322 object.__proto__ = inherited; | |
| 11323 } | |
| 11324 return object; | |
| 11325 } | |
| 11326 } else { | |
| 11327 prototype.chainObject = function(object, inherited) { | |
| 11328 if (object && inherited && object !== inherited) { | |
| 11329 var chained = Object.create(inherited); | |
| 11330 object = extend(chained, object); | |
| 11331 } | |
| 11332 return object; | |
| 11333 } | |
| 11334 } | |
| 11335 | |
| 11336 // On platforms that do not support __proto__ (versions of IE), the prototype | |
| 11337 // chain of a custom element is simulated via installation of __proto__. | |
| 11338 // Although custom elements manages this, we install it here so it's | |
| 11339 // available during desugaring. | |
| 11340 function ensurePrototypeTraversal(prototype) { | |
| 11341 if (!Object.__proto__) { | |
| 11342 var ancestor = Object.getPrototypeOf(prototype); | |
| 11343 prototype.__proto__ = ancestor; | |
| 11344 if (isBase(ancestor)) { | |
| 11345 ancestor.__proto__ = Object.getPrototypeOf(ancestor); | |
| 11346 } | |
| 11347 } | |
| 11348 } | |
| 11349 | |
| 11350 // exports | |
| 11351 | |
| 11352 api.declaration.prototype = prototype; | |
| 11353 | |
| 11354 })(Polymer); | |
| 11355 | |
| 11356 (function(scope) { | |
| 11357 | |
| 11358 /* | |
| 11359 | |
| 11360 Elements are added to a registration queue so that they register in | |
| 11361 the proper order at the appropriate time. We do this for a few reasons: | |
| 11362 | |
| 11363 * to enable elements to load resources (like stylesheets) | |
| 11364 asynchronously. We need to do this until the platform provides an efficient | |
| 11365 alternative. One issue is that remote @import stylesheets are | |
| 11366 re-fetched whenever stamped into a shadowRoot. | |
| 11367 | |
| 11368 * to ensure elements loaded 'at the same time' (e.g. via some set of | |
| 11369 imports) are registered as a batch. This allows elements to be enured from | |
| 11370 upgrade ordering as long as they query the dom tree 1 task after | |
| 11371 upgrade (aka domReady). This is a performance tradeoff. On the one hand, | |
| 11372 elements that could register while imports are loading are prevented from | |
| 11373 doing so. On the other, grouping upgrades into a single task means less | |
| 11374 incremental work (for example style recalcs), Also, we can ensure the | |
| 11375 document is in a known state at the single quantum of time when | |
| 11376 elements upgrade. | |
| 11377 | |
| 11378 */ | |
| 11379 var queue = { | |
| 11380 | |
| 11381 // tell the queue to wait for an element to be ready | |
| 11382 wait: function(element) { | |
| 11383 if (!element.__queue) { | |
| 11384 element.__queue = {}; | |
| 11385 elements.push(element); | |
| 11386 } | |
| 11387 }, | |
| 11388 | |
| 11389 // enqueue an element to the next spot in the queue. | |
| 11390 enqueue: function(element, check, go) { | |
| 11391 var shouldAdd = element.__queue && !element.__queue.check; | |
| 11392 if (shouldAdd) { | |
| 11393 queueForElement(element).push(element); | |
| 11394 element.__queue.check = check; | |
| 11395 element.__queue.go = go; | |
| 11396 } | |
| 11397 return (this.indexOf(element) !== 0); | |
| 11398 }, | |
| 11399 | |
| 11400 indexOf: function(element) { | |
| 11401 var i = queueForElement(element).indexOf(element); | |
| 11402 if (i >= 0 && document.contains(element)) { | |
| 11403 i += (HTMLImports.useNative || HTMLImports.ready) ? | |
| 11404 importQueue.length : 1e9; | |
| 11405 } | |
| 11406 return i; | |
| 11407 }, | |
| 11408 | |
| 11409 // tell the queue an element is ready to be registered | |
| 11410 go: function(element) { | |
| 11411 var readied = this.remove(element); | |
| 11412 if (readied) { | |
| 11413 element.__queue.flushable = true; | |
| 11414 this.addToFlushQueue(readied); | |
| 11415 this.check(); | |
| 11416 } | |
| 11417 }, | |
| 11418 | |
| 11419 remove: function(element) { | |
| 11420 var i = this.indexOf(element); | |
| 11421 if (i !== 0) { | |
| 11422 //console.warn('queue order wrong', i); | |
| 11423 return; | |
| 11424 } | |
| 11425 return queueForElement(element).shift(); | |
| 11426 }, | |
| 11427 | |
| 11428 check: function() { | |
| 11429 // next | |
| 11430 var element = this.nextElement(); | |
| 11431 if (element) { | |
| 11432 element.__queue.check.call(element); | |
| 11433 } | |
| 11434 if (this.canReady()) { | |
| 11435 this.ready(); | |
| 11436 return true; | |
| 11437 } | |
| 11438 }, | |
| 11439 | |
| 11440 nextElement: function() { | |
| 11441 return nextQueued(); | |
| 11442 }, | |
| 11443 | |
| 11444 canReady: function() { | |
| 11445 return !this.waitToReady && this.isEmpty(); | |
| 11446 }, | |
| 11447 | |
| 11448 isEmpty: function() { | |
| 11449 for (var i=0, l=elements.length, e; (i<l) && | |
| 11450 (e=elements[i]); i++) { | |
| 11451 if (e.__queue && !e.__queue.flushable) { | |
| 11452 return; | |
| 11453 } | |
| 11454 } | |
| 11455 return true; | |
| 11456 }, | |
| 11457 | |
| 11458 addToFlushQueue: function(element) { | |
| 11459 flushQueue.push(element); | |
| 11460 }, | |
| 11461 | |
| 11462 flush: function() { | |
| 11463 // prevent re-entrance | |
| 11464 if (this.flushing) { | |
| 11465 return; | |
| 11466 } | |
| 11467 this.flushing = true; | |
| 11468 var element; | |
| 11469 while (flushQueue.length) { | |
| 11470 element = flushQueue.shift(); | |
| 11471 element.__queue.go.call(element); | |
| 11472 element.__queue = null; | |
| 11473 } | |
| 11474 this.flushing = false; | |
| 11475 }, | |
| 11476 | |
| 11477 ready: function() { | |
| 11478 // TODO(sorvell): As an optimization, turn off CE polyfill upgrading | |
| 11479 // while registering. This way we avoid having to upgrade each document | |
| 11480 // piecemeal per registration and can instead register all elements | |
| 11481 // and upgrade once in a batch. Without this optimization, upgrade time | |
| 11482 // degrades significantly when SD polyfill is used. This is mainly because | |
| 11483 // querying the document tree for elements is slow under the SD polyfill. | |
| 11484 var polyfillWasReady = CustomElements.ready; | |
| 11485 CustomElements.ready = false; | |
| 11486 this.flush(); | |
| 11487 if (!CustomElements.useNative) { | |
| 11488 CustomElements.upgradeDocumentTree(document); | |
| 11489 } | |
| 11490 CustomElements.ready = polyfillWasReady; | |
| 11491 Polymer.flush(); | |
| 11492 requestAnimationFrame(this.flushReadyCallbacks); | |
| 11493 }, | |
| 11494 | |
| 11495 addReadyCallback: function(callback) { | |
| 11496 if (callback) { | |
| 11497 readyCallbacks.push(callback); | |
| 11498 } | |
| 11499 }, | |
| 11500 | |
| 11501 flushReadyCallbacks: function() { | |
| 11502 if (readyCallbacks) { | |
| 11503 var fn; | |
| 11504 while (readyCallbacks.length) { | |
| 11505 fn = readyCallbacks.shift(); | |
| 11506 fn(); | |
| 11507 } | |
| 11508 } | |
| 11509 }, | |
| 11510 | |
| 11511 /** | |
| 11512 Returns a list of elements that have had polymer-elements created but | |
| 11513 are not yet ready to register. The list is an array of element definitions. | |
| 11514 */ | |
| 11515 waitingFor: function() { | |
| 11516 var e$ = []; | |
| 11517 for (var i=0, l=elements.length, e; (i<l) && | |
| 11518 (e=elements[i]); i++) { | |
| 11519 if (e.__queue && !e.__queue.flushable) { | |
| 11520 e$.push(e); | |
| 11521 } | |
| 11522 } | |
| 11523 return e$; | |
| 11524 }, | |
| 11525 | |
| 11526 waitToReady: true | |
| 11527 | |
| 11528 }; | |
| 11529 | |
| 11530 var elements = []; | |
| 11531 var flushQueue = []; | |
| 11532 var importQueue = []; | |
| 11533 var mainQueue = []; | |
| 11534 var readyCallbacks = []; | |
| 11535 | |
| 11536 function queueForElement(element) { | |
| 11537 return document.contains(element) ? mainQueue : importQueue; | |
| 11538 } | |
| 11539 | |
| 11540 function nextQueued() { | |
| 11541 return importQueue.length ? importQueue[0] : mainQueue[0]; | |
| 11542 } | |
| 11543 | |
| 11544 function whenReady(callback) { | |
| 11545 queue.waitToReady = true; | |
| 11546 Polymer.endOfMicrotask(function() { | |
| 11547 HTMLImports.whenReady(function() { | |
| 11548 queue.addReadyCallback(callback); | |
| 11549 queue.waitToReady = false; | |
| 11550 queue.check(); | |
| 11551 }); | |
| 11552 }); | |
| 11553 } | |
| 11554 | |
| 11555 /** | |
| 11556 Forces polymer to register any pending elements. Can be used to abort | |
| 11557 waiting for elements that are partially defined. | |
| 11558 @param timeout {Integer} Optional timeout in milliseconds | |
| 11559 */ | |
| 11560 function forceReady(timeout) { | |
| 11561 if (timeout === undefined) { | |
| 11562 queue.ready(); | |
| 11563 return; | |
| 11564 } | |
| 11565 var handle = setTimeout(function() { | |
| 11566 queue.ready(); | |
| 11567 }, timeout); | |
| 11568 Polymer.whenReady(function() { | |
| 11569 clearTimeout(handle); | |
| 11570 }); | |
| 11571 } | |
| 11572 | |
| 11573 // exports | |
| 11574 scope.elements = elements; | |
| 11575 scope.waitingFor = queue.waitingFor.bind(queue); | |
| 11576 scope.forceReady = forceReady; | |
| 11577 scope.queue = queue; | |
| 11578 scope.whenReady = scope.whenPolymerReady = whenReady; | |
| 11579 })(Polymer); | |
| 11580 | |
| 11581 (function(scope) { | |
| 11582 | |
| 11583 // imports | |
| 11584 | |
| 11585 var extend = scope.extend; | |
| 11586 var api = scope.api; | |
| 11587 var queue = scope.queue; | |
| 11588 var whenReady = scope.whenReady; | |
| 11589 var getRegisteredPrototype = scope.getRegisteredPrototype; | |
| 11590 var waitingForPrototype = scope.waitingForPrototype; | |
| 11591 | |
| 11592 // declarative implementation: <polymer-element> | |
| 11593 | |
| 11594 var prototype = extend(Object.create(HTMLElement.prototype), { | |
| 11595 | |
| 11596 createdCallback: function() { | |
| 11597 if (this.getAttribute('name')) { | |
| 11598 this.init(); | |
| 11599 } | |
| 11600 }, | |
| 11601 | |
| 11602 init: function() { | |
| 11603 // fetch declared values | |
| 11604 this.name = this.getAttribute('name'); | |
| 11605 this.extends = this.getAttribute('extends'); | |
| 11606 queue.wait(this); | |
| 11607 // initiate any async resource fetches | |
| 11608 this.loadResources(); | |
| 11609 // register when all constraints are met | |
| 11610 this.registerWhenReady(); | |
| 11611 }, | |
| 11612 | |
| 11613 // TODO(sorvell): we currently queue in the order the prototypes are | |
| 11614 // registered, but we should queue in the order that polymer-elements | |
| 11615 // are registered. We are currently blocked from doing this based on | |
| 11616 // crbug.com/395686. | |
| 11617 registerWhenReady: function() { | |
| 11618 if (this.registered | |
| 11619 || this.waitingForPrototype(this.name) | |
| 11620 || this.waitingForQueue() | |
| 11621 || this.waitingForResources()) { | |
| 11622 return; | |
| 11623 } | |
| 11624 queue.go(this); | |
| 11625 }, | |
| 11626 | |
| 11627 _register: function() { | |
| 11628 //console.log('registering', this.name); | |
| 11629 // warn if extending from a custom element not registered via Polymer | |
| 11630 if (isCustomTag(this.extends) && !isRegistered(this.extends)) { | |
| 11631 console.warn('%s is attempting to extend %s, an unregistered element ' + | |
| 11632 'or one that was not registered with Polymer.', this.name, | |
| 11633 this.extends); | |
| 11634 } | |
| 11635 this.register(this.name, this.extends); | |
| 11636 this.registered = true; | |
| 11637 }, | |
| 11638 | |
| 11639 waitingForPrototype: function(name) { | |
| 11640 if (!getRegisteredPrototype(name)) { | |
| 11641 // then wait for a prototype | |
| 11642 waitingForPrototype(name, this); | |
| 11643 // emulate script if user is not supplying one | |
| 11644 this.handleNoScript(name); | |
| 11645 // prototype not ready yet | |
| 11646 return true; | |
| 11647 } | |
| 11648 }, | |
| 11649 | |
| 11650 handleNoScript: function(name) { | |
| 11651 // if explicitly marked as 'noscript' | |
| 11652 if (this.hasAttribute('noscript') && !this.noscript) { | |
| 11653 this.noscript = true; | |
| 11654 // imperative element registration | |
| 11655 Polymer(name); | |
| 11656 } | |
| 11657 }, | |
| 11658 | |
| 11659 waitingForResources: function() { | |
| 11660 return this._needsResources; | |
| 11661 }, | |
| 11662 | |
| 11663 // NOTE: Elements must be queued in proper order for inheritance/composition | |
| 11664 // dependency resolution. Previously this was enforced for inheritance, | |
| 11665 // and by rule for composition. It's now entirely by rule. | |
| 11666 waitingForQueue: function() { | |
| 11667 return queue.enqueue(this, this.registerWhenReady, this._register); | |
| 11668 }, | |
| 11669 | |
| 11670 loadResources: function() { | |
| 11671 this._needsResources = true; | |
| 11672 this.loadStyles(function() { | |
| 11673 this._needsResources = false; | |
| 11674 this.registerWhenReady(); | |
| 11675 }.bind(this)); | |
| 11676 } | |
| 11677 | |
| 11678 }); | |
| 11679 | |
| 11680 // semi-pluggable APIs | |
| 11681 | |
| 11682 // TODO(sjmiles): should be fully pluggable (aka decoupled, currently | |
| 11683 // the various plugins are allowed to depend on each other directly) | |
| 11684 api.publish(api.declaration, prototype); | |
| 11685 | |
| 11686 // utility and bookkeeping | |
| 11687 | |
| 11688 function isRegistered(name) { | |
| 11689 return Boolean(HTMLElement.getPrototypeForTag(name)); | |
| 11690 } | |
| 11691 | |
| 11692 function isCustomTag(name) { | |
| 11693 return (name && name.indexOf('-') >= 0); | |
| 11694 } | |
| 11695 | |
| 11696 // boot tasks | |
| 11697 | |
| 11698 whenReady(function() { | |
| 11699 document.body.removeAttribute('unresolved'); | |
| 11700 document.dispatchEvent( | |
| 11701 new CustomEvent('polymer-ready', {bubbles: true}) | |
| 11702 ); | |
| 11703 }); | |
| 11704 | |
| 11705 // register polymer-element with document | |
| 11706 | |
| 11707 document.registerElement('polymer-element', {prototype: prototype}); | |
| 11708 | |
| 11709 })(Polymer); | |
| 11710 | |
| 11711 (function(scope) { | |
| 11712 | |
| 11713 /** | |
| 11714 * @class Polymer | |
| 11715 */ | |
| 11716 | |
| 11717 var whenReady = scope.whenReady; | |
| 11718 | |
| 11719 /** | |
| 11720 * Loads the set of HTMLImports contained in `node`. Notifies when all | |
| 11721 * the imports have loaded by calling the `callback` function argument. | |
| 11722 * This method can be used to lazily load imports. For example, given a | |
| 11723 * template: | |
| 11724 * | |
| 11725 * <template> | |
| 11726 * <link rel="import" href="my-import1.html"> | |
| 11727 * <link rel="import" href="my-import2.html"> | |
| 11728 * </template> | |
| 11729 * | |
| 11730 * Polymer.importElements(template.content, function() { | |
| 11731 * console.log('imports lazily loaded'); | |
| 11732 * }); | |
| 11733 * | |
| 11734 * @method importElements | |
| 11735 * @param {Node} node Node containing the HTMLImports to load. | |
| 11736 * @param {Function} callback Callback called when all imports have loaded. | |
| 11737 */ | |
| 11738 function importElements(node, callback) { | |
| 11739 if (node) { | |
| 11740 document.head.appendChild(node); | |
| 11741 whenReady(callback); | |
| 11742 } else if (callback) { | |
| 11743 callback(); | |
| 11744 } | |
| 11745 } | |
| 11746 | |
| 11747 /** | |
| 11748 * Loads an HTMLImport for each url specified in the `urls` array. | |
| 11749 * Notifies when all the imports have loaded by calling the `callback` | |
| 11750 * function argument. This method can be used to lazily load imports. | |
| 11751 * For example, | |
| 11752 * | |
| 11753 * Polymer.import(['my-import1.html', 'my-import2.html'], function() { | |
| 11754 * console.log('imports lazily loaded'); | |
| 11755 * }); | |
| 11756 * | |
| 11757 * @method import | |
| 11758 * @param {Array} urls Array of urls to load as HTMLImports. | |
| 11759 * @param {Function} callback Callback called when all imports have loaded. | |
| 11760 */ | |
| 11761 function _import(urls, callback) { | |
| 11762 if (urls && urls.length) { | |
| 11763 var frag = document.createDocumentFragment(); | |
| 11764 for (var i=0, l=urls.length, url, link; (i<l) && (url=urls[i]); i++) { | |
| 11765 link = document.createElement('link'); | |
| 11766 link.rel = 'import'; | |
| 11767 link.href = url; | |
| 11768 frag.appendChild(link); | |
| 11769 } | |
| 11770 importElements(frag, callback); | |
| 11771 } else if (callback) { | |
| 11772 callback(); | |
| 11773 } | |
| 11774 } | |
| 11775 | |
| 11776 // exports | |
| 11777 scope.import = _import; | |
| 11778 scope.importElements = importElements; | |
| 11779 | |
| 11780 })(Polymer); | |
| 11781 | |
| 11782 /** | |
| 11783 * The `auto-binding` element extends the template element. It provides a quick | |
| 11784 * and easy way to do data binding without the need to setup a model. | |
| 11785 * The `auto-binding` element itself serves as the model and controller for the | |
| 11786 * elements it contains. Both data and event handlers can be bound. | |
| 11787 * | |
| 11788 * The `auto-binding` element acts just like a template that is bound to | |
| 11789 * a model. It stamps its content in the dom adjacent to itself. When the | |
| 11790 * content is stamped, the `template-bound` event is fired. | |
| 11791 * | |
| 11792 * Example: | |
| 11793 * | |
| 11794 * <template is="auto-binding"> | |
| 11795 * <div>Say something: <input value="{{value}}"></div> | |
| 11796 * <div>You said: {{value}}</div> | |
| 11797 * <button on-tap="{{buttonTap}}">Tap me!</button> | |
| 11798 * </template> | |
| 11799 * <script> | |
| 11800 * var template = document.querySelector('template'); | |
| 11801 * template.value = 'something'; | |
| 11802 * template.buttonTap = function() { | |
| 11803 * console.log('tap!'); | |
| 11804 * }; | |
| 11805 * <\/script> | |
| 11806 * | |
| 11807 * @module Polymer | |
| 11808 * @status stable | |
| 11809 */ | |
| 11810 | |
| 11811 (function() { | |
| 11812 | |
| 11813 var element = document.createElement('polymer-element'); | |
| 11814 element.setAttribute('name', 'auto-binding'); | |
| 11815 element.setAttribute('extends', 'template'); | |
| 11816 element.init(); | |
| 11817 | |
| 11818 Polymer('auto-binding', { | |
| 11819 | |
| 11820 createdCallback: function() { | |
| 11821 this.syntax = this.bindingDelegate = this.makeSyntax(); | |
| 11822 // delay stamping until polymer-ready so that auto-binding is not | |
| 11823 // required to load last. | |
| 11824 Polymer.whenPolymerReady(function() { | |
| 11825 this.model = this; | |
| 11826 this.setAttribute('bind', ''); | |
| 11827 // we don't bother with an explicit signal here, we could ust a MO | |
| 11828 // if necessary | |
| 11829 this.async(function() { | |
| 11830 // note: this will marshall *all* the elements in the parentNode | |
| 11831 // rather than just stamped ones. We'd need to use createInstance | |
| 11832 // to fix this or something else fancier. | |
| 11833 this.marshalNodeReferences(this.parentNode); | |
| 11834 // template stamping is asynchronous so stamping isn't complete | |
| 11835 // by polymer-ready; fire an event so users can use stamped elements | |
| 11836 this.fire('template-bound'); | |
| 11837 }); | |
| 11838 }.bind(this)); | |
| 11839 }, | |
| 11840 | |
| 11841 makeSyntax: function() { | |
| 11842 var events = Object.create(Polymer.api.declaration.events); | |
| 11843 var self = this; | |
| 11844 events.findController = function() { return self.model; }; | |
| 11845 | |
| 11846 var syntax = new PolymerExpressions(); | |
| 11847 var prepareBinding = syntax.prepareBinding; | |
| 11848 syntax.prepareBinding = function(pathString, name, node) { | |
| 11849 return events.prepareEventBinding(pathString, name, node) || | |
| 11850 prepareBinding.call(syntax, pathString, name, node); | |
| 11851 }; | |
| 11852 return syntax; | |
| 11853 } | |
| 11854 | |
| 11855 }); | |
| 11856 | |
| 11857 })(); | |
| 11858 ; | |
| 11859 | |
| 11860 | |
| 11861 (function(scope) { | |
| 11862 | |
| 11863 /** | |
| 11864 `Polymer.CoreResizable` and `Polymer.CoreResizer` are a set of mixins that can
be used | |
| 11865 in Polymer elements to coordinate the flow of resize events between "resizers"
(elements | |
| 11866 that control the size or hidden state of their children) and "resizables" (ele
ments that | |
| 11867 need to be notified when they are resized or un-hidden by their parents in ord
er to take | |
| 11868 action on their new measurements). | |
| 11869 | |
| 11870 Elements that perform measurement should add the `Core.Resizable` mixin to the
ir | |
| 11871 Polymer prototype definition and listen for the `core-resize` event on themsel
ves. | |
| 11872 This event will be fired when they become showing after having been hidden, | |
| 11873 when they are resized explicitly by a `CoreResizer`, or when the window has be
en resized. | |
| 11874 Note, the `core-resize` event is non-bubbling. | |
| 11875 | |
| 11876 `CoreResizable`'s must manually call the `resizableAttachedHandler` from the e
lement's | |
| 11877 `attached` callback and `resizableDetachedHandler` from the element's `detache
d` | |
| 11878 callback. | |
| 11879 | |
| 11880 @element CoreResizable | |
| 11881 @status beta | |
| 11882 @homepage github.io | |
| 11883 */ | |
| 11884 | |
| 11885 scope.CoreResizable = { | |
| 11886 | |
| 11887 /** | |
| 11888 * User must call from `attached` callback | |
| 11889 * | |
| 11890 * @method resizableAttachedHandler | |
| 11891 */ | |
| 11892 resizableAttachedHandler: function(cb) { | |
| 11893 cb = cb || this._notifyResizeSelf; | |
| 11894 this.async(function() { | |
| 11895 var detail = {callback: cb, hasParentResizer: false}; | |
| 11896 this.fire('core-request-resize', detail); | |
| 11897 if (!detail.hasParentResizer) { | |
| 11898 this._boundWindowResizeHandler = cb.bind(this); | |
| 11899 // log('adding window resize handler', null, this); | |
| 11900 window.addEventListener('resize', this._boundWindowResizeHandler); | |
| 11901 } | |
| 11902 }.bind(this)); | |
| 11903 }, | |
| 11904 | |
| 11905 /** | |
| 11906 * User must call from `detached` callback | |
| 11907 * | |
| 11908 * @method resizableDetachedHandler | |
| 11909 */ | |
| 11910 resizableDetachedHandler: function() { | |
| 11911 this.fire('core-request-resize-cancel', null, this, false); | |
| 11912 if (this._boundWindowResizeHandler) { | |
| 11913 window.removeEventListener('resize', this._boundWindowResizeHandler); | |
| 11914 } | |
| 11915 }, | |
| 11916 | |
| 11917 // Private: fire non-bubbling resize event to self; returns whether | |
| 11918 // preventDefault was called, indicating that children should not | |
| 11919 // be resized | |
| 11920 _notifyResizeSelf: function() { | |
| 11921 return this.fire('core-resize', null, this, false).defaultPrevented; | |
| 11922 } | |
| 11923 | |
| 11924 }; | |
| 11925 | |
| 11926 /** | |
| 11927 `Polymer.CoreResizable` and `Polymer.CoreResizer` are a set of mixins that can
be used | |
| 11928 in Polymer elements to coordinate the flow of resize events between "resizers"
(elements | |
| 11929 that control the size or hidden state of their children) and "resizables" (ele
ments that | |
| 11930 need to be notified when they are resized or un-hidden by their parents in ord
er to take | |
| 11931 action on their new measurements). | |
| 11932 | |
| 11933 Elements that cause their children to be resized (e.g. a splitter control) or
hide/show | |
| 11934 their children (e.g. overlay) should add the `Core.CoreResizer` mixin to their
| |
| 11935 Polymer prototype definition and then call `this.notifyResize()` any time the
element | |
| 11936 resizes or un-hides its children. | |
| 11937 | |
| 11938 `CoreResizer`'s must manually call the `resizerAttachedHandler` from the eleme
nt's | |
| 11939 `attached` callback and `resizerDetachedHandler` from the element's `detached` | |
| 11940 callback. | |
| 11941 | |
| 11942 Note: `CoreResizer` extends `CoreResizable`, and can listen for the `core-resi
ze` event | |
| 11943 on itself if it needs to perform resize work on itself before notifying childr
en. | |
| 11944 In this case, returning `false` from the `core-resize` event handler (or calli
ng | |
| 11945 `preventDefault` on the event) will prevent notification of children if requir
ed. | |
| 11946 | |
| 11947 @element CoreResizer | |
| 11948 @extends CoreResizable | |
| 11949 @status beta | |
| 11950 @homepage github.io | |
| 11951 */ | |
| 11952 | |
| 11953 scope.CoreResizer = Polymer.mixin({ | |
| 11954 | |
| 11955 /** | |
| 11956 * User must call from `attached` callback | |
| 11957 * | |
| 11958 * @method resizerAttachedHandler | |
| 11959 */ | |
| 11960 resizerAttachedHandler: function() { | |
| 11961 this.resizableAttachedHandler(this.notifyResize); | |
| 11962 this._boundResizeRequested = this._boundResizeRequested || this._handleRes
izeRequested.bind(this); | |
| 11963 var listener; | |
| 11964 if (this.resizerIsPeer) { | |
| 11965 listener = this.parentElement || (this.parentNode && this.parentNode.hos
t); | |
| 11966 listener._resizerPeers = listener._resizerPeers || []; | |
| 11967 listener._resizerPeers.push(this); | |
| 11968 } else { | |
| 11969 listener = this; | |
| 11970 } | |
| 11971 listener.addEventListener('core-request-resize', this._boundResizeRequeste
d); | |
| 11972 this._resizerListener = listener; | |
| 11973 }, | |
| 11974 | |
| 11975 /** | |
| 11976 * User must call from `detached` callback | |
| 11977 * | |
| 11978 * @method resizerDetachedHandler | |
| 11979 */ | |
| 11980 resizerDetachedHandler: function() { | |
| 11981 this.resizableDetachedHandler(); | |
| 11982 this._resizerListener.removeEventListener('core-request-resize', this._bou
ndResizeRequested); | |
| 11983 }, | |
| 11984 | |
| 11985 /** | |
| 11986 * User should call when resizing or un-hiding children | |
| 11987 * | |
| 11988 * @method notifyResize | |
| 11989 */ | |
| 11990 notifyResize: function() { | |
| 11991 // Notify self | |
| 11992 if (!this._notifyResizeSelf()) { | |
| 11993 // Notify requestors if default was not prevented | |
| 11994 var r = this.resizeRequestors; | |
| 11995 if (r) { | |
| 11996 for (var i=0; i<r.length; i++) { | |
| 11997 var ri = r[i]; | |
| 11998 if (!this.resizerShouldNotify || this.resizerShouldNotify(ri.target)
) { | |
| 11999 // log('notifying resize', null, ri.target, true); | |
| 12000 ri.callback.apply(ri.target); | |
| 12001 // logEnd(); | |
| 12002 } | |
| 12003 } | |
| 12004 } | |
| 12005 } | |
| 12006 }, | |
| 12007 | |
| 12008 /** | |
| 12009 * User should implement to introduce filtering when notifying children. | |
| 12010 * Generally, children that are hidden by the CoreResizer (e.g. non-active | |
| 12011 * pages) need not be notified during resize, since they will be notified | |
| 12012 * again when becoming un-hidden. | |
| 12013 * | |
| 12014 * Return `true` if CoreResizable passed as argument should be notified of | |
| 12015 * resize. | |
| 12016 * | |
| 12017 * @method resizeerShouldNotify | |
| 12018 * @param {Element} el | |
| 12019 */ | |
| 12020 // resizeerShouldNotify: function(el) { } // User to implement if needed | |
| 12021 | |
| 12022 /** | |
| 12023 * Set to `true` if the resizer is actually a peer to the elements it | |
| 12024 * resizes (e.g. splitter); in this case it will listen for resize requests | |
| 12025 * events from its peers on its parent. | |
| 12026 * | |
| 12027 * @property resizerIsPeer | |
| 12028 * @type Boolean | |
| 12029 * @default false | |
| 12030 */ | |
| 12031 | |
| 12032 // Private: Handle requests for resize | |
| 12033 _handleResizeRequested: function(e) { | |
| 12034 var target = e.path[0]; | |
| 12035 if ((target == this) || | |
| 12036 (target == this._resizerListener) || | |
| 12037 (this._resizerPeers && this._resizerPeers.indexOf(target) < 0)) { | |
| 12038 return; | |
| 12039 } | |
| 12040 // log('resize requested', target, this); | |
| 12041 if (!this.resizeRequestors) { | |
| 12042 this.resizeRequestors = []; | |
| 12043 } | |
| 12044 this.resizeRequestors.push({target: target, callback: e.detail.callback}); | |
| 12045 target.addEventListener('core-request-resize-cancel', this._cancelResizeRe
quested.bind(this)); | |
| 12046 e.detail.hasParentResizer = true; | |
| 12047 e.stopPropagation(); | |
| 12048 }, | |
| 12049 | |
| 12050 // Private: Handle cancellation requests for resize | |
| 12051 _cancelResizeRequested: function(e) { | |
| 12052 // Exit early if we're already out of the DOM (resizeRequestors will alrea
dy be null) | |
| 12053 if (this.resizeRequestors) { | |
| 12054 for (var i=0; i<this.resizeRequestors.length; i++) { | |
| 12055 if (this.resizeRequestors[i].target == e.target) { | |
| 12056 // log('resizeCanceled', e.target, this); | |
| 12057 this.resizeRequestors.splice(i, 1); | |
| 12058 break; | |
| 12059 } | |
| 12060 } | |
| 12061 } | |
| 12062 } | |
| 12063 | |
| 12064 }, Polymer.CoreResizable); | |
| 12065 | |
| 12066 // function prettyName(el) { | |
| 12067 // return el.localName + (el.id ? '#' : '') + el.id; | |
| 12068 // } | |
| 12069 | |
| 12070 // function log(what, from, to, group) { | |
| 12071 // var args = [what]; | |
| 12072 // if (from) { | |
| 12073 // args.push('from ' + prettyName(from)); | |
| 12074 // } | |
| 12075 // if (to) { | |
| 12076 // args.push('to ' + prettyName(to)); | |
| 12077 // } | |
| 12078 // if (group) { | |
| 12079 // console.group.apply(console, args); | |
| 12080 // } else { | |
| 12081 // console.log.apply(console, args); | |
| 12082 // } | |
| 12083 // } | |
| 12084 | |
| 12085 // function logEnd() { | |
| 12086 // console.groupEnd(); | |
| 12087 // } | |
| 12088 | |
| 12089 })(Polymer); | |
| 12090 | |
| 12091 ; | |
| 12092 Polymer.mixin2 = function(prototype, mixin) { | |
| 12093 | |
| 12094 // adds a single mixin to prototype | |
| 12095 | |
| 12096 if (mixin.mixinPublish) { | |
| 12097 prototype.publish = prototype.publish || {}; | |
| 12098 Polymer.mixin(prototype.publish, mixin.mixinPublish); | |
| 12099 } | |
| 12100 | |
| 12101 if (mixin.mixinDelegates) { | |
| 12102 prototype.eventDelegates = prototype.eventDelegates || {}; | |
| 12103 for (var e in mixin.mixinDelegates) { | |
| 12104 if (!prototype.eventDelegates[e]) { | |
| 12105 prototype.eventDelegates[e] = mixin.mixinDelegates[e]; | |
| 12106 } | |
| 12107 } | |
| 12108 } | |
| 12109 | |
| 12110 if (mixin.mixinObserve) { | |
| 12111 prototype.observe = prototype.observe || {}; | |
| 12112 for (var o in mixin.mixinObserve) { | |
| 12113 if (!prototype.observe[o] && !prototype[o + 'Changed']) { | |
| 12114 prototype.observe[o] = mixin.mixinObserve[o]; | |
| 12115 } | |
| 12116 } | |
| 12117 } | |
| 12118 | |
| 12119 Polymer.mixin(prototype, mixin); | |
| 12120 | |
| 12121 delete prototype.mixinPublish; | |
| 12122 delete prototype.mixinDelegates; | |
| 12123 delete prototype.mixinObserve; | |
| 12124 | |
| 12125 return prototype; | |
| 12126 };; | |
| 12127 /** | |
| 12128 * @group Polymer Mixins | |
| 12129 * | |
| 12130 * `Polymer.CoreFocusable` is a mixin for elements that the user can interact wi
th. | |
| 12131 * Elements using this mixin will receive attributes reflecting the focus, press
ed | |
| 12132 * and disabled states. | |
| 12133 * | |
| 12134 * @element Polymer.CoreFocusable | |
| 12135 * @status unstable | |
| 12136 */ | |
| 12137 | |
| 12138 Polymer.CoreFocusable = { | |
| 12139 | |
| 12140 mixinPublish: { | |
| 12141 | |
| 12142 /** | |
| 12143 * If true, the element is currently active either because the | |
| 12144 * user is touching it, or the button is a toggle | |
| 12145 * and is currently in the active state. | |
| 12146 * | |
| 12147 * @attribute active | |
| 12148 * @type boolean | |
| 12149 * @default false | |
| 12150 */ | |
| 12151 active: {value: false, reflect: true}, | |
| 12152 | |
| 12153 /** | |
| 12154 * If true, the element currently has focus due to keyboard | |
| 12155 * navigation. | |
| 12156 * | |
| 12157 * @attribute focused | |
| 12158 * @type boolean | |
| 12159 * @default false | |
| 12160 */ | |
| 12161 focused: {value: false, reflect: true}, | |
| 12162 | |
| 12163 /** | |
| 12164 * If true, the user is currently holding down the button. | |
| 12165 * | |
| 12166 * @attribute pressed | |
| 12167 * @type boolean | |
| 12168 * @default false | |
| 12169 */ | |
| 12170 pressed: {value: false, reflect: true}, | |
| 12171 | |
| 12172 /** | |
| 12173 * If true, the user cannot interact with this element. | |
| 12174 * | |
| 12175 * @attribute disabled | |
| 12176 * @type boolean | |
| 12177 * @default false | |
| 12178 */ | |
| 12179 disabled: {value: false, reflect: true}, | |
| 12180 | |
| 12181 /** | |
| 12182 * If true, the button toggles the active state with each tap. | |
| 12183 * Otherwise, the button becomes active when the user is holding | |
| 12184 * it down. | |
| 12185 * | |
| 12186 * @attribute toggle | |
| 12187 * @type boolean | |
| 12188 * @default false | |
| 12189 */ | |
| 12190 toggle: false | |
| 12191 | |
| 12192 }, | |
| 12193 | |
| 12194 mixinDelegates: { | |
| 12195 contextMenu: '_contextMenuAction', | |
| 12196 down: '_downAction', | |
| 12197 up: '_upAction', | |
| 12198 focus: '_focusAction', | |
| 12199 blur: '_blurAction' | |
| 12200 }, | |
| 12201 | |
| 12202 mixinObserve: { | |
| 12203 disabled: '_disabledChanged' | |
| 12204 }, | |
| 12205 | |
| 12206 _disabledChanged: function() { | |
| 12207 if (this.disabled) { | |
| 12208 this.style.pointerEvents = 'none'; | |
| 12209 this.removeAttribute('tabindex'); | |
| 12210 this.setAttribute('aria-disabled', ''); | |
| 12211 } else { | |
| 12212 this.style.pointerEvents = ''; | |
| 12213 this.setAttribute('tabindex', 0); | |
| 12214 this.removeAttribute('aria-disabled'); | |
| 12215 } | |
| 12216 }, | |
| 12217 | |
| 12218 _downAction: function() { | |
| 12219 this.pressed = true; | |
| 12220 | |
| 12221 if (this.toggle) { | |
| 12222 this.active = !this.active; | |
| 12223 } else { | |
| 12224 this.active = true; | |
| 12225 } | |
| 12226 }, | |
| 12227 | |
| 12228 // Pulling up the context menu for an item should focus it; but we need to | |
| 12229 // be careful about how we deal with down/up events surrounding context | |
| 12230 // menus. The up event typically does not fire until the context menu | |
| 12231 // closes: so we focus immediately. | |
| 12232 // | |
| 12233 // This fires _after_ downAction. | |
| 12234 _contextMenuAction: function(e) { | |
| 12235 // Note that upAction may fire _again_ on the actual up event. | |
| 12236 this._upAction(e); | |
| 12237 this._focusAction(); | |
| 12238 }, | |
| 12239 | |
| 12240 _upAction: function() { | |
| 12241 this.pressed = false; | |
| 12242 | |
| 12243 if (!this.toggle) { | |
| 12244 this.active = false; | |
| 12245 } | |
| 12246 }, | |
| 12247 | |
| 12248 _focusAction: function() { | |
| 12249 if (!this.pressed) { | |
| 12250 // Only render the "focused" state if the element gains focus due to | |
| 12251 // keyboard navigation. | |
| 12252 this.focused = true; | |
| 12253 } | |
| 12254 }, | |
| 12255 | |
| 12256 _blurAction: function() { | |
| 12257 this.focused = false; | |
| 12258 } | |
| 12259 | |
| 12260 } | |
| 12261 ; | |
| 12262 | |
| 12263 | |
| 12264 Polymer('core-xhr', { | |
| 12265 | |
| 12266 /** | |
| 12267 * Sends a HTTP request to the server and returns the XHR object. | |
| 12268 * | |
| 12269 * @method request | |
| 12270 * @param {Object} inOptions | |
| 12271 * @param {String} inOptions.url The url to which the request is sent. | |
| 12272 * @param {String} inOptions.method The HTTP method to use, default is
GET. | |
| 12273 * @param {boolean} inOptions.sync By default, all requests are sent as
ynchronously. To send synchronous requests, set to true. | |
| 12274 * @param {Object} inOptions.params Data to be sent to the server. | |
| 12275 * @param {Object} inOptions.body The content for the request body for
POST method. | |
| 12276 * @param {Object} inOptions.headers HTTP request headers. | |
| 12277 * @param {String} inOptions.responseType The response type. Default is
'text'. | |
| 12278 * @param {boolean} inOptions.withCredentials Whether or not to send cr
edentials on the request. Default is false. | |
| 12279 * @param {Object} inOptions.callback Called when request is completed. | |
| 12280 * @returns {Object} XHR object. | |
| 12281 */ | |
| 12282 request: function(options) { | |
| 12283 var xhr = new XMLHttpRequest(); | |
| 12284 var url = options.url; | |
| 12285 var method = options.method || 'GET'; | |
| 12286 var async = !options.sync; | |
| 12287 // | |
| 12288 var params = this.toQueryString(options.params); | |
| 12289 if (params && method.toUpperCase() == 'GET') { | |
| 12290 url += (url.indexOf('?') > 0 ? '&' : '?') + params; | |
| 12291 } | |
| 12292 var xhrParams = this.isBodyMethod(method) ? (options.body || params) : n
ull; | |
| 12293 // | |
| 12294 xhr.open(method, url, async); | |
| 12295 if (options.responseType) { | |
| 12296 xhr.responseType = options.responseType; | |
| 12297 } | |
| 12298 if (options.withCredentials) { | |
| 12299 xhr.withCredentials = true; | |
| 12300 } | |
| 12301 this.makeReadyStateHandler(xhr, options.callback); | |
| 12302 this.setRequestHeaders(xhr, options.headers); | |
| 12303 xhr.send(xhrParams); | |
| 12304 if (!async) { | |
| 12305 xhr.onreadystatechange(xhr); | |
| 12306 } | |
| 12307 return xhr; | |
| 12308 }, | |
| 12309 | |
| 12310 toQueryString: function(params) { | |
| 12311 var r = []; | |
| 12312 for (var n in params) { | |
| 12313 var v = params[n]; | |
| 12314 n = encodeURIComponent(n); | |
| 12315 r.push(v == null ? n : (n + '=' + encodeURIComponent(v))); | |
| 12316 } | |
| 12317 return r.join('&'); | |
| 12318 }, | |
| 12319 | |
| 12320 isBodyMethod: function(method) { | |
| 12321 return this.bodyMethods[(method || '').toUpperCase()]; | |
| 12322 }, | |
| 12323 | |
| 12324 bodyMethods: { | |
| 12325 POST: 1, | |
| 12326 PUT: 1, | |
| 12327 PATCH: 1, | |
| 12328 DELETE: 1 | |
| 12329 }, | |
| 12330 | |
| 12331 makeReadyStateHandler: function(xhr, callback) { | |
| 12332 xhr.onreadystatechange = function() { | |
| 12333 if (xhr.readyState == 4) { | |
| 12334 callback && callback.call(null, xhr.response, xhr); | |
| 12335 } | |
| 12336 }; | |
| 12337 }, | |
| 12338 | |
| 12339 setRequestHeaders: function(xhr, headers) { | |
| 12340 if (headers) { | |
| 12341 for (var name in headers) { | |
| 12342 xhr.setRequestHeader(name, headers[name]); | |
| 12343 } | |
| 12344 } | |
| 12345 } | |
| 12346 | |
| 12347 }); | |
| 12348 | |
| 12349 ; | |
| 12350 | |
| 12351 | |
| 12352 Polymer('core-ajax', { | |
| 12353 /** | |
| 12354 * Fired when a response is received. | |
| 12355 * | |
| 12356 * @event core-response | |
| 12357 */ | |
| 12358 | |
| 12359 /** | |
| 12360 * Fired when an error is received. | |
| 12361 * | |
| 12362 * @event core-error | |
| 12363 */ | |
| 12364 | |
| 12365 /** | |
| 12366 * Fired whenever a response or an error is received. | |
| 12367 * | |
| 12368 * @event core-complete | |
| 12369 */ | |
| 12370 | |
| 12371 /** | |
| 12372 * The URL target of the request. | |
| 12373 * | |
| 12374 * @attribute url | |
| 12375 * @type string | |
| 12376 * @default '' | |
| 12377 */ | |
| 12378 url: '', | |
| 12379 | |
| 12380 /** | |
| 12381 * Specifies what data to store in the `response` property, and | |
| 12382 * to deliver as `event.response` in `response` events. | |
| 12383 * | |
| 12384 * One of: | |
| 12385 * | |
| 12386 * `text`: uses `XHR.responseText`. | |
| 12387 * | |
| 12388 * `xml`: uses `XHR.responseXML`. | |
| 12389 * | |
| 12390 * `json`: uses `XHR.responseText` parsed as JSON. | |
| 12391 * | |
| 12392 * `arraybuffer`: uses `XHR.response`. | |
| 12393 * | |
| 12394 * `blob`: uses `XHR.response`. | |
| 12395 * | |
| 12396 * `document`: uses `XHR.response`. | |
| 12397 * | |
| 12398 * @attribute handleAs | |
| 12399 * @type string | |
| 12400 * @default 'text' | |
| 12401 */ | |
| 12402 handleAs: '', | |
| 12403 | |
| 12404 /** | |
| 12405 * If true, automatically performs an Ajax request when either `url` or `par
ams` changes. | |
| 12406 * | |
| 12407 * @attribute auto | |
| 12408 * @type boolean | |
| 12409 * @default false | |
| 12410 */ | |
| 12411 auto: false, | |
| 12412 | |
| 12413 /** | |
| 12414 * Parameters to send to the specified URL, as JSON. | |
| 12415 * | |
| 12416 * @attribute params | |
| 12417 * @type string | |
| 12418 * @default '' | |
| 12419 */ | |
| 12420 params: '', | |
| 12421 | |
| 12422 /** | |
| 12423 * The response for the current request, or null if it hasn't | |
| 12424 * completed yet or the request resulted in error. | |
| 12425 * | |
| 12426 * @attribute response | |
| 12427 * @type Object | |
| 12428 * @default null | |
| 12429 */ | |
| 12430 response: null, | |
| 12431 | |
| 12432 /** | |
| 12433 * The error for the current request, or null if it hasn't | |
| 12434 * completed yet or the request resulted in success. | |
| 12435 * | |
| 12436 * @attribute error | |
| 12437 * @type Object | |
| 12438 * @default null | |
| 12439 */ | |
| 12440 error: null, | |
| 12441 | |
| 12442 /** | |
| 12443 * Whether the current request is currently loading. | |
| 12444 * | |
| 12445 * @attribute loading | |
| 12446 * @type boolean | |
| 12447 * @default false | |
| 12448 */ | |
| 12449 loading: false, | |
| 12450 | |
| 12451 /** | |
| 12452 * The progress of the current request. | |
| 12453 * | |
| 12454 * @attribute progress | |
| 12455 * @type {loaded: number, total: number, lengthComputable: boolean} | |
| 12456 * @default {} | |
| 12457 */ | |
| 12458 progress: null, | |
| 12459 | |
| 12460 /** | |
| 12461 * The HTTP method to use such as 'GET', 'POST', 'PUT', or 'DELETE'. | |
| 12462 * Default is 'GET'. | |
| 12463 * | |
| 12464 * @attribute method | |
| 12465 * @type string | |
| 12466 * @default '' | |
| 12467 */ | |
| 12468 method: '', | |
| 12469 | |
| 12470 /** | |
| 12471 * HTTP request headers to send. | |
| 12472 * | |
| 12473 * Example: | |
| 12474 * | |
| 12475 * <core-ajax | |
| 12476 * auto | |
| 12477 * url="http://somesite.com" | |
| 12478 * headers='{"X-Requested-With": "XMLHttpRequest"}' | |
| 12479 * handleAs="json" | |
| 12480 * on-core-response="{{handleResponse}}"></core-ajax> | |
| 12481 * | |
| 12482 * @attribute headers | |
| 12483 * @type Object | |
| 12484 * @default null | |
| 12485 */ | |
| 12486 headers: null, | |
| 12487 | |
| 12488 /** | |
| 12489 * Optional raw body content to send when method === "POST". | |
| 12490 * | |
| 12491 * Example: | |
| 12492 * | |
| 12493 * <core-ajax method="POST" auto url="http://somesite.com" | |
| 12494 * body='{"foo":1, "bar":2}'> | |
| 12495 * </core-ajax> | |
| 12496 * | |
| 12497 * @attribute body | |
| 12498 * @type Object | |
| 12499 * @default null | |
| 12500 */ | |
| 12501 body: null, | |
| 12502 | |
| 12503 /** | |
| 12504 * Content type to use when sending data. | |
| 12505 * | |
| 12506 * @attribute contentType | |
| 12507 * @type string | |
| 12508 * @default 'application/x-www-form-urlencoded' | |
| 12509 */ | |
| 12510 contentType: 'application/x-www-form-urlencoded', | |
| 12511 | |
| 12512 /** | |
| 12513 * Set the withCredentials flag on the request. | |
| 12514 * | |
| 12515 * @attribute withCredentials | |
| 12516 * @type boolean | |
| 12517 * @default false | |
| 12518 */ | |
| 12519 withCredentials: false, | |
| 12520 | |
| 12521 /** | |
| 12522 * Additional properties to send to core-xhr. | |
| 12523 * | |
| 12524 * Can be set to an object containing default properties | |
| 12525 * to send as arguments to the `core-xhr.request()` method | |
| 12526 * which implements the low-level communication. | |
| 12527 * | |
| 12528 * @property xhrArgs | |
| 12529 * @type Object | |
| 12530 * @default null | |
| 12531 */ | |
| 12532 xhrArgs: null, | |
| 12533 | |
| 12534 created: function() { | |
| 12535 this.progress = {}; | |
| 12536 }, | |
| 12537 | |
| 12538 ready: function() { | |
| 12539 this.xhr = document.createElement('core-xhr'); | |
| 12540 }, | |
| 12541 | |
| 12542 receive: function(response, xhr) { | |
| 12543 if (this.isSuccess(xhr)) { | |
| 12544 this.processResponse(xhr); | |
| 12545 } else { | |
| 12546 this.processError(xhr); | |
| 12547 } | |
| 12548 this.complete(xhr); | |
| 12549 }, | |
| 12550 | |
| 12551 isSuccess: function(xhr) { | |
| 12552 var status = xhr.status || 0; | |
| 12553 return !status || (status >= 200 && status < 300); | |
| 12554 }, | |
| 12555 | |
| 12556 processResponse: function(xhr) { | |
| 12557 var response = this.evalResponse(xhr); | |
| 12558 if (xhr === this.activeRequest) { | |
| 12559 this.response = response; | |
| 12560 } | |
| 12561 this.fire('core-response', {response: response, xhr: xhr}); | |
| 12562 }, | |
| 12563 | |
| 12564 processError: function(xhr) { | |
| 12565 var response = xhr.status + ': ' + xhr.responseText; | |
| 12566 if (xhr === this.activeRequest) { | |
| 12567 this.error = response; | |
| 12568 } | |
| 12569 this.fire('core-error', {response: response, xhr: xhr}); | |
| 12570 }, | |
| 12571 | |
| 12572 processProgress: function(progress, xhr) { | |
| 12573 if (xhr !== this.activeRequest) { | |
| 12574 return; | |
| 12575 } | |
| 12576 // We create a proxy object here because these fields | |
| 12577 // on the progress event are readonly properties, which | |
| 12578 // causes problems in common use cases (e.g. binding to | |
| 12579 // <paper-progress> attributes). | |
| 12580 var progressProxy = { | |
| 12581 lengthComputable: progress.lengthComputable, | |
| 12582 loaded: progress.loaded, | |
| 12583 total: progress.total | |
| 12584 } | |
| 12585 this.progress = progressProxy; | |
| 12586 }, | |
| 12587 | |
| 12588 complete: function(xhr) { | |
| 12589 if (xhr === this.activeRequest) { | |
| 12590 this.loading = false; | |
| 12591 } | |
| 12592 this.fire('core-complete', {response: xhr.status, xhr: xhr}); | |
| 12593 }, | |
| 12594 | |
| 12595 evalResponse: function(xhr) { | |
| 12596 return this[(this.handleAs || 'text') + 'Handler'](xhr); | |
| 12597 }, | |
| 12598 | |
| 12599 xmlHandler: function(xhr) { | |
| 12600 return xhr.responseXML; | |
| 12601 }, | |
| 12602 | |
| 12603 textHandler: function(xhr) { | |
| 12604 return xhr.responseText; | |
| 12605 }, | |
| 12606 | |
| 12607 jsonHandler: function(xhr) { | |
| 12608 var r = xhr.responseText; | |
| 12609 try { | |
| 12610 return JSON.parse(r); | |
| 12611 } catch (x) { | |
| 12612 console.warn('core-ajax caught an exception trying to parse response as
JSON:'); | |
| 12613 console.warn('url:', this.url); | |
| 12614 console.warn(x); | |
| 12615 return r; | |
| 12616 } | |
| 12617 }, | |
| 12618 | |
| 12619 documentHandler: function(xhr) { | |
| 12620 return xhr.response; | |
| 12621 }, | |
| 12622 | |
| 12623 blobHandler: function(xhr) { | |
| 12624 return xhr.response; | |
| 12625 }, | |
| 12626 | |
| 12627 arraybufferHandler: function(xhr) { | |
| 12628 return xhr.response; | |
| 12629 }, | |
| 12630 | |
| 12631 urlChanged: function() { | |
| 12632 if (!this.handleAs) { | |
| 12633 var ext = String(this.url).split('.').pop(); | |
| 12634 switch (ext) { | |
| 12635 case 'json': | |
| 12636 this.handleAs = 'json'; | |
| 12637 break; | |
| 12638 } | |
| 12639 } | |
| 12640 this.autoGo(); | |
| 12641 }, | |
| 12642 | |
| 12643 paramsChanged: function() { | |
| 12644 this.autoGo(); | |
| 12645 }, | |
| 12646 | |
| 12647 bodyChanged: function() { | |
| 12648 this.autoGo(); | |
| 12649 }, | |
| 12650 | |
| 12651 autoChanged: function() { | |
| 12652 this.autoGo(); | |
| 12653 }, | |
| 12654 | |
| 12655 // TODO(sorvell): multiple side-effects could call autoGo | |
| 12656 // during one micro-task, use a job to have only one action | |
| 12657 // occur | |
| 12658 autoGo: function() { | |
| 12659 if (this.auto) { | |
| 12660 this.goJob = this.job(this.goJob, this.go, 0); | |
| 12661 } | |
| 12662 }, | |
| 12663 | |
| 12664 getParams: function(params) { | |
| 12665 params = this.params || params; | |
| 12666 if (params && typeof(params) == 'string') { | |
| 12667 params = JSON.parse(params); | |
| 12668 } | |
| 12669 return params; | |
| 12670 }, | |
| 12671 | |
| 12672 /** | |
| 12673 * Performs an Ajax request to the specified URL. | |
| 12674 * | |
| 12675 * @method go | |
| 12676 */ | |
| 12677 go: function() { | |
| 12678 var args = this.xhrArgs || {}; | |
| 12679 // TODO(sjmiles): we may want XHR to default to POST if body is set | |
| 12680 args.body = this.body || args.body; | |
| 12681 args.params = this.getParams(args.params); | |
| 12682 args.headers = this.headers || args.headers || {}; | |
| 12683 if (args.headers && typeof(args.headers) == 'string') { | |
| 12684 args.headers = JSON.parse(args.headers); | |
| 12685 } | |
| 12686 var hasContentType = Object.keys(args.headers).some(function (header) { | |
| 12687 return header.toLowerCase() === 'content-type'; | |
| 12688 }); | |
| 12689 // No Content-Type should be specified if sending `FormData`. | |
| 12690 // The UA must set the Content-Type w/ a calculated multipart boundary ID
. | |
| 12691 if (args.body instanceof FormData) { | |
| 12692 delete args.headers['Content-Type']; | |
| 12693 } | |
| 12694 else if (!hasContentType && this.contentType) { | |
| 12695 args.headers['Content-Type'] = this.contentType; | |
| 12696 } | |
| 12697 if (this.handleAs === 'arraybuffer' || this.handleAs === 'blob' || | |
| 12698 this.handleAs === 'document') { | |
| 12699 args.responseType = this.handleAs; | |
| 12700 } | |
| 12701 args.withCredentials = this.withCredentials; | |
| 12702 args.callback = this.receive.bind(this); | |
| 12703 args.url = this.url; | |
| 12704 args.method = this.method; | |
| 12705 | |
| 12706 this.response = this.error = this.progress = null; | |
| 12707 this.activeRequest = args.url && this.xhr.request(args); | |
| 12708 if (this.activeRequest) { | |
| 12709 this.loading = true; | |
| 12710 var activeRequest = this.activeRequest; | |
| 12711 // IE < 10 doesn't support progress events. | |
| 12712 if ('onprogress' in activeRequest) { | |
| 12713 this.activeRequest.addEventListener( | |
| 12714 'progress', | |
| 12715 function(progress) { | |
| 12716 this.processProgress(progress, activeRequest); | |
| 12717 }.bind(this), false); | |
| 12718 } else { | |
| 12719 this.progress = { | |
| 12720 lengthComputable: false, | |
| 12721 } | |
| 12722 } | |
| 12723 } | |
| 12724 return this.activeRequest; | |
| 12725 }, | |
| 12726 | |
| 12727 /** | |
| 12728 * Aborts the current active request if there is one and resets internal | |
| 12729 * state appropriately. | |
| 12730 * | |
| 12731 * @method abort | |
| 12732 */ | |
| 12733 abort: function() { | |
| 12734 if (!this.activeRequest) return; | |
| 12735 this.activeRequest.abort(); | |
| 12736 this.activeRequest = null; | |
| 12737 this.progress = {}; | |
| 12738 this.loading = false; | |
| 12739 } | |
| 12740 | |
| 12741 }); | |
| 12742 | |
| 12743 ; | |
| 12744 | |
| 12745 (function() { | |
| 12746 | |
| 12747 var ID = 0; | |
| 12748 function generate(node) { | |
| 12749 if (!node.id) { | |
| 12750 node.id = 'core-label-' + ID++; | |
| 12751 } | |
| 12752 return node.id; | |
| 12753 } | |
| 12754 | |
| 12755 Polymer('core-label', { | |
| 12756 /** | |
| 12757 * A query selector string for a "target" element not nested in the `<co
re-label>` | |
| 12758 * | |
| 12759 * @attribute for | |
| 12760 * @type string | |
| 12761 * @default '' | |
| 12762 */ | |
| 12763 publish: { | |
| 12764 'for': {reflect: true, value: ''} | |
| 12765 }, | |
| 12766 eventDelegates: { | |
| 12767 'tap': 'tapHandler' | |
| 12768 }, | |
| 12769 created: function() { | |
| 12770 generate(this); | |
| 12771 this._forElement = null; | |
| 12772 }, | |
| 12773 ready: function() { | |
| 12774 if (!this.for) { | |
| 12775 this._forElement = this.querySelector('[for]'); | |
| 12776 this._tie(); | |
| 12777 } | |
| 12778 }, | |
| 12779 tapHandler: function(ev) { | |
| 12780 if (!this._forElement) { | |
| 12781 return; | |
| 12782 } | |
| 12783 if (ev.target === this._forElement) { | |
| 12784 return; | |
| 12785 } | |
| 12786 this._forElement.focus(); | |
| 12787 this._forElement.click(); | |
| 12788 this.fire('tap', null, this._forElement); | |
| 12789 }, | |
| 12790 _tie: function() { | |
| 12791 if (this._forElement) { | |
| 12792 this._forElement.setAttribute('aria-labelledby', this.id); | |
| 12793 } | |
| 12794 }, | |
| 12795 _findScope: function() { | |
| 12796 var n = this.parentNode; | |
| 12797 while(n && n.parentNode) { | |
| 12798 n = n.parentNode; | |
| 12799 } | |
| 12800 return n; | |
| 12801 }, | |
| 12802 forChanged: function(oldFor, newFor) { | |
| 12803 if (this._forElement) { | |
| 12804 this._forElement.removeAttribute('aria-labelledby'); | |
| 12805 } | |
| 12806 var scope = this._findScope(); | |
| 12807 if (!scope) { | |
| 12808 return; | |
| 12809 } | |
| 12810 this._forElement = scope.querySelector(newFor); | |
| 12811 if (this._forElement) { | |
| 12812 this._tie(); | |
| 12813 } | |
| 12814 } | |
| 12815 }); | |
| 12816 })(); | |
| 12817 ; | |
| 12818 | |
| 12819 | |
| 12820 (function() { | |
| 12821 | |
| 12822 var waveMaxRadius = 150; | |
| 12823 // | |
| 12824 // INK EQUATIONS | |
| 12825 // | |
| 12826 function waveRadiusFn(touchDownMs, touchUpMs, anim) { | |
| 12827 // Convert from ms to s | |
| 12828 var touchDown = touchDownMs / 1000; | |
| 12829 var touchUp = touchUpMs / 1000; | |
| 12830 var totalElapsed = touchDown + touchUp; | |
| 12831 var ww = anim.width, hh = anim.height; | |
| 12832 // use diagonal size of container to avoid floating point math sadness | |
| 12833 var waveRadius = Math.min(Math.sqrt(ww * ww + hh * hh), waveMaxRadius) * 1
.1 + 5; | |
| 12834 var duration = 1.1 - .2 * (waveRadius / waveMaxRadius); | |
| 12835 var tt = (totalElapsed / duration); | |
| 12836 | |
| 12837 var size = waveRadius * (1 - Math.pow(80, -tt)); | |
| 12838 return Math.abs(size); | |
| 12839 } | |
| 12840 | |
| 12841 function waveOpacityFn(td, tu, anim) { | |
| 12842 // Convert from ms to s. | |
| 12843 var touchDown = td / 1000; | |
| 12844 var touchUp = tu / 1000; | |
| 12845 var totalElapsed = touchDown + touchUp; | |
| 12846 | |
| 12847 if (tu <= 0) { // before touch up | |
| 12848 return anim.initialOpacity; | |
| 12849 } | |
| 12850 return Math.max(0, anim.initialOpacity - touchUp * anim.opacityDecayVeloci
ty); | |
| 12851 } | |
| 12852 | |
| 12853 function waveOuterOpacityFn(td, tu, anim) { | |
| 12854 // Convert from ms to s. | |
| 12855 var touchDown = td / 1000; | |
| 12856 var touchUp = tu / 1000; | |
| 12857 | |
| 12858 // Linear increase in background opacity, capped at the opacity | |
| 12859 // of the wavefront (waveOpacity). | |
| 12860 var outerOpacity = touchDown * 0.3; | |
| 12861 var waveOpacity = waveOpacityFn(td, tu, anim); | |
| 12862 return Math.max(0, Math.min(outerOpacity, waveOpacity)); | |
| 12863 } | |
| 12864 | |
| 12865 // Determines whether the wave should be completely removed. | |
| 12866 function waveDidFinish(wave, radius, anim) { | |
| 12867 var waveOpacity = waveOpacityFn(wave.tDown, wave.tUp, anim); | |
| 12868 | |
| 12869 // If the wave opacity is 0 and the radius exceeds the bounds | |
| 12870 // of the element, then this is finished. | |
| 12871 return waveOpacity < 0.01 && radius >= Math.min(wave.maxRadius, waveMaxRad
ius); | |
| 12872 }; | |
| 12873 | |
| 12874 function waveAtMaximum(wave, radius, anim) { | |
| 12875 var waveOpacity = waveOpacityFn(wave.tDown, wave.tUp, anim); | |
| 12876 | |
| 12877 return waveOpacity >= anim.initialOpacity && radius >= Math.min(wave.maxRa
dius, waveMaxRadius); | |
| 12878 } | |
| 12879 | |
| 12880 // | |
| 12881 // DRAWING | |
| 12882 // | |
| 12883 function drawRipple(ctx, x, y, radius, innerAlpha, outerAlpha) { | |
| 12884 // Only animate opacity and transform | |
| 12885 if (outerAlpha !== undefined) { | |
| 12886 ctx.bg.style.opacity = outerAlpha; | |
| 12887 } | |
| 12888 ctx.wave.style.opacity = innerAlpha; | |
| 12889 | |
| 12890 var s = radius / (ctx.containerSize / 2); | |
| 12891 var dx = x - (ctx.containerWidth / 2); | |
| 12892 var dy = y - (ctx.containerHeight / 2); | |
| 12893 | |
| 12894 ctx.wc.style.webkitTransform = 'translate3d(' + dx + 'px,' + dy + 'px,0)'; | |
| 12895 ctx.wc.style.transform = 'translate3d(' + dx + 'px,' + dy + 'px,0)'; | |
| 12896 | |
| 12897 // 2d transform for safari because of border-radius and overflow:hidden cl
ipping bug. | |
| 12898 // https://bugs.webkit.org/show_bug.cgi?id=98538 | |
| 12899 ctx.wave.style.webkitTransform = 'scale(' + s + ',' + s + ')'; | |
| 12900 ctx.wave.style.transform = 'scale3d(' + s + ',' + s + ',1)'; | |
| 12901 } | |
| 12902 | |
| 12903 // | |
| 12904 // SETUP | |
| 12905 // | |
| 12906 function createWave(elem) { | |
| 12907 var elementStyle = window.getComputedStyle(elem); | |
| 12908 var fgColor = elementStyle.color; | |
| 12909 | |
| 12910 var inner = document.createElement('div'); | |
| 12911 inner.style.backgroundColor = fgColor; | |
| 12912 inner.classList.add('wave'); | |
| 12913 | |
| 12914 var outer = document.createElement('div'); | |
| 12915 outer.classList.add('wave-container'); | |
| 12916 outer.appendChild(inner); | |
| 12917 | |
| 12918 var container = elem.$.waves; | |
| 12919 container.appendChild(outer); | |
| 12920 | |
| 12921 elem.$.bg.style.backgroundColor = fgColor; | |
| 12922 | |
| 12923 var wave = { | |
| 12924 bg: elem.$.bg, | |
| 12925 wc: outer, | |
| 12926 wave: inner, | |
| 12927 waveColor: fgColor, | |
| 12928 maxRadius: 0, | |
| 12929 isMouseDown: false, | |
| 12930 mouseDownStart: 0.0, | |
| 12931 mouseUpStart: 0.0, | |
| 12932 tDown: 0, | |
| 12933 tUp: 0 | |
| 12934 }; | |
| 12935 return wave; | |
| 12936 } | |
| 12937 | |
| 12938 function removeWaveFromScope(scope, wave) { | |
| 12939 if (scope.waves) { | |
| 12940 var pos = scope.waves.indexOf(wave); | |
| 12941 scope.waves.splice(pos, 1); | |
| 12942 // FIXME cache nodes | |
| 12943 wave.wc.remove(); | |
| 12944 } | |
| 12945 }; | |
| 12946 | |
| 12947 // Shortcuts. | |
| 12948 var pow = Math.pow; | |
| 12949 var now = Date.now; | |
| 12950 if (window.performance && performance.now) { | |
| 12951 now = performance.now.bind(performance); | |
| 12952 } | |
| 12953 | |
| 12954 function cssColorWithAlpha(cssColor, alpha) { | |
| 12955 var parts = cssColor.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/); | |
| 12956 if (typeof alpha == 'undefined') { | |
| 12957 alpha = 1; | |
| 12958 } | |
| 12959 if (!parts) { | |
| 12960 return 'rgba(255, 255, 255, ' + alpha + ')'; | |
| 12961 } | |
| 12962 return 'rgba(' + parts[1] + ', ' + parts[2] + ', ' + parts[3] + ', ' + a
lpha + ')'; | |
| 12963 } | |
| 12964 | |
| 12965 function dist(p1, p2) { | |
| 12966 return Math.sqrt(pow(p1.x - p2.x, 2) + pow(p1.y - p2.y, 2)); | |
| 12967 } | |
| 12968 | |
| 12969 function distanceFromPointToFurthestCorner(point, size) { | |
| 12970 var tl_d = dist(point, {x: 0, y: 0}); | |
| 12971 var tr_d = dist(point, {x: size.w, y: 0}); | |
| 12972 var bl_d = dist(point, {x: 0, y: size.h}); | |
| 12973 var br_d = dist(point, {x: size.w, y: size.h}); | |
| 12974 return Math.max(tl_d, tr_d, bl_d, br_d); | |
| 12975 } | |
| 12976 | |
| 12977 Polymer('paper-ripple', { | |
| 12978 | |
| 12979 /** | |
| 12980 * The initial opacity set on the wave. | |
| 12981 * | |
| 12982 * @attribute initialOpacity | |
| 12983 * @type number | |
| 12984 * @default 0.25 | |
| 12985 */ | |
| 12986 initialOpacity: 0.25, | |
| 12987 | |
| 12988 /** | |
| 12989 * How fast (opacity per second) the wave fades out. | |
| 12990 * | |
| 12991 * @attribute opacityDecayVelocity | |
| 12992 * @type number | |
| 12993 * @default 0.8 | |
| 12994 */ | |
| 12995 opacityDecayVelocity: 0.8, | |
| 12996 | |
| 12997 backgroundFill: true, | |
| 12998 pixelDensity: 2, | |
| 12999 | |
| 13000 eventDelegates: { | |
| 13001 down: 'downAction', | |
| 13002 up: 'upAction' | |
| 13003 }, | |
| 13004 | |
| 13005 ready: function() { | |
| 13006 this.waves = []; | |
| 13007 }, | |
| 13008 | |
| 13009 downAction: function(e) { | |
| 13010 var wave = createWave(this); | |
| 13011 | |
| 13012 this.cancelled = false; | |
| 13013 wave.isMouseDown = true; | |
| 13014 wave.tDown = 0.0; | |
| 13015 wave.tUp = 0.0; | |
| 13016 wave.mouseUpStart = 0.0; | |
| 13017 wave.mouseDownStart = now(); | |
| 13018 | |
| 13019 var rect = this.getBoundingClientRect(); | |
| 13020 var width = rect.width; | |
| 13021 var height = rect.height; | |
| 13022 var touchX = e.x - rect.left; | |
| 13023 var touchY = e.y - rect.top; | |
| 13024 | |
| 13025 wave.startPosition = {x:touchX, y:touchY}; | |
| 13026 | |
| 13027 if (this.classList.contains("recenteringTouch")) { | |
| 13028 wave.endPosition = {x: width / 2, y: height / 2}; | |
| 13029 wave.slideDistance = dist(wave.startPosition, wave.endPosition); | |
| 13030 } | |
| 13031 wave.containerSize = Math.max(width, height); | |
| 13032 wave.containerWidth = width; | |
| 13033 wave.containerHeight = height; | |
| 13034 wave.maxRadius = distanceFromPointToFurthestCorner(wave.startPosition, {
w: width, h: height}); | |
| 13035 | |
| 13036 // The wave is circular so constrain its container to 1:1 | |
| 13037 wave.wc.style.top = (wave.containerHeight - wave.containerSize) / 2 + 'p
x'; | |
| 13038 wave.wc.style.left = (wave.containerWidth - wave.containerSize) / 2 + 'p
x'; | |
| 13039 wave.wc.style.width = wave.containerSize + 'px'; | |
| 13040 wave.wc.style.height = wave.containerSize + 'px'; | |
| 13041 | |
| 13042 this.waves.push(wave); | |
| 13043 | |
| 13044 if (!this._loop) { | |
| 13045 this._loop = this.animate.bind(this, { | |
| 13046 width: width, | |
| 13047 height: height | |
| 13048 }); | |
| 13049 requestAnimationFrame(this._loop); | |
| 13050 } | |
| 13051 // else there is already a rAF | |
| 13052 }, | |
| 13053 | |
| 13054 upAction: function() { | |
| 13055 for (var i = 0; i < this.waves.length; i++) { | |
| 13056 // Declare the next wave that has mouse down to be mouse'ed up. | |
| 13057 var wave = this.waves[i]; | |
| 13058 if (wave.isMouseDown) { | |
| 13059 wave.isMouseDown = false | |
| 13060 wave.mouseUpStart = now(); | |
| 13061 wave.mouseDownStart = 0; | |
| 13062 wave.tUp = 0.0; | |
| 13063 break; | |
| 13064 } | |
| 13065 } | |
| 13066 this._loop && requestAnimationFrame(this._loop); | |
| 13067 }, | |
| 13068 | |
| 13069 cancel: function() { | |
| 13070 this.cancelled = true; | |
| 13071 }, | |
| 13072 | |
| 13073 animate: function(ctx) { | |
| 13074 var shouldRenderNextFrame = false; | |
| 13075 | |
| 13076 var deleteTheseWaves = []; | |
| 13077 // The oldest wave's touch down duration | |
| 13078 var longestTouchDownDuration = 0; | |
| 13079 var longestTouchUpDuration = 0; | |
| 13080 // Save the last known wave color | |
| 13081 var lastWaveColor = null; | |
| 13082 // wave animation values | |
| 13083 var anim = { | |
| 13084 initialOpacity: this.initialOpacity, | |
| 13085 opacityDecayVelocity: this.opacityDecayVelocity, | |
| 13086 height: ctx.height, | |
| 13087 width: ctx.width | |
| 13088 } | |
| 13089 | |
| 13090 for (var i = 0; i < this.waves.length; i++) { | |
| 13091 var wave = this.waves[i]; | |
| 13092 | |
| 13093 if (wave.mouseDownStart > 0) { | |
| 13094 wave.tDown = now() - wave.mouseDownStart; | |
| 13095 } | |
| 13096 if (wave.mouseUpStart > 0) { | |
| 13097 wave.tUp = now() - wave.mouseUpStart; | |
| 13098 } | |
| 13099 | |
| 13100 // Determine how long the touch has been up or down. | |
| 13101 var tUp = wave.tUp; | |
| 13102 var tDown = wave.tDown; | |
| 13103 longestTouchDownDuration = Math.max(longestTouchDownDuration, tDown); | |
| 13104 longestTouchUpDuration = Math.max(longestTouchUpDuration, tUp); | |
| 13105 | |
| 13106 // Obtain the instantenous size and alpha of the ripple. | |
| 13107 var radius = waveRadiusFn(tDown, tUp, anim); | |
| 13108 var waveAlpha = waveOpacityFn(tDown, tUp, anim); | |
| 13109 var waveColor = cssColorWithAlpha(wave.waveColor, waveAlpha); | |
| 13110 lastWaveColor = wave.waveColor; | |
| 13111 | |
| 13112 // Position of the ripple. | |
| 13113 var x = wave.startPosition.x; | |
| 13114 var y = wave.startPosition.y; | |
| 13115 | |
| 13116 // Ripple gravitational pull to the center of the canvas. | |
| 13117 if (wave.endPosition) { | |
| 13118 | |
| 13119 // This translates from the origin to the center of the view based
on the max dimension of | |
| 13120 var translateFraction = Math.min(1, radius / wave.containerSize * 2
/ Math.sqrt(2) ); | |
| 13121 | |
| 13122 x += translateFraction * (wave.endPosition.x - wave.startPosition.x)
; | |
| 13123 y += translateFraction * (wave.endPosition.y - wave.startPosition.y)
; | |
| 13124 } | |
| 13125 | |
| 13126 // If we do a background fill fade too, work out the correct color. | |
| 13127 var bgFillColor = null; | |
| 13128 if (this.backgroundFill) { | |
| 13129 var bgFillAlpha = waveOuterOpacityFn(tDown, tUp, anim); | |
| 13130 bgFillColor = cssColorWithAlpha(wave.waveColor, bgFillAlpha); | |
| 13131 } | |
| 13132 | |
| 13133 // Draw the ripple. | |
| 13134 drawRipple(wave, x, y, radius, waveAlpha, bgFillAlpha); | |
| 13135 | |
| 13136 // Determine whether there is any more rendering to be done. | |
| 13137 var maximumWave = waveAtMaximum(wave, radius, anim); | |
| 13138 var waveDissipated = waveDidFinish(wave, radius, anim); | |
| 13139 var shouldKeepWave = !waveDissipated || maximumWave; | |
| 13140 // keep rendering dissipating wave when at maximum radius on upAction | |
| 13141 var shouldRenderWaveAgain = wave.mouseUpStart ? !waveDissipated : !max
imumWave; | |
| 13142 shouldRenderNextFrame = shouldRenderNextFrame || shouldRenderWaveAgain
; | |
| 13143 if (!shouldKeepWave || this.cancelled) { | |
| 13144 deleteTheseWaves.push(wave); | |
| 13145 } | |
| 13146 } | |
| 13147 | |
| 13148 if (shouldRenderNextFrame) { | |
| 13149 requestAnimationFrame(this._loop); | |
| 13150 } | |
| 13151 | |
| 13152 for (var i = 0; i < deleteTheseWaves.length; ++i) { | |
| 13153 var wave = deleteTheseWaves[i]; | |
| 13154 removeWaveFromScope(this, wave); | |
| 13155 } | |
| 13156 | |
| 13157 if (!this.waves.length && this._loop) { | |
| 13158 // clear the background color | |
| 13159 this.$.bg.style.backgroundColor = null; | |
| 13160 this._loop = null; | |
| 13161 this.fire('core-transitionend'); | |
| 13162 } | |
| 13163 } | |
| 13164 | |
| 13165 }); | |
| 13166 | |
| 13167 })(); | |
| 13168 | |
| 13169 ; | |
| 13170 | |
| 13171 (function() { | |
| 13172 /* | |
| 13173 * Chrome uses an older version of DOM Level 3 Keyboard Events | |
| 13174 * | |
| 13175 * Most keys are labeled as text, but some are Unicode codepoints. | |
| 13176 * Values taken from: http://www.w3.org/TR/2007/WD-DOM-Level-3-Events-200712
21/keyset.html#KeySet-Set | |
| 13177 */ | |
| 13178 var KEY_IDENTIFIER = { | |
| 13179 'U+0009': 'tab', | |
| 13180 'U+001B': 'esc', | |
| 13181 'U+0020': 'space', | |
| 13182 'U+002A': '*', | |
| 13183 'U+0030': '0', | |
| 13184 'U+0031': '1', | |
| 13185 'U+0032': '2', | |
| 13186 'U+0033': '3', | |
| 13187 'U+0034': '4', | |
| 13188 'U+0035': '5', | |
| 13189 'U+0036': '6', | |
| 13190 'U+0037': '7', | |
| 13191 'U+0038': '8', | |
| 13192 'U+0039': '9', | |
| 13193 'U+0041': 'a', | |
| 13194 'U+0042': 'b', | |
| 13195 'U+0043': 'c', | |
| 13196 'U+0044': 'd', | |
| 13197 'U+0045': 'e', | |
| 13198 'U+0046': 'f', | |
| 13199 'U+0047': 'g', | |
| 13200 'U+0048': 'h', | |
| 13201 'U+0049': 'i', | |
| 13202 'U+004A': 'j', | |
| 13203 'U+004B': 'k', | |
| 13204 'U+004C': 'l', | |
| 13205 'U+004D': 'm', | |
| 13206 'U+004E': 'n', | |
| 13207 'U+004F': 'o', | |
| 13208 'U+0050': 'p', | |
| 13209 'U+0051': 'q', | |
| 13210 'U+0052': 'r', | |
| 13211 'U+0053': 's', | |
| 13212 'U+0054': 't', | |
| 13213 'U+0055': 'u', | |
| 13214 'U+0056': 'v', | |
| 13215 'U+0057': 'w', | |
| 13216 'U+0058': 'x', | |
| 13217 'U+0059': 'y', | |
| 13218 'U+005A': 'z', | |
| 13219 'U+007F': 'del' | |
| 13220 }; | |
| 13221 | |
| 13222 /* | |
| 13223 * Special table for KeyboardEvent.keyCode. | |
| 13224 * KeyboardEvent.keyIdentifier is better, and KeyBoardEvent.key is even bett
er than that | |
| 13225 * | |
| 13226 * Values from: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEve
nt.keyCode#Value_of_keyCode | |
| 13227 */ | |
| 13228 var KEY_CODE = { | |
| 13229 9: 'tab', | |
| 13230 13: 'enter', | |
| 13231 27: 'esc', | |
| 13232 33: 'pageup', | |
| 13233 34: 'pagedown', | |
| 13234 35: 'end', | |
| 13235 36: 'home', | |
| 13236 32: 'space', | |
| 13237 37: 'left', | |
| 13238 38: 'up', | |
| 13239 39: 'right', | |
| 13240 40: 'down', | |
| 13241 46: 'del', | |
| 13242 106: '*' | |
| 13243 }; | |
| 13244 | |
| 13245 /* | |
| 13246 * KeyboardEvent.key is mostly represented by printable character made by th
e keyboard, with unprintable keys labeled | |
| 13247 * nicely. | |
| 13248 * | |
| 13249 * However, on OS X, Alt+char can make a Unicode character that follows an A
pple-specific mapping. In this case, we | |
| 13250 * fall back to .keyCode. | |
| 13251 */ | |
| 13252 var KEY_CHAR = /[a-z0-9*]/; | |
| 13253 | |
| 13254 function transformKey(key) { | |
| 13255 var validKey = ''; | |
| 13256 if (key) { | |
| 13257 var lKey = key.toLowerCase(); | |
| 13258 if (lKey.length == 1) { | |
| 13259 if (KEY_CHAR.test(lKey)) { | |
| 13260 validKey = lKey; | |
| 13261 } | |
| 13262 } else if (lKey == 'multiply') { | |
| 13263 // numpad '*' can map to Multiply on IE/Windows | |
| 13264 validKey = '*'; | |
| 13265 } else { | |
| 13266 validKey = lKey; | |
| 13267 } | |
| 13268 } | |
| 13269 return validKey; | |
| 13270 } | |
| 13271 | |
| 13272 var IDENT_CHAR = /U\+/; | |
| 13273 function transformKeyIdentifier(keyIdent) { | |
| 13274 var validKey = ''; | |
| 13275 if (keyIdent) { | |
| 13276 if (IDENT_CHAR.test(keyIdent)) { | |
| 13277 validKey = KEY_IDENTIFIER[keyIdent]; | |
| 13278 } else { | |
| 13279 validKey = keyIdent.toLowerCase(); | |
| 13280 } | |
| 13281 } | |
| 13282 return validKey; | |
| 13283 } | |
| 13284 | |
| 13285 function transformKeyCode(keyCode) { | |
| 13286 var validKey = ''; | |
| 13287 if (Number(keyCode)) { | |
| 13288 if (keyCode >= 65 && keyCode <= 90) { | |
| 13289 // ascii a-z | |
| 13290 // lowercase is 32 offset from uppercase | |
| 13291 validKey = String.fromCharCode(32 + keyCode); | |
| 13292 } else if (keyCode >= 112 && keyCode <= 123) { | |
| 13293 // function keys f1-f12 | |
| 13294 validKey = 'f' + (keyCode - 112); | |
| 13295 } else if (keyCode >= 48 && keyCode <= 57) { | |
| 13296 // top 0-9 keys | |
| 13297 validKey = String(48 - keyCode); | |
| 13298 } else if (keyCode >= 96 && keyCode <= 105) { | |
| 13299 // num pad 0-9 | |
| 13300 validKey = String(96 - keyCode); | |
| 13301 } else { | |
| 13302 validKey = KEY_CODE[keyCode]; | |
| 13303 } | |
| 13304 } | |
| 13305 return validKey; | |
| 13306 } | |
| 13307 | |
| 13308 function keyboardEventToKey(ev) { | |
| 13309 // fall back from .key, to .keyIdentifier, to .keyCode, and then to .detai
l.key to support artificial keyboard events | |
| 13310 var normalizedKey = transformKey(ev.key) || transformKeyIdentifier(ev.keyI
dentifier) || transformKeyCode(ev.keyCode) || transformKey(ev.detail.key) || ''; | |
| 13311 return { | |
| 13312 shift: ev.shiftKey, | |
| 13313 ctrl: ev.ctrlKey, | |
| 13314 meta: ev.metaKey, | |
| 13315 alt: ev.altKey, | |
| 13316 key: normalizedKey | |
| 13317 }; | |
| 13318 } | |
| 13319 | |
| 13320 /* | |
| 13321 * Input: ctrl+shift+f7 => {ctrl: true, shift: true, key: 'f7'} | |
| 13322 * ctrl/space => {ctrl: true} || {key: space} | |
| 13323 */ | |
| 13324 function stringToKey(keyCombo) { | |
| 13325 var keys = keyCombo.split('+'); | |
| 13326 var keyObj = Object.create(null); | |
| 13327 keys.forEach(function(key) { | |
| 13328 if (key == 'shift') { | |
| 13329 keyObj.shift = true; | |
| 13330 } else if (key == 'ctrl') { | |
| 13331 keyObj.ctrl = true; | |
| 13332 } else if (key == 'alt') { | |
| 13333 keyObj.alt = true; | |
| 13334 } else { | |
| 13335 keyObj.key = key; | |
| 13336 } | |
| 13337 }); | |
| 13338 return keyObj; | |
| 13339 } | |
| 13340 | |
| 13341 function keyMatches(a, b) { | |
| 13342 return Boolean(a.alt) == Boolean(b.alt) && Boolean(a.ctrl) == Boolean(b.ct
rl) && Boolean(a.shift) == Boolean(b.shift) && a.key === b.key; | |
| 13343 } | |
| 13344 | |
| 13345 /** | |
| 13346 * Fired when a keycombo in `keys` is pressed. | |
| 13347 * | |
| 13348 * @event keys-pressed | |
| 13349 */ | |
| 13350 function processKeys(ev) { | |
| 13351 var current = keyboardEventToKey(ev); | |
| 13352 for (var i = 0, dk; i < this._desiredKeys.length; i++) { | |
| 13353 dk = this._desiredKeys[i]; | |
| 13354 if (keyMatches(dk, current)) { | |
| 13355 ev.preventDefault(); | |
| 13356 ev.stopPropagation(); | |
| 13357 this.fire('keys-pressed', current, this, false); | |
| 13358 break; | |
| 13359 } | |
| 13360 } | |
| 13361 } | |
| 13362 | |
| 13363 function listen(node, handler) { | |
| 13364 if (node && node.addEventListener) { | |
| 13365 node.addEventListener('keydown', handler); | |
| 13366 } | |
| 13367 } | |
| 13368 | |
| 13369 function unlisten(node, handler) { | |
| 13370 if (node && node.removeEventListener) { | |
| 13371 node.removeEventListener('keydown', handler); | |
| 13372 } | |
| 13373 } | |
| 13374 | |
| 13375 Polymer('core-a11y-keys', { | |
| 13376 created: function() { | |
| 13377 this._keyHandler = processKeys.bind(this); | |
| 13378 }, | |
| 13379 attached: function() { | |
| 13380 if (!this.target) { | |
| 13381 this.target = this.parentNode; | |
| 13382 } | |
| 13383 listen(this.target, this._keyHandler); | |
| 13384 }, | |
| 13385 detached: function() { | |
| 13386 unlisten(this.target, this._keyHandler); | |
| 13387 }, | |
| 13388 publish: { | |
| 13389 /** | |
| 13390 * The set of key combinations that will be matched (in keys syntax). | |
| 13391 * | |
| 13392 * @attribute keys | |
| 13393 * @type string | |
| 13394 * @default '' | |
| 13395 */ | |
| 13396 keys: '', | |
| 13397 /** | |
| 13398 * The node that will fire keyboard events. | |
| 13399 * Default to this element's parentNode unless one is assigned | |
| 13400 * | |
| 13401 * @attribute target | |
| 13402 * @type Node | |
| 13403 * @default this.parentNode | |
| 13404 */ | |
| 13405 target: null | |
| 13406 }, | |
| 13407 keysChanged: function() { | |
| 13408 // * can have multiple mappings: shift+8, * on numpad or Multiply on num
pad | |
| 13409 var normalized = this.keys.replace('*', '* shift+*'); | |
| 13410 this._desiredKeys = normalized.toLowerCase().split(' ').map(stringToKey)
; | |
| 13411 }, | |
| 13412 targetChanged: function(oldTarget) { | |
| 13413 unlisten(oldTarget, this._keyHandler); | |
| 13414 listen(this.target, this._keyHandler); | |
| 13415 } | |
| 13416 }); | |
| 13417 })(); | |
| 13418 ; | |
| 13419 | |
| 13420 | |
| 13421 Polymer('paper-radio-button', { | |
| 13422 | |
| 13423 /** | |
| 13424 * Fired when the checked state changes due to user interaction. | |
| 13425 * | |
| 13426 * @event change | |
| 13427 */ | |
| 13428 | |
| 13429 /** | |
| 13430 * Fired when the checked state changes. | |
| 13431 * | |
| 13432 * @event core-change | |
| 13433 */ | |
| 13434 | |
| 13435 publish: { | |
| 13436 /** | |
| 13437 * Gets or sets the state, `true` is checked and `false` is unchecked. | |
| 13438 * | |
| 13439 * @attribute checked | |
| 13440 * @type boolean | |
| 13441 * @default false | |
| 13442 */ | |
| 13443 checked: {value: false, reflect: true}, | |
| 13444 | |
| 13445 /** | |
| 13446 * The label for the radio button. | |
| 13447 * | |
| 13448 * @attribute label | |
| 13449 * @type string | |
| 13450 * @default '' | |
| 13451 */ | |
| 13452 label: '', | |
| 13453 | |
| 13454 /** | |
| 13455 * Normally the user cannot uncheck the radio button by tapping once | |
| 13456 * checked. Setting this property to `true` makes the radio button | |
| 13457 * toggleable from checked to unchecked. | |
| 13458 * | |
| 13459 * @attribute toggles | |
| 13460 * @type boolean | |
| 13461 * @default false | |
| 13462 */ | |
| 13463 toggles: false, | |
| 13464 | |
| 13465 /** | |
| 13466 * If true, the user cannot interact with this element. | |
| 13467 * | |
| 13468 * @attribute disabled | |
| 13469 * @type boolean | |
| 13470 * @default false | |
| 13471 */ | |
| 13472 disabled: {value: false, reflect: true} | |
| 13473 }, | |
| 13474 | |
| 13475 eventDelegates: { | |
| 13476 tap: 'tap' | |
| 13477 }, | |
| 13478 | |
| 13479 tap: function() { | |
| 13480 if (this.disabled) { | |
| 13481 return; | |
| 13482 } | |
| 13483 var old = this.checked; | |
| 13484 this.toggle(); | |
| 13485 if (this.checked !== old) { | |
| 13486 this.fire('change'); | |
| 13487 } | |
| 13488 }, | |
| 13489 | |
| 13490 toggle: function() { | |
| 13491 this.checked = !this.toggles || !this.checked; | |
| 13492 }, | |
| 13493 | |
| 13494 checkedChanged: function() { | |
| 13495 this.setAttribute('aria-checked', this.checked ? 'true' : 'false'); | |
| 13496 this.fire('core-change'); | |
| 13497 }, | |
| 13498 | |
| 13499 labelChanged: function() { | |
| 13500 this.setAttribute('aria-label', this.label); | |
| 13501 } | |
| 13502 | |
| 13503 }); | |
| 13504 | |
| 13505 ; | |
| 13506 | |
| 13507 | |
| 13508 Polymer('paper-checkbox', { | |
| 13509 | |
| 13510 /** | |
| 13511 * Fired when the checked state changes due to user interaction. | |
| 13512 * | |
| 13513 * @event change | |
| 13514 */ | |
| 13515 | |
| 13516 /** | |
| 13517 * Fired when the checked state changes. | |
| 13518 * | |
| 13519 * @event core-change | |
| 13520 */ | |
| 13521 | |
| 13522 toggles: true, | |
| 13523 | |
| 13524 checkedChanged: function() { | |
| 13525 this.$.checkbox.classList.toggle('checked', this.checked); | |
| 13526 this.setAttribute('aria-checked', this.checked ? 'true': 'false'); | |
| 13527 this.$.checkmark.classList.toggle('hidden', !this.checked); | |
| 13528 this.fire('core-change'); | |
| 13529 }, | |
| 13530 | |
| 13531 checkboxAnimationEnd: function() { | |
| 13532 var cl = this.$.checkmark.classList; | |
| 13533 cl.toggle('hidden', !this.checked && cl.contains('hidden')); | |
| 13534 } | |
| 13535 | |
| 13536 }); | |
| 13537 | |
| 13538 ; | |
| 13539 | |
| 13540 Polymer('paper-shadow',{ | |
| 13541 | |
| 13542 publish: { | |
| 13543 | |
| 13544 /** | |
| 13545 * The z-depth of this shadow, from 0-5. Setting this property | |
| 13546 * after element creation has no effect. Use `setZ()` instead. | |
| 13547 * | |
| 13548 * @attribute z | |
| 13549 * @type number | |
| 13550 * @default 1 | |
| 13551 */ | |
| 13552 z: 1, | |
| 13553 | |
| 13554 /** | |
| 13555 * Set this to true to animate the shadow when setting a new | |
| 13556 * `z` value. | |
| 13557 * | |
| 13558 * @attribute animated | |
| 13559 * @type boolean | |
| 13560 * @default false | |
| 13561 */ | |
| 13562 animated: false | |
| 13563 | |
| 13564 }, | |
| 13565 | |
| 13566 /** | |
| 13567 * Set the z-depth of the shadow. This should be used after element | |
| 13568 * creation instead of setting the z property directly. | |
| 13569 * | |
| 13570 * @method setZ | |
| 13571 * @param {Number} newZ | |
| 13572 */ | |
| 13573 setZ: function(newZ) { | |
| 13574 if (this.z !== newZ) { | |
| 13575 this.$['shadow-bottom'].classList.remove('paper-shadow-bottom-z-' + this
.z); | |
| 13576 this.$['shadow-bottom'].classList.add('paper-shadow-bottom-z-' + newZ); | |
| 13577 this.$['shadow-top'].classList.remove('paper-shadow-top-z-' + this.z); | |
| 13578 this.$['shadow-top'].classList.add('paper-shadow-top-z-' + newZ); | |
| 13579 this.z = newZ; | |
| 13580 } | |
| 13581 } | |
| 13582 | |
| 13583 }); | |
| 13584 ; | |
| 13585 | |
| 13586 | |
| 13587 (function() { | |
| 13588 | |
| 13589 var SKIP_ID = 'meta'; | |
| 13590 var metaData = {}, metaArray = {}; | |
| 13591 | |
| 13592 Polymer('core-meta', { | |
| 13593 | |
| 13594 /** | |
| 13595 * The type of meta-data. All meta-data with the same type with be | |
| 13596 * stored together. | |
| 13597 * | |
| 13598 * @attribute type | |
| 13599 * @type string | |
| 13600 * @default 'default' | |
| 13601 */ | |
| 13602 type: 'default', | |
| 13603 | |
| 13604 alwaysPrepare: true, | |
| 13605 | |
| 13606 ready: function() { | |
| 13607 this.register(this.id); | |
| 13608 }, | |
| 13609 | |
| 13610 get metaArray() { | |
| 13611 var t = this.type; | |
| 13612 if (!metaArray[t]) { | |
| 13613 metaArray[t] = []; | |
| 13614 } | |
| 13615 return metaArray[t]; | |
| 13616 }, | |
| 13617 | |
| 13618 get metaData() { | |
| 13619 var t = this.type; | |
| 13620 if (!metaData[t]) { | |
| 13621 metaData[t] = {}; | |
| 13622 } | |
| 13623 return metaData[t]; | |
| 13624 }, | |
| 13625 | |
| 13626 register: function(id, old) { | |
| 13627 if (id && id !== SKIP_ID) { | |
| 13628 this.unregister(this, old); | |
| 13629 this.metaData[id] = this; | |
| 13630 this.metaArray.push(this); | |
| 13631 } | |
| 13632 }, | |
| 13633 | |
| 13634 unregister: function(meta, id) { | |
| 13635 delete this.metaData[id || meta.id]; | |
| 13636 var i = this.metaArray.indexOf(meta); | |
| 13637 if (i >= 0) { | |
| 13638 this.metaArray.splice(i, 1); | |
| 13639 } | |
| 13640 }, | |
| 13641 | |
| 13642 /** | |
| 13643 * Returns a list of all meta-data elements with the same type. | |
| 13644 * | |
| 13645 * @property list | |
| 13646 * @type array | |
| 13647 * @default [] | |
| 13648 */ | |
| 13649 get list() { | |
| 13650 return this.metaArray; | |
| 13651 }, | |
| 13652 | |
| 13653 /** | |
| 13654 * Retrieves meta-data by ID. | |
| 13655 * | |
| 13656 * @method byId | |
| 13657 * @param {String} id The ID of the meta-data to be returned. | |
| 13658 * @returns Returns meta-data. | |
| 13659 */ | |
| 13660 byId: function(id) { | |
| 13661 return this.metaData[id]; | |
| 13662 } | |
| 13663 | |
| 13664 }); | |
| 13665 | |
| 13666 })(); | |
| 13667 | |
| 13668 ; | |
| 13669 | |
| 13670 Polymer('core-transition', { | |
| 13671 | |
| 13672 type: 'transition', | |
| 13673 | |
| 13674 /** | |
| 13675 * Run the animation. | |
| 13676 * | |
| 13677 * @method go | |
| 13678 * @param {Node} node The node to apply the animation on | |
| 13679 * @param {Object} state State info | |
| 13680 */ | |
| 13681 go: function(node, state) { | |
| 13682 this.complete(node); | |
| 13683 }, | |
| 13684 | |
| 13685 /** | |
| 13686 * Set up the animation. This may include injecting a stylesheet, | |
| 13687 * applying styles, creating a web animations object, etc.. This | |
| 13688 * | |
| 13689 * @method setup | |
| 13690 * @param {Node} node The animated node | |
| 13691 */ | |
| 13692 setup: function(node) { | |
| 13693 }, | |
| 13694 | |
| 13695 /** | |
| 13696 * Tear down the animation. | |
| 13697 * | |
| 13698 * @method teardown | |
| 13699 * @param {Node} node The animated node | |
| 13700 */ | |
| 13701 teardown: function(node) { | |
| 13702 }, | |
| 13703 | |
| 13704 /** | |
| 13705 * Called when the animation completes. This function also fires the | |
| 13706 * `core-transitionend` event. | |
| 13707 * | |
| 13708 * @method complete | |
| 13709 * @param {Node} node The animated node | |
| 13710 */ | |
| 13711 complete: function(node) { | |
| 13712 this.fire('core-transitionend', null, node); | |
| 13713 }, | |
| 13714 | |
| 13715 /** | |
| 13716 * Utility function to listen to an event on a node once. | |
| 13717 * | |
| 13718 * @method listenOnce | |
| 13719 * @param {Node} node The animated node | |
| 13720 * @param {string} event Name of an event | |
| 13721 * @param {Function} fn Event handler | |
| 13722 * @param {Array} args Additional arguments to pass to `fn` | |
| 13723 */ | |
| 13724 listenOnce: function(node, event, fn, args) { | |
| 13725 var self = this; | |
| 13726 var listener = function() { | |
| 13727 fn.apply(self, args); | |
| 13728 node.removeEventListener(event, listener, false); | |
| 13729 } | |
| 13730 node.addEventListener(event, listener, false); | |
| 13731 } | |
| 13732 | |
| 13733 }); | |
| 13734 ; | |
| 13735 | |
| 13736 Polymer('core-key-helper', { | |
| 13737 ENTER_KEY: 13, | |
| 13738 ESCAPE_KEY: 27 | |
| 13739 }); | |
| 13740 ; | |
| 13741 | |
| 13742 (function() { | |
| 13743 | |
| 13744 Polymer('core-overlay-layer', { | |
| 13745 publish: { | |
| 13746 opened: false | |
| 13747 }, | |
| 13748 openedChanged: function() { | |
| 13749 this.classList.toggle('core-opened', this.opened); | |
| 13750 }, | |
| 13751 /** | |
| 13752 * Adds an element to the overlay layer | |
| 13753 */ | |
| 13754 addElement: function(element) { | |
| 13755 if (!this.parentNode) { | |
| 13756 document.querySelector('body').appendChild(this); | |
| 13757 } | |
| 13758 if (element.parentNode !== this) { | |
| 13759 element.__contents = []; | |
| 13760 var ip$ = element.querySelectorAll('content'); | |
| 13761 for (var i=0, l=ip$.length, n; (i<l) && (n = ip$[i]); i++) { | |
| 13762 this.moveInsertedElements(n); | |
| 13763 this.cacheDomLocation(n); | |
| 13764 n.parentNode.removeChild(n); | |
| 13765 element.__contents.push(n); | |
| 13766 } | |
| 13767 this.cacheDomLocation(element); | |
| 13768 this.updateEventController(element); | |
| 13769 var h = this.makeHost(); | |
| 13770 h.shadowRoot.appendChild(element); | |
| 13771 element.__host = h; | |
| 13772 } | |
| 13773 }, | |
| 13774 makeHost: function() { | |
| 13775 var h = document.createElement('overlay-host'); | |
| 13776 h.createShadowRoot(); | |
| 13777 this.appendChild(h); | |
| 13778 return h; | |
| 13779 }, | |
| 13780 moveInsertedElements: function(insertionPoint) { | |
| 13781 var n$ = insertionPoint.getDistributedNodes(); | |
| 13782 var parent = insertionPoint.parentNode; | |
| 13783 insertionPoint.__contents = []; | |
| 13784 for (var i=0, l=n$.length, n; (i<l) && (n=n$[i]); i++) { | |
| 13785 this.cacheDomLocation(n); | |
| 13786 this.updateEventController(n); | |
| 13787 insertionPoint.__contents.push(n); | |
| 13788 parent.appendChild(n); | |
| 13789 } | |
| 13790 }, | |
| 13791 updateEventController: function(element) { | |
| 13792 element.eventController = this.element.findController(element); | |
| 13793 }, | |
| 13794 /** | |
| 13795 * Removes an element from the overlay layer | |
| 13796 */ | |
| 13797 removeElement: function(element) { | |
| 13798 element.eventController = null; | |
| 13799 this.replaceElement(element); | |
| 13800 var h = element.__host; | |
| 13801 if (h) { | |
| 13802 h.parentNode.removeChild(h); | |
| 13803 } | |
| 13804 }, | |
| 13805 replaceElement: function(element) { | |
| 13806 if (element.__contents) { | |
| 13807 for (var i=0, c$=element.__contents, c; (c=c$[i]); i++) { | |
| 13808 this.replaceElement(c); | |
| 13809 } | |
| 13810 element.__contents = null; | |
| 13811 } | |
| 13812 if (element.__parentNode) { | |
| 13813 var n = element.__nextElementSibling && element.__nextElementSibling | |
| 13814 === element.__parentNode ? element.__nextElementSibling : null; | |
| 13815 element.__parentNode.insertBefore(element, n); | |
| 13816 } | |
| 13817 }, | |
| 13818 cacheDomLocation: function(element) { | |
| 13819 element.__nextElementSibling = element.nextElementSibling; | |
| 13820 element.__parentNode = element.parentNode; | |
| 13821 } | |
| 13822 }); | |
| 13823 | |
| 13824 })(); | |
| 13825 ; | |
| 13826 | |
| 13827 (function() { | |
| 13828 | |
| 13829 Polymer('core-overlay',Polymer.mixin({ | |
| 13830 | |
| 13831 publish: { | |
| 13832 /** | |
| 13833 * The target element that will be shown when the overlay is | |
| 13834 * opened. If unspecified, the core-overlay itself is the target. | |
| 13835 * | |
| 13836 * @attribute target | |
| 13837 * @type Object | |
| 13838 * @default the overlay element | |
| 13839 */ | |
| 13840 target: null, | |
| 13841 | |
| 13842 | |
| 13843 /** | |
| 13844 * A `core-overlay`'s size is guaranteed to be | |
| 13845 * constrained to the window size. To achieve this, the sizingElement | |
| 13846 * is sized with a max-height/width. By default this element is the | |
| 13847 * target element, but it can be specifically set to a specific element | |
| 13848 * inside the target if that is more appropriate. This is useful, for | |
| 13849 * example, when a region inside the overlay should scroll if needed. | |
| 13850 * | |
| 13851 * @attribute sizingTarget | |
| 13852 * @type Object | |
| 13853 * @default the target element | |
| 13854 */ | |
| 13855 sizingTarget: null, | |
| 13856 | |
| 13857 /** | |
| 13858 * Set opened to true to show an overlay and to false to hide it. | |
| 13859 * A `core-overlay` may be made initially opened by setting its | |
| 13860 * `opened` attribute. | |
| 13861 * @attribute opened | |
| 13862 * @type boolean | |
| 13863 * @default false | |
| 13864 */ | |
| 13865 opened: false, | |
| 13866 | |
| 13867 /** | |
| 13868 * If true, the overlay has a backdrop darkening the rest of the screen. | |
| 13869 * The backdrop element is attached to the document body and may be styled | |
| 13870 * with the class `core-overlay-backdrop`. When opened the `core-opened` | |
| 13871 * class is applied. | |
| 13872 * | |
| 13873 * @attribute backdrop | |
| 13874 * @type boolean | |
| 13875 * @default false | |
| 13876 */ | |
| 13877 backdrop: false, | |
| 13878 | |
| 13879 /** | |
| 13880 * If true, the overlay is guaranteed to display above page content. | |
| 13881 * | |
| 13882 * @attribute layered | |
| 13883 * @type boolean | |
| 13884 * @default false | |
| 13885 */ | |
| 13886 layered: false, | |
| 13887 | |
| 13888 /** | |
| 13889 * By default an overlay will close automatically if the user | |
| 13890 * taps outside it or presses the escape key. Disable this | |
| 13891 * behavior by setting the `autoCloseDisabled` property to true. | |
| 13892 * @attribute autoCloseDisabled | |
| 13893 * @type boolean | |
| 13894 * @default false | |
| 13895 */ | |
| 13896 autoCloseDisabled: false, | |
| 13897 | |
| 13898 /** | |
| 13899 * By default an overlay will focus its target or an element inside | |
| 13900 * it with the `autoFocus` attribute. Disable this | |
| 13901 * behavior by setting the `autoFocusDisabled` property to true. | |
| 13902 * @attribute autoFocusDisabled | |
| 13903 * @type boolean | |
| 13904 * @default false | |
| 13905 */ | |
| 13906 autoFocusDisabled: false, | |
| 13907 | |
| 13908 /** | |
| 13909 * This property specifies an attribute on elements that should | |
| 13910 * close the overlay on tap. Should not set `closeSelector` if this | |
| 13911 * is set. | |
| 13912 * | |
| 13913 * @attribute closeAttribute | |
| 13914 * @type string | |
| 13915 * @default "core-overlay-toggle" | |
| 13916 */ | |
| 13917 closeAttribute: 'core-overlay-toggle', | |
| 13918 | |
| 13919 /** | |
| 13920 * This property specifies a selector matching elements that should | |
| 13921 * close the overlay on tap. Should not set `closeAttribute` if this | |
| 13922 * is set. | |
| 13923 * | |
| 13924 * @attribute closeSelector | |
| 13925 * @type string | |
| 13926 * @default "" | |
| 13927 */ | |
| 13928 closeSelector: '', | |
| 13929 | |
| 13930 /** | |
| 13931 * The transition property specifies a string which identifies a | |
| 13932 * <a href="../core-transition/">`core-transition`</a> element that | |
| 13933 * will be used to help the overlay open and close. The default | |
| 13934 * `core-transition-fade` will cause the overlay to fade in and out. | |
| 13935 * | |
| 13936 * @attribute transition | |
| 13937 * @type string | |
| 13938 * @default 'core-transition-fade' | |
| 13939 */ | |
| 13940 transition: 'core-transition-fade' | |
| 13941 | |
| 13942 }, | |
| 13943 | |
| 13944 captureEventName: 'tap', | |
| 13945 targetListeners: { | |
| 13946 'tap': 'tapHandler', | |
| 13947 'keydown': 'keydownHandler', | |
| 13948 'core-transitionend': 'transitionend' | |
| 13949 }, | |
| 13950 | |
| 13951 attached: function() { | |
| 13952 this.resizerAttachedHandler(); | |
| 13953 }, | |
| 13954 | |
| 13955 detached: function() { | |
| 13956 this.resizerDetachedHandler(); | |
| 13957 }, | |
| 13958 | |
| 13959 resizerShouldNotify: function() { | |
| 13960 return this.opened; | |
| 13961 }, | |
| 13962 | |
| 13963 registerCallback: function(element) { | |
| 13964 this.layer = document.createElement('core-overlay-layer'); | |
| 13965 this.keyHelper = document.createElement('core-key-helper'); | |
| 13966 this.meta = document.createElement('core-transition'); | |
| 13967 this.scrim = document.createElement('div'); | |
| 13968 this.scrim.className = 'core-overlay-backdrop'; | |
| 13969 }, | |
| 13970 | |
| 13971 ready: function() { | |
| 13972 this.target = this.target || this; | |
| 13973 // flush to ensure styles are installed before paint | |
| 13974 Polymer.flush(); | |
| 13975 }, | |
| 13976 | |
| 13977 /** | |
| 13978 * Toggle the opened state of the overlay. | |
| 13979 * @method toggle | |
| 13980 */ | |
| 13981 toggle: function() { | |
| 13982 this.opened = !this.opened; | |
| 13983 }, | |
| 13984 | |
| 13985 /** | |
| 13986 * Open the overlay. This is equivalent to setting the `opened` | |
| 13987 * property to true. | |
| 13988 * @method open | |
| 13989 */ | |
| 13990 open: function() { | |
| 13991 this.opened = true; | |
| 13992 }, | |
| 13993 | |
| 13994 /** | |
| 13995 * Close the overlay. This is equivalent to setting the `opened` | |
| 13996 * property to false. | |
| 13997 * @method close | |
| 13998 */ | |
| 13999 close: function() { | |
| 14000 this.opened = false; | |
| 14001 }, | |
| 14002 | |
| 14003 domReady: function() { | |
| 14004 this.ensureTargetSetup(); | |
| 14005 }, | |
| 14006 | |
| 14007 targetChanged: function(old) { | |
| 14008 if (this.target) { | |
| 14009 // really make sure tabIndex is set | |
| 14010 if (this.target.tabIndex < 0) { | |
| 14011 this.target.tabIndex = -1; | |
| 14012 } | |
| 14013 this.addElementListenerList(this.target, this.targetListeners); | |
| 14014 this.target.style.display = 'none'; | |
| 14015 this.target.__overlaySetup = false; | |
| 14016 } | |
| 14017 if (old) { | |
| 14018 this.removeElementListenerList(old, this.targetListeners); | |
| 14019 var transition = this.getTransition(); | |
| 14020 if (transition) { | |
| 14021 transition.teardown(old); | |
| 14022 } else { | |
| 14023 old.style.position = ''; | |
| 14024 old.style.outline = ''; | |
| 14025 } | |
| 14026 old.style.display = ''; | |
| 14027 } | |
| 14028 }, | |
| 14029 | |
| 14030 transitionChanged: function(old) { | |
| 14031 if (!this.target) { | |
| 14032 return; | |
| 14033 } | |
| 14034 if (old) { | |
| 14035 this.getTransition(old).teardown(this.target); | |
| 14036 } | |
| 14037 this.target.__overlaySetup = false; | |
| 14038 }, | |
| 14039 | |
| 14040 // NOTE: wait to call this until we're as sure as possible that target | |
| 14041 // is styled. | |
| 14042 ensureTargetSetup: function() { | |
| 14043 if (!this.target || this.target.__overlaySetup) { | |
| 14044 return; | |
| 14045 } | |
| 14046 if (!this.sizingTarget) { | |
| 14047 this.sizingTarget = this.target; | |
| 14048 } | |
| 14049 this.target.__overlaySetup = true; | |
| 14050 this.target.style.display = ''; | |
| 14051 var transition = this.getTransition(); | |
| 14052 if (transition) { | |
| 14053 transition.setup(this.target); | |
| 14054 } | |
| 14055 var style = this.target.style; | |
| 14056 var computed = getComputedStyle(this.target); | |
| 14057 if (computed.position === 'static') { | |
| 14058 style.position = 'fixed'; | |
| 14059 } | |
| 14060 style.outline = 'none'; | |
| 14061 style.display = 'none'; | |
| 14062 }, | |
| 14063 | |
| 14064 openedChanged: function() { | |
| 14065 this.transitioning = true; | |
| 14066 this.ensureTargetSetup(); | |
| 14067 this.prepareRenderOpened(); | |
| 14068 // async here to allow overlay layer to become visible. | |
| 14069 this.async(function() { | |
| 14070 this.target.style.display = ''; | |
| 14071 // force layout to ensure transitions will go | |
| 14072 this.target.offsetWidth; | |
| 14073 this.renderOpened(); | |
| 14074 }); | |
| 14075 this.fire('core-overlay-open', this.opened); | |
| 14076 }, | |
| 14077 | |
| 14078 // tasks which must occur before opening; e.g. making the element visible | |
| 14079 prepareRenderOpened: function() { | |
| 14080 if (this.opened) { | |
| 14081 addOverlay(this); | |
| 14082 } | |
| 14083 this.prepareBackdrop(); | |
| 14084 // async so we don't auto-close immediately via a click. | |
| 14085 this.async(function() { | |
| 14086 if (!this.autoCloseDisabled) { | |
| 14087 this.enableElementListener(this.opened, document, | |
| 14088 this.captureEventName, 'captureHandler', true); | |
| 14089 } | |
| 14090 }); | |
| 14091 this.enableElementListener(this.opened, window, 'resize', | |
| 14092 'resizeHandler'); | |
| 14093 | |
| 14094 if (this.opened) { | |
| 14095 // force layout so SD Polyfill renders | |
| 14096 this.target.offsetHeight; | |
| 14097 this.discoverDimensions(); | |
| 14098 // if we are showing, then take care when positioning | |
| 14099 this.preparePositioning(); | |
| 14100 this.positionTarget(); | |
| 14101 this.updateTargetDimensions(); | |
| 14102 this.finishPositioning(); | |
| 14103 if (this.layered) { | |
| 14104 this.layer.addElement(this.target); | |
| 14105 this.layer.opened = this.opened; | |
| 14106 } | |
| 14107 } | |
| 14108 }, | |
| 14109 | |
| 14110 // tasks which cause the overlay to actually open; typically play an | |
| 14111 // animation | |
| 14112 renderOpened: function() { | |
| 14113 this.notifyResize(); | |
| 14114 var transition = this.getTransition(); | |
| 14115 if (transition) { | |
| 14116 transition.go(this.target, {opened: this.opened}); | |
| 14117 } else { | |
| 14118 this.transitionend(); | |
| 14119 } | |
| 14120 this.renderBackdropOpened(); | |
| 14121 }, | |
| 14122 | |
| 14123 // finishing tasks; typically called via a transition | |
| 14124 transitionend: function(e) { | |
| 14125 // make sure this is our transition event. | |
| 14126 if (e && e.target !== this.target) { | |
| 14127 return; | |
| 14128 } | |
| 14129 this.transitioning = false; | |
| 14130 if (!this.opened) { | |
| 14131 this.resetTargetDimensions(); | |
| 14132 this.target.style.display = 'none'; | |
| 14133 this.completeBackdrop(); | |
| 14134 removeOverlay(this); | |
| 14135 if (this.layered) { | |
| 14136 if (!currentOverlay()) { | |
| 14137 this.layer.opened = this.opened; | |
| 14138 } | |
| 14139 this.layer.removeElement(this.target); | |
| 14140 } | |
| 14141 } | |
| 14142 this.fire('core-overlay-' + (this.opened ? 'open' : 'close') + | |
| 14143 '-completed'); | |
| 14144 this.applyFocus(); | |
| 14145 }, | |
| 14146 | |
| 14147 prepareBackdrop: function() { | |
| 14148 if (this.backdrop && this.opened) { | |
| 14149 if (!this.scrim.parentNode) { | |
| 14150 document.body.appendChild(this.scrim); | |
| 14151 this.scrim.style.zIndex = currentOverlayZ() - 1; | |
| 14152 } | |
| 14153 trackBackdrop(this); | |
| 14154 } | |
| 14155 }, | |
| 14156 | |
| 14157 renderBackdropOpened: function() { | |
| 14158 if (this.backdrop && getBackdrops().length < 2) { | |
| 14159 this.scrim.classList.toggle('core-opened', this.opened); | |
| 14160 } | |
| 14161 }, | |
| 14162 | |
| 14163 completeBackdrop: function() { | |
| 14164 if (this.backdrop) { | |
| 14165 trackBackdrop(this); | |
| 14166 if (getBackdrops().length === 0) { | |
| 14167 this.scrim.parentNode.removeChild(this.scrim); | |
| 14168 } | |
| 14169 } | |
| 14170 }, | |
| 14171 | |
| 14172 preparePositioning: function() { | |
| 14173 this.target.style.transition = this.target.style.webkitTransition = 'none'
; | |
| 14174 this.target.style.transform = this.target.style.webkitTransform = 'none'; | |
| 14175 this.target.style.display = ''; | |
| 14176 }, | |
| 14177 | |
| 14178 discoverDimensions: function() { | |
| 14179 if (this.dimensions) { | |
| 14180 return; | |
| 14181 } | |
| 14182 var target = getComputedStyle(this.target); | |
| 14183 var sizer = getComputedStyle(this.sizingTarget); | |
| 14184 this.dimensions = { | |
| 14185 position: { | |
| 14186 v: target.top !== 'auto' ? 'top' : (target.bottom !== 'auto' ? | |
| 14187 'bottom' : null), | |
| 14188 h: target.left !== 'auto' ? 'left' : (target.right !== 'auto' ? | |
| 14189 'right' : null), | |
| 14190 css: target.position | |
| 14191 }, | |
| 14192 size: { | |
| 14193 v: sizer.maxHeight !== 'none', | |
| 14194 h: sizer.maxWidth !== 'none' | |
| 14195 }, | |
| 14196 margin: { | |
| 14197 top: parseInt(target.marginTop) || 0, | |
| 14198 right: parseInt(target.marginRight) || 0, | |
| 14199 bottom: parseInt(target.marginBottom) || 0, | |
| 14200 left: parseInt(target.marginLeft) || 0 | |
| 14201 } | |
| 14202 }; | |
| 14203 }, | |
| 14204 | |
| 14205 finishPositioning: function(target) { | |
| 14206 this.target.style.display = 'none'; | |
| 14207 this.target.style.transform = this.target.style.webkitTransform = ''; | |
| 14208 // force layout to avoid application of transform | |
| 14209 this.target.offsetWidth; | |
| 14210 this.target.style.transition = this.target.style.webkitTransition = ''; | |
| 14211 }, | |
| 14212 | |
| 14213 getTransition: function(name) { | |
| 14214 return this.meta.byId(name || this.transition); | |
| 14215 }, | |
| 14216 | |
| 14217 getFocusNode: function() { | |
| 14218 return this.target.querySelector('[autofocus]') || this.target; | |
| 14219 }, | |
| 14220 | |
| 14221 applyFocus: function() { | |
| 14222 var focusNode = this.getFocusNode(); | |
| 14223 if (this.opened) { | |
| 14224 if (!this.autoFocusDisabled) { | |
| 14225 focusNode.focus(); | |
| 14226 } | |
| 14227 } else { | |
| 14228 focusNode.blur(); | |
| 14229 if (currentOverlay() == this) { | |
| 14230 console.warn('Current core-overlay is attempting to focus itself as ne
xt! (bug)'); | |
| 14231 } else { | |
| 14232 focusOverlay(); | |
| 14233 } | |
| 14234 } | |
| 14235 }, | |
| 14236 | |
| 14237 positionTarget: function() { | |
| 14238 // fire positioning event | |
| 14239 this.fire('core-overlay-position', {target: this.target, | |
| 14240 sizingTarget: this.sizingTarget, opened: this.opened}); | |
| 14241 if (!this.dimensions.position.v) { | |
| 14242 this.target.style.top = '0px'; | |
| 14243 } | |
| 14244 if (!this.dimensions.position.h) { | |
| 14245 this.target.style.left = '0px'; | |
| 14246 } | |
| 14247 }, | |
| 14248 | |
| 14249 updateTargetDimensions: function() { | |
| 14250 this.sizeTarget(); | |
| 14251 this.repositionTarget(); | |
| 14252 }, | |
| 14253 | |
| 14254 sizeTarget: function() { | |
| 14255 this.sizingTarget.style.boxSizing = 'border-box'; | |
| 14256 var dims = this.dimensions; | |
| 14257 var rect = this.target.getBoundingClientRect(); | |
| 14258 if (!dims.size.v) { | |
| 14259 this.sizeDimension(rect, dims.position.v, 'top', 'bottom', 'Height'); | |
| 14260 } | |
| 14261 if (!dims.size.h) { | |
| 14262 this.sizeDimension(rect, dims.position.h, 'left', 'right', 'Width'); | |
| 14263 } | |
| 14264 }, | |
| 14265 | |
| 14266 sizeDimension: function(rect, positionedBy, start, end, extent) { | |
| 14267 var dims = this.dimensions; | |
| 14268 var flip = (positionedBy === end); | |
| 14269 var m = flip ? start : end; | |
| 14270 var ws = window['inner' + extent]; | |
| 14271 var o = dims.margin[m] + (flip ? ws - rect[end] : | |
| 14272 rect[start]); | |
| 14273 var offset = 'offset' + extent; | |
| 14274 var o2 = this.target[offset] - this.sizingTarget[offset]; | |
| 14275 this.sizingTarget.style['max' + extent] = (ws - o - o2) + 'px'; | |
| 14276 }, | |
| 14277 | |
| 14278 // vertically and horizontally center if not positioned | |
| 14279 repositionTarget: function() { | |
| 14280 // only center if position fixed. | |
| 14281 if (this.dimensions.position.css !== 'fixed') { | |
| 14282 return; | |
| 14283 } | |
| 14284 if (!this.dimensions.position.v) { | |
| 14285 var t = (window.innerHeight - this.target.offsetHeight) / 2; | |
| 14286 t -= this.dimensions.margin.top; | |
| 14287 this.target.style.top = t + 'px'; | |
| 14288 } | |
| 14289 | |
| 14290 if (!this.dimensions.position.h) { | |
| 14291 var l = (window.innerWidth - this.target.offsetWidth) / 2; | |
| 14292 l -= this.dimensions.margin.left; | |
| 14293 this.target.style.left = l + 'px'; | |
| 14294 } | |
| 14295 }, | |
| 14296 | |
| 14297 resetTargetDimensions: function() { | |
| 14298 if (!this.dimensions || !this.dimensions.size.v) { | |
| 14299 this.sizingTarget.style.maxHeight = ''; | |
| 14300 this.target.style.top = ''; | |
| 14301 } | |
| 14302 if (!this.dimensions || !this.dimensions.size.h) { | |
| 14303 this.sizingTarget.style.maxWidth = ''; | |
| 14304 this.target.style.left = ''; | |
| 14305 } | |
| 14306 this.dimensions = null; | |
| 14307 }, | |
| 14308 | |
| 14309 tapHandler: function(e) { | |
| 14310 // closeSelector takes precedence since closeAttribute has a default non-n
ull value. | |
| 14311 if (e.target && | |
| 14312 (this.closeSelector && e.target.matches(this.closeSelector)) || | |
| 14313 (this.closeAttribute && e.target.hasAttribute(this.closeAttribute))) { | |
| 14314 this.toggle(); | |
| 14315 } else { | |
| 14316 if (this.autoCloseJob) { | |
| 14317 this.autoCloseJob.stop(); | |
| 14318 this.autoCloseJob = null; | |
| 14319 } | |
| 14320 } | |
| 14321 }, | |
| 14322 | |
| 14323 // We use the traditional approach of capturing events on document | |
| 14324 // to to determine if the overlay needs to close. However, due to | |
| 14325 // ShadowDOM event retargeting, the event target is not useful. Instead | |
| 14326 // of using it, we attempt to close asynchronously and prevent the close | |
| 14327 // if a tap event is immediately heard on the target. | |
| 14328 // TODO(sorvell): This approach will not work with modal. For | |
| 14329 // this we need a scrim. | |
| 14330 captureHandler: function(e) { | |
| 14331 if (!this.autoCloseDisabled && (currentOverlay() == this)) { | |
| 14332 this.autoCloseJob = this.job(this.autoCloseJob, function() { | |
| 14333 this.close(); | |
| 14334 }); | |
| 14335 } | |
| 14336 }, | |
| 14337 | |
| 14338 keydownHandler: function(e) { | |
| 14339 if (!this.autoCloseDisabled && (e.keyCode == this.keyHelper.ESCAPE_KEY)) { | |
| 14340 this.close(); | |
| 14341 e.stopPropagation(); | |
| 14342 } | |
| 14343 }, | |
| 14344 | |
| 14345 /** | |
| 14346 * Extensions of core-overlay should implement the `resizeHandler` | |
| 14347 * method to adjust the size and position of the overlay when the | |
| 14348 * browser window resizes. | |
| 14349 * @method resizeHandler | |
| 14350 */ | |
| 14351 resizeHandler: function() { | |
| 14352 this.updateTargetDimensions(); | |
| 14353 }, | |
| 14354 | |
| 14355 // TODO(sorvell): these utility methods should not be here. | |
| 14356 addElementListenerList: function(node, events) { | |
| 14357 for (var i in events) { | |
| 14358 this.addElementListener(node, i, events[i]); | |
| 14359 } | |
| 14360 }, | |
| 14361 | |
| 14362 removeElementListenerList: function(node, events) { | |
| 14363 for (var i in events) { | |
| 14364 this.removeElementListener(node, i, events[i]); | |
| 14365 } | |
| 14366 }, | |
| 14367 | |
| 14368 enableElementListener: function(enable, node, event, methodName, capture) { | |
| 14369 if (enable) { | |
| 14370 this.addElementListener(node, event, methodName, capture); | |
| 14371 } else { | |
| 14372 this.removeElementListener(node, event, methodName, capture); | |
| 14373 } | |
| 14374 }, | |
| 14375 | |
| 14376 addElementListener: function(node, event, methodName, capture) { | |
| 14377 var fn = this._makeBoundListener(methodName); | |
| 14378 if (node && fn) { | |
| 14379 Polymer.addEventListener(node, event, fn, capture); | |
| 14380 } | |
| 14381 }, | |
| 14382 | |
| 14383 removeElementListener: function(node, event, methodName, capture) { | |
| 14384 var fn = this._makeBoundListener(methodName); | |
| 14385 if (node && fn) { | |
| 14386 Polymer.removeEventListener(node, event, fn, capture); | |
| 14387 } | |
| 14388 }, | |
| 14389 | |
| 14390 _makeBoundListener: function(methodName) { | |
| 14391 var self = this, method = this[methodName]; | |
| 14392 if (!method) { | |
| 14393 return; | |
| 14394 } | |
| 14395 var bound = '_bound' + methodName; | |
| 14396 if (!this[bound]) { | |
| 14397 this[bound] = function(e) { | |
| 14398 method.call(self, e); | |
| 14399 }; | |
| 14400 } | |
| 14401 return this[bound]; | |
| 14402 } | |
| 14403 | |
| 14404 }, Polymer.CoreResizer)); | |
| 14405 | |
| 14406 // TODO(sorvell): This should be an element with private state so it can | |
| 14407 // be independent of overlay. | |
| 14408 // track overlays for z-index and focus managemant | |
| 14409 var overlays = []; | |
| 14410 function addOverlay(overlay) { | |
| 14411 var z0 = currentOverlayZ(); | |
| 14412 overlays.push(overlay); | |
| 14413 var z1 = currentOverlayZ(); | |
| 14414 if (z1 <= z0) { | |
| 14415 applyOverlayZ(overlay, z0); | |
| 14416 } | |
| 14417 } | |
| 14418 | |
| 14419 function removeOverlay(overlay) { | |
| 14420 var i = overlays.indexOf(overlay); | |
| 14421 if (i >= 0) { | |
| 14422 overlays.splice(i, 1); | |
| 14423 setZ(overlay, ''); | |
| 14424 } | |
| 14425 } | |
| 14426 | |
| 14427 function applyOverlayZ(overlay, aboveZ) { | |
| 14428 setZ(overlay.target, aboveZ + 2); | |
| 14429 } | |
| 14430 | |
| 14431 function setZ(element, z) { | |
| 14432 element.style.zIndex = z; | |
| 14433 } | |
| 14434 | |
| 14435 function currentOverlay() { | |
| 14436 return overlays[overlays.length-1]; | |
| 14437 } | |
| 14438 | |
| 14439 var DEFAULT_Z = 10; | |
| 14440 | |
| 14441 function currentOverlayZ() { | |
| 14442 var z; | |
| 14443 var current = currentOverlay(); | |
| 14444 if (current) { | |
| 14445 var z1 = window.getComputedStyle(current.target).zIndex; | |
| 14446 if (!isNaN(z1)) { | |
| 14447 z = Number(z1); | |
| 14448 } | |
| 14449 } | |
| 14450 return z || DEFAULT_Z; | |
| 14451 } | |
| 14452 | |
| 14453 function focusOverlay() { | |
| 14454 var current = currentOverlay(); | |
| 14455 // We have to be careful to focus the next overlay _after_ any current | |
| 14456 // transitions are complete (due to the state being toggled prior to the | |
| 14457 // transition). Otherwise, we risk infinite recursion when a transitioning | |
| 14458 // (closed) overlay becomes the current overlay. | |
| 14459 // | |
| 14460 // NOTE: We make the assumption that any overlay that completes a transition | |
| 14461 // will call into focusOverlay to kick the process back off. Currently: | |
| 14462 // transitionend -> applyFocus -> focusOverlay. | |
| 14463 if (current && !current.transitioning) { | |
| 14464 current.applyFocus(); | |
| 14465 } | |
| 14466 } | |
| 14467 | |
| 14468 var backdrops = []; | |
| 14469 function trackBackdrop(element) { | |
| 14470 if (element.opened) { | |
| 14471 backdrops.push(element); | |
| 14472 } else { | |
| 14473 var i = backdrops.indexOf(element); | |
| 14474 if (i >= 0) { | |
| 14475 backdrops.splice(i, 1); | |
| 14476 } | |
| 14477 } | |
| 14478 } | |
| 14479 | |
| 14480 function getBackdrops() { | |
| 14481 return backdrops; | |
| 14482 } | |
| 14483 })(); | |
| 14484 ; | |
| 14485 | |
| 14486 | |
| 14487 Polymer('core-transition-css', { | |
| 14488 | |
| 14489 /** | |
| 14490 * The class that will be applied to all animated nodes. | |
| 14491 * | |
| 14492 * @attribute baseClass | |
| 14493 * @type string | |
| 14494 * @default "core-transition" | |
| 14495 */ | |
| 14496 baseClass: 'core-transition', | |
| 14497 | |
| 14498 /** | |
| 14499 * The class that will be applied to nodes in the opened state. | |
| 14500 * | |
| 14501 * @attribute openedClass | |
| 14502 * @type string | |
| 14503 * @default "core-opened" | |
| 14504 */ | |
| 14505 openedClass: 'core-opened', | |
| 14506 | |
| 14507 /** | |
| 14508 * The class that will be applied to nodes in the closed state. | |
| 14509 * | |
| 14510 * @attribute closedClass | |
| 14511 * @type string | |
| 14512 * @default "core-closed" | |
| 14513 */ | |
| 14514 closedClass: 'core-closed', | |
| 14515 | |
| 14516 /** | |
| 14517 * Event to listen to for animation completion. | |
| 14518 * | |
| 14519 * @attribute completeEventName | |
| 14520 * @type string | |
| 14521 * @default "transitionEnd" | |
| 14522 */ | |
| 14523 completeEventName: 'transitionend', | |
| 14524 | |
| 14525 publish: { | |
| 14526 /** | |
| 14527 * A secondary configuration attribute for the animation. The class | |
| 14528 * `<baseClass>-<transitionType` is applied to the animated node during | |
| 14529 * `setup`. | |
| 14530 * | |
| 14531 * @attribute transitionType | |
| 14532 * @type string | |
| 14533 */ | |
| 14534 transitionType: null | |
| 14535 }, | |
| 14536 | |
| 14537 registerCallback: function(element) { | |
| 14538 this.transitionStyle = element.templateContent().firstElementChild; | |
| 14539 }, | |
| 14540 | |
| 14541 // template is just for loading styles, we don't need a shadowRoot | |
| 14542 fetchTemplate: function() { | |
| 14543 return null; | |
| 14544 }, | |
| 14545 | |
| 14546 go: function(node, state) { | |
| 14547 if (state.opened !== undefined) { | |
| 14548 this.transitionOpened(node, state.opened); | |
| 14549 } | |
| 14550 }, | |
| 14551 | |
| 14552 setup: function(node) { | |
| 14553 if (!node._hasTransitionStyle) { | |
| 14554 if (!node.shadowRoot) { | |
| 14555 node.createShadowRoot().innerHTML = '<content></content>'; | |
| 14556 } | |
| 14557 this.installScopeStyle(this.transitionStyle, 'transition', | |
| 14558 node.shadowRoot); | |
| 14559 node._hasTransitionStyle = true; | |
| 14560 } | |
| 14561 node.classList.add(this.baseClass); | |
| 14562 if (this.transitionType) { | |
| 14563 node.classList.add(this.baseClass + '-' + this.transitionType); | |
| 14564 } | |
| 14565 }, | |
| 14566 | |
| 14567 teardown: function(node) { | |
| 14568 node.classList.remove(this.baseClass); | |
| 14569 if (this.transitionType) { | |
| 14570 node.classList.remove(this.baseClass + '-' + this.transitionType); | |
| 14571 } | |
| 14572 }, | |
| 14573 | |
| 14574 transitionOpened: function(node, opened) { | |
| 14575 this.listenOnce(node, this.completeEventName, function() { | |
| 14576 if (!opened) { | |
| 14577 node.classList.remove(this.closedClass); | |
| 14578 } | |
| 14579 this.complete(node); | |
| 14580 }); | |
| 14581 node.classList.toggle(this.openedClass, opened); | |
| 14582 node.classList.toggle(this.closedClass, !opened); | |
| 14583 } | |
| 14584 | |
| 14585 }); | |
| 14586 ; | |
| 14587 | |
| 14588 | |
| 14589 Polymer('paper-dialog-base',{ | |
| 14590 | |
| 14591 publish: { | |
| 14592 | |
| 14593 /** | |
| 14594 * The title of the dialog. | |
| 14595 * | |
| 14596 * @attribute heading | |
| 14597 * @type string | |
| 14598 * @default '' | |
| 14599 */ | |
| 14600 heading: '', | |
| 14601 | |
| 14602 /** | |
| 14603 * @attribute transition | |
| 14604 * @type string | |
| 14605 * @default '' | |
| 14606 */ | |
| 14607 transition: '', | |
| 14608 | |
| 14609 /** | |
| 14610 * @attribute layered | |
| 14611 * @type boolean | |
| 14612 * @default true | |
| 14613 */ | |
| 14614 layered: true | |
| 14615 }, | |
| 14616 | |
| 14617 ready: function() { | |
| 14618 this.super(); | |
| 14619 this.sizingTarget = this.$.scroller; | |
| 14620 }, | |
| 14621 | |
| 14622 headingChanged: function(old) { | |
| 14623 var label = this.getAttribute('aria-label'); | |
| 14624 if (!label || label === old) { | |
| 14625 this.setAttribute('aria-label', this.heading); | |
| 14626 } | |
| 14627 }, | |
| 14628 | |
| 14629 openAction: function() { | |
| 14630 if (this.$.scroller.scrollTop) { | |
| 14631 this.$.scroller.scrollTop = 0; | |
| 14632 } | |
| 14633 } | |
| 14634 | |
| 14635 }); | |
| 14636 | |
| 14637 ; | |
| 14638 Polymer('paper-dialog');; | |
| 14639 | |
| 14640 Polymer('core-selection', { | |
| 14641 /** | |
| 14642 * If true, multiple selections are allowed. | |
| 14643 * | |
| 14644 * @attribute multi | |
| 14645 * @type boolean | |
| 14646 * @default false | |
| 14647 */ | |
| 14648 multi: false, | |
| 14649 ready: function() { | |
| 14650 this.clear(); | |
| 14651 }, | |
| 14652 clear: function() { | |
| 14653 this.selection = []; | |
| 14654 }, | |
| 14655 /** | |
| 14656 * Retrieves the selected item(s). | |
| 14657 * @method getSelection | |
| 14658 * @returns Returns the selected item(s). If the multi property is true, | |
| 14659 * getSelection will return an array, otherwise it will return | |
| 14660 * the selected item or undefined if there is no selection. | |
| 14661 */ | |
| 14662 getSelection: function() { | |
| 14663 return this.multi ? this.selection : this.selection[0]; | |
| 14664 }, | |
| 14665 /** | |
| 14666 * Indicates if a given item is selected. | |
| 14667 * @method isSelected | |
| 14668 * @param {any} item The item whose selection state should be checked. | |
| 14669 * @returns Returns true if `item` is selected. | |
| 14670 */ | |
| 14671 isSelected: function(item) { | |
| 14672 return this.selection.indexOf(item) >= 0; | |
| 14673 }, | |
| 14674 setItemSelected: function(item, isSelected) { | |
| 14675 if (item !== undefined && item !== null) { | |
| 14676 if (isSelected) { | |
| 14677 this.selection.push(item); | |
| 14678 } else { | |
| 14679 var i = this.selection.indexOf(item); | |
| 14680 if (i >= 0) { | |
| 14681 this.selection.splice(i, 1); | |
| 14682 } | |
| 14683 } | |
| 14684 this.fire("core-select", {isSelected: isSelected, item: item}); | |
| 14685 } | |
| 14686 }, | |
| 14687 /** | |
| 14688 * Set the selection state for a given `item`. If the multi property | |
| 14689 * is true, then the selected state of `item` will be toggled; otherwise | |
| 14690 * the `item` will be selected. | |
| 14691 * @method select | |
| 14692 * @param {any} item: The item to select. | |
| 14693 */ | |
| 14694 select: function(item) { | |
| 14695 if (this.multi) { | |
| 14696 this.toggle(item); | |
| 14697 } else if (this.getSelection() !== item) { | |
| 14698 this.setItemSelected(this.getSelection(), false); | |
| 14699 this.setItemSelected(item, true); | |
| 14700 } | |
| 14701 }, | |
| 14702 /** | |
| 14703 * Toggles the selection state for `item`. | |
| 14704 * @method toggle | |
| 14705 * @param {any} item: The item to toggle. | |
| 14706 */ | |
| 14707 toggle: function(item) { | |
| 14708 this.setItemSelected(item, !this.isSelected(item)); | |
| 14709 } | |
| 14710 }); | |
| 14711 ; | |
| 14712 | |
| 14713 | |
| 14714 Polymer('core-selector', { | |
| 14715 | |
| 14716 /** | |
| 14717 * Gets or sets the selected element. Default to use the index | |
| 14718 * of the item element. | |
| 14719 * | |
| 14720 * If you want a specific attribute value of the element to be | |
| 14721 * used instead of index, set "valueattr" to that attribute name. | |
| 14722 * | |
| 14723 * Example: | |
| 14724 * | |
| 14725 * <core-selector valueattr="label" selected="foo"> | |
| 14726 * <div label="foo"></div> | |
| 14727 * <div label="bar"></div> | |
| 14728 * <div label="zot"></div> | |
| 14729 * </core-selector> | |
| 14730 * | |
| 14731 * In multi-selection this should be an array of values. | |
| 14732 * | |
| 14733 * Example: | |
| 14734 * | |
| 14735 * <core-selector id="selector" valueattr="label" multi> | |
| 14736 * <div label="foo"></div> | |
| 14737 * <div label="bar"></div> | |
| 14738 * <div label="zot"></div> | |
| 14739 * </core-selector> | |
| 14740 * | |
| 14741 * this.$.selector.selected = ['foo', 'zot']; | |
| 14742 * | |
| 14743 * @attribute selected | |
| 14744 * @type Object | |
| 14745 * @default null | |
| 14746 */ | |
| 14747 selected: null, | |
| 14748 | |
| 14749 /** | |
| 14750 * If true, multiple selections are allowed. | |
| 14751 * | |
| 14752 * @attribute multi | |
| 14753 * @type boolean | |
| 14754 * @default false | |
| 14755 */ | |
| 14756 multi: false, | |
| 14757 | |
| 14758 /** | |
| 14759 * Specifies the attribute to be used for "selected" attribute. | |
| 14760 * | |
| 14761 * @attribute valueattr | |
| 14762 * @type string | |
| 14763 * @default 'name' | |
| 14764 */ | |
| 14765 valueattr: 'name', | |
| 14766 | |
| 14767 /** | |
| 14768 * Specifies the CSS class to be used to add to the selected element. | |
| 14769 * | |
| 14770 * @attribute selectedClass | |
| 14771 * @type string | |
| 14772 * @default 'core-selected' | |
| 14773 */ | |
| 14774 selectedClass: 'core-selected', | |
| 14775 | |
| 14776 /** | |
| 14777 * Specifies the property to be used to set on the selected element | |
| 14778 * to indicate its active state. | |
| 14779 * | |
| 14780 * @attribute selectedProperty | |
| 14781 * @type string | |
| 14782 * @default '' | |
| 14783 */ | |
| 14784 selectedProperty: '', | |
| 14785 | |
| 14786 /** | |
| 14787 * Specifies the attribute to set on the selected element to indicate | |
| 14788 * its active state. | |
| 14789 * | |
| 14790 * @attribute selectedAttribute | |
| 14791 * @type string | |
| 14792 * @default 'active' | |
| 14793 */ | |
| 14794 selectedAttribute: 'active', | |
| 14795 | |
| 14796 /** | |
| 14797 * Returns the currently selected element. In multi-selection this returns | |
| 14798 * an array of selected elements. | |
| 14799 * Note that you should not use this to set the selection. Instead use | |
| 14800 * `selected`. | |
| 14801 * | |
| 14802 * @attribute selectedItem | |
| 14803 * @type Object | |
| 14804 * @default null | |
| 14805 */ | |
| 14806 selectedItem: null, | |
| 14807 | |
| 14808 /** | |
| 14809 * In single selection, this returns the model associated with the | |
| 14810 * selected element. | |
| 14811 * Note that you should not use this to set the selection. Instead use | |
| 14812 * `selected`. | |
| 14813 * | |
| 14814 * @attribute selectedModel | |
| 14815 * @type Object | |
| 14816 * @default null | |
| 14817 */ | |
| 14818 selectedModel: null, | |
| 14819 | |
| 14820 /** | |
| 14821 * In single selection, this returns the selected index. | |
| 14822 * Note that you should not use this to set the selection. Instead use | |
| 14823 * `selected`. | |
| 14824 * | |
| 14825 * @attribute selectedIndex | |
| 14826 * @type number | |
| 14827 * @default -1 | |
| 14828 */ | |
| 14829 selectedIndex: -1, | |
| 14830 | |
| 14831 /** | |
| 14832 * Nodes with local name that are in the list will not be included | |
| 14833 * in the selection items. In the following example, `items` returns four | |
| 14834 * `core-item`'s and doesn't include `h3` and `hr`. | |
| 14835 * | |
| 14836 * <core-selector excludedLocalNames="h3 hr"> | |
| 14837 * <h3>Header</h3> | |
| 14838 * <core-item>Item1</core-item> | |
| 14839 * <core-item>Item2</core-item> | |
| 14840 * <hr> | |
| 14841 * <core-item>Item3</core-item> | |
| 14842 * <core-item>Item4</core-item> | |
| 14843 * </core-selector> | |
| 14844 * | |
| 14845 * @attribute excludedLocalNames | |
| 14846 * @type string | |
| 14847 * @default '' | |
| 14848 */ | |
| 14849 excludedLocalNames: '', | |
| 14850 | |
| 14851 /** | |
| 14852 * The target element that contains items. If this is not set | |
| 14853 * core-selector is the container. | |
| 14854 * | |
| 14855 * @attribute target | |
| 14856 * @type Object | |
| 14857 * @default null | |
| 14858 */ | |
| 14859 target: null, | |
| 14860 | |
| 14861 /** | |
| 14862 * This can be used to query nodes from the target node to be used for | |
| 14863 * selection items. Note this only works if `target` is set | |
| 14864 * and is not `core-selector` itself. | |
| 14865 * | |
| 14866 * Example: | |
| 14867 * | |
| 14868 * <core-selector target="{{$.myForm}}" itemsSelector="input[type=radi
o]"></core-selector> | |
| 14869 * <form id="myForm"> | |
| 14870 * <label><input type="radio" name="color" value="red"> Red</label>
<br> | |
| 14871 * <label><input type="radio" name="color" value="green"> Green</lab
el> <br> | |
| 14872 * <label><input type="radio" name="color" value="blue"> Blue</label
> <br> | |
| 14873 * <p>color = {{color}}</p> | |
| 14874 * </form> | |
| 14875 * | |
| 14876 * @attribute itemsSelector | |
| 14877 * @type string | |
| 14878 * @default '' | |
| 14879 */ | |
| 14880 itemsSelector: '', | |
| 14881 | |
| 14882 /** | |
| 14883 * The event that would be fired from the item element to indicate | |
| 14884 * it is being selected. | |
| 14885 * | |
| 14886 * @attribute activateEvent | |
| 14887 * @type string | |
| 14888 * @default 'tap' | |
| 14889 */ | |
| 14890 activateEvent: 'tap', | |
| 14891 | |
| 14892 /** | |
| 14893 * Set this to true to disallow changing the selection via the | |
| 14894 * `activateEvent`. | |
| 14895 * | |
| 14896 * @attribute notap | |
| 14897 * @type boolean | |
| 14898 * @default false | |
| 14899 */ | |
| 14900 notap: false, | |
| 14901 | |
| 14902 defaultExcludedLocalNames: 'template', | |
| 14903 | |
| 14904 observe: { | |
| 14905 'selected multi': 'selectedChanged' | |
| 14906 }, | |
| 14907 | |
| 14908 ready: function() { | |
| 14909 this.activateListener = this.activateHandler.bind(this); | |
| 14910 this.itemFilter = this.filterItem.bind(this); | |
| 14911 this.excludedLocalNamesChanged(); | |
| 14912 this.observer = new MutationObserver(this.updateSelected.bind(this)); | |
| 14913 if (!this.target) { | |
| 14914 this.target = this; | |
| 14915 } | |
| 14916 }, | |
| 14917 | |
| 14918 /** | |
| 14919 * Returns an array of all items. | |
| 14920 * | |
| 14921 * @property items | |
| 14922 */ | |
| 14923 get items() { | |
| 14924 if (!this.target) { | |
| 14925 return []; | |
| 14926 } | |
| 14927 var nodes = this.target !== this ? (this.itemsSelector ? | |
| 14928 this.target.querySelectorAll(this.itemsSelector) : | |
| 14929 this.target.children) : this.$.items.getDistributedNodes(); | |
| 14930 return Array.prototype.filter.call(nodes, this.itemFilter); | |
| 14931 }, | |
| 14932 | |
| 14933 filterItem: function(node) { | |
| 14934 return !this._excludedNames[node.localName]; | |
| 14935 }, | |
| 14936 | |
| 14937 excludedLocalNamesChanged: function() { | |
| 14938 this._excludedNames = {}; | |
| 14939 var s = this.defaultExcludedLocalNames; | |
| 14940 if (this.excludedLocalNames) { | |
| 14941 s += ' ' + this.excludedLocalNames; | |
| 14942 } | |
| 14943 s.split(/\s+/g).forEach(function(n) { | |
| 14944 this._excludedNames[n] = 1; | |
| 14945 }, this); | |
| 14946 }, | |
| 14947 | |
| 14948 targetChanged: function(old) { | |
| 14949 if (old) { | |
| 14950 this.removeListener(old); | |
| 14951 this.observer.disconnect(); | |
| 14952 this.clearSelection(); | |
| 14953 } | |
| 14954 if (this.target) { | |
| 14955 this.addListener(this.target); | |
| 14956 this.observer.observe(this.target, {childList: true}); | |
| 14957 this.updateSelected(); | |
| 14958 } | |
| 14959 }, | |
| 14960 | |
| 14961 addListener: function(node) { | |
| 14962 Polymer.addEventListener(node, this.activateEvent, this.activateListener
); | |
| 14963 }, | |
| 14964 | |
| 14965 removeListener: function(node) { | |
| 14966 Polymer.removeEventListener(node, this.activateEvent, this.activateListe
ner); | |
| 14967 }, | |
| 14968 | |
| 14969 /** | |
| 14970 * Returns the selected item(s). If the `multi` property is true, | |
| 14971 * this will return an array, otherwise it will return | |
| 14972 * the selected item or undefined if there is no selection. | |
| 14973 */ | |
| 14974 get selection() { | |
| 14975 return this.$.selection.getSelection(); | |
| 14976 }, | |
| 14977 | |
| 14978 selectedChanged: function() { | |
| 14979 // TODO(ffu): Right now this is the only way to know that the `selected` | |
| 14980 // is an array and was mutated, as opposed to newly assigned. | |
| 14981 if (arguments.length === 1) { | |
| 14982 this.processSplices(arguments[0]); | |
| 14983 } else { | |
| 14984 this.updateSelected(); | |
| 14985 } | |
| 14986 }, | |
| 14987 | |
| 14988 updateSelected: function() { | |
| 14989 this.validateSelected(); | |
| 14990 if (this.multi) { | |
| 14991 this.clearSelection(this.selected) | |
| 14992 this.selected && this.selected.forEach(function(s) { | |
| 14993 this.setValueSelected(s, true) | |
| 14994 }, this); | |
| 14995 } else { | |
| 14996 this.valueToSelection(this.selected); | |
| 14997 } | |
| 14998 }, | |
| 14999 | |
| 15000 validateSelected: function() { | |
| 15001 // convert to an array for multi-selection | |
| 15002 if (this.multi && !Array.isArray(this.selected) && | |
| 15003 this.selected != null) { | |
| 15004 this.selected = [this.selected]; | |
| 15005 // use the first selected in the array for single-selection | |
| 15006 } else if (!this.multi && Array.isArray(this.selected)) { | |
| 15007 var s = this.selected[0]; | |
| 15008 this.clearSelection([s]); | |
| 15009 this.selected = s; | |
| 15010 } | |
| 15011 }, | |
| 15012 | |
| 15013 processSplices: function(splices) { | |
| 15014 for (var i = 0, splice; splice = splices[i]; i++) { | |
| 15015 for (var j = 0; j < splice.removed.length; j++) { | |
| 15016 this.setValueSelected(splice.removed[j], false); | |
| 15017 } | |
| 15018 for (var j = 0; j < splice.addedCount; j++) { | |
| 15019 this.setValueSelected(this.selected[splice.index + j], true); | |
| 15020 } | |
| 15021 } | |
| 15022 }, | |
| 15023 | |
| 15024 clearSelection: function(excludes) { | |
| 15025 this.$.selection.selection.slice().forEach(function(item) { | |
| 15026 var v = this.valueForNode(item) || this.items.indexOf(item); | |
| 15027 if (!excludes || excludes.indexOf(v) < 0) { | |
| 15028 this.$.selection.setItemSelected(item, false); | |
| 15029 } | |
| 15030 }, this); | |
| 15031 }, | |
| 15032 | |
| 15033 valueToSelection: function(value) { | |
| 15034 var item = this.valueToItem(value); | |
| 15035 this.$.selection.select(item); | |
| 15036 }, | |
| 15037 | |
| 15038 setValueSelected: function(value, isSelected) { | |
| 15039 var item = this.valueToItem(value); | |
| 15040 if (isSelected ^ this.$.selection.isSelected(item)) { | |
| 15041 this.$.selection.setItemSelected(item, isSelected); | |
| 15042 } | |
| 15043 }, | |
| 15044 | |
| 15045 updateSelectedItem: function() { | |
| 15046 this.selectedItem = this.selection; | |
| 15047 }, | |
| 15048 | |
| 15049 selectedItemChanged: function() { | |
| 15050 if (this.selectedItem) { | |
| 15051 var t = this.selectedItem.templateInstance; | |
| 15052 this.selectedModel = t ? t.model : undefined; | |
| 15053 } else { | |
| 15054 this.selectedModel = null; | |
| 15055 } | |
| 15056 this.selectedIndex = this.selectedItem ? | |
| 15057 parseInt(this.valueToIndex(this.selected)) : -1; | |
| 15058 }, | |
| 15059 | |
| 15060 valueToItem: function(value) { | |
| 15061 return (value === null || value === undefined) ? | |
| 15062 null : this.items[this.valueToIndex(value)]; | |
| 15063 }, | |
| 15064 | |
| 15065 valueToIndex: function(value) { | |
| 15066 // find an item with value == value and return it's index | |
| 15067 for (var i=0, items=this.items, c; (c=items[i]); i++) { | |
| 15068 if (this.valueForNode(c) == value) { | |
| 15069 return i; | |
| 15070 } | |
| 15071 } | |
| 15072 // if no item found, the value itself is probably the index | |
| 15073 return value; | |
| 15074 }, | |
| 15075 | |
| 15076 valueForNode: function(node) { | |
| 15077 return node[this.valueattr] || node.getAttribute(this.valueattr); | |
| 15078 }, | |
| 15079 | |
| 15080 // events fired from <core-selection> object | |
| 15081 selectionSelect: function(e, detail) { | |
| 15082 this.updateSelectedItem(); | |
| 15083 if (detail.item) { | |
| 15084 this.applySelection(detail.item, detail.isSelected); | |
| 15085 } | |
| 15086 }, | |
| 15087 | |
| 15088 applySelection: function(item, isSelected) { | |
| 15089 if (this.selectedClass) { | |
| 15090 item.classList.toggle(this.selectedClass, isSelected); | |
| 15091 } | |
| 15092 if (this.selectedProperty) { | |
| 15093 item[this.selectedProperty] = isSelected; | |
| 15094 } | |
| 15095 if (this.selectedAttribute && item.setAttribute) { | |
| 15096 if (isSelected) { | |
| 15097 item.setAttribute(this.selectedAttribute, ''); | |
| 15098 } else { | |
| 15099 item.removeAttribute(this.selectedAttribute); | |
| 15100 } | |
| 15101 } | |
| 15102 }, | |
| 15103 | |
| 15104 // event fired from host | |
| 15105 activateHandler: function(e) { | |
| 15106 if (!this.notap) { | |
| 15107 var i = this.findDistributedTarget(e.target, this.items); | |
| 15108 if (i >= 0) { | |
| 15109 var item = this.items[i]; | |
| 15110 var s = this.valueForNode(item) || i; | |
| 15111 if (this.multi) { | |
| 15112 if (this.selected) { | |
| 15113 this.addRemoveSelected(s); | |
| 15114 } else { | |
| 15115 this.selected = [s]; | |
| 15116 } | |
| 15117 } else { | |
| 15118 this.selected = s; | |
| 15119 } | |
| 15120 this.asyncFire('core-activate', {item: item}); | |
| 15121 } | |
| 15122 } | |
| 15123 }, | |
| 15124 | |
| 15125 addRemoveSelected: function(value) { | |
| 15126 var i = this.selected.indexOf(value); | |
| 15127 if (i >= 0) { | |
| 15128 this.selected.splice(i, 1); | |
| 15129 } else { | |
| 15130 this.selected.push(value); | |
| 15131 } | |
| 15132 }, | |
| 15133 | |
| 15134 findDistributedTarget: function(target, nodes) { | |
| 15135 // find first ancestor of target (including itself) that | |
| 15136 // is in nodes, if any | |
| 15137 while (target && target != this) { | |
| 15138 var i = Array.prototype.indexOf.call(nodes, target); | |
| 15139 if (i >= 0) { | |
| 15140 return i; | |
| 15141 } | |
| 15142 target = target.parentNode; | |
| 15143 } | |
| 15144 }, | |
| 15145 | |
| 15146 selectIndex: function(index) { | |
| 15147 var item = this.items[index]; | |
| 15148 if (item) { | |
| 15149 this.selected = this.valueForNode(item) || index; | |
| 15150 return item; | |
| 15151 } | |
| 15152 }, | |
| 15153 | |
| 15154 /** | |
| 15155 * Selects the previous item. This should be used in single selection only
. | |
| 15156 * | |
| 15157 * @method selectPrevious | |
| 15158 * @param {boolean} wrapped if true and it is already at the first item, | |
| 15159 * wrap to the end | |
| 15160 * @returns the previous item or undefined if there is none | |
| 15161 */ | |
| 15162 selectPrevious: function(wrapped) { | |
| 15163 var i = wrapped && !this.selectedIndex ? | |
| 15164 this.items.length - 1 : this.selectedIndex - 1; | |
| 15165 return this.selectIndex(i); | |
| 15166 }, | |
| 15167 | |
| 15168 /** | |
| 15169 * Selects the next item. This should be used in single selection only. | |
| 15170 * | |
| 15171 * @method selectNext | |
| 15172 * @param {boolean} wrapped if true and it is already at the last item, | |
| 15173 * wrap to the front | |
| 15174 * @returns the next item or undefined if there is none | |
| 15175 */ | |
| 15176 selectNext: function(wrapped) { | |
| 15177 var i = wrapped && this.selectedIndex >= this.items.length - 1 ? | |
| 15178 0 : this.selectedIndex + 1; | |
| 15179 return this.selectIndex(i); | |
| 15180 } | |
| 15181 | |
| 15182 }); | |
| 15183 ; | |
| 15184 | |
| 15185 | |
| 15186 Polymer('paper-radio-group', { | |
| 15187 nextIndex: function(index) { | |
| 15188 var items = this.items; | |
| 15189 var newIndex = index; | |
| 15190 do { | |
| 15191 newIndex = (newIndex + 1) % items.length; | |
| 15192 if (newIndex === index) { | |
| 15193 break; | |
| 15194 } | |
| 15195 } while (items[newIndex].disabled); | |
| 15196 return newIndex; | |
| 15197 }, | |
| 15198 previousIndex: function(index) { | |
| 15199 var items = this.items; | |
| 15200 var newIndex = index; | |
| 15201 do { | |
| 15202 newIndex = (newIndex || items.length) - 1; | |
| 15203 if (newIndex === index) { | |
| 15204 break; | |
| 15205 } | |
| 15206 } while (items[newIndex].disabled); | |
| 15207 return newIndex; | |
| 15208 }, | |
| 15209 selectNext: function() { | |
| 15210 var node = this.selectIndex(this.nextIndex(this.selectedIndex)); | |
| 15211 node.focus(); | |
| 15212 }, | |
| 15213 selectPrevious: function() { | |
| 15214 var node = this.selectIndex(this.previousIndex(this.selectedIndex)); | |
| 15215 node.focus(); | |
| 15216 }, | |
| 15217 selectedAttribute: 'checked', | |
| 15218 activateEvent: 'change' | |
| 15219 | |
| 15220 }); | |
| 15221 | |
| 15222 ; | |
| 15223 | |
| 15224 Polymer('paper-spinner',{ | |
| 15225 eventDelegates: { | |
| 15226 'animationend': 'reset', | |
| 15227 'webkitAnimationEnd': 'reset' | |
| 15228 }, | |
| 15229 publish: { | |
| 15230 /** | |
| 15231 * Displays the spinner. | |
| 15232 * | |
| 15233 * @attribute active | |
| 15234 * @type boolean | |
| 15235 * @default false | |
| 15236 */ | |
| 15237 active: {value: false, reflect: true}, | |
| 15238 | |
| 15239 /** | |
| 15240 * Alternative text content for accessibility support. | |
| 15241 * If alt is present, it will add an aria-label whose content matches al
t when active. | |
| 15242 * If alt is not present, it will default to 'loading' as the alt value. | |
| 15243 * @attribute alt | |
| 15244 * @type string | |
| 15245 * @default 'loading' | |
| 15246 */ | |
| 15247 alt: {value: 'loading', reflect: true} | |
| 15248 }, | |
| 15249 | |
| 15250 ready: function() { | |
| 15251 // Allow user-provided `aria-label` take preference to any other text al
ternative. | |
| 15252 if (this.hasAttribute('aria-label')) { | |
| 15253 this.alt = this.getAttribute('aria-label'); | |
| 15254 } else { | |
| 15255 this.setAttribute('aria-label', this.alt); | |
| 15256 } | |
| 15257 if (!this.active) { | |
| 15258 this.setAttribute('aria-hidden', 'true'); | |
| 15259 } | |
| 15260 }, | |
| 15261 | |
| 15262 activeChanged: function() { | |
| 15263 if (this.active) { | |
| 15264 this.$.spinnerContainer.classList.remove('cooldown'); | |
| 15265 this.$.spinnerContainer.classList.add('active'); | |
| 15266 this.removeAttribute('aria-hidden'); | |
| 15267 } else { | |
| 15268 this.$.spinnerContainer.classList.add('cooldown'); | |
| 15269 this.setAttribute('aria-hidden', 'true'); | |
| 15270 } | |
| 15271 }, | |
| 15272 | |
| 15273 altChanged: function() { | |
| 15274 if (this.alt === '') { | |
| 15275 this.setAttribute('aria-hidden', 'true'); | |
| 15276 } else { | |
| 15277 this.removeAttribute('aria-hidden'); | |
| 15278 } | |
| 15279 this.setAttribute('aria-label', this.alt); | |
| 15280 }, | |
| 15281 | |
| 15282 reset: function() { | |
| 15283 this.$.spinnerContainer.classList.remove('active', 'cooldown'); | |
| 15284 } | |
| 15285 }); | |
| 15286 ; | |
| 15287 Polymer('core-menu');; | |
| 15288 | |
| 15289 | |
| 15290 (function() { | |
| 15291 | |
| 15292 var p = { | |
| 15293 | |
| 15294 eventDelegates: { | |
| 15295 down: 'downAction', | |
| 15296 up: 'upAction' | |
| 15297 }, | |
| 15298 | |
| 15299 toggleBackground: function() { | |
| 15300 if (this.active) { | |
| 15301 | |
| 15302 if (!this.$.bg) { | |
| 15303 var bg = document.createElement('div'); | |
| 15304 bg.setAttribute('id', 'bg'); | |
| 15305 bg.setAttribute('fit', ''); | |
| 15306 bg.style.opacity = 0.25; | |
| 15307 this.$.bg = bg; | |
| 15308 this.shadowRoot.insertBefore(bg, this.shadowRoot.firstChild); | |
| 15309 } | |
| 15310 this.$.bg.style.backgroundColor = getComputedStyle(this).color; | |
| 15311 | |
| 15312 } else { | |
| 15313 | |
| 15314 if (this.$.bg) { | |
| 15315 this.$.bg.style.backgroundColor = ''; | |
| 15316 } | |
| 15317 } | |
| 15318 }, | |
| 15319 | |
| 15320 activeChanged: function() { | |
| 15321 this.super(); | |
| 15322 | |
| 15323 if (this.toggle && (!this.lastEvent || this.matches(':host-context([noin
k])'))) { | |
| 15324 this.toggleBackground(); | |
| 15325 } | |
| 15326 }, | |
| 15327 | |
| 15328 pressedChanged: function() { | |
| 15329 this.super(); | |
| 15330 | |
| 15331 if (!this.lastEvent) { | |
| 15332 return; | |
| 15333 } | |
| 15334 | |
| 15335 if (this.$.ripple && !this.hasAttribute('noink')) { | |
| 15336 if (this.pressed) { | |
| 15337 this.$.ripple.downAction(this.lastEvent); | |
| 15338 } else { | |
| 15339 this.$.ripple.upAction(); | |
| 15340 } | |
| 15341 } | |
| 15342 | |
| 15343 this.adjustZ(); | |
| 15344 }, | |
| 15345 | |
| 15346 focusedChanged: function() { | |
| 15347 this.adjustZ(); | |
| 15348 }, | |
| 15349 | |
| 15350 disabledChanged: function() { | |
| 15351 this._disabledChanged(); | |
| 15352 this.adjustZ(); | |
| 15353 }, | |
| 15354 | |
| 15355 recenteringTouchChanged: function() { | |
| 15356 if (this.$.ripple) { | |
| 15357 this.$.ripple.classList.toggle('recenteringTouch', this.recenteringTou
ch); | |
| 15358 } | |
| 15359 }, | |
| 15360 | |
| 15361 fillChanged: function() { | |
| 15362 if (this.$.ripple) { | |
| 15363 this.$.ripple.classList.toggle('fill', this.fill); | |
| 15364 } | |
| 15365 }, | |
| 15366 | |
| 15367 adjustZ: function() { | |
| 15368 if (!this.$.shadow) { | |
| 15369 return; | |
| 15370 } | |
| 15371 if (this.active) { | |
| 15372 this.$.shadow.setZ(2); | |
| 15373 } else if (this.disabled) { | |
| 15374 this.$.shadow.setZ(0); | |
| 15375 } else if (this.focused) { | |
| 15376 this.$.shadow.setZ(3); | |
| 15377 } else { | |
| 15378 this.$.shadow.setZ(1); | |
| 15379 } | |
| 15380 }, | |
| 15381 | |
| 15382 downAction: function(e) { | |
| 15383 this._downAction(); | |
| 15384 | |
| 15385 if (this.hasAttribute('noink')) { | |
| 15386 return; | |
| 15387 } | |
| 15388 | |
| 15389 this.lastEvent = e; | |
| 15390 if (!this.$.ripple) { | |
| 15391 var ripple = document.createElement('paper-ripple'); | |
| 15392 ripple.setAttribute('id', 'ripple'); | |
| 15393 ripple.setAttribute('fit', ''); | |
| 15394 if (this.recenteringTouch) { | |
| 15395 ripple.classList.add('recenteringTouch'); | |
| 15396 } | |
| 15397 if (!this.fill) { | |
| 15398 ripple.classList.add('circle'); | |
| 15399 } | |
| 15400 this.$.ripple = ripple; | |
| 15401 this.shadowRoot.insertBefore(ripple, this.shadowRoot.firstChild); | |
| 15402 // No need to forward the event to the ripple because the ripple | |
| 15403 // is triggered in activeChanged | |
| 15404 } | |
| 15405 }, | |
| 15406 | |
| 15407 upAction: function() { | |
| 15408 this._upAction(); | |
| 15409 | |
| 15410 if (this.toggle) { | |
| 15411 this.toggleBackground(); | |
| 15412 if (this.$.ripple) { | |
| 15413 this.$.ripple.cancel(); | |
| 15414 } | |
| 15415 } | |
| 15416 } | |
| 15417 | |
| 15418 }; | |
| 15419 | |
| 15420 Polymer.mixin2(p, Polymer.CoreFocusable); | |
| 15421 Polymer('paper-button-base',p); | |
| 15422 | |
| 15423 })(); | |
| 15424 | |
| 15425 ; | |
| 15426 | |
| 15427 Polymer('paper-button',{ | |
| 15428 | |
| 15429 publish: { | |
| 15430 | |
| 15431 /** | |
| 15432 * If true, the button will be styled with a shadow. | |
| 15433 * | |
| 15434 * @attribute raised | |
| 15435 * @type boolean | |
| 15436 * @default false | |
| 15437 */ | |
| 15438 raised: false, | |
| 15439 | |
| 15440 /** | |
| 15441 * By default the ripple emanates from where the user touched the button
. | |
| 15442 * Set this to true to always center the ripple. | |
| 15443 * | |
| 15444 * @attribute recenteringTouch | |
| 15445 * @type boolean | |
| 15446 * @default false | |
| 15447 */ | |
| 15448 recenteringTouch: false, | |
| 15449 | |
| 15450 /** | |
| 15451 * By default the ripple expands to fill the button. Set this to true to | |
| 15452 * constrain the ripple to a circle within the button. | |
| 15453 * | |
| 15454 * @attribute fill | |
| 15455 * @type boolean | |
| 15456 * @default true | |
| 15457 */ | |
| 15458 fill: true | |
| 15459 | |
| 15460 }, | |
| 15461 | |
| 15462 _activate: function() { | |
| 15463 this.click(); | |
| 15464 this.fire('tap'); | |
| 15465 if (!this.pressed) { | |
| 15466 var bcr = this.getBoundingClientRect(); | |
| 15467 var x = bcr.left + (bcr.width / 2); | |
| 15468 var y = bcr.top + (bcr.height / 2); | |
| 15469 this.downAction({x: x, y: y}); | |
| 15470 var fn = function fn() { | |
| 15471 this.upAction(); | |
| 15472 this.removeEventListener('keyup', fn); | |
| 15473 }.bind(this); | |
| 15474 this.addEventListener('keyup', fn); | |
| 15475 } | |
| 15476 } | |
| 15477 | |
| 15478 }); | |
| 15479 ; | |
| 15480 | |
| 15481 | |
| 15482 (function() { | |
| 15483 | |
| 15484 function docElem(property) { | |
| 15485 var t; | |
| 15486 return ((t = document.documentElement) || (t = document.body.parentNode)) &&
(typeof t[property] === 'number') ? t : document.body; | |
| 15487 } | |
| 15488 | |
| 15489 // View width and height excluding any visible scrollbars | |
| 15490 // http://www.highdots.com/forums/javascript/faq-topic-how-do-i-296669.html | |
| 15491 // 1) document.client[Width|Height] always reliable when available, includi
ng Safari2 | |
| 15492 // 2) document.documentElement.client[Width|Height] reliable in standards m
ode DOCTYPE, except for Safari2, Opera<9.5 | |
| 15493 // 3) document.body.client[Width|Height] is gives correct result when #2 do
es not, except for Safari2 | |
| 15494 // 4) When document.documentElement.client[Width|Height] is unreliable, it
will be size of <html> element either greater or less than desired view size | |
| 15495 // https://bugzilla.mozilla.org/show_bug.cgi?id=156388#c7 | |
| 15496 // 5) When document.body.client[Width|Height] is unreliable, it will be siz
e of <body> element less than desired view size | |
| 15497 function viewSize() { | |
| 15498 // This algorithm avoids creating test page to determine if document.documen
tElement.client[Width|Height] is greater then view size, | |
| 15499 // will succeed where such test page wouldn't detect dynamic unreliability, | |
| 15500 // and will only fail in the case the right or bottom edge is within the wid
th of a scrollbar from edge of the viewport that has visible scrollbar(s). | |
| 15501 var doc = docElem('clientWidth'); | |
| 15502 var body = document.body; | |
| 15503 var w, h; | |
| 15504 return typeof document.clientWidth === 'number' ? | |
| 15505 {w: document.clientWidth, h: document.clientHeight} : | |
| 15506 doc === body || (w = Math.max( doc.clientWidth, body.clientWidth )) > self
.innerWidth || (h = Math.max( doc.clientHeight, body.clientHeight )) > self.inne
rHeight ? | |
| 15507 {w: body.clientWidth, h: body.clientHeight} : {w: w, h: h }; | |
| 15508 } | |
| 15509 | |
| 15510 Polymer('core-dropdown',{ | |
| 15511 | |
| 15512 publish: { | |
| 15513 | |
| 15514 /** | |
| 15515 * The element associated with this dropdown, usually the element that tri
ggers | |
| 15516 * the menu. If unset, this property will default to the target's parent n
ode | |
| 15517 * or shadow host. | |
| 15518 * | |
| 15519 * @attribute relatedTarget | |
| 15520 * @type Node | |
| 15521 */ | |
| 15522 relatedTarget: null, | |
| 15523 | |
| 15524 /** | |
| 15525 * The horizontal alignment of the popup relative to `relatedTarget`. `lef
t` | |
| 15526 * means the left edges are aligned together. `right` means the right edge
s | |
| 15527 * are aligned together. | |
| 15528 * | |
| 15529 * Accepted values: 'left', 'right' | |
| 15530 * | |
| 15531 * @attribute halign | |
| 15532 * @type String | |
| 15533 * @default 'left' | |
| 15534 */ | |
| 15535 halign: 'left', | |
| 15536 | |
| 15537 /** | |
| 15538 * The vertical alignment of the popup relative to `relatedTarget`. `top`
means | |
| 15539 * the top edges are aligned together. `bottom` means the bottom edges are | |
| 15540 * aligned together. | |
| 15541 * | |
| 15542 * Accepted values: 'top', 'bottom' | |
| 15543 * | |
| 15544 * @attribute valign | |
| 15545 * @type String | |
| 15546 * @default 'top' | |
| 15547 */ | |
| 15548 valign: 'top', | |
| 15549 | |
| 15550 }, | |
| 15551 | |
| 15552 measure: function() { | |
| 15553 var target = this.target; | |
| 15554 // remember position, because core-overlay may have set the property | |
| 15555 var pos = target.style.position; | |
| 15556 | |
| 15557 // get the size of the target as if it's positioned in the top left | |
| 15558 // corner of the screen | |
| 15559 target.style.position = 'fixed'; | |
| 15560 target.style.left = '0px'; | |
| 15561 target.style.top = '0px'; | |
| 15562 | |
| 15563 var rect = target.getBoundingClientRect(); | |
| 15564 | |
| 15565 target.style.position = pos; | |
| 15566 target.style.left = null; | |
| 15567 target.style.top = null; | |
| 15568 | |
| 15569 return rect; | |
| 15570 }, | |
| 15571 | |
| 15572 resetTargetDimensions: function() { | |
| 15573 var dims = this.dimensions; | |
| 15574 var style = this.target.style; | |
| 15575 if (dims.position.h_by === this.localName) { | |
| 15576 style[dims.position.h] = null; | |
| 15577 dims.position.h_by = null; | |
| 15578 } | |
| 15579 if (dims.position.v_by === this.localName) { | |
| 15580 style[dims.position.v] = null; | |
| 15581 dims.position.v_by = null; | |
| 15582 } | |
| 15583 style.width = null; | |
| 15584 style.height = null; | |
| 15585 this.super(); | |
| 15586 }, | |
| 15587 | |
| 15588 positionTarget: function() { | |
| 15589 if (!this.relatedTarget) { | |
| 15590 this.relatedTarget = this.target.parentElement || (this.target.parentNod
e && this.target.parentNode.host); | |
| 15591 if (!this.relatedTarget) { | |
| 15592 this.super(); | |
| 15593 return; | |
| 15594 } | |
| 15595 } | |
| 15596 | |
| 15597 // explicitly set width/height, because we don't want it constrained | |
| 15598 // to the offsetParent | |
| 15599 var target = this.sizingTarget; | |
| 15600 var rect = this.measure(); | |
| 15601 target.style.width = Math.ceil(rect.width) + 'px'; | |
| 15602 target.style.height = Math.ceil(rect.height) + 'px'; | |
| 15603 | |
| 15604 if (this.layered) { | |
| 15605 this.positionLayeredTarget(); | |
| 15606 } else { | |
| 15607 this.positionNestedTarget(); | |
| 15608 } | |
| 15609 }, | |
| 15610 | |
| 15611 positionLayeredTarget: function() { | |
| 15612 var target = this.target; | |
| 15613 var rect = this.relatedTarget.getBoundingClientRect(); | |
| 15614 | |
| 15615 var dims = this.dimensions; | |
| 15616 var margin = dims.margin; | |
| 15617 var vp = viewSize(); | |
| 15618 | |
| 15619 if (!dims.position.h) { | |
| 15620 if (this.halign === 'right') { | |
| 15621 target.style.right = vp.w - rect.right - margin.right + 'px'; | |
| 15622 dims.position.h = 'right'; | |
| 15623 } else { | |
| 15624 target.style.left = rect.left - margin.left + 'px'; | |
| 15625 dims.position.h = 'left'; | |
| 15626 } | |
| 15627 dims.position.h_by = this.localName; | |
| 15628 } | |
| 15629 | |
| 15630 if (!dims.position.v) { | |
| 15631 if (this.valign === 'bottom') { | |
| 15632 target.style.bottom = vp.h - rect.bottom - margin.bottom + 'px'; | |
| 15633 dims.position.v = 'bottom'; | |
| 15634 } else { | |
| 15635 target.style.top = rect.top - margin.top + 'px'; | |
| 15636 dims.position.v = 'top'; | |
| 15637 } | |
| 15638 dims.position.v_by = this.localName; | |
| 15639 } | |
| 15640 | |
| 15641 if (dims.position.h_by || dims.position.v_by) { | |
| 15642 target.style.position = 'fixed'; | |
| 15643 } | |
| 15644 }, | |
| 15645 | |
| 15646 positionNestedTarget: function() { | |
| 15647 var target = this.target; | |
| 15648 var related = this.relatedTarget; | |
| 15649 | |
| 15650 var t_op = target.offsetParent; | |
| 15651 var r_op = related.offsetParent; | |
| 15652 if (window.ShadowDOMPolyfill) { | |
| 15653 t_op = wrap(t_op); | |
| 15654 r_op = wrap(r_op); | |
| 15655 } | |
| 15656 | |
| 15657 if (t_op !== r_op && t_op !== related) { | |
| 15658 console.warn('core-dropdown-overlay: dropdown\'s offsetParent must be th
e relatedTarget or the relatedTarget\'s offsetParent!'); | |
| 15659 } | |
| 15660 | |
| 15661 // Don't use CSS to handle halign/valign so we can use | |
| 15662 // dimensions.position to detect custom positioning | |
| 15663 | |
| 15664 var dims = this.dimensions; | |
| 15665 var margin = dims.margin; | |
| 15666 var inside = t_op === related; | |
| 15667 | |
| 15668 if (!dims.position.h) { | |
| 15669 if (this.halign === 'right') { | |
| 15670 target.style.right = ((inside ? 0 : t_op.offsetWidth - related.offsetL
eft - related.offsetWidth) - margin.right) + 'px'; | |
| 15671 dims.position.h = 'right'; | |
| 15672 } else { | |
| 15673 target.style.left = ((inside ? 0 : related.offsetLeft) - margin.left)
+ 'px'; | |
| 15674 dims.position.h = 'left'; | |
| 15675 } | |
| 15676 dims.position.h_by = this.localName; | |
| 15677 } | |
| 15678 | |
| 15679 if (!dims.position.v) { | |
| 15680 if (this.valign === 'bottom') { | |
| 15681 target.style.bottom = ((inside ? 0 : t_op.offsetHeight - related.offse
tTop - related.offsetHeight) - margin.bottom) + 'px'; | |
| 15682 dims.position.v = 'bottom'; | |
| 15683 } else { | |
| 15684 target.style.top = ((inside ? 0 : related.offsetTop) - margin.top) + '
px'; | |
| 15685 dims.position.v = 'top'; | |
| 15686 } | |
| 15687 dims.position.v_by = this.localName; | |
| 15688 } | |
| 15689 } | |
| 15690 | |
| 15691 }); | |
| 15692 | |
| 15693 })(); | |
| 15694 | |
| 15695 ; | |
| 15696 | |
| 15697 | |
| 15698 Polymer('core-dropdown-base',{ | |
| 15699 | |
| 15700 publish: { | |
| 15701 | |
| 15702 /** | |
| 15703 * True if the menu is open. | |
| 15704 * | |
| 15705 * @attribute opened | |
| 15706 * @type boolean | |
| 15707 * @default false | |
| 15708 */ | |
| 15709 opened: false | |
| 15710 | |
| 15711 }, | |
| 15712 | |
| 15713 eventDelegates: { | |
| 15714 'tap': 'toggleOverlay' | |
| 15715 }, | |
| 15716 | |
| 15717 overlayListeners: { | |
| 15718 'core-overlay-open': 'openAction' | |
| 15719 }, | |
| 15720 | |
| 15721 get dropdown() { | |
| 15722 if (!this._dropdown) { | |
| 15723 this._dropdown = this.querySelector('.dropdown'); | |
| 15724 for (var l in this.overlayListeners) { | |
| 15725 this.addElementListener(this._dropdown, l, this.overlayListeners[l]); | |
| 15726 } | |
| 15727 } | |
| 15728 return this._dropdown; | |
| 15729 }, | |
| 15730 | |
| 15731 attached: function() { | |
| 15732 // find the dropdown on attach | |
| 15733 // FIXME: Support MO? | |
| 15734 this.dropdown; | |
| 15735 }, | |
| 15736 | |
| 15737 addElementListener: function(node, event, methodName, capture) { | |
| 15738 var fn = this._makeBoundListener(methodName); | |
| 15739 if (node && fn) { | |
| 15740 Polymer.addEventListener(node, event, fn, capture); | |
| 15741 } | |
| 15742 }, | |
| 15743 | |
| 15744 removeElementListener: function(node, event, methodName, capture) { | |
| 15745 var fn = this._makeBoundListener(methodName); | |
| 15746 if (node && fn) { | |
| 15747 Polymer.removeEventListener(node, event, fn, capture); | |
| 15748 } | |
| 15749 }, | |
| 15750 | |
| 15751 _makeBoundListener: function(methodName) { | |
| 15752 var self = this, method = this[methodName]; | |
| 15753 if (!method) { | |
| 15754 return; | |
| 15755 } | |
| 15756 var bound = '_bound' + methodName; | |
| 15757 if (!this[bound]) { | |
| 15758 this[bound] = function(e) { | |
| 15759 method.call(self, e); | |
| 15760 }; | |
| 15761 } | |
| 15762 return this[bound]; | |
| 15763 }, | |
| 15764 | |
| 15765 openedChanged: function() { | |
| 15766 if (this.disabled) { | |
| 15767 return; | |
| 15768 } | |
| 15769 var dropdown = this.dropdown; | |
| 15770 if (dropdown) { | |
| 15771 dropdown.opened = this.opened; | |
| 15772 } | |
| 15773 }, | |
| 15774 | |
| 15775 openAction: function(e) { | |
| 15776 this.opened = !!e.detail; | |
| 15777 }, | |
| 15778 | |
| 15779 toggleOverlay: function() { | |
| 15780 this.opened = !this.opened; | |
| 15781 } | |
| 15782 | |
| 15783 }); | |
| 15784 | |
| 15785 ; | |
| 15786 | |
| 15787 | |
| 15788 Polymer('core-iconset', { | |
| 15789 | |
| 15790 /** | |
| 15791 * The URL of the iconset image. | |
| 15792 * | |
| 15793 * @attribute src | |
| 15794 * @type string | |
| 15795 * @default '' | |
| 15796 */ | |
| 15797 src: '', | |
| 15798 | |
| 15799 /** | |
| 15800 * The width of the iconset image. This must only be specified if the | |
| 15801 * icons are arranged into separate rows inside the image. | |
| 15802 * | |
| 15803 * @attribute width | |
| 15804 * @type number | |
| 15805 * @default 0 | |
| 15806 */ | |
| 15807 width: 0, | |
| 15808 | |
| 15809 /** | |
| 15810 * A space separated list of names corresponding to icons in the iconset | |
| 15811 * image file. This list must be ordered the same as the icon images | |
| 15812 * in the image file. | |
| 15813 * | |
| 15814 * @attribute icons | |
| 15815 * @type string | |
| 15816 * @default '' | |
| 15817 */ | |
| 15818 icons: '', | |
| 15819 | |
| 15820 /** | |
| 15821 * The size of an individual icon. Note that icons must be square. | |
| 15822 * | |
| 15823 * @attribute iconSize | |
| 15824 * @type number | |
| 15825 * @default 24 | |
| 15826 */ | |
| 15827 iconSize: 24, | |
| 15828 | |
| 15829 /** | |
| 15830 * The horizontal offset of the icon images in the inconset src image. | |
| 15831 * This is typically used if the image resource contains additional images | |
| 15832 * beside those intended for the iconset. | |
| 15833 * | |
| 15834 * @attribute offsetX | |
| 15835 * @type number | |
| 15836 * @default 0 | |
| 15837 */ | |
| 15838 offsetX: 0, | |
| 15839 /** | |
| 15840 * The vertical offset of the icon images in the inconset src image. | |
| 15841 * This is typically used if the image resource contains additional images | |
| 15842 * beside those intended for the iconset. | |
| 15843 * | |
| 15844 * @attribute offsetY | |
| 15845 * @type number | |
| 15846 * @default 0 | |
| 15847 */ | |
| 15848 offsetY: 0, | |
| 15849 type: 'iconset', | |
| 15850 | |
| 15851 created: function() { | |
| 15852 this.iconMap = {}; | |
| 15853 this.iconNames = []; | |
| 15854 this.themes = {}; | |
| 15855 }, | |
| 15856 | |
| 15857 ready: function() { | |
| 15858 // TODO(sorvell): ensure iconset's src is always relative to the main | |
| 15859 // document | |
| 15860 if (this.src && (this.ownerDocument !== document)) { | |
| 15861 this.src = this.resolvePath(this.src, this.ownerDocument.baseURI); | |
| 15862 } | |
| 15863 this.super(); | |
| 15864 this.updateThemes(); | |
| 15865 }, | |
| 15866 | |
| 15867 iconsChanged: function() { | |
| 15868 var ox = this.offsetX; | |
| 15869 var oy = this.offsetY; | |
| 15870 this.icons && this.icons.split(/\s+/g).forEach(function(name, i) { | |
| 15871 this.iconNames.push(name); | |
| 15872 this.iconMap[name] = { | |
| 15873 offsetX: ox, | |
| 15874 offsetY: oy | |
| 15875 } | |
| 15876 if (ox + this.iconSize < this.width) { | |
| 15877 ox += this.iconSize; | |
| 15878 } else { | |
| 15879 ox = this.offsetX; | |
| 15880 oy += this.iconSize; | |
| 15881 } | |
| 15882 }, this); | |
| 15883 }, | |
| 15884 | |
| 15885 updateThemes: function() { | |
| 15886 var ts = this.querySelectorAll('property[theme]'); | |
| 15887 ts && ts.array().forEach(function(t) { | |
| 15888 this.themes[t.getAttribute('theme')] = { | |
| 15889 offsetX: parseInt(t.getAttribute('offsetX')) || 0, | |
| 15890 offsetY: parseInt(t.getAttribute('offsetY')) || 0 | |
| 15891 }; | |
| 15892 }, this); | |
| 15893 }, | |
| 15894 | |
| 15895 // TODO(ffu): support retrived by index e.g. getOffset(10); | |
| 15896 /** | |
| 15897 * Returns an object containing `offsetX` and `offsetY` properties which | |
| 15898 * specify the pixel locaion in the iconset's src file for the given | |
| 15899 * `icon` and `theme`. It's uncommon to call this method. It is useful, | |
| 15900 * for example, to manually position a css backgroundImage to the proper | |
| 15901 * offset. It's more common to use the `applyIcon` method. | |
| 15902 * | |
| 15903 * @method getOffset | |
| 15904 * @param {String|Number} icon The name of the icon or the index of the | |
| 15905 * icon within in the icon image. | |
| 15906 * @param {String} theme The name of the theme. | |
| 15907 * @returns {Object} An object specifying the offset of the given icon | |
| 15908 * within the icon resource file; `offsetX` is the horizontal offset and | |
| 15909 * `offsetY` is the vertical offset. Both values are in pixel units. | |
| 15910 */ | |
| 15911 getOffset: function(icon, theme) { | |
| 15912 var i = this.iconMap[icon]; | |
| 15913 if (!i) { | |
| 15914 var n = this.iconNames[Number(icon)]; | |
| 15915 i = this.iconMap[n]; | |
| 15916 } | |
| 15917 var t = this.themes[theme]; | |
| 15918 if (i && t) { | |
| 15919 return { | |
| 15920 offsetX: i.offsetX + t.offsetX, | |
| 15921 offsetY: i.offsetY + t.offsetY | |
| 15922 } | |
| 15923 } | |
| 15924 return i; | |
| 15925 }, | |
| 15926 | |
| 15927 /** | |
| 15928 * Applies an icon to the given element as a css background image. This | |
| 15929 * method does not size the element, and it's often necessary to set | |
| 15930 * the element's height and width so that the background image is visible. | |
| 15931 * | |
| 15932 * @method applyIcon | |
| 15933 * @param {Element} element The element to which the background is | |
| 15934 * applied. | |
| 15935 * @param {String|Number} icon The name or index of the icon to apply. | |
| 15936 * @param {Number} scale (optional, defaults to 1) A scaling factor | |
| 15937 * with which the icon can be magnified. | |
| 15938 * @return {Element} The icon element. | |
| 15939 */ | |
| 15940 applyIcon: function(element, icon, scale) { | |
| 15941 var offset = this.getOffset(icon); | |
| 15942 scale = scale || 1; | |
| 15943 if (element && offset) { | |
| 15944 var icon = element._icon || document.createElement('div'); | |
| 15945 var style = icon.style; | |
| 15946 style.backgroundImage = 'url(' + this.src + ')'; | |
| 15947 style.backgroundPosition = (-offset.offsetX * scale + 'px') + | |
| 15948 ' ' + (-offset.offsetY * scale + 'px'); | |
| 15949 style.backgroundSize = scale === 1 ? 'auto' : | |
| 15950 this.width * scale + 'px'; | |
| 15951 if (icon.parentNode !== element) { | |
| 15952 element.appendChild(icon); | |
| 15953 } | |
| 15954 return icon; | |
| 15955 } | |
| 15956 } | |
| 15957 | |
| 15958 }); | |
| 15959 | |
| 15960 ; | |
| 15961 | |
| 15962 (function() { | |
| 15963 | |
| 15964 // mono-state | |
| 15965 var meta; | |
| 15966 | |
| 15967 Polymer('core-icon', { | |
| 15968 | |
| 15969 /** | |
| 15970 * The URL of an image for the icon. If the src property is specified, | |
| 15971 * the icon property should not be. | |
| 15972 * | |
| 15973 * @attribute src | |
| 15974 * @type string | |
| 15975 * @default '' | |
| 15976 */ | |
| 15977 src: '', | |
| 15978 | |
| 15979 /** | |
| 15980 * Specifies the icon name or index in the set of icons available in | |
| 15981 * the icon's icon set. If the icon property is specified, | |
| 15982 * the src property should not be. | |
| 15983 * | |
| 15984 * @attribute icon | |
| 15985 * @type string | |
| 15986 * @default '' | |
| 15987 */ | |
| 15988 icon: '', | |
| 15989 | |
| 15990 /** | |
| 15991 * Alternative text content for accessibility support. | |
| 15992 * If alt is present and not empty, it will set the element's role to img an
d add an aria-label whose content matches alt. | |
| 15993 * If alt is present and is an empty string, '', it will hide the element fr
om the accessibility layer | |
| 15994 * If alt is not present, it will set the element's role to img and the elem
ent will fallback to using the icon attribute for its aria-label. | |
| 15995 * | |
| 15996 * @attribute alt | |
| 15997 * @type string | |
| 15998 * @default '' | |
| 15999 */ | |
| 16000 alt: null, | |
| 16001 | |
| 16002 observe: { | |
| 16003 'icon': 'updateIcon', | |
| 16004 'alt': 'updateAlt' | |
| 16005 }, | |
| 16006 | |
| 16007 defaultIconset: 'icons', | |
| 16008 | |
| 16009 ready: function() { | |
| 16010 if (!meta) { | |
| 16011 meta = document.createElement('core-iconset'); | |
| 16012 } | |
| 16013 | |
| 16014 // Allow user-provided `aria-label` in preference to any other text altern
ative. | |
| 16015 if (this.hasAttribute('aria-label')) { | |
| 16016 // Set `role` if it has not been overridden. | |
| 16017 if (!this.hasAttribute('role')) { | |
| 16018 this.setAttribute('role', 'img'); | |
| 16019 } | |
| 16020 return; | |
| 16021 } | |
| 16022 this.updateAlt(); | |
| 16023 }, | |
| 16024 | |
| 16025 srcChanged: function() { | |
| 16026 var icon = this._icon || document.createElement('div'); | |
| 16027 icon.textContent = ''; | |
| 16028 icon.setAttribute('fit', ''); | |
| 16029 icon.style.backgroundImage = 'url(' + this.src + ')'; | |
| 16030 icon.style.backgroundPosition = 'center'; | |
| 16031 icon.style.backgroundSize = '100%'; | |
| 16032 if (!icon.parentNode) { | |
| 16033 this.appendChild(icon); | |
| 16034 } | |
| 16035 this._icon = icon; | |
| 16036 }, | |
| 16037 | |
| 16038 getIconset: function(name) { | |
| 16039 return meta.byId(name || this.defaultIconset); | |
| 16040 }, | |
| 16041 | |
| 16042 updateIcon: function(oldVal, newVal) { | |
| 16043 if (!this.icon) { | |
| 16044 this.updateAlt(); | |
| 16045 return; | |
| 16046 } | |
| 16047 var parts = String(this.icon).split(':'); | |
| 16048 var icon = parts.pop(); | |
| 16049 if (icon) { | |
| 16050 var set = this.getIconset(parts.pop()); | |
| 16051 if (set) { | |
| 16052 this._icon = set.applyIcon(this, icon); | |
| 16053 if (this._icon) { | |
| 16054 this._icon.setAttribute('fit', ''); | |
| 16055 } | |
| 16056 } | |
| 16057 } | |
| 16058 // Check to see if we're using the old icon's name for our a11y fallback | |
| 16059 if (oldVal) { | |
| 16060 if (oldVal.split(':').pop() == this.getAttribute('aria-label')) { | |
| 16061 this.updateAlt(); | |
| 16062 } | |
| 16063 } | |
| 16064 }, | |
| 16065 | |
| 16066 updateAlt: function() { | |
| 16067 // Respect the user's decision to remove this element from | |
| 16068 // the a11y tree | |
| 16069 if (this.getAttribute('aria-hidden')) { | |
| 16070 return; | |
| 16071 } | |
| 16072 | |
| 16073 // Remove element from a11y tree if `alt` is empty, otherwise | |
| 16074 // use `alt` as `aria-label`. | |
| 16075 if (this.alt === '') { | |
| 16076 this.setAttribute('aria-hidden', 'true'); | |
| 16077 if (this.hasAttribute('role')) { | |
| 16078 this.removeAttribute('role'); | |
| 16079 } | |
| 16080 if (this.hasAttribute('aria-label')) { | |
| 16081 this.removeAttribute('aria-label'); | |
| 16082 } | |
| 16083 } else { | |
| 16084 this.setAttribute('aria-label', this.alt || | |
| 16085 this.icon.split(':').pop()); | |
| 16086 if (!this.hasAttribute('role')) { | |
| 16087 this.setAttribute('role', 'img'); | |
| 16088 } | |
| 16089 if (this.hasAttribute('aria-hidden')) { | |
| 16090 this.removeAttribute('aria-hidden'); | |
| 16091 } | |
| 16092 } | |
| 16093 } | |
| 16094 | |
| 16095 }); | |
| 16096 | |
| 16097 })(); | |
| 16098 ; | |
| 16099 | |
| 16100 | |
| 16101 Polymer('core-iconset-svg', { | |
| 16102 | |
| 16103 | |
| 16104 /** | |
| 16105 * The size of an individual icon. Note that icons must be square. | |
| 16106 * | |
| 16107 * @attribute iconSize | |
| 16108 * @type number | |
| 16109 * @default 24 | |
| 16110 */ | |
| 16111 iconSize: 24, | |
| 16112 type: 'iconset', | |
| 16113 | |
| 16114 created: function() { | |
| 16115 this._icons = {}; | |
| 16116 }, | |
| 16117 | |
| 16118 ready: function() { | |
| 16119 this.super(); | |
| 16120 this.updateIcons(); | |
| 16121 }, | |
| 16122 | |
| 16123 iconById: function(id) { | |
| 16124 return this._icons[id] || (this._icons[id] = this.querySelector('[id="'
+ id +'"]')); | |
| 16125 }, | |
| 16126 | |
| 16127 cloneIcon: function(id) { | |
| 16128 var icon = this.iconById(id); | |
| 16129 if (icon) { | |
| 16130 var content = icon.cloneNode(true); | |
| 16131 content.removeAttribute('id'); | |
| 16132 var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'
); | |
| 16133 svg.setAttribute('viewBox', '0 0 ' + this.iconSize + ' ' + | |
| 16134 this.iconSize); | |
| 16135 // NOTE(dfreedm): work around https://crbug.com/370136 | |
| 16136 svg.style.pointerEvents = 'none'; | |
| 16137 svg.appendChild(content); | |
| 16138 return svg; | |
| 16139 } | |
| 16140 }, | |
| 16141 | |
| 16142 get iconNames() { | |
| 16143 if (!this._iconNames) { | |
| 16144 this._iconNames = this.findIconNames(); | |
| 16145 } | |
| 16146 return this._iconNames; | |
| 16147 }, | |
| 16148 | |
| 16149 findIconNames: function() { | |
| 16150 var icons = this.querySelectorAll('[id]').array(); | |
| 16151 if (icons.length) { | |
| 16152 return icons.map(function(n){ return n.id }); | |
| 16153 } | |
| 16154 }, | |
| 16155 | |
| 16156 /** | |
| 16157 * Applies an icon to the given element. The svg icon is added to the | |
| 16158 * element's shadowRoot if one exists or directly to itself. | |
| 16159 * | |
| 16160 * @method applyIcon | |
| 16161 * @param {Element} element The element to which the icon is | |
| 16162 * applied. | |
| 16163 * @param {String|Number} icon The name the icon to apply. | |
| 16164 * @return {Element} The icon element | |
| 16165 */ | |
| 16166 applyIcon: function(element, icon) { | |
| 16167 var root = element; | |
| 16168 // remove old | |
| 16169 var old = root.querySelector('svg'); | |
| 16170 if (old) { | |
| 16171 old.remove(); | |
| 16172 } | |
| 16173 // install new | |
| 16174 var svg = this.cloneIcon(icon); | |
| 16175 if (!svg) { | |
| 16176 return; | |
| 16177 } | |
| 16178 svg.setAttribute('height', '100%'); | |
| 16179 svg.setAttribute('width', '100%'); | |
| 16180 svg.setAttribute('preserveAspectRatio', 'xMidYMid meet'); | |
| 16181 svg.style.display = 'block'; | |
| 16182 root.insertBefore(svg, root.firstElementChild); | |
| 16183 return svg; | |
| 16184 }, | |
| 16185 | |
| 16186 /** | |
| 16187 * Tell users of the iconset, that the set has loaded. | |
| 16188 * This finds all elements matching the selector argument and calls | |
| 16189 * the method argument on them. | |
| 16190 * @method updateIcons | |
| 16191 * @param selector {string} css selector to identify iconset users, | |
| 16192 * defaults to '[icon]' | |
| 16193 * @param method {string} method to call on found elements, | |
| 16194 * defaults to 'updateIcon' | |
| 16195 */ | |
| 16196 updateIcons: function(selector, method) { | |
| 16197 selector = selector || '[icon]'; | |
| 16198 method = method || 'updateIcon'; | |
| 16199 var deep = window.ShadowDOMPolyfill ? '' : 'html /deep/ '; | |
| 16200 var i$ = document.querySelectorAll(deep + selector); | |
| 16201 for (var i=0, e; e=i$[i]; i++) { | |
| 16202 if (e[method]) { | |
| 16203 e[method].call(e); | |
| 16204 } | |
| 16205 } | |
| 16206 } | |
| 16207 | |
| 16208 | |
| 16209 }); | |
| 16210 | |
| 16211 ; | |
| 16212 | |
| 16213 | |
| 16214 (function() { | |
| 16215 | |
| 16216 var p = { | |
| 16217 | |
| 16218 publish: { | |
| 16219 | |
| 16220 /** | |
| 16221 * A label for the control. The label is displayed if no item is selected. | |
| 16222 * | |
| 16223 * @attribute label | |
| 16224 * @type string | |
| 16225 * @default 'Select an item' | |
| 16226 */ | |
| 16227 label: 'Select an item', | |
| 16228 | |
| 16229 /** | |
| 16230 * The icon to display when the drop-down is opened. | |
| 16231 * | |
| 16232 * @attribute openedIcon | |
| 16233 * @type string | |
| 16234 * @default 'arrow-drop-up' | |
| 16235 */ | |
| 16236 openedIcon: 'arrow-drop-up', | |
| 16237 | |
| 16238 /** | |
| 16239 * The icon to display when the drop-down is closed. | |
| 16240 * | |
| 16241 * @attribute closedIcon | |
| 16242 * @type string | |
| 16243 * @default 'arrow-drop-down' | |
| 16244 */ | |
| 16245 closedIcon: 'arrow-drop-down' | |
| 16246 | |
| 16247 }, | |
| 16248 | |
| 16249 selectedItemLabel: '', | |
| 16250 | |
| 16251 overlayListeners: { | |
| 16252 'core-overlay-open': 'openAction', | |
| 16253 'core-activate': 'activateAction', | |
| 16254 'core-select': 'selectAction' | |
| 16255 }, | |
| 16256 | |
| 16257 activateAction: function(e) { | |
| 16258 this.opened = false; | |
| 16259 }, | |
| 16260 | |
| 16261 selectAction: function(e) { | |
| 16262 var detail = e.detail; | |
| 16263 if (detail.isSelected) { | |
| 16264 this.selectedItemLabel = detail.item.label || detail.item.textContent; | |
| 16265 } else { | |
| 16266 this.selectedItemLabel = ''; | |
| 16267 } | |
| 16268 } | |
| 16269 | |
| 16270 }; | |
| 16271 | |
| 16272 Polymer.mixin2(p, Polymer.CoreFocusable); | |
| 16273 Polymer('core-dropdown-menu',p); | |
| 16274 | |
| 16275 })(); | |
| 16276 | |
| 16277 ; | |
| 16278 | |
| 16279 | |
| 16280 Polymer('core-item', { | |
| 16281 | |
| 16282 /** | |
| 16283 * The URL of an image for the icon. | |
| 16284 * | |
| 16285 * @attribute src | |
| 16286 * @type string | |
| 16287 * @default '' | |
| 16288 */ | |
| 16289 | |
| 16290 /** | |
| 16291 * Specifies the icon from the Polymer icon set. | |
| 16292 * | |
| 16293 * @attribute icon | |
| 16294 * @type string | |
| 16295 * @default '' | |
| 16296 */ | |
| 16297 | |
| 16298 /** | |
| 16299 * Specifies the label for the menu item. | |
| 16300 * | |
| 16301 * @attribute label | |
| 16302 * @type string | |
| 16303 * @default '' | |
| 16304 */ | |
| 16305 | |
| 16306 }); | |
| 16307 | |
| 16308 ; | |
| 16309 | |
| 16310 Polymer('stats-dimension-filter', { | |
| 16311 count: 0, | |
| 16312 dimensions: [], | |
| 16313 filters: {}, | |
| 16314 props: [], | |
| 16315 value: null, | |
| 16316 values: {}, | |
| 16317 | |
| 16318 stringify: function(d) { | |
| 16319 return JSON.stringify(d); | |
| 16320 }, | |
| 16321 | |
| 16322 // Update this.props and this.values | |
| 16323 dimensionsChanged: function() { | |
| 16324 var ds = this.dimensions; | |
| 16325 var attrs = {}, values = {}; | |
| 16326 var props = []; | |
| 16327 for (var i = 0; i < ds.length; ++i) { | |
| 16328 var d = ds[i]; | |
| 16329 if (typeof d === "object") { | |
| 16330 for (var attr in d) { | |
| 16331 if (!attrs[attr]) { | |
| 16332 attrs[attr] = {}; | |
| 16333 } | |
| 16334 attrs[attr][d[attr]] = true; | |
| 16335 } | |
| 16336 } else { | |
| 16337 console.error('dimension should be an object'); | |
| 16338 } | |
| 16339 } | |
| 16340 for (var attr in attrs) { | |
| 16341 props.push(attr); | |
| 16342 if (!values[attr]) { | |
| 16343 values[attr] = []; | |
| 16344 } | |
| 16345 for (var i in attrs[attr]) { | |
| 16346 values[attr].push(i); | |
| 16347 } | |
| 16348 } | |
| 16349 this.props = props; | |
| 16350 this.values = values; | |
| 16351 }, | |
| 16352 | |
| 16353 dimensionFilter: function(dimensions, filters, count) { | |
| 16354 var hasFilter = false; | |
| 16355 var arr = dimensions.filter(function(d) { | |
| 16356 for (var prop in filters) { | |
| 16357 hasFilter = true; | |
| 16358 if (d[prop] !== filters[prop]) { | |
| 16359 return false; | |
| 16360 } | |
| 16361 } | |
| 16362 return true; | |
| 16363 }); | |
| 16364 if (hasFilter) { | |
| 16365 this.value = JSON.stringify(arr[0]); | |
| 16366 } | |
| 16367 return arr; | |
| 16368 }, | |
| 16369 | |
| 16370 propPickerFilter: function(props, filters, count) { | |
| 16371 return props.filter(function(prop) { | |
| 16372 for (var p in filters) { | |
| 16373 if (prop === p) { | |
| 16374 return false; | |
| 16375 } | |
| 16376 } | |
| 16377 return true; | |
| 16378 }); | |
| 16379 }, | |
| 16380 | |
| 16381 filtersArray: function(filters, count) { | |
| 16382 var arr = []; | |
| 16383 for (var prop in filters) { | |
| 16384 arr.push({ | |
| 16385 prop: prop, | |
| 16386 value: filters[prop] | |
| 16387 }); | |
| 16388 } | |
| 16389 return arr; | |
| 16390 }, | |
| 16391 | |
| 16392 // HACK: this is to trigger the dimensionFilter due to a bug in Polymer | |
| 16393 // https://github.com/Polymer/polymer/issues/1082 | |
| 16394 filtersChanged: function() { | |
| 16395 this.count++; | |
| 16396 }, | |
| 16397 | |
| 16398 propSelected: function(event, detail, sender) { | |
| 16399 var prop = sender.label; | |
| 16400 var v = sender.selectedItemLabel; | |
| 16401 if (v) { | |
| 16402 this.filters[prop] = v; | |
| 16403 } | |
| 16404 // TODO: use observer pattern | |
| 16405 this.filtersChanged(); | |
| 16406 }, | |
| 16407 | |
| 16408 propValueFilter: function(values, dimensions, filters, prop) { | |
| 16409 var ds = this.dimensionFilter(dimensions, filters); | |
| 16410 var selection = {}; | |
| 16411 for (var i = 0; i < ds.length; ++i) { | |
| 16412 if (ds[i].hasOwnProperty(prop)) { | |
| 16413 selection[ds[i][prop]] = true; | |
| 16414 } | |
| 16415 } | |
| 16416 var ret = []; | |
| 16417 for (var i in selection) { | |
| 16418 ret.push(i); | |
| 16419 } | |
| 16420 return ret; | |
| 16421 }, | |
| 16422 | |
| 16423 onFilterUnchecked: function(event, detail, sender) { | |
| 16424 delete this.filters[sender.getAttribute('prop')]; | |
| 16425 // TODO: use observer pattern | |
| 16426 this.filtersChanged(); | |
| 16427 }, | |
| 16428 | |
| 16429 selectDimension: function(event, detail, sender) { | |
| 16430 this.value = sender.innerHTML; | |
| 16431 }, | |
| 16432 | |
| 16433 onClearDimension: function(event, detail, sender) { | |
| 16434 this.value = null; | |
| 16435 } | |
| 16436 }); | |
| 16437 ; | |
| 16438 | |
| 16439 (function() { | |
| 16440 | |
| 16441 Polymer('core-shared-lib',{ | |
| 16442 | |
| 16443 notifyEvent: 'core-shared-lib-load', | |
| 16444 | |
| 16445 ready: function() { | |
| 16446 if (!this.url && this.defaultUrl) { | |
| 16447 this.url = this.defaultUrl; | |
| 16448 } | |
| 16449 }, | |
| 16450 | |
| 16451 urlChanged: function() { | |
| 16452 require(this.url, this, this.callbackName); | |
| 16453 }, | |
| 16454 | |
| 16455 provide: function() { | |
| 16456 this.async('notify'); | |
| 16457 }, | |
| 16458 | |
| 16459 notify: function() { | |
| 16460 this.fire(this.notifyEvent, arguments); | |
| 16461 } | |
| 16462 | |
| 16463 }); | |
| 16464 | |
| 16465 var apiMap = {}; | |
| 16466 | |
| 16467 function require(url, notifiee, callbackName) { | |
| 16468 // make hashable string form url | |
| 16469 var name = nameFromUrl(url); | |
| 16470 // lookup existing loader instance | |
| 16471 var loader = apiMap[name]; | |
| 16472 // create a loader as needed | |
| 16473 if (!loader) { | |
| 16474 loader = apiMap[name] = new Loader(name, url, callbackName); | |
| 16475 } | |
| 16476 loader.requestNotify(notifiee); | |
| 16477 } | |
| 16478 | |
| 16479 function nameFromUrl(url) { | |
| 16480 return url.replace(/[\:\/\%\?\&\.\=\-\,]/g, '_') + '_api'; | |
| 16481 } | |
| 16482 | |
| 16483 var Loader = function(name, url, callbackName) { | |
| 16484 this.instances = []; | |
| 16485 this.callbackName = callbackName; | |
| 16486 if (this.callbackName) { | |
| 16487 window[this.callbackName] = this.success.bind(this); | |
| 16488 } else { | |
| 16489 if (url.indexOf(this.callbackMacro) >= 0) { | |
| 16490 this.callbackName = name + '_loaded'; | |
| 16491 window[this.callbackName] = this.success.bind(this); | |
| 16492 url = url.replace(this.callbackMacro, this.callbackName); | |
| 16493 } else { | |
| 16494 // TODO(sjmiles): we should probably fallback to listening to script.loa
d | |
| 16495 throw 'core-shared-api: a %%callback%% parameter is required in the API
url'; | |
| 16496 } | |
| 16497 } | |
| 16498 // | |
| 16499 this.addScript(url); | |
| 16500 }; | |
| 16501 | |
| 16502 Loader.prototype = { | |
| 16503 | |
| 16504 callbackMacro: '%%callback%%', | |
| 16505 loaded: false, | |
| 16506 | |
| 16507 addScript: function(src) { | |
| 16508 var script = document.createElement('script'); | |
| 16509 script.src = src; | |
| 16510 script.onerror = this.error.bind(this); | |
| 16511 var s = document.querySelector('script'); | |
| 16512 s.parentNode.insertBefore(script, s); | |
| 16513 this.script = script; | |
| 16514 }, | |
| 16515 | |
| 16516 removeScript: function() { | |
| 16517 if (this.script.parentNode) { | |
| 16518 this.script.parentNode.removeChild(this.script); | |
| 16519 } | |
| 16520 this.script = null; | |
| 16521 }, | |
| 16522 | |
| 16523 error: function() { | |
| 16524 this.cleanup(); | |
| 16525 }, | |
| 16526 | |
| 16527 success: function() { | |
| 16528 this.loaded = true; | |
| 16529 this.cleanup(); | |
| 16530 this.result = Array.prototype.slice.call(arguments); | |
| 16531 this.instances.forEach(this.provide, this); | |
| 16532 this.instances = null; | |
| 16533 }, | |
| 16534 | |
| 16535 cleanup: function() { | |
| 16536 delete window[this.callbackName]; | |
| 16537 }, | |
| 16538 | |
| 16539 provide: function(instance) { | |
| 16540 instance.notify(instance, this.result); | |
| 16541 }, | |
| 16542 | |
| 16543 requestNotify: function(instance) { | |
| 16544 if (this.loaded) { | |
| 16545 this.provide(instance); | |
| 16546 } else { | |
| 16547 this.instances.push(instance); | |
| 16548 } | |
| 16549 } | |
| 16550 | |
| 16551 }; | |
| 16552 | |
| 16553 })(); | |
| 16554 ; | |
| 16555 | |
| 16556 Polymer('google-jsapi',{ | |
| 16557 defaultUrl: 'https://www.google.com/jsapi?callback=%%callback%%', | |
| 16558 | |
| 16559 /** | |
| 16560 * Fired when the API library is loaded and available. | |
| 16561 * @event api-load | |
| 16562 */ | |
| 16563 notifyEvent: 'api-load', | |
| 16564 | |
| 16565 /** | |
| 16566 * Wrapper for `google` API namespace. | |
| 16567 * @property api | |
| 16568 */ | |
| 16569 get api() { | |
| 16570 return google; | |
| 16571 } | |
| 16572 }); | |
| 16573 ; | |
| 16574 | |
| 16575 (function() { | |
| 16576 'use strict'; | |
| 16577 | |
| 16578 Polymer('google-chart',{ | |
| 16579 /** | |
| 16580 * Fired when the graph is displayed. | |
| 16581 * | |
| 16582 * @event google-chart-render | |
| 16583 */ | |
| 16584 | |
| 16585 | |
| 16586 /** | |
| 16587 * Sets the type of the chart. | |
| 16588 * | |
| 16589 * Should be one of: | |
| 16590 * - `area`, `bar`, `bubble`, `candlestick`, `column`, `combo`, `geo`, | |
| 16591 * `histogram`, `line`, `pie`, `scatter`, `stepped-area` | |
| 16592 * | |
| 16593 * See <a href="https://google-developers.appspot.com/chart/interactive/
docs/gallery">Google Visualization API reference (Chart Gallery)</a> for details
. | |
| 16594 * | |
| 16595 * @attribute type | |
| 16596 * @type string | |
| 16597 */ | |
| 16598 type: 'column', | |
| 16599 | |
| 16600 /** | |
| 16601 * Sets the options for the chart. | |
| 16602 * | |
| 16603 * Example: | |
| 16604 * <pre>{ | |
| 16605 * title: "Chart title goes here", | |
| 16606 * hAxis: {title: "Categories"}, | |
| 16607 * vAxis: {title: "Values", minValue: 0, maxValue: 2}, | |
| 16608 * legend: "none" | |
| 16609 * };</pre> | |
| 16610 * See <a href="https://google-developers.appspot.com/chart/interactive/
docs/gallery">Google Visualization API reference (Chart Gallery)</a> | |
| 16611 * for the options available to each chart type. | |
| 16612 * | |
| 16613 * @attribute options | |
| 16614 * @type object | |
| 16615 */ | |
| 16616 options: null, | |
| 16617 | |
| 16618 /** | |
| 16619 * Sets the data columns for this object. | |
| 16620 * | |
| 16621 * When specifying data with `cols` you must also specify `rows`, and | |
| 16622 * not specify `data`. | |
| 16623 * | |
| 16624 * Example: | |
| 16625 * <pre>[{label: "Categories", type: "string"}, | |
| 16626 * {label: "Value", type: "number"}]</pre> | |
| 16627 * See <a href="https://google-developers.appspot.com/chart/interactive/
docs/reference#DataTable_addColumn">Google Visualization API reference (addColum
n)</a> | |
| 16628 * for column definition format. | |
| 16629 * | |
| 16630 * @attribute cols | |
| 16631 * @type array | |
| 16632 */ | |
| 16633 cols: null, | |
| 16634 | |
| 16635 /** | |
| 16636 * Sets the data rows for this object. | |
| 16637 * | |
| 16638 * When specifying data with `rows` you must also specify `cols`, and | |
| 16639 * not specify `data`. | |
| 16640 * | |
| 16641 * Example: | |
| 16642 * <pre>[["Category 1", 1.0], | |
| 16643 * ["Category 2", 1.1]]</pre> | |
| 16644 * See <a href="https://google-developers.appspot.com/chart/interactive/
docs/reference#addrow">Google Visualization API reference (addRow)</a> | |
| 16645 * for row format. | |
| 16646 * | |
| 16647 * @attribute rows | |
| 16648 * @type array | |
| 16649 */ | |
| 16650 rows: null, | |
| 16651 | |
| 16652 /** | |
| 16653 * Sets the entire dataset for this object. | |
| 16654 * Can be used to provide the data directly, or to provide a URL from | |
| 16655 * which to request the data. | |
| 16656 * | |
| 16657 * The data format can be a two-dimensional array or the DataTable forma
t | |
| 16658 * expected by Google Charts. | |
| 16659 * See <a href="https://google-developers.appspot.com/chart/interactive/
docs/reference#DataTable">Google Visualization API reference (DataTable construc
tor)</a> | |
| 16660 * for data table format details. | |
| 16661 * | |
| 16662 * When specifying data with `data` you must not specify `cols` or `rows
`. | |
| 16663 * | |
| 16664 * Example: | |
| 16665 * <pre>[["Categories", "Value"], | |
| 16666 * ["Category 1", 1.0], | |
| 16667 * ["Category 2", 1.1]]</pre> | |
| 16668 * | |
| 16669 * @attribute data | |
| 16670 * @type array, object, or string | |
| 16671 */ | |
| 16672 data: null, | |
| 16673 | |
| 16674 chartTypes: null, | |
| 16675 | |
| 16676 chartObject: null, | |
| 16677 | |
| 16678 isReady: false, | |
| 16679 | |
| 16680 canDraw: false, | |
| 16681 | |
| 16682 dataTable: null, | |
| 16683 | |
| 16684 created: function() { | |
| 16685 this.chartTypes = {}; | |
| 16686 this.cols = []; | |
| 16687 this.data = []; | |
| 16688 this.options = {}; | |
| 16689 this.rows = []; | |
| 16690 this.dataTable = null; | |
| 16691 }, | |
| 16692 | |
| 16693 readyForAction: function(e, detail, sender) { | |
| 16694 google.load("visualization", "1", { | |
| 16695 packages: ['corechart'], | |
| 16696 callback: function() { | |
| 16697 this.isReady = true; | |
| 16698 this.loadChartTypes(); | |
| 16699 this.loadData(); | |
| 16700 }.bind(this) | |
| 16701 }); | |
| 16702 }, | |
| 16703 | |
| 16704 typeChanged: function() { | |
| 16705 // Invalidate current chart object. | |
| 16706 this.chartObject = null; | |
| 16707 this.loadData(); | |
| 16708 }, | |
| 16709 | |
| 16710 observe: { | |
| 16711 rows: 'loadData', | |
| 16712 cols: 'loadData', | |
| 16713 data: 'loadData' | |
| 16714 }, | |
| 16715 | |
| 16716 /** | |
| 16717 * Draws the chart. | |
| 16718 * | |
| 16719 * Called automatically on first load and whenever one of the attributes | |
| 16720 * changes. Can be called manually to handle e.g. page resizes. | |
| 16721 * | |
| 16722 * @method drawChart | |
| 16723 * @return {Object} Returns null. | |
| 16724 */ | |
| 16725 drawChart: function() { | |
| 16726 if (this.canDraw) { | |
| 16727 if (!this.options) { | |
| 16728 this.options = {}; | |
| 16729 } | |
| 16730 if (!this.chartObject) { | |
| 16731 var chartClass = this.chartTypes[this.type]; | |
| 16732 if (chartClass) { | |
| 16733 this.chartObject = new chartClass(this.$.chartdiv); | |
| 16734 } | |
| 16735 } | |
| 16736 if (this.chartObject) { | |
| 16737 google.visualization.events.addOneTimeListener(this.chartObject, | |
| 16738 'ready', function() { | |
| 16739 this.fire('google-chart-render'); | |
| 16740 }.bind(this)); | |
| 16741 this.chartObject.draw(this.dataTable, this.options); | |
| 16742 } else { | |
| 16743 this.$.chartdiv.innerHTML = 'Undefined chart type'; | |
| 16744 } | |
| 16745 } | |
| 16746 return null; | |
| 16747 }, | |
| 16748 | |
| 16749 loadChartTypes: function() { | |
| 16750 this.chartTypes = { | |
| 16751 'area': google.visualization.AreaChart, | |
| 16752 'bar': google.visualization.BarChart, | |
| 16753 'bubble': google.visualization.BubbleChart, | |
| 16754 'candlestick': google.visualization.CandlestickChart, | |
| 16755 'column': google.visualization.ColumnChart, | |
| 16756 'combo': google.visualization.ComboChart, | |
| 16757 'geo': google.visualization.GeoChart, | |
| 16758 'histogram': google.visualization.Histogram, | |
| 16759 'line': google.visualization.LineChart, | |
| 16760 'pie': google.visualization.PieChart, | |
| 16761 'scatter': google.visualization.ScatterChart, | |
| 16762 'stepped-area': google.visualization.SteppedAreaChart | |
| 16763 }; | |
| 16764 }, | |
| 16765 | |
| 16766 loadData: function() { | |
| 16767 this.canDraw = false; | |
| 16768 if (this.isReady) { | |
| 16769 if (typeof this.data == 'string' || this.data instanceof String) { | |
| 16770 // Load data asynchronously, from external URL. | |
| 16771 this.$.ajax.go(); | |
| 16772 } else { | |
| 16773 var dataTable = this.createDataTable(); | |
| 16774 this.canDraw = true; | |
| 16775 if (dataTable) { | |
| 16776 this.dataTable = dataTable; | |
| 16777 this.drawChart(); | |
| 16778 } | |
| 16779 } | |
| 16780 } | |
| 16781 }, | |
| 16782 | |
| 16783 externalDataLoaded: function(e, detail, sender) { | |
| 16784 var dataTable = this.createDataTable(this.$.ajax.response); | |
| 16785 this.canDraw = true; | |
| 16786 this.dataTable = dataTable; | |
| 16787 this.drawChart(); | |
| 16788 }, | |
| 16789 | |
| 16790 createDataTable: function(data) { | |
| 16791 var dataTable = null; | |
| 16792 | |
| 16793 // If a data object was not passed to this function, default to the | |
| 16794 // chart's data attribute. Passing a data object is necessary for | |
| 16795 // cases when the data attribute is a URL pointing to an external | |
| 16796 // data source. | |
| 16797 if (!data) { | |
| 16798 data = this.data; | |
| 16799 } | |
| 16800 | |
| 16801 if (this.rows && this.rows.length > 0 && this.cols && | |
| 16802 this.cols.length > 0) { | |
| 16803 // Create the data table from cols and rows. | |
| 16804 dataTable = new google.visualization.DataTable(); | |
| 16805 dataTable.cols = this.cols; | |
| 16806 | |
| 16807 for (var i = 0; i < this.cols.length; i++) { | |
| 16808 dataTable.addColumn(this.cols[i]); | |
| 16809 } | |
| 16810 | |
| 16811 dataTable.addRows(this.rows); | |
| 16812 } else { | |
| 16813 // Create dataTable from the passed data or the data attribute. | |
| 16814 // Data can be in the form of raw DataTable data or a two dimensiona
l | |
| 16815 // array. | |
| 16816 if (data.rows && data.cols) { | |
| 16817 dataTable = new google.visualization.DataTable(data); | |
| 16818 } else if (data.length > 0) { | |
| 16819 dataTable = google.visualization.arrayToDataTable(data); | |
| 16820 } | |
| 16821 } | |
| 16822 | |
| 16823 return dataTable; | |
| 16824 } | |
| 16825 }); | |
| 16826 })(); | |
| 16827 ; | |
| 16828 | |
| 16829 | |
| 16830 (function() { | |
| 16831 function formatToIsoUnit(val) { | |
| 16832 for (var n = 0; val >= 1000; n++) { | |
| 16833 val /= 1000; | |
| 16834 } | |
| 16835 // Enforce 2 decimals. | |
| 16836 if (n > 0) { | |
| 16837 val = val.toFixed(2); | |
| 16838 } | |
| 16839 return val + ISO_SUFFIXES[n]; | |
| 16840 } | |
| 16841 | |
| 16842 var p = { | |
| 16843 dataTable: null, | |
| 16844 isReady: false, | |
| 16845 resolution: 'hours', | |
| 16846 titleText: '', | |
| 16847 | |
| 16848 observe: { | |
| 16849 'data': 'loadData' | |
| 16850 }, | |
| 16851 | |
| 16852 ready: function() { | |
| 16853 // FIXME: I tried option='{"title": "{{titleText}}"" }'' | |
| 16854 this.$.chart.options.title = this.titleText; | |
| 16855 }, | |
| 16856 | |
| 16857 attachView: function(view) { | |
| 16858 this.$.chart.setAttribute('data', view.toDataTable().toJSON()); | |
| 16859 }, | |
| 16860 | |
| 16861 getKeyFormatter: function() { | |
| 16862 if (this.resolution == 'days') { | |
| 16863 return new google.visualization.DateFormat({pattern: 'yyyy/MM/dd'}); | |
| 16864 } else { | |
| 16865 return new google.visualization.DateFormat({pattern: 'MM/dd HH:mm'}); | |
| 16866 } | |
| 16867 }, | |
| 16868 | |
| 16869 formatDataColumnToIsoUnit: function(column) { | |
| 16870 for (var i = 0; i < this.dataTable.getNumberOfRows(); i++) { | |
| 16871 this.dataTable.setFormattedValue( | |
| 16872 i, column, formatToIsoUnit(this.dataTable.getValue(i, column))); | |
| 16873 } | |
| 16874 }, | |
| 16875 | |
| 16876 populate: function() { | |
| 16877 // override by subclass | |
| 16878 }, | |
| 16879 | |
| 16880 readyForAction: function(e, detail, sender) { | |
| 16881 google.load("visualization", "1", { | |
| 16882 packages: ['corechart'], | |
| 16883 callback: function() { | |
| 16884 this.isReady = true; | |
| 16885 this.loadData(); | |
| 16886 }.bind(this) | |
| 16887 }); | |
| 16888 }, | |
| 16889 | |
| 16890 // Makes sure ALL custom formatting is removed. | |
| 16891 resetFormattedData: function() { | |
| 16892 for (var i = 0; i < this.dataTable.getNumberOfColumns(); i++) { | |
| 16893 this.resetFormattedDataColumn(i); | |
| 16894 } | |
| 16895 }, | |
| 16896 | |
| 16897 // Makes sure custom formatting is removed for a specific column. | |
| 16898 resetFormattedDataColumn: function(column) { | |
| 16899 for (var i = 0; i < this.dataTable.getNumberOfRows(); i++) { | |
| 16900 this.dataTable.setFormattedValue(i, column, null); | |
| 16901 } | |
| 16902 }, | |
| 16903 | |
| 16904 loadData: function() { | |
| 16905 if (this.isReady && this.data) { | |
| 16906 this.dataTable = new google.visualization.DataTable(this.data); | |
| 16907 this.populate(); | |
| 16908 } | |
| 16909 } | |
| 16910 }; | |
| 16911 | |
| 16912 Polymer('stats-chart-base', p); | |
| 16913 })(); | |
| 16914 | |
| 16915 ; | |
| 16916 | |
| 16917 Polymer('stats-request-chart', { | |
| 16918 titleText: 'Requests', | |
| 16919 | |
| 16920 populate: function() { | |
| 16921 if (this.hidden) { | |
| 16922 return; | |
| 16923 } | |
| 16924 this.resetFormattedData(); | |
| 16925 | |
| 16926 // These indexes are relative to stats_gviz._Summary.ORDER. | |
| 16927 this.getKeyFormatter().format(this.dataTable, 0); | |
| 16928 | |
| 16929 var view = new google.visualization.DataView(this.dataTable); | |
| 16930 view.setColumns([0, 1, 2]); | |
| 16931 this.attachView(view); | |
| 16932 } | |
| 16933 }); | |
| 16934 ; | |
| 16935 | |
| 16936 Polymer('stats-work-chart', { | |
| 16937 isDimension: false, | |
| 16938 titleText: 'Shards Activity', | |
| 16939 | |
| 16940 populate: function() { | |
| 16941 this.resetFormattedData(); | |
| 16942 | |
| 16943 // These indexes are relative to stats_gviz._Summary.ORDER. | |
| 16944 this.getKeyFormatter().format(this.dataTable, 0); | |
| 16945 | |
| 16946 var view = new google.visualization.DataView(this.dataTable); | |
| 16947 if (this.isDimension) { | |
| 16948 view.setColumns([0, 1, 2, 9, 10]); | |
| 16949 } else { | |
| 16950 view.setColumns([0, 3, 4, 11, 12]); | |
| 16951 } | |
| 16952 | |
| 16953 this.attachView(view); | |
| 16954 } | |
| 16955 }); | |
| 16956 ; | |
| 16957 | |
| 16958 Polymer('stats-table-chart', { | |
| 16959 observe: { | |
| 16960 'data': 'loadData' | |
| 16961 }, | |
| 16962 | |
| 16963 readyForAction: function() { | |
| 16964 google.load("visualization", "1", { | |
| 16965 packages: ['table'], | |
| 16966 callback: function() { | |
| 16967 this.isReady = true; | |
| 16968 this.loadData(); | |
| 16969 }.bind(this) | |
| 16970 }); | |
| 16971 }, | |
| 16972 | |
| 16973 loadData: function() { | |
| 16974 if (this.data && this.isReady) { | |
| 16975 if (!this.table) { | |
| 16976 this.table = new google.visualization.Table(this.$.chart); | |
| 16977 } | |
| 16978 this.table.draw(new google.visualization.DataTable(this.data)); | |
| 16979 } | |
| 16980 } | |
| 16981 }); | |
| 16982 ; | |
| 16983 | |
| 16984 Polymer('stats-time-chart', { | |
| 16985 isDimension: false, | |
| 16986 titleText: 'Times (s)', | |
| 16987 | |
| 16988 populate: function() { | |
| 16989 this.resetFormattedData(); | |
| 16990 | |
| 16991 // These indexes are relative to stats_gviz._Summary.ORDER. | |
| 16992 this.getKeyFormatter().format(this.dataTable, 0); | |
| 16993 | |
| 16994 var round3 = new google.visualization.NumberFormat( | |
| 16995 {decimalSymbol:'.', fractionDigits:3}); | |
| 16996 // These indexes are relative to stats_gviz._GVIZ_COLUMNS_ORDER. | |
| 16997 if (this.isDimension) { | |
| 16998 round3.format(this.dataTable, 6); | |
| 16999 round3.format(this.dataTable, 7); | |
| 17000 round3.format(this.dataTable, 8); | |
| 17001 } else { | |
| 17002 round3.format(this.dataTable, 8); | |
| 17003 round3.format(this.dataTable, 9); | |
| 17004 round3.format(this.dataTable, 10); | |
| 17005 } | |
| 17006 | |
| 17007 var view = new google.visualization.DataView(this.dataTable); | |
| 17008 if (this.dimension) { | |
| 17009 view.setColumns([0, 6, 7, 8]); | |
| 17010 } else { | |
| 17011 view.setColumns([0, 8, 9, 10]); | |
| 17012 } | |
| 17013 | |
| 17014 this.attachView(view); | |
| 17015 } | |
| 17016 }); | |
| 17017 ; | |
| 17018 | |
| 17019 Polymer('stats-app', { | |
| 17020 observe: { | |
| 17021 'dimension': 'paramsChanged', | |
| 17022 'duration': 'paramsChanged', | |
| 17023 'resolution': 'paramsChanged' | |
| 17024 }, | |
| 17025 | |
| 17026 ready: function() { | |
| 17027 var self = this; | |
| 17028 window.onpopstate = function(event) { | |
| 17029 self.restoringState = true; | |
| 17030 self.dimension = event.state.dimension; | |
| 17031 self.duration = event.state.duration; | |
| 17032 self.resolution = event.state.resolution; | |
| 17033 }; | |
| 17034 this.restoreState(); | |
| 17035 }, | |
| 17036 | |
| 17037 restoreState: function() { | |
| 17038 try { | |
| 17039 var stateObj = JSON.parse( | |
| 17040 decodeURIComponent(window.location.search.substr(1))); | |
| 17041 this.dimension = stateObj.dimension; | |
| 17042 this.duration = stateObj.duration || 120; | |
| 17043 this.resolution = stateObj.resolution || 'hours'; | |
| 17044 this.restoringState = true; | |
| 17045 var stateObj = this.getState(); | |
| 17046 var url = '/stats?' + JSON.stringify(stateObj); | |
| 17047 window.history.replaceState(stateObj, '', url); | |
| 17048 } catch (e) { | |
| 17049 this.restoringState = false; | |
| 17050 this.resolution = 'hours'; | |
| 17051 this.duration = 120; | |
| 17052 } | |
| 17053 }, | |
| 17054 | |
| 17055 getState: function() { | |
| 17056 return { | |
| 17057 dimension: this.dimension, | |
| 17058 duration: this.duration, | |
| 17059 resolution: this.resolution, | |
| 17060 }; | |
| 17061 }, | |
| 17062 | |
| 17063 pushState: function() { | |
| 17064 var stateObj = this.getState(); | |
| 17065 var url = '/stats?' + JSON.stringify(stateObj); | |
| 17066 window.history.pushState(stateObj, '', url); | |
| 17067 }, | |
| 17068 | |
| 17069 paramsChanged: function(oldValue, newValue) { | |
| 17070 if (this.dimension) { | |
| 17071 this.$.get_stats_dimension.go(); | |
| 17072 } else { | |
| 17073 this.$.get_stats_summary.go(); | |
| 17074 } | |
| 17075 if (!this.restoringState) { | |
| 17076 this.pushState(); | |
| 17077 } else { | |
| 17078 this.restoringState = false; | |
| 17079 } | |
| 17080 }, | |
| 17081 | |
| 17082 onGetStatsSummarySuccess: function(event, detail, sender) { | |
| 17083 this.dataTable = detail.response.table; | |
| 17084 }, | |
| 17085 | |
| 17086 ajaxLoadingChanged: function(oldValue, newValue) { | |
| 17087 if (newValue) { | |
| 17088 this.$.loading.active = true; | |
| 17089 } else { | |
| 17090 this.$.loading.active = false; | |
| 17091 } | |
| 17092 } | |
| 17093 }); | |
| 17094 | |
| OLD | NEW |