At the start of the web we had tags..like
<form>
and <select>
. We didn't need much more to build our pages.
They were declarative, encapsulated and meaningful. They had default UI.
They'd inform us if something interesting happened.
Over time we've stuffed boilerplate, widgets and plugins into our pages.
We end up with bloated HTML. bloated JS. Or both.
What if HTML was expressive enough to allow us to create our own tags to fill in the gaps in the platform?
Examples
Polymer
Polymer is a new type of library for the web, built on top of Web Components, and designed to leverage the evolving web platform … on modern browsers.
polymer-project.org
What is Polymer?
Polyfills?
A framework?
UI widgets?
Let's go back in time!
Get A T-Shirt!
<select> <option>Small</option> <option>Medium</option> <option>Large</option> </select>
<select id="schwag"> ... <option disabled>Medium</option> <option disabled>Large</option> <option selected>XX-large</option> </select>
<select size="4" multiple> <option>Do</option> <option>Re</option> <option>Mi</option> ... </select>
<select> <optgroup label="French cars"> <option>Bugatti</option> <option>Citroen</option> </optgroup> ... </select>
Where are we now?
<gangnam-style></gangnam-style>
<photo-booth></photo-booth>
<button is="mega-button">Mega button</button>
Tabs: a common component on the web
Reminder: building one today..
Pile on the JavaScript
Our markup is terrible
Seriously?
Markup can be meaningful again
<my-tabs> <my-tab selected>...</my-tab> <my-tab>...</my-tab> <my-tab>...</my-tab> </my-tabs>
var tabs = document.createElement('my-tabs'); var tab1 = document.createElement('my-tab'); var tab2 = document.createElement('my-tab'); var tab3 = document.createElement('my-tab'); tabs.appendChild(tab1); tabs.appendChild(tab2); tabs.appendChild(tab3); tab1.setAttribute('selected', true);
Markup can be meaningful again
<hangout-module> <hangout-chat from="Paul, Addy" profile="118075919496626375791"> <hangout-discussion> <hangout-message from="Paul" profile="profile.png" datetime="2013-07-17T12:02"> <p>Feelin' this Web Components thing.</p> <p>Heard of it?</p> </hangout-message> <hangout-message from="Addy" datetime="2013-07-17T12:12">...</hangout-message> <hangout-message>...</hangout-message> ... </hangout-discussion> </hangout-chat> <hangout-chat></hangout-chat> </hangout-module>
Polymer
Built on standards
Templates
HTML Imports
Custom Elements
Shadow DOM
Using a component today
<link rel="stylesheet" type="text/css" href="my-widget.css" /> <script src="my-widget.js"></script>
<div data-my-widget />
$(function() { $('[data-my-widget]').myWidget(); });
div.innerHTML = '<div data-my-widget />'
$(div).find('[data-my-widget]').myWidget();
The Web Component way
Import:
<link rel="import" href="my-widget.html" />
Consume:
<my-widget />
div.innerHTML = '<my-widget />';
Get encapsulation for free:
#document-fragment <div> <input type="text" /> <button>Go</button> </div>
Reminder: DOM Elements
Elements can be instantiated with markup
<input type="text" />
Elements can be instantiated with JS
var input = document.createElement('input'); el.innerHTML = '<input type="text" />';
Elements can respond to attribute changes
input.setAttribute('value', 'Foobar') input.value; // "Foobar"
DOM Elements
Elements can have hidden internal DOM structures
input.bullet(type="date") <input type="date" /> dateInput.children.length; // 0
Elements can have their own private styles
input.bullet(type="date")
Elements can provide style hooks to their internals
dialog::backdrop { background: black; }
Custom Elements
Register a new custom element my-element
var MyElement = document.registerElement('my-element'); document.body.appendChild(new MyElement());
Instantiate a custom element in JS
var myElement = document.createElement('my-element'); myElement.addEventListener('click', function(e) { alert('Thanks!'); });
Custom elements can perform their own initialisation
document.registerElement('my-element', { prototype: Object.create(HTMLElement.prototype, { ...
Custom Elements can respond to change
Custom elements can respond to attribute changes
document.registerElement('my-element', { prototype: Object.create(HTMLElement.prototype, { attributeChangedCallback: { value: function(attr, oldVal, newVal) { this.innerHTML = '<h1>ATTRIBUTE CHANGED!</h1>';; } } }) });
Other lifecycle events:
- createdCallback
- attachedCallback
- detachedCallback
Custom Elements can be extended
Extending native custom elements
var MegaButton = document.registerElement('mega-button', { // inherit the prototype of HTMLButtonElement prototype: Object.create(HTMLButtonElement.prototype) });
Type extension custom element:
<button is="mega-button">
Custom Elements can encapsulate styles & DOM
Custom elements have encapsulated styles by default
shadow = this.createShadowRoot(); shadow.innerHTML = "<style>span { color: green; }</style>" + "<span>I'm green</span>";
<my-element /> <span>I'm not green</span>
Custom elements can have hidden internal DOM structures (shadow host)
<button>Hello, world!</button> <script> var host = document.querySelector('button'); var root = host.createShadowRoot(); root.textContent = 'How you doing?'; </script>
Custom Elements can be styled
Style the current element hosting a shadow tree
:host { color: green; }
Piece one level of the Shadow DOM (^) ~ upper shadow boundary
my-element ^ h2{ color: green; }
Pierce every layer of the Shadow DOM (^^) ~ all shadow boundaries
my-element ^^ h2{ font-family: 'Comic Sans'; }
What is Polymer?
Shadow DOM, Custom Elements, HTML Imports, Pointer Events...
expresses opinionated way to use web component concepts together
comprehensive set (in progress)
Philosophy & Goals
Everything
is
an
element
Everything
is
an
element
Philosophy & Goals
Everything is an element
HTML is cool. DOM feels good.
Eliminate boilerplate
Remove tediousness of building web component-based apps
Utilize the modern web platform
Support modern browsers
Polymer
polymer-project.org/docs/elements/
Polymer
Utilize the modern web platform.
1st-class support for spec features...
Lifecycle callbacks
Support for the lifecycle methods...but shorter names!
Polymer('my-input', { ready: function() { ... }, // Polymer addition for when element is fully initialized. created: function() { ... }, enteredView: function() { ... }, leftView: function() { ... }, attributeChanged: function(attrName, oldVal, newVal) { ... } });
Use cases:
- perform setup/teardown work
- notification when element is inserted/removed from page
Insertion points
define an internal structure
<polymer-element name="my-tabs" noscript> <template> <style>...</style> <header> <content select="h2"></content> </header> <content select="section"></content> </template> </polymer-element>
<link rel="import" href="my-tabs.html"> <my-tabs> <h2>Title</h2> <section>content</section> <h2>Title 2</h2> ... </my-tabs>
Scoped styling
Support for styling features (scoped styles, applyAuthorStyles
, etc.)
<polymer-element name="my-element"> <template> <style>...</style> <!-- Styles are scoped to the element --> </template> <script> Polymer('my-element', { applyAuthorStyles: true, resetStyleInheritance: false }); </script> </polymer-element>
- Polymer attempts to polyfill most Shadow DOM style features
Bundle & deliver CSS/HTML/JS
Reuse others' components:
<link rel="import" href="x-toolbar.html"> <link rel="import" href="menu-item.html"> <polymer-element name="awesome-menu"> <template> <x-toolbar responsive> <menu-item src="images/do.png" selected>Do</menu-item> <menu-item src="images/re.png">Re</menu-item> <menu-item src="images/mi.png">Mi</menu-item> <x-toolbar> </template> ... </polymer-element>
<link rel="import" href="awesome-menu.html"> <awesome-menu></awesome-menu>
Everything is an element
AJAX...using DOM
<script src="platform.js"></script> <link rel="import" href="polymer-ajax.html">
<polymer-ajax url="http://gdata.youtube.com/feeds/api/videos/" params='{"alt":"json"}'></polymer-ajax>
var ajax = document.querySelector('polymer-ajax'); ajax.addEventListener('polymer-response', function(e) { console.log(JSON.parse(this.response).feed.entry); }); ajax.go();
Real-world examples of polymer-ajax
chromestatus.com/features
polymer-project.org/build/
Everything is an element
read files...using DOM
<script src="platform.js"></script> <link rel="import" href="polymer-file.html">
<polymer-file readas="dataurl"></polymer-file>
var pFile = document.querySelector('polymer-file'); pFile.addEventListener('polymer-result', function(e) { console.log(this.result); }); pFile.blob = new Blob(['abc'], {type: 'text/plain'}); // Set the file to read pFile.read();
Everything is an element
flexbox...using DOM
<script src="platform.js"></script> <link rel="import" href="polymer-flex-layout.html">
<polymer-flex-layout vertical iscontainer> <div>Header</div> <div flex>Body</div> <div>Footer</div> </polymer-flex-layout>
Polymer elements
non-visual utility elements
Layout
<polymer-layout>
<polymer-flex-layout>
<polymer-grid-layout>
View
<polymer-media-query>
<polymer-page>
Services / libs
<polymer-shared-lib>
<polymer-google-jsapi>
Data
<polymer-xhr>
<polymer-file>
<polymer-meta>
Behavior / interaction
<polymer-signals>
<polymer-selector>
Polymer UI elements
visual elements
<polymer-ui-accordion>
demo
<polymer-ui-animated-pages>
<polymer-ui-overlay>
<polymer-ui-card>
demo
<polymer-ui-sidebar-menu>
demo
<polymer-ui-tabs>
demo
<polymer-ui-toggle-button>
demo
<polymer-ui-theme-aware>
...
Polymer
polymer-project.org/polymer.html
Custom elements without Polymer :(
<template id="template">
<style>input { color: orange; }</style>
<input type="text">
</template>
<script>
var proto = Object.create(HTMLElement.prototype, {
createdCallback: {
value: function() {
var t = document.querySelector('#template');
this.createShadowRoot().appendChild(t.content.cloneNode(true));
}
}
});
var MyInput = document.registerElement('my-input', {prototype: proto});
</script>
Custom elements with Polymer :)
declarative custom elements
<link rel="import" href="polymer.html"/>
-
Create an element definition
<polymer-element name="my-input" constructor="MyInput" noscript> <!-- Note: Polymer creates Shadow DOM from the first <template>. --> <template> <style>input { color: orange; }</style> <input type="text"> </template> </polymer-element>
-
Instantiate - declare it, create DOM, or use
new
in JS<my-input></my-input> // var myInput = document.createElement('my-input'); // var myInput = new MyInput();
Default attributes
User-defined attributes are included on each instance of the element:
<polymer-element name="my-input" customattr class="active"> <template>...</template> </polymer-element> <my-input></my-input>
Instances include your attributes:
<my-input customattr class="active"></my-input>
Complex elements require more juice...
define an API
- Properties/methods are added to
prototype
this
refers to the element itself (e.g.this.localName == "my-input"
)- Can reference external scripts/stylesheets (e.g. CSP friendly)
Publishing properties & data-binding
- Inside the element → data-binding via the attribute
- From outside world → users can initialization the property via its attribute
- User overrides
color
buttype
remains its default ("text") - Since
val
isn't published, can't use it as a bindable attribute.
Features in action
data-binding / published properties
Using <polymer-ajax>
in another element:
<script src="polymer.min.js"></script> <link rel="import" href="polymer-ajax.html">
<polymer-element name="youtube-videos" attributes="query"> <template> <polymer-ajax url="http://gdata.youtube.com/feeds/api/videos/" params="{{params}}" handleAs="json" response="{{response}}" auto></polymer-ajax> <ul> <template repeat="{{entry in response.feed.entry}}"> <li>{{entry.title.$t}}</li> </template> </ul> </template> <script> Polymer('youtube-videos', { ready: function() { this.params = {alt: 'json', q: this.query}; } }); </script> </polymer-element> <youtube-videos query="cats"></youtube-videos>
Features in action
$ node finding / changed watchers / declarative event handlers
Using <polymer-file>
in another element:
<script src="polymer.min.js"></script> <link rel="import" href="polymer-file.html">
<polymer-element name="read-me" on-click="{{onClick}}"> <template> <polymer-file id="file" readas="arraybuffer" result="{{result}}"></polymer-file> </template> <script> Polymer('read-me', { resultChanged: function() { console.log(this.result); }, onClick: function(e, detail, sender) { this.$.file.read(); } }); </script> </polymer-element>
var el = document.createElement('read-me'); el.blob = new Blob(['abc'], {type: 'text/plain'});
Dynamic markup
additional magic for HTML <template>
Conditionals:
<template if="{{ isActive }}">
<!-- shown if isActive property is true -->
</template>
<template if="{{ showDefault || users.length < 10 }}">
...
</template>
Iteration:
<template repeat="{{ user in users }}">
<template repeat="{{ file in user.files }}">
{{ user.name }} owners {{ file.name }}
</template>
</template>
Features in action
responsive design...using DOM (Bonus)
<script src="platform.js"></script> <link rel="import" href="polymer-media-query.html">
<polymer-element name="responsive-element" attributes="responsive"> <template> <polymer-media-query query="max-width:640px" queryMatches="{{isPhone}}"></polymer-media-query> <polymer-media-query query="max-width:1024px" queryMatches="{{isTablet}}"></polymer-media-query> <template if="{{isPhone && responsive}}"> <!-- Phone markup. --> </template> <template if="{{isTablet && responsive}}"> <!-- Tablet markup. --> </template> <template if="{{!responsive}}"> <!-- Default markup for non-responsive case. --> </template> </template> <script>Polymer('responsive-element', {responsive: false});</script> </polymer-element> <responsive-element responsive></<responsive-element>
FOUC prevention
Initially hide elements using polymer-veiled
class or manage a list in JS:
-
Add
polymer-veiled
class:<x-foo class="polymer-veiled">If you see me, elements are upgraded!</x-foo> <div class="polymer-veiled"></div>
-
Polymer.veiledElements = ['x-foo', 'div'];
polymer-unveiled
swapped in atWebComponentsReady
event → elements fade-in.- Note:
polymer-veiled
is added to<body>
by default.
Everything is an element.
Polymer
Utilize the modern web platform.
1st-class support for spec features...
Lifecycle callbacks
Support for the lifecycle methods...but shorter names!
Polymer('my-input', { ready: function() { ... }, // Polymer addition for when element is fully initialized. created: function() { ... }, ready: function() { ... }, attached: function() { ... }, detached: function() { ... }, attributeChanged: function(attrName, oldVal, newVal) { ... } });
Use cases:
- perform setup/teardown work
- notification when element is inserted/removed from page
Scoped styling
Support for styling features (scoped styles, ^ and ^^ selectors)
<polymer-element name="x-foo"> <template> <style> x-foo { ... } x-foo:hover { ... } x-foo.foo > .bar, .foo x-foo > bar {...} </style> ...
- Polymer attempts to polyfill most Shadow DOM style features
Custom Elements
Register new elements in HTML
document.registerElement('x-foo');
- 1st arg: tag name must contain a "="
- 2nd arg: optional object containing a
prototype
// Equivalent to document.registerElement('x-foo'). var XFoo = document.registerElement('x-foo', { prototype: Object.create(HTMLElement.prototype) });
Instantiate a custom element
Once registered, use it like any standard DOM element:
Declare it
<x-foo></x-foo>
Create DOM in JS
var xFoo = document.createElement('x-foo'); xFoo.addEventListener('click', function(e) { alert('Thanks!'); });
Use new
var xFoo = new XFoo(); document.body.appendChild(xFoo);
Define an API
add properties and methods
var proto = Object.create(HTMLElement.prototype); Object.defineProperty(proto, 'name', {value: 'Eric'}); proto.hello = function() { alert('Hiiiiii ' + this.name); }; var XFoo = document.registerElement('x-foo', {prototype: proto});
var el = document.createElement('x-foo'); // el.name == 'Eric' // el.hello();
Lifecycle callbacks
hook into an element's voyage
Four special methods (optional):
var proto = Object.create(HTMLElement.prototype); proto.createdCallback = function() { this.textContent = "I'm an x-foo!"; }; proto.enteredViewCallback = function() { ... }; proto.leftViewCallback = function() { ... }; proto.attributeChangedCallback = function(attrName, oldVal, newVal) { ... };
Use cases:
- perform setup/teardown work
- notified when element is inserted/removed from document
Demo
<x-foo></x-foo> <script> var proto = Object.create(HTMLElement.prototype); proto.hello = function() { alert('Hiiii ' + this.name); }; ... document.registerElement('x-foo', {prototype: proto}); document.querySelector('x-foo').addEventListener('click', function(e) { e.target.hello(); }); </script>
Extending native HTML elements
Inherit from the prototype
of the element:
var MegaButtonProto = Object.create(HTMLButtonElement.prototype); ... var MegaButton = document.register('mega-button', {prototype: MegaButtonProto});
An instance is called a type extension custom element
// <button is="mega-button"> var megaButton = document.createElement('button', 'mega-button'); // megaButton instanceof MegaButton === true
Bundle & deliver CSS/HTML/JS
Reuse others' components:
<link rel="import" href="x-toolbar.html"> <link rel="import" href="menu-item.html"> <polymer-element name="awesome-menu"> <template> <x-toolbar responsive> <menu-item src="images/do.png" selected>Do</menu-item> <menu-item src="images/re.png">Re</menu-item> <menu-item src="images/mi.png">Mi</menu-item> <x-toolbar> </template> ... </polymer-element>
<link rel="import" href="awesome-menu.html"> <awesome-menu></awesome-menu>
Polymer Tooling
Helping you stay more productive
Polymer + X-Tag sitting in a tree..
They can work great together
Use Platform.js to load X-Tag elements via Imports
<script src="../platform/platform.js"></script> <link rel="import" href="x-iconbutton.html"> <link rel="import" href="x-tabbar.html">
X-Tag can be used as normal in an import
<link rel="import" href="x-tag-core.html"> <link rel="stylesheet" href="../iconbutton/src/iconbutton.css"> <script src="../iconbutton/src/iconbutton.js"></script>
Summary of Polymer features
declarative web components
- Declarative element registration:
<polymer-element>
- Declarative inheritance:
<polymer-element extends="...">
- Declarative two-way data-binding:
<input id="input" value="{{foo}}">
- Declarative event handlers:
<button on-click="{{handleClick}}">
- Published properties:
xFoo.bar = 5 <-> <x-foo bar="5">
- Property change watchers:
barChanged: function() {...}
- Automatic node finding:
this.$.input.value = 5
- PointerEvents / PointerGestures by default