XulRunner and Rails Tutorial, Part 2: Connecting the Two
Today we’re going to Connect Rails to our new XulRunner app. Sounds easy enough, right?
Well….
Let’s start on the Xul side of it. All this XmlHttpRequest stuff is crap. And Ajax is Ajax+J.
Under the xul/content directory, let’s create a new one called “js” to store all of our javascript in, and let’s create another called “lib” to store the more library orientated files in.
Great, now let’s do a new file, we’re gonna call it something warm and fuzzy, something like JaxaJax. So create JaxaJax.js file.
Now, we’re not in a “pure” Ajax enviroment. We’ve got stuff running, stopping, finishing in the background. We’re not talking 20 or 30 Ajax calls per session, we’re talking lots of potential Ajax calls.
So we’re going to need:
<pre>
var processlist = new Array();
var ror_server = "http://localhost:3000/server"; // We'll redefine the list, probably. And definately before release.
</pre>
With me so far? Let’s create the function. The proclist declaration goes outside of the function, we want it global.
I’ll stop and explain when I can, but read the comments.
<pre>
// Action is the action on the remote server to call
// Data is the data we pass along with it, in the POST or the GET request
// Callback is the function to call and return the data to upon completion of the call...
function JaxaJax(action, data, callback, method) {
http_request = new XMLHttpRequest();
}
</pre>
We don’t need the simple workaround to get our http_request obj for IE, since we’re on XulRunner. Ah, not coding for multiple browser javascript, nice.
Let’s continue. We’re going to need to store this in the proclist, somewhere unique so it doesn’t get overwritten later. Why? That way we can get data out of it later.
Also, we don’t really need the http_request variable, let’s just create the obj in the processlist itself.
Once the request is completed, we need to decode the data and execute the callback function, passing that data along to the callback function. We have to store the callback function in this way, so it runs properly… If you’re not used to javascript, it may look weird.
<pre>
function JaxaJax(action, data, callback, method) {
var uniq = new Date();
uniq = uniq.getTime();
uniq = action + uniq + Math.round(10000*Math.random());
// Should be good enough...
processlist[uniq] = new XMLHttpRequest();
processlist[uniq].onreadystatechange=function() {
if(processlist[uniq].readyState == 4) {
eval("var data = " + processlist[uniq].responseText + ";");
eval(callback + "(data)");
}
// Otherwise it's not ready yet....
};
}
</pre>
Useful, right? Well, a quick explanation. readyState 4 is finished, meaning all the data has returned and the other side has signaled all data has been sent. We decode that data, then execute the callback function with the data returned. I know what you’re thinking, just an eval for the returned data? Yeah. We’re using JSON to pass the data back. Much faster than bringing up an XML parser, plus it’s easier to most, not all purposes.
Let’s take a side break and talk about JSON security. JSON is just raw data from a source. The only place it’s going to get the data it needs is from what’s in the ror_server variable. So, us. It’s not XSS you need to worry about, it’s someone breaking into your server and changing the code. So, only if the server is compromised do you have to worry. If you’re paranoid, use the function at the bottom of this file called parse that uses a regular expression to check for valid JSON data before parsing. You would include it in your source somewhere, and replace eval(”var data = ” … with var data = parse(…
Using this in regular RoR is not recommended, as I think it would break RJS Templates. But, in regular RoR everything is handled for you anyway!
Ok, so you’re still paranoid? Well, those Moz guys have created a solution for that too. See evalInSandbox. I haven’t used it personally, and couldn’t see when you would use it, and I don’t know if it really makes things more secure. But hey. there ya go. Another avenue for security.
If you really hate JSON, for whatever reason, you can always use XML. We’ll go over this in a later part of this tutorial, just suck it up for now and continue with me. We will go over XML in Xul, and touch on rxml templates briefly. There’s no way I can do this tutorial without talking at least briefly about E4X. It’s just too cool. But I grew sour on Moz’s XML support when I started doing Xul, back in 1.0, and E4X didn’t come in until 1.5, and with the speed improvements it was just too difficult to go back(not to mention re-re-writing the backend, of a different app that’s in PHP, no RoR).
So back to it. Where we’re at now, we create some stuff, but don’t actually do anything. We need to be able to pass the action and associated data back. We could convert it back into JSON, that’d make sense. JSON both ways? But, we don’t need to. It’s a simple app, so we need to do only what we need to. Besides, we could always change it later.
Now, we can do both GETs and POSTs. And their formation is pretty similar. Let’s do a function to create query strings.
<pre>
function createQueryString(obj) {
var a = new Date();
var when = Math.floor(a.valueOf()/1000);
queryString = "when=" + when; // Just so we know what time, if we have to do any debugging, etc...
for (key in obj) {
queryString = queryString + "&" + escape(key) + "=" + escape(obj[key]);
}
return(queryString);
}
</pre>
Nice, simple, and quick. We’ll use it for both POSTs and GETs. Yeah, we’re pretty much skipping the whole pretty URL’s thing here. I’ll make it up to you later. This is a nice method because it works with systems that don’t easily do pretty URL’s such as PHP.
Back to JaxaJax.
<pre>
function JaxaJax(action, data, callback, method) {
var uniq = new Date();
uniq = uniq.getTime();
uniq = action + uniq + Math.round(10000*Math.random());
// Should be good enough...
processlist[uniq] = new XMLHttpRequest();
processlist[uniq].onreadystatechange=function() {
if(processlist[uniq].readyState == 4) {
eval("var data = " + processlist[uniq].responseText + ";");
eval(callback + "(data)");
}
// Otherwise it's not ready yet....
};
var querystring = createQueryString(data);
if (method == "GET") {
processlist[uniq].open("GET", ror_server + "/" + action + "?" + querystring, true);
processlist[uniq].send(null); // Send null to complete the connection from our side.
} else {
processlist[uniq].open("POST", ror_server + "/" + action, true);
processlist[uniq].setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
processlist[uniq].send(querystring);
}
}
</pre>
There, that’s pretty nice. I wonder if it works.
Here’s the code we have so far.
<pre>
var processlist = new Array();
var ror_server = "http://localhost:3000/server"; // We'll redefine the list, probably. And definately before release.
// Action is the action on the remote server to call
// Data is the data we pass along with it, in the POST or the GET request
// Callback is the function to call and return the data to upon completion of the call...
function JaxaJax(action, data, callback, method) {
var uniq = new Date();
uniq = uniq.getTime();
uniq = action + uniq + Math.round(10000*Math.random());
// Should be good enough...
processlist[uniq] = new XMLHttpRequest();
processlist[uniq].onreadystatechange=function() {
if(processlist[uniq].readyState == 4) {
eval("var data = " + processlist[uniq].responseText + ";");
eval(callback + "(data)");
}
// Otherwise it's not ready yet....
};
var querystring = createQueryString(data);
if (method == "GET") {
processlist[uniq].open("GET", ror_server + "/" + action + "?" + querystring, true);
processlist[uniq].send(null);
} else {
processlist[uniq].open("POST", ror_server + "/" + action, true);
processlist[uniq].setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
processlist[uniq].send(querystring);
}
}
function createQueryString(obj) {
var a = new Date();
var when = Math.floor(a.valueOf()/1000);
queryString = "when=" + when; // Just so we know what time, if we have to do any debugging, etc...
for (key in obj) {
queryString = queryString + "&" + escape(key) + "=" + escape(obj[key]);
}
return(queryString);
}
</pre>
I’m sure it could be cleaned up, but not gonna worry about that now. I’d rather clean up working code than broken code.
Let’s do a test.
Let’s use this line:
<pre>
function echo(data) {
alert(data);
}
data = {"echo":"Hello Echo!"};
JaxaJax("echo", data, "echo", "GET");
</pre>
See why we did that huge ugly function? Because now all of our Ajax calls are one liners. =) Actually, that’s a terrible interface. I think we should clean that up. But let’s see if it works.
Let’s update our main.xul file to include this line after the window tag.
<pre>
<script type="application/x-javascript" src="chrome://xrforum/content/js/lib/JaxaJax.js" />
</pre>
Of course, there is no server controlled in Rails, so let’s switch back to radrails real quick.
Server is probably a bad name for a controller, but hey.
So now we need to edit the controller, you should end up with this:
<pre>
class ServerController < ApplicationController
def echo
render_text params[:echo].to_json
end
end
</pre></pre>
Huh? Too easy? Yeah, that’s why I’m loving rails right now. =)
You can test it at this URL, assuming your server is on port 3000, http://localhost:3000/server/echo?echo=test
You should get the following back:
<pre>"test"</pre>
That’s it. Pretty simple.
Now go run XRForum and you’ll get Hello Echo! as an alert box.
If you’re having any trouble, make sure you’re using code like above. But let’s take a brief moment to talk about debugging XUL, we’ll later cover Alchemy and how to turn Dirt into Gold, and Water into Wine, both far easier once you’ve learned how to debug XUL.
Let’s update our prefs.js file, you remember, xul/defaults/preferences/prefs.js
Make it read this:
<pre>
pref("toolkit.defaultChromeURI", "chrome://xrforum/content/main.xul");
pref("xrforum.debug", true);
pref("javascript.options.showInConsole", true);
pref(“javascript.options.strict”, true);
pref(“browser.dom.window.dump.enabled”, true);
pref(“nglayout.debug.disable_xul_cache”, true);
pref(“nglayout.debug.disable_xul_fastload”, true);
</pre>
Whoa, that’s a lot of stuff. It all helps though. Let’s try something new. Try running XRForum.exe -jsconsole (run it from the commandline).
Cool, now you have a JavaScript console to give you errors. Now try running XRForum.exe -console. Neat. But we don’t use that one as much, but we’ll set it up to use the console later.
Whew. Well, we’re done for now. I’ve heard that you can use XMLHttpGet’s onload variable to do callback functions when it finishes loading the document, but I haven’t gotten it to work yet, so we’ll stick with this method. It’s the preferred method of WebDev’s since it’s (almost) cross platform.
Of course, Ruby on Rails handles all that, it doesn’t do XUL though, we handle that on our own.
Now, we’ve gotten them communicating together. Tomorrow we’ll work on listing the posts.
Comments
Comment from Michael Mathers
Time: April 8, 2006, 12:38 am
I am lost at “Let’s do a test”
function echo(data) {
alert(data);
}
data = {”echo”:”Hello Echo!”};
JaxaJax(”echo”, data, “echo”, “GET”);
I pasted this at the end of JaxaJax.js (I didn’t know where else to put it), but it doesn’t appear to affect anything if it’s there or not. My URL test of “echo=test” works but when running XRForum.exe it still says “Hello XRForum!”
What did I miss?
Comment from admin
Time: April 8, 2006, 9:17 am
What’s coming up when you run XRForum.exe -jsconsole ?
Comment from admin
Time: April 8, 2006, 9:22 am
Make sure the server is started in RadRails, and on port 3000. It should say something along the lines of
XRForumServer XRForum Started 3000 development
at the bottom in the Servers tab. If 3000 is a different number, change the var ror_server line to reflect that. If it says stopped, select it and hit the little green arrow to start it.
Make sure you added that line to main.xul as well.
If not, let me know what it says when you run XRForum.exe -jsconsole
Make sure to add those pref’s at the bottom of the page to the xul/defaults/preferences/prefs.js file as well.
Comment from arcelone
Time: September 27, 2006, 10:02 am
Hi,
i’ve got a problem with test http://localhost:3000/server/echo?echo=test
Where do you extract the value ’server’??
Is there a ’script/generate…’ after run rake migrate ?
Thanks.

Write a comment