Detectando alterações não salvas

94

Eu tenho um requisito para implementar um prompt "Alterações não salvas" em um aplicativo ASP .Net. Se um usuário modificar os controles em um formulário da web e tentar navegar antes de salvar, um prompt deve aparecer avisando-o de que há alterações não salvas e dando-lhe a opção de cancelar e permanecer na página atual. O prompt não deve ser exibido se o usuário não tiver tocado em nenhum dos controles.

Idealmente, gostaria de implementar isso em JavaScript, mas antes de seguir o caminho de rolar meu próprio código, há alguma estrutura existente ou padrões de design recomendados para fazer isso? O ideal é algo que possa ser facilmente reutilizado em várias páginas com alterações mínimas.

Tbreffni
fonte
Estou interessado no mesmo, não apenas para asp.net, mas acho que há uma solução geral por aí.
Michael Neale
A solução que postei abaixo pode ser usada em HTML / Javscript simples. Simplesmente altere o "codebehind" para atributos nas próprias tags HTML.
Wayne,
Sei que estou 5 anos atrasado aqui, mas acho que posso melhorar as soluções anteriores ... Acabei de corrigir e atualizar minha resposta abaixo.
skibulk

Respostas:

96

Usando jQuery:

var _isDirty = false;
$("input[type='text']").change(function(){
  _isDirty = true;
});
// replicate for other input types and selects

Combine com onunload/onbeforeunload métodos conforme necessário.

A partir dos comentários, o seguinte faz referência a todos os campos de entrada, sem duplicar o código:

