Ogólny sposób wykrywania, czy edytowany jest formularz HTML

96

Mam formularz HTML z zakładkami. Po przejściu z jednej karty do drugiej dane bieżącej karty są utrwalane (w bazie danych), nawet jeśli nie ma żadnych zmian w danych.

Chciałbym wykonać wywołanie trwałości tylko wtedy, gdy formularz jest edytowany. Formularz może zawierać dowolny rodzaj kontrolki. Zabrudzenie formularza nie musi polegać na wpisaniu jakiegoś tekstu, ale wybranie daty w kontrolce kalendarza również się kwalifikuje.

Jednym ze sposobów na osiągnięcie tego byłoby domyślne wyświetlanie formularza w trybie tylko do odczytu i posiadanie przycisku „Edytuj”, a jeśli użytkownik kliknie przycisk edycji, następuje wywołanie bazy danych (jeszcze raz, niezależnie od tego, czy dane są modyfikowane .To lepsze ulepszenie tego, co obecnie istnieje).

Chciałbym wiedzieć, jak napisać ogólną funkcję javascript, która sprawdzałaby, czy któraś z wartości kontrolnych została zmodyfikowana?

Sathya
źródło
Ciekawe rozwiązanie zostało wysłane przez Craiga Buckler na SitePoint. Co ciekawe, rozwiązanie nie opiera się na jQuery i jest kompatybilne z różnymi przeglądarkami.
MagicAndi

Odpowiedzi:

162

W czystym javascript nie byłoby to łatwe zadanie, ale jQuery sprawia, że ​​jest to bardzo łatwe:

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

Następnie przed zapisaniem możesz sprawdzić, czy zostało zmienione:

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

W powyższym przykładzie identyfikator formularza jest równy „myform”.

Jeśli potrzebujesz tego w wielu formach, możesz łatwo przekształcić go we wtyczkę:

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

Następnie możesz po prostu powiedzieć:

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

i sprawdź, czy formularz się zmienił:

if ($("#myform").isChanged()) {
   // ...
}
Philippe Leybaert
źródło
13
To jest ładne i proste. Jeśli jednak użytkownik zmieni dane wejściowe formularza, a następnie cofnie zmianę (na przykład dwukrotnie klikając pole wyboru), formularz zostanie uznany za zmodyfikowany. To, czy jest to dopuszczalne, czy nie, zależy oczywiście od kontekstu. Alternatywę można znaleźć na stackoverflow.com/questions/10311663/…
jlh
1
dla wejść na żywo potrzebne są pewne zmiany trackChanges: function () { $(document).on('change', $(this).find(':input'), function (e) { var el = $(e.target); $(el).closest('form').data("changed", true); });
Dimmduh
36

W przypadku, gdy JQuery nie wchodzi w grę. Szybkie wyszukiwanie w Google znalazło implementacje algorytmów mieszania MD5 i SHA1 w Javascript. Jeśli chcesz, możesz połączyć wszystkie dane wejściowe formularza i zhaszować je, a następnie zapisać tę wartość w pamięci. Kiedy użytkownik skończy. Połącz ponownie wszystkie wartości i hash. Porównaj 2 skróty. Jeśli są takie same, użytkownik nie zmienił żadnych pól formularza. Jeśli są różne, coś zostało zmodyfikowane i musisz wywołać swój kod trwałości.

Matthew Vines
źródło
2
Tego się spodziewałem w przypadku tego pytania: Czy jest jakaś biblioteka?
Hamedz
Czy nie przyniosłoby to tego samego wyniku, gdybyś połączył wszystkie pola, ale ich nie haszował?
ashleedawg
Tak, ale same dane mogą być większe niż skrót.
JRG
28

Nie jestem pewien, czy dobrze odpowiem na Twoje pytanie, ale co z addEventListener? Jeśli nie przejmujesz się zbytnio obsługą IE8, powinno to wystarczyć. Poniższy kod działa dla mnie:

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

form.addEventListener("input", function () {
    console.log("Form has changed!");
});
mekograf
źródło
idealne dla moich potrzeb
Błogosławieństwo
8

Innym sposobem osiągnięcia tego jest serializacja formularza:

$(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
źródło
Podoba mi się, jak myślisz, zwłaszcza jeśli wyrywasz jQuery i robisz to za pomocą waniliowego JavaScript. Zdaję sobie sprawę, że pytanie zakładało, że jQuery jest dostępne, ale nie ma powodu, aby nie mieć jeszcze szerszego zastosowania rozwiązania, podobnego do tego z zastrzeżeniami w komentarzach.
ruffin
Pamiętaj tylko, serialize()że nie będzie zawierał disabledelementów. Może być OK w wielu przypadkach, ale może stanowić problem w innych przypadkach.
zed
4

Oto jak to zrobiłem (bez użycia jQuery).

W moim przypadku chciałem, aby jeden element formularza nie był liczony, ponieważ był to element, który wyzwalał sprawdzanie i zawsze będzie się zmieniać. Wyjątkowy element nosi nazwę „okres_raportowania” i jest zakodowany na stałe w funkcji „hasFormChanged ()”.

Aby przetestować, spraw, aby element wywołał funkcję „changeReportingPeriod ()”, której prawdopodobnie będziesz chciał nazwać coś innego.

WAŻNE: musisz wywołać metodę setInitialValues ​​(), gdy wartości zostały ustawione na ich oryginalne wartości (zwykle podczas ładowania strony, ale nie w moim przypadku).

UWAGA: Nie twierdzę, że jest to eleganckie rozwiązanie, tak naprawdę nie wierzę w eleganckie rozwiązania JavaScript. Mój osobisty nacisk w JavaScript kładzie się na czytelność, a nie na strukturalną elegancję (tak jakby było to możliwe w JavaScript). Nie przejmuję się w ogóle rozmiarem pliku podczas pisania JavaScript, ponieważ do tego służy gzip, a próba napisania bardziej zwartego kodu JavaScript niezmiennie prowadzi do nieznośnych problemów z konserwacją. Nie przepraszam, nie wyrażam wyrzutów sumienia i odmawiam dyskusji. To jest JavaScript. Przepraszam, musiałem to wyjaśnić, aby przekonać się, że powinienem zawracać sobie głowę wysyłaniem. Bądź szczęśliwy! :)


    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
źródło
4

Zmiany formularzy można łatwo wykryć w natywnym JavaScript bez 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()można bezpiecznie wywoływać wiele razy w całym cyklu życia strony: zobacz Testowanie w JSBin


W przypadku starszych przeglądarek, które nie obsługują nowszych funkcji strzałek / tablic:

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
źródło
zawsze zwraca prawdę dla mojej formy
Rich Stone
Czy możesz opublikować swój formularz w JSBin lub Gist, abyśmy mogli zobaczyć Twój formularz?
AnthumChris
moja wina, wybrałem formę z jquery a nie z JS jak na twoim przykładzie. Jest to ładne i czyste rozwiązanie js, bez przeciążenia serializacji. Dziękuję Ci!
Rich Stone
2

Oto demonstracja metody polyfill w natywnym języku JavaScript, która wykorzystuje FormData()interfejs API do wykrywania utworzonych, zaktualizowanych i usuniętych wpisów formularzy. Możesz sprawdzić, czy cokolwiek zostało zmienione za pomocą HTMLFormElement#isChangedi pobrać obiekt zawierający różnice z formularza resetowania za pomocą HTMLFormElement#changes(zakładając, że nie są one maskowane przez nazwę wejściową):

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
źródło