変更が保存されていないWebページを離れる前にユーザーに警告する


341

アプリケーションにフォームのあるページがいくつかあります。

誰かがブラウザのタブを離れたり閉じたりした場合に、保存されていないデータをフォームに残したいかどうかを確認するプロンプトが表示されるようにフォームを保護するにはどうすればよいですか?



3
ここでこの質問はあなたが後にしている答えている必要がありますstackoverflow.com/questions/1244535/...を
Nexerus


回答:


556

短い、間違った答え:

これを行うにはbeforeunloadイベントを処理してnull以外の文字列を返します

window.addEventListener("beforeunload", function (e) {
    var confirmationMessage = 'It looks like you have been editing something. '
                            + 'If you leave before saving, your changes will be lost.';

    (e || window.event).returnValue = confirmationMessage; //Gecko + IE
    return confirmationMessage; //Gecko + Webkit, Safari, Chrome etc.
});

このアプローチの問題は、フォーム送信すると、アンロードイベントも発生することです。これは、フォームを送信することを示すフラグを追加することで簡単に修正できます。

var formSubmitting = false;
var setFormSubmitting = function() { formSubmitting = true; };

window.onload = function() {
    window.addEventListener("beforeunload", function (e) {
        if (formSubmitting) {
            return undefined;
        }

        var confirmationMessage = 'It looks like you have been editing something. '
                                + 'If you leave before saving, your changes will be lost.';

        (e || window.event).returnValue = confirmationMessage; //Gecko + IE
        return confirmationMessage; //Gecko + Webkit, Safari, Chrome etc.
    });
};

次に、送信時にセッターを呼び出します。

<form method="post" onsubmit="setFormSubmitting()">     
    <input type="submit" />
</form>

しかし、読んでください...

長く正しい答え:

また、ユーザーがフォームで何も変更していないときにこのメッセージを表示したくない場合もあります。1つの解決策は、beforeunloadイベントを「ダーティ」フラグと組み合わせて使用することです。これは、本当に関連がある場合にのみプロンプトをトリガーします。

var isDirty = function() { return false; }

window.onload = function() {
    window.addEventListener("beforeunload", function (e) {
        if (formSubmitting || !isDirty()) {
            return undefined;
        }

        var confirmationMessage = 'It looks like you have been editing something. '
                                + 'If you leave before saving, your changes will be lost.';

        (e || window.event).returnValue = confirmationMessage; //Gecko + IE
        return confirmationMessage; //Gecko + Webkit, Safari, Chrome etc.
    });
};

isDirtyメソッドを実装するために、さまざまなアプローチがあります。

jQueryとフォームのシリアル化を使用できますが、このアプローチにはいくつかの欠点があります。最初に、任意のフォームで機能するようにコードを変更する必要があります$("form").each()が、最大の問題は、jQuery serialize()が名前付きの無効化されていない要素でのみ機能するため、無効化された要素または名前のない要素を変更してもダーティフラグがトリガーされないことです。そのための回避策があります。たとえば、コントロールを有効にする代わりに読み取り専用にし、シリアル化してから再度無効にするなどです。

したがって、イベントは進むべき道のようです。キープレスを聞いてみることができます。このイベントにはいくつかの問題があります:

  • チェックボックス、ラジオボタン、またはマウス入力によって変更されているその他の要素でトリガーしません。
  • キーのような無関係なCtrlキーを押すとトリガーされます。
  • JavaScriptコードで設定された値でトリガーされません。
  • コンテキストメニューからテキストを切り取ったり貼り付けたりしてもトリガーされません。
  • JavaScriptを介して非表示の入力に値を保存する、日付ピッカーやチェックボックス/ラジオボタンの美化機能などの仮想入力では機能しません。

このchangeイベントはJavaScriptコードから設定された値に対してトリガーされないため、仮想入力に対しても機能しません

inputイベントをinputtextareaselectページ上すべてのs(およびsとs)バインドしても、古いブラウザでは機能せず、上記のすべてのイベント処理ソリューションと同様に、元に戻すはサポートされていません。ユーザーがテキストボックスを変更してから元に戻すか、チェックボックスをオンまたはオフにしても、フォームはダーティと見なされます。

