Загальний спосіб визначити, чи редагується форма html


94

У мене є HTML-форма з вкладками. При переході з однієї вкладки на іншу дані поточної вкладки зберігаються (у БД), навіть якщо дані не змінюються.

Я хотів би зробити дзвінок постійності лише за умови редагування форми. Форма може містити будь-який тип контролю. Забруднення форми не вимагається шляхом введення тексту, але вибір дати в елементі керування календарем також відповідає вимогам.

Одним із способів досягти цього було б відобразити форму в режимі лише для читання за замовчуванням і мати кнопку «Редагувати», і якщо користувач натисне кнопку редагування, тоді буде здійснено виклик до БД (ще раз, незалежно від того, чи змінено дані Це краще покращення того, що існує на даний момент).

Я хотів би знати, як написати загальну функцію javascript, яка перевіряла б, чи було змінено якесь із значень елементів керування?


Цікаве рішення опублікував Крейг Баклер на SitePoint. Особливий інтерес рішення не покладається на jQuery і сумісно з переглядачами.
MagicAndi

Відповіді:


158

У чистому javascript це було б нелегким завданням, але jQuery робить це дуже простим:

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

Потім перед збереженням ви можете перевірити, чи не було його змінено:

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

У наведеному вище прикладі форма має ідентифікатор, що дорівнює "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
Це приємно і просто. Однак якщо користувач змінює введення форми, а потім повертає зміни (наприклад, двічі клацнувши прапорець), тоді форма буде вважатися зміненою. Прийнятно це чи ні, залежить, звичайно, від контексту. Альтернативний см stackoverflow.com/questions/10311663 / ...
JLH

1
для реальних входів потрібні деякі зміни trackChanges: function () { $(document).on('change', $(this).find(':input'), function (e) { var el = $(e.target); $(el).closest('form').data("changed", true); });
Dimmduh,

36

На випадок, якщо про JQuery не може бути й мови. Швидкий пошук у Google знайшов реалізації Javascript хеш-алгоритмів MD5 та SHA1. Якщо ви хочете, ви можете об'єднати всі входи форми та хешувати їх, а потім зберегти це значення в пам'яті. Коли користувач закінчить. Знову об’єднайте всі значення та хеш. Порівняйте 2 хеші. Якщо вони однакові, користувач не змінив жодного поля форми. Якщо вони різні, щось було відредаговано, і вам потрібно зателефонувати за своїм кодом постійності.


2
Це те, що я очікував із цього питання. Чи існує якась бібліотека?
Хамедзь

Чи не мав би той самий результат, якби ви об'єднали всі поля, але не хешували його?
ashleedawg

Так, але самі дані можуть бути більшими за хеш.
JRG

25

Я не впевнений, що я правильно зрозумів ваше запитання, але як щодо 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 був доступний, але немає жодної причини, щоб не мати ще більш застосовного рішення, подібного до цього із застереженнями в коментарях.
jorffin

4

Ось як я це зробив (без використання jQuery).

У моєму випадку я хотів, щоб один конкретний елемент форми не враховувався, оскільки саме цей елемент ініціював перевірку, і тому він завжди буде змінюватися. Винятковий елемент називається 'report_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

Зміни форми можна легко виявити у власному JavaScript без jQuery:

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

завжди повертає істину для моєї форми
Rich Stone

Не могли б ви опублікувати форму у JSBin або Gist, щоб ми могли побачити вашу форму?
AnthumChris

з моєї вини, я вибрав форму з jquery, а не з JS, як у вашому прикладі. Це приємне і чисте рішення js, без перевантаження серіалізації. Дякую!
Rich Stone

2

Ось демонстрація методу polyfill у власному JavaScript, який використовує FormData()API для виявлення створених, оновлених та видалених записів форми. Ви можете перевірити, чи щось було змінено за допомогою, 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>

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.