Maneira genérica de detectar se o formulário html é editado

96

Eu tenho um formulário html com guias. Ao navegar de uma guia para outra, os dados da guia atual são persistidos (no banco de dados) mesmo que não haja alteração nos dados.

Gostaria de fazer a chamada de persistência apenas se o formulário for editado. O formulário pode conter qualquer tipo de controle. Sujar o formulário não precisa ser digitando algum texto, mas a escolha de uma data em um controle de calendário também se qualifica.

Uma maneira de conseguir isso seria exibir o formulário no modo somente leitura por padrão e ter um botão 'Editar' e se o usuário clicar no botão editar, então a chamada para DB é feita (mais uma vez, independentemente de os dados serem modificados . Esta é uma melhoria melhor em relação ao que existe atualmente).

Gostaria de saber como escrever uma função javascript genérica que verificaria se algum dos valores dos controles foi modificado?

Sathya
fonte
Uma solução interessante foi postada por Craig Buckler no SitePoint. De particular interesse, a solução não depende do jQuery e é compatível com vários navegadores.
MagicAndi

Respostas:

162

Em javascript puro, isso não seria uma tarefa fácil, mas jQuery torna muito fácil de fazer:

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

Então, antes de salvar, você pode verificar se foi alterado:

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

No exemplo acima, o formulário tem um id igual a "myform".

Se você precisar disso de várias formas, pode facilmente transformá-lo em um plug-in:

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

Então você pode simplesmente dizer:

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

e verifique se um formulário foi alterado:

if ($("#myform").isChanged()) {
   // ...
}
Philippe Leybaert
fonte
13
Isso é bom e simples. No entanto, se um usuário alterar uma entrada de formulário e depois reverter a alteração (por exemplo, clicando duas vezes em uma caixa de seleção), o formulário será considerado modificado. Se isso é aceitável ou não, depende, é claro, do contexto. Para uma alternativa, consulte stackoverflow.com/questions/10311663/…
jlh
1
para entradas ao vivo precisa de algumas mudanças trackChanges: function () { $(document).on('change', $(this).find(':input'), function (e) { var el = $(e.target); $(el).closest('form').data("changed", true); });
Dimmduh
36

Caso o JQuery esteja fora de questão. Uma rápida pesquisa no Google encontrou implementações Javascript de algoritmos de hash MD5 e SHA1. Se você quiser, pode concatenar todas as entradas do formulário e fazer um hash delas e, em seguida, armazenar esse valor na memória. Quando o usuário terminar. Concatene todos os valores e hash novamente. Compare os 2 hashes. Se forem iguais, o usuário não alterou nenhum campo do formulário. Se forem diferentes, algo foi editado e você precisa chamar seu código de persistência.

Matthew Vines
fonte
2
Isso é o que eu esperava para esta pergunta, existe alguma biblioteca?
Hamedz
Não teria o mesmo resultado se você concatenasse todos os campos, mas não fizesse o hash?
ashleedawg
Sim, mas os próprios dados podem ser maiores do que o hash.
JRG
28

Não tenho certeza se entendi bem sua pergunta, mas e o addEventListener? Se você não se preocupa muito com o suporte do IE8, não há problema. O código a seguir está funcionando para mim:

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

form.addEventListener("input", function () {
    console.log("Form has changed!");
});
mecógrafo
fonte
perfeito para minhas necessidades
Bênção
8

Outra maneira de conseguir isso é serializar o formulário:

$(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
fonte
Gosto de como você está pensando, especialmente se você extrair o jQuery e fizer isso com o JavaScript básico. Sei que a questão presumia que o jQuery estava disponível, mas não há razão para não ter uma solução ainda mais amplamente aplicável, semelhante a esta com as advertências nos comentários.
Ruffin de
Lembre- serialize()se de que não incluirá disabledelementos. Pode ser bom para muitos casos, mas pode ser um problema em outros casos.
zed
4

Veja como eu fiz (sem usar jQuery).

No meu caso, eu queria que um elemento específico do formulário não fosse contado, porque foi o elemento que acionou a verificação e, portanto, sempre terá mudado. O elemento excepcional é denominado 'reporting_period' e é codificado na função 'hasFormChanged ()'.

Para testar, faça com que um elemento chame a função "changeReportingPeriod ()", que você provavelmente vai querer dar outro nome.

IMPORTANTE: Você deve chamar setInitialValues ​​() quando os valores forem definidos para seus valores originais (normalmente no carregamento da página, mas não no meu caso).

NOTA: Não afirmo que esta seja uma solução elegante, na verdade não acredito em soluções JavaScript elegantes. Minha ênfase pessoal no JavaScript está na legibilidade, não na elegância estrutural (como se isso fosse possível no JavaScript). Não me preocupo com o tamanho do arquivo ao escrever JavaScript porque é para isso que serve o gzip, e tentar escrever um código JavaScript mais compacto invariavelmente leva a problemas intoleráveis ​​de manutenção. Não peço desculpas, não expresso remorso e me recuso a debatê-lo. É JavaScript. Desculpe, eu tive que deixar isso claro para me convencer de que deveria me preocupar em postar. Seja 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
fonte
4

As alterações de formulário podem ser facilmente detectadas em JavaScript nativo sem 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()pode ser chamado com segurança várias vezes ao longo do ciclo de vida de sua página: Consulte Teste em JSBin


Para navegadores mais antigos que não suportam funções de seta / array mais recentes:

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
fonte
sempre retorna verdadeiro para minha forma
Rich Stone
Você poderia postar seu formulário no JSBin ou no Gist para que possamos ver o seu formulário?
AnthumChris
culpa minha, selecionei o formulário com jquery e não com JS como no seu exemplo. É uma solução js agradável e limpa, sem a sobrecarga de serialização. Obrigado!
Rich Stone
2

Aqui está uma demonstração do método polyfill em JavaScript nativo que usa a FormData()API para detectar entradas de formulário criadas, atualizadas e excluídas. Você pode verificar se algo foi alterado usando HTMLFormElement#isChangede obter um objeto contendo as diferenças de um formulário de redefinição usando HTMLFormElement#changes(presumindo que não estejam mascarados por um nome 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
fonte