Trigger on DOM text changed - javascript

Im developing some web-automatization tool (C# but this topic is not abou it..).
Tool unsing WebBrowser as a browser and connect to some web-site.
Web site has dynamic content, it updates once per second. Web site using setTimeout to update its content. Then data resopnsed web site update content inside DOM element (table). Its delete 3 old rows and insert 3 new rows.
My target is to make a trigger for this event.
I checked and the website doesn't use document.write. As I understand there are some ways the site can modify content. First is innerHTML property, but table has 4 rows , site dont update/delete first row. So another way is appendChild and removeChild its more real.
So my question is how i can trigger this action?
As i know it possible to define my own funcs for DOM element. So how i can redefine appendChild and removeChild methods?
Possible something like this?:
var realRC = contentTable.removeChild;
contentTable.removeChild = function(el){
alert("Trigged!");
realRC(el);
}

I assume you mean trigger when you say "trig".
Triggering it
In browsers timeouts and intervals all have IDs, you can clear them but you can't trigger it directly. The best bet would be looking for setTimeout/setInterval statements and replicating them.
Listening to the event
Anyway, there is a dirty way and a clean way. Let's assume you have access to your table in:
var target = document.querySelector('#some-id');
The clean way would be using DOM Mutation Observers
// Example from MDN
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
console.log(mutation.type); // here you'll get the changes
});
});
// configuration of the observer:
var config = { attributes: true, childList: true, characterData: true };
// pass in the target node, as well as the observer options
observer.observe(target, config);
The dirty way would be dirty checking
It's better supported and more stable since observers are really new and there are still issues.
Every 16 miliseconds check if the .innerHTML value of the table changed.
var oldHTML = "";
setTimeout(function(){
if(target.innerHTML !== oldHTML){
changed(); // changed is a function that you define and notify
}
},16);
If there are HTML changes like position/etc you can use .textContent instead.
Honestly, the dirty way is simpler and more stable - but uglier. Still I'd go for it.

Benjamin Gruenbaum solve it)
Im post this here for more readable!
var dataTable = document.getElementById("event_1538016");
dataTable.appendChild = function(node)
{
console.log("trigged!");
document.appendChild.apply(dataTable,[node]); // args is array!!
return node;
}
works perfectly on mozilla! Im hope it will works on IE9+

Related

How to keep the title of a website as original when the website loaded and auto-add '(1 message)' in the front title in Javascript?

There are some websites that will auto add '(1 message)' in the front title, and I want to remove the characters added.
I try to use 'observe' to the element title to keep it as original in a Tampermonkey script, but some pages work, some pages with the same host do not.
Even it seems that it doesn't get the real original title of the page that run-at the document-start, and it will work one time after the title changes again.
So that means that the page title keeps adding '(1 message)' no matter when the title deletes '(1 message)'.
Is there another way to keep the title as original or did I miss something in the following code?
// identify an element to observe
var originalInnerHTML = document.getElementsByTagName("title")[0].innerHTML;
var elementToObserve = window.document.getElementsByTagName("title")[0];
// create a new instance of 'MutationObserver' named 'observer',
// passing it a callback function
const observer = new MutationObserver(function(mutationsList, observer) {
console.log(mutationsList);
var titleElement=document.getElementsByTagName("title")[0];
titleElement.innerHTML = originalInnerHTML;
observer.disconnect();//stop the MutationObserver
});
// call 'observe' on that MutationObserver instance,
// passing it the element to observe, and the options object
observer.observe(elementToObserve, {characterData: true, childList: true, attributes: true, subtree: true});
Fixing The Observer
The observer should work. The thing that jumps out is that you're disconnecting the observer the first time it sees a change. So it won't see the next change. To fix that, remove the call to disconnect.
If The Observer Doesn't Work
If you fix that but find that the observer isn't reliable (which would surprise me), then I'd take a simple-minded approach to it:
const originalTitle = document.title;
setInterval(() => {
document.title = originalTitle;
}, 100);
That will grab the title when the code runs and then repeatedly replace the old title with it, every 10th of a second. (Or make it every fourth of a second — 250ms  — or whatever.) Note that browsers will de-prioritize timers in inactive tabs, but that should affect their code updating the title as much as your code updating the title.
(Side note: I've used document.title there, as it's simpler than the getElementsByTagName("title")[0].innerHTML approach.)