また、特定の要素を無視するなど、より多くの動作を実装する場合は、さらに多くの作業が必要になります。

車輪を再発明しないでください:

したがって、これらのソリューションと必要なすべての回避策の実装について考える前に、車輪を再発明していて、他の人がすでに解決している問題に遭遇する傾向があることを理解してください。

アプリケーションですでにjQueryを使用している場合は、独自のコードをロールする代わりに、テスト済みの保守されたコードを使用して、これらすべてにサードパーティのライブラリを使用することもできます。jQuery's Are You Sure?プラグインは素晴らしいです、彼らのデモページを見てください。それはこれと同じくらい簡単です:

<script src="jquery.are-you-sure.js"></script>

<script>
  $(function() {
    $('#myForm').areYouSure(
      {
        message: 'It looks like you have been editing something. '
               + 'If you leave before saving, your changes will be lost.'
      }
    );
  });

</script>

どこでもサポートされていないカスタムメッセージ

Firefox 4はこのダイアログのカスタムメッセージをサポートしていないことに注意してください。2016年4月の時点で、カスタムメッセージも削除されるChrome 51がロールアウトされています

このサイトの他の場所にもいくつかの選択肢がありますが、このようなダイアログは十分明確だと思います。

このサイトを離れますか?

行った変更は保存されない場合があります。

Leave Stay


6
Chromeではカスタム文字列を使用することはできません。chromestatus.com/feature/5349061406228480
amann

5
はい、それは私の回答の最後のセクションで説明されています。
CodeCaster 2017年

2
@Chrisは、AngularがDOMを操作してページを変更する一方で、現在のドキュメントから移動しないためです。
CodeCaster 2017年

15
jQuery areYouSureは2014年から中止されています(Githubで 57の未解決の問題)。これは私の経験からのバグでいっぱいです。最初は動作しているように見えるかもしれませんが、いくつかの簡単なテストの後、常に動作するとは限りません。これはまったくお勧めしません
Mark

4
@JacopoStanchi残念ながら見つかりませんでした。そして私はかなりよく見ました-しかし、これは1月10日に戻ったので、誰かが何かを書いたかもしれませんが、私はそれを疑っています。あなたの最善の策はそれを自分で実装することです-それはあなたが本当に残念に聞きたいものではありません。(しかし、少なくともjQuery areYouSureを使用したときのような驚きは得られません。)加えて、オープンソースにして、実装を他の人と共有することもできます;)
Mark

75

JavaScriptのonbeforeunloadイベントを確認してください。これはMicrosoftによって導入された非標準のJavaScriptですが、ほとんどのブラウザーで機能し、onbeforeunloadのドキュメントには詳細情報と例が含まれています。


3
今では標準になっていると思います。>>このイベントは当初、MicrosoftによってInternet Explorer 4で導入され、HTML5仕様で標準化されました。
VEE

34

jquery経由

$('#form').data('serialize',$('#form').serialize()); // On load save form current state

$(window).bind('beforeunload', function(e){
    if($('#form').serialize()!=$('#form').data('serialize'))return true;
    else e=null; // i.e; if form state change show warning box, else don't show it.
});

Google JQuery Form Serialize関数を使用すると、すべてのフォーム入力が収集され、配列に保存されます。これで十分だと思います:)


6
ウィンドウを作成し、フォームという名前のオブジェクトになるので、フォームIDとして#FORMを使用しないでください:)
mdikici

1
うまくいかない場合は、ここを確認してください:stackoverflow.com/questions/7080269/…–
Leniel Maccaferri 2014

16

contenteditable要素を含むすべての入力変更を自動的に検出する構成を必要としないユニバーサルソリューション:

