From d65c0345628674286ea84a8a3a11b167519ccbe1 Mon Sep 17 00:00:00 2001 From: Kate Jeffreys Date: Thu, 3 Jan 2019 13:01:42 -0800 Subject: [PATCH 1/3] Draft skeleton --- docs/_data/guide.yml | 3 + docs/_guide/events.md | 281 ++++++++++++++++++ .../projects/events/events/index.html | 17 ++ .../_includes/projects/events/events/index.ts | 1 + .../projects/events/events/manifest.json | 14 + .../projects/events/events/my-element.js | 32 ++ 6 files changed, 348 insertions(+) create mode 100644 docs/_guide/events.md create mode 100644 docs/_includes/projects/events/events/index.html create mode 100644 docs/_includes/projects/events/events/index.ts create mode 100644 docs/_includes/projects/events/events/manifest.json create mode 100644 docs/_includes/projects/events/events/my-element.js diff --git a/docs/_data/guide.yml b/docs/_data/guide.yml index 9068b089..90669f36 100644 --- a/docs/_data/guide.yml +++ b/docs/_data/guide.yml @@ -18,6 +18,9 @@ toc: title: Properties desc: Declare and configure a component's properties and attributes. url: /guide/properties + events: + title: Events + url: /guide/events lifecycle: title: Lifecycle desc: Specify when an element should update. Respond to updates, or wait for an update to complete. diff --git a/docs/_guide/events.md b/docs/_guide/events.md new file mode 100644 index 00000000..e9f3b648 --- /dev/null +++ b/docs/_guide/events.md @@ -0,0 +1,281 @@ +--- +layout: guide +title: Events +slug: events +--- + +TODO: Tidy up this page + +{::options toc_levels="1..3" /} +* ToC +{:toc} + +## Overview + +* Default event context is `this` - no need for `this.handlerMethod.bind(this)` +* You can use `this` referring to the host inside an event handler +* Add event listeners in a method that is guaranteed to fire before the event occurs +* For optimal loading performance, add your event listener as late as possible + +## Add an event listener to a LitElement host + +You can use the `firstUpdated` callback to add an event listener after the element has first been rendered: + +```js +class MyElement extends LitElement { + firstUpdated(changedProperties) { + this.addEventListener('click', (e) => { ... }); +}) +``` + +```js +class MyElement extends LitElement { + firstUpdated(changedProperties) { + this.addEventListener('click', this.clickHandler); + } + clickHandler(e) { ... } +) +``` + +If your event might occur before the element has finished rendering, you can add the listener from the constructor: + +```js +class MyElement extends LitElement { + constructor() { + super.constructor(); + this.addEventListener('some-event', (e) => { ... }); +}) +``` + +```js +class MyElement extends LitElement { + constructor() { + super.constructor(); + this.addEventListener('some-event', this.someEventHandler); + } + someEventHandler(e) { ... } +) +``` + +## Add an event listener to the child element of a LitElement host + +**Option 1: Use lit-html template syntax** + +You can add an event listener to an element's DOM child using lit-html data binding syntax: + +```js +render() { + return html``; +} +handleClick(e) { + // ... +} +``` + +See the documentation on [Templates](templates) for more information on lit-html template syntax. + +**Option 2: Add the listener imperatively** + +To add an event listener to a child element imperatively, you must do so in or after `firstUpdated` to ensure that the child element exists. For example: + +```js +render() { + return html``; +} + +firstUpdated(changedProperties) { + let el = this.shadowRoot.getElementById('mybutton'); + el.addEventListener('click', this.handleClick); +} +... +handleClick(e) { + ... +} +``` + +### Bubbling, custom events, composed path, etc + +**Event bubbling** + +```js +//????? +constructor() { + this.super(); + this.addEventListener('click', (e) => { console.log(e.target)}); +} +render() { + return html``; +} +handleClick(e) { + console.log(e.target.id); +} +``` + +**Event retargeting** + +```js +render() { + return html``; +} +handleClick(e) { + console.log(e.target.id); +} +``` + +**Composed path** + +```js +render() { + return html``; +} +handleClick(e) { + let origin = e.composedPath()[0]; + console.log(origin.id); +} +``` + +By default, custom events stop at shadow DOM boundaries. To make a custom event pass through shadow DOM boundaries, set the composed flag to true when you create the event: + + +**Composed path** + +```js +constructor() { + this.super(); + this.addEventListener('my-event', (e) => { console.log(e.composedPath())}); +} +render() { + return html``; +} +doThing() { + let myEvent = new CustomEvent('my-event', { bubbles: true, composed: true }); + this.dispatchEvent(myEvent); +} +``` + +## Add an event listener to anything else + +When you're adding an event listener to the host element or its children, you don't need to worry about memory leaks. The memory allocated to the event listener will be destroyed when the host element is destroyed. + +However, when adding an event listener to something other than the host element or its children, you should add the listener in `connectedCallback` and remove it in `disconnectedCallback`: + +```js +connectedCallback() { + super.connectedCallback(); + window.addEventListener('hashchange', this.hashChangeListener); +} + +disconnectedCallback() { + super.disconnectedCallback(); + window.removeEventListener('hashchange', this.hashChangeListener); +} +``` + +### + +Scenario 1 + +MyElement constructor is called; ChildThing constructor is called; event listener is added to ChildThing +Stuff happens, event listener fires, memory is allocated in its scope +MyElement is destroyed; ChildThing is destroyed, and with it, the scope of the event listener; memory is freed up, all is well +Scenario 2 + +MyElement constructor is called; event listener is added to Window +Stuff happens, event listener fires, memory is allocated in its scope +MyElement is destroyed +In Scenario 2, I am not sure when or how the memory in the scope created by the event listener on Window would get released. My understanding is that garbage collection works differently in different browsers and this scenario might not be good for memory management. + +Scenario 2.A + +MyElement's connectedCallback fires, event listener is added to Window +Stuff happens, event listener fires, memory is allocated in its scope +disconnectedCallback fires, event listener is removed from Window, memory is freed up and all is well +The recommendation to use disconnectedCallback to remove event listeners added to things that aren't MyElement or its children seems clear in this scenario. As for why you would add it in connectedCallback instead of the constructor in this scenario, I can't say for sure, but it does seem nice and clean to do stuff in connectedCallback if you're going to undo it in disconnectedCallback. Maybe a scenario like the following would make it more relevant: + +Scenario 3 + +MyElement is appended to ParentThing, MyElement's connectedCallback fires, event listener is added to ParentThing +Stuff happens, event listener fires, memory is allocated in its scope +MyElement is moved to a new position in DOM, disconnectedCallback fires, event listener is removed from ParentThing +MyElement is appended to OtherThing, connectedCallback fires again, etc + + +### A heading3 + +```js +some code +``` + +Something: + +* A list item +* A list item + +## Heading 2 + +```js +code +``` + +```js +code +``` + + +**Example: Add an event listener to a child element** + +```js +{% include projects/events/events/my-element.js %} +``` + +{% include project.html folder="events/events" openFile="my-element.js" %} + +## Add an event listener to some other DOM element + + + + +## Best practices + +From which method should you add an event listener? + +* Add your event listener before the event can occur. +* You can +* You must add your + +### How can I ensure that the memory allocated to the event listener is cleaned up? + + +A custom element can add an event listener to itself or its own children from its constructor without any issues + +Adding the listener in the constructor ensures that you will catch events that could possibly occur before the custom element has been added to DOM +If the custom element adds an event listener to anything except itself or its children (e.g. window), you should add the listener in connectedCallback and remove it in disconnectedCallback + + + + + + +{:.alert .alert-warning} +
+ +**Something.** Something something. + +```js +code. +``` + +Something. + +
+ + + + + +{:.alert .alert-warning} +
+ +**Remember to something.** Something something. + +
\ No newline at end of file diff --git a/docs/_includes/projects/events/events/index.html b/docs/_includes/projects/events/events/index.html new file mode 100644 index 00000000..205b902b --- /dev/null +++ b/docs/_includes/projects/events/events/index.html @@ -0,0 +1,17 @@ + + + + + + + + + lit-element code sample + + + + + + + + diff --git a/docs/_includes/projects/events/events/index.ts b/docs/_includes/projects/events/events/index.ts new file mode 100644 index 00000000..735d0c5f --- /dev/null +++ b/docs/_includes/projects/events/events/index.ts @@ -0,0 +1 @@ +import './my-element.js'; diff --git a/docs/_includes/projects/events/events/manifest.json b/docs/_includes/projects/events/events/manifest.json new file mode 100644 index 00000000..759a8a50 --- /dev/null +++ b/docs/_includes/projects/events/events/manifest.json @@ -0,0 +1,14 @@ +{ + "title": "lit-element code sample", + "description": "lit-element code sample", + "files": [ + "my-element.js", + "index.html", + "index.ts" + ], + "dependencies": { + "@polymer/lit-element": "latest", + "@webcomponents/webcomponentsjs": "latest" + }, + "template": "typescript" +} diff --git a/docs/_includes/projects/events/events/my-element.js b/docs/_includes/projects/events/events/my-element.js new file mode 100644 index 00000000..3170425c --- /dev/null +++ b/docs/_includes/projects/events/events/my-element.js @@ -0,0 +1,32 @@ +import { LitElement, html } from '@polymer/lit-element'; + +class MyElement extends LitElement { + static get properties() { return { + prop1: { type: String }, + prop2: { type: Number }, + prop3: { type: Boolean }, + prop4: { type: Array }, + prop5: { type: Object } + };} + + constructor() { + super(); + this.prop1 = 'Hello World'; + this.prop2 = 5; + this.prop3 = false; + this.prop4 = [1,2,3]; + this.prop5 = { subprop1: 'prop 5 subprop1 value' } + } + + render() { + return html` +

prop1: ${this.prop1}

+

prop2: ${this.prop2}

+

prop3: ${this.prop3}

+

prop4[0]:

${this.prop4[0]}

+

prop5.subprop1: ${this.prop5.subprop1}

+ `; + } +} + +customElements.define('my-element', MyElement); From 67092d3ccdae32fc1543f348301c3642c5c91a7a Mon Sep 17 00:00:00 2001 From: Kate Jeffreys Date: Thu, 3 Jan 2019 13:24:27 -0800 Subject: [PATCH 2/3] Flesh out events docs --- docs/_guide/events.md | 202 +++++------------- .../projects/events/child/index.html | 17 ++ docs/_includes/projects/events/child/index.ts | 1 + .../projects/events/child/manifest.json | 14 ++ .../projects/events/child/my-element.js | 14 ++ .../events/childimperative/index.html | 17 ++ .../projects/events/childimperative/index.ts | 1 + .../events/childimperative/manifest.json | 14 ++ .../events/childimperative/my-element.js | 19 ++ .../_includes/projects/events/host/index.html | 17 ++ docs/_includes/projects/events/host/index.ts | 1 + .../projects/events/host/manifest.json | 14 ++ .../projects/events/host/my-element.js | 17 ++ 13 files changed, 202 insertions(+), 146 deletions(-) create mode 100644 docs/_includes/projects/events/child/index.html create mode 100644 docs/_includes/projects/events/child/index.ts create mode 100644 docs/_includes/projects/events/child/manifest.json create mode 100644 docs/_includes/projects/events/child/my-element.js create mode 100644 docs/_includes/projects/events/childimperative/index.html create mode 100644 docs/_includes/projects/events/childimperative/index.ts create mode 100644 docs/_includes/projects/events/childimperative/manifest.json create mode 100644 docs/_includes/projects/events/childimperative/my-element.js create mode 100644 docs/_includes/projects/events/host/index.html create mode 100644 docs/_includes/projects/events/host/index.ts create mode 100644 docs/_includes/projects/events/host/manifest.json create mode 100644 docs/_includes/projects/events/host/my-element.js diff --git a/docs/_guide/events.md b/docs/_guide/events.md index e9f3b648..ffd7c6a1 100644 --- a/docs/_guide/events.md +++ b/docs/_guide/events.md @@ -22,40 +22,12 @@ TODO: Tidy up this page You can use the `firstUpdated` callback to add an event listener after the element has first been rendered: ```js -class MyElement extends LitElement { - firstUpdated(changedProperties) { - this.addEventListener('click', (e) => { ... }); -}) +{% include projects/events/host/my-element.js %} ``` -```js -class MyElement extends LitElement { - firstUpdated(changedProperties) { - this.addEventListener('click', this.clickHandler); - } - clickHandler(e) { ... } -) -``` +{% include project.html folder="events/host" openFile="my-element.js" %} -If your event might occur before the element has finished rendering, you can add the listener from the constructor: - -```js -class MyElement extends LitElement { - constructor() { - super.constructor(); - this.addEventListener('some-event', (e) => { ... }); -}) -``` - -```js -class MyElement extends LitElement { - constructor() { - super.constructor(); - this.addEventListener('some-event', this.someEventHandler); - } - someEventHandler(e) { ... } -) -``` +If your event might occur before the element has finished rendering, you can add the listener from the constructor. ## Add an event listener to the child element of a LitElement host @@ -64,14 +36,11 @@ class MyElement extends LitElement { You can add an event listener to an element's DOM child using lit-html data binding syntax: ```js -render() { - return html``; -} -handleClick(e) { - // ... -} +{% include projects/events/child/my-element.js %} ``` +{% include project.html folder="events/child" openFile="my-element.js" %} + See the documentation on [Templates](templates) for more information on lit-html template syntax. **Option 2: Add the listener imperatively** @@ -79,21 +48,59 @@ See the documentation on [Templates](templates) for more information on lit-html To add an event listener to a child element imperatively, you must do so in or after `firstUpdated` to ensure that the child element exists. For example: ```js -render() { - return html``; -} +{% include projects/events/childimperative/my-element.js %} +``` + +{% include project.html folder="events/childimperative" openFile="my-element.js" %} -firstUpdated(changedProperties) { - let el = this.shadowRoot.getElementById('mybutton'); - el.addEventListener('click', this.handleClick); +## Add an event listener to anything else + +When you're adding an event listener to the host element or its children, you don't need to worry about memory leaks. The memory allocated to the event listener will be destroyed when the host element is destroyed. + +However, when adding an event listener to something other than the host element or its children, you should add the listener in `connectedCallback` and remove it in `disconnectedCallback`: + +```js +connectedCallback() { + super.connectedCallback(); + window.addEventListener('hashchange', this.hashChangeListener); } -... -handleClick(e) { - ... + +disconnectedCallback() { + super.disconnectedCallback(); + window.removeEventListener('hashchange', this.hashChangeListener); } ``` -### Bubbling, custom events, composed path, etc +### Why + +Because garbage collection. + +**Bad** + +1. MyElement creates an event listener on Window +2. Event listener fires, memory is allocated in its scope +3. MyElement is destroyed + +However, the memory allocated to the event listener is still in use. To avoid this: + +**Good** + +1. MyElement adds event listener to Window +2. Event listener fires, memory is allocated in its scope +3. MyElement's disconnectedCallback fires, event listener is removed from Window + +Memory is freed up and all is well. + +**Also Good** + +If the element can move around the DOM during its lifecycle, you may need to add the event listener in `connectedCallback`: + +1. MyElement is appended to ParentElement, MyElement's connectedCallback fires, event listener is added to ParentElement +2. Event listener fires, memory is allocated in its scope +3. MyElement is moved to a new position in DOM, disconnectedCallback fires, event listener is removed from ParentElement, memory is freed +4. MyElement is appended to OtherElement, connectedCallback fires again, etc + +## Bubbling, custom events, composed path, etc **Event bubbling** @@ -153,104 +160,6 @@ doThing() { } ``` -## Add an event listener to anything else - -When you're adding an event listener to the host element or its children, you don't need to worry about memory leaks. The memory allocated to the event listener will be destroyed when the host element is destroyed. - -However, when adding an event listener to something other than the host element or its children, you should add the listener in `connectedCallback` and remove it in `disconnectedCallback`: - -```js -connectedCallback() { - super.connectedCallback(); - window.addEventListener('hashchange', this.hashChangeListener); -} - -disconnectedCallback() { - super.disconnectedCallback(); - window.removeEventListener('hashchange', this.hashChangeListener); -} -``` - -### - -Scenario 1 - -MyElement constructor is called; ChildThing constructor is called; event listener is added to ChildThing -Stuff happens, event listener fires, memory is allocated in its scope -MyElement is destroyed; ChildThing is destroyed, and with it, the scope of the event listener; memory is freed up, all is well -Scenario 2 - -MyElement constructor is called; event listener is added to Window -Stuff happens, event listener fires, memory is allocated in its scope -MyElement is destroyed -In Scenario 2, I am not sure when or how the memory in the scope created by the event listener on Window would get released. My understanding is that garbage collection works differently in different browsers and this scenario might not be good for memory management. - -Scenario 2.A - -MyElement's connectedCallback fires, event listener is added to Window -Stuff happens, event listener fires, memory is allocated in its scope -disconnectedCallback fires, event listener is removed from Window, memory is freed up and all is well -The recommendation to use disconnectedCallback to remove event listeners added to things that aren't MyElement or its children seems clear in this scenario. As for why you would add it in connectedCallback instead of the constructor in this scenario, I can't say for sure, but it does seem nice and clean to do stuff in connectedCallback if you're going to undo it in disconnectedCallback. Maybe a scenario like the following would make it more relevant: - -Scenario 3 - -MyElement is appended to ParentThing, MyElement's connectedCallback fires, event listener is added to ParentThing -Stuff happens, event listener fires, memory is allocated in its scope -MyElement is moved to a new position in DOM, disconnectedCallback fires, event listener is removed from ParentThing -MyElement is appended to OtherThing, connectedCallback fires again, etc - - -### A heading3 - -```js -some code -``` - -Something: - -* A list item -* A list item - -## Heading 2 - -```js -code -``` - -```js -code -``` - - -**Example: Add an event listener to a child element** - -```js -{% include projects/events/events/my-element.js %} -``` - -{% include project.html folder="events/events" openFile="my-element.js" %} - -## Add an event listener to some other DOM element - - - - -## Best practices - -From which method should you add an event listener? - -* Add your event listener before the event can occur. -* You can -* You must add your - -### How can I ensure that the memory allocated to the event listener is cleaned up? - - -A custom element can add an event listener to itself or its own children from its constructor without any issues - -Adding the listener in the constructor ensures that you will catch events that could possibly occur before the custom element has been added to DOM -If the custom element adds an event listener to anything except itself or its children (e.g. window), you should add the listener in connectedCallback and remove it in disconnectedCallback - @@ -278,4 +187,5 @@ Something. **Remember to something.** Something something. - \ No newline at end of file + + diff --git a/docs/_includes/projects/events/child/index.html b/docs/_includes/projects/events/child/index.html new file mode 100644 index 00000000..205b902b --- /dev/null +++ b/docs/_includes/projects/events/child/index.html @@ -0,0 +1,17 @@ + + + + + + + + + lit-element code sample + + + + + + + + diff --git a/docs/_includes/projects/events/child/index.ts b/docs/_includes/projects/events/child/index.ts new file mode 100644 index 00000000..735d0c5f --- /dev/null +++ b/docs/_includes/projects/events/child/index.ts @@ -0,0 +1 @@ +import './my-element.js'; diff --git a/docs/_includes/projects/events/child/manifest.json b/docs/_includes/projects/events/child/manifest.json new file mode 100644 index 00000000..759a8a50 --- /dev/null +++ b/docs/_includes/projects/events/child/manifest.json @@ -0,0 +1,14 @@ +{ + "title": "lit-element code sample", + "description": "lit-element code sample", + "files": [ + "my-element.js", + "index.html", + "index.ts" + ], + "dependencies": { + "@polymer/lit-element": "latest", + "@webcomponents/webcomponentsjs": "latest" + }, + "template": "typescript" +} diff --git a/docs/_includes/projects/events/child/my-element.js b/docs/_includes/projects/events/child/my-element.js new file mode 100644 index 00000000..66893134 --- /dev/null +++ b/docs/_includes/projects/events/child/my-element.js @@ -0,0 +1,14 @@ +import { LitElement, html } from '@polymer/lit-element'; + +class MyElement extends LitElement { + render() { + return html` + + `; + } + doThing(e) { + console.log(e); + } +} + +customElements.define('my-element', MyElement); diff --git a/docs/_includes/projects/events/childimperative/index.html b/docs/_includes/projects/events/childimperative/index.html new file mode 100644 index 00000000..205b902b --- /dev/null +++ b/docs/_includes/projects/events/childimperative/index.html @@ -0,0 +1,17 @@ + + + + + + + + + lit-element code sample + + + + + + + + diff --git a/docs/_includes/projects/events/childimperative/index.ts b/docs/_includes/projects/events/childimperative/index.ts new file mode 100644 index 00000000..735d0c5f --- /dev/null +++ b/docs/_includes/projects/events/childimperative/index.ts @@ -0,0 +1 @@ +import './my-element.js'; diff --git a/docs/_includes/projects/events/childimperative/manifest.json b/docs/_includes/projects/events/childimperative/manifest.json new file mode 100644 index 00000000..759a8a50 --- /dev/null +++ b/docs/_includes/projects/events/childimperative/manifest.json @@ -0,0 +1,14 @@ +{ + "title": "lit-element code sample", + "description": "lit-element code sample", + "files": [ + "my-element.js", + "index.html", + "index.ts" + ], + "dependencies": { + "@polymer/lit-element": "latest", + "@webcomponents/webcomponentsjs": "latest" + }, + "template": "typescript" +} diff --git a/docs/_includes/projects/events/childimperative/my-element.js b/docs/_includes/projects/events/childimperative/my-element.js new file mode 100644 index 00000000..3fe4d293 --- /dev/null +++ b/docs/_includes/projects/events/childimperative/my-element.js @@ -0,0 +1,19 @@ +import { LitElement, html } from '@polymer/lit-element'; + +class MyElement extends LitElement { + + firstUpdated() { + let button = this.shadowRoot.getElementById('mybutton'); + button.addEventListener('click', this.doThing); + } + render() { + return html` + + `; + } + doThing(e) { + console.log(e); + } +} + +customElements.define('my-element', MyElement); diff --git a/docs/_includes/projects/events/host/index.html b/docs/_includes/projects/events/host/index.html new file mode 100644 index 00000000..205b902b --- /dev/null +++ b/docs/_includes/projects/events/host/index.html @@ -0,0 +1,17 @@ + + + + + + + + + lit-element code sample + + + + + + + + diff --git a/docs/_includes/projects/events/host/index.ts b/docs/_includes/projects/events/host/index.ts new file mode 100644 index 00000000..735d0c5f --- /dev/null +++ b/docs/_includes/projects/events/host/index.ts @@ -0,0 +1 @@ +import './my-element.js'; diff --git a/docs/_includes/projects/events/host/manifest.json b/docs/_includes/projects/events/host/manifest.json new file mode 100644 index 00000000..759a8a50 --- /dev/null +++ b/docs/_includes/projects/events/host/manifest.json @@ -0,0 +1,14 @@ +{ + "title": "lit-element code sample", + "description": "lit-element code sample", + "files": [ + "my-element.js", + "index.html", + "index.ts" + ], + "dependencies": { + "@polymer/lit-element": "latest", + "@webcomponents/webcomponentsjs": "latest" + }, + "template": "typescript" +} diff --git a/docs/_includes/projects/events/host/my-element.js b/docs/_includes/projects/events/host/my-element.js new file mode 100644 index 00000000..026c8203 --- /dev/null +++ b/docs/_includes/projects/events/host/my-element.js @@ -0,0 +1,17 @@ +import { LitElement, html } from '@polymer/lit-element'; + +class MyElement extends LitElement { + firstUpdated() { + this.addEventListener('click', this.clickHandler); + } + clickHandler(e) { + console.log(e.target); + } + render() { + return html` +

