View on GitHub

Enhance Workshop

This is the Enhance Workshop

Module Index

Module 10: Progressive Enhancement

Up until this point in the workshop we have only written server side code. It just goes to show you how great the platform has gotten in the past few years. Now we are going to focus on progressive enhancement.

The word progressive in progressive enhancement means creating a design that achieves a simpler-but-still-usable experience for users of older browsers and devices with limited capabilities, while at the same time being a design that progresses the user experience up to a more-compelling, fully-featured experience for users of newer browsers and devices with richer capabilities. From MDN

Progressively Enhancing Components

Let’s make the user experience for deleting links from our database a bit nicer. Currently, when you delete one of the links it requires a page reload. That’s fine but we can make that user experience a bit smoother.

/* globals customElements, document */
import CustomElement from '@enhance/custom-element'

export default class DeleteButton extends CustomElement {
  static observedAttributes = ['key']

  keyChanged(value) {
    this.querySelector('form').setAttribute('action', `/links/${value}/delete`)
  }

  connectedCallback() {
    this.button = this.querySelector('button')
    this.button.addEventListener('click', this.#handleClick);
  }

  disconnectedCallback() {
    this.button.removeEventListener('click', this.#handleClick);
  }

  #handleClick = async event => {
    event.preventDefault()
    let element = document.getElementById(this.getAttribute('key'))
    let display = element.style.display
    element.style.display = 'none'
    let { action, method } = event.target.closest('form')
    try {
      await fetch(action, {
        method: method,
        headers: {
          "Content-Type": "application/json",
          Accept: "application/json"
        },
      })
      element.remove()
    } catch(error) {
      console.error("Whoops!", error)
      element.style.display = display
    }
  }

  render({ html, state  }) {
    const { attrs } = state
    const { key } = attrs
    return html`
      <form action="/links/${key}/delete" method="POST" class="mb-1">
          <enhance-submit-button><span slot="label">Delete this link</span></enhance-submit-button>
      </form>
      `
  }
}

customElements.define('delete-button', DeleteButton)

A note about the #handleClick function. You might be wondering why the method name starts with a #? We are using a private method in this case as we don’t want any JavaScript outside of the web component to be able to delete our links. Deleting is a destructive action so we want to limit it to user initiated actions.

Also, you may have noticed we are using an arrow function. This is purely a preference of mine but it saves you from having to bind the value of this to the class.

Attributes

<delete-button key="link1"></delete-button>

Back to PE

import DeleteButton from '../components/delete-button.mjs'

export { DeleteButton }
<form action="/links/${item.key}/delete" method="POST" class="mb-1">
  <enhance-submit-button><span slot="label">Delete this link</span></enhance-submit-button>
</form>

with

<delete-button key="${item.key}"></delete-button>