XSL is an XML document type that defines transformations that define how to translate one variation of XML to another, in this case taking the XForms XML data and turning it into HTML. The great thing about having the XSL being used from a separate file is allows you to define multiple skins, and apply them to the same form. This means that a form can change based on a user's preference, in the same way as modifying a style sheet. You can also change the actions, validations, and transformations, and customize them to the user. This enables you implement forms that are based on the same data, rendered with the same code, but provide entirely different functionality depending on the type of user who is using the page. This book doesn't have the space to define XSL in depth, but I'll describe the basics of how it works and how you can use it to create and extend the XSL that's been provided by ColdFusion MX 7 to define your own libraries. NOTE The complete definition of XSLT can be found at http://www.w3.org/TR/xslt. XSL works by doing XPath searches, which return groups of matches called nodesets, and applying rules to them. The XSL language has three parts, XPath, the language use for referencing the various parts of an XML document, XSLT, or XSL Transformations, which is a language for describing the transformation of one XML document into another (in this case, XHTML), and XSL, the Extensible Stylesheet Language, which is XSLT plus a set of objects and properties that allow you to format the document. The XSL documents we'll be dealing with are also called XSL stylesheets; their XML document root is <xsl:stylesheet>, or sometime <xsl:transform>, which is turns out is exactly the same thing. Here's a very short example of an XSL stylesheet that converts an XML document into HTML. First, here's the overly simply document we want to translate: <?xml version="1.0"?> <italic>This is in italics</italic> and this isn't. All we want to do is convert this to an HTML document, with the <italic> converted to an HTML <EM> tag. This style sheet will do that: <?xml version='1.0'?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <xsl:template match="italic"> <EM><xsl:apply-templates/></EM> </xsl:template> </xsl:stylesheet> How does that work? Each of the <xsl:template> tags in the document match text in the document. match="italic" in the above matches the <italic> in the document, and anything that doesn't match is passed through by the <xsl:apply-templates>, which tells the system to apply any template handlers that follow this one (if you leave that out, it'll stop processing here, and it applies the templates in the order they are in the document.) Since there's no deeper templatethere aren't any more in the file at allthe default handler, which doesn't do anything at all, handles them by just passing through the text. Therefore, the output of this stylesheet applied to this XML file would be: <EM>This is in italics</EM> and this isn't. The most important elements of XSL (as far as the ColdFusion MX 7 XSL stylesheets are concerned) and what they do are in Table 20.5. All xsl elements start with xsl. All of the expressions in Table 20.6 are either XPath expressions that match some part of the document, or variables referenced by ${varname}.
There is an XSL file for every skin provided with ColdFusion, as well as several utility files that are included by the skins. These files are shown in Table 20.7.
Let's look at one of the XSL files provided as part of the ColdFusion installation to see how they work. These files are located on your Web server at {webroot}/CFIDE/scripts/XSL. The next several code listings are the basic.xsl file that comes with ColdFusion MX 7. Listing 20.4. basic.xslHeader[View full width] <?xml version="1.0" encoding="UTF-8"?> <!-- Copyright (c) 1995-2005 Macromedia, Inc. All rights reserved. --> <xsl:stylesheet version="1.0" xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance" xmlns:cf="http://www.macromedia.com/2004/cfform" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xf="http://www.w3.org/2002/xforms" xmlns:html="http://www.w3.org/1999/xhtml" exclude-result-prefixes="xsi cf xsl xf html"> <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/> <xsl:output omit-xml-declaration="yes"/> This part of the file contains the definition of the stylesheet and defines the name spaces that are used, the same as in our XML form. The two <xsl:output> lines define the fact we're generating an XML file, and that we won't need the <?xml …> statement that normally appears at the top of any XML file. Listing 20.4a. basic.xslParameter Definitions<xsl:param name="HTTP_USER_AGENT"/> <xsl:param name="SCRIPTSRC"/> <xsl:param name="CONTEXTPATH"/> The parameters HTTP_USER_AGENT, SCRIPTSRC, and CONTEXTPATH are next; <xsl:param> statements must always be the first thing after the stylesheet definition. Listing 20.4b. Form Validation Includes<!-- include cfform javascript generation --> <xsl:include href="_cfformvalidation.xsl" /> The next thing the template does is include the form validation templates, which are used in the <xsl:call-template> tags below. Listing 20.4c. Templates<!-- start form --> <xsl:template match="/"> <xsl:variable name="formName" select="//form/@cf:name"/> The first <xsl:match="/"> tells the template to match the top level, which matches all the elements in the template. Everything in herethe rest of the filewill be run for the entire file. The next line defines a local variable called formName, which we can refer to by $formName later in the code. The select="//form/@cf:name" matches any form elements found at any level, followed by anything that starts with @cf:name. Now we start to use all those cf:xxx elements that were in Listing 20.3, the XML output of our form. Our form statement looked like: <form cf:archive="/CFIDE/classes/cfapplets.jar"... cf:instance="1" cf:name="xActorForm" cf:scriptsrc="/books/2/449/1/html/2//CFIDE/scripts/" ...> Therefore, this matches the cf:name attribute, which was "xActorForm". ColdFusion's XML form generation works mostly by leaving a lot of these types of placeholders in the form, so when the XSL starts matching element it can tell what the form's author originally intended to do, and render it back to the desired HTML. Listing 20.4d. JavaScript Generation<!-- generate the correct javascript, based on xf:bind, to validate onSubmit --> <xsl:call-template name="onSubmitValidation" /> This template, onSubmitValidation, is located in the _cfformvalidation.xsl that we included earlier. I'll leave the code out of this document for brevity, but this template creates the script source and starts building the standard form validation JavaScript, the same code used in a plain-Jane <cfform>. As additional elements are found in the document, their validation code is added to this function invocation. In addition, a variable named $SCRIPTSRC is filled in with the value of the scriptsrc attribute of the <cfform>, if any, or the default from the <xsl:param>. Listing 20.4e. Link to Stylesheet<xsl:element name="link"> <xsl:attribute name="href"><xsl:value-of select="$SCRIPTSRC"/>css/basic_style.css</xsl:attribute> <xsl:attribute name="rel">stylesheet</xsl:attribute> <xsl:attribute name="type">text/css</xsl:attribute> <xsl:attribute name="media">all</xsl:attribute> </xsl:element> This code generates the stylesheet link, using the $SCRIPTSRC we just generated. Listing 20.4f. Create the Form<xsl:element name="div"> <xsl:attribute name="class">cfform</xsl:attribute> <xsl:if test="/form/@html:width or /form/@html:height or /form/@html:style"> <xsl:attribute name="style"> <xsl:if test="/form/@html:width"> width: <xsl:value-of select="/form/@html:width"/>; </xsl:if> <xsl:if test="/form/@html:height"> height: <xsl:value-of select="/form/@html:height"/>; </xsl:if> <xsl:if test="/form/@html:style"> <xsl:value-of select="/form/@html:style"/> </xsl:if> </xsl:attribute> </xsl:if> <!-- generate the correct root form tag, based on the submission definition in the model --> <xsl:element name="form"> <xsl:attribute name="id"><xsl:value-of select="$formName"/></xsl:attribute> <xsl:attribute name="action"> <xsl:value-of select="/form/xf:model/xf:submission/@action"/> </xsl:attribute> <xsl:attribute name="method"> <xsl:value-of select="/form/xf:model/xf:submission/@method"/> </xsl:attribute> <xsl:choose> <xsl:when test="/form/@html:onsubmit"> <xsl:attribute name="onsubmit"> <xsl:value-of select="/form/@html:onsubmit"/> </xsl:attribute> </xsl:when> <xsl:otherwise> <xsl:attribute name="onsubmit"> <xsl:value-of select="concat(concat('return _CF_check', $formName), '(this);')"/> </xsl:attribute> </xsl:otherwise> </xsl:choose> <!-- loop over the form children elements --> <xsl:for-each select="//form/@html:*"> <xsl:if test="local-name() != 'width' and local-name() != 'height' and local-name() != 'validate' and local-name() != 'validation' and local-name() != 'type' and local-name() != 'onsubmit' and local-name() != 'style'"> <xsl:attribute name="{local-name()}"> <xsl:value-of select="."/> </xsl:attribute> </xsl:if> </xsl:for-each> This code matches the form elements, passing through any JavaScript that the user wished to add via onsubmit, style information, height and width. Note the use of <xsl:value-of> to extract the elements we want. Listing 20.4g. Add the Form's Child Elements<!-- loop over the form children elements --> <xsl:for-each select="*[not(self::xf:model)]"> <xsl:call-template name="vertical"/> </xsl:for-each> The child elements are processed by calling the "vertical" template for anything inside the form. That complex select chooses any nodes that aren't the form element itself (not self). Listing 20.4h. Server Side Validation.<!-- generate the correct hidden fields, based on xf:bind, to trigger server side validation --> <xsl:call-template name="onServerValidation" /> The system now adds hidden fields to provide server side validation for those users who don't have JavaScript enabled, another nice thing ColdFusion's XML forms do for you. Listing 20.4i. <cfformgroup> Processing</xsl:element> </xsl:element> </xsl:template> <xsl:template match="xf:model"/> <xsl:template match="xf:extension"/> <!-- **************************************************************************** Supported Groups ***************************************************************************--> <!-- passthrough match for any groups not defined --> <xsl:template match="xf:group"> <xsl:apply-templates select="*"/> </xsl:template> <!-- include groups that will be supported for this skin--> <xsl:include href="_group_vertical_table.xsl" /> <xsl:include href="_group_horizontal_table.xsl" /> <xsl:include href="_group_fieldset.xsl"/> The next part of the close ends the initial processing, and then ignores any model and extension tags by matching them and not doing anything with them. It then matches any group attributes, created by <cfformgroup> tags, and applies any remaining templates to them. The next three includes define the processing of those groups. These templates match any elements inside them, and wrap them in table elements or a fieldset for HTML display. Listing 20.4j. Form Elements Processing<!-- *************************************************************************** Form Elements ***************************************************************************--> <!-- include the default rules for form elements.--> <xsl:include href="_formelements.xsl" /> </xsl:stylesheet> Finally, we include the form elements themselves, which are defined in _formelements.xsl. These configure the standard form elements, and may actually be added to as we'll see in the next section. Writing Extensions to XForms Using XSLThere are several things to know if you want to extend XForms in ColdFusion MX 7:
Let's modify our form to provide a color chooser custom control. The control will simply stick a hidden field into our form and put three image tags into our form that will fill in the hidden field when clicked. We need to be able to provide the name of the form control as an attribute, and also provide a label to the form. We want the form to look like Figure 20.5. Figure 20.5. Color Picker Custom Form Control.The HTML source we want to generate will look like Listing 20.5. Listing 20.5. ColorPicker.htmlColor Picker Control (excerpt)<link href="/CFIDE/scripts/css/custom_style.css" rel="stylesheet"> <form name="xColorForm"> <div > <label for="colorPick">Pick a color:</label> <!-- hidden class is defined in custom_style.css --> <input name="colorPick" value="red"> <input type="button" value="green" style="background-color:green; color:white; border:1px solid black; width:100px; height:20px;" onClick="document.xColorForm.colorPick.value='green';return false"/> <input type="button" value="red" style="background-color:red; color:white; border:1px solid black; width:100px; height:20px;" onClick="document.xColorForm.colorPick.value='red';return false"/> <input type="button" value="blue" style="background-color:blue; color:white; border:1px solid black; width:100px; height:20px;" onClick="document.xColorForm.colorPick.value='blue';return false"/> <input type="submit" value="Pick!"> </div> </form> We could define a hidden style right in the file, but instead I'm going to copy the basic.css file to a custom_style.css, and define the hidden style simply as: Listing 20.5. custom_style.css (excerpt).hidden { display:none; } In order to generate this code, we'll use a <cfinput type="colorPick"> command in our <cfform>, as can be seen in Listing 20.6. Listing 20.6. colorPickerXML.cfmXML Form for Color Picker<!--- colorPickerXML.cfm Color Picker Form Ken Fricklas (kenf@fricklas.com) Modified: 2/21/2005 Listing 20.6 ---> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> <title>Color Picker Form</title> </head> <body> <cfform action="#cgi.script_name#" method="post" format="xml" name="xColorForm" skin="gray"> <cfinput type="colorPick" name="colorPick" value="red" label="Pick a color:"> <cfinput type="submit" name="subButton" value="Pick a Color"> </cfform> <hr> <cfoutput>#HTMLCodeFormat(xColorForm)#</cfoutput> </body> </html> We're dumping out the form XML to see what code is generated at the end of the template, as shown in Listing 20.7. Listing 20.7. ColorPicker XML Output<form cf:archive="/CFIDE/classes/cfapplets.jar" cf:codebase="http://java.sun.com/products/plugin/1.3/jinstall-13- win32.cab#Version=1,3,0,0" cf:instance="1" cf:name="xColorForm" cf:scriptsrc="/books/2/449/1/html/2//CFIDE/scripts/" html:action="/ows/20/colorPickerXML.cfm" html:method="post" html:name="xColorForm" xmlns:cf="http://www.macromedia.com/2004/cfform" xmlns:ev="http://www.w3.org/2001/xml-events" xmlns:html="http://www.w3.org/1999/xhtml" xmlns:xf="http://www.w3.org/2002/xforms"> <xf:model > <xf:instance> <cf:data> <colorPick>red</colorPick> </cf:data> </xf:instance> <xf:submission action="/ows/20/colorPickerXML.cfm" method="post"/> <xf:bind nodeset="//xf:model/xf:instance/cf:data/colorPick" required="false()"> <xf:extension> <cf:attribute name="type">COLORPICK</cf:attribute> <cf:attribute name="onerror">_CF_onError</cf:attribute> </xf:extension> </xf:bind> </xf:model> <xf:input bind="colorPick" > <xf:label>Pick a color:</xf:label> <xf:extension> <cf:attribute name="type">colorpick</cf:attribute> </xf:extension> </xf:input> <xf:submit submission="xColorForm"> <xf:label>Pick a Color</xf:label> <xf:extension> <cf:attribute name="type">submit</cf:attribute> <cf:attribute name="name">subButton</cf:attribute> </xf:extension> </xf:submit> </form> As Listing 20.7 shows, it generated <cf:attribute name="type">colorpick</cf:attribute>, which is what we want to look for when processing the input fields. Now it's time to get our hands dirty and write some XSL. All we need to do is look for the input with a type of colorpick, and add the HTML we previously wrote, filling in the values for the form and control names. The easiest way I've found to pick out this type requires some modifications to the _formelements.xsl file, so I've copied it to a local file called _cpFormElements.xsl (cp for colorpicker!). The modifications to this file occur in the test for the input tag. The existing code in _formelements.xsl looks like Listing 20.8. Listing 20.8. _formelements.xsl (excerpt)<xsl:for-each select="xf:extension/cf:attribute"> <xsl:if test="@name != 'width' and @name != 'validate' and @name = 'validation'"> <xsl:attribute name="{@name}"><xsl:value-of select="text()"/></xsl:attribute> </xsl:if> </xsl:for-each> This code simply finds any cf:attributes that were passed in, and passes them back out unchanged. Our type="colorPick"is one of these, so we don't want to ignore it. Instead we'll build a template specifically to generate our code, and call that. The changed code does the following:
The modified code is in Listing 20.9. Listing 20.9. _cpFormElements.xsl (excerpt)<xsl:for-each select="xf:extension/cf:attribute"> <xsl:choose> <xsl:when test="@name = 'width' or @name = 'validate' or @name = 'validation'"> <!-- ignore --> </xsl:when> <xsl:when test="@name = 'type'"> <xsl:choose> <!-- test for 'colorpick' in the text of the attribute --> <xsl:when test="text() = 'colorpick'"> <!-- mark hidden, then call the colorpick in colorPick.xsl --> <xsl:attribute name="class">hidden</xsl:attribute> <xsl:call-template name='colorpick'> <xsl:with-param name="id"><xsl:value-of select="$id"/></xsl:with-param> </xsl:call-template> </xsl:when> <xsl:otherwise> <!-- its another type attribute, so pass it through --> <xsl:attribute name="'type'"><xsl:value-of select="text()"/></xsl:attribute> </xsl:otherwise> </xsl:choose> </xsl:when> <xsl:otherwise> <!-- its some other attribute, so pass it through unchanged --> <xsl:attribute name="{@name}"><xsl:value-of select="text()"/></xsl:attribute> </xsl:otherwise> </xsl:choose> </xsl:for-each> The color picker itself is pretty short. We just build the input tags using <xsl:element> and <xsl:attribute>. Text handling in XSL is a little clunky, so I create variables to hold both parts of the JavaScript text to make my life easier (lots of string concatenation!). I use an <xsl:param> to get the id value from the calling template, _cpFormElements.xsl, in the code shown in Listing 20.9. This is in Listing 20.10. Listing 20.10. colorPick.xsl<?xml version="1.0" encoding="UTF-8"?> <!-- Copyright (c) 1995-2005 Macromedia, Inc. All rights reserved. --> <xsl:stylesheet version="1.0" xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:cf="http://www.macromedia.com/2004/cfform" xmlns:xf="http://www.w3.org/2002/xforms" xmlns:html="http://www.w3.org/1999/xhtml" exclude-result-prefixes="xsi cf xsl xf html"> <!-- <cfinput type="colorpick" > --> <xsl:template name="colorpick"> <!-- get the id value of the control to put in the javascript --> <xsl:param name="id"/> <!-- get the form name --> <xsl:variable name="formName" select="//form/@cf:name"/> <!-- create 2 variables to make the javascript code below less repetitive --> <xsl:variable name="jstext1" select="concat(concat(concat(concat ('document.',$formName), '.'), $id), '.value=')"/> <xsl:variable name="jstext2" select="';return false'"/> <!-- build the inputs --> <xsl:element name="input"> <xsl:attribute name="type">button</xsl:attribute> <xsl:attribute name="value">green</xsl:attribute> <xsl:attribute name="style">background-color:green; color:white; border:1px solid black; width:100px; height:20px;</xsl:attribute> <xsl:attribute name="onClick"> <xsl:value-of select="$jstext1"/>'green'<xsl:value-of select="$jstext2"/> </xsl:attribute> </xsl:element> <xsl:element name="input"> <xsl:attribute name="type">button</xsl:attribute> <xsl:attribute name="value">red</xsl:attribute> <xsl:attribute name="style">background-color:red; color:white; border:1px solid black; width:100px; height:20px;</xsl:attribute> <xsl:attribute name="onClick"> <xsl:value-of select="$jstext1"/>'red'<xsl:value-of select="$jstext2"/> </xsl:attribute> </xsl:element> <xsl:element name="input"> <xsl:attribute name="type">button</xsl:attribute> <xsl:attribute name="value">blue</xsl:attribute> <xsl:attribute name="style">background-color:blue; color:white; border:1px solid black; width:100px; height:20px;</xsl:attribute> <xsl:attribute name="onClick"> <xsl:value-of select="$jstext1"/>'blue'<xsl:value-of select="$jstext2"/> </xsl:attribute> </xsl:element> </xsl:template> </xsl:stylesheet> The last step is putting it all together. All we need to do is create our custom xsl file, which I'm calling custom.xsl. This is just a copy of the lightgrey.xsl file we exhaustively examined earlier in the chapter, with one minor change: we need to include our new colorpick.xsl file. Here are the last few lines of custom.xsl, shown in Figure 20.11. Listing 20.11. custom.xsl (excerpt)<!-- include the default rules for form elements.--> <xsl:include href="_formelements.xsl" /> <xsl:include href="colorpick.xsl"/> </xsl:stylesheet> That's itwe've implemented a custom control. XSL and XForms are new technologies that require a good deal of learning, but the power in them is well worth it. XForms adds a new, powerful tool to your arsenal of ways to extend ColdFusion. |