XSLT Design Guidelines

posted Sep 13, 2011, 1:29 PM by Eric Patrick   [ updated Jun 26, 2017, 7:09 AM ]
QBO 3 XSLTs should adhere to the following guidelines.
  • Output HTLML: use <xsl:output method="html"/>
  • Root Nodes: use <xsl:template match="/"/> for compliance with XmlReaders
  • Large Transforms: XmlReaders should be used for large data sets
  • Table Headers and Footers: use thead, tbody, and tfoot in tables so css can be applied to rows appropriately
  • Pagination: use the qbo.Paginate behavior
  • Events: leverage event delegation as much as possible
  • Hyperlinks: allow for QBO 3 to be installed in virtual folders
Output HTML

Use the <xsl:output method="html"/> tag to avoid HTML tags being incorrectly inline-closed by the XSLT processor.  For example:

<textarea></textarea>

will result in an "illegal":

<textarea/>

if the output method is XML. 

Root Nodes

Ensure compliance with DataSets, DataReaders and XmlReaders by using <xsl:template match="/"/> instead of <xsl:template match="*"/>. XmlReaders returned directly from SQL are efficient, but typically don't include a root node.  The HttpHandler transformation allows document fragments, but match="*" will not work as expected with a document fragment.

Large Transforms

Large transforms (e.g. long reports or very complicated XPath expressions) should leverage an XmlReader directly from SQL. To define a method for a class that returns an XmlReader, do the following:
  • Use FOR XML PATH('{NodeName'}) in your statement
  • Configure your DbStatement to return an XmlReader
Example statement:

SELECT * FROM Contact WHERE Contact.ObjectID = @ObjectID and Contact.Object=@Object FOR XML PATH('ContactItem')

Example DbStatement:

<Statement Name="List" Query="SELECT * FROM Contact WHERE Contact.ObjectID = @ObjectID and Contact.Object=@Object FOR XML PATH('ContactItem')" ReturnType="XmlReader"/>

Table Headers and Footers

Using thead, tbody, and tfoot enables use of css to create neat effects without extra markup. For example, to get a "zebra" effect on a table, we can do this:

.grid tr:nth-child(even)
{
background-color: #f5f5ff;
}

However, that class will apply to all rows, including any header and footer rows.  To target just the "data" rows, we instead use:

.grid tbody>tr:nth-child(even) 
{
background-color: #f5f5ff;
}

Pagination

QBO 3 leverages Mootool's Behaviors, and includes a qbo.Pagination behavior. This means your XSLT needs only to render:

<span data-behavior="Paginate" 
data-paginate-options="{{'display': '{$DisplaySize}', 'start': '{$RecordStart}', 'count': '{$RecordCount}' }}"/>

and the qbo.Pagination behavior will parse this data and render the appropriate text and links to other pages. Moreover, a site can override the standard qbo.Pagination behavior and the change will be applied instantly to all XSLTs that use pagination.

Events

Read up on event delegation, and use it.  For example, qbo.Paginate creates a clickable link for every page. Each link call the same JavaScript function, setting a different start parameter.  Rather than creating an event handler on every link, the link is simply:

<a class="page" start="25">2</a>

and the JavaScript is this:

document.id(myDivTagWithPagination).addEvent('click:relay(a.page)', function(e, el) { alert(el.get('start')); });

Hyperlinks

QBO 3 will need to live side-by-side with QBO 2.0, and with some clients, may need to be installed into a virtual folder. In order to ensure hyperlinks are always correct, we use a <base href="MyFolder"/> tag to establish where the root of the QBO 3 install it.

Thus, if a Foreclosure page (found in Mortgage/Default) references a Contact page (found in Contact), the property hyperlink is:

<a href="Contact/Contact.ashx/Select?ContactID=1">View Contact Details</a>

Note that both of the following would be incorrect:

<a href="/Contact/Contact.ashx/Select?ContactID=1">View Contact Details</a> <!- breaks if QBO 3 is not in the root folder -->
<a href="../../Contact/Contact.ashx/Select?ContactID=1">View Contact Details</a> <!- breaks because of the base href tag -->

Parameters

