Adobe ColdFusion 8

Example 2: a more complex tree with leaf node handling

The following tree uses the cfartgallery database to populate a tree where the top level is the art medium, the second level is the artist, and the leaf nodes are individual works of art. When the user clicks on an art work, the application shows the art image.

This example shows how to generate return values that are specific to the level in the tree and the parent value and the use of the LEAFNODE return structure element.

In this application, the CFC return structure keys are specified in lowercase letters, and ColdFusion automatically converts them to uppercase. Notice that the database contains entries only for the painting, sculpture, and photography categories, so just those top-level tree nodes have child nodes.

The following examples shows the main application page:

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<!--- The loadimage function displays the image of the selected art.
        It is called when the user clicks the image item. --->
<script>
    function loadImage(img) {
        var imgURL = '<img src="/cfdocs/images/artgallery/'+img+'">';
        var imgDiv = document.getElementById('image');
            imgDiv.innerHTML = imgURL;
    }
</script>

</head>
<body>

<!--- The form uses a table to place the tree and the image. --->
<cfform name="ex1" action="ex1.cfm" method="post">
    <table>
        <tr valign="top">
            <td>
                <cftree name="mytree" format="html">
                    <!--- When you use a bind expression, you must have only one
                            cftreeitem, which populates the tree level. --->
                    <cftreeitem bind="cfc:tree.getItems({cftreeitempath},
                        {cftreeitemvalue})">
                </cftree>
            </td>
            <td>
                <div id="image"></div>
            </td>
        </tr>
    </table>
</cfform>
</body>
</html>

The following example shows the tree.cfc file:

<cfcomponent output="false">

<cfset variables.dsn = "cfartgallery">

<!--- Function to populate the current level of the tree. --->
<cffunction name="getItems" returnType="array" output="false" access="remote">
    <cfargument name="path" type="string" required="false" default="">
    <cfargument name="value" type="string" required="false" default="">
    <cfset var result = arrayNew(1)>
    <cfset var q = "">
    <cfset var s = "">
    
    <!--- The cfif statements determine the tree level. --->
    <!--- If there is no value argument, The tree is empty. Get the media types. --->
    <cfif arguments.value is "">
        <cfquery name="q" datasource="#variables.dsn#">
        SELECT mediaid, mediatype
        FROM media
        </cfquery>
        <cfloop query="q">
            <cfset s = structNew()>
            <cfset s.value = mediaid>
            <cfset s.display = mediatype>
            <cfset arrayAppend(result, s)>        
        </cfloop>

    <!--- If the value argument has one list entry, its a media type. Get the artists for
            the media type.--->
    <cfelseif listLen(arguments.value) is 1>
        <cfquery name="q" datasource="#variables.dsn#">
        SELECT artists.lastname, artists.firstname, artists.artistid
        FROM art, artists
        WHERE art.mediaid = <cfqueryparam cfsqltype="cf_sql_integer"
                value="#arguments.value#">
        AND art.artistid = artists.artistid 
        GROUP BY artists.artistid, artists.lastname, artists.firstname
        </cfquery>
        <cfloop query="q">
            <cfset s = structNew()>
            <cfset s.value = arguments.value & "," & artistid>
            <cfset s.display = firstName & " " & lastname>
            <cfset arrayAppend(result, s)>        
        </cfloop>
    
    <!--- We only get here when populating an artist's works. --->
    <cfelse>
        <cfquery name="q" datasource="#variables.dsn#">
        SELECT art.artid, art.artname, art.price, art.description,
                art.largeimage, artists.lastname, artists.firstname
        FROM art, artists
        WHERE art.mediaid = <cfqueryparam cfsqltype="cf_sql_integer"
                value="#listFirst(arguments.value)#">
        AND art.artistid = artists.artistid 
        AND artists.artistid = <cfqueryparam cfsqltype="cf_sql_integer" 
                value="#listLast(arguments.value)#">
        </cfquery>
        <cfloop query="q">
            <cfset s = structNew()>
            <cfset s.value = arguments.value & "," & artid>
            <cfset s.display = artname & " (" & dollarFormat(price) & ")">
            <cfset s.href = "javaScript:loadImage('#largeimage#');">
            <cfset s.children=arrayNew(1)>
            <!--- leafnode=true prevents node expansion and further calls to the
                bind expression. --->
            <cfset s.leafnode=true>
            <cfset arrayAppend(result, s)>        
        </cfloop>
        
    </cfif>
        
    <cfreturn result>