$(':input').change(function () {

Usar $(":input")refere-se a todos os elementos de entrada, área de texto, seleção e botão.

Aaron Powell
fonte
32
Vote positivamente porque usei essa solução, mas há uma maneira um pouco mais fácil. Se você usar: $ (': input'). Change (function () {então isso funciona para todos os campos de entrada, você não precisa replicar para outros tipos de entrada. $ (": Input") seleciona todas as entradas, textarea , selecionar e elementos de botão.
CodeClimber
5
Acho que isso pode ter problemas no Firefox. Se um usuário modificar o formulário e clicar no botão Atualizar do navegador, a página será recarregada e o Firefox preencherá o formulário com as entradas anteriores do usuário - sem disparar o evento de mudança. Agora o formulário estará sujo, mas o sinalizador não será definido a menos que o usuário faça outra edição.
Oskar Berggren
8
Apenas uma dica. Se o formulário estiver sujo, ainda não significa que ele contém dados realmente alterados. Para uma melhor usabilidade, você deve comparar os dados antigos com os novos;)
Slava Fomin II
Lembre-se de que, changepara entradas de texto, os gatilhos são acionados uma vez ao sair / perder o foco, enquanto os inputgatilhos para cada pressionamento de tecla. (Eu geralmente uso junto changecom JQuery one(), a fim de evitar vários eventos.)
SpazzMarticus
46

Uma peça do quebra-cabeça:

/**
 * Determines if a form is dirty by comparing the current value of each element
 * with its default value.
 *
 * @param {Form} form the form to be checked.
 * @return {Boolean} <code>true</code> if the form is dirty, <code>false</code>
 *                   otherwise.
 */
function formIsDirty(form) {
  for (var i = 0; i < form.elements.length; i++) {
    var element = form.elements[i];
    var type = element.type;
    if (type == "checkbox" || type == "radio") {
      if (element.checked != element.defaultChecked) {
        return true;
      }
    }
    else if (type == "hidden" || type == "password" ||
             type == "text" || type == "textarea") {
      if (element.value != element.defaultValue) {
        return true;
      }
    }
    else if (type == "select-one" || type == "select-multiple") {
      for (var j = 0; j < element.options.length; j++) {
        if (element.options[j].selected !=
            element.options[j].defaultSelected) {
          return true;
        }
      }
    }
  }
  return false;
}

E outro :

window.onbeforeunload = function(e) {
  e = e || window.event;  
  if (formIsDirty(document.forms["someForm"])) {
    // For IE and Firefox
    if (e) {
      e.returnValue = "You have unsaved changes.";
    }
    // For Safari
    return "You have unsaved changes.";
  }
};

Enrole tudo e o que você ganha?

var confirmExitIfModified = (function() {
  function formIsDirty(form) {
    // ...as above
  }

  return function(form, message) {
    window.onbeforeunload = function(e) {
      e = e || window.event;
      if (formIsDirty(document.forms[form])) {
        // For IE and Firefox
        if (e) {
          e.returnValue = message;
        }
        // For Safari
        return message;
      }
    };
  };
})();

confirmExitIfModified("someForm", "You have unsaved changes.");

Provavelmente, você também desejará alterar o registro do beforeunloadmanipulador de eventos para usar LIBRARY_OF_CHOICEo registro de eventos de.

Jonny Buchanan
fonte
No Firefox 9.0.1 não recebo nenhuma mensagem. Tenho uma mensagem padrão que diz "Tem certeza de que deseja sair da página? Alguns dados podem ser perdidos" (Esta é mais ou menos a tradução da mensagem que meu navegador não inglês exibe para mim)
Romain Guidoux
1
Este script é incrível, mas para mim também exibiu uma mensagem de aviso ao enviar meu formulário. Corrigi isso desvinculando o evento em um onClick ( onclick="window.onbeforeunload=null")
Joost
1
Eu gosto da abordagem de comparar propriedades defaultValue etc, em vez de definir um bit sujo. Isso significa que se alguém alterar um campo e depois alterá-lo de volta, o formulário não será relatado como sujo.
thelem
Obrigado, isso é ótimo. Você pode nos dizer como se registrar no jquery? A menos que eu tenha esse script na mesma página html, ele não dispara. Se eu tiver dentro de um arquivo .js externo, ele não funciona.
Shiva Naru
17

Na página .aspx, você precisa de uma função Javascript para saber se as informações do formulário estão "sujas" ou não

<script language="javascript">
    var isDirty = false;

    function setDirty() {
        isDirty = true;
    }

    function checkSave() {
        var sSave;
        if (isDirty == true) {
            sSave = window.confirm("You have some changes that have not been saved. Click OK to save now or CANCEL to continue without saving.");
            if (sSave == true) {
                document.getElementById('__EVENTTARGET').value = 'btnSubmit';
                document.getElementById('__EVENTARGUMENT').value = 'Click';  
                window.document.formName.submit();
            } else {
                 return true;
            }
        }
    }
</script>
<body class="StandardBody" onunload="checkSave()">

e no code-behind, adicione os gatilhos aos campos de entrada, bem como redefina os botões de envio / cancelamento ....

btnSubmit.Attributes.Add("onclick", "isDirty = 0;");
btnCancel.Attributes.Add("onclick", "isDirty = 0;");
txtName.Attributes.Add("onchange", "setDirty();");
txtAddress.Attributes.Add("onchange", "setDirty();");
//etc..
Wayne
fonte
9

O seguinte usa a função onbeforeunload do navegador e jquery para capturar qualquer evento onchange. A TI também procura por quaisquer botões enviar ou redefinir para redefinir o sinalizador, indicando que as alterações ocorreram.

dataChanged = 0;     // global variable flags unsaved changes      

function bindForChange(){    
     $('input,checkbox,textarea,radio,select').bind('change',function(event) { dataChanged = 1})
     $(':reset,:submit').bind('click',function(event) { dataChanged = 0 })
}


function askConfirm(){  
    if (dataChanged){ 
        return "You have some unsaved changes.  Press OK to continue without saving." 
    }
}

window.onbeforeunload = askConfirm;
window.onload = bindForChange;
Colin Houghton
fonte
Oi colin reset não funcionou quando coloquei uma caixa de seleção, inicialmente eu marquei e desmarquei novamente, mas o alerta está sendo disparado, como posso redefinir quando o usuário desmarcou a caixa de seleção
Desenvolvedor
8

Obrigado pelas respostas a todos. Acabei implementando uma solução usando JQuery e o plug-in Protect-Data . Isso me permite aplicar automaticamente o monitoramento a todos os controles em uma página.

No entanto, existem algumas ressalvas, especialmente ao lidar com um aplicativo ASP .Net:

  • Quando um usuário escolhe a opção cancelar, a função doPostBack lançará um erro de JavaScript. Tive que colocar manualmente um try-catch em torno da chamada .submit em doPostBack para suprimi-la.

  • Em algumas páginas, um usuário pode realizar uma ação que executa um postback para a mesma página, mas não é um salvamento. Isso resulta em qualquer reinicialização da lógica do JavaScript, então ele pensa que nada mudou após o postback, quando algo pode ter mudado. Tive que implementar uma caixa de texto oculta que é postada de volta com a página e é usada para conter um valor booleano simples que indica se os dados estão sujos. Isso é persistido em postbacks.

  • Você pode querer que alguns postbacks na página não acionem a caixa de diálogo, como o botão Salvar. Nesse caso, você pode usar JQuery para adicionar uma função OnClick que define window.onbeforeunload como null.

Espero que isso seja útil para qualquer pessoa que precise implementar algo semelhante.

Tbreffni
fonte
7

Solução geral com suporte a vários formulários em uma determinada página ( basta copiar e colar em seu projeto )

$(document).ready(function() {
    $('form :input').change(function() {
        $(this).closest('form').addClass('form-dirty');
    });

    $(window).bind('beforeunload', function() {
        if($('form:not(.ignore-changes).form-dirty').length > 0) {
            return 'You have unsaved changes, are you sure you want to discard them?';
        }
    });

    $('form').bind('submit',function() {
        $(this).closest('form').removeClass('form-dirty');
        return true;
    });
});

Observação: esta solução é combinada com outras soluções para criar uma solução integrada geral.

Recursos:

  • Basta copiar e colar em seu aplicativo.
  • Suporta vários formulários.
  • Você pode estilizar ou fazer ações sujas de forms, desde que tenham a classe "form-dirty".
  • Você pode excluir alguns formulários adicionando a classe 'ignore-changes'.
MhdSyrwan
fonte
Isso funciona bem, mas eu substituo as classes adicionadas pela _isDirtyvariável conforme descrito por Aaron Powell. Não vi a necessidade de adicionar e remover classes do HTML em vez de variáveis ​​no javascript.
Martin
1
A ideia de usar classes html aqui é anexar o atributo 'sujo' a cada formulário, já que você não pode simplesmente usar variáveis ​​para fazer uma solução js / html geral do aplicativo wize.
MhdSyrwan
1
Em outras palavras, usar uma variável funcionará apenas para um único formulário, caso contrário, você precisará de um array de sinalizadores sujos que tornará a solução muito mais complicada.
MhdSyrwan
1
Obrigado por explicar esse ponto. Minha necessidade era apenas de uma única página de formulário.
Martin
6

A solução a seguir funciona para protótipo (testado em FF, IE 6 e Safari). Ele usa um observador de formulário genérico (que dispara o formulário: alterado quando qualquer campo do formulário foi modificado), que você pode usar para outras coisas também.

/* use this function to announce changes from your own scripts/event handlers.
 * Example: onClick="makeDirty($(this).up('form'));"
 */
function makeDirty(form) {
    form.fire("form:changed");
}

function handleChange(form, event) {
    makeDirty(form);
}

/* generic form observer, ensure that form:changed is being fired whenever
 * a field is being changed in that particular for
 */
function setupFormChangeObserver(form) {
    var handler = handleChange.curry(form);

    form.getElements().each(function (element) {
        element.observe("change", handler);
    });
}

/* installs a form protector to a form marked with class 'protectForm' */
function setupProtectForm() {
    var form = $$("form.protectForm").first();

    /* abort if no form */
    if (!form) return;

    setupFormChangeObserver(form);

    var dirty = false;
    form.observe("form:changed", function(event) {
        dirty = true;
    });

    /* submitting the form makes the form clean again */
    form.observe("submit", function(event) {
        dirty = false;
    });

    /* unfortunatly a propper event handler doesn't appear to work with IE and Safari */
    window.onbeforeunload = function(event) {
        if (dirty) {
            return "There are unsaved changes, they will be lost if you leave now.";
        }
    };
}

document.observe("dom:loaded", setupProtectForm);
reto
fonte
6

Aqui está uma solução javascript / jquery que é simples. Ele é responsável por "desfazer" pelo usuário, é encapsulado em uma função para facilitar a aplicação e não falha no envio. Basta chamar a função e passar o ID do seu formulário.

Esta função serializa o formulário uma vez quando a página é carregada e novamente antes de o usuário sair da página. Se os dois estados do formulário forem diferentes, o prompt será mostrado.

Experimente: http://jsfiddle.net/skibulk/Ydt7Y/

function formUnloadPrompt(formSelector) {
    var formA = $(formSelector).serialize(), formB, formSubmit = false;

    // Detect Form Submit
    $(formSelector).submit( function(){
        formSubmit = true;
    });

    // Handle Form Unload    
    window.onbeforeunload = function(){
        if (formSubmit) return;
        formB = $(formSelector).serialize();
        if (formA != formB) return "Your changes have not been saved.";
    };
}

$(function(){
    formUnloadPrompt('form');
});
skibulk
fonte
2
Tentei sua abordagem. Ele funciona para Chrome e Firefox, mas não pode funcionar para Safari no Ipad Mini.
Dimitry K
4

Recentemente, contribuí para um plugin jQuery de código aberto chamado dirtyForms .

O plug-in é projetado para funcionar com HTML adicionado dinamicamente, suporta vários formulários, pode suportar virtualmente qualquer estrutura de diálogo, retorna ao diálogo antes de descarregar do navegador, tem uma estrutura auxiliar plugável para oferecer suporte à obtenção de status sujo de editores personalizados (um plug-in tinyMCE está incluído) , funciona em iframes e o status sujo pode ser definido ou redefinido à vontade.

https://github.com/snikch/jquery.dirtyforms

NightOwl888
fonte
4

Detectar alterações de formulário usando jQuery é muito simples:

var formInitVal = $('#formId').serialize(); // detect form init value after form is displayed

// check for form changes
if ($('#formId').serialize() != formInitVal) {
    // show confirmation alert
}
v.babak
fonte
2

Eu expandi a sugestão de Slace acima, para incluir a maioria dos elementos editáveis ​​e também excluir certos elementos (com um estilo CSS chamado "srSearch" aqui) de causar o sinalizador sujo a ser definido.

<script type="text/javascript">
        var _isDirty = false;
        $(document).ready(function () {            

            // Set exclude CSS class on radio-button list elements
            $('table.srSearch input:radio').addClass("srSearch");

            $("input[type='text'],input[type='radio'],select,textarea").not(".srSearch").change(function () {
                _isDirty = true;
            });
        });

        $(window).bind('beforeunload', function () {
            if (_isDirty) {
                return 'You have unsaved changes.';
            }
        });        

PeterX
fonte
2
      var unsaved = false;
    $(":input").change(function () {         
        unsaved = true;
    });

    function unloadPage() {         
        if (unsaved) {             
          alert("You have unsaved changes on this page. Do you want to leave this page and discard your changes or stay on this page?");
        }
    } 

window.onbeforeunload = unloadPage;

Ankit
fonte
0

Um método , usando arrays para manter as variáveis ​​para que as alterações possam ser rastreadas.

Aqui está um método muito simples para detectar mudanças , mas o resto não é tão elegante.

Outro método bastante simples e pequeno, do Farfetched Blog :

<body onLoad="lookForChanges()" onBeforeUnload="return warnOfUnsavedChanges()">
<form>
<select name=a multiple>
 <option value=1>1
 <option value=2>2
 <option value=3>3
</select>
<input name=b value=123>
<input type=submit>
</form>

<script>
var changed = 0;
function recordChange() {
 changed = 1;
}
function recordChangeIfChangeKey(myevent) {
 if (myevent.which && !myevent.ctrlKey && !myevent.ctrlKey)
  recordChange(myevent);
}
function ignoreChange() {
 changed = 0;
}
function lookForChanges() {
 var origfunc;
 for (i = 0; i < document.forms.length; i++) {
  for (j = 0; j < document.forms[i].elements.length; j++) {
   var formField=document.forms[i].elements[j];
   var formFieldType=formField.type.toLowerCase();
   if (formFieldType == 'checkbox' || formFieldType == 'radio') {
    addHandler(formField, 'click', recordChange);
   } else if (formFieldType == 'text' || formFieldType == 'textarea') {
    if (formField.attachEvent) {
     addHandler(formField, 'keypress', recordChange);
    } else {
     addHandler(formField, 'keypress', recordChangeIfChangeKey);
    }
   } else if (formFieldType == 'select-multiple' || formFieldType == 'select-one') {
    addHandler(formField, 'change', recordChange);
   }
  }
  addHandler(document.forms[i], 'submit', ignoreChange);
 }
}
function warnOfUnsavedChanges() {
 if (changed) {
  if ("event" in window) //ie
   event.returnValue = 'You have unsaved changes on this page, which will be discarded if you leave now. Click "Cancel" in order to save them first.';
  else //netscape
   return false;
 }
}
function addHandler(target, eventName, handler) {
 if (target.attachEvent) {
  target.attachEvent('on'+eventName, handler);
 } else {
  target.addEventListener(eventName, handler, false);
 }
}
</script>
Adam Davis
fonte
0

No IE, document.ready não funcionará corretamente, ele atualizará os valores de entrada.

portanto, precisamos vincular o evento load dentro da função document.ready que também será usada no navegador IE.

abaixo está o código que você deve colocar dentro da função document.ready.

 $(document).ready(function () {
   $(window).bind("load", function () { 
    $("input, select").change(function () {});
   });
});
Krupall
fonte