Related link: http://www.understandingxml.com/archives/2006/02/blogging_kids_s.html
Scattered thoughts this posting:
Blogging Kids
It’s been a little while since my last post, though not for lack of trying. I’ve been intending to write on the Northern Voice conference I attended last weekend, but after four attempts that have gone nowhere, I’ve rather given up. It was not that it wasn’t an interesting conference … it was, especially as it was one of the first conferences I’ve attended with my youngest daughter, Jennie (shown here podcasting away).
The sense I picked up at the conference though has left me somewhat depressed. In some respects I see in it the end of journalism as we know it, but then this has been something of a foregone conclusion for years. When you break down the barriers to entry by making it ridiculously easy to post content to the Internet, to format that content in as many different ways as you need and to present it in anything from a web page to a perfect bound hard-back book, big media’s days are numbered. New structures will, of course, take their place. Maybe one of these days I’ll articulate a little more clearly what exactly those new structures are. For now, I just want to get back to code.
SVG Conference
Before jumping into code full bore, however, I wanted to formally announce the SVG Open 2006 Conference in Victoria, British Columbia, to be held on August 22 through 25th, 2006. It’s purpose is simple - as with past years, the conference will promote Scalable Vector Graphics (and related “Open Graphics” standards), along with the community, product developers, map makers, theorists, and graphic artists that are now in this space. Because of the much greater visibility that SVG has had this year over last (especially within browsers, graphics chips, on the mobile market and so forth), I’m expecting this to be a major conference.
The website is still “drying” in places, and certan critical parts - call for papers, registration, sponsorship information and the like - will be posted over the next few days, but the site (at http://www.svgconference.com) is intended to be a clearinghouse not just for the conference but for people interested in SVG in general. Please check it out and sign up (the community, though not the conference, is free).
Scrolling Madness - Creating an SVG Component for Firefox
For some time, I’ve been thinking about being able to make use of SVG within Firefox to build user interfaces. It is one thing to talk about the fact that you can create compelling interfaces with XML-based vector graphics, but the reality is, unfortunately, a little more complex. SVG 1.1, as it exists right now, is a fairly low level API - it talks about shapes and paths and fills and similar things like that, and while it is possible to tie the various disparate pieces into something approaching utility, in point of fact, you need to have at least one, and potentially two levels of abstraction between this low level description and interactive web content.
One of the more maddening challenges in trying to demonstrate SVG is that most of the simple use cases that people deal with for interfaces - tables, lists, even tabs - can actually be accomplished with some basic Javascript code, XHTML and CSS … especially when dealing with components that flow consistently with the dominant flow of the web page that contain them (i.e., for most Western dialects, text that moves from left to write on the page). If you want your text to flow at an angle of forty five degrees, yeah, SVG’s your tool of choice, but frankly the number of compelling use cases for such a technology are at best very limited (maps being the most obvious exception). If you’re willing to play with moving transparent bitmap graphics in something like a scrollbar, then you can even fake such a scroll bar with XHTML+CSS, but the problem gets more complex when you want that scrollbar to resize or scale (and is impossible in cases where the scrollbar and correlative content are rotated) … for that, you need SVG … and a little bit of ingenuity.
What I ultimately set out to do was to create a tag, called <scroller> which would let you insert an SVG scrollbar into a web page. This element would let you specify such things as the minimum and maximum values, the current value, and whether the values within the component were discrete (integers) or continuous (floating point). It would also provide an onchange handler that could be applied to the scrollbar in order to read the current slider values. A simple sample web page (Firefox 1.5+ only) that displays this usage is shown in Listing 1, with the inactive and active states described by this shown in Figures 2 and 3 respectively. (You can also try this application by opening up the scroller.xhtml file - though it will only work with Firefox 1.5, Mozilla 1.8, or Seamonkey currently.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title/>
<style type="text/css"><![CDATA[
html {background-color:black;}
scroller {-moz-binding:url(svgComponents.xml#scroller);}
]]></style>
</head>
<body>
<scroller minimum="0" maximum="100" type="discrete"
value="100" id="scr1" onchange="
var bar = document.getElementById('posn');
var dataDisplay = document.getElementById('dataDisplay');
bar.textContent=this.value;
bar.style.width = this.value * 10 +'px';
dataDisplay.value = this.value;
"/>
<div id="posn" style="background-color:blue;border:outset 3px blue;color:white;"> </div>
<div>
<input type="text" id="dataDisplay"/>
<button onclick="
var scroller = document.getElementById('scr1');
var dataDisplay = document.getElementById('dataDisplay');
scroller.value =dataDisplay.value;
"
>Set Value</button>
</div>
</body>
</html>
Listing 1. Scroller.xhtml document, containing the <scroller> element. Note CSS declaration.

Figure 2. Scroller in the inactive state.

Figure 3. Scroller in the active state.
The <scroller> element is fairly robust, and while I’ve included it in the XHTML namespace (for now), it should more properly be in its own namespace. Here, it utilizes
five attributes - minimum, maximum, value, type (discrete or continuous), and the onchange event handler, in order to set the initial conditions
for the scrollbar.
<scroller minimum="0" maximum="100" type="discrete"
value="100" id="scr1" onchange="
var bar = document.getElementById('posn');
var dataDisplay = document.getElementById('dataDisplay');
bar.textContent=this.value;
bar.style.width = this.value * 10 +'px';
dataDisplay.value = this.value;
"/>
Listing 4. The <scroller> element in detail.
The way that this is set up, the <scroller> element is assigned its functionality inside the <style> block:
<style type="text/css"><![CDATA[
html {background-color:black;}
scroller {-moz-binding:url(svgComponents.xml#scroller);}
]]></style>
employing the Mozilla -moz-binding property within CSS to associate it with a specific XBL file, in this case svgComponents.xml.While the binding mechanism is usually associated with XUL, it works perfectly well in the context of XHTML within the browser itself.
The binding is tied not just into the svgComponents.xml file, but also to a specific entry within this document, the <xbl:binding> element with an id of scroller. The svgComponents.xml file could potentially have a number of such components defined in this manner (and indeed if you have a number of related components it makes sense to place them all within a single binding as this file would then only need to be downloaded once). The formal listing for svgComponents.xml is shown in Listing 5.
<xbl:bindings
xmlns:xbl="http://www.mozilla.org/xbl"
xmlns:h="http://www.w3.org/1999/xhtml"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:s="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<xbl:binding id="scroller">
<xbl:content>
</xbl:content>
<xbl:implementation>
<xbl:constructor action=""><![CDATA[
// This is Javascript code invoked when the
// binding is first attached to the element.
paint();
]]></xbl:constructor>
<xbl:method name="paint">
<xbl:body><![CDATA[
var boundElement = this;
var scrNode = new XML((new XMLSerializer()).serializeToString(boundElement));
function iif(a,b,f){
if (a != null){
if (f != null){
var result = eval(f+"('"+a+"')");
}
else {
var result = a;
}
}
else {
var result = b;
}
return result;
};
var data = {minimum:iif(scrNode.@minimum,0,'parseFloat'),
maximum:iif(scrNode.@maximum,100,'parseFloat'),
type:iif(scrNode.@type,'discrete'),
onchange:iif(scrNode.@onchange,''),
updateInterval:iif(scrNode.@updateInterval,10,'parseInt'),
value:iif(scrNode.@value,0,'parseFloat')};
var http=new XMLHttpRequest();
http.open("GET","scroller.xml",true);
http.onload = function(){
var result = http.responseXML;
boundElement.appendChild(result.documentElement.cloneNode(true));
boundElement.scroller = new Scroller(boundElement,data);
}
http.send(null);
]]></xbl:body>
</xbl:method>
<xbl:property name="value">
<xbl:setter><![CDATA[
// This defines a setter for the property
// and assumes the rvalue parameter is contained
// in the default parameter "val"
var boundElement = this;
boundElement.setAttribute("value",val);
boundElement.scroller.setTo(val,true);
]]></xbl:setter>
<xbl:getter><![CDATA[
// This defines a getter for the property
var boundElement = this;
return boundElement.getAttribute("value");
]]></xbl:getter>
</xbl:property> -->
</xbl:implementation>
</xbl:binding>
</xbl:bindings>
Listing 4. The <scroller> element in detail.
The binding here is simple, and reflects a basic level of functionality only. It defines one method - paint() - which gets invoked from the constructor, as well as a property - value - which is used to manually set or retrieve the value of the scroll bar based upon its current position. There are a couple of interesting points to keep in mind with this. First of all is the (quiet) use of E4X in order to get an instance of the scroller node as an XML() object contained in the variable scrNode. The expression, scrNode.@minimum, is then a shortcut for scrNode.getAttribute(”minimum”). I’ve also defined a small helper function called iif() which simplifies the assignment of default values. The data hash loads the current values into a single data structure, before an AJAX call to the SVG file scroller.xml retrieves this file, inserts it into the bound <scroller> object, and finally creates a new Scroller() Javascript object that associates the this bound element and the data structure with this object.
One thing to note in this listing … I’ve deliberately gone out of my way to NOT specify any hard information about the underlying SVG object, beyond the fact that it requires the parametric values in data to set the value of the object. Put another way - the binding serves as a layer of abstraction between the XHTML and the SVG, such that, so long as certain key design (and programmatic interface) considerations were satisfied, you could effectively replace one SVG with another.
You might have noted the reference to the Scroller() class. This is an object that is in fact defined within the SVG file directly, and acts as the actual controller for the scroller component. The full listings for this file, scroller.xml, are given in Listing 5.
<s:svg width="400px" height="40px" viewBox="0 0 400 40" xmlns:s="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<s:defs>
<s:linearGradient id="bedGradient">
<s:stop stop-color="navy" offset="0"/>
<s:stop stop-color="black" offset="0.5"/>
<s:stop stop-color="blue" offset="1"/>
</s:linearGradient>
<s:radialGradient id="thumbGradientOff" gradientTransform="translate(0.05,-0.05)">
<s:stop stop-color="red" offset="0"/>
<s:stop stop-color="maroon" offset="1"/>
</s:radialGradient>
<s:radialGradient id="thumbGradientOn" gradientTransform="translate(0.05,-0.05)">
<s:stop stop-color="yellow" offset="0"/>
<s:stop stop-color="red" offset="0.75"/>
<s:stop stop-color="maroon" offset="1.2"/>
</s:radialGradient>
<s:g id="thumbGraphicOff">
<s:circle cx="23" cy="23" r="12" fill="black" opacity="0.3"/>
<s:circle cx="20" cy="20" r="12" stroke="black" fill="url(#thumbGradientOff)"/>
</s:g>
<s:g id="thumbGraphicOn">
<s:circle cx="23" cy="23" r="18" fill="black" opacity="0.3"/>
<s:circle cx="20" cy="20" r="18" stroke="black" fill="url(#thumbGradientOn)"/>
</s:g>
<s:g id="bedGraphic">
<s:rect x="0" y="0" width="400" height="40" fill="url(#bedGradient)"/>
<s:polyline points="0,40 0,0 400,0" fill="none" stroke="navy" stroke-width="3"/>
<s:polyline points="0,39 399,39 399,0" fill="none" stroke="lightBlue" stroke-width="3"/>
</s:g>
</s:defs>
<s:use xlink:href="#bedGraphic" x="0" y="0" id="bed" width="400"/>
<s:use xlink:href="#thumbGraphicOff" x="0" y="0" id="thumb" width="40"/>
<s:script type="text/javascript"><![CDATA[
function Scroller(bindingElement,scrollData){
var thumb = document.getElementById("thumb");
var bed = document.getElementById("bed");
var scroller = this;
this.thumb = thumb;
this.bed = bed;
this.isActive = false;
this.maxXVal = bed.width.baseVal.value - thumb.width.baseVal.value;
this.minXVal = 0;
thumb.addEventListener("mousedown",function(evt){scroller.start(evt)},true);
thumb.addEventListener("mousemove",function(evt){scroller.move(evt)},true);
bed.addEventListener("mousedown",function(evt){
scroller.start(evt);
scroller.moveTo(evt);
scroller.stop(evt);
},true);
thumb.addEventListener("mouseup",function(evt){scroller.stop(evt)},true);
this.start = function(evt){
scroller.isActive = true;
thumb.setAttributeNS("http://www.w3.org/1999/xlink","href","#thumbGraphicOn");
position = {x:thumb.x.baseVal.value,y:thumb.y.baseVal.value};
scroller.delta = {x:evt.clientX - position.x , y:evt.clientY - position.y};
};
this.move = function(evt){
if (this.isActive){
mousePos = {x:evt.clientX,y:evt.clientY,toString:scroller.coordToString};
newPosition = {x:scroller.constrainX(mousePos.x - scroller.delta.x),
y:mousePos.y - scroller.delta.y,
toString:scroller.coordToString};
thumb.setAttribute("x",newPosition.x);
thumb.setAttribute("y",0);
scrollData.value = scrollData.minimum + (scrollData.maximum - scrollData.minimum) *
(newPosition.x - this.minXVal)/(this.maxXVal - this.minXVal); // Continued from previous line
if (scrollData.type == "discrete"){
bindingElement.setAttribute("value",Math.round(scrollData.value));
}
else {
bindingElement.setAttribute("value",scrollData.value);
}
bindingElement.onchange();
}
};
this.moveTo = function(evt){
if (this.isActive){
mousePos = {x:evt.clientX,y:evt.clientY,toString:scroller.coordToString};
newPosition = {x:scroller.constrainX(mousePos.x - (thumb.width.baseVal.value/2)),y:0, toString:scroller.coordToString};
var currentPosX = thumb.x.baseVal.value;
var posXArr = new Array();
for (var i=0;i<=10;i++){
var factor = i/10;
posXArr.push(parseInt(currentPosX + (newPosition.x - currentPosX)*(1-factor*factor)));
}
var key = window.setInterval(function(){
if (thumb.getAttributeNS("http://www.w3.org/1999/xlink","href")!= "#thumbGraphicOn"){
thumb.setAttributeNS("http://www.w3.org/1999/xlink","href","#thumbGraphicOn");
}
var pos = posXArr.pop();
thumb.setAttribute("x",pos);
thumb.setAttribute("y",0);
scrollData.value = scrollData.minimum + (scrollData.maximum - scrollData.minimum) *
(pos - scroller.minXVal)/(scroller.maxXVal - scroller.minXVal); // Continued from previus line
if (scrollData.type == "discrete"){
bindingElement.setAttribute("value",Math.round(scrollData.value));
}
else {
bindingElement.setAttribute("value",scrollData.value);
}
bindingElement.onchange();
if (posXArr.length == 0){
window.clearInterval(key);
thumb.setAttributeNS("http://www.w3.org/1999/xlink","href","#thumbGraphicOff");
}
},50);
}
};
this.setTo = function(val,noAnimFlag){
var transformedValue = scroller.minXVal + (thumb.width.baseVal.value)/2 + (val - scrollData.minimum)*
(scroller.maxXVal - scroller.minXVal)/(scrollData.maximum - scrollData.minimum); // Continued from previus line
var mousePos = {x:transformedValue,y:0,toString:scroller.coordToString};
newPosition = {x:scroller.constrainX(mousePos.x - (thumb.width.baseVal.value/2)),y:0, toString:scroller.coordToString};
var currentPosX = thumb.x.baseVal.value;
if (noAnimFlag){
thumb.setAttribute("x",newPosition.x);
thumb.setAttributeNS("http://www.w3.org/1999/xlink","href","#thumbGraphicOff");
scroller.isActive =false;
bindingElement.onchange();
}
else {
var posXArr = new Array();
for (var i=0;i<=10;i++){
var factor = i/10;
posXArr.push(parseInt(currentPosX + (newPosition.x - currentPosX)*(1-factor*factor)));
}
var key = window.setInterval(function(){
if (thumb.getAttributeNS("http://www.w3.org/1999/xlink","href")!= "#thumbGraphicOn"){
thumb.setAttributeNS("http://www.w3.org/1999/xlink","href","#thumbGraphicOn");
}
var pos = posXArr.pop();
thumb.setAttribute("x",pos);
thumb.setAttribute("y",0);
scrollData.value = scrollData.minimum + (scrollData.maximum - scrollData.minimum) *
(pos - scroller.minXVal)/(scroller.maxXVal - scroller.minXVal); // Continued from previous line
if (scrollData.type == "discrete"){
bindingElement.value = Math.round(scrollData.value);
}
else {
bindingElement.value = scrollData.value;
}
bindingElement.onchange();
if (posXArr.length == 0){
window.clearInterval(key);
thumb.setAttributeNS("http://www.w3.org/1999/xlink","href","#thumbGraphicOff");
}
},50);
}
};
this.coordToString = function(){return "("+this.x+","+this.y+")";};
this.constrainX = function(x){
if (x<0){
var newX = 0;
}
else {
if (x>scroller.maxXVal){
var newX = scroller.maxXVal;
}
else {
var newX = x;
}
}
return newX;
}
this.stop = function(evt){
thumb.setAttributeNS("http://www.w3.org/1999/xlink","href","#thumbGraphicOff");
this.isActive = false;
}
// initialize;
this.setTo(scrollData.value);
}
]]></s:script>
</s:svg>
Listing 5. The scroller.xml SVG file contains the actual graphics generation.
The key to understanding Listing 5 is to recognize where the abstractions have been introduced. Like most programmatic SVG documents, the key items have been abstracted via <svg:use> elements, in this case,
<s:use xlink:href="#bedGraphic" x="0" y="0" id="bed" width="400"/>
<s:use xlink:href="#thumbGraphicOff" x="0" y="0" id="thumb" width="40"/>
The bed and thumb items in turn point back to #bedGraphic and #thumbGraphicOff. This is significant … so long as these items fit within the prescribed widths (400 and 40 user units respectively), These graphics can be ANYTHING that you want them to be. The component also defines a #thumbGraphicOn which is used when the thumb is moving or being moved. What this means in practice is that you could create multiple different SVG files, each of which use a similar controller and the same interface descriptions, and changing the user interface becomes as simple as switching the link being referred to.
This particular component has two major drawbacks. Currently, it is incumbent upon the user to specify the dimensions directly into the graphic itself, then propagate these back up the chain so that these dimensions are reflected in the XHTML file. There are ways around this (for instance, embedding the relevant templates as an XSLT file and passing this information parametrically), but the final version of this component (which I will also post once done with it) will incorporate these mechanisms. The second drawback is somewhat more problematic - currently because you have the referential structure in place and id elements in this component are visible within the broader XHTML scope (due to a known bug in the Mozilla code), you have the potential for multiple elements with the same id if you have more than one <scroller> element in place. There are ways around this as well, and the final version of this code will incorporate these in order to make the scroller component usable for multiple instances.
The Scroller() object itself also makes use of some advanced Javascript concepts, including the notion of closure in order to perform previously declared animations. For instance, in the setTo() method, the thumb actually transits between its previous and new location parametrically, with this information encoded in a dynamic array where the content is pushed on as it’s calculated, then popped asynchronously. Once this stack is empty, then the setInterval process is cleared. This little trick could, with a little bit of work, be used to provide the animation support that didn’t make it into Firefox 1.5.
This sample is a work in progress … I’ll be incorporating more extensive support for resizing and rebalancing of the limits when I repost this, quite possible as part of a more comprehensive Source Forge project. You are welcome to work with this (consider it to be under the GPL license), and I welcome comments and suggestions on both improvements to this component and ideas about other SVG based components that could be made available. As a recap, the relevant files are available as follows:
scroller.xhtml, Main File
svgComponents.xml, XBL Binding
scroller.xml, SVG Source
Kurt Cagle is Director for New Technologies and Training with Mercurial Communications in Victoria, British Columbia, and is the author of multiple books on Web technologies, XML, SVG and AJAX. He is also the Chairman of the SVG Open 2006 Conference.
What other components would you like to see discussed or developed within this venue?

