Slide 1

Slide 1 text

A GLIMMER OF HOPE Jessica Jordan @jjordan_dev THE MODERN STATE OF WEB COMPONENTS

Slide 2

Slide 2 text

jjordan_dev jessica-jordan https://the-emberjs-times.ongoodbits.com/ https://www.meetup.com/ Ember-js-Berlin/

Slide 3

Slide 3 text

BREAKING DOWN COMPLEXITY INTO COMPONENTS

Slide 4

Slide 4 text

BREAKING DOWN COMPLEXITY INTO COMPONENTS

Slide 5

Slide 5 text

REUSE FUNCTIONALITY THROUGH COMPONENTS

Slide 6

Slide 6 text

REUSE FUNCTIONALITY THROUGH COMPONENTS

Slide 7

Slide 7 text

Ember Addons REUSE FUNCTIONALITY THROUGH COMPONENTS Extending apps built upon the same JS framework ✅

Slide 8

Slide 8 text

Ember Addons REUSE FUNCTIONALITY THROUGH COMPONENTS Extending apps built upon the same JS framework ✅

Slide 9

Slide 9 text

Ember Addons ? REUSE FUNCTIONALITY THROUGH COMPONENTS Extending apps built upon the same JS framework Extending any JS app? ✅

Slide 10

Slide 10 text

WEB COMPONENTS REUSABILITY BASED ON OPEN WEB STANDARDS

Slide 11

Slide 11 text

THE WEB COMPONENT SPEC CUSTOM ELEMENTS HTML IMPORT SHADOW DOM TEMPLATE

Slide 12

Slide 12 text

THE WEB COMPONENT SPEC - CUSTOM ELEMENTS V1 CUSTOM ELEMENTS HTML IMPORT SHADOW DOM TEMPLATE See also W3C working draft on custom elements

Slide 13

Slide 13 text

CUSTOMELEMENTREGISTRY INTERFACE class CustomElementClass extends HTMLElement { // ... }

Slide 14

Slide 14 text

CUSTOMELEMENTREGISTRY INTERFACE customElements.define(name, constructor, options);

Slide 15

Slide 15 text

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);

Slide 16

Slide 16 text

CREATING CUSTOM ELEMENTS More on the MDN Guide on Custom Elements

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

Image from giphy.com

Slide 20

Slide 20 text

bower install --save webcomponents/webcomponentsjs npm install @webcomponents/webcomponentsjs

Slide 21

Slide 21 text

Image from https://tenor.com/

Slide 22

Slide 22 text

“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

Slide 23

Slide 23 text

WITH GLIMMER.JS BUILDING WEB COMPONENTS

Slide 24

Slide 24 text

BUILDING A STREET MAP https://goo.gl/TY4o9t

Slide 25

Slide 25 text

GETTING STARTED yarn global add ember-cli ember new glimmer-map -b @glimmer/blueprint —-web-component

Slide 26

Slide 26 text

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 ...

Slide 27

Slide 27 text

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); }

Slide 28

Slide 28 text

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); }

Slide 29

Slide 29 text

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); }

Slide 30

Slide 30 text

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); }

Slide 31

Slide 31 text

DEPENDENCY MANAGEMENT yarn add --dev leaflet

Slide 32

Slide 32 text

DEPENDENCY MANAGEMENT yarn add --dev leaflet

Slide 33

Slide 33 text

DEPENDENCY MANAGEMENT yarn add --dev leaflet Modules Syntax

Slide 34

Slide 34 text

DEPENDENCY MANAGEMENT yarn add --dev leaflet Modules Syntax

Slide 35

Slide 35 text

DEPENDENCY MANAGEMENT yarn add --dev leaflet Modules Syntax Read more about dependency management in the related blog post: https://goo.gl/TY4o9t

Slide 36

Slide 36 text

DEPENDENCY MANAGEMENT // src/ui/components/glimmer-map/component.ts import Component from '@glimmer/component'; import L from 'leaflet'; export default class GlimmerMap extends Component { }

Slide 37

Slide 37 text

