Détection des modifications non enregistrées

94

J'ai une exigence pour implémenter une invite «Changements non enregistrés» dans une application ASP .Net. Si un utilisateur modifie des contrôles sur un formulaire Web et tente de s'en éloigner avant d'enregistrer, une invite doit apparaître pour l'avertir qu'il a des modifications non enregistrées et lui donner la possibilité d'annuler et de rester sur la page en cours. L'invite ne doit pas s'afficher si l'utilisateur n'a touché aucune des commandes.

Idéalement, j'aimerais l'implémenter en JavaScript, mais avant de suivre la voie du déploiement de 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 puisse facilement être réutilisé sur plusieurs pages avec des modifications minimes.

tbreffni
la source
Je suis intéressé par la même chose, mais pas pour asp.net, mais je pense qu'il existe une solution générale.
Michael Neale
La solution que j'ai publiée ci-dessous peut être utilisée en HTML / Javscript. Changez simplement le "codebehind" en attributs dans les balises HTML elles-mêmes.
Wayne
Je sais que j'ai 5 ans de retard ici, mais je pense que je peux améliorer les solutions précédentes ... J'ai juste corrigé et mis à jour ma réponse ci-dessous.
skibulk

Réponses:

96

Utilisation de jQuery:

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

Combinez avec onunload/ onbeforeunloadmethods au besoin.

À partir des commentaires, ce qui suit fait référence à tous les champs de saisie, sans dupliquer le code:

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

Utiliser $(":input")fait référence à tous les éléments input, textarea, select et button.

Aaron Powell
la source
32
Upvote parce que j'ai utilisé cette solution, mais il existe un moyen un peu plus simple. Si vous utilisez: $ (': input'). Change (function () {alors cela fonctionne pour tous les champs d'entrée, vous n'avez pas besoin de répliquer pour d'autres types d'entrée. $ (": Input") sélectionne toutes les entrées, textarea , sélectionnez et touchez les éléments.
CodeClimber
5
Je pense que cela peut avoir des problèmes dans Firefox. Si un utilisateur modifie le formulaire, puis clique sur le bouton Actualiser du navigateur, la page sera rechargée et Firefox remplira le formulaire avec les entrées précédentes de l'utilisateur - sans déclencher l'événement de changement. Maintenant, le formulaire sera sale, mais le drapeau ne sera pas défini à moins que l'utilisateur ne fasse une autre modification.
Oskar Berggren
8
Juste un indice. Si le formulaire est sale, cela ne signifie toujours pas qu'il contient des données réellement modifiées. Pour une meilleure convivialité, vous devez comparer les anciennes données avec les nouvelles;)
Slava Fomin II
Gardez à l'esprit que changepour les entrées de texte, les déclencheurs se déclenchent une fois en quittant / perdant le focus, alors que les inputdéclencheurs à chaque frappe. (Je l'utilise généralement changeavec JQuery one(), afin d'éviter plusieurs événements.)
SpazzMarticus
46

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

Enveloppez tout cela 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 voudrez probablement également modifier l'enregistrement du beforeunloadgestionnaire d'événements pour utiliser LIBRARY_OF_CHOICEl'enregistrement d'événements de.

Jonny Buchanan
la source
Dans Firefox 9.0.1, je ne reçois aucun message. J'ai un message par défaut disant "Êtes-vous sûr de vouloir quitter la page? Certaines données pourraient être perdues" (Il s'agit plus ou moins de la traduction du message que mon navigateur non anglais m'affiche)
Romain Guidoux
1
Ce script est génial, mais pour moi, il a également affiché le message d'avertissement lors de la soumission de mon formulaire. J'ai corrigé cela en dissociant l'événement dans un onClick ( onclick="window.onbeforeunload=null")
Joost
1
J'aime l'approche consistant à comparer les propriétés defaultValue, etc. plutôt que de définir un peu sale. Cela signifie que si quelqu'un modifie un champ, puis le modifie à nouveau, le formulaire ne sera pas signalé comme sale.
thelem le
Merci, c'est génial. Pouvez-vous nous dire comment vous inscrire à jquery? Sauf si j'ai ce script sur la même page html, il ne se déclenche pas. Si je l'ai dans un fichier .js externe, cela ne fonctionne pas.
Shiva Naru
17

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 / d'annulation ....

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

