Tuesday, December 06, 2005

Lotus Notes - Computed Subforms and Web Query Open (WQO)

Two powerful aspects of the Notes environment are:
  1. Computed Subforms
    An entire region of a form can be turned on or off according to a value that is computed at runtime. The maintainability of an approach based on computed subforms stands head and shoulders above the use of paragraph by paragraph "hide-when" formulae.
  2. Web Query Open (WQO)
    A Domino-hosted form, viewed on the web, can be set to run a server-side agent when the form is loaded. In this way any information passed in the URL (using multiple occurrences of "&" followed by text) can become parameters to a server executed routine.

Unfortunately these two aspects do not work together - you cannot use a WQO agent to select a computed subform because the Domino server decides which subforms to use before a WQO agent is executed.

The very simple answer to this is to use formulae in the form being loaded to directly decode the contents of the Query_String field in order to decide on the subform. A "computed for display" Query_String field is required for any passing of URL parameters to the server.

It took me a while to realise this (blindingly obvious?) fact but once I had taken it on board I began to think of the maintenance issues and the effect of this approach on other Domino solutions I was using. The following approach needs to be read with that in mind.

URL Syntax Policy

There is no set policy for what comes after the action part (eg ?OpenForm) of the URL. This part of the URL will be consumed by server-side code that you provide. In my Domino applications I choose to make this part of the URL consist of a series of name/value pairs. For example:

http://....?OpenForm¶m1&hello¶m2&goodbye

is used to assign the value "hello" to a variable named "param1" and "goodbye" to a variable named "param2".

Any solution to the subform problem needs, for me, to be compatible with this policy for Query_String. In practice this type of URL can lead to the setting of values on a form through my SetFields agent which the WQO agent for many forms.

The Subforms field

My web computed subform solution begins with a Shared Field named "Subforms". This field can be included in any form and has the following settings:
  • Computed for display (ie not written to the document on the server)
  • Formula:
    @Middle(@Right (Query_String;"Subforms");1;"&")

    This simple formula locates, in Query_String everything to the right of "Subforms" and then skips (1;) the "&" for the value and reads to the next "&"
The effect of this is to set the value of the field named Subforms to the value passed in the URL.

Computing the subform

The formula for the computed subform needs to read from the Subforms field, take account of the possibility of multiple subforms (separated with a ",") and return the appropriate value. I use:
  • subformnum:=1;
    @Subset(@Subset(@Explode(Subforms;",");subformnum);-1)

    The subformnum variable is not strictly necessary but aids maintenance.
    The Subforms field (a string) is made into a list at the "," boundaries via @Explode.
    The inner call to @Subset extracts the first subformnum entries from that list.
    The outer call to @Subset extracts the last entry in that list (position parameter = -1)
That's all!

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.