</cffunction>

</cfcomponent>

Binding other controls to a tree

ColdFusion tags that use bind expressions can bind to the selected node of a tree by using the following formats:

  • {[form:]tree.node} retrieves the value of the selected tree node.
  • {[form:]tree.path} retrieves the path of the selected tree node. If the completePath attribute value is true, the bound path includes the root node.

The bind expression is evaluated each time a select event occurs on an item in the tree. If you specify any other event in the bind parameter, it is ignored.

Tree JavaScript functions

You can use the following JavaScript functions to manage an HTML format tree:

Function

Description

ColdFusion.Tree.getTreeObject

Gets the underlying Yahoo User Interface Library TreeView JavaScript object.

ColdFusion.Tree.refresh

Manually refreshes a tree.

For more information, see the ColdFusion.Tree.getTreeObject and ColdFusion.Tree.refresh functions in the CFML Reference.

Using the rich text editor

The ColdFusion rich text editor lets users enter and format rich HTML text by using an icon-driven interface based on the open source FCKeditor Ajax widget. The editor includes numerous formatting controls, and icons for such standard operations as searching, printing, and previewing text. This topic does not cover the text editor controls. For detailed information on the editor icons and controls, see http://wiki.fckeditor.net/UsersGuide.

The following example shows a simple rich text editor. When a user enters text and clicks the Enter button, the application refreshes and displays the formatted text above the editor region.

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
</head>

<body>
<!--- Display the text if the form has been submitted with text. --->
<cfif isdefined("form.text01") AND (form.text01 NEQ "")>
    <cfoutput>#form.text01#</cfoutput><br />
</cfif>

<!--- A form with a basic rich text editor and a submit button. --->
<cfform name="form01" >
    <cftextarea richtext=true name="text01" />
    <cfinput type="submit" value="Enter" name="submit01"/>
</cfform>
</body>
</html>

Note: If you use the rich text editor in your pages, you cannot configure your web server to have ColdFusion process files with the .html or .htm extensions. The default HTML processor must handle pages with these extensions.

Configuring the rich text editor

You can customize the rich text editor in many ways. The cftextarea attributes support some basic customization techniques. For more detailed information, see the FCKEditor website at http://wiki.fckeditor.net/.

Defining custom toolbars

You can use the following techniques to control the appearance of the toolbar:

  • Specify the toolbar name in the toolbar attribute
  • Create custom toolbars in the fckconfig.js file.

The editor has a single toolbar consisting of a set of active icons and fields, and separators. The toolbar attribute lets you select the toolbar configuration. The attribute value specifies the name of a toolbar set, which you define in a FCKConfig.ToolbarSets entry in the cf_webRoot/CFIDE/scripts/ajax/FCKEditor/fckconfig.js file.

The rich text editor comes configured with two toolbar sets: the Default set, which contains all supported editing controls, and a minimal Basic set. By default, the editor uses the Default set. To create a custom toolbar named BasicText with only text-editing controls, create the following entry in the fckconfig.js file, and specify toolbar="BasicText" in the textarea tag.

FCKConfig.ToolbarSets["BasicText"] = [
     ['Source','DocProps','-','NewPage','Preview'],
     ['Cut','Copy','Paste','PasteText','PasteWord','-','Print','SpellCheck'],
     ['Undo','Redo','-','Find','Replace','-','SelectAll','RemoveFormat'],
     ['Bold','Italic','Underline'],
     ['Outdent','Indent'],
     ['JustifyLeft','JustifyCenter','JustifyRight','JustifyFull'],
     '/',
     ['Style','FontFormat','FontName','FontSize'],
     ['TextColor','BGColor'],
     ['FitWindow','-','About']
    ];

This configuration defines a toolbar with two rows that contain a subset of the full tool set designed to support basic text editing.

