POLYMER PLEASANT FRONT-END PROGRAMMING WITH WEB COMPONENTS
HI, I AM PLAMEN
COORDINATES: psstoev@gmail.com Twitter: @psstoev G+: +PlamenStoev Jabber: plamenstoev@jabber.minus273.org
WEB DEVELOPER
WHAT WE WILL SEE TODAY: The story so far What is "web components" How Polymer makes them easy Core-elements, paper-elements Where to go from here?
WHAT WILL YOU NEED? Patience :-) A little bit of knowledge in JavaScript
HOW DID WE END UP HERE?
1995 - AND THERE WAS THE DOM html head body h1 p
THE DOM IS A HIGHLY-IMPERATIVE API: function removeRed(root) { var child; for (var i = 0; i < root.children.length; i++) { child = root.children[i]; if (child.children.length > 0) { removeRed(child); } if (child.className.indexOf('red') !== -1) { root.removeChild(child); } } }
Use CSS selectors for DOM traversal. Nice, eh? $('.red').remove(); jQuery makes our code more declarative... ...but doesn't give us a structure, it's just a library
/MV(C?)/ Gives us a structure Emits events when our model changes We can subscribe to them in the views But the process is more manual than it should be
SUPERHEROIC CLIENT-SIDE MVW FRAMEWORK
DATA-BINDING AND DIRECTIVES <tabs> <pane title="Localization"> Date: {{ '2012-04-01' | date:'fullDate' }} <br> Currency: {{ 123456 | currency }} <br> Number: {{ 98765.4321 | number }} <br> </pane> <pane title="Pluralization"> <div ng-controller="BeerCounter"> <div ng-repeat="beerCount in beers"> <ng-pluralize count="beerCount" when="beerForms"> </ng-pluralize> </div> </div> </pane> </tabs>
WEB COMPONENTS
WHY? Encapsulation Orthogonality Keep your code DRY
THE REQUIRED INGREDIENTS: <template> Shadow DOM HTML imports
<TEMPLATE> <template id="grumpy-template"> <img src="/grumpy-cat.png"> <p id="grumpy-caption">I'm not amused</p> </template> The content of the <template> tag is not activated. No scripts run, no images load, etc. The content can be extracted with document.importNode() The content is invisible to document.querySelector(), document.getElementById(), etc. No interpolation. No nesting.
SHADOW DOM
SHADOW DOM element.createShadowRoot() The Shadow DOM provides style and markup encapsulation The elements inside Shadow DOM are invisible to document.querySelector(), etc. The elements inside Shadow DOM are unaffected by "outer" CSS Unless you use the ::shadow pseudo-element The browser renders the shadowRoot instead of the contents of the tag Used internally by the browser for tags like <video>
SHADOW DOM div p body shadowRoot
SHADOW DOM var div = document.createElement('div'); div.createShadowRoot(); document.body.appendChild(div); var p = document.createElement('p'); p.id = 'shadow-p'; div.shadowRoot.appendChild(p); document.getElementById('shadow-p'); // null
HTML IMPORTS <link rel="import" href="bundles/bootstrap.html"> #include for the web Finally a proper way of loading HTML resources You can import any markup
"VANILLA" WEB COMPONENTS
IN THE GOOD TRADITION OF DOM... document.registerElement('my-awesome-element') Inherits from HTMLElement by default The name must contain a hyphen, e.g. <mega-button>
LIFECYCLE CALLBACKS createdCallback() - called when an instance is created attachedCallback() - called when the element is attached to DOM detachedCallback() - called when the element is detached to DOM attributeChangedCallback() - called when an attribute is changed
FORTUNE-COOKIE var fortunes = ['Health', 'Money', 'Luck']; function draw(container) { container.textContent = fortunes[Math.floor(Math.random() * 3)]; }
FORTUNE-COOKIE <template id="fortune-template"> <p id="fortune"></p> </template>
var tmpl = document.getElementById('fortune-template'); var FortunePrototype = Object.create(HTMLElement.prototype); FortunePrototype.createdCallback = function() { var tmplContent = document.importNode(tmpl.content, true); this.createShadowRoot().appendChild(tmplContent); var fortune = this.shadowRoot.getElementById('fortune'); draw(fortune); fortune.addEventListener('click', draw.bind(null, fortune)); }; FortuneCookie = document.registerElement('fortune-cookie', { prototype: FortunePrototype });
ONCE DECLARED IT CAN BE CREATED IN THE FOLLOWING WAYS: <fortune-cookie></fortune-cookie> document.createElement('fortune-cookie') new FortuneCookie()
"LIVE DEMO" :) Health
POLYMER
POLYMER Not a "framework", just a library Think "jQuery for the Web Components API" Built on top of the Web Components APIs (plus a few more)
FORTUNE-COOKIE WITH POLYMER <link rel="import" href="bower_components/polymer/polymer.html"> <polymer-element name="polymer-cookie"> <template> <p on-click="{{ _draw }}">{{ fortune }}</p> </template> <script>(function() { var fortunes = ['Health', 'Money', 'Luck']; Polymer({ created: function() { this._draw(); }, _draw: function() { this.fortune = fortunes[Math.floor(Math.random() * 3)]; } }); }())</script> </polymer-element>
"LIVE DEMO" 2.0 Health
POLYMER STEP BY STEP
INSTALLATION $ bower install --save Polymer/polymer
WEBCOMPONENTS.JS <script src="bower_components/webcomponents/webcomponents.js"></script> This must be the first script on the page
WEBCOMPONENTS.JS
BROWSER COMPATIBILITY :-( Polymer runs "natively" Chrome >= 36 Webcomponents.js is used in every other browser IE <= 9 not supported
WE HEARD YOU LIKE WEB COMPONENTS SO WE LET YOU CREATE WEB COMPONENTS USING WEB COMPONENTS
<link rel="import" href="bower_components/polymer/polymer.html"> <polymer-element name="personal-greeter" attributes="name" constructor="PersonalGreeter"> <template> <style> p { color: #ddd; } </style> <p>Hello, {{ name }}</p> </template> <script> Polymer('personal-greeter', {}); </script> </polymer-element>
Polymer('personal-greeter', { created: function() {}, attached: function() {}, detached: function() {}, attributeChanged: function() {}, ready: function() {}, domReady: function() {} });
<link rel="import" href="bower_components/polymer/polymer.html"> <polymer-element name="personal-greeter" attributes="name" constructor="PersonalGreeter"> <template> <p>Hello, {{ name }}</p> <input type="text" value="{{ name }}"> </template> </polymer-element>
<personal-greeter name="Plamen"></personal-greeter> Hello, Plamen Plamen
NOT A SINGLE LINE OF JAVASCRIPT™
ATTRIBUTES, REVISITED Any attributes we expose become properties of the instance object But HTML attributes are strings - what if we want numbers/arrays/etc.? Polymer allows us to hint the type of an attribute We do this by providing default values
<polymer-element name="cart-item" attributes="name price quantity"> <script> Polymer({ name: '', // typeof 'string' === true price: 0, // typeof 'number' === true created: function() { // Or, we can provide the default values // in the 'created' callback. this.quantity = 0; // typeof 'number' === true } }); </script> </polymer-element>
WATCH OUT WHEN USING ARRAYS/OBJECTS <polymer-element name="another-cool-element" attributes="aList anObject"> <script> Polymer({ aList: [], anObject: {} }); </script> </polymer-element>
ALLWAYS INITIALIZE FIELDS IN THE CREATED() CALLBACK <polymer-element name="another-cool-element" attributes="aList anObject aNumber"> <script> Polymer({ created: function() { this.aNumber = 0; this.aList = []; this.anObject = {}; } }); </script> </polymer-element>
"STATIC" FIELDS If you want to share something between all instances you can use JavaScript's scoping. <polymer-element name="another-cool-element"> <script> (function() { var sharedCache = {}; // Visible to all instances Polymer({ created: function() { // ... } }); }()); </script> </polymer-element>
DECLARATIVE EVENT BINDING <polymer-element name="click-counter"> <template> <div on-click="{{ countClick }}"> Click here (click count: {{ count }}) </div> </template> <script> Polymer({ created: function() { this.count = 0; }, countClick: function() { this.count++; } }); </script> </polymer-element> Click here (click count: 0)
CUSTOM EVENTS <polymer-element name="three-clicks"> <template> <div on-click="{{ countClick }}">Click here</div> </template> <script> Polymer({ created: function() { this.count = 0; }, countClick: function() { this.count++; if (this.count === 3) { this.count = 0; this.fire('triple'); } } }); </script> </polymer-element>
CUSTOM EVENTS Click here <three-clicks></three-clicks> <script> document .querySelector('three-clicks') .addEventListener('triple', function() { alert('You clicked three times in a row'); }); </script>
AUTO-BINDING SOMETIMES WE DON'T WANT TO CREATE NEW ELEMENTS <template is="auto-binding"> <p>Hello, {{ name }}</p> <input type="text" value="{{ name }}" on-keydown="{{ prevent }}"> </template> Hello,
EMBRACE THE DOM MARKUP IS COOL AGAIN. <select name="quantity"> <option value="1">One</option> <option value="2">Two</option> </select>
CREATE LAYOUTS USING ONLY ATTRIBUTES <div horizontal layout> <div flex three>One</div> <div flex>Two</div> <div flex two>Three</div> </div> RESULT: One Two Three
AJAX CALLS WITH MARKUP <core-ajax auto url="http://gdata.youtube.com/feeds/api/videos/" params='{"alt":"json", "q":"chrome"}' handleAs="json" on-core-response="{{ handleResponse }}"> </core-ajax>
MEDIA QUERIES WITH MARKUP <core-media-query query="max-width: 640px" queryMatches="{{ phoneScreen }}"> </core-media-query>
PRISM-JS <prism-js language="javascript" theme="okaidia"> Polymer('personal-greeter', { created: function() {}, attached: function() {}, detached: function() {}, attributeChanged: function() {}, ready: function() {}, domReady: function() {} }); </prism-js>
CORE-ELEMENTS AND PAPER-ELEMENTS A nice collection of predefined custom elements Buttons, tabs, dialogs, icons... Most things from Bootstrap become obsolete
PAPER-BUTTON <paper-button raised>Click me</paper-button> CLICK ME
PAPER-CHECKBOX <paper-checkbox checked></paper-checkbox>
PAPER-INPUT-DECORATOR <paper-input-decorator label="Your e-mail" floatingLabel> <input is="core-input" type="email"> </paper-input-decorator> Your e-mail
"THE GREATEST BOOK EVER" WELL...
FIND OUT MORE: Polymer Project Component kitchen Patterns in Polymer (by Chris Strom) Vulcanize
THANK YOU!
QUESTIONS?

Polymer - pleasant client-side programming with web components

  • 1.
    POLYMER PLEASANT FRONT-ENDPROGRAMMING WITH WEB COMPONENTS
  • 2.
    HI, I AMPLAMEN
  • 3.
    COORDINATES: psstoev@gmail.com Twitter: @psstoev G+: +PlamenStoev Jabber: plamenstoev@jabber.minus273.org
  • 4.
  • 5.
    WHAT WE WILLSEE TODAY: The story so far What is "web components" How Polymer makes them easy Core-elements, paper-elements Where to go from here?
  • 6.
    WHAT WILL YOUNEED? Patience :-) A little bit of knowledge in JavaScript
  • 7.
    HOW DID WEEND UP HERE?
  • 8.
    1995 - ANDTHERE WAS THE DOM html head body h1 p
  • 9.
    THE DOM ISA HIGHLY-IMPERATIVE API: function removeRed(root) { var child; for (var i = 0; i < root.children.length; i++) { child = root.children[i]; if (child.children.length > 0) { removeRed(child); } if (child.className.indexOf('red') !== -1) { root.removeChild(child); } } }
  • 10.
    Use CSS selectorsfor DOM traversal. Nice, eh? $('.red').remove(); jQuery makes our code more declarative... ...but doesn't give us a structure, it's just a library
  • 11.
    /MV(C?)/ Gives usa structure Emits events when our model changes We can subscribe to them in the views But the process is more manual than it should be
  • 12.
  • 13.
    DATA-BINDING AND DIRECTIVES <tabs> <pane title="Localization"> Date: {{ '2012-04-01' | date:'fullDate' }} <br> Currency: {{ 123456 | currency }} <br> Number: {{ 98765.4321 | number }} <br> </pane> <pane title="Pluralization"> <div ng-controller="BeerCounter"> <div ng-repeat="beerCount in beers"> <ng-pluralize count="beerCount" when="beerForms"> </ng-pluralize> </div> </div> </pane> </tabs>
  • 14.
  • 15.
  • 16.
    THE REQUIRED INGREDIENTS: <template> Shadow DOM HTML imports
  • 17.
    <TEMPLATE> <template id="grumpy-template"> <img src="/grumpy-cat.png"> <p id="grumpy-caption">I'm not amused</p> </template> The content of the <template> tag is not activated. No scripts run, no images load, etc. The content can be extracted with document.importNode() The content is invisible to document.querySelector(), document.getElementById(), etc. No interpolation. No nesting.
  • 18.
  • 19.
    SHADOW DOM element.createShadowRoot() The Shadow DOM provides style and markup encapsulation The elements inside Shadow DOM are invisible to document.querySelector(), etc. The elements inside Shadow DOM are unaffected by "outer" CSS Unless you use the ::shadow pseudo-element The browser renders the shadowRoot instead of the contents of the tag Used internally by the browser for tags like <video>
  • 20.
    SHADOW DOM div p body shadowRoot
  • 21.
    SHADOW DOM vardiv = document.createElement('div'); div.createShadowRoot(); document.body.appendChild(div); var p = document.createElement('p'); p.id = 'shadow-p'; div.shadowRoot.appendChild(p); document.getElementById('shadow-p'); // null
  • 22.
    HTML IMPORTS <linkrel="import" href="bundles/bootstrap.html"> #include for the web Finally a proper way of loading HTML resources You can import any markup
  • 23.
  • 24.
    IN THE GOODTRADITION OF DOM... document.registerElement('my-awesome-element') Inherits from HTMLElement by default The name must contain a hyphen, e.g. <mega-button>
  • 25.
    LIFECYCLE CALLBACKS createdCallback()- called when an instance is created attachedCallback() - called when the element is attached to DOM detachedCallback() - called when the element is detached to DOM attributeChangedCallback() - called when an attribute is changed
  • 26.
    FORTUNE-COOKIE var fortunes= ['Health', 'Money', 'Luck']; function draw(container) { container.textContent = fortunes[Math.floor(Math.random() * 3)]; }
  • 27.
    FORTUNE-COOKIE <template id="fortune-template"> <p id="fortune"></p> </template>
  • 28.
    var tmpl =document.getElementById('fortune-template'); var FortunePrototype = Object.create(HTMLElement.prototype); FortunePrototype.createdCallback = function() { var tmplContent = document.importNode(tmpl.content, true); this.createShadowRoot().appendChild(tmplContent); var fortune = this.shadowRoot.getElementById('fortune'); draw(fortune); fortune.addEventListener('click', draw.bind(null, fortune)); }; FortuneCookie = document.registerElement('fortune-cookie', { prototype: FortunePrototype });
  • 29.
    ONCE DECLARED ITCAN BE CREATED IN THE FOLLOWING WAYS: <fortune-cookie></fortune-cookie> document.createElement('fortune-cookie') new FortuneCookie()
  • 30.
  • 31.
  • 32.
    POLYMER Not a"framework", just a library Think "jQuery for the Web Components API" Built on top of the Web Components APIs (plus a few more)
  • 33.
    FORTUNE-COOKIE WITH POLYMER <link rel="import" href="bower_components/polymer/polymer.html"> <polymer-element name="polymer-cookie"> <template> <p on-click="{{ _draw }}">{{ fortune }}</p> </template> <script>(function() { var fortunes = ['Health', 'Money', 'Luck']; Polymer({ created: function() { this._draw(); }, _draw: function() { this.fortune = fortunes[Math.floor(Math.random() * 3)]; } }); }())</script> </polymer-element>
  • 34.
  • 35.
  • 36.
    INSTALLATION $ bowerinstall --save Polymer/polymer
  • 37.
  • 38.
  • 39.
    BROWSER COMPATIBILITY :-( Polymer runs "natively" Chrome >= 36 Webcomponents.js is used in every other browser IE <= 9 not supported
  • 40.
    WE HEARD YOULIKE WEB COMPONENTS SO WE LET YOU CREATE WEB COMPONENTS USING WEB COMPONENTS
  • 41.
    <link rel="import" href="bower_components/polymer/polymer.html"> <polymer-element name="personal-greeter" attributes="name" constructor="PersonalGreeter"> <template> <style> p { color: #ddd; } </style> <p>Hello, {{ name }}</p> </template> <script> Polymer('personal-greeter', {}); </script> </polymer-element>
  • 42.
    Polymer('personal-greeter', { created:function() {}, attached: function() {}, detached: function() {}, attributeChanged: function() {}, ready: function() {}, domReady: function() {} });
  • 43.
    <link rel="import" href="bower_components/polymer/polymer.html"> <polymer-element name="personal-greeter" attributes="name" constructor="PersonalGreeter"> <template> <p>Hello, {{ name }}</p> <input type="text" value="{{ name }}"> </template> </polymer-element>
  • 44.
  • 45.
    NOT A SINGLELINE OF JAVASCRIPT™
  • 46.
    ATTRIBUTES, REVISITED Anyattributes we expose become properties of the instance object But HTML attributes are strings - what if we want numbers/arrays/etc.? Polymer allows us to hint the type of an attribute We do this by providing default values
  • 47.
    <polymer-element name="cart-item" attributes="nameprice quantity"> <script> Polymer({ name: '', // typeof 'string' === true price: 0, // typeof 'number' === true created: function() { // Or, we can provide the default values // in the 'created' callback. this.quantity = 0; // typeof 'number' === true } }); </script> </polymer-element>
  • 48.
    WATCH OUT WHENUSING ARRAYS/OBJECTS <polymer-element name="another-cool-element" attributes="aList anObject"> <script> Polymer({ aList: [], anObject: {} }); </script> </polymer-element>
  • 49.
    ALLWAYS INITIALIZE FIELDSIN THE CREATED() CALLBACK <polymer-element name="another-cool-element" attributes="aList anObject aNumber"> <script> Polymer({ created: function() { this.aNumber = 0; this.aList = []; this.anObject = {}; } }); </script> </polymer-element>
  • 50.
    "STATIC" FIELDS Ifyou want to share something between all instances you can use JavaScript's scoping. <polymer-element name="another-cool-element"> <script> (function() { var sharedCache = {}; // Visible to all instances Polymer({ created: function() { // ... } }); }()); </script> </polymer-element>
  • 51.
    DECLARATIVE EVENT BINDING <polymer-element name="click-counter"> <template> <div on-click="{{ countClick }}"> Click here (click count: {{ count }}) </div> </template> <script> Polymer({ created: function() { this.count = 0; }, countClick: function() { this.count++; } }); </script> </polymer-element> Click here (click count: 0)
  • 52.
    CUSTOM EVENTS <polymer-elementname="three-clicks"> <template> <div on-click="{{ countClick }}">Click here</div> </template> <script> Polymer({ created: function() { this.count = 0; }, countClick: function() { this.count++; if (this.count === 3) { this.count = 0; this.fire('triple'); } } }); </script> </polymer-element>
  • 53.
    CUSTOM EVENTS Clickhere <three-clicks></three-clicks> <script> document .querySelector('three-clicks') .addEventListener('triple', function() { alert('You clicked three times in a row'); }); </script>
  • 54.
    AUTO-BINDING SOMETIMES WEDON'T WANT TO CREATE NEW ELEMENTS <template is="auto-binding"> <p>Hello, {{ name }}</p> <input type="text" value="{{ name }}" on-keydown="{{ prevent }}"> </template> Hello,
  • 55.
    EMBRACE THE DOM MARKUP IS COOL AGAIN. <select name="quantity"> <option value="1">One</option> <option value="2">Two</option> </select>
  • 56.
    CREATE LAYOUTS USINGONLY ATTRIBUTES <div horizontal layout> <div flex three>One</div> <div flex>Two</div> <div flex two>Three</div> </div> RESULT: One Two Three
  • 57.
    AJAX CALLS WITHMARKUP <core-ajax auto url="http://gdata.youtube.com/feeds/api/videos/" params='{"alt":"json", "q":"chrome"}' handleAs="json" on-core-response="{{ handleResponse }}"> </core-ajax>
  • 58.
    MEDIA QUERIES WITHMARKUP <core-media-query query="max-width: 640px" queryMatches="{{ phoneScreen }}"> </core-media-query>
  • 59.
    PRISM-JS <prism-js language="javascript"theme="okaidia"> Polymer('personal-greeter', { created: function() {}, attached: function() {}, detached: function() {}, attributeChanged: function() {}, ready: function() {}, domReady: function() {} }); </prism-js>
  • 60.
    CORE-ELEMENTS AND PAPER-ELEMENTS A nice collection of predefined custom elements Buttons, tabs, dialogs, icons... Most things from Bootstrap become obsolete
  • 61.
    PAPER-BUTTON <paper-button raised>Clickme</paper-button> CLICK ME
  • 62.
  • 63.
    PAPER-INPUT-DECORATOR <paper-input-decorator label="Youre-mail" floatingLabel> <input is="core-input" type="email"> </paper-input-decorator> Your e-mail
  • 64.
    "THE GREATEST BOOKEVER" WELL...
  • 65.
    FIND OUT MORE: Polymer Project Component kitchen Patterns in Polymer (by Chris Strom) Vulcanize
  • 66.
  • 67.