"use strict";
(() => {
const modified_inputs = new Set;
const defaultValue = "defaultValue";
// store default values
addEventListener("beforeinput", (evt) => {
    const target = evt.target;
    if (!(defaultValue in target || defaultValue in target.dataset)) {
        target.dataset[defaultValue] = ("" + (target.value || target.textContent)).trim();
    }
});
// detect input modifications
addEventListener("input", (evt) => {
    const target = evt.target;
    let original;
    if (defaultValue in target) {
        original = target[defaultValue];
    } else {
        original = target.dataset[defaultValue];
    }
    if (original !== ("" + (target.value || target.textContent)).trim()) {
        if (!modified_inputs.has(target)) {
            modified_inputs.add(target);
        }
    } else if (modified_inputs.has(target)) {
        modified_inputs.delete(target);
    }
});
// clear modified inputs upon form submission
addEventListener("submit", () => {
    modified_inputs.clear();
    // to prevent the warning from happening, it is advisable
    // that you clear your form controls back to their default
    // state after submission
});
// warn before closing if any inputs are modified
addEventListener("beforeunload", (evt) => {
    if (modified_inputs.size) {
        const unsaved_changes_warning = "Changes you made may not be saved.";
        evt.returnValue = unsaved_changes_warning;
        return unsaved_changes_warning;
    }
});
})();

1
クールな豆。このコードをコピーしてテストページに貼り付け、変更を加えて、[更新]をクリックしてください。保存されていない変更があることを警告するネイティブブラウザアラートが表示されます。
TheLegendaryCopyCoder 2018年

1
これはChrome 71.0.3578.98(公式ビルド)(64ビット)で動作しました
JacobRossDev

2
これは非常にうまく機能しますが、送信用のフォームでこれを使用している場合は、最初にmodified_inputsセットをクリアする必要があります。そうしないと、変更の確認アラートが表示されます。送信時に呼び出すフォームにイベントリスナーを追加し、set関数をクリアして実行しました。これにより、beforeunloadイベントリスナーの実行時にmodified_inputsがクリアされ、投稿を続行できます。
ステップクイック

1
クールなコピーと貼り付け。時間を節約してくれてありがとう。Firfox 75.0で私のために働く
Connectify_user

1
このスニペットが大好き
Rawburner

10

Wasim A.のシリアル化を使用するという優れたアイデア上に構築されています。問題は、フォームの送信時に警告も表示されることでした。これはここで修正されました。

var isSubmitting = false

$(document).ready(function () {
    $('form').submit(function(){
        isSubmitting = true
    })

    $('form').data('initial-state', $('form').serialize());

    $(window).on('beforeunload', function() {
        if (!isSubmitting && $('form').serialize() != $('form').data('initial-state')){
            return 'You have unsaved changes which will not be saved.'
        }
    });
})

ChromeとIE 11でテストされています。


Wasim AとEerik Sven Puudist-すべてのボタンのタイプがsubmit <form action = "" id = "marksform" method = "POST"> <td> <input type =だったので、フォームにはidを、保存ボタンにはidを使用していただきありがとうございます"submit" name = "submit_button" id = "save" value = "Save"> </ td> <td> <input type = "submit" name = "submit_button" value = "Next"> </ td>およびいくつか。より多くのすべての種類は、そこで提出.Submitは(特定で$( '#保存')をクリックする代わりに'形式'は(関数(){isSubmitting =真})を使用する'#marksform'をクリック置き換え
Jayanta

7

以前の回答に基づいて、スタックオーバーフローのさまざまな場所からまとめて、実際に変更を送信したい場合に対処するために私が思いついた解決策を次に示します。

window.thisPage = window.thisPage || {};
window.thisPage.isDirty = false;

window.thisPage.closeEditorWarning = function (event) {
    if (window.thisPage.isDirty)
        return 'It looks like you have been editing something' +
               ' - if you leave before saving, then your changes will be lost.'
    else
        return undefined;
};

$("form").on('keyup', 'textarea', // You can use input[type=text] here as well.
             function () { 
                 window.thisPage.isDirty = true; 
             });

$("form").submit(function () {
    QC.thisPage.isDirty = false;
});
window.onbeforeunload = window.thisPage.closeEditorWarning;

IE11がアラートを表示しないようにするには、closeEditorWarning関数が戻る必要があるようですundefined


このソリューションは、値が元の状態から変更されていることを検証しないため、ユーザーがテキストを入力し、そのテキストを削除してから別の場所に移動しようとした場合は無効になります。
Brett Weber 14

良い点、@ BrettWeber。その場合、closeEditorWarningを変更して、「if(window.thisPage.isDirty && uiHasChanged())」を含めることができます。ここで、uiHasChangedは、サーバーからの元の状態を認識し、すべての違いをチェックする関数ですが、この解決策は特に余分な作業をすべて行いたくなかったケースです。これは、テキスト領域で実際のキー操作が行われたときにのみ誤検知が発生するため、妥当な妥協案だと感じました。:)
ジョナサン

