XmlHttpRequest and friends
Marco Radaelli

If I understand it, XmlHttpRequest allows manipulating data after the page has loaded. E.g. I can show a button and when the user clicks it a javascript function can use the object to retrieve the MOTD and place it somewhere in the page.

How would I go having it working in different browsers? Does each one have it's own way to create the object and interact with it?

I found that for IE it's
var req = new ActiveXObject("Microsoft.XMLHTTP");

while for FF
var req = new XMLHttpRequest();

(according to this page).

What's for other browsers?

miran
Quote:

What's for other browsers?

Should be either the same as for FF or not supported at all.

Marco Radaelli

Good.

What's best to check browser type? Client-side with JS or server-side looking at UserAgent (if someone is faking it I wouldn't care)

miran

I use code that looks like this in JS to asynchronously get data from the server with xmlhttprequest:

1var req = false;
2var waiting_for_data = false;
3var already_created_xmlhttprequest_object = false;
4 
5function request_data(url) {
6 if (!waiting_for_data) {
7 if (!already_created_xmlhttprequest_object) {
8 // for standard browsers
9 if (window.XMLHttpRequest) {
10 try {
11 req = new XMLHttpRequest();
12 already_created_xmlhttprequest_object = true;
13 }
14 catch (e) {
15 req = false;
16 }
17 }
18 // for IE
19 else if (window.ActiveXObject) {
20 try {
21 req = new ActiveXObject("Msxml2.XMLHTTP");
22 already_created_xmlhttprequest_object = true;
23 }
24 catch (e) {
25 try {
26 req = new ActiveXObject("Microsoft.XMLHTTP");
27 already_created_xmlhttprequest_object = true;
28 }
29 catch (e) {
30 req = false;
31 }
32 }
33 }
34 }
35
36 if (req) {
37 waiting_for_data = true;
38 
39 req.open("GET", url, true);
40 req.setRequestHeader("Content-Type","application/x-www-form-urlencoded");
41 
42 req.onreadystatechange = receive_data;
43 req.send("");
44 }
45 }
46}
47 
48function receive_data() {
49 if (req && waiting_for_data) {
50 if (req.readyState == 4) {
51 if (req.status == 200) {
52 do_something_with(req.responseText);
53 waiting_for_data = false;
54 }
55 }
56 }
57}

Marco Radaelli

Wow, thanks :D
I'll study it when I get home.

miran

Note that some of that code is a little specific for my project. I'm talking about that check that makes sure no other request can be made until data was received from the previous request. In your project you will probably want to change this...

FMC
Jonny Cook

See, now those are good tutorials. They just give you the facts, plain and simple. I really hate it when they pad the tutorial with a bunch useless crap to "ease" the learning process. It just makes it so you have to sift through it all to really get anything done... the whole world should think exactly like I do!>:(

[edit]
Although I must agree with the second tutorial that the first tutorial may not have had the best code examples.

Marco Radaelli
miran said:

Note that some of that code is a little specific for my project. I'm talking about that check that makes sure no other request can be made until data was received from the previous request. In your project you will probably want to change this...

I was in a hurry when I posted that reply, I didn't closely look at the code. Now I do, I'll keep the checking part.

FMC said:

This might help [rajshekhar.net]

[edit]and this http://aleembawany.com/weblog/webdev/000051_ajax_instant_tutorial.html

Thanks, but both do not seem to use XML in their example.

I finally found how to get the XML tree :)

Marcello
Marco Radaelli
50 websites... o_O

It looks like my troubles aren't finished :D

I managed to access that xml document I have and read its XML-tree.

Now the problem is that I'll add its childs to a xhtml element. While I can removeChild(), it doesn't let me appendChild(), I can't understand why.

xml file
<?xml version="1.0" ?>
<options>
  <option value="0">Zero</option>
  <option value="1">Uno</option>
  <option value="2">Due</option>
  <option value="3">Tre</option>
  <option value="4">Quattro</option>
</options>

