Setup
The first step is obtaining Polymer, which is generally done with the
Bower
package manager. The following bower.json
will install Polymer's base and material design elements.
{
"name": "my-aurelia-polymer-project",
"private": true,
"dependencies": {
"polymer": "Polymer/polymer#^1.2.0",
"paper-elements": "PolymerElements/paper-elements#^1.0.6",
"iron-elements": "PolymerElements/iron-elements#^1.0.4",
"webcomponentsjs": "webcomponents/webcomponentsjs#^0.7.20"
}
}
Make sure Bower is installed and then use Bower to install the packages for each of the Polymer elements.
$ npm install -g bower
$ bower install
Aurelia must also be configured to use the HTML Imports template loader and the aurelia-polymer
plugin, both of which can be installed with JSPM.
$ jspm install aurelia-html-import-template-loader
$ jspm install aurelia-polymer@^1.0.0-beta
In index.html
, Polymer and the webcomponents.js polyfills need to be loaded before Aurelia is, so that the aurelia-polymer
plugin can hook into Polymer's element registration system.
<!DOCTYPE html>
<html>
<head>
<!-- ... -->
<script src="bower_components/webcomponentsjs/webcomponents-lite.js"></script>
<link rel="import" href="bower_components/polymer/polymer.html">
</head>
<!-- ... -->
</html>
It's also a good idea to wait until the web components polyfill has loaded Polymer to bootstrap Aurelia. Instead of directly importing aurelia-bootstrapper
, wait until the WebComponentsReady
event fires in index.html
.
<!DOCTYPE html>
<script>
document.addEventListener('WebComponentsReady', function() {
System.import('aurelia-bootstrapper');
});
</script>
In main.js
, the two Aurelia plugins installed before need to be loaded as well.
export function configure(aurelia) {
aurelia.use
.standardConfiguration()
.developmentLogging();
aurelia.use.plugin('aurelia-polymer');
aurelia.use.plugin('aurelia-html-import-template-loader');
aurelia.start().then(a => a.setRoot());
}
import {Aurelia} from 'aurelia-framework';
export function configure(aurelia: Aurelia): void {
aurelia.use
.standardConfiguration()
.developmentLogging();
aurelia.use.plugin('aurelia-polymer');
aurelia.use.plugin('aurelia-html-import-template-loader');
aurelia.start().then(a => a.setRoot());
}
At this point, Aurelia and Polymer are ready to go. The examples below incorporate various Polymer elements into the Aurelia skeleton navigation starter.
Importing Elements
With the normal Aurelia template loader, nothing is allowed outside the root <template>
element. When using HTML imports, however, the import statements must be before the <template>
.
<link rel="import" href="/bower_components/paper-drawer-panel/paper-drawer-panel.html">
<link rel="import" href="/bower_components/paper-toolbar/paper-toolbar.html">
<link rel="import" href="/bower_components/paper-menu/paper-menu.html">
<link rel="import" href="/bower_components/paper-item/paper-item.html">
<link rel="import" href="/bower_components/paper-scroll-header-panel/paper-scroll-header-panel.html">
<link rel="import" href="/bower_components/paper-icon-button/paper-icon-button.html">
<link rel="import" href="/bower_components/iron-icons/iron-icons.html">
<template>
<!-- ... -->
</template>
Data Binding
Given the imports above, this shows how to implement a basic layout in app.html
with Polymer components.
<template>
<paper-drawer-panel force-narrow>
<div drawer>
<paper-toolbar class="paper-header">
<span>Menu</span>
</paper-toolbar>
<paper-menu>
<paper-item repeat.for="row of router.navigation" active.bind="row.isActive">
<a href.bind="row.href">${row.title}</a>
</paper-item>
</paper-menu>
</div>
<div main>
<paper-toolbar class="paper-header">
<paper-icon-button icon="menu" tabindex="1" paper-drawer-toggle></paper-icon-button>
<div class="title">${router.title}</div>
<span if.bind="router.isNavigating"><iron-icon icon="autorenew"></iron-icon></span>
</paper-toolbar>
<div>
<router-view></router-view>
</div>
</div>
</paper-drawer-panel>
</template>
Notice that Poylmer elements such as <paper-drawer-panel>
or <paper-menu>
, once imported, can be used just like
normal HTML elements.
All of the standard Aurelia binding syntax continues to work as well. For example,
repeat.for
is used to populate menu items from the Aurelia router. The active.bind
binding on the <paper-item>
elements in the navigation menu shows that attributes
defined by Polymer are also supported by Aurelia.
Forms and Two-Way Binding
The updated welcome.html
uses Polymer input elements to enhance its form.
<link rel="import" href="../bower_components/paper-input/paper-input.html">
<link rel="import" href="../bower_components/iron-form/iron-form.html">
<template>
<section class="au-animate">
<h2>${heading}</h2>
<form is="iron-form" role="form" submit.delegate="submit()">
<div class="form-group">
<label for="fn">First Name</label>
<paper-input id="fn" value.two-way="firstName" placeholder="first name"></paper-input>
</div>
<div class="form-group">
<label for="ln">Last Name</label>
<paper-input id="fn" value.two-way="lastName" placeholder="last name"></paper-input>
</div>
<div class="form-group">
<label>Full Name</label>
<p class="help-block">${fullName | upper}</p>
</div>
<button type="submit" class="btn btn-default">Submit</button>
</form>
</section>
</template>
While not strictly necessary in this case, making the form an <iron-form>
ensures
that Polymer input elements are submitted along with native HTML elements.
Note that the <paper-input>
elements have .two-way
bindings, not just .bind
.
With the exception of native HTML form elements, Aurelia defaults to one-way bindings,
so two-way data binding must be specified explicitly.
A normal <button>
element is used with Aurelia's submit.delegate
binding, since
Polymer elements cannot submit forms.
If form submission semantics were not needed, another option would be to
use a click.delegate
on a Polymer button instead.
<paper-button raised click.delegate="submit()">Submit</paper-button>
How the Plugin Works
The aurelia-polymer
plugin is fairly simple. For each Polymer element, it
registers the element's properties and what events they support with the
EventManager
in Aurelia's binding component. The core of the plugin, the
registerElement
function, loops over all properties of the element and any behavior it implements
so that the Aurelia binding engine will register event listeners properly.
var propertyConfig = {'bind-value': ['change', 'input']}; // Not explicitly listed for all elements that use it
function handleProp(propName, prop) {
if (prop.notify) {
propertyConfig[propName] = ['change', 'input'];
}
}
Object.keys(prototype.properties).forEach(propName => handleProp(propName, prototype.properties[propName]));
prototype.behaviors.forEach(behavior => {
if (typeof behavior.properties != 'undefined') {
Object.keys(behavior.properties).forEach(propName => handleProp(propName, behavior.properties[propName]));
}
});
eventManager.registerElementConfig({
tagName: prototype.is,
properties: propertyConfig
});
var propertyConfig = {'bind-value': ['change', 'input']}; // Not explicitly listed for all elements that use it
function handleProp(propName, prop) {
if (prop.notify) {
propertyConfig[propName] = ['change', 'input'];
}
}
Object.keys(prototype.properties).forEach(propName => handleProp(propName, prototype.properties[propName]));
prototype.behaviors.forEach(behavior => {
if (typeof behavior.properties != 'undefined') {
Object.keys(behavior.properties).forEach(propName => handleProp(propName, behavior.properties[propName]));
}
});
eventManager.registerElementConfig({
tagName: prototype.is,
properties: propertyConfig
});
Element properties marked with notify
are eligible for Polymer's two-way data
binding system and also trigger a {property-name}-changed
event. This corresponds
to the input
and change
events in Aurelia.
Whenever a Polymer element is imported, it triggers a call to Polymer.telemetry._registrate
,
which adds the element prototype to a list of registered Polymer elements. The
aurelia-polymer
plugin replaces this registration function with one that also
calls registerElement
on the prototype so that it is configured with Aurelia
as well. The need to override this function is why Polymer must be loaded before
the plugin is.