Update: there have been some improvements to this plugin. Have a look at this post regarding the update. Thanks for the feedback!
After some days of hard labor, I finished my cross site Ajax plugin for the prototype framework 1.5.0. (Download Plugin Here) While working on a new product of mine I realized I needed cross site Ajax, which is not supported in the Prototype framework.
During cross site Ajax requests the standard XmlHttpRequest approach breaks down. The problem is that XmlHttpRequest is bounded by the same site policy. Fortunately the script tag has the freedom to do as it pleases.
Some other libraries such as dojo and jquery do support the script method for doing Ajax. There is even a project on source-forge called COWS, which is dedicated to this purpose. This plugin is an adaptation of the jquery plugin, but modeled to look like an XmlHttpRequest. The credits of the original code go to Ralf S. Engelschall , which amazingly achieved to make it nicely cross browser compatible. This plugin supports FF, IE, Safari, Opera and Konqueror, but has only been properly tested in FF and IE.
Prototype’s structured way of doing Ajax was my main reason to choose the prototype framework. Furthermore it is also included in the great Symfony framework. In Prototype Ajax requests are written like this:
new Ajax.Request('myurl', {
method: 'GET',
crossSite: true,
parameters: Form.serialize(obj),
onLoading: function() {
//things to do at the start
},
onSuccess: function(transport) {
//things to do when everything goes well
},
onFailure: function(transport) {
//things to do when we encounter a failure
}
});
The cross site plugin simply allows you to do Ajax cross site, by specifying crossSite: true (line 3 of the above example). I will now cover some technical aspects of the plugin, but if you just want to start using it simply skip to the plug and play instructions below.
How it works – Technical Aspects
This plugin uses the dynamic script tag technique. This basically means that we insert new <script> tags into the Dom. Since this script tag is not bound to the same site you can send and receive data in the Ajax way. In its most basic form the javascript would be like this:
this.node = document.createElement('SCRIPT');
this.node.type = 'text/javascript';
this.node.src = 'http://www.serversite.com';
var head = document.getElementsByTagName('HEAD')[0];
head.appendChild(this.node);
In order to make it very easy to use with Prototype, or any other library for that matter, I decided to mimic the functions of the XmlHttpRequest. This is easily achieved by implementing the functions open, send and onreadystatechange. Furthermore I needed to specify the variables readyState and status in order to support prototype’s onLoad, onSucces and onFailure.
Detecting the loading of a script element is not that easy. Browsers such as Safari and Konqueror simply give no indication of this at all. One common solution to dealing with this is to use an interval and perform a check. The work at TrainOfThoughts however takes the beautiful approach of inserting a helper script. This exploits the fact that the dynamically added scripts are executed in sequence. This approach makes the plugin nicely cross browser compatible.
Detecting failure is rather cumbersome for the script technique. As far as I know there is no way to read the headers on the incoming file, or to inspect its contents through javascript. This leaves us with the rather blunt approach of setting a global variable using the server output. It works, but it could be prettier.
Plug and Play implementation instructions
Firstly you need to load the plugin javascript file: download cross site ajax plugin for the prototype framework 1.5.0.
Secondly you need to change your regular prototype Ajax request, by ensuring that you instruct it to use the crossSite and GET methods, as such (observe line 2 and 3):
new Ajax.Request(baseurl+'/comment/giveratingjs', {
method: 'GET',
crossSite: true,
parameters: Form.serialize(obj),
onLoading: function() {
//things to do at the start
},
onSuccess: function(transport) {
//things to do when everything goes well
},
onFailure: function(transport) {
//things to do when we encounter a failure
}
});
Thirdly you might need to rewrite some of your javascript code to accommodate the instant execution of the scripts.
Fourthly, if you want to use onFailure for any of your scripts you need to send some javascript instructions back from the server. You need to do this both on success and on failure (since a global variable is used). This is the javascript variable you need to set:
'var _xsajax$transport_status = 200;' Or
'var _xsajax$transport_status = 404;'
Symfony specific tips
Symfony detects if it receives a XmlHttpRequest and automatically turns off your debug bar and layout. Unfortunately it is not so kind to the script technique. So in your action you need to do this manually:
sfConfig::set('sf_web_debug', false);
$this->setLayout(false);
Furthermore your validation files by default only look at POST variables (this one tricked me). To instruct them to look at both, simply mention
methods: [post, get]
at the top of your validation.yml
Since you will probably want to send html to the browser, I would suggest you put this little function (found in the symfony escape helpers) in your toolbox.
public static function esc_js($value) {
return addcslashes($value, "\0..\37\\'\"\177..\377\/");
}
Conclusion
The dynamic script tag technique opens up a wide range of possibilities. Personally I am very glad with the results and would like to thank Ralf S. Engelschall for his superb cbc work. Unfortunately I didn’t include an example this time. You will have to wait for the products’ launch:). Comments and improvements are always appreciated. Enjoy your cross site scripting!
Javascript News » Blog Archive » Introducing a cross site Ajax plugin for Prototype responded on 26 Oct 2007 at 6:14 pm #
[...] Schellenbach has implemented a Prototype plugin that allows you to do cross site remoting using the dynamic script tag method that other frameworks such as Dojo and jQuery have supported [...]
Kris Kowal responded on 26 Oct 2007 at 9:37 pm #
You can probably get around the need for a global variable (or function call) in your helper script by adding an empty script and blocking until it has finished running then sending your completion signals. John Resig over in jQuery just posted some code based on work by Andrea Giammarchi that appears to evaluate a script in global context synchronously in all browsers, which may be of some use here.
http://dev.jquery.com/changeset/3501
Gary Gurevich responded on 26 Oct 2007 at 9:37 pm #
Unfortunately, in my tests under Safari, there is no guarantee that scripts will be loaded in the order they are created within the DOM. See my test case here:
http://xorox.net/trax/jsloadtest.html
Expected output for sequential execution would be 1,2,3,4 but Safari 413.2 shows 3,1,2,4.
tschellenbach responded on 26 Oct 2007 at 11:28 pm #
Looks interesting, Ill test it through and see how I can improve this script. On this site 5% of my visitors use Safari, so I should better get it right :)
napyfab:blog» Blog Archive » links for 2007-10-26 responded on 26 Oct 2007 at 11:32 pm #
[...] Mellow Morning » Introducing a cross site ajax plugin for Prototype (tags: ajax javascript prototype crossdomain library xss script dynamic webdev web development) [...]
Kris Kowal responded on 27 Oct 2007 at 10:00 am #
@Gary I ran your test a few times. In Safari 2, the text script insertions are always evaluated synchronously, and the src script insertions are always evaluated asynchronously. So, yes, the text script never actually runs after the src script inserted before it, and the order of the src scripts is non-determinisitic. In your particular example, the text node always runs first (3) and the src scripts load in any other.
I adapted an experiment of my own,
http://cixar.com/~kris.kowal/javascript/trunk/experiment/crossSite.html
This uses a PHP script that waits 4 – n seconds and then prints a script that logs n. The script demonstrates that in FireFox, the scripts do execute in order, waiting for the requesting script to complete before running the subsequent ones. It also demonstrates that in Safari 2, all of the text scripts run synchronously and all of the src scripts load asynchronously.
In conclusion, in FireFox, no script insertions block, but they do execute in order. So, placing a continuation in a global variable and executing it within the text script works in FireFox, but baring an epiphany, we can’t get away without using a global variable. In Safari 2, text scripts block, so you wouldn’t need a global variable to verify that a text script had completed; you could apply the continuation after the text block ran. However, this gets you no-where in Safari 2 since the text script runs before the preceding src script even starts.
I will refrain from concluding that this technique will not work in Safari 2 with either the Prototype or jQuery solution until I’ve actually run a unit test for each of them, but I doubt that either works in Safari 2 having read their code.
eric.polerecky.com » Blog Archive » links for 2007-10-27 responded on 27 Oct 2007 at 12:25 pm #
[...] Mellow Morning » Introducing a cross site ajax plugin for Prototype (tags: development javascript) [...]
Cross-site Remote Scripting ??? at ericsk’s blog responded on 28 Oct 2007 at 2:21 am #
[...] XXMLHttpRequest ??????? domain ?? URL???????????????????? cross-site remote scripting ?????????? [...]
hm responded on 29 Oct 2007 at 12:13 pm #
Cool script, although seems to generate errors in Firebug and doesn’t work in IE6 (still the web’s dominant browser by a large margin). Any plans for a fix?
tschellenbach responded on 29 Oct 2007 at 2:10 pm #
Regarding the current browser issues,
I hope to have all the problems fixed, either this week or coming week.
cross site Ajax- ??Ajax | The Third Part responded on 30 Oct 2007 at 7:19 am #
[...] – ?dojo?jquery?prototype?cross site Ajax?????sourceforge?COWS?????????script [...]
jason pollard responded on 30 Oct 2007 at 10:09 pm #
Hi, Great work, but I’m getting a couple of showstopper problems. First, Firebug is giving me a syntax error on line one of the response, making me think that the browser is trying to execute it as javascript. Is there some way to just get the response as text? I don’t want to get elbows deep into prototype ajax at the moment.
Also there’s a “_xsajax$transport_status is not defined” error, which relates to your 4th plug & play implementation instruction above. Would it be possible to just set this value manually, since I’m not interested in the status? (I know it’ll be 200 anyway).
Andy responded on 31 Oct 2007 at 12:20 pm #
This is exactly what I need. Great work. Looking forward to your next update.
Barry Watson responded on 02 Nov 2007 at 3:21 am #
Hello…Man i love reading your blog, interesting posts ! it was a great Thursday
tschellenbach responded on 03 Nov 2007 at 8:12 pm #
Have just fixed the problem, will have a follow up post about it in the coming week.
links for 2007-11-04 « toonz responded on 05 Nov 2007 at 12:21 am #
[...] Mellow Morning » Introducing a cross site ajax plugin for Prototype (tags: ajax prototype) [...]
Mark B responded on 05 Nov 2007 at 10:39 am #
I’m seeing the same problems as Jason Pollard:
missing ; before statement
[Break on this error]
Mark B responded on 05 Nov 2007 at 10:40 am #
I’m seeing the same problems as Jason Pollard:
missing ; before statement
[Break on this error] …
_xsajax$transport_status is not defined
[Break on this error] this.status = (_xsajax$transport_status) ? _xsajax$transport_status : 200;
Very close as when I click on the error in Firebug I can see the XML I’m trying to grab.
Have you got a quick fix by any chance?
tschellenbach responded on 06 Nov 2007 at 8:45 am #
Thanks for pointing that one out, be sure to look at the new version of this script in the coming days.
Mellow Morning » Updated cross site ajax plugin for Prototype responded on 07 Nov 2007 at 10:08 pm #
[...] first post on my cross site ajax plugin for Prototype (1.5.0) was received with great enthusiasm. It was very nice to see my own work on the great [...]
tschellenbach responded on 07 Nov 2007 at 11:53 pm #
The update is here :)
http://www.mellowmorning.com/2007/11/07/updated-cross-site-ajax-plugin-for-prototype/
nniico responded on 13 Nov 2007 at 4:34 pm #
@Mark B
@Jason pollard
This script is only working while fetching JSON formated data. This is because the fetched data is included in a “script” tag with mime type set to “text/javascript”. When trying to fetch plain text or XML, the javascript parser raises an error (this missing semi-colon). The onFailure callback is then called and the status of the request is looked in _xsajax$transport_status, which is indeed not defined.
_xsajax$transport_status should be first tested against “undefined”.
I tried to change the mime-type to “text/plain”, but then the “onload” event is broken (at least with Firefox).
Tim responded on 04 Feb 2008 at 9:19 pm #
Simple non-technical method I use with php. Make a normal AJAX call to a file on my site I call ajax.php?ajaxUrl=http://someothersite.com that has the following in it:
Tim responded on 04 Feb 2008 at 9:20 pm #
echo @file_get_contents($ajaxUrl);
tschellenbach responded on 05 Feb 2008 at 8:42 am #
You need control over the local site in order to apply a proxy, doesn’t work if you are looking to create widgets.
Flow responded on 25 Feb 2008 at 4:39 pm #
Hi,
you mentioned the symfony escape helper as little hint for integrating your script into smyfony. After adding your script I get an “illegal character” error if i want to do a cross site request to an image hosted on a remote machine. Can you please explain again how to escape the response I receive after doing my request? That took my for a very long time.
Thanks.
Dmitry Golomidov responded on 04 Apr 2008 at 8:22 pm #
Did anyone make it work in Safari? I am also having an issue that originates from Safari not executing the dynamic script upon creation
Thierry Schellenbach responded on 04 Apr 2008 at 9:45 pm #
http://www.mellowmorning.com/2007/11/07/updated-cross-site-ajax-plugin-for-prototype/
supports safari, also quite easy to port to prototype 1.6
hooyes responded on 10 Apr 2008 at 10:58 am #
oh,good!
Frank Wang responded on 16 Apr 2008 at 9:54 am #
May i have a sample for your script?
Or show me the setup steps~~
I can’t run the cross site ajax in my script!
Please Help!
Russ responded on 04 Jun 2008 at 1:43 am #
i am using this script and the client has asked me if it is a security risk and that someone could use it to inject Javascripit into the site is this true??
Thierry responded on 04 Jun 2008 at 8:22 am #
The risk is the same as loading any javascript file from an external source.
If you don’t control the server sending you the javascript, arbitraty frontend code could be run.
Only use this with trusted parties.
????-rediaSpace » Blog Archive » ??? ??? Ajax ?? - Introducing a cross site Ajax plugin for Prototype responded on 01 Jul 2008 at 2:51 pm #
[...] Schellenbach ? Prototype JS? ????(plugin)? ??? ??? ???(cross site remoting) ajax ??? ???? ?? ??? ??????. ???? ?? Prototype.js [...]
Cristian responded on 22 Jul 2008 at 1:52 pm #
the functin transport.js result me this error:
this.setOptions is not a function
[Break on this error] this.setOptions(options);
tim responded on 24 Aug 2008 at 10:20 pm #
I’m getting an error from transport.js when trying to use it. “this.setOptions doesn’t exist”. And it’s true. There is no setOptions function in transport.js or prototype.js.
Dave Szabo responded on 13 Nov 2008 at 8:22 pm #
I’m getting the same error with setOptions as above, has anyone figured this out? Please let me know.
cristiroma responded on 14 Nov 2008 at 1:10 pm #
Of course is giving you the error, because you are using prototype 1.6 instead of 1.5, but while using 1.5 gives another error:
Error: _xsajax$transport_status is not defined
Source File: transport.js
Line: 62
JP responded on 04 Jan 2009 at 10:28 am #
Slightly modified version for Prototype 1.6 here:
http://developer.idapted.com/2009/1/4/cross-site-ajax-with-prototype
Would certainly be good for someone more familiar with prototype internals to take a look and help out…
tschellenbach responded on 04 Jan 2009 at 10:49 am #
nice :)
Vauncher responded on 01 Mar 2009 at 3:41 pm #
This script doesn’t work! Anybody, who know how it works, please give us an example!!!! I think and I’m SURE that lots of people can’t run the script!
Dear admins, GIVE US AN EXAMPLE!!!!
THX!:)
Vauncher responded on 01 Mar 2009 at 4:33 pm #
Oh, I forgot one thing..
Does admins read sometimes this page?:) or I gotta go from here and never come back for reading comments? :)
carvin responded on 02 Apr 2009 at 7:41 am #
I can’t run the cross site ajax in my script!
Please Help!
Max Kiesler - Designer » Blog Archive » Learning AJAX & Javascript by Example - Tutorials, Source-Code and Documentation responded on 02 Jun 2009 at 7:08 am #
[...] Introducing a Cross Site AJAX Plugin for Prototype “During cross site Ajax requests the standard XmlHttpRequest approach breaks down. The problem is that XmlHttpRequest is bounded by the same site policy. Fortunately the script tag has the freedom to do as it pleases.” [...]
Icaro Dourado responded on 30 Jul 2009 at 3:23 am #
Hi everyone,
This solution helps me A LOT!
Randy responded on 28 Jul 2010 at 7:11 am #
this.setOptions is not a function
The same error with me
Bny responded on 10 Nov 2010 at 11:16 am #
Hey Guys,
i was searching as well for a solution for prototype 1.6 and couldnt find any, so i kicked my ass and did my own.
1: Open your prototype lib and search for “Ajax.Request”
2: Go to the line where “this.transport” gets defined and replace it by this
“this.transport=(!A.crossSite)?Ajax.getTransport():new scriptTransport;”
i got a compressed protoype so “A” is equal to “this.options”
3: save it and open the “transport.js” and remove at the bottom the hole Ajax.Request extentsion
4: . . .
5: profit???! :D
I hope i could help you guys, have fun and happy crossbrowsing
K responded on 23 Feb 2011 at 2:38 pm #
Many thanks Bny,
it works for me !!