Components)are)the)future)of)the)web.It's%going%to%be%okay.
Tessa%Thornton
Front&end)developer.
I"work"at"Shopify.In"Toronto."
@tessthornton
A"roman(cized"and"slightly"inaccurate"history"of"web"
apps
<form action="submit.php" method="POST"> <input name="name" type="text"> <select name="favourite_color"> <option value="00ffff">Red</option> <option value="ff00ff">Blue</option> <option value="ff0000">Yellow</option> </select> <input type="submit"></form>
Then%JavaScript%grew%up<form> <input name="email" type="text" onblur="validateName()"> <button onclick="submitForm()">Submit</button></form>
Strict&separa+on&of&concerns!
• HTML&is&for&content
• JavaScript&is&for&behaviour
• CSS&is&for&presenta/on
and$so$it$was$wri+en
<button id="formSubmitButton">Submit</button>
<script>$("#formSubmitButton").click(function () { $(".form-input").each(function () { $(this).validateWhatever().errorMessages(); }); if (allThingsAreValid) { $.ajaxStuff(function (response) { $(".success-message").show(); $(".form").hide(); }); }});</script>
and$we$called$it$Best%Prac*ces
Remember&when&Our$best$prac,ces$were$killing$us?
..."We"did"it"again.
these%best%prac+ces%haven't%kept%up%with%the%reali+es%of%building%complex%UIs
Is#HTML#really#for#content?<select id="countryList"></select><script>$.get('countrylist.json', function(data) { $.each(data, function (i, item) { $('#countryList').append('<option val=' + item.val + '>' + item.name + '</option>'); })});</script>
Is#this#content?<input type="range" min="0" max="10" step="2" value="6">
HTML%isn't%just%for%content,%and%it%never%was
<input type="range" min="0" max="10" step="2" value="6">
<picture> <source srcset="images/extralarge.jpg" media="(min-width: 1000px)"> <source srcset="images/large.jpg" media="(min-width: 800px)"> <img srcset="images/medium.jpg" alt="A giant stone face "></picture>
<select name="provinces"> <option value="AB">Alberta</option> <option value="BC">British Columbia</option></select>
Time%out:%What's%a%declara3ve%API,%and%why%do%I%
want%one?
Declara've)vs.)Impera've)programming
Impera've!specifies!the!steps!to!take!to!achieve!the!desired!result
Declara've!code!specifies!what!the!desired!result!is!and!leaves!the!implementa4on!up!to...!something!else
Standard'Impera,ve'vs.'Declara,ve'code'example
var nums = [1, 3, 7, 11, 13];
// imperative:
var total = 0;
for (var i = 0; i < nums.length; i++) { total += nums[i];}
// declarative:
var total = nums.reduce(function (accumulator, num) { return accumulator + num;}, 0)
Example:)SQL)is)everybody's)favourite)declara:ve)language
SELECT * FROM students WHERE id = 5
HTML%has%declara.ve%APIs<input type="range" min="0" max="10" step="2" value="6">
We#didn't#really#learn#from#those#built4in#examples
<div id="tabs-container"> <ul class="tabs-menu"> <li class="current"><a href="#tab-1">Tab 1</a></li> <a href="#tab-1">Tab 1</a> </ul> <div class="tab"> <div id="tab-1" class="tab-content"></div> <div id="tab-2" class="tab-content"></div> </div></div>
$(".tabs-menu a").click(function(event) { event.preventDefault(); $(this).parent().addClass("current"); $(this).parent().siblings().removeClass("current"); var tab = $(this).attr("href"); $(".tab-content").not(tab).css("display", "none"); $(tab).show();});
We#have#to#dump#a#bunch#of#info#about#our#DOM#into#our#JS#files:
SELECTORS = { messages: '.form__messages', errors: '.form__messages .error', suggest: '.form__messages .suggest', wrappers: '.input-wrapper', userFields: '.input', submitBtn: '.js-signup-submit'};
/*#namespaces#have#been#changed#to#protect#the#innocent
What%about%this%bit%of%JavaScript:%
$(".signup-button").click(function(event) {});
We're%wri(ng%JavaScript%like%we%don't%have%control%over%our%
HTML
We#think#our#HTML#should#be#dumb,#but#then#we#have#to#do#all#sorts#of#shit#in#JS#to#
make#up#for#it
So#what#happens#when#we#re-introduce#some#declara4ve#a6ribtues#
to#our#HTML?
2009:%Meet%Angular.js
<a href="" ng-click="archive()">archive</a>
Angular's*HTML*made*a*lot*of*people*very*angry
<a href="" ng-click="archive()">archive</a>
"I#don't#like#how#it#breaks#the#separa4on#between#html/js/css"
"I#hate#the#way#the#HTML#looks""Isn't#this#a#step#backwards???"
!!"some"nerds"on"twi-er
<a href="" ng-click="archive()">archive</a>
OMG$separa*on$of$concerns
I"have"a"lot"of"things"to"say"about"this
<a href="" ng-click="archive()">archive</a>
OMG$inline$event$handlers
• it's&ok
<a href="" ng-click="archive()">archive</a>
But$it$looks$bad$and$you$should$feel$bad
• no
If#we're#ok#with#this#then...
..."let's"slide"down"this"slippery"slope
Make%up%your%own%declara0ve%a2ributes
<button onClick="openModoal()" modal-target="#salesModal">Open</button>
Make%up%your%own%declara0ve%elements
<modal-trigger-button target="#salesModal">Open</modal-trigger-button>
Welcome'to'the'future'it'is'called'web'components
What%are%Web%Components?• Custom(elements
• Shadow(DOM
• templates
• HTML(imports
An#example:#tabs,#a#be/er#way.<demo-tabs> <demo-tab title="Tab 1">Tab 1 content</demo-tab> <demo-tab title="Tab 2">Tab 2 content</demo-tab></demo-tabs>
<template id="template"> <p>Spooky shadow DOM</p></template>
<script>var proto = Object.create(HTMLElement.prototype, { createdCallback: { value: function() { var t = document.querySelector('#template'); var clone = document.importNode(t.content, true); this.createShadowRoot().appendChild(clone); } }});document.registerElement('demo-tabs', {prototype: proto});</script>
<demo-tabs></demo-tabs>
Also%cool:%composability<fancy-toggle active={{drawerVisible}}>Open Drawer</fancy-toggle>
<animated-drawer shown={{drawerVisible}}> Things in a drawer</animated-drawer>
OKAY%I%WANT%SOME%WEB%COMPONENTS%GIVE%ME%
SOME%WEB%COMPONENTS
...it's&a&li)le&bit&complicated&right&now.&
Browser'support'for'these'specs'is...'not'great.'YET.'
BUMMER
There's'some'op+ons'though
Polymerwww.polymer*project.org/
<paper-tabs selected="{{selected}}"> <paper-tab>Tab 1</paper-tab> <paper-tab>Tab 2</paper-tab> <paper-tab>Tab 3</paper-tab></paper-tabs>
<core-pages selected="{{selected}}"> <div>Page 1</div> <div>Page 2</div> <div>Page 3</div></core-pages>
The$philosophy$behind$components$has$spread$to$a$
framework$near$you
AngularDirec&ves
<tabset> <tab ng-repeat="tab in tabs" heading="{{tab.title}}" active="tab.active"> {{tab.content}} </tab> </tabset>
Ember&COMPONENTS{{demo-tabs}} {{demo-tab title="Tab 1"}}Tab 1 content{{/demo-tab}} {{demo-tab title="Tab 2"}}Tab 2 content{{/demo-tab}}{{/demo-tabs}}
React.jsReact.createClass({ render: function() { return ( <Tabs> <Tabs.Panel title='Tab #1'> <h2>Content #1 here</h2> </Tabs.Panel> <Tabs.Panel title='Tab #2'> <h2>Content #2 here</h2> </Tabs.Panel> </Tabs> ); }});
Declara've)data,binding)libraries
Twine.js/knockout.js
<input bind="color" type="text"><p>You selected <span bind="color"></span></p>
The$JavaScripts$of$the$future
• rather'than'going'back'and'forth'between'dumb'but'specific'markup'and'JS'with'a'lot'of'DOM'querying
• you'll'be'going'back'and'forth'between'wiring'together'components'and'tweaking'their'implementa@ons
The$limits$of$declara0ve$programming
• A#lot#of#this#is#big#wins#for#produc4vity#and#developer#happiness
• but#if#declara4ve#programming#was#perfect#we#wouldn't#s4ll#be#wri4ng#C#et#al
• some4mes#you#need#to#tweak#the#implementa4ons
Let's&do&a&demo
~!DEMO!TIME!~
In#Summarythe$"Declara+ve$renaissance"$of$the$web$is$
coming$whether$you$like$it$or$not
• Angular)is)doing)it
• Ember)is)doing)it
• React)is)doing)it
• The)browsers)themselves)are)doing)it
But$you're$going$to$like$it• declara(ve*APIs
• Meaningful*HTML
• encapsula(on*(styles*too*in*the*na(ve*spec)
• re?usability
• composability
Choose&your&own&adventure• Angular)direc.ves
• Ember)components
• React)components
• Polymer
• Na.ve)(Coming)Soon(ish))
You're'going'to'"pollute"'your'HTML'and'you're'going'to'
like'it
Links• The%state%of%web%components
• Declara4ve%vs%impera4ve%programming
• Our%best%prac4ces%are%killing%us
• The%web's%declara4ve,%composable%future
• Angular.js
• Angular%2.0
• Ember.js
• Ember*road*to*2.0
• React.js
• Polymer
• Web*Components