├── README.md ├── examples ├── dummy │ ├── example.html │ └── x-dummy.js └── helloworld │ └── helloworld.html ├── x-react.js └── x-tag-components.js /README.md: -------------------------------------------------------------------------------- 1 | react-xtags 2 | =========== 3 | 4 | Using [x-tags](http://www.x-tags.org/) from Mozilla, we can write custom tags within the DOM and have them being rendered in [React](http://facebook.github.io/react/). 5 | 6 | Read [this blog article for more information](http://blog.vjeux.com/2013/javascript/custom-components-react-x-tags.html). 7 | 8 | 9 | ```html 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 27 | 28 | 29 | ``` 30 | 31 | The rendered DOM tree lives in the shadow DOM. This lets us manipulate both the `` component as well as the rendered `
` using Web Inspector. 32 | 33 | ![image](https://f.cloud.github.com/assets/197597/658657/b58f239c-d5ff-11e2-887e-88f845938805.png) 34 | 35 | Anytime you modify the `` component, whether it is in the inspector or in Javascript with the regular DOM API, React is going to be invoked to update the rendered version. 36 | -------------------------------------------------------------------------------- /examples/dummy/example.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | x-react demo 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | It 16 | works 17 | well! 18 | Text 19 | Change my font-size in the inspector! 20 | I'm nested 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /examples/dummy/x-dummy.js: -------------------------------------------------------------------------------- 1 | /** @jsx React.DOM */ 2 | 3 | var Dummy = React.createClass({ 4 | render: function() { 5 | return ( 6 |
7 | React Component: {this.props.name} 8 | {this.props.children} 9 |
10 | ); 11 | } 12 | }); 13 | 14 | xreact.register('x-dummy', Dummy); 15 | -------------------------------------------------------------------------------- /examples/helloworld/helloworld.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /x-react.js: -------------------------------------------------------------------------------- 1 | /** @jsx React.DOM */ 2 | 3 | (function(global) { 4 | 5 | var XReact = React.createClass({ 6 | componentDidMount: function() { 7 | // When anything change in the DOM subtree, we want to update the React 8 | // version of the element 9 | new MutationObserver(this.forceUpdate.bind(this)).observe( 10 | this.props.element, { 11 | childList: true, 12 | attributes: true, 13 | characterData: true, 14 | subtree: true 15 | } 16 | ); 17 | }, 18 | 19 | render: function() { 20 | return convertDOMToReact(this.props.element); 21 | } 22 | }); 23 | 24 | global.xreact = { 25 | tags: {}, 26 | register: function(name, component){ 27 | // We register the react component internally 28 | xreact.tags[name.toLowerCase()] = component; 29 | 30 | xtag.register(name, { 31 | lifecycle: { 32 | created: function() { 33 | // We render the special XReact component inside of the shadow 34 | // root of the DOM node that was passed it. 35 | React.renderComponent( 36 | XReact({element: this}), 37 | this.webkitCreateShadowRoot() 38 | ); 39 | } 40 | } 41 | }); 42 | } 43 | }; 44 | 45 | function convertDOMToReact(dom) { 46 | // If it's a text node, we just return it as string 47 | if (dom.nodeType === 3) { 48 | return dom.textContent; 49 | } 50 | 51 | // Otherwise we find the React component associated to the DOM node. It can 52 | // either be in React.DOM for standard HTML components or in xreact.tags for 53 | // React elements we created 54 | var tag = dom.tagName.toLowerCase(); 55 | return (React.DOM[tag] || xreact.tags[tag])( 56 | convertAttributes(dom), 57 | getChildren(dom).map(convertDOMToReact) 58 | ); 59 | } 60 | 61 | // Helper to get an array of all the children (including text) of a dom node 62 | function getChildren(dom) { 63 | var children = []; 64 | var child = dom.firstChild; 65 | while (child) { 66 | children.push(child); 67 | child = child.nextSibling; 68 | } 69 | return children; 70 | } 71 | 72 | // Helper to convert dom.attributes to an object React can read 73 | function convertAttributes(dom) { 74 | var result = {}; 75 | for (var i = 0; i < dom.attributes.length; ++i) { 76 | var attribute = dom.attributes[i]; 77 | result[attribute.name] = attribute.value; 78 | } 79 | 80 | if (result.style) { 81 | // Convert "font-size: 10px" to {'font-size': '10px'} 82 | var style = {}; 83 | result.style.split(';').forEach(function(rule) { 84 | var split = rule.split(':'); 85 | style[split[0]] = split[1]; 86 | }); 87 | result.style = style; 88 | } 89 | 90 | return result; 91 | } 92 | 93 | })(this); 94 | -------------------------------------------------------------------------------- /x-tag-components.js: -------------------------------------------------------------------------------- 1 | // We don't use the platform bootstrapper, so fake this stuff. 2 | 3 | window.Platform = {}; 4 | var logFlags = {}; 5 | /* 6 | * Copyright 2012 The Polymer Authors. All rights reserved. 7 | * Use of this source code is goverened by a BSD-style 8 | * license that can be found in the LICENSE file. 9 | */ 10 | 11 | // SideTable is a weak map where possible. If WeakMap is not available the 12 | // association is stored as an expando property. 13 | var SideTable; 14 | // TODO(arv): WeakMap does not allow for Node etc to be keys in Firefox 15 | if (typeof WeakMap !== 'undefined' && navigator.userAgent.indexOf('Firefox/') < 0) { 16 | SideTable = WeakMap; 17 | } else { 18 | (function() { 19 | var defineProperty = Object.defineProperty; 20 | var hasOwnProperty = Object.hasOwnProperty; 21 | var counter = new Date().getTime() % 1e9; 22 | 23 | SideTable = function() { 24 | this.name = '__st' + (Math.random() * 1e9 >>> 0) + (counter++ + '__'); 25 | }; 26 | 27 | SideTable.prototype = { 28 | set: function(key, value) { 29 | defineProperty(key, this.name, {value: value, writable: true}); 30 | }, 31 | get: function(key) { 32 | return hasOwnProperty.call(key, this.name) ? key[this.name] : undefined; 33 | }, 34 | delete: function(key) { 35 | this.set(key, undefined); 36 | } 37 | } 38 | })(); 39 | } 40 | 41 | /* 42 | * Copyright 2013 The Polymer Authors. All rights reserved. 43 | * Use of this source code is governed by a BSD-style 44 | * license that can be found in the LICENSE file. 45 | */ 46 | (function() { 47 | 48 | // poor man's adapter for template.content on various platform scenarios 49 | window.templateContent = window.templateContent || function(inTemplate) { 50 | return inTemplate.content; 51 | }; 52 | 53 | // so we can call wrap/unwrap without testing for ShadowDOMPolyfill 54 | 55 | window.wrap = window.unwrap = function(n){ 56 | return n; 57 | } 58 | 59 | window.createShadowRoot = function(inElement) { 60 | return inElement.webkitCreateShadowRoot(); 61 | }; 62 | 63 | window.templateContent = function(inTemplate) { 64 | // if MDV exists, it may need to boostrap this template to reveal content 65 | if (window.HTMLTemplateElement && HTMLTemplateElement.bootstrap) { 66 | HTMLTemplateElement.bootstrap(inTemplate); 67 | } 68 | // fallback when there is no Shadow DOM polyfill, no MDV polyfill, and no 69 | // native template support 70 | if (!inTemplate.content && !inTemplate._content) { 71 | var frag = document.createDocumentFragment(); 72 | while (inTemplate.firstChild) { 73 | frag.appendChild(inTemplate.firstChild); 74 | } 75 | inTemplate._content = frag; 76 | } 77 | return inTemplate.content || inTemplate._content; 78 | }; 79 | 80 | })(); 81 | /* 82 | * Copyright 2013 The Polymer Authors. All rights reserved. 83 | * Use of this source code is governed by a BSD-style 84 | * license that can be found in the LICENSE file. 85 | */ 86 | 87 | (function(scope) { 88 | 89 | // Old versions of iOS do not have bind. 90 | 91 | if (!Function.prototype.bind) { 92 | Function.prototype.bind = function(scope) { 93 | var self = this; 94 | var args = Array.prototype.slice.call(arguments, 1); 95 | return function() { 96 | var args2 = args.slice(); 97 | args2.push.apply(args2, arguments); 98 | return self.apply(scope, args2); 99 | }; 100 | }; 101 | } 102 | 103 | // namespace an import from CustomElements 104 | // TODO(sjmiles): clean up this global 105 | scope.mixin = window.mixin; 106 | 107 | })(window.Platform); 108 | // Copyright 2011 Google Inc. 109 | // 110 | // Licensed under the Apache License, Version 2.0 (the "License"); 111 | // you may not use this file except in compliance with the License. 112 | // You may obtain a copy of the License at 113 | // 114 | // http://www.apache.org/licenses/LICENSE-2.0 115 | // 116 | // Unless required by applicable law or agreed to in writing, software 117 | // distributed under the License is distributed on an "AS IS" BASIS, 118 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 119 | // See the License for the specific language governing permissions and 120 | // limitations under the License. 121 | 122 | (function(scope) { 123 | 124 | 'use strict'; 125 | 126 | // polyfill DOMTokenList 127 | // * add/remove: allow these methods to take multiple classNames 128 | // * toggle: add a 2nd argument which forces the given state rather 129 | // than toggling. 130 | 131 | var add = DOMTokenList.prototype.add; 132 | var remove = DOMTokenList.prototype.remove; 133 | DOMTokenList.prototype.add = function() { 134 | for (var i = 0; i < arguments.length; i++) { 135 | add.call(this, arguments[i]); 136 | } 137 | }; 138 | DOMTokenList.prototype.remove = function() { 139 | for (var i = 0; i < arguments.length; i++) { 140 | remove.call(this, arguments[i]); 141 | } 142 | }; 143 | DOMTokenList.prototype.toggle = function(name, bool) { 144 | if (arguments.length == 1) { 145 | bool = !this.contains(name); 146 | } 147 | bool ? this.add(name) : this.remove(name); 148 | }; 149 | DOMTokenList.prototype.switch = function(oldName, newName) { 150 | oldName && this.remove(oldName); 151 | newName && this.add(newName); 152 | }; 153 | 154 | // make forEach work on NodeList 155 | 156 | NodeList.prototype.forEach = function(cb, context) { 157 | Array.prototype.slice.call(this).forEach(cb, context); 158 | }; 159 | 160 | HTMLCollection.prototype.forEach = function(cb, context) { 161 | Array.prototype.slice.call(this).forEach(cb, context); 162 | }; 163 | 164 | // polyfill performance.now 165 | 166 | if (!window.performance) { 167 | var start = Date.now(); 168 | // only at millisecond precision 169 | window.performance = {now: function(){ return Date.now() - start }}; 170 | } 171 | 172 | // polyfill for requestAnimationFrame 173 | 174 | if (!window.requestAnimationFrame) { 175 | window.requestAnimationFrame = (function() { 176 | var nativeRaf = window.webkitRequestAnimationFrame || 177 | window.mozRequestAnimationFrame; 178 | 179 | return nativeRaf ? 180 | function(callback) { 181 | return nativeRaf(function() { 182 | callback(performance.now()); 183 | }); 184 | } : 185 | function( callback ){ 186 | return window.setTimeout(callback, 1000 / 60); 187 | }; 188 | })(); 189 | } 190 | 191 | if (!window.cancelAnimationFrame) { 192 | window.cancelAnimationFrame = (function() { 193 | return window.webkitCancelAnimationFrame || 194 | window.mozCancelAnimationFrame || 195 | function(id) { 196 | clearTimeout(id); 197 | }; 198 | })(); 199 | } 200 | 201 | // utility 202 | 203 | function createDOM(inTagOrNode, inHTML, inAttrs) { 204 | var dom = typeof inTagOrNode == 'string' ? 205 | document.createElement(inTagOrNode) : inTagOrNode.cloneNode(true); 206 | dom.innerHTML = inHTML; 207 | if (inAttrs) { 208 | for (var n in inAttrs) { 209 | dom.setAttribute(n, inAttrs[n]); 210 | } 211 | } 212 | return dom; 213 | } 214 | 215 | // exports 216 | 217 | scope.createDOM = createDOM; 218 | 219 | })(window.Platform); 220 | 221 | /* 222 | * Copyright 2013 The Polymer Authors. All rights reserved. 223 | * Use of this source code is governed by a BSD-style 224 | * license that can be found in the LICENSE file. 225 | */ 226 | 227 | // poor man's adapter for template.content on various platform scenarios 228 | window.templateContent = window.templateContent || function(inTemplate) { 229 | return inTemplate.content; 230 | }; 231 | /* 232 | * Copyright 2013 The Polymer Authors. All rights reserved. 233 | * Use of this source code is governed by a BSD-style 234 | * license that can be found in the LICENSE file. 235 | */ 236 | 237 | (function(scope) { 238 | 239 | if (!scope) { 240 | scope = window.HTMLImports = {flags:{}}; 241 | } 242 | 243 | var IMPORT_LINK_TYPE = 'import'; 244 | 245 | // highlander object represents a primary document (the argument to 'parse') 246 | // at the root of a tree of documents 247 | 248 | var importer = { 249 | documents: {}, 250 | cache: {}, 251 | preloadSelectors: [ 252 | 'link[rel=' + IMPORT_LINK_TYPE + ']', 253 | 'script[src]', 254 | 'link[rel=stylesheet]' 255 | ].join(','), 256 | load: function(inDocument, inNext) { 257 | // construct a loader instance 258 | loader = new Loader(importer.loaded, inNext); 259 | // alias the loader cache (for debugging) 260 | loader.cache = importer.cache; 261 | // add nodes from document into loader queue 262 | importer.preload(inDocument); 263 | }, 264 | preload: function(inDocument) { 265 | // all preloadable nodes in inDocument 266 | var nodes = inDocument.querySelectorAll(importer.preloadSelectors); 267 | // only load imports from the main document 268 | // TODO(sjmiles): do this by altering the selector list instead 269 | if (inDocument === document) { 270 | nodes = Array.prototype.filter.call(nodes, function(n) { 271 | return isDocumentLink(n); 272 | }); 273 | } 274 | // add these nodes to loader's queue 275 | loader.addNodes(nodes); 276 | }, 277 | loaded: function(inUrl, inElt, inResource) { 278 | if (isDocumentLink(inElt)) { 279 | var document = importer.documents[inUrl]; 280 | // if we've never seen a document at this url 281 | if (!document) { 282 | // generate an HTMLDocument from data 283 | document = makeDocument(inResource, inUrl); 284 | // resolve resource paths relative to host document 285 | path.resolvePathsInHTML(document); 286 | // cache document 287 | importer.documents[inUrl] = document; 288 | // add nodes from this document to the loader queue 289 | importer.preload(document); 290 | } 291 | // store document resource 292 | inElt.content = inElt.__resource = document; 293 | } else { 294 | inElt.__resource = inResource; 295 | // resolve stylesheet resource paths relative to host document 296 | if (isStylesheetLink(inElt)) { 297 | path.resolvePathsInStylesheet(inElt); 298 | } 299 | } 300 | } 301 | }; 302 | 303 | function isDocumentLink(inElt) { 304 | return isLinkRel(inElt, IMPORT_LINK_TYPE); 305 | } 306 | 307 | function isStylesheetLink(inElt) { 308 | return isLinkRel(inElt, 'stylesheet'); 309 | } 310 | 311 | function isLinkRel(inElt, inRel) { 312 | return (inElt.localName === 'link' && inElt.getAttribute('rel') === inRel); 313 | } 314 | 315 | function inMainDocument(inElt) { 316 | return inElt.ownerDocument === document || 317 | // TODO(sjmiles): ShadowDOMPolyfill intrusion 318 | inElt.ownerDocument.impl === document; 319 | } 320 | 321 | function makeDocument(inHTML, inUrl) { 322 | // create a new HTML document 323 | var doc = document.implementation.createHTMLDocument(IMPORT_LINK_TYPE); 324 | // cache the new document's source url 325 | doc._URL = inUrl; 326 | // establish a relative path via 327 | var base = doc.createElement('base'); 328 | base.setAttribute('href', document.baseURI); 329 | doc.head.appendChild(base); 330 | // install html 331 | doc.body.innerHTML = inHTML; 332 | return doc; 333 | } 334 | 335 | var loader; 336 | 337 | var Loader = function(inOnLoad, inOnComplete) { 338 | this.onload = inOnLoad; 339 | this.oncomplete = inOnComplete; 340 | this.inflight = 0; 341 | this.pending = {}; 342 | this.cache = {}; 343 | }; 344 | 345 | Loader.prototype = { 346 | addNodes: function(inNodes) { 347 | // number of transactions to complete 348 | this.inflight += inNodes.length; 349 | // commence transactions 350 | forEach(inNodes, this.require, this); 351 | // anything to do? 352 | this.checkDone(); 353 | }, 354 | require: function(inElt) { 355 | var url = path.nodeUrl(inElt); 356 | // TODO(sjmiles): ad-hoc 357 | inElt.__nodeUrl = url; 358 | // deduplication 359 | if (!this.dedupe(url, inElt)) { 360 | // fetch this resource 361 | this.fetch(url, inElt); 362 | } 363 | }, 364 | dedupe: function(inUrl, inElt) { 365 | if (this.pending[inUrl]) { 366 | // add to list of nodes waiting for inUrl 367 | this.pending[inUrl].push(inElt); 368 | // don't need fetch 369 | return true; 370 | } 371 | if (this.cache[inUrl]) { 372 | // complete load using cache data 373 | this.onload(inUrl, inElt, loader.cache[inUrl]); 374 | // finished this transaction 375 | this.tail(); 376 | // don't need fetch 377 | return true; 378 | } 379 | // first node waiting for inUrl 380 | this.pending[inUrl] = [inElt]; 381 | // need fetch (not a dupe) 382 | return false; 383 | }, 384 | fetch: function(inUrl, inElt) { 385 | xhr.load(inUrl, function(err, resource) { 386 | this.receive(inUrl, inElt, err, resource); 387 | }.bind(this)); 388 | }, 389 | receive: function(inUrl, inElt, inErr, inResource) { 390 | if (!inErr) { 391 | loader.cache[inUrl] = inResource; 392 | } 393 | loader.pending[inUrl].forEach(function(e) { 394 | if (!inErr) { 395 | this.onload(inUrl, e, inResource); 396 | } 397 | this.tail(); 398 | }, this); 399 | loader.pending[inUrl] = null; 400 | }, 401 | tail: function() { 402 | --this.inflight; 403 | this.checkDone(); 404 | }, 405 | checkDone: function() { 406 | if (!this.inflight) { 407 | this.oncomplete(); 408 | } 409 | } 410 | }; 411 | 412 | var path = { 413 | nodeUrl: function(inNode) { 414 | return path.resolveUrl(path.getDocumentUrl(document), path.hrefOrSrc(inNode)); 415 | }, 416 | hrefOrSrc: function(inNode) { 417 | return inNode.getAttribute("href") || inNode.getAttribute("src"); 418 | }, 419 | documentUrlFromNode: function(inNode) { 420 | return path.getDocumentUrl(inNode.ownerDocument); 421 | }, 422 | getDocumentUrl: function(inDocument) { 423 | var url = inDocument && 424 | // TODO(sjmiles): ShadowDOMPolyfill intrusion 425 | (inDocument._URL || (inDocument.impl && inDocument.impl._URL) 426 | || inDocument.baseURI || inDocument.URL) 427 | || ''; 428 | // take only the left side if there is a # 429 | return url.split('#')[0]; 430 | }, 431 | resolveUrl: function(inBaseUrl, inUrl, inRelativeToDocument) { 432 | if (this.isAbsUrl(inUrl)) { 433 | return inUrl; 434 | } 435 | var url = this.compressUrl(this.urlToPath(inBaseUrl) + inUrl); 436 | if (inRelativeToDocument) { 437 | url = path.makeRelPath(path.getDocumentUrl(document), url); 438 | } 439 | return url; 440 | }, 441 | isAbsUrl: function(inUrl) { 442 | return /(^data:)|(^http[s]?:)|(^\/)/.test(inUrl); 443 | }, 444 | urlToPath: function(inBaseUrl) { 445 | var parts = inBaseUrl.split("/"); 446 | parts.pop(); 447 | parts.push(''); 448 | return parts.join("/"); 449 | }, 450 | compressUrl: function(inUrl) { 451 | var parts = inUrl.split("/"); 452 | for (var i=0, p; i= 200 && inRequest.status < 300) 547 | || (inRequest.status === 304) 548 | || (inRequest.status === 0); 549 | }, 550 | load: function(url, next, nextContext) { 551 | var request = new XMLHttpRequest(); 552 | if (scope.flags.debug || scope.flags.bust) { 553 | url += '?' + Math.random(); 554 | } 555 | request.open('GET', url, xhr.async); 556 | request.addEventListener('readystatechange', function(e) { 557 | if (request.readyState === 4) { 558 | next.call(nextContext, !xhr.ok(request) && request, 559 | request.response, url); 560 | } 561 | }); 562 | request.send(); 563 | } 564 | }; 565 | 566 | var forEach = Array.prototype.forEach.call.bind(Array.prototype.forEach); 567 | 568 | // exports 569 | 570 | scope.xhr = xhr; 571 | scope.importer = importer; 572 | scope.getDocumentUrl = path.getDocumentUrl; 573 | 574 | // bootstrap 575 | 576 | // IE shim for CustomEvent 577 | if (typeof window.CustomEvent !== 'function') { 578 | window.CustomEvent = function(inType) { 579 | var e = document.createEvent('HTMLEvents'); 580 | e.initEvent(inType, true, true); 581 | return e; 582 | }; 583 | } 584 | 585 | document.addEventListener('DOMContentLoaded', function() { 586 | // preload document resource trees 587 | importer.load(document, function() { 588 | // TODO(sjmiles): ShadowDOM polyfill pollution 589 | var doc = window.ShadowDOMPolyfill ? ShadowDOMPolyfill.wrap(document) 590 | : document; 591 | HTMLImports.readyTime = new Date().getTime(); 592 | // send HTMLImportsLoaded when finished 593 | doc.body.dispatchEvent( 594 | new CustomEvent('HTMLImportsLoaded', {bubbles: true}) 595 | ); 596 | }); 597 | }); 598 | 599 | })(window.HTMLImports); 600 | 601 | /* 602 | * Copyright 2013 The Polymer Authors. All rights reserved. 603 | * Use of this source code is governed by a BSD-style 604 | * license that can be found in the LICENSE file. 605 | */ 606 | 607 | if (!window.MutationObserver) { 608 | window.MutationObserver = 609 | window.WebKitMutationObserver || 610 | window.JsMutationObserver; 611 | if (!MutationObserver) { 612 | throw new Error("no mutation observer support"); 613 | } 614 | } 615 | 616 | /* 617 | * Copyright 2013 The Polymer Authors. All rights reserved. 618 | * Use of this source code is governed by a BSD-style 619 | * license that can be found in the LICENSE file. 620 | */ 621 | 622 | /** 623 | * Implements `document.register` 624 | * @module CustomElements 625 | */ 626 | 627 | /** 628 | * Polyfilled extensions to the `document` object. 629 | * @class Document 630 | */ 631 | 632 | (function(scope) { 633 | 634 | if (!scope) { 635 | scope = window.CustomElements = {flags:{}}; 636 | } 637 | 638 | // native document.register? 639 | 640 | scope.hasNative = (document.webkitRegister || document.register) && scope.flags.register === 'native'; 641 | if (scope.hasNative) { 642 | 643 | // normalize 644 | document.register = document.register || document.webkitRegister; 645 | 646 | var nop = function() {}; 647 | 648 | // exports 649 | scope.registry = {}; 650 | scope.upgradeElement = nop; 651 | 652 | } else { 653 | 654 | /** 655 | * Registers a custom tag name with the document. 656 | * 657 | * When a registered element is created, a `readyCallback` method is called 658 | * in the scope of the element. The `readyCallback` method can be specified on 659 | * either `inOptions.prototype` or `inOptions.lifecycle` with the latter taking 660 | * precedence. 661 | * 662 | * @method register 663 | * @param {String} inName The tag name to register. Must include a dash ('-'), 664 | * for example 'x-component'. 665 | * @param {Object} inOptions 666 | * @param {String} [inOptions.extends] 667 | * (_off spec_) Tag name of an element to extend (or blank for a new 668 | * element). This parameter is not part of the specification, but instead 669 | * is a hint for the polyfill because the extendee is difficult to infer. 670 | * Remember that the input prototype must chain to the extended element's 671 | * prototype (or HTMLElement.prototype) regardless of the value of 672 | * `extends`. 673 | * @param {Object} inOptions.prototype The prototype to use for the new 674 | * element. The prototype must inherit from HTMLElement. 675 | * @param {Object} [inOptions.lifecycle] 676 | * Callbacks that fire at important phases in the life of the custom 677 | * element. 678 | * 679 | * @example 680 | * FancyButton = document.register("fancy-button", { 681 | * extends: 'button', 682 | * prototype: Object.create(HTMLButtonElement.prototype, { 683 | * readyCallback: { 684 | * value: function() { 685 | * console.log("a fancy-button was created", 686 | * } 687 | * } 688 | * }) 689 | * }); 690 | * @return {Function} Constructor for the newly registered type. 691 | */ 692 | function register(inName, inOptions) { 693 | //console.warn('document.register("' + inName + '", ', inOptions, ')'); 694 | // construct a defintion out of options 695 | // TODO(sjmiles): probably should clone inOptions instead of mutating it 696 | var definition = inOptions || {}; 697 | if (!inName) { 698 | // TODO(sjmiles): replace with more appropriate error (Erik can probably 699 | // offer guidance) 700 | throw new Error('Name argument must not be empty'); 701 | } 702 | // record name 703 | definition.name = inName; 704 | // must have a prototype, default to an extension of HTMLElement 705 | // TODO(sjmiles): probably should throw if no prototype, check spec 706 | if (!definition.prototype) { 707 | // TODO(sjmiles): replace with more appropriate error (Erik can probably 708 | // offer guidance) 709 | throw new Error('Options missing required prototype property'); 710 | } 711 | // ensure a lifecycle object so we don't have to null test it 712 | definition.lifecycle = definition.lifecycle || {}; 713 | // build a list of ancestral custom elements (for native base detection) 714 | // TODO(sjmiles): we used to need to store this, but current code only 715 | // uses it in 'resolveTagName': it should probably be inlined 716 | definition.ancestry = ancestry(definition.extends); 717 | // extensions of native specializations of HTMLElement require localName 718 | // to remain native, and use secondary 'is' specifier for extension type 719 | resolveTagName(definition); 720 | // some platforms require modifications to the user-supplied prototype 721 | // chain 722 | resolvePrototypeChain(definition); 723 | // overrides to implement attributeChanged callback 724 | overrideAttributeApi(definition.prototype); 725 | // 7.1.5: Register the DEFINITION with DOCUMENT 726 | registerDefinition(inName, definition); 727 | // 7.1.7. Run custom element constructor generation algorithm with PROTOTYPE 728 | // 7.1.8. Return the output of the previous step. 729 | definition.ctor = generateConstructor(definition); 730 | definition.ctor.prototype = definition.prototype; 731 | // force our .constructor to be our actual constructor 732 | definition.prototype.constructor = definition.ctor; 733 | // if initial parsing is complete 734 | if (scope.ready) { 735 | // upgrade any pre-existing nodes of this type 736 | scope.upgradeAll(document); 737 | } 738 | return definition.ctor; 739 | } 740 | 741 | function ancestry(inExtends) { 742 | var extendee = registry[inExtends]; 743 | if (extendee) { 744 | return ancestry(extendee.extends).concat([extendee]); 745 | } 746 | return []; 747 | } 748 | 749 | function resolveTagName(inDefinition) { 750 | // if we are explicitly extending something, that thing is our 751 | // baseTag, unless it represents a custom component 752 | var baseTag = inDefinition.extends; 753 | // if our ancestry includes custom components, we only have a 754 | // baseTag if one of them does 755 | for (var i=0, a; (a=inDefinition.ancestry[i]); i++) { 756 | baseTag = a.is && a.tag; 757 | } 758 | // our tag is our baseTag, if it exists, and otherwise just our name 759 | inDefinition.tag = baseTag || inDefinition.name; 760 | if (baseTag) { 761 | // if there is a base tag, use secondary 'is' specifier 762 | inDefinition.is = inDefinition.name; 763 | } 764 | } 765 | 766 | function resolvePrototypeChain(inDefinition) { 767 | // if we don't support __proto__ we need to locate the native level 768 | // prototype for precise mixing in 769 | if (!Object.__proto__) { 770 | // default prototype 771 | var native = HTMLElement.prototype; 772 | // work out prototype when using type-extension 773 | if (inDefinition.is) { 774 | var inst = document.createElement(inDefinition.tag); 775 | native = Object.getPrototypeOf(inst); 776 | } 777 | } 778 | // cache this in case of mixin 779 | inDefinition.native = native; 780 | } 781 | 782 | // SECTION 4 783 | 784 | function instantiate(inDefinition) { 785 | // 4.a.1. Create a new object that implements PROTOTYPE 786 | // 4.a.2. Let ELEMENT by this new object 787 | // 788 | // the custom element instantiation algorithm must also ensure that the 789 | // output is a valid DOM element with the proper wrapper in place. 790 | // 791 | return upgrade(domCreateElement(inDefinition.tag), inDefinition); 792 | } 793 | 794 | function upgrade(inElement, inDefinition) { 795 | // some definitions specify an 'is' attribute 796 | if (inDefinition.is) { 797 | inElement.setAttribute('is', inDefinition.is); 798 | } 799 | // make 'element' implement inDefinition.prototype 800 | implement(inElement, inDefinition); 801 | // flag as upgraded 802 | inElement.__upgraded__ = true; 803 | // there should never be a shadow root on inElement at this point 804 | // we require child nodes be upgraded before ready 805 | scope.upgradeSubtree(inElement); 806 | // lifecycle management 807 | ready(inElement); 808 | // OUTPUT 809 | return inElement; 810 | } 811 | 812 | function implement(inElement, inDefinition) { 813 | // prototype swizzling is best 814 | if (Object.__proto__) { 815 | inElement.__proto__ = inDefinition.prototype; 816 | } else { 817 | // where above we can re-acquire inPrototype via 818 | // getPrototypeOf(Element), we cannot do so when 819 | // we use mixin, so we install a magic reference 820 | customMixin(inElement, inDefinition.prototype, inDefinition.native); 821 | inElement.__proto__ = inDefinition.prototype; 822 | } 823 | } 824 | 825 | function customMixin(inTarget, inSrc, inNative) { 826 | // TODO(sjmiles): 'used' allows us to only copy the 'youngest' version of 827 | // any property. This set should be precalculated. We also need to 828 | // consider this for supporting 'super'. 829 | var used = {}; 830 | // start with inSrc 831 | var p = inSrc; 832 | // sometimes the default is HTMLUnknownElement.prototype instead of 833 | // HTMLElement.prototype, so we add a test 834 | // the idea is to avoid mixing in native prototypes, so adding 835 | // the second test is WLOG 836 | while (p !== inNative && p !== HTMLUnknownElement.prototype) { 837 | var keys = Object.getOwnPropertyNames(p); 838 | for (var i=0, k; k=keys[i]; i++) { 839 | if (!used[k]) { 840 | Object.defineProperty(inTarget, k, 841 | Object.getOwnPropertyDescriptor(p, k)); 842 | used[k] = 1; 843 | } 844 | } 845 | p = Object.getPrototypeOf(p); 846 | } 847 | } 848 | 849 | function ready(inElement) { 850 | // invoke readyCallback 851 | if (inElement.readyCallback) { 852 | inElement.readyCallback(); 853 | } 854 | } 855 | 856 | // attribute watching 857 | 858 | function overrideAttributeApi(prototype) { 859 | // overrides to implement callbacks 860 | // TODO(sjmiles): should support access via .attributes NamedNodeMap 861 | // TODO(sjmiles): preserves user defined overrides, if any 862 | var setAttribute = prototype.setAttribute; 863 | prototype.setAttribute = function(name, value) { 864 | changeAttribute.call(this, name, value, setAttribute); 865 | } 866 | var removeAttribute = prototype.removeAttribute; 867 | prototype.removeAttribute = function(name, value) { 868 | changeAttribute.call(this, name, value, removeAttribute); 869 | } 870 | } 871 | 872 | function changeAttribute(name, value, operation) { 873 | var oldValue = this.getAttribute(name); 874 | operation.apply(this, arguments); 875 | if (this.attributeChangedCallback 876 | && (this.getAttribute(name) !== oldValue)) { 877 | this.attributeChangedCallback(name, oldValue); 878 | } 879 | } 880 | 881 | // element registry (maps tag names to definitions) 882 | 883 | var registry = {}; 884 | 885 | function registerDefinition(inName, inDefinition) { 886 | registry[inName] = inDefinition; 887 | } 888 | 889 | function generateConstructor(inDefinition) { 890 | return function() { 891 | return instantiate(inDefinition); 892 | }; 893 | } 894 | 895 | function createElement(inTag) { 896 | var definition = registry[inTag]; 897 | if (definition) { 898 | return new definition.ctor(); 899 | } 900 | return domCreateElement(inTag); 901 | } 902 | 903 | function upgradeElement(inElement) { 904 | if (!inElement.__upgraded__ && (inElement.nodeType === Node.ELEMENT_NODE)) { 905 | var type = inElement.getAttribute('is') || inElement.localName; 906 | var definition = registry[type]; 907 | return definition && upgrade(inElement, definition); 908 | } 909 | } 910 | 911 | function cloneNode(deep) { 912 | // call original clone 913 | var n = domCloneNode.call(this, deep); 914 | // upgrade the element and subtree 915 | scope.upgradeAll(n); 916 | return n; 917 | } 918 | // capture native createElement before we override it 919 | 920 | var domCreateElement = document.createElement.bind(document); 921 | 922 | // capture native cloneNode before we override it 923 | 924 | var domCloneNode = Node.prototype.cloneNode; 925 | 926 | // exports 927 | 928 | document.register = register; 929 | document.createElement = createElement; // override 930 | Node.prototype.cloneNode = cloneNode; // override 931 | 932 | scope.registry = registry; 933 | 934 | /** 935 | * Upgrade an element to a custom element. Upgrading an element 936 | * causes the custom prototype to be applied, an `is` attribute 937 | * to be attached (as needed), and invocation of the `readyCallback`. 938 | * `upgrade` does nothing if the element is already upgraded, or 939 | * if it matches no registered custom tag name. 940 | * 941 | * @method ugprade 942 | * @param {Element} inElement The element to upgrade. 943 | * @return {Element} The upgraded element. 944 | */ 945 | scope.upgrade = upgradeElement; 946 | 947 | } 948 | 949 | })(window.CustomElements); 950 | 951 | /* 952 | Copyright 2013 The Polymer Authors. All rights reserved. 953 | Use of this source code is governed by a BSD-style 954 | license that can be found in the LICENSE file. 955 | */ 956 | 957 | (function(scope){ 958 | 959 | /* 960 | if (HTMLElement.prototype.webkitShadowRoot) { 961 | Object.defineProperty(HTMLElement.prototype, 'shadowRoot', { 962 | get: function() { 963 | return this.webkitShadowRoot; 964 | } 965 | }; 966 | } 967 | */ 968 | 969 | // walk the subtree rooted at node, applying 'find(element, data)' function 970 | // to each element 971 | // if 'find' returns true for 'element', do not search element's subtree 972 | function findAll(node, find, data) { 973 | var e = node.firstElementChild; 974 | if (!e) { 975 | e = node.firstChild; 976 | while (e && e.nodeType !== Node.ELEMENT_NODE) { 977 | e = e.nextSibling; 978 | } 979 | } 980 | while (e) { 981 | if (find(e, data) !== true) { 982 | findAll(e, find, data); 983 | } 984 | e = e.nextElementSibling; 985 | } 986 | return null; 987 | } 988 | 989 | // walk the subtree rooted at node, including descent into shadow-roots, 990 | // applying 'cb' to each element 991 | function forSubtree(node, cb) { 992 | //logFlags.dom && node.childNodes && node.childNodes.length && console.group('subTree: ', node); 993 | findAll(node, function(e) { 994 | if (cb(e)) { 995 | return true; 996 | } 997 | if (e.webkitShadowRoot) { 998 | forSubtree(e.webkitShadowRoot, cb); 999 | } 1000 | }); 1001 | if (node.webkitShadowRoot) { 1002 | forSubtree(node.webkitShadowRoot, cb); 1003 | } 1004 | //logFlags.dom && node.childNodes && node.childNodes.length && console.groupEnd(); 1005 | } 1006 | 1007 | // manage lifecycle on added node 1008 | function added(node) { 1009 | if (upgrade(node)) { 1010 | insertedNode(node); 1011 | return true; 1012 | } 1013 | inserted(node); 1014 | } 1015 | 1016 | // manage lifecycle on added node's subtree only 1017 | function addedSubtree(node) { 1018 | forSubtree(node, function(e) { 1019 | if (added(e)) { 1020 | return true; 1021 | } 1022 | }); 1023 | } 1024 | 1025 | // manage lifecycle on added node and it's subtree 1026 | function addedNode(node) { 1027 | return added(node) || addedSubtree(node); 1028 | } 1029 | 1030 | // upgrade custom elements at node, if applicable 1031 | function upgrade(node) { 1032 | if (!node.__upgraded__ && node.nodeType === Node.ELEMENT_NODE) { 1033 | var type = node.getAttribute('is') || node.localName; 1034 | var definition = scope.registry[type]; 1035 | if (definition) { 1036 | logFlags.dom && console.group('upgrade:', node.localName); 1037 | scope.upgrade(node); 1038 | logFlags.dom && console.groupEnd(); 1039 | return true; 1040 | } 1041 | } 1042 | } 1043 | 1044 | function insertedNode(node) { 1045 | inserted(node); 1046 | if (inDocument(node)) { 1047 | forSubtree(node, function(e) { 1048 | inserted(e); 1049 | }); 1050 | } 1051 | } 1052 | 1053 | // TODO(sjmiles): if there are descents into trees that can never have inDocument(*) true, fix this 1054 | 1055 | function inserted(element) { 1056 | // TODO(sjmiles): it's possible we were inserted and removed in the space 1057 | // of one microtask, in which case we won't be 'inDocument' here 1058 | // But there are other cases where we are testing for inserted without 1059 | // specific knowledge of mutations, and must test 'inDocument' to determine 1060 | // whether to call inserted 1061 | // If we can factor these cases into separate code paths we can have 1062 | // better diagnostics. 1063 | // TODO(sjmiles): when logging, do work on all custom elements so we can 1064 | // track behavior even when callbacks not defined 1065 | //console.log('inserted: ', element.localName); 1066 | if (element.insertedCallback || (element.__upgraded__ && logFlags.dom)) { 1067 | logFlags.dom && console.group('inserted:', element.localName); 1068 | if (inDocument(element)) { 1069 | element.__inserted = (element.__inserted || 0) + 1; 1070 | // if we are in a 'removed' state, bluntly adjust to an 'inserted' state 1071 | if (element.__inserted < 1) { 1072 | element.__inserted = 1; 1073 | } 1074 | // if we are 'over inserted', squelch the callback 1075 | if (element.__inserted > 1) { 1076 | logFlags.dom && console.warn('inserted:', element.localName, 1077 | 'insert/remove count:', element.__inserted) 1078 | } else if (element.insertedCallback) { 1079 | logFlags.dom && console.log('inserted:', element.localName); 1080 | element.insertedCallback(); 1081 | } 1082 | } 1083 | logFlags.dom && console.groupEnd(); 1084 | } 1085 | } 1086 | 1087 | function removedNode(node) { 1088 | removed(node); 1089 | forSubtree(node, function(e) { 1090 | removed(e); 1091 | }); 1092 | } 1093 | 1094 | function removed(element) { 1095 | // TODO(sjmiles): temporary: do work on all custom elements so we can track 1096 | // behavior even when callbacks not defined 1097 | if (element.removedCallback || (element.__upgraded__ && logFlags.dom)) { 1098 | logFlags.dom && console.log('removed:', element.localName); 1099 | if (!inDocument(element)) { 1100 | element.__inserted = (element.__inserted || 0) - 1; 1101 | // if we are in a 'inserted' state, bluntly adjust to an 'removed' state 1102 | if (element.__inserted > 0) { 1103 | element.__inserted = 0; 1104 | } 1105 | // if we are 'over removed', squelch the callback 1106 | if (element.__inserted < 0) { 1107 | logFlags.dom && console.warn('removed:', element.localName, 1108 | 'insert/remove count:', element.__inserted) 1109 | } else if (element.removedCallback) { 1110 | element.removedCallback(); 1111 | } 1112 | } 1113 | } 1114 | } 1115 | 1116 | function inDocument(element) { 1117 | var p = element; 1118 | while (p) { 1119 | if (p == element.ownerDocument) { 1120 | return true; 1121 | } 1122 | p = p.parentNode || p.host; 1123 | } 1124 | } 1125 | 1126 | function watchShadow(node) { 1127 | if (node.webkitShadowRoot && !node.webkitShadowRoot.__watched) { 1128 | logFlags.dom && console.log('watching shadow-root for: ', node.localName); 1129 | observe(node.webkitShadowRoot); 1130 | node.webkitShadowRoot.__watched = true; 1131 | } 1132 | } 1133 | 1134 | function watchAllShadows(node) { 1135 | watchShadow(node); 1136 | forSubtree(node, function(e) { 1137 | watchShadow(node); 1138 | }); 1139 | } 1140 | 1141 | function filter(inNode) { 1142 | switch (inNode.localName) { 1143 | case 'style': 1144 | case 'script': 1145 | case 'template': 1146 | case undefined: 1147 | return true; 1148 | } 1149 | } 1150 | 1151 | function handler(mutations) { 1152 | // 1153 | if (logFlags.dom) { 1154 | var mx = mutations[0]; 1155 | if (mx && mx.type === 'childList' && mx.addedNodes) { 1156 | if (mx.addedNodes) { 1157 | var d = mx.addedNodes[0]; 1158 | while (d && d !== document && !d.host) { 1159 | d = d.parentNode; 1160 | } 1161 | var u = d && (d.URL || d._URL || (d.host && d.host.localName)) || ''; 1162 | u = u.split('/?').shift().split('/').pop(); 1163 | } 1164 | } 1165 | console.group('mutations (%d) [%s]', mutations.length, u || ''); 1166 | } 1167 | // 1168 | mutations.forEach(function(mx) { 1169 | //logFlags.dom && console.group('mutation'); 1170 | if (mx.type === 'childList') { 1171 | forEach(mx.addedNodes, function(n) { 1172 | //logFlags.dom && console.log(n.localName); 1173 | if (filter(n)) { 1174 | return; 1175 | } 1176 | // watch shadow-roots on nodes that have had them attached manually 1177 | // TODO(sjmiles): remove if createShadowRoot is overridden 1178 | // TODO(sjmiles): removed as an optimization, manual shadow roots 1179 | // must be watched explicitly 1180 | //watchAllShadows(n); 1181 | // nodes added may need lifecycle management 1182 | addedNode(n); 1183 | }); 1184 | // removed nodes may need lifecycle management 1185 | forEach(mx.removedNodes, function(n) { 1186 | //logFlags.dom && console.log(n.localName); 1187 | if (filter(n)) { 1188 | return; 1189 | } 1190 | removedNode(n); 1191 | }); 1192 | } 1193 | //logFlags.dom && console.groupEnd(); 1194 | }); 1195 | logFlags.dom && console.groupEnd(); 1196 | }; 1197 | 1198 | var observer = new MutationObserver(handler); 1199 | 1200 | function takeRecords() { 1201 | // TODO(sjmiles): ask Raf why we have to call handler ourselves 1202 | handler(observer.takeRecords()); 1203 | } 1204 | 1205 | var forEach = Array.prototype.forEach.call.bind(Array.prototype.forEach); 1206 | 1207 | function observe(inRoot) { 1208 | observer.observe(inRoot, {childList: true, subtree: true}); 1209 | } 1210 | 1211 | function observeDocument(document) { 1212 | observe(document); 1213 | } 1214 | 1215 | function upgradeDocument(document) { 1216 | logFlags.dom && console.group('upgradeDocument: ', (document.URL || document._URL || '').split('/').pop()); 1217 | addedNode(document); 1218 | logFlags.dom && console.groupEnd(); 1219 | } 1220 | 1221 | // exports 1222 | 1223 | scope.watchShadow = watchShadow; 1224 | scope.watchAllShadows = watchAllShadows; 1225 | 1226 | scope.upgradeAll = addedNode; 1227 | scope.upgradeSubtree = addedSubtree; 1228 | 1229 | scope.observeDocument = observeDocument; 1230 | scope.upgradeDocument = upgradeDocument; 1231 | 1232 | scope.takeRecords = takeRecords; 1233 | 1234 | })(window.CustomElements); 1235 | 1236 | /* 1237 | * Copyright 2013 The Polymer Authors. All rights reserved. 1238 | * Use of this source code is governed by a BSD-style 1239 | * license that can be found in the LICENSE file. 1240 | */ 1241 | 1242 | (function(){ 1243 | 1244 | var HTMLElementElement = function(inElement) { 1245 | inElement.register = HTMLElementElement.prototype.register; 1246 | parseElementElement(inElement); 1247 | return inElement; 1248 | }; 1249 | 1250 | HTMLElementElement.prototype = { 1251 | register: function(inMore) { 1252 | if (inMore) { 1253 | this.options.lifecycle = inMore.lifecycle; 1254 | if (inMore.prototype) { 1255 | mixin(this.options.prototype, inMore.prototype); 1256 | } 1257 | } 1258 | } 1259 | }; 1260 | 1261 | function parseElementElement(inElement) { 1262 | // options to glean from inElement attributes 1263 | var options = { 1264 | name: '', 1265 | extends: null 1266 | }; 1267 | // glean them 1268 | takeAttributes(inElement, options); 1269 | // default base 1270 | var base = HTMLElement.prototype; 1271 | // optional specified base 1272 | if (options.extends) { 1273 | // build an instance of options.extends 1274 | var archetype = document.createElement(options.extends); 1275 | // acquire the prototype 1276 | // TODO(sjmiles): __proto__ may be hinted by the custom element 1277 | // system on platforms that don't support native __proto__ 1278 | // on those platforms the API is mixed into archetype and the 1279 | // effective base is not archetype's real prototype 1280 | base = archetype.__proto__ || Object.getPrototypeOf(archetype); 1281 | } 1282 | // extend base 1283 | options.prototype = Object.create(base); 1284 | // install options 1285 | inElement.options = options; 1286 | // locate user script 1287 | var script = inElement.querySelector('script:not([type]),script[type="text/javascript"],scripts'); 1288 | if (script) { 1289 | // execute user script in 'inElement' context 1290 | executeComponentScript(script.textContent, inElement, options.name); 1291 | }; 1292 | // register our new element 1293 | var ctor = document.register(options.name, options); 1294 | inElement.ctor = ctor; 1295 | // store optional constructor reference 1296 | var refName = inElement.getAttribute('constructor'); 1297 | if (refName) { 1298 | window[refName] = ctor; 1299 | } 1300 | } 1301 | 1302 | // each property in inDictionary takes a value 1303 | // from the matching attribute in inElement, if any 1304 | function takeAttributes(inElement, inDictionary) { 1305 | for (var n in inDictionary) { 1306 | var a = inElement.attributes[n]; 1307 | if (a) { 1308 | inDictionary[n] = a.value; 1309 | } 1310 | } 1311 | } 1312 | 1313 | // invoke inScript in inContext scope 1314 | function executeComponentScript(inScript, inContext, inName) { 1315 | // set (highlander) context 1316 | context = inContext; 1317 | // source location 1318 | var owner = context.ownerDocument; 1319 | var url = (owner._URL || owner.URL || owner.impl 1320 | && (owner.impl._URL || owner.impl.URL)); 1321 | // ensure the component has a unique source map so it can be debugged 1322 | // if the name matches the filename part of the owning document's url, 1323 | // use this, otherwise, add ":" to the document url. 1324 | var match = url.match(/.*\/([^.]*)[.]?.*$/); 1325 | if (match) { 1326 | var name = match[1]; 1327 | url += name != inName ? ':' + inName : ''; 1328 | } 1329 | // compose script 1330 | var code = "__componentScript('" 1331 | + inName 1332 | + "', function(){" 1333 | + inScript 1334 | + "});" 1335 | + "\n//@ sourceURL=" + url + "\n" 1336 | ; 1337 | // inject script 1338 | eval(code); 1339 | } 1340 | 1341 | var context; 1342 | 1343 | // global necessary for script injection 1344 | window.__componentScript = function(inName, inFunc) { 1345 | inFunc.call(context); 1346 | }; 1347 | 1348 | // utility 1349 | 1350 | // copy top level properties from props to obj 1351 | function mixin(obj, props) { 1352 | obj = obj || {}; 1353 | try { 1354 | Object.getOwnPropertyNames(props).forEach(function(n) { 1355 | var pd = Object.getOwnPropertyDescriptor(props, n); 1356 | if (pd) { 1357 | Object.defineProperty(obj, n, pd); 1358 | } 1359 | }); 1360 | } catch(x) { 1361 | } 1362 | return obj; 1363 | } 1364 | 1365 | // exports 1366 | 1367 | window.HTMLElementElement = HTMLElementElement; 1368 | 1369 | })(); 1370 | 1371 | /* 1372 | * Copyright 2013 The Polymer Authors. All rights reserved. 1373 | * Use of this source code is governed by a BSD-style 1374 | * license that can be found in the LICENSE file. 1375 | */ 1376 | 1377 | (function() { 1378 | 1379 | var IMPORT_LINK_TYPE = 'import'; 1380 | 1381 | // highlander object for parsing a document tree 1382 | 1383 | var componentParser = { 1384 | selectors: [ 1385 | 'link[rel=' + IMPORT_LINK_TYPE + ']', 1386 | 'link[rel=stylesheet]', 1387 | 'script[src]', 1388 | 'script:not([type])', 1389 | 'script[type="text/javascript"]', 1390 | 'style', 1391 | 'element' 1392 | ], 1393 | map: { 1394 | link: 'parseLink', 1395 | script: 'parseScript', 1396 | element: 'parseElement', 1397 | style: 'parseStyle' 1398 | }, 1399 | parse: function(inDocument) { 1400 | if (!inDocument.__parsed) { 1401 | // only parse once 1402 | inDocument.__parsed = true; 1403 | // all parsable elements in inDocument (depth-first pre-order traversal) 1404 | var elts = inDocument.querySelectorAll(cp.selectors); 1405 | // for each parsable node type, call the mapped parsing method 1406 | forEach(elts, function(e) { 1407 | //console.log(map[e.localName] + ":", path.nodeUrl(e)); 1408 | cp[cp.map[e.localName]](e); 1409 | }); 1410 | // upgrade all upgradeable static elements, anything dynamically 1411 | // created should be caught by observer 1412 | CustomElements.upgradeDocument(inDocument); 1413 | // observe document for dom changes 1414 | CustomElements.observeDocument(inDocument); 1415 | } 1416 | }, 1417 | parseLink: function(inLinkElt) { 1418 | // imports 1419 | if (isDocumentLink(inLinkElt)) { 1420 | if (inLinkElt.content) { 1421 | cp.parse(inLinkElt.content); 1422 | } 1423 | } else if (canAddToMainDoument(inLinkElt)) { 1424 | document.head.appendChild(inLinkElt); 1425 | } 1426 | }, 1427 | parseScript: function(inScriptElt) { 1428 | if (canAddToMainDoument(inScriptElt)) { 1429 | // otherwise, evaluate now 1430 | var code = inScriptElt.__resource || inScriptElt.textContent; 1431 | if (code) { 1432 | code += "\n//@ sourceURL=" + inScriptElt.__nodeUrl + "\n"; 1433 | eval.call(window, code); 1434 | } 1435 | } 1436 | }, 1437 | parseStyle: function(inStyleElt) { 1438 | if (canAddToMainDoument(inStyleElt)) { 1439 | document.head.appendChild(inStyleElt); 1440 | } 1441 | }, 1442 | parseElement: function(inElementElt) { 1443 | new HTMLElementElement(inElementElt); 1444 | } 1445 | }; 1446 | 1447 | var cp = componentParser; 1448 | 1449 | // nodes can be moved to the main document 1450 | // if they are not in the main document, are not children of 1451 | // and are in a tree at parse time. 1452 | function canAddToMainDoument(node) { 1453 | return !inMainDocument(node) 1454 | && node.parentNode 1455 | && !isElementElementChild(node); 1456 | } 1457 | 1458 | function inMainDocument(inElt) { 1459 | return inElt.ownerDocument === document || 1460 | // TODO(sjmiles): ShadowDOMPolyfill intrusion 1461 | inElt.ownerDocument.impl === document; 1462 | } 1463 | 1464 | function isDocumentLink(inElt) { 1465 | return (inElt.localName === 'link' 1466 | && inElt.getAttribute('rel') === IMPORT_LINK_TYPE); 1467 | } 1468 | 1469 | function isElementElementChild(inElt) { 1470 | if (inElt.parentNode && inElt.parentNode.localName === 'element') { 1471 | return true; 1472 | } 1473 | } 1474 | 1475 | var forEach = Array.prototype.forEach.call.bind(Array.prototype.forEach); 1476 | 1477 | // exports 1478 | 1479 | CustomElements.parser = componentParser; 1480 | 1481 | })(); 1482 | /* 1483 | * Copyright 2013 The Polymer Authors. All rights reserved. 1484 | * Use of this source code is governed by a BSD-style 1485 | * license that can be found in the LICENSE file. 1486 | */ 1487 | (function(){ 1488 | 1489 | // bootstrap parsing 1490 | 1491 | // IE shim for CustomEvent 1492 | if (typeof window.CustomEvent !== 'function') { 1493 | window.CustomEvent = function(inType) { 1494 | var e = document.createEvent('HTMLEvents'); 1495 | e.initEvent(inType, true, true); 1496 | return e; 1497 | }; 1498 | } 1499 | 1500 | function bootstrap() { 1501 | // go async so call stack can unwind 1502 | setTimeout(function() { 1503 | // parse document 1504 | CustomElements.parser.parse(document); 1505 | // set internal flag 1506 | CustomElements.ready = true; 1507 | CustomElements.readyTime = new Date().getTime(); 1508 | if (window.HTMLImports) { 1509 | CustomElements.elapsed = CustomElements.readyTime - HTMLImports.readyTime; 1510 | } 1511 | // notify system 1512 | document.body.dispatchEvent( 1513 | new CustomEvent('WebComponentsReady', {bubbles: true}) 1514 | ); 1515 | }, 0); 1516 | } 1517 | 1518 | // TODO(sjmiles): 'window' has no wrappability under ShadowDOM polyfill, so 1519 | // we are forced to split into two versions 1520 | 1521 | if (window.HTMLImports) { 1522 | document.addEventListener('HTMLImportsLoaded', bootstrap); 1523 | } else { 1524 | if (document.readyState === 'complete' || document.readyState === 'interactive') { 1525 | boostrap(); 1526 | } else { 1527 | window.addEventListener('DOMContentLoaded', bootstrap); 1528 | } 1529 | } 1530 | 1531 | })(); 1532 | 1533 | /* 1534 | * Copyright 2013 The Polymer Authors. All rights reserved. 1535 | * Use of this source code is governed by a BSD-style 1536 | * license that can be found in the LICENSE file. 1537 | */ 1538 | (function() { 1539 | 1540 | // inject style sheet 1541 | var style = document.createElement('style'); 1542 | style.textContent = 'element {display: none;} /* injected by platform.js */'; 1543 | var head = document.querySelector('head'); 1544 | head.insertBefore(style, head.firstChild); 1545 | 1546 | if (window.ShadowDOMPolyfill) { 1547 | 1548 | function nop() {}; 1549 | 1550 | // disable shadow dom watching 1551 | CustomElements.watchShadow = nop; 1552 | CustomElements.watchAllShadows = nop; 1553 | 1554 | // ensure wrapped inputs for these functions 1555 | var fns = ['upgradeAll', 'upgradeSubtree', 'observeDocument', 1556 | 'upgradeDocument']; 1557 | 1558 | // cache originals 1559 | var original = {}; 1560 | fns.forEach(function(fn) { 1561 | original[fn] = CustomElements[fn]; 1562 | }); 1563 | 1564 | // override 1565 | fns.forEach(function(fn) { 1566 | CustomElements[fn] = function(inNode) { 1567 | return original[fn](wrap(inNode)); 1568 | }; 1569 | }); 1570 | 1571 | } 1572 | 1573 | })(); 1574 | 1575 | (function () { 1576 | 1577 | /*** Variables ***/ 1578 | 1579 | var win = window, 1580 | doc = document, 1581 | noop = function(){}, 1582 | regexPseudoSplit = /([\w-]+(?:\([^\)]+\))?)/g, 1583 | regexPseudoReplace = /(\w*)(?:\(([^\)]*)\))?/, 1584 | regexDigits = /(\d+)/g, 1585 | keypseudo = { 1586 | action: function (pseudo, event) { 1587 | return pseudo.value.match(regexDigits).indexOf(String(event.keyCode)) > -1 == (pseudo.name == 'keypass'); 1588 | } 1589 | }, 1590 | prefix = (function () { 1591 | var styles = win.getComputedStyle(doc.documentElement, ''), 1592 | pre = (Array.prototype.slice 1593 | .call(styles) 1594 | .join('') 1595 | .match(/-(moz|webkit|ms)-/) || (styles.OLink === '' && ['', 'o']) 1596 | )[1]; 1597 | return { 1598 | dom: pre == 'ms' ? pre.toUpperCase() : pre, 1599 | lowercase: pre, 1600 | css: '-' + pre + '-', 1601 | js: pre[0].toUpperCase() + pre.substr(1) 1602 | }; 1603 | 1604 | })(), 1605 | matchSelector = Element.prototype.matchesSelector || Element.prototype[prefix.lowercase + 'MatchesSelector']; 1606 | 1607 | /*** Functions ***/ 1608 | 1609 | // Utilities 1610 | 1611 | var typeObj = {}; 1612 | function typeOf(obj) { 1613 | return typeObj.toString.call(obj).match(/\s([a-zA-Z]+)/)[1].toLowerCase(); 1614 | } 1615 | 1616 | function clone(item, type){ 1617 | var fn = clone[type || typeOf(item)]; 1618 | return fn ? fn(item) : item; 1619 | } 1620 | clone.object = function(src){ 1621 | var obj = {}; 1622 | for (var key in src) obj[key] = clone(src[key]); 1623 | return obj; 1624 | }; 1625 | clone.array = function(src){ 1626 | var i = src.length, array = new Array(i); 1627 | while (i--) array[i] = clone(src[i]); 1628 | return array; 1629 | }; 1630 | 1631 | var unsliceable = ['number', 'boolean', 'string', 'function']; 1632 | function toArray(obj){ 1633 | return unsliceable.indexOf(typeOf(obj)) == -1 ? 1634 | Array.prototype.slice.call(obj, 0) : 1635 | [obj]; 1636 | } 1637 | 1638 | // DOM 1639 | var str = ''; 1640 | function query(element, selector){ 1641 | return (selector || str).length ? toArray(element.querySelectorAll(selector)) : []; 1642 | } 1643 | 1644 | function parseMutations(element, mutations) { 1645 | var diff = { added: [], removed: [] }; 1646 | mutations.forEach(function(record){ 1647 | record._mutation = true; 1648 | for (var z in diff) { 1649 | var type = element._records[(z == 'added') ? 'inserted' : 'removed'], 1650 | nodes = record[z + 'Nodes'], length = nodes.length; 1651 | for (var i = 0; i < length && diff[z].indexOf(nodes[i]) == -1; i++){ 1652 | diff[z].push(nodes[i]); 1653 | type.forEach(function(fn){ 1654 | fn(nodes[i], record); 1655 | }); 1656 | } 1657 | } 1658 | }); 1659 | } 1660 | 1661 | // Mixins 1662 | 1663 | function mergeOne(source, key, current){ 1664 | var type = typeOf(current); 1665 | if (type == 'object' && typeOf(source[key]) == 'object') xtag.merge(source[key], current); 1666 | else source[key] = clone(current, type); 1667 | return source; 1668 | } 1669 | 1670 | function mergeMixin(type, mixin, option) { 1671 | var original = {}; 1672 | for (var o in option) original[o.split(':')[0]] = true; 1673 | for (var x in mixin) if (!original[x.split(':')[0]]) option[x] = mixin[x]; 1674 | } 1675 | 1676 | function applyMixins(tag) { 1677 | tag.mixins.forEach(function (name) { 1678 | var mixin = xtag.mixins[name]; 1679 | for (var type in mixin) { 1680 | switch (type) { 1681 | case 'lifecycle': case 'methods': 1682 | mergeMixin(type, mixin[type], tag[type]); 1683 | break; 1684 | case 'accessors': case 'prototype': 1685 | for (var z in mixin[type]) mergeMixin(z, mixin[type], tag.accessors); 1686 | break; 1687 | case 'events': 1688 | break; 1689 | } 1690 | } 1691 | }); 1692 | return tag; 1693 | } 1694 | 1695 | // Events 1696 | 1697 | function touchFilter(custom, event) { 1698 | if (custom.listener.touched) return custom.listener.touched = false; 1699 | else { 1700 | if (event.type.match('touch')) custom.listener.touched = true; 1701 | } 1702 | } 1703 | 1704 | function createFlowEvent(type) { 1705 | var flow = type == 'over'; 1706 | return { 1707 | base: 'OverflowEvent' in win ? 'overflowchanged' : type + 'flow', 1708 | condition: function (custom, event) { 1709 | event.flow = type; 1710 | return event.type == (type + 'flow') || 1711 | ((event.orient === 0 && event.horizontalOverflow == flow) || 1712 | (event.orient == 1 && event.verticalOverflow == flow) || 1713 | (event.orient == 2 && event.horizontalOverflow == flow && event.verticalOverflow == flow)); 1714 | } 1715 | }; 1716 | } 1717 | 1718 | // Accessors 1719 | 1720 | function getArgs(attr, value){ 1721 | return { 1722 | value: attr.boolean ? '' : value, 1723 | method: attr.boolean && (value === false || value === null) ? 'removeAttribute' : 'setAttribute' 1724 | }; 1725 | } 1726 | 1727 | function setAttr(attr, name, value){ 1728 | var args = getArgs(attr, value); 1729 | this[args.method](name, args.value); 1730 | } 1731 | 1732 | function syncAttr(attr, name, value){ 1733 | var args = getArgs(attr, value), 1734 | nodes = attr.property ? [this.xtag[attr.property]] : attr.selector ? xtag.query(this, attr.selector) : [], 1735 | index = nodes.length; 1736 | while (index--) nodes[index][args.method](name, args.value); 1737 | } 1738 | 1739 | function wrapAttr(tag, method){ 1740 | var original = tag.prototype[method] || HTMLElement.prototype[method]; 1741 | tag.prototype[method] = { 1742 | writable: true, 1743 | enumberable: true, 1744 | value: function (name, value, skip){ 1745 | var attr = tag.attributes[name.toLowerCase()]; 1746 | original.call(this, name, attr && attr.boolean ? '' : value); 1747 | if (attr) { 1748 | syncAttr.call(this, attr, name, value); 1749 | if (!attr.skip && !this.xtag._skipAttr) { 1750 | attr.setter.call(this, attr.boolean ? method == 'setAttribute' : this.getAttribute(name)); 1751 | } 1752 | delete this.xtag._skipAttr; 1753 | } 1754 | } 1755 | }; 1756 | } 1757 | 1758 | function updateTemplate(element, name, value){ 1759 | if (element.template){ 1760 | element.xtag.template.updateBindingValue(element, name, value); 1761 | } 1762 | } 1763 | 1764 | function attachProperties(tag, prop, z, accessor, attr, name){ 1765 | var key = z.split(':'), type = key[0]; 1766 | if (type == 'get') { 1767 | key[0] = prop; 1768 | tag.prototype[prop].get = xtag.applyPseudos(key.join(':'), accessor[z], tag.pseudos); 1769 | } 1770 | else if (type == 'set') { 1771 | key[0] = prop; 1772 | if (attr) attr.setter = accessor[z]; 1773 | tag.prototype[prop].set = xtag.applyPseudos(key.join(':'), attr ? function(value, skip){ 1774 | syncAttr.call(this, attr, name, value); 1775 | accessor[z].call(this, value); 1776 | if (!skip && !attr.skip) { 1777 | this.xtag._skipAttr = true; 1778 | setAttr.call(this, attr, name, value); 1779 | } 1780 | updateTemplate(this, name, value); 1781 | 1782 | } : accessor[z] ? function(value){ 1783 | accessor[z].call(this, value); 1784 | updateTemplate(this, name, value); 1785 | } : null, tag.pseudos); 1786 | } 1787 | else tag.prototype[prop][z] = accessor[z]; 1788 | } 1789 | 1790 | function parseAccessor(tag, prop){ 1791 | tag.prototype[prop] = {}; 1792 | var accessor = tag.accessors[prop], 1793 | attr = accessor.attribute, 1794 | name = attr && attr.name ? attr.name.toLowerCase() : prop; 1795 | 1796 | if (attr) tag.attributes[name] = attr; 1797 | for (var z in accessor) attachProperties(tag, prop, z, accessor, attr, name); 1798 | 1799 | if (attr) { 1800 | if (!tag.prototype[prop].get) { 1801 | var method = (attr.boolean ? 'has' : 'get') + 'Attribute'; 1802 | tag.prototype[prop].get = function(){ 1803 | return this[method](name); 1804 | }; 1805 | } 1806 | if (!tag.prototype[prop].set) tag.prototype[prop].set = function(value){ 1807 | this.xtag._skipAttr = true; 1808 | setAttr.call(this, attr, name, value); 1809 | updateTemplate(this, name, value); 1810 | }; 1811 | } 1812 | } 1813 | 1814 | /*** X-Tag Object Definition ***/ 1815 | 1816 | var xtag = { 1817 | tags: {}, 1818 | defaultOptions: { 1819 | pseudos: [], 1820 | mixins: [], 1821 | events: {}, 1822 | methods: {}, 1823 | accessors: { 1824 | template: { 1825 | attribute: {}, 1826 | set: function(value){ 1827 | var last = this.getAttribute('template'); 1828 | this.xtag.__previousTemplate__ = last; 1829 | xtag.fireEvent(this, 'templatechange', { template: value }); 1830 | } 1831 | } 1832 | }, 1833 | lifecycle: {}, 1834 | attributes: {}, 1835 | 'prototype': { 1836 | xtag: { 1837 | get: function(){ 1838 | return this.__xtag__ ? this.__xtag__ : (this.__xtag__ = { data: {} }); 1839 | } 1840 | } 1841 | } 1842 | }, 1843 | register: function (name, options) { 1844 | var element, _name; 1845 | if (typeof name == 'string') { 1846 | _name = name.toLowerCase(); 1847 | } else if (name.nodeName == 'ELEMENT') { 1848 | element = name; 1849 | _name = element.getAttribute('name').toLowerCase(); 1850 | } else { 1851 | return; 1852 | } 1853 | 1854 | var tag = xtag.tags[_name] = applyMixins(xtag.merge({}, xtag.defaultOptions, options)); 1855 | 1856 | for (var z in tag.events) tag.events[z] = xtag.parseEvent(z, tag.events[z]); 1857 | for (z in tag.lifecycle) tag.lifecycle[z.split(':')[0]] = xtag.applyPseudos(z, tag.lifecycle[z], tag.pseudos); 1858 | for (z in tag.methods) tag.prototype[z.split(':')[0]] = { value: xtag.applyPseudos(z, tag.methods[z], tag.pseudos), enumerable: true }; 1859 | for (z in tag.accessors) parseAccessor(tag, z); 1860 | 1861 | var ready = tag.lifecycle.created || tag.lifecycle.ready; 1862 | tag.prototype.readyCallback = { 1863 | enumerable: true, 1864 | value: function(){ 1865 | var element = this; 1866 | var template = element.getAttribute('template'); 1867 | if (template){ 1868 | xtag.fireEvent(this, 'templatechange', { template: template }); 1869 | } 1870 | xtag.addEvents(this, tag.events); 1871 | tag.mixins.forEach(function(mixin){ 1872 | if (xtag.mixins[mixin].events) xtag.addEvents(element, xtag.mixins[mixin].events); 1873 | }); 1874 | var output = ready ? ready.apply(this, toArray(arguments)) : null; 1875 | for (var name in tag.attributes) { 1876 | var attr = tag.attributes[name], 1877 | hasAttr = this.hasAttribute(name), 1878 | value = attr.boolean ? hasAttr : this.getAttribute(name); 1879 | if (attr.boolean || hasAttr) { 1880 | syncAttr.call(this, attr, name, value); 1881 | if (attr.setter) attr.setter.call(this, value, true); 1882 | } 1883 | } 1884 | tag.pseudos.forEach(function(obj){ 1885 | obj.onAdd.call(element, obj); 1886 | }); 1887 | return output; 1888 | } 1889 | }; 1890 | 1891 | if (tag.lifecycle.inserted) tag.prototype.insertedCallback = { value: tag.lifecycle.inserted, enumerable: true }; 1892 | if (tag.lifecycle.removed) tag.prototype.removedCallback = { value: tag.lifecycle.removed, enumerable: true }; 1893 | if (tag.lifecycle.attributeChanged) tag.prototype.attributeChangedCallback = { value: tag.lifecycle.attributeChanged, enumerable: true }; 1894 | 1895 | wrapAttr(tag, 'setAttribute'); 1896 | wrapAttr(tag, 'removeAttribute'); 1897 | 1898 | if (element){ 1899 | element.register({ 1900 | 'prototype': Object.create(Object.prototype, tag.prototype) 1901 | }); 1902 | } else { 1903 | return doc.register(_name, { 1904 | 'extends': options['extends'], 1905 | 'prototype': Object.create(Object.create((options['extends'] ? 1906 | document.createElement(options['extends']).constructor : 1907 | win.HTMLElement).prototype, tag.prototype), tag.prototype) 1908 | }); 1909 | } 1910 | }, 1911 | 1912 | /* Exposed Variables */ 1913 | 1914 | mixins: {}, 1915 | prefix: prefix, 1916 | templates: {}, 1917 | captureEvents: ['focus', 'blur'], 1918 | customEvents: { 1919 | overflow: createFlowEvent('over'), 1920 | underflow: createFlowEvent('under'), 1921 | animationstart: { 1922 | base: [ 1923 | 'animationstart', 1924 | 'oAnimationStart', 1925 | 'MSAnimationStart', 1926 | 'webkitAnimationStart' 1927 | ] 1928 | }, 1929 | transitionend: { 1930 | base: [ 1931 | 'transitionend', 1932 | 'oTransitionEnd', 1933 | 'MSTransitionEnd', 1934 | 'webkitTransitionEnd' 1935 | ] 1936 | }, 1937 | tap: { 1938 | base: ['click', 'touchend'], 1939 | condition: touchFilter 1940 | }, 1941 | tapstart: { 1942 | base: ['mousedown', 'touchstart'], 1943 | condition: touchFilter 1944 | }, 1945 | tapend: { 1946 | base: ['mouseup', 'touchend'], 1947 | condition: touchFilter 1948 | }, 1949 | tapenter: { 1950 | base: ['mouseover', 'touchenter'], 1951 | condition: touchFilter 1952 | }, 1953 | tapleave: { 1954 | base: ['mouseout', 'touchleave'], 1955 | condition: touchFilter 1956 | }, 1957 | tapmove: { 1958 | base: ['mousemove', 'touchmove'], 1959 | condition: touchFilter 1960 | } 1961 | }, 1962 | pseudos: { 1963 | keypass: keypseudo, 1964 | keyfail: keypseudo, 1965 | delegate: { 1966 | action: function (pseudo, event) { 1967 | var target = query(this, pseudo.value).filter(function (node) { 1968 | return node == event.target || node.contains ? node.contains(event.target) : false; 1969 | })[0]; 1970 | return target ? pseudo.listener = pseudo.listener.bind(target) : false; 1971 | } 1972 | }, 1973 | preventable: { 1974 | action: function (pseudo, event) { 1975 | return !event.defaultPrevented; 1976 | } 1977 | } 1978 | }, 1979 | 1980 | /* UTILITIES */ 1981 | 1982 | clone: clone, 1983 | typeOf: typeOf, 1984 | toArray: toArray, 1985 | 1986 | wrap: function (original, fn) { 1987 | return function(){ 1988 | var args = toArray(arguments), 1989 | returned = original.apply(this, args); 1990 | return returned === false ? false : fn.apply(this, typeof returned != 'undefined' ? toArray(returned) : args); 1991 | }; 1992 | }, 1993 | 1994 | merge: function(source, k, v){ 1995 | if (typeOf(k) == 'string') return mergeOne(source, k, v); 1996 | for (var i = 1, l = arguments.length; i < l; i++){ 1997 | var object = arguments[i]; 1998 | for (var key in object) mergeOne(source, key, object[key]); 1999 | } 2000 | return source; 2001 | }, 2002 | 2003 | /* DOM */ 2004 | 2005 | query: query, 2006 | 2007 | skipTransition: function(element, fn, bind){ 2008 | var prop = prefix.js + 'TransitionProperty'; 2009 | element.style[prop] = element.style.transitionProperty = 'none'; 2010 | xtag.requestFrame(function(){ 2011 | var callback; 2012 | if (fn) callback = fn.call(bind); 2013 | xtag.requestFrame(function(){ 2014 | element.style[prop] = element.style.transitionProperty = ''; 2015 | if (callback) xtag.requestFrame(callback); 2016 | }); 2017 | }); 2018 | }, 2019 | 2020 | requestFrame: (function(){ 2021 | var raf = win.requestAnimationFrame || 2022 | win[prefix.lowercase + 'RequestAnimationFrame'] || 2023 | function(fn){ return win.setTimeout(fn, 20); }; 2024 | return function(fn){ 2025 | return raf.call(win, fn); 2026 | }; 2027 | })(), 2028 | 2029 | matchSelector: function (element, selector) { 2030 | return matchSelector.call(element, selector); 2031 | }, 2032 | 2033 | set: function (element, method, value) { 2034 | element[method] = value; 2035 | if (window.CustomElements) CustomElements.upgradeAll(element); 2036 | }, 2037 | 2038 | innerHTML: function(el, html){ 2039 | xtag.set(el, 'innerHTML', html); 2040 | }, 2041 | 2042 | hasClass: function (element, klass) { 2043 | return element.className.split(' ').indexOf(klass.trim())>-1; 2044 | }, 2045 | 2046 | addClass: function (element, klass) { 2047 | var list = element.className.trim().split(' '); 2048 | klass.trim().split(' ').forEach(function (name) { 2049 | if (!~list.indexOf(name)) list.push(name); 2050 | }); 2051 | element.className = list.join(' ').trim(); 2052 | return element; 2053 | }, 2054 | 2055 | removeClass: function (element, klass) { 2056 | var classes = klass.trim().split(' '); 2057 | element.className = element.className.trim().split(' ').filter(function (name) { 2058 | return name && !~classes.indexOf(name); 2059 | }).join(' '); 2060 | return element; 2061 | }, 2062 | 2063 | toggleClass: function (element, klass) { 2064 | return xtag[xtag.hasClass(element, klass) ? 'removeClass' : 'addClass'].call(null, element, klass); 2065 | 2066 | }, 2067 | 2068 | queryChildren: function (element, selector) { 2069 | var id = element.id, 2070 | guid = element.id = id || 'x_' + new Date().getTime(), 2071 | attr = '#' + guid + ' > '; 2072 | selector = attr + (selector + '').replace(',', ',' + attr, 'g'); 2073 | var result = element.parentNode.querySelectorAll(selector); 2074 | if (!id) element.removeAttribute('id'); 2075 | return toArray(result); 2076 | }, 2077 | 2078 | createFragment: function(content) { 2079 | var frag = doc.createDocumentFragment(); 2080 | if (content) { 2081 | var div = frag.appendChild(doc.createElement('div')), 2082 | nodes = toArray(content.nodeName ? arguments : !(div.innerHTML = content) || div.children), 2083 | length = nodes.length, 2084 | index = 0; 2085 | while (index < length) frag.insertBefore(nodes[index++], div); 2086 | frag.removeChild(div); 2087 | } 2088 | return frag; 2089 | }, 2090 | 2091 | manipulate: function(element, fn){ 2092 | var next = element.nextSibling, 2093 | parent = element.parentNode, 2094 | frag = doc.createDocumentFragment(), 2095 | returned = fn.call(frag.appendChild(element), frag) || element; 2096 | if (next) parent.insertBefore(returned, next); 2097 | else parent.appendChild(returned); 2098 | }, 2099 | 2100 | /* PSEUDOS */ 2101 | 2102 | applyPseudos: function(key, fn, element) { 2103 | var listener = fn, 2104 | pseudos = {}; 2105 | if (key.match(':')) { 2106 | var split = key.match(regexPseudoSplit), 2107 | i = split.length; 2108 | while (--i) { 2109 | split[i].replace(regexPseudoReplace, function (match, name, value) { 2110 | if (!xtag.pseudos[name]) throw "pseudo not found: " + name + " " + split; 2111 | var pseudo = pseudos[i] = Object.create(xtag.pseudos[name]); 2112 | pseudo.key = key; 2113 | pseudo.name = name; 2114 | pseudo.value = value; 2115 | var last = listener; 2116 | listener = function(){ 2117 | var args = toArray(arguments), 2118 | obj = { 2119 | key: key, 2120 | name: name, 2121 | value: value, 2122 | listener: last 2123 | }; 2124 | if (pseudo.action && pseudo.action.apply(this, [obj].concat(args)) === false) return false; 2125 | return obj.listener.apply(this, args); 2126 | }; 2127 | if (element && pseudo.onAdd) { 2128 | if (element.getAttribute) { 2129 | pseudo.onAdd.call(element, pseudo); 2130 | } else { 2131 | element.push(pseudo); 2132 | } 2133 | } 2134 | }); 2135 | } 2136 | } 2137 | for (var z in pseudos) { 2138 | if (pseudos[z].onCompiled) listener = pseudos[z].onCompiled(listener, pseudos[z]); 2139 | } 2140 | return listener; 2141 | }, 2142 | 2143 | removePseudos: function(element, event){ 2144 | event._pseudos.forEach(function(obj){ 2145 | obj.onRemove.call(element, obj); 2146 | }); 2147 | }, 2148 | 2149 | /*** Events ***/ 2150 | 2151 | parseEvent: function(type, fn) { 2152 | var pseudos = type.split(':'), 2153 | key = pseudos.shift(), 2154 | event = xtag.merge({ 2155 | base: key, 2156 | pseudos: '', 2157 | _pseudos: [], 2158 | onAdd: noop, 2159 | onRemove: noop, 2160 | condition: noop 2161 | }, xtag.customEvents[key] || {}); 2162 | event.type = key + (event.pseudos.length ? ':' + event.pseudos : '') + (pseudos.length ? ':' + pseudos.join(':') : ''); 2163 | if (fn) { 2164 | var chained = xtag.applyPseudos(event.type, fn, event._pseudos); 2165 | event.listener = function(){ 2166 | var args = toArray(arguments); 2167 | if (event.condition.apply(this, [event].concat(args)) === false) return false; 2168 | return chained.apply(this, args); 2169 | }; 2170 | } 2171 | return event; 2172 | }, 2173 | 2174 | addEvent: function (element, type, fn) { 2175 | var event = (typeof fn == 'function') ? xtag.parseEvent(type, fn) : fn; 2176 | event.listener.event = event; 2177 | event._pseudos.forEach(function(obj){ 2178 | obj.onAdd.call(element, obj); 2179 | }); 2180 | event.onAdd.call(element, event, event.listener); 2181 | toArray(event.base).forEach(function (name) { 2182 | element.addEventListener(name, event.listener, xtag.captureEvents.indexOf(name) > -1); 2183 | }); 2184 | return event.listener; 2185 | }, 2186 | 2187 | addEvents: function (element, events) { 2188 | var listeners = {}; 2189 | for (var z in events) { 2190 | listeners[z] = xtag.addEvent(element, z, events[z]); 2191 | } 2192 | return listeners; 2193 | }, 2194 | 2195 | removeEvent: function (element, type, fn) { 2196 | var event = fn.event; 2197 | event.onRemove.call(element, event, fn); 2198 | xtag.removePseudos(element, event); 2199 | toArray(event.base).forEach(function (name) { 2200 | element.removeEventListener(name, fn); 2201 | }); 2202 | }, 2203 | 2204 | removeEvents: function(element, listeners){ 2205 | for (var z in listeners) xtag.removeEvent(element, z, listeners[z]); 2206 | }, 2207 | 2208 | fireEvent: function(element, type, data, options){ 2209 | options = options || {}; 2210 | var event = doc.createEvent('Event'); 2211 | event.initEvent(type, 'bubbles' in options ? options.bubbles : true, 'cancelable' in options ? options.cancelable : true); 2212 | for (var z in data) event[z] = data[z]; 2213 | element.dispatchEvent(event); 2214 | }, 2215 | 2216 | addObserver: function(element, type, fn){ 2217 | if (!element._records) { 2218 | element._records = { inserted: [], removed: [] }; 2219 | if (mutation){ 2220 | element._observer = new mutation(function(mutations) { 2221 | parseMutations(element, mutations); 2222 | }); 2223 | element._observer.observe(element, { 2224 | subtree: true, 2225 | childList: true, 2226 | attributes: !true, 2227 | characterData: false 2228 | }); 2229 | } 2230 | else ['Inserted', 'Removed'].forEach(function(type){ 2231 | element.addEventListener('DOMNode' + type, function(event){ 2232 | event._mutation = true; 2233 | element._records[type.toLowerCase()].forEach(function(fn){ 2234 | fn(event.target, event); 2235 | }); 2236 | }, false); 2237 | }); 2238 | } 2239 | if (element._records[type].indexOf(fn) == -1) element._records[type].push(fn); 2240 | }, 2241 | 2242 | removeObserver: function(element, type, fn){ 2243 | var obj = element._records; 2244 | if (obj && fn){ 2245 | obj[type].splice(obj[type].indexOf(fn), 1); 2246 | } 2247 | else{ 2248 | obj[type] = []; 2249 | } 2250 | } 2251 | 2252 | }; 2253 | 2254 | if (typeof define == 'function' && define.amd) define(xtag); 2255 | else win.xtag = xtag; 2256 | 2257 | doc.addEventListener('WebComponentsReady', function(){ 2258 | xtag.fireEvent(doc.body, 'DOMComponentsLoaded'); 2259 | }); 2260 | 2261 | })(); --------------------------------------------------------------------------------