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 (deprecated), or use DOMSubtreeModified
MutationObserver
s, 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
Shiny: Start the App with Hidden Tabs, with No Delay
Is There a Jquery Autogrow Plugin for Text Fields
How to Parse JavaScript Using Nokogiri and Ruby
Sorting Arrays Numerically by Object Property Value
What's the Best Way to Calculate Date Difference in JavaScript
How to Pre-Populate a Jquery Datepicker Textbox with Today's Date
The $.Param( ) Inverse Function in JavaScript/Jquery
Leaderboard Ranking with Firebase
Simplest Way to Wait Some Asynchronous Tasks Complete, in JavaScript
IE8 Var W= Window.Open() - "Message: Invalid Argument."
How to Print an Iframe from JavaScript in Safari/Chrome
Ckeditor 4: Uncaught Typeerror: Cannot Read Property 'Langentries' of Null
How to Preview Uploaded Image Instantly with Paperclip in Ruby on Rails
Rendering React Components with Promises Inside the Render Method
JavaScript Search Array of Arrays