xhtml page
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
  <head>
    <script language="Javascript">
    
      function go()
        {
         /* Recupero file XML */
         
         xmlobj = new ActiveXObject("Msxml2.XMLHTTP.3.0");

         xmlobj.open("GET", "http://localhost/xml-test/x.xml", false);
         xmlobj.send();
         
         /* Lettura albero XML */
         
         xmldoc = xmlobj.responseXML;

         sel = document.getElementById("comuni");

         sel.removeChild(sel.lastChild);
         
         /* lastChild because it seems that firstChild is <xml>... */ 
         opt = xmldoc.lastChild.childNodes;
         //options = xmldoc.getElementsByTagName("option");

         for(i = 0; i < opt.length; i++)
           {
            sel.appendChild(opt.item(i));
           }
        }
        
    </script>
  </head>

  <body>
    <input type="button" value="o_O" onclick="go()" />
    <form>

    <select id="comuni" size="1" disabled="disabled">
      <option selected>Scegliere una provincia</option>
    </select>
    
    </form>
  </body>
</html>

In short, in the webpage there's a <select> with a single <option>, which says "Select a province", when the button is clicked I want that <option> to be deleted (which works) and then the five <option>s in the xml to be added there (which doesn't work, keeps saying 'interface not supported' on the appendChild() line)

I still have to integrate the browser check, will do it when it all works :)
Matthew Leverton

You cannot add a node from one document into another. You need to import it (importNode ?), but of course IE6 doesn't support that.

The whole "AJAX" name is a joke. No one seriously uses XML with it. It's really "AJAJ" or "AJAH". Look for what's known as "JSON". It's basically a way to convert XML like data into a string that represents a JavaScript object.

AJAJ example:

server generates:
{options:[{value:0,text:"zero"},{value:1,text:"one"}]}

client evaluates it:
eval("var response = " + json);
alert(response.options[0].value);

Or alternatively use AJAH and just pass it as HTML, and use foo.innerHTML = HTML;.

miran

Yes, I was going to say the same thing as Matthew, but was too busy watching football. :P You will rarely want to handle XML in JS as JSON is so much easier and more convenient and has more support. Even if your source data is XML, it is much easier to convert it to JSON on the server and then just use eval() in JS than it is to manipulate XML in JS.

Matthew Leverton

I meant to comment on this:

Quote:

What's best to check browser type? Client-side with JS or server-side looking at UserAgent

Don't ever check the browser type to determine functionality. Always check browser features, starting with the standard ones first:

if (foo.addEventListner) {
  // W3C Model
}
else if (foo.attachEvent) {
  // IE Model
}

The only times you want to check the user agent string with JS is if a certain browser has a glitch that you need to work around. For example, I check for the literal string "Safari" on the dynamic combobox widget because Safari does not support CSS system colors, so I have to provide default colors.

That is the only future compatible method. If I were to check for features, I might get the detection wrong if another browser matches it or Safari changes functionality. (Obviously if they ever fix this problem, then I would need to add a version check as well.)

Marco Radaelli

Ok, I found how to manipulate the <select> childs:

Roughly

         opt = document.createElement("option");
         sel.options.add(opt);
         opt.innerHTML = "Prova";
         opt.value = 1;

I thought I'd go the easy way:

  • generate the js script server-side

  • load it with the nice object

  • eval() the code

Of course that doesn't work :P
I guess it's because the XmlHttpRequest object won't load non-xml data.

So? JSON requires more than five minutes to get it to work, but I still don't get how will I retrieve the js source code (either from a file or from a server-side script) :'(

miran
Quote:

I guess it's because the XmlHttpRequest object won't load non-xml data.

??? Of course it does. The server serves plain text. This text can be interpreted on client side as you wish, either as XML or JSON or anything else. On the server you just create a string containing JSON code, write it to the response object and that's it. I only know ASP, but I suppose it's similar in othe server side scripting languages. You just do something like

dim json as string
json = "{options:[{value:0,text:"zero"},{value:1,text:"one"}]}"
Response.Write(json)

and on the client in JS you do something like

var json = req.responseText;
eval("var response = " + json);

Marco Radaelli
I said:

I guess it's because the XmlHttpRequest object won't load non-xml data.

Ok, call me dumb.

I replaced the text in the file and IE started to say "Permission denied" on xmlobj.open()

I thought it was due because the file didn't contain valid xml code, but now I have closed and reopened IE completely and it works perfectly ::)

Well, for now I'll just directly generate the js script, avoiding that JSON stuff (I'm a bit in a hurry for this project). I'll keep an eye on it tough :)

Too bad I was so optimistic to think that browser capabilities were the only trouble.

Consider a whole box of cookies for you all ^_^

Matthew Leverton

There are also many JSON classes available for scripting languages. The one I use for PHP works like:

