Using AJAX to Update a Web Page in Real Time
Mike G
Posts: 2,702
I’m receiving more and more PMs and emails asking the same basic question. How do I display the state of my devices on a single web page in real time? I prefer AJAX to do this sort of thing.
Before jumping into AJAX, we need to dabble in client server architecture for a moment.
In the Internet world, there are servers and clients. Clients request server resources and server fulfill requests with a response. A browser running on a PC is an example of a client. It is important to understand that once a server sends a response to a client, the transaction is over. The client is only connected to the server during the request/response transaction. Once the transaction is complete, the client is no longer connected to the server and the server is free to serve up resources to the next client.
Back to the original question, how do I see my stuff in real time?
AJAX Asynchronous JavaScript And XML.
Client side JavaScript requests XML resources from a server, the server responds, the client extracts the data, then writes the data to specific tags on the loaded HTML page. This all happens behind the scene and without requesting an entire web page.
Below is an example of an XML document. It has a root element named <root> and child elements. The child elements contain values.
The objective of this exercise is to place the element values of <text>, <number>, <pi>, and <date> in an HTML page loaded in the browser.
Attached are two files, xmlexample.htm and data.xml, the web page and xml document respectively. Download and unzip the example to a folder on your PC. Open the file xmlexample.htm in a browser and click the Get Data link. The page will update and display the 4 XML element values above.
AJAX Code explanation:
Instantiate the XMLHttpRequest (req) used to create client requests. This code runs the first time the page loads; var req = getXmlHttpRequestObject();.
Create an HTTP GET request and assign a handler. The handler is invoked when the HTTP requests returns from the server - Asynchronous.
The handler receives the XML document and directs execution to parseState. parseState is responsible for placing the element values in the HTML page; xmlexample.htm.
document.getElementById("text") gets a reference to the HTML tag to update.
xDoc.getElementsByTagName("text")[0].childNodes[0].nodeValue gets the xml element value
Below are the HTML tags to update. Notice the ID attributes have the same name as the XML elements. This is not necessary. I did this so the example is easy to read.
Clicking the link invokes the HTTP get
Lastly, use the Javascript setInterval method to setup a timer to request the XML. This timer will create a client request every second.
A Spinneret is not required to the run this example. All you need is a web browser. So have fun and hack away.
More information:
http://www.w3schools.com/xml/xml_http.asp
Before jumping into AJAX, we need to dabble in client server architecture for a moment.
In the Internet world, there are servers and clients. Clients request server resources and server fulfill requests with a response. A browser running on a PC is an example of a client. It is important to understand that once a server sends a response to a client, the transaction is over. The client is only connected to the server during the request/response transaction. Once the transaction is complete, the client is no longer connected to the server and the server is free to serve up resources to the next client.
Back to the original question, how do I see my stuff in real time?
AJAX Asynchronous JavaScript And XML.
Client side JavaScript requests XML resources from a server, the server responds, the client extracts the data, then writes the data to specific tags on the loaded HTML page. This all happens behind the scene and without requesting an entire web page.
Below is an example of an XML document. It has a root element named <root> and child elements. The child elements contain values.
<?xml version="1.0" encoding="utf-8"?> <root> <text>Hello world</text> <number>1234</number> <pi>3.1415</pi> <date>1/1/1753</date> </root>
The objective of this exercise is to place the element values of <text>, <number>, <pi>, and <date> in an HTML page loaded in the browser.
Attached are two files, xmlexample.htm and data.xml, the web page and xml document respectively. Download and unzip the example to a folder on your PC. Open the file xmlexample.htm in a browser and click the Get Data link. The page will update and display the 4 XML element values above.
AJAX Code explanation:
Instantiate the XMLHttpRequest (req) used to create client requests. This code runs the first time the page loads; var req = getXmlHttpRequestObject();.
function getXmlHttpRequestObject() { if (window.XMLHttpRequest) { return new XMLHttpRequest(); } else if (window.ActiveXObject) { return new ActiveXObject("Microsoft.XMLHTTP"); } else { alert("Your Browser does not support AJAX!\nIt's about time to upgrade don't you think?"); } } //XmlHttpRequest object var req = getXmlHttpRequestObject();
Create an HTTP GET request and assign a handler. The handler is invoked when the HTTP requests returns from the server - Asynchronous.
function getRequest(resource) { // handle the case where a querystring is not detected var char = "&"; if(resource.indexOf("?", 0) == -1) { char = "?"; } if (req.readyState == 4 || req.readyState == 0) { req.open("GET", resource + char + 'ms=' + new Date().getTime(), true); req.onreadystatechange = handleResponse; req.send(null); return false; } }
The handler receives the XML document and directs execution to parseState. parseState is responsible for placing the element values in the HTML page; xmlexample.htm.
document.getElementById("text") gets a reference to the HTML tag to update.
xDoc.getElementsByTagName("text")[0].childNodes[0].nodeValue gets the xml element value
function handleResponse() { if (req.readyState == 4) { parseState(req.responseXML); } } function parseState(xDoc){ if(xDoc == null) return //Reference the <div> tag with the ID "text"; <div id="text">XML</div> var target = document.getElementById("text"); //Get the value of the xml node named <text> and place the value in <div id="text">here</div> target.innerHTML = xDoc.getElementsByTagName("text")[0].childNodes[0].nodeValue; target = document.getElementById("number"); target.innerHTML = xDoc.getElementsByTagName("number")[0].childNodes[0].nodeValue; target = document.getElementById("pi"); target.innerHTML = xDoc.getElementsByTagName("pi")[0].childNodes[0].nodeValue; target = document.getElementById("date"); target.innerHTML = xDoc.getElementsByTagName("date")[0].childNodes[0].nodeValue; }
Below are the HTML tags to update. Notice the ID attributes have the same name as the XML elements. This is not necessary. I did this so the example is easy to read.
Clicking the link invokes the HTTP get
<body> <h1>XML Example</h1> <div id="text">null</div> <div id="number">nothing</div> <div id="pi">nada</div> <div id="date">blank</div> <a href="#" onclick="getRequest('data.xml'); return false;">Get Data</a> </body>
Lastly, use the Javascript setInterval method to setup a timer to request the XML. This timer will create a client request every second.
setInterval(getRequest('data.xml'), 1000)
A Spinneret is not required to the run this example. All you need is a web browser. So have fun and hack away.
More information:
http://www.w3schools.com/xml/xml_http.asp
Comments
Thanks for that and for all your server/client tips and Spinneret code! Without your contributions, I doubt that the Spinneret would be nearly as viable a product as you've helped to make it.
-Phil
Ok- I've followed this example, but something still isn't making sense for me. I'm not sure where to go from here.
I'm trying to automatically update my"Live.htm" file whenever I click the "get data" link. The data comes from a long Tank_Level which could be a value from 0 to 186.
Live.htm
Livedata.xml
Dispatcher Method
And Live method
If I drop both the files on the SD card, it works as expected... when I enter the IP, Port and File name it shows up with "----" and then when I click the "Get Data" link, the text "Here's my live data" shows up.
I'm not getting how to go about making it show the value of my Tank_Level variable.. Help? Heeeelllllllllppp?!
Update the HTML/JavaScript
Add the filter
When I enter "http://172.16.150.242:5000/Live.htm" in my browser, I get the Live.htm page to render, but the "get data" link does nothing.
When I enter "http://172.16.150.242:5000/Live" instead, I get the following error:
XML Parsing Error: not well-formed
Location: http://172.16.150.242:5000/Live
Line Number 1, Column 80:<?xml version='1.0' encoding='utf-8'?><root application='multisocket'><Live.htm'186'><![CDATA[]]></Live></root>
^
My new Dispatcher and Live methods are:
The XML should look like
You can remove [CDATA[]] if you want. CDATA allows the XML to contain special characters.
Success!!!
I had a couple type-O's in there and got them straightened out. With your input and the type-o's gone, it's working. I'm going to work on the setInterval method for auto updates next but I need to get some family time in for the afternoon. Thanks again!
Robert
I have realized I don't need an xml file on the SD card, since the Dispatcher method is building the xml header and body on the fly. I just have to have the htm file on the card. But for some reason, Firefox displays everything perfectly and IE8 (I'm running windows XP here) doesn't update the values. Is this a common issue? I'm not getting any errors in IE8 or anywhere else that I know of. It just doesn't... well, doesn't update the data on the screen.
[php]
function getRequest(resource) {
// handle the case where a querystring is not detected
var char = "&";
if(resource.indexOf("?", 0) == -1) {
char = "?";
}
if (req.readyState == 4 || req.readyState == 0) {
req.open("GET", resource + char + 'ms=' + new Date().getTime(), true);
req.onreadystatechange = handleResponse;
req.send(null);
return false;
}
}
[/php]
Firstly - ditto PhilPi's comment - thanks for the work you put into this. Its a great plaform.
Secondly.... A question. I've downloaded the htm & xml file, and trying to run them locally using IE9 (32bit) on Win7 x64, but nothing happens when I press Get Data. Javascript is installed and working. Browser defaults to IE9 mode. Any ideas? IE Security change needed?
Thanks,
James
This code works if you put it on a webserver, spineret or IIS.
I've got this far from editing the w3schools example, and trying to fuse it with the html5 graph code in other thread.
All I am stuck with now is how to get the xmlDoc.getElementsByTagName("data")[0].childNodes[0].nodeValue; to return the value into the 'data': [1,2,3] graph bit.
my data.xml looks like:
Any ideas?
James
The data node contains a string. I'm guess you want an array? What are you trying to do? It is possible to create an array from the comma separated values. First the string must be split by the comma; data.split(","). The individual array elements (numbers) must be converted to integers using intParse().
This foramt allows you to enumerate the XML in JavaScript
or this
Thougth I jump in , I must have forgotten to put the js files also in the html5graph tread . anyway
the javascript and spin code that takes the values and puts them in the correspondending nodes inside the xml looks like below
first this code is what,s keeps calling the request to keep getting the data live
this calls the js below
[HTML]//XmlHttpRequest object
var req = getXmlHttpRequestObject();
var htmlTarget;
var timer = 0;
function getRequest(resource, elementId) {
// handle the case where a querystring is not detected
var char = "&";
if(resource.indexOf("?", 0) == -1) {
char = "?";
}
if (req.readyState == 4 || req.readyState == 0) {
req.open("GET", resource + char + 'ms=' + new Date().getTime(), true);
req.onreadystatechange = handleResponse;
htmlTarget = elementId;
req.send(null);
return false;
}
}
function handleResponse() {
if (req.readyState == 4) {
timer = 0;
parseState(req.responseXML);
}
/*
if(req.readState == 1 || req.readyState == 3){
timer++;
}
if(timer == 2000)
parseState(null);
*/
}
function parseState(xDoc) {
if(xDoc == null)
return
var target = document.getElementById("placeholder");
target.innerHTML = xDoc.getElementsByTagName("temp")[0].childNodes[0].nodeValue;
var target = document.getElementById("placeholder2");
target.innerHTML = xDoc.getElementsByTagName("humidity")[0].childNodes[0].nodeValue;
var target = document.getElementById("tmax");
target.innerHTML = xDoc.getElementsByTagName("tmax")[0].childNodes[0].nodeValue;
var target = document.getElementById("tmin");
target.innerHTML = xDoc.getElementsByTagName("tmin")[0].childNodes[0].nodeValue;
var target = document.getElementById("lvmax");
target.innerHTML = xDoc.getElementsByTagName("lvmax")[0].childNodes[0].nodeValue;
var target = document.getElementById("lvmin");
target.innerHTML = xDoc.getElementsByTagName("lvmin")[0].childNodes[0].nodeValue;
var target = document.getElementById("setpoint_1");
target.innerHTML = xDoc.getElementsByTagName("setpoint_1")[0].childNodes[0].nodeValue;
var target = document.getElementById("offset_1");
target.innerHTML = xDoc.getElementsByTagName("offset_1")[0].childNodes[0].nodeValue;
var target = document.getElementById("function_1");
target.innerHTML = xDoc.getElementsByTagName("function_1")[0].childNodes[0].nodeValue;
}[/HTML]
letting the xml look like below
[HTML]<root>
<temp>24.0</temp>
<humidity>43</humidity>
<tmax>26.6</tmax>
<tmin>19.7</tmin>
<lvmax>51</lvmax>
<lvmin>32</lvmin>
<setpoint_1>00</setpoint_1>
<offset_1>00</offset_1>
<function_1>00</function_1>
</root>[/HTML]
At the prop side . ,. There is this below taking place
1 . in the DAT section there is the XMl placeholders .
[HTML] xml byte "HTTP/1.1 200 OK", CR, LF, {
} "Content-Type: text/xml", CR, LF, CR, LF, {
} "<?xml version='1.0' encoding='utf-8'?><root><temp>"
temperature byte $30, $30, $2E, $30, "</temp><humidity>"
lvlv byte $30, $30, "</humidity><tmax>"
t_max byte $30, $30, $2E, $30, "</tmax><tmin>"
t_min byte $30, $30, $2E, $30, "</tmin><lvmax>"
lv_max byte $30, $30, "</lvmax><lvmin>"
lv_min byte $30, $30, "</lvmin><setpoint_1>"
set_p byte $30, $30, "</setpoint_1><offset_1>"
off_s byte $30, $30, "</offset_1><function_1>"
fun_1 byte $30, $30, "</function_1></root>", 0[/HTML]
in the multisocket, where the parseresourese gets the xml call . it does the following
wich gets the data from the rom/ram and gets it placed inside the right placeholders ,. after that they get sent out .
hopefully you can follow it to modify it to your needs.
Yes I want to use an array, based on a comma seperated string. I'll knock up some more Javascript then.
Will let you know,
James
Is messy, but takes a comma delimited string (which will come from 'data.xml') and pass it to the graph using the:
Just need to modify the default behaviours in the graph.js library so it doesn't start doing its 'call' method.
Update later.
James
@ Mike , I was trying to get a placeholder to display a text based on the value of a second placeholder(
the xml loads the function_1 placeholder . with a value ranging from 1 to 4 depending on this settting.
This number , I want to convert to the name of the funtion , But cant realy get it workin g,
I was tring with a javaschript to get the values , but this is not working ,and I am deffinitly not good with javascript
Or will it be best to go with a different approach completely. Letting the prop itself place the right text in the placeholder with a if if statment , and get the text sended out inside the xml ?
Thouth the javascript would be a easier fix , thus saving valuable programming space on the prop.
Any advice ?
1) The variable, name, references the DOM object (html) with id="function_2"
2) The variable, name, is assigned a string.
3) The property, name.innerHTML, is assigned to the value of XML node function_2 - Error?
It looks like the code is trying to do 3 different things with the same variable.
If you want to write a string to an HTML tag like <div id="mytag"></div> then
so now it looks like this .
inside the HTML . the div thats gonna hold the text
[HTML]<div id='interfacebuttons7'></div> [/HTML]
and using a button to test .
[HTML]<button type="button" onclick="setting_display()">Try it</button>[/HTML]
And the script
[HTML]function setting_display()
{
var s= document.getElementById("offset_1").innerHTML;
if (setting == 01)
{
document.getElementById("interfacebuttons7").innerHTML="Kachel";
};
if (s == 02)
{
document.getElementById("interfacebuttons7").innerHTML="Koeling";
};
if (s == 03)
{
document.getElementById("interfacebuttons7").innerHTML="Bevochtiger";
};
if (s == 04)
{
document.getElementById("interfacebuttons7").innerHTML="Ontvochtiger";
};
};[/HTML]
The part of getting the text displayed inside the correct div , looks to be going ok .
But I think its im not getting the the variable from the function_1 placholder ,. or its not comparring right .
To do the different actions , putting different names , when the corresponding value is found.
cant seem to get somewhere with a good explination yet , working on it , Verry appriciate your help
Now whenever pressed it completes the last statement putting Ontvochtiger" inside the div
UPDATE .
I found out what was missing whit reading and testing alot , the ..innerHTML was missing not letting me get the value .
anyway , I have modified the code above . This is how it does work , getting a name displayed in a div box depending on what value 1-4 it reads out of the xml that gets loaded
using this code on the client, and a data.xml page returned from the server:
Looks like:
Could be expanded to set parameters for the graph in the xml file, I probably will TBH.
Cheers!
In the above code, I can dynamically build the tags IOS0...IOS42. It works fine, with no problem. I'm making my javascript get updates every 3 seconds.
I am wondering why it stops working when I try to render 43 or more tags?
At first, I thought maybe I was trying to update from the htm file too quickly, so I changed it to 15 seconds. No difference. Then, I tried to increase the "stackspace[20] to 50 in the HTTPServer object. No dice.
Can you take a stab and help me understand what I need to do? I eventually need to create about 100 tags. Doing it this way is quite easy, if I can figure out the problem here.
Thanks in advance
Robert
Thanks for posting this information regarding AJAX. I have heard that term many times before, but never knew what it meant, now I know, and I see it to be very useful!
I was wondering if you knew of a way to "push" data to the client (browser) when new information is available, similar to using this method of AJAX.
Thanks,
Marcus
It would be a lot easier to write a custom application.
The xmlhttp POST ( xmlhttp.open("POST","(data to post)",true); ) method doesnt seem to work, with a onclick (button)command to post some data to the wiznet The ipad never sends out the post to the wiznet
By changing the POST to a GET ( xmlhttp.open("GET","(data to post)",true); ) , it all works fine again . Thouth not the best solution because a GET command is suppose to get some data back , and I only need to post the data to the wiznet to let it do a command.
But changing them to get did make it all work on the ipad also .
Never had this problem with computer based brouwsers , there it would work fine with the POST command
[HTML]function post_to_wiznet()
{
var xmlhttp;
if (window.XMLHttpRequest)
{// code for IE7+, Firefox, Chrome, Opera, Safari
xmlhttp=new XMLHttpRequest();
}
else
{// code for IE6, IE5
xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
}
xmlhttp.open("GET","(data to post)",true);
xmlhttp.send();
}[/HTML]
Any rejections to using this method ? or a better way to keep it working on the pc and the ipad withou writing 2 different codes to do the same thing?
You are mistaken, both GET and POST return a response. GET sends parameters in the URL while POST sends parameters in the message body.