Follow these rules when you define a toolbar:

  • Start the definition with FCKConfig.ToolbarSets.
  • Specify the toolbar name in double quotation marks and square brackets ([""]). You must use this name, case correct, in the cftextarea tag toolbar attribute.
  • Follow the toolbar name with an equals sign (=).
  • Place all the toolbar controls inside a set of square brackets, and follow the definition with a semicolon (;).
  • Group controls in square brackets.
  • Put each entry in single quotation marks (') and separate the entries with commas (,).
  • Use the hyphen (-) character to specify a separator.
  • Use a forward slash (/) character to start a new row.

For a complete list of the valid toolbar entries, see the Default configuration in fckconfig.js.

Defining custom styles

You can add custom styles that users can chose in the Styles selector and apply to selected text. To create a custom style, add a Style element to /CFIDE/scripts/ajax/FCKEditor/fckstyles.xml. The Style XML element has the following format:

  • The name attribute specifies the name that appears in the Style selector.
  • The element attribute specifies the HTML element that surrounds the text.
  • Each Attribute child element defines the name and value of an attribute of the HTML tag.

For example, the following definition creates a style that makes the selected text bold and underlined:

<Style name="Custom Bold And Underline " element="span">
    <Attribute name="style" value="font-weight: bold; text-decoration: underline;"/>
</Style>

You can use a custom XML file, instead of fckstyles.xml, to define your styles. If you do so, specify the filepath in the stylesXML attribute.

Defining custom templates

The editor includes a set of basic templates that insert HTML formatting into the textarea control. For example, the Image and Title template puts a placeholder for an image on the left of the area, and a title and text to the right of the image. Then you can right-click the image area to specify the image source and other properties, and replace the placeholder title and text.

You create your own templates by creating entries in cf_webRoot/CFIDE/scripts/ajax/FCKEditor/fcktemplates.xml file. Each template XML entry has the following format:

    <Template title="template title" image="template image">
        <Description>template description</Description>
        <Html>
            <![CDATA[
                HTML to insert in the text area when the user selects the template.
            ]]>
        </Html>
    </Template>

The template title, image and description appear in the Templates dialog box that appears when the user clicks the template icon on the rich text editor toolbar.

The following example template defines a title followed by text:

<Template title="Title and Text" image="template1.gif">
    <Description>A Title followed by text.</Description>
    <Html>
        <![CDATA[
            <h3>Type the title here</h3>
            Type the text here
        ]]>
    </Html>
</Template>

The name "Title and Text" and the template1.gif image appear in the template selection dialog box.

You can use a custom XML file, instead of fcktemplates.xml, to define your templates. If you do so, specify the file path in the templatesXML attribute.

Defining custom skins

To create a custom skin that you can specify in the skin attribute, create a subdirectory of the cf_webRoot/CFIDE/scripts/ajax/FCKeditor/editor/skins directory. The name of this subdirectory is the name that you use to specify the skin in the skin attribute. The custom skin directory must contain an images subdirectory and have the following files:

  • fck_editor.css: Defines the main interface, including the toolbar, its items (buttons, panels, etc.) and the context menu.
  • fck_dialog.css: Defines the basic structure of dialog boxes (standard for all dialogs).
  • fck_strip.gif: Defines the Default toolbar buttons and context menu icons. It is a vertical image that contains all icons placed one above the other. Each icon must correspond to a 16x16 pixels image. You can add custom images to this strip.
  • images/toolbar.buttonarrow.gif: Defines the small arrow image used in the toolbar combos and panel buttons.

Place all other images used by the skin (those that are specified in the CSS files) in the images subfolder.

The most common way of customizing the skin is to make changes to the fck_editor.css and fck_dialog.css files. For information on the skin format and contents, see the comments in those files.

Using the datefield input control

The HTML format cfinput control with a type value of datefield lets users select dates from a pop-up calendar or enter the dates directly in the input box. When you use the control, you must keep the following considerations in mind:

  • To correctly display label text next to the control in both Internet Explorer and Firefox, you must surround the label text in a <div style="float:left;"> tag and put three <br> tags between each line.
  • Consider specifying an overflow attribute with a value of visible in the cflayoutarea tag so that if the pop-up calendar exceeds the layout area boundaries, it appears completely.
  • If you use a mask attribute to control the date format, it does not prevent the user from entering dates that do not conform to the mask. The mask attribute determines the format for dates that users select in the pop-up calendar. Also, if the user types a date in the field and opens the pop-up calendar, the calendar displays the selected date only if the entered text follows the mask pattern. If you do not specify a mask attribute, the pop-up only matches the default matching pattern.
  • If the user types a date with a month name or abbreviation in the control, instead of picking a date from the calendar, the selected date appears in the pop-up calendar only if both of the following conditions are true:
    • The month position and name format match the mask pattern.
    • The month name matches, case correct, the month names specified by the monthNames attribute, or, for an mmm mask, their three-letter abbreviations.
  • If the date mask specifies yy for the years, the pop-up calendar uses dates in the range 1951-2050, so if the user enters 3/3/49 in the text field, the calendar displays March 3, 2049.
  • If the user enters invalid numbers in a date, the pop-up calendar calculates a valid date that corresponds to the invalid input. For example, if the user enters 32/13/2007 for a calendar with a dd/mm/yyyy mask, the pop-up calendar displays 01/02/2008.

The following example shows a simple tabbed layout where each tab contains a form with several datefield controls.:

<html>
<head>
</head>

<body>
<cflayout type="tab" tabheight="250px" style="width:400px;">
    <cflayoutarea title="test" overflow="visible">
        <br>
        <cfform name="mycfform1">
            <div style="float:left;">Date 1: </div>
            <cfinput type="datefield" name="mydate1"><br><br><br>
            <div style="float:left;">Date 2: </div>
            <cfinput type="datefield" name="mydate2" value="15/1/2007"><br><br><br>
            <div style="float:left;">Date 3: </div>
            <cfinput type="datefield" name="mydate3" required="yes"><br><br><br>
            <div style="float:left;">Date 4: </div>
            <cfinput type="datefield" name="mydate4" required="no"><br><br><br>
        </cfform>
    </cflayoutarea>    
    <cflayoutarea title="Mask" overflow="visible">
        <cfform name="mycfform2">
            <br>
            <div style="float:left;">Date 1: </div>
            <cfinput type="datefield" name="mydate5" mask="dd/mm/yyyy">
                (dd/mm/yyyy)<br><br><br>
            <div style="float:left;">Date 2: </div>
            <cfinput type="datefield" name="mydate6" mask="mm/dd/yyyy">
                (mm/dd/yyyy)<br><br><br>
            <div style="float:left;">Date 3: </div>
            <cfinput type="datefield" name="mydate7" mask="d/m/yy">
                (d/m/yy)<br><br><br>
            <div style="float:left;">Date 4: </div>
            <cfinput type="datefield" name="mydate8" mask="m/d/yy">
                (m/d/yy)<br><br><br>
        </cfform>
    </cflayoutarea>
</cflayout>

</body>
</html>

Note: In Internet Explorer versions prior to IE 7, this example might show the calendars for the first three fields in a page behind the following input controls.

Using autosuggest text input fields

When you create a text input (type="text") in an HTML format form, you can use the autosuggest attribute to specify a static or dynamic source that provides field completion suggestions as the user types. Use the autosuggestMinLength attribute to specify the number of characters the user must type before first displaying any suggestions.

Note: To put label text next to a cfinput control that uses an autosuggest attribute and have it display correctly in both Internet Explorer and Firefox, you must surround the label text in an HTML div tag with a style="float: left" attribute. Also if you have multiple controls, and put them on separate lines, follow the input controls with three <br> tags, as in the following example. Otherwise, the label and control do not lay out properly.

<div style="float: left"> Name: </div>
<cfinput name="userName" type="text" autosuggest="Andrew, Jane, Robert"> <br><br><br>

The control can suggest entries from a static list of values. To use a static suggestion list, specify the list entries in the autosuggest attribute, and separate the entries by the character specified by the delimiter attribute (by default, a comma), as the following example shows:

<cfinput type="text" 
    autosuggest="Alabama,Alaska,Arkansas,Arizona,Maryland,Minnesota,Missouri"
    name="city" delimiter="\">

In this example, if you type the character a (in uppercase or lowercase) in the cfinput control, the list of states that start with A appears in a drop-down list. You navigate to a selection by using the arrow keys, and press Enter to select the item.

You can also have the control suggest values from a dynamically generated suggestion list. To use a dynamic list, specify a CFC function, JavaScript function, or URL in the autosuggest attribute. Use the autosuggestBindDelay attribute to specify the minimum time between function invocations as the user types, and thereby limit the number of requests that are sent to the server. If you use a dynamic list, the input field has an icon to its right that animates while suggestions are fetched.

When you use a bind expression you must include a {cfautosuggestvalue} bind parameter in the function call or URL parameters. This parameter binds to the user input in the input control and passes it to the function or page.

A CFC or JavaScript autosuggest function must return the suggestion values as a one-dimensional array or as a comma-delimited list.

The HTTP response body from a URL must consist only of the array or list of suggestion values in JSON format. In ColdFusion you can use the serializeJSON function to convert an array to JSON format. If an array with the suggestions is named nodeArray, for example, the following line would specify the only output on a CFML page that is called by using a bind expression with a URL:

<cfoutput>#serializeJSON(nodeArray)#</cfoutput>

You do not have to limit the returned data to values that match the cfautosuggestvalue contents, because the client-side code displays only the values that match the user input. In fact, the called function or page does not even have to use the value of the cfautosuggestvalue parameter that you pass to it. You should, however, use the parameter if the returned data would otherwise be long.

The following example shows how you can a bind expression to populate autosuggest lists. The Last Name text box displays an autosuggest list with all last names in the database that match the keys typed in the box. The First Name text box uses binding to the Last Name text box to display only the first names that correspond to the last name and the text entered in the box. The database query limits the responses to only include results that match the autosuggest criteria, so the autosuggest list displays all the returned results, and the suggestions only match if the database entry has a case-correct match.

To test this example with the cfdocexamples database, type S in the first box and the autosuggest list shows Smith and Stewart. If you select Smith and enter A or J in the First Name box, you get a name suggestion.

The following example shows the application:

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
</head>
<body>

<cfform>
    Last Name:<br />
    <cfinput type="text" name="lastName"
        autosuggest="cfc:suggestcfc.getLNames({cfautosuggestvalue})"><br />
    <br />
    First Name:<br />
    <cfinput type="text" name="firstName"
        autosuggest="cfc:suggestcfc.getFNames({cfautosuggestvalue},{lastName})">
</cfform>
</body>
</html>

The following example shows the suggestcfc.cfc file:

<cfcomponent>

    <cffunction name="getLNames" access="remote" returntype="array" output="false">
        <cfargument name="suggestvalue" required="true">
        <!--- The function must return suggestions as an array. --->
        <cfset var myarray = ArrayNew(1)>
        <!--- Get all unique last names that match the typed characters. --->
        <cfquery name="getDBNames" datasource="cfdocexamples">
        SELECT DISTINCT LASTNAME FROM Employees
        WHERE LASTNAME LIKE <cfqueryparam value="#suggestvalue#%"
            cfsqltype="cf_sql_varchar">
        </cfquery>
        <!--- Convert the query to an array. --->
        <cfloop query="getDBNames">
            <cfset arrayAppend(myarray, lastname)>
        </cfloop>
        <cfreturn myarray>
    </cffunction>

    <cffunction name="getFNames" access="remote" returntype="array"
            output="false">
        <cfargument name="suggestvalue" required="true">
        <cfargument name="lastName" required="true">
        <cfset var myarray = ArrayNew(1)>
        <cfquery name="getFirstNames" datasource="cfdocexamples">
        <!--- Get the first names that match the last name and the typed characters. --->
        SELECT FIRSTNAME FROM Employees
        WHERE LASTNAME = <cfqueryparam value="#lastName#"
            cfsqltype="cf_sql_varchar">
        AND FIRSTNAME LIKE <cfqueryparam value="#suggestvalue & '%'#"
            cfsqltype="cf_sql_varchar">
        </cfquery>
        <cfloop query="getFirstNames">
            <cfset arrayAppend(myarray, Firstname)>
        </cfloop>
        <cfreturn myarray>
    </cffunction>
    
 </cfcomponent>