I had this problem I was trying to solve for work, and surprisingly, I couldn't find the solution anywhere else online, so I solved it myself and now offer the fruits of my labor.
Problem: retrieve report data in XML format from a SOA application and generate an HTML textual representation of the report data (ex. a table) as well as a graphical representation (ex. bar graph, pie chart, etc) on the client side using Ajax.
Solution: ok, there's plenty of information online regarding generating the XMLHTTPRequest to fetch the XML data and render HTML, so I won't go into that here other than to say I utilized the prototype.js package to make my life a little simpler. Also, to make my XML manipulation easier cross-browser, I utilized the zXML package from Nickolas C. Zakas (co-author of Professional Ajax which is where I read about it). The one caveat regarding using the zXML package, is that I had to use it to parse the XML from the XMLHTTPRequest.responseText instead of using the pre-parsed XMLHTTPRequest.responseXML like so:
currentReportDOM = zXmlDom.createDocument();
currentReportDOM.async = false;
currentReportDOM.loadXML(request.responseText);
Ok, so I have the report XML data in a DOM object in the variable named currentReportDOM. For this example I chose to format the XML into SVG using XSLT. You could of course do direct manipulation of the DOM object in Javascript if you'd prefer. Nothing spectacular in the XSLT file so I won't show it's contents here, but I apply the XSLT as shown:
// first convert the XML to SVG using XSLT
var xslDom = zXmlDom.createDocument();
xslDom.async = false;
xslDom.load("chart.xsl");
var str = zXslt.transformToText(currentReportDOM.documentElement, xslDom);
Now, the str variable contains the SVG XML as a string, and here's where the difficulties start. If this was XHTML in Firefox, I could just paste in the SVG data in some div like:
$("reportSVGDiv").innerHTML = str;
However, since this needs to work cross-browser, and Internet Explorer doesn't handle
.xhtml
files, this is a .html
file and when you do that in HTML Firefox no longer renders it as an SVG, but just as plain XML, and Internet Explorer doesn't know what to do with it either since it doesn't support SVG natively.Most instructions for embedding inline SVG in an HTML page for Internet Explorer instruct you to use either an
embed
, an object
, or an iframe
and set the source as the SVG file. This works great as along as your SVG data is actually in a file and the file ends with either a .svg
or a .svgz
(compressed) extension. If it was in a file, I could add an object to my page dynamically with the SVG file like:
var svgObject = document.createElement('object');
svgObject.setAttribute('type', 'image/svg+xml');
svgObject.setAttribute('data', 'svgdata.svg');
$("reportSVGDiv").appendChild(svgObject);
And it would be rendered. However, in this case, the data was received as raw XML from a server and rendered to SVG by the browser. Theoretically, you could add the data to the object inline like this:
var svgObject = document.createElement('object');
svgObject.setAttribute('type', 'image/svg+xml');
svgObject.setAttribute('data', 'data:image/svg+xml,'+ str);
$("reportSVGDiv").appendChild(svgObject);
However, since you're no longer referencing a file with a
.svg
or .svgz
extension, the IE MIME type handling -- which depends on file extensions, not the specified MIME type (image/svg+xml
) -- won't recognize it as an SVG.How to get it working in Internet Explorer:
To get Internet Explorer working you first have to install the Adobe SVG Viewer plugin so that IE can render SVGs. Then you have to define the
svg
namespace in your HTML header like:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
Then in the
head
section add the following lines to associate the Adobe SVG viewer with the svg
namespace (since Internet Explorer bases MIME types off of file extensions and we have no file in this case).
<object id="AdobeSVG" classid="clsid:78156a80-c6a1-4bbf-8e6a-3cd390eeb4e2"></object>
<?import namespace="svg" implementation="#AdobeSVG"?>
Now, as long as the SVG data you generate is tagged with the svg namespace (ex. <svg:svg>...</svg:svg>) then placing the SVG XML data in the innerHTML of some div will actually work! Additionally, you don't want the SVG embedded in an object tag in IE since Adobe SVG Viewer always disables scripting when it determines that the SVG file is embedded using the OBJECT tag.
How to get it working in Firefox:
Ironically, even though Firefox supports SVG natively (in XHTML) you can't just stick it in the HTML without embedding it in an
object
(or embed
, or iframe
). So, we have to build the object
and add the SVG data inline as I demonstrated earlier, but I'll give the complete example now:
// remove any old charts
while ($("reportSVGDiv").hasChildNodes()) {
$("reportSVGDiv").removeChild($("reportSVGDiv").firstChild);
}
var svgObject = document.createElement('object');
svgObject.setAttribute('name', 'svg');
svgObject.setAttribute('codebase', 'http://www.adobe.com/svg/viewer/install/');
svgObject.setAttribute('classid', 'clsid:78156a80-c6a1-4bbf-8e6a-3cd390eeb4e2');
svgObject.setAttribute('data', 'data:image/svg+xml,'+ str);
svgObject.setAttribute('width', '1050');
svgObject.setAttribute('height', '550');
svgObject.setAttribute('type', 'image/svg+xml');
$("reportSVGDiv").appendChild(svgObject);
How to get it working cross-browser:
The last piece in the puzzle is how to determine when we're in IE with the Adobe SVG viewer installed, and when we're not. To do that, I use a method to check for the Adobe SVG viewer:
// isASVInstalled
//
// utililty function to check for Adobe SVG viewer
//
function isASVInstalled() {
try {
var asv = new ActiveXObject("Adobe.SVGCtl");
return true;
}
catch(e){ }
return false;
}
Putting it all together:
With all the pieces in place, my final Javascript function to render the SVG inline with dynamic XML data received via Ajax from an SOA looks like:
// toSVG
//
// convert the report XML data to a SVG (scalable vector graphic) image
//
function toSVG() {
// first convert the XML to SVG using XSLT
var xslDom = zXmlDom.createDocument();
xslDom.async = false;
xslDom.load("chart.xsl");
var str = zXslt.transformToText(currentReportDOM.documentElement, xslDom);
// check if we're using the AdobeSVG viewer (Internet Explorer)
if (isASVInstalled() ) {
$("reportSVGDiv").innerHTML = straw;
} else {
// otherwise, we're assuming firebug/Mozilla which can render the SVG directly
// if this was true XHTML instead of HTML, then firebug would also render it directly
// using the inheriting above, however, since it has to be HTML to make IA happy,
// we then have to wrap the SVG in an
I hope this helps someone and saves them from the aggravation I encounted trying to get this to work!
5 comments:
You may have made a typo in the name of the "str" variable:
var str = zXslt.transformToText(currentReportDOM.documentElement, xslDom);
probably should be:
var straw = zXslt.transformToText(currentReportDOM.documentElement, xslDom);
Smiles said: "...all I get is the text elements in my svg rendered as though they were plain text."
Same here. Firefox just works by transforming the xml. But I can't get IE 7 to show anything except the raw text from my text elements. Anybody know why that might be?
Thanks a lot for you post. It did save me a lot of time. However, when I was adding content from javascript, I couldn't add my svn stuff in the 'data' attribute of the object as you have shown in the firefox case. After reading some portions of the w3c spec I learnt that the object element tries to render its contents if it's unable to fetch the url in data attribute. So assigning my svg text to its innerHTML worked.
Post a Comment