Moyen générique de détecter si le formulaire html est modifié

94

J'ai un formulaire HTML à onglets. Lors de la navigation d'un onglet à l'autre, les données de l'onglet actuel sont conservées (sur la base de données) même s'il n'y a pas de modification des données.

Je souhaite effectuer l'appel de persistance uniquement si le formulaire est modifié. Le formulaire peut contenir tout type de contrôle. Il n'est pas nécessaire de salir le formulaire en tapant du texte, mais le choix d'une date dans un contrôle de calendrier serait également admissible.

Une façon d'y parvenir serait d'afficher le formulaire en mode lecture seule par défaut et d'avoir un bouton `` Modifier '' et si l'utilisateur clique sur le bouton d'édition, l'appel à la base de données est effectué (encore une fois, que les données soient modifiées ou non C'est une meilleure amélioration par rapport à ce qui existe actuellement).

Je voudrais savoir comment écrire une fonction javascript générique qui vérifierait si l'une des valeurs des contrôles a été modifiée?

Sathya
la source
Une solution intéressante a été publiée par Craig Buckler sur SitePoint. D'un intérêt particulier, la solution ne repose pas sur jQuery et est compatible avec tous les navigateurs.
MagicAndi

Réponses:

158

En javascript pur, ce ne serait pas une tâche facile, mais jQuery le rend très facile à faire:

$("#myform :input").change(function() {
   $("#myform").data("changed",true);
});

Ensuite, avant d'enregistrer, vous pouvez vérifier s'il a été modifié:

if ($("#myform").data("changed")) {
   // submit the form
}

Dans l'exemple ci-dessus, le formulaire a un identifiant égal à "myform".

Si vous en avez besoin sous de nombreuses formes, vous pouvez facilement le transformer en plugin:

$.fn.extend({
 trackChanges: function() {
   $(":input",this).change(function() {
      $(this.form).data("changed", true);
   });
 }
 ,
 isChanged: function() { 
   return this.data("changed"); 
 }
});

Ensuite, vous pouvez simplement dire:

$("#myform").trackChanges();

et vérifiez si un formulaire a changé:

if ($("#myform").isChanged()) {
   // ...
}
Philippe Leybaert
la source
13
C'est sympa et simple. Cependant, si un utilisateur modifie une entrée de formulaire puis annule la modification (par exemple en cliquant deux fois sur une case à cocher), le formulaire sera considéré comme modifié. Que ce soit acceptable ou non dépend bien sûr du contexte. Pour une alternative, voir stackoverflow.com/questions/10311663/…
jlh
1
pour les entrées live ont besoin de quelques changements trackChanges: function () { $(document).on('change', $(this).find(':input'), function (e) { var el = $(e.target); $(el).closest('form').data("changed", true); });
Dimmduh
36

Au cas où JQuery serait hors de question. Une recherche rapide sur Google a trouvé des implémentations Javascript des algorithmes de hachage MD5 et SHA1. Si vous le souhaitez, vous pouvez concaténer toutes les entrées de formulaire et les hacher, puis stocker cette valeur en mémoire. Lorsque l'utilisateur a terminé. Concaténez toutes les valeurs et hachez à nouveau. Comparez les 2 hachages. S'ils sont identiques, l'utilisateur n'a modifié aucun champ de formulaire. S'ils sont différents, quelque chose a été modifié et vous devez appeler votre code de persistance.

Matthieu Vines
la source
2
C'est ce que j'attendais pour cette question, y a-t-il une bibliothèque?
Hamedz
N'aurait-il pas le même résultat si vous concaténez tous les champs sans les hacher?
ashleedawg
Oui, mais les données elles-mêmes peuvent être plus volumineuses que le hachage.
JRG
26

Je ne sais pas si j'ai bien répondu à votre question, mais qu'en est-il de addEventListener? Si vous ne vous souciez pas trop du support IE8, cela devrait être bien. Le code suivant fonctionne pour moi:

var form = document.getElementById("myForm");

form.addEventListener("input", function () {
    console.log("Form has changed!");
});
mécographe
la source
parfait pour mes besoins
Bénédiction
8

Une autre façon d'y parvenir est de sérialiser le formulaire:

$(function() {
    var $form = $('form');
    var initialState = $form.serialize();
    
    $form.submit(function (e) {
      if (initialState === $form.serialize()) {
        console.log('Form is unchanged!');
      } else {
        console.log('Form has changed!');
      }
      e.preventDefault();
    });
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<form>
Field 1: <input type="text" name="field_1" value="My value 1"> <br>
Field 2: <input type="text" name="field_2" value="My value 2"> <br>
Check: <input type="checkbox" name="field_3" value="1"><br>
<input type="submit">
</form>

Nikoskip
la source
J'aime la façon dont vous pensez, surtout si vous extrayez le jQuery et le faites avec du JavaScript vanille. Je me rends compte que la question supposait que jQuery était disponible, mais aucune raison de ne pas avoir une solution encore plus applicable, similaire à celle-ci avec les mises en garde dans les commentaires.
ruffin le
4

Voici comment je l'ai fait (sans utiliser jQuery).

Dans mon cas, je voulais qu'un élément de formulaire particulier ne soit pas compté, car c'est l'élément qui a déclenché la vérification et il aura donc toujours changé. L'élément exceptionnel est nommé 'reporting_period' et est codé en dur dans la fonction 'hasFormChanged ()'.

Pour tester, faites un élément appeler la fonction "changeReportingPeriod ()", que vous voudrez probablement nommer autre chose.

IMPORTANT: vous devez appeler setInitialValues ​​() lorsque les valeurs ont été définies sur leurs valeurs d'origine (généralement au chargement de la page, mais pas dans mon cas).

REMARQUE: je ne prétends pas que ce soit une solution élégante, en fait je ne crois pas aux solutions JavaScript élégantes. Mon accent personnel en JavaScript est la lisibilité, pas l'élégance structurelle (comme si cela était possible en JavaScript). Je ne me préoccupe pas du tout de la taille du fichier lors de l'écriture de JavaScript, car c'est à cela que sert gzip, et essayer d'écrire du code JavaScript plus compact conduit invariablement à des problèmes de maintenance intolérables. Je ne présente aucune excuse, n'exprime aucun remords et refuse d'en débattre. C'est du JavaScript. Désolée, je devais clarifier cela pour me convaincre que je devrais prendre la peine de poster. Soyez heureux! :)


    var initial_values = new Array();

    // Gets all form elements from the entire document.
    function getAllFormElements() {
        // Return variable.
        var all_form_elements = Array();

        // The form.
        var form_activity_report = document.getElementById('form_activity_report');

        // Different types of form elements.
        var inputs = form_activity_report.getElementsByTagName('input');
        var textareas = form_activity_report.getElementsByTagName('textarea');
        var selects = form_activity_report.getElementsByTagName('select');

        // We do it this way because we want to return an Array, not a NodeList.
        var i;
        for (i = 0; i < inputs.length; i++) {
            all_form_elements.push(inputs[i]);
        }
        for (i = 0; i < textareas.length; i++) {
            all_form_elements.push(textareas[i]);
        }
        for (i = 0; i < selects.length; i++) {
            all_form_elements.push(selects[i]);
        }

        return all_form_elements;
    }

    // Sets the initial values of every form element.
    function setInitialFormValues() {
        var inputs = getAllFormElements();
        for (var i = 0; i < inputs.length; i++) {
            initial_values.push(inputs[i].value);
        }
    }

    function hasFormChanged() {
        var has_changed = false;
        var elements = getAllFormElements();

        for (var i = 0; i < elements.length; i++) {
            if (elements[i].id != 'reporting_period' && elements[i].value != initial_values[i]) {
                has_changed = true;
                break;
            }
        }

        return has_changed;
    }

    function changeReportingPeriod() {
        alert(hasFormChanged());
    }


Teekin
la source
3

Les modifications de formulaire peuvent facilement être détectées dans JavaScript natif sans jQuery:

function initChangeDetection(form) {
  Array.from(form).forEach(el => el.dataset.origValue = el.value);
}
function formHasChanges(form) {
  return Array.from(form).some(el => 'origValue' in el.dataset && el.dataset.origValue !== el.value);
}


initChangeDetection()peut être appelé en toute sécurité plusieurs fois tout au long du cycle de vie de votre page: voir Test sur JSBin


Pour les navigateurs plus anciens qui ne prennent pas en charge les nouvelles fonctions de flèche / tableau:

function initChangeDetection(form) {
  for (var i=0; i<form.length; i++) {
    var el = form[i];
    el.dataset.origValue = el.value;
  }
}
function formHasChanges(form) {
  for (var i=0; i<form.length; i++) {
    var el = form[i];
    if ('origValue' in el.dataset && el.dataset.origValue !== el.value) {
      return true;
    }
  }
  return false;
}
AnthumChris
la source
renvoie toujours vrai pour ma forme
Rich Stone
Pourriez-vous publier votre formulaire sur JSBin ou un Gist afin que nous puissions voir votre formulaire?
AnthumChris
ma faute, j'ai choisi le formulaire avec jquery et non avec JS comme dans votre exemple. C'est une solution js agréable et propre, sans la surcharge de sérialisation. Merci!
Rich Stone
2

Voici une démonstration de la méthode polyfill en JavaScript natif qui utilise l' FormData()API pour détecter les entrées de formulaire créées, mises à jour et supprimées. Vous pouvez vérifier si quelque chose a été modifié en utilisant HTMLFormElement#isChangedet obtenir un objet contenant les différences d'un formulaire de réinitialisation en utilisant HTMLFormElement#changes(en supposant qu'ils ne soient pas masqués par un nom d'entrée):

Object.defineProperties(HTMLFormElement.prototype, {
  isChanged: {
    configurable: true,
    get: function isChanged () {
      'use strict'

      var thisData = new FormData(this)
      var that = this.cloneNode(true)

      // avoid masking: https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/reset
      HTMLFormElement.prototype.reset.call(that)

      var thatData = new FormData(that)

      const theseKeys = Array.from(thisData.keys())
      const thoseKeys = Array.from(thatData.keys())

      if (theseKeys.length !== thoseKeys.length) {
        return true
      }

      const allKeys = new Set(theseKeys.concat(thoseKeys))

      function unequal (value, index) {
        return value !== this[index]
      }

      for (const key of theseKeys) {
        const theseValues = thisData.getAll(key)
        const thoseValues = thatData.getAll(key)

        if (theseValues.length !== thoseValues.length) {
          return true
        }

        if (theseValues.some(unequal, thoseValues)) {
          return true
        }
      }

      return false
    }
  },
  changes: {
    configurable: true,
    get: function changes () {
      'use strict'

      var thisData = new FormData(this)
      var that = this.cloneNode(true)

      // avoid masking: https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/reset
      HTMLFormElement.prototype.reset.call(that)

      var thatData = new FormData(that)

      const theseKeys = Array.from(thisData.keys())
      const thoseKeys = Array.from(thatData.keys())

      const created = new FormData()
      const deleted = new FormData()
      const updated = new FormData()

      const allKeys = new Set(theseKeys.concat(thoseKeys))

      function unequal (value, index) {
        return value !== this[index]
      }

      for (const key of allKeys) {
        const theseValues = thisData.getAll(key)
        const thoseValues = thatData.getAll(key)

        const createdValues = theseValues.slice(thoseValues.length)
        const deletedValues = thoseValues.slice(theseValues.length)

        const minLength = Math.min(theseValues.length, thoseValues.length)

        const updatedValues = theseValues.slice(0, minLength).filter(unequal, thoseValues)

        function append (value) {
          this.append(key, value)
        }

        createdValues.forEach(append, created)
        deletedValues.forEach(append, deleted)
        updatedValues.forEach(append, updated)
      }

      return {
        created: Array.from(created),
        deleted: Array.from(deleted),
        updated: Array.from(updated)
      }
    }
  }
})

document.querySelector('[value="Check"]').addEventListener('click', function () {
  if (this.form.isChanged) {
    console.log(this.form.changes)
  } else {
    console.log('unchanged')
  }
})
<form>
  <div>
    <label for="name">Text Input:</label>
    <input type="text" name="name" id="name" value="" tabindex="1" />
  </div>

  <div>
    <h4>Radio Button Choice</h4>

    <label for="radio-choice-1">Choice 1</label>
    <input type="radio" name="radio-choice-1" id="radio-choice-1" tabindex="2" value="choice-1" />

    <label for="radio-choice-2">Choice 2</label>
    <input type="radio" name="radio-choice-2" id="radio-choice-2" tabindex="3" value="choice-2" />
  </div>

  <div>
    <label for="select-choice">Select Dropdown Choice:</label>
    <select name="select-choice" id="select-choice">
      <option value="Choice 1">Choice 1</option>
      <option value="Choice 2">Choice 2</option>
      <option value="Choice 3">Choice 3</option>
    </select>
  </div>

  <div>
    <label for="textarea">Textarea:</label>
    <textarea cols="40" rows="8" name="textarea" id="textarea"></textarea>
  </div>

  <div>
    <label for="checkbox">Checkbox:</label>
    <input type="checkbox" name="checkbox" id="checkbox" />
  </div>

  <div>
    <input type="button" value="Check" />
  </div>
</form>

Patrick Roberts
la source