すべきではQC.thisPage.isDirty = false;ないwindow.thisPage.isDirty = false;?このようにして、フォームを送信しているかどうかをチェックし、警告を表示しません。私のテストではうまくいくようです。また、ほとんどのフォームにはではinputなくがありtextareaます。私はかなりうまく機能する次のものを使用しました:$("form").on('keyup', 'textarea,input,select', function () {
Mike

7

次のワンライナーは私のために働いています。

window.onbeforeunload = s => modified ? "" : null;

アプリケーションの状態に応じて、trueまたはfalseに設定modifiedするだけです。


2
いくつかの提案:一部のブラウザーはそれを表示するため(例えばEdge)、空の文字列ではなくカスタムメッセージを返し、false の場合はIEが 'null'を出力するのではundefinedなく返します。nullmodified
ケビン・リー

1
モバイルデバイスはどうですか?私の理解は、IOSデバイスはonbeforeunloadを尊重しないということです。
jaybro

4

次のコードはうまくいきます。id属性を介してフォーム要素の入力変更に到達する必要があります。

var somethingChanged=false;
            $('#managerForm input').change(function() { 
                somethingChanged = true; 
           }); 
            $(window).bind('beforeunload', function(e){
                if(somethingChanged)
                    return "You made some changes and it's not saved?";
                else 
                    e=null; // i.e; if form state change show warning box, else don't show it.
            });
        });

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

別のファイルに挿入して<script src = "warnOnChange.js"> </ script>に含めましたが、テキストフィールドを変更してからヘッダーコマンドを実行しても何も起こりません。
Fabrizio Bartolomucci

2

serialize()を使用すると、フォームの値をシリアル化してURLエンコードされたテキスト文字列を作成し、フォームが変更されたかどうかを確認してからアンロードできます。

$(document).ready(function(){
    var form = $('#some-form'),
        original = form.serialize()

    form.submit(function(){
        window.onbeforeunload = null
    })

    window.onbeforeunload = function(){
        if (form.serialize() != original)
            return 'Are you sure you want to leave?'
    }
})

このリンクを参照してくださいhttps://coderwall.com/p/gny70a/alert-when-leaving-page-with-unsaved-form Vladimir Sidorenkoにより作成


1
動作しましたが、コードキャスターが回答したように、このメソッドにはいくつかの欠陥があります。この説明を参照してください。stackoverflow.com
a/7317311/

を使用した回答はすでにいくつかありますserialize()が、実際には欠点があります。
CodeCaster

これは正解で、非常に使いやすく、期待どおりに機能するはずです。
ジョディショップ

1

@codecasterのアイデアに追加すると、これをフォームのあるすべてのページに追加できます(私の場合、グローバルな方法で使用するため、フォームでのみこれが警告されます)に関数を変更します

if ( formSubmitting || document.getElementsByTagName('form').length == 0) 

また、ログインとキャンセルボタンのリンクを含むフォーム送信をオンにすると、ユーザーがキャンセルまたはフォームを送信したときに、フォームのないすべてのページでも警告がトリガーされなくなります...

<a class="btn btn-danger btn-md" href="back/url" onclick="setFormSubmitting()">Cancel</a>

1

ここで詳細な説明を確認できます:http : //techinvestigations.redexp.in/comparison-of-form-values-on-load-and-before-close/ 比較・オブ・フォームの値オンロード、および-終了前

メインコード:

function formCompare(defaultValues, valuesOnClose) {

    // Create arrays of property names
    var aPropsFormLoad = Object.keys(defaultValues);
    var aPropsFormClose = Object.keys(valuesOnClose);

    // If number of properties is different,
    // objects are not equivalent
    if (aPropsFormLoad.length != aPropsFormClose.length) {
        return false;
    }

    for (var i = 0; i < aPropsFormLoad.length; i++) {
        var propName = aPropsFormLoad[i];

        // If values of same property are not equal,
        // objects are not equivalent
        if (defaultValues[aPropsFormLoad]+"" !== valuesOnClose[aPropsFormLoad]+"") {
            return false;
        }
    }

    // If we made it this far, objects
    // are considered equivalent
    return true;

}

