Forma genérica de detectar si se edita el formulario html

94

Tengo un formulario html con pestañas. Al navegar de una pestaña a otra, los datos de la pestaña actual se conservan (en la base de datos) incluso si no hay cambios en los datos.

Me gustaría realizar la llamada de persistencia solo si se edita el formulario. El formulario puede contener cualquier tipo de control. No es necesario que ensucie el formulario escribiendo texto, pero también calificaría elegir una fecha en un control de calendario.

Una forma de lograr esto sería mostrar el formulario en modo de solo lectura de forma predeterminada y tener un botón 'Editar' y si el usuario hace clic en el botón editar, se realiza la llamada a DB (una vez más, independientemente de si se modifican los datos Esta es una mejor mejora de lo que existe actualmente).

Me gustaría saber cómo escribir una función genérica de JavaScript que verifique si se ha modificado alguno de los valores de los controles.

Sathya
fuente
Craig Buckler ha publicado una solución interesante en SitePoint. De particular interés, la solución no se basa en jQuery y es compatible con varios navegadores.
MagicAndi

Respuestas:

158

En javascript puro, esto no sería una tarea fácil, pero jQuery lo hace muy fácil de hacer:

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

Luego, antes de guardar, puede verificar si se cambió:

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

En el ejemplo anterior, el formulario tiene un id igual a "myform".

Si necesita esto en muchas formas, puede convertirlo fácilmente en un complemento:

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

Entonces puedes simplemente decir:

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

y verifique si un formulario ha cambiado:

if ($("#myform").isChanged()) {
   // ...
}
Philippe Leybaert
fuente
13
Esto es agradable y sencillo. Sin embargo, si un usuario cambia la entrada de un formulario y luego revierte el cambio (por ejemplo, al hacer clic en una casilla de verificación dos veces), el formulario se considerará modificado. Que eso sea aceptable o no depende, por supuesto, del contexto. Para obtener una alternativa, consulte stackoverflow.com/questions/10311663/…
jlh
1
para entradas en vivo necesitan algunos cambios trackChanges: function () { $(document).on('change', $(this).find(':input'), function (e) { var el = $(e.target); $(el).closest('form').data("changed", true); });
Dimmduh
36

En caso de que JQuery esté fuera de discusión. Una búsqueda rápida en Google encontró implementaciones de Javascript de los algoritmos hash MD5 y SHA1. Si lo desea, puede concatenar todas las entradas de formulario y hash, luego almacenar ese valor en la memoria. Cuando el usuario haya terminado. Concatenar todos los valores y volver a hacer hash. Compara los 2 hashes. Si son iguales, el usuario no cambió ningún campo del formulario. Si son diferentes, se ha editado algo y debe llamar a su código de persistencia.

Matthew Vines
fuente
2
Esto es lo que esperaba para esta pregunta, ¿hay alguna biblioteca?
Hamedz
¿No obtendría el mismo resultado si concatenó todos los campos pero no los hash?
ashleedawg
Sí, pero los datos en sí pueden ser más grandes que el hash.
JRG
26

No estoy seguro de haber respondido bien su pregunta, pero ¿qué pasa con addEventListener? Si no le importa demasiado el soporte de IE8, esto debería estar bien. El siguiente código me funciona:

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

form.addEventListener("input", function () {
    console.log("Form has changed!");
});
mecógrafo
fuente
perfecto para mis necesidades
Bendición
8

Otra forma de lograr esto es serializar el formulario:

$(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
fuente
Me gusta cómo estás pensando, especialmente si arrancas jQuery y lo haces con JavaScript vanilla. Me doy cuenta de que la pregunta asumió que jQuery estaba disponible, pero no hay razón para no tener una solución aún más aplicable, similar a esta con las advertencias en los comentarios.
Ruffin
4

Así es como lo hice (sin usar jQuery).

En mi caso, quería que no se contara un elemento de formulario en particular, porque era el elemento que activaba la verificación y, por lo tanto, siempre habrá cambiado. El elemento excepcional se llama 'reporting_period' y está codificado en la función 'hasFormChanged ()'.

Para probar, haga que un elemento llame a la función "changeReportingPeriod ()", que probablemente desee nombrar de otra manera.

IMPORTANTE: Debe llamar a setInitialValues ​​() cuando los valores se hayan establecido en sus valores originales (generalmente al cargar la página, pero no en mi caso).

NOTA: No pretendo que esta sea una solución elegante, de hecho, no creo en las soluciones elegantes de JavaScript. Mi énfasis personal en JavaScript está en la legibilidad, no en la elegancia estructural (como si eso fuera posible en JavaScript). No me preocupo en absoluto por el tamaño del archivo cuando escribo JavaScript porque para eso es gzip, y tratar de escribir un código JavaScript más compacto conduce invariablemente a problemas intolerables con el mantenimiento. No me disculpo, no expreso ningún remordimiento y me niego a debatirlo. Es JavaScript. Lo siento, tuve que dejar esto en claro para convencerme de que debería molestarme en publicar. ¡Sea feliz! :)


    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
fuente
3

Los cambios de formulario se pueden detectar fácilmente en JavaScript nativo sin 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()se puede llamar de forma segura varias veces a lo largo del ciclo de vida de su página: consulte Prueba en JSBin


Para navegadores más antiguos que no admiten funciones de flecha / matriz más nuevas:

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
fuente
siempre devuelve verdadero para mi forma
Rich Stone
¿Podría publicar su formulario en JSBin o Gist para que podamos ver su formulario?
AnthumChris
mi culpa, seleccioné el formulario con jquery y no con JS como en tu ejemplo. Es una solución js agradable y limpia, sin la sobrecarga de serialización. ¡Gracias!
Rich Stone
2

Aquí hay una demostración del método polyfill en JavaScript nativo que usa la FormData()API para detectar entradas de formulario creadas, actualizadas y eliminadas. Puede verificar si se cambió algo usando HTMLFormElement#isChangedy obtener un objeto que contenga las diferencias de un formulario de reinicio usando HTMLFormElement#changes(asumiendo que no están enmascarados por un nombre de entrada):

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
fuente