Detectar cambios no guardados

94

Tengo el requisito de implementar un mensaje "Cambios no guardados" en una aplicación ASP .Net. Si un usuario modifica los controles en un formulario web e intenta salir antes de guardar, debería aparecer un mensaje advirtiéndole que tiene cambios sin guardar y darle la opción de cancelar y permanecer en la página actual. El mensaje no debería aparecer si el usuario no ha tocado ninguno de los controles.

Idealmente, me gustaría implementar esto en JavaScript, pero antes de seguir el camino de implementar mi propio código, ¿existen marcos existentes o patrones de diseño recomendados para lograr esto? Idealmente, me gustaría algo que se pueda reutilizar fácilmente en varias páginas con cambios mínimos.

tbreffni
fuente
Estoy interesado en lo mismo, pero no para asp.net, pero creo que existe una solución general.
Michael Neale
La solución que publiqué a continuación se puede usar en HTML / Javscript simple. Simplemente cambie el "código subyacente" a atributos en las propias etiquetas HTML.
Wayne
Sé que llego 5 años tarde aquí, pero creo que puedo mejorar las soluciones anteriores ... Acabo de arreglar y actualizar mi respuesta a continuación.
skibulk

Respuestas:

96

Usando jQuery:

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

Combine con los métodos onunload/ onbeforeunloadsegún sea necesario.

A partir de los comentarios, lo siguiente hace referencia a todos los campos de entrada, sin duplicar el código:

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

El uso se $(":input")refiere a todos los elementos de entrada, área de texto, selección y botón.

