In three previous posts (1, 2, 3) I've discussed getting browser-side dynamically generated SVGs to render properly across browsers. I now have a solution which is working for me.
Below is a screenshot my test application now. The XML used as sample data and the XSLT to product the SVG data was provided earlier.
(Note: I was also able to add some linking between the SVG and HTML so that as you mouse over each bar it will be highlighted in the SVG and simultaneously highlighted the corresponding row in the HTML table.)
In order to make this work I used the embed
tag for IE and the object
tag for all other browsers (tested with Firefox and Opera). My HTML has a div
named svgDiv which will dynamically be given the SVG to render. For Firefox I can create my object
with the dynamic SVG in one step, but for Internet Explorer, I first have to embed a static, blank SVG (as mentioned earlier) and then replace the blank SVG with the dynamic SVG. However, I determined that this could not be done all in one JavaScript function. I found that if I programatically add the embedded blank SVG, and them immediately replace the blank SVG with the dynamic one, it would fail, but having something like an alert
message between adding the blank SVG and replacing it with the dynamic SVG would work. The only explanation I could reason for this is that the actual addition of the embed
element is not completed by the browser until the JavaScript reaches a pause in processing (ie. not flushed/committed), and that the need to create the alert
popup causes the changes to be committed. (does anyone know how to make the browser commit the changes?). My solution to this problem was to add the blank SVG when the page is loaded (if IE) so that it will be available for me to replace when I'm ready.
When the page is loaded, if the browser is IE I call this function to add the blank SVG:
// embedBlankSVG
//
// add an <embed> tag to display a blank static SVG
// this is needed for IE to replace with dynamic content later
SVG.prototype.embedBlankSVG = function(width, height) {
var svgEmbed = document.createElement('embed');
svgEmbed.setAttribute('codebase', 'http://www.adobe.com/svg/viewer/install/');
svgEmbed.setAttribute('classid', 'clsid:78156a80-c6a1-4bbf-8e6a-3cd390eeb4e2');
svgEmbed.setAttribute('pluginspage', 'http://www.adobe.com/svg/viewer/install/');
svgEmbed.setAttribute('src', 'blank.svg');
svgEmbed.setAttribute('width', width);
svgEmbed.setAttribute('height', height);
svgEmbed.setAttribute('type', 'image/svg+xml');
svgEmbed.setAttribute('id', 'embeddedSVG');
this.svgDiv.appendChild(svgEmbed);
};
Then, whenever I want to render a new SVG, I invoke this function:
// render
//
// render the dynamic SVG in the page
SVG.prototype.render = function() {
var str = this.toString();
if (this.isIE) {
// For IE, we use an <embed> tab
// build replacement SVG document
var newSvgElement = document.createElement("svg");
newSvgElement.setAttribute("xmlns", "http://www.w3.org/2000/svg");
newSvgElement.setAttribute('xmlns:xlink', "http://www.w3.org/1999/xlink");
newSvgElement.setAttribute("width", this.width);
newSvgElement.setAttribute("height", this.height);
newSvgElement.innerHTML = str;
// replace blank SVG with new SVG
domtodom(newSvgElement, 'embeddedSVG');
} else {
this.clearPage();
// For all others (mainly Mozilla based), we use an <object> tag
// with the SVG data added inline
var svgObject = document.createElement('object');
svgObject.setAttribute('name', 'svgObj');
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', '850');
svgObject.setAttribute('height', '350');
svgObject.setAttribute('type', 'image/svg+xml');
this.svgDiv.appendChild(svgObject);
}
}
where the
toString
function produces SVG data from XML data (whether read from a file or returned in an XMLHttpRequest
response) and the domtodom
function comes from Chris Bayes' domtodom.jsThis was tested in Firefox 2.0, IE 7.0, and Opera 9.02 (though the table highlighting isn't currently working in Opera. It says I have a Security Error: attempted to read protected variable).
Help requested
I would like to be able to correspondingly highlight bars in the graph when I mouse over the table rows, but every attempt to add the:
<script type="text/ecmascript">
<![CDATA[
...
]]>
</script>
to the SVG data (as described here) immediately causes Firefox to crash.
Updated 12/07/2006 per comment below
The comment below asked what the
clearPage
function does. Since I can't format it nicely in the comments section, I decided to update the post with it. It doesn't really do anything too profound which is why I didn't post it originally.
// clear out any old displayed data from the page
SVG.prototype.clearPage = function() {
this.svgDiv.innerHTML = '';
this.tableDiv.innerHTML = '';
if (this.isIE) {
this.embedBlankSVG(this.svgWidth, this.svgHeight);
}
}
Additionally, while I'm updating this posting anyway, I was able to add JavaScript to my SVG through the XSLT (see Help Requested section) by adding it like this in the XSL:
<xsl:element name="script">
<xsl:attribute name="type">
<xsl:value-of select="'text/ecmascript'" />
</xsl:attribute>
<xsl:text>
<![CDATA[
...
]]>
</xsl:text>
</xsl:element>
I haven't gotten it to do everything that I want yet, but at least the browser no longer immediately crashes.