Javascript object destroy event handler

MyMapStaticObject
var PlaceViewModel = function(){
MyMapStaticObject.addLayer(someLayer);
}
PlaceViewModel.prototype.addMarker = function(item){
}
I have a PlaceViewModel that has a function named addMarker to add marker to map. I will use PlaceViewModel new istances in different classes.
var inst = new PlaceViewModel();
When I initialize the PlaceViewModel, I am adding new layer to map via MyMapStaticObject. I should remove layer when instance destroyed.
Can I handle javascript destroy event?
Javascript does not have a destroy event. It is a garbage collected language and will free an object when there is no longer any code that can reach the object reference. When it does free the object, it does not provide any event to notify of that.
If you want to implement some sort of clean-up code that will remove the layer, then you will have to add a method that you can call when you are done with the object, so you can call that method and it can then remove the layer in that method. Calling this method will have to be a manual operation on your part (most likely it will be hooked into the management of other things going on in your code and you can call it by that code at the appropriate time).
Bit late to the party here, but I wanted to know when an object got destroyed. Problem is JS doesn't have any built in way of doing this. I wanted this so that I could add websocket events to a page, and then remove when it went to another page, of course I could have implemented this in say the page loading section, but I have different frameworks and wanted a more generic solution to object destroying problem. Javascript is a garbage collected language, but it still would have been nice to have some life-cycle events we could attach too. There is of course proxy's, but again that wouldn't help here, because it's the proxy itself I would need to know that has been deleted.
Well, there is one place you do get a kind of destroy event, and that's with the MutationObserver, that most modern browsers now support. It's not strictly destroying, it's adding and removing nodes from the DOM. But generally speaking if you have events, you are likely to have some DOM node you could attach too, or even if it's none visual you could just add a none visible DOM element.
So I have a little function called domDestroy(element, obj), when it detects the element been removed, it then checks if the obj has a destroy method, if one exists it will call it.
Now one gotcha I had is that I create my pages in an hidden DOM node, and of course when I placed into the visible DOM node, I was getting a delete because I was detaching from the invisible DOM node, and then attaching to the visible DOM. Not what we want at all.
The solution was pretty simple, when doing this kind of double buffering, it's normally done in 1 step, eg. hide current page, show new page. So what I do is keep track of when it's been removed and keep in a simple Set, and then also keep track of elements been added, and if this element is part of the Set I will remove it. I then just check this Set again on the next tick, if's it's still there, it's been really deleted and we call the destroy method of the object.
Below is a simple example, basically if you right click and inspect the page, you can move the LI's up and down with dragging and dropping, this would cause a DOM detach and re-attach,. But if you instead delete one of the LI's, you will notice it say delete then, because it now knows it wasn't re-attached to another DOM.
Of course, one thing to be aware of, if you do any attaching / detaching of DOM elements try and do this within the same tick, IOW: be aware of asynchronous ops in between. Also you might use detached DOM's to build your pages, here you could easily alter the function to cope with this too, basically add these using the destroyObserver.observe(.
const dsy = "__dom-destroy-obj";
const destroyList = new Set();
let tm;
function cleanUp() {
tm = null;
for (const el of destroyList) {
for (const d of el[dsy]) {
d.destroy();
}
}
destroyList.clear();
}
function checkDestroy(el) {
if (el[dsy]) {
for (const d of el[dsy]) {
if (!d.destroy) {
console.warn("No destroy, on dom-destroy-obj target");
} else {
destroyList.add(el);
if (tm) return; //already a timer running
tm = setTimeout(cleanUp, 1);
}
}
}
if (el.childNodes) for (const n of el.childNodes) checkDestroy(n);
}
function checkAdded(el) {
if (el[dsy]) {
destroyList.delete(el);
}
if (el.childNodes) for (const n of el.childNodes) checkAdded(n);
}
const destroyObserver = new MutationObserver(
function (mutations) {
for (const m of mutations) {
if (m.removedNodes.length) {
for (const i of m.removedNodes) {
checkDestroy(i);
}
}
if (m.addedNodes.length) {
for (const i of m.addedNodes) {
checkAdded(i);
}
}
}
}
);
destroyObserver.observe(document.body, {
childList: true,
subtree: true
});
function domDestroy(element, obj) {
if (!element[dsy]) element[dsy] = new Set();
element[dsy].add(obj);
}
//simple test.
for (const i of document.querySelectorAll("li")) {
domDestroy(i, {
destroy: () => console.log("destroy")
});
}
<span>
From your browsers inspector, try moving the LI's, and deleting them. Only when you delete the DOM node, should the destroy method get called.
</span>
<ul>
<li>Re order</li>
<li>Or delete</li>
<li>Some of these</li>
</ul>

