PDA

View Full Version : AJAX without the Ax


¥åßßå
07-28-2008, 07:08 PM
Javascript is still required
As a follow on from something I mentioned in this post ( Creating Tag Clouds? The nuts and bolts.. (http://www.htmlforums.com/web-20-websites-technology-discussion/t-creating-tag-clouds-the-nuts-and-bolts-105785.html) ) I thought I'd share a method of achieving the "AJAX effect", without the AX bit. For those of you who don't know AJAX stands for Asynchronous Javascript And XML, you can find a full description of what it is and how it works on wikipedia ( wikipedia : Ajax (programming) (http://en.wikipedia.org/wiki/AJAX) ). However, there is another way to achieve the same effect without using the XMLHttp Request method ( wikipedia : XMLHttpRequest (http://en.wikipedia.org/wiki/XMLHttpRequest) ) which has the advantage that it's able to make cross-(sub)domain requests, which is impossible to do with AJAX, and something that we require for version 3 of our blog software ( b2evolution (http://b2evolution.net) ) so that it can still fulfil it's "multi-domain" aspect and we can add all the fancy bells and whistles that are in the pipeline as several of these need the ability to communicate with admin which can easily be on a separate (sub)domain from the frontend.

How does it work?
Creating a request is pretty simple, you just add a <script> tag to the pages <body> pointing to the url of your choice. This url can be on any (sub)domain you like as there are no cross-domain restrictions. In the example below the scripts "answer" is going to be pulled from my own blogs url ( innervisions.org.uk ) to show you that it really does work across domains ;) The url can also contain any $_GET ( php.net : $_GET (http://uk.php.net/manual/en/reserved.variables.get.php) ) variables that you like. This leads us to the 2 limitations of this method :
1) The request can only use $_GET
2) The response must be a javascript response ( see example below )

Once the request hits the server it can do whatever it likes with the variables passed and then "reply" to the requesting page by imitating a javascript file. This basically means that it has to set the correct headers and reply using normal javascript syntax. It's not as hard as it sounds.

Example code
To show you how it works we'll do a really easy example that takes the text typed into a box, sends it to the server which will reverse it and send it back to the calling page. The code on the calling page looks like this :

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-UK" lang="en-UK">
<head>
<title>test</title>
</head>
<body>
<label for="theText">Type in the text to be reversed : <input type="text" name="theText" id="theText" /></label> <button onclick="reverseMe();">Reverse</button>
<script type="text/javascript">

function reverseMe()
{
var theText = document.getElementById( 'theText' ).value; // grab the text
var the_call = document.createElement( 'script' ); // create script element
the_call.src = 'http://innervisions.org.uk/nonAjax.php?theString='+theText; // set the url
the_call.type = 'text/javascript'; // to be sure to be sure
document.body.appendChild( the_call ); // add script to body and let browser do the rest
}

function serversReply( theAnswer )
{
window.alert( 'The server said : '+theAnswer ); // stun the crowd with a reply from the server
}
</script>
</body>
</html>

The code for the page being called looks like this ( note : it's very basic and it can be "broken", but it's just for example huh? ;) ) :
<?php
$theText = ( empty( $_GET['theString' ] ) ? '' : $_GET['theString'] ); // grab the text to be reversed
if( $theText )
{ // we have some text, reverse it
$theAnswer = strrev( $theText );// reverse the text
}
else
{ // we don't have any text
$theAnswer = 'Gimme summat to do!'; // inform the user we're bored
}
$theAnswer = str_replace( "'", "\'", $theAnswer ); // single quotes would break the reply
header( 'Content-Type: text/javascript' );// set the correct mime type
echo 'serversReply( \''.$theAnswer.'\');';// trigger the function on the calling page
?>

