├── 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 | 
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 |
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 | })();
--------------------------------------------------------------------------------