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
How to Dodge Jquery Promises Completely When Chaining Two Async Jquery Functions
Alternation Operator Inside Square Brackets Does Not Work
React-Router - Pass Props to Handler Component
How to Tell If Caps Lock Is on Using JavaScript
Remove All Elements Contained in Another Array
Add a "Hook" to All Ajax Requests on a Page
Array VS. Object Efficiency in JavaScript
How to Get a Word Under Cursor Using JavaScript
Jquery Check If Element Is Visible in Viewport
How to Programmatically Click a Link with JavaScript
How to Mock an Es6 Module Import Using Jest
Redirect Parent Window from an Iframe Action
Ajax Jquery Refresh Div Every 5 Seconds