Table of Contents:
Below is a description of the current Polymer features, followed by individual feature guides.
Bare-minum Custom Element sugaring
Feature | Usage |
---|---|
Custom element constructor | Polymer.Class({ … }); |
Custom element registration | Polymer({ is: ‘...’, … }}; |
Bespoke constructor support | factoryImpl: function() { … } |
Basic lifecycle callbacks | created, attached, detached, attributeChanged |
Native HTML element extension | extends: ‘…’ |
Configure properties | properties: { … } |
Attribute deserialization to property | properties: { <property>: <Type> } |
Static attributes on host | hostAttributes: { <attribute>: <value> } |
Behavior mixins | behaviors: [ … ] |
Template stamped into "local DOM" and tree lifecycle
Feature | Usage |
---|---|
Template stamping into local DOM | <dom-module><template>...</template></dom-module> |
Scoped styling | <style> in <dom-module>, Shadow-DOM styling rules (:host, ...) |
DOM (re-)distribution | <content> |
DOM API | Polymer.dom |
Configuring default values | properties: <prop>: { value: <primitive>|<function> } |
Bottom-up callback after configuration | ready: function() { … } |
Declarative data binding, events, and property effects
Feature | Usage |
---|---|
Local node marshalling | this.$.<id> |
Event listener setup | listeners: { ‘<node>.<event>’: ‘function’, ... } |
Annotated event listener setup | <element on-[event]=”function”> |
Property change callbacks | properties: <prop>: { observer: ‘function’ } |
Annotated property binding | <element prop=”{{property|path}}”> |
Property change notification | properties: { <prop>: { notify: true } } |
Binding to structured data | <element prop=”{{obj.sub.path}}”> |
Path change notification | setPathValue(<path>, <value>) |
Declarative attribute binding | <element attr$=”{{property|path}}”> |
Binding to native element attributes | class$="{{...}}", style$="{{...}}"> |
Reflecting properties to attributes | properties: <prop>: { reflectToAttribute: true } } |
Computed properties | computed: { <property>: ‘computeFn(dep1, dep2)’ } |
Annotated computed properties | <span>{{computeFn(dep1, dep2)}}</span> |
Read-only properties | properties: { <prop>: { readOnly: true } } |
Utility functions | toggleClass, toggleAttribute, fire, async, … |
General polymer settings | <script> Polymer = { ... }; </script> |
Experimental elements and features
These features are experimental and there is a higher liklihood for future API change. Names for custom elements starting with x-
are placeholders while in experimental status; these will be renamed prior to 1.0.
Feature | Usage |
---|---|
Template repeater | <template is="x-repeat" items="{{arr}}"> |
Array selector | <x-array-selector items="{{arr}}" selected="{{selected}}"> |
Conditional template | <template is="x-if"> |
Auto-binding template | <template is="x-autobind"> |
Cross-scope styling | --custom-prop: value, var(--custom-prop), mixin(--custom-mixin) |
Custom element for styling features | <style is="x-style"> |
External stylesheets | <link rel="import" type="css" href="..."> |
Polymer feature layers | polymer-micro.html, polymer-mini.html, polymer.html |
The most basic Polymer API is Polymer.Class({...})
, which takes an object expressing the prototype of your custom element, chains it to Polymer's Base
prototype (which provides value-add features described below), and returns a constructor that can be passed to document.registerElement()
to register your element with the HTML parser, and after which can be used to instantiate new instances of your element via code.
The only requirement for the prototype passed to Polymer.Class
is that is
property specifies the HTML tag name the element will be registered as.
Example:
var MyElement = Polymer.Class({
is: 'my-element',
// See below for lifecycle callbacks
created: function() {
this.innerHTML = 'My element!';
}
});
document.registerElement('my-element', MyElement);
// Equivalent:
var el1 = new MyElement();
var el2 = document.createElement('my-element');
Polymer.Class
is designed to provide similar ergonomics to a speculative future where an ES6 class may be defined and provided to document.registerElement
to achieve the same effect.
Because the vast majority of users will always want to register the custom element prototype generated by Polymer, Polymer provides a Polymer({ ... })
function that wraps calling Polymer.Class
and document.registerElement
.
Example:
MyElement = Polymer({
is: 'my-element',
// See below for lifecycle callbacks
created: function() {
this.innerHTML = 'My element!';
}
});
var el1 = new MyElement();
var el2 = document.createElement('my-element');
While the standard Polymer.Class()
and Polymer()
functions return a basic constructor that can be used to instance the custom element, Polymer also supports providing a factoryImpl
function on the prototype that can, for example, accept arguments to configure the element. In this case, the actual constructor returned from Polymer
will first create an instance using document.createElement
, then invoke the user-supplied factoryImpl
function with this
bound to the element instance.
Example:
MyElement = Polymer({
is: 'my-element',
factoryImpl: function(foo, bar) {
el.foo = foo;
el.configureWithBar(bar);
},
configureWithBar: function(bar) {
...
}
});
var el = new MyElement(42, 'octopus');
Polymer 0.8 currently only supports extending native HTML elements (e.g. input
, button
, etc., as opposed to extending other custom elements). To extend a native HTML element, set the extends
property to the tag name of the element to extend.
Example:
MyInput = Polymer({
is: 'my-input',
extends: 'input',
created: function() {
this.style.border = '1px solid red';
}
});
var el1 = new MyInput();
console.log(el1 instanceof HTMLInputElement); // true
var el2 = document.createElement('input', 'my-input');
console.log(el2 instanceof HTMLInputElement); // true
Polymer's Base prototype implements the standard Custom Element lifecycle callbacks to perform tasks necessary for Polymer's built-in features. The hooks in turn call shorter-named lifecycle methods on your prototype.
created
instead ofcreatedCallback
attached
instead ofattachedCallback
detached
instead ofdetachedCallback
attributeChanged
instead ofattributeChangedCallback
You can always fallback to using the low-level methods if you wish (in other words, you could simply implement createdCallback
in your prototype).
Example:
MyElement = Polymer({
is: 'my-element',
created: function() {
console.log(this.localName + '#' + this.id + ' was created');
},
attached: function() {
console.log(this.localName + '#' + this.id + ' was attached');
},
detached: function() {
console.log(this.localName + '#' + this.id + ' was detached');
},
attributeChanged: function(name, type) {
console.log(this.localName + '#' + this.id + ' attribute ' + name +
' was changed to ' + this.getAttribute(name));
}
});
Polymer.Base
also implements registerCallback
, which will be called by Polymer()
to allow Polymer.Base
to supply a layering system for Polymer abstractions.
See the section on configuring elements for a more in-depth description of the practical uses of each callback.
Placing an object-valued properties
property on your prototype allows you to define metadata regarding your Custom Element's properties, which can then be accessed via an API for use by other Polymer features.
By itself, the properties
feature doesn't do anything. It only provides API for asking questions about these special properties (see featues below for details).
Example:
Polymer({
is: 'x-custom',
properties: {
user: String,
isHappy: Boolean,
count: {
type: Number,
readOnly: true,
notify: true
}
},
ready: function() {
this.innerHTML = 'Hello World, I am a <b>Custom Element!</b>';
}
});
Remember that the fields assigned to count
, such as readOnly
and notify
don't do anything by themselves, it requires other features to give them life, and may depend on which layer of Polymer is in use.
If a property is configured in the properties
object with a type
field, an attribute on the instance matching the property name will be deserialized according to the type specified and assigned to a property of the same name on the element instance. If no other properties
options are specified for a property, the type
(specified using the type constructor, e.g. Object
, String
, etc.) can be set directly as the value of the property in the properties
object; otherwise it should be provided as the value to the type
key in the properties
configuration object.
The type system includes support for Object and Array values expressed as JSON, or Date objects expressed as any Date-parsable string representation. Boolean properties set based on the existence of the attribute: if the attribute exists at all, its value is true, regardless of its string-value (and the value is only false if the attribute does not exist).
Example:
<script>
Polymer({
is: 'x-custom',
properties: {
user: String,
manager: {
type: Boolean,
notify: true
}
},
attached: function() {
// render
this.innerHTML = 'Hello World, my user is ' + (this.user || 'nobody') + '.\n' +
'This user is ' + (this.manager ? '' : 'not') + ' a manager.';
}
});
</script>
<x-custom user="Scott" manager></x-custom>
<!--
<x-custom>'s innerHTML becomes:
Hello World, my user is Scott.
This user is a manager.
-->
In order to configure camel-case properties of elements using attributes, dash-case should be used in the attribute name. Example:
<script>
Polymer({
is: 'x-custom',
properties: {
userName: String,
}
});
</script>
<x-custom user-name="Scott"></x-custom>
<!-- Sets <x-custom>.userName = 'Scott'; -->
Note: Deserialization occurs both at create time, as well as at runtime, e.g. when the attribute is changed via setAttribute
. However, it is encouraged that attributes only be used for configuring properties in static markup, and instead that properties are set directly for changes at runtime.
If a custom elements needs HTML attributes set on it at create-time, these may be declared in a hostAttributes
property on the prototype, where keys are the attribtue name and values are the values to be assigned. Values should typically be provided as strings, as HTML attributes can only be strings; however, the standard serialize
method is used to convert values to strings, so true
will serialize to an empty attribute, and false
will result in no attribtue set, and so forth (see here for more details).
Example:
<script>
Polymer({
is: 'x-custom',
hostAttributes: {
role: 'button',
'aria-disabled': true
tabindex: 0
}
});
</script>
Results in:
<x-custom role="button" aria-disabled tabindex="0"></x-custom>
Polymer supports extending custom element prototypes with shared code modules called "behaviors".
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. Additionally, 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: highlight-behavior.html
HighlightBehavior = {
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
<link rel="import" href="highlight-behavior.html">
<script>
Polymer({
is: 'my-element',
behaviors: [HighlightBehavior]
});
</script>
We call the dom which an element is in charge of creating and managing its local DOM
. This is distinct from the element's children which are sometimes called its light DOM
for clarity.
When native Shadow DOM is used, "local DOM" is actually contained in a shadow root. When the Shady DOM system is used, "local DOM" is a virtual notion maintained by Polymer with similar semantics to Shadow DOM. Polymer normalizes these two systems via a common API, such that you can always think about the "local DOM" and "light DOM" trees in the same way regardless of the underlying implementation.
To specify dom to use for an element's local DOM, use the <dom-module>
element.
Give the <dom-module>
an id
attribute that matches its element's
is
property and put a <template>
inside the <dom-module>
.
Polymer will automatically stamp this template into the element's local DOM.
Example:
<dom-module id="x-foo">
<template>I am x-foo!</template>
</dom-module>
<script>
Polymer({
is: 'x-foo'
});
</script>
We say that an element definition has an imperative and declarative portion. The imperative
portion is the call to Polymer({...})
, and the declarative portion is the <dom-module>
element. The imperative and declarative portions of an element's definition may be placed
in the same html file or in separate files.
NOTE: Defining an element in the main html document is not currently supported.
Polymer 0.8 uses "Shadow DOM styling rules" for providing scoped styling of the element's local DOM. Scoped styles should be provided via <style>
tags placed inside the <dom-module>
for an element (but not inside the <template>
-- note this is a slight deviation from typical Shadow DOM rules).
<dom-module id="my-element">
<style>
:host {
display: block;
border: 1px solid red;
}
#child-element {
background: yellow;
}
/* styling elements distributed to content (via ::content) requires */
/* selecting the parent of the <content> element for compatibility with */
/* shady DOM . This can be :host or a wrapper element. */
.content-wrapper > ::content .special {
background: orange;
}
</style>
<template>
<div id="child-element">In local Dom!</div>
<div class="content-wrapper"><content></content></div>
</template>
</dom-module>
<script>
Polymer({
is: 'my-element'
});
</script>
Loading external stylesheets (as opposed to defining them inline in HTML) for styling local DOM is currently supported via an experimental feature.
To support composition of an element's light DOM with its local DOM, Polymer supports the <content>
element. The <content>
element provides an insertion point at which an element's light DOM is combined with its local DOM. The <content>
element supports a select
attribute which filters nodes via a simple selector.
Polymer supports multiple local DOM implementations. On browsers that support ShadowDOM, ShadowDOM may be used to create local DOM. On other supported browsers, Polymer provides local DOM via a custom implementation called ShadyDOM which is inspired by and compatible with ShadowDOM.
Example:
<template>
<header>Local dom header followed by distributed dom.</header>
<content select=".content"></content>
<footer>Footer after distributed dom.</footer>
</template>
Polymer provides custom API for manipulating DOM such that local DOM and light DOM trees are properly maintained.
The following methods are provided:
Polymer.dom(parent).appendChild(node)
Polymer.dom(parent).insertBefore(node, beforeNode)
Polymer.dom(parent).removeChild(node)
Polymer.dom(parent).querySelector(selector)
Polymer.dom(parent).querySelectorAll(selector)
Polymer.dom(parent).childNodes
Polymer.dom(node).parentNode
Polymer.dom(contentElement).getDistributedNodes()
Polymer.dom(node).getDestinationInsertionPoints()
Polymer.dom.flush()
- The insert, append, and remove operations are trasnacted lazily in certain cases for performance. In order to interrogate the dom (e.g.offsetHeight
,getComputedStyle
, etc.) immediately after one of these operations, callPolymer.dom.flush()
first.
Calling append
/insertBefore
where parent
is a custom Polymer element adds the node to the light DOM of the element. In order to insert/append into the shadow root of a custom element, use this.root
as the parent.
Polymer.dom
properties and methods that return a list of nodes return an Array
, not a NodeList
like the standard DOM equivalent.
Example:
var toLight = document.createElement('div');
Polymer.dom(this).appendChild(toLight);
var toLocal = document.createElement('div');
var beforeNode = Polymer.dom(this.root).childNodes[0];
Polymer.dom(this.root).insertBefore(toLocal, beforeNode);
var allSpans = Polymer.dom(this).querySelectorAll('span');
You can use Polymer.dom
on any node, whether or not it has a local DOM tree:
Example:
<template>
<div id="container">
<div id="first"></div>
<content></content>
</div>
</template>
...
var insert = document.createElement('div');
Polymer.dom(this.$.container).insertBefore(insert, this.$.first);
Sometimes it's necessary to access the elements which have been distributed to a given <content>
insertion point or to know to which <content>
a given node has been distributed. The getDistributedNodes
and getDestinationInsertionPoints
respectively provide this information.
Example:
<x-foo>
<div></div>
</x-foo>
// x-foo's template
<template>
<content></content>
</template>
var div = Polymer.dom(xFoo).querySelector('div');
var content = Polymer.dom(xFoo.root).querySelector('content');
var distributed = Polymer.dom(content).getDistributedNodes()[0];
var insertedTo = Polymer.dom(div).getDestinationInsertionPoints();
// the following should be true:
assert.equal(distributed, div);
assert.equal(insertedTo, content)
Default values for properties may be specified in the properties
object using the value
field. The value may either be a primitive value, or a function that returns a value (which should be used for initializing Objects and Arrays to avoid shared objects on instances).
Example:
Polymer({
is: 'x-custom',
properties: {
mode: {
type: String,
value: 'auto'
},
data: {
type: Object,
notify: true,
value: function() { return {}; }
}
}
});
The ready
method is part of an element's lifecycle and is automatically called 'bottom-up' after the element's template has been stamped and all elements inside the element's local DOM have been configured (with values bound from parents, deserialized attributes, or else default values) and had their ready
method called. Implement ready
when it's necessary to manipulate an element's local DOM when the element is constructed.
Example:
ready: function() {
this.$.ajax.go();
}
Polymer automatically builds a map of instance nodes stamped into its local DOM, to provide convenient access to frequently used nodes without the need to query for (and memoize) them manually. Any node specified in the element's template with an id
is stored on the this.$
hash by id
.
Example:
<dom-module id="x-custom">
<template>
Hello World from <span id="name"></span>!
</template>
</dom-module>
<script>
Polymer({
is: 'x-custom',
created: function() {
this.$.name.textContent = this.name;
}
});
</script>
Event listeners can be added to the host element by providing an object-valued listeners
property that maps events to event handler function names.
Example:
<dom-module id="x-custom">
<template>
<div>I will respond</div>
<div>to a click on</div>
<div>any of my children!</div>
</template>
</dom-module>
<script>
Polymer({
is: 'x-custom',
listeners: {
'click': 'handleClick'
},
handleClick: function(e) {
alert("Thank you for clicking");
}
});
</script>
For adding event listeners to local-DOM children, a more convenient on-<event>
annotation syntax is supported directly in the template. This often eliminates the need to give an element an id
solely for the purpose of binding an event listener.
Example:
<dom-module id="x-custom">
<template>
<button on-click="handleClick">Kick Me</button>
</template>
</dom-module>
<script>
Polymer({
is: 'x-custom',
handleClick: function() {
alert('Ow!');
}
});
</script>
Custom element properties may be observed for changes by specifying observer
property in the properties
for the property that gives the name of a funciton to call. When the property changes, the change handler will be called with the new and old values as arguments.
Example:
Polymer({
is: 'x-custom',
properties: {
disabled: {
type: Boolean,
observer: 'disabledChanged'
},
highlight: {
observer: 'highlightChanged'
}
},
disabledChanged: function(newValue, oldValue) {
this.toggleClass('disabled', newValue);
this.highlight = true;
},
highlightChanged: function() {
this.classList.add('highlight');
setTimeout(function() {
this.classList.remove('highlight');
}, 300);
}
});
Note that property change observation is achieved in Polymer by installing setters on the custom element prototype for properties with registered interest (as opposed to observation via Object.observe or dirty checking, for example).
Observing changes to multiple properties is supported via the observers
array on the prototype, using a string containing a method signature that includes any dependent arguments. Once all properties are defined (!== undefined
), the observer method will be called once for each change to a dependent property. The current values of the dependent properties will be passed as arguments to the observer method in the order defined in the observers
method signature.
Note, computing functions will only be called once all dependent properties are defined (!=undefined
). If one or more of the properties are optional, they would need default value
's defined in properties
to ensure the observer is called.
Note that any observers defined in the observers
array will not receive old
values as arguments, only new values. Only single-property observers defined in the properties
object received both old
and new
values.
Example:
Polymer({
is: 'x-custom',
properties: {
preload: Boolean,
src: String,
size: String
},
observers: [
'updateImage(preload, src, size)'
],
updateImage: function(preload, src, size) {
// ... do work using dependent values
}
});
Observing changes to object sub-properties is also supported via the same observers
array, by specifying a path (e.g. user.manager.name
).
Example:
Polymer({
is: 'x-custom',
properties: {
user: Object
},
observers: [
'userManagerChanged(user.manager)'
],
userManagerChanged: function(user) {
console.log('new manager name is ' + user.name);
}
});
Note that observing changes to paths (object sub-properties) is dependent on one of two requirements: either the value at the path in question changed via a Polymer property binding to another element, or the value was changed using the setPathValue
API, which provides the required notification to elements with registered interest.
Additionally, wildcard matching of path changes is also supported via the observers
array, which allows notification when any (deep) sub-property of an object changes. Note that the argument passed for a path with a wildcard is a change record object containing the path
that changed, the new value
of the path that changed, and the base
value of the wildcard expression.
Example:
Polymer({
is: 'x-custom',
properties: {
user: Object
},
observers: [
'userManagerChanged(user.manager.*)'
],
userManagerChanged: function(changeRecord) {
if (changeRecord.path == 'user.manager') {
// user.manager object itself changed
console.log('new manager name is ' + newValue.name);
} else {
// sub-property of user.manager changed
console.log(changeRecord.path + ' changed to ' + changeRecord.value);
}
}
});
Properties of the custom element may be bound into text content or properties of local DOM elements using binding annotations in the template.
To bind to textContent, the binding annotation must currently span the entire content of the tag:
<dom-module id="user-view">
<template>
<!-- Supported -->
First: <span>{{first}}</span><br>
Last: <span>{{last}}</span>
<!-- Not currently supported! -->
<div>First: {{first}}</div>
<div>Last: {{last}}</div>
</template>
</dom-module>
<script>
Polymer({
is: 'user-view',
properties: {
first: String,
last: String
}
});
</script>
<user-view first="Samuel" last="Adams"></user-view>
To bind to properties, the binding annotation should be provided as the value to an attribute with the same name of the JS property to bind to:
<dom-module id="main-view">
<template>
<user-view first="{{user.first}}" last="{{user.last}}"></user-view>
</template>
</dom-module>
<script>
Polymer({
is: 'main-view',
properties: {
user: Object
}
});
</script>
As in the example above, paths to object sub-properties may also be specified in templates. See Binding to structured data for details.
In order to bind to camel-case properties of elements, dash-case should be used in the attribute name. Example:
<user-view first-name="{{managerName}}"></user-view>
<!-- will set <user-view>.firstName = this.managerName; -->
Note that while HTML attributes are used to specify bindings, values are assigned directly to JS properties, not to the HTML attributes of the elements unless specific attribute bindings are used.
Polymer supports cooperative two-way binding between elements, allowing elements that "produce" data or changes to data to propagate those changes upwards to hosts when desired.
When a Polymer elements changes a property that was configured in properties
with the notify
flag set to true, it automatically fires a non-bubbling DOM event to indicate those changes to interested hosts. These events follow a naming convention of <property>-changed
, and contain a value
property in the event.detail
object indicating the new value.
As such, one could attach an on-<property>-changed
listener to an element to be notified of changes to such properties, set the event.detail.value
to a property on itself, and take necessary actions based on the new value. However, given this is a common pattern, bindings using "curly-braces" (e.g. {{property}}
) will automatically perform this upwards binding automatically without the user needing to perform those tasks. This can be disabled by using "square-brace" syntax (e.g. [[property]]
), which results in only one-way (downward) data-binding.
To summarize, two-way data-binding is achieved when both the host and the child agree to participate, satisfying these three conditions:
- The host must use curly-brace
{{property}}
syntax. Square-brace[[property]]
syntax results in one-way downward binding, regardless of the notify state of the child's property. - The child property being bound to must be configured with the
notify
flag set to true (or otherwise send a<property>-changed
custom event). If the property being bound does not have thenotify
flag set, only one-way (downward) binding will occur. - The child property being bound to must not be configured with the
readOnly
flag set to true. If the child property isnotify: true
andreadOnly:true
, and the host binding uses curly-brace syntax, the binding will effectively be one-way (upward).
Example 1: Two-way binding
<script>
Polymer({
is: 'custom-element',
properties: {
prop: {
type: String,
notify: true
}
}
});
</script>
...
<!-- changes to `value` propagate downward to `prop` on child -->
<!-- changes to `prop` propagate upward to `value` on host -->
<custom-element prop="{{value}}"></custom-element>
Example 2: One-way binding (downward)
<script>
Polymer({
is: 'custom-element',
properties: {
prop: {
type: String,
notify: true
}
}
});
</script>
...
<!-- changes to `value` propagate downward to `prop` on child -->
<!-- changes to `prop` are ignored by host due to square-bracket syntax -->
<custom-element prop="[[value]]"></custom-element>
Example 3: One-way binding (downward)
<script>
Polymer({
is: 'custom-element',
properties: {
prop: String // no `notify:true`!
}
});
</script>
...
<!-- changes to `value` propagate downward to `prop` on child -->
<!-- changes to `prop` are not notified to host due to notify:falsey -->
<custom-element prop="{{value}}"></custom-element>
Example 4: One-way binding (upward)
<script>
Polymer({
is: 'custom-element',
properties: {
prop: {
type: String,
notify: true,
readOnly: true
}
}
});
</script>
...
<!-- changes to `value` are ignored by child due to readOnly:true -->
<!-- changes to `prop` propagate upward to `value` on host -->
<custom-element prop="{{value}}"></custom-element>
Example 5: Error / non-sensical state
<script>
Polymer({
is: 'custom-element',
properties: {
prop: {
type: String,
notify: true,
readOnly: true
}
}
});
</script>
...
<!-- changes to `value` are ignored by child due to readOnly:true -->
<!-- changes to `prop` are ignored by host due to square-bracket syntax -->
<!-- binding serves no purpose -->
<custom-element prop="[[value]]"></custom-element>
As mentioned above, Polymer uses an event naming convention to achieve two-way binding. The act of two-way binding to a property using target-prop={{hostProp}}
syntax results in Polymer adding a <target-prop>-changed
event listener to the element by default. All properties of a Polymer element with notify: true
send events using this convention to notify of changes.
In order to two-way bind to native elements or non-Polymer elements that do not follow this event naming convention when notifying changes, you may specify a custom event name in the curley braces, delimited with ::
.
Example:
<!-- Listens for `input` event and sets hostValue to <input>.value -->
<input value="{{hostValue::input}}">
<!-- Listens for `change` event and sets hostChecked to <input>.checked -->
<input type="checkbox" checked="{{hostChecked::change}}">
<!-- Listens for `timeupdate ` event and sets hostTime to <video>.currentTime -->
<video url="..." current-time="{{hostTime::timeupdate}}">
Note: When binding to standard notifying properties on Polymer elements, specifying the event name is unnecessary, as the default convention will be used. The following constructions are equivalent:
<!-- Listens for `value-changed` event -->
<my-element value="{{hostValue::value-changed}}">
<!-- Listens for `value-changed` event using Polymer convention by default -->
<my-element value="{{hostValue}}">
Sub-properties of objects may be two-way bound to properties of custom elements as well by specifying the path of interest to the binding annotation.
Example:
<template>
<div>{{user.manager.name}}</div>
<user-element user="{{user}}"></user-element>
</template>
As with change handlers for paths, bindings to paths (object sub-properties) are dependent on one of two requirements: either the value at the path in question changed via a Polymer property binding to another element, or the value was changed using the setPathValue
API, which provides the required notification to elements with registered interest, as discussed below.
Note that path bindings are distinct from property bindings in a subtle way: when a property's value changes, an assignment must occur for the value to propagate to the property on the element at the other side of the binding. However, if two elements are bound to the same path of a shared object and the value at that path changes (via a property binding or via setPathValue
), the value seen by both elements actually changes with no additional assignment necessary, by virtue of it being a property on a shared object reference. In this case, the element who changed the path must notify the system so that other elements who have registered interest in the same path may take side effects. However, there is no concept of one-way binding in this case, since there is no concept of propagation. That is, all bindings and change handlers for the same path will always be notified and update when the value of the path changes.
Two-way data-binding and observation of paths in Polymer is achieved using a similar strategy to the one described above for 2-way property binding: When a sub-property of a property configured with type: Object
changes, an element fires a non-bubbling <property>-changed
DOM event with a detail.path
value indicating the path on the object that changed. Elements that have registered interest in that object (either via binding or change handler) may then take side effects based on knowledge of the path having changed. Finally, those elements will forward the notification on to any children they have bound the object to, and will also fire a new <property>-changed
event where property
is the root object, to notify any hosts that may have bound root object down. Through this method, a notification will reach any part of the tree that has registered interest in that path so that side effects occur.
This system "just works" to the extent that changes to object sub-properties occur as a result of being bound to a notifying custom element property that changed. However, sometimes imperative code needs to "poke" at an object's sub-properties directly. As we avoid more sophisticated observation mechanisms such as Object.observe or dirty-checking in order to achieve the best startup and runtime performance cross-platform for the most common use cases, changing an object's sub-properties directly requires cooperation from the user.
Specifically, Polymer provides two API's that allow such changes to be notified to the system: notifyPath(path, value)
and setPathValue(path, value)
.
Example:
<dom-module id="custom-element">
<template>
<div>{{user.manager.name}}</div>
</template>
</dom-module>
<script>
Polymer({
is: 'custom-element',
reassignManager: function(newManager) {
this.user.manager = newManager;
// Notification required for binding to update!
this.notifyPath('user.manager', this.user.manager);
}
});
</script>
Since in the majority of cases, notifyPath will be called directly after an assignment, a convenience function setPathValue
is provided that performs both actions:
reassignManager: function(newManager) {
this.setPathValue('user.manager', newManager);
}
Currently the only binding expression supported in Polymer binding annotations is negation using !
:
Example:
<template>
<div hidden="{{!enabled}}"></div>
</template>
In the vast majority of cases, binding data to other elements should use property binding described above, where changes are propagated by setting the new value to the JavaScript property on the element.
However, there may be cases where a user actually needs to set an attribute on an element, as opposed to a property. These include a handful of problematic native HTML attributes, when attribute selectors are used for CSS or for for interoperability with elements that require using attribute-based API.
Polymer provides an alternate binding annotation syntax to make it explicit when binding values to attributes is desired by using $=
rather than =
. This results in in a call to element.setAttribute('<attr>', value);
, as opposed to element.property = value;
.
<template>
<!-- Attribute binding -->
<my-element selected$="{{value}}"></my-element>
<!-- results in <my-element>.setAttribute('selected', this.value); -->
<!-- Property binding -->
<my-element selected="{{value}}"></my-element>
<!-- results in <my-element>.selected = this.value; -->
</template>
Values will be serialized according to type: Arrays/Objects will be JSON.stringify
'ed, booleans will result in a non-valued attribute to be either set or removed, and Dates
and all primitive types will be serialized using the value returned from toString
.
Again, as values must be serialized to strings when binding to attributes, it is always more performant to use property binding for pure data propagation.
There are a handful of extremely common native element attributes which can also be modified as properties. Due to cross-browser limitations with the ability to place binding braces {{...}}
in some of these attribute values, as well as the fact that some of these attributes map to differently named JS properties, it is recommended to always use attribute binding (using $=
) when binding dynamic values to these specific attributes, rather than binding to their property names.
Normal attribute assignment to static values:
<!-- class -->
<div class="foo"></div>
<!-- style -->
<div style="background: red;"></div>
<!-- href -->
<a href="http://foo.com">
<!-- label for -->
<label for="bar"></label>
<!-- dataset -->
<div data-bar="baz"></div>
Attribute binding to dynamic values (use $=
):
<!-- class -->
<div class$="{{foo}}"></div>
<!-- style -->
<div style$="{{background}}"></div>
<!-- href -->
<a href$="{{url}}">
<!-- label for -->
<label for$="{{bar}}"></label>
<!-- dataset -->
<div data-bar$="{{baz}}"></div>
In specific cases, it may be useful to keep an HTML attribute value in sync with a property value. This may be achieved by setting reflectToAttribute: true
on a property in the properties
configuration object. This will cause any change to the property to be serialized out to an attribute of the same name.
<script>
Polymer({
properties: {
response: {
type: Object,
reflectToAttribute: true
}
},
responseHandler: function(response) {
this.response = 'loaded';
// results in this.setAttribute('response', 'loaded');
}
});
</script>
<a name="attribute-serialization'>
Values will be serialized according to type; by default Arrays/Objects will be JSON.stringify
'ed, booleans will result in a non-valued attribute to be either set or removed, and Dates
and all primitive types will be serialized using the value returned from toString
. The serialize
method may be overridden to supply custom object serialization.
Polymer supports virtual properties whose values are calculated from other properties. Computed properties can be defined in the properties
object by providing a computed
key mapping to a computing function. The name of the function to compute the value is provided as a string with dependent properties as arguments in parenthesis. Once all properties are defined (!== undefined
), the computing function will be called to update the computed property once for each change to a dependent property.
Note, computing functions will only be called once all dependent properties are defined (!=undefined
). If one or more of the properties are optional, they would need default value
's defined in properties
to ensure the property is computed.
<dom-module id="x-custom">
<template>
My name is <span>{{fullName}}</span>
</template>
<dom-module id="x-custom">
<script>
Polymer({
is: 'x-custom',
properties: {
first: String,
last: String,
fullName: {
type: String,
// when `first` or `last` changes `computeFullName` is called once
// (asynchronously) and the value it returns is stored as `fullName`
computed: 'computeFullName(first, last)'
}
},
computeFullName: function(first, last) {
return first + ' ' + last;
}
...
});
</script>
Note: Only direct properties of the element (as opposed to sub-properties of an object) can be used as dependencies at this time.
Anonymous computed properties may also be placed directly in template binding annotations. This is useful when the property need not be a part of the element's API or otherwise used by logic in the element, and is only used for downward data propagation.
Note: this is the only form of functions allowed in template bindings, and they must specify one or more dependent properties as arguments, otherwise the function will not be called.
Note, computing functions will only be called once all dependent properties are defined (!=undefined
). If one or more of the properties are optional, they would need default value
's defined in properties
to ensure the property is computed.
Example:
<dom-module id="x-custom">
<template>
My name is <span>{{computeFullName(first, last)}}</span>
</template>
<dom-module id="x-custom">
<script>
Polymer({
is: 'x-custom',
properties: {
first: String,
last: String
},
computeFullName: function(first, last) {
return first + ' ' + last;
}
...
});
</script>
When a property only "produces" data and never consumes data, this can be made explicit to avoid accidental changes from the host by setting the readOnly
flag to true
in the properties
property definition. In order for the element to actually change the value of the property, it must use a private generated setter of the convention _set<Property>(value)
.
<script>
Polymer({
properties: {
response: {
type: Object,
readOnly: true,
notify: true
}
},
responseHandler: function(response) {
this._setResponse(response);
}
...
});
</script>
Generally, read-only properties should also be set to notify: true
such that their changes are observable from above.
Polymer's Base prototype provides a set of useful convenience/utility functions for instances to use. See API documentation for more details.
- toggleClass: function(name, bool, [node])
- toggleAttribute: function(name, bool, [node])
- attributeFollows: function(name, neo, old)
- fire: function(type, [detail], [onNode], [bubbles], [cancelable])
- async: function(method)
- transform: function(node, transform)
- translate3d: function(node, x, y, z)
- importHref: function(href, onload, onerror)
Document-level global Polymer settings can be set before loading by setting a Polymer
object on window as the first script in the main document:
<html>
<head>
<meta charset="utf-8">
<script> Polymer = { dom: 'shadow' }; </script>
<script src="../../../webcomponentsjs/webcomponents-lite.js"></script>
<link rel="import" href="components/my-app.html">
</head>
<body>
...
Settings can also be switched on the URL query string:
http://myserver.com/test-app/index.html?dom=shadow
Available settings:
dom
- options:shady
- all local DOM will be rendered using Shady DOM (even where shadow-DOM supported (current default)shadow
- local DOM will be rendered using Shadow DOM where supported (this will be made default soon)
EXPERIMENTAL - API MAY CHANGE
Elements in a template can be automatically repeated and bound to array items using a custom HTMLTemplateElement
type extension called x-repeat
. x-repeat
accepts an items
property, and one instance of the template is stamped for each item into the DOM at the location of the x-repeat
element. The item
property will be set on each instance's binding scope, thus templates should bind to sub-properties of item
. Example:
<dom-module id="employee-list">
<template>
<div> Employee list: </div>
<template is="x-repeat" items="{{employees}}">
<div>First name: <span>{{item.first}}</span></div>
<div>Last name: <span>{{item.last}}</span></div>
</template>
</template>
<script>
Polymer({
is: 'employee-list',
ready: function() {
this.employees = [
{first: 'Bob', last: 'Smith'},
{first: 'Sally', last: 'Johnson'},
...
];
}
});
</script>
</dom-module>
Notifications for changes to items sub-properties will be forwarded to template instances, which will update via the normal structured data notification system.
Mutations to the items
array itself (push
, pop
, splice
, shift
, unshift
) are observed via Array.observe
(where supported, or an experimental shim of this API on unsupported browsers), and template instances are kept in sync with the data in the array.
A view-specific filter/sort may be applied to each x-repeat
by supplying a filter
and/or sort
property. This may be a string that names a function on the host, or a function may be assigned to the property directly. The functions should implemented following the standard Array
filter/sort API.
In order to re-run the filter or sort functions based on changes to sub-fields of items
, the observe
property may be set as a space-separated list of item
sub-fields that should cause a re-filter/sort when modified.
For example, for an x-repeat
with a filter of the following:
isEngineer: function(item) {
return item.type == 'engineer' || item.manager.type == 'engineer';
}
Then the observe
property should be configured as follows:
<template is="x-repeat" items="{{employees}}"
filter="isEngineer" observe="type manager.type">
Note, to reach the outer parent scope, bindings in an x-repeat
template may be prefixed with parent.<property>
.
EXPERIMENTAL - API MAY CHANGE
Keeping structured data in sync requires that Polymer understand the path associations of data being bound. The x-array-selector
element ensures path linkage when selecting specific items from an array (either single or multiple). The items
property accepts an array of user data, and via the select(item)
and deselect(item)
API, updates the selected
property which may be bound to other parts of the application, and any changes to sub-fields of selected
item(s) will be kept in sync with items in the items
array. When multi
is false, selected
is a property representing the last selected item. When multi
is true, selected
is an array of multiply selected items.
<dom-module id="employee-list">
<template>
<div> Employee list: </div>
<template is="x-repeat" id="employeeList" items="{{employees}}">
<div>First name: <span>{{item.first}}</span></div>
<div>Last name: <span>{{item.last}}</span></div>
<button on-click="toggleSelection">Select</button>
</template>
<x-array-selector id="selector" items="{{employees}}" selected="{{selected}}" multi toggle></x-array-selector>
<div> Selected employees: </div>
<template is="x-repeat" items="{{selected}}">
<div>First name: <span>{{item.first}}</span></div>
<div>Last name: <span>{{item.last}}</span></div>
</template>
</template>
<script>
Polymer({
is: 'employee-list',
ready: function() {
this.employees = [
{first: 'Bob', last: 'Smith'},
{first: 'Sally', last: 'Johnson'},
...
];
},
toggleSelection: function(e) {
var item = this.$.employeeList.itemForElement(e.target);
this.$.selector.select(item);
}
});
</script>
</dom-module>
EXPERIMENTAL - API MAY CHANGE
Elements can be conditionally stamped based on a boolean property by wrapping them in a custom HTMLTemplateElement
type extension called x-if
. The x-if
template stamps itself into the DOM only when its if
property becomes truthy.
If the if
property becomes falsy again, by default all stamped elements will be hidden (but will remain in DOM) for faster performance should the if
property become truthy again. This behavior may be disabled by setting the restamp
property, which results in slower if
switching behavior as the elements are destroyed and re-stamped each time.
Note, to reach the outer parent scope, all bindings in an x-if
template must be prefixed with parent.<property>
, as shown below.
Example:
Note, this is a simple example for illustrative purposes only. Read below for guidance on recommended usage of conditional templates.
<dom-module id="user-page">
<template>
All users will see this:
<div>{{user.name}}</div>
<template is="x-if" if="{{user.isAdmin}}">
Only admins will see this.
<div>{{parent.user.secretAdminStuff}}</div>
</template>
</template>
<script>
Polymer({
is: 'user-page',
properties: {
user: Object
}
});
</script>
</dom-module>
Note, since it is generally much faster to hide/show elements rather than create/destroy them, conditional templates are only useful to save initial creation cost when the elements being stamped are relatively heavyweight and the conditional may rarely (or never) be true in given useages. Otherwise, liberal use of conditional templates can actually add significant runtime performance overhead.
Consider an app with 4 screens, plus an optional admin screen. If most users will use all 4 screens during normal use of the app, it is generally better to incur the cost of stamping those elements once at startup (where some app initialization time is expected) and simply hide/show the screens as the user navigates through the app, rather than re-create and destroy all the elements of each screen as the user navigates. Using a conditional template here may be a poor choice, since although it may save time at startup by stamping only the first screen, that saved time gets shifted to runtime latency for each user interaction, since the time to show the second screen will be slower as it must create the second screen from scratch rather than simply showing that screen. Hiding/showing elements is as simple as attribute-binding to the hidden
attribute (e.g. <div hidden$="{{!shouldShow}}">
), and does not require conditional templating at all.
However, using a conditional template may be appropriate in the case of an admin screen that should only be shown to admin users of an app. Since most users would not be admins, there may be performance benefits to not burdening most of the users with the cost of stamping the elements for the admin page, especially if it is relatively heavyweight.
EXPERIMENTAL - API MAY CHANGE
Polymer's binding features are only available within templates that are managed by Polymer. As such, these features are available in templates used to define Polymer elements, for example, but not for elements placed directly in the main document.
In order to use Polymer bindings without defining a new custom element, you may wrap the elements utilizing bindings with a custom template extension called x-autobind
. This template will immediately stamp itself into the main document and bind elements to the template itself as the binding scope.
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<script src="components/webcomponentsjs/webcomponents-lite.js"></script>
<link rel="import" href="components/polymer/polymer.html">
<link rel="import" href="components/core-ajax/core-ajax.html">
</head>
<body>
<!-- Wrap elements in with auto-binding template to -->
<!-- allow use of Polymer bindings main document -->
<template is="x-autobind">
<core-ajax url="http://..." lastresponse="{{data}}"></core-ajax>
<template is="x-repeat" items="{{data}}">
<div><span>{{item.first}}</span> <span>{{item.last}}</span></div>
</template>
</template>
</body>
</html>
EXPERIMENTAL - API MAY CHANGE
Shadow DOM (and its approximation via Shady DOM) bring much needed benefits of scoping and style encapsulation to web development, making it safer and easier to reason about the effects of CSS on parts of your application. Styles do not leak into the local DOM from above, and styles do not leak from one local DOM into the local DOM of other elements inside.
This is great for protecting scopes from unwanted style leakage. But what about when you intentionally want to customize the style of a custom element's local DOM, as the user of an element? This often comes up under the umbrella of "theming". For example a "custom-checkbox" element that may interally use a .checked
class can protect itself from being affected by CSS from other components that may also happen to use a .checked
class. However, as the user of the checkbox you may wish to intentionally change the color of the check to match your product's branding, for example. The "protection" that Shadow DOM provides at the same time introduces a practical barrier to "theming" use cases.
One solution the Shadow DOM spec authors provided to address the theming problem are the /deep/
and ::shadow
combinators, which allow writing rules that pierce through the Shadow DOM encapsulation boundary. Although Polymer 0.5 promoted this mechanism for theming, it was ultimately unsatisfying for several reasons:
- Using
/deep/
and::shadow
for theming leaks details of an otherwise encapsulated element to the user, leading to brittle selectors piercing into the internal details of an element's Shadow DOM that are prone to breakage when the internal implementation changes. As a result, the structure of of an element's Shadow DOM inadvertently becomes API surface subject to breakage, diminishing the practical effectiveness of Shadow DOM as an encapsulation primitive. - Although Shadow DOM's style encapsulation improves the predictability of style recalc performance since the side effects of a style change are limited to a small subset of the document, using
/deep/
and::shadow
re-open the style invalidation area and reduce Shadow DOM's effectiveness as a performance primitive. - Using
/deep/
and::shadow
lead to verbose and difficult to understand selectors.
For the reasons above, the Polymer team is currently exploring other options for theming that address the shortcomings above and provide a possible path to obsolescence of /deep/
and ::shadow
altogether.
Polymer 0.8 includes a highly experimental and opt-in shim for custom CSS properties inspired by (and compatible with) the future W3C CSS Custom Properties for Cascading Variables specification (see explainer on MDN here).
Rather than exposing the details of an element's internal implementation for theming, instead an element author would define one or more custom CSS properties as part of the element's API which it would consume to style internals of the element deemed important for theming by the element's author. These custom properties can be defined similar to other standard CSS properties and will inherit from the point of definition down the composed DOM tree, similar to the effect of color
and font-family
.
In the simple example below, the author of my-toolbar
identified the need for users of the toolbar to be able to change the color of the toolbar title. The author exposed a custom property called --my-toolbar-title-color
which is assigned to the color
property of the selector for the title element. Users of the toolbar may define this variable in a CSS rule anywhere up the tree, and the value of the property will inherit down to the toolbar where it is used if defined, similar to other standard inheriting CSS properties.
Example:
<dom-module id="my-toolbar">
<style>
:host {
padding: 4px;
background-color: gray;
}
.title {
color: var(--my-toolbar-title-color);
}
</style>
<template>
<span class="title">{{title}}</span>
</template>
<script>
Polymer({
is: 'my-toolbar',
properties: {
title: String
},
// The custom properties shim is currently an opt-in feature
enableCustomStyleProperties: true
});
</script>
</dom-module>
Example usage of my-toolbar
:
<dom-module id="my-element">
<style>
/* Make all toolbar titles in this host green by default */
:host {
--my-toolbar-title-color: green;
}
/* Make only toolbars with the .warning class red */
.warning {
--my-toolbar-title-color: red;
}
</style>
<template>
<my-toolbar title="This one is green."></my-toolbar>
<my-toolbar title="This one is green too."></my-toolbar>
<my-toolbar class="warning" title="This one is red."></my-toolbar>
</template>
</dom-module>
The --my-toolbar-title-color
property will only affect the color of the title element encapsulated in my-toolbar
's internal implementation. If in the future the my-toolbar
author chose to rename the .title
class or otherwise restructure the internal details of my-toolbar
, users are shielded from this change via the indirection afforded by custom properties.
Thus, custom CSS properties introduce a powerful way for element authors to expose a theming API to their users in a way that naturally fits right alongside normal CSS styling and avoids the problems with /deep/
and ::shadow
, and is already on a standards track with shipping implementation by Mozilla and planned support by Chrome.
However, it may be tedious (or impossible) for an element author to anticipate and expose every possible CSS property that may be important for theming an element as individual CSS properties (for example, what if a user needed to adjust the opacity
of the toolbar title?). For this reason, the custom properties shim included in Polymer includes an experimental extension allowing a bag of CSS properties to be defined as a custom property and allowing all properties in the bag to be applied to a specific CSS rule in an element's local DOM. For this, we introduce a mixin
capability that is analogous to var
, but allows an entire bag of properties to be mixed in.
Example:
<dom-module id="my-toolbar">
<style>
:host {
padding: 4px;
background-color: gray;
mixin(--my-toolbar-theme);
}
.title {
mixin(--my-toolbar-title-theme);
}
</style>
<template>
<span class="title">{{title}}</span>
</template>
...
</dom-module>
Example usage of my-toolbar
:
<dom-module id="my-element">
<style>
/* Apply custom theme to toolbars */
:host {
--my-toolbar-theme: {
background-color: green;
border-radius: 4px;
border: 1px solid gray;
}
--my-toolbar-title-theme: {
color: green;
}
}
/* Make only toolbars with the .warning class red and bold */
.warning {
--my-toolbar-title-theme: {
color: red;
font-weight: bold;
}
}
</style>
<template>
<my-toolbar title="This one is green."></my-toolbar>
<my-toolbar title="This one is green too."></my-toolbar>
<my-toolbar class="warning" title="This one is red."></my-toolbar>
</template>
</dom-module>
Experimental cross-platform support for custom properties is provided in Polymer by a Javascript library that approximates the capabilities of the CSS Variables specification for the specific use case of theming custom elements, while also extending it to add the mixin capability described above. It is important to note that this is not a full polyfill, as doing so would be prohibitively expensive; rather this is a shim that is inspired by that specification and trades off aspects of the full dynamism possible in CSS with practicality and performance.
Below are current limitations of this experimental system. Improvements to performance and dynamism will continue to be explored.
-
As this feature is still experimental, custom properties are not currently applied to elements by default. To enable usage of custom properties, set an
enableCustomStyleProperties: true
property on the Polymer element prototype. -
Only rules which match the element at creation time are applied. Any dynamic changes that update variable values are not applied automatically.
<div class="container"> <x-foo class="a"></x-foo> </div>
/* applies */ x-foo.a { --foo: brown; } /* does not apply */ x-foo.b { --foo: orange; } /* does not apply to x-foo */ .container { --nog: blue; }
-
Re-evaluation of custom property styles does not currently occur as a result of changes to the DOM. Re-evaluation can be forced by calling
this.updateStyles()
on a Polymer element. For example, if classb
was added tox-foo
above, the scope must callthis.updateStyles()
to apply the styling. This re-calcs/applies styles down the tree from this point. -
Dynamic effects are reflected at the point of a variable’s application, but not its definition.
For the following example, adding/removing the
highlighted
class on the#title
element will have the desired effect, since the dynamism is related to application of a custom property.#title { background-color: var(--title-background-normal); } #title.highlighted { background-color: var(--title-background-highlighted); }
However, the shim does not currently support dynamism at the point of definition of a custom property. In the following example,
this.updateStyles()
would be required to update the value of--title-background
being applied to#title
when thehighlighted
class was added or removed.#title { --title-background: gray; } #title.highlighted { --title-background: yellow; }
EXPERIMENTAL - API MAY CHANGE
An experimental <style is="x-style">
custom element is provided for defining styles in the main document that can take advantage of several special features of Polymer's styling system:
- Document styles defined in an
x-style
will be shimmed to ensure they do not leak into local DOM when running on browsers without non-native Shadow DOM. - Shadow DOM-specific
/deep/
and::shadow
combinators will be shimmed on browsers without non-native Shadow DOM. - Custom properties used by Polymer's experimental shim for cross-scope styling may be defined in an
x-style
.
Example:
<!doctype html>
<html>
<head>
<script src="components/webcomponentsjs/webcomponents-lite.js"></script>
<link rel="import" href="components/polymer/polymer.html">
<style is="x-style">
/* Will be prevented from affecting local DOM of Polymer elements */
* {
box-sizing: border-box;
}
/* Can use /deep/ and ::shadow combinators */
body /deep/ .my-special-view::shadow #thing-inside {
background: yellow;
}
/* Custom properties that inherit down the document tree may be defined */
* {
--my-toolbar-title-color: green;
}
</style>
</head>
<body>
...
</body>
</html>
Note, all features of x-style
are available when defining styles as part of Polymer elements (e.g. <style>
elements within <dom-module>
's used for defining Polymer elements. The x-style
extension should only be used for defining document styles, outside of a custom element's local DOM.
EXPERIMENTAL - API MAY CHANGE
Polymer includes an experimental feature to support loading external stylesheets that will be applied to the local DOM of an element. This is typically convenient for developers who like to separate styles, share common styles between elements, or use style pre-processing tools. The syntax is slightly different from how stylesheets are typically loaded, as the feature leverages HTML Imports (or the HTML Imports polyfill, where appropriate) to load the stylesheet text such that it may be properly shimmed and/or injected as an inline style.
To include a remote stylesheet that applies to your Polymer element's local DOM, place a special HTML import <link>
tag with type="css"
in your <dom-module>
that refers to the external stylesheet to load.
Example:
<dom-module id="my-awesome-button">
<!-- special import with type=css used to load remote CSS -->
<link rel="import" type="css" href="my-awesome-button.css">
<template>
...
</template>
<script>
Polymer({
is: 'my-awesome-button',
...
});
</script>
</dom-module>
EXPERIMENTAL - API MAY CHANGE
Polymer 0.8 is currently layered into 3 sets of features provided as 3 discrete HTML imports, such that an individual element developer can depend on a version of Polymer whose feature set matches their tastes/needs. For authors who opt out of the more opinionated local DOM or data-binding features, their element's dependencies would not be payload- or runtime-burdened by these higher-level features, to the extent that a user didn't depend on other elements using those features on that page. That said, all features are designed to have low runtime cost when unused by a given element.
Higher layers depend on lower layers, and elements requiring lower layers will actually be imbued with features of the highest-level version of Polymer used on the page (those elements would simply not use/take advantage of those features). This provides a good tradeoff between element authors being able to avoid direct dependencies on unused features when their element is used standalone, while also allowing end users to mix-and-match elements created with different layers on the same page.
- polymer-micro.html: Polymer micro features (bare-minum Custom Element sugaring)
- polymer-mini.html: Polymer mini features (template stamped into "local DOM" and tree lifecycle)
- polymer.html: Polymer standard features (all other features: declarative data binding and event handlers, property nofication, computed properties, and experimental features)
This layering is subject to change in the future and the number of layers may be reduced.
This section covers how to deal with yet-unimplemented and/or de-scoped features in Polymer 0.8 as compared to 0.5. Many of these are simply un-implemented; that is, we will likely have a final "solution" that addresses the need, we just haven't tackled that feature yet as we address items in priority order. Other solutions in 0.8 may be lower-level as compared to 0.5, and will be explained here.
As the final 0.8 API solidifies, this section will be updated accordingly. As such, this section should be considered answers "how do I solve problem xyz TODAY", rather than a representation of the final Polymer 0.8 API.
TL;DR: When binding to camel-cased properties, use "dash-case" attribute names to indicate the "camelCase" property to bind to.
Example: bind this.myValue
to <x-foo>.thatValue
:
BEFORE: 0.5
<x-foo thatValue="{{myValue}}"></x-foo>
AFTER: 0.8
<x-foo that-value="{{myValue}}"></x-foo>
In 0.5, binding annotations were allowed to mixed-case properties (despite the fact that attribute names always get converted to lower-case by the HTML parser), and the Node.bind implementation at the "receiving end" of the binding automatically inferred the mixed-case property it was assumed to refer to at instance time.
In 0.8, "binding" is done at prorotype time before the type of the element being bound to is known, hence knowing the exact JS property to bind to allows better efficiency.
Current limitations that are on the backlog for evaluation/improvement are listed below, with current workarounds:
-
Sub-textContent/property binding
-
You cannot currrently do any of the following:
<div> stuff here: {{stuff}}</div> <div class$="{{thing}} {{another}}"></div> <x-custom prop="{{thing}} {{another}}"></x-custom>
-
Instead, use
<span>
's to break up textContent into discrete elements:<div> stuff here: <span>{{stuff}}</span></div>
-
Use computed properties for concatenating into properties/attributes:
<div class$="{{computeDivClass(thing, another)}}"></div> <x-custom prop="{{computeCustomProp(thing, another}}"></x-custom>
-
-
CSS class binding:
- May bind entire class list from one property to
class
attribute:<div class$="{{classes}}">
- Otherwise,
this.classList.add/remove
from change handlers
- May bind entire class list from one property to
-
CSS inline-style binding:
- May bind entire inline style from one property to
style
attribute:<div style$="{{styles}}">
- Otherwise, assign
this.style.props
from change handlers
- May bind entire inline style from one property to
To notify non-bound structured data changes, use setPathValue
and notifyPath
:
this.setPathValue('user.manager', 'Matt');
Which is equivalent to:
this.user.manager = 'Matt';
this.notifyPath('user.manager', this.user.manager);
Repeating templates is moved to a custom element (HTMLTemplateElement type extension called x-repeat
):
<template is="x-repeat" items="{{users}}">
<div>{{item.name}}</div>
</template>
This area is in high flux. Arrays bound to x-repeat
are currently observed using Array.observe
(or equivalent shim) and x-repeat
will reflect changes to array mutations (push, pop, shift, unshift, splice) asynchronously.
In-place sort of array is not supported. Sorting/filtering will likely be provided as a feature of x-repeat
(and possibly other array-aware elements such as x-list
) in the future.
Implementation and usage details will likely change, stay tuned.
TODO - use composition for now
TODO - use standard DOM for now until gesture support is ported