Varunkumar Nagarajan
Powerful combo - HTML5, JS, CSS3
Thanks to HTML5, JS and CSS3, we can do amazing stuff on the web
Follow better engineering practices
Write code that is maintainable
The code must be
Build re-usable components
Any Object Oriented language will help us get there
How do we incorporate these on the web platform?
The fundamental problem is the lack of DOM encapsulation which could lead to
Libraries work around these problems by following certain assumptions and conventions
MV* frameworks help us to some extent
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
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. ★ Seamless iframe
<template>
MutationObserver
Object.observe()
calc()
A collection of new capabilities on the browser
<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));
Is complex! Let's cover the basics:
@host
at-rule
So...browser vendor's have been holding out on us!
<div id="host"> <h1>Varunkumar Nagarajan</h1> <h2>Hyderabad, India</h2> <div>...other content...</div> </div>
var host = document.querySelector('#host'); var shadow = host.createShadowRoot(); shadow.innerHTML = '<h2>Yo, you just got replaced!</h2>' + '<div>Who? Varunkumar (@varunkumar)</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>Who? Varunkumar (@varunkumar)</div>';
Author's styles don't cross shadow boundary by default.
Change the behavior via properties resetStyleInheritance
and applyAuthorStyles
@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>
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>Varunkumar Nagarajan</h1> <h2>Hyderabad, India</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>Who? Varunkumar (@varunkumar)</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.<div id="host"> <firstName>Varunkumar</firstName> <lastName>Nagarajan</lastName> <city>Hyderabad, India</city> <div>...other content...</div> </div>
<style> h2 {color: red;} </style> <hgroup> <h2><content select="firstName"></content> (@varunkumar)</h2> <content select="city"></content> </hgroup> <content select="*"></content>
oninput
, click
)MutationRecord
s)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.
<template>
)@host
, styling hooks)Object.observe()
WE DO :)
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>
Define an imperative API:
<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(); tabs.addEventListener('click', function(e) { e.target.doSomething(); }); document.body.appendChild(tabs); </script>
Or, we can even extend existing elements
about:flags
about:flags
You don't have Shadow DOM enabled
You don't have Object.observe()
You don't have MutationObservers
<x-gangnam-style></x-gangnam-style>
( hover over the bar )
Web Components / Custom Elements
Shadow DOM
Mutation Observers / Object.observe
Thank you