I am trying to write a cross-browser userscript that hides IMDb's rating and replaces it with a link that allows the user to rate the show/film later on.
In Opera, this approach already works, but Firefox and Chrome both stumble upon an undefined function and/or variable. here's my code so far:
function hideimdbratings() {
if (document.getElementsByClassName("star-box")[0]) {
reenabled = document.getElementsByClassName("star-box")[0].innerHTML;
document.getElementsByClassName("star-box")[0].innerHTML = '<a href="#" id="showvote" onclick="reenableit();">Rate!</a>';
}
}
function reenableit() {
document.getElementsByClassName("star-box")[0].innerHTML = reenabled;
}
window.addEventListener('load', hideimdbratings, false);
The first part works fine, hideimdbratings()
is executed and hides the rating.
But, upon clicking the "Rate!"-link, Firefox and Chrome both say that reenableit()
is not defined.
Trying this:
onclick="document.getElementsByClassName(\"star-box\")[0].innerHTML = reenabled;"
Results in them saying reenabled
is not defined.
I tried putting the code directly into the event listener:
window.addEventListener("load", function(e) {
// ...
}, false);
and this way of defining the functions (with unsafeWindow for Firefox):
var window.reenableit = function() { }
But, whatever I do, both reenableit()
and reenabled
remain undefined. From what I understand, neither the function nor the variable are global in browsers other than Opera, but I can't seem to find a solution just yet.
What am I missing/doing wrong?
That code won't work in Chrome because Chrome userscripts operate in a sandbox ("isolated world"), and you cannot set or use page-scope javascript objects in Chrome. And, Chrome does not fully/properly support unsafeWindow
.
The code would work in Firefox+Greasemonkey with careful use of unsafeWindow
, but that is not recommended here (and won't help with Chrome).
The classic approach, when one needs to interact with page-scope javascript in a cross-browser way is to use Script Injection. This is the only thing that works well in Chrome.
However, the smartest thing to do is not use page-scope JS at all if you don't have to. And, for what's in this question, you don't need to. (Hint: never use onclick
or similar attributes! Always use addEventListener()
, or equivalent.)
Refactoring the code to avoid leaving the sandbox scope, it becomes:
function hideImdbRatings () {
var oldStarBoxHTML;
var starBox = document.getElementsByClassName ("star-box");
if (starBox.length) {
starBox = starBox[0];
oldStarBoxHTML = starBox.innerHTML;
starBox.innerHTML = '<a href="#" id="showVote">Rate!</a>';
document.getElementById ("showVote").addEventListener (
"click",
function () {
//-- "this" is a special javascript scope.
this.innerHTML = oldStarBoxHTML;
}
);
}
}
window.addEventListener ('load', hideImdbRatings, false);
However 2, you'll notice that all of the code so far, busts the interaction of IMDB's rating widget. This is because it's overwriting innerHTML
, which trashes the widget's event handlers. Don't use innerHTML
like that.
The smartest-er thing to do is to hide the block, similar to Geo's answer, like so:
function hideImdbRatings () {
var starBox = document.getElementsByClassName ("star-box");
if (starBox.length) {
starBox = starBox[0];
starBox.style.display = 'none';
var rateLink = document.createElement ('a');
rateLink.id = 'showVote';
rateLink.href = '#';
rateLink.textContent = 'Rate!';
starBox.parentNode.insertBefore (rateLink, starBox);
document.getElementById ("showVote").addEventListener (
"click",
function () {
//-- "this" is a special javascript scope.
this.style.display = 'none';
starBox.style.display = 'block';
}
);
}
}
window.addEventListener ('load', hideImdbRatings, false);
There is an additional factor that may stop the script from working in Chrome. By default, Chrome userscripts may run after the load
event. To work around that, specify @run-at document-end
in the metadata block of your script.