So, does it work?
Try it for yourself ;) ( AstonishMe : Ajax without the ax (http://astonishme.co.uk/web.php/ajax-without-the-ax) ).

¥

Horus_Kol
07-28-2008, 07:42 PM
thanks for the post Yabba - I'll have to give it a go when I get a chance...

¥åßßå
07-28-2008, 07:46 PM
Feel free to abuse my bandwidth with your test page ;)

¥

JoeyDaly
07-30-2008, 11:41 PM
Great stuff Yabba! You should write a book on this topic 'AJAX without the Ax' - I'm actually more inclined to learn using more advanced JS with appendChild n so forth... the last thing I did was creating dynamic form fields that you could add/remove from a form.

AJAX here I come!

¥åßßå
07-31-2008, 04:07 AM
It'd be a very short book :rolleyes:

Word of warning, if you try adding a radio element then you'll have fun and games with IE ( nothing new there huh? ). If you do hit that problem then you can find a solution here ( Internet Explorer - wasting production time worldwide (http://waffleson.co.uk/2007/12/explorer-can-t-name-radio-dom) ) ;)

¥

BonRouge
07-31-2008, 05:06 AM
This seems great. :) (I tried to give you a bit of rep, but it told me I had to spread the love a little first). :(

I've been playing with your code. I've taken the javascript out of the body and changed the call a little so that it can be used with other scripts.

I hope this is interesting and it helps some-one.

HTML

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<title>test</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<style type="text/css">
* {
margin:0;
padding:0;
}
body {
background-color:white;
}
</style>
<script type="text/javascript">
function query(q,value) {
var the_call = document.createElement('script'); // create script element
the_call.src = 'nonAjax.php?'+q+'='+value; // set the url
the_call.type = 'text/javascript'; // to be sure to be sure
document.getElementsByTagName('head')[0].appendChild(the_call);
}


function reverseMe(id) {
var theText = document.getElementById(id).value; // grab the text
query('reverse',theText);
}
function reverseMeReply(reply) {
window.alert('The server said : '+reply); // stun the crowd with a reply from the server
}


function shuffleMe(id) {
var theText = document.getElementById(id).value; // grab the text
query('shuffle',theText);
}
function shuffleMeReply(reply) {
window.alert('The server said : '+reply); // stun the crowd with a reply from the server
}


window.onload=function () {
document.getElementById('reverseButton').onclick=function() {reverseMe('reverse')};
document.getElementById('shuffleButton').onclick=function() {shuffleMe('shuffle')};
}
</script>
</head>
<body>
<p>
<label for="reverse">Type in the text to be reversed : <input type="text" name="reverse" id="reverse" /></label> <button id="reverseButton">Reverse</button>
</p>
<p>
<label for="shuffle">Type in the text to be shuffled : <input type="text" name="shuffle" id="shuffle" /></label> <button id="shuffleButton">Shuffle</button>
</p>
</body>
</html>


PHP

<?php
header( 'Content-Type: text/javascript' );// set the correct mime type

if(isset($_GET['reverse'])) {
$rText=$_GET['reverse'];
}
if (!empty($rText)) {
$reply = strrev($rText);// reverse the text
$reply = str_replace( "'", "\'", $reply ); // single quotes would break the reply
echo 'reverseMeReply( \''.$reply.'\');'; // trigger the function on the calling page
}

if(isset($_GET['shuffle'])) {
$sText=$_GET['shuffle'];
}
if (!empty($sText)) {
$reply = str_shuffle($sText);// reverse the text
$reply = str_replace( "'", "\'", $reply ); // single quotes would break the reply
echo 'shuffleMeReply( \''.$reply.'\');'; // trigger the function on the calling page
}
?>


It can probably be simplified a little more.

http://bonrouge.com/test/nonAjax.htm

¥åßßå
07-31-2008, 05:41 AM
No worries, I feel the lurv ;)

To show you how it's used in the our blogs ( CVS version, so this file is liable to be re-written at any time ), this is our "communications.js", it uses jQuery as that's the js library that was chosen for inclusion in our core :

