webcomponents / polyfills

Web Components Polyfills

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Implement FormData event

dfreedm opened this issue · comments

The FormData event allows Custom Elements, and other elements, to add new data to a Form element before submission.

More info: https://www.chromestatus.com/feature/5662230242656256

Note this is not supported in Safari

commented

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

would it be possible to avoid this one from going stale?

I think these new comments should prevent the bot from closing this issue. FWIW, we're planning on coming back to this soon after we finish everything off for the next stable release of Lit.

I have been playing about with a very light polyfill for this. I have only attempted to support appending data since that is probably the 99% use case, especially for web components.

The FormData class is basically available everywhere, the only missing part is the corresponding formdata event. So we can utilise this to keep the polyfill small. I add a hidden input to the form when data is appended.

Here's what i have so far, including a feature test. It works in safari. I'm going to experiment with it further.

function supportsFormDataEvent({ document }) {
  let isSupported = false

  const form = document.createElement("form")
  document.body.appendChild(form)

  form.addEventListener("submit", e => {
    e.preventDefault()
    // this dispatches formdata event in browsers that support it
    new FormData(e.target)
  })
  form.addEventListener("formdata", () => {
    isSupported = true
  })

  form.dispatchEvent(new Event("submit"))
  form.remove()

  return isSupported
}

export function polyfillFormData(win) {
  if (!win.FormData) {
    return
  }
  if (supportsFormDataEvent(win)) {
    return
  }

  class FormDataPoly extends FormData {
    constructor(form) {
      super(form)
      this.form = form
    }

    append(name, value) {
      super.append(name, value)
      let input = this.form.elements[name]

      if (!input) {
        input = document.createElement("input")
        input.type = "hidden"
        input.name = name
      }

      input.value = value
      this.form.appendChild(input)
    }
  }

  class FormDataEvent extends Event {
    constructor(form) {
      super("formdata")
      this.formData = new FormDataPoly(form)
    }
  }

  win.addEventListener("submit", e => {
    if (!e.defaultPrevented) {
      e.target.dispatchEvent(new FormDataEvent(e.target))
    }
  })
}

I just noticed a polyfill has already been added to the repo, which is obviously more complete (at the cost of file size/invasiveness). Perhaps this issue can be closed if it is good to go :)

There were actually some small issues with the code I posted above. It didn't correctly handle anything but the first form submission, ending up with duplicate entries on subsequent submissions. Nor did it support manually calling new FormData(form) to gather form values outside of a submit event.

The version below fixes these issues, and works well in my testing.

Here's a gist of the code with an explicit license: https://gist.github.com/WickyNilliams/eb6a44075356ee504dd9491c5a3ab0be

/* eslint-disable max-classes-per-file */

class FormDataEvent extends Event {
  constructor(formData) {
    super("formdata")
    this.formData = formData
  }
}

class FormDataPolyfilled extends FormData {
  constructor(form) {
    super(form)
    this.form = form
    form.dispatchEvent(new FormDataEvent(this))
  }

  append(name, value) {
    let input = this.form.elements[name]

    if (!input) {
      input = document.createElement("input")
      input.type = "hidden"
      input.name = name
      this.form.appendChild(input)
    }

    // if the name already exists, there is already a hidden input in the dom
    // and it will have been picked up by FormData during construction.
    // in this case, we can't just blindly append() since that will result in two entries.
    // nor can we blindly delete() the entry, since there can be multiple entries per name (e.g. checkboxes).
    // so we must carefully splice out the old value, and add back in the new value
    if (this.has(name)) {
      const entries = this.getAll(name)
      const index = entries.indexOf(input.value)

      if (index !== -1) {
        entries.splice(index, 1)
      }

      entries.push(value)
      this.set(name, entries)
    } else {
      super.append(name, value)
    }

    input.value = value
  }
}

function supportsFormDataEvent({ document }) {
  let isSupported = false

  const form = document.createElement("form")
  document.body.appendChild(form)

  form.addEventListener("submit", e => {
    e.preventDefault()
    // this dispatches formdata event in browsers that support it
    new FormData(e.target) // eslint-disable-line no-new
  })

  form.addEventListener("formdata", () => {
    isSupported = true
  })

  form.dispatchEvent(new Event("submit", { cancelable: true }))
  form.remove()

  return isSupported
}

function polyfillFormData(win) {
  if (!win.FormData || supportsFormDataEvent(win)) {
    return
  }

  window.FormData = FormDataPolyfilled
  win.addEventListener("submit", e => {
    if (!e.defaultPrevented) {
      // eslint-disable-next-line no-new
      new FormData(e.target)
    }
  })
}

polyfillFormData(window)
commented

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

Any update in 2023 ?

New version of React official document is dropping Class components, Web components is the right way to write Class components, hope to improve these details.