Monday, November 27, 2006

Why is sliced bread such a great thing?

I happened to be pondering why everything is compared to sliced bread (ie. "That's the greatest thing since sliced bread"), so I looked around a little and found this article to justify why everything is the greatest thing since sliced bread:

"Sliced bread was the culmination of a century of technological innovation."
-- http://www.engineerguy.com/comm/4263.htm

Wednesday, November 08, 2006

Using XML, XPath, and XSLT with JavaScript

There are several JavaScript XML libraries available to try to provide a cross-browser XML library. I've previously cited zXML in my Inline SVG article, there's also Sarissa and XML for <Script> and I'm sure others. However, when I started using them I discovered various problems and limitations with them for what I needed to do. So, I decided to write my own cross-browser XML routines. I wanted an XML library that was as lightweight as possible, doing just what I needed and utilizing as much of the built in browser capabilities as possible. In this article, I will first present a simple XML class which just holds the XML DOM object returned from an AJAX request and provides functions to get and change node values based on XPaths. Secondly, I will give a simple XSLT class to preform XSL Transformations. The complete code for the XML and XSL library is available here

XML

First off is the constructor which determined if we will be using IE (ActiveX) functions or W3C standard functions for XML manipulation and either stores the XML DOM returned as the responseXML from an XMLHttpRequest(XHR) or creates an empty DOM object for later manipulation.
//
// XML
//
function XML(xmlDom) {
this.isIE = window.ActiveXObject;
if (xmlDom != null) {
 this.xmlDom = xmlDom;
} else {
 // create an empty document
 if (this.isIE) {
    Try.these (
       function() { axDom = new ActiveXObject("MSXML2.DOMDocument.5.0"); },
       function() { axDom = new ActiveXObject("MSXML2.DOMDocument.4.0"); },
       function() { axDom = new ActiveXObject("MSXML2.DOMDocument.3.0"); },
       function() { axDom = new ActiveXObject("MSXML2.DOMDocument"); },
       function() { axDom = new ActiveXObject("Microsoft.XmlDom"); }
    );
    this.xmlDom = axDom;
 } else {
    this.xmlDom = document.implementation.createDocument("", "", null);
 }
}
};
In case you're not getting the XML data from an XHR response, I also provided a load function to load an XML file from an URL.
// load
//
// Loads an XML file from an URL
XML.prototype.load = function(url) {
 this.xmlDom.async = false;
 this.xmlDom.load(url);
};
Next, I want to be able to find a single node in the XML DOM based on an XPath. The getNode function will do that for me.
// getNode
//
// get a single node from the XML DOM using the XPath
XML.prototype.getNode = function(xpath) {
 if (this.isIE) {
    var result = this.xmlDom.selectSingleNode(xpath);
 } else {
    var evaluator = new XPathEvaluator();
    var result = evaluator.evaluate(xpath, this.xmlDom, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
 }
 return result;
};
More often, I don't need the actual DOM node object, but the value of that node, so I provide the getNodeValue a function to get the value of a node.
// getNodeValue
//
// get the value of a the element specified by the XPath in the XML DOM
XML.prototype.getNodeValue = function(xpath) {
 var value = null;
 try {
    var node = this.getNode(xpath);

    if (this.isIE &&amp; node) {
       value = node.text;
    } else if (!this.isIE && node.singleNodeValue) {
       value = node.singleNodeValue.textContent;
    }
 } catch (e) {}

 return value;
};
If you're just looking for a particular node/value, the previous functions are fine, but more often you need a group of node or node values (ex. all the book titles). So, the full JavaScript file also contains corresponding functions to get multiple nodes (getNodes) and node values as an array (getNodeValues). At some point, you'll probably want to access this XML DOM as XML data (string), so we need a function to serialize the XML from the DOM to a String. The getNodeAsXml function will do that for you. If you pass in the root XPath ("/") it will serialize the complete XML file. Also, the included prettyPrintXml function can be used to escape the HTML characters.
// getNodeAsXml
//
// get the XML contents of a node specified by the XPath
XML.prototype.getNodeAsXml = function(xpath) {
 var str = null;
 var aNode = this.getNode(xpath);
 try {
    if (this.isIE) {
       str = aNode.xml;
    } else {
       var serializer = new XMLSerializer();
       str = serializer.serializeToString(aNode.singleNodeValue);
    }
 } catch (e) {
    str = "ERROR: No such node in XML";
 }
 return str;
};
So far, we've only read the XML data we received. But we may also want to change the contents of the XML. So, here are the functions necessary to change existing node values (updateNodeValue), add new nodes/values (insertNode), and delete existing nodes (removeNode). Note: the XPaths used for node insertions cannot be overly complex as I wanted to keep this library simple.
// updateNodeValue
//
// update a specific element value in the XML DOM
XML.prototype.updateNodeValue = function(xpath, newvalue) {
 var node = this.getNode(xpath);
 var changeMade = false;
 newvalue = newvalue.trim();

 if (this.isIE &&amp; node) {
    if (node.text != newvalue) {
       node.text = newvalue;
       changeMade = true;
    }
 } else if (!this.isIE && node.singleNodeValue) {
    if (node.singleNodeValue.textContent != newvalue) {
       node.singleNodeValue.textContent = newvalue;
       changeMade = true;
    }
 } else {
    if (newvalue.length > 0) {
       this.insertNode(xpath);
       changeMade = this.updateNodeValue(xpath, newvalue);
    }
 }

 return changeMade;
};

// insertNode
//
// insert a new element (node) into the XML document based on the XPath
XML.prototype.insertNode = function(xpath) {
 var xpathComponents = xpath.split("/");
 var newChildName = xpathComponents.last();
 var parentPath = xpath.substr(0, xpath.length - newChildName.length - 1);
 var qualifierLoc = newChildName.indexOf("[");
 // remove qualifier for node being added
 if (qualifierLoc != -1) {
    newChildName = newChildName.substr(0, qualifierLoc);
 }
 var node = this.getNode(parentPath);
 var newChild = null;
 if (this.isIE && node) {
    newChild = this.xmlDom.createElement(newChildName);
    node.appendChild(newChild);
 } else if ((!this.isIE) && node.singleNodeValue) {
    newChild = this.xmlDom.createElement(newChildName);
    node.singleNodeValue.appendChild(newChild);
 } else {
    // add the parent, then re-try to add this child
    var parentNode = this.insertNode(parentPath);
    newChild = this.xmlDom.createElement(newChildName);
    parentNode.appendChild(newChild);
 }
 return newChild;
};

// removeNode
//
// remove an element (node) from the XML document based on the xpath
XML.prototype.removeNode = function(xpath) {
 var node = this.getNode(xpath);
 var changed = false;
 if (this.isIE &&amp; node) {
    node.parentNode.removeChild(node);
    changed = true;
 } else if ((!this.isIE) && node.singleNodeValue) {
    node.singleNodeValue.parentNode.removeChild(node.singleNodeValue);
    changed = true;
 }
 return changed;
};
You should now be able to fully read and manipulate XML using XPaths. For any significant reformatting or processing of the XML, you'll probably want to utilize XSLT. So, next I'll provide a simple XSLT class. It will preload (into a DOM object) an XSL file in its constructor. This provides rapid transformations of new XML data passed in to it's transform function (including parameters). First, the constructor which requires an URL pointing to the XSL file to be used for transformations. It does not accept a DOM object like the XML class did as it is assumed that the XSL (layout information) is more-or-less static and stored in a file whereas the XML data would most likely be dynamic and received via an XHR request.
//
// XSLT Processor
//
function XSLT(xslUrl) {
 this.isIE = window.ActiveXObject;
 if (this.isIE) {
    var xslDom = new ActiveXObject("MSXML2.FreeThreadedDOMDocument");
    xslDom.async = false;
    xslDom.load(xslUrl);
    if (xslDom.parseError.errorCode != 0) {
       var strErrMsg = "Problem Parsing Style Sheet:\n" +
          " Error #: " + xslDom.parseError.errorCode + "\n" +
          " Description: " + xslDom.parseError.reason + "\n" +
          " In file: " + xslDom.parseError.url + "\n" +
          " Line #: " + xslDom.parseError.line + "\n" +
          " Character # in line: " + xslDom.parseError.linepos + "\n" +
          " Character # in file: " + xslDom.parseError.filepos + "\n" +
          " Source line: " + xslDom.parseError.srcText;
         alert(strErrMsg);
       return false;
    }
    var xslTemplate = new ActiveXObject("MSXML2.XSLTemplate");
    xslTemplate.stylesheet = xslDom;
    this.xslProcessor = xslTemplate.createProcessor();
 } else {
    var xslDom = document.implementation.createDocument("", "", null);
    xslDom.async = false;
    xslDom.load(xslUrl);
    this.xslProcessor = new XSLTProcessor();
    this.xslProcessor.importStylesheet(xslDom);
}
};
Finally, the transform function will preform the XSL transformation and return the result. It accepts parameters to be passed to the XSL as an associative array of parameter name and value pairs.
// transform
//
// Transform an XML document
XSLT.prototype.transform = function(xml, params) {
 // set stylesheet parameters
 for (var param in params) {
    if (typeof params[param] != 'function') {
       if (this.isIE) {
          this.xslProcessor.addParameter(param, params[param]);
       } else {
          this.xslProcessor.setParameter(null, param, params[param]);
       }
    }
 }

 if (this.isIE) {
    this.xslProcessor.input = xml.xmlDom;
    this.xslProcessor.transform();
    var output = this.xslProcessor.output;
 } else {
    var resultDOM = this.xslProcessor.transformToDocument(xml.xmlDom);
    var serializer = new XMLSerializer();
    var output = serializer.serializeToString(resultDOM);
 }
 return output;
};
You should now be able to easily utilize XML and XSL on your web pages. An example usage of this XML/XSLT library would be to first build an XSLT object during the page initialization (onload).
xslProcessor = new XSLT(xslUrl);
Then, when XML responses are received from an AJAX request, process them. For example,
// get the XML data
try {
 var xmlData = new XML(request.responseXML);
} catch (e) {
 alert(request.responseText);
}

// get the username out of the XML
var userName = xmlData.getNodeValue('//userName');

// transform the XML to some HTML content
var newData = xslProcessor.transform(xmlData, {'param':'value'});
document.getElementById('someDiv').innerHTML = newData;
Update (February 27, 2007): I previously had a function in my XML class to generate valid HTML for the XML, but I've now modified it to use Oliver Becker's XML to HTML Verbatim Formatter with Syntax Highlighting stylesheet to format the XML. If that is not available, it will default to simply serialize the XML and convert the special characters. The code for this looks like:
/ Define a class variable which can be used to apply a style sheet to
// the XML for format it to HTML
XML.htmlFormatter = new XSLT("xsl/xmlverbatim.xsl");

// toHTML
//
// Transform the XML into formatted HTML
//
XML.prototype.toHTML = function() {
var html = null;
if (XML.htmlFormatter) {
 html = XML.htmlFormatter.transform(this);
} else {
 html = this.getNodeAsXml('/');
 if (html != null) {
  html = html.replace(/&/g, "&");
  html = html.replace(/</g, "&lt;");
  html = html.replace(/>/g, "&gt;<br/>");
 }
}
   return html;
}
As before, the complete package is available to download Update (March 7, 2007): I have updated this package to utilize Google's AJAXSLT XSL-T implementation when a native JavaScript XSLT function is not available. I need to test it more before releasing it however.

Thursday, October 26, 2006

Cross-browser inline SVG solved

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.js

This 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>
&lt;![CDATA[
...
]]&gt;
</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.

Tuesday, October 24, 2006

Scalable Vector Graphics (SVG) Intro

I ran across an introduction to SVG today entitled, Scalable Vector Graphics (SVG), Creating High-End 2D Graphics Using XML while I was looking for something else, but it gives a nice summary of SVG so I thought I'd share it. It covers the main questions:

  • What is SVG (and what is is not)?
  • How do you use SVG?
  • Why should you use SVG?
It's a few years old (2002), but still a nice article.

Thursday, October 19, 2006

IE7, any real improvement?

Now that IE7 is out, I will be 'upgrading' to it to ensure my web sites support it, but I wonder have much of an upgrade it really is -- especially since they've had 5 years to improve it.

I got an email this morning from eWEEK highlighting Jim Rapoza's glowing review of IE7. One one hand, he acknowledges that IE7 hasn't made any revolutionary step forward in the browser space, but has merely caught up as he says,

While we wouldn't yet call IE 7 one of the best browsers available today, Microsoft has greatly closed the distance between its browser and those of its competitors. Version 7 catches IE up with now-common browser features...



but after summarizing the key user interface and security enhancements, he makes this statement,

On the standards side, IE 7 does a much better job than previous versions of the product at supporting key standards, such as Cascading Style Sheets support and JavaScript. IE 7 still doesn't have perfect standards support, but it is much better than previous versions of the browser.



That statement puzzles me since according to this analysis which summarizes web browser standards support, I see very little improvement in standards support except in CSS 2.1 & 3 Basic Selectors.

To me, it sounds like Microsoft has merely put a new glossy finish on a rotting infrastructure (a whitewashed tomb so to speak) which is really disappointing to me, since after listening to this interview of the IE7 team by the Ajaxians last month, I was more optimistic about standards support.

Thursday, October 05, 2006

Using Cookies in JavaScript

Update (February 23, 2007): I have updated the cookies functionality below to utilize flash storage if available and fall back to cookies if not available. See Cookies and Flash for local storage

Whether you like them or not, cookies are an easy way store simple user specific information across web pages and sessions. Here are the basic JavaScript methods to create (and update), read, or delete (CRUD) cookies with JavaScript.


function createCookie(name, value, days) {
if (days) {
var date = new Date();
date.setTime(date.getTime()+(days*24*60*60*1000));
var expires = "; expires="+date.toGMTString();
}
else {
var expires = ";";
}
document.cookie = name + "=" + value + expires + "; path=/";
}

function readCookie(name) {
var nameEQ = name + "=";
var cookies = document.cookie.split(';');
for(var i = 0; i < cookies.length;i++) {
var cookie = cookies[i];
while (cookie.charAt(0) == ' ') {
cookie = cookie.substring(1, cookie.length);
}
if (cookie.indexOf(nameEQ) == 0) {
return cookie.substring(nameEQ.length, cookie.length);
}
}
return null;
}

function eraseCookie(name) {
createCookie(name, "", -1);
}


Detailed explanation of code how this code works is available here. This script was originally written by Scott Andrew.

Add/remove options to/from a select list

In my past two posts I've show how to move options between select lists and reorder options in a list. Now, to round out my little series on selection list manipulation with JavaScript, I'll give simple functions to add a value or remove a value from a list.


// addSelectOption
//
// Add the single select option to the selection list with the id specified
//
function addSelectOption(selectId, value, display) {
if (display == null) {
display = value;
}
var anOption = document.createElement('option');
anOption.value = value;
anOption.innerHTML = display;
document.getElementById(selectId).appendChild(anOption);
return anOption;
}

// removeSelectOption
//
// Remove the option with the specified value from the list of options
// in the selection list with the id specified
//
function removeSelectOption(selectId, value) {
var select = document.getElementById(selectId);
var kids = select.childNodes;
var numkids = kids.length;
for (var i = 0; i < numkids; i++) {
if (kids[i].value == value) {
select.removeChild(kids[i]);
break;
}
}
}

Wednesday, October 04, 2006

Move options up and down select lists


In my previous post I showed how to move options in a select list from one list to another with JavaScript. Once you've moved them, you may want to reorder them if order is important. So, now I'll show how to do that.

First, define your control in HTML to look something like what is shown to the right where you have up and down buttons which will be used to move selected elements up and down in the list.

Your HTML will look something like:


<select id="orderedList" multiple="multiple"></select>
<img src="moveup.gif" alt="Move Up" onclick="moveOptionsUp('orderedList')" />
<img src="movedown.gif" alt="Move Down" onclick="moveOptionsDown('orderedList')" />

As you can see, I've added two image which when you click on them invoke the moveOptionsUp and moveOptionsDown function. The JavaScript for these functions looks like:

// moveOptionsUp
//
// move the selected options up one location in the select list
//
function moveOptionsUp(selectId) {
var selectList = document.getElementById(selectId);
var selectOptions = selectList.getElementsByTagName('option');
for (var i = 1; i < selectOptions.length; i++) {
var opt = selectOptions[i];
if (opt.selected) {
selectList.removeChild(opt);
selectList.insertBefore(opt, selectOptions[i - 1]);
}
}
}

and

// moveOptionsDown
//
// move the selected options down one location in the select list
//
function moveOptionsDown(selectId) {
var selectList = document.getElementById(selectId);
var selectOptions = selectList.getElementsByTagName('option');
for (var i = selectOptions.length - 2; i >= 0; i--) {
var opt = selectOptions[i];
if (opt.selected) {
var nextOpt = selectOptions[i + 1];
opt = selectList.removeChild(opt);
nextOpt = selectList.replaceChild(opt, nextOpt);
selectList.insertBefore(nextOpt, opt);
}
}
}

Unfortunately there's no insertAfter function for a node, so the moveOptionsDown function is a little funky.

Move options across select lists


Perhaps you've seen controls like what is shown to the right where you have some list of items in one selection list and you can move them in and out of another by pressing the '<' and '>' buttons and you want to know how to do it.

Here's one way. First, define your select lists and buttons similarly to that shown below. In my case, I fill in the select options with AJAX, so I have no options defined in the HTML, but if you generated the HTML on the server, then you'll have options in the HTML as well.


<select id="available" size="10" multiple="multiple"></select>
<input type="button" value=">"
onclick="moveOptionsAcross($('available'), $('selected'))" />></input>
<input type="button" value="<"
onclick="moveOptionsAcross($('selected'), $('available'))" /><</input>
<select id="selected" size="10" multiple="multiple"></select>


Now include the following JavaScript code:


// moveOptionsAcross
//
// Move selected options from one select list to another
//
function moveOptionsAcross(fromSelectList, toSelectList) {
var selectOptions = fromSelectList.getElementsByTagName('option');
for (var i = 0; i < selectOptions.length; i++) {
var opt = selectOptions[i];
if (opt.selected) {
fromSelectList.removeChild(opt);
toSelectList.appendChild(opt);

// originally, this loop decremented from length to 0 so that you
// wouldn't have to worry about adjusting the index. However, then
// moving multiple options resulted in the order being reversed from when
// was in the original selection list which can be confusing to the user.
// So now, the index is adjusted to make sure we don't skip an option.
i--;
}
}
}


You should now be able to select options in one list and press the appropriate button to move them to the other list.

Checkbox as Radio Buttons

Sometimes you want the appearance of a checkbox on your form, but you want it to function like a radio button where only a single option can be selected at a time. The following JavaScript function will make a group of checkboxes function like a radiogroup.


function singleSelectCheckbox(checkbox) {
var aGroup = checkbox.parentNode;
var allInputs = aGroup.getElementsByTagName("input");
for (var i = 0; i < allInputs.length; i++) {
if (allInputs[i] == checkbox) {
continue;
} else {
allInputs[i].checked = false;
}
}
}

This assumes you've placed the checkboxes to work as a radio button group in some sort of containing HTML element (a td or span or something like that).
Your HTML code would look something like:

<span>
<input onclick="singleSelectCheckbox(this)" type="checkbox">Option 1</input>
<input onclick="singleSelectCheckbox(this)" type="checkbox">Option 2</input>
<input onclick="singleSelectCheckbox(this)" type="checkbox">Option 3</input>
</span>

Wednesday, September 27, 2006

The move from Netgear MP101 to Xbox Media Center

For some time now, I've been using a Netgear MP101 with the free TwonkyVision MusicServer. Unfortunately, the free version of MusicServer is no longer available from TwonkyVision as it is now part of the TwonkyMedia package, but you can still get it from places like the "Files" section of the NetgearMP101 Yahoo! Group. Even though the MP101 includes wireless B support, in order to have optimum playback performance, I used two Belkin Wireless G routers bridged together and connected the MP101 to the bridged router via ethernet. Using the matched routers to do the wireless communication gives significant communications improvements over using the wireless B. Well, that was well and good for music, but I wanted to do more (view pictures, videos, etc). I first tried out MythTV and MediaPortal on a spare computer. They were ok, though a bit slow (particularly in startup time) and MythTV proved a little difficult to get working with the hardware I had. Though the biggest factor in not going that route was that my wife was not happy with the computer case sitting next to the entertainment center. Yes, I could buy a small form-factor case, motherboard, etc. and build a small PC to look like another audio component to fit in the entertainment canter, but I wanted to do this cheaply. Then I ran into this article in Popular Science (printed edition) to build an "Xbox Media Monster". I knew you could hack Xboxes to run Linux or make them into media centers, but I thought you needed a modchip or good soldering skills (which I don't have), but apparently now you no longer need to do any hardware modifications! The instructions in the printed Popular Science article were a little too general, so I found more complete instructions at http://www.productwiki.com/microsoft_xbox/article/how_to_go_from_xbox_to_xbox_media_center_in_30_minutes.html which include the specific part numbers you need for the original MechAssault. So, I made a quick stop to GameStop to buy a used Xbox, an Action Replay memory device, and an original copy of the game MechAssault. Finding the original MechAssault was the only tricky part and required me to go to a second store. Then I simply followed the instructions and I was up and running XBMC (XBox Media Center) in no time. I used the XBMC BLINGBLING edition as it was the most up-to-date edition I could find. I'm loving my Xbox Media Center now. Besides being able to view and stream my pictures, music, and videos from my Linux server, XBMC also contains a scripting ability for which many scripts are available to do things like view the latest videos on YouTube, Google Videos, or your favorite community video source, stream audio from Shoutcast, Live365, or other audio sources, view current weather radar, TV listings, etc. If there's a media source online, someone probably has a script to view it via XBMC. XBMC can play video DVD and audio CDs (and rip them for you). You can use Xlink Kai to play games across the internet instead of a normal LAN (Local Area Network). Additionally, you can install Xbox apps like dvd2xbox to rip your Xbox games to the Xbox hard drive. Extracting your favorite game to the hard drive can help game performance. Another Xbox application you may want to try is MAMEoX to play your favorite classic arcade game on your Xbox. XBMC includes a web server so you can remotely control your Xbox, and an FTP server to move files to/from your Xbox. All in all, it's an impressive system for around $140 (for Xbox and all necessary accessories).

