about:flagsabout:flagsYou don't have Shadow DOM enabled
You don't have Object.observe()
You don't have MutationObservers
Eric Bidelman -
Team
O'Reilly book - "Using the HTML5 Filesystem API"
<gangnam-style></gangnam-style>
( hover over the bar )
<my-megabutton>Mega button</my-megabutton>
A tectonic shift for web development
Important new constructs for building complex web apps
"Declarative Renaissance"
Not everyone agrees agreed.
Web Components have the same identity crisis.
<template> - Scaffold/BlueprintMutationObserverObject.observe()calc()A collection new capabilities in the browser
Method #1: "offscreen" DOM using [hidden] or display:none
<div id="mytemplate" hidden> <img src="logo.png"> <div class="comment"></div> </div>
<img>)#mytemplate.Method #2: manipulating markup as string. Overload <script>:
<script id="mytemplate" type="text/x-handlebars-template"> <img src="logo.png"> <div class="comment"></div> </script>
.innerHTML)Examples: handlebars.js, John Resig's micro-template script
<template>Contains inert markup intended to be used later:
<template id="mytemplate"> <img src=""> <div class="comment"></div> </template>
<script>s don't run, images aren't loaded, media doesn't play, etc.
var t = document.querySelector('#mytemplate');
t.content.querySelector('img').src = 'http://...';
document.body.appendChild(t.content.cloneNode(true));
.content provides access to the <template>'s guts:
interface HTMLTemplateElement : HTMLElement {
attribute DocumentFragment content;
}
Fundamental foundation of OOP
Separates code you wrote from the code that will consume it
We don't have it on the web!
Well, sort of:
<iframe> <iframe> are heavy and restrictive
Is complex! Let's cover the basics:
@host at-rule
So...browser vendor's have been holding out on us!
<div id="host"> <h1>My Title</h1> <h2>My Subtitle</h2> <div>...other content...</div> </div>
var host = document.querySelector('#host');
var shadow = host.createShadowRoot();
shadow.innerHTML = '<h2>Yo, you got replaced!</h2>' +
'<div>by my awesome content</div>';
// host.shadowRoot;
<style>s defined in ShadowRoot are scoped.
var shadow = document.querySelector('#host').createShadowRoot();
shadow.innerHTML = '<style>h2 { color: red; }</style>' +
'<h2>Yo, you got replaced!</h2>' +
'<div>by my awesome content</div>';
Author's styles don't cross shadow boundary by default, but have full control.
var shadow = document.querySelector('#host').createShadowRoot();
shadow.innerHTML = '<style>h2 { color: red; }</style><h2>Yo, you got replaced!</h2>' +
'<div>by my awesome content</div>';
// shadow.resetStyleInheritance = true; // click me
// shadow.applyAuthorStyles = true; // click me
.resetStyleInheritancefalse - (default) inheritable CSS properties continue to inherit.true - resets properties to initial at the shadow boundary..applyAuthorStylestrue to allow author's styles to "bleed" across shadow boundary. Default is false.@host at-rule)@host selects the shadow host element.
<style>
@host {
/* Gotcha: higher specificity than any selector,
lower specificity than declarations from a <style> attribute. */
* {
opacity: 0.2;
transition: opacity 400ms ease-in-out;
}
*:hover {
opacity: 1;
}
}
</style>
input[type=range].custom {
-webkit-appearance: none;
background-color: red;
}
input[type=range].custom::-webkit-slider-thumb {
-webkit-appearance: none;
background-color: blue;
width: 10px;
height: 40px;
}
Widget author can designate certain elements be styleable by outsiders.
<style>
#host::x-slider-thumb {
background-color: blue;
}
</style>
<div id="host"></div>
<script>
document.querySelector('#host').createShadowRoot().innerHTML = '<div>' +
'<div pseudo="x-slider-thumb"></div>' +
'</div>';
</script>
x-"Widget author includes variable placeholders:
button {
color: var(button-text-color);
font: var(button-font);
}
Widget embedder applies styles to the element:
#host {
var-button-text-color: green;
var-button-font: "Comic Sans MS", "Comic Sans", cursive;
}
<div id="host"> <h1>My Title</h1> <h2>My Subtitle</h2> <div>...other content...</div> </div>
that guy rendered as:
<div id="host">
#shadow-root
<style>h2 {color: red;}</style>
<h2>Yo, you got replaced!</h2>
<div>by my awesome content</div>
</div>
...everything was replaced when we attached the shadow DOM
<content> elements are insertion points.select attribute uses CSS selectors to specify where children are funneled.content.getDistributedNodes() returns list of element distributed in the insertion point.<div id="host"> <h1>My Title</h1> <h2>My Subtitle</h2> <div>...other content...</div> </div>
<style>
h2 {color: red;}
</style>
<hgroup>
<content select="h2"></content>
<div>You got enhanced</div>
<content select="h1:first-child"></content>
</hgroup>
<content select="*"></content>
oninput, click)MutationRecords)MutationEvent performance / stability bottlenecks.
var observer = new MutationObserver(function(mutations, observer) {
mutations.forEach(function(record) {
for (var i = 0, node; node = record.addedNodes[i]; i++) {
console.log(node);
}
});
});
observer.observe(el, {
childList: true, // include childNode insertion/removals
//subtree: true, // observe the subtree root at el
//characterData: true, // include textContent changes
//attribute: true // include changes to attributes within the subtree
});
// observer.disconnect() // Stop observations
MutationEvent
Don't use Mutation Events!
// MutationEvent
document.addEventListener('DOMNodeInserted', function(e) {
console.log(e.target);
}, false);
// MutationObserver
var observer = new MutationObserver(function(mutations, observer) {
mutations.forEach(function(record) {
for (var i = 0, node; node = record.addedNodes[i]; i++) {
console.log(node);
}
});
}).observe(document, {childList: true});
Object.observe()
Object.observe : JS objects :: MutationObserver : DOM
function observeChanges(changes) {
console.log('== Callback ==');
changes.forEach(function(change) {
console.log('What Changed?', change.name);
console.log('How did it change?', change.type);
console.log('What was the old value?', change.oldValue );
console.log('What is the present value?', change.object[change.name]);
});
}
var o = {};
Object.observe(o, observeChanges);
// Object.unobserve(o, observeChanges); // Stop watching.
Object.freeze)about:flags.See Bocoup's writeup.
The
AngularJS team experimented with a prototype build of Chromium:
20-40x faster!
<div id="example">
<ul>
<template iterate>
<li>{{ name }}
<ul><template iterate="skills"><li>...</li></template></ul>
</li>
</template>
</ul>
</div>
<script>
document.querySelector('#example').model = [
{name: 'Sally', skills: ['carpentry']},
{name: 'Helen', skills: ['weaving', 'omnipotence']}
];
</script>
Play with it today: code.google.com/p/mdv/
AngularJSA (close-ish) taste of the future:
<div ng-controller="AppController">
<ul>
<li ng-repeat="person in model">{{ person.name }}
<ul><li ng-repeat="skill in person.skills">{{skill}}</li></ul>
</ul>
</div>
<script>
function AppController($scope) {
$scope.model = [{name: 'Sally', skills: ['carpentry']},
{name: 'Helen', skills: ['weaving', 'omnipotence']}];
}
</script>
<div id="demo">
<ul>
<li><a href="#foo">foo</a></li>
<li><a href="#bar">bar</a></li>
</ul>
<div>
<div id="foo"><p>foo content</p></div>
<div id="bar"><p>bar content</p></div>
</div>
</div>
YUI().use('tabview', function(Y) {
var tabview = new Y.TabView({srcNode: '#demo'});
tabview.render();
});
<template>)@host, custom pseudo elements, styling hooks)Object.observe()WE DO :)
Define a declarative "API" using insertion points:
<element name="my-tabs">
<template>
<style>...</style>
<content select="hgroup:first-child"></content>
</template>
</element>
Include and use it:
<link rel="import" href="my-tabs.html">
<my-tabs>
<hgroup>
<h2>Title</h2>
...
</hgroup>
</my-tabs>
Define an imperative API:
<element name="my-tabs" constructor="TabsController">
<template>...</template>
<script>
TabsController.prototype = {
doSomething: function() { ... }
};
</script>
</element>
Declared constructor goes on global scope:
<link rel="import" href="my-tabs.html">
<script>
var tabs = new TabsController();
tabs.addEventListener('click', function(e) { e.target.doSomething(); });
document.body.appendChild(tabs);
</script>
<element name="my-megabutton" extends="button" constructor="MegaButton">
<template>
<button><content></content></button>
</template>
<script>
MegaButton.prototype = {
megaClick: function(e) {
play('moo.mp3');
}
};
</script>
</element>
Use it:
<link rel="import" href="my-megabutton.html"> <my-megabutton>Mega button</my-megabutton>
<my-megabutton>Mega button</my-megabutton>
<my-meme src="images/beaches.jpg"> <h1 contenteditable>Stay classy</h1> <h2 contenteditable>Web!</h2> </my-meme>

<my-tab>
<h2>One</h2>
<section>
Code to instantiate this component:
<pre></pre>
</section>
<h2>Two is bigger than three</h2>
<section>
Second panel hotness
</section>
</my-tab>
AngularJS
<tabs ng-app="tabs">
<pane title="One">
Code to instantiate this component:
<pre></pre>
</pane>
<pane title="Two is bigger than three">
Second panel hotness.
</pane>
</tabs>
about:flagsabout:flagsPolyfills:
Web Components / Custom Elements
Shadow DOM
Mutation Observers / Object.observe