Menu

Prototyping One-to-many Links with XSLT

March 5, 2003

Bob DuCharme

In the ongoing debate about the future of linking, a key topic is the representation of one-to-many links. There are several ways to implement them, mostly by using JavaScript code to create pop-up menus, but the only standard way to represent them is W3C XLink extended links, and these have not caught on.

While reading Micah Dubinko's SkunkLink proposal, I noticed his nested a links and thought they were a nice, simple way to represent one-to-many links. Before telling people "this is how XHTML should represent one-to-many links," I thought it would be better to get a working prototype running. How could I turn nested a elements into working pop-up menus? With an XSLT stylesheet, of course. Because Internet Explorer and Mozilla can dynamically apply a stylesheet to an XML document when you ask them to display that document, it makes a particularly nice demo: you bring up the document that has nested a elements, and pop-up menus appear when you click on them.

So I did it; though it doesn't work with all releases of all browsers on all platforms, about which more later. I originally thought that I'd explain how in this column, but I don't want anyone to think that I'm pushing nested a links as a general-purpose link architecture, or that the particular JavaScript library that I used is the best way to implement one-to-many links, or even that pop-up menus are the best UI for representing one-to-many links. My goal is to show that XSLT is a great way to demonstrate how to turn any link architecture into something that can demonstrate the value of that architecture. In other words, I'm focusing on box B below, not A or C. I encourage you to use your imagination for both A and C.

diagram: converting link architecture to prototype with XSLT
Figure 1: Converting link architecture to prototype with XSLT

Pop-up Menus

Pop-up menus are the most obvious way to render one-to-many links in a browser. If Internet Explorer and Mozilla had used similar pop-up menus to implement the applicable XLink extended links, XLink would be much more popular today, and one-to-many links would be a common part of every web developer's vocabulary.

Instead, web pages use a variety of tricks to implement these menus. Most of these tricks have good and bad points, tradeoffs to consider when choosing involve cost, verbosity, and cross-platform and cross-browser compatibility. Many packages and tricks such as HierMenus are available. I've had good luck prototyping with the Netscape DevEdge JavaScript menu component. By "good luck prototyping" I mean that I've heard that my nested a link demo worked under at least one recent Mozilla release (on Windows and Linux and a Macintosh), as well as with Internet Explorer under Windows. If it doesn't work for you, try different releases of your browser or other menuing implementations. And remember that it's for prototyping, not for robust implementations. If it works on a currently available release of a free browser on a Mac and on Windows and under Linux, you can demonstrate how your linking could work without much trouble.

If you want it to work on a wide variety of browser-release/platform combinations, you will end up tearing a lot of hair out.

A Sample Linking Architecture

Instead of discussing the implementation of one-to-many links using nested a elements, I used the same implementation techniques with a different linking architecture to show the versatility of these techniques. I've posted a sample XML document that uses this linking architecture. It points to an XSLT stylesheet that converts the linking markup to the necessary HTML and JavaScript to demonstrate a potential UI for the architecture.

The basic unit of this sample linking architecture is a multilink element, which I embed as needed in an XHTML file. The following shows the markup for a sample paragraph that includes one of these links:

<p>I have written multi-part columns on topics such as

  <multilink id="l001" type="columns">
    <title>XML.com columns on controlling whitespace</title>
    <indicator>controlling whitespace</indicator>
    <linkend title="Controlling Whitespace, Part 1"
             URI="http://www.xml.com/pub/a/2001/11/07/whitespace.html"/>
    <linkend title="Controlling Whitespace, Part 2"
             URI="http://www.xml.com/pub/a/2001/12/05/whitespace.html"/>
    <linkend title="Controlling Whitespace, Part 3"
             URI="http://www.xml.com/pub/a/2002/01/02/whitespace.html"/>
  </multilink>.</p>

The multilink element consists of a title describing the link as a whole, an indicator element to hold text (or perhaps a reference to a picture) used to identify the presence of a link in the rendered document, and one or more linkend elements. Each linkend element, which is empty, has a title attribute to identify the linked resource and a URI attribute to show the resource's location. The multilink element has two attributes of its own: an id attribute for a unique ID and a type attribute to assign the link a type from a choice created for the purposes of the demo article. (It's really a link destination type, and not a link type; the values describe the linked resource, not the link connecting the resources. Either way, it can provide an aid to navigation.)

Don't take this "linking architecture" too seriously. I don't really think that a link end title should be an attribute of linkend. I also think that connections to individual link ends should be typed as well as the links themselves (see XLink's arcrole attribute) because different link ends can play different roles within a link. The multilink architecture shown here is a quick stab at representing multi-ended links with some useful metadata that isn't tied to a particular rendering medium. Now, let's see about rendering it.

Getting it to Work

Before your XSLT stylesheet can turn your link architecture into HTML, including the widgets that implement your one-to-many links, you must be sure of the HTML and UI widget code that your stylesheet will create. In my case, that meant being sure that I understood the JavaScript code that would be inserted into the HTML. I created some HTML documents by hand that used the Netscape DevEdge menuing code just to make sure that the menus popped up when they were supposed to.

The two steps for each menu are to first define it in a script element within your HTML document's head element and then to invoke the menu from the href attribute of one or more HTML a elements. For the sample multilink element above, the JavaScript code necessary to define a menu follows this model:

window.l001 = new Menu();
l001.addMenuItem("Controlling Whitespace, Part 1",
     "location='http://www.xml.com/pub/a/2001/11/07/whitespace.html'");
l001.addMenuItem("Controlling Whitespace, Part 2",
     "location='http://www.xml.com/pub/a/2001/12/05/whitespace.html'");
l001.addMenuItem("Controlling Whitespace, Part 3",
      "location='http://www.xml.com/pub/a/2002/01/02/whitespace.html'");
l001.disableDrag = true;
l001.fontSize = 12;
l001.menuItemHeight = 30;
l001.menuItemIndent = 5;

Each pop-up menu needs a name, so I used the value of the multilink element's id attribute to create the name: "l001". The addMenuItem method that adds a choice to the menu gets called three times above; several menu properties are set directly to tweak the menu's appearance.

The markup necessary to call the defined menu is as simple as this:

... topics such as <a href="javascript:window.showMenu(window.l001)">controlling 

whitespace</a> and...

It's a regular HTML a element, except that instead of a URI as the value of its href attribute, it triggers the JavaScript menu defined earlier.

Once you have a good idea of the HTML and JavaScript that needs to be generated, the next step is to write the XSLT stylesheet to generate it. To keep the linking implementation simple, my stylesheet assumes that the source document is straight XHTML with the new markup representing the linking architecture incorporated into it. That way, in addition to the specialized code for dealing with the links, all we need is a single template rule to copy everything.

In last month's column I reviewed several tricks and caveats for using XSLT to create HTML with embedded JavaScript. The simplest way is to keep the JavaScript outside of the result HTML and point to it from a src attribute of an HTML script attribute. This will only partially help us create the pop-up menus that represent our one-to-many links; our result web page will point to the menu.js library of JavaScript code on the Netscape site, but our result must still include code that follows the model shown above to define the menus needed for the links in our result document. To some, writing XSLT instructions that generate JavaScript code may seem annoying and complicated; to an XSLT geek, it's fun. Our XSLT code will dynamically generate JavaScript code customized for our source data.

My XSLT stylesheet only has four template rules. The first one, upon finding the source document's head element, copies its contents and then adds the script element containing the JavaScript code to declare the necessary menus. Because the link architecture described here uses an element called multilink to enclose each one-to-many link and an element called linkend to identify the destination of each potential multilink traversal, the XSLT instructions to create this menu declaration code following the form shown above take this basic form:

<xsl:for-each select="//multilink">
  <xsl:variable name="windowName" select="@id"/>

  <!-- JavaScript (JS) declaration to create window -->
  <xsl:text>window.</xsl:text><xsl:value-of select="$windowName"/>
  <xsl:text> = new Menu();

  <xsl:for-each select="linkend">
    <xsl:value-of select="$windowName"/>
    <xsl:text>.addMenuItem("</xsl:text><xsl:value-of select="@title"/>
    <xsl:text>","location='</xsl:text>
    <xsl:value-of select="@URI"/>
    <xsl:text>'");
  </xsl:for-each>

  <xsl:value-of select="$windowName"/><xsl:text>.disableDrag = true;
  <!-- output other lines to tweak menu's appearance -->
</xsl:for-each>

(This excerpt leaves a few lines out to make the important parts of the structure clearer.)

The outer xsl:for-each instruction cycles through all the multilink elements in the document, outputting the JavaScript code to create a menu for each. The inner one cycles through the linkend elements within each multilink element, outputting the JavaScript code to add a menu choice line to the menu defined in the outer xsl:for-each loop.

These two nested xsl:for-each elements are the heart of this mapping of a linking architecture to the UI code that shows how the architecture might be implemented. By finding all the link markup in the document and mapping the information there to the code required for the user interface widgets that you use, similarly nested xsl:for-each elements could implement other one-to-many link architectures to take advantage of the same Netscape DevEdge library or a different one. Whether the information you pull out of your linking elements are in their subelements or attributes (or, for that matter, processing instructions) depends on the linking architecture being demonstrated. (For example, compare this article's multilink/linkend links with the nested a elements that I used the first time I tried this.) What you do with this information depends on the features available in the UI that you use to demonstrate your architecture.

The stylesheet's second template rule copies the HTML source document's body element, adding a little DevEdge JavaScript code to the end to make the implementation more cross-browser compatible. This template rule also adds a paragraph at the beginning of the body of the HTML document to give the reader a key to the simple trick I used to take advantage of link destination typing in my prototyped interface: it tells what type of link destination each color represents. (Actual mapping of link destination types to colors is done in the mlink.css CSS stylesheet.)

The third template rule is brief enough to show in its entirety here:

<xsl:template match="multilink">
  <!-- Generate an html:a element with a call to the menu. Model:
  <a href="javascript:window.showMenu(window.myMenu)">anchor text</a> -->

  <a href="javascript:window.showMenu(window.{@id})"
     class="{@type}" title="Prototyping One-to-many Links with XSLT">
    <!-- indicator element of multilink element as anchor text. -->
    <xsl:value-of select="indicator"/> 
  </a>
</xsl:template>

When the XSLT processor finds a multilink element during its normal traversal of the source tree (as opposed to finding one with the xsl:for-each loop used in the first template rule), it adds an HTML a element to the result tree. When clicked, this a element displays the menu defined earlier for this link. It also adds the menu's type (in my example document, a value of either "columns," "specs," or "postings") as a class attribute so that a CSS stylesheet can assign it a specific color based on that class. The last attribute added is the a element's title, which stores the multilink element's title value so that a mouseover action will display that title in a little pop-up rectangle.

The fourth and final template rule is a standard one in many XSLT stylesheets. It copies everything not caught by other template rules to the result tree verbatim. Because this stylesheet assumes that everything in the document apart from the multilink elements will be XHTML, this template rule ensures that any semantics of the source document unrelated to multilink will have the desired effect in the browser.

Telling someone to point their browser at an XML document that demonstrates your linking architecture with a slick UI will be great, but be sure to perform some intermediate tests before relying on the XSLT processors built into the browsers. Test your sample document and stylesheet with a command-line XSLT processor such as Xalan or Saxon. Use one or both of these to create an HTML disk file from your source XML and your stylesheet, and then see if the HTML file does everything you expect when displayed in a browser.

Once your source document and stylesheet work together to create an HTML page that acts like you want it to, you're ready for the next step. Sending people's browsers to an HTML page full of JavaScript won't show off your linking architecture; you want them to go to an XML page with the linking markup you've developed and then see it in action. Last month's column told how the W3C Recommendation Associating Style Sheets with XML Documents describes a processing instruction to include at the beginning of a document naming a stylesheet to apply to that document.

    

Also in Transforming XML

Automating Stylesheet Creation

Appreciating Libxslt

Push, Pull, Next!

Seeking Equality

The Path of Control

Recent releases of Mozilla and Internet Explorer, when displaying a document with one of these processing instructions, will apply the named stylesheet to the document and render the result. A "View Source" when viewing the document will display the XML source, not the HTML representing the rendered result, so the viewer will see the original linking markup you developed and not the HTML and JavaScript created by your XSLT stylesheet to implement the linking UI. (I called my stylesheet mlinks.xsl.xml, not mlinks.xsl: some web servers serve a file with an extension of ".xsl" using a MIME type of text/html, and Mozilla won't know that it's a stylesheet to apply to the document. With the extension of ".xml," it gets sent to the browser with a MIME type of text/xml and Mozilla applies it as a stylesheet to the document whose processing instruction points at that stylesheet.)

Remember that your prototype is supposed to demonstrate what could happen with your linking architecture, not what should happen. A good architecture lends itself to multiple UIs. Different demos like the one described here for the same linking architecture would be a great way to demonstrate the architecture's value. Take the different pieces of information in the architecture and the different features of the UI being used and mix and match them. Or skip the UI for one demo: write something that harvests the links into a database and then does something interesting with that.

How do you think one-to-many links should be represented? What's your favorite code for implementing them? Try using XSLT to convert your fantasy link markup to your favorite JavaScript (or ASP, or...) UI tricks, and then let me and everyone else on the xml-hypertext mailing list know how it goes. Maybe your prototype will convince the folks behind Mozilla, Internet Explorer, and Opera that they should implement your linking architecture natively.