|
Example provided by Andreas Neumann
This ECMAScript object (getData) should assist SVG developers to do network requests to fetch text or XML data on request, without having to reload the SVG file. This functionality is also known as the buzzword "Ajax" (Asynchronous Javascript and XML) ... However, this is nothing new and has been around for many years, both in the Adobe SVG viewer (since 2000) and in Microsoft's Internet Explorer (since version 5), which first introduced the XMLHttpRequest object, albeit as an ActiveX component. Internet Explorer 7 can do it natively without the hassle of the ActiveX component.
To complicate things a bit, not all SVG user agents support the same network request methods. The getData object is therefore a wrapper object around the three methods getURL(), postURL() and XMLHttpRequests(). The author can just use the wrapper function and does not have to care which function is actually available or used. getURL() and postURL() is supported in Apache Batik and the Adobe SVG viewer, XMLHttpRequest() is implemented in all webbrowsers. This object can be used under the terms of the LGPL license.
See an example of the getData object. This example asks the web server for its current time and displays it in a text element (left text). This is done using the get method of the object. Alternatively, a demonstration of the post method transfers the client's date to the server and the server sends this information back to the client (right text).
Download the helper_functions.js and place it in the same directory where your svg file resides in (or adopt pathes as appropriate). Link the javascript file using the following syntax:
<script type="text/ecmascript" xlink:href="helper_functions.js" />
The following code creates an instance of the getData object. Place this code in the appropriate position of your javascript function or object. The following example uses the get method.
var getDataObj = new getData(url,callBackFunction,returnFormat,method,postText);
Constructor of the getData object in order of the parameters to be passed over:
Examples:
var getDataObj = new getData("myWeatherData.xml",parseWeatherData,"xml","get",undefined,undefined);
//preparing the get request var currentYGet = 80; var baseUrlGet = "senddatetime.php?currentY="; var getDataObj = new getData(baseUrlGet,receiveData,"xml","get",undefined,undefined);
//preparing the post request
var currentYPost = 80;
var postDataObj = new getData("sendBackClientTime.php",receiveData,"xml","post","",undefined);
The second and third example are preparing the get and post requests in our example file. Note that creating the object instance does not yet trigger the network request.
When creating the instance of the getData object, the network request is not immediately made, until the .getData() method is called. The idea being that the same object instance can be used to repeatedly trigger network requests, potentially changing URL get parameters or the .postText property in between. The following 2 examples trigger the get and post requests in our example:
//calling the method .getData() of the getDataObj instance //this is triggered by the click event on the left text element currentYGet+=20; getDataObj.url=baseUrlGet+currentYGet; getDataObj.getData();
//calling the method .getData() of the getDataObj instance //this is triggered by the click event on the left text element currentYPost+=20; var curDateTime=new Date(); postDataObj.postText=currentYPost+'$'+curDateTime.toGMTString(); postDataObj.getData();
Note that currentYPost and currentYGet are increased by twenty units each time the network request is made. This information is passed to the PHP script (server) and used for creating the response.
In the simplest case the resource to be loaded with the getData network request is a static XML or text file. Often, it is a serverside scriping application or CGI program. Below is a simple PHP code (of our example) processing the get request:
<?php
header('Content-type: text/xml');
$currentY = $_GET['currentY'];
print '<g xmlns="http://www.w3.org/2000/svg">'."\n";
print "\t".'<text x="10" y="'.$currentY.'" font-size="15">'.date("M d Y H:i:s",time()).'</text>';
print "</g>\n";
?>
This code reads currentY from the get request and then sends a SVG snippet back containing a text element with the current date of the server and the currentY value for the text value (which is increasing whith each request). Also not the text/xml content type and the use of the SVG namespace, which is required that the client correctly determines the namespace of the XML fragment received. In case of a SVG <g/> (group) element or other grouping structure, only the parent element needs to include the SVG namespace. The following PHP code processes a post request:
<?php
header('Content-type: text/xml');
list($currentY,$clientDate) = explode("$",$HTTP_RAW_POST_DATA);
print '<g xmlns="http://www.w3.org/2000/svg">'."\n";
print "\t".'<text x="500" y="'.$currentY.'" font-size="15">'.$clientDate.'</text>'."\n";
print "</g>\n";
?>
Compared to the get request, the whole post textstring resides in one variable, not every parameter separately, as with the get variables.
Finally, we need to create the callBack functions which are triggered when the web browser receives the data he requested from the resource. In our example we requested XML data chunks in both cases. This means that our callBack functions receive ready to use XML nodes. Because the PHP scripts return a chunk of SVG (text elements) we can directly use these SVG elements and add them to the DOM tree of the SVG document. This is done by the following callback function found in index.svg:
function receiveData(node) {
document.documentElement.appendChild(node);
}
In this case we receive the SVG <text/> element and append it in the root element of the document, which means at the end of the file.
Note that the XMLHttpRequest() can do much more besides requesting data with the get and post method. As an example, one could monitor download progress for larger resources that are loaded, using the progress event. Additionally, one could create, rename, move and delete resources on a web server, assuming that the web user has the permissions to do so. See the W3C XMLHttpRequest() specification for a full list of options.
It is also important to know that for security reasons, network requests (at least in the case of getURL/postURL) are restricted to the same resource where the currently loaded SVG files came from. One cannot load the SVG file from server A and then do network requests to server B. If one needs data from server B, server A has to act as a proxy server and request the data from server B and forward it to the client.
Other Resources:
This code is provided for the curious who would like to know how the getData wrapper object works:
/**
* Wrapper object for network requests, uses getURL or XMLHttpRequest depending on availability
* The callBackFunction receives a XML or text node representing the rootElement
* of the fragment received or the return text, depending on the returnFormat.
* See also the following <a href="http://www.carto.net/papers/svg/network_requests/">documentation</a>.
* @class this is a wrapper object to provide network request functionality (get|post)
* @param {String} url the URL/IRI of the network resource to be called
* @param {Function|Object} callBackFunction the callBack function or object that is called after the data was received, in case of an object, the method 'receiveData' is called; both the function and the object's 'receiveData' method get 2 return parameters: 'node.firstChild'|text (the root element of the XML or text resource), this.additionalParams (if defined)
* @param {String} returnFormat the return format, either 'xml' or 'json' (or text)
* @param {String} method the method of the network request, either 'get' or 'post'
* @param {String|Undefined} postText the String containing the post text (optional) or Undefined (if not a 'post' request)
* @param {Object|Array|String|Number|Undefined} additionalParams additional parameters that will be passed to the callBackFunction or object (optional) or Undefined
* @return a new getData instance
* @type getData
* @constructor
* @version 1.0 (2007-02-23)
*/
function getData(url,callBackFunction,returnFormat,method,postText,additionalParams) {
this.url = url;
this.callBackFunction = callBackFunction;
this.returnFormat = returnFormat;
this.method = method;
this.additionalParams = additionalParams;
if (method != "get" && method != "post") {
alert("Error in network request: parameter 'method' must be 'get' or 'post'");
}
this.postText = postText;
this.xmlRequest = null; //@private reference to the XMLHttpRequest object
}
/**
* triggers the network request defined in the constructor
*/
getData.prototype.getData = function() {
//call getURL() if available
if (window.getURL) {
if (this.method == "get") {
getURL(this.url,this);
}
if (this.method == "post") {
postURL(this.url,this.postText,this);
}
}
//or call XMLHttpRequest() if available
else if (window.XMLHttpRequest) {
var _this = this;
this.xmlRequest = new XMLHttpRequest();
if (this.method == "get") {
if (this.returnFormat == "xml") {
this.xmlRequest.overrideMimeType("text/xml");
}
this.xmlRequest.open("GET",this.url,true);
}
if (this.method == "post") {
this.xmlRequest.open("POST",this.url,true);
}
this.xmlRequest.onreadystatechange = function() {_this.handleEvent()};
if (this.method == "get") {
this.xmlRequest.send(null);
}
if (this.method == "post") {
//test if postText exists and is of type string
var reallyPost = true;
if (!this.postText) {
reallyPost = false;
alert("Error in network post request: missing parameter 'postText'!");
}
if (typeof(this.postText) != "string") {
reallyPost = false;
alert("Error in network post request: parameter 'postText' has to be of type 'string')");
}
if (reallyPost) {
this.xmlRequest.send(this.postText);
}
}
}
//write an error message if neither method is available
else {
alert("your browser/svg viewer neither supports window.getURL nor window.XMLHttpRequest!");
}
}
/**
* this is the callback method for the getURL() or postURL() case
* @private
*/
getData.prototype.operationComplete = function(data) {
//check if data has a success property
if (data.success) {
//parse content of the XML format to the variable "node"
if (this.returnFormat == "xml") {
//convert the text information to an XML node and get the first child
var node = parseXML(data.content,document);
//distinguish between a callback function and an object
if (typeof(this.callBackFunction) == "function") {
this.callBackFunction(node.firstChild,this.additionalParams);
}
if (typeof(this.callBackFunction) == "object") {
this.callBackFunction.receiveData(node.firstChild,this.additionalParams);
}
}
if (this.returnFormat == "json") {
if (typeof(this.callBackFunction) == "function") {
this.callBackFunction(data.content,this.additionalParams);
}
if (typeof(this.callBackFunction) == "object") {
this.callBackFunction.receiveData(data.content,this.additionalParams);
}
}
}
else {
alert("something went wrong with dynamic loading of geometry!");
}
}
/**
* this is the callback method for the XMLHttpRequest case
* @private
*/
getData.prototype.handleEvent = function() {
if (this.xmlRequest.readyState == 4) {
if (this.returnFormat == "xml") {
//we need to import the XML node first
var importedNode = document.importNode(this.xmlRequest.responseXML.documentElement,true);
if (typeof(this.callBackFunction) == "function") {
this.callBackFunction(importedNode,this.additionalParams);
}
if (typeof(this.callBackFunction) == "object") {
this.callBackFunction.receiveData(importedNode,this.additionalParams);
}
}
if (this.returnFormat == "json") {
if (typeof(this.callBackFunction) == "function") {
this.callBackFunction(this.xmlRequest.responseText,this.additionalParams);
}
if (typeof(this.callBackFunction) == "object") {
this.callBackFunction.receiveData(this.xmlRequest.responseText,this.additionalParams);
}
}
}
}
| Last modified:
Tuesday, 01-May-2007 11:53:06 CEST
© carto:net (andré m. winter & andreas neumann) original URL for reference: http://www.carto.net/papers/svg/network_requests/index.shtml |