Skip to content

Commit

Permalink
Merge pull request #1433 from Polymer/0.8-behaviors
Browse files Browse the repository at this point in the history
0.8 behaviors
  • Loading branch information
Steve Orvell committed Apr 22, 2015
2 parents 58908c7 + f905a12 commit 79d0490
Show file tree
Hide file tree
Showing 41 changed files with 638 additions and 276 deletions.
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

0 comments on commit 79d0490

Please sign in to comment.