Question

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?

Was it helpful?

Solution

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.

OTHER TIPS

I couldn't figure out how to make the variable or the function global, though here's a work around that should work just fine for you:

function hideimdbratings() {
    if (document.getElementsByClassName('star-box')[0]) {

        // hide the "star-box" container
        var b = document.getElementsByClassName('star-box')[0];
        b.style.display = 'none';

        // add the "Rate!" link
        var n = document.createElement('a');
        n.setAttribute('href', 'javascript:void(0);');
        n.setAttribute('onclick', 'document.getElementsByClassName("star-box")[0].style.display = "block"; this.style.display = "none";');
        n.innerHTML = 'Rate!';
        b.parentNode.insertBefore(n, b.nextSibling);
    }
}

window.addEventListener('load', hideimdbratings, false);

Aha! Found it. Try this:

var uw = (this.unsafeWindow) ? this.unsafeWindow : window;
var b = document.getElementsByClassName('star-box')[0];
uw.reenabled = b.innerHTML;
b.innerHTML = '<a href="javascript:void(0);" onclick="document.getElementsByClassName(\'star-box\')[0].innerHTML = reenabled;">Rate!</a>';

Inspired by this post

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top