Обнаружение изменения входного значения с помощью MutationObserver

Я хочу определить, когда текст / значение изменяется в поле ввода. Даже если я изменю значение с помощью js, я хочу обнаружить эти изменения.

Вот то, что я пробовал до сих пор в демонстрации в скрипте.

HTML:

<input type="text" id="exNumber"/>

JavaScript:

var observer = new MutationObserver(function(mutations) {
  mutations.forEach(function(mutation) {
    // console.log('Mutation type: ' + mutation.type);
    if ( mutation.type == 'childList' ) {
      if (mutation.addedNodes.length >= 1) {
        if (mutation.addedNodes[0].nodeName != '#text') {
           // console.log('Added ' + mutation.addedNodes[0].tagName + ' tag.');
        }
      }
      else if (mutation.removedNodes.length >= 1) {
         // console.log('Removed ' + mutation.removedNodes[0].tagName + ' tag.')
      }
    }
     if (mutation.type == 'attributes') {
      console.log('Modified ' + mutation.attributeName + ' attribute.')
    }
  });   
});

var observerConfig = {
        attributes: true,
        childList: false,
        characterData: false
};

// Listen to all changes to body and child nodes
var targetNode = document.getElementById("exNumber");
observer.observe(targetNode, observerConfig);

person while true    schedule 03.09.2015    source источник
comment
Если вы нетерпеливы и хотите прямо сейчас ужасно, ужасно, бесполезно, действительно плохо, то я сделал для вас именно то, что вам нужно: IDL-Property-Observe. После запуска этой библиотеки приведенный выше код будет работать нормально за счет принесения в жертву передовых методов работы с собственными прототипами. Ваше здоровье!   -  person Jack Giffin    schedule 19.08.2019


Ответы (4)


Чтобы понять, что происходит, необходимо прояснить разницу между атрибутом (атрибут содержимого) и свойством (атрибут IDL). Я не буду вдаваться в подробности, так как в SO уже есть отличные ответы на эту тему:

Когда вы изменяете содержимое элемента input, набрав или с помощью JS:

targetNode.value="foo";

браузер обновляет value свойство, но не value атрибут (который вместо этого отражает свойство defaultValue).

Затем, если мы посмотрим на спецификацию MutationObserver, мы увидим, что атрибуты < / em> - один из членов объекта, которые можно использовать. Итак, если вы явно установите атрибут value:

targetNode.setAttribute("value", "foo");

MutationObserver уведомит об изменении атрибута. Но ничего подобного свойствам в списке спецификации нет: свойство value не может соблюдаться.

Если вы хотите определить, когда пользователь изменяет содержимое вашего элемента ввода, input event - самый простой способ. Если вам нужно отловить изменения JS, перейдите на setInterval и сравните новое значение со старым.

Чтобы узнать, проверьте этот вопрос SO о различных альтернативах и их ограничениях.

person David    schedule 03.09.2015
comment
onkeyup бесполезен для моего случая. На самом деле внешний скрипт я не могу изменить по какой-то причине. скрипт изменяет значение поля ввода. поэтому я хочу определить, когда изменяется текст - person while true; 03.09.2015
comment
В этом случае проверьте этот другой. - person David; 03.09.2015
comment
tnx david, поэтому единственное уродливое решение - использовать таймер. Я не хотел, поэтому я попробовал Observer. но, к сожалению, другого способа сделать это не может. - person while true; 03.09.2015
comment
Я так не думаю (и согласен с тем, что таймер - это некрасиво), если у вас нет доступа к внешнему скрипту. В любом случае, мой ответ охватывает исходный вопрос о том, как ведет себя MutationObserver, поэтому было бы неплохо, если бы вы отметили его как правильное :) - person David; 03.09.2015
comment
@ Дэвид Спасибо. Это убивало меня долгое время. - person Soham Dasgupta; 17.02.2016
comment
У вас есть какие-нибудь подробности, почему дом не меняется? В конце концов, я вижу изменения в окне браузера. - person Mene; 23.10.2017
comment
@Mene Спасибо, что указали на это: это неправильно. Думаю, я хотел сказать HTML (который определяет атрибуты) вместо DOM (который определяет свойства). Обычно изменение атрибута называют изменением HTML. В любом случае, будучи строгим, HTML - это статический текст, который находится на сервере (он не меняется), в то время как DOM - это объект в памяти, полученный в результате анализа разметки (он изменяется всякий раз, когда пользователь или скрипт изменяют его), поэтому я просто удалил это предложение. Собственно, я все это переписал, чтобы быть более конкретным. - person David; 27.10.2017
comment
Отлично! Мне было сложно понять, что происходит, или даже найти ссылки на это, ваши правки мне очень помогают! - person Mene; 27.10.2017
comment
Я использовал ссылку JSFiddle, предоставленную op, и она работает. Я имею в виду, что он фиксирует все изменения всякий раз, когда я ввожу ввод. Но тот же код не работает вне JSFiddle, у вас есть идеи? - person Foo Bar Zoo; 17.02.2019
comment
@FooBarZoo - Не должно работать, если мы будем следовать спецификации. JSFiddle не является открытым исходным кодом, поэтому единственный способ узнать, что они изменяют нормальное поведение Mutationbserver, - это спросить их (но для меня это звучит странно). Это также может быть связано с реализацией браузера. Я просто снова пробую скрипку на FF 64 в Linux, и она ведет себя так, как ожидалось (никаких уведомлений, по крайней мере, изменение атрибута). - person David; 17.02.2019
comment
@ Дэвид спасибо за быстрый ответ. Дело в том, что код JSFiddle работает в Firefox на OS X даже вне редактора JSFiddle. Единственная разница в том, что он работает в Chrome на OSX с редактором JSFiddle и не работает иначе. Возможно, JSFiddle добавляет дополнительный файл JS, который добавляет некоторые прослушиватели событий для полей ввода, чтобы заставить его работать для MutationObserver, но еще одна потрясающая вещь заключается в том, что поведение Chrome и Firefox полностью отличается, один фиксирует входные изменения, а другой делает нет (я имею в виду вне редактора JSFiddle). - person Foo Bar Zoo; 18.02.2019

