Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

2.0 Improvements to binding API #4510

Merged
merged 18 commits into from
Apr 13, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,14 @@ addons:
sources:
- google-chrome
packages:
- google-chrome-stable
- google-chrome-beta
cache:
directories:
- node_modules
before_script:
- mkdir -p ~/bin
- ln -s /usr/bin/google-chrome-beta ~/bin/google-chrome
- export PATH=$HOME/bin:$PATH
- npm install -g bower gulp-cli@1
- bower install
- gulp lint
Expand Down
2 changes: 1 addition & 1 deletion lib/elements/dom-bind.html
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,8 @@
observer.observe(this, {childList: true});
return;
}
this._bindTemplate(template);
this.root = this._stampTemplate(template);
this.$ = this.root.$;
this.__children = [];
for (let n=this.root.firstChild; n; n=n.nextSibling) {
this.__children[this.__children.length] = n;
Expand Down
20 changes: 18 additions & 2 deletions lib/mixins/element-mixin.html
Original file line number Diff line number Diff line change
Expand Up @@ -420,7 +420,7 @@
if (window.ShadyCSS) {
window.ShadyCSS.prepareTemplate(template, is, ext);
}
proto._bindTemplate(template, propertiesForClass(proto.constructor));
proto._bindTemplate(template);
}

function flushPropertiesStub() {}
Expand Down Expand Up @@ -576,7 +576,9 @@
let value = typeof info.value == 'function' ?
info.value.call(this) :
info.value;
if (this._hasPropertyEffect(p)) {
// Set via `_setProperty` if there is an accessor, to enable
// initializing readOnly property defaults
if (this._hasAccessor(p)) {
this._setProperty(p, value)
} else {
this[p] = value;
Expand Down Expand Up @@ -622,6 +624,7 @@
hostStack.beginHosting(this);
this.root = this._stampTemplate(this._template);
hostStack.endHosting(this);
this.$ = this.root.$;
}
super.ready();
}
Expand Down Expand Up @@ -745,6 +748,19 @@
return Polymer.ResolveUrl.resolveUrl(url, base);
}

/**
* Overrides `PropertyAccessors` to add map of dynamic functions on
* template info, for consumption by `PropertyEffects` template binding
* code. This map determines which method templates should have accessors
* created for them.
*
* @override
*/
static _parseTemplateContent(template, templateInfo, nodeInfo) {
templateInfo.dynamicFns = templateInfo.dynamicFns || propertiesForClass(this);
return super._parseTemplateContent(template, templateInfo, nodeInfo);
}

}

return PolymerElement;
Expand Down
114 changes: 97 additions & 17 deletions lib/mixins/property-accessors.html
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,10 @@
* the standard `static get observedAttributes()`, implement `_propertiesChanged`
* on the class, and then call `MyClass.createPropertiesForAttributes()` once
* on the class to generate property accessors for each observed attribute
* prior to instancing. Any `observedAttributes` will automatically be
* prior to instancing. Last, call `this._flushProperties()` once to enable
* the accessors.
*
* Any `observedAttributes` will automatically be
* deserialized via `attributeChangedCallback` and set to the associated
* property using `dash-case`-to-`camelCase` convention.
*
Expand Down Expand Up @@ -129,13 +132,25 @@
_initializeProperties() {
this.__serializing = false;
this.__dataCounter = 0;
this.__dataInitialized = false;
this.__dataInvalid = false;
// initialize data with prototype values saved when creating accessors
this.__data = {};
this.__dataPending = null;
this.__dataOld = null;
if (this.__dataProto) {
this._initializeProtoProperties(this.__dataProto);
this.__dataProto = null;
}
// Capture instance properties; these will be set into accessors
// during first flush. Don't set them here, since we want
// these to overwrite defaults/constructor assignments
for (let p in this.__dataHasAccessor) {
if (this.hasOwnProperty(p)) {
this.__dataInstanceProps = this.__dataInstanceProps || {};
this.__dataInstanceProps[p] = this[p];
delete this[p];
}
}
}