$response = array(
  'foo' => 'bar'
);

$json = new JSON;
echo $json->encode($response);

It may seem like overkill at first, but it becomes quite nice when you have a very dynamic response to build. Plus, it automatically handles converting UTF-8 encoded strings to proper JS escapes (eg, "\u00A9").

Marco Radaelli

Mmh... looks like the request ends before the whole code is retrieved, if the latter is too long.

Any way to wait for it? I'm thinking at onreadystate, but a busy-wait on it won't work without some sort of sleep()

Marcello

You're doing something wrong, then. The examples above will not do that.

Perhaps include some code?

Marcello

Matthew Leverton

As long as you are waiting for readyState == 4, everything will be there.

Marco Radaelli
Quote:

You're doing something wrong, then. The examples above will not do that.

Perhaps include some code?

Here's it (I still didn't converted it to JSON)

Client-side, when the selected item of a <select> changes, this deletes all the current <option>s in it and loads a js from listacomuni.php

1function aggiornacomuni(prov)
2{
3 /* Recupero codice javascript */
4 
5 if (window.XMLHttpRequest)
6 {
7 req = new XMLHttpRequest();
8 req.open("GET",
9 '<?php echo "http://".$_SERVER['SERVER_NAME'].
10 dirname($_SERVER['PHP_SELF']).
11 "/listacomuni.php?p=" ?>' + prov,
12 false);
13 }
14 else if(window.ActiveXObject)
15 {
16 req = new ActiveXObject("Msxml2.XMLHTTP");
17 
18 req.open("GET",
19 '<?php echo "http://".$_SERVER['SERVER_NAME'].
20 dirname($_SERVER['PHP_SELF']).
21 "/listacomuni.php?p=" ?>' + prov,
22 false);
23 req.send();
24 }
25
26 js = req.responseTEXT;
27
28 select = document.getElementById('comune');
29
30 for(i = 0; i < select.options.length; i++)
31 select.remove(i);
32
33 eval(js);
34}

Server-side, generates js that will add the new <option>s to the <select>

1 db_query("SELECT codice, nome FROM comuni WHERE codprovincia={$provincia} ORDER BY nome");
2 
3 header("Content-type: text/javascript");
4
5 echo "options = new Array(";
6 
7 if($res = db_getresult_byrow())
8 echo "\n ".
9 "new Array({$res['codice']}, \"{$res['nome']}\")";
10
11 while($res = db_getresult_byrow())
12 echo ",\n ".
13 "new Array({$res['codice']}, \"{$res['nome']}\")";
14
15 echo "\n);\n\n";
16 
17 echo "for(i = 0; i < options.length; i++)\n";
18 echo " {\n";
19 echo " option = document.createElement(\"option\");\n";
20 echo " select.options.add(option);\n";
21 
22 echo " option.value = options<i>[1];\n";
23 echo " option.innerHTML = options<i>[1];\n";
24 echo "}\n";

While the code isn't 100% fixed (I still have issues alternatively in IE and FF :)), when the query returns few rows it generates the whole script, but if the rows are many the script gets broken (I saw this by replacing eval() with alert() in the client-side script

An example is attached (couldn't resist to try the new upload thing ;D)

Quote:

As long as you are waiting for readyState == 4, everything will be there.

while(req.readystate != 4);
just like that?

Matthew Leverton
Quote:

while(req.readystate != 4);

No, the function will be called multiple times:

function onLoad()
{
  if (req.readystate != 4) return;
}

I would do this:

req.open("GET", url, true /* not false */);
req.onreadystatechange = function() {
  if (req.readystate != 4) return;
  // process
};

And use a callback function. It's the only good way to do it. (See Miran's example.)

BAF
Quote:

req.open("GET",
'<?php echo "http://".$_SERVER['SERVER_NAME'].
dirname($_SERVER['PHP_SELF']).
"/listacomuni.php?p=" ?>' + prov,

All your open()'s have stray php. Is that intended?

Marco Radaelli
BAF said:

All your open()'s have stray php. Is that intended?

Yes; due to the fact that the xml~ stuff needs an absolute path, that php code ensures everything works if I move the whole directory :)

Great... it doesn't work. I mean, I think I did everything you suggested

client-side code

