polyfills icon indicating copy to clipboard operation
polyfills copied to clipboard

Implement FormData event

Open dfreedm opened this issue 6 years ago • 9 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

dfreedm avatar Jul 12 '19 00:07 dfreedm

Note this is not supported in Safari

YonatanKra avatar Aug 04 '20 06:08 YonatanKra

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.

stale[bot] avatar Aug 04 '21 16:08 stale[bot]

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

fernandopasik avatar Aug 04 '21 17:08 fernandopasik

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.

bicknellr avatar Aug 04 '21 19:08 bicknellr

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

WickyNilliams avatar Sep 21 '21 09:09 WickyNilliams

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

WickyNilliams avatar Sep 21 '21 09:09 WickyNilliams

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)

WickyNilliams avatar Sep 29 '21 09:09 WickyNilliams

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.

stale[bot] avatar Jan 21 '23 04:01 stale[bot]

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.

TechQuery avatar Jan 23 '23 07:01 TechQuery