Fire Greasemonkey Script on Ajax Request

Fire Greasemonkey script on AJAX request

The smart way to rerun the script's code on AJAX requests, is to focus on the key bits of the page and check for changes.

For example, suppose a page contained HTML like so:

<div id="userBlather">
<div class="comment"> Comment 1... </div>
<div class="comment"> Comment 2... </div>
...
</div>

and you wanted the script to do something with each comment as it came in.

Now you could intercept all AJAX calls, or listen for DOMSubtreeModified(deprecated), or use MutationObservers, but these methods can get tricky, finicky, and overly complicated.

A simpler, more robust way to get ajax-ified content on a wild page is to poll for it using something like the waitForKeyElements function, below.

For example, this script will highlight comments that contain "beer", as they AJAX-in:

// ==UserScript==
// @name _Refire on key Ajax changes
// @include http://YOUR_SITE.com/YOUR_PATH/*
// @require http://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js
// ==/UserScript==

function highlightGoodComments (jNode) {

//***** YOUR CODE HERE *****

if (/beer/i.test (jNode.text () ) ) {
jNode.css ("background", "yellow");
}
//...
}
waitForKeyElements ("#userBlather div.comment", highlightGoodComments);

/*--- waitForKeyElements(): A utility function, for Greasemonkey scripts,
that detects and handles AJAXed content.

IMPORTANT: This function requires your script to have loaded jQuery.
*/
function waitForKeyElements (
selectorTxt, /* Required: The jQuery selector string that
specifies the desired element(s).
*/
actionFunction, /* Required: The code to run when elements are
found. It is passed a jNode to the matched
element.
*/
bWaitOnce, /* Optional: If false, will continue to scan for
new elements even after the first match is
found.
*/
iframeSelector /* Optional: If set, identifies the iframe to
search.
*/
) {
var targetNodes, btargetsFound;

if (typeof iframeSelector == "undefined")
targetNodes = $(selectorTxt);
else
targetNodes = $(iframeSelector).contents ()
.find (selectorTxt);

if (targetNodes && targetNodes.length > 0) {
btargetsFound = true;
/*--- Found target node(s). Go through each and act if they
are new.
*/
targetNodes.each ( function () {
var jThis = $(this);
var alreadyFound = jThis.data ('alreadyFound') || false;

if (!alreadyFound) {
//--- Call the payload function.
var cancelFound = actionFunction (jThis);
if (cancelFound)
btargetsFound = false;
else
jThis.data ('alreadyFound', true);
}
} );
}
else {
btargetsFound = false;
}

//--- Get the timer-control variable for this selector.
var controlObj = waitForKeyElements.controlObj || {};
var controlKey = selectorTxt.replace (/[^\w]/g, "_");
var timeControl = controlObj [controlKey];

//--- Now set or clear the timer as appropriate.
if (btargetsFound && bWaitOnce && timeControl) {
//--- The only condition where we need to clear the timer.
clearInterval (timeControl);
delete controlObj [controlKey]
}
else {
//--- Set a timer, if needed.
if ( ! timeControl) {
timeControl = setInterval ( function () {
waitForKeyElements ( selectorTxt,
actionFunction,
bWaitOnce,
iframeSelector
);
},
300
);
controlObj [controlKey] = timeControl;
}
}
waitForKeyElements.controlObj = controlObj;
}

Update:

For convenience, waitForKeyElements() is now hosted on GitHub.

This answer shows an example of how to use the hosted function.

How to load a greasemonkey script after AJAX-Request

Thanks Howard. It was the way in the right direction.

It's quite hard to find the right entry point to hijack a function on a minifyied script. But I found that there is a huck in the twitter script. It does call window.onPageChange after the Ajax request. (I wonder if this is a common best practice in javascript and if other do this as well?) I did found some code on http://userscripts.org/scripts/review/47998 wich does use this posibility to attach events.

        if (typeof unsafeWindow.onPageChange === 'function') {
var _onPageChange = unsafeWindow.onPageChange;
unsafeWindow.onPageChange = function(){
_onPageChange();
filter();
};
} else {
unsafeWindow.onPageChange = filter;
}

How can I run a Greasemonkey function after an AJAX update?

This is a common problem.

Use the waitForKeyElements() function as shown in this answer.

By default, it will handle every new element, of the type that you specify via jQuery selector, once each, and within milliseconds of that new element appearing.

~~~

Link to the target page and specify exactly what you are doing for a more specific application.

How do I make Greasemonkey do something when an AJAX call returns?

You can intercept the AJAX calls like:

  • This answer (generic JavaScript)

    or
  • This answer (practical Greasemonkey)

But that gets messy and you have the problem of telling the AJAX calls you care about from the ones you don't.

In practice, it's simpler to just focus on the end result HTML and process it as it changes.

I have a handy utility function that works very well in these situations. See this answer to "Fire Greasemonkey script on ajax request".

Using Greasemonkey and jQuery to intercept JSON/AJAX data from a page, and process it

Since the target page uses jQuery, you can eavesdrop on the JSON data easily using ajaxSuccess().

The problem then becomes getting the data from the page's scope to the GM sandbox... That can be done by putting the data into a special page node.

From there, it's just a matter of using my other (brilliant :D ) answer.

Putting it all together, the following should get you started.:

