Skip to content

Commit

Permalink
feat: unify event handling
Browse files Browse the repository at this point in the history
  • Loading branch information
renet committed Jan 25, 2022
1 parent 162f48f commit 59b4e28
Show file tree
Hide file tree
Showing 28 changed files with 730 additions and 478 deletions.
4 changes: 2 additions & 2 deletions src/docs/pages/design-tokens.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
eleventyNavigation:
key: Design tokens
parent: Introduction
order: 9
order: 11
layout: layout.njk
title: Design tokens
permalink: introduction/design-tokens/
Expand Down Expand Up @@ -41,4 +41,4 @@ As you can see, the command is invoked with `npx`. It uses an environment variab
<docs-page-nav prev-href="introduction/tailwindcss-integration/" next-title="Sandbox applications" next-href="introduction/sandbox-applications/"></docs-page-nav>

[liquid tokens on figma]: https://www.figma.com/file/JcDMeUwec9e185HfBgT9XE/Liquid-Oxygen?node-id=2615%3A28396
[liquid tokens on figma]: https://www.figma.com/file/JcDMeUwec9e185HfBgT9XE/Liquid-Oxygen?node-id=2615%3A28396
52 changes: 52 additions & 0 deletions src/docs/pages/event-handling.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
---
eleventyNavigation:
key: Event handling
parent: Introduction
order: 7
layout: layout.njk
title: Event handling
permalink: introduction/event-handling/
---


# Event handling

