web-dev-qa-db-ja.com

未保存の変更でWebページを離れる前にユーザーに警告する

私のアプリケーションにはいくつかのページがあります。

だれかがブラウザタブを移動したりブラウザタブを閉じたりした場合に、フォームを未保存のデータで本当に残したいかどうかを確認するように求められるようにフォームを保護するにはどうすればよいですか。

284
Khan

短く、間違った答え:

これは 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.
});

このアプローチの問題点は、フォームを送信するとunloadイベントも発生するということです。これは、フォームを送信しているというフラグを追加することで簡単に解決できます。

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イベントをすべてのinputs(およびtextareasおよびselects)にバインドする)古いブラウザでは動作しません そして、上記のすべてのイベント処理ソリューションと同様に、元に戻すをサポートします。ユーザーがテキストボックスを変更してから元に戻す、またはチェックボックスをオンまたはオフにしても、フォームはまだ汚れていると見なされます。

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

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

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

アプリケーションがすでにjQueryを使用している場合は、独自のコードを使用するのではなく、テスト済みの保守されたコードを使用し、そのためにサードパーティのライブラリを使用することもできます。 jQueryの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 このダイアログではカスタムメッセージをサポートしていません。先月の時点で、Chrome 51はロールアウトされています カスタムメッセージも削除されています

このサイトの他の場所にいくつかの選択肢がありますが、このような対話は十分に明確であると思います。

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

行った変更は保存されない可能性があります。

LeaveStay

491
CodeCaster

JavaScript onbeforeunloadイベント を調べてください。これはMicrosoftによって導入された非標準のJavaScriptですが、ほとんどのブラウザで動作します。 彼らのオンライン上のドキュメント にはより多くの情報と例があります。

72
Paul Annesley

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フォームシリアライズ機能ができます、これはすべてのフォーム入力を集めて、それを配列に保存します。これで十分だと思います:)

32
Wasim A.

満足のいく要素を含む、すべての入力変更を自動的に検出する設定を必要としないユニバーサルソリューション。

"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);
    }
});
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;
    }
});
})();
9
Eli Grey

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でテスト済みです。

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を返すことを要求しているようです。

7
Jonathan

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

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

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

6
spencer.sm

以下のコードはとてもうまくいきます。あなたは、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.
            });
        });
4
metzelder
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;
3
Ankit

あなたはここで詳細な説明をチェックすることができます: http://techinaminationations.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
Kanika Sud

@codecasterのアイデアに追加すると、フォームを持つすべてのページにこれを追加することができます(私の場合はグローバルに使用しているため、フォームでのみこの警告が表示されます)。

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

また、ログインを含むフォームの送信とキャンセルボタンのリンクを設定すると、フォームを送信するすべてのページで、キャンセルまたはフォーム送信を押すと警告が表示されなくなります。

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

短い答え:

let pageModified = true

window.addEventListener("beforeunload", 
  () => pageModified ? 'Close page without saving data?' : null
)
1
Daniel

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
mtjmohr

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]のみを使用して実際のデフォルト値を保存します。

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

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

Wechatブラウザーでも(部分的に)動作するようにするには、もう1つの改善点があります。

  '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
Jeff Tian