Modo generico per rilevare se il modulo html viene modificato


94

Ho un modulo html a schede. Passando da una scheda all'altra, i dati della scheda corrente vengono mantenuti (sul DB) anche se non vengono apportate modifiche ai dati.

Vorrei effettuare la chiamata di persistenza solo se il modulo è stato modificato. Il modulo può contenere qualsiasi tipo di controllo. Non è necessario sporcare il modulo digitando del testo, ma si qualifica anche la scelta di una data in un controllo del calendario.

Un modo per ottenere ciò sarebbe visualizzare il modulo in modalità di sola lettura per impostazione predefinita e disporre di un pulsante 'Modifica' e se l'utente fa clic sul pulsante di modifica, viene effettuata la chiamata al DB (ancora una volta, indipendentemente dal fatto che i dati vengano modificati . Questo è un miglioramento migliore di ciò che è attualmente esistente).

Vorrei sapere come scrivere una funzione javascript generica che verificherebbe se uno dei valori dei controlli è stato modificato?


Una soluzione interessante è stata pubblicata da Craig Buckler su SitePoint. Di particolare interesse, la soluzione non si basa su jQuery ed è compatibile con tutti i browser.
MagicAndi

Risposte:


158

In javascript puro, questo non sarebbe un compito facile, ma jQuery lo rende molto facile da fare:

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

Quindi prima di salvare, puoi controllare se è stato modificato:

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

Nell'esempio sopra, il modulo ha un id uguale a "myform".

Se ne hai bisogno in molte forme, puoi facilmente trasformarlo in un plugin:

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

Quindi puoi semplicemente dire:

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

e controlla se un modulo è cambiato:

if ($("#myform").isChanged()) {
   // ...
}

13
Questo è carino e semplice. Tuttavia, se un utente modifica un input del modulo e quindi annulla la modifica (ad esempio facendo clic due volte su una casella di controllo), il modulo verrà considerato modificato. Che sia accettabile o meno dipende ovviamente dal contesto. Per un'alternativa vedere stackoverflow.com/questions/10311663/...
jlh

1
per gli ingressi dal vivo sono necessarie alcune modifiche trackChanges: function () { $(document).on('change', $(this).find(':input'), function (e) { var el = $(e.target); $(el).closest('form').data("changed", true); });
Dimmduh

36

Nel caso in cui JQuery sia fuori discussione. Una rapida ricerca su Google ha trovato implementazioni Javascript degli algoritmi hash MD5 e SHA1. Se lo desideri, puoi concatenare tutti gli input del modulo e sottoporli a hashing, quindi memorizzare quel valore in memoria. Quando l'utente ha finito. Concatena nuovamente tutti i valori e hash. Confronta i 2 hash. Se sono uguali, l'utente non ha modificato alcun campo del modulo. Se sono diversi, qualcosa è stato modificato e devi chiamare il tuo codice di persistenza.


2
Questo è quello che mi aspettavo per questa domanda, c'è qualche libreria?
Hamedz

Non avrebbe lo stesso risultato se si concatenassero tutti i campi ma non li hash?
ashleedawg

Sì, ma i dati stessi potrebbero essere più grandi dell'hash.
JRG

26

Non sono sicuro di aver risposto correttamente alla tua domanda, ma per quanto riguarda addEventListener? Se non ti interessa troppo del supporto di IE8, questo dovrebbe andare bene. Il codice seguente funziona per me:

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

form.addEventListener("input", function () {
    console.log("Form has changed!");
});

perfetto per le mie esigenze
Benedizione

8

Un altro modo per ottenere ciò è serializzare il modulo:

$(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>


Mi piace come stai pensando, specialmente se estrai jQuery e lo fai con JavaScript vanilla. Mi rendo conto che la domanda presupponeva che jQuery fosse disponibile, ma non c'è motivo per non avere una soluzione ancora più applicabile, simile a questa con gli avvertimenti nei commenti.
ruffin

4

Ecco come l'ho fatto (senza usare jQuery).

Nel mio caso, volevo che un particolare elemento del modulo non venisse conteggiato, perché era l'elemento che ha attivato il controllo e quindi sarà sempre cambiato. L'elemento eccezionale è denominato "reporting_period" ed è hardcoded nella funzione "hasFormChanged ()".

Per testare, fai in modo che un elemento chiami la funzione "changeReportingPeriod ()", che probabilmente vorrai chiamare qualcos'altro.

IMPORTANTE: è necessario chiamare setInitialValues ​​() quando i valori sono stati impostati sui valori originali (in genere al caricamento della pagina, ma non nel mio caso).

NOTA: non pretendo che questa sia una soluzione elegante, infatti non credo nelle soluzioni JavaScript eleganti. La mia personale enfasi in JavaScript è sulla leggibilità, non sull'eleganza strutturale (come se ciò fosse possibile in JavaScript). Non mi preoccupo affatto della dimensione del file quando scrivo JavaScript perché è a questo che serve gzip, e provare a scrivere codice JavaScript più compatto porta invariabilmente a problemi intollerabili con la manutenzione. Non mi scuso, non esprimo rimorso e mi rifiuto di discuterne. È JavaScript. Scusa, ho dovuto chiarire questo punto per convincermi che avrei dovuto preoccuparmi di postare. Siate felici! :)


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



3

Le modifiche al modulo possono essere facilmente rilevate in JavaScript nativo senza 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()può essere chiamato in sicurezza più volte durante il ciclo di vita della tua pagina: vedi Test su JSBin


Per i browser meno recenti che non supportano le nuove funzioni freccia / array:

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

restituisce sempre vero per la mia forma
Rich Stone

Potresti pubblicare il tuo modulo su JSBin o un Gist in modo che possiamo vedere il tuo modulo?
AnthumChris

colpa mia, ho selezionato il form con jquery e non con JS come nel tuo esempio. È una soluzione js bella e pulita, senza il sovraccarico di serializzazione. Grazie!
Rich Stone,

2

Ecco una demo del metodo polyfill in JavaScript nativo che utilizza l' FormData()API per rilevare le voci del modulo create, aggiornate ed eliminate. Puoi controllare se qualcosa è stato modificato usando HTMLFormElement#isChangede ottenere un oggetto contenente le differenze da un modulo di ripristino usando HTMLFormElement#changes(supponendo che non siano mascherati da un nome di input):

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>

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.