Menu

Extending SVG for XForms

May 22, 2002

Antoine Quint

Introduction

If you've read my previous columns, it should be clear that SVG provides what programmers and designers need to create interactive 2D graphics. What makes SVG a little special is the versatility it inherits from XML. XML vocabularies are meant to be extended and intermixed with one another. But despite some noteworthy efforts to create XML-aware browsers, like XSmiles, Amaya, and Mozilla, rock-solid SVG-centric browser applications are not quite ready. But it may be possible to start implementing XForms UI controls in SVG.

In previous columns we looked at SVG DOM in simple drag and drop or event handling and remote animation control. This month we're going to implement the XForms <button> element with CSS support, which will serve as an applied study on extending SVG's semantics with foreign elements and attributes.

The Task at Hand

Our task has three parts: parsing, instantiating objects, and interacting with them. Parsing is the part where we are going to look up XForms content in our document and try to make some sense of it. We will then use the gathered information to build an internal EcmaScript object model and draw the form controls. Interaction is the part where we use event handlers to create the different states of the button and allow for callbacks to be registered when it is pressed. This month, we'll take care of the XForms <button> element alone, concentrating on the process of integrating a new namespace with SVG; we'll look at the the interactivity and object modeling next month. So our first task is to read an SVG+XForms document and draw a static button. Consider this simple document:


<?xml version="1.0"?>

<svg  xmlns="http://www.w3.org/2000/svg"

      xmlns:a3="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"

      xmlns:forms="http://www.w3.org/2002/01/forms"

      a3:scriptImplementation="Adobe"

      xml:space="preserve"

      onload="XForms.init(evt)">



  <title>Simple implementation of the XForms <button> element</title>

  <script a3:scriptImplementation="Adobe" type="text/ecmascript"

          xlink:href="XFormsButton.es" />



  <g transform="translate(50,50)">

    <forms:button style=" color: blue;

                          border-color: red;

                          border-width: 1px;

                          background-color: pink;">

      <forms:caption>This is a test</forms:caption>

    </forms:button>

  </g>



</svg>

      

View this example as (static) SVG

In addition to the Adobe-specific attributes, you'll notice the declaration of the XForms namespace on the <svg> element and the <forms:button> element in that namespace. You will also notice the use of CSS properties straight out of the CSS 2 Box Model. These are all things that we will need to parse to draw a pink button.

Parsing XForms elements with the DOM

To get started, we have to navigate through the XML tree looking for <forms:button> elements, which hold the data we need. The fact that these elements are described in a separate namespace make this easy. In fact, getting a data structure (here, a NodeList) holding all the buttons is done in a single line of code:


var buttonElements = root.getElementsByTagNameNS(XForms.ns, 'button');

      

If you look at the small EcmaScript library I've built, you'll see that I've referenced the XForms namespace in an object, so it is easy to reference. This line of code was taken from the Button.init() function, called by the XForms.init() that we call when loading the SVG document is done. The rest of this function will recurse through all the nodes in our NodeList and call further initialization code on each element:


for (var i=0; i<buttonElements.length; i++) {

  var elem = buttonElements.item(i);

  var button = new Button(elem);

  button._draw();

}

      

   Related Reading

SVG Essentials

SVG Essentials

We have built a custom Button EcmaScript object that can be constructed with a pointer to a <forms:button> element as an argument. Looking at that object we can see it has the following methods defined: generateSVGStyle() and generateSVG(). Both these methods will be called from Button._draw(), which renders the button as SVG.

In order to parse XML with the DOM we read the caption's text from the <forms:caption> element. Text in XML is a bit tricky to handle. We have something called text nodes, but unfortunately, a text node might not be exactly what you expect. Even though you printed out a nicely-formatted line of text, you might end up with a heap of text nodes that will be a pain to process. Here's how we took a crack at it in the Caption.getText() method:


Caption.prototype.getText = function () {

  this.element.normalize();

  return this.element.firstChild.data;

}

      

The normalize() method (from the mother-of-all Node interfaces) is a great help here, since calling it on our <forms:caption> element will ensure we will encounter only one text node for the whole string. After calling it, we can safely return our first and only child's data.

Parsing CSS strings with the DOM

Locating and making sense of the actual XML elements in the XForms namespace was a good start. But our <forms:button> elements also have some CSS properties that we need to parse since SVG does not support the CSS 2 Box Model as-is. When reading CSS from SVG elements, we use the style field of SVG DOM objects, which returns a nice CSSStyleDeclaration CSS DOM object. The SVG Viewer knows from the DTD that the style attribute in the SVG world maps to CSS DOM objects. Alas, the style attribute is just a random attribute in XForms as far as the SVG Viewer is concerned, so it's up to us to parse the CSS data.

I read from the <forms:button>'s style attributes and we build a CSSStyleDeclaration object from it. The CSS DOM does not have functionality to do this, so I implemented it in Button.generateSVGStyle():


