Files
polymer/src/elements/element.html

292 lines
9.1 KiB
HTML
Raw Normal View History

<!--
@license
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<link rel="import" href="../utils/boot.html">
<link rel="import" href="../utils/utils.html">
<link rel="import" href="../utils/compat-dom-utils.html">
<link rel="import" href="../utils/case-map.html">
<link rel="import" href="../utils/async-render.html">
<link rel="import" href="../events/gesture-event-listeners.html">
<link rel="import" href="../template/template-stamp.html">
<link rel="import" href="../attributes/attribute-to-from-property.html">
<link rel="import" href="../properties/meta-effects.html">
<script>
(function() {
'use strict';
var utils = Polymer.Utils;
var caseMap = Polymer.CaseMap;
var events = new Polymer.GestureEventListeners();
var templateStamp = new Polymer.TemplateStamp(events);
var attributes = new Polymer.AttributeToFromProperty();
var data = new Polymer.MetaEffects(templateStamp, attributes);
Polymer.ElementMixin = function(Base) {
class Element extends Base {
static get ownConfig() {
if (!this.hasOwnProperty('_ownConfig')) {
this._ownConfig = this.hasOwnProperty('config') ? this.config : {};
}
return this._ownConfig;
}
static get flattenedProperties() {
if (!this.hasOwnProperty('_flattenedProperties')) {
this._flattenedProperties = this.ownConfig.properties || {};
var superCtor = Object.getPrototypeOf(this.prototype).constructor;
if (superCtor.prototype instanceof Element) {
this._flattenedProperties = utils.mixin(
Object.create(superCtor.flattenedProperties),
this._flattenedProperties);
}
}
return this._flattenedProperties;
}
static get observedAttributes() {
// TODO(kschaaf): revisit: capture import document, to aid finding dom-module
var currentScript = document._currentScript || document.currentScript;
this.__importDoc = currentScript && currentScript.ownerDocument;
// observedAttributes must be finalized at registration time
var attrs = [];
return this.addPropertiesToAttributes(this.flattenedProperties, attrs);
}
static addPropertiesToAttributes(properties, attrs) {
for (var prop in properties) {
var attr = Polymer.CaseMap.camelToDashCase(prop);
if (attrs.indexOf(attr) < 0) {
attrs.push(attr);
}
}
return attrs;
}
static createProperties(properties) {
if (properties) {
this.data.createProperties(this.prototype, properties);
}
}
static createExpressionObservers(observers) {
if (observers) {
this.data.createExpressionObservers(this.prototype, observers);
}
}
static get decorated() {
return this.prototype.hasOwnProperty('__isDecorated');
}
static set decorated(value) {
this.prototype.__isDecorated = value;
}
// bikeshed this name... we have `prepare` below which is not good.
static decorate() {
var proto = this.prototype;
if (!this.decorated) {
if (this.hasOwnProperty('is') && this.is) {
Polymer.telemetry.register(proto);
}
this.decorated = true;
var superProto = Object.getPrototypeOf(proto);
var superCtor = superProto && superProto.constructor;
if (superCtor.prototype instanceof Element) {
superCtor.decorate();
}
var config = this.ownConfig;
this.createProperties(config.properties);
this.createExpressionObservers(config.observers);
var template = this.prepareTemplate();
if (template) {
proto.__notStyleScopeCacheable = template.__notStyleScopeCacheable;
proto._template = template;
this.data.bindTemplate(proto, proto._template);
}
}
}
static prepareTemplate() {
// TODO(sorvell): support more ways to acquire template.
// this requires `is` on constructor...
// TODO(sorvell): `__importDoc` may not be set if super class
// has not run defined... falling back to document here is
// incorrect. This gambit cannot work as is since if the superclass
// document cannot be discovered via the subclass.
var template = Polymer.DomModule.import(this.is,
'template', this.__importDoc || document);
if (template) {
Polymer.CompatStyleUtil.normalizeForBC(template.content);
// TODO(sorvell): cannot use `this` here, refactor this to only do
// template preparation and take a name.
// TODO(dfreedm): factor so that we do not need an object/element
// argument
var info = {
localName: this.is,
is: this.is,
extends: this.extends,
__cssBuild: this.__cssBuild
}
Polymer.StyleLib.prepareTemplate(info, template);
template.__notStyleScopeCacheable = info.__notStyleScopeCacheable;
}
return template;
}
constructor() {
super();
// note: `this.constructor.prototype` is wrong in Safari so make sure to
// use `__proto__`
if (!this.constructor.decorated) {
this.constructor.decorate();
}
Polymer.StyleLib.prepareHost(this, this._template);
Polymer.telemetry.instanceCount++;
// setup batched effects library; will call initialize when flushing
this.constructor.data.prepare(this, this.ready);
// add self to host's pending client list
hostStack.registerHost(this);
// apply defaults first.
this.constructor.data.setPropertyDefaults(this,
this.constructor.flattenedProperties);
}
// reserved for canonical behavior
connectedCallback() {
if (hostStack.isEmpty()) {
this.constructor.data.flush(this);
this.updateStyles();
}
this.isAttached = true;
}
ready() {
if (!this.root) {
if (this._template) {
// BREAKME(sorvell): remove v0 support when we can...
this.root = Polymer.shadowDomV0 ? this.createShadowRoot() :
this.attachShadow({mode: 'open'});
} else {
this.root = this;
}
}
if (this._template) {
hostStack.beginHosting(this);
var dom = this.constructor.data.stamp(this, this._template);
this.root.appendChild(dom);
hostStack.endHosting(this);
}
this.constructor.data.flush(this);
}
addListeners(listeners) {
this.constructor.events.addMethodListeners(this, listeners);
}
ensureAttributes(attrs) {
this.constructor.attributes.ensureAttributes(this, attrs);
}
disconnectedCallback() {
this.isAttached = false;
}
attributeChangedCallback(name, old, value) {
var property = caseMap.dashToCamelCase(name);
var type = this.constructor.flattenedProperties[property].type;
if (!this.constructor.data.hasReadOnlyEffect(
this.__proto__, name)) {
this.constructor.attributes.attributeToProperty(this,
name, value, type);
}
}
updateStyles(properties) {
Polymer.StyleLib.applyStyle(this, properties);
}
}
Element.events = events;
Element.templateStamp = templateStamp;
Element.attributes = attributes;
Element.data = data;
return Element;
}
var hostStack = {
stack: [],
isEmpty() {
return !this.stack.length;
},
registerHost(inst) {
if (this.stack.length) {
var host = this.stack[this.stack.length-1];
data.enqueueClient(host, inst);
}
},
beginHosting(inst) {
this.stack.push(inst);
},
endHosting(inst) {
var stackLen = this.stack.length;
if (stackLen && this.stack[stackLen-1] == inst) {
this.stack.pop();
}
}
}
if (window.CustomElements) {
Polymer.instanceof = CustomElements.instanceof;
} else {
Polymer.instanceof = function(obj, ctor) {
return obj instanceof ctor;
};
}
Polymer.isInstance = function(obj) {
return Boolean(obj && obj.__isPolymerInstance);
};
// telemetry
Polymer.telemetry = {
instanceCount: 0,
registrations: [],
_regLog: function(prototype) {
console.log('[' + prototype.is + ']: registered')
},
register: function(prototype) {
this.registrations.push(prototype);
Polymer.log && this._regLog(prototype);
},
dumpRegistrations: function() {
this.registrations.forEach(this._regLog);
}
};
Polymer.Element = Polymer.ElementMixin(HTMLElement);
})();
</script>