/**
* Server communication functions
*
* Ajax without the pain
*
* This file is part of the evoCore framework - {@link http://evocore.net/}
* See also {@link http://sourceforge.net/projects/evocms/}.
*
* {@internal License choice
* - If you have received this file as part of a package, please find the license.txt file in
* the same folder or the closest folder above for complete license terms.
* - If you have received this file individually (e-g: from http://evocms.cvs.sourceforge.net/)
* then you must choose one of the following licenses before using the file:
* - GNU General Public License 2 (GPL) - http://www.opensource.org/licenses/gpl-license.php
* - Mozilla Public License 1.1 (MPL) - http://www.opensource.org/licenses/mozilla1.1.php
* }}
*
* @package main
*
* {@internal Below is a list of authors who have contributed to design/coding of this file: }}
* @author yabs {@link http://innervisions.org.uk/ }
*/

var postbacks_count = 0;

/**
* Init : adds required elements to the document tree
*
*/
jQuery(document).ready(function()
{
jQuery( '<div id="server_messages"></div>' ).prependTo( '.pblock' );// placeholder for error/success messages
});


/**
* Sends a javascript request to admin
*
* @param string ctrl Admin control to send request to
* @param string action Action to take
* @param string query_string Any extra data
*/
function SendAdminRequest( ctrl, action, query_string )
{
SendServerRequest( b2evo_dispatcher_url + '?ctrl='+ctrl+'&action='+action+( query_string ? '&'+query_string : '' ) );
}


/**
* Sends a javascript request to the server
*
* @param string the url to request
*/
function SendServerRequest( url )
{
if( url.indexOf( '?' ) )
{ // we already have a query string
url = url + '&';
}
var the_call = document.createElement( 'script' ); // create script element
the_call.src = url+'display_mode=js'; // add flag for js display mode
the_call.type = 'text/javascript'; // to be sure to be sure
document.body.appendChild( the_call ); // add script to body and let browser do the rest
}


/**
* Sends a forms request as javascript request
*
* @param string DOM ID of form to attach to
*/
function AttachServerRequest( whichForm )
{
postbacks_count += 1;
jQuery( '<input type="hidden" name="display_mode" value="js" /><input type="hidden" name="js_target" value="window.parent." /><input type="hidden" name="callback_ID" value="'+postbacks_count+'" />' ).appendTo( '#' + whichForm ); // add our inputs
jQuery( '<iframe id="server_postback_'+postbacks_count+'" name="server_postback_'+postbacks_count+'"></iframe>' ).appendTo( 'body' ); // used for POST requests
jQuery( '#server_postback_'+postbacks_count ).css( { position:'absolute',left:"-1000em",top:"-1000em" } );
jQuery( '#'+whichForm ).attr( 'target', 'server_postback_'+postbacks_count ); // redirect form via hidden iframe
}


/**
* Displays $Messages->display()
*
* @param string message The html to display
*/
function DisplayServerMessages( messages )
{ // display any server messages and highlight them
jQuery( '#server_messages' ).html( messages );
// highlight success message
jQuery( '#server_messages' ).find( '.log_success' ).animate({
backgroundColor: "#88ff88"
},"fast" ).animate({
backgroundColor: "#ffffff"
},"fast", "", function(){jQuery( this ).removeAttr( "style" );
});
// highlight error message
jQuery( '#server_messages' ).find('.log_error' ).animate({
backgroundColor: "#ff8888"
},"fast" ).animate({
backgroundColor: "#ffffff"
},"fast", "", function(){jQuery( this ).removeAttr( "style" );
});
}

function CallbackComplete( which )
{
jQuery( '#server_postback_'+which ).remove();

}

The main thing about it is we can attach the Ajax stuff to any form, which automatically adds a "please reply in javascript" hidden input and then redirects the postback via a hidden <iframe>. This means that all of our forms are automatically upgraded if javascript is available, and if not then they just continue to function as usual.

