Recently, I've discovered Greasemonkey, a tool that lets me control the operation of my web browser.
I like to set time aside each week to learn something new about computers. Work has been busy, so I can't do it on company time, but I won't let that stop me! I spend an hour or so each night after my son is asleep, looking at technologies on the web. It was during one of these late night "geek sessions" that I discovered Greasemonkey, a Mozilla Firefox plugin that lets you write scripts that control the browser.
What does that have to do with IBM i programming? I thought this sounded interesting for integrating applications together. For example, you could have a third-party application with a web interface, and you want to get data from your RPG application loaded into the third party screen. Greasemonkey would call your RPG, get the data, insert it into the form fields on the screen, and submit the data--all with very little user interaction.
What Is Greasemonkey?
Greasemonkey is a browser add-on for Firefox. In simple terms, it lets you insert your own JavaScript code that will be loaded when the given web page is loaded. The JavaScript you choose can be run automatically when the page loads, or it can be set to run when a particular event occurs, such as clicking a button. When you install each script, you tell Greasemonkey which web pages it affects, by specifying the URL of the website. You can use wildcards if you want your script to run on multiple pages.
In order to use Greasemonkey scripts, you must be using Firefox version 1.5 or higher, and you must install the Greasemonkey add-on. The add-on is cross-platform; any operating system that can run Firefox can also run Greasemonkey. I've used it on both Windows and FreeBSD.
You can download the Greasemonkey add-on.
Although Greasemonkey does not work in Microsoft Internet Explorer (IE), a similar tool, IEScripts, is available. IEScripts is part of the IE7Pro add-on for IE. I don't use IE, so haven't tried this tool. But if you're interested, check out the site.
How Can You Use Greasemonkey?
You can do lots of interesting things with Greasemonkey. Perhaps the easiest thing to do is use HTML's Document Object Model (DOM) to modify an existing web page. Ever been to a web page that you think would be easier to use if it had a different feature? Maybe you don't like the colors on the page. Or maybe it's missing a "check all" button, or a "remember my settings" button? You can use Greasemonkey to modify the page.
You can also use Greasemonkey to automate a process on the page. Perhaps there are 50 links on the page, and you don't want to click them all manually. Or perhaps the page has data that you want to read into a variable, then insert somewhere else or send back to an RPG program. Or perhaps you want data from an RPG program to be inserted into a web page. All that is possible with Greasemonkey.
Existing Greasemonkey Scripts--Free!
There are a ton of free Greasemonkey scripts already available. They're a great way to get started with Greasemonkey, both as tools to enhance your browsing experience, and also as sample code to help you understand how to write your own scripts. A repository of Greasemoney scripts is available.
Writing Your Own
As a programmer, I really like the fact that Greasemoney lets me write code to make things do what I want them to do or work the way I want them to work.
A recent example showed up in the System iNetwork forums. Someone said that he has a hard time differentiating between links he's already visited versus those he has not, because the colors are similar. With Greasemonkey, I can change the way the colors appear. Here's an example:
// ==UserScript==
// @name System iNetwork Colors
// @namespace http://www.scottklement.com/scripts/forums/
// @description Change the color scheme in System iNetwork Forums
// @include http://forums.systeminetwork.com/*
// ==/UserScript==
GM_addStyle('.scklink:link { color: blue; }');
GM_addStyle('.scklink:visited { color: red; }');
var allLinks, thisLink, modColors, buttonText;
allLinks = document.evaluate(
'//a[@href]',
document,
null,
XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
null);
for (var i = 0; i < allLinks.snapshotLength; i++) {
thisLink = allLinks.snapshotItem(i);
thisLink.className="scklink";
}
Lines that start with // in JavaScript are comments. In this case, however, there are some very important comments that Greasemonkey uses to control how the script works. Everything between the ==UserScript== and the ==/UserScript== are special keywords. @name is the name of the script that will be displayed when it is installed into the browser. @namespace is used to keep my script's variables unique from other scripts on the Internet. For example, if I set a variable named "colors" in my script, and there was another script that also had a variable named "colors," they'd be kept separate as long as each has a different name space. @description is a description shown to the user to help him understand the purpose of the script, and @include specifies the default sites that the script will run under. (The user can change this.)
The lines beginning with GM_addStyle() add new CSS styles to the HTML document. When I visit a page that starts with "forums.systeminetwork.com," this script runs, and new CSS styles named "scklink" will be added to the page.
The remainder of the code queries the current web page to find all links (that is, <a> tags) that contain an href attribute. For each link found, the class name is changed to "scklink," therefore causing the page to display with my new colors. You'll note that visited links (those that I've already clicked) will be shown in red, whereas links I haven't visited will be displayed in blue.
Want to try it out? Open up a copy of Firefox in which you've already installed the Greasemonkey add-on. Then follow these steps:
- Click Tools| Greasemonkey|New User Script
- Under Name, type: System iNetwork Colors.
- Under Namespace, type: http://www.scottklement.com/scripts/forums/.
- Under Description, type: Change the color scheme in System iNetwork Forums.
- Under Includes, type: http://forums.systeminetwork.com/*.
- Leave Excludes blank, and click OK.
- If this is the first time you've used Greasemonkey, it will ask you which application you'd like to use to edit your scripts. I use Notepad.
- It should open Notepad (or whatever you chose) with a text document that looks like this:
// ==UserScript==
// @name System iNetwork Colors
// @namespace http://www.scottklement.com/scripts/forums/
// @description Change the color scheme in System iNetwork Forums
// @include http://forums.systeminetwork.com/*
// ==/UserScript==
As you can see, Greasemonkey already generated the keywords for you. Now, underneath that, copy/paste the code (from my example above) that queries the page and changes the colors of all the links. Just paste that into Notepad below the keywords section, and save your changes (by choosing Save from the File menu).
Now point your browser to http://forums.systeminetwork.com, or if you're already there, click the Firefox Reload button to reload the page. You should see the changed colors.
Although Greasemonkey scripts are written in JavaScript, there are some differences between a Greasemonkey script and the JavaScript you'd traditionally place in a web page.
- Greasemonkey scripts are saved inside the browser and will run only for a user who has them installed in his or her browser. By contrast, typically JavaScript is kept on the server and is run for anyone who visits the page.
- Because Greasemonkey runs only in Firefox, you don't have to worry about writing JavaScript code that will also work in IE.
- Greasemonkey scripts are "isolated" from other JavaScripts on the page to avoid some possible security flaws.
- A typical JavaScript that implements Ajax can communicate only with programs running on the same host that the web page was downloaded from. Greasemonkey Ajax can connect to any host.
- There are APIs available only to Greasemonkey scripts. These special functions begin with GM_. You've already seen one example, the GM_addStyle() API.
To extend that functionality, I add a button to the page so that I can turn the color changes on and off. Here's the code to do that:
// ==UserScript==
// @name System iNetwork Colors
// @namespace http://www.scottklement.com/scripts/forums/
// @description Change the color scheme in System iNetwork Forums
// @include http://forums.systeminetwork.com/*
// ==/UserScript==
// ----------------------------------------------------------------
// Add styles to the page
// ----------------------------------------------------------------
GM_addStyle('.scklink:link { color: blue; }');
GM_addStyle('.scklink:visited { color: red; }');
GM_addStyle('.sckbutton { position:absolute; z-index:9999; top:2px; left:2px;
overflow:hidden; }');
// ----------------------------------------------------------------
// if the change_colors variable is on, change the links to
// use the styles, above.
// ----------------------------------------------------------------
var allLinks, thisLink, chgColors, buttonText;
chgColors=GM_getValue("change_colors","on");
buttonText = "Change Colors";
if (chgColors == "on") {
buttonText = "Original Colors";
allLinks = document.evaluate(
'//a[@href]',
document,
null,
XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
null);
for (var i = 0; i < allLinks.snapshotLength; i++) {
thisLink = allLinks.snapshotItem(i);
thisLink.className="scklink";
}
}
// ----------------------------------------------------------------
// Add a toggle button to the top of the page.
// ----------------------------------------------------------------
var sckButtDiv = document.createElement('div');
sckButtDiv.innerHTML =
'<form class="sckbutton" method="GET" action="#">' +
'<input id="sckbutton1" type="button" value="' + buttonText + '">' +
'</form>';
document.body.insertBefore(sckButtDiv, document.body.firstChild);
var button = document.getElementById("sckbutton1");
button.addEventListener('click', toggleButton, true);
// ----------------------------------------------------------------
// This routine is called when someone clicks the button.
// It will update the change_colors variable and then
// reload the page with the new scheme.
// ----------------------------------------------------------------
function toggleButton(event) {
var buttonText, chgColors;
chgColors = GM_getValue("change_colors", "on");
if (chgColors=="on") {
chgColors="off";
} else {
chgColors="on";
}
GM_setValue("change_colors", chgColors);
location.reload();
}
I've highlighted my changes in green. You'll see that I added a new style named sckbutton with the GM_addStyle() API. This style is for an HTML element that is placed at a fixed position on the page. It says that an HTML element in this class should be positioned 2 pixels down from the top and 2 pixels to the left of the edge of the window. The z-index keyword brings the element to the front, so when I display my button, it will display "on top" of any other element in the same position.
The
GM_getValue("change_colors", "on") API call retrieves a static variable, in this case named change_colors, from Firefox. The first parameter to GM_getValue() is the name of the variable to be retrieved, and the second parameter is the default value. In this case, if the change_colors variable hasn't already been set, the API will return "on". If the variable has been set, it'll return whatever it was set to. Later on, you'll see that I'll call GM_setValue() to set the value of this variable when the user clicks a button. So if the button has never been clicked, change_colors will be on, and the script will show the modified colors on the page. When the button is clicked, it'll toggle that, causing the original colors to be displayed, and so forth.
The value of change_colors is stored in the chgColors variable. When chgColors is set to "on," I do all the colors changes I did in the simpler version of the script. When it's set off, I skip that section of code.
After that, I create an HTML element: a new <div> tag. The new tag will be in my sckbutton class--the one that positions it at the top/left of the browser window. I set the HTML inside the div tag to show a button that says either "Original Colors" or "Change Colors." When the user clicks it, it toggles between them.
The addEventListener() method of the HTML button is used to tell the browser to call a function (similar to an RPG subprocedure) when the button is clicked. In this case, I'm calling the toggleButton() function, which toggles the value of the change_colors static variable, and then after updating it, reloads the page to start the script over again.
The result is a page that looks something like this:
Automating a Process
Okay, time to link this up with my RPG program. I wanted to use Greasemonkey to automatically fill in an HTML form provided by a third-party software vendor and submit that form. Of course, I can't show you the actual third-party vendor's screen--so I've decided to whip up a quick sample to show you. It's just a really basic screen on which you key in a person's address and submit it:
You can always see the source code for a vendor's HTML in Firefox by right-clicking the page (or frame) and choosing View Page Source. For the sake of my sample screen, above, the source code looks like this:
<html>
<body>
<h1>Enter Addresses</h1>
<form id="sample" method="post" action="example.php">
<table border="0">
<tr>
<td>Name:</td>
<td><input type="text" name="name" id="name"></td>
</tr>
<tr>
<td>Street:</td>
<td><input type="text" name="street" id="street"></td>
</tr>
<tr>
<td>City:</td>
<td><input type="text" name="city" id="city"></td>
</tr>
<tr>
<td>State:</td>
<td><input type="text" name="state" id="state"></td>
</tr>
<tr>
<td>Postal:</td>
<td><input type="text" name="postal" id="postal"></td>
</tr>
<tr>
<td colspan="2"><input type="submit" value=" Ok "></td>
</tr>
</table>
</form>
</body>
</html>
This HTML is simpler than a vendor's might be, but it contains the same elements that you're looking for. The <input> tags are where data needs to be inserted.
My script will insert a button (just as I did in the forum example) that says "Input RPG Data." When you click it, the script will store a static number named recno in Firefox and set it to zero. Then it will run a routine that adds 1 to the number and makes an Ajax call to an RPG program, and the RPG program will return the first record from its data. When the result is received, the Greasemonkey script will get the data, insert it into the form fields, and submit the form.
function startRunning() {
GM_setValue("running", "yes");
GM_setValue("recno", 0);
insertRpgData();
}
The preceding code is what gets called when you click the button. You can see that it sets two static variables. Running keeps track of when the script is currently inserting data into the form, and recno keeps track of which record is currently being processed. The insertRpgData() function is the one that calls the RPG routine.
function insertRpgData() {
var recno = GM_getValue("recno", 0);
recno = recno + 1;
GM_setValue("recno", recno);
GM_xmlhttpRequest({
method: 'GET',
url: 'http://as400.example.com/clubtechp/getaddr.pgm?' + recno,
onload: function (details) {
if (details.responseText == "#EOF#" ) {
GM_setValue("running", "no");
insertButton();
} else {
var data = details.responseText.split("|");
fillOutForm( data[0], data[1], data[2], data[3], data[4]);
}
}
});
}
It starts out by incrementing the recno variable. Then it calls the GM_xmlHttpRequest() API from Greasemonkey to make an Ajax request. The URL points to my IBM i system and the RPG program I want to call--GETADDR. It passes the recno variable as a "query string" to my program.
When the request completes, and the data returned by the RPG is #EOF#, it will stop automatically inserting data by setting running off.
If the data returned is not #EOF#, it'll be one address to be inserted into the form on the HTML screen. I decided to return one record at a time as a pipe-delimited string. So this is what the string of data returned from RPG will look like:
Scott Klement|123 Example St|Milwaukee|WI|54321
In JavaScript, I can split that string into individual fields by calling the split() method,
var data = details.responseText.split("|"), and the result will be an array named data where each element of the array has one field from my pipe-delimited data.
data[0] = Scott Klement
data[1] = 123 Example St
data[2] = Milwaukee
data[3] = WI
data[4] = 54321
I simply pass those fields as parameters to the fillOutForm() function, and here's what fillOutForm() looks like:
function fillOutForm( name, street, city, state, postal) {
var field = document.getElementById("name");
field.value = name;
var field = document.getElementById("street");
field.value = street;
var field = document.getElementById("city");
field.value = city;
var field = document.getElementById("state");
field.value = state;
var field = document.getElementById("postal");
field.value = postal;
var form = document.getElementById("sample");
form.submit();
}
You'll notice that the HTML for the form had ID attributes, such as id="name". That's what these getElementsById() calls are referring to; they're retrieving the HTML tag using its ID attribute. Then I can set the value of each field, in turn.
Finally, at the end, I retrieve the form itself and call the form's submit() method. This will serve the same purpose as clicking the OK button in the HTML--the form gets submitted to the third-party software.
The third-party software will send back a screen that says, "Data has been entered." So my Greasemonkey script will look for that page, which in this example is named example.php, and if that's the page loaded, it'll redirect the browser back to the previous page, where the whole process starts over, recno becomes 2, and more data is retrieved from RPG and inserted into the form.
So the mainline of my script has three possibilities:
- I'm on the example.php screen and need to go back to the test.html screen (the one where the data is entered)
- The running variable has been set, so I want to insert the next record from the RPG program.
- Nothing has happened yet, and I should display the button for the user to start the process.
With that in mind, my mainline looks like this:
// ==UserScript==
// @name EnterData
// @namespace http://www.scottklement.com/scripts/enterdata
// @description Enter Data from RPG Script to Web Page
// @include http://as400:89/test.html
// @include http://as400:89/example.php
// ==/UserScript==
var running = GM_getValue("running", "no");
var url = window.location.href;
if (url.search("example.php")!=-1) {
window.location.href="http://as400.example.com/test.html";
}
else if (running == "no") {
insertButton();
}
else {
insertRpgData();
}
You'll notice that if the URL contains "example.php" it redirects back to test.html, and my third-party software's form is on that page. If it's not example.php, I will insert a check to see whether running=yes or running=no. If it's not running, I insert the button, otherwise I call the same insertRpgData() routine that the button calls, which increments recno and asks for the next record, etc.
Conclusion
I don't have the space here to explain Greasemonkey in depth, but I really think this is an interesting tool, so I wanted to spread the word. Learn more about Greasemonkey, including tutorials and the reference manual from Greasemonkey's Wiki.
There are lots of places to learn JavaScript itself. I personally enjoyed this tutorial.
You can download the source code for the scripts, HTML code, and RPG code I present in this article.
Happy scripting!