//add polyfill for older browsers, as explained on the link above

//use the block below on load
    for(i=0; i < document.forms[0].elements.length; i++){
    console.log("The field name is: " + document.forms[0].elements[i].name +
        " and it’s value is: " + document.forms[0].elements[i].value );
    aPropsFormLoad[i] = document.forms[0].elements[i].value;
    }

//create a similar array on window unload event.

//and call the utility function
    if (!formCompare(aPropsOnLoad, aPropsOnClose))
    {
    //perform action: 
    //ask user for confirmation or
    //display message about changes made
    }


1

Eerik Sven Puudistによるソリューション...

var isSubmitting = false;

$(document).ready(function () {
    $('form').submit(function(){
        isSubmitting = true
    })

    $('form').data('initial-state', $('form').serialize());

    $(window).on('beforeunload', function() {
        if (!isSubmitting && $('form').serialize() != $('form').data('initial-state')){
            return 'You have unsaved changes which will not be saved.'
        }
    });
})

...複雑なオブジェクト指向の設定で、変更を必要とせずに自発的に仕事をしました。

私が適用した唯一の変更は、 "formForm"( 'form'-> '#formForm')と呼ばれる具象フォーム(ファイルごとに1つのフォームのみ)を参照することでした。

<form ... id="formForm" name="formForm" ...>

特に良いのは、送信ボタンが「そのままにされている」という事実です。

さらに、Firefoxの最新バージョン(2019年2月7日現在)でも動作します。


1

Eli Greyのユニバーサルソリューションをテストしました。

  'use strict';
  (() => {
    const modified_inputs = new Set();
    const defaultValue = 'defaultValue';
    // store default values
    addEventListener('beforeinput', evt => {
      const target = evt.target;
      if (!(defaultValue in target.dataset)) {
        target.dataset[defaultValue] = ('' + (target.value || target.textContent)).trim();
      }
    });

    // detect input modifications
    addEventListener('input', evt => {
      const target = evt.target;
      let original = target.dataset[defaultValue];

      let current = ('' + (target.value || target.textContent)).trim();

      if (original !== current) {
        if (!modified_inputs.has(target)) {
          modified_inputs.add(target);
        }
      } else if (modified_inputs.has(target)) {
        modified_inputs.delete(target);
      }
    });

    addEventListener(
      'saved',
      function(e) {
        modified_inputs.clear()
      },
      false
    );

    addEventListener('beforeunload', evt => {
      if (modified_inputs.size) {
        const unsaved_changes_warning = 'Changes you made may not be saved.';
        evt.returnValue = unsaved_changes_warning;
        return unsaved_changes_warning;
      }
    });

  })();

彼への変更は削除され、使用target[defaultValue]のみtarget.dataset[defaultValue]、実際のデフォルト値を保存するにれます。

また、保存アクションが成功したときに自分で「保存」イベントがトリガーされる「保存」イベントリスナーを追加しました。

ただし、この「ユニバーサル」ソリューションはブラウザーでのみ機能し、アプリのWebビューでは機能しません(例:wechatブラウザー)。