On the server end it struts it's stuff and then checks if it's been asked for a js reply. If so then it spits out the answer in js and exits. If not then it does the whole "full html page" stuff ... gotta love degradation :D

¥

Note to self : cure the bug you just noticed in the above code :rolleyes:

BonRouge
07-31-2008, 06:38 AM
I have three questions:
Where did you get the idea from?
Does this idea have a name? ('AJAX without the Ax' is a bit of a mouthful)
Are there are any drawbacks to this?

ngaisteve1
07-31-2008, 06:41 AM
For ASP, I tried and can see the result but I couldn't get the translate this line's syntax

echo 'shuffleMeReply( \''.$reply.'\');';

anyone?

Anyway, for those who want to try in asp, just create an asp file (ie test.asp) with below code


<%
Response.ContentType = "text/javascript"

' reply = strReverse(request.querystring("reverse"))
' response.Write "reverseMeReply(reply);"

response.Write "reverseMeReply('This is a reply');"
%>


For the HTML page, I have reduced the code by taking out shuffleMe's stuff and changed the file name to asp.


<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<title>test</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<style type="text/css">
* {
margin:0;
padding:0;
}
body {
background-color:white;
}
</style>
<script type="text/javascript">
function query(q,value) {
var the_call = document.createElement('script'); // create script element
the_call.src = 'test.asp?'+q+'='+value; // set the url
the_call.type = 'text/javascript'; // to be sure to be sure
document.getElementsByTagName('head')[0].appendChild(the_call);
}

function reverseMe(id) {
var theText = document.getElementById(id).value; // grab the text
query('reverse',theText);
}

function reverseMeReply(reply) {
window.alert('The server said : '+reply); // stun the crowd with a reply from the server
}

window.onload=function () {
document.getElementById('reverseButton').onclick=function() {reverseMe('reverse')};
}
</script>
</head>
<body>
<label for="reverse">Type in the text to be reversed : <input type="text" name="reverse" id="reverse" /></label> <button id="reverseButton">Reverse</button>
</body>
</html>

¥åßßå
07-31-2008, 08:16 AM
I have three questions:
Where did you get the idea from?
Does this idea have a name? ('AJAX without the Ax' is a bit of a mouthful)
Are there are any drawbacks to this?

1) I was trying to break the internet and needed to make cross-domain calls to do it ;)
2) urm ... damn, you mean I need to think of a cool acronym now? ... urm, how about ASCUJ? Asyncronous Server Communication Utilising Javascript :rolleyes:
3) As I mentioned on the other thread, there are a few drawbacks
. a) you can only make GET calls, not POST ( unless you use the hidden frame method )
. b) the reply has to be a valid javascript reply ( which helps with "c" )
. c) you really have to watch out for security, it's very simple to open an XSS/CSRF hole.

For ASP, I tried and can see the result but I couldn't get the translate this line's syntax
This should work :

response.write( 'shuffleMeReply( \''+reply+'\');')

¥

BonRouge
07-31-2008, 08:49 AM
2) urm ... damn, you mean I need to think of a cool acronym now? ... urm, how about ASCUJ? Asyncronous Server Communication Utilising Javascript :rolleyes:
Hmmm... I think you'll have to do a bit better than that.
3) As I mentioned on the other thread, there are a few drawbacks

. a) you can only make GET calls, not POST ( unless you use the hidden frame method )
That's all I do with AJAX anyway.

. b) the reply has to be a valid javascript reply ( which helps with "c" )

Is that very different to AJAX? I mean, the AJAX results have to be handled by javascript anyway, don't they?

. c) you really have to watch out for security, it's very simple to open an XSS/CSRF hole.
I had wondered about security. Maybe you'll need to check the referer... (?)

Really though, I think this is a great idea and you should get a big cash prize or something for it. :)

¥åßßå
07-31-2008, 09:02 AM
Hmmm... I think you'll have to do a bit better than that.
I did think of AWTAX .... but I hate any sentence that contains tax in it ;)

