Who am I?
- Self-taught developer
- Past: spent 5 years as a PM at Mozilla
- Now: PM at Microsoft working on developer experience
- Technical Working Group contributor to the W3 Web Components family of specifications
What's on tap for today?
- The Long Road to Web Components
- Meet the Web Components Family
- Brass Tacks on Polyfills and Use in Prod
- Libraries, Frameworks, and Dev Resources
- The Code-Off (OMG, what could it be?!?)
- AMA and Open Discussion
- 1:1 Development Help
The Long Road to Web Components
Let's party like it's 1999!
- Smartphones didn't exist
- The Web just got AJAX
- A tech bubble was brewing
- Pluto was still a planet
- IE 5.5 was released
The Long Road to Web Components
HTC Files & CSS Behaviors
HTC stands for "HTML Components", a non-standard component definition API built on proprietary extensions of HTML and JScript/VBScript that was introduced in IE5.5 to allow developers to specify components with encapsulated behaviors and styles.
<PUBLIC:COMPONENT tagName="checkers">
<PUBLIC:PROPERTY NAME="boardWidth" />
<PUBLIC:METHOD name="newGame()" />
<PUBLIC:ATTACH event="onmouseover" onevent="mouseover()" />
</PUBLIC:COMPONENT>
<SCRIPT Language="Javascript">
function newGame(){
// insert code to initialize a new game here
}
function mouseover(){
// insert code to handle mouseover events
}
</SCRIPT>
<?IMPORT namespace="games" implementation="checkers.htc" >
<games:checkers />
Define Components:
Import Components:
Use Component:
The Long Road to Web Components
Issues with HTCs & CSS Behaviors
- Lacked multi-vendor buy-in
- Mutable bindings via CSS were a mistake
- Suffered from performance issues under normal use-cases
- Created an entirely new DSL and a huge API surface area
Result: Never standardized, removed in IE 10
The Long Road to Web Components
A New Hope for Web Components
All was not lost! A new champion for Web Components was ready to throw down the gauntlet, enter: Dimitri Glazkov
In 2010, Glazkov started work on a new set of specs that would bring web devs the component APIs they long deserved.
The Long Road to Web Components
Meet the Web Components Family:
Custom Elements
HTML Imports
HTML Templates
Shadow DOM
Hooks for reactive composability live demo
document.registerElement('x-foo', {
prototype: Object.create(HTMLElement.prototype, {
bar: {
get: function(){ return 'test' }
}
})
});
document.registerElement('x-super-input', {
extends: 'input',
prototype: Object.create(
HTMLInputElement.prototype, {
// custom prototype properties
})
});
document.createElement('x-foo');
document.createElement('input', 'x-super-input');
document.registerElement('x-foo', {
prototype: Object.create(HTMLElement.prototype, {
createdCallback: { value: function(){} },
attachedCallback: { value: function(){} },
detachedCallback: { value: function(){} },
attributeChangedCallback: { value: function(){} }
})
});
Create your own elements with custom prototypes live demo
Extend existing, native elements live demo
Automatically bootstraps custom elements. No more $('.my-widget').myWidget() boilerplate and brittle DOM structures.
<x-foo>I'm an x-foo element!</x-foo>
<input is="x-super-input" />
Meet the Web Components Family
var myDiv = document.createElement('div');
document.body.appendChild(myDiv);
var root = myDiv.createShadowRoot();
root.appendChild(document.createElement('span'));
var span = myDiv.querySelector('span');
// this query will return NULL
div::shadow span { color: red; }
div /deep/ span { color: red; }
/* ::shadow selects 1st-level shadow content, while
/deep/ selects all spans at any shadow tree depth */
:host(:active) { color: red; }
/* the host node will have red text when :active */
:host-context(section) { color: red; }
/* host nodes in sections will have red text */
::content span { color: red; }
/* all spans within content insertion points */
Create encapsulated Shadow Roots live demo
Encapsulation semi-permeably blocks content access and style leakage
Encapsulation of shadowed content access and style targeting can be breached if a developer explicitly chooses to do so
div span { color: red; }
/* this style will not be applied */
var span = myDiv.querySelector('::shadow span');
// this query will return the shadowed span
root.innerHTML = '<content select="span"></content>' +
'<span>Foo</span>';
// only spans added to the host will be caught & shown
root.innerHTML = '<content></content><span>Foo</span>'
// all nodes added to the host will be caught & shown
Content elements dictate where injected elements are placed, and whether they are visible to the user
Meet the Web Components Family
<template id="titled_list">
<h2>
<content select="span"></content>
</h2>
<ul>
<content select="li"></content>
</ul>
</template>
var template = document.querySelector('#titled_list');
var clone = template.content.cloneNode(true);
document.body.appendChild(clone);
var div = document.createElement('div');
var root = div.createShadowRoot();
var clone = template.content.cloneNode(true);
var title = document.createElement('span');
title.textContent = 'My List';
root.appendChild(clone);
div.appendChild(title);
// the title span is captured by the content
// node and displayed inside the H2 element
Create your own, reusable templates using HTML markup
Generate copies of your templates
Inject your template copies into a Shadow Root to use their contents just as you would any other shadow content
Meet the Web Components Family
<head>
<link rel="import" href="/path/to/import.html">
</head>
<script async>
function importLoad(event) { }
function importError(event) { }
</script>
<link rel="import"
href="file.html"
onload="importLoad(event)"
onerror="importError(event)" />
<script async>
function importLoad(event) {
var doc = event.target.import;
var template = doc.querySelector('#widget_template');
// do something with your templates, assets, etc.
}
</script>
Import HTML sub-documents - fetches included scripts, styles, and templates
Act on import documents when they arrive, or fail due to an error
Utilize the imported resources when they arrive by accessing the import document attached to the link element
<!-- assume this is inside the imported sub document -->
<script>
var importDoc = document.currentScript.ownerDocument;
// this provides a hook to the imported sub-document
var mainDoc = document;
// the 'document' variable is a reference to the
// parent document that is bringing in the import
</script>
Access the importing parent document or the imported sub-document via script from within the import sub-document
Meet the Web Components Family
Brass Tacks on Polyfills & Use in Prod
Native browser support is landing, but we'll still need polyfills for the foreseeable future
The Polyfills
Native Status
- Custom Elements & Templates: get you some!
- HTML Imports: strict CSP environments caused issues in the past
- Shadow DOM: not recommended for production use due to performance and API coverage issues
- Desktop Browsers: support extends to recent versions of major browsers (Firefox, IE10+, Safari 6)
- Mobile browsers: Android support is limited to Android 4+
Libraries, Frameworks, and Developer Resources
X-Tag
Summary:
A lightweight library focused on Custom Elements that wraps the imperative JavaScript APIs for Custom Elements to enable lightning-fast development of components for use in production sites today.
Key Features:
- Does not rely on Shadow DOM
- Advanced Event and Function Pseudo systems
- Ergonomics and performance-focused APIs and features
- Optional hooks for easy use of advanced APIs like Shadow DOM
Polymer
Summary:
A component and data-binding framework built atop Web Components APIs that offers a full suite of features. Comparable in size, depth, and breadth to projects like Angular and Ember.
Key Features:
- Offers a declarative element definition API
- Includes the Shadow DOM polyfill by default
- Contains built-in data binding features
Libraries and Frameworks
X-Tag
Polymer
Libraries and Frameworks
(function(){
function executeTarget(e){
var targets = xtag.query(document, this.target);
var method = this.method;
var event = this.event;
(targets[0] ? targets : [this]).forEach(function(target){
if (typeof target[method] === 'function'){
target[method]();
}
if (event) xtag.fireEvent(target, event);
});
}
xtag.register('x-action', {
events: {
tap: executeTarget
},
accessors: {
target: { attribute: {} },
method: { attribute: {} },
event: { attribute: {} }
},
methods: {
execute: executeTarget
}
});
});
<polymer-element name="polymer-action"
attributes="target method event">
<script>
(function(){
function executeTarget(e){
var targets = Array.prototype.slice.call(
document.querySelectorAll(this.target)
);
var method = this.method;
var event = this.event;
(targets[0] ? targets : [this]).forEach(function(target){
if (typeof target[method] === 'function'){
target[method]();
}
if (event) {
target.dispatchEvent(
new CustomEvent(event)
);
}
});
}
Polymer({
created: function(){
this.addEventListener('tap', executeTarget);
},
execute: executeTarget
});
})();
</script>
</polymer-element>
Where to learn, code, and contribute:
The Code-Off
"This will be a straight code-off, old school rules." - David Bowie
Challenge 1: Meme Tag
Description: Build a tag that displays top and bottom text over a meme picture
Requirements:
- The image and top/bottom text should be dynamically configurable via attributes
Extra Credit:
- Leverage a meme API to allow for grabbing meme pics on-demand
Challenge 2: Photo Stack
Description: Build a tag that arranges image elements in an organically uneven stack appearance
Requirements:
- Enable cycling through the stack on click and touch with an animated transition
- Handle new image elements being added dynamically
Extra Credit:
- Allow the order of the photo stacked images to be reversed using a boolean attribute
Challenge 3: Comment Feed
Description: Create a comment feed element that supports custom child elements for comments/replies
Requirements:
- Use custom elements for the main feed element, and the comment/reply elements it contains
- Store the page's comment data so it repopulates the feed on refresh
- Support common actions used in comments/replies: edit, delete, up-vote.
Extra Credit:
- Sync your comment data to a remote source that works regardless of the containing page's domain
web-components-qconf
By Daniel Buchner
web-components-qconf
- 4,399