Aller au contenu

Formulaires et JavaScript⚓︎

C’est quoi ce bazard ?

Vous remarquerez que les solutions de double soumission suivantes sont toutes assez différentes. Je n’essaye pas d’ajouter de cohérence car elles ont toutes leurs subtilités qui m’ont permis d’apprendre des propriétés de JavaScript.

Éviter la multiple soumission⚓︎

Une solution que je pensais venir de là mais en fait non. Ou alors le billet de blog a changé depuis.

forms-double-submit.js
;(function () {
  function stopClickEvent(ev) {
    ev.preventDefault()
    ev.stopPropagation()
  }
  document.body.addEventListener('click', function (ev) {
    if (
      ev.target.tagName === 'A' ||
      ev.target.getAttribute('type').toLowerCase() === 'submit'
    ) {
      setTimeout(function () {
        // Needs to happen _after_ the request goes through, hence the timeout
        ev.target.addEventListener('click', stopClickEvent)
      }, 0)
      setTimeout(function () {
        ev.target.removeEventListener('click', stopClickEvent)
      }, 500) // (1)
    }
  })
})()
  1. Possibilité d’adapter la valeur du Timeout.

En ajoutant une barre de progression⚓︎

Une proposition qui vient de Jeremy Keith.

forms-double-submit-progress.js
;(function (win, doc) {
  'use strict'
  if (!doc.querySelectorAll || !win.addEventListener) {
    // doesn't cut the mustard.
    return
  }
  var forms = doc.querySelectorAll('form[method="post"]'),
    formcount = forms.length,
    i,
    submitting = false,
    checkForm = function (ev) {
      if (submitting) {
        ev.preventDefault()
      } else {
        submitting = true
        this.appendChild(doc.createElement('progress'))
      }
    }
  for (i = 0; i < formcount; i = i + 1) {
    forms[i].addEventListener('submit', checkForm, false)
  }
})(this, this.document)

Avec déclaration explicite⚓︎

Une proposition qui vient de Andrea Giammarchi.

forms-double-submit-explicit.js
function disableMultipleSubmits() {
  // by Andrea Giammarchi - WTFPL
  Array.prototype.forEach.call(
    document.querySelectorAll('form[disablemultiplesubmits]'),
    function (form) {
      form.addEventListener('submit', this, true)
    },
    {
      // button to disable
      query: 'input[type=submit],button[type=submit]',
      // delay before re-enabling
      delay: 500,
      // handler
      handleEvent: function (e) {
        var button = e.currentTarget.querySelector(this.query)
        button.disabled = true
        setTimeout(function () {
          button.disabled = false
        }, this.delay)
      },
    }
  )
}

Prévisualiser les images téléversées⚓︎

Un bout de code qui vient de Jeremy Keith :

preview-image-upload.js
;(function (win, doc) {
  if (!win.FileReader || !win.addEventListener || !doc.querySelectorAll) {
    // doesn't cut the mustard. (1)
    return
  }
  doc
    .querySelectorAll('input[type="file"][accept="image/*"]')
    .forEach(function (fileInput) {
      fileInput.addEventListener('change', function () {
        var files = fileInput.files
        if (files) {
          files.forEach(function (file) {
            var fileReader = new FileReader()
            fileReader.readAsDataURL(file)
            fileReader.addEventListener('load', function () {
              fileInput.insertAdjacentHTML(
                'afterend',
                '<img src="' + this.result + '">'
              )
            })
          })
        }
      })
    })
})(this, this.document)
  1. Pour en savoir plus sur la notion de moutarde coupée (et une version plus moderne).

Il devrait être effectif pour tous les inputs de ce type : <input type="file" accept="image/*">.

Aller plus loin avec les images ?

Un exemple complet est documenté sur une page dédiée.

Sauvegarder le contenu des textareas⚓︎

Encore(!) un bout de code qui vient de Jeremy Keith :

forms-save-textarea.js
// Licensed under a CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
// http://creativecommons.org/publicdomain/zero/1.0/

;(function (win, doc) {
  // Cut the mustard. (1)
  if (!win.localStorage) return
  // You should probably use a more specific selector than this.
  var textarea = doc.querySelector('textarea')
  // The key for the key/value pair in localStorage is the current URL.
  var key = win.location.href
  var item = null
  // Use the 'pagehide' event in modern browsers or 'beforeunload' in older browsers.
  var unloadEvent
  if ('onpagehide' in win) {
    unloadEvent = 'pagehide'
  } else {
    unloadEvent = 'beforeunload'
  }
  // If there's a localStorage entry for the current URL, update the textarea with the saved value.
  item = win.localStorage.getItem(key)
  if (item) {
    var data = JSON.parse(item)
    textarea.value = data.content
  }
  // This function will store the current value of the textarea in localStorage (or delete it if the textarea is blank).
  function updateStorage() {
    if (textarea.value) {
      item = JSON.stringify({ content: textarea.value })
      win.localStorage.setItem(key, item)
    } else {
      win.localStorage.removeItem(key)
    }
    // This event listener is no longer needed now so remove it.
    win.removeEventListener(unloadEvent, updateStorage)
  }
  // When the user presses a key just *once* inside the textarea, run the storage function when the page is unloaded.
  textarea.addEventListener(
    'keyup',
    function () {
      win.addEventListener(unloadEvent, updateStorage)
      win.setInterval(updateStorage, 60000)
    },
    { once: true }
  )
  // When the form is submitted, delete the localStorage key/value pair.
  textarea.form.addEventListener('submit', function () {
    win.localStorage.removeItem(key)
    win.removeEventListener(unloadEvent, updateStorage)
  })
})(this, this.document)
  1. Pour en savoir plus sur la notion de moutarde coupée (et une version plus moderne).

Nécessite une adaptation

Comme le fait remarquer Jeremy en commentaire, il faut adapter le sélecteur car vous avez probablement plus d’un textarea dans la page. Il serait possible de le rendre générique mais il y a peut-être des cas où vous ne voulez pas que ça sauvegarde, je préfère le laisser en explicitement activable.


Dernière mise à jour: 2022-08-23