var style = this.element.getAttribute('style');

var dummy = document.createElementNS(SVG.ns, 'rect');

dummy.setAttribute('style', style);

      

The trick is to create a dummy SVG element, which will never get rendered, in order use its ability to return a CSSStyleDeclaration from its style attribute. So we read the style attribute from our <forms:button> element, fed it to our dummy SVG element, and now we will be able to get its corresponding CSSStyleDeclaration by calling dummy.style. Now that we have an easy access to the CSS data, we can construct a new CSS string for the SVG representation of our button:


  var fill = dummy.style.getPropertyValue('background-color');

  var stroke = dummy.style.getPropertyValue('border-color');

  var strokeWidth = dummy.style.getPropertyValue('border-width');

  if (fill == '') {

    fill = '#d4d0c8';

  }

  if (stroke == '') {

    stroke = '#404040';

  }

  if (strokeWidth == '') {

    strokeWidth = '1px';

  }

  style = '';

  style += 'shape-rendering: optimizeSpeed; ';

  style += 'fill: ' + fill + '; ';

  style += 'stroke: ' + stroke + '; ';

  style += 'stroke-width: ' + strokeWidth + '; ';

      

I also took the liberty to specify default values for the XForms CSS properties. Having a <forms:button> element with no style specified will result in an unattractive grayish color. It's always nice to allow for some flexibility. Here we could also have created another CSSStyleDeclaration to start building our new CSS string. It would have been less error-prone, but here just creating a simple string proved to be just fine. Another thing that would have been smart here would have been to create an EcmaScript CSSStyleDeclaration object that would have allowed both constructing from a plain string containing CSS data and printing the object to a plain string. This would have hidden the SVG dummy element's logic, but I tried to keep the code as simple as possible.

Drawing the Button

Now that we have figured out the XML and CSS parsing, it would be the right time to start drawing something on the screen. Back in the initialization method you noticed the call button._draw(). Taking a closer look at that method, we see it starts by asking for the SVG to be generated through a call to this.generateSVG(). Let's check out the Button.generateSVG() method:


Button.prototype.generateSVG = function () {

  this.caption.generateSVG();

  this.SVGElement = document.createElementNS(SVG.ns, 'rect');

  var width = Math.round(this.caption.getSize().width + this.marginLeft * 2);

  var height = Math.round(this.caption.getSize().height + this.marginTop * 2);

  this.SVGElement.setAttribute('width', width);

  this.SVGElement.setAttribute('height', height);

  this.SVGElement.setAttribute('style', this.style);

}

      

We start by asking our button's caption to generate its SVG code:


Caption.prototype.generateSVG = function () {

  this.SVGElement = document.createElementNS(SVG.ns, 'text');

  var textNode = document.createTextNode(this.getText());

  this.SVGElement.appendChild(textNode);

  var x = this.button.marginLeft;

  var y = this.getSize().height + this.button.marginTop;

  this.SVGElement.setAttribute('x', x);

  this.SVGElement.setAttribute('y', y);

  this.SVGElement.setAttribute('style', this.SVGStyle);

}

      

Both methods go through approximately the same process. The Button creates an SVG <rect> element, while the Caption generates a <text> element. Both also apply the CSS style computed with their respective generateSVGStyle() to their style attributes. The tricky part in drawing a button is to get the size of the SVG <text> element we will create in order to compute its location as well as its wrapping rectangle's size. In effect, I've implemented a getSize() method on the Caption object:


Caption.prototype.getSize = function () {

  return this.SVGElement.getBBox();

}

      

Also in Sacré SVG

Big Lists in Small Spaces

SVG At the Movies

Mobile SVG

SVG and Typography: Animation

Going Mobile With SVG: Standards

And who's showing up? It's our old friend the SVGLocatable::getBBox() method from the SVG DOM. Not only does it apply to SVG shapes, but it also applies to text elements. So all we need to do is take into account the margin settings we have specified on our Button object (the marginLeft and marginTop properties) to figure out the correct layout. Now the last thing to worry about is where we are going to put that new SVG code in our existing DOM tree.

Looking back at the Button._draw() method, we can see that the new graphics (a <rect> and a <text>) have been appended as siblings to the XForms button element. Doing so offers two main advantages. First, it kept the XForms XML and SVG representation at the same hierarchical levels, retaining the structural meaning of the original XForms element. Second, our SVG element's will now inherit transformations which will allow explicit positioning that's completely painless since the SVG Viewer will take care of it. So in the end, we managed to combine comfort with value, a good deal all in all.

Wrapping the First Part Up

Though this was a bit of a trek, all we've ended up with is a static button, though it can be style and positioned. For now it was important to be able to make sense of a document with multiple namespaces through DOM scripting. In next month's column we'll add SVG interactivity.