Detect Input Value Change with Mutationobserver

Detect input value change with MutationObserver

To understand what is going on is necessary to clear up the difference between attribute (content attribute) and property (IDL attribute). I won't expand on this as in SO there are already excellent answers covering the topic:

  • Properties and Attributes in HTML
  • .prop() vs .attr()
  • What is happening behind .setAttribute vs .attribute=?

When you change the content of a input element, by typing in or by JS:

targetNode.value="foo";

the browser updates the value property but not the value attribute (which reflects the defaultValue property instead).

Then, if we look at the spec of MutationObserver, we will see that attributes is one of the object members that can be used. So if you explicitly set the value attribute:

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

MutationObserver will notify an attribute modification. But there is nothing like properties in the list of the spec: the value property can not be observed.

If you want to detect when an user alters the content of your input element, the input event is the most straightforward way. If you need to catch JS modifications, go for setInterval and compare the new value with the old one.

Check this SO question to know about different alternatives and its limitations.

How to detect input value length using MutationObserver

You can achieve what you want without using the observer:

        ready('#phoneInput', function (element) {
element.addEventListener("input", function (e) {
if (e.target.value.length < 10) {
element.style.borderColor = 'red';
return;
}
// otherwise, set it to green
element.style.borderColor = 'green';
});
});

If you have to use the observer I would move your conditions setting the borderColor to your check func:

                        if (!element.ready) {
element.ready = true;
// Invoke the callback with the element
listener.fn.call(element, element);
} else {
// do something
if (element.value.length < 10) {
element.style.borderColor = 'red';
return;
}
// otherwise, set it to green
element.style.borderColor = 'green';
}

and add and event listener to your ready section:

        ready('#phoneInput', function (element) {
element.addEventListener("input", function (e) {
element.setAttribute("value", e.target.value)
});
});

You will need to add attributes to your observer opts:

                    observer.observe(doc.documentElement, {
childList: true,
subtree: true,
attributes: true
});

Note: I think this may be a duplicate of/similar issue to Detect input value change with MutationObserver - there may be a few useful suggestions that answer your question in that thread? :)

MutationObserver detecting element appereance and changing of element's value

mutation is a MutationRecord object that contains the array-like addedNodes NodeList collection that you missed in your code, but it's not an array so it doesn't have forEach. You can use ES6 for-of enumeration in modern browsers or a plain for loop or invoke forEach.call.

A much easier solution for this particular case is to use the dynamically updated live collection returned by getElementsByClassName since it's superfast, usually much faster than enumeration of all the mutation records and all their added nodes within.

const target = document.querySelector('.player-bar');
// this is a live collection - when the node is added the [0] element will be defined
const badges = target.getElementsByClassName('el-badge__content');
let prevBadge, prevBadgeText;

const mo = new MutationObserver(() => {
const badge = badges[0];
if (badge && (
// the element was added/replaced entirely
badge !== prevBadge ||
// or just its internal text node
badge.textContent !== prevBadgeText
)) {
prevBadge = badge;
prevBadgeText = badge.textContent;
doSomething();
}
});
mo.observe(target, {subtree: true, childList: true});

function doSomething() {
const badge = badges[0];
console.log(badge, badge.textContent);
}

As you can see the second observer is added on the badge element itself. When the badge element is removed, the observer will be automatically removed by the garbage collector.

Is it possible to detect a change of select length immediately using MutationObserver?

Here's a demonstration of the behavior that you described. Now matter how many times I run it, the longest delta I see is always less than 1.5 ms. Is that what you mean by "delay"?

I wanted to write this as a comment initially, but there's no way to show a demo on-site in a comment, so I'm writing an answer. If you can refine your question to clarify or differentiate the issue, then I can potentially update this answer to address it (or delete it if it's no longer relevant to the new information).

<div>
<select>
<option>No options</option>
</select>
</div>
<script type="module">

const select = document.querySelector('select');
let {length} = select;
let previousTime = performance.now();

const observer = new MutationObserver((mutations) => {
for (const {addedNodes, removedNodes, type} of mutations) {
if (type !== 'childList') continue;
for (const node of addedNodes) {
if (node.tagName !== 'OPTION') continue;
length += 1;
}
for (const node of removedNodes) {
if (node.tagName !== 'OPTION') continue;
length -= 1;
}
}
const deltaMs = performance.now() - previousTime;
console.log('Observed', `length: ${length} (${deltaMs} ms)`);
});

observer.observe(select, {childList: true});

const setOptions = (items) => {
const options = items.map(str => {
const option = document.createElement('option');
option.textContent = str;
return option;
});

while (select.firstChild) select.firstChild.remove();
for (const option of options) select.appendChild(option);
previousTime = performance.now();
console.log('Set', `length: ${select.length}`);
};

const delay = (ms) => new Promise(res => setTimeout(res, ms));

setOptions(['a', 'b', 'c']);
await delay(2e3);

setOptions(['none']);
await delay(2e3);

setOptions(['one', 'two', 'three']);
await delay(2e3);

observer.disconnect();

</script>

Mutation Observer Not Detecting Text Change

It's because textContent triggers a different change than innerHTML, and your observer configuration is not configured to observe the changes made by textContent.

textContent changes the child text node of the target. According to MDN setting textContent:

Setting this property on a node removes all of its children and
replaces them with a single text node with the given value.

While innerHTML changes the the element itself, and it's subtree.

So to catch innerHTML your configuration should be:

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

While to catch textContent use:

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

Demo:

function mutate(mutations) {  mutations.forEach(function(mutation) {    alert(mutation.type);  });}
setTimeout(function() { document.querySelector('div#mainContainer > p').textContent = 'some other text.'; }, 1000); var target = document.querySelector('div#mainContainer > p') var observer = new MutationObserver( mutate ); var config = { characterData: false, attributes: false, childList: true, subtree: false };
observer.observe(target, config);
<div id="mainContainer">  <h1>Heading</h1>  <p>Paragraph.</p></div>


Related Topics



Leave a reply



Submit