web-dev-qa-db-fra.com

Détection des modifications non enregistrées

J'ai une obligation d'implémenter une invite "Modifications non enregistrées" dans une application ASP .Net. Si un utilisateur modifie les contrôles sur un formulaire Web et tente de s'éloigner avant d'enregistrer, une invite doit apparaître les avertissant qu'ils ont des modifications non enregistrées et leur donnant la possibilité d'annuler et de rester sur la page actuelle. L'invite ne devrait pas s'afficher si l'utilisateur n'a touché aucun des contrôles.

Idéalement, j'aimerais implémenter cela en JavaScript, mais avant de commencer à rouler mon propre code, existe-t-il des cadres existants ou des modèles de conception recommandés pour y parvenir? Idéalement, j'aimerais quelque chose qui peut facilement être réutilisé sur plusieurs pages avec un minimum de changements.

88
tbreffni

Utilisation de jQuery:

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

Combinez avec les méthodes onunload/onbeforeunload selon vos besoins.

À partir des commentaires, les éléments suivants font référence à tous les champs de saisie, sans dupliquer le code:

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

L'utilisation de $(":input") fait référence à tous les éléments d'entrée, de zone de texte, de sélection et de bouton.

91
Aaron Powell

Une pièce du puzzle:

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

Et un autre :

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

Récapitulez tout, et qu'obtenez-vous?

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

Vous souhaiterez probablement également modifier l'enregistrement du gestionnaire d'événements beforeunload pour utiliser l'enregistrement d'événements de LIBRARY_OF_CHOICE.

41
Jonny Buchanan

Dans la page .aspx, vous avez besoin d'une fonction Javascript pour dire si les informations du formulaire sont "sales" ou non

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

et dans le codebehind, ajoutez les déclencheurs aux champs de saisie ainsi que les réinitialisations sur les boutons de soumission/annulation ....

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

Ce qui suit utilise la fonction onbeforeunload du navigateur et jquery pour capturer tout événement onchange. Le service informatique recherche également les boutons d'envoi ou de réinitialisation pour réinitialiser l'indicateur indiquant que des modifications ont eu lieu.

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;
9
Colin Houghton

Merci à tous pour vos réponses. J'ai fini par implémenter une solution utilisant JQuery et le plug-in Protect-Data . Cela me permet d'appliquer automatiquement la surveillance à tous les contrôles d'une page.

Il existe cependant quelques mises en garde, en particulier lorsqu'il s'agit d'une application ASP .Net:

  • Lorsqu'un utilisateur choisit l'option d'annulation, la fonction doPostBack générera une erreur JavaScript. J'ai dû mettre manuellement un try-catch autour de l'appel .submit dans doPostBack pour le supprimer.

  • Sur certaines pages, un utilisateur peut effectuer une action qui effectue une publication sur la même page, mais ce n'est pas une sauvegarde. Cela entraîne la réinitialisation de toute logique JavaScript, de sorte qu'il pense que rien n'a changé après la publication lorsque quelque chose peut avoir. J'ai dû implémenter une zone de texte cachée qui est publiée avec la page et utilisée pour contenir une valeur booléenne simple indiquant si les données sont sales. Cela persiste à travers les publications.

  • Vous souhaiterez peut-être que certaines publications sur la page ne déclenchent pas la boîte de dialogue, comme un bouton Enregistrer. Dans ce cas, vous pouvez utiliser JQuery pour ajouter une fonction OnClick qui définit window.onbeforeunload sur null.

J'espère que cela est utile pour toute autre personne qui doit mettre en œuvre quelque chose de similaire.

7
tbreffni

La solution suivante fonctionne pour le prototype (testé dans FF, IE 6 et Safari). Elle utilise un observateur de formulaire générique (qui déclenche form: changé lorsque des champs du formulaire ont été modifiés), qui vous pouvez également l'utiliser pour d'autres choses.

/* 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);
6
reto

Solution générale prenant en charge plusieurs formulaires dans une page donnée (il suffit de copier-coller dans votre projet)

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

Remarque: Cette solution est combinée à partir d'autres solutions pour créer une solution intégrée générale.

Fonctionnalités:

  • Copiez et collez simplement dans votre application.
  • Prend en charge plusieurs formulaires.
  • Vous pouvez styliser ou rendre des actions des formulaires sales, car ils ont la classe "form-dirty".
  • Vous pouvez exclure certains formulaires en ajoutant la classe 'ignore-changes'.
6
MhdSyrwan

Voici une solution javascript/jquery qui est simple. Il tient compte des "annulations" par l'utilisateur, il est encapsulé dans une fonction pour faciliter l'application, et il ne se trompe pas lors de la soumission. Appelez simplement la fonction et passez l'ID de votre formulaire.

Cette fonction sérialise le formulaire une fois lorsque la page est chargée, puis de nouveau avant que l'utilisateur ne quitte la page. Si les deux états du formulaire sont différents, l'invite s'affiche.

Essayez-le: 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');
});
6
skibulk

Détecter les changements de formulaire avec jQuery est très simple:

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

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

J'ai récemment contribué à un plugin jQuery open source appelé dirtyForms .

Le plugin est conçu pour fonctionner avec du HTML ajouté dynamiquement, prend en charge plusieurs formulaires, peut prendre en charge pratiquement n'importe quel cadre de dialogue, revient au navigateur avant de télécharger la boîte de dialogue, dispose d'un cadre d'aide enfichable pour prendre en charge l'obtention du statut sale des éditeurs personnalisés (un minuscule plugin MCE est inclus) , fonctionne dans iFrames, et le statut sale peut être défini ou réinitialisé à volonté.

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

4
NightOwl888

J'ai développé la suggestion de Slace ci-dessus, pour inclure la plupart des éléments modifiables et également pour exclure certains éléments (avec un style CSS appelé "srSearch" ici) de provoquer la définition du drapeau sale.

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

2
Ankit

C'est exactement ce que le plugin Fleegix.js fleegix.form.diff ( http://js.fleegix.org/plugins/form/diff ) a été créé pour. Sérialiser l'état initial du formulaire lors du chargement à l'aide de fleegix.form.toObject ( http://js.fleegix.org/ref#fleegix.form.toObject ) et l'enregistrer dans une variable, puis comparer avec l'état actuel en utilisant fleegix.form.diff au déchargement. C'est de la tarte.

1
mde

Dans IE document.ready ne fonctionnera pas correctement, il mettra à jour les valeurs d'entrée.

nous devons donc lier l'événement de chargement à l'intérieur de la fonction document.ready qui gérera également le navigateur IE.

ci-dessous est le code que vous devez mettre dans la fonction document.ready.

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

ne méthode , en utilisant des tableaux pour contenir les variables afin que les changements puissent être suivis.

Voici une méthode très simple pour détecter les changements , mais le reste n'est pas aussi élégant.

Une autre méthode qui est assez simple et petite, 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>
0
Adam Davis