Is that very different to AJAX? I mean, the AJAX results have to be handled by javascript anyway, don't they?

AJAX replies are meant to be XML, but can also just be normal HTML. Although they're handled by JS they don't have to be in written in JS

I had wondered about security. Maybe you'll need to check the referer... (?)
You have full access to cookies, so you can do your normal "is logged in" style of checks, you could also check the referrer ( some firewalls remove referrer data though, so you can't rely on that as your only defence ). Ideally you'll include a "key", based off your sessions table, and then check for that key during postbacks ... which is probably the route that we'll be taking ... ish.

Really though, I think this is a great idea and you should get a big cash prize or something for it. :)

Variations of this idea have been around for years, in days of old you used to use javascript to make one-way calls via images, even postbacks via hidden iframes isn't "original" .... but I wouldn't say no to a big cash prize :D

¥

Horus_Kol
07-31-2008, 06:43 PM
AJAX replies are meant to be XML, but can also just be normal HTML. Although they're handled by JS they don't have to be in written in JS

Well... AJAX did once imply that - but there are many AJAX-like systems which don't use XML or even HTML as the transport, and you don't have to use JS (as you've noted) - and there are some who argue that other asynchronous systems (like in Flash or Java) can be grouped into something that a lot of people are just calling Ajax these days...

ngaisteve1
07-31-2008, 09:21 PM
This should work :

response.write( 'shuffleMeReply( \''+reply+'\');')

¥

Still not. I tried

response.write('reverseMeReply(\''+reply+'\');') - no error but no response too.

response.write("reverseMeReply(\''+reply+'\');") - js error. "invalid character".

The whole thing I have is


<%Response.ContentType = "text/javascript"
reply = strReverse(request.querystring("reverse"))
response.write('reverseMeReply(\''+reply+'\');')
'response.Write("reverseMeReply('This is a reply');")
%>

¥åßßå
08-01-2008, 03:13 AM
It's been a long time since I last used asp, lets have another go ;)

response.write( "reverseMeReply( '"+reply+"');")

*crosses fingers

¥

ngaisteve1
08-01-2008, 03:36 AM
It's been a long time since I last used asp, lets have another go ;)

response.write( "reverseMeReply( '"+reply+"');")

*crosses fingers

¥

Still syntax error.:D

¥åßßå
08-01-2008, 03:55 AM
Third time lucky? :rolleyes:

response.write( "reverseMeReply( '"&reply&"');")

¥

ngaisteve1
08-01-2008, 04:30 AM
Third time lucky? :rolleyes:

response.write( "reverseMeReply( '"&reply&"');")

¥

This time round ... Bingo! :D

¥åßßå
08-01-2008, 04:40 AM
Yay :D

Now you can post the full ( working ) asp version ;)

¥

ngaisteve1
08-01-2008, 05:23 AM
Now you can post the full ( working ) asp version ;)¥

Alrite. I changed my code back to follow your code (the original) and just translate it (as literally as possible) to classic ASP.

HTML

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-UK" lang="en-UK">
<head>
<title>test</title>
</head>
<body>
<label for="theText">Type in the text to be reversed : <input type="text" name="theText" id="theText" /></label>
<button onclick="reverseMe();">Reverse</button>
<script type="text/javascript">

function reverseMe()
{
var theText = document.getElementById( 'theText' ).value; // grab the text
var the_call = document.createElement( 'script' ); // create script element
the_call.src = 'nonAjax.asp?theString='+theText; // set the url
the_call.type = 'text/javascript'; // to be sure to be sure
document.body.appendChild( the_call ); // add script to body and let browser do the rest
}

function serversReply( theAnswer )
{
window.alert( 'The server said : '+theAnswer ); // stun the crowd with a reply from the server
}
</script>
</body>
</html>


ASP (nonAjax.asp)

<%
Response.ContentType = "text/javascript" ' set the correct mime type

theText = request.querystring("theString") ' grab the text to be reversed