Utilizing Firefox's default/built-in Event Listeners

I have a context menuitem which is activated if an image is right-clicked, the exact same way that 'context-copyimage' is activated.
Is it possible to tie/pair that menuitem to the 'context-copyimage' therefore eliminating the need to add extra (duplicate) event-listeners and show/hide handlers??!!
(Adding an observer to 'context-copyimage' defeats the purpose)
If not, is it possible to use the event-listener that 'context-copyimage' uses?
Update:
I am trying to reduce listeners. At the moment, script has a popupshowing listeners. On popupshowing, it checks for gContextMenu.onImag and if true, it shows the menuitem. Firefox's context-copyimage does the exact same thing. I was wondering if it was possible to tie these 2 in order to remove/reduce the in-script event listeners.
I was also chatting with Dagger and he said that:
... the state of built-in items isn't set from an event handler, it's
set from the constructor for nsContextMenu, and there are no
mechanisms to hook into it
So it seems, that is not possible
No, there is no sane way of avoiding the event listener that would perform better than another event listener and is compatible with unloading the add-on in session.
Hooking nsContextMenu
As you have been already told, the state is initialized via gContextMenu = new nsContextMenu(...). So you'd need to hook the stuff, which is actually quite easy.
var newProto = Object.create(nsContextMenu.prototype);
newProto.initMenuOriginal = nsContextMenu.prototype.initMenu;
newProto.initMenu = function() {
let rv = this.initMenuOriginal.apply(this, arguments);
console.log("ctx", this.onImage, this); // Or whatever code you'd like to run.
return rv;
};
nsContextMenu.prototype = newProto;
Now, the first question is: Does it actually perform better? After all this just introduced another link in the prototype-chain. Of course, one could avoid Object.create and just override nsContextMenu.prototype.initMenu directly.
But the real question is: How would one remove the hook again? Answer: you really cannot, as other add-ons might have hooked the same thing after you and unhooking would also unhook the other add-ons. But you need to get rid of the reference, or else the add-on will leak memory when disabled/uninstalled. Well, you could fight with Components.utils.makeObjectPropsNormal, but that doesn't really help with closed-over variables. So lets avoid closures... Hmm... You'd need some kind of messaging, e.g. event listeners or observers... and we're back to square one.
Also I wouldn't call this sane compared to
document.getElementById("contentAreaContextMenu").addEventListener(...)
I'd call it "overkill for no measurable benefit".
Overriding onpopupshowing=
One could override the <menupopup onpopupshowing=. Yeah, that might fly... Except that other add-ons might have the same idea, so welcome to compatibility hell. Also this again involves pushing stuff into the window, which causes cross-compartment wrappers, which makes things error-prone again.
Is this a solution? Maybe, but not a sane one.
What else?
Not much, really.
Yes this is absolutely possible.
Morat from mozillazine gave a great solution here: http://forums.mozillazine.org/viewtopic.php?p=13307339&sid=0700480c573017c00f6e99b74854b0b2#p13307339
function handleClick(event) {
window.removeEventListener("click", handleClick, true);
event.preventDefault();
event.stopPropagation();
var node = document.popupNode;
document.popupNode = event.originalTarget;
var menuPopup = document.getElementById("contentAreaContextMenu");
var shiftKey = false;
gContextMenu = new nsContextMenu(menuPopup, shiftKey);
if (gContextMenu.onImage) {
var imgurl = gContextMenu.mediaURL || gContextMenu.imageURL;
}
else if (gContextMenu.hasBGImage && !gContextMenu.isTextSelected) {
var imgurl = gContextMenu.bgImageURL;
}
console.log('imgurl = ', imgurl)
document.popupNode = node;
gContextMenu = null;
}
window.addEventListener("click", handleClick, true);
this gives you access to gContextMenu which has all kinds of properties like if you are over a link, or if you right click on an image, and if you did than gContextMenu.imageURL holds its value. cool stuff
This code here console logs imgurl, if you are not over an image it will log undefined