Я немного изменил метод Шона и хотел им поделиться. Не могу поверить, что на самом деле есть решение.

Введите текст в поле ввода, чтобы увидеть поведение по умолчанию. Теперь откройте DevTools и выберите элемент ввода, затем измените его значение, например $0.value = "hello". Изучите разницу между пользовательским интерфейсом и API. Кажется, что взаимодействия с пользовательским интерфейсом не изменяют свойство value напрямую. Если бы это было так, он также записал бы "...changed via API...".

let inputBox = document.querySelector("#inputBox");

inputBox.addEventListener("input", function () {
    console.log("Input value changed via UI. New value: '%s'", this.value);
});

observeElement(inputBox, "value", function (oldValue, newValue) {
    console.log("Input value changed via API. Value changed from '%s' to '%s'", oldValue, newValue);
});

function observeElement(element, property, callback, delay = 0) {
    let elementPrototype = Object.getPrototypeOf(element);
    if (elementPrototype.hasOwnProperty(property)) {
        let descriptor = Object.getOwnPropertyDescriptor(elementPrototype, property);
        Object.defineProperty(element, property, {
            get: function() {
                return descriptor.get.apply(this, arguments);
            },
            set: function () {
                let oldValue = this[property];
                descriptor.set.apply(this, arguments);
                let newValue = this[property];
                if (typeof callback == "function") {
                    setTimeout(callback.bind(this, oldValue, newValue), delay);
                }
                return newValue;
            }
        });
    }
}
<input type="text" id="inputBox" placeholder="Enter something" />

person akinuri    schedule 23.05.2020

значение свойства можно наблюдать, не теряйте зря время.

function changeValue (event, target) {
    document.querySelector("#" + target).value = new Date().getTime();
}
 
function changeContentValue () {
    document.querySelector("#content").value = new Date().getTime();
}
 
Object.defineProperty(document.querySelector("#content"), "value", {
    set:  function (t) {
        alert('#changed content value');
        var caller = arguments.callee
            ? (arguments.callee.caller ? arguments.callee.caller : arguments.callee)
            : ''
 
        console.log('this =>', this);
        console.log('event => ', event || window.event);
        console.log('caller => ', caller);
        return this.textContent = t;
    }
});
<form id="form" name="form" action="test.php" method="post">
        <input id="writer" type="text" name="writer" value="" placeholder="writer" /> <br />
        <textarea id="content" name="content" placeholder="content" ></textarea> <br />
        <button type="button" >Submit (no action)</button>
</form>
<button type="button" onClick="changeValue(this, 'content')">Change Content</button>

person GracefulLight    schedule 27.06.2018
comment
Я работаю над аналогичной проблемой, и меня интересует этот ответ, но я немного борюсь. Это работает только для текстового поля? Поскольку вы устанавливаете textContent, который появляется внутри поля textarea, но textContent не отображается внутри поля ввода в HTML. - person Josh; 03.10.2018
comment
В поле ввода @Josh используется return this.defaultValue = t; :) - person GracefulLight; 03.10.2018
comment
Не могли бы вы подробнее рассказать, как работает ваш код? - person Polyducks; 29.04.2019

Это работает, сохраняет и связывает исходный сеттер и получатель, чтобы все остальное в вашем поле по-прежнему работало.

var registered = [];
var setDetectChangeHandler = function(field) {
  if (!registered.includes(field)) {
    var superProps = Object.getPrototypeOf(field);
    var superSet = Object.getOwnPropertyDescriptor(superProps, "value").set;
    var superGet = Object.getOwnPropertyDescriptor(superProps, "value").get;
    var newProps = {
      get: function() {
        return superGet.apply(this, arguments);
      },
      set: function (t) {
        var _this = this;
        setTimeout( function() { _this.dispatchEvent(new Event("change")); }, 50);
        return superSet.apply(this, arguments);
      }
    };
    Object.defineProperty(field, "value", newProps);
    registered.push(field);
  }
}
person Shawn Regan    schedule 17.04.2019
comment
Вы можете объяснить, как работает ваше решение? Что он делает? - person Polyducks; 29.04.2019
comment
Думаю, это лучшее готовое решение. Отлично работал в моем приложении, и мне это нравится, потому что он оставляет в покое исходный сеттер. - person Xandor; 25.10.2019
comment
Для каждого поля формы вызовите: setDetectChangeHandler (document.querySelector ('. MyFrom .fieldN')); Затем, чтобы уловить все изменения, добавьте слушателя к форме document.querySelector ('. MyForm'). AddEventListener ('change', event = ›{// обрабатываем изменения поля здесь}); - person Systemsplanet; 14.01.2020
comment
Мне нравится это решение. Хотя это вызывает крошечную проблему в конкретной ситуации. Я добавил прослушиватель событий (для входного события) в поле с поведением отладки. Значение, которое я ввожу в поле, обновляется (корректирующий формат валюты) через 500 мс. Итак, я обновляю поле, и поле обновляется само (свое значение). Если я использую этот (setDetectChangeHandler) метод, у меня получается цикл. "input" событие возникает постоянно. Поэтому вместо запуска события в set я передаю обратный вызов setDetectChangeHandler и вызываю его в set. Это дает больше контроля. - person akinuri; 23.05.2020
comment
Как мне это использовать? field это что? - person Jarad; 19.02.2021