htmlフォームが編集されているかどうかを検出する一般的な方法


94

タブ付きのhtmlフォームがあります。あるタブから別のタブに移動すると、データに変更がない場合でも、現在のタブのデータが(DB上に)保持されます。

フォームが編集されている場合にのみ永続性呼び出しを行いたいのですが。フォームには、あらゆる種類のコントロールを含めることができます。フォームを汚すのは、テキストを入力する必要はありませんが、カレンダーコントロールで日付を選択することもできます。

これを実現する1つの方法は、デフォルトでフォームを読み取り専用モードで表示し、[編集]ボタンを設定し、ユーザーが編集ボタンをクリックすると、DBが呼び出されることです(データが変更されているかどうかに関係なく、もう一度) 。これは、現在存在しているものに対するより良い改善です)。

コントロール値のいずれかが変更されているかどうかをチェックする汎用javascript関数を作成する方法を知りたいですか?


興味深いソリューションはSitePointにクレイグ・バックラーによって投稿されました。特に興味深いのは、このソリューションはjQueryに依存せず、ブラウザー間で互換性があることです。
MagicAndi 2011

回答:


158

純粋なJavaScriptでは、これは簡単な作業ではありませんが、jQueryを使用すると非常に簡単に実行できます。

$("#myform :input").change(function() {
   $("#myform").data("changed",true);
});

次に、保存する前に、変更されたかどうかを確認できます。

if ($("#myform").data("changed")) {
   // submit the form
}

上記の例では、フォームのIDは「myform」です。

これをさまざまな形で必要とする場合は、簡単にプラグインに変えることができます。

$.fn.extend({
 trackChanges: function() {
   $(":input",this).change(function() {
      $(this.form).data("changed", true);
   });
 }
 ,
 isChanged: function() { 
   return this.data("changed"); 
 }
});

次に、あなたは簡単に言うことができます:

$("#myform").trackChanges();

フォームが変更されたかどうかを確認します。

if ($("#myform").isChanged()) {
   // ...
}

13
これは素晴らしくてシンプルです。ただし、ユーザーがフォーム入力を変更してから変更を元に戻すと(たとえば、チェックボックスを2回クリックして)、フォームは変更されたと見なされます。それが受け入れられるかどうかは、もちろん状況によって異なります。別の方法については、stackoverflow.com
questions / 10311663 /…