Is there 'element rendered' event?

I need to accurately measure the dimensions of text within my web app, which I am achieving by creating an element (with relevant CSS classes), setting its innerHTML then adding it to the container using appendChild.
After doing this, there is a wait before the element has been rendered and its offsetWidth can be read to find out how wide the text is.
Currently, I'm using setTimeout(processText, 100) to wait until the render is complete.
Is there any callback I can listen to, or a more reliable way of telling when an element I have created has been rendered?
The accepted answer is from 2014 and is now outdated. A setTimeout may work, but it's not the cleanest and it doesn't necessarily guarantee that the element has been added to the DOM.
As of 2018, a MutationObserver is what you should use to detect when an element has been added to the DOM. MutationObservers are now widely supported across all modern browsers (Chrome 26+, Firefox 14+, IE11, Edge, Opera 15+, etc).
When an element has been added to the DOM, you will be able to retrieve its actual dimensions.
Here's a simple example of how you can use a MutationObserver to listen for when an element is added to the DOM.
For brevity, I'm using jQuery syntax to build the node and insert it into the DOM.
var myElement = $("<div>hello world</div>")[0];
var observer = new MutationObserver(function(mutations) {
if (document.contains(myElement)) {
console.log("It's in the DOM!");
observer.disconnect();
}
});
observer.observe(document, {attributes: false, childList: true, characterData: false, subtree:true});
$("body").append(myElement); // console.log: It's in the DOM!
The observer event handler will trigger whenever any node is added or removed from the document. Inside the handler, we then perform a contains check to determine if myElement is now in the document.
You don't need to iterate over each MutationRecord stored in mutations because you can perform the document.contains check directly upon myElement.
To improve performance, replace document with the specific element that will contain myElement in the DOM.
There is currently no DOM event indicating that an element has been fully rendered (eg. attached CSS applied and drawn). This can make some DOM manipulation code return wrong or random results (like getting the height of an element).
Using setTimeout to give the browser some overhead for rendering is the simplest way. Using
setTimeout(function(){}, 0)
is perhaps the most practically accurate, as it puts your code at the end of the active browser event queue without any more delay - in other words your code is queued right after the render operation (and all other operations happening at the time).
This blog post By Swizec Teller, suggests using requestAnimationFrame, and checking for the size of the element.
function try_do_some_stuff() {
if (!$("#element").size()) {
window.requestAnimationFrame(try_do_some_stuff);
} else {
$("#element").do_some_stuff();
}
};
in practice it only ever retries once. Because no matter what, by the next render frame, whether it comes in a 60th of a second, or a minute, the element will have been rendered.
You actually need to wait yet a bit after to get the after render time. requestAnimationFrame fires before the next paint. So requestAnimationFrame(()=>setTimeout(onrender, 0)) is right after the element has been rendered.
In my case solutions like setTimeout or MutationObserver weren't totaly realiable.
Instead I used the ResizeObserver. According to MDN:
Implementations should, if they follow the specification, invoke
resize events before paint and after layout.
So basically the observer always fires after layout, thus we should be able to get the correct dimensions of the observed element.
As a bonus the observer already returns the dimensions of the element. Therefore we don't even need to call something like offsetWidth (even though it should work too)
const myElement = document.createElement("div");
myElement.textContent = "test string";
const resizeObserver = new ResizeObserver(entries => {
const lastEntry = entries.pop();
// alternatively use contentBoxSize here
// Note: older versions of Firefox (<= 91) provided a single size object instead of an array of sizes
// https://bugzilla.mozilla.org/show_bug.cgi?id=1689645
const width = lastEntry.borderBoxSize?.inlineSize ?? lastEntry.borderBoxSize[0].inlineSize;
const height = lastEntry.borderBoxSize?.blockSize ?? lastEntry.borderBoxSize[0].blockSize;
resizeObserver.disconnect();
console.log("width:", width, "height:", height);
});
resizeObserver.observe(myElement);
document.body.append(myElement);
This can also we wrapped in a handy async function like this:
function appendAwaitLayout(parent, element) {
return new Promise((resolve, reject) => {
const resizeObserver = new ResizeObserver((entries) => {
resizeObserver.disconnect();
resolve(entries);
});
resizeObserver.observe(element);
parent.append(element);
});
}
// call it like this
appendAwaitLayout(document.body, document.createElement("div")).then((entries) => {
console.log(entries)
// do stuff here ...
});
The MutationObserver is probably the best approach, but here's a simple alternative that may work
I had some javascript that built the HTML for a large table and set the innerHTML of a div to the generated HTML. If I fetched Date() immediately after setting the innerHTML, I found that the timestamp was for a time prior to the table being completely rendered. I wanted to know how long the rendering was taking (meaning I needed to check Date() after the rendering was done). I found I could do this by setting the innerHTML of the div and then (in the same script) calling the click method of some button on the page. The click handler would get executed only after the HTML was fully rendered, not just after the innerHTML property of div got set. I verified this by comparing the Date() value generated by the click handler to the Date() value retrieved by the script that was setting the innerHTML property of the div.
Hope someone finds this useful
suppose your element has classname class="test"
The following function continue test if change has occured
if it does, run the function
function addResizeListener(elem, fun) {
let id;
let style = getComputedStyle(elem);
let wid = style.width;
let hei = style.height;
id = requestAnimationFrame(test)
function test() {
let newStyle = getComputedStyle(elem);
if (wid !== newStyle.width ||
hei !== newStyle.height) {
fun();
wid = newStyle.width;
hei = newStyle.height;
}
id = requestAnimationFrame(test);
}
}
let test = document.querySelector('.test');
addResizeListener(test,function () {
console.log("I changed!!")
});
when you make for example
var clonedForm = $('#empty_form_to_clone').clone(true)[0];
var newForm = $(clonedForm).html().replace(/__prefix__/g, next_index_id_form);
// next_index_id_form is just a integer
What am I doing here?
I clone a element already rendered and change the html to be rendered.
Next i append that text to a container.
$('#container_id').append(newForm);
The problem comes when i want to add a event handler to a button inside newForm, WELL, just use ready event.
$(clonedForm).ready(function(event){
addEventHandlerToFormButton();
})
I hope this help you.
PS: Sorry for my English.
According to #Elliot B.'s answer, I made a plan that suits me.
const callback = () => {
const el = document.querySelector('#a');
if (el) {
observer.disconnect();
el.addEventListener('click', () => {});
}
};
const observer = new MutationObserver(callback);
observer.observe(document.body, { subtree: true, childList: true });

how to intercept innerHTML changes in javascript?

I need to intercept any changes in the content of a cell inside my webpage.
The following code shows me that addEventListener does not work.
function modifyText() {
alert("!");
}
var el=document.getElementById("mycell");
el.innerHTML="a"
el.addEventListener("change", modifyText, false);
// After next instruction I expect an alert message but it does not appear...
el.innerHTML="Z";
The code is just a toy example. In my real case the changes in the page (and therefore in the cell, too) are made by a webapp that I have NO control over.
There is a modern way to catch innerhtml changes:
https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver/observe
Example:
// identify an element to observe
elementToObserve = window.document.getElementById('y-range').children[0];
// create a new instance of 'MutationObserver' named 'observer',
// passing it a callback function
observer = new MutationObserver(function(mutationsList, observer) {
console.log(mutationsList);
});
// call 'observe' on that MutationObserver instance,
// passing it the element to observe, and the options object
observer.observe(elementToObserve, {characterData: false, childList: true, attributes: false});
childList mutation fires on innerHTML change.
You can't listen to a DOM element change that way. change event is mostly for inputs
There is some other new DOM 3 events that would help you on this.
Here is some:
DOMCharacterDataModified //Draft
DOMSubtreeModified
Does this Most efficient method of detecting/monitoring DOM changes? help?
It seems like there aren't any 100% cross browser solutions, and one of the workarounds is to poll the elements of interest to see if their innerHTML.length changes!

Categories