wechatブラウザーで(部分的に)動作するようにするために、もう一度別の改善を行いました。

  'use strict';
  (() => {
    const modified_inputs = new Set();
    const defaultValue = 'defaultValue';
    // store default values
    addEventListener('beforeinput', evt => {
      const target = evt.target;
      if (!(defaultValue in target.dataset)) {
        target.dataset[defaultValue] = ('' + (target.value || target.textContent)).trim();
      }
    });

    // detect input modifications
    addEventListener('input', evt => {
      const target = evt.target;
      let original = target.dataset[defaultValue];

      let current = ('' + (target.value || target.textContent)).trim();

      if (original !== current) {
        if (!modified_inputs.has(target)) {
          modified_inputs.add(target);
        }
      } else if (modified_inputs.has(target)) {
        modified_inputs.delete(target);
      }

      if(modified_inputs.size){
        const event = new Event('needSave')
        window.dispatchEvent(event);
      }
    });

    addEventListener(
      'saved',
      function(e) {
        modified_inputs.clear()
      },
      false
    );

    addEventListener('beforeunload', evt => {
      if (modified_inputs.size) {
        const unsaved_changes_warning = 'Changes you made may not be saved.';
        evt.returnValue = unsaved_changes_warning;
        return unsaved_changes_warning;
      }
    });

    const ua = navigator.userAgent.toLowerCase();

    if(/MicroMessenger/i.test(ua)) {
      let pushed = false

      addEventListener('needSave', evt => {
        if(!pushed) {
          pushHistory();

          window.addEventListener("popstate", function(e) {
            if(modified_inputs.size) {
              var cfi = confirm('确定要离开当前页面嘛?' + JSON.stringify(e));
              if (cfi) {
                modified_inputs.clear()
                history.go(-1)
              }else{
                e.preventDefault();
                e.stopPropagation();
              }
            }
          }, false);
        }

        pushed = true
      });
    }

    function pushHistory() {
      var state = {
        title: document.title,
        url: "#flag"
      };
      window.history.pushState(state, document.title, "#flag");
    }
  })();

0

私はそれを別の方法で行いました。誰かが助けを借りられるようにここで共有し、Chromeでのみテストしました。

いくつかの変更がある場合にのみ、タブを閉じる前にユーザーに警告します。

<input type="text" name="field" value="" class="onchange" />

var ischanged = false;

$('.onchange').change(function () {
    ischanged = true;
});

window.onbeforeunload = function (e) {
    if (ischanged) {
        return "Make sure to save all changes.";
    }        
};

正常に機能しますが、別の問題が発生しました。フォームを送信すると、不要な警告が表示され、多くの回避策が表示されました。これは、onbeforeunloadがonsubmitの前に発生するためonbeforeunload = null、のようなonsubmitイベントで処理できないためです。これらの両方のイベントの前に送信ボタンのonclickイベントが発生するため、コードを更新しました

var isChanged = false;
var isSubmit = false;

window.onbeforeunload = function (e) {
    if (isChanged && (!isSubmit)) {
        return "Make sure to save all changes.";
    }        
};

$('#submitbutton').click(function () {
    isSubmit = true;
});

$('.onchange').change(function () {
    isChanged = true;
});

-5

まず第一に、ほとんどのブラウザはデフォルトでこの機能を持っています。そして、なぜこれが必要なのですか?フォームの同期を維持しないのはなぜですか?つまり、ユーザーからの送信を待たずに、変更を保存します。Googleコンタクトのように。もちろん、フォーム内のすべてのフィールドのみが必須である場合。ユーザーは、必要なものであるかどうかを考えるために立ち去る機会なしに、何かを強制的に満たすことを好まない。:)


あなたは正しいですが、私のフォームは動的に生成され、フィールドは私の制御下にないので、私にとってすべてのフィールドを追跡してajax経由でリクエストを送信することはできません。
Khan

それはあなたの目的には必要ないかもしれませんが、それはまだ実装することができます。フォーム要素が画面上にある場合は、独自のイベントハンドラーをアタッチして、必要なデータを取得できます。次に、AJAXを使用して、データを好きな場所に保存します。フォームにランダムに生成されたIDまたはクラス名がある場合でも、フォームの構造が予想できる場合はXPathを使用できます。XML / HTMLでしょ?したがって、動的に生成されたフォームの責任者と一緒にJADを用意し、このプロジェクトでXSTLが両方にどのように役立つかについて話すこともできます。Just a
Thought

1
save it on any change without waiting any submitting from userあなたはいつもこれをしたくありません。場合によっては、ユーザーが多くの変更を加えてから、保存したいことを確認して確認したいことがあります。
BadHorsie 2016年

1
フォームを送信することはお勧めしませんでした。データはlocalStorage、キャッシュ、またはドラフトフラグ付きで送信することもできます。この動作には多くの方法があります。フォームに入力しなかったことをユーザーに警告するよりもはるかに優れています。フォームを完了しなかった場合、ウィンドウを閉じないと、次回はフォームに入力する必要があります。「いらっしゃい、怠惰にしないでください。電車、バス、飛行機などは問題ではありません。ラップトップから充電器を忘れてもかまいません。このフォームに記入してください!」
YuS
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.