| 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
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..7a400f307a6e1785fdca7600a0fab86a4c13b95a
|
| --- /dev/null
|
| +++ b/third_party/polymer/v0_8/components-chromium/polymer/polymer.js
|
| @@ -0,0 +1,6840 @@
|
| +
|
| +
|
| + 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);
|
| + }
|
| + }
|
| +
|
| + });
|
| +
|
|
|