Thursday, September 21, 2006

Creating Mashups

For those of you interested in creating mashups (a website or web application that combines content from more than one source - wikipedia), then ProgrammableWeb is the place to go!

ProgrammableWeb showcases the latest mashup sites. For each site showcased, they provide a list of the mashup APIs used. They also have an API dashboard which provides a comprehensive database of API resources available to you as a developer and they have lots of tutorials and articles.

At the time of this writting, there are 1001 mashup sites showcased.

Using IE in Linux

If you're a web developer, you mostly likely have multiple browsers installed, or readily available to you, to test your web pages with. This is even more essential if you're building AJAX applications as there are many norotious variances between JavaScript and CSS implementations - particularly between Internet Explorer and more W3C compliant browsers like Mozilla, Firefox, and Opera.

To use IE in Linux, you have a few options.

  1. Use WINE (Windows Emulator)
  2. Use a virtual machine like VMWARE
  3. Connect remotely to a Windows machine with something like VNC, NX, or Citrix.
I've used all three of these methods, and they all have their pros and cons, which it not really the point of this article, but I'll provide a few.
  • WINE

    pros

    free, good performance and minimal memory requirements

    cons

    can be hard to setup, may not always work due to emulation problems (ex. APIs not implmented in WINE)

  • VMWARE

    pros

    fully supported, descent performance with sufficient hardware

    cons

    not free, resource intensive, takes longer to startup

  • Remote connection

    pros

    real windows machine

    cons

    requires a second machine, may have network latency