Ce qui suit utilise la fonction onbeforeunload et jquery du navigateur 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;
Colin Houghton
la source
Salut la réinitialisation de Colin ne fonctionnait pas lorsque j'ai placé une case à cocher, au départ, je l'ai cochée et décochée à nouveau, mais l'alerte est déclenchée, comment puis-je réinitialiser lorsque l'utilisateur décoche la case
Développeur
8

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 y a 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 lèvera 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 n'est pas une sauvegarde. Cela entraîne la réinitialisation de toute logique JavaScript, donc il pense que rien n'a changé après la publication lorsque quelque chose a pu avoir. J'ai dû implémenter une zone de texte cachée qui est publiée avec la page et est utilisée pour contenir une simple valeur booléenne indiquant si les données sont sales. Cela persiste dans 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 sera utile pour quiconque doit mettre en œuvre quelque chose de similaire.

tbreffni
la source
7

Solution générale Prise en charge de plusieurs formulaires dans une page donnée (il suffit de copier et 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 de solutions d'autres 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 les actions sales, car elles ont la classe "form-dirty".
  • Vous pouvez exclure certains formulaires en ajoutant la classe 'ignore-changes'.
MhdSyrwan
la source
Cela fonctionne bien, mais je remplace les classes ajoutées par la _isDirtyvariable décrite par Aaron Powell. Je n'ai pas vu la nécessité d'ajouter et de supprimer des classes du HTML au lieu de variables dans le javascript.
Martin le
1
L'idée d'utiliser des classes html ici est d'attacher l'attribut «dirty» à chaque formulaire puisque vous ne pouvez pas simplement utiliser des variables pour créer une solution générale application-wize js / html.
MhdSyrwan
1
En d'autres termes, l'utilisation d'une variable ne fonctionnera que pour un seul formulaire, sinon vous aurez besoin d'un tableau d'indicateurs sales qui rendront la solution beaucoup plus compliquée.
MhdSyrwan
1
Merci d'avoir expliqué ce point. Mon besoin était seulement avec une seule page de formulaire.
Martin le
6

La solution suivante fonctionne pour le prototype (testé dans FF, IE 6 et Safari). Il utilise un observateur de formulaire générique (qui déclenche le formulaire: changé lorsque des champs du formulaire ont été modifiés), que vous pouvez également 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);
reto
la source
6

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 transmettez l'ID de votre formulaire.

Cette fonction sérialise le formulaire une fois lorsque la page est chargée, et à nouveau avant que l'utilisateur 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');
});
skibulk
la source
2
J'ai essayé votre approche. Il fonctionne pour Chrome et Firefox, mais ne peut pas le faire fonctionner pour Safari sur Ipad Mini ..
Dimitry K
4

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 la boîte de dialogue de déchargement, a un cadre d'aide enfichable pour prendre en charge l'obtention du statut sale des éditeurs personnalisés (un petit plugin MCE est inclus) , fonctionne dans les iFrames, et le statut sale peut être défini ou réinitialisé à volonté.

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

NightOwl888
la source
4

Détecter les changements de formulaire avec l'utilisation de 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
}
v.babak
la source
2

J'ai développé la suggestion de Slace ci-dessus, pour inclure la plupart des éléments modifiables et exclure également 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.';
            }
        });        

PeterX
la source
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
la source
0

Une méthode , utilisant des tableaux pour contenir les variables afin que les modifications puissent être suivies.

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 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>
Adam Davis
la source
0

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 dans 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 () {});
   });
});
Krupall
la source