Interceptar evento de saída de página

117

Ao editar uma página em meu sistema, um usuário pode decidir navegar para outro site e, ao fazer isso, pode perder todas as edições que não salvou.

Gostaria de interceptar qualquer tentativa de ir para outra página e solicitar que o usuário tenha certeza de que deseja que isso aconteça, pois pode perder seu trabalho atual.

O Gmail faz isso de maneira muito semelhante. Por exemplo, escreva um novo e-mail, comece a digitar no corpo da mensagem e insira um novo local na barra de endereço (digamos twitter.com ou algo assim). Ele solicitará a pergunta "Tem certeza?"

Idéias como replicar isso? Estou visando o IE8, mas gostaria de ser compatível com FF e Chrome também.

Chris Ballance
fonte

Respostas:

154

Semelhante à resposta de Ghommey, mas também suporta versões antigas do IE e Firefox.

window.onbeforeunload = function (e) {
  var message = "Your confirmation message goes here.",
  e = e || window.event;
  // For IE and Firefox
  if (e) {
    e.returnValue = message;
  }

  // For Safari
  return message;
};
Eli Gray
fonte
1
Você poderia explicar a primeira linha (var message = ...)? Eu não sei o que aquela vírgula e a segunda expressão estão fazendo.
mtmurdock
7
@mtmurdock Isso é apenas sintaxe Javascript para declarar várias variáveis. var foo, bar;é o mesmo quevar foo; var bar;
T Nguyen
4
@mtmurdock, A segunda declaração " var e = e || window.event;" significa conjunto e igual ao parâmetro e (se for verdadeiro) ou window.event se não for verdadeiro. Não será verdade se o parâmetro e for nulo.
T Nguyen
3
@Blieque addEventListener não é compatível com o IE8. Não edite as respostas das pessoas sem nem mesmo ler a pergunta.
Eli Gray de
2
Não funciona mais no Chrome (obsoleto): developers.google.com/web/updates/2016/04/…
Micha Schwab
25

Veja este artigo. O recurso que você está procurando é o onbeforeunload

Código de amostra:

  <script language="JavaScript">
  window.onbeforeunload = confirmExit;
  function confirmExit()
  {
    return "You have attempted to leave this page.  If you have made any changes to the fields without clicking the Save button, your changes will be lost.  Are you sure you want to exit this page?";
  }
</script>
Jantimon
fonte
2
Existe alguma maneira de identificar que o usuário optou pela opção "Sair desta página" em vez de "Permanecer nesta página"?
Shilpa Soni de
@ShilpaSoni você obteria em breve um evento de descarregamento subsequente, developer.mozilla.org/en-US/docs/Web/API/Window/unload_event , mas não acho que haja uma maneira síncrona.
scipilot
6

Em vez de um pop-up de confirmação irritante, seria bom deixar um pouco (questão de milissegundos) para gerenciar com êxito a postagem dos dados não salvos no servidor, que gerenciei para meu site escrevendo um texto fictício no console como este:

window.onbeforeunload=function(e){
  // only take action (iterate) if my SCHEDULED_REQUEST object contains data        
  for (var key in SCHEDULED_REQUEST){   
    postRequest(SCHEDULED_REQUEST); // post and empty SCHEDULED_REQUEST object
    for (var i=0;i<1000;i++){
      // do something unnoticable but time consuming like writing a lot to console
      console.log('buying some time to finish saving data'); 
    };
    break;
  };
}; // no return string --> user will leave as normal but data is send to server

Editar: Veja também Synchronous_AJAX e como fazer isso com jquery

Remi
fonte
Eu gosto dessa alternativa ao pop-up. Obrigado pela sugestão, Remi.
Chris Ballance
2
isso não faz absolutamente nenhum sentido -1. javascript não está encadeado o que você está fazendo é fazer um postRequest e então pausar o cálculo do site sem permitir que ninguém faça nada (incluindo o seu postRequest, que entretanto já foi enviado antes da pausa do cálculo).
fmsf
E é exatamente por isso que funciona: de fato, a pós-solicitação foi enviada antes da pausa de cálculo, mas esse pequeno bloqueio da página antes de ser deixada garante tempo suficiente para o cliente do navegador (não javascript) realmente terminar de enviar essa solicitação corretamente . Reparei que quando apenas envio o pedido (sem bloqueio) e saio da página imediatamente a seguir, o pedido nem sempre é enviado.
Remi
6
Eu não usaria isso na produção. console.log não é um navegador cruzado; cada postRequest sofre com o atraso dos anteriores (o que também significa que a página ficará travada durante o loop * de solicitações programadas); as solicitações não são iniciadas em paralelo; você não tem a garantia de realmente enviar seus pedidos. Se eu fosse forçado a enfrentar esse problema, faria uma solicitação de ajax síncrona, se houver apenas uma solicitação. Com várias solicitações, eu usaria solicitações assíncronas de ajax e um loop que verifica os resultados (sucesso ou erro) de retornos de chamada das solicitações.
framp
Eu não sabia que o console.log não é um navegador cruzado e uma solicitação Ajax síncrona deveria ser a solução preferida (para uma única solicitação). O mencionado SCHEDULED_REQUESTé um objeto que acumula qualquer informação não urgente que deva ser enviada ao servidor, como um buffer de dados, juntando efetivamente várias solicitações a uma (claro que o url dessas solicitações é o mesmo). Usar uma solicitação Ajax síncrona antes de descarregar deve resolver o problema entre navegadores, como você disse. Obrigado pelo seu valioso comentário!
Remi,
0

Tenho usuários que não preencheram todos os dados necessários.

<cfset unloadCheck=0>//a ColdFusion precheck in my page generation to see if unload check is needed
var erMsg="";
$(document).ready(function(){
<cfif q.myData eq "">
    <cfset unloadCheck=1>
    $("#myInput").change(function(){
        verify(); //function elsewhere that checks all fields and populates erMsg with error messages for any fail(s)
        if(erMsg=="") window.onbeforeunload = null; //all OK so let them pass
        else window.onbeforeunload = confirmExit(); //borrowed from Jantimon above;
    });
});
<cfif unloadCheck><!--- if any are outstanding, set the error message and the unload alert --->
    verify();
    window.onbeforeunload = confirmExit;
    function confirmExit() {return "Data is incomplete for this Case:"+erMsg;}
</cfif>
Gordon
fonte