XSL: The eXtensible Stylesheet Language


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}.

Table 20.6. XSL Elements

XSL ELEMENT

DESCRIPTION

<xsl:template match=xpath-pattern name=template-name>

Match some part of the input document and run what's inside.

<xsl:apply-templates>

Continue processing more templates on the current item.

<xsl:call-template name=template-name>

Call another template by the name specified in the <xsl:template> and continue processing there.

<xsl:if test=boolean-expression>

Contents are only evaluated if the expression is true. For else functionality, use <xsl:choose>.

<xsl:choose>, <xsl:when test=boolean-expression>,<xsl:otherwise>

These work like <cfswitch>, except each case defined by <xsl:when> contains its own conditional expression, providing a if/elseif/else functionality.

<xsl:for-each select=expression>

Process each item matched by the loop expression.

<xsl:text>

Inserts literal text.

<xsl:value-of select=string-expression>

Inserts the value of the expression, like evaluate() in CFML.

<xsl:param name=value>

Allows values to be passed into template. All ColdFusion XSL templates are passed HTTP_USER_AGENT, SCRIPTSRC, and CONTEXTPATH.

<xsl:with-param name=value>

Passes in the value to a receiving <xsl:param> in another template.

<xsl:include href=path-or-URL>

Includes another template. Works like <cfinclude>, in that no variable protect is provided and the file is included and processed at the point and in the order included. If a template is redefined, the last one defined will be used, overwriting any previous templates with the same name.

<xsl:import href=path-or-URL>

Same as <xsl:include>, except that if any templates in the included file have the same name as one already defined, they are ignored.

<xsl:element name=value>

Lists the names of attribute sets (<xsl:attribute-set>) to be copied into the result tree. Standard attributes are defined with <xsl:attribute> tags. Mostly this is used with name="input", which creates an HTML <input> tag.

<xsl:attribute name=value>

Defines an attribute within an <xsl:element>


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.

Table 20.7. Skin Format and XSL File Locations

XSL FILE

DESCRIPTION

basic.xsl, basiccss.xsl, basiccss_top.xsl, color.xsl

Form formats for the standard file types. basiccss.xsl lays out using CSS, basic.xsl lays out using tables, and basiccss_top.xsl lays out using CSS, but with the label above the form items rather than to the left. The color.xsl files (for example, lightgray.xsl) also use table based layout.

_cfformvalidation.xsl

ColdFusion validation rules (required, integer, range, etc.)

_formelements.xsl

Standard form elements

_group_horizontal_css.xsl, _group_horizontal_css_top.xsl, _group_horizontal_table.xsl, _group_vertical_css.xsl, _group_vertical_css_top.xsl, _group_vertical_table.xsl, _group_fieldset.xsl

cfformgroup transformations, css or table version. Versions with _top are used in basiccss_top.xsl.


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 XSL

There are several things to know if you want to extend XForms in ColdFusion MX 7:

  • <cfformgroup> and <cfformitem> allow user-defined types, which you can access via a generated id called appearance.

  • If you want to define additional control types, use an existing form control types and add whatever additional attributes you want to match to the <cfinput> tags. Unrecognized attributes to <cfinput> are passed through to the XSL untouched, and you can override existing input type handling from _formelements.xsl by modifying that file, or including your own.

  • Include your own XSL files last, which will cause them to override any previous templates with the same name. Use narrower select criteria (match only input tags with an attribute of myType, for example) so the default control handling will still work.

  • Modify _cfformvalidation.xsl to add custom JavaScript or server-side validation.

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:

  • Checks to see if the name is width, validate, or validation, as it used to, and ignores those (same as before).

  • If it's none of those, but it is 'type', check to see if its 'colorpick'. If so, set the class to 'hidden' to hide the existing control which will be filled in from the color picker. Then it calls our custom template, passing it the id of the control to use in the JavaScript.

  • If it's any other 'type' attribute, just pass it through.

  • And finally, if it didn't match any of thoseit's a different attribute than type, width, validate, or validationthen pass it through again.

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.



Advanced Macromedia ColdFusion MX 7 Application Development
Advanced Macromedia ColdFusion MX 7 Application Development
ISBN: 0321292693
EAN: 2147483647
Year: 2006
Pages: 240
Authors: Ben Forta, et al

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net