If theText = "" then
theAnswer = "Gimme summat to do!" ' inform the user we're bored
else ' we have some text, reverse it
theAnswer = strReverse(theText) ' reverse the text
end if

theAnswer = replace(theAnswer,"'","") ' single quotes would break the reply

response.write("serversReply('" & theAnswer & "');") 'trigger the function on the calling page
%>


(actually what tag shouid I use to wrap asp code?)

JoeyDaly
08-07-2008, 07:20 AM
Mmm I'll do this in cf if u want when I get to work and have some time ;)

¥åßßå
08-07-2008, 11:37 AM
The more sample code the better ;)

¥

Toady
08-29-2008, 04:00 PM
Cool concept indeed. Here is a quick and dirty example of this concept with Google suggestion.



<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xml:lang="en" lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<style type="text/css">
.FormContainer{
position:relative;
left:250px;
}
input.QueryInput{
width:400px;
margin-bottom: 0px;
}
div.DropDown{
border: 1px solid black;
background-color:white;
width: 400px;
z-index:1;
display:none;
position:absolute;
}
div.DropdownItem{
color:black;
cursor:default;
text-indent:2px;
display:block;
position:relative;
font-style:arial;
}
div.DropdownItem:hover span{
color:white;
}
div.DropdownItem:hover{
background-color:#3366CC;
}
span.numresults{
position:absolute;
right:2px;
color:green;
font-size:8pt;;
bottom:2px;
}
span.results{
font-size:11pt;
}
</style>
<script type="text/javascript">

var suggestions = false;
var response;

function onChangeEvent(obj,dropdownID){
var div = document.getElementById(dropdownID);
if (obj.value == ""){div.style.display='none'; return;}
response = document.createElement("script");
var path = "http://suggestqueries.google.com/complete/search?json=true&jsonp=ParseAndPresent&qu="+encodeURI(obj.value);
response.src = path;
response.type = "text/javascript";
document.getElementsByTagName('head')[0].appendChild(response);
}

function ParseAndPresent(obj){
var div = document.getElementById('DropDown');
suggestions = eval(obj);
var showVal = "";
for (var i = 0; i < suggestions[1].length; i++){
showVal += "<div class=DropdownItem onclick=changeInput(" + i + ")><span class=results>" + formatLength(suggestions[1][i],30) + "</span><span class=numresults>" + formatLength(suggestions[2][i],25) + "</span></div>";
}
div.innerHTML = showVal;
div.style.display = (suggestions[1][0]) ? 'block' : 'none';
}

function formatLength(aString,maxLength){
if (aString.length > maxLength){
aString = aString.substr(0,maxLength) + "...";
}
return aString;
}

function changeInput(index){
var myIn = document.getElementById('myInput');
document.getElementById('DropDown').style.display = 'none';
myIn.value = suggestions[1][index];
myIn.focus();
}
</script>
</head>
<body>
<form action="http://www.google.com/search">
<div class="FormContainer">
<br />
<input name=hl type=hidden value=en>
<input type=text id="myInput" autocomplete="off" maxlength=2048 name=q title="Google Search" class="QueryInput" onkeyup="onChangeEvent(this,'DropDown')"/><br />
<div align="left" id="DropDown" class="DropDown"></div>
<input name=btnG type=submit value="Search" /><input name=btnI type=submit value="I'm feeling lucky!" />
</div>
</form>
</body>
</html>


If i'm violating some laws by posting this, please delete.

¥åßßå
08-29-2008, 04:43 PM
You aren't violating this post ... and even if you were, I don't have the power to delete you .... so you're safe :D

I'm glad that this inspired you to expand it's use, but ( there's always a but ) ... in keeping with the "lets play with the dom" you might like to replace all your innerHTML ( ++ the string that's built before hand ) with document.createElement('div') ... etc (urm.. hunts for link ... found it dom stuff (http://www.javascriptkit.com/domref/) )

Appreciate your work though :D

¥