ColdFusion in Context: Organized Help

Suppose you need to provide on-line help for an application that has role-based access. You don't want to explain features that a user isn't allowed to use. You want to describe the application's screens in context, letting the user follow the same navigational structure as the application itself, recognizing that not everyone should be offered the same paths. Because portions of your application have features driven by major customers, you want to be able to move this unique help into separate directories to simplify its upkeep. You want to be able to have pages include other pages, but you don't want to incorporate complex code into each page to keep it from being browsed by the general public.

This tip can meet all of these needs: organized help.

Data

Create a table named Screenhelp. Here is half of each row, delimited by asterisks:

Ref*Name*Needroles*Location
d1*Weather Applications*a,b,c,d*../help/d1.cfm
d2*Precipitation Control*a,b,c,d*../help/d2.cfm
d3*Wind*a,b*../help/d3.cfm
d4*Temperature Control*c,d*../help/d4.cfm
d5*Direction Control*c*../help2/d5.cfm
d6*Intensity Control*d*../help2/d6.cfm

Here's the other half, with Ref repeated for clarity. (You can parse these with Excel to get them into your database.)

Ref*FromList*ToList*Description
d1**d2,d3*Do something about the weather
d2*d1*d4*Changes the level of moisture
d3*d1*d5,d6*Modifies the airflow
d4*d1,d5**Makes it colder or warmer
d5*d3*d4,d6*Changes wind direction
d6*d3**Change wind intensity

The data is nonsense, but its structure is not; consider each column of the first half of the row in turn. Ref is a short reference used as a link parameter and table key. It can certainly be closer to the meaning of the screen than the example shown here, as long as it doesn't have spaces or odd punctuation. Name will be displayed as a title and in selection lists. Needroles contains a comma-delimited LIST of roles, any of which are allowed to access the screen. Location tells how to find the page.

Consider second half of the row. Fromlist is a LIST of Refs for screens that can reach this one; screens at the top of the food chain have none. Tolist is a LIST of Refs for screens that this one can reach; screens at the bottom of the food chain have no children. Description is handy to avoid confusion during maintenance and to introduce a help page.

Demo

To see the organization as it's meant to be seen, you have to simulate a user's roles: arbitrarily a, b, c, d for this demo. That's why you need demo.cfm. Fill demo.cfm with the following code; it lets you set a cookie containing a list of the user's active roles and then leap into the topmost help page.

Set defaults for the form's submit button and list of checkboxes, then test. If the submit button was used, replace the list in the cookie with the list of roles for which boxes were checked.

<cfparam name="form.Set" default="">
<cfparam name="form.Rolelist" default="">
<cfif len(trim(form.Set))>
  <cfcookie name="Helpdemo" value="#form.Rolelist#">
</cfif>

The form is straightforward and will be posted. Because the help facility to be created will use cflocation to shift from one page to another, we won't set the cookie and move to the first help in the same motion. We'll take two steps: set and see the checkboxes, and select the link to begin the help demo. This means we need to test the submitted value of each checkbox and supply it to cause the boxes to actually be checked after submission (to avoid confusion). Checkboxes having the same name return a comma-delimited list of values; so, the contains function is a natural for this application. (If your values are more complex, use listContains instead.)

At the end of the form, supply the link for the help demo. This link doesn't go to the first page directly. It supplies the Ref value for the first page to work/help.cfm. Notice that for simplicity later on, the help function and its related pages are in a work directory of their own, one level below demo.cfm.

<form method="post">
Check your roles for the demonstration.<br>
<input type="checkbox" name="Rolelist" value="a"
<cfif Rolelist contains "a">checked</cfif>>a
<input type="checkbox" name="Rolelist" value="b"
<cfif Rolelist contains "b">checked</cfif>>b
<input type="checkbox" name="Rolelist" value="c"
<cfif Rolelist contains "c">checked</cfif>>c
<input type="checkbox" name="Rolelist" value="d"
<cfif Rolelist contains "d">checked</cfif>>d
<input type="submit" name="Set" value="Set"><br>
<a href="work/help.cfm?Ref=d1">Begin</a>
</form>

Organize

The user has basically two kinds of choices to make once inside the help organization: pick a link within the normal navigation hierarchy, or pick a link that will display a list of all available pages. Put the organizing code to do this in work/help.cfm.

Begin by handling the "list" case. Skip the rest of the logic if a list was requested.

<!--- Provide the "list" if requested --->
<cfparam name="url.Ref" default="">
<cfif url.Ref is "list">
  <cfinclude template="list.cfm">
  <cfabort>
</cfif>

