Upgrade to Pro — share decks privately, control downloads, hide ads and more …

AfrotechFest 2018: A Glimmer of Hope - The Mode...

AfrotechFest 2018: A Glimmer of Hope - The Modern State of Web Components

Talk about the current state of web components and how Glimmer.js can be used to build them at Afrotechfest 2018, London, UK.

Jessy Jordan

January 27, 2018
Tweet

More Decks by Jessy Jordan

Other Decks in Programming

Transcript

  1. Ember Addons ? REUSE FUNCTIONALITY THROUGH COMPONENTS Extending apps built

    upon the same JS framework Extending any JS app? ✅
  2. THE WEB COMPONENT SPEC - CUSTOM ELEMENTS V1 CUSTOM ELEMENTS

    HTML IMPORT SHADOW DOM TEMPLATE See also W3C working draft on custom elements
  3. CREATING CUSTOM ELEMENTS More on the MDN Guide on Custom

    Elements class LinkedImage extends HTMLElement { constructor() { super(); var img = document.createElement('img'); img.alt = this.getAttribute('data-name'); img.src = this.getAttribute('data-img'); // …more setup work img.addEventListener('click', () => { window.location = this.getAttribute('data-url'); }); } customElements.define(‘linked-image’, LinkedImage);
  4. THE WEB COMPONENT SPEC - BROWSER SUPPORT CUSTOM ELEMENTS HTML

    IMPORT SHADOW DOM TEMPLATE See more about browser support on webcomponents.org http://slides.com/sara_harkousse/web-components-talk-ruhrjs-2017#/19 It’s all rainbows and unicorns! Is it? Sara Harkousse at RuhrJS 2017
  5. THE WEB COMPONENT SPEC - BROWSER SUPPORT CUSTOM ELEMENTS HTML

    IMPORT SHADOW DOM TEMPLATE ⛔ ⛔ ✴ See more about browser support on webcomponents.org http://slides.com/sara_harkousse/web-components-talk-ruhrjs-2017#/19 It’s all rainbows and unicorns! Is it? Sara Harkousse at RuhrJS 2017
  6. “Fast and light-weight UI components for the web” announced March

    2017 Yehuda Katz & Tom Dale, EmberConf 2017, Keynote extracted from Ember’s rendering engine Glimmer
  7. Glimmer-map |── config | |—- environment.js | |—- module-map.ts |

    |—- resolver-configuration.ts | |── dist |── src | |── ui | | |── components | | | └── my-app | | | |── component.ts | | | |── template.hbs | | | | | |── styles | | | └── app.css | | | | | |── index.html | | | |── index.ts | |── main.ts | |── ember-cli-build.js | ... other files ...
  8. INITIALIZING THE CUSTOM ELEMENT function initializeCustomElement(app: Application, name: string): void

    { // creating a GlimmerElement instance from HTMLElement function GlimmerElement() { return Reflect.construct(HTMLElement, [], GlimmerElement); } GlimmerElement.prototype = Object.create(HTMLElement.prototype, { constructor: { value: GlimmerElement }, connectedCallback: { value: function connectedCallback(): void { // ... bring element into the DOM and do setup work // ... } } }); // finally registering the component via customElements v1 API window.customElements.define(name, GlimmerElement); }
  9. INITIALIZING THE CUSTOM ELEMENT function initializeCustomElement(app: Application, name: string): void

    { // creating a GlimmerElement instance from HTMLElement function GlimmerElement() { return Reflect.construct(HTMLElement, [], GlimmerElement); } GlimmerElement.prototype = Object.create(HTMLElement.prototype, { constructor: { value: GlimmerElement }, connectedCallback: { value: function connectedCallback(): void { // ... bring element into the DOM and do setup work // ... } } }); // finally registering the component via customElements v1 API window.customElements.define(name, GlimmerElement); }
  10. INITIALIZING THE CUSTOM ELEMENT function initializeCustomElement(app: Application, name: string): void

    { // creating a GlimmerElement instance from HTMLElement function GlimmerElement() { return Reflect.construct(HTMLElement, [], GlimmerElement); } GlimmerElement.prototype = Object.create(HTMLElement.prototype, { constructor: { value: GlimmerElement }, connectedCallback: { value: function connectedCallback(): void { // ... bring element into the DOM and do setup work // ... } } }); // finally registering the component via customElements v1 API window.customElements.define(name, GlimmerElement); }
  11. INITIALIZING THE CUSTOM ELEMENT function initializeCustomElement(app: Application, name: string): void

    { // creating a GlimmerElement instance from HTMLElement function GlimmerElement() { return Reflect.construct(HTMLElement, [], GlimmerElement); } GlimmerElement.prototype = Object.create(HTMLElement.prototype, { constructor: { value: GlimmerElement }, connectedCallback: { value: function connectedCallback(): void { // ... bring element into the DOM and do setup work // ... } } }); // finally registering the component via customElements v1 API window.customElements.define(name, GlimmerElement); }
  12. DEPENDENCY MANAGEMENT yarn add --dev leaflet Modules Syntax Read more

    about dependency management in the related blog post: https://goo.gl/TY4o9t
  13. COMPONENT LIFECYCLE HOOKS // src/ui/components/glimmer-map/component.ts import Component from "@glimmer/component"; import

    L from 'leaflet'; export default class GlimmerMap extends Component { didInsertElement() { } }
  14. COMPONENT LIFECYCLE HOOKS // src/ui/components/glimmer-map/component.ts import Component from "@glimmer/component"; import

    L from 'leaflet'; export default class GlimmerMap extends Component { didInsertElement() { } }
  15. COMPONENT LIFECYCLE HOOKS // src/ui/components/glimmer-map/component.ts import Component from "@glimmer/component"; import

    L from 'leaflet'; export default class GlimmerMap extends Component { didInsertElement() { this.createMapInstance(); } }
  16. COMPONENT LIFECYCLE HOOKS // src/ui/components/glimmer-map/component.ts //… import L from 'leaflet';

    export default class GlimmerMap extends Component { didInsertElement() { this.createMapInstance(); } createMapInstance() const element = this.bounds.firstNode.querySelector(‘#map'); this.map = L.map(element).setView([41.08, 11.068], 12); } }
  17. // src/ui/components/glimmer-map/component.ts import Component from "@glimmer/component"; import L from 'leaflet';

    export default class GlimmerMap extends Component { didInsertElement() { this.createMapInstance(); } createMapInstance() const element = this.bounds.firstNode.querySelector(‘#map'); this.map = L.map(element).setView([41.08, 11.068], 12); } } COMPONENT LIFECYCLE HOOKS
  18. // src/ui/components/glimmer-map/component.ts import Component from "@glimmer/component"; import L from 'leaflet';

    export default class GlimmerMap extends Component { didInsertElement() { this.createMapInstance(); } createMapInstance() const element = this.bounds.firstNode.querySelector(‘#map'); this.map = L.map(element).setView([41.08, 11.068], 12);; } } COMPONENT LIFECYCLE HOOKS
  19. // src/ui/components/glimmer-map/component.ts import Component from "@glimmer/component"; import L from 'leaflet';

    export default class GlimmerMap extends Component { didInsertElement() { this.createMapInstance(); this.renderMap(); } createMapInstance() const element = this.bounds.firstNode.querySelector(‘#map'); this.map = L.map(element).setView([41.08, 11.068], 12); } } COMPONENT LIFECYCLE HOOKS
  20. export default class GlimmerMap extends Component { didInsertElement() { this.createMapInstance();

    this.renderMap(); } renderMap() { L.tileLayer('https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token={accessToken}', { attribution: ‘….’, maxZoom: 18, id: 'mapbox.streets', accessToken: 'your.mapbox.access.token' }).addTo(this.map); } } COMPONENT LIFECYCLE HOOKS
  21. COMPUTED PROPERTIES <script> class XCustom extends Polymer.Element { static get

    properties() { return { first: String, last: String, fullName: { type: String, computed: 'computeFullName(first, last)' } } } } </script> <template> My name is <span>{{fullName}}</span> </template> import Component from '@ember/component'; import { computed } from '@ember/object'; export default Component.extend({ firstName: null, lastName: null, fullName: computed('firstName', 'lastName', function() { return `${this.get('firstName')} {this.get('lastName')}`; }) });
  22. COMPUTED PROPERTIES <script> class XCustom extends Polymer.Element { static get

    properties() { return { first: String, last: String, fullName: { type: String, computed: 'computeFullName(first, last)' } } } } </script> <template> My name is <span>{{fullName}}</span> </template> import Component from '@ember/component'; import { computed } from '@ember/object'; export default Component.extend({ firstName: null, lastName: null, fullName: computed('firstName', 'lastName', function() { return `${this.get('firstName')} {this.get('lastName')}`; }) });
  23. COMPUTED PROPERTIES <script> class XCustom extends Polymer.Element { static get

    properties() { return { first: String, last: String, fullName: { type: String, computed: 'computeFullName(first, last)' } } } } </script> <template> My name is <span>{{fullName}}</span> </template> import Component from '@ember/component'; import { computed } from '@ember/object'; export default Component.extend({ firstName: null, lastName: null, fullName: computed('firstName', 'lastName', function() { return `${this.get('firstName')} {this.get('lastName')}`; }) }); // app/templates/components/x-custom.hbs My name is <span>{{fullName}}</span>
  24. COMPUTED PROPERTIES <script> class XCustom extends Polymer.Element { static get

    properties() { return { first: String, last: String, fullName: { type: String, computed: 'computeFullName(first, last)' } } } } </script> <template> My name is <span>{{fullName}}</span> </template> import Component from '@ember/component'; import { computed } from '@ember/object'; export default Component.extend({ firstName: null, lastName: null, fullName: computed('firstName', 'lastName', function() { return `${this.get('firstName')} {this.get('lastName')}`; }) }); // app/templates/components/x-custom.hbs My name is <span>{{fullName}}</span>
  25. COMPUTED PROPERTIES <script> class XCustom extends Polymer.Element { static get

    properties() { return { first: String, last: String, fullName: { type: String, computed: 'computeFullName(first, last)' } } } } </script> <template> My name is <span>{{fullName}}</span> </template> import Component from '@ember/component'; import { computed } from '@ember/object'; export default Component.extend({ firstName: null, lastName: null, fullName: computed('firstName', 'lastName', function() { return `${this.get('firstName')} {this.get('lastName')}`; }) }); // app/templates/components/x-custom.hbs My name is <span>{{fullName}}</span> Com puted Properties in Em berJS
  26. COMPUTED PROPERTIES <script> class XCustom extends Polymer.Element { static get

    properties() { return { first: String, last: String, fullName: { type: String, computed: 'computeFullName(first, last)' } } } } </script> <template> My name is <span>{{fullName}}</span> </template> import Component from '@ember/component'; import { computed } from '@ember/object'; export default Component.extend({ firstName: null, lastName: null, fullName: computed('firstName', 'lastName', function() { return `${this.get('firstName')} {this.get('lastName')}`; }) }); // app/templates/components/x-custom.hbs My name is <span>{{fullName}}</span> Com puted Properties in Em berJS
  27. RERENDER ON USER INTERACTION // src/ui/components/glimmer-map/component.ts export default class GlimmerMap

    extends Component { @tracked lon: number = 11.6020; @tracked lat: number = 48.1351; //... }
  28. // src/ui/components/glimmer-map/component.ts export default class GlimmerMap extends Component { @tracked

    lon: number = 11.6020; @tracked lat: number = 48.1351; //… } RERENDER ON USER INTERACTION
  29. // src/ui/components/glimmer-map/component.ts export default class GlimmerMap extends Component { @tracked

    lon: number = 11.6020; @tracked lat: number = 48.1351; //… } <!-- src/ui/components/glimmer-map/template.hbs --> <div class="glimmer-map"> <div id="map"></div> E: <input class="x-coord" type="number" step="0.0001" value={{lon}}/> N: <input class="y-coord" type="number" step="0.0001" value={{lat}}/> </div> RERENDER ON USER INTERACTION
  30. // src/ui/components/glimmer-map/component.ts export default class GlimmerMap extends Component { @tracked

    lon: number = 11.6020; @tracked lat: number = 48.1351; //… } <!-- src/ui/components/glimmer-map/template.hbs --> <div class="glimmer-map"> <div id="map"></div> E: <input class="x-coord" type="number" step="0.0001" value={{lon}} oninput={{action setView}}/> N: <input class="y-coord" type="number" step="0.0001" value={{lat}} oninput={{action setView}} /> </div> RERENDER ON USER INTERACTION
  31. // src/ui/components/glimmer-map/component.ts export default class GlimmerMap extends Component { @tracked

    lon: number = 11.6020; @tracked lat: number = 48.1351; setView() { this.lon = this.element.querySelector('x-coord').value; this.lat = this.element.querySelector('y-coord').value; this.map.setView([this.lat, this.lon], 12); } //… } <!-- src/ui/components/glimmer-map/template.hbs --> <div class="glimmer-map"> <div id="map"></div> E: <input class="x-coord" type="number" step="0.0001" value={{lon}} oninput={{action setView}}/> N: <input class="y-coord" type="number" step="0.0001" value={{lat}} oninput={{action setView}} /> </div> RERENDER ON USER INTERACTION
  32. TRACKING CHANGES FOR USER INTERACTION // src/ui/components/glimmer-map/component.ts export default class

    GlimmerMap extends Component { @tracked lon: number = 11.6020; @tracked lat: number = 48.1351; setView() { this.lon = this.element.querySelector('x-coord').value; this.lat = this.element.querySelector('y-coord').value; this.map.setView([this.lat, this.lon], 12); } //… }
  33. IMPORTING IT INTO EXISTING APP <!DOCTYPE html> <html> <head> <title>My

    Other App</title> <script src="/assets/webcomponentsjs/webcomponents-lite.js"></script> <link rel="stylesheet" href="/assets/glimmer-map/app.css"></link> </head> <body> <glimmer-map></glimmer-map> <script src=“/assets/glimmer-map/app.js"></script> </body> </html>
  34. UPGRADE FROM GLIMMER COMPONENTS TO EMBER APPS Tom Dale: EmberConf

    2017: State of the Union https://www.emberjs.com/blog/2017/04/05/emberconf-2017-state-of-the-union.html
  35. UPGRADE FROM GLIMMER COMPONENTS TO EMBER APPS Request for Comments

    in Final Comment Period: Splitting Ember into Packages https://github.com/emberjs/rfcs/pull/284