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

0.8 behaviors #1433

Merged
merged 15 commits into from
Apr 22, 2015
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
59 changes: 35 additions & 24 deletions PRIMER.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Below is a description of the current Polymer features, followed by individual f
| [Configure properties](#property-config) | properties: { … }
| [Attribute deserialization to property](#attribute-deserialization) | properties: { \<property>: \<Type> }
| [Static attributes on host](#host-attributes) | hostAttributes: { \<attribute>: \<value> }
| [Prototype mixins](#prototype-mixins) | mixins: [ … ]
| [Behavior mixins](#behaviors) | behaviors: [ … ]

<a name="polymer-mini"></a>
**Template stamped into "local DOM" and tree lifecycle**
Expand Down Expand Up @@ -68,6 +68,7 @@ Below is a description of the current Polymer features, followed by individual f
|---------|-------
| [Template repeater](#x-repeat) | \<template is="x-repeat" items="{{arr}}">
| [Array selector](#x-array-selector) | \<x-array-selector items="{{arr}}" selected="{{selected}}">
| [Conditional template](#x-if) | \<template is="x-if">
| [Auto-binding template](#x-autobind) | \<template is="x-autobind">
| [Cross-scope styling](#xscope-styling) | --custom-prop: value, var(--custom-prop), mixin(--custom-mixin)
| [Custom element for styling features](#x-style) | \<style is="x-style">
Expand Down Expand Up @@ -357,47 +358,57 @@ Results in:
<x-custom role="button" aria-disabled tabindex="0"></x-custom>
```

<a name="prototype-mixins"></a>
## Prototype mixins
<a name="behaviors"></a>
## Behaviors

Polymer will "mixin" objects specified in a `mixin` array into the prototype. This can be useful for adding common code between multiple elements.
Polymer supports extending custom element prototypes with shared code modules called "behaviors".

The current mixin feature in 0.8 is basic; it simply loops over properties in the provided object and adds property descriptors for those on the prototype (such that `set`/`get` accessors are copied in addition to properties and functions). Note that there is currently no support for configuring properties or hooking lifecycle callbacks directly via mixins. The general pattern is for the mixin to supply functions to be called by the target element as part of its usage contract (and should be documented as such). These limitations will likely be revisited in the future.
A behavior is simply an object that looks very similar to a typical Polymer prototype. It may define lifecycle callbacks, `properties`, `hostAttributes`, or other features described later in this document like `observers` and `listeners`. To add a behavior to a Polymer element definition, include it in a `behaviors` array on the prototype.

Lifecycle callbacks will be called on the base prototype first, then for each behavior in the order given in the `behaviors` array. Additonally, any non-lifecycle functions on the behavior object are mixed into the base prototype (and will overwrite the function on the prototype, if they exist); these may be useful for adding API or implementing observer or event listener callbacks defined by the behavior, for example.

Example: `fun-mixin.html`
Example: `highlight-behavior.html`

```js
FunMixin = {
HighlightBehavior = {

funCreatedCallback: function() {
this.makeElementFun();
},

makeElementFun: function() {
this.style.border = 'border: 20px dotted fuchsia;';
properties: {
isHighlighted: {
type: Boolean,
value: false,
notify: true,
observer: '_highlightChanged'
}
};
},

listeners: {
click: '_toggleHighlight'
},

created: function() {
console.log('Highlighting for ', this, + 'enabled!');
},

});
_toggleHighlight: function() {
this.isHighlighted = !this.isHighlighted;
},

_highlightChanged: function(value) {
this.toggleClass('highlighted', value);
}

};
```

Example: `my-element.html`

```html
<link rel="import" href="fun-mixin.html">
<link rel="import" href="highlight-behavior.html">

<script>
Polymer({

is: 'my-element',

mixins: [FunMixin],

created: function() {
this.funCreatedCallback();
}

behaviors: [HighlightBehavior]
});
</script>
```
Expand Down
24 changes: 18 additions & 6 deletions polymer-micro.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,37 @@
-->
<link rel="import" href="src/polymer-lib.html">
<link rel="import" href="src/micro/tag.html">
<link rel="import" href="src/micro/mixins.html">
<link rel="import" href="src/micro/behaviors.html">
<link rel="import" href="src/micro/extends.html">
<link rel="import" href="src/micro/constructor.html">
<link rel="import" href="src/micro/properties.html">
<link rel="import" href="src/micro/attributes.html">

<script>

Polymer.Base.addFeature({
Polymer.Base._addFeature({

registerFeatures: function() {
_registerFeatures: function() {
// identity
this._prepIs();
this._prepMixins();
// shared behaviors
this._prepBehaviors();
// inheritance
this._prepExtends();
// factory
this._prepConstructor();
},

initFeatures: function() {
this._marshalAttributes();
_prepBehavior: function() {},

_initFeatures: function() {
// acquire behaviors
this._marshalBehaviors();
},

_marshalBehavior: function(b) {
// publish attributes to instance
this._installHostAttributes(b.hostAttributes);
}

});
Expand Down
31 changes: 25 additions & 6 deletions polymer-mini.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,24 +19,43 @@

Polymer.DomModule = document.createElement('dom-module');

Polymer.Base.addFeature({
Polymer.Base._addFeature({

registerFeatures: function() {
_registerFeatures: function() {
// identity
this._prepIs();
this._prepMixins();
// shared behaviors
this._prepBehaviors();
// inheritance
this._prepExtends();
// factory
this._prepConstructor();
// template
this._prepTemplate();
this._prepContent();
// dom encapsulation
this._prepShady();
},

initFeatures: function() {
_prepBehavior: function() {},

_initFeatures: function() {
// manage local dom
this._poolContent();
// host stack
this._pushHost();
// instantiate template
this._stampTemplate();
// host stack
this._popHost();
this._marshalAttributes();
// 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);
}

});
Expand Down
44 changes: 38 additions & 6 deletions polymer.html
Original file line number Diff line number Diff line change
Expand Up @@ -22,30 +22,62 @@

<script>

Polymer.Base.addFeature({
Polymer.Base._addFeature({

registerFeatures: function() {
_registerFeatures: function() {
// identity
this._prepIs();
this._prepMixins();
// inheritance
this._prepExtends();
// factory
this._prepConstructor();
// template
this._prepTemplate();
// template markup
this._prepAnnotations();
// accessors
this._prepEffects();
this._prepContent();
// shared behaviors
this._prepBehaviors();
// accessors part 2
this._prepBindings();
// dom encapsulation
this._prepShady();
},

initFeatures: function() {
_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();
// concretize effects on instance
this._marshalInstanceEffects();
// acquire instance behaviors
this._marshalBehaviors();
// acquire initial instance attribute values
this._marshalAttributes();
this._marshalListeners();
// 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);
}

});
Expand Down
71 changes: 23 additions & 48 deletions src/lib/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,83 +11,58 @@

Polymer.Base = {

// (semi-)pluggable features for Base
addFeature: function(feature) {
// 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.registered();
},

registered: function() {
// for overriding
// `this` context is a prototype, not an instance
this._registerFeatures(); // abstract
this._doBehavior('registered'); // abstract
},

createdCallback: function() {
Polymer.telemetry.instanceCount++;
this.root = this;
this.beforeCreated();
this.created();
this.afterCreated();
this.initFeatures(); // abstract
},

beforeCreated: function() {
// for overriding
},

created: function() {
// for overriding
},

afterCreated: function() {
// for overriding
this._doBehavior('created'); // abstract
this._initFeatures(); // abstract
},

// reserved for canonical behavior
attachedCallback: function() {
this.isAttached = true;
// reserved for canonical behavior
this.attached();
},

attached: function() {
// for overriding
this._doBehavior('attached'); // abstract
},

// reserved for canonical behavior
detachedCallback: function() {
this.isAttached = false;
// reserved for canonical behavior
this.detached();
},

detached: function() {
// for overriding
this._doBehavior('detached'); // abstract
},

// reserved for canonical behavior
attributeChangedCallback: function(name) {
this.setAttributeToProperty(this, name);
// reserved for canonical behavior
this.attributeChanged.apply(this, arguments);
},

attributeChanged: function() {
// for overriding
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) {
var pd = Object.getOwnPropertyDescriptor(api, n);
if (pd) {
Object.defineProperty(prototype, n, pd);
}
});
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);
}
}

};
Expand Down
4 changes: 2 additions & 2 deletions src/lib/bind/accessors.html
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
// TODO(sjmiles): oops, `fire` doesn't exist at this layer
this.fire(eventName, {
value: this[property]
}, null, false);
}, {bubbles: false});
},

// TODO(sjmiles): removing _notifyListener from here breaks accessors.html
Expand Down Expand Up @@ -95,7 +95,7 @@
fx.push({
kind: kind,
effect: effect
});
});
},

createBindings: function(model) {
Expand Down
2 changes: 1 addition & 1 deletion src/lib/expr/focus.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
-->
<script>

Base.addFeature({
Base._addFeature({

init: function() {
if (this.focusable) {
Expand Down
Loading