Liquid Oxygen aims to stick to the browser standards where possible. That is why we try to use as few custom events as possible. This is especially the case for components that are mainly meant to just "skin" native HTML elements with a Liquid Oxygen look, like `ld-input`. But Web Components are a bit tricky here: There are many events, especially the ones that are the result of a direct user interaction, like `click`, `focusin/focusout` that bubble just fine from inside a Web Component's shadow DOM into the light DOM. But others, like the `change` event, don't. The difference between those events is that the former are defined as [composed events](https://developer.mozilla.org/en-US/docs/Web/API/Event/composed), while the latter are not.

We try to dispatch non-composed events by manually creating them on the Web Component's host elements, but they do not behave 1:1 like their native equivalents. This results in unexpected behavior, for example React "swallowing" `change` events that are created in such a way and event handlers set with the `onChange` prop not being invoked.

Because of that, Liquid Oxygen components dispatch custom events that are named like the native events they are meant to replace, but prefixed with "ld". So form components like `ld-input`, for example, dispatch an `ldchange` event. Which custom events are available is documented in the "Events" section of each component's documentation page.

## Examples

These examples show how to use the custom "ld"-prefixed events with Vanilla JS and React.

### Vanilla JS

```html
<ld-input id="example" />

<script>
const handleLdchange = () => {
console.log('changed!')
}
document.getElementById("example").addEventListener('ldchange', handleLdchange)
</script>
```

### React

```jsx
const MyApp = () => {
const handleLdchange = React.useCallback(() => {
console.log('changed!')
}, [])

return <ld-input onLdchange={handleLdchange} />
}
```

> **Note:** React usually triggers event handlers set with the `onChange` prop differently than browsers actually handle `change` events. Event handlers set with the `onChange` prop usually are invoked everytime the element's value changes in React, while by definition the `change` event is only dispatched after the element loses focus.<br/><br/>Liquid Oxygen components stick to that browser default behavior and only dispatch the `ldchange`/`change` events after an element loses focus. Thus, you cannot expect an event handler set neither via `onLdchange` nor `onChange` to be invoked everytime the value changes. If you want that, the `input` event is exactly what you're looking for, so please use the `onInput` prop in these cases, instead. (There is no custom `ldinput` event, as the `input` event is a composed native event that bubbles into the light DOM.)
<docs-page-nav prev-href="introduction/server-side-rendering/" next-title="Form validation" next-href="introduction/form-validation/"></docs-page-nav>
4 changes: 2 additions & 2 deletions src/docs/pages/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
eleventyNavigation:
key: FAQ
parent: Introduction
order: 11
order: 13
layout: layout.njk
title: FAQ
permalink: introduction/faq/
Expand Down Expand Up @@ -46,4 +46,4 @@ Yes, Web Components and CSS Components are both standards conform and can be use

Absolutely! Liquid Oxygen is not only open for contributions and feedback but also prioritizes its backlog according to the input from the community. If you feel you need a specific component badly, you can either contribute to the project directly or create a feature request. If you'd like to get involved, check out our [contributing docs](https://github.com/emdgroup-liquid/liquid/blob/develop/CONTRIBUTING.md).

<docs-page-nav prev-href="introduction/sandbox-applications/"></docs-page-nav>
<docs-page-nav prev-href="introduction/sandbox-applications/"></docs-page-nav>
226 changes: 226 additions & 0 deletions src/docs/pages/form-validation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
---
eleventyNavigation:
key: Form validation
parent: Introduction
order: 8
layout: layout.njk
title: Form validation
permalink: introduction/form-validation/
---


# Form validation

Liquid Oxygen aims to make integrating form components with forms and form validation libraries as easy as possible. There are mainly three possible ways of accessing the values of form-related components:

- Integrating the Web Components with an associated `<form>` element and accessing their values using the `FormData` of the form
- Directly accessing the props of a Web Component
- Using the `detail` attribute of a custom event dispatched by the Web Component

## Form association

There are two ways of associating a form-related Web Component with an existing `<form>` element:

```js
// Using the form prop
<form id="registrationForm" />
<ld-input form="registrationForm" name="firstName" />

// or wrapping it with a form element
<form>
<ld-input name="firstName" />
</form>
```

> To make the Web Component accessible via the `FormData` of the `<form>` element, the `name` prop of the component has to be set.
If a form-related component is associated with a form in one of the above ways, the component's current value can be accessed using the form itself like it would be the case for a native `<input>` element.

### Example

{% example '{ "opened": true }' %}
<form name="registrationForm1" style="display:flex;align-items:center">
<ld-label>
First Name*
<ld-input name="firstName" placeholder="Jane Doe" required></ld-input>
<ld-input-message style="visibility:hidden">This field is required.</ld-input-message>
</ld-label>
<ld-button style="margin:-0.3rem 0 0 1rem">Submit</ld-button>
</form>

<script>
const form = document.registrationForm1
const inputField1 = form.querySelector('ld-input')
const inputMessage1 = form.querySelector('ld-input-message')

function validateInput1() {
const formData = new FormData(form)

if (!formData.get('firstName')) {
inputField1.invalid = true
inputMessage1.style.visibility = 'inherit'
return false
}

inputField1.invalid = false
inputMessage1.style.visibility = 'hidden'
return true
}

inputField1.addEventListener('input', validateInput1)

inputField1.addEventListener('blur', validateInput1)

function getValues() {
const formData = new FormData(form)
let values = '';

formData.forEach((value, key) => {
values += `
${key}: ${value}`
})

return values
}

form.addEventListener('submit', event => {
event.preventDefault()
const isValid = validateInput1()
const values = getValues()

// setTimeout is used in order to let the style update before showing the alert.
// You can probably dismiss the timeout, if you're using a UI framework like React or Vue.
setTimeout(() => {
if (isValid) {
window.alert(`Form submitted. FormData:${values}`)
} else {
window.alert('Form is invalid.')
}
}, 100)
})
</script>
{% endexample %}

## Accessing props

Accessing props works just as it would with native input elements. The props, which allow you to access component-specific values, are documented in each component's documentation page.

### Example

{% example '{ "opened": true }' %}
<div style="display:flex;align-items:center">
<ld-label>
Gender*
<ld-select id="gender2" name="gender" required>
<ld-option value="m">male</ld-option>
<ld-option value="f">female</ld-option>
<ld-option value="d">diverse</ld-option>
</ld-select>
<ld-input-message id="msg2" style="visibility:hidden">This field is required.</ld-input-message>
</ld-label>
<ld-button id="btn2" style="margin:-0.3rem 0 0 1rem">Submit</ld-button>
</div>

<script>
const selectField2 = document.getElementById('gender2')
const inputMessage2 = document.getElementById('msg2')
const button2 = document.getElementById('btn2')

function validateSelect2(input) {
if (!input.selected.length) {
input.invalid = true
inputMessage2.style.visibility = 'inherit'
return false
}

input.invalid = false
inputMessage2.style.visibility = 'hidden'
return true
}

selectField2.addEventListener('input', event => {
validateSelect2(event.target)
})

selectField2.addEventListener('blur', event => {
validateSelect2(event.target)
})

button2.addEventListener('click', event => {
const isValid = validateSelect2(selectField2)

// setTimeout is used in order to let the style update before showing the alert.
// You can probably dismiss the timeout, if you're using a UI framework like React or Vue.
setTimeout(() => {
if (isValid) {
window.alert(`Form submitted with:
${selectField2.name}: ${selectField2.selected[0].value} (${selectField2.selected[0].text})`)
} else {
window.alert('Form is invalid.')
}
}, 100)
})
</script>
{% endexample %}

## Using event details

Custom events dispatched by Liquid Oxygen Web Components contain the relevant information about the component's state in their `detail` attribute. It's an easy way of accessing the current value of an input without having to access the props of the component itself.

### Example

{% example '{ "opened": true }' %}
<div style="display:flex;align-items:center">
<ld-label>
First Name*
<ld-input id="name3" name="firstName" placeholder="Jane Doe" required></ld-input>
<ld-input-message id="msg3" style="visibility:hidden">This field is required.</ld-input-message>
</ld-label>
<ld-button id="btn3" style="margin:-0.3rem 0 0 1rem">Submit</ld-button>
</div>

<script>
const inputField3 = document.getElementById('name3')
const inputMessage3 = document.getElementById('msg3')
const button3 = document.getElementById('btn3')
let currentValue

function validateInput3() {
if (!currentValue) {
inputField3.invalid = true
inputMessage3.style.visibility = 'inherit'
return false
}

inputField3.invalid = false
inputMessage3.style.visibility = 'hidden'
return true
}

inputField3.addEventListener('ldchange', event => {
currentValue = event.detail
validateInput3()
})

inputField3.addEventListener('blur', event => {
validateInput3()
})

button3.addEventListener('click', event => {
const isValid = validateInput3()

// setTimeout is used in order to let the style update before showing the alert.
// You can probably dismiss the timeout, if you're using a UI framework like React or Vue.
setTimeout(() => {
if (isValid) {
window.alert(`Form submitted with:
firstName: ${currentValue}`)
} else {
window.alert('Form is invalid.')
}
}, 100)
})
</script>
{% endexample %}

<docs-page-nav prev-href="introduction/event-handling/" next-title="React bindings" next-href="introduction/react-bindings/"></docs-page-nav>
6 changes: 3 additions & 3 deletions src/docs/pages/react-bindings.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@
eleventyNavigation:
key: React bindings
parent: Introduction
order: 7
order: 9
layout: layout.njk
title: React bindings
permalink: introduction/react-bindings/
---


# React Bindings
# React bindings

Setting event listeners via `on<EventName>`-prop on Liquid Oxygen Web Components does not work properly in React. For this reason, we provide special React bindings for Liquid Oxygen. They allow you to set event listeners via prop just like you are used to do in React without having to use `reference`.

Expand Down Expand Up @@ -43,4 +43,4 @@ When using React bindings, you do not need to use the `setAssetPath` function to
}
```

<docs-page-nav prev-href="introduction/server-side-rendering/" next-title="Tailwind CSS integration" next-href="introduction/tailwindcss-integration/"></docs-page-nav>
<docs-page-nav prev-href="introduction/form-validation/" next-title="Tailwind CSS integration" next-href="introduction/tailwindcss-integration/"></docs-page-nav>
4 changes: 2 additions & 2 deletions src/docs/pages/sandbox-applications.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
eleventyNavigation:
key: Sandbox applications
parent: Introduction
order: 10
order: 12
layout: layout.njk
title: Sandbox applications
permalink: introduction/sandbox-applications/
Expand All @@ -18,4 +18,4 @@ In order to help you get started with Liquid we have begun implementing small sa
- Liquid + **React + Typescript + Tailwind CSS** ([repository](https://github.com/emdgroup-liquid/liquid-sandbox-react-tailwind), [code sandbox](https://codesandbox.io/s/liquid-sandbox-react-tailwind-5mmvd))
- Liquid + **Next + Typescript + Tailwind CSS** ([repository](https://github.com/emdgroup-liquid/liquid-sandbox-next-tailwind), [code sandbox](https://codesandbox.io/s/liquid-sandbox-next-tailwind-q070f))

<docs-page-nav prev-href="introduction/design-tokens/" next-title="FAQ" next-href="introduction/faq/"></docs-page-nav>
<docs-page-nav prev-href="introduction/design-tokens/" next-title="FAQ" next-href="introduction/faq/"></docs-page-nav>
2 changes: 1 addition & 1 deletion src/docs/pages/server-side-rendering.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,4 @@ useEffect(()=>{

For working examples check out our [sandbox apps](introduction/sandbox-applications/).

<docs-page-nav prev-href="introduction/type-checking-and-intellisense/" next-title="React bindings" next-href="introduction/react-bindings/"></docs-page-nav>
<docs-page-nav prev-href="introduction/type-checking-and-intellisense/" next-title="Event handling" next-href="introduction/event-handling/"></docs-page-nav>
2 changes: 1 addition & 1 deletion src/docs/pages/tailwindcss-integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
eleventyNavigation:
key: Tailwind CSS integration
parent: Introduction
order: 8
order: 10
layout: layout.njk
title: Tailwind CSS integration
permalink: introduction/tailwindcss-integration/
Expand Down
Loading

0 comments on commit 59b4e28

Please sign in to comment.