1<script language="Javascript">
2 
3var req = false;
4 
5function aggiornacomuni(prov)
6{
7 /* Recupero codice javascript */
8 
9 if (window.XMLHttpRequest)
10 {
11 req = new XMLHttpRequest();
12 req.open("GET",
13 '<?php echo "http://".$_SERVER['SERVER_NAME'].
14 dirname($_SERVER['PHP_SELF']).
15 "/listacomuni.php?p=" ?>' + prov,
16 true);
17 }
18 else if(window.ActiveXObject)
19 {
20 req = new ActiveXObject("Msxml2.XMLHTTP");
21 req.open("GET",
22 '<?php echo "http://".$_SERVER['SERVER_NAME'].
23 dirname($_SERVER['PHP_SELF']).
24 "/listacomuni.php?p=" ?>' + prov,
25 true);
26 }
27
28 req.onreadystatechange = runjs;
29 req.send("");
30
31 select = document.getElementById('comune');
32
33 for(i = 0; i < select.options.length; i++)
34 select.remove(i);
35}
36 
37function runjs()
38{
39 if(req.readyState != 4)
40 return;
41
42 alert(req.responseTEXT);
43}
44</script>

server-side code

1<?php
2 
3 $provincia = $_GET['p'];
4 
5 include_once("db_fx.php");
6 
7 db_query("SELECT codice, nome FROM comuni WHERE codprovincia={$provincia} ORDER BY nome");
8 
9 header("Content-type: text/javascript");
10 
11 echo "options = new Array(";
12 
13 if($res = db_getresult_byrow())
14 echo "\n ".
15 "new Array({$res['codice']}, \"{$res['nome']}\")";
16
17 while($res = db_getresult_byrow())
18 echo ",\n ".
19 "new Array({$res['codice']}, \"{$res['nome']}\")";
20
21 echo "\n);\n\n";
22 
23 echo "for(i = 0; i < options.length; i++)\n";
24 echo " {\n";
25 echo " option = document.createElement(\"option\");\n";
26 echo " select.options.add(option);\n";
27 
28 echo " option.value = options<i>[0];\n";
29 echo " option.innerHTML = options<i>[1];\n";
30 echo "}\n";
31 
32?>

But it keeps failing (output is truncated) if the query returns too many rows. I have no idea about what to do, I can't understand why readyState becomes 4 before the script ends its output.

I also tried to put every js piece in a variable and echo that only at the end: same result.

I don't think it's a timeout issue, because it all ends in 1-2 seconds. I don't even understand why there are those boxes in the generated javascript (long.png in my post above), alert() doesn't have troubles with accented letters.

:(

[edit]

I changed
eval(req.responseTEXT);
to
eval(req.responseText);

and now it works in FF (it does wait for the whole data, while IE still doesn't ???). But I have another issue to add ;D
select.remove(i);
is not working ::)

Matthew Leverton

Are you sure it's truncated, or is the alert box just not displaying it all?

Quote:

alert() doesn't have troubles with accented letters.

No, but the encoding might get lost. How is it encoded on the server? UTF-8? If so, you'll need to convert them to JS's "\u1234" format. As a quick hack, you could use PHP's utf8_decode() function, but that will only help for chars < 256.

Marco Radaelli
Quote:

Are you sure it's truncated, or is the alert box just not displaying it all?

I guess so, because if I use eval() IE pops up an error saying 'Unterminated string...'.
Err... wait, that's what it kept saying on my home computer, here in university there seems to be another error: it says that 'select' is undefined, but only if I run the javascript more than once.

To sum it up I have two drop-down menus. The content of the latter depends on the selected item of the former.

When an element is selected, I delete all the content of the former <select> with

 select = document.getElementById('comune');
 
 for(i = 0; i < select.options.length; i++)
   select.remove(i);

This works when it contains only one element, but fails if its content has been filled through the javascript server-generated. I'm investigating it, any idea?

Quote:

No, but the encoding might get lost. How is it encoded on the server? UTF-8? If so, you'll need to convert them to JS's "\u1234" format. As a quick hack, you could use PHP's utf8_decode() function, but that will only help for chars < 256.

Resolved :), I just made the php script output the charset along with the content type in the HTTP Response header
header("Content-type: text/javascript; charset=iso-8859-1");

[edit]

Wohoo :D

this is the way to remove all the <option>s

 while(select.options.length)
   select.remove(0);

not this

 for(i = 0; i < select.options.length; i++)
   select.remove(i);

Thread #585870. Printed from Allegro.cc