If the user didn't ask for a list, get information about the screen and see if the user has a role needed for access to the screen. The code loops through needed roles, stopping the search when the contains function finds that the list of the user's roles (in the cookie) contains a role that's needed.

<!--- Otherwise... get help parameters --->
<cfquery name="Helpget1" datasource="context">
select * from Screenhelp
where Ref = '#Ref#'
</cfquery>

<!--- See if user has a needed role --->
<cfset OK=0>
<cfloop list="#helpget1.Needroles#" index="Needs">
<cfif cookie.Helpdemo contains Needs>
  <cfset OK=1>
  <cfbreak>
</cfif>
</cfloop>

If so, present a response. Include work/fromlist.cfm to show a list of links from which the user can follow to get to this screen. Include work/tolist.cfm to show a list of links the user can go to from this screen. Provide a heading consisting of the name and the description. Include the location found in the Screenlist table. You would normally close with a link that invites the user to list available screens. However, because this is a demo, add one more link to get back to work/demo.cfm in order to change roles and start over.

If not, block the user. Tell the user to employ the back button to get back on track. (The user showed creativity to get this response.)

<!--- If so, present a response --->
<cfif OK>
  <cfinclude template="fromlist.cfm">
  <cfinclude template="tolist.cfm">
  <cfoutput>
  <strong>#helpget1.Name#</strong> - #helpget1.Description#
  </cfoutput>
  <hr>
  <cfinclude template="#helpget1.Location#">
  <hr>
  <a href="help.cfm?Ref=list">
  List all available screens</a>
  <hr>
  <a href="../demo.cfm">demo roles</a>
  <cfabort>
</cfif>

<!--- If not, block the user --->
You cannot perform this function.<br>
Please press your back button to continue.
<cfabort>

"From" List

Use work/fromlist.cfm to hold the code that lists links that can reach this screen. Loop through the comma-delimited Fromlist, looking up each reference, determining if the user's allowed to see it, and displaying the link if it's OK to show it.

<!--- Loop through "from" references --->
<cfset HeadShown=0>
<cfloop list="#helpget1.FromList#" index="Ref">

<!--- Get parameters for a "from" reference --->
<cfquery name="helpGet" datasource="context">
select * from Screenhelp
where Ref = '#Ref#'
</cfquery>

<!--- See if the user has a needed role --->
<cfset OK=0>
<cfloop list="#helpget.Needroles#" index="Needs">
<cfif cookie.Helpdemo contains Needs>
  <cfset OK=1>
  <cfbreak>
</cfif>
</cfloop>

<!--- If so, show:
- a header (once)
- a list of "from" screen links the user can follow
--->
<cfif OK>
  <cfif not HeadShown>
    <strong><cfoutput>#helpget1.Name#
    </cfoutput></strong> can be reached from<br>
    <cfset HeadShown=1>
  </cfif>
    <cfoutput>
        <a href=
    "help.cfm?Ref=#Helpget.Ref#"
    >#Helpget.Name#</a>
  </cfoutput><br>
</cfif>

<!--- Close the loop --->
</cfloop>

"To" List

To make work/tolist.cfm, COPY work/fromlist.cfm and change "from" to "to". The result will list links that the user can go to from this screen and will look like this:

<!--- Loop through "to" references --->
<cfset HeadShown=0>
<cfloop list="#helpget1.ToList#" index="Ref">

<!--- Get parameters for a "to" reference --->
<cfquery name="helpGet" datasource="context">
select * from Screenhelp
where Ref = '#Ref#'
</cfquery>

<!--- See if the user has a needed role --->
<cfset OK=0>
<cfloop list="#helpget.Needroles#" index="Needs">
<cfif cookie.Helpdemo contains Needs>
  <cfset OK=1>
  <cfbreak>
</cfif>
</cfloop>

<!--- If so, show:
- a header (once)
- a list of "to" screen links the user can follow
--->
<cfif OK>
  <cfif not HeadShown>
    <strong><cfoutput>#helpget1.Name#
    </cfoutput></strong> can reach<br>
    <cfset HeadShown=1>
  </cfif>
    <cfoutput>
        <a href=
    "help.cfm?Ref=#Helpget.Ref#"
    >#Helpget.Name#</a>
  </cfoutput><br>
</cfif>

<!--- Close the loop --->
</cfloop>

List

A link at the bottom of each help screen lets the user navigate to a list of available screens. This page, work/list.cfm, is the destination. It gets a list by name, then makes the familiar role test for each screen. If the user has a needed role, the name of the screen is displayed as a link whose reference is fed to help.cfm when the link is selected. Otherwise, the screen name and link do not appear.

<!--- Get all screens --->
<cfquery name="Screenlist" datasource="context">
select * from Screenhelp
order by Name
</cfquery>

