Pregunta

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

Idealmente, me gustaría implementar esto en JavaScript, pero antes de continuar con mi propio código, ¿existen marcos de trabajo o patrones de diseño recomendados para lograrlo? Idealmente, me gustaría algo que pueda reutilizarse fácilmente en varias páginas con cambios mínimos.

¿Fue útil?

Solución

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 / onbeforeunload según sea necesario.

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

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

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

Otros consejos

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 querrá cambiar el registro del controlador de eventos beforeunload para usar el registro de eventos de LIBRARY_OF_CHOICE .

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

<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, agregue los desencadenantes a los campos de entrada, así como los reinicios en 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..

Lo siguiente utiliza la función de descarga antes de la descarga del navegador y la jQuery para capturar cualquier evento onchange. IT también busca los botones de enviar o restablecer para restablecer 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;

Gracias por las respuestas a todos. Terminé implementando una solución utilizando JQuery y el complemento Protect-Data . Esto me permite aplicar automáticamente el monitoreo a todos los controles en 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 de cancelación, la función doPostBack generará 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 a la misma página, pero no es una operación de guardar. Esto provoca que se restablezca la lógica de JavaScript, por lo que cree que nada ha cambiado después de la devolución cuando algo puede tener. Tuve que implementar un cuadro de texto oculto que se vuelve a publicar con la página, y se usa para mantener un valor booleano simple que indica si los datos están sucios. Esto se conserva 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 to null.

Esperemos que esto sea útil para cualquier otra persona que tenga que implementar algo similar.

La siguiente solución funciona para un prototipo (probado en FF, IE 6 y Safari). Utiliza un observador de formulario genérico (que activa el formulario: se modifica cuando se modifica 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);

Aquí hay una solución javascript / jquery que es simple. Cuenta para " undos " por el 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 que 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 indicador.

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');
});

Solución general Compatibilidad con múltiples formularios en una página determinada ( Simplemente 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 de las soluciones de otros para crear una solución general integrada.

Características:

  • Solo tienes que copiar y pegar en tu aplicación.
  • Admite formularios múltiples.
  • Puedes aplicar estilos o hacer acciones sucias, ya que tienen la clase "forma sucia".
  • Puede excluir algunos formularios agregando la clase 'ignore-changes'.

Hace poco 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, recae en el navegador antes de descargar el diálogo, tiene un marco de ayuda conectable para admitir el estado sucio de editores personalizados (un complemento de tinyMCE está incluido), funciona dentro de iFrames, y el estado sucio se puede configurar o restablecer a voluntad.

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

Detectar cambios de formulario con el uso de 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
}

Amplié la sugerencia de Slace anterior, para incluir la mayoría de los elementos editables y también la exclusión de ciertos elementos (con un estilo CSS llamado "srSearch" aquí) para que no 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.';
            }
        });        

      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;

Esto es exactamente lo que el complemento Fleegix.js fleegix.form.diff ( http://js.fleegix.org/plugins/form/diff ) fue creado para. Serialice el estado inicial del formulario en carga usando fleegix.form.toObject ( http://js.fleegix.org/ref#fleegix .form.toObject ) y guárdelo en una variable, luego compárelo con el estado actual usando fleegix.form.diff al descargar. Fácil como pastel.

Un método , usando matrices para mantener las variables para poder rastrear los cambios.

Aquí hay 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 Blog descalzo :

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

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

por lo tanto, debemos vincular el evento de carga dentro de la función document.ready que también se manejará para el navegador IE.

debajo está el código que debe colocar dentro de la función document.ready.

 $(document).ready(function () {
   $(window).bind("load", function () { 
    $("input, select").change(function () {});
   });
});
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top