By default, XSLTs have access to the following information as parameters:
  • every form element passed in an HTTP GET or POST (see XsltArgumentList.AddParams())
  • BaseHref: the root of the QBO3 website (see XsltArgumentList.Initialize())
  • IIS server variable: (see XsltArgumentList.AddServerVariables())
    • which variables are passed can be controlled via the qbo.Application.Properties.XsltArgumentListServerVariables setting
    • default variables include: LOGON_USER, HTTPS, PATH_INFO, SERVER_NAME, SERVER_PORT, HTTP_HOST, HTTP_USER_AGENT, QUERY_STRING, HTTP_X_REQUESTED_WITH
  • Extension Objects (see below)
Developers can leverage these parameters to customize UI behavior. For example, assume we have an Attachment panel that supports uploading documents, by may want to data-drive what type of document are to be accepted.

The Attachment.Search.xslt supports and uses an Accept parameter:

<xsl:param name="Accept">*/*</xsl:param>
...
<a href="qbo3.getObject(this).push('Edit', {{..., 'Accept': {$Accept} }}>Upload Document</a>

Then, different pages can leverage this feature as follows:

<!-- Default Behavior -->
<div id="search" data-behavior="ObjectBind" data-objectbind-options="{{ 'class':'qbo3.AttachmentObject', 'method': 'Search' }}">
  ...
</div>

<!-- Just PDFs -->
<div id="search" data-behavior="ObjectBind" data-objectbind-options="{{ 'class':'qbo3.AttachmentObject', 'method': 'Search', 'Accept': '*.pdf'}}">
  ...
</div>

<!-- Just XML -->
<div id="search" data-behavior="ObjectBind" data-objectbind-options="{{ 'class':'qbo3.AttachmentObject', 'method': 'Search', 'Accept': '*.xml'}}">
  ...
</div>



Items to note:
  • XSLT parameters must be defined near the top of an XSLT document, usually just before the first <xsl:template> node
  • When using an XSLT parameters, remember to include the dollar sign: e.g. <xsl:value-of select="$Accept"/>
  • In XSLT elements, remember to "double up" curly braces for JSON
    • {$Accept} will be replaced the with $Accept variable, but {{$Accept}} will result in {$Accept} in the final HTML

Extension Objects

QBO 3 supports the use of Xslt Extension objects defined in a configuration file. These extension objects allow XSLT to call custom C# (or other CLR) methods. To use Xslt extension objects:
  • Ensure web.config includes a section definition for 'qbo/XsltConfiguration' (this is part of the standard setup)
<!-- Common configuration files -->
...
<section name="XsltConfiguration" type="qbo.Application.Configuration.XsltExtensionConfiguration, qbo.Application"/>
...
<XsltConfiguration configSource="config\XsltExtension.config"/>
  • Ensure the XsltExtension.config contains references to the extension object classes you wish to reference:
<XsltConfiguration>
<XsltExtensions>
<Extension Name="Formatting" Type="qbo.Application.Utilities.XsltFormatting, qbo.Application" Namespace="urn:qbo3-formatting"/>
</XsltExtensions>
</XsltConfiguration>
  • Ensure your XSLT defines the Namespace referred to in the configuration file:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:format="urn:qbo3-formatting"
>
...
<xsl:value-of select="format:today('yyyy-MM-dd')"/>
...

To define a new Xslt Extension object:
  • Create a class that implements the qbo.Application.Interfaces.XsltExtension interface
  • Make the class library (DLL) available to the application (install to /bin, the GAC, or reference it in a configuration file's assemblies section)
  • Add a reference to the class in XsltConfiguration.config
The extension objects are wired as follows:
  • Web calls are routed through IHttpHandler.cs
  • IHttpHander calls new qbo.Application.Utilities.XsltArgumentList(User, Parameters)
  • XsltArgumentList reads the Xslt configuration section, and adds any defined extension objects
Configuration errors should be trapped and logged, including:
  • If the root configuration (web.config) is missing the qbo/XsltConfiguration section,
  • If the qbo/XsltConfiguration section is invalid, or
  • If an Xslt Extension object fails to implement IXsltExtension correctly
Differences between QBO 3 and QBO 2.0 Xslt Extension objects: in QBO 3
  • all Xslt Extension objects will be instantiated with a user context, so all objects have access to an IPerson context
  • the configuration files need only specify a type, not a full path to an assembly
  • argument should be passed using a qbo.Application.Utilities.XsltArgumentList, which wires extension objects automatically

Mistakes to Avoid


Comments