Click me

+ `; + } +} + +customElements.define('my-element', MyElement); From 9b1c13a0bd9e8b83286599a46bbd831cd66b2939 Mon Sep 17 00:00:00 2001 From: Kate Jeffreys Date: Mon, 4 Feb 2019 15:03:30 -0800 Subject: [PATCH 3/3] Heavy edits to events docs --- docs/_guide/events.md | 262 +++++++++++++++++++++++------------------- 1 file changed, 145 insertions(+), 117 deletions(-) diff --git a/docs/_guide/events.md b/docs/_guide/events.md index ffd7c6a1..2df398d5 100644 --- a/docs/_guide/events.md +++ b/docs/_guide/events.md @@ -4,188 +4,216 @@ title: Events slug: events --- -TODO: Tidy up this page - {::options toc_levels="1..3" /} * ToC {:toc} ## Overview -* Default event context is `this` - no need for `this.handlerMethod.bind(this)` -* You can use `this` referring to the host inside an event handler -* Add event listeners in a method that is guaranteed to fire before the event occurs -* For optimal loading performance, add your event listener as late as possible - -## Add an event listener to a LitElement host +### Where to add your event listeners -You can use the `firstUpdated` callback to add an event listener after the element has first been rendered: +You need to add event listeners in a method that is guaranteed to fire before the event occurs. However, for optimal loading performance, you should add your event listener as late as possible. -```js -{% include projects/events/host/my-element.js %} -``` +You can add event listeners: -{% include project.html folder="events/host" openFile="my-element.js" %} +* **Via your component's template.** -If your event might occur before the element has finished rendering, you can add the listener from the constructor. + You can use lit-html `@event` bindings in your template inside the `render` function to add event listeners to your component. -## Add an event listener to the child element of a LitElement host + **Example** -**Option 1: Use lit-html template syntax** - -You can add an event listener to an element's DOM child using lit-html data binding syntax: - -```js -{% include projects/events/child/my-element.js %} -``` + ```js + render() { + return html``; + } + handleClick(e) { + console.log(this.prop); + } +} +``` -1. MyElement is appended to ParentElement, MyElement's connectedCallback fires, event listener is added to ParentElement -2. Event listener fires, memory is allocated in its scope -3. MyElement is moved to a new position in DOM, disconnectedCallback fires, event listener is removed from ParentElement, memory is freed -4. MyElement is appended to OtherElement, connectedCallback fires again, etc +See the [documentation for `this` on MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this) for more information. -## Bubbling, custom events, composed path, etc +### Use cases -**Event bubbling** +* [Fire a custom event from a LitElement-based component](#fire-custom-event). +* [Handle a custom event fired by a LitElement-based component](#handle-custom-event). +* [Handle an event fired by a shadow DOM child of your component](#handle-shadow-dom-event). +* [Add event listeners imperatively](#imperative). -```js -//????? -constructor() { - this.super(); - this.addEventListener('click', (e) => { console.log(e.target)}); -} -render() { - return html``; -} -handleClick(e) { - console.log(e.target.id); -} -``` +### Fire an event from a LitElement-based component {#fire-event} -**Event retargeting** +Fire a custom event: ```js -render() { - return html``; -} -handleClick(e) { - console.log(e.target.id); +class MyElement extends LitElement { + render() { + return html`
Hello World
`; + } + firstUpdated(changedProperties) { + let event = new CustomEvent('my-event', { + detail: { + message: 'Something important happened' + } + }); + this.dispatchEvent(event); + } } ``` -**Composed path** +Fire a standard event: ```js -render() { - return html``; -} -handleClick(e) { - let origin = e.composedPath()[0]; - console.log(origin.id); +class MyElement extends LitElement { + render() { + return html`
Hello World
`; + } + updated(changedProperties) { + let click = new Event('click'); + this.dispatchEvent(click); + } } ``` -By default, custom events stop at shadow DOM boundaries. To make a custom event pass through shadow DOM boundaries, set the composed flag to true when you create the event: +### Handle an event fired by a LitElement-based component {#handle-fired-event} +Handle events fired by a LitElement-based component the same way you would handle any event fired from a standard DOM element. -**Composed path** +To handle an event fired by a LitElement-based component that you're using on any HTML page: -```js -constructor() { - this.super(); - this.addEventListener('my-event', (e) => { console.log(e.composedPath())}); -} -render() { - return html``; -} -doThing() { - let myEvent = new CustomEvent('my-event', { bubbles: true, composed: true }); - this.dispatchEvent(myEvent); -} +```html + + ``` +To handle a custom event fired by a LitElement-based component from inside another LitElement template: +```html + +``` +## Working with events and shadow DOM +When working with events and shadow DOM, there are a few things you need to know about. +### Event bubbling -{:.alert .alert-warning} -
+Some events bubble up through the DOM tree, so that they are detectable by any element on the page. -**Something.** Something something. +Whether or not an event bubbles depends on the value of its `bubbles` property. To check if a particular event bubbles: ```js -code. +handleEvent(e){ + console.log(e.bubbles); +} ``` -Something. +See the MDN documentation on the [Event interface](https://developer.mozilla.org/en-US/docs/Web/API/Event) for more information. -
+### Event retargeting +Bubbling events fired from within shadow DOM are retargeted so that, to any listener external to your component, they appear to come from your component itself. +**Example** +```html + +``` +```js +render() { + return html` + `; +} +``` + +When handling such an event, you can find where it originated from with `composedPath`: -{:.alert .alert-warning} -
+```js +handleMyEvent(event) { + console.log('Origin: ', event.composedPath()[0]); +} +``` -**Remember to something.** Something something. +### Custom events -
+By default, a bubbling custom event fired inside shadow DOM will stop bubbling when it reaches the shadow root. + +To make a custom event pass through shadow DOM boundaries, you must set both the `composed` and `bubbles` flags to `true`: + +```js +firstUpdated(changedProperties) { + let myEvent = new CustomEvent('my-event', { + detail: { message: 'my-event happened.' }, + bubbles: true, + composed: true }); + this.dispatchEvent(myEvent); +} +``` +See the [MDN documentation on custom events](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent) for more information.