1
ライブ入力の場合、いくつかの変更が必要です trackChanges: function () { $(document).on('change', $(this).find(':input'), function (e) { var el = $(e.target); $(el).closest('form').data("changed", true); });
Dimmduh 2018年

36

JQueryが問題外の場合。Googleですばやく検索すると、MD5およびSHA1ハッシュアルゴリズムのJavascript実装が見つかりました。必要に応じて、すべてのフォーム入力を連結してハッシュし、その値をメモリに保存できます。ユーザーが完了したとき。すべての値を連結し、再度ハッシュします。2つのハッシュを比較します。それらが同じである場合、ユーザーはフォームフィールドを変更しませんでした。それらが異なる場合は、何かが編集されているため、永続性コードを呼び出す必要があります。


2
これは私がこの質問に期待したことです、ライブラリはありますか?
ハメズ2018

すべてのフィールドを連結したがハッシュしなかった場合、同じ結果になりませんか?
ashleedawg 2018

はい。ただし、データ自体はハッシュよりも大きい場合があります。
JRG

26

あなたの質問が正しいかどうかはわかりませんが、addEventListenerはどうですか?IE8のサポートをあまり気にしないのであれば、これで問題ありません。次のコードは私のために働いています:

var form = document.getElementById("myForm");

form.addEventListener("input", function () {
    console.log("Form has changed!");
});

私のニーズにぴったり
祝福

8

これを実現する別の方法は、フォームをシリアル化することです。

$(function() {
    var $form = $('form');
    var initialState = $form.serialize();
    
    $form.submit(function (e) {
      if (initialState === $form.serialize()) {
        console.log('Form is unchanged!');
      } else {
        console.log('Form has changed!');
      }
      e.preventDefault();
    });
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<form>
Field 1: <input type="text" name="field_1" value="My value 1"> <br>
Field 2: <input type="text" name="field_2" value="My value 2"> <br>
Check: <input type="checkbox" name="field_3" value="1"><br>
<input type="submit">
</form>


特にjQueryを取り除いて、バニラJavaScriptを使用する場合は、あなたの考え方が気に入っています。私は、jQueryが利用可能であると仮定した質問を理解していますが、コメントの警告を伴うこれ同様に、さらに広く適用可能なソリューションを持たない理由はありません。
ラフィン

4

これが私がそれをした方法です(jQueryを使用せずに)。

私の場合、特定のフォーム要素をカウントしないようにしたかったのです。これは、チェックをトリガーした要素であり、常に変更されるためです。例外的な要素は「reporting_period」という名前で、関数「hasFormChanged()」にハードコードされています。

テストするには、要素に関数 "changeReportingPeriod()"を呼び出させます。これは、おそらく別の名前を付ける必要があります。

重要:値が元の値に設定されている場合は、setInitialValues()を呼び出す必要があります(通常はページの読み込み時ですが、私の場合はそうではありません)。

注:これがエレガントなソリューションであるとは主張していません。実際、エレガントなJavaScriptソリューションを信じていません。私がJavaScriptで個人的に強調しているのは、構造的な優雅さではなく、読みやすさです(JavaScriptで可能であるかのように)。JavaScriptを作成するときは、ファイルサイズをまったく気にしません。これは、gzipの目的であり、よりコンパクトなJavaScriptコードを作成しようとすると、常にメンテナンスに耐えられない問題が発生するためです。私は謝罪を申し出ず、後悔を表明せず、それについて議論することを拒否します。それはJavaScriptです。申し訳ありませんが、わざわざ投稿する必要があることを自分に納得させるために、これを明確にする必要がありました。幸せになる!:)


    var initial_values = new Array();

    // Gets all form elements from the entire document.
    function getAllFormElements() {
        // Return variable.
        var all_form_elements = Array();

        // The form.
        var form_activity_report = document.getElementById('form_activity_report');

        // Different types of form elements.
        var inputs = form_activity_report.getElementsByTagName('input');
        var textareas = form_activity_report.getElementsByTagName('textarea');
        var selects = form_activity_report.getElementsByTagName('select');

        // We do it this way because we want to return an Array, not a NodeList.
        var i;
        for (i = 0; i < inputs.length; i++) {
            all_form_elements.push(inputs[i]);
        }
        for (i = 0; i < textareas.length; i++) {
            all_form_elements.push(textareas[i]);
        }
        for (i = 0; i < selects.length; i++) {
            all_form_elements.push(selects[i]);
        }

        return all_form_elements;
    }

    // Sets the initial values of every form element.
    function setInitialFormValues() {
        var inputs = getAllFormElements();
        for (var i = 0; i < inputs.length; i++) {
            initial_values.push(inputs[i].value);
        }
    }

    function hasFormChanged() {
        var has_changed = false;
        var elements = getAllFormElements();

        for (var i = 0; i < elements.length; i++) {
            if (elements[i].id != 'reporting_period' && elements[i].value != initial_values[i]) {
                has_changed = true;
                break;
            }
        }

        return has_changed;
    }

    function changeReportingPeriod() {
        alert(hasFormChanged());
    }



3

フォームの変更は、jQueryを使用せずにネイティブJavaScriptで簡単に検出できます。

function initChangeDetection(form) {
  Array.from(form).forEach(el => el.dataset.origValue = el.value);
}
function formHasChanges(form) {
  return Array.from(form).some(el => 'origValue' in el.dataset && el.dataset.origValue !== el.value);
}


initChangeDetection()ページのライフサイクル全体で複数回安全に呼び出すことができます:JSBinでのテストを参照してください


新しい矢印/配列関数をサポートしていない古いブラウザの場合:

function initChangeDetection(form) {
  for (var i=0; i<form.length; i++) {
    var el = form[i];
    el.dataset.origValue = el.value;
  }
}
function formHasChanges(form) {
  for (var i=0; i<form.length; i++) {
    var el = form[i];
    if ('origValue' in el.dataset && el.dataset.origValue !== el.value) {
      return true;
    }
  }
  return false;
}

いつも私のフォームのためにtrueを返す
リッチストーン

フォームをJSBinまたはGistに投稿して、フォームを確認していただけますか?
AnthumChris

私のせいで、あなたの例のようにJSではなくjqueryでフォームを選択しました。これは、シリアル化の過負荷がなく、すてきでクリーンなjsソリューションです。ありがとうございました!
リッチストーン

2

これは、FormData()APIを使用して作成、更新、および削除されたフォームエントリを検出するネイティブJavaScriptのポリフィルメソッドのデモです。を使用して何かが変更されたかどうかを確認し、を使用HTMLFormElement#isChangedしてリセットフォームとの違いを含むオブジェクトを取得できますHTMLFormElement#changes(入力名でマスクされていない場合)。

Object.defineProperties(HTMLFormElement.prototype, {
  isChanged: {
    configurable: true,
    get: function isChanged () {
      'use strict'

      var thisData = new FormData(this)
      var that = this.cloneNode(true)

      // avoid masking: https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/reset
      HTMLFormElement.prototype.reset.call(that)

      var thatData = new FormData(that)

      const theseKeys = Array.from(thisData.keys())
      const thoseKeys = Array.from(thatData.keys())

      if (theseKeys.length !== thoseKeys.length) {
        return true
      }

      const allKeys = new Set(theseKeys.concat(thoseKeys))

      function unequal (value, index) {
        return value !== this[index]
      }

      for (const key of theseKeys) {
        const theseValues = thisData.getAll(key)
        const thoseValues = thatData.getAll(key)

        if (theseValues.length !== thoseValues.length) {
          return true
        }

        if (theseValues.some(unequal, thoseValues)) {
          return true
        }
      }

      return false
    }
  },
  changes: {
    configurable: true,
    get: function changes () {
      'use strict'

      var thisData = new FormData(this)
      var that = this.cloneNode(true)

      // avoid masking: https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/reset
      HTMLFormElement.prototype.reset.call(that)

      var thatData = new FormData(that)

      const theseKeys = Array.from(thisData.keys())
      const thoseKeys = Array.from(thatData.keys())

      const created = new FormData()
      const deleted = new FormData()
      const updated = new FormData()

      const allKeys = new Set(theseKeys.concat(thoseKeys))

      function unequal (value, index) {
        return value !== this[index]
      }

      for (const key of allKeys) {
        const theseValues = thisData.getAll(key)
        const thoseValues = thatData.getAll(key)

        const createdValues = theseValues.slice(thoseValues.length)
        const deletedValues = thoseValues.slice(theseValues.length)

        const minLength = Math.min(theseValues.length, thoseValues.length)

        const updatedValues = theseValues.slice(0, minLength).filter(unequal, thoseValues)

        function append (value) {
          this.append(key, value)
        }

        createdValues.forEach(append, created)
        deletedValues.forEach(append, deleted)
        updatedValues.forEach(append, updated)
      }

      return {
        created: Array.from(created),
        deleted: Array.from(deleted),
        updated: Array.from(updated)
      }
    }
  }
})

document.querySelector('[value="Check"]').addEventListener('click', function () {
  if (this.form.isChanged) {
    console.log(this.form.changes)
  } else {
    console.log('unchanged')
  }
})
<form>
  <div>
    <label for="name">Text Input:</label>
    <input type="text" name="name" id="name" value="" tabindex="1" />
  </div>

  <div>
    <h4>Radio Button Choice</h4>

    <label for="radio-choice-1">Choice 1</label>
    <input type="radio" name="radio-choice-1" id="radio-choice-1" tabindex="2" value="choice-1" />

    <label for="radio-choice-2">Choice 2</label>
    <input type="radio" name="radio-choice-2" id="radio-choice-2" tabindex="3" value="choice-2" />
  </div>

  <div>
    <label for="select-choice">Select Dropdown Choice:</label>
    <select name="select-choice" id="select-choice">
      <option value="Choice 1">Choice 1</option>
      <option value="Choice 2">Choice 2</option>
      <option value="Choice 3">Choice 3</option>
    </select>
  </div>

  <div>
    <label for="textarea">Textarea:</label>
    <textarea cols="40" rows="8" name="textarea" id="textarea"></textarea>
  </div>

  <div>
    <label for="checkbox">Checkbox:</label>
    <input type="checkbox" name="checkbox" id="checkbox" />
  </div>

  <div>
    <input type="button" value="Check" />
  </div>
</form>

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.