<x-gangnam-style></x-gangnam-style>
<x-megabutton>Mega button</x-megabutton>
<template>- Scaffold/Blueprint<element> (custom elements) - ToolbeltMethod #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.<template>
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;
}
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.
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
<style>
@host {
/* Gotcha: higher specificity than any selector, lower than <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;
}
Author allow certain elements to be styled 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-"Author includes variable placeholders:
button {
color: var(button-text-color);
font: var(button-font);
}
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">
#document-fragment
<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.getDistributedNodes(): list of elements 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>
Define a declarative "API" using insertion points:
<element name="x-tabs">
<template>
<style>...</style>
<content select="hgroup:first-child"></content>
</template>
</element>
Include and use it:
<link rel="components" href="x-tabs.html">
<x-tabs>
<hgroup>
<h2>Title</h2>
...
</hgroup>
</x-tabs>
<element name="x-tabs" constructor="TabsController">
<template>...</template>
<script>
TabsController.prototype = {
doSomething: function() { ... }
};
</script>
</element>
Declared constructor goes on global scope:
<link rel="components" href="x-tabs.html">
<script>
var tabs = new TabsController(); // or document.createElement('x-tabs')
tabs.addEventListener('click', function(e) { e.target.doSomething(); });
document.body.appendChild(tabs);
</script>
<element name="x-megabutton" extends="button" constructor="MegaButton">
<template>
<content></content>
</template>
<script>
MegaButton.prototype = {
megaClick: function(e) { alert('BOOM!'); }
};
</script>
</element>
Use it:
<link rel="components" href="x-megabutton.html"> <x-megabutton>Mega button</x-megabutton>

public class Money {
private int amount;
public Money(double amount) { this.amount = Math.floor(amount * 100); }
public Money(int amount) { this.amount = amount; }
public Money(String amount) { this(Double.parseDouble(amount); }
public Money add(Money other) { return new Money(this.amount + other.amount); }
public Money add(Money other, boolean chargeTax) {
return new Money(add(other).amount * 0.93);
}
public int getAmount() { return amount; }
}
class Money {
final int amount;
static final Money ZERO = new Money(0);
Money(this.amount);
Money.fromDouble(double amount) : this((amount*100).toInt());
Money add(Money other, {bool chargeTax: false}) {
var newAmount = (this.amount + other.amount) * (chargeTax ? 0.93 : 1.0);
return new Money(newAmount.toInt());
}
Money operator +(Money other) => add(other);
String toString() => amount.toString();
}
class AwesomeButton {
int awesomeDial;
ButtonElement elem;
AwesomeButton(this.elem) {
elem.on.click.add((e) => crankTheAwesome());
}
crankTheAwesome() {
awesomeDial = 11;
}
}
recalculate(origin, offset, estimate) {
...
}
num recalculate(Point origin, num offset, {bool estimate: false}) {
...
}
import 'dart:html';
void main() {
var button = new ButtonElement();
button.text = 'Click me';
button.classes.add('important');
button.onClick.listen((e) => window.alert('Clicked!!'));
document.body.elements.add(button);
}
import 'dart:html';
void main() {
var button = new ButtonElement()
..text = 'Click me'
..classes.add('important')
..onClick.listen((e) => window.alert('Clicked!!'));
document.body.elements.add(button);
}
Stop paying the complete library tax
<p>MDV is {{ superlative }}</p>
<button id="change-it" on-click="changeIt()">Change</button>
import 'package:web_ui/web_ui.dart';
@observable
String superlative = 'awesome';
int i = 0;
List<String> alternatives = const <String>['wicked cool', 'sweet', 'fantastic'];
changeIt() => superlative = alternatives[i++ % alternatives.length];
main() { }
<select name="language" bind-value="langChoice">
<option value="">-- Choose one --</option>
<option value="js">JavaScript</option>
<option value="java">Java</option>
</select>
<div template if="langChoice != null && !langChoice.isEmpty">
<h3>{{ langChoice }}</h3>
<code><pre>{{ languageExamples[langChoice] }}</pre></code>
</div>
Map languageExamples = {
'js': 'function Person(firstName, lastName) { ... };',
'java': 'public class Person { ... }'
};
String langChoice = '';
main() { }
<ul template iterate="feature in features">
<li>
<label>
<input type="checkbox" value="{{ feature }}"
on-click="addToFavorites($event)">
{{ feature }}
</label>
</li>
</ul>
import 'dart:html';
List<String> features = const <String>['lexical scope', 'closures', 'getters and setters', ...];
List<String> userFavorites = new List<String>();
addToFavorites(Event e) {
InputElement checkbox = e.target;
var fav = checkbox.value;
// add or remove from userFavorites
}
main() { }
<input type="text" name="firstName" bind-value="person.firstName" placeholder="First name">
<input type="text" name="lastName" bind-value="person.lastName" placeholder="Last name">
<div class="full-name">{{ person.firstName }} {{ person.lastName }}</div>
class Person {
String firstName;
String lastName;
Address address;
Person(this.firstName, this.lastName, this.address);
}
Person person;
main() {
person = new Person('Bob', 'Smith', new Address('Main St', 'Metropolis', '11122'));
}
The main HTML page.
<input type="text" id="new-todo">
<button on-click="createNewTodo()">
Create New Todo
</button>
<ul template id="todos" iterate="todo in todoItems">
<li>
<x-todo-item bind-todo="todo"></x-todo-item>
</li>
</ul>
The custom Element.
<!DOCTYPE html>
<html>
<body>
<element name="x-todo-item" constructor="TodoItemComponent"
apply-author-styles="" extends="div">
<template>
<label class="{{ completeClass }}">
<input type="checkbox" on-change="toggle()">
<span>{{ todo.actionItem }}</span>
</label>
</template>
<script type="application/dart" src="TodoItemComponent.dart"></script>
</element>
</body>
</html>
The custom element class definition.
import 'package:web_ui/web_ui.dart';
import 'models.dart';
class TodoItemComponent extends WebComponent {
TodoItem todo;
toggle() => todo.toggle();
String get completeClass => todo.complete ? 'completed' : '';
}
The model class.
library models;
class TodoItem {
String actionItem;
bool complete = false;
TodoItem(this.actionItem);
toggle() => complete = !complete;
}
<h1>Web UI FTW!</h1> <x-clock></x-clock>
class Clock extends WebComponent {
Date time = new Date.now();
Timer timer;
inserted() {
timer = new Timer.repeating(1000, (t) {
time = new Date.now();
watchers.dispatch();
});
}
removed() {
timer.cancel();
}
}
<div is="x-fancy"></div>
<element name="x-fancy" extends="div">
<template>
This is fancy!
</template>
<script type="application/dart">
import 'package:web_ui/web_ui.dart';
class Fancy extends WebComponent {
beFancy() {
appendText("So wear a bow tie");
}
}
</script>
</element>
<button on-click="query('div[is=x-fancy]').xtag.beFancy()">Class it up</button>
class Clock extends WebComponent {
@observable
DateTime time = new DateTime.now();
Timer timer;
inserted() {
timer = new Timer.repeating(new Duration(seconds:1), (_) {
time = new DateTime.now();
});
}
removed() {
timer.cancel();
}
}