Update after almost 4 years: The code below is now obsolete due to many changes in Firefox and Greasemonkey. I don't plan on re-engineering it due to lack of interest and also because it's not the best approach for most RL tasks. For most cases; the most robust, portable, and reliable method remains smart polling. See an example of a handy utility for that.

// ==UserScript==
// @name _Fun with JSON
// @include http://www.trada.net/*
// @require http://ajax.googleapis.com/ajax/libs/jquery/1.5.1/jquery.min.js
// ==/UserScript==

//--- Create a cell for transmitting the date from page scope to GM scope.
$('body'). prepend ('<div id="LatestJSON_Data"></div>');

var J_DataCell = $('#LatestJSON_Data');

//--- Evesdrop on the page's AJAX calls and paste the data into our special div.
unsafeWindow.$('body').ajaxSuccess (
function (event, requestData)
{
J_DataCell.text (requestData.responseText);
}
);

//--- Listen for changes to the special div and parse the data.
J_DataCell.bind ('DOMSubtreeModified', ParseJSON_Data);

function ParseJSON_Data ()
{
//--- Get the latest data from the special cell and parse it.
var myJson = J_DataCell.text ();
var jsonObj = $.parseJSON (myJson);

//--- The JSON should return a 2-D array, named "d".
var AuctionDataArray = jsonObj.d;

//--- Loop over each row in the array.
$.each (
AuctionDataArray,
function (rowIndex, singleAuctionData) {

//--- Print the 7th column.
console.log ('Row: ' + (parseInt (rowIndex) + 1) + ' Column: 7 Value: ' + singleAuctionData[6]);
}
);
}

//--- Format our special cell with CSS. Add "visibility: hidden;" or "display: none;", if desired.
GM_addStyle ( (<><![CDATA[
#LatestJSON_Data
{
background: gold;
border: 3px ridge #0000DD;
font-size: 10px;
margin: 0 2em;
padding: 1ex 1em;
width: 94%;
opacity: 0.8;
overflow: hidden;
z-index: 666;
position: absolute;
color: black;
}
]]></>).toString () );


Note that the question's proposal may violate the site's Terms of Service and/or the site can take countermeasures. (They already obfuscate their JS.)

I have to refresh the page for my Greasemonkey script to run?

See "addEventListener only working at page refresh?" for more information and a similar scenario.

Page elements, that your script expects, are no doubt appearing after the load event has fired. Additionally, from your comments, it sounds like whole sections of the page are swapped out by AJAX, but the AJAX is polite enough to change the URL hash. This means you'll want to fire off the hashchange event.

Don't use addEventListener ("load"... in this case. Use the waitForKeyElements() utility in conjunction with hashchange.

Without refactoring the whole script to use jQuery (which would give clearer and more robust code), replace everything before function addLinks() {..., with:

// ==UserScript==
// @name Job Aids
// @description Aid in closing tickets
// @include https://techaccess.ad.qintra.com/WorkJobs/WorkJobs.aspx*
// @namespace camzilla.net
// @version 1.1.20121128
// @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.
*/

//-- Pages are "loaded" via AJAX...
window.addEventListener ("hashchange", fireOnNewPage, false);

waitForKeyElements ("#crasCircuitLoss", crasResults);
waitForKeyElements ("#finalTestInsightNo", finalResults);
waitForKeyElements ("#flatRateJacks", closingComments);
waitForKeyElements ("#ToneSlopeInsightNo", toneSlopeResults);
waitForKeyElements ("div[data-bind="CurrentJob.addr"]", addLinks);

function fireOnNewPage () {
switch (location.hash.toLowerCase() ) {
case "#finaltest":
case "#threetoneslope":
case "#codes":
case "#cras":
case "#jobinfo":
//-- No action needed, waitForKeyElements() handles this.
break;
default:
if (getCookie("updater") == null) {
var d = new Date();

setCookie("updater", d.getTime(), 1);
try {
updateCheck();
} catch(err) {
// alert('Update checking failed');
}
}
break;
}
}
fireOnNewPage (); //-- Initial run on initial, full page load.

How to get jQuery ajax calls to work from greasemonkey page events

Your code works fine for me. Why/How do you think $.get is not working?

Remember that you will never see the 'jQuery.get worked' message if there is a server error (404 etc.) with index.php. Did you check the Firebug Net panel, or Wireshark, etc. to see if the AJAX call was made?

Anyway, you can see that code working, plus some error handling if you install this Greasemonkey script:

// ==UserScript==
// @name _delme9h762
// @include http://YOUR_SERVER.COM/YOUR_PATH/*
// @include http://fiddle.jshell.net/ZqhRH/*
// @require http://code.jquery.com/jquery.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.
*/

function runMyFunc() {
console.log('myFunc is called');

$.get('index.php', function () {
console.log ('jQuery.get worked');
} )
.error ( function (respObj) {
console.log ("Error! ", respObj.status, respObj.statusText);
} )
.complete ( function (respObj) {
console.log ("AJAX Complete. Status: ", respObj.status);
} )
;
}

$("#someHook").before('<a id="myLink">Link</a>');

$('#myLink').click(runMyFunc);


And then visit fiddle.jshell.net/ZqhRH/1/show/.

Note, the console will show:

myFunc is called

Error! 404 NOT FOUND

AJAX Complete. Status: 404

On the jsFiddle site, because index.php does not exist there, but $.get() is otherwise working perfectly.



Related Topics



Leave a reply



Submit