To address the "hard to setup" aspect of getting IE to run in WINE, I've used winetools before which works well, but it's really meant to set up a whole lot more than just IE. I've recently run across IEs4Linux which is a more straightforward method to simply set up IE in Linux under Wine.

Speeding up Linux

I'm a happy Linux user and like to tweak my operating systems to get as much performance out of them as possible, though not at the expense of sacrificing too much user experience niceties.
This article posses 7 Ways to Speed Up Your Linux Desktop which in a nutshell are:

  1. Use a Minimalistic Window Manager/Desktop Environment
  2. Customizing GNOME, KDE, or Xfce
  3. Get More RAM
  4. Kill Unneeded Processes
  5. Remove Virtual Terminals
  6. Use “Small” Applications
  7. Remove Start-Up Services
Like I said, I still want a nice user experience, so I'm sticking with KDE and Firefox for my browser (for many reasons, not the least of which is that I can synchronize bookmarks and plugins across multiple computers and OSes for a common Internet experience). However, the other tips can be useful.

Web Design Trends

This article analyzes some of the current trends in web design. It breaks them down into the following 8 categories:

  • Web Desktop Style
  • Silver Orange Style
  • Apple Style
  • Microsoft Style
  • Magazine Style
  • Rounded Plain Style
  • Web 2.0 Design Style
  • Adobe / Macromedia Style