<!--- For each screen... --->
<cfoutput query="Screenlist">

<!--- See if user has a needed role --->
<cfset OK=0>
<cfloop list="#Screenlist.Needroles#" index="Needs">
<cfif cookie.Helpdemo contains Needs>
  <cfset OK=1>
  <cfbreak>
</cfif>
</cfloop>

<!--- If so, show the screen name in a link --->
<cfif OK>
  <a href="help.cfm?Ref=#Ref#">#Name#</a><br>
</cfif>

<!--- ...close the loop --->
</cfoutput>

Junk

Before we leave this directory, it's worth noting that demonstration text is short. To make it longer and to demonstrate that pages can be included by help text, fill work/junktext.cfm with the following nonsense text that will eventually appear as part of each help screen to provide the right visual effect.

Ze wtg vmttri qt kjqvm qdjbrjvqztbr, wtg vjb eqc td rivgdipw vtcw qmin fzdivqpw qt tgd ezpi ridaid. J sjqihjw ridaid czvor gc qmi ezpi kw rivgdi nijbr, jdvmzair zq, jbf cjrrir zq qt qmi hik ridaid. Qmi hik ridaid cidetdnr qmi rjni ifzqr ijvm qdjbrjvqztb htgpf divizai ze zq hidi ibqidif azj qmi kdthrid. Ze rtni te qmi qdjbrjvqztbr hidi btq vtddivq, qmi dirq jdi ctrqif qt qmi hik fjqjkjri jr ze qmiw vjni edtn qmi kdthrid.

It's actually a cryptogram with no connection to the task at hand except for use as page filler.

Bouncer

Every establishment needs to consider having a bouncer. Use this text in help/Application.cfm to keep ColdFusion pages in the directory from being browsed. (If they should be seen, they'll be included by the help facility. The user won't browse them directly.) A simple cfabort with an explanation will do nicely.

You lack permission to explore this area.<br>
Please press your back button to continue.
<cfabort>

Use the same contents in help2/Application.cfm to protect the help2 directory in the same fashion. For the purpose of this demo, help2 represents a major customer's functions that might be supported by a different team.

Help

Please forgive my whimsey in these sample screens. It's not easy coming up with sheer nonsense (unless it's part of a larger document, such as a budget [groan]).

Each of these documents has a cfinclude tag to make the text longer and to demonstrate that the full range of ColdFusion tags is available here.

Here's help/d1.cfm. Notice the directory name.

Weather Applications can modify key weather attributes over a small area. The effect is too small to affect a region over the long term but can save a party or make an enemy VERY uncomfortable during key battles. <cfinclude template="../work/junktext.cfm">

Here's help/d2.cfm.

Precipitation Control shifts moisture from one area to another. Its indirect effects can be wider-ranging than its direct effects. For example, if used in a dry climate, it may have to suck moisture from an area hundreds of miles on a side in order to dump twelve inches of rain on an area 3 miles to a side. <cfinclude template="../work/junktext.cfm">

Here's help/d3.cfm.

Wind is the most powerful tool in the kit; because, its impact can be restricted to a small area. Other tools in a zero-sum manner must shift a weather attribute from one place to another in order to concentrate its impact, but spiral wind can be designed to have a local impact. <cfinclude template="../work/junktext.cfm">

Here's help/d4.cfm.

Temperature Control has the capacity to freeze or bake nine square miles. <cfinclude template="../work/junktext.cfm">

Here's help2/d5.cfm. Notice this directory name, simulating a place for another team to maintain pages.

Direction Control shapes spiral airflow or determines the primary direction of a non-spiral airflow. <cfinclude template="../work/junktext.cfm">

Here's help2/d6.cfm.

Intensity provides power, but it also requires power. Doubling the perceived effect requires the input power to be squared (not cubed; because, effects have a built-in height limitation). <cfinclude template="../work/junktext.cfm">

Application

Browse demo.cfm. Set a few roles. Select the link at the bottom of the page to begin exploring. Notice that unless you set all roles, some pages aren't visible to you, and their links don't appear. You can navigate logically, or you can select screens from the list.

For your own application, tie this structure to yours, or include this structure in your application. For example, replace Needroles in Screenlist with ties to your existing permission structure; replace Fromlist and Tolist in Screenlist with ties to the navigation of your own application. Or, add columns to tables in your application so that a separate table won't be needed to support organized help; it will truly be integrated with your application.

Either adjustment will align your on-line help with the application in the manner shown here. Subject matter specialists from multiple teams can maintain your help screens, which can be anywhere in your application. In some cases, you may want your help pages to perform queries so their text is secondary to their purpose. Improve this organization for on-line help, and tell us how it works for you. =Marty=