DEPENDENCY MANAGEMENT // src/ui/components/glimmer-map/component.ts import Component from '@glimmer/component'; import L from 'leaflet'; export default class GlimmerMap extends Component { }

Slide 38

Slide 38 text

DEPENDENCY MANAGEMENT // src/ui/components/glimmer-map/component.ts import Component from '@glimmer/component'; import L from 'leaflet'; export default class GlimmerMap extends Component { }

Slide 39

Slide 39 text

DEPENDENCY MANAGEMENT // src/ui/components/glimmer-map/component.ts import Component from '@glimmer/component'; import L from 'leaflet'; export default class GlimmerMap extends Component { . . . }

Slide 40

Slide 40 text

DOM Removal DOM Insertion Re-render

Slide 41

Slide 41 text

DOM Removal DOM Insertion Re-render didInsertElement(){}

Slide 42

Slide 42 text

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() { } }

Slide 43

Slide 43 text

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() { } }

Slide 44

Slide 44 text

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(); } }

Slide 45

Slide 45 text

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); } }

Slide 46

Slide 46 text

// 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

Slide 47

Slide 47 text

// 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

Slide 48

Slide 48 text

// 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

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

No content

Slide 51

Slide 51 text

GLIMMER.JS TRACKING CHANGES IN COMPONENTS

Slide 52

Slide 52 text

COMPUTED PROPERTIES class XCustom extends Polymer.Element { static get properties() { return { first: String, last: String, fullName: { type: String, computed: 'computeFullName(first, last)' } } } } My name is {{fullName}} 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')}`; }) });

Slide 53

Slide 53 text

COMPUTED PROPERTIES class XCustom extends Polymer.Element { static get properties() { return { first: String, last: String, fullName: { type: String, computed: 'computeFullName(first, last)' } } } } My name is {{fullName}} 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')}`; }) });

Slide 54

Slide 54 text

COMPUTED PROPERTIES class XCustom extends Polymer.Element { static get properties() { return { first: String, last: String, fullName: { type: String, computed: 'computeFullName(first, last)' } } } } My name is {{fullName}} 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 {{fullName}}

Slide 55

Slide 55 text

COMPUTED PROPERTIES class XCustom extends Polymer.Element { static get properties() { return { first: String, last: String, fullName: { type: String, computed: 'computeFullName(first, last)' } } } } My name is {{fullName}} 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 {{fullName}}

Slide 56

Slide 56 text

COMPUTED PROPERTIES class XCustom extends Polymer.Element { static get properties() { return { first: String, last: String, fullName: { type: String, computed: 'computeFullName(first, last)' } } } } My name is {{fullName}} 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 {{fullName}} Com puted Properties in Em berJS

Slide 57

Slide 57 text

COMPUTED PROPERTIES class XCustom extends Polymer.Element { static get properties() { return { first: String, last: String, fullName: { type: String, computed: 'computeFullName(first, last)' } } } } My name is {{fullName}} 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 {{fullName}} Com puted Properties in Em berJS

Slide 58

Slide 58 text

GLIMMER = IMMUTABLE PROPERTIES PER DEFAULT

Slide 59

Slide 59 text

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; //... }

Slide 60

Slide 60 text

// 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

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

// 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); } //… }
E: N:
RERENDER ON USER INTERACTION

Slide 64

Slide 64 text

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); } //… }

Slide 65

Slide 65 text

IMPORTING IT INTO EXISTING APP ember build --environment=production

Slide 66

Slide 66 text

IMPORTING IT INTO EXISTING APP My Other App

Slide 67

Slide 67 text

https://goo.gl/TY4o9t

Slide 68

Slide 68 text

THE MEANING OF GLIMMER.JS WEB COMPONENTS VS. COMPLEX APPLICATIONS

Slide 69

Slide 69 text

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

Slide 70

Slide 70 text

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

Slide 71

Slide 71 text

Jessica Jordan @jjordan_dev EXCITING TIMES FOR CREATING APPS AND WEB COMPONENTS

Slide 72

Slide 72 text

THANK YOU Jessica Jordan @jjordan_dev