Aaron Powell
fuente
32
Voto porque utilicé esta solución, pero hay una forma un poco más fácil. Si usa: $ (': input'). Change (function () {entonces eso funciona para todos los campos de entrada, no necesita replicar para otros tipos de entrada. $ (": Input") selecciona todas las entradas, textarea , seleccionar y botones de elementos.
CodeClimber
5
Creo que esto puede tener problemas en Firefox. Si un usuario modifica el formulario, luego hace clic en el botón Actualizar del navegador, la página se recargará y Firefox completará el formulario con las entradas anteriores del usuario, sin activar el evento de cambio. Ahora el formulario estará sucio, pero la bandera no se establecerá a menos que el usuario realice otra edición.
Oskar Berggren
8
Solo una pista. Si el formulario está sucio, no significa que contenga datos realmente modificados. Para una mejor usabilidad, debe comparar los datos antiguos con los nuevos;)
Slava Fomin II
Tenga en cuenta que changepara las entradas de texto se activa una vez al dejar / perder el enfoque, mientras que se inputactiva por cada pulsación de tecla. (Por lo general, lo uso junto changecon JQuery one(), para evitar múltiples eventos).
SpazzMarticus
46

Una pieza del rompecabezas:

/**
 * 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;
}

Y otro :

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.";
  }
};

Envuélvelo todo y ¿qué obtienes?

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.");

Probablemente también desee cambiar el registro del beforeunloadcontrolador de eventos para usar LIBRARY_OF_CHOICEel registro de eventos.

Jonny Buchanan
fuente
En Firefox 9.0.1 no recibo ningún mensaje. Tengo un mensaje predeterminado que dice "¿Estás seguro de que quieres salir de la página? Es posible que se pierdan algunos datos" (esta es más o menos la traducción del mensaje que me muestra mi navegador en otro idioma)
Romain Guidoux
1
Este script es increíble, pero para mí también mostró el mensaje de advertencia al enviar mi formulario. Solucioné esto al desvincular el evento en un onClick ( onclick="window.onbeforeunload=null")
Joost
1
Me gusta el enfoque de comparar las propiedades de defaultValue, etc. en lugar de establecer un bit sucio. Esto significa que si alguien cambia un campo y luego lo vuelve a cambiar, el formulario no se notificará como sucio.
thelem
Gracias, esto es genial. ¿Puede decirnos cómo registrarse en jquery? A menos que tenga este script en la misma página html, no se dispara. Si lo tengo dentro de un archivo .js externo, no funciona.
Shiva Naru
17

En la página .aspx, necesita una función de Javascript para saber si la información del formulario está "sucia" o no

<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()">

y en el código subyacente, agregue los desencadenadores a los campos de entrada y restablezca los botones de envío / cancelación ...

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

Lo siguiente utiliza la función onbeforeunload del navegador y jquery para capturar cualquier evento onchange. TI también busca cualquier botón de envío o reinicio para reiniciar el indicador que indica que se han producido cambios.

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
fuente
Hola, el restablecimiento de Colin no funcionó cuando coloqué una casilla de verificación, inicialmente la marqué y la desmarqué de nuevo, pero la alerta se activa, ¿cómo puedo restablecer cuando el usuario desmarcó la casilla de verificación?
Desarrollador
8

Gracias por las respuestas a todos. Terminé implementando una solución usando JQuery y el complemento Protect-Data . Esto me permite aplicar automáticamente la supervisión a todos los controles de una página.

Sin embargo, hay algunas advertencias, especialmente cuando se trata de una aplicación ASP .Net:

  • Cuando un usuario elige la opción cancelar, la función doPostBack arrojará un error de JavaScript. Tuve que poner manualmente un try-catch alrededor de la llamada .submit dentro de doPostBack para suprimirlo.

  • En algunas páginas, un usuario puede realizar una acción que realiza una devolución de datos en la misma página, pero no es un guardado. Esto da como resultado el restablecimiento de la lógica de JavaScript, por lo que cree que nada ha cambiado después de la devolución cuando algo puede haber cambiado. Tuve que implementar un cuadro de texto oculto que se vuelve a publicar con la página y se usa para contener un valor booleano simple que indica si los datos están sucios. Esto persiste en las devoluciones de datos.

  • Es posible que desee que algunas devoluciones de datos en la página no activen el cuadro de diálogo, como un botón Guardar. En este caso, puede usar JQuery para agregar una función OnClick que establece window.onbeforeunload en nulo.

Con suerte, esto es útil para cualquier otra persona que tenga que implementar algo similar.

tbreffni
fuente
7

Solución general que admite varios formularios en una página determinada ( solo copie y pegue en su proyecto )

$(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;
    });
});

Nota: Esta solución se combina con las soluciones de otros para crear una solución integrada general.

caracteristicas:

  • Simplemente copie y pegue en su aplicación.
  • Soporta múltiples formas.
  • Puede diseñar o convertir acciones en formularios sucios, ya que tienen la clase "formulario-sucio".
  • Puede excluir algunos formularios agregando la clase 'ignorar-cambios'.
MhdSyrwan
fuente
Esto funciona bien, pero reemplazo las clases agregadas con la _isDirtyvariable descrita por Aaron Powell. No vi la necesidad de agregar y eliminar clases del HTML en lugar de variables en el javascript.
Martin
1
La idea de usar clases html aquí es adjuntar el atributo 'sucio' con cada formulario, ya que no puede simplemente usar variables para hacer una solución general de application-wize js / html.
MhdSyrwan
1
En otras palabras, el uso de una variable funcionará solo para un solo formulario; de lo contrario, necesitará una matriz de indicadores sucios que harán que la solución sea mucho más complicada.
MhdSyrwan
1
Gracias por explicar ese punto. Mi necesidad era solo con una sola página de formulario.
Martin
6

La siguiente solución funciona para prototipos (probada en FF, IE 6 y Safari). Utiliza un observador de formulario genérico (que dispara formulario: cambiado cuando se ha modificado cualquier campo del formulario), que también puede usar para otras cosas.

/* 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);
réplica
fuente
6

Aquí hay una solución javascript / jquery que es simple. Tiene en cuenta las "deshacer" por parte del usuario, está encapsulado dentro de una función para facilitar la aplicación y no falla en el envío. Simplemente llame a la función y pase el ID de su formulario.

Esta función serializa el formulario una vez cuando se carga la página y nuevamente antes de que el usuario abandone la página. Si los dos estados del formulario son diferentes, se muestra el mensaje.

Pruébelo: 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
fuente
2
Probé tu enfoque. Funciona para Chrome y Firefox, pero no puede hacerlo funcionar para Safari en Ipad Mini ..
Dimitry K
4

Recientemente contribuí a un complemento jQuery de código abierto llamado dirtyForms .

El complemento está diseñado para funcionar con HTML agregado dinámicamente, admite múltiples formularios, puede admitir prácticamente cualquier marco de diálogo, recurre al navegador antes de descargar el cuadro de diálogo, tiene un marco auxiliar conectable para admitir la obtención del estado sucio de editores personalizados (se incluye un complemento tinyMCE) , funciona dentro de iFrames, y el estado sucio se puede configurar o restablecer a voluntad.

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

NightOwl888
fuente
4

Detectar cambios de formulario con jQuery es muy simple:

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

Amplié la sugerencia de Slace anterior, para incluir la mayoría de los elementos editables y también excluir ciertos elementos (con un estilo CSS llamado "srSearch" aquí) para que no provoquen que se establezca la bandera sucia.

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

Un método , usar matrices para contener las variables para que se pueda rastrear los cambios.

Este es un método muy simple para detectar cambios , pero el resto no es tan elegante.

Otro método que es bastante simple y pequeño, de 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
fuente
0

En IE document.ready no funcionará correctamente, actualizará los valores de entrada.

por lo que debemos vincular el evento de carga dentro de la función document.ready que también se encargará del navegador IE.

A continuación se muestra el código que debe poner dentro de la función document.ready.

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