Expand All @@ -157,6 +172,22 @@
}
}

/**
* Called at ready time with bag of instance properties that overwrote
* accessors when the element upgraded.
*
* The default implementation sets these properties back into the
* setter at ready time. This method is provided as an override
* point for customizing or providing more efficient initialization.
*
* @param {Object} props Bag of property values that were overwritten
* when creating property accessors.
* @protected
*/
_initializeInstanceProperties(props) {
Object.assign(this, props);
}

/**
* Ensures the element has the given attribute. If it does not,
* assigns the given value to the attribute.
Expand Down Expand Up @@ -347,15 +378,31 @@
* @protected
*/
_createPropertyAccessor(property, readOnly) {
saveAccessorValue(this, property);
Object.defineProperty(this, property, {
get: function() {
return this.__data[property];
},
set: readOnly ? function() { } : function(value) {
this._setProperty(property, value);
}
});
if (!this.hasOwnProperty('__dataHasAccessor')) {
this.__dataHasAccessor = Object.assign({}, this.__dataHasAccessor);
}
if (!this.__dataHasAccessor[property]) {
this.__dataHasAccessor[property] = true;
saveAccessorValue(this, property);
Object.defineProperty(this, property, {
get: function() {
return this.__data[property];
},
set: readOnly ? function() { } : function(value) {
this._setProperty(property, value);
}
});
}
}

/**
* Returns true if this library created an accessor for the given property.
*
* @param {string} property Property name
* @return {boolean} True if an accessor was created
*/
_hasAccessor(property) {
return this.__dataHasAccessor && this.__dataHasAccessor[property];
}

/**
Expand Down Expand Up @@ -417,7 +464,7 @@
* @protected
*/
_invalidateProperties() {
if (!this.__dataInvalid) {
if (!this.__dataInvalid && this.__dataInitialized) {
this.__dataInvalid = true;
microtask.run(() => {
if (this.__dataInvalid) {
Expand All @@ -433,15 +480,48 @@
* pending changes (and old values recorded when pending changes were
* set), and resets the pending set of changes.
*
* Note that this method must be called once to enable the property
* accessors system. For elements, generally `connectedCallback`
* is a normal spot to do so.
*
* @protected
*/
_flushProperties() {
let oldProps = this.__dataOld;
let changedProps = this.__dataPending;
this.__dataPending = null;
this.__dataCounter++;
this._propertiesChanged(this.__data, changedProps, oldProps);
this.__dataCounter--;
if (!this.__dataInitialized) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that we have __dataInitialized it probably makes sense to prevent _invalidateProperties from scheduling a flush if this flag is not true.

this.ready()
} else if (this.__dataPending) {
let changedProps = this.__dataPending;
this.__dataPending = null;
this.__dataCounter++;
this._propertiesChanged(this.__data, changedProps, this.__dataOld);
this.__dataCounter--;
}
}

/**
* Lifecycle callback called the first time properties are being flushed.
* Prior to `ready`, all property sets through accessors are queued and
* their effects are flushed after this method returns.
*
* Users may override this function to implement behavior that is
* dependent on the element having its properties initialized, e.g.
* from defaults (initialized from `constructor`, `_initializeProperties`),
* `attributeChangedCallback`, or values propagated from host e.g. via
* bindings. `super.ready()` must be called to ensure the data system
* becomes enabled.
*
* @public
*/
ready() {
// Update instance properties that shadowed proto accessors; these take
// priority over any defaults set in constructor or attributeChangedCallback
if (this.__dataInstanceProps) {
this._initializeInstanceProperties(this.__dataInstanceProps);
this.__dataInstanceProps = null;
}
this.__dataInitialized = true;
// Run normal flush
this._flushProperties();
}

/**
Expand Down
Loading