webcomponents / polyfills

Web Components Polyfills
BSD 3-Clause "New" or "Revised" License
1.13k stars 167 forks source link

Implement FormData event #172

Open dfreedm opened 5 years ago

dfreedm commented 5 years ago

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

YonatanKra commented 4 years ago

Note this is not supported in Safari

stale[bot] commented 3 years ago

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.

fernandopasik commented 3 years ago

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

bicknellr commented 3 years ago

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.

WickyNilliams commented 2 years ago

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 commented 2 years ago

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 commented 2 years ago

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)
stale[bot] commented 1 year ago

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.

TechQuery commented 1 year ago

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.