Index: third_party/polymer/v0_8/components-chromium/polymer/polymer.js |
diff --git a/third_party/polymer/v0_8/components-chromium/polymer/polymer.js b/third_party/polymer/v0_8/components-chromium/polymer/polymer.js |
deleted file mode 100644 |
index 7a400f307a6e1785fdca7600a0fab86a4c13b95a..0000000000000000000000000000000000000000 |
--- a/third_party/polymer/v0_8/components-chromium/polymer/polymer.js |
+++ /dev/null |
@@ -1,6840 +0,0 @@ |
- |
- |
- Polymer = { |
- Settings: (function() { |
- // NOTE: Users must currently opt into using ShadowDOM. They do so by doing: |
- // Polymer = {dom: 'shadow'}; |
- // TODO(sorvell): Decide if this should be auto-use when available. |
- // TODO(sorvell): if SD is auto-use, then the flag above should be something |
- // like: Polymer = {dom: 'shady'} |
- |
- // via Polymer object |
- var user = window.Polymer || {}; |
- |
- // via url |
- location.search.slice(1).split('&').forEach(function(o) { |
- o = o.split('='); |
- o[0] && (user[o[0]] = o[1] || true); |
- }); |
- |
- var wantShadow = (user.dom === 'shadow'); |
- var hasShadow = Boolean(Element.prototype.createShadowRoot); |
- var nativeShadow = hasShadow && !window.ShadowDOMPolyfill; |
- var useShadow = wantShadow && hasShadow; |
- |
- var hasNativeImports = Boolean('import' in document.createElement('link')); |
- var useNativeImports = hasNativeImports; |
- |
- var useNativeCustomElements = (!window.CustomElements || |
- window.CustomElements.useNative); |
- |
- return { |
- wantShadow: wantShadow, |
- hasShadow: hasShadow, |
- nativeShadow: nativeShadow, |
- useShadow: useShadow, |
- useNativeShadow: useShadow && nativeShadow, |
- useNativeImports: useNativeImports, |
- useNativeCustomElements: useNativeCustomElements |
- }; |
- })() |
- }; |
- |
- |
-; |
- |
- // until ES6 modules become standard, we follow Occam and simply stake out |
- // a global namespace |
- |
- // Polymer is a Function, but of course this is also an Object, so we |
- // hang various other objects off of Polymer.* |
- (function() { |
- var userPolymer = window.Polymer; |
- |
- window.Polymer = function(prototype) { |
- var ctor = desugar(prototype); |
- // native Custom Elements treats 'undefined' extends property |
- // as valued, the property must not exist to be ignored |
- var options = { |
- prototype: ctor.prototype |
- }; |
- if (prototype.extends) { |
- options.extends = prototype.extends; |
- } |
- Polymer.telemetry._registrate(prototype); |
- document.registerElement(prototype.is, options); |
- return ctor; |
- }; |
- |
- var desugar = function(prototype) { |
- prototype = Polymer.Base.chainObject(prototype, Polymer.Base); |
- prototype.registerCallback(); |
- return prototype.constructor; |
- }; |
- |
- window.Polymer = Polymer; |
- |
- if (userPolymer) { |
- for (var i in userPolymer) { |
- Polymer[i] = userPolymer[i]; |
- } |
- } |
- |
- Polymer.Class = desugar; |
- |
- })(); |
- /* |
- // Raw usage |
- [ctor =] Polymer.Class(prototype); |
- document.registerElement(name, ctor); |
- |
- // Simplified usage |
- [ctor = ] Polymer(prototype); |
- */ |
- |
- // telemetry: statistics, logging, and debug |
- |
- Polymer.telemetry = { |
- registrations: [], |
- _regLog: function(prototype) { |
- console.log('[' + prototype.is + ']: registered') |
- }, |
- _registrate: function(prototype) { |
- this.registrations.push(prototype); |
- Polymer.log && this._regLog(prototype); |
- }, |
- dumpRegistrations: function() { |
- this.registrations.forEach(this._regLog); |
- } |
- }; |
- |
- |
-; |
- |
- // a tiny bit of sugar for `document.currentScript.ownerDocument` |
- Object.defineProperty(window, 'currentImport', { |
- enumerable: true, |
- configurable: true, |
- get: function() { |
- return (document._currentScript || document.currentScript).ownerDocument; |
- } |
- }); |
- |
- |
-; |
- |
- Polymer.Base = { |
- |
- // pluggable features |
- // `this` context is a prototype, not an instance |
- _addFeature: function(feature) { |
- this.extend(this, feature); |
- }, |
- |
- // `this` context is a prototype, not an instance |
- registerCallback: function() { |
- this._registerFeatures(); // abstract |
- this._doBehavior('registered'); // abstract |
- }, |
- |
- createdCallback: function() { |
- Polymer.telemetry.instanceCount++; |
- this.root = this; |
- this._doBehavior('created'); // abstract |
- this._initFeatures(); // abstract |
- }, |
- |
- // reserved for canonical behavior |
- attachedCallback: function() { |
- this.isAttached = true; |
- this._doBehavior('attached'); // abstract |
- }, |
- |
- // reserved for canonical behavior |
- detachedCallback: function() { |
- this.isAttached = false; |
- this._doBehavior('detached'); // abstract |
- }, |
- |
- // reserved for canonical behavior |
- attributeChangedCallback: function(name) { |
- this.setAttributeToProperty(this, name); |
- this._doBehavior('attributeChanged', arguments); // abstract |
- }, |
- |
- // copy own properties from `api` to `prototype` |
- extend: function(prototype, api) { |
- if (prototype && api) { |
- Object.getOwnPropertyNames(api).forEach(function(n) { |
- this.copyOwnProperty(n, api, prototype); |
- }, this); |
- } |
- return prototype || api; |
- }, |
- |
- copyOwnProperty: function(name, source, target) { |
- var pd = Object.getOwnPropertyDescriptor(source, name); |
- if (pd) { |
- Object.defineProperty(target, name, pd); |
- } |
- } |
- |
- }; |
- |
- if (Object.__proto__) { |
- Polymer.Base.chainObject = function(object, inherited) { |
- if (object && inherited && object !== inherited) { |
- object.__proto__ = inherited; |
- } |
- return object; |
- }; |
- } else { |
- Polymer.Base.chainObject = function(object, inherited) { |
- if (object && inherited && object !== inherited) { |
- var chained = Object.create(inherited); |
- object = Polymer.Base.extend(chained, object); |
- } |
- return object; |
- }; |
- } |
- |
- Polymer.Base = Polymer.Base.chainObject(Polymer.Base, HTMLElement.prototype); |
- |
- // TODO(sjmiles): ad hoc telemetry |
- Polymer.telemetry.instanceCount = 0; |
- |
- |
-; |
- |
-(function() { |
- |
- var modules = {}; |
- |
- var DomModule = function() { |
- return document.createElement('dom-module'); |
- }; |
- |
- DomModule.prototype = Object.create(HTMLElement.prototype); |
- |
- DomModule.prototype.constructor = DomModule; |
- |
- DomModule.prototype.createdCallback = function() { |
- var id = this.id || this.getAttribute('name') || this.getAttribute('is'); |
- if (id) { |
- this.id = id; |
- modules[id] = this; |
- } |
- }; |
- |
- DomModule.prototype.import = function(id, slctr) { |
- var m = modules[id]; |
- if (!m) { |
- // If polyfilling, a script can run before a dom-module element |
- // is upgraded. We force the containing document to upgrade |
- // and try again to workaround this polyfill limitation. |
- forceDocumentUpgrade(); |
- m = modules[id]; |
- } |
- if (m && slctr) { |
- m = m.querySelector(slctr); |
- } |
- return m; |
- }; |
- |
- // NOTE: HTMLImports polyfill does not |
- // block scripts on upgrading elements. However, we want to ensure that |
- // any dom-module in the tree is available prior to a subsequent script |
- // processing. |
- // Therefore, we force any dom-modules in the tree to upgrade when dom-module |
- // is registered by temporarily setting CE polyfill to crawl the entire |
- // imports tree. (Note: this should only upgrade any imports that have been |
- // loaded by this point. In addition the HTMLImports polyfill should be |
- // changed to upgrade elements prior to running any scripts.) |
- var cePolyfill = window.CustomElements && !CustomElements.useNative; |
- if (cePolyfill) { |
- var ready = CustomElements.ready; |
- CustomElements.ready = true; |
- } |
- document.registerElement('dom-module', DomModule); |
- if (cePolyfill) { |
- CustomElements.ready = ready; |
- } |
- |
- function forceDocumentUpgrade() { |
- if (cePolyfill) { |
- var script = document._currentScript || document.currentScript; |
- if (script) { |
- CustomElements.upgradeAll(script.ownerDocument); |
- } |
- } |
- } |
- |
-})(); |
- |
- |
-; |
- |
- Polymer.Base._addFeature({ |
- |
- _prepIs: function() { |
- if (!this.is) { |
- var module = |
- (document._currentScript || document.currentScript).parentNode; |
- if (module.localName === 'dom-module') { |
- var id = module.id || module.getAttribute('name') |
- || module.getAttribute('is') |
- this.is = id; |
- } |
- } |
- } |
- |
- }); |
- |
- |
-; |
- |
- /** |
- * Automatically extend using objects referenced in `behaviors` array. |
- * |
- * someBehaviorObject = { |
- * accessors: { |
- * value: {type: Number, observer: '_numberChanged'} |
- * }, |
- * observers: [ |
- * // ... |
- * ], |
- * ready: function() { |
- * // called before prototoype's ready |
- * }, |
- * _numberChanged: function() {} |
- * }; |
- * |
- * Polymer({ |
- * |
- * behaviors: [ |
- * someBehaviorObject |
- * ] |
- * |
- * ... |
- * |
- * }); |
- * |
- * @class base feature: behaviors |
- */ |
- |
- Polymer.Base._addFeature({ |
- |
- behaviors: [], |
- |
- _prepBehaviors: function() { |
- this._flattenBehaviors(); |
- this._prepBehavior(this); |
- this.behaviors.forEach(function(b) { |
- this._mixinBehavior(b); |
- this._prepBehavior(b); |
- }, this); |
- }, |
- |
- _flattenBehaviors: function() { |
- var flat = []; |
- this.behaviors.forEach(function(b) { |
- if (!b) { |
- console.warn('Polymer: undefined behavior in [' + this.is + ']'); |
- } else if (b instanceof Array) { |
- flat = flat.concat(b); |
- } else { |
- flat.push(b); |
- } |
- }, this); |
- this.behaviors = flat; |
- }, |
- |
- _mixinBehavior: function(b) { |
- Object.getOwnPropertyNames(b).forEach(function(n) { |
- switch (n) { |
- case 'registered': |
- case 'properties': |
- case 'observers': |
- case 'listeners': |
- case 'keyPresses': |
- case 'hostAttributes': |
- case 'created': |
- case 'attached': |
- case 'detached': |
- case 'attributeChanged': |
- case 'configure': |
- case 'ready': |
- break; |
- default: |
- this.copyOwnProperty(n, b, this); |
- break; |
- } |
- }, this); |
- }, |
- |
- _doBehavior: function(name, args) { |
- this.behaviors.forEach(function(b) { |
- this._invokeBehavior(b, name, args); |
- }, this); |
- this._invokeBehavior(this, name, args); |
- }, |
- |
- _invokeBehavior: function(b, name, args) { |
- var fn = b[name]; |
- if (fn) { |
- fn.apply(this, args || Polymer.nar); |
- } |
- }, |
- |
- _marshalBehaviors: function() { |
- this.behaviors.forEach(function(b) { |
- this._marshalBehavior(b); |
- }, this); |
- this._marshalBehavior(this); |
- } |
- |
- }); |
- |
- |
-; |
- |
- /** |
- * Support `extends` property (for type-extension only). |
- * |
- * If the mixin is String-valued, the corresponding Polymer module |
- * is mixed in. |
- * |
- * Polymer({ |
- * is: 'pro-input', |
- * extends: 'input', |
- * ... |
- * }); |
- * |
- * Type-extension objects are created using `is` notation in HTML, or via |
- * the secondary argument to `document.createElement` (the type-extension |
- * rules are part of the Custom Elements specification, not something |
- * created by Polymer). |
- * |
- * Example: |
- * |
- * <!-- right: creates a pro-input element --> |
- * <input is="pro-input"> |
- * |
- * <!-- wrong: creates an unknown element --> |
- * <pro-input> |
- * |
- * <script> |
- * // right: creates a pro-input element |
- * var elt = document.createElement('input', 'pro-input'); |
- * |
- * // wrong: creates an unknown element |
- * var elt = document.createElement('pro-input'); |
- * <\script> |
- * |
- * @class base feature: extends |
- */ |
- |
- Polymer.Base._addFeature({ |
- |
- _prepExtends: function() { |
- if (this.extends) { |
- this.__proto__ = this.getExtendedPrototype(this.extends); |
- } |
- }, |
- |
- getExtendedPrototype: function(tag) { |
- return this.getExtendedNativePrototype(tag); |
- }, |
- |
- nativePrototypes: {}, // static |
- |
- getExtendedNativePrototype: function(tag) { |
- var p = this.nativePrototypes[tag]; |
- if (!p) { |
- var np = this.getNativePrototype(tag); |
- p = this.extend(Object.create(np), Polymer.Base); |
- this.nativePrototypes[tag] = p; |
- } |
- return p; |
- }, |
- |
- getNativePrototype: function(tag) { |
- // TODO(sjmiles): sad necessity |
- return Object.getPrototypeOf(document.createElement(tag)); |
- } |
- |
- }); |
- |
- |
-; |
- |
- /** |
- * Generates a boilerplate constructor. |
- * |
- * XFoo = Polymer({ |
- * is: 'x-foo' |
- * }); |
- * ASSERT(new XFoo() instanceof XFoo); |
- * |
- * You can supply a custom constructor on the prototype. But remember that |
- * this constructor will only run if invoked **manually**. Elements created |
- * via `document.createElement` or from HTML _will not invoke this method_. |
- * |
- * Instead, we reuse the concept of `constructor` for a factory method which |
- * can take arguments. |
- * |
- * MyFoo = Polymer({ |
- * is: 'my-foo', |
- * constructor: function(foo) { |
- * this.foo = foo; |
- * } |
- * ... |
- * }); |
- * |
- * @class base feature: constructor |
- */ |
- |
- Polymer.Base._addFeature({ |
- |
- // registration-time |
- |
- _prepConstructor: function() { |
- // support both possible `createElement` signatures |
- this._factoryArgs = this.extends ? [this.extends, this.is] : [this.is]; |
- // thunk the constructor to delegate allocation to `createElement` |
- var ctor = function() { |
- return this._factory(arguments); |
- }; |
- if (this.hasOwnProperty('extends')) { |
- ctor.extends = this.extends; |
- } |
- // ensure constructor is set. The `constructor` property is |
- // not writable on Safari; note: Chrome requires the property |
- // to be configurable. |
- Object.defineProperty(this, 'constructor', {value: ctor, |
- writable: true, configurable: true}); |
- ctor.prototype = this; |
- }, |
- |
- _factory: function(args) { |
- var elt = document.createElement.apply(document, this._factoryArgs); |
- if (this.factoryImpl) { |
- this.factoryImpl.apply(elt, args); |
- } |
- return elt; |
- } |
- |
- }); |
- |
- |
-; |
- |
- /** |
- * Define property metadata. |
- * |
- * properties: { |
- * <property>: <Type || Object>, |
- * ... |
- * } |
- * |
- * Example: |
- * |
- * properties: { |
- * // `foo` property can be assigned via attribute, will be deserialized to |
- * // the specified data-type. All `properties` properties have this behavior. |
- * foo: String, |
- * |
- * // `bar` property has additional behavior specifiers. |
- * // type: as above, type for (de-)serialization |
- * // notify: true to send a signal when a value is set to this property |
- * // reflectToAttribute: true to serialize the property to an attribute |
- * // readOnly: if true, the property has no setter |
- * bar: { |
- * type: Boolean, |
- * notify: true |
- * } |
- * } |
- * |
- * By itself the properties feature doesn't do anything but provide property |
- * information. Other features use this information to control behavior. |
- * |
- * The `type` information is used by the `attributes` feature to convert |
- * String values in attributes to typed properties. The `bind` feature uses |
- * property information to control property access. |
- * |
- * Marking a property as `notify` causes a change in the property to |
- * fire a non-bubbling event called `<property>-changed`. Elements that |
- * have enabled two-way binding to the property use this event to |
- * observe changes. |
- * |
- * `readOnly` properties have a getter, but no setter. To set a read-only |
- * property, use the private setter method `_set_<property>(value)`. |
- * |
- * @class base feature: properties |
- */ |
- |
- // null object |
- Polymer.nob = Object.create(null); |
- |
- Polymer.Base._addFeature({ |
- |
- properties: { |
- }, |
- |
- getPropertyInfo: function(property) { |
- var info = this._getPropertyInfo(property, this.properties); |
- if (!info) { |
- this.behaviors.some(function(b) { |
- return info = this._getPropertyInfo(property, b.properties); |
- }, this); |
- } |
- return info || Polymer.nob; |
- }, |
- |
- _getPropertyInfo: function(property, properties) { |
- var p = properties && properties[property]; |
- if (typeof(p) === 'function') { |
- p = properties[property] = { |
- type: p |
- }; |
- } |
- return p; |
- }, |
- |
- getPropertyType: function(property) { |
- return this.getPropertyInfo(property).type; |
- }, |
- |
- isReadOnlyProperty: function(property) { |
- return this.getPropertyInfo(property).readOnly; |
- }, |
- |
- isNotifyProperty: function(property) { |
- return this.getPropertyInfo(property).notify; |
- }, |
- |
- isReflectedProperty: function(property) { |
- return this.getPropertyInfo(property).reflectToAttribute; |
- } |
- |
- }); |
- |
- |
-; |
- |
- Polymer.CaseMap = { |
- |
- _caseMap: {}, |
- |
- dashToCamelCase: function(dash) { |
- var mapped = Polymer.CaseMap._caseMap[dash]; |
- if (mapped) { |
- return mapped; |
- } |
- // TODO(sjmiles): is rejection test actually helping perf? |
- if (dash.indexOf('-') < 0) { |
- return Polymer.CaseMap._caseMap[dash] = dash; |
- } |
- return Polymer.CaseMap._caseMap[dash] = dash.replace(/-([a-z])/g, |
- function(m) { |
- return m[1].toUpperCase(); |
- } |
- ); |
- }, |
- |
- camelToDashCase: function(camel) { |
- var mapped = Polymer.CaseMap._caseMap[camel]; |
- if (mapped) { |
- return mapped; |
- } |
- return Polymer.CaseMap._caseMap[camel] = camel.replace(/([a-z][A-Z])/g, |
- function (g) { |
- return g[0] + '-' + g[1].toLowerCase() |
- } |
- ); |
- } |
- |
- }; |
- |
- |
-; |
- |
- /** |
- * Support for `hostAttributes` property. |
- * |
- * hostAttributes: 'block vertical layout' |
- * |
- * `hostAttributes` is a space-delimited string of boolean attribute names to |
- * set true on each instance. |
- * |
- * Support for mapping attributes to properties. |
- * |
- * Properties that are configured in `properties` with a type are mapped |
- * to attributes. |
- * |
- * A value set in an attribute is deserialized into the specified |
- * data-type and stored into the matching property. |
- * |
- * Example: |
- * |
- * properties: { |
- * // values set to index attribute are converted to Number and propagated |
- * // to index property |
- * index: Number, |
- * // values set to label attribute are propagated to index property |
- * label: String |
- * } |
- * |
- * Types supported for deserialization: |
- * |
- * - Number |
- * - Boolean |
- * - String |
- * - Object (JSON) |
- * - Array (JSON) |
- * - Date |
- * |
- * This feature implements `attributeChanged` to support automatic |
- * propagation of attribute values at run-time. If you override |
- * `attributeChanged` be sure to call this base class method |
- * if you also want the standard behavior. |
- * |
- * @class base feature: attributes |
- */ |
- |
- Polymer.Base._addFeature({ |
- |
- _marshalAttributes: function() { |
- this._takeAttributes(); |
- }, |
- |
- _installHostAttributes: function(attributes) { |
- if (attributes) { |
- this.applyAttributes(this, attributes); |
- } |
- }, |
- |
- applyAttributes: function(node, attr$) { |
- for (var n in attr$) { |
- this.serializeValueToAttribute(attr$[n], n, this); |
- } |
- }, |
- |
- _takeAttributes: function() { |
- this._takeAttributesToModel(this); |
- }, |
- |
- _takeAttributesToModel: function(model) { |
- for (var i=0, l=this.attributes.length; i<l; i++) { |
- var a = this.attributes[i]; |
- var property = Polymer.CaseMap.dashToCamelCase(a.name); |
- var info = this.getPropertyInfo(property); |
- if (info || this._propertyEffects[property]) { |
- model[property] = |
- this.deserialize(a.value, info.type); |
- } |
- } |
- }, |
- |
- setAttributeToProperty: function(model, attrName) { |
- // Don't deserialize back to property if currently reflecting |
- if (!this._serializing) { |
- var propName = Polymer.CaseMap.dashToCamelCase(attrName); |
- if (propName in this.properties) { |
- var type = this.getPropertyType(propName); |
- var val = this.getAttribute(attrName); |
- model[propName] = this.deserialize(val, type); |
- } |
- } |
- }, |
- |
- _serializing: false, |
- reflectPropertyToAttribute: function(name) { |
- this._serializing = true; |
- this.serializeValueToAttribute(this[name], |
- Polymer.CaseMap.camelToDashCase(name)); |
- this._serializing = false; |
- }, |
- |
- serializeValueToAttribute: function(value, attribute, node) { |
- var str = this.serialize(value); |
- (node || this) |
- [str === undefined ? 'removeAttribute' : 'setAttribute'] |
- (attribute, str); |
- }, |
- |
- deserialize: function(value, type) { |
- switch (type) { |
- case Number: |
- value = Number(value); |
- break; |
- |
- case Boolean: |
- value = (value !== null); |
- break; |
- |
- case Object: |
- try { |
- value = JSON.parse(value); |
- } catch(x) { |
- // allow non-JSON literals like Strings and Numbers |
- } |
- break; |
- |
- case Array: |
- try { |
- value = JSON.parse(value); |
- } catch(x) { |
- value = null; |
- console.warn('Polymer::Attributes: couldn`t decode Array as JSON'); |
- } |
- break; |
- |
- case Date: |
- value = new Date(value); |
- break; |
- |
- case String: |
- default: |
- break; |
- } |
- return value; |
- }, |
- |
- serialize: function(value) { |
- switch (typeof value) { |
- case 'boolean': |
- return value ? '' : undefined; |
- |
- case 'object': |
- if (value instanceof Date) { |
- return value; |
- } else if (value) { |
- try { |
- return JSON.stringify(value); |
- } catch(x) { |
- return ''; |
- } |
- } |
- |
- default: |
- return value != null ? value : undefined; |
- } |
- } |
- |
- }); |
- |
- |
-; |
- |
- Polymer.Base._addFeature({ |
- |
- _setupDebouncers: function() { |
- this._debouncers = {}; |
- }, |
- |
- /** |
- * Debounce signals. |
- * |
- * Call `debounce` to collapse multiple requests for a named task into |
- * one invocation which is made after the wait time has elapsed with |
- * no new request. |
- * |
- * debouncedClickAction: function(e) { |
- * // will not call `processClick` more than once per 100ms |
- * this.debounce('click', function() { |
- * this.processClick; |
- * }, 100); |
- * } |
- * |
- * @method debounce |
- * @param String {String} jobName A string to indentify the debounce job. |
- * @param Function {Function} callback A function that is called (with `this` context) when the wait time elapses. |
- * @param Number {Number} wait Time in milliseconds (ms) after the last signal that must elapse before invoking `callback` |
- * @type Handle |
- */ |
- debounce: function(jobName, callback, wait) { |
- this._debouncers[jobName] = Polymer.Debounce.call(this, |
- this._debouncers[jobName], callback, wait); |
- }, |
- |
- isDebouncerActive: function(jobName) { |
- var debouncer = this._debouncers[jobName]; |
- return debouncer && debouncer.finish; |
- }, |
- |
- flushDebouncer: function(jobName) { |
- var debouncer = this._debouncers[jobName]; |
- if (debouncer) { |
- debouncer.complete(); |
- } |
- } |
- |
- }); |
- |
- |
-; |
- |
- Polymer.Base._addFeature({ |
- |
- _registerFeatures: function() { |
- // identity |
- this._prepIs(); |
- // shared behaviors |
- this._prepBehaviors(); |
- // inheritance |
- this._prepExtends(); |
- // factory |
- this._prepConstructor(); |
- }, |
- |
- _prepBehavior: function() {}, |
- |
- _initFeatures: function() { |
- // setup debouncers |
- this._setupDebouncers(); |
- // acquire behaviors |
- this._marshalBehaviors(); |
- }, |
- |
- _marshalBehavior: function(b) { |
- // publish attributes to instance |
- this._installHostAttributes(b.hostAttributes); |
- } |
- |
- }); |
- |
- |
-; |
- |
- /** |
- * Automatic template management. |
- * |
- * The `template` feature locates and instances a `<template>` element |
- * corresponding to the current Polymer prototype. |
- * |
- * The `<template>` element may be immediately preceeding the script that |
- * invokes `Polymer()`. |
- * |
- * @class standard feature: template |
- */ |
- |
- Polymer.Base._addFeature({ |
- |
- _prepTemplate: function() { |
- // locate template using dom-module |
- this._template = |
- this._template || Polymer.DomModule.import(this.is, 'template'); |
- // fallback to look at the node previous to the currentScript. |
- if (!this._template) { |
- var script = document._currentScript || document.currentScript; |
- var prev = script && script.previousElementSibling; |
- if (prev && prev.localName === 'template') { |
- this._template = prev; |
- } |
- } |
- }, |
- |
- _stampTemplate: function() { |
- if (this._template) { |
- // note: root is now a fragment which can be manipulated |
- // while not attached to the element. |
- this.root = this.instanceTemplate(this._template); |
- } |
- }, |
- |
- instanceTemplate: function(template) { |
- var dom = |
- document.importNode(template._content || template.content, true); |
- return dom; |
- } |
- |
- }); |
- |
- |
-; |
- |
- /** |
- * Provides `ready` lifecycle callback which is called parent to child. |
- * |
- * This can be useful in a number of cases. Here are some examples: |
- * |
- * Setting a default property value that should have a side effect: To ensure |
- * the side effect, an element must set a default value no sooner than |
- * `created`; however, since `created` flows child to host, this is before the |
- * host has had a chance to set a property value on the child. The `ready` |
- * method solves this problem since it's called host to child. |
- * |
- * Dom distribution: To support reprojection efficiently, it's important to |
- * distribute from host to child in one shot. The `attachedCallback` mostly |
- * goes in the desired order except for elements that are in dom to start; in |
- * this case, all children are attached before the host element. Ready also |
- * addresses this case since it's guaranteed to be called host to child. |
- * |
- * @class standard feature: ready |
- */ |
- |
-(function() { |
- |
- var baseAttachedCallback = Polymer.Base.attachedCallback; |
- |
- Polymer.Base._addFeature({ |
- |
- hostStack: [], |
- |
- // for overriding |
- ready: function() { |
- }, |
- |
- // NOTE: The concept of 'host' is overloaded. There are two different |
- // notions: |
- // 1. an element hosts the elements in its local dom root. |
- // 2. an element hosts the elements on which it configures data. |
- // Practially, these notions are almost always coincident. |
- // Some special elements like templates may separate them. |
- // In order not to over-emphaisize this technical difference, we expose |
- // one concept to the user and it maps to the dom-related meaning of host. |
- // |
- // 1. set this element's `host` and push this element onto the `host`'s |
- // list of `client` elements |
- // 2. establish this element as the current hosting element (allows |
- // any elements we stamp to easily set host to us). |
- _pushHost: function(host) { |
- // NOTE: The `dataHost` of an element never changes. |
- this.dataHost = host = host || |
- Polymer.Base.hostStack[Polymer.Base.hostStack.length-1]; |
- // this.dataHost reflects the parent element who manages |
- // any bindings for the element. Only elements originally |
- // stamped from Polymer templates have a dataHost, and this |
- // never changes |
- if (host && host._clients) { |
- host._clients.push(this); |
- } |
- this._beginHost(); |
- }, |
- |
- _beginHost: function() { |
- Polymer.Base.hostStack.push(this); |
- if (!this._clients) { |
- this._clients = []; |
- } |
- }, |
- |
- _popHost: function() { |
- // this element is no longer the current hosting element |
- Polymer.Base.hostStack.pop(); |
- }, |
- |
- _tryReady: function() { |
- if (this._canReady()) { |
- this._ready(); |
- } |
- }, |
- |
- _canReady: function() { |
- return !this.dataHost || this.dataHost._clientsReadied; |
- }, |
- |
- _ready: function() { |
- // extension point |
- this._beforeClientsReady(); |
- this._readyClients(); |
- // extension point |
- this._afterClientsReady(); |
- this._readySelf(); |
- }, |
- |
- _readyClients: function() { |
- // prepare root |
- this._setupRoot(); |
- // logically distribute self |
- this._beginDistribute(); |
- // now fully prepare localChildren |
- var c$ = this._clients; |
- for (var i=0, l= c$.length, c; (i<l) && (c=c$[i]); i++) { |
- c._ready(); |
- } |
- // perform actual dom composition |
- this._finishDistribute(); |
- // ensure elements are attached if they are in the dom at ready time |
- // helps normalize attached ordering between native and polyfill ce. |
- // TODO(sorvell): worth perf cost? ~6% |
- // if (!Polymer.Settings.useNativeCustomElements) { |
- // CustomElements.takeRecords(); |
- // } |
- this._clientsReadied = true; |
- this._clients = null; |
- }, |
- |
- // mark readied and call `ready` |
- // note: called localChildren -> host |
- _readySelf: function() { |
- this._doBehavior('ready'); |
- this._readied = true; |
- if (this._attachedPending) { |
- this._attachedPending = false; |
- this.attachedCallback(); |
- } |
- }, |
- |
- // for system overriding |
- _beforeClientsReady: function() {}, |
- _afterClientsReady: function() {}, |
- |
- // normalize lifecycle: ensure attached occurs only after ready. |
- attachedCallback: function() { |
- if (this._readied) { |
- baseAttachedCallback.call(this); |
- } else { |
- this._attachedPending = true; |
- } |
- } |
- |
- }); |
- |
-})(); |
- |
- |
-; |
- |
-Polymer.ArraySplice = (function() { |
- |
- function newSplice(index, removed, addedCount) { |
- return { |
- index: index, |
- removed: removed, |
- addedCount: addedCount |
- }; |
- } |
- |
- var EDIT_LEAVE = 0; |
- var EDIT_UPDATE = 1; |
- var EDIT_ADD = 2; |
- var EDIT_DELETE = 3; |
- |
- function ArraySplice() {} |
- |
- ArraySplice.prototype = { |
- |
- // Note: This function is *based* on the computation of the Levenshtein |
- // "edit" distance. The one change is that "updates" are treated as two |
- // edits - not one. With Array splices, an update is really a delete |
- // followed by an add. By retaining this, we optimize for "keeping" the |
- // maximum array items in the original array. For example: |
- // |
- // 'xxxx123' -> '123yyyy' |
- // |
- // With 1-edit updates, the shortest path would be just to update all seven |
- // characters. With 2-edit updates, we delete 4, leave 3, and add 4. This |
- // leaves the substring '123' intact. |
- calcEditDistances: function(current, currentStart, currentEnd, |
- old, oldStart, oldEnd) { |
- // "Deletion" columns |
- var rowCount = oldEnd - oldStart + 1; |
- var columnCount = currentEnd - currentStart + 1; |
- var distances = new Array(rowCount); |
- |
- // "Addition" rows. Initialize null column. |
- for (var i = 0; i < rowCount; i++) { |
- distances[i] = new Array(columnCount); |
- distances[i][0] = i; |
- } |
- |
- // Initialize null row |
- for (var j = 0; j < columnCount; j++) |
- distances[0][j] = j; |
- |
- for (var i = 1; i < rowCount; i++) { |
- for (var j = 1; j < columnCount; j++) { |
- if (this.equals(current[currentStart + j - 1], old[oldStart + i - 1])) |
- distances[i][j] = distances[i - 1][j - 1]; |
- else { |
- var north = distances[i - 1][j] + 1; |
- var west = distances[i][j - 1] + 1; |
- distances[i][j] = north < west ? north : west; |
- } |
- } |
- } |
- |
- return distances; |
- }, |
- |
- // This starts at the final weight, and walks "backward" by finding |
- // the minimum previous weight recursively until the origin of the weight |
- // matrix. |
- spliceOperationsFromEditDistances: function(distances) { |
- var i = distances.length - 1; |
- var j = distances[0].length - 1; |
- var current = distances[i][j]; |
- var edits = []; |
- while (i > 0 || j > 0) { |
- if (i == 0) { |
- edits.push(EDIT_ADD); |
- j--; |
- continue; |
- } |
- if (j == 0) { |
- edits.push(EDIT_DELETE); |
- i--; |
- continue; |
- } |
- var northWest = distances[i - 1][j - 1]; |
- var west = distances[i - 1][j]; |
- var north = distances[i][j - 1]; |
- |
- var min; |
- if (west < north) |
- min = west < northWest ? west : northWest; |
- else |
- min = north < northWest ? north : northWest; |
- |
- if (min == northWest) { |
- if (northWest == current) { |
- edits.push(EDIT_LEAVE); |
- } else { |
- edits.push(EDIT_UPDATE); |
- current = northWest; |
- } |
- i--; |
- j--; |
- } else if (min == west) { |
- edits.push(EDIT_DELETE); |
- i--; |
- current = west; |
- } else { |
- edits.push(EDIT_ADD); |
- j--; |
- current = north; |
- } |
- } |
- |
- edits.reverse(); |
- return edits; |
- }, |
- |
- /** |
- * Splice Projection functions: |
- * |
- * A splice map is a representation of how a previous array of items |
- * was transformed into a new array of items. Conceptually it is a list of |
- * tuples of |
- * |
- * <index, removed, addedCount> |
- * |
- * which are kept in ascending index order of. The tuple represents that at |
- * the |index|, |removed| sequence of items were removed, and counting forward |
- * from |index|, |addedCount| items were added. |
- */ |
- |
- /** |
- * Lacking individual splice mutation information, the minimal set of |
- * splices can be synthesized given the previous state and final state of an |
- * array. The basic approach is to calculate the edit distance matrix and |
- * choose the shortest path through it. |
- * |
- * Complexity: O(l * p) |
- * l: The length of the current array |
- * p: The length of the old array |
- */ |
- calcSplices: function(current, currentStart, currentEnd, |
- old, oldStart, oldEnd) { |
- var prefixCount = 0; |
- var suffixCount = 0; |
- |
- var minLength = Math.min(currentEnd - currentStart, oldEnd - oldStart); |
- if (currentStart == 0 && oldStart == 0) |
- prefixCount = this.sharedPrefix(current, old, minLength); |
- |
- if (currentEnd == current.length && oldEnd == old.length) |
- suffixCount = this.sharedSuffix(current, old, minLength - prefixCount); |
- |
- currentStart += prefixCount; |
- oldStart += prefixCount; |
- currentEnd -= suffixCount; |
- oldEnd -= suffixCount; |
- |
- if (currentEnd - currentStart == 0 && oldEnd - oldStart == 0) |
- return []; |
- |
- if (currentStart == currentEnd) { |
- var splice = newSplice(currentStart, [], 0); |
- while (oldStart < oldEnd) |
- splice.removed.push(old[oldStart++]); |
- |
- return [ splice ]; |
- } else if (oldStart == oldEnd) |
- return [ newSplice(currentStart, [], currentEnd - currentStart) ]; |
- |
- var ops = this.spliceOperationsFromEditDistances( |
- this.calcEditDistances(current, currentStart, currentEnd, |
- old, oldStart, oldEnd)); |
- |
- var splice = undefined; |
- var splices = []; |
- var index = currentStart; |
- var oldIndex = oldStart; |
- for (var i = 0; i < ops.length; i++) { |
- switch(ops[i]) { |
- case EDIT_LEAVE: |
- if (splice) { |
- splices.push(splice); |
- splice = undefined; |
- } |
- |
- index++; |
- oldIndex++; |
- break; |
- case EDIT_UPDATE: |
- if (!splice) |
- splice = newSplice(index, [], 0); |
- |
- splice.addedCount++; |
- index++; |
- |
- splice.removed.push(old[oldIndex]); |
- oldIndex++; |
- break; |
- case EDIT_ADD: |
- if (!splice) |
- splice = newSplice(index, [], 0); |
- |
- splice.addedCount++; |
- index++; |
- break; |
- case EDIT_DELETE: |
- if (!splice) |
- splice = newSplice(index, [], 0); |
- |
- splice.removed.push(old[oldIndex]); |
- oldIndex++; |
- break; |
- } |
- } |
- |
- if (splice) { |
- splices.push(splice); |
- } |
- return splices; |
- }, |
- |
- sharedPrefix: function(current, old, searchLength) { |
- for (var i = 0; i < searchLength; i++) |
- if (!this.equals(current[i], old[i])) |
- return i; |
- return searchLength; |
- }, |
- |
- sharedSuffix: function(current, old, searchLength) { |
- var index1 = current.length; |
- var index2 = old.length; |
- var count = 0; |
- while (count < searchLength && this.equals(current[--index1], old[--index2])) |
- count++; |
- |
- return count; |
- }, |
- |
- calculateSplices: function(current, previous) { |
- return this.calcSplices(current, 0, current.length, previous, 0, |
- previous.length); |
- }, |
- |
- equals: function(currentValue, previousValue) { |
- return currentValue === previousValue; |
- } |
- }; |
- |
- return new ArraySplice(); |
- |
-})(); |
- |
-; |
- |
- Polymer.EventApi = (function() { |
- |
- var Settings = Polymer.Settings; |
- |
- var EventApi = function(event) { |
- this.event = event; |
- }; |
- |
- if (Settings.useShadow) { |
- |
- EventApi.prototype = { |
- |
- get rootTarget() { |
- return this.event.path[0]; |
- }, |
- |
- get localTarget() { |
- return this.event.target; |
- }, |
- |
- get path() { |
- return this.event.path; |
- } |
- |
- }; |
- |
- } else { |
- |
- EventApi.prototype = { |
- |
- get rootTarget() { |
- return this.event.target; |
- }, |
- |
- get localTarget() { |
- var current = this.event.currentTarget; |
- var currentRoot = current && Polymer.dom(current).getOwnerRoot(); |
- var p$ = this.path; |
- for (var i=0; i < p$.length; i++) { |
- if (Polymer.dom(p$[i]).getOwnerRoot() === currentRoot) { |
- return p$[i]; |
- } |
- } |
- }, |
- |
- // TODO(sorvell): simulate event.path. This probably incorrect for |
- // non-bubbling events. |
- get path() { |
- if (!this.event._path) { |
- var path = []; |
- var o = this.rootTarget; |
- while (o) { |
- path.push(o); |
- o = Polymer.dom(o).parentNode || o.host; |
- } |
- // event path includes window in most recent native implementations |
- path.push(window); |
- this.event._path = path; |
- } |
- return this.event._path; |
- } |
- |
- }; |
- |
- } |
- |
- var factory = function(event) { |
- if (!event.__eventApi) { |
- event.__eventApi = new EventApi(event); |
- } |
- return event.__eventApi; |
- }; |
- |
- return { |
- factory: factory |
- }; |
- |
- })(); |
- |
- |
-; |
- |
- Polymer.DomApi = (function() { |
- |
- var Debounce = Polymer.Debounce; |
- var Settings = Polymer.Settings; |
- |
- var nativeInsertBefore = Element.prototype.insertBefore; |
- var nativeRemoveChild = Element.prototype.removeChild; |
- var nativeAppendChild = Element.prototype.appendChild; |
- |
- var dirtyRoots = []; |
- |
- var DomApi = function(node, patch) { |
- this.node = node; |
- if (patch) { |
- this.patch(); |
- } |
- }; |
- |
- DomApi.prototype = { |
- |
- // experimental: support patching selected native api. |
- patch: function() { |
- var self = this; |
- this.node.appendChild = function(node) { |
- return self.appendChild(node); |
- }; |
- this.node.insertBefore = function(node, ref_node) { |
- return self.insertBefore(node, ref_node); |
- }; |
- this.node.removeChild = function(node) { |
- return self.removeChild(node); |
- }; |
- }, |
- |
- get childNodes() { |
- var c$ = getLightChildren(this.node); |
- return Array.isArray(c$) ? c$ : Array.prototype.slice.call(c$); |
- }, |
- |
- get children() { |
- return Array.prototype.filter.call(this.childNodes, function(n) { |
- return (n.nodeType === Node.ELEMENT_NODE); |
- }); |
- }, |
- |
- get parentNode() { |
- return this.node.lightParent || this.node.parentNode; |
- }, |
- |
- flush: function() { |
- for (var i=0, host; i<dirtyRoots.length; i++) { |
- host = dirtyRoots[i]; |
- host.flushDebouncer('_distribute'); |
- } |
- dirtyRoots = []; |
- }, |
- |
- _lazyDistribute: function(host) { |
- if (host.shadyRoot) { |
- host.shadyRoot._distributionClean = false; |
- } |
- // TODO(sorvell): optimize debounce so it does less work by default |
- // and then remove these checks... |
- // need to dirty distribution once. |
- if (!host.isDebouncerActive('_distribute')) { |
- host.debounce('_distribute', host._distributeContent); |
- dirtyRoots.push(host); |
- } |
- }, |
- |
- // cases in which we may not be able to just do standard appendChild |
- // 1. container has a shadyRoot (needsDistribution IFF the shadyRoot |
- // has an insertion point) |
- // 2. container is a shadyRoot (don't distribute, instead set |
- // container to container.host. |
- // 3. node is <content> (host of container needs distribution) |
- appendChild: function(node) { |
- var distributed; |
- this._removeNodeFromHost(node); |
- if (this._nodeIsInLogicalTree(this.node)) { |
- var host = this._hostForNode(this.node); |
- this._addLogicalInfo(node, this.node, host && host.shadyRoot); |
- this._addNodeToHost(node); |
- if (host) { |
- distributed = this._maybeDistribute(node, this.node, host); |
- } |
- } |
- if (!distributed) { |
- // if adding to a shadyRoot, add to host instead |
- var container = this.node._isShadyRoot ? this.node.host : this.node; |
- nativeAppendChild.call(container, node); |
- } |
- return node; |
- }, |
- |
- insertBefore: function(node, ref_node) { |
- if (!ref_node) { |
- return this.appendChild(node); |
- } |
- var distributed; |
- this._removeNodeFromHost(node); |
- if (this._nodeIsInLogicalTree(this.node)) { |
- saveLightChildrenIfNeeded(this.node); |
- var children = this.childNodes; |
- var index = children.indexOf(ref_node); |
- if (index < 0) { |
- throw Error('The ref_node to be inserted before is not a child ' + |
- 'of this node'); |
- } |
- var host = this._hostForNode(this.node); |
- this._addLogicalInfo(node, this.node, host && host.shadyRoot, index); |
- this._addNodeToHost(node); |
- if (host) { |
- distributed = this._maybeDistribute(node, this.node, host); |
- } |
- } |
- if (!distributed) { |
- // if ref_node is <content> replace with first distributed node |
- ref_node = ref_node.localName === CONTENT ? |
- this._firstComposedNode(ref_node) : ref_node; |
- // if adding to a shadyRoot, add to host instead |
- var container = this.node._isShadyRoot ? this.node.host : this.node; |
- nativeInsertBefore.call(container, node, ref_node); |
- } |
- return node; |
- }, |
- |
- /** |
- Removes the given `node` from the element's `lightChildren`. |
- This method also performs dom composition. |
- */ |
- removeChild: function(node) { |
- var distributed; |
- if (this._nodeIsInLogicalTree(this.node)) { |
- var host = this._hostForNode(this.node); |
- distributed = this._maybeDistribute(node, this.node, host); |
- this._removeNodeFromHost(node); |
- } |
- if (!distributed) { |
- // if removing from a shadyRoot, remove form host instead |
- var container = this.node._isShadyRoot ? this.node.host : this.node; |
- nativeRemoveChild.call(container, node); |
- } |
- return node; |
- }, |
- |
- replaceChild: function(node, ref_node) { |
- this.insertBefore(node, ref_node); |
- this.removeChild(ref_node); |
- return node; |
- }, |
- |
- getOwnerRoot: function() { |
- return this._ownerShadyRootForNode(this.node); |
- }, |
- |
- _ownerShadyRootForNode: function(node) { |
- if (node._ownerShadyRoot === undefined) { |
- var root; |
- if (node._isShadyRoot) { |
- root = node; |
- } else { |
- var parent = Polymer.dom(node).parentNode; |
- if (parent) { |
- root = parent._isShadyRoot ? parent : |
- this._ownerShadyRootForNode(parent); |
- } else { |
- root = null; |
- } |
- } |
- node._ownerShadyRoot = root; |
- } |
- return node._ownerShadyRoot; |
- |
- }, |
- |
- _maybeDistribute: function(node, parent, host) { |
- var nodeNeedsDistribute = this._nodeNeedsDistribution(node); |
- var distribute = this._parentNeedsDistribution(parent) || |
- nodeNeedsDistribute; |
- if (nodeNeedsDistribute) { |
- this._updateInsertionPoints(host); |
- } |
- if (distribute) { |
- this._lazyDistribute(host); |
- } |
- return distribute; |
- }, |
- |
- _updateInsertionPoints: function(host) { |
- host.shadyRoot._insertionPoints = |
- factory(host.shadyRoot).querySelectorAll(CONTENT); |
- }, |
- |
- _nodeIsInLogicalTree: function(node) { |
- return Boolean(node._isShadyRoot || |
- this._ownerShadyRootForNode(node) || |
- node.shadyRoot); |
- }, |
- |
- // note: a node is its own host |
- _hostForNode: function(node) { |
- var root = node.shadyRoot || (node._isShadyRoot ? |
- node : this._ownerShadyRootForNode(node)); |
- return root && root.host; |
- }, |
- |
- _parentNeedsDistribution: function(parent) { |
- return parent.shadyRoot && hasInsertionPoint(parent.shadyRoot); |
- }, |
- |
- // TODO(sorvell): technically we should check non-fragment nodes for |
- // <content> children but since this case is assumed to be exceedingly |
- // rare, we avoid the cost and will address with some specific api |
- // when the need arises. |
- _nodeNeedsDistribution: function(node) { |
- return (node.localName === CONTENT) || |
- ((node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) && |
- node.querySelector(CONTENT)); |
- }, |
- |
- _removeNodeFromHost: function(node) { |
- if (node.lightParent) { |
- var root = this._ownerShadyRootForNode(node); |
- if (root) { |
- root.host._elementRemove(node); |
- } |
- this._removeLogicalInfo(node, node.lightParent); |
- } |
- this._removeOwnerShadyRoot(node); |
- }, |
- |
- _addNodeToHost: function(node) { |
- var checkNode = node.nodeType === Node.DOCUMENT_FRAGMENT_NODE ? |
- node.firstChild : node; |
- var root = this._ownerShadyRootForNode(checkNode); |
- if (root) { |
- root.host._elementAdd(node); |
- } |
- }, |
- |
- _addLogicalInfo: function(node, container, root, index) { |
- saveLightChildrenIfNeeded(container); |
- var children = factory(container).childNodes; |
- index = index === undefined ? children.length : index; |
- // handle document fragments |
- if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) { |
- var n = node.firstChild; |
- while (n) { |
- children.splice(index++, 0, n); |
- n.lightParent = container; |
- n = n.nextSibling; |
- } |
- } else { |
- children.splice(index, 0, node); |
- node.lightParent = container; |
- } |
- }, |
- |
- // NOTE: in general, we expect contents of the lists here to be small-ish |
- // and therefore indexOf to be nbd. Other optimizations can be made |
- // for larger lists (linked list) |
- _removeLogicalInfo: function(node, container) { |
- var children = factory(container).childNodes; |
- var index = children.indexOf(node); |
- if ((index < 0) || (container !== node.lightParent)) { |
- throw Error('The node to be removed is not a child of this node'); |
- } |
- children.splice(index, 1); |
- node.lightParent = null; |
- }, |
- |
- _removeOwnerShadyRoot: function(node) { |
- // TODO(sorvell): need to clear any children of element? |
- node._ownerShadyRoot = undefined; |
- }, |
- |
- // TODO(sorvell): This will fail if distribution that affects this |
- // question is pending; this is expected to be exceedingly rare, but if |
- // the issue comes up, we can force a flush in this case. |
- _firstComposedNode: function(content) { |
- var n$ = factory(content).getDistributedNodes(); |
- for (var i=0, l=n$.length, n, p$; (i<l) && (n=n$[i]); i++) { |
- p$ = factory(n).getDestinationInsertionPoints(); |
- // means that we're composed to this spot. |
- if (p$[p$.length-1] === content) { |
- return n; |
- } |
- } |
- }, |
- |
- // TODO(sorvell): consider doing native QSA and filtering results. |
- querySelector: function(selector) { |
- return this.querySelectorAll(selector)[0]; |
- }, |
- |
- querySelectorAll: function(selector) { |
- return this._query(function(n) { |
- return matchesSelector.call(n, selector); |
- }, this.node); |
- }, |
- |
- _query: function(matcher, node) { |
- var list = []; |
- this._queryElements(factory(node).childNodes, matcher, list); |
- return list; |
- }, |
- |
- _queryElements: function(elements, matcher, list) { |
- for (var i=0, l=elements.length, c; (i<l) && (c=elements[i]); i++) { |
- if (c.nodeType === Node.ELEMENT_NODE) { |
- this._queryElement(c, matcher, list); |
- } |
- } |
- }, |
- |
- _queryElement: function(node, matcher, list) { |
- if (matcher(node)) { |
- list.push(node); |
- } |
- this._queryElements(factory(node).childNodes, matcher, list); |
- }, |
- |
- getDestinationInsertionPoints: function() { |
- return this.node._destinationInsertionPoints || []; |
- }, |
- |
- getDistributedNodes: function() { |
- return this.node._distributedNodes || []; |
- }, |
- |
- /* |
- Returns a list of nodes distributed within this element. These can be |
- dom children or elements distributed to children that are insertion |
- points. |
- */ |
- queryDistributedElements: function(selector) { |
- var c$ = this.childNodes; |
- var list = []; |
- this._distributedFilter(selector, c$, list); |
- for (var i=0, l=c$.length, c; (i<l) && (c=c$[i]); i++) { |
- if (c.localName === CONTENT) { |
- this._distributedFilter(selector, factory(c).getDistributedNodes(), |
- list); |
- } |
- } |
- return list; |
- }, |
- |
- _distributedFilter: function(selector, list, results) { |
- results = results || []; |
- for (var i=0, l=list.length, d; (i<l) && (d=list[i]); i++) { |
- if ((d.nodeType === Node.ELEMENT_NODE) && |
- (d.localName !== CONTENT) && |
- matchesSelector.call(d, selector)) { |
- results.push(d); |
- } |
- } |
- return results; |
- } |
- |
- }; |
- |
- if (Settings.useShadow) { |
- |
- DomApi.prototype.querySelectorAll = function(selector) { |
- return Array.prototype.slice.call(this.node.querySelectorAll(selector)); |
- }; |
- |
- DomApi.prototype.patch = function() {}; |
- |
- DomApi.prototype.getOwnerRoot = function() { |
- var n = this.node; |
- while (n) { |
- if (n.nodeType === Node.DOCUMENT_FRAGMENT_NODE && n.host) { |
- return n; |
- } |
- n = n.parentNode; |
- } |
- }; |
- |
- DomApi.prototype.getDestinationInsertionPoints = function() { |
- var n$ = this.node.getDestinationInsertionPoints(); |
- return n$ ? Array.prototype.slice.call(n$) : []; |
- }; |
- |
- DomApi.prototype.getDistributedNodes = function() { |
- var n$ = this.node.getDistributedNodes(); |
- return n$ ? Array.prototype.slice.call(n$) : []; |
- }; |
- |
- |
- } |
- |
- var CONTENT = 'content'; |
- |
- var factory = function(node, patch) { |
- node = node || document; |
- if (!node.__domApi) { |
- node.__domApi = new DomApi(node, patch); |
- } |
- return node.__domApi; |
- }; |
- |
- Polymer.dom = function(obj, patch) { |
- if (obj instanceof Event) { |
- return Polymer.EventApi.factory(obj); |
- } else { |
- return factory(obj, patch); |
- } |
- }; |
- |
- // make flush available directly. |
- Polymer.dom.flush = DomApi.prototype.flush; |
- |
- function getLightChildren(node) { |
- var children = node.lightChildren; |
- return children ? children : node.childNodes; |
- } |
- |
- function saveLightChildrenIfNeeded(node) { |
- // Capture the list of light children. It's important to do this before we |
- // start transforming the DOM into "rendered" state. |
- // |
- // Children may be added to this list dynamically. It will be treated as the |
- // source of truth for the light children of the element. This element's |
- // actual children will be treated as the rendered state once lightChildren |
- // is populated. |
- if (!node.lightChildren) { |
- var children = []; |
- for (var child = node.firstChild; child; child = child.nextSibling) { |
- children.push(child); |
- child.lightParent = child.lightParent || node; |
- } |
- node.lightChildren = children; |
- } |
- } |
- |
- function hasInsertionPoint(root) { |
- return Boolean(root._insertionPoints.length); |
- } |
- |
- var p = Element.prototype; |
- var matchesSelector = p.matches || p.matchesSelector || |
- p.mozMatchesSelector || p.msMatchesSelector || |
- p.oMatchesSelector || p.webkitMatchesSelector; |
- |
- return { |
- getLightChildren: getLightChildren, |
- saveLightChildrenIfNeeded: saveLightChildrenIfNeeded, |
- matchesSelector: matchesSelector, |
- hasInsertionPoint: hasInsertionPoint, |
- factory: factory |
- }; |
- |
- })(); |
- |
- |
-; |
- |
- (function() { |
- /** |
- |
- Implements a pared down version of ShadowDOM's scoping, which is easy to |
- polyfill across browsers. |
- |
- */ |
- Polymer.Base._addFeature({ |
- |
- _prepShady: function() { |
- // Use this system iff localDom is needed. |
- this._useContent = this._useContent || Boolean(this._template); |
- if (this._useContent) { |
- this._template._hasInsertionPoint = |
- this._template.content.querySelector('content'); |
- } |
- }, |
- |
- // called as part of content initialization, prior to template stamping |
- _poolContent: function() { |
- if (this._useContent) { |
- // capture lightChildren to help reify dom scoping |
- saveLightChildrenIfNeeded(this); |
- } |
- }, |
- |
- // called as part of content initialization, after template stamping |
- _setupRoot: function() { |
- if (this._useContent) { |
- this._createLocalRoot(); |
- } |
- }, |
- |
- _createLocalRoot: function() { |
- this.shadyRoot = this.root; |
- this.shadyRoot._distributionClean = false; |
- this.shadyRoot._isShadyRoot = true; |
- this.shadyRoot._dirtyRoots = []; |
- // capture insertion point list |
- // TODO(sorvell): it's faster to do this via native qSA than annotator. |
- this.shadyRoot._insertionPoints = this._template._hasInsertionPoint ? |
- this.shadyRoot.querySelectorAll('content') : []; |
- // save logical tree info for shadyRoot. |
- saveLightChildrenIfNeeded(this.shadyRoot); |
- this.shadyRoot.host = this; |
- }, |
- |
- /** |
- * Return the element whose local dom within which this element |
- * is contained. This is a shorthand for |
- * `Polymer.dom(this).getOwnerRoot().host`. |
- */ |
- get domHost() { |
- var root = Polymer.dom(this).getOwnerRoot(); |
- return root && root.host; |
- }, |
- |
- /** |
- * Force this element to distribute its children to its local dom. |
- * A user should call `distributeContent` if distribution has been |
- * invalidated due to changes to selectors on child elements that |
- * effect distribution. For example, if an element contains an |
- * insertion point with <content select=".foo"> and a `foo` class is |
- * added to a child, then `distributeContent` must be called to update |
- * local dom distribution. |
- */ |
- distributeContent: function() { |
- if (this._useContent) { |
- this.shadyRoot._distributionClean = false; |
- this._distributeContent(); |
- } |
- }, |
- |
- _distributeContent: function() { |
- if (this._useContent && !this.shadyRoot._distributionClean) { |
- // logically distribute self |
- this._beginDistribute(); |
- this._distributeDirtyRoots(); |
- this._finishDistribute(); |
- } |
- }, |
- |
- _beginDistribute: function() { |
- if (this._useContent && hasInsertionPoint(this.shadyRoot)) { |
- // reset distributions |
- this._resetDistribution(); |
- // compute which nodes should be distributed where |
- // TODO(jmesserly): this is simplified because we assume a single |
- // ShadowRoot per host and no `<shadow>`. |
- this._distributePool(this.shadyRoot, this._collectPool()); |
- } |
- }, |
- |
- _distributeDirtyRoots: function() { |
- var c$ = this.shadyRoot._dirtyRoots; |
- for (var i=0, l= c$.length, c; (i<l) && (c=c$[i]); i++) { |
- c._distributeContent(); |
- } |
- this.shadyRoot._dirtyRoots = []; |
- }, |
- |
- _finishDistribute: function() { |
- // compose self |
- if (this._useContent) { |
- if (hasInsertionPoint(this.shadyRoot)) { |
- this._composeTree(); |
- } else { |
- if (!this.shadyRoot._hasDistributed) { |
- this.textContent = ''; |
- this.appendChild(this.shadyRoot); |
- } else { |
- // simplified non-tree walk composition |
- var children = this._composeNode(this); |
- this._updateChildNodes(this, children); |
- } |
- } |
- this.shadyRoot._hasDistributed = true; |
- this.shadyRoot._distributionClean = true; |
- } |
- }, |
- |
- // This is a polyfill for Element.prototype.matches, which is sometimes |
- // still prefixed. Alternatively we could just polyfill it somewhere. |
- // Note that the arguments are reversed from what you might expect. |
- elementMatches: function(selector, node) { |
- if (node === undefined) { |
- node = this; |
- } |
- return matchesSelector.call(node, selector); |
- }, |
- |
- // Many of the following methods are all conceptually static, but they are |
- // included here as "protected" methods to allow overriding. |
- |
- _resetDistribution: function() { |
- // light children |
- var children = getLightChildren(this); |
- for (var i = 0; i < children.length; i++) { |
- var child = children[i]; |
- if (child._destinationInsertionPoints) { |
- child._destinationInsertionPoints = undefined; |
- } |
- } |
- // insertion points |
- var root = this.shadyRoot; |
- var p$ = root._insertionPoints; |
- for (var j = 0; j < p$.length; j++) { |
- p$[j]._distributedNodes = []; |
- } |
- }, |
- |
- // Gather the pool of nodes that should be distributed. We will combine |
- // these with the "content root" to arrive at the composed tree. |
- _collectPool: function() { |
- var pool = []; |
- var children = getLightChildren(this); |
- for (var i = 0; i < children.length; i++) { |
- var child = children[i]; |
- if (isInsertionPoint(child)) { |
- pool.push.apply(pool, child._distributedNodes); |
- } else { |
- pool.push(child); |
- } |
- } |
- return pool; |
- }, |
- |
- // perform "logical" distribution; note, no actual dom is moved here, |
- // instead elements are distributed into a `content._distributedNodes` |
- // array where applicable. |
- _distributePool: function(node, pool) { |
- var p$ = node._insertionPoints; |
- for (var i=0, l=p$.length, p; (i<l) && (p=p$[i]); i++) { |
- this._distributeInsertionPoint(p, pool); |
- } |
- }, |
- |
- _distributeInsertionPoint: function(content, pool) { |
- // distribute nodes from the pool that this selector matches |
- var anyDistributed = false; |
- for (var i=0, l=pool.length, node; i < l; i++) { |
- node=pool[i]; |
- // skip nodes that were already used |
- if (!node) { |
- continue; |
- } |
- // distribute this node if it matches |
- if (this._matchesContentSelect(node, content)) { |
- distributeNodeInto(node, content); |
- // remove this node from the pool |
- pool[i] = undefined; |
- // since at least one node matched, we won't need fallback content |
- anyDistributed = true; |
- var parent = content.lightParent; |
- // dirty a shadyRoot if a change may trigger reprojection! |
- if (parent && parent.shadyRoot && |
- hasInsertionPoint(parent.shadyRoot)) { |
- parent.shadyRoot._distributionClean = false; |
- this.shadyRoot._dirtyRoots.push(parent); |
- } |
- } |
- } |
- // Fallback content if nothing was distributed here |
- if (!anyDistributed) { |
- var children = getLightChildren(content); |
- for (var j = 0; j < children.length; j++) { |
- distributeNodeInto(children[j], content); |
- } |
- } |
- }, |
- |
- // Reify dom such that it is at its correct rendering position |
- // based on logical distribution. |
- _composeTree: function() { |
- this._updateChildNodes(this, this._composeNode(this)); |
- var p$ = this.shadyRoot._insertionPoints; |
- for (var i=0, l=p$.length, p, parent; (i<l) && (p=p$[i]); i++) { |
- parent = p.lightParent || p.parentNode; |
- if (!parent._useContent && (parent !== this) && |
- (parent !== this.shadyRoot)) { |
- this._updateChildNodes(parent, this._composeNode(parent)); |
- } |
- } |
- }, |
- |
- // Returns the list of nodes which should be rendered inside `node`. |
- _composeNode: function(node) { |
- var children = []; |
- var c$ = getLightChildren(node.shadyRoot || node); |
- for (var i = 0; i < c$.length; i++) { |
- var child = c$[i]; |
- if (isInsertionPoint(child)) { |
- var distributedNodes = child._distributedNodes; |
- for (var j = 0; j < distributedNodes.length; j++) { |
- var distributedNode = distributedNodes[j]; |
- if (isFinalDestination(child, distributedNode)) { |
- children.push(distributedNode); |
- } |
- } |
- } else { |
- children.push(child); |
- } |
- } |
- return children; |
- }, |
- |
- // Ensures that the rendered node list inside `node` is `children`. |
- _updateChildNodes: function(node, children) { |
- var splices = |
- Polymer.ArraySplice.calculateSplices(children, node.childNodes); |
- for (var i=0; i<splices.length; i++) { |
- var s = splices[i]; |
- // remove |
- for (var j=0, c; j < s.removed.length; j++) { |
- c = s.removed[j]; |
- if (c.previousSibling == children[s.index-1]) { |
- remove(c); |
- } |
- } |
- // insert |
- for (var idx=s.index, ch, o; idx < s.index + s.addedCount; idx++) { |
- ch = children[idx]; |
- o = node.childNodes[idx]; |
- while (o && o === ch) { |
- o = o.nextSibling; |
- } |
- insertBefore(node, ch, o); |
- } |
- } |
- }, |
- |
- _matchesContentSelect: function(node, contentElement) { |
- var select = contentElement.getAttribute('select'); |
- // no selector matches all nodes (including text) |
- if (!select) { |
- return true; |
- } |
- select = select.trim(); |
- // same thing if it had only whitespace |
- if (!select) { |
- return true; |
- } |
- // selectors can only match Elements |
- if (!(node instanceof Element)) { |
- return false; |
- } |
- // only valid selectors can match: |
- // TypeSelector |
- // * |
- // ClassSelector |
- // IDSelector |
- // AttributeSelector |
- // negation |
- var validSelectors = /^(:not\()?[*.#[a-zA-Z_|]/; |
- if (!validSelectors.test(select)) { |
- return false; |
- } |
- return this.elementMatches(select, node); |
- }, |
- |
- // system override point |
- _elementAdd: function() {}, |
- |
- // system override point |
- _elementRemove: function() {} |
- |
- }); |
- |
- var saveLightChildrenIfNeeded = Polymer.DomApi.saveLightChildrenIfNeeded; |
- var getLightChildren = Polymer.DomApi.getLightChildren; |
- var matchesSelector = Polymer.DomApi.matchesSelector; |
- var hasInsertionPoint = Polymer.DomApi.hasInsertionPoint; |
- |
- function distributeNodeInto(child, insertionPoint) { |
- insertionPoint._distributedNodes.push(child); |
- var points = child._destinationInsertionPoints; |
- if (!points) { |
- child._destinationInsertionPoints = [insertionPoint]; |
- // TODO(sorvell): _destinationInsertionPoints may not be cleared when |
- // nodes are dynamically added/removed, therefore test before adding |
- // insertion points. |
- } else if (points.indexOf(insertionPoint) < 0) { |
- points.push(insertionPoint); |
- } |
- } |
- |
- function isFinalDestination(insertionPoint, node) { |
- var points = node._destinationInsertionPoints; |
- return points && points[points.length - 1] === insertionPoint; |
- } |
- |
- function isInsertionPoint(node) { |
- // TODO(jmesserly): we could add back 'shadow' support here. |
- return node.localName == 'content'; |
- } |
- |
- var nativeInsertBefore = Element.prototype.insertBefore; |
- var nativeRemoveChild = Element.prototype.removeChild; |
- |
- function insertBefore(parentNode, newChild, refChild) { |
- // remove child from its old parent first |
- remove(newChild); |
- // make sure we never lose logical DOM information: |
- // if the parentNode doesn't have lightChildren, save that information now. |
- saveLightChildrenIfNeeded(parentNode); |
- // insert it into the real DOM |
- nativeInsertBefore.call(parentNode, newChild, refChild || null); |
- } |
- |
- function remove(node) { |
- var parentNode = node.parentNode; |
- if (parentNode) { |
- // make sure we never lose logical DOM information: |
- // if the parentNode doesn't have lightChildren, save that information now. |
- saveLightChildrenIfNeeded(parentNode); |
- // remove it from the real DOM |
- nativeRemoveChild.call(parentNode, node); |
- } |
- } |
- |
- })(); |
- |
- |
-; |
- |
- /** |
- Implements `shadyRoot` compatible dom scoping using native ShadowDOM. |
- */ |
- |
- // Transform styles if not using ShadowDOM or if flag is set. |
- |
- if (Polymer.Settings.useShadow) { |
- |
- Polymer.Base._addFeature({ |
- |
- // no-op's when ShadowDOM is in use |
- _poolContent: function() {}, |
- _beginDistribute: function() {}, |
- distributeContent: function() {}, |
- _distributeContent: function() {}, |
- _finishDistribute: function() {}, |
- |
- // create a shadowRoot |
- _createLocalRoot: function() { |
- this.createShadowRoot(); |
- this.shadowRoot.appendChild(this.root); |
- this.root = this.shadowRoot; |
- } |
- |
- }); |
- |
- } |
- |
- |
-; |
- |
- Polymer.DomModule = document.createElement('dom-module'); |
- |
- Polymer.Base._addFeature({ |
- |
- _registerFeatures: function() { |
- // identity |
- this._prepIs(); |
- // shared behaviors |
- this._prepBehaviors(); |
- // inheritance |
- this._prepExtends(); |
- // factory |
- this._prepConstructor(); |
- // template |
- this._prepTemplate(); |
- // dom encapsulation |
- this._prepShady(); |
- }, |
- |
- _prepBehavior: function() {}, |
- |
- _initFeatures: function() { |
- // manage local dom |
- this._poolContent(); |
- // host stack |
- this._pushHost(); |
- // instantiate template |
- this._stampTemplate(); |
- // host stack |
- this._popHost(); |
- // setup debouncers |
- this._setupDebouncers(); |
- // instance shared behaviors |
- this._marshalBehaviors(); |
- // top-down initial distribution, configuration, & ready callback |
- this._tryReady(); |
- }, |
- |
- _marshalBehavior: function(b) { |
- // publish attributes to instance |
- this._installHostAttributes(b.hostAttributes); |
- } |
- |
- }); |
- |
- |
-; |
-(function(scope) { |
- |
- function withDependencies(task, depends) { |
- depends = depends || []; |
- if (!depends.map) { |
- depends = [depends]; |
- } |
- return task.apply(this, depends.map(marshal)); |
- } |
- |
- function module(name, dependsOrFactory, moduleFactory) { |
- var module = null; |
- switch (arguments.length) { |
- case 0: |
- return; |
- case 2: |
- // dependsOrFactory is `factory` in this case |
- module = dependsOrFactory.apply(this); |
- break; |
- default: |
- // dependsOrFactory is `depends` in this case |
- module = withDependencies(moduleFactory, dependsOrFactory); |
- break; |
- } |
- modules[name] = module; |
- }; |
- |
- function marshal(name) { |
- return modules[name]; |
- } |
- |
- var modules = {}; |
- |
- var using = function(depends, task) { |
- withDependencies(task, depends); |
- }; |
- |
- // exports |
- |
- scope.marshal = marshal; |
- // `module` confuses commonjs detectors |
- scope.modulate = module; |
- scope.using = using; |
- |
-})(this); |
- |
-; |
-/** |
- * Scans a template to produce an annotation list that that associates |
- * metadata culled from markup with tree locations |
- * metadata and information to associate the metadata with nodes in an instance. |
- * |
- * Supported expressions include: |
- * |
- * Double-mustache annotations in text content. The annotation must be the only |
- * content in the tag, compound expressions are not supported. |
- * |
- * <[tag]>{{annotation}}<[tag]> |
- * |
- * Double-escaped annotations in an attribute, either {{}} or [[]]. |
- * |
- * <[tag] someAttribute="{{annotation}}" another="[[annotation]]"><[tag]> |
- * |
- * `on-` style event declarations. |
- * |
- * <[tag] on-<event-name>="annotation"><[tag]> |
- * |
- * Note that the `annotations` feature does not implement any behaviors |
- * associated with these expressions, it only captures the data. |
- * |
- * Generated data-structure: |
- * |
- * [ |
- * { |
- * id: '<id>', |
- * events: [ |
- * { |
- * name: '<name>' |
- * value: '<annotation>' |
- * }, ... |
- * ], |
- * bindings: [ |
- * { |
- * kind: ['text'|'attribute'], |
- * mode: ['{'|'['], |
- * name: '<name>' |
- * value: '<annotation>' |
- * }, ... |
- * ], |
- * // TODO(sjmiles): this is annotation-parent, not node-parent |
- * parent: <reference to parent annotation object>, |
- * index: <integer index in parent's childNodes collection> |
- * }, |
- * ... |
- * ] |
- * |
- * @class Template feature |
- */ |
- |
- // null-array (shared empty array to avoid null-checks) |
- Polymer.nar = []; |
- |
- Polymer.Annotations = { |
- |
- // preprocess-time |
- |
- // construct and return a list of annotation records |
- // by scanning `template`'s content |
- // |
- parseAnnotations: function(template) { |
- var list = []; |
- var content = template._content || template.content; |
- this._parseNodeAnnotations(content, list); |
- return list; |
- }, |
- |
- // add annotations gleaned from subtree at `node` to `list` |
- _parseNodeAnnotations: function(node, list) { |
- return node.nodeType === Node.TEXT_NODE ? |
- this._parseTextNodeAnnotation(node, list) : |
- // TODO(sjmiles): are there other nodes we may encounter |
- // that are not TEXT_NODE but also not ELEMENT? |
- this._parseElementAnnotations(node, list); |
- }, |
- |
- // add annotations gleaned from TextNode `node` to `list` |
- _parseTextNodeAnnotation: function(node, list) { |
- var v = node.textContent, escape = v.slice(0, 2); |
- if (escape === '{{' || escape === '[[') { |
- // NOTE: use a space here so the textNode remains; some browsers |
- // (IE) evacipate an empty textNode. |
- node.textContent = ' '; |
- var annote = { |
- bindings: [{ |
- kind: 'text', |
- mode: escape[0], |
- value: v.slice(2, -2) |
- }] |
- }; |
- list.push(annote); |
- return annote; |
- } |
- }, |
- |
- // add annotations gleaned from Element `node` to `list` |
- _parseElementAnnotations: function(element, list) { |
- var annote = { |
- bindings: [], |
- events: [] |
- }; |
- this._parseChildNodesAnnotations(element, annote, list); |
- // TODO(sjmiles): is this for non-ELEMENT nodes? If so, we should |
- // change the contract of this method, or filter these out above. |
- if (element.attributes) { |
- this._parseNodeAttributeAnnotations(element, annote, list); |
- // TODO(sorvell): ad hoc callback for doing work on elements while |
- // leveraging annotator's tree walk. |
- // Consider adding an node callback registry and moving specific |
- // processing out of this module. |
- if (this.prepElement) { |
- this.prepElement(element); |
- } |
- } |
- if (annote.bindings.length || annote.events.length || annote.id) { |
- list.push(annote); |
- } |
- return annote; |
- }, |
- |
- // add annotations gleaned from children of `root` to `list`, `root`'s |
- // `annote` is supplied as it is the annote.parent of added annotations |
- _parseChildNodesAnnotations: function(root, annote, list, callback) { |
- if (root.firstChild) { |
- for (var i=0, node=root.firstChild; node; node=node.nextSibling, i++){ |
- if (node.localName === 'template' && |
- !node.hasAttribute('preserve-content')) { |
- this._parseTemplate(node, i, list, annote); |
- } |
- // |
- var childAnnotation = this._parseNodeAnnotations(node, list, callback); |
- if (childAnnotation) { |
- childAnnotation.parent = annote; |
- childAnnotation.index = i; |
- } |
- } |
- } |
- }, |
- |
- // 1. Parse annotations from the template and memoize them on |
- // content._notes (recurses into nested templates) |
- // 2. Parse template bindings for parent.* properties and memoize them on |
- // content._parentProps |
- // 3. Create bindings in current scope's annotation list to template for |
- // parent props found in template |
- // 4. Remove template.content and store it in annotation list, where it |
- // will be the responsibility of the host to set it back to the template |
- // (this is both an optimization to avoid re-stamping nested template |
- // children and avoids a bug in Chrome where nested template children |
- // upgrade) |
- _parseTemplate: function(node, index, list, parent) { |
- // TODO(sjmiles): simply altering the .content reference didn't |
- // work (there was some confusion, might need verification) |
- var content = document.createDocumentFragment(); |
- content._notes = this.parseAnnotations(node); |
- content.appendChild(node.content); |
- // Special-case treatment of 'parent.*' props for nested templates |
- // Automatically bind `prop` on host to `_parent_prop` on template |
- // for any `parent.prop`'s encountered in template binding; it is |
- // responsibility of the template implementation to forward |
- // these properties as appropriate |
- var bindings = []; |
- this._discoverTemplateParentProps(content); |
- for (var prop in content._parentProps) { |
- bindings.push({ |
- index: index, |
- kind: 'property', |
- mode: '{', |
- name: '_parent_' + prop, |
- value: prop |
- }); |
- } |
- // TODO(sjmiles): using `nar` to avoid unnecessary allocation; |
- // in general the handling of these arrays needs some cleanup |
- // in this module |
- list.push({ |
- bindings: bindings, |
- events: Polymer.nar, |
- templateContent: content, |
- parent: parent, |
- index: index |
- }); |
- }, |
- |
- // Finds all parent.* properties in template content and stores |
- // the path members in content._parentPropChain, which is an array |
- // of maps listing the properties of parent templates required at |
- // each level. Each outer template merges inner _parentPropChains to |
- // propagate inner parent property needs to outer templates. |
- // The top-level parent props from the chain (corresponding to this |
- // template) are stored in content._parentProps. |
- _discoverTemplateParentProps: function(content) { |
- var chain = content._parentPropChain = []; |
- content._notes.forEach(function(n) { |
- // Find all bindings to parent.* and spread them into _parentPropChain |
- n.bindings.forEach(function(b) { |
- var m; |
- if (m = b.value.match(/parent\.((parent\.)*[^.]*)/)) { |
- var parts = m[1].split('.'); |
- for (var i=0; i<parts.length; i++) { |
- var pp = chain[i] || (chain[i] = {}); |
- pp[parts[i]] = true; |
- } |
- } |
- }); |
- // Merge child _parentPropChain[n+1] into this _parentPropChain[n] |
- if (n.templateContent) { |
- var tpp = n.templateContent._parentPropChain; |
- for (var i=1; i<tpp.length; i++) { |
- if (tpp[i]) { |
- var pp = chain[i-1] || (chain[i-1] = {}); |
- Polymer.Base.simpleMixin(pp, tpp[i]); |
- } |
- } |
- } |
- }); |
- // Store this template's parentProps map |
- content._parentProps = chain[0]; |
- }, |
- |
- // add annotation data from attributes to the `annotation` for node `node` |
- // TODO(sjmiles): the distinction between an `annotation` and |
- // `annotation data` is not as clear as it could be |
- // Walk attributes backwards, since removeAttribute can be vetoed by |
- // IE in certain cases (e.g. <input value="foo">), resulting in the |
- // attribute staying in the attributes list |
- _parseNodeAttributeAnnotations: function(node, annotation) { |
- for (var i=node.attributes.length-1, a; (a=node.attributes[i]); i--) { |
- var n = a.name, v = a.value; |
- // id |
- if (n === 'id') { |
- annotation.id = v; |
- } |
- // events (on-*) |
- else if (n.slice(0, 3) === 'on-') { |
- node.removeAttribute(n); |
- annotation.events.push({ |
- name: n.slice(3), |
- value: v |
- }); |
- } |
- // bindings (other attributes) |
- else { |
- var b = this._parseNodeAttributeAnnotation(node, n, v); |
- if (b) { |
- annotation.bindings.push(b); |
- } |
- } |
- } |
- }, |
- |
- // construct annotation data from a generic attribute, or undefined |
- _parseNodeAttributeAnnotation: function(node, n, v) { |
- var mode = '', escape = v.slice(0, 2), name = n; |
- if (escape === '{{' || escape === '[[') { |
- // Mode (one-way or two) |
- mode = escape[0]; |
- v = v.slice(2, -2); |
- // Negate |
- var not = false; |
- if (v[0] == '!') { |
- v = v.substring(1); |
- not = true; |
- } |
- // Attribute or property |
- var kind = 'property'; |
- if (n[n.length-1] == '$') { |
- name = n.slice(0, -1); |
- kind = 'attribute'; |
- } |
- // Custom notification event |
- var notifyEvent, colon; |
- if (mode == '{' && (colon = v.indexOf('::')) > 0) { |
- notifyEvent = v.substring(colon + 2); |
- v = v.substring(0, colon); |
- } |
- // Remove annotation |
- node.removeAttribute(n); |
- // Case hackery: attributes are lower-case, but bind targets |
- // (properties) are case sensitive. Gambit is to map dash-case to |
- // camel-case: `foo-bar` becomes `fooBar`. |
- // Attribute bindings are excepted. |
- if (kind === 'property') { |
- name = Polymer.CaseMap.dashToCamelCase(name); |
- } |
- return { |
- kind: kind, |
- mode: mode, |
- name: name, |
- value: v, |
- negate: not, |
- event: notifyEvent |
- }; |
- } |
- }, |
- |
- // instance-time |
- |
- _localSubTree: function(node, host) { |
- return (node === host) ? node.childNodes : |
- (node.lightChildren || node.childNodes); |
- }, |
- |
- findAnnotatedNode: function(root, annote) { |
- // recursively ascend tree until we hit root |
- var parent = annote.parent && |
- Polymer.Annotations.findAnnotatedNode(root, annote.parent); |
- // unwind the stack, returning the indexed node at each level |
- return !parent ? root : |
- Polymer.Annotations._localSubTree(parent, root)[annote.index]; |
- } |
- |
- }; |
- |
- |
- |
-; |
- |
- (function() { |
- |
- // path fixup for urls in cssText that's expected to |
- // come from a given ownerDocument |
- function resolveCss(cssText, ownerDocument) { |
- return cssText.replace(CSS_URL_RX, function(m, pre, url, post) { |
- return pre + '\'' + |
- resolve(url.replace(/["']/g, ''), ownerDocument) + |
- '\'' + post; |
- }); |
- } |
- |
- // url fixup for urls in an element's attributes made relative to |
- // ownerDoc's base url |
- function resolveAttrs(element, ownerDocument) { |
- for (var name in URL_ATTRS) { |
- var a$ = URL_ATTRS[name]; |
- for (var i=0, l=a$.length, a, at, v; (i<l) && (a=a$[i]); i++) { |
- if (name === '*' || element.localName === name) { |
- at = element.attributes[a]; |
- v = at && at.value; |
- if (v && (v.search(BINDING_RX) < 0)) { |
- at.value = (a === 'style') ? |
- resolveCss(v, ownerDocument) : |
- resolve(v, ownerDocument); |
- } |
- } |
- } |
- } |
- } |
- |
- function resolve(url, ownerDocument) { |
- var resolver = getUrlResolver(ownerDocument); |
- resolver.href = url; |
- return resolver.href || url; |
- } |
- |
- var tempDoc; |
- var tempDocBase; |
- function resolveUrl(url, baseUri) { |
- if (!tempDoc) { |
- tempDoc = document.implementation.createHTMLDocument('temp'); |
- tempDocBase = tempDoc.createElement('base'); |
- tempDoc.head.appendChild(tempDocBase); |
- } |
- tempDocBase.href = baseUri; |
- return resolve(url, tempDoc); |
- } |
- |
- function getUrlResolver(ownerDocument) { |
- return ownerDocument.__urlResolver || |
- (ownerDocument.__urlResolver = ownerDocument.createElement('a')); |
- } |
- |
- var CSS_URL_RX = /(url\()([^)]*)(\))/g; |
- var URL_ATTRS = { |
- '*': ['href', 'src', 'style', 'url'], |
- form: ['action'] |
- }; |
- var BINDING_RX = /\{\{|\[\[/; |
- |
- // exports |
- Polymer.ResolveUrl = { |
- resolveCss: resolveCss, |
- resolveAttrs: resolveAttrs, |
- resolveUrl: resolveUrl |
- }; |
- |
- })(); |
- |
- |
-; |
- |
-/** |
- * Scans a template to produce an annotation object that stores expression |
- * metadata along with information to associate the metadata with nodes in an |
- * instance. |
- * |
- * Elements with `id` in the template are noted and marshaled into an |
- * the `$` hash in an instance. |
- * |
- * Example |
- * |
- * <template> |
- * <div id="foo"></div> |
- * </template> |
- * <script> |
- * Polymer({ |
- * task: function() { |
- * this.$.foo.style.color = 'red'; |
- * } |
- * }); |
- * </script> |
- * |
- * Other expressions that are noted include: |
- * |
- * Double-mustache annotations in text content. The annotation must be the only |
- * content in the tag, compound expressions are not (currently) supported. |
- * |
- * <[tag]>{{path.to.host.property}}<[tag]> |
- * |
- * Double-mustache annotations in an attribute. |
- * |
- * <[tag] someAttribute="{{path.to.host.property}}"><[tag]> |
- * |
- * Only immediate host properties can automatically trigger side-effects. |
- * Setting `host.path` in the example above triggers the binding, setting |
- * `host.path.to.host.property` does not. |
- * |
- * `on-` style event declarations. |
- * |
- * <[tag] on-<event-name>="{{hostMethodName}}"><[tag]> |
- * |
- * Note: **the `annotations` feature does not actually implement the behaviors |
- * associated with these expressions, it only captures the data**. |
- * |
- * Other optional features contain actual data implementations. |
- * |
- * @class standard feature: annotations |
- */ |
- |
-/* |
- |
-Scans a template to produce an annotation map that stores expression metadata |
-and information that associates the metadata to nodes in a template instance. |
- |
-Supported annotations are: |
- |
- * id attributes |
- * binding annotations in text nodes |
- * double-mustache expressions: {{expression}} |
- * double-bracket expressions: [[expression]] |
- * binding annotations in attributes |
- * attribute-bind expressions: name="{{expression}} || [[expression]]" |
- * property-bind expressions: name*="{{expression}} || [[expression]]" |
- * property-bind expressions: name:="expression" |
- * event annotations |
- * event delegation directives: on-<eventName>="expression" |
- |
-Generated data-structure: |
- |
- [ |
- { |
- id: '<id>', |
- events: [ |
- { |
- mode: ['auto'|''], |
- name: '<name>' |
- value: '<expression>' |
- }, ... |
- ], |
- bindings: [ |
- { |
- kind: ['text'|'attribute'|'property'], |
- mode: ['auto'|''], |
- name: '<name>' |
- value: '<expression>' |
- }, ... |
- ], |
- // TODO(sjmiles): confusingly, this is annotation-parent, not node-parent |
- parent: <reference to parent annotation>, |
- index: <integer index in parent's childNodes collection> |
- }, |
- ... |
- ] |
- |
-TODO(sjmiles): this module should produce either syntactic metadata |
-(e.g. double-mustache, double-bracket, star-attr), or semantic metadata |
-(e.g. manual-bind, auto-bind, property-bind). Right now it's half and half. |
- |
-*/ |
- |
- Polymer.Base._addFeature({ |
- |
- // registration-time |
- |
- _prepAnnotations: function() { |
- if (!this._template) { |
- this._notes = []; |
- } else { |
- // TODO(sorvell): ad hoc method of plugging behavior into Annotations |
- Polymer.Annotations.prepElement = this._prepElement.bind(this); |
- this._notes = Polymer.Annotations.parseAnnotations(this._template); |
- Polymer.Annotations.prepElement = null; |
- } |
- }, |
- |
- _prepElement: function(element) { |
- Polymer.ResolveUrl.resolveAttrs(element, this._template.ownerDocument); |
- }, |
- |
- // instance-time |
- |
- findAnnotatedNode: Polymer.Annotations.findAnnotatedNode, |
- |
- // marshal all teh things |
- _marshalAnnotationReferences: function() { |
- if (this._template) { |
- this._marshalIdNodes(); |
- this._marshalAnnotatedNodes(); |
- this._marshalAnnotatedListeners(); |
- } |
- }, |
- |
- // push configuration references at configure time |
- _configureAnnotationReferences: function() { |
- this._configureTemplateContent(); |
- }, |
- |
- // nested template contents have been stored prototypically to avoid |
- // unnecessary duplication, here we put references to the |
- // indirected contents onto the nested template instances |
- _configureTemplateContent: function() { |
- this._notes.forEach(function(note) { |
- if (note.templateContent) { |
- var template = this.findAnnotatedNode(this.root, note); |
- template._content = note.templateContent; |
- } |
- }, this); |
- }, |
- |
- // construct `$` map (from id annotations) |
- _marshalIdNodes: function() { |
- this.$ = {}; |
- this._notes.forEach(function(a) { |
- if (a.id) { |
- this.$[a.id] = this.findAnnotatedNode(this.root, a); |
- } |
- }, this); |
- }, |
- |
- // concretize `_nodes` map (from anonymous annotations) |
- _marshalAnnotatedNodes: function() { |
- if (this._nodes) { |
- this._nodes = this._nodes.map(function(a) { |
- return this.findAnnotatedNode(this.root, a); |
- }, this); |
- } |
- }, |
- |
- // install event listeners (from event annotations) |
- _marshalAnnotatedListeners: function() { |
- this._notes.forEach(function(a) { |
- if (a.events && a.events.length) { |
- var node = this.findAnnotatedNode(this.root, a); |
- a.events.forEach(function(e) { |
- this.listen(node, e.name, e.value); |
- }, this); |
- } |
- }, this); |
- } |
- |
- }); |
- |
- |
-; |
- |
-(function(scope) { |
- |
- 'use strict'; |
- |
- var async = scope.Base.async; |
- |
- var Gestures = { |
- gestures: {}, |
- |
- // automate the event listeners for the native events |
- // TODO(dfreedm): add a way to remove handlers. |
- add: function(evType, node, handler) { |
- // listen for events in order to "recognize" this event |
- var g = this.gestures[evType]; |
- var gn = '_' + evType; |
- var info = {started: false, abortTrack: false, oneshot: false}; |
- if (g && !node[gn]) { |
- if (g.touchaction) { |
- this._setupTouchAction(node, g.touchaction, info); |
- } |
- for (var i = 0, n, sn, fn; i < g.deps.length; i++) { |
- n = g.deps[i]; |
- fn = g[n].bind(g, info); |
- sn = '_' + evType + '-' + n; |
- // store the handler on the node for future removal |
- node[sn] = fn; |
- node.addEventListener(n, fn); |
- } |
- node[gn] = 0; |
- } |
- // listen for the gesture event |
- node[gn]++; |
- node.addEventListener(evType, handler); |
- }, |
- |
- remove: function(evType, node, handler) { |
- var g = this.gestures[evType]; |
- var gn = '_' + evType; |
- if (g && node[gn]) { |
- for (var i = 0, n, sn, fn; i < g.deps.length; i++) { |
- n = g.deps[i]; |
- sn = '_' + evType + '-' + n; |
- fn = node[sn]; |
- if (fn){ |
- node.removeEventListener(n, fn); |
- // remove stored handler to allow GC |
- node[sn] = undefined; |
- } |
- } |
- node[gn] = node[gn] ? (node[gn] - 1) : 0; |
- node.removeEventListener(evType, handler); |
- } |
- }, |
- |
- register: function(recog) { |
- this.gestures[recog.name] = recog; |
- }, |
- |
- // touch will make synthetic mouse events |
- // preventDefault on touchend will cancel them, |
- // but this breaks <input> focus and link clicks |
- // Disabling "mouse" handlers for 500ms is enough |
- |
- _cancelFunction: null, |
- |
- cancelNextClick: function(timeout) { |
- if (!this._cancelFunction) { |
- timeout = timeout || 500; |
- var self = this; |
- var reset = function() { |
- var cfn = self._cancelFunction; |
- if (cfn) { |
- clearTimeout(cfn.id); |
- document.removeEventListener('click', cfn, true); |
- self._cancelFunction = null; |
- } |
- }; |
- var canceller = function(e) { |
- e.tapPrevented = true; |
- reset(); |
- }; |
- canceller.id = setTimeout(reset, timeout); |
- this._cancelFunction = canceller; |
- document.addEventListener('click', canceller, true); |
- } |
- }, |
- |
- // try to use the native touch-action, if it exists |
- _hasNativeTA: typeof document.head.style.touchAction === 'string', |
- |
- // set scrolling direction on node to check later on first move |
- // must call this before adding event listeners! |
- setTouchAction: function(node, value) { |
- if (this._hasNativeTA) { |
- node.style.touchAction = value; |
- } |
- node.touchAction = value; |
- }, |
- |
- _setupTouchAction: function(node, value, info) { |
- // reuse custom value on node if set |
- var ta = node.touchAction; |
- value = ta || value; |
- // set an anchor point to see how far first move is |
- node.addEventListener('touchstart', function(e) { |
- var t = e.changedTouches[0]; |
- info.initialTouch = {x: t.clientX, y: t.clientY}; |
- info.abortTrack = false; |
- info.oneshot = false; |
- }); |
- node.addEventListener('touchmove', function(e) { |
- // only run this once |
- if (info.oneshot) { |
- return; |
- } |
- info.oneshot = true; |
- // "none" means always track |
- if (value === 'none') { |
- return; |
- } |
- // "auto" is default, always scroll |
- // bail-out if touch-action did its job |
- // the touchevent is non-cancelable if the page/area is scrolling |
- if (value === 'auto' || !value || (ta && !e.cancelable)) { |
- info.abortTrack = true; |
- return; |
- } |
- // check first move direction |
- // unfortunately, we can only make the decision in the first move, |
- // so we have to use whatever values are available. |
- // Typically, this can be a really small amount, :( |
- var t = e.changedTouches[0]; |
- var x = t.clientX, y = t.clientY; |
- var dx = Math.abs(info.initialTouch.x - x); |
- var dy = Math.abs(info.initialTouch.y - y); |
- // scroll in x axis, abort track if we move more in x direction |
- if (value === 'pan-x') { |
- info.abortTrack = dx >= dy; |
- // scroll in y axis, abort track if we move more in y direction |
- } else if (value === 'pan-y') { |
- info.abortTrack = dy >= dx; |
- } |
- }); |
- }, |
- |
- fire: function(target, type, detail, bubbles, cancelable) { |
- return target.dispatchEvent( |
- new CustomEvent(type, { |
- detail: detail, |
- bubbles: bubbles, |
- cancelable: cancelable |
- }) |
- ); |
- } |
- |
- }; |
- |
- Gestures.register({ |
- name: 'track', |
- touchaction: 'none', |
- deps: ['mousedown', 'touchmove', 'touchend'], |
- |
- mousedown: function(info, e) { |
- var t = e.currentTarget; |
- var self = this; |
- var movefn = function movefn(e, up) { |
- if (!info.tracking && !up) { |
- // set up tap prevention |
- Gestures.cancelNextClick(); |
- } |
- // first move is 'start', subsequent moves are 'move', mouseup is 'end' |
- var state = up ? 'end' : (!info.started ? 'start' : 'move'); |
- info.started = true; |
- self.fire(t, e, state); |
- e.preventDefault(); |
- }; |
- var upfn = function upfn(e) { |
- // call mousemove function with 'end' state |
- movefn(e, true); |
- info.started = false; |
- // remove the temporary listeners |
- document.removeEventListener('mousemove', movefn); |
- document.removeEventListener('mouseup', upfn); |
- }; |
- // add temporary document listeners as mouse retargets |
- document.addEventListener('mousemove', movefn); |
- document.addEventListener('mouseup', upfn); |
- }, |
- |
- touchmove: function(info, e) { |
- var t = e.currentTarget; |
- var ct = e.changedTouches[0]; |
- // if track was aborted, stop tracking |
- if (info.abortTrack) { |
- return; |
- } |
- e.preventDefault(); |
- // the first track event is sent after some hysteresis with touchmove. |
- // Use `started` state variable to differentiate the "first" move from |
- // the rest to make track.state == 'start' |
- // first move is 'start', subsequent moves are 'move' |
- var state = !info.started ? 'start' : 'move'; |
- info.started = true; |
- this.fire(t, ct, state); |
- }, |
- |
- touchend: function(info, e) { |
- var t = e.currentTarget; |
- var ct = e.changedTouches[0]; |
- // only trackend if track was started and not aborted |
- if (info.started && !info.abortTrack) { |
- // reset started state on up |
- info.started = false; |
- var ne = this.fire(t, ct, 'end'); |
- // iff tracking, always prevent tap |
- e.tapPrevented = true; |
- } |
- }, |
- |
- fire: function(target, touch, state) { |
- return Gestures.fire(target, 'track', { |
- state: state, |
- x: touch.clientX, |
- y: touch.clientY |
- }); |
- } |
- |
- }); |
- |
- // dispatch a *bubbling* "tap" only at the node that is the target of the |
- // generating event. |
- // dispatch *synchronously* so that we can implement prevention of native |
- // actions like links being followed. |
- // |
- // TODO(dfreedm): a tap should not occur when there's too much movement. |
- // Right now, a tap can occur when a touchend happens very far from the |
- // generating touch. |
- // This *should* obviate the need for tapPrevented via track. |
- Gestures.register({ |
- name: 'tap', |
- deps: ['click', 'touchend'], |
- |
- click: function(info, e) { |
- this.forward(e); |
- }, |
- |
- touchend: function(info, e) { |
- Gestures.cancelNextClick(); |
- this.forward(e); |
- }, |
- |
- forward: function(e) { |
- // prevent taps from being generated from events that have been |
- // canceled (e.g. via cancelNextClick) or already handled via |
- // a listener lower in the tree. |
- if (!e.tapPrevented) { |
- e.tapPrevented = true; |
- this.fire(e.target); |
- } |
- }, |
- |
- // fire a bubbling event from the generating target. |
- fire: function(target) { |
- Gestures.fire(target, 'tap', {}, true); |
- } |
- |
- }); |
- |
- scope.Gestures = Gestures; |
- |
-})(Polymer); |
- |
- |
-; |
- |
- /** |
- * Supports `listeners` and `keyPresses` objects. |
- * |
- * Example: |
- * |
- * using('Base', function(Base) { |
- * |
- * Polymer({ |
- * |
- * listeners: { |
- * // `click` events on the host are delegated to `clickHandler` |
- * 'click': 'clickHandler' |
- * }, |
- * |
- * keyPresses: { |
- * // 'ESC' key presses are delegated to `escHandler` |
- * Base.ESC_KEY: 'escHandler' |
- * }, |
- * |
- * ... |
- * |
- * }); |
- * |
- * }); |
- * |
- * @class standard feature: events |
- * |
- */ |
- |
- Polymer.Base._addFeature({ |
- |
- listeners: {}, |
- |
- _listenListeners: function(listeners) { |
- var node, name, key; |
- for (key in listeners) { |
- if (key.indexOf('.') < 0) { |
- node = this; |
- name = key; |
- } else { |
- name = key.split('.'); |
- node = this.$[name[0]]; |
- name = name[1]; |
- } |
- this.listen(node, name, listeners[key]); |
- } |
- }, |
- |
- listen: function(node, eventName, methodName) { |
- var host = this; |
- var handler = function(e) { |
- if (host[methodName]) { |
- host[methodName](e, e.detail); |
- } else { |
- console.warn('[%s].[%s]: event handler [%s] is null in scope (%o)', |
- node.localName, eventName, methodName, host); |
- } |
- }; |
- switch (eventName) { |
- case 'tap': |
- case 'track': |
- Polymer.Gestures.add(eventName, node, handler); |
- break; |
- |
- default: |
- node.addEventListener(eventName, handler); |
- break; |
- } |
- }, |
- |
- keyCodes: { |
- ESC_KEY: 27, |
- ENTER_KEY: 13, |
- LEFT: 37, |
- UP: 38, |
- RIGHT: 39, |
- DOWN: 40, |
- SPACE: 32 |
- } |
- |
- }); |
- |
- |
-; |
- |
-Polymer.Async = (function() { |
- |
- var currVal = 0; |
- var lastVal = 0; |
- var callbacks = []; |
- var twiddle = document.createTextNode(''); |
- |
- function runAsync(callback, waitTime) { |
- if (waitTime > 0) { |
- return ~setTimeout(callback, waitTime); |
- } else { |
- twiddle.textContent = currVal++; |
- callbacks.push(callback); |
- return currVal - 1; |
- } |
- } |
- |
- function cancelAsync(handle) { |
- if (handle < 0) { |
- clearTimeout(~handle); |
- } else { |
- var idx = handle - lastVal; |
- if (idx >= 0) { |
- if (!callbacks[idx]) { |
- throw 'invalid async handle: ' + handle; |
- } |
- callbacks[idx] = null; |
- } |
- } |
- } |
- |
- function atEndOfMicrotask() { |
- var len = callbacks.length; |
- for (var i=0; i<len; i++) { |
- var cb = callbacks[i]; |
- if (cb) { |
- cb(); |
- } |
- } |
- callbacks.splice(0, len); |
- lastVal += len; |
- } |
- |
- new (window.MutationObserver || JsMutationObserver)(atEndOfMicrotask) |
- .observe(twiddle, {characterData: true}) |
- ; |
- |
- // exports |
- |
- return { |
- run: runAsync, |
- cancel: cancelAsync |
- }; |
- |
-})(); |
- |
- |
-; |
- |
-Polymer.Debounce = (function() { |
- |
- // usage |
- |
- // invoke cb.call(this) in 100ms, unless the job is re-registered, |
- // which resets the timer |
- // |
- // this.job = this.debounce(this.job, cb, 100) |
- // |
- // returns a handle which can be used to re-register a job |
- |
- var Async = Polymer.Async; |
- |
- var Debouncer = function(context) { |
- this.context = context; |
- this.boundComplete = this.complete.bind(this); |
- }; |
- |
- Debouncer.prototype = { |
- go: function(callback, wait) { |
- var h; |
- this.finish = function() { |
- Async.cancel(h); |
- }; |
- h = Async.run(this.boundComplete, wait); |
- this.callback = callback; |
- }, |
- stop: function() { |
- if (this.finish) { |
- this.finish(); |
- this.finish = null; |
- } |
- }, |
- complete: function() { |
- if (this.finish) { |
- this.stop(); |
- this.callback.call(this.context); |
- } |
- } |
- }; |
- |
- function debounce(debouncer, callback, wait) { |
- if (debouncer) { |
- debouncer.stop(); |
- } else { |
- debouncer = new Debouncer(this); |
- } |
- debouncer.go(callback, wait); |
- return debouncer; |
- } |
- |
- // exports |
- |
- return debounce; |
- |
-})(); |
- |
- |
-; |
- |
- Polymer.Base._addFeature({ |
- |
- $$: function(slctr) { |
- return Polymer.dom(this.root).querySelector(slctr); |
- }, |
- |
- toggleClass: function(name, bool, node) { |
- node = node || this; |
- if (arguments.length == 1) { |
- bool = !node.classList.contains(name); |
- } |
- if (bool) { |
- node.classList.add(name); |
- } else { |
- node.classList.remove(name); |
- } |
- }, |
- |
- toggleAttribute: function(name, bool, node) { |
- (node || this)[bool ? 'setAttribute' : 'removeAttribute'](name, ''); |
- }, |
- |
- classFollows: function(className, neo, old) { |
- if (old) { |
- old.classList.remove(className); |
- } |
- if (neo) { |
- neo.classList.add(className); |
- } |
- }, |
- |
- attributeFollows: function(name, neo, old) { |
- if (old) { |
- old.removeAttribute(name); |
- } |
- if (neo) { |
- neo.setAttribute(name, ''); |
- } |
- }, |
- |
- getContentChildNodes: function(slctr) { |
- return Polymer.dom(Polymer.dom(this.root).querySelector( |
- slctr || 'content')).getDistributedNodes(); |
- }, |
- |
- getContentChildren: function(slctr) { |
- return this.getContentChildNodes(slctr).filter(function(n) { |
- return (n.nodeType === Node.ELEMENT_NODE); |
- }); |
- }, |
- |
- fire: function(type, detail, options) { |
- options = options || Polymer.nob; |
- var node = options.node || this; |
- var detail = (detail === null || detail === undefined) ? Polymer.nob : detail; |
- var bubbles = options.bubbles === undefined ? true : options.bubbles; |
- var event = new CustomEvent(type, { |
- bubbles: Boolean(bubbles), |
- cancelable: Boolean(options.cancelable), |
- detail: detail |
- }); |
- node.dispatchEvent(event); |
- return event; |
- }, |
- |
- async: function(method, waitTime) { |
- return Polymer.Async.run(method.bind(this), waitTime); |
- }, |
- |
- cancelAsync: function(handle) { |
- Polymer.Async.cancel(handle); |
- }, |
- |
- arrayDelete: function(array, item) { |
- var index = array.indexOf(item); |
- if (index >= 0) { |
- return array.splice(index, 1); |
- } |
- }, |
- |
- transform: function(node, transform) { |
- node.style.webkitTransform = transform; |
- node.style.transform = transform; |
- }, |
- |
- translate3d: function(node, x, y, z) { |
- this.transform(node, 'translate3d(' + x + ',' + y + ',' + z + ')'); |
- }, |
- |
- importHref: function(href, onload, onerror) { |
- var l = document.createElement('link'); |
- l.rel = 'import'; |
- l.href = href; |
- if (onload) { |
- l.onload = onload.bind(this); |
- } |
- if (onerror) { |
- l.onerror = onerror.bind(this); |
- } |
- document.head.appendChild(l); |
- return l; |
- }, |
- |
- create: function(tag, props) { |
- var elt = document.createElement(tag); |
- if (props) { |
- for (var n in props) { |
- elt[n] = props[n]; |
- } |
- } |
- return elt; |
- }, |
- |
- simpleMixin: function(a, b) { |
- for (var i in b) { |
- a[i] = b[i]; |
- } |
- } |
- |
- }); |
- |
- |
-; |
- |
- Polymer.Bind = { |
- |
- // for prototypes (usually) |
- |
- prepareModel: function(model) { |
- model._propertyEffects = {}; |
- model._bindListeners = []; |
- // TODO(sjmiles): no mixin function? |
- var api = this._modelApi; |
- for (var n in api) { |
- model[n] = api[n]; |
- } |
- }, |
- |
- _modelApi: { |
- |
- _notifyChange: function(property) { |
- var eventName = Polymer.CaseMap.camelToDashCase(property) + '-changed'; |
- // TODO(sjmiles): oops, `fire` doesn't exist at this layer |
- this.fire(eventName, { |
- value: this[property] |
- }, {bubbles: false}); |
- }, |
- |
- // TODO(sjmiles): removing _notifyListener from here breaks accessors.html |
- // as a standalone lib. This is temporary, as standard/configure.html |
- // installs it's own version on Polymer.Base, and we need that to work |
- // right now. |
- // NOTE: exists as a hook for processing listeners |
- /* |
- _notifyListener: function(fn, e) { |
- // NOTE: pass e.target because e.target can get lost if this function |
- // is queued asynchrously |
- return fn.call(this, e, e.target); |
- }, |
- */ |
- |
- _propertySet: function(property, value, effects) { |
- var old = this._data[property]; |
- if (old !== value) { |
- this._data[property] = value; |
- if (typeof value == 'object') { |
- this._clearPath(property); |
- } |
- if (effects) { |
- this._effectEffects(property, value, effects, old); |
- } |
- } |
- return old; |
- }, |
- |
- _effectEffects: function(property, value, effects, old) { |
- effects.forEach(function(fx) { |
- //console.log(fx); |
- var fn = Polymer.Bind[fx.kind + 'Effect']; |
- if (fn) { |
- fn.call(this, property, value, fx.effect, old); |
- } |
- }, this); |
- }, |
- |
- _clearPath: function(path) { |
- for (var prop in this._data) { |
- if (prop.indexOf(path + '.') === 0) { |
- this._data[prop] = undefined; |
- } |
- } |
- } |
- |
- }, |
- |
- // a prepared model can acquire effects |
- |
- ensurePropertyEffects: function(model, property) { |
- var fx = model._propertyEffects[property]; |
- if (!fx) { |
- fx = model._propertyEffects[property] = []; |
- } |
- return fx; |
- }, |
- |
- addPropertyEffect: function(model, property, kind, effect) { |
- var fx = this.ensurePropertyEffects(model, property); |
- fx.push({ |
- kind: kind, |
- effect: effect |
- }); |
- }, |
- |
- createBindings: function(model) { |
- //console.group(model.is); |
- // map of properties to effects |
- var fx$ = model._propertyEffects; |
- if (fx$) { |
- // for each property with effects |
- for (var n in fx$) { |
- // array of effects |
- var fx = fx$[n]; |
- // effects have priority |
- fx.sort(this._sortPropertyEffects); |
- // create accessors |
- this._createAccessors(model, n, fx); |
- } |
- } |
- //console.groupEnd(); |
- }, |
- |
- _sortPropertyEffects: (function() { |
- // TODO(sjmiles): EFFECT_ORDER buried this way is not ideal, |
- // but presumably the sort method is going to be a hot path and not |
- // have a `this`. There is also a problematic dependency on effect.kind |
- // values here, which are otherwise pluggable. |
- var EFFECT_ORDER = { |
- 'compute': 0, |
- 'annotation': 1, |
- 'computedAnnotation': 2, |
- 'reflect': 3, |
- 'notify': 4, |
- 'observer': 5, |
- 'complexObserver': 6, |
- 'function': 7 |
- }; |
- return function(a, b) { |
- return EFFECT_ORDER[a.kind] - EFFECT_ORDER[b.kind]; |
- }; |
- })(), |
- |
- // create accessors that implement effects |
- |
- _createAccessors: function(model, property, effects) { |
- var defun = { |
- get: function() { |
- // TODO(sjmiles): elide delegation for performance, good ROI? |
- return this._data[property]; |
- } |
- }; |
- var setter = function(value) { |
- this._propertySet(property, value, effects); |
- }; |
- // ReadOnly properties have a private setter only |
- // TODO(kschaaf): Per current Bind factoring, we shouldn't |
- // be interrogating the prototype here |
- if (model.isReadOnlyProperty && model.isReadOnlyProperty(property)) { |
- //model['_' + property + 'Setter'] = setter; |
- //model['_set_' + property] = setter; |
- model['_set' + this.upper(property)] = setter; |
- } else { |
- defun.set = setter; |
- } |
- Object.defineProperty(model, property, defun); |
- }, |
- |
- upper: function(name) { |
- return name[0].toUpperCase() + name.substring(1); |
- }, |
- |
- _addAnnotatedListener: function(model, index, property, path, event) { |
- var fn = this._notedListenerFactory(property, path, |
- this._isStructured(path), this._isEventBogus); |
- var eventName = event || |
- (Polymer.CaseMap.camelToDashCase(property) + '-changed'); |
- model._bindListeners.push({ |
- index: index, |
- property: property, |
- path: path, |
- changedFn: fn, |
- event: eventName |
- }); |
- }, |
- |
- _isStructured: function(path) { |
- return path.indexOf('.') > 0; |
- }, |
- |
- _isEventBogus: function(e, target) { |
- return e.path && e.path[0] !== target; |
- }, |
- |
- _notedListenerFactory: function(property, path, isStructured, bogusTest) { |
- return function(e, target) { |
- if (!bogusTest(e, target)) { |
- if (e.detail && e.detail.path) { |
- this.notifyPath(this._fixPath(path, property, e.detail.path), |
- e.detail.value); |
- } else { |
- var value = target[property]; |
- if (!isStructured) { |
- this[path] = target[property]; |
- } else { |
- // TODO(kschaaf): dirty check avoids null references when the object has gone away |
- if (this._data[path] != value) { |
- this.setPathValue(path, value); |
- } |
- } |
- } |
- } |
- }; |
- }, |
- |
- // for instances |
- |
- prepareInstance: function(inst) { |
- inst._data = Object.create(null); |
- }, |
- |
- setupBindListeners: function(inst) { |
- inst._bindListeners.forEach(function(info) { |
- // Property listeners: |
- // <node>.on.<property>-changed: <path]> = e.detail.value |
- //console.log('[_setupBindListener]: [%s][%s] listening for [%s][%s-changed]', this.localName, info.path, info.id || info.index, info.property); |
- var node = inst._nodes[info.index]; |
- node.addEventListener(info.event, inst._notifyListener.bind(inst, info.changedFn)); |
- }); |
- } |
- |
- }; |
- |
- |
-; |
- |
- Polymer.Base.extend(Polymer.Bind, { |
- |
- _shouldAddListener: function(effect) { |
- return effect.name && |
- effect.mode === '{' && |
- !effect.negate && |
- effect.kind != 'attribute' |
- ; |
- }, |
- |
- annotationEffect: function(source, value, effect) { |
- if (source != effect.value) { |
- value = this.getPathValue(effect.value); |
- this._data[effect.value] = value; |
- } |
- var calc = effect.negate ? !value : value; |
- return this._applyEffectValue(calc, effect); |
- }, |
- |
- reflectEffect: function(source) { |
- this.reflectPropertyToAttribute(source); |
- }, |
- |
- notifyEffect: function(source) { |
- this._notifyChange(source); |
- }, |
- |
- // Raw effect for extension; effect.function is an actual function |
- functionEffect: function(source, value, effect, old) { |
- effect.function.call(this, source, value, effect, old); |
- }, |
- |
- observerEffect: function(source, value, effect, old) { |
- this[effect.method](value, old); |
- }, |
- |
- complexObserverEffect: function(source, value, effect) { |
- var args = Polymer.Bind._marshalArgs(this._data, effect, source, value); |
- if (args) { |
- this[effect.method].apply(this, args); |
- } |
- }, |
- |
- computeEffect: function(source, value, effect) { |
- var args = Polymer.Bind._marshalArgs(this._data, effect, source, value); |
- if (args) { |
- this[effect.property] = this[effect.method].apply(this, args); |
- } |
- }, |
- |
- annotatedComputationEffect: function(source, value, effect) { |
- var args = Polymer.Bind._marshalArgs(this._data, effect, source, value); |
- if (args) { |
- var computedHost = this._rootDataHost || this; |
- var computedvalue = |
- computedHost[effect.method].apply(computedHost, args); |
- this._applyEffectValue(computedvalue, effect); |
- } |
- }, |
- |
- // path & value are used to fill in wildcard descriptor when effect is |
- // being called as a result of a path notification |
- _marshalArgs: function(model, effect, path, value) { |
- var values = []; |
- var args = effect.args; |
- for (var i=0, l=args.length; i<l; i++) { |
- var arg = args[i]; |
- var name = arg.name; |
- var v = arg.structured ? |
- Polymer.Base.getPathValue(name, model) : model[name]; |
- if (v === undefined) { |
- return; |
- } |
- if (arg.wildcard) { |
- // Only send the actual path changed info if the change that |
- // caused the observer to run matched the wildcard |
- var baseChanged = (name.indexOf(path + '.') === 0); |
- var matches = (effect.arg.name.indexOf(name) === 0 && !baseChanged); |
- values[i] = { |
- path: matches ? path : name, |
- value: matches ? value : v, |
- base: v |
- }; |
- } else { |
- values[i] = v; |
- } |
- } |
- return values; |
- } |
- |
- }); |
- |
- |
-; |
- |
- /** |
- * Support for property side effects. |
- * |
- * Key for effect objects: |
- * |
- * property | ann | anCmp | cmp | obs | cplxOb | description |
- * ---------|-----|-------|-----|-----|--------|---------------------------------------- |
- * method | | X | X | X | X | function name to call on instance |
- * args | | X | X | | X | list of all arg descriptors for fn |
- * arg | | X | X | | X | arg descriptor for effect |
- * property | | | X | X | | property for effect to set or get |
- * name | X | | | | | annotation value (text inside {{...}}) |
- * kind | X | X | | | | binding type (property or attribute) |
- * index | X | X | | | | node index to set |
- * |
- */ |
- |
- Polymer.Base._addFeature({ |
- |
- _addPropertyEffect: function(property, kind, effect) { |
- // TODO(sjmiles): everything to the right of the first '.' is lost, implies |
- // there is some duplicate information flow (not the only sign) |
- var model = property.split('.').shift(); |
- Polymer.Bind.addPropertyEffect(this, model, kind, effect); |
- }, |
- |
- // prototyping |
- |
- _prepEffects: function() { |
- Polymer.Bind.prepareModel(this); |
- this._addAnnotationEffects(this._notes); |
- }, |
- |
- _prepBindings: function() { |
- Polymer.Bind.createBindings(this); |
- }, |
- |
- _addPropertyEffects: function(effects) { |
- if (effects) { |
- for (var n in effects) { |
- var effect = effects[n]; |
- if (effect.observer) { |
- this._addObserverEffect(n, effect.observer); |
- } |
- if (effect.computed) { |
- this._addComputedEffect(n, effect.computed); |
- } |
- if (effect.notify) { |
- this._addPropertyEffect(n, 'notify'); |
- } |
- if (effect.reflectToAttribute) { |
- this._addPropertyEffect(n, 'reflect'); |
- } |
- if (this.isReadOnlyProperty(n)) { |
- // Ensure accessor is created |
- Polymer.Bind.ensurePropertyEffects(this, n); |
- } |
- } |
- } |
- }, |
- |
- _parseMethod: function(expression) { |
- var m = expression.match(/(\w*)\((.*)\)/); |
- if (m) { |
- return { |
- method: m[1], |
- args: m[2].split(/[^\w.*]+/).map(this._parseArg) |
- }; |
- } |
- }, |
- |
- _parseArg: function(arg) { |
- var a = { name: arg }; |
- a.structured = arg.indexOf('.') > 0; |
- if (a.structured) { |
- a.wildcard = (arg.slice(-2) == '.*'); |
- if (a.wildcard) { |
- a.name = arg.slice(0, -2); |
- } |
- } |
- return a; |
- }, |
- |
- _addComputedEffect: function(name, expression) { |
- var sig = this._parseMethod(expression); |
- sig.args.forEach(function(arg) { |
- this._addPropertyEffect(arg.name, 'compute', { |
- method: sig.method, |
- args: sig.args, |
- arg: arg, |
- property: name |
- }); |
- }, this); |
- }, |
- |
- _addObserverEffect: function(property, observer) { |
- this._addPropertyEffect(property, 'observer', { |
- method: observer, |
- property: property |
- }); |
- }, |
- |
- _addComplexObserverEffects: function(observers) { |
- if (observers) { |
- observers.forEach(function(observer) { |
- this._addComplexObserverEffect(observer); |
- }, this); |
- } |
- }, |
- |
- _addComplexObserverEffect: function(observer) { |
- var sig = this._parseMethod(observer); |
- sig.args.forEach(function(arg) { |
- this._addPropertyEffect(arg.name, 'complexObserver', { |
- method: sig.method, |
- args: sig.args, |
- arg: arg |
- }); |
- }, this); |
- }, |
- |
- _addAnnotationEffects: function(notes) { |
- // create a virtual annotation list, must be concretized at instance time |
- this._nodes = []; |
- // process annotations that have been parsed from template |
- notes.forEach(function(note) { |
- // where to find the node in the concretized list |
- var index = this._nodes.push(note) - 1; |
- note.bindings.forEach(function(binding) { |
- this._addAnnotationEffect(binding, index); |
- }, this); |
- }, this); |
- }, |
- |
- _addAnnotationEffect: function(note, index) { |
- // TODO(sjmiles): annotations have 'effects' proper and 'listener' |
- if (Polymer.Bind._shouldAddListener(note)) { |
- // <node>.on.<dash-case-property>-changed: <path> = e.detail.value |
- Polymer.Bind._addAnnotatedListener(this, index, |
- note.name, note.value, note.event); |
- } |
- var sig = this._parseMethod(note.value); |
- if (sig) { |
- this._addAnnotatedComputationEffect(sig, note, index); |
- } else { |
- // capture the node index |
- note.index = index; |
- // discover top-level property (model) from path |
- var model = note.value.split('.').shift(); |
- // add 'annotation' binding effect for property 'model' |
- this._addPropertyEffect(model, 'annotation', note); |
- } |
- }, |
- |
- _addAnnotatedComputationEffect: function(sig, note, index) { |
- sig.args.forEach(function(arg) { |
- this._addPropertyEffect(arg.name, 'annotatedComputation', { |
- kind: note.kind, |
- method: sig.method, |
- args: sig.args, |
- arg: arg, |
- property: note.name, |
- index: index |
- }); |
- }, this); |
- }, |
- |
- // instancing |
- |
- _marshalInstanceEffects: function() { |
- Polymer.Bind.prepareInstance(this); |
- Polymer.Bind.setupBindListeners(this); |
- }, |
- |
- _applyEffectValue: function(value, info) { |
- var node = this._nodes[info.index]; |
- // TODO(sorvell): ideally, the info object is normalized for easy |
- // lookup here. |
- var property = info.property || info.name || 'textContent'; |
- // special processing for 'class' and 'className'; 'class' handled |
- // when attr is serialized. |
- if (info.kind == 'attribute') { |
- this.serializeValueToAttribute(value, property, node); |
- } else { |
- // TODO(sorvell): consider pre-processing this step so we don't need |
- // this lookup. |
- if (property === 'className') { |
- value = this._scopeElementClass(node, value); |
- } |
- return node[property] = value; |
- } |
- } |
- |
- }); |
- |
- |
-; |
- |
- /* |
- Process inputs efficiently via a configure lifecycle callback. |
- Configure is called top-down, host before local dom. Users should |
- implement configure to supply a set of default values for the element by |
- returning an object containing the properties and values to set. |
- |
- Configured values are not immediately set, instead they are set when |
- an element becomes ready, after its local dom is ready. This ensures |
- that any user change handlers are not called before ready time. |
- |
- */ |
- |
- /* |
- Implementation notes: |
- |
- Configured values are collected into _config. At ready time, properties |
- are set to the values in _config. This ensures properties are set child |
- before host and change handlers are called only at ready time. The host |
- will reset a value already propagated to a child, but this is not |
- inefficient because of dirty checking at the set point. |
- |
- Bind notification events are sent when properties are set at ready time |
- and thus received by the host before it is ready. Since notifications result |
- in property updates and this triggers side effects, handling notifications |
- is deferred until ready time. |
- |
- In general, events can be heard before an element is ready. This may occur |
- when a user sends an event in a change handler or listens to a data event |
- directly (on-foo-changed). |
- */ |
- |
- Polymer.Base._addFeature({ |
- |
- // storage for configuration |
- _setupConfigure: function(initialConfig) { |
- this._config = initialConfig || {}; |
- this._handlers = []; |
- }, |
- |
- // static attributes are deserialized into _config |
- _takeAttributes: function() { |
- this._takeAttributesToModel(this._config); |
- }, |
- |
- // at configure time values are stored in _config |
- _configValue: function(name, value) { |
- this._config[name] = value; |
- }, |
- |
- // Override polymer-mini thunk |
- _beforeClientsReady: function() { |
- this._configure(); |
- }, |
- |
- // configure: returns user supplied default property values |
- // combines with _config to create final property values |
- _configure: function() { |
- this._configureAnnotationReferences(); |
- // get individual default values from property configs |
- var config = {}; |
- this._configureProperties(this.properties, config); |
- // behave! |
- this.behaviors.forEach(function(b) { |
- this._configureProperties(b.properties, config); |
- }, this); |
- // get add'l default values from central configure |
- // combine defaults returned from configure with inputs in _config |
- this._mixinConfigure(config, this._config); |
- // this is the new _config, which are the final values to be applied |
- this._config = config; |
- // pass configuration data to bindings |
- this._distributeConfig(this._config); |
- }, |
- |
- _configureProperties: function(properties, config) { |
- for (i in properties) { |
- var c = properties[i]; |
- if (c.value !== undefined) { |
- var value = c.value; |
- if (typeof value == 'function') { |
- // pass existing config values (this._config) to value function |
- value = value.call(this, this._config); |
- } |
- config[i] = value; |
- } |
- } |
- }, |
- |
- _mixinConfigure: function(a, b) { |
- for (var prop in b) { |
- if (!this.isReadOnlyProperty(prop)) { |
- a[prop] = b[prop]; |
- } |
- } |
- }, |
- |
- // distribute config values to bound nodes. |
- _distributeConfig: function(config) { |
- var fx$ = this._propertyEffects; |
- if (fx$) { |
- for (var p in config) { |
- var fx = fx$[p]; |
- if (fx) { |
- for (var i=0, l=fx.length, x; (i<l) && (x=fx[i]); i++) { |
- if (x.kind === 'annotation') { |
- var node = this._nodes[x.effect.index]; |
- // seeding configuration only |
- if (node._configValue) { |
- var value = (p === x.effect.value) ? config[p] : |
- this.getPathValue(x.effect.value, config); |
- node._configValue(x.effect.name, value); |
- } |
- } |
- } |
- } |
- } |
- } |
- }, |
- |
- // Override polymer-mini thunk |
- _afterClientsReady: function() { |
- this._applyConfig(this._config); |
- this._flushHandlers(); |
- }, |
- |
- // NOTE: values are already propagated to children via |
- // _distributeConfig so propagation triggered by effects here is |
- // redundant, but safe due to dirty checking |
- _applyConfig: function(config) { |
- for (var n in config) { |
- // Don't stomp on values that may have been set by other side effects |
- if (this[n] === undefined) { |
- // Call _propertySet for any properties with accessors, which will |
- // initialize read-only properties also |
- // TODO(kschaaf): consider passing fromAbove here to prevent |
- // unnecessary notify for: 1) possible perf, 2) debuggability |
- var effects = this._propertyEffects[n]; |
- if (effects) { |
- this._propertySet(n, config[n], effects); |
- } else { |
- this[n] = config[n]; |
- } |
- } |
- } |
- }, |
- |
- // NOTE: Notifications can be processed before ready since |
- // they are sent at *child* ready time. Since notifications cause side |
- // effects and side effects must not be processed before ready time, |
- // handling is queue/defered until then. |
- _notifyListener: function(fn, e) { |
- if (!this._clientsReadied) { |
- this._queueHandler([fn, e, e.target]); |
- } else { |
- return fn.call(this, e, e.target); |
- } |
- }, |
- |
- _queueHandler: function(args) { |
- this._handlers.push(args); |
- }, |
- |
- _flushHandlers: function() { |
- var h$ = this._handlers; |
- for (var i=0, l=h$.length, h; (i<l) && (h=h$[i]); i++) { |
- h[0].call(this, h[1], h[2]); |
- } |
- } |
- |
- }); |
- |
- |
-; |
- |
- /** |
- * Changes to an object sub-field (aka "path") via a binding |
- * (e.g. `<x-foo value="{{item.subfield}}"`) will notify other elements bound to |
- * the same object automatically. |
- * |
- * When modifying a sub-field of an object imperatively |
- * (e.g. `this.item.subfield = 42`), in order to have the new value propagated |
- * to other elements, a special `setPathValue(path, value)` API is provided. |
- * `setPathValue` sets the object field at the path specified, and then notifies the |
- * binding system so that other elements bound to the same path will update. |
- * |
- * Example: |
- * |
- * Polymer({ |
- * |
- * is: 'x-date', |
- * |
- * properties: { |
- * date: { |
- * type: Object, |
- * notify: true |
- * } |
- * }, |
- * |
- * attached: function() { |
- * this.date = {}; |
- * setInterval(function() { |
- * var d = new Date(); |
- * // Required to notify elements bound to date of changes to sub-fields |
- * // this.date.seconds = d.getSeconds(); <-- Will not notify |
- * this.setPathValue('date.seconds', d.getSeconds()); |
- * this.setPathValue('date.minutes', d.getMinutes()); |
- * this.setPathValue('date.hours', d.getHours() % 12); |
- * }.bind(this), 1000); |
- * } |
- * |
- * }); |
- * |
- * Allows bindings to `date` sub-fields to update on changes: |
- * |
- * <x-date date="{{date}}"></x-date> |
- * |
- * Hour: <span>{{date.hours}}</span> |
- * Min: <span>{{date.minutes}}</span> |
- * Sec: <span>{{date.seconds}}</span> |
- * |
- * @class data feature: path notification |
- */ |
- |
- Polymer.Base._addFeature({ |
- /** |
- Notify that a path has changed. For example: |
- |
- this.item.user.name = 'Bob'; |
- this.notifyPath('item.user.name', this.item.user.name); |
- |
- Returns true if notification actually took place, based on |
- a dirty check of whether the new value was already known |
- */ |
- notifyPath: function(path, value, fromAbove) { |
- var old = this._propertySet(path, value); |
- // manual dirty checking for now... |
- if (old !== value) { |
- // console.group((this.localName || this.dataHost.id + '-' + this.dataHost.dataHost.index) + '#' + (this.id || this.index) + ' ' + path, value); |
- // Take path effects at this level for exact path matches, |
- // and notify down for any bindings to a subset of this path |
- this._pathEffector(path, value); |
- // Send event to notify the path change upwards |
- // Optimization: don't notify up if we know the notification |
- // is coming from above already (avoid wasted event dispatch) |
- if (!fromAbove) { |
- // TODO(sorvell): should only notify if notify: true? |
- this._notifyPath(path, value); |
- } |
- // console.groupEnd((this.localName || this.dataHost.id + '-' + this.dataHost.dataHost.index) + '#' + (this.id || this.index) + ' ' + path, value); |
- } |
- }, |
- |
- /** |
- Convienence method for setting a value to a path and calling |
- notify path |
- */ |
- setPathValue: function(path, value) { |
- var parts = path.split('.'); |
- if (parts.length > 1) { |
- var last = parts.pop(); |
- var prop = this; |
- while (parts.length) { |
- prop = prop[parts.shift()]; |
- if (!prop) { |
- return; |
- } |
- } |
- // TODO(kschaaf): want dirty-check here? |
- // if (prop[last] !== value) { |
- prop[last] = value; |
- this.notifyPath(path, value); |
- // } |
- } else { |
- this[path] = value; |
- } |
- }, |
- |
- getPathValue: function(path, root) { |
- var parts = path.split('.'); |
- var last = parts.pop(); |
- var prop = root || this; |
- while (parts.length) { |
- prop = prop[parts.shift()]; |
- if (!prop) { |
- return; |
- } |
- } |
- return prop[last]; |
- }, |
- |
- // TODO(kschaaf): This machine can be optimized to memoize compiled path |
- // effectors as new paths are notified for performance, since it involves |
- // a fair amount of runtime lookup |
- _pathEffector: function(path, value) { |
- // get root property |
- var model = this._modelForPath(path); |
- // search property effects of the root property for 'annotation' effects |
- var fx$ = this._propertyEffects[model]; |
- if (fx$) { |
- fx$.forEach(function(fx) { |
- var fxFn = this[fx.kind + 'PathEffect']; |
- if (fxFn) { |
- fxFn.call(this, path, value, fx.effect); |
- } |
- }, this); |
- } |
- // notify runtime-bound paths |
- if (this._boundPaths) { |
- this._notifyBoundPaths(path, value); |
- } |
- }, |
- |
- annotationPathEffect: function(path, value, effect) { |
- if (effect.value === path || effect.value.indexOf(path + '.') === 0) { |
- // TODO(sorvell): ideally the effect function is on this prototype |
- // so we don't have to call it like this. |
- Polymer.Bind.annotationEffect.call(this, path, value, effect); |
- } else if ((path.indexOf(effect.value + '.') === 0) && !effect.negate) { |
- // locate the bound node |
- var node = this._nodes[effect.index]; |
- if (node && node.notifyPath) { |
- var p = this._fixPath(effect.name , effect.value, path); |
- node.notifyPath(p, value, true); |
- } |
- } |
- }, |
- |
- complexObserverPathEffect: function(path, value, effect) { |
- if (this._pathMatchesEffect(path, effect)) { |
- Polymer.Bind.complexObserverEffect.call(this, path, value, effect); |
- } |
- }, |
- |
- computePathEffect: function(path, value, effect) { |
- if (this._pathMatchesEffect(path, effect)) { |
- Polymer.Bind.computeEffect.call(this, path, value, effect); |
- } |
- }, |
- |
- annotatedComputationPathEffect: function(path, value, effect) { |
- if (this._pathMatchesEffect(path, effect)) { |
- Polymer.Bind.annotatedComputationEffect.call(this, path, value, effect); |
- } |
- }, |
- |
- _pathMatchesEffect: function(path, effect) { |
- var effectArg = effect.arg.name; |
- return (effectArg == path) || |
- (effectArg.indexOf(path + '.') === 0) || |
- (effect.arg.wildcard && path.indexOf(effectArg) === 0); |
- }, |
- |
- bindPaths: function(to, from) { |
- this._boundPaths = this._boundPaths || {}; |
- if (from) { |
- this._boundPaths[to] = from; |
- // this.setPathValue(to, this.getPathValue(from)); |
- } else { |
- this.unbindPath(to); |
- // this.setPathValue(to, from); |
- } |
- }, |
- |
- unbindPaths: function(path) { |
- if (this._boundPaths) { |
- delete this._boundPaths[path]; |
- } |
- }, |
- |
- _notifyBoundPaths: function(path, value) { |
- var from, to; |
- for (var a in this._boundPaths) { |
- var b = this._boundPaths[a]; |
- if (path.indexOf(a + '.') == 0) { |
- from = a; |
- to = b; |
- break; |
- } |
- if (path.indexOf(b + '.') == 0) { |
- from = b; |
- to = a; |
- break; |
- } |
- } |
- if (from && to) { |
- var p = this._fixPath(to, from, path); |
- this.notifyPath(p, value); |
- } |
- }, |
- |
- _fixPath: function(property, root, path) { |
- return property + path.slice(root.length); |
- }, |
- |
- _notifyPath: function(path, value) { |
- var rootName = this._modelForPath(path); |
- var dashCaseName = Polymer.CaseMap.camelToDashCase(rootName); |
- var eventName = dashCaseName + this._EVENT_CHANGED; |
- this.fire(eventName, { |
- path: path, |
- value: value |
- }, {bubbles: false}); |
- }, |
- |
- _modelForPath: function(path) { |
- return path.split('.').shift(); |
- }, |
- |
- _EVENT_CHANGED: '-changed', |
- |
- }); |
- |
- |
-; |
- |
- Polymer.Base._addFeature({ |
- |
- resolveUrl: function(url) { |
- // TODO(sorvell): do we want to put the module reference on the prototype? |
- var module = Polymer.DomModule.import(this.is); |
- var root = ''; |
- if (module) { |
- var assetPath = module.getAttribute('assetpath') || ''; |
- root = Polymer.ResolveUrl.resolveUrl(assetPath, module.ownerDocument.baseURI); |
- } |
- return Polymer.ResolveUrl.resolveUrl(url, root); |
- } |
- |
- }); |
- |
- |
-; |
- |
-/* |
- Extremely simple css parser. Intended to be not more than what we need |
- and definitely not necessarly correct =). |
-*/ |
-(function() { |
- |
- // given a string of css, return a simple rule tree |
- function parse(text) { |
- text = clean(text); |
- return parseCss(lex(text), text); |
- } |
- |
- // remove stuff we don't care about that may hinder parsing |
- function clean(cssText) { |
- return cssText.replace(rx.comments, '').replace(rx.port, ''); |
- } |
- |
- // super simple {...} lexer that returns a node tree |
- function lex(text) { |
- var root = {start: 0, end: text.length}; |
- var n = root; |
- for (var i=0, s=0, l=text.length; i < l; i++) { |
- switch (text[i]) { |
- case OPEN_BRACE: |
- //console.group(i); |
- if (!n.rules) { |
- n.rules = []; |
- } |
- var p = n; |
- var previous = p.rules[p.rules.length-1]; |
- n = {start: i+1, parent: p, previous: previous}; |
- p.rules.push(n); |
- break; |
- case CLOSE_BRACE: |
- //console.groupEnd(n.start); |
- n.end = i+1; |
- n = n.parent || root; |
- break; |
- } |
- } |
- return root; |
- } |
- |
- // add selectors/cssText to node tree |
- function parseCss(node, text) { |
- var t = text.substring(node.start, node.end-1); |
- node.cssText = t.trim(); |
- if (node.parent) { |
- var ss = node.previous ? node.previous.end : node.parent.start; |
- t = text.substring(ss, node.start-1); |
- // TODO(sorvell): ad hoc; make selector include only after last ; |
- // helps with mixin syntax |
- t = t.substring(t.lastIndexOf(';')+1); |
- node.selector = t.trim(); |
- } |
- var r$ = node.rules; |
- if (r$) { |
- for (var i=0, l=r$.length, r; (i<l) && (r=r$[i]); i++) { |
- parseCss(r, text); |
- } |
- } |
- return node; |
- } |
- |
- // stringify parsed css. |
- function stringify(node, text) { |
- text = text || ''; |
- // calc rule cssText |
- var cssText = ''; |
- if (node.cssText || node.rules) { |
- var r$ = node.rules; |
- if (r$ && !hasMixinRules(r$)) { |
- for (var i=0, l=r$.length, r; (i<l) && (r=r$[i]); i++) { |
- cssText = stringify(r, cssText); |
- } |
- } else { |
- cssText = removeCustomProps(node.cssText).trim(); |
- if (cssText) { |
- cssText = ' ' + cssText + '\n'; |
- } |
- } |
- } |
- // emit rule iff there is cssText |
- if (cssText) { |
- if (node.selector) { |
- text += node.selector + ' ' + OPEN_BRACE + '\n'; |
- } |
- text += cssText; |
- if (node.selector) { |
- text += CLOSE_BRACE + '\n\n'; |
- } |
- } |
- return text; |
- } |
- |
- var OPEN_BRACE = '{'; |
- var CLOSE_BRACE = '}'; |
- |
- function hasMixinRules(rules) { |
- return (rules[0].selector.indexOf(VAR_START) >= 0); |
- } |
- |
- function removeCustomProps(cssText) { |
- return cssText |
- .replace(rx.customProp, '') |
- .replace(rx.mixinProp, '') |
- .replace(rx.mixinApply, ''); |
- } |
- |
- var VAR_START = '--'; |
- |
- // helper regexp's |
- var rx = { |
- comments: /\/\*[^*]*\*+([^/*][^*]*\*+)*\//gim, |
- port: /@import[^;]*;/gim, |
- customProp: /--[^;{]*?:[^{};]*?;/gim, |
- mixinProp: /--[^;{]*?:[^{;]*?{[^}]*?}/gim, |
- mixinApply: /@mixin[\s]*\([^)]*?\)[\s]*;/gim |
- }; |
- |
- // exports |
- Polymer.CssParse = { |
- parse: parse, |
- stringify: stringify |
- }; |
- |
-})(); |
- |
- |
-; |
- |
- (function() { |
- |
- function toCssText(rules, callback) { |
- if (typeof rules === 'string') { |
- rules = Polymer.CssParse.parse(rules); |
- } |
- if (callback) { |
- forEachStyleRule(rules, callback); |
- } |
- return Polymer.CssParse.stringify(rules); |
- } |
- |
- function forEachStyleRule(node, cb) { |
- var s = node.selector; |
- var skipRules = false; |
- if (s) { |
- if ((s.indexOf(AT_RULE) !== 0) && (s.indexOf(MIXIN_SELECTOR) !== 0)) { |
- cb(node); |
- } |
- skipRules = (s.indexOf(KEYFRAME_RULE) >= 0) || |
- (s.indexOf(MIXIN_SELECTOR) >= 0); |
- } |
- var r$ = node.rules; |
- if (r$ && !skipRules) { |
- for (var i=0, l=r$.length, r; (i<l) && (r=r$[i]); i++) { |
- forEachStyleRule(r, cb); |
- } |
- } |
- } |
- |
- // add a string of cssText to the document. |
- function applyCss(cssText, moniker, target, lowPriority) { |
- var style = document.createElement('style'); |
- if (moniker) { |
- style.setAttribute('scope', moniker); |
- } |
- style.textContent = cssText; |
- target = target || document.head; |
- if (lowPriority) { |
- var n$ = target.querySelectorAll('style[scope]'); |
- var ref = n$.length ? n$[n$.length-1].nextSibling : target.firstChild; |
- target.insertBefore(style, ref); |
- } else { |
- target.appendChild(style); |
- } |
- return style; |
- } |
- |
- var AT_RULE = '@'; |
- var KEYFRAME_RULE = 'keyframe'; |
- var MIXIN_SELECTOR = '--'; |
- |
- // exports |
- Polymer.StyleUtil = { |
- parser: Polymer.CssParse, |
- applyCss: applyCss, |
- forEachStyleRule: forEachStyleRule, |
- toCssText: toCssText |
- }; |
- |
- })(); |
- |
- |
-; |
- |
- (function() { |
- |
- /* Transforms ShadowDOM styling into ShadyDOM styling |
- |
- * scoping: |
- |
- * elements in scope get scoping selector class="x-foo-scope" |
- * selectors re-written as follows: |
- |
- div button -> div.x-foo-scope button.x-foo-scope |
- |
- * :host -> scopeName |
- |
- * :host(...) -> scopeName... |
- |
- * ::content -> ' ' NOTE: requires use of scoping selector and selectors |
- cannot otherwise be scoped: |
- e.g. :host ::content > .bar -> x-foo > .bar |
- |
- * ::shadow, /deep/: processed simimlar to ::content |
- |
- * :host-context(...): NOT SUPPORTED |
- |
- */ |
- |
- // Given a node and scope name, add a scoping class to each node |
- // in the tree. This facilitates transforming css into scoped rules. |
- function transformDom(node, scope, useAttr, shouldRemoveScope) { |
- _transformDom(node, scope || '', useAttr, shouldRemoveScope); |
- } |
- |
- function _transformDom(node, selector, useAttr, shouldRemoveScope) { |
- if (node.setAttribute) { |
- transformElement(node, selector, useAttr, shouldRemoveScope); |
- } |
- var c$ = Polymer.dom(node).childNodes; |
- for (var i=0; i<c$.length; i++) { |
- _transformDom(c$[i], selector, useAttr, shouldRemoveScope); |
- } |
- } |
- |
- function transformElement(element, scope, useAttr, shouldRemoveScope) { |
- if (useAttr) { |
- if (shouldRemoveScope) { |
- element.removeAttribute(SCOPE_NAME); |
- } else { |
- element.setAttribute(SCOPE_NAME, scope); |
- } |
- } else { |
- // note: if using classes, we add both the general 'style-scope' class |
- // as well as the specific scope. This enables easy filtering of all |
- // `style-scope` elements |
- if (scope) { |
- if (shouldRemoveScope) { |
- element.classList.remove(SCOPE_NAME, scope); |
- } else { |
- element.classList.add(SCOPE_NAME, scope); |
- } |
- } |
- } |
- } |
- |
- function transformHost(host, scope) { |
- } |
- |
- // Given a string of cssText and a scoping string (scope), returns |
- // a string of scoped css where each selector is transformed to include |
- // a class created from the scope. ShadowDOM selectors are also transformed |
- // (e.g. :host) to use the scoping selector. |
- function transformCss(rules, scope, ext, callback, useAttr) { |
- var hostScope = calcHostScope(scope, ext); |
- scope = calcElementScope(scope, useAttr); |
- return Polymer.StyleUtil.toCssText(rules, function(rule) { |
- transformRule(rule, scope, hostScope); |
- if (callback) { |
- callback(rule, scope, hostScope); |
- } |
- }); |
- } |
- |
- function calcElementScope(scope, useAttr) { |
- if (scope) { |
- return useAttr ? |
- CSS_ATTR_PREFIX + scope + CSS_ATTR_SUFFIX : |
- CSS_CLASS_PREFIX + scope; |
- } else { |
- return ''; |
- } |
- } |
- |
- function calcHostScope(scope, ext) { |
- return ext ? '[is=' + scope + ']' : scope; |
- } |
- |
- function transformRule(rule, scope, hostScope) { |
- _transformRule(rule, transformComplexSelector, |
- scope, hostScope); |
- } |
- |
- // transforms a css rule to a scoped rule. |
- function _transformRule(rule, transformer, scope, hostScope) { |
- var p$ = rule.selector.split(COMPLEX_SELECTOR_SEP); |
- for (var i=0, l=p$.length, p; (i<l) && (p=p$[i]); i++) { |
- p$[i] = transformer(p, scope, hostScope); |
- } |
- rule.selector = p$.join(COMPLEX_SELECTOR_SEP); |
- } |
- |
- function transformComplexSelector(selector, scope, hostScope) { |
- var stop = false; |
- selector = selector.replace(SIMPLE_SELECTOR_SEP, function(m, c, s) { |
- if (!stop) { |
- var o = transformCompoundSelector(s, c, scope, hostScope); |
- if (o.stop) { |
- stop = true; |
- } |
- c = o.combinator; |
- s = o.value; |
- } |
- return c + s; |
- }); |
- return selector; |
- } |
- |
- function transformCompoundSelector(selector, combinator, scope, hostScope) { |
- // replace :host with host scoping class |
- var jumpIndex = selector.search(SCOPE_JUMP); |
- if (selector.indexOf(HOST) >=0) { |
- // :host(...) |
- selector = selector.replace(HOST_PAREN, function(m, host, paren) { |
- return hostScope + paren; |
- }); |
- // now normal :host |
- selector = selector.replace(HOST, hostScope); |
- // replace other selectors with scoping class |
- } else if (jumpIndex !== 0) { |
- selector = scope ? transformSimpleSelector(selector, scope) : selector; |
- } |
- // remove left-side combinator when dealing with ::content. |
- if (selector.indexOf(CONTENT) >= 0) { |
- combinator = ''; |
- } |
- // process scope jumping selectors up to the scope jump and then stop |
- // e.g. .zonk ::content > .foo ==> .zonk.scope > .foo |
- var stop; |
- if (jumpIndex >= 0) { |
- selector = selector.replace(SCOPE_JUMP, ' '); |
- stop = true; |
- } |
- return {value: selector, combinator: combinator, stop: stop}; |
- } |
- |
- function transformSimpleSelector(selector, scope) { |
- var p$ = selector.split(PSEUDO_PREFIX); |
- p$[0] += scope; |
- return p$.join(PSEUDO_PREFIX); |
- } |
- |
- function transformRootRule(rule) { |
- _transformRule(rule, transformRootSelector); |
- } |
- |
- function transformRootSelector(selector) { |
- return selector.match(SCOPE_JUMP) ? |
- transformComplexSelector(selector) : |
- selector.trim() + SCOPE_ROOT_SELECTOR; |
- } |
- |
- var SCOPE_NAME = 'style-scope'; |
- var SCOPE_ROOT_SELECTOR = ':not([' + SCOPE_NAME + '])' + |
- ':not(.' + SCOPE_NAME + ')'; |
- var COMPLEX_SELECTOR_SEP = ','; |
- var SIMPLE_SELECTOR_SEP = /(^|[\s>+~]+)([^\s>+~]+)/g; |
- var HOST = ':host'; |
- // NOTE: this supports 1 nested () pair for things like |
- // :host(:not([selected]), more general support requires |
- // parsing which seems like overkill |
- var HOST_PAREN = /(\:host)(?:\(((?:\([^)(]*\)|[^)(]*)+?)\))/g; |
- var CONTENT = '::content'; |
- var SCOPE_JUMP = /\:\:content|\:\:shadow|\/deep\//; |
- var CSS_CLASS_PREFIX = '.'; |
- var CSS_ATTR_PREFIX = '[' + SCOPE_NAME + '~='; |
- var CSS_ATTR_SUFFIX = ']'; |
- var PSEUDO_PREFIX = ':'; |
- |
- // exports |
- Polymer.StyleTransformer = { |
- element: transformElement, |
- dom: transformDom, |
- host: transformHost, |
- css: transformCss, |
- rule: transformRule, |
- rootRule: transformRootRule, |
- SCOPE_NAME: SCOPE_NAME |
- }; |
- |
- })(); |
- |
- |
-; |
- |
- (function() { |
- |
- var prepTemplate = Polymer.Base._prepTemplate; |
- var prepElement = Polymer.Base._prepElement; |
- var baseStampTemplate = Polymer.Base._stampTemplate; |
- var nativeShadow = Polymer.Settings.useNativeShadow; |
- |
- Polymer.Base._addFeature({ |
- |
- // declaration-y |
- _prepTemplate: function() { |
- prepTemplate.call(this); |
- var port = Polymer.DomModule.import(this.is); |
- if (this._encapsulateStyle === undefined) { |
- this._encapsulateStyle = |
- Boolean(port && !nativeShadow); |
- } |
- // scope css |
- // NOTE: dom scoped via annotations |
- if (nativeShadow || this._encapsulateStyle) { |
- this._scopeCss(); |
- } |
- }, |
- |
- _prepElement: function(element) { |
- if (this._encapsulateStyle) { |
- Polymer.StyleTransformer.element(element, this.is, |
- this._scopeCssViaAttr); |
- } |
- prepElement.call(this, element); |
- }, |
- |
- _scopeCss: function() { |
- this._styles = this._prepareStyles(); |
- this._scopeStyles(this._styles); |
- }, |
- |
- // search for extra style modules via `styleModules` |
- _prepareStyles: function() { |
- var cssText = '', m$ = this.styleModules; |
- if (m$) { |
- for (var i=0, l=m$.length, m; (i<l) && (m=m$[i]); i++) { |
- cssText += this._cssFromModule(m); |
- } |
- } |
- cssText += this._cssFromModule(this.is); |
- var styles = []; |
- if (cssText) { |
- var s = document.createElement('style'); |
- s.textContent = cssText; |
- styles.push(s); |
- } |
- return styles; |
- }, |
- |
- // returns cssText of styles in a given module; also un-applies any |
- // styles that apply to the document. |
- _cssFromModule: function(moduleId) { |
- var m = Polymer.DomModule.import(moduleId); |
- if (m && !m._cssText) { |
- var cssText = ''; |
- var e$ = Array.prototype.slice.call(m.querySelectorAll('style')); |
- this._unapplyStyles(e$); |
- e$ = e$.concat(Array.prototype.map.call( |
- m.querySelectorAll(REMOTE_SHEET_SELECTOR), function(l) { |
- return l.import.body; |
- })); |
- m._cssText = this._cssFromStyles(e$); |
- } |
- return m && m._cssText || ''; |
- }, |
- |
- _cssFromStyles: function(styles) { |
- var cssText = ''; |
- for (var i=0, l=styles.length, s; (i<l) && (s = styles[i]); i++) { |
- if (s && s.textContent) { |
- cssText += |
- Polymer.ResolveUrl.resolveCss(s.textContent, s.ownerDocument); |
- } |
- } |
- return cssText; |
- }, |
- |
- _unapplyStyles: function(styles) { |
- for (var i=0, l=styles.length, s; (i<l) && (s = styles[i]); i++) { |
- s = s.__appliedElement || s; |
- s.parentNode.removeChild(s); |
- } |
- }, |
- |
- _scopeStyles: function(styles) { |
- for (var i=0, l=styles.length, s; (i<l) && (s=styles[i]); i++) { |
- // transform style if necessary and place in correct place |
- if (nativeShadow) { |
- if (this._template) { |
- this._template.content.appendChild(s); |
- } |
- } else { |
- var rules = this._rulesForStyle(s); |
- Polymer.StyleUtil.applyCss( |
- Polymer.StyleTransformer.css(rules, this.is, this.extends, |
- null, this._scopeCssViaAttr), |
- this.is, null, true); |
- } |
- } |
- }, |
- |
- _rulesForStyle: function(style) { |
- if (!style.__cssRules) { |
- style.__cssRules = Polymer.StyleUtil.parser.parse(style.textContent); |
- } |
- return style.__cssRules; |
- }, |
- |
- // instance-y |
- _stampTemplate: function() { |
- if (this._encapsulateStyle) { |
- Polymer.StyleTransformer.host(this, this.is); |
- } |
- baseStampTemplate.call(this); |
- }, |
- |
- // add scoping class whenever an element is added to localDOM |
- _elementAdd: function(node) { |
- if (this._encapsulateStyle && !node.__styleScoped) { |
- Polymer.StyleTransformer.dom(node, this.is, this._scopeCssViaAttr); |
- } |
- }, |
- |
- // remove scoping class whenever an element is removed from localDOM |
- _elementRemove: function(node) { |
- if (this._encapsulateStyle) { |
- Polymer.StyleTransformer.dom(node, this.is, this._scopeCssViaAttr, true); |
- } |
- }, |
- |
- /** |
- * Apply style scoping to the specified `container` and all its |
- * descendants. If `shoudlObserve` is true, changes to the container are |
- * monitored via mutation observer and scoping is applied. |
- */ |
- scopeSubtree: function(container, shouldObserve) { |
- if (nativeShadow) { |
- return; |
- } |
- var self = this; |
- var scopify = function(node) { |
- if (node.nodeType === Node.ELEMENT_NODE) { |
- node.className = self._scopeElementClass(node, node.className); |
- var n$ = node.querySelectorAll('*'); |
- Array.prototype.forEach.call(n$, function(n) { |
- n.className = self._scopeElementClass(n, n.className); |
- }); |
- } |
- }; |
- scopify(container); |
- if (shouldObserve) { |
- var mo = new MutationObserver(function(mxns) { |
- mxns.forEach(function(m) { |
- if (m.addedNodes) { |
- for (var i=0; i < m.addedNodes.length; i++) { |
- scopify(m.addedNodes[i]); |
- } |
- } |
- }); |
- }); |
- mo.observe(container, {childList: true, subtree: true}); |
- return mo; |
- } |
- } |
- |
- }); |
- |
- var REMOTE_SHEET_SELECTOR = 'link[rel=import][type~=css]'; |
- |
- })(); |
- |
- |
-; |
- |
- (function() { |
- |
- var defaultSheet = document.createElement('style'); |
- |
- function applyCss(cssText) { |
- defaultSheet.textContent += cssText; |
- defaultSheet.__cssRules = |
- Polymer.StyleUtil.parser.parse(defaultSheet.textContent); |
- } |
- |
- applyCss(''); |
- |
- // exports |
- Polymer.StyleDefaults = { |
- applyCss: applyCss, |
- defaultSheet: defaultSheet |
- }; |
- |
- })(); |
- |
-; |
- (function() { |
- |
- var baseAttachedCallback = Polymer.Base.attachedCallback; |
- var baseSerializeValueToAttribute = Polymer.Base.serializeValueToAttribute; |
- |
- var nativeShadow = Polymer.Settings.useNativeShadow; |
- |
- // TODO(sorvell): consider if calculating properties and applying |
- // styles with properties should be separate modules. |
- Polymer.Base._addFeature({ |
- |
- attachedCallback: function() { |
- baseAttachedCallback.call(this); |
- if (!this._xScopeSelector) { |
- this._updateOwnStyles(); |
- } |
- }, |
- |
- _updateOwnStyles: function() { |
- if (this.enableCustomStyleProperties) { |
- this._styleProperties = this._computeStyleProperties(); |
- this._applyStyleProperties(this._styleProperties); |
- } |
- }, |
- |
- _computeStyleProperties: function() { |
- var props = {}; |
- this.simpleMixin(props, this._computeStylePropertiesFromHost()); |
- this.simpleMixin(props, this._computeOwnStyleProperties()); |
- this._reifyCustomProperties(props); |
- return props; |
- }, |
- |
- _computeStylePropertiesFromHost: function() { |
- // TODO(sorvell): experimental feature, global defaults! |
- var props = {}, styles = [Polymer.StyleDefaults.defaultSheet]; |
- var host = this.domHost; |
- if (host) { |
- // enable finding styles in hosts without `enableStyleCustomProperties` |
- if (!host._styleProperties) { |
- host._styleProperties = host._computeStyleProperties(); |
- } |
- props = Object.create(host._styleProperties); |
- styles = host._styles; |
- } |
- this.simpleMixin(props, |
- this._customPropertiesFromStyles(styles, host)); |
- return props; |
- |
- }, |
- |
- _computeOwnStyleProperties: function() { |
- var props = {}; |
- this.simpleMixin(props, this._customPropertiesFromStyles(this._styles)); |
- if (this.styleProperties) { |
- for (var i in this.styleProperties) { |
- props[i] = this.styleProperties[i]; |
- } |
- } |
- return props; |
- }, |
- |
- _customPropertiesFromStyles: function(styles, hostNode) { |
- var props = {}; |
- var p = this._customPropertiesFromRule.bind(this, props, hostNode); |
- if (styles) { |
- for (var i=0, l=styles.length, s; (i<l) && (s=styles[i]); i++) { |
- Polymer.StyleUtil.forEachStyleRule(this._rulesForStyle(s), p); |
- } |
- } |
- return props; |
- }, |
- |
- // test if a rule matches the given node and if so, |
- // collect any custom properties |
- // TODO(sorvell): support custom variable assignment within mixins |
- _customPropertiesFromRule: function(props, hostNode, rule) { |
- hostNode = hostNode || this; |
- // TODO(sorvell): file crbug, ':host' does not match element. |
- if (this.elementMatches(rule.selector) || |
- ((hostNode === this) && (rule.selector === ':host'))) { |
- // --g: var(--b); or --g: 5; |
- this._collectPropertiesFromRule(rule, CUSTOM_VAR_ASSIGN, props); |
- // --g: { ... } |
- this._collectPropertiesFromRule(rule, CUSTOM_MIXIN_ASSIGN, props); |
- } |
- }, |
- |
- // given a rule and rx that matches key and value, set key in properties |
- // to value |
- _collectPropertiesFromRule: function(rule, rx, properties) { |
- var m; |
- while (m = rx.exec(rule.cssText)) { |
- properties[m[1]] = m[2].trim(); |
- } |
- }, |
- |
- _reifyCustomProperties: function(props) { |
- for (var i in props) { |
- props[i] = this._valueForCustomProperty(props[i], props); |
- } |
- }, |
- |
- _valueForCustomProperty: function(property, props) { |
- var cv; |
- while ((typeof property === 'string') && |
- (cv = property.match(CUSTOM_VAR_VALUE))) { |
- property = props[cv[1]]; |
- } |
- return property; |
- }, |
- |
- // apply styles |
- _applyStyleProperties: function(bag) { |
- var s$ = this._styles; |
- if (s$) { |
- var style = styleFromCache(this.is, bag, s$); |
- var old = this._xScopeSelector; |
- this._ensureScopeSelector(style ? style._scope : null); |
- if (!style) { |
- var cssText = this._generateCustomStyleCss(bag, s$); |
- style = cssText ? this._applyCustomCss(cssText) : {}; |
- cacheStyle(this.is, style, this._xScopeSelector, |
- this._styleProperties, s$); |
- } else if (nativeShadow) { |
- this._applyCustomCss(style.textContent); |
- } |
- if (style.textContent || old /*&& !nativeShadow*/) { |
- this._applyXScopeSelector(this._xScopeSelector, old); |
- } |
- } |
- }, |
- |
- _applyXScopeSelector: function(selector, old) { |
- var c = this._scopeCssViaAttr ? this.getAttribute(SCOPE_NAME) : |
- this.className; |
- v = old ? c.replace(old, selector) : |
- (c ? c + ' ' : '') + XSCOPE_NAME + ' ' + selector; |
- if (c !== v) { |
- if (this._scopeCssViaAttr) { |
- this.setAttribute(SCOPE_NAME, v); |
- } else { |
- this.className = v; |
- } |
- } |
- }, |
- |
- _generateCustomStyleCss: function(properties, styles) { |
- var b = this._applyPropertiesToRule.bind(this, properties); |
- var cssText = ''; |
- // TODO(sorvell): don't redo parsing work each time as below; |
- // instead create a sheet with just custom properties |
- for (var i=0, l=styles.length, s; (i<l) && (s=styles[i]); i++) { |
- cssText += this._transformCss(s.textContent, b) + '\n\n'; |
- } |
- return cssText.trim(); |
- }, |
- |
- _transformCss: function(cssText, callback) { |
- return nativeShadow ? |
- Polymer.StyleUtil.toCssText(cssText, callback) : |
- Polymer.StyleTransformer.css(cssText, this.is, this.extends, callback, |
- this._scopeCssViaAttr); |
- }, |
- |
- _xScopeCount: 0, |
- |
- _ensureScopeSelector: function(selector) { |
- selector = selector || (this.is + '-' + |
- (Object.getPrototypeOf(this)._xScopeCount++)); |
- this._xScopeSelector = selector; |
- }, |
- |
- _applyCustomCss: function(cssText) { |
- if (this._customStyle) { |
- this._customStyle.textContent = cssText; |
- } else if (cssText) { |
- this._customStyle = Polymer.StyleUtil.applyCss(cssText, |
- this._xScopeSelector, |
- nativeShadow ? this.root : null); |
- } |
- return this._customStyle; |
- }, |
- |
- _applyPropertiesToRule: function(properties, rule) { |
- if (!nativeShadow) { |
- this._scopifyRule(rule); |
- } |
- if (rule.cssText.match(CUSTOM_RULE_RX)) { |
- rule.cssText = this._applyPropertiesToText(rule.cssText, properties); |
- } else { |
- rule.cssText = ''; |
- } |
- //console.log(rule.cssText); |
- }, |
- |
- _applyPropertiesToText: function(cssText, props) { |
- var output = ''; |
- var m, v; |
- // e.g. color: var(--color); |
- while (m = CUSTOM_VAR_USE.exec(cssText)) { |
- v = props[m[2]]; |
- if (v) { |
- output += '\t' + m[1].trim() + ': ' + this._propertyToCss(v); |
- } |
- } |
- // e.g. @mixin(--stuff); |
- while (m = CUSTOM_MIXIN_USE.exec(cssText)) { |
- v = m[1]; |
- if (v) { |
- var parts = v.split(' '); |
- for (var i=0, p; i < parts.length; i++) { |
- p = props[parts[i].trim()]; |
- if (p) { |
- output += '\t' + this._propertyToCss(p); |
- } |
- } |
- } |
- } |
- return output; |
- }, |
- |
- _propertyToCss: function(property) { |
- var p = property.trim(); |
- p = p[p.length-1] === ';' ? p : p + ';'; |
- return p + '\n'; |
- }, |
- |
- // Strategy: x scope shim a selector e.g. to scope `.x-foo-42` (via classes): |
- // non-host selector: .a.x-foo -> .x-foo-42 .a.x-foo |
- // host selector: x-foo.wide -> x-foo.x-foo-42.wide |
- _scopifyRule: function(rule) { |
- var selector = rule.selector; |
- var host = this.is; |
- var rx = new RegExp(HOST_SELECTOR_PREFIX + host + HOST_SELECTOR_SUFFIX); |
- var parts = selector.split(','); |
- var scope = this._scopeCssViaAttr ? |
- SCOPE_PREFIX + this._xScopeSelector + SCOPE_SUFFIX : |
- '.' + this._xScopeSelector; |
- for (var i=0, l=parts.length, p; (i<l) && (p=parts[i]); i++) { |
- parts[i] = p.match(rx) ? |
- p.replace(host, host + scope) : |
- scope + ' ' + p; |
- } |
- rule.selector = parts.join(','); |
- }, |
- |
- _scopeElementClass: function(element, selector) { |
- if (!nativeShadow && !this._scopeCssViaAttr) { |
- selector += (selector ? ' ' : '') + SCOPE_NAME + ' ' + this.is + |
- (element._xScopeSelector ? ' ' + XSCOPE_NAME + ' ' + |
- element._xScopeSelector : ''); |
- } |
- return selector; |
- }, |
- |
- // override to ensure whenever classes are set, we need to shim them. |
- serializeValueToAttribute: function(value, attribute, node) { |
- if (attribute === 'class') { |
- // host needed to scope styling. |
- var host = node === this ? |
- Polymer.dom(this).getOwnerRoot() || this.dataHost : |
- this; |
- if (host) { |
- value = host._scopeElementClass(node, value); |
- } |
- } |
- baseSerializeValueToAttribute.call(this, value, attribute, node); |
- }, |
- |
- updateStyles: function() { |
- this._updateOwnStyles(); |
- this._updateRootStyles(this.root); |
- }, |
- |
- updateHostStyles: function() { |
- var host = Polymer.dom(this).getOwnerRoot() || this.dataHost; |
- if (host) { |
- host.updateStyles(); |
- } else { |
- this._updateRootStyles(document); |
- } |
- }, |
- |
- _updateRootStyles: function(root) { |
- // TODO(sorvell): temporary way to find local dom that needs |
- // x-scope styling. |
- var scopeSelector = this._scopeCssViaAttr ? |
- '[' + SCOPE_NAME + '~=' + XSCOPE_NAME + ']' : '.' + XSCOPE_NAME; |
- var c$ = Polymer.dom(root).querySelectorAll(scopeSelector); |
- for (var i=0, l= c$.length, c; (i<l) && (c=c$[i]); i++) { |
- if (c.updateStyles) { |
- c.updateStyles(); |
- } |
- } |
- } |
- |
- }); |
- |
- var styleCache = {}; |
- function cacheStyle(is, style, scope, bag, styles) { |
- style._scope = scope; |
- style._properties = bag; |
- style._styles = styles; |
- var s$ = styleCache[is] = styleCache[is] || []; |
- s$.push(style); |
- } |
- |
- function styleFromCache(is, bag, checkStyles) { |
- var styles = styleCache[is]; |
- if (styles) { |
- for (var i=0, s; i < styles.length; i++) { |
- s = styles[i]; |
- if (objectsEqual(bag, s._properties) && |
- objectsEqual(checkStyles, s._styles)) { |
- return s; |
- } |
- } |
- } |
- } |
- |
- function objectsEqual(a, b) { |
- for (var i in a) { |
- if (a[i] !== b[i]) { |
- return false; |
- } |
- } |
- for (var i in b) { |
- if (a[i] !== b[i]) { |
- return false; |
- } |
- } |
- return true; |
- } |
- |
- var SCOPE_NAME= Polymer.StyleTransformer.SCOPE_NAME; |
- var XSCOPE_NAME = 'x-scope'; |
- var SCOPE_PREFIX = '[' + SCOPE_NAME + '~='; |
- var SCOPE_SUFFIX = ']'; |
- var HOST_SELECTOR_PREFIX = '(?:^|[^.])'; |
- var HOST_SELECTOR_SUFFIX = '($|[.:[\\s>+~])'; |
- var CUSTOM_RULE_RX = /mixin|var/; |
- var CUSTOM_VAR_ASSIGN = /(--[^\:;]*?):\s*?([^;{]*?);/g; |
- var CUSTOM_MIXIN_ASSIGN = /(--[^\:;]*?):[^{;]*?{([^}]*?)}/g; |
- var CUSTOM_VAR_VALUE = /^var\(([^)]*?)\)/; |
- var CUSTOM_VAR_USE = /(?:^|[;}\s])([^;{}]*?):[\s]*?var\(([^)]*)?\)/gim; |
- var CUSTOM_MIXIN_USE = /mixin\(([^)]*)\)/gim; |
- |
- })(); |
- |
-; |
- |
- Polymer.Base._addFeature({ |
- |
- _registerFeatures: function() { |
- // identity |
- this._prepIs(); |
- // inheritance |
- this._prepExtends(); |
- // factory |
- this._prepConstructor(); |
- // template |
- this._prepTemplate(); |
- // template markup |
- this._prepAnnotations(); |
- // accessors |
- this._prepEffects(); |
- // shared behaviors |
- this._prepBehaviors(); |
- // accessors part 2 |
- this._prepBindings(); |
- // dom encapsulation |
- this._prepShady(); |
- }, |
- |
- _prepBehavior: function(b) { |
- this._addPropertyEffects(b.properties || b.accessors); |
- this._addComplexObserverEffects(b.observers); |
- }, |
- |
- _initFeatures: function() { |
- // manage local dom |
- this._poolContent(); |
- // manage configuration |
- this._setupConfigure(); |
- // host stack |
- this._pushHost(); |
- // instantiate template |
- this._stampTemplate(); |
- // host stack |
- this._popHost(); |
- // concretize template references |
- this._marshalAnnotationReferences(); |
- // setup debouncers |
- this._setupDebouncers(); |
- // concretize effects on instance |
- this._marshalInstanceEffects(); |
- // acquire instance behaviors |
- this._marshalBehaviors(); |
- // acquire initial instance attribute values |
- this._marshalAttributes(); |
- // top-down initial distribution, configuration, & ready callback |
- this._tryReady(); |
- }, |
- |
- _marshalBehavior: function(b) { |
- // publish attributes to instance |
- this._installHostAttributes(b.hostAttributes); |
- // establish listeners on instance |
- this._listenListeners(b.listeners); |
- } |
- |
- }); |
- |
- |
-; |
-(function() { |
- |
- Polymer({ |
- |
- is: 'x-style', |
- extends: 'style', |
- |
- created: function() { |
- var rules = Polymer.StyleUtil.parser.parse(this.textContent); |
- this.applyProperties(rules); |
- // TODO(sorvell): since custom rules must match directly, they tend to be |
- // made with selectors like `*`. |
- // We *remove them here* so they don't apply too widely and nerf recalc. |
- // This means that normal properties mixe in rules with custom |
- // properties will *not* apply. |
- var cssText = Polymer.StyleUtil.parser.stringify(rules); |
- this.textContent = this.scopeCssText(cssText); |
- }, |
- |
- scopeCssText: function(cssText) { |
- return Polymer.Settings.useNativeShadow ? |
- cssText : |
- Polymer.StyleUtil.toCssText(cssText, function(rule) { |
- Polymer.StyleTransformer.rootRule(rule); |
- }); |
- }, |
- |
- applyProperties: function(rules) { |
- var cssText = ''; |
- Polymer.StyleUtil.forEachStyleRule(rules, function(rule) { |
- if (rule.cssText.match(CUSTOM_RULE)) { |
- // TODO(sorvell): use parser.stringify, it needs an option not to |
- // strip custom properties. |
- cssText += rule.selector + ' {\n' + rule.cssText + '\n}\n'; |
- } |
- }); |
- if (cssText) { |
- Polymer.StyleDefaults.applyCss(cssText); |
- } |
- } |
- |
- }); |
- |
- var CUSTOM_RULE = /--[^;{'"]*\:/; |
- |
-})(); |
- |
-; |
- |
- Polymer({ |
- |
- is: 'x-autobind', |
- |
- extends: 'template', |
- |
- _registerFeatures: function() { |
- this._prepExtends(); |
- this._prepConstructor(); |
- }, |
- |
- _finishDistribute: function() { |
- var parentDom = Polymer.dom(Polymer.dom(this).parentNode); |
- parentDom.insertBefore(this.root, this); |
- }, |
- |
- _initFeatures: function() { |
- this._template = this; |
- this._prepAnnotations(); |
- this._prepEffects(); |
- this._prepBehaviors(); |
- this._prepBindings(); |
- Polymer.Base._initFeatures.call(this); |
- } |
- |
- }); |
- |
- |
-; |
- |
- Polymer.Templatizer = { |
- |
- templatize: function(template) { |
- this._templatized = template; |
- // TODO(sjmiles): supply _alternate_ content reference missing from root |
- // templates (not nested). `_content` exists to provide content sharing |
- // for nested templates. |
- if (!template._content) { |
- template._content = template.content; |
- } |
- // fast path if template's anonymous class has been memoized |
- if (template._content._ctor) { |
- this.ctor = template._content._ctor; |
- //console.log('Templatizer.templatize: using memoized archetype'); |
- // forward parent properties to archetype |
- this._prepParentProperties(this.ctor.prototype); |
- return; |
- } |
- // `archetype` is the prototype of the anonymous |
- // class created by the templatizer |
- var archetype = Object.create(Polymer.Base); |
- // normally Annotations.parseAnnotations(template) but |
- // archetypes do special caching |
- this.customPrepAnnotations(archetype, template); |
- |
- // setup accessors |
- archetype._prepEffects(); |
- archetype._prepBehaviors(); |
- archetype._prepBindings(); |
- |
- // forward parent properties to archetype |
- this._prepParentProperties(archetype); |
- |
- // boilerplate code |
- archetype._notifyPath = this._notifyPathImpl; |
- archetype._scopeElementClass = this._scopeElementClassImpl; |
- // boilerplate code |
- var _constructor = this._constructorImpl; |
- var ctor = function TemplateInstance(model, host) { |
- _constructor.call(this, model, host); |
- }; |
- // standard references |
- ctor.prototype = archetype; |
- archetype.constructor = ctor; |
- // TODO(sjmiles): constructor cache? |
- template._content._ctor = ctor; |
- // TODO(sjmiles): choose less general name |
- this.ctor = ctor; |
- }, |
- |
- _getRootDataHost: function() { |
- return (this.dataHost && this.dataHost._rootDataHost) || this.dataHost; |
- }, |
- |
- _getAllStampedChildren: function(children) { |
- children = children || []; |
- if (this._getStampedChildren) { |
- var c$ = this._getStampedChildren(); |
- for (var i=0, c; c = c$[i]; i++) { |
- children.push(c); |
- if (c._getAllStampedChildren) { |
- c._getAllStampedChildren(children); |
- } |
- } |
- } |
- return children; |
- }, |
- |
- customPrepAnnotations: function(archetype, template) { |
- if (template) { |
- archetype._template = template; |
- var c = template._content; |
- if (c) { |
- var rootDataHost = archetype._rootDataHost; |
- if (rootDataHost) { |
- Polymer.Annotations.prepElement = |
- rootDataHost._prepElement.bind(rootDataHost); |
- } |
- archetype._notes = c._notes || |
- Polymer.Annotations.parseAnnotations(template); |
- c._notes = archetype._notes; |
- Polymer.Annotations.prepElement = null; |
- archetype._parentProps = c._parentProps; |
- } |
- else { |
- console.warn('no _content'); |
- } |
- } |
- else { |
- console.warn('no _template'); |
- } |
- }, |
- |
- // Sets up accessors on the template to call abstract _forwardParentProp |
- // API that should be implemented by Templatizer users to get parent |
- // properties to their template instances. These accessors are memoized |
- // on the archetype and copied to instances. |
- _prepParentProperties: function(archetype) { |
- var parentProps = this._parentProps = archetype._parentProps; |
- if (this._forwardParentProp && parentProps) { |
- // Prototype setup (memoized on archetype) |
- var proto = archetype._parentPropProto; |
- if (!proto) { |
- proto = archetype._parentPropProto = Object.create(null); |
- if (this._templatized != this) { |
- // Assumption: if `this` isn't the template being templatized, |
- // assume that the template is not a Poylmer.Base, so prep it |
- // for binding |
- Polymer.Bind.prepareModel(proto); |
- } |
- // Create accessors for each parent prop that forward the property |
- // to template instances through abstract _forwardParentProp API |
- // that should be implemented by Templatizer users |
- for (var prop in parentProps) { |
- var parentProp = '_parent_' + prop; |
- var effects = [{ |
- kind: 'function', |
- effect: { function: this._createForwardPropEffector(prop) } |
- }]; |
- Polymer.Bind._createAccessors(proto, parentProp, effects); |
- } |
- } |
- // Instance setup |
- if (this._templatized != this) { |
- Polymer.Bind.prepareInstance(this._templatized); |
- this._templatized._forwardParentProp = |
- this._forwardParentProp.bind(this); |
- } |
- this._extendTemplate(this._templatized, proto); |
- } |
- }, |
- |
- _createForwardPropEffector: function(prop) { |
- return function(source, value) { |
- this._forwardParentProp(prop, value); |
- }; |
- }, |
- |
- // Similar to Polymer.Base.extend, but retains any previously set instance |
- // values (_propertySet back on instance once accessor is installed) |
- _extendTemplate: function(template, proto) { |
- Object.getOwnPropertyNames(proto).forEach(function(n) { |
- var val = template[n]; |
- var pd = Object.getOwnPropertyDescriptor(proto, n); |
- Object.defineProperty(template, n, pd); |
- if (val !== undefined) { |
- template._propertySet(n, val); |
- } |
- }); |
- }, |
- |
- _notifyPathImpl: function(path, value) { |
- var p = path.match(/([^.]*)\.(([^.]*).*)/); |
- // 'root.sub.path' |
- var root = p[1]; // 'root' |
- var sub = p[3]; // 'sub' |
- var subPath = p[2]; // 'sub.path' |
- // Notify host of parent.* path/property changes |
- var dataHost = this.dataHost; |
- if (root == 'parent') { |
- if (sub == subPath) { |
- dataHost.dataHost[sub] = value; |
- } else { |
- dataHost.notifyPath('_parent_' + subPath, value); |
- } |
- } |
- // Extension point for Templatizer sub-classes |
- if (dataHost._forwardInstancePath) { |
- dataHost._forwardInstancePath.call(dataHost, this, root, subPath, value); |
- } |
- }, |
- |
- // Overrides Base notify-path module |
- _pathEffector: function(path, value, fromAbove) { |
- if (this._forwardParentPath) { |
- if (path.indexOf('_parent_') === 0) { |
- this._forwardParentPath(path.substring(8), value); |
- } |
- } |
- Polymer.Base._pathEffector.apply(this, arguments); |
- }, |
- |
- _constructorImpl: function(model, host) { |
- var rootDataHost = host._getRootDataHost(); |
- if (rootDataHost) { |
- this.listen = rootDataHost.listen.bind(rootDataHost); |
- this._rootDataHost = rootDataHost; |
- } |
- this._setupConfigure(model); |
- this._pushHost(host); |
- this.root = this.instanceTemplate(this._template); |
- this.root.__styleScoped = true; |
- this._popHost(); |
- this._marshalAnnotatedNodes(); |
- this._marshalInstanceEffects(); |
- this._marshalAnnotatedListeners(); |
- this._tryReady(); |
- }, |
- |
- _scopeElementClassImpl: function(node, value) { |
- var host = this._rootDataHost; |
- if (host) { |
- return host._scopeElementClass(node, value); |
- } |
- }, |
- |
- stamp: function(model) { |
- model = model || {}; |
- if (this._parentProps) { |
- // TODO(kschaaf): Maybe this is okay |
- // model.parent = this.dataHost; |
- model.parent = model.parent || {}; |
- for (var prop in this._parentProps) { |
- model.parent[prop] = this['_parent_' + prop]; |
- } |
- } |
- return new this.ctor(model, this); |
- } |
- |
- // TODO(sorvell): note, using the template as host is ~5-10% faster if |
- // elements have no default values. |
- // _constructorImpl: function(model, host) { |
- // this._setupConfigure(model); |
- // host._beginHost(); |
- // this.root = this.instanceTemplate(this._template); |
- // host._popHost(); |
- // this._marshalTemplateContent(); |
- // this._marshalAnnotatedNodes(); |
- // this._marshalInstanceEffects(); |
- // this._marshalAnnotatedListeners(); |
- // this._ready(); |
- // }, |
- |
- // stamp: function(model) { |
- // return new this.ctor(model, this.dataHost); |
- // } |
- |
- |
- }; |
- |
- |
-; |
- |
- /** |
- * Creates a pseudo-custom-element that maps property values to bindings |
- * in DOM. |
- * |
- * `stamp` method creates an instance of the pseudo-element. The instance |
- * references a document-fragment containing the stamped and bound dom |
- * via it's `root` property. |
- * |
- */ |
- Polymer({ |
- |
- is: 'x-template', |
- extends: 'template', |
- |
- behaviors: [ |
- Polymer.Templatizer |
- ], |
- |
- ready: function() { |
- this.templatize(this); |
- } |
- |
- }); |
- |
- |
-; |
- |
-(function() { |
- |
- var callbacks = new WeakMap(); |
- |
- function observe(array, cb) { |
- if (Array.observe) { |
- var ncb = function(changes) { |
- changes = changes.filter(function(o) { return o.type == 'splice'; }); |
- if (changes.length) { |
- cb(changes); |
- } |
- }; |
- callbacks.set(cb, ncb); |
- Array.observe(array, ncb); |
- } else { |
- if (!array.__polymerObservable) { |
- makeObservable(array); |
- } |
- callbacks.get(array).push(cb); |
- } |
- } |
- |
- function unobserve(array, cb) { |
- if (Array.observe) { |
- var ncb = callbacks.get(cb); |
- callbacks.delete(cb); |
- Array.unobserve(array, ncb); |
- } else { |
- var cbs = callbacks.get(array); |
- var idx = cbs.indexOf(cb); |
- if (idx >= 0) { |
- cbs.splice(idx, 1); |
- } |
- } |
- } |
- |
- function makeObservable(array) { |
- var splices = []; |
- var debounce; |
- var orig = { |
- push: array.push, |
- pop: array.pop, |
- splice: array.splice, |
- shift: array.shift, |
- unshift: array.unshift, |
- sort: array.sort |
- }; |
- var addSplice = function(index, added, removed) { |
- splices.push({ |
- index: index, |
- addedCount: added, |
- removed: removed, |
- object: array, |
- type: 'splice' |
- }); |
- }; |
- callbacks.set(array, []); |
- array.push = function() { |
- debounce = Polymer.Debounce(debounce, fin); |
- addSplice(array.length, 1, []); |
- return orig.push.apply(this, arguments); |
- }; |
- array.pop = function() { |
- debounce = Polymer.Debounce(debounce, fin); |
- addSplice(array.length - 1, 0, array.slice(-1)); |
- return orig.pop.apply(this, arguments); |
- }; |
- array.splice = function(start, deleteCount) { |
- debounce = Polymer.Debounce(debounce, fin); |
- addSplice(start, arguments.length - 2, array.slice(start, start + deleteCount)); |
- return orig.splice.apply(this, arguments); |
- }; |
- array.shift = function() { |
- debounce = Polymer.Debounce(debounce, fin); |
- addSplice(0, 0, [array[0]]); |
- return orig.shift.apply(this, arguments); |
- }; |
- array.unshift = function() { |
- debounce = Polymer.Debounce(debounce, fin); |
- addSplice(0, 1, []); |
- return orig.unshift.apply(this, arguments); |
- }; |
- array.sort = function() { |
- debounce = Polymer.Debounce(debounce, fin); |
- console.warn('[ArrayObserve]: sort not observable'); |
- return orig.sort.apply(this, arguments); |
- }; |
- var fin = function() { |
- var cbs = callbacks.get(array); |
- for (var i=0; i<cbs.length; i++) { |
- cbs[i](splices); |
- } |
- splices = []; |
- }; |
- array.__polymerObservable = true; |
- } |
- |
- Polymer.ArrayObserve = { |
- observe: observe, |
- unobserve: unobserve |
- }; |
- |
-})(); |
- |
- |
-; |
- |
- Polymer._collections = new WeakMap(); |
- |
- Polymer.Collection = function(userArray, noObserve) { |
- Polymer._collections.set(userArray, this); |
- this.userArray = userArray; |
- this.store = userArray.slice(); |
- this.callbacks = []; |
- this.debounce = null; |
- this.map = null; |
- this.added = []; |
- this.removed = []; |
- if (!noObserve) { |
- Polymer.ArrayObserve.observe(userArray, this.applySplices.bind(this)); |
- this.initMap(); |
- } |
- }; |
- |
- Polymer.Collection.prototype = { |
- constructor: Polymer.Collection, |
- |
- initMap: function() { |
- var map = this.map = new WeakMap(); |
- var s = this.store; |
- var u = this.userArray; |
- for (var i=0; i<s.length; i++) { |
- var v = s[i]; |
- if (v) { |
- switch (typeof v) { |
- case 'string': |
- v = s[i] = u[i]= new String(v); |
- break; |
- case 'number': |
- v = s[i] = u[i]= new Number(v); |
- break; |
- case 'boolean': |
- v = s[i] = u[i]= new Boolean(v); |
- break; |
- } |
- map.set(v, i); |
- } |
- } |
- }, |
- |
- add: function(item, squelch) { |
- var key = this.store.push(item) - 1; |
- if (item != null && this.map) { |
- this.map.set(item, key); |
- } |
- if (!squelch) { |
- this.added.push(key); |
- this.debounce = Polymer.Debounce(this.debounce, this.notify.bind(this)); |
- } |
- return key; |
- }, |
- |
- removeKey: function(key) { |
- if (this.map) { |
- this.map.delete(this.store[key]); |
- } |
- delete this.store[key]; |
- this.removed.push(key); |
- this.debounce = Polymer.Debounce(this.debounce, this.notify.bind(this)); |
- }, |
- |
- remove: function(item, squelch) { |
- var key = this.getKey(item); |
- if (item != null && this.map) { |
- this.map.delete(item); |
- } |
- delete this.store[key]; |
- if (!squelch) { |
- this.removed.push(key); |
- this.debounce = Polymer.Debounce(this.debounce, this.notify.bind(this)); |
- } |
- return key; |
- }, |
- |
- notify: function(splices) { |
- if (!splices) { |
- splices = [{ |
- added: this.added, |
- removed: this.removed |
- }]; |
- this.added = []; |
- this.removed = []; |
- } |
- this.callbacks.forEach(function(cb) { |
- cb(splices); |
- }, this); |
- }, |
- |
- observe: function(callback) { |
- this.callbacks.push(callback); |
- }, |
- |
- unobserve: function(callback) { |
- this.callbacks.splice(this.callbacks.indexOf(callback), 1); |
- }, |
- |
- getKey: function(item) { |
- if (item != null && this.map) { |
- return this.map.get(item); |
- } else { |
- return this.store.indexOf(item); |
- } |
- }, |
- |
- getKeys: function() { |
- return Object.keys(this.store); |
- }, |
- |
- setItem: function(key, value) { |
- this.store[key] = value; |
- }, |
- |
- getItem: function(key) { |
- return this.store[key]; |
- }, |
- |
- getItems: function() { |
- var items = [], store = this.store; |
- for (var key in store) { |
- items.push(store[key]); |
- } |
- return items; |
- }, |
- |
- applySplices: function(splices) { |
- var map = this.map; |
- var keySplices = []; |
- for (var i=0; i<splices.length; i++) { |
- var j, o, key, s = splices[i]; |
- // Removed keys |
- var removed = []; |
- for (j=0; j<s.removed.length; j++) { |
- o = s.removed[j]; |
- key = this.remove(o, true); |
- removed.push(key); |
- } |
- // Added keys |
- var added = []; |
- for (j=0; j<s.addedCount; j++) { |
- o = this.userArray[s.index + j]; |
- key = this.add(o, true); |
- added.push(key); |
- } |
- // Record splice |
- keySplices.push({ |
- index: s.index, |
- removed: removed, |
- added: added |
- }); |
- } |
- this.notify(keySplices); |
- } |
- |
- }; |
- |
- Polymer.Collection.get = function(userArray, noObserve) { |
- return Polymer._collections.get(userArray) |
- || new Polymer.Collection(userArray, noObserve); |
- }; |
- |
- |
-; |
- |
- Polymer({ |
- |
- is: 'x-repeat', |
- extends: 'template', |
- |
- properties: { |
- |
- /** |
- * An array containing items determining how many instances of the template |
- * to stamp and that that each template instance should bind to. |
- */ |
- items: { |
- type: Array |
- }, |
- |
- /** |
- * A function that should determine the sort order of the items. This |
- * property should either be provided as a string, indicating a method |
- * name on the element's host, or else be an actual function. The |
- * function should match the sort function passed to `Array.sort`. |
- * Using a sort function has no effect on the underlying `items` array. |
- */ |
- sort: { |
- type: Function, |
- observer: '_sortChanged' |
- }, |
- |
- /** |
- * A function that can be used to filter items out of the view. This |
- * property should either be provided as a string, indicating a method |
- * name on the element's host, or else be an actual function. The |
- * function should match the sort function passed to `Array.filter`. |
- * Using a filter function has no effect on the underlying `items` array. |
- */ |
- filter: { |
- type: Function, |
- observer: '_filterChanged' |
- }, |
- |
- /** |
- * When using a `filter` or `sort` function, the `observe` property |
- * should be set to a space-separated list of the names of item |
- * sub-fields that should trigger a re-sort or re-filter when changed. |
- * These should generally be fields of `item` that the sort or filter |
- * function depends on. |
- */ |
- observe: { |
- type: String, |
- observer: '_observeChanged' |
- }, |
- |
- /** |
- * When using a `filter` or `sort` function, the `delay` property |
- * determines a debounce time after a change to observed item |
- * properties that must pass before the filter or sort is re-run. |
- * This is useful in rate-limiting shuffing of the view when |
- * item changes may be frequent. |
- */ |
- delay: Number |
- }, |
- |
- behaviors: [ |
- Polymer.Templatizer |
- ], |
- |
- observers: [ |
- '_itemsChanged(items.*)' |
- ], |
- |
- created: function() { |
- this.boundCollectionObserver = this.render.bind(this); |
- }, |
- |
- ready: function() { |
- // Templatizing (generating the instance constructor) needs to wait |
- // until attached, since it may not have its template content handed |
- // back to it until then, following its host template stamping |
- if (!this.ctor) { |
- this.templatize(this); |
- } |
- }, |
- |
- _sortChanged: function() { |
- var dataHost = this._getRootDataHost(); |
- this._sortFn = this.sort && (typeof this.sort == 'function' ? |
- this.sort : dataHost[this.sort].bind(this.host)); |
- if (this.items) { |
- this.debounce('render', this.render); |
- } |
- }, |
- |
- _filterChanged: function() { |
- var dataHost = this._getRootDataHost(); |
- this._filterFn = this.filter && (typeof this.filter == 'function' ? |
- this.filter : dataHost[this.filter].bind(this.host)); |
- if (this.items) { |
- this.debounce('render', this.render); |
- } |
- }, |
- |
- _observeChanged: function() { |
- this._observePaths = this.observe && |
- this.observe.replace('.*', '.').split(' '); |
- }, |
- |
- _itemsChanged: function(change) { |
- if (change.path == 'items') { |
- this._unobserveCollection(); |
- if (change.value) { |
- this._observeCollection(change.value); |
- this.debounce('render', this.render); |
- } |
- } else { |
- this._forwardItemPath(change.path, change.value); |
- this._checkObservedPaths(change.path); |
- } |
- }, |
- |
- _checkObservedPaths: function(path) { |
- if (this._observePaths && path.indexOf('items.') === 0) { |
- path = path.substring(path.indexOf('.', 6) + 1); |
- var paths = this._observePaths; |
- for (var i=0; i<paths.length; i++) { |
- if (path.indexOf(paths[i]) === 0) { |
- this.debounce('render', this.render, this.delay); |
- return; |
- } |
- } |
- } |
- }, |
- |
- _observeCollection: function(items) { |
- this.collection = Array.isArray(items) ? Polymer.Collection.get(items) : items; |
- this.collection.observe(this.boundCollectionObserver); |
- }, |
- |
- _unobserveCollection: function() { |
- if (this.collection) { |
- this.collection.unobserve(this.boundCollectionObserver); |
- } |
- }, |
- |
- render: function(splices) { |
- this.flushDebouncer('render'); |
- var c = this.collection; |
- if (splices) { |
- if (this._sortFn || splices[0].index == null) { |
- this._applySplicesViewSort(splices); |
- } else { |
- this._applySplicesArraySort(splices); |
- } |
- } else { |
- this._sortAndFilter(); |
- } |
- var rowForKey = this._rowForKey = {}; |
- var keys = this._orderedKeys; |
- // Assign items and keys |
- this.rows = this.rows || []; |
- for (var i=0; i<keys.length; i++) { |
- var key = keys[i]; |
- var item = c.getItem(key); |
- var row = this.rows[i]; |
- rowForKey[key] = i; |
- if (!row) { |
- this.rows.push(row = this._insertRow(i, null, item)); |
- } |
- row.item = item; |
- row.key = key; |
- row.index = i; |
- } |
- // Remove extra |
- for (; i<this.rows.length; i++) { |
- this._detachRow(i); |
- } |
- this.rows.splice(keys.length, this.rows.length-keys.length); |
- }, |
- |
- _sortAndFilter: function() { |
- var c = this.collection; |
- this._orderedKeys = c.getKeys(); |
- // Filter |
- if (this._filterFn) { |
- this._orderedKeys = this._orderedKeys.filter(function(a) { |
- return this._filterFn(c.getItem(a)); |
- }, this); |
- } |
- // Sort |
- if (this._sortFn) { |
- this._orderedKeys.sort(function(a, b) { |
- return this._sortFn(c.getItem(a), c.getItem(b)); |
- }.bind(this)); |
- } |
- }, |
- |
- _keySort: function(a, b) { |
- return this.collection.getKey(a) - this.collection.getKey(b); |
- }, |
- |
- _applySplicesViewSort: function(splices) { |
- var c = this.collection; |
- var keys = this._orderedKeys; |
- var rows = this.rows; |
- var removedRows = []; |
- var addedKeys = []; |
- var pool = []; |
- var sortFn = this._sortFn || this._keySort.bind(this); |
- splices.forEach(function(s) { |
- // Collect all removed row idx's |
- for (var i=0; i<s.removed.length; i++) { |
- var idx = this._rowForKey[s.removed[i]]; |
- if (idx != null) { |
- removedRows.push(idx); |
- } |
- } |
- // Collect all added keys |
- for (i=0; i<s.added.length; i++) { |
- addedKeys.push(s.added[i]); |
- } |
- }, this); |
- if (removedRows.length) { |
- // Sort removed rows idx's |
- removedRows.sort(); |
- // Remove keys and pool rows (backwards, so we don't invalidate rowForKey) |
- for (i=removedRows.length-1; i>=0 ; i--) { |
- var idx = removedRows[i]; |
- pool.push(this._detachRow(idx)); |
- rows.splice(idx, 1); |
- keys.splice(idx, 1); |
- } |
- } |
- if (addedKeys.length) { |
- // Filter added keys |
- if (this._filterFn) { |
- addedKeys = addedKeys.filter(function(a) { |
- return this._filterFn(c.getItem(a)); |
- }, this); |
- } |
- // Sort added keys |
- addedKeys.sort(function(a, b) { |
- return this.sortFn(c.getItem(a), c.getItem(b)); |
- }, this); |
- // Insert new rows using sort (from pool or newly created) |
- var start = 0; |
- for (i=0; i<addedKeys.length; i++) { |
- start = this._insertRowIntoViewSort(start, addedKeys[i], pool); |
- } |
- } |
- }, |
- |
- _insertRowIntoViewSort: function(start, key, pool) { |
- var c = this.collection; |
- var item = c.getItem(key); |
- var end = this.rows.length - 1; |
- var idx = -1; |
- var sortFn = this._sortFn || this._keySort.bind(this); |
- // Binary search for insertion point |
- while (start <= end) { |
- var mid = (start + end) >> 1; |
- var midKey = this._orderedKeys[mid]; |
- var cmp = sortFn(c.getItem(midKey), item); |
- if (cmp < 0) { |
- start = mid + 1; |
- } else if (cmp > 0) { |
- end = mid - 1; |
- } else { |
- idx = mid; |
- break; |
- } |
- } |
- if (idx < 0) { |
- idx = end + 1; |
- } |
- // Insert key & row at insertion point |
- this._orderedKeys.splice(idx, 0, key); |
- this.rows.splice(idx, 0, this._insertRow(idx, pool)); |
- return idx; |
- }, |
- |
- _applySplicesArraySort: function(splices) { |
- var keys = this._orderedKeys; |
- var pool = []; |
- splices.forEach(function(s) { |
- // Remove & pool rows first, to ensure we can fully reuse removed rows |
- for (var i=0; i<s.removed.length; i++) { |
- pool.push(this._detachRow(s.index + i)); |
- } |
- this.rows.splice(s.index, s.removed.length); |
- }, this); |
- var c = this.collection; |
- var filterDelta = 0; |
- splices.forEach(function(s) { |
- // Filter added keys |
- var addedKeys = s.added; |
- if (this._filterFn) { |
- addedKeys = addedKeys.filter(function(a) { |
- return this._filterFn(c.getItem(a)); |
- }, this); |
- filterDelta += (s.added.length - addedKeys.length); |
- } |
- var idx = s.index - filterDelta; |
- // Apply splices to keys |
- var args = [idx, s.removed.length].concat(addedKeys); |
- keys.splice.apply(keys, args); |
- // Insert new rows (from pool or newly created) |
- var addedRows = []; |
- for (i=0; i<s.added.length; i++) { |
- addedRows.push(this._insertRow(idx + i, pool)); |
- } |
- args = [s.index, 0].concat(addedRows); |
- this.rows.splice.apply(this.rows, args); |
- }, this); |
- }, |
- |
- _detachRow: function(idx) { |
- var row = this.rows[idx]; |
- var parentNode = Polymer.dom(this).parentNode; |
- for (var i=0; i<row._children.length; i++) { |
- var el = row._children[i]; |
- Polymer.dom(row.root).appendChild(el); |
- } |
- return row; |
- }, |
- |
- _insertRow: function(idx, pool, item) { |
- var row = (pool && pool.pop()) || this._generateRow(idx, item); |
- var beforeRow = this.rows[idx]; |
- var beforeNode = beforeRow ? beforeRow._children[0] : this; |
- var parentNode = Polymer.dom(this).parentNode; |
- Polymer.dom(parentNode).insertBefore(row.root, beforeNode); |
- return row; |
- }, |
- |
- _generateRow: function(idx, item) { |
- var row = this.stamp({ |
- index: idx, |
- key: this.collection.getKey(item), |
- item: item |
- }); |
- // each row is a document fragment which is lost when we appendChild, |
- // so we have to track each child individually |
- var children = []; |
- for (var n = row.root.firstChild; n; n=n.nextSibling) { |
- children.push(n); |
- n._templateInstance = row; |
- } |
- // Since archetype overrides Base/HTMLElement, Safari complains |
- // when accessing `children` |
- row._children = children; |
- return row; |
- }, |
- |
- // Implements extension point from Templatizer mixin |
- _getStampedChildren: function() { |
- var children = []; |
- if (this.rows) { |
- for (var i=0; i<this.rows.length; i++) { |
- var c = this.rows[i]._children; |
- for (var j=0; j<c.length; j++) |
- children.push(c[j]); |
- } |
- } |
- return children; |
- }, |
- |
- // Implements extension point from Templatizer |
- // Called as a side effect of a template instance path change, responsible |
- // for notifying items.<key-for-row>.<path> change up to host |
- _forwardInstancePath: function(row, root, subPath, value) { |
- if (root == 'item') { |
- this.notifyPath('items.' + row.key + '.' + subPath, value); |
- } |
- }, |
- |
- // Implements extension point from Templatizer mixin |
- // Called as side-effect of a host property change, responsible for |
- // notifying parent.<prop> path change on each row |
- _forwardParentProp: function(prop, value) { |
- if (this.rows) { |
- this.rows.forEach(function(row) { |
- row.parent[prop] = value; |
- row.notifyPath('parent.' + prop, value, true); |
- }, this); |
- } |
- }, |
- |
- // Implements extension point from Templatizer |
- // Called as side-effect of a host path change, responsible for |
- // notifying parent.<path> path change on each row |
- _forwardParentPath: function(path, value) { |
- if (this.rows) { |
- this.rows.forEach(function(row) { |
- row.notifyPath('parent.' + path, value, true); |
- }, this); |
- } |
- }, |
- |
- // Called as a side effect of a host items.<key>.<path> path change, |
- // responsible for notifying item.<path> changes to row for key |
- _forwardItemPath: function(path, value) { |
- if (this._rowForKey) { |
- // 'items.'.length == 6 |
- var dot = path.indexOf('.', 6); |
- var key = path.substring(6, dot < 0 ? path.length : dot); |
- var idx = this._rowForKey[key]; |
- var row = this.rows[idx]; |
- if (row) { |
- if (dot >= 0) { |
- path = 'item.' + path.substring(dot+1); |
- row.notifyPath(path, value, true); |
- } else { |
- row.item = value; |
- } |
- } |
- } |
- }, |
- |
- _instanceForElement: function(el) { |
- while (el && !el._templateInstance) { |
- el = el.parentNode; |
- } |
- return el && el._templateInstance; |
- }, |
- |
- /** |
- * Returns the item associated with a given element stamped by |
- * this `x-repeat`. |
- */ |
- itemForElement: function(el) { |
- var instance = this._instanceForElement(el); |
- return instance && instance.item; |
- }, |
- |
- /** |
- * Returns the `Polymer.Collection` key associated with a given |
- * element stamped by this `x-repeat`. |
- */ |
- keyForElement: function(el) { |
- var instance = this._instanceForElement(el); |
- return instance && instance.key; |
- }, |
- |
- /** |
- * Returns the index in `items` associated with a given element |
- * stamped by this `x-repeat`. |
- */ |
- indexForElement: function(el) { |
- var instance = this._instanceForElement(el); |
- return this.rows.indexOf(instance); |
- } |
- |
- }); |
- |
- |
- |
-; |
- |
- Polymer({ |
- is: 'x-array-selector', |
- |
- properties: { |
- |
- /** |
- * An array containing items from which selection will be made. |
- */ |
- items: { |
- type: Array, |
- observer: '_itemsChanged' |
- }, |
- |
- /** |
- * When `multi` is true, this is an array that contains any selected. |
- * When `multi` is false, this is the currently selected item, or `null` |
- * if no item is selected. |
- */ |
- selected: { |
- type: Object, |
- notify: true |
- }, |
- |
- /** |
- * When `true`, calling `select` on an item that is already selected |
- * will deselect the item. |
- */ |
- toggle: Boolean, |
- |
- /** |
- * When `true`, multiple items may be selected at once (in this case, |
- * `selected` is an array of currently selected items). When `false`, |
- * only one item may be selected at a time. |
- */ |
- multi: Boolean |
- }, |
- |
- _itemsChanged: function() { |
- // Unbind previous selection |
- if (Array.isArray(this.selected)) { |
- for (var i=0; i<this.selected.length; i++) { |
- this.unbindPaths('selected.' + i); |
- } |
- } else { |
- this.unbindPaths('selected'); |
- } |
- // Initialize selection |
- if (this.multi) { |
- this.selected = []; |
- } else { |
- this.selected = null; |
- } |
- }, |
- |
- /** |
- * Deselects the given item if it is already selected. |
- */ |
- deselect: function(item) { |
- if (this.multi) { |
- var scol = Polymer.Collection.get(this.selected); |
- // var skey = scol.getKey(item); |
- // if (skey >= 0) { |
- var sidx = this.selected.indexOf(item); |
- if (sidx >= 0) { |
- var skey = scol.getKey(item); |
- this.selected.splice(sidx, 1); |
- // scol.remove(item); |
- this.unbindPaths('selected.' + skey); |
- return true; |
- } |
- } else { |
- this.selected = null; |
- this.unbindPaths('selected'); |
- } |
- }, |
- |
- /** |
- * Selects the given item. When `toggle` is true, this will automatically |
- * deselect the item if already selected. |
- */ |
- select: function(item) { |
- var icol = Polymer.Collection.get(this.items); |
- var key = icol.getKey(item); |
- if (this.multi) { |
- // var sidx = this.selected.indexOf(item); |
- // if (sidx < 0) { |
- var scol = Polymer.Collection.get(this.selected); |
- var skey = scol.getKey(item); |
- if (skey >= 0) { |
- this.deselect(item); |
- } else if (this.toggle) { |
- this.selected.push(item); |
- // this.bindPaths('selected.' + sidx, 'items.' + skey); |
- // skey = Polymer.Collection.get(this.selected).add(item); |
- this.async(function() { |
- skey = scol.getKey(item); |
- this.bindPaths('selected.' + skey, 'items.' + key); |
- }); |
- } |
- } else { |
- if (this.toggle && item == this.selected) { |
- this.deselect(); |
- } else { |
- this.bindPaths('selected', 'items.' + key); |
- this.selected = item; |
- } |
- } |
- } |
- |
- }); |
- |
- |
-; |
- |
- /** |
- * Stamps the template iff the `if` property is truthy. |
- * |
- * When `if` becomes falsey, the stamped content is hidden but not |
- * removed from dom. When `if` subsequently becomes truthy again, the content |
- * is simply re-shown. This approach is used due to its favorable performance |
- * characteristics: the expense of creating template content is paid only |
- * once and lazily. |
- * |
- * Set the `restamp` property to true to force the stamped content to be |
- * created / destroyed when the `if` condition changes. |
- */ |
- Polymer({ |
- |
- is: 'x-if', |
- extends: 'template', |
- |
- properties: { |
- |
- 'if': { |
- type: Boolean, |
- value: false |
- }, |
- |
- restamp: { |
- type: Boolean, |
- value: false |
- } |
- |
- }, |
- |
- behaviors: [ |
- Polymer.Templatizer |
- ], |
- |
- observers: [ |
- 'render(if, restamp)' |
- ], |
- |
- render: function() { |
- this.debounce('render', function() { |
- if (this.if) { |
- if (!this.ctor) { |
- this._wrapTextNodes(this._content); |
- this.templatize(this); |
- } |
- this._ensureInstance(); |
- } else if (this.restamp) { |
- this._teardownInstance(); |
- } |
- if (!this.restamp && this._instance) { |
- this._showHideInstance(this.if); |
- } |
- }); |
- }, |
- |
- _ensureInstance: function() { |
- if (!this._instance) { |
- // TODO(sorvell): pickup stamping logic from x-repeat |
- this._instance = this.stamp(); |
- var root = this._instance.root; |
- this._instance._children = Array.prototype.slice.call(root.childNodes); |
- // TODO(sorvell): this incantation needs to be simpler. |
- var parent = Polymer.dom(Polymer.dom(this).parentNode); |
- parent.insertBefore(root, this); |
- } |
- }, |
- |
- _teardownInstance: function() { |
- if (this._instance) { |
- var parent = Polymer.dom(Polymer.dom(this).parentNode); |
- this._instance._children.forEach(function(n) { |
- parent.removeChild(n); |
- }); |
- this._instance = null; |
- } |
- }, |
- |
- _wrapTextNodes: function(root) { |
- // wrap text nodes in span so they can be hidden. |
- for (var n = root.firstChild; n; n=n.nextSibling) { |
- if (n.nodeType === Node.TEXT_NODE) { |
- var s = document.createElement('span'); |
- root.insertBefore(s, n); |
- s.appendChild(n); |
- n = s; |
- } |
- } |
- }, |
- |
- // Implements extension point from Templatizer mixin |
- _getStampedChildren: function() { |
- return this._instance._children; |
- }, |
- |
- _showHideInstance: function(showing) { |
- this._getAllStampedChildren().forEach(function(n) { |
- if (n.setAttribute) { |
- this.serializeValueToAttribute(!showing, 'hidden', n); |
- } |
- }, this); |
- }, |
- |
- // Implements extension point from Templatizer mixin |
- // Called as side-effect of a host property change, responsible for |
- // notifying parent.<prop> path change on instance |
- _forwardParentProp: function(prop, value) { |
- if (this._instance) { |
- this._instance.parent[prop] = value; |
- this._instance.notifyPath('parent.' + prop, value, true); |
- } |
- }, |
- |
- // Implements extension point from Templatizer |
- // Called as side-effect of a host path change, responsible for |
- // notifying parent.<path> path change on each row |
- _forwardParentPath: function(path, value) { |
- if (this._instance) { |
- this._instance.notifyPath('parent.' + path, value, true); |
- } |
- } |
- |
- }); |
- |