For each style, the distinctive elements for each style are described, a sample screenshot snippet is provided as well a links to sites illustrating these styles, then pros and cons for using each style is given. Perhaps this will be useful for deciding how to design your next webpage.

Web Metrics

Good metrics for web sites are nearly as ellusive as good metrics in software development. The most frequently used metric in software is probably the lines of code (LOC) count which totally rewards inefficient, bloated, cut-n-paste style coding over well designed and reusable coding styles. Similarly, web metrics like hit counts and page views can similarly reward inefficient web coding styles (such as lots of extra page elements (graphics) to be downloaded, or excessive numbers of pages to be navigated to get the content).

This article has a nice sumary of some metrics like pageviews and reach and how redesigning some populate sites to use newer techniques like AJAX and RSS would significantly change their current metrics. It doesn't offer a solution for a good metric, but demonstrates the need for one.

In the aforementioned article, it has a link for specifically measuring AJAX applications with Analytics which is useful information.

Wednesday, September 20, 2006

No phishing allowed

I ran across the OpenDNS.org service the other day from some technology video podcast where David Ulevitch (founder of OpenDNS) was being interviewed about it. You simply set the DNS in your router (or on your PC) to the following two addresses:

  • 208.67.222.222
  • 208.67.220.220
Then, whenever you attempt to access a phishing (scam) site, it will instead give you a message that the site you attempted to access was a phishing site.

This service will also correct obvious typos in your URL which can be handy too.

They also claim to be a faster DNS because the have "a really big, smart cache" and are "geographically distributed", however I don't see how accessing their regional server can be faster than the local DNS on your ISP network, but I'm currently using this service and it's plenty fast.

Wednesday, August 09, 2006

Tracking satellites

I've always had a passing interest in tracking satellites. For over 10 years I've had STSORBIT PLUS Space Shuttle and Satellite Tracking Software by David H. Ransom, Jr. installed on my computer to bring up once in a while, particularly during Space Shuttle missions. However, it's kind of antiquated and requires manual fetching of the current orbital data ("TLEs" or "2-Line Elements") to use it.

Similarly, in my earlier post on using XPlanet to generate current satellite wallpaper I discuss how to build a dynamic wallpaper which shows satellite tracking over the current earth image (with current cloud cover) but it also requires fetching the TLEs.

However, now it appears I can drop STSPLUS in favor of an Ajax enabled online version which has lots of nice features and utilizes Google maps for the mapping.

Wednesday, July 12, 2006

Get request parameters through JavaScript

This is not original content, I'm just a reciting information I found elsewhere (see original), but with more processing done on the client side in JavaScript for Ajax, it's sometimes necessary to be able to process parameters passed on the URL with JavaScript as you would with PHP, JSP, etc. on the server. So since I found a method to do it, I'm posting it here for my own benefit so I don't have to find it again. You can also download it here.


function getParameter ( queryString, parameterName ) {
// Add "=" to the parameter name (i.e. parameterName=value)
var parameterName = parameterName + "=";
if ( queryString.length > 0 ) {
// Find the beginning of the string
begin = queryString.indexOf ( parameterName );
// If the parameter name is not found, skip it, otherwise return the value
if ( begin != -1 ) {
// Add the length (integer) to the beginning
begin += parameterName.length;
// Multiple parameters are separated by the "&" sign
end = queryString.indexOf ( "&" , begin );
if ( end == -1 ) {
end = queryString.length
}
// Return the string
return unescape ( queryString.substring ( begin, end ) );
}
// Return "null" if no parameter has been found
return "null";
}
}


The queryString is obtained via var queryString = window.top.location.search.substring(1);

Thursday, July 06, 2006

Follow up on inline SVG

This is just a brief follow-up to my previous request for help.

I had built my XSLT to render the SVG as described in the article, Render dynamic graphs in SVG and my mouseover coloring events was modeled after the article, Add interactivity to your SVG, which were both articles from developerWorks.

So, my generated code for a bar in my graph looked like:

<svg:rect x="35" y="272.88" height="57.12" width="109" style="fill:rgb(49,0,0)">
<set attributeName="fill" from="rgb(49,0,0)" to="red" begin="mouseover" end="mouseout" />
</svg:rect>
However, after taking the advice from Martin Honnen (SVG Developers Yahoo! Group) my generated code for the same bar in the graph looks like:
<svg:rect x="35" y="272.88" height="57.12" width="109" fill="rgb(49,0,0)" onmouseover="evt.target.setAttribute('fill', 'red');" onmouseout="evt.target.setAttribute('fill','rgb(49,0,0)');"/>
The result is that the correct colorings are now displayed when rendered inline in IE, and the mouseover events work correctly in Firefox.

The only thing still not working is the mouseover events when rendered inline in IE.

Martin Honnen further suggested to start with a blank SVG placed in the page with the embed tag, and then access it to manipulate it with the getSVGDocument method (which should work in IE, Firefox 1.5+, and Opera 9) to add the content. However, my attempts at this method have not been successful to date.

IBM's Death Spiral

Robert X. Cringley has a very interesting article on Why IBM is in Trouble.

"[IBM] has entered a death spiral of under-bidding and then under-delivering."

"decades no longer matter to publicly traded American companies. All that really matter are fiscal quarters."



I'm tempted to add my own personal editorial on the subject, but I don't want to put my job in jeopardy.

Wednesday, June 28, 2006

Help with dynamic inline SVG

Normally, I like to post solutions to problems on this blog, but today I'm hoping someone can help me.

In a previous post I presented some methods to embed SVG inline after generating it from XML data. However, there's still a problem, particularly in Internet Explorer, which I discovered when I tried to add some event handling to the SVG. Let me walk through the problem.

First, we start out with some sample XML data like:

<?xml version="1.0" ?>
<SrmData>
<data>
<display_name>srmdb2</display_name>
<date>2006-04-30</date>
<processor_busy>19.04</processor_busy>
</data>
<data>
<display_name>srmxml05</display_name>
<date>2006-04-30</date>
<processor_busy>37.68</processor_busy>
</data>
<data>
<display_name>srmdev1</display_name>
<date>2006-04-30</date>
<processor_busy>26.6</processor_busy>
</data>
<data>
<display_name>srmxml01</display_name>
<date>2006-04-30</date>
<processor_busy>42.74</processor_busy>
</data>
<data>
<display_name>srmweb2</display_name>
<date>2006-04-30</date>
<processor_busy>1.66</processor_busy>
</data>
<data>
<display_name>srmweb1</display_name>
<date>2006-04-30</date>
<processor_busy>5.61</processor_busy>
</data>
<data>
<display_name>srmxml02</display_name>
<date>2006-04-30</date>
<processor_busy>22.74</processor_busy>
</data>
</SrmData>
Which I run through my XSLT and get the following SVG (notice the mouseover events):
<svg:svg baseProfile="full" width="850" height="350" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<svg:g>
<!--Now Draw the main X and Y axis-->
<svg:g style="stroke-width:3; stroke:black">
<!--X Axis-->
<svg:path d="M 30 330 L 830 330 Z" />
<!--Y Axis-->
<svg:path d="M 30 30 L 30 330 Z" />
</svg:g>
<svg:g style="fill:none; stroke:#B0B0B0; stroke-width:2; stroke-dasharray:2 4;text-anchor:end; font-size:12px; stroke-width:5; stroke:black ">
<!--Add dotted line at 50-->
<svg:path d="M 30 30 L 830 30 Z" />
<svg:path d="M 30 165 L 830165 Z" />
<!--Add Y-axis labels-->
<svg:text style="fill:black; stroke:none" x="20" y="30">
100 </svg:text>
<svg:text style="fill:black; stroke:none" x="20" y="165">
50 </svg:text>
<svg:text style="fill:black; stroke:none" x="20" y="330">
0 </svg:text>
</svg:g>
<svg:rect x="35" y="272.88" height="57.12" width="109" style="fill:rgb(49,0,0)">
<set attributeName="fill" from="rgb(49,0,0)" to="red" begin="mouseover" end="mouseout" />
</svg:rect>
<svg:g>
<svg:text style="fill:black; stroke:none" x="35" y="347" rotate="45" font-size="8">
srmdb2</svg:text>
</svg:g>
<svg:rect x="149" y="216.96" height="113.03999999999999" width="109" style="fill:rgb(96,0,0)">
<set attributeName="fill" from="rgb(96,0,0)" to="red" begin="mouseover" end="mouseout" />
</svg:rect>
<svg:g>
<svg:text style="fill:black; stroke:none" x="149" y="347" rotate="45" font-size="8">
srmxml05</svg:text>
</svg:g>
<svg:rect x="263" y="250.2" height="79.80000000000001" width="109" style="fill:rgb(68,0,0)">
<set attributeName="fill" from="rgb(68,0,0)" to="red" begin="mouseover" end="mouseout" />
</svg:rect>
<svg:g>
<svg:text style="fill:black; stroke:none" x="263" y="347" rotate="45" font-size="8">
srmdev1</svg:text>
</svg:g>
<svg:rect x="377" y="201.78" height="128.22" width="109" style="fill:rgb(109,0,0)">
<set attributeName="fill" from="rgb(109,0,0)" to="red" begin="mouseover" end="mouseout" />
</svg:rect>
<svg:g>
<svg:text style="fill:black; stroke:none" x="377" y="347" rotate="45" font-size="8">
srmxml01</svg:text>
</svg:g>
<svg:rect x="491" y="325.02" height="4.9799999999999995" width="109" style="fill:rgb(4,0,0)">
<set attributeName="fill" from="rgb(4,0,0)" to="red" begin="mouseover" end="mouseout" />
</svg:rect>
<svg:g>
<svg:text style="fill:black; stroke:none" x="491" y="347" rotate="45" font-size="8">
srmweb2</svg:text>
</svg:g>
<svg:rect x="605" y="313.17" height="16.830000000000002" width="109" style="fill:rgb(14,0,0)">
<set attributeName="fill" from="rgb(14,0,0)" to="red" begin="mouseover" end="mouseout" />
</svg:rect>
<svg:g>
<svg:text style="fill:black; stroke:none" x="605" y="347" rotate="45" font-size="8">
srmweb1</svg:text>
</svg:g>
<svg:rect x="719" y="261.78" height="68.22" width="109" style="fill:rgb(58,0,0)">
<set attributeName="fill" from="rgb(58,0,0)" to="red" begin="mouseover" end="mouseout" />
</svg:rect>
<svg:g>
<svg:text style="fill:black; stroke:none" x="719" y="347" rotate="45" font-size="8">
srmxml02</svg:text>
</svg:g>
</svg:g>
</svg:svg>
If I embed the above SVG from a static file (via <embed src="myfile.svg">), it is rendered correctly in IE as:
where in the above snapshot, the mouse is over the first bar so the mouseover event has changed it to bright red to highlight it.

The problem is that when it is rendered dynmically inline in Internet Explorer, as described in my previous post, it looks like:

where there is no coloring and no mouseover events. Both cases require the Adobe SVG Viewer to be installed to render it. So why is it rendered differently?

There is one interesting difference in how IE handles this. When using the embed tag, if you right click on the image, the pop-up context menu will look like this:

where it is obviously using the Adobe SVG Viewer as evidenced by the "About Adobe SVG Viewer..." menu option and other options such as copying, viewing, and saving the SVG. On the other hand, when using the inlined SVG method, the context menu will look like:

as it would on any other part of the web page. So even though the Adobe plugin rendered the image, it is not apparent.

Meanwhile, on the Firefox side of things, regardless of whether you embed a static SVG file, put the SVG directly inline (if you're using XHTML), or put it in an object tag, it always looks the same:
which is good except for the fact that the Firefox (Gecko) SVG rendering engine doesn't seem to support the mouseover events either.

As an alternative method, I tried PlotKit which can generate dynamic SVG graphs and charts. So, I built an HTML table from my XML data and told PlotKit to use that and in both IE and Firefox it worked correctly to give me a graph such as:
The context menu of this graph is like my inline approach and doesn't acknowledge the Adobe SVG Viewer, however, PlotKit was able to get coloring to work. The current version of PlotKit does not support events which made it both not acceptable for my overall solution as well as provided me no way to test if the mouseover would have worked with the approach used by PlotKit. However, since at least colors worked, I started investigating how he (Alastair Tse) was able to render the dynamic inline SVG in IE.

I found this note in one of his samples:
Note: With SVG, the most compatible way is NOT to put in SVG tags, but create the SVG element with Javascript. Therefore, instead of adding <svg> tag, we just have a <div> container and create the SVG element using SVGRenderer.SVG().

And looking at the PlotKit code I can see that his library is dynamically building the SVG using JavaScript DOM APIs to add each element.

Well, since I want to use XSLT to generate my SVG from XML and not write my own version of the PlotKit library to build my SVGs, I tried a hybrid approach. I used the DOM APIs to create a temporary DOM element. I assigned my generated SVG to that element's innerHTML in order to have the browser build a DOM tree out of my SVG. I then created an svg DOM element, and appended the first child DOM element of my SVG DOM (the first g element) to the svg DOM element and appened the svg DOM element to the document. Which I would think would be theoretically the same thing, but to no avail.


Does anyone out there know how to fix this? How can I make Internet Explorer render my dynamic inline SVG correctly in order to get proper coloring and event handling?

Update: See follow-up

Monday, June 26, 2006

OpenAjax Alliance

I'm now a memeber of the OpenAjax Alliance (http://www.openajaxalliance.org/)

Saturday, June 03, 2006

Compressing HTML, CSS, and JavaScript files

Adding gzip compression to files received from your web server can enhance the network latency of your web application. As simple way to do this is to define a handler in your .htaccess file to compress your HTML, CSS, and JavaScript files. To do this, add these lines to your .htaccess file:


AddHandler application/x-httpd-php .css .html .js
php_value auto_prepend_file /path/to/gzip-page.php
php_flag zlib.output_compression On

where the path is specific to your installation.

The gzip-page.php is needed to define the content-type for the compressed data so that they are not assumed to be PHP files. The code should look something like:


<?php
$pathinfo = pathinfo($PHP_SELF);
$extension = $pathinfo['extension'];
switch ($extension) {
case "css" :
header("Content-type: text/css");
break;
case "html" :
header("Content-type: text/html");
break;
case "js" :
header("Content-type: text/javascript");
break;

default :
break;
}
?>


Now all your HTML, CSS, and JavaScript content will be compressed if the browser supports compressed data (which most do). For Ajax applications this is most useful since you'll probably have a decent amount of JavaScript code to transmit and potentially a fair amount of CSS as well.

Thursday, June 01, 2006

SVG in IE and Firefox

As follow-on work to my previous post Inline dynamic SVG from XML with Ajax where I discussed how to incorporate dynamically generated SVG in your web page, I've been trying to figure out how to make IE correctly prompt you to install the SVG plugin. That is usually accomplished with the PLUGINSPAGE attribute of the embed tag (see http://developer.apple.com/quicktime/compatibility.html for a nice explanation of the technique), but I'm using the object tag (which is recommended since it complies with the W3C HTML specifications while embed does not and it allows the data to be added inline). Unfortunately, adding the codebase attribute of http://www.adobe.com/svg/viewer/install/ (the Adobe SVG Viewer install site) and the classid attribute of clsid:78156a80-c6a1-4bbf-8e6a-3cd390eeb4e2 (the Adobe SVG Viewer ActiveX control id) didn't do what I had hoped for.

I haven't figured out how to make IE prompt for the plugin yet, but I did find this page which provides an alternative method to check for IE and alternate using the object or the embed (in his case or inline SVG in my example) rather than using the isASVInstalled() method I used.

In his example, Spartanicus uses the MS "uplevel revealed" conditional comment to define a CSS type as:


.svg{display:none}
*>.svg{display:inline}


Then his HTML looks like:


<!--[if IE]><embed src="img/butterfly_vector.svg" height="120" width="170"><![endif]-->
<object data="img/butterfly_vector.svg" type="image/svg+xml" class="svg" height="120" width="170"/>


Where the class of svg in the object version will causes it not to display if not in Internet Explorer.

It's an interesting technique, but in my case of inline generated SVG where I can't use the embed tag since I have no src so I'd have to insert the SVG inline for IE and then also have the wrapped object version. Unfortunately, the non-object version would still display the data in the SVG XML even though it would not be an SVG graphic which would look terrible. So, it doesn't really help me in this case, but I thought it was interesting.

Saturday, May 27, 2006

Inline dynamic SVG from XML with Ajax

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 .svgzextension, 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 svgnamespace 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 tag
// You can wrap the SVG in an tag for IA as well if you include the SVG from
// a file with a .svg extension, but since we're generating it dynamically, we don't
// have a file. The HTML spec supports loading the data inline, however, since IE's
// MIME types depend on file extensions, this doesn't work in IA.

// 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,'+ straw);
svgObject.setAttribute('width', '1050');
svgObject.setAttribute('height', '550');
svgObject.setAttribute('type', 'image/svg+xml');
$("reportSVGDiv").appendChild(svgObject);
}
}


I hope this helps someone and saves them from the aggravation I encounted trying to get this to work!

Blog renamed

I decided to rename my blog. Until now, it had been entitled Pothoven's Pansophy

When I originally named it, I picked pansophy because I wanted a word for knowledge or wisdom that started with 'P' for some alliteration with my name. So, looking up knowledge or wisdom in a thesaurus I found:

pansophy
\Pan"so*phy\, n. [Pan- + Gr. ? wisdom, ? wise: cf. F. pansophie.] Universal wisdom; esp., a system of universal knowledge proposed by Comenius (1592 -- 1671), a Moravian educator.

However, every time I look at it, it looks too much like pansy. So, I'm renaming the blog to Pothoven Post to sound more like a newspaper, but still with the alliteration.

Thursday, April 20, 2006

Ajax: HTML vs XML responses

A friend of mine has a discussion on his blog regarding returning straight HTML or XML in your AJAX responses (see What type of data should an Ajax call return?)

I voiced a comment regarding my appreciation for the direction used by Rico where XML is always returned, though it may contain an HTML snippet such as:


<ajax-response>
<response type="element" id="personInfo">
[valid XHTML here]
</response>
</ajax-response>


where the type="element" id="personInfo" indicates to Rico to replace the innerHTML of the element with id personInfo with the contents of the response.

Or alternatively:


<ajax-response>
<response type="object" id="formLetterUpdater">
[valid XML data here]
</response>
</ajax-response>


The type="object" id="formLetterUpdater" calls a JavaScript object registered under the name matching the id of formLetterUpdater. That Javascript would then process the contents of the response as XML to extract whatever data is necessary.

For either mechanism, multiple "response" clauses can be returned in a single "ajax-response" allowing multiple sections of a web page to be updated from a single AJAX action if necessary.

My apprehension about this method is that the server side processing has too much knowledge about how the data will be used (by dictating the ids). Of course, that might be able to be handled by having the AJAX response send which ids it wants data for.

Of these two mechanisms from Rico, I think the XML data mechanism has the most flexibility, as it describes the data in XML and the UI can extract what it needs and format it correctly whether by coding manual JavaScript DOM processing as indicated with the formLetterUpdater above, or by utilizing XSLT on the browser to format it as desired. Using XSLT provides multiple advantages for formatting and re-formatting the data for the user without the need to communicate with the server over and over to format the HTML. For example, switching themes, languages, or switching from a table to a graph.

Tuesday, April 18, 2006

Wrapping long strings with XSL 1.0

For my recent work with XML and XSL, I needed to wrap strings when generating a text report. I found examples online to do it, but they depended on XSL 2.0 functions such as the string tokenizer. I needed a way to do it with plain old XSL 1.0, so the solution I came up with is below. This solution is a bit more complex than necessary since I needed the ability to add markup around the wrapped lines (the prefix and suffix). Additionally, I needed the ability to add different markup before and after wrapped lines as well (the hangingPrefix and hangingSuffix). I could have taken it out to simplify this example, however, I thought some people might find it useful. Additionally, it's designed for all the prefixes and suffixes to be optional anyway. Here's my solution (the second template called splitString is the entry point):


<!-- recursive section of splitString... don't call directly -->
<xsl:template name="firstString">
<xsl:param name="string" />
<xsl:param name="length" />
<xsl:param name="currentpos" />
<xsl:param name="prefix" />
<xsl:param name="hangingPrefix" />
<xsl:param name="suffix" />
<xsl:param name="hangingSuffix" />
<xsl:param name="padding" />

<xsl:choose>
<xsl:when test="substring($string, $currentpos, 1) = ' '">
<xsl:value-of select="substring($string, 1, $currentpos - 1)" />
<xsl:value-of select="$suffix" />
<xsl:value-of select="$newline" />

<xsl:call-template name="splitString">
<xsl:with-param name="string" select="substring($string, $currentpos + 1)" />
<xsl:with-param name="length" select="$length" />
<xsl:with-param name="prefix" select="$hangingPrefix" />
<xsl:with-param name="hangingPrefix" select="$hangingPrefix" />
<xsl:with-param name="suffix" select="$hangingSuffix" />
<xsl:with-param name="hangingSuffix" select="$hangingSuffix" />
<xsl:with-param name="padding" select="$padding" />
</xsl:call-template>
</xsl:when>
<xsl:when test="substring($string, $currentpos, 1) = '/'">
<xsl:value-of select="substring($string, 1, $currentpos)" />
<xsl:value-of select="$suffix" />
<xsl:value-of select="$newline" />

<xsl:call-template name="splitString">
<xsl:with-param name="string" select="substring($string, $currentpos + 1)" />
<xsl:with-param name="length" select="$length" />
<xsl:with-param name="prefix" select="$hangingPrefix" />
<xsl:with-param name="hangingPrefix" select="$hangingPrefix" />
<xsl:with-param name="suffix" select="$hangingSuffix" />
<xsl:with-param name="hangingSuffix" select="$hangingSuffix" />
<xsl:with-param name="padding" select="$padding" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="firstString">
<xsl:with-param name="string" select="$string" />
<xsl:with-param name="length" select="$length" />
<xsl:with-param name="currentpos" select="$currentpos - 1" />
<xsl:with-param name="prefix" select="$prefix" />
<xsl:with-param name="hangingPrefix" select="$hangingPrefix" />
<xsl:with-param name="suffix" select="$suffix" />
<xsl:with-param name="hangingSuffix" select="$hangingSuffix" />
<xsl:with-param name="padding" select="$padding" />
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>

<!-- split a string to a specified size.
Note: there is no newline at the end of the last line to allow additions to the end of the line

string: string to split
length: max length string can be per line
prefix: optional string to prepend to the first line
hangingPrefix: optional string to prepend subsequent lines with
suffix: optional string to append to the end of the first line
hangingSuffix: optional string to append to subsequent lines
padding: optional string to pad the last line of a splt string with to allow extra text to be added after it
-->
<xsl:template name="splitString">
<xsl:param name="string" />
<xsl:param name="length" select="$maxLineLength" />
<xsl:param name="prefix" />
<xsl:param name="hangingPrefix" select="$prefix" />
<xsl:param name="suffix" />
<xsl:param name="hangingSuffix" select="$suffix" />
<xsl:param name="padding" />

<xsl:choose>
<xsl:when test="(string-length($prefix) +
string-length($string) +
string-length($suffix)) > $length">
<xsl:variable name="currentpos" select="$length - string-length($suffix)" />
<xsl:choose>
<xsl:when test="contains(substring($string, 1, $currentpos), ' ') or
contains(substring($string, 1, $currentpos), '/')">
<xsl:call-template name="firstString">
<xsl:with-param name="string" select="concat($prefix, $string)" />
<xsl:with-param name="length" select="$length" />
<xsl:with-param name="currentpos" select="$currentpos" />
<xsl:with-param name="prefix" select="$prefix" />
<xsl:with-param name="hangingPrefix" select="$hangingPrefix" />
<xsl:with-param name="suffix" select="$suffix" />
<xsl:with-param name="hangingSuffix" select="$hangingSuffix" />
<xsl:with-param name="padding" select="$padding" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<!-- no line split character within maximum line length -->
<xsl:value-of select="$prefix" />
<xsl:value-of select="substring($string, 1, $currentpos - string-length($prefix))" />
<xsl:value-of select="$suffix" />
<xsl:value-of select="$newline" />

<xsl:call-template name="splitString">
<xsl:with-param name="string" select="substring($string, $currentpos + 1 - string-length($prefix))" />
<xsl:with-param name="length" select="$length" />
<xsl:with-param name="prefix" select="$hangingPrefix" />
<xsl:with-param name="hangingPrefix" select="$hangingPrefix" />
<xsl:with-param name="suffix" select="$hangingSuffix" />
<xsl:with-param name="hangingSuffix" select="$hangingSuffix" />
<xsl:with-param name="padding" select="$padding" />
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:when>

<!-- less than $length chars left, just write them -->
<xsl:otherwise>
<xsl:value-of select="$prefix" />
<xsl:value-of select="$string" />
<xsl:value-of select="$suffix" />
<xsl:value-of select="substring($padding, 1, ($length - string-length($prefix) - string-length($string) - string-length($suffix))) " />
</xsl:otherwise>
</xsl:choose>
</xsl:template>

Sorting groups in XSL

For some of my recent work I had to generate reports from XML using XSL. One of the techniques I had to use frequently was grouping data using the Muenchian Method.

The referenced web page shows an example of getting a group of contacts and sorting it by surname. That's all well and good if your only computing the group once and processing it straight-forward as shown. But what if the XML data is large so calculating the group is costly (in terms of memory and CPU) and you need to use it multiple times and the order is important because you're using it within the context of processing some larger processing loop (ex. 10 at a time associated with another value)?

For an example, let's just say you need to generate a report that has a cover sheet listing all the unique surnames in your file, then separate statistics for each surname such as how many people share that surname. This is a very contrived example, but at least it will demonstrate what I mean.

Using the example in the referenced web page, we could build a list of unique sorted surnames to use in various places in our XSL via:


<xsl:key name="contacts-by-surname" match="contact" use="surname"/>
<xsl:variable name="surnames" select="contact[count(. | key('contacts-by-surname', surname)[1]) = 1]/surname"/>

<xsl:template match="/">
<xsl:call-template name="sortList">
<xsl:with-param name="list" select="$surnames"/>
</xsl:call-template>

<!-- list is now sorted to use whenever necessary -->
<xsl:for-each select="$surnames">
[process sorted surname]
</xsl:for-each>
</xsl:template>


<!-- sort list by the value of its current node -->
<xsl:template name="sortList">
<xsl:param name="list"/>
<xsl:for-each select="$list">
<xsl:sort select="." />
</xsl:for-each>
</xsl:template>

The call to sortList will sort it for all subsequent uses.

So, to see it in action, here is our sample data:

<records>
<contact id="0001">
<title>Mr</title>
<forename>John</forename>
<surname>Smith</surname>
</contact>
<contact id="0002">
<title>Dr</title>
<forename>Amy</forename>
<surname>Jones</surname>
</contact>
<contact id="0003">
<title>Mr</title>
<forename>John</forename>
<surname>Doe</surname>
</contact>
<contact id="0004">
<title>Mr</title>
<forename>Jack</forename>
<surname>Smith</surname>
</contact>
<contact id="0005">
<title>Mr</title>
<forename>Mike</forename>
<surname>Jordan</surname>
</contact>
<contact id="0006">
<title>Mr</title>
<forename>Jim</forename>
<surname>Johnson</surname>
</contact>
<contact id="0007">
<title>Mr</title>
<forename>Randy</forename>
<surname>Jones</surname>
</contact>
<contact id="0008">
<title>Mr</title>
<forename>Bobby</forename>
<surname>Jones</surname>
</contact>
<contact id="0009">
<title>Mr</title>
<forename>Glenn</forename>
<surname>Smith</surname>
</contact>
<contact id="0010">
<title>Mr</title>
<forename>Bill</forename>
<surname>McDonnald</surname>
</contact>

</records>


Here is the complete style sheet:

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" indent="no" omit-xml-declaration="yes" />

<xsl:variable name="newline">
<!-- Hex 10 is a line feed, and hex 13 is a carriage return -->
<xsl:value-of select="'&#13;'" />
</xsl:variable>

<!-- Remove all whitespace from the source XML so that it is not echoed into the new report -->
<xsl:strip-space elements="//*" />

<xsl:key name="contacts-by-surname" match="contact" use="surname" />
<xsl:variable name="surnames" select="//contact[count(. | key('contacts-by-surname', surname)[1]) = 1]/surname" />

<xsl:template match="/">
<xsl:text>Full list of surnames:</xsl:text>
<xsl:value-of select="$newline" />
<xsl:for-each select="//surname">
<xsl:value-of select="." />
<xsl:value-of select="' '" />
</xsl:for-each>
<xsl:value-of select="$newline" />
<xsl:value-of select="$newline" />

<xsl:text>List of unique surnames:</xsl:text>
<xsl:value-of select="$newline" />
<xsl:for-each select="$surnames">
<xsl:value-of select="." />
<xsl:value-of select="' '" />
</xsl:for-each>
<xsl:value-of select="$newline" />
<xsl:value-of select="$newline" />

<xsl:call-template name="sortList">
<xsl:with-param name="list" select="$surnames" />
</xsl:call-template>

<!-- list is now sorted to use whenever necessary -->
<xsl:text>List of unique surnames (sorted):</xsl:text>
<xsl:value-of select="$newline" />
<xsl:for-each select="$surnames">
<xsl:value-of select="." />
<xsl:value-of select="' '" />
</xsl:for-each>
<xsl:value-of select="$newline" />
<xsl:value-of select="$newline" />

<xsl:text>People per surname:</xsl:text>
<xsl:value-of select="$newline" />
<xsl:for-each select="$surnames">
<xsl:variable name="surname" select="."/>
<xsl:value-of select="$surname" />
<xsl:value-of select="' ('" />
<xsl:value-of select="count(//contact[surname = $surname])" />
<xsl:value-of select="'):'" />
<xsl:value-of select="$newline" />
<xsl:for-each select="//contact[surname = $surname]">
<xsl:sort select="forname"/>
<xsl:value-of select="' '" />
<xsl:value-of select="forename" />
<xsl:value-of select="$newline" />
</xsl:for-each>
<xsl:value-of select="$newline" />
</xsl:for-each>


</xsl:template>

<!-- sort list by the value of its current node -->
<xsl:template name="sortList">
<xsl:param name="list" />
<xsl:for-each select="$list">
<xsl:sort select="." />
</xsl:for-each>
</xsl:template>

</xsl:stylesheet>


And here is the report:

Full list of surnames:
Smith Jones Doe Smith Jordan Johnson Jones Jones Smith McDonnald

List of unique surnames:
Smith Jones Doe Jordan Johnson McDonnald

List of unique surnames (sorted):
Doe Johnson Jones Jordan McDonnald Smith

People per surname:
Doe (1):
John

Johnson (1):
Jim

Jones (3):
Amy
Randy
Bobby

Jordan (1):
Mike

McDonnald (1):
Bill

Smith (3):
John
Jack
Glenn

Monday, January 30, 2006

Enabling JSP in Tomcat

Sometimes you just want to enable a directory on your tomcat server to be able to run JSP pages without actually installing a WAR or EAR file. Here are the essential steps to enable a directory to execute JSP files.

For this example, I'm setting it up on a SuSE Linux system so your directory structure may vary.


  1. Create a JSP directory in your tomcat webapps directory. In this case, the default webapps directory for Tomcat is /srv/www/tomcat5/base/webapps/ so I make a jsp directory there.

  2. Under the jsp directory I create the basic webapp directory structure and create a directory named /srv/www/tomcat5/base/webapps/jsp/WEB-INF

  3. In the WEB-INF directory, I create a basic deployment descriptor named web.xml which looks like:

    <?xml version="1.0" encoding="ISO-8859-1"?>
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/j2ee/dtds/web-app_2_3.dtd">
    <web-app>
    <display-name>My JSPs<display-name>
    </web-app>


  4. In the tomcat configuration directory /etc/tomcat5/base/Catalina/localhost add the context path for the JSPs in a file named jsp.xml that looks like:

    <Context path="/jsp" docBase="jsp"
    debug="0" reloadable="true" />


  5. Restart Tomcat (rctomcat5 restart)

  6. Create some sample JSP file in the jsp directory (/srv/www/tomcat5/base/webapps/jsp such as:

    <html>
    <head>
    <title>Hello World</title>
    </head>
    <body>
    <h1><%= "Hello World" %><h1>
    </body>
    </html>


  7. Make sure it's working via http://localhost:8080/jsp/index.jsp

  8. I run Tomcat under Apache via mod_jk so that I can utilize the SSL layer of Apache. So, to make the JSP transparently accessible from Apache, I update the /etc/apache2/conf.d/jk.conf and add the following lines:

    # Add the jsp directory
    Alias /jsp "/srv/www/tomcat5/base/webapps/jsp"
    <Directory "/srv/www/tomcat5/base/webapps/jsp">
    Options Indexes FollowSymLinks
    allow from all
    </Directory>
    JkMount /jsp ajp13
    JkMount /jsp/* ajp13
    <Location "/jsp/WEB-INF/">
    AllowOverride None
    deny from all
    </Location>


  9. After restarting Apache (rcapache2 restart) I can then reference the JSPs directly from Apache, such as http://localhost/jsp/index.jsp

Multimedia setup for SuSE 10

This was a very useful resource for setting up multimedia capabilities for SuSE 10:
http://www.thejemreport.com/mambo/content/view/178/42/

Additionally, adding some of the extra Yast software sources from http://www.suseforums.net/index.php?showtopic=18575 or http://www.opensuse.org/Additional_YaST_Package_Repositories#External_YaST_Repositories is useful.

Current Earth image wallpaper

When I'm using Windows, I use WeatherPulse to display the current satelite picture on my desktop (the "GOES E on Color" option). I'm very happy with it and wanted something similar on my SuSE Linux KDE desktop. To do that, I used XPlanet. Below are the setup steps I followed:


  1. Install xplanet RPM if not already installed.

  2. I made a .xplanet directory in my home directory in which I copied the config file from /usr/share/xplanet/config

  3. In the [earth] section of the config file, I added the lines:

    map=/home/username/.xplanet/earthmap4k.png
    bump_map=/home/username/.xplanet/earthbump4k.png
    bump_scale=5
    cloud_map=/home/pothoven/.xplanet/clouds.jpg

  4. To get the dynamic cloud map, I downloaded the download_clouds.pl Perl script from http://xplanet.sourceforge.net/clouds.php to my .xplanet directory. Note: you may wish to change this line in the script to suite your preferences:

    my $MaxDownloadFrequencyHours = 1;


  5. I then added this line to my crontab

    0 * * * * cd /home/username/.xplanet; perl download_clouds.pl > /dev/null

    to update the cloud information once an hour

  6. To get the topography and enahanced coloring maps in my config, they can be purchased from J.H.T.'s Planetary Pixel Emporium (though a Google search for earthmap4k may reveal other sources).

  7. If you want to track a satelite, such as the Hubble telescope or International Space Station (ISS), download the lastest tracking data with either:

    wget -O science.tle http://www.celestrak.com/NORAD/elements/science.txt

    for scientific satelites (Hubble) or:

    wget -O stations.tle http://celestrak.com/NORAD/elements/stations.txt

    for space stations (ISS). In the resulting science.tle file, you will see a section with the header "HST" (Hubble space telescope), place those lines in a file named hubble.tle which will then look like:

    HST
    1 20580U 90037B 06008.34262361 .00000587 00000-0 33114-4 0 5098
    2 20580 28.4688 11.8178 0003636 285.4875 74.5312 14.99943058661070

    If you also want to track the IST, also add the "ISS (ZARYA)" lines from the stations.tle file. These lines will look something like:

    ISS (ZARYA)
    1 25544U 98067A 06009.46434028 .00013978 00000-0 10043-3 0 1177
    2 25544 51.6445 283.5661 0010748 133.7206 217.3373 15.73896465408147

    Then, create a file named hubble in your .xplanet directory that contains:

    20580 "" image=hubble.png transparent={0,0,0} trail={orbit,-10,0,5} color=green

    for tracking the Hubble and/or:

    25544 "" image=iss.png transparent={0,0,0} trail={orbit,-10,0,5} color=green

    for tracking the space station. Finally, add this line to the [earth] clause of the config:

    satellite_file=/home/username/.xplanet/hubble

  8. Finally, set it up in KDE. right click on the background.

  9. Select Configure Desktop.

  10. Click on Background in the left hand panel.

  11. In the Background group box, I have No picture selected.

  12. Click on Advanced Options.

  13. I have Use the following program for drawing the background checked.

  14. Select XPlanet in the list.

  15. Click on Modify.

  16. My Command entry is:

    xplanet --config /home/username/.xplanet/config
    --geometry %xx%y --num_times 1 --output %f.jpg
    --latitude 27.96 --longitude -82.48 --radius 60 &&
    mv %f.jpg %f

    (the latitude and longitude is for where I live -- adjust to suit)

  17. My Preview cmd entry is the same as above.

  18. I have my Refresh time set to 5 minutes if I'm tracking satellites, and 30 minutes if not.

  19. Enjoy a dynamic updating picture of the earth such as:





Update - December 19, 2007


First off, I want to point out that XPlanet is an option for Windows as well. See XPlanet on Microsoft Windows. You can use the same Perl script to download the latest cloud cover image using ActivePerl. When you run XPlanet use the -fork option to have it periodically update the background.

Secondly, the instructions above were for KDE, but if you use Gnome, here are the instructions to get XPlanet to work with Gnome2.