Reputation: 2060
I am using the following excellent userscript (for Chrome, Chrome's Tampermonkey and Firefox Greasemonkey). It's supposed to display movie ratings next to each IMDb movie link, but it stopped working properly.
This is the complete script:
// ==UserScript==
// @name Add IMDb rating next to all IMDb links (+voter count)
// @author Ali
// @description Adds movie ratings and number of voters to any IMDb link. Modified version of http://userscripts.org/scripts/show/9174
// @include *
// @version 2013-05-12
// @namespace http://userscripts.org/scripts/show/96884
// @grant GM_xmlhttpRequest
// @downloadURL http://www.alibakir.com/upload/addimdbratings.js
// @updateURL http://www.alibakir.com/upload/addimdbratings.js
// ==/UserScript==
var IMDBpluginlinks = document.links;
var IMDBcontinueelement=document.createElement("button");
IMDBcontinueelement.innerHTML="Get rating";
function processIMDBLinks(s){
IMDBcontinueelement.style.display = 'none';
var r=0;
for (IMDBi = s; IMDBi < IMDBpluginlinks.length; IMDBi++) {
if (IMDBpluginlinks[IMDBi].href.indexOf("/title/") != -1 && IMDBpluginlinks[IMDBi].href.indexOf("imdb.") != -1){
if(r>300){
IMDBcontinueelement.onclick=function(){ processIMDBLinks(IMDBi); };
IMDBcontinueelement.style.display='inline';
IMDBpluginlinks[IMDBi].parentNode.insertBefore(IMDBcontinueelement, IMDBpluginlinks[IMDBi]);
break;
}
r++;
GM_xmlhttpRequest({
method: 'get',
headers: {
},
url: IMDBpluginlinks[IMDBi].href,
onload: function (IMDBi){return function(result) {
rating = result.responseText.match(/Users rated this (.*) \(/);
votes = result.responseText.match(/\((.*) votes\) -/);
IMDBpluginlinks[IMDBi].parentNode.insertBefore(document.createElement("span"), IMDBpluginlinks[IMDBi]).innerHTML = (rating ? "<b> [" + rating[1] + " - "+votes[1]+"] </b>" : "<b style='color: red;'>[NA] </b> ");
}}(IMDBi)
});
}
}
}
processIMDBLinks(0);
And this is how an example page looks at the moment:
As you can see, the script displays the results in the wrong places.
Why does it present them in the wrong place and how can it be fixed?
Upvotes: 0
Views: 660
Reputation: 93473
The issue causing your main complaint (results in the wrong places) is that GM_xmlhttpRequest
operates asynchronously (which is good) and the onload
was improperly constructed.
You either need to wrap the call to GM_xmlhttpRequest
in a proper closure or provide a context
in the GM_xmlhttpRequest
call. (See the code below.)
For more information on why closures are needed, see this answer to the same type of problem.
Other big problems include AJAX-fetching dozens of improper links and firing on every page and iframe. Both of these slow the browser down quite a bit.
Don't use @include *
. Even if you don't mind, other users of the script will. Add @include
or @match
lines for just the sites that you know have IMDB links.
I thought that I might want to use this script myself, so I started cleaning it up. You can read the inline comments and compare to the original script for an idea of some of the lesser problems. (Don't use onclick
and always check match
returns, etc.)
// ==UserScript==
// @name add IMDb rating next to all IMDb links (+voter count)
// @description Adds movie ratings and number of voters to any imdb link. Modified version of http://userscripts.org/scripts/show/96884
// @match *://www.imdb.com/*
// @grant GM_xmlhttpRequest
// ==/UserScript==
var maxLinksAtATime = 50; //-- pages can have 100's of links to fetch. Don't spam server or browser.
var fetchedLinkCnt = 0;
function processIMDB_Links () {
//--- Get only links that could be to IMBD movie/TV pages.
var linksToIMBD_Shows = document.querySelectorAll ("a[href*='/title/']");
for (var J = 0, L = linksToIMBD_Shows.length; J < L; J++) {
var currentLink = linksToIMBD_Shows[J];
/*--- Strict tests for the correct IMDB link to keep from spamming the page
with erroneous results.
*/
if ( ! /^(?:www\.)?IMDB\.com$/i.test (currentLink.hostname)
|| ! /^\/title\/tt\d+\/?$/i.test (currentLink.pathname)
)
continue;
if (! currentLink.getAttribute ("data-gm-fetched") ){
if (fetchedLinkCnt >= maxLinksAtATime){
//--- Position the "continue" button.
continueBttn.style.display = 'inline';
currentLink.parentNode.insertBefore (continueBttn, currentLink);
break;
}
fetchTargetLink (currentLink); //-- AJAX-in the ratings for a given link.
//---Mark the link with a data attribute, so we know it's been fetched.
currentLink.setAttribute ("data-gm-fetched", "true");
fetchedLinkCnt++;
}
}
}
function fetchTargetLink (linkNode) {
//--- This function provides a closure so that the callbacks can work correctly.
/*--- Must either call AJAX in a closure or pass a context.
But Tampermonkey does not implement context correctly!
(Tries to JSON serialize a DOM node.)
*/
GM_xmlhttpRequest ( {
method: 'get',
url: linkNode.href,
//context: linkNode,
onload: function (response) {
prependIMDB_Rating (response, linkNode);
},
onload: function (response) {
prependIMDB_Rating (response, linkNode);
},
onabort: function (response) {
prependIMDB_Rating (response, linkNode);
}
} );
}
function prependIMDB_Rating (resp, targetLink) {
var isError = true;
var ratingTxt = "** Unknown Error!";
if (resp.status != 200 && resp.status != 304) {
ratingTxt = '** ' + resp.status + ' Error!';
}
else {
if (/\(awaiting \d+ votes\)|\(voting begins after release\)|in development/i.test (resp.responseText) ) {
ratingTxt = "NR";
isError = false;
}
else {
var ratingM = resp.responseText.match (/Users rated this (.*) \(/);
var votesM = resp.responseText.match (/\((.*) votes\) -/);
if (ratingM && ratingM.length > 1 && votesM && votesM.length > 1) {
isError = false;
ratingTxt = ratingM[1] + " - " + votesM[1];
}
}
}
var resltSpan = document.createElement ("span");
resltSpan.innerHTML = '<b> [' + ratingTxt + '] </b> ';
if (isError)
resltSpan.style.color = 'red';
//var targetLink = resp.context;
//console.log ("targetLink: ", targetLink);
targetLink.parentNode.insertBefore (resltSpan, targetLink);
}
//--- Create the continue button
var continueBttn = document.createElement ("button");
continueBttn.innerHTML = "Get ratings";
continueBttn.addEventListener ("click", function (){
fetchedLinkCnt = 0;
continueBttn.style.display = 'none';
processIMDB_Links ();
},
false
);
processIMDB_Links ();
Upvotes: 2