Sunday, September 11, 2005

XSL sort - parameter driven

So this has been my other quest this weekend - how to cause different XSL sorts to occur based on passed parameters - suprisingly this is not as easy as it sounds.

Tag syntax

The first difficulty arises from the syntax of the XSL sort tag:

<xsl:sort
select="some text that picks a node"
data-type="some text that defines a data type"
order="some text that defines a sort order"/>



The sort "command" accepts the three parameters shown - only "select" is compulsory - the other two have default values ("text" and "ascending")

The "data-type" and "order" are easy to paramterise. Assuming that you have XSL variables "type" and "order" that have been set - to the values "number" and "descending" - the following fragment shows how a decreasing numeric sort by "payrate" could be requested. We assume that the nodes being matched have a "payrate" child:

<xsl:sort
select="payrate"
data-type="{$type}"
order="{$order}"/>


Note that the {} brackets force evaluation of the variable that is indicated with the "$".

The problem is though that you cannot do the same with the "select" parameter. The select parameter must be a node-set - in other words (in the context of a sort) an expression that leads the XSLT processor to identify a single node. Examples of such expressions are:

text() - the content of the current node
age - the content of the "age" child of the current node
payrate/tax - the content of the "payrate/tax" grandchild of the current node

Most XSLT processors (the program - usually on the server) that "applies" your XSLT transformation do not know how to convert text in a parameter into a node-set. In other words the following will fail even if the variable "target" has the value "payrate":

<xsl:sort
select="{$target}"
data-type="{$type}"
order="{$order}"/>

The answer is to write a slightly more complex "select" expression that includes a component that does accept a text value:

<xsl:sort
select="(*|*/*)[name()=$target]"
data-type="{$type}"
order="{$order}"/>


All the "*" stuff selects the current node or any descendents and the square bracket says that the name of the node must match the parameter value - if the node has a child named "payrate" then setting "target" to "payrate" will sort by the value of this element.

All this is fine so long as the sort "target" can be uniquely defined by a node name. What if "payrate" contains multiple "tax" elements (say federal, state and local) and we want to sort by one of these:

<xsl:sort
select="payrate/tax[position()=$target]"
data-type="{$type}"
order="{$order}"/>


This example expects "target" to be set to 1, 2, 3... to indicate which payrate/tax child is to be applied to this sort.

Conditional sorting

This leads to the second problem - how to apply one of these sorts in preference to any other.

The problem arises from the fact that "xsl:sort" can only be the child of "xsl:for-each" or "xsl:apply-templates" - we simply are not allowed to use "xsl:if" or "xsl:choose" to select a sort to apply.

The answer is that multiple sorts can be defined inline:

<xsl:sort select="(*|*/*)[name()=$target]" data-type="{$type}" order="{$order}"/>
<xsl:sort select="payrate/tax[position()=$target]" data-type="{$type}" order="{$order}"/>

Ordinarily this would be used to apply nested sorting (ie sort by one field and then by another) but in this case, so long as only one of the "select" expressions evaluates to a non-empty node-set, a single sort will be applied.

Obviously this must be used with care - consider whether more than one of the "select" expressions will succeed - if so try something else.

Scrolling HTML tables

It's very nice to have table headers stay put whilst you scroll the data but it's been hard to separate the wood from the trees in reading about this on the web. So this is an attempt to be really basic about this topic.

As with any HTML question many of the complexities arise when the question of support in all the different browsers is raised so the solution here is very simple in the hope (!) that many browsers can use it.

The key to it all is the HTML div tag - meaning "division" - through which a style can be defined for a region of a document:

<div style="width:10%">
<table>
<tr><td....
</table>
</div>


This will squeeze the table, whatever it is, into 10% of the width of the screen.

There are many possible style settings but the one that matters for scrolling tables is overflow:auto. This will make a table that scrolls:


<div style="overflow:auto">
<table>
<tr><td....
</table>
</div>

This will make the header scroll too - which is not what we want - but if the headers are in a separate table (with the same column widths) and before the div, things begin to look promising:


<table>
<tr><th....
</table>
<div style="overflow:auto">
<table>
<tr><td....
</table>
</div>

Really the only thing left to do is to allow for the width of the scrollbar by making the header table slightly narrower:

<table width="98%">
<tr><th....
</table>
<div style="overflow:auto">
<table>
<tr><td....
</table>
</div>

Thursday, September 08, 2005

Starting Out

Well I have turned the corner into the new universe - and why?
Mainly because I told Alison (my sister-in-law) that she would like this and so I had to see it for myself.