In a userscript, I do lots of detection of element changes using waitForKeyElements()
.
However, I've run into a specific example where waitForKeyElements
doesn't reliably fire on change.
My userscript adjusts theirID
to insert the internal span, such as you see here:
<span id='theirID' class='myclass'>
<span class='myclass2'>text</span> more text
</span>
The site then changes this code to erase my internal span, so that it then looks like:
<span id='theirID' class='myclass'>
some text
</span>
For some reason, on some cases, waitForKeyElements
just doesn't get triggered on that change. I've tried to detect changes on the outer span, the inner span, the outer span's parent, its parent, etc.
So I'm now wondering if there's some other way to detect, for example, whether the element w/ myclass2
(the inner span) has vanished? I could poll $('.myclass2').length
I suppose, but I wouldn't know where in the document it vanished from. I'd ideally know the specific parent that held the now-missing item.
Any ideas?
Here are two answers, one for this specific case (you're using waitForKeyElements
, and another for the general case.
Since you are using waitForKeyElements()
already, you can take advantage of the return value in the node handler.
Suppose the target page originally had HTML like this:
<span id='theirID1' class='myclass'>
Original text 1.
</span>
And your script used waitForKeyElements()
like this:
waitForKeyElements ("span.myclass", wrapImportantWords);
function wrapImportantWords (jNode) {
var newContent = jNode.text ().replace (/Original/ig, "<span class='myclass2'>My GM</span>");
jNode.html (newContent);
}
Yielding:
<span class="myclass" id="theirID1">
<span class="myclass2">My GM</span> text 1.
</span>
Then, you could:
wrapImportantWords
return true
-- which tells waitForKeyElements
that the node was not found after all, so it keeps checking.span.myclass2
is (still) present.Like so:
waitForKeyElements ("span.myclass", wrapImportantWords);
function wrapImportantWords (jNode) {
if (jNode.has ("span.myclass2").length == 0) {
var newContent = jNode.text ().replace (/Original/ig, "<span class='myclass2'>My GM</span>");
jNode.html (newContent);
}
return true;
}
In your specific case, this seems to be overkill. But, here's a method for general reference.
Notes:
span.myclass2
s are deleted so we observe their parents (span.myclass
).Here is a complete Firefox Greasemonkey script. You can test it against this page. (Note that the Chrome code is the same except for the usual changes due to the lack of @require
.)
// ==UserScript==
// @name _Node watcher 1
// @include http://jsbin.com/exigal/*
// @include http://YOUR_SERVER.COM/YOUR_PATH/*
// @require http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js
// @require https://gist.github.com/raw/2625891/waitForKeyElements.js
// @grant GM_addStyle
// ==/UserScript==
/*- The @grant directive is needed to work around a design change introduced
in GM 1.0. It restores the sandbox.
*/
waitForKeyElements ("span.myclass", wrapImportantWords);
function wrapImportantWords (jNode) {
var newContent = jNode.text ().replace (
/Original/ig, "<span class='myclass2'>My GM</span>"
);
jNode.html (newContent);
}
/*--- Start of Mutation observer code...
*/
var targetNodes = $("span.myclass");
var MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
var myObserver = new MutationObserver (mutationHandler);
var obsConfig = {
childList: true, characterData: true, attributes: true, subtree: true
};
//--- Add a target node to the observer. Can only add one node at a time.
targetNodes.each ( function () {
myObserver.observe (this, obsConfig);
} );
function mutationHandler (mutationRecords) {
mutationRecords.forEach ( function (mutation) {
if ( mutation.type == "childList"
&& typeof mutation.removedNodes == "object"
) {
var remdNodes = $(mutation.removedNodes);
if (remdNodes.is ("span.myclass2") ) {
console.log ("Desired node was deleted! Restoring...");
var targNode = $(mutation.target);
wrapImportantWords (targNode);
}
}
} );
}