<a href="OneForm.cfm?Content=Exhibit&Mode=Add">Exhibits</a> <p> <a href="OneForm.cfm?Content=ShopLocation&Mode=Add">Shop Locations</a> <p> <a href="OneForm.cfm?Content=StateOrCountryCode&Mode=Add">State/Country Codes</a>
<cfscript> Title="Exhibits"; AllQuery="allExhibit"; OneQuery="oneExhibit"; NotQuery="oneExhibitNot"; AddQuery="addExhibit"; DelQuery="delExhibit"; UpdQuery="updExhibit"; Field1="Exhibit"; Field1Max=20; Field2=""; Field2Max=0; FieldX=""; FieldXMax=0; FieldW="Description"; FieldWMax=240; </cfscript>
An explanation is in order. Title is used in captions. The query names OneQuery, NotQuery, etc. are the names of the corresponding query files when .cfm is added to them. Yes, this application only uses six query files, no matter how many valid values tables you have. Their names are self-explanatory except perhaps for the one beginning in "Not"; NotQuery.cfm actually uses the same query name as OneQuery.cfm except that it brings back no records. Field1 is the key. Field2 is a text field; if it's given a length greater than zero (Field2Max), the user must enter it. It has no length in this example. FieldW is a Wide field. FieldX is either "X" or empty. The "max" fields indicate the length of the corresponding field in the database. These are all text fields.
Create the Exhibit table to go with this description. Give it fields Exhibit and Description with the lengths shown above (20 and 240).
Here's ShopLocation.cfm. It has only one field, which makes it pretty typical for a valid values table. Create ShopLocation. Give it the field ShopLocation with the length shown below.
<cfscript> ContentFile="ShopLocation.cfm"; Title="Shop Locations"; AllQuery="allShopLocation"; OneQuery="oneShopLocation"; NotQuery="oneShopLocationNot"; AddQuery="addShopLocation"; DelQuery="delShopLocation"; UpdQuery="updShopLocation"; Field1="ShopLocation"; Field1Max=20; Field2=""; Field2Max=0; FieldX=""; FieldXMax=0; FieldW=""; FieldWMax=0; </cfscript>
Here's StateOrCountryCode.cfm. It has a field that is either X (indicating a country) or empty (indicating a state). Create the corresponding table: StateOrCountryCode. (Give it fields StateOrCountryCode, Name, and CountryFlag with the lengths indicated.)
<cfscript> ContentFile="StateOrCountryCode.cfm"; Title="State and Country Codes"; AllQuery="allStateOrCountryCode"; OneQuery="oneStateOrCountryCode"; NotQuery="oneStateOrCountryCode"; AddQuery="addStateOrCountryCode"; DelQuery="delStateOrCountryCode"; UpdQuery="updStateOrCountryCode"; Field1="StateOrCountryCode"; Field1Max=3; Field2="Name"; Field2Max=25; FieldX="CountryFlag"; FieldXMax=1; FieldW=""; FieldWMax=0; </cfscript>
<cfquery name="#AllQuery#" datasource="context"> select * from #Content# order by #Field1# </cfquery>
<cfquery name="#OneQuery#" datasource="context"> select * from #Content# where #url.KeyName# = '#urlDecode(url.Key)#' </cfquery>
<!--- This pretends to be OneQuery but is empty ---> <cfquery name="#OneQuery#" datasource="context"> select * from #Content# where 1 = 0 </cfquery>
<cftry>
<cfquery name="#DelQuery#" datasource="context">
delete *
from #Content#
where #url.KeyName# = '#urlDecode(url.Key)#'
</cfquery>
<cfcatch>
<cfset Temp="../common/OneForm.cfm?">
<cfset Temp=Temp&"Context=#form.Context#">
<cfset Temp=Temp&"&Mode=Add">
<cfset Message=urlEncodedFormat("#urlDecode(url.Key)# not deleted: reason unknown")>
<cfset Temp=Temp&"&Message=#Message#">
<cflocation url=#Temp#>
</cfcatch>
</cftry>
<cftry>
<cfquery name="#UpdQuery#" datasource="context">
update #Content# set
#Field1# = '#trim(evaluate(Field1))#'
<cfif Field2Max>
, #Field2# = '#trim(evaluate(Field2))#'
</cfif>
<cfif FieldWMax>
, #FieldW# = '#trim(evaluate(FieldW))#'
</cfif>
<cfif FieldXMax>
<cfset FX="form.#FieldX#">
<cfif len(trim(evaluate(FX)))>
, #FieldX# = 'X'
<cfelse>
, #FieldX# = ''
</cfif>
</cfif>
where #Field1# = '#evaluate(Field1)#'
</cfquery>
<cfcatch>
<cfset Temp="../common/OneForm.cfm?">
<cfset Temp=Temp&"Context=#form.Context#">
<cfset Temp=Temp&"&Mode=Add">
<cfset Message=urlEncodedFormat("#evaluate(Field1)# unchanged for reasons unknown")>
<cfset Temp=Temp&"&Message=#Message#">
<cflocation url=#Temp#>
</cfcatch>
</cftry>
<cftry>
<cfquery name="AddQuery" datasource="context">
insert into #Content# (
#Field1#
<cfif Field2Max>
, #Field2#
</cfif>
<cfif FieldWMax>
, #FieldW#
</cfif>
<cfif FieldXMax>
, #FieldX#
</cfif>
)
values (
<cfset F1="form.#Field1#">
'#trim(evaluate(F1))#'
<cfif Field2Max>
<cfset F2="form.#Field2#">
, '#trim(evaluate(F2))#'
</cfif>
<cfif FieldWMax>
<cfset F3="form.#FieldW#">
, '#trim(evaluate(F3))#'
</cfif>
<cfif FieldXMax>
<cfset FX="form.#FieldX#">
<cfif len(trim(evaluate(FX)))>
, 'X'
<cfelse>
, ''
</cfif>
</cfif>
)
</cfquery>
<cfcatch>
<cfset Temp="OneForm.cfm?">
<cfset Temp=Temp&"Content=#form.Content#">
<cfset Temp=Temp&"&Mode=Add">
<cfset Message=urlEncodedFormat("#evaluate(Field1)# not added; probable duplicate")>
<cfset Temp=Temp&"&Message=#Message#">
<cflocation url=#Temp#>
</cfcatch>
</cftry>
If the value of "Content" is provided in the URL (as it will be if this page passes it to itself), then pretend it came from a form for consistency (as it would if it had come from the add/edit page). What you might not expect is that you can define a new form variable for this page even though the page has no form of its own. The variable won't be treated as a real form variable would; because, it won't be passed out of the page, but it's close enough for our purpose here. Use Content to get the name of the page that contains the table description and include the description. Display the title from the description page.
<cfif not isDefined("form.Content")>
<cfif not isDefined("url.Content") and len(url.Content)>
Content must be provided: e.g., filename?Content={something}
<cfabort>
</cfif>
<cfset form.Content=url.Content>
</cfif>
<cfset Script="#form.Content#"&".cfm">
<cfinclude template="#script#">
<h3><cfoutput>#Title#</cfoutput></h3>
If the function to be performed is to add a record (based on input from the add/edit form), then include the add query page to add the record. When control returns from the add query page, compose a "success" message and go back to the add/edit page where the message will be displayed. (Remember that if there is a failure, control doesn't return here; the add query page composes its own message and changes directly to the add/edit page.)
<cfif isDefined("form.Mode") and (form.Mode is "Add")>
<cfinclude template="AddOne.cfm">
<cfset Temp="OneForm.cfm?">
<cfset Temp=Temp&"Content=#form.Content#">
<cfset Temp=Temp&"&Mode=Add">
<cfset Message=urlEncodedFormat("#evaluate(Field1)# has been added")>
<cfset Temp=Temp&"&Message=#Message#">
<cflocation url=#Temp#>
</cfif>
If the function to be performed is to edit a record (based on input from the add/edit form), then include the update query page. When control returns from the update query page, compose a "success" message and go back to the add/edit page where the message will be displayed. (Remember that if there is a failure, control doesn't return here; the update query page composes its own message and changes directly to the add/edit page.)
<cfif isDefined("form.Mode") and (form.Mode is "Edit")>
<cfinclude template="UpdOne.cfm">
<cfset Temp="OneForm.cfm?">
<cfset Temp=Temp&"Content=#form.Content#">
<cfset Temp=Temp&"&Mode=Add">
<cfset Message=urlEncodedFormat("#evaluate(Field1)# has been changed")>
<cfset Temp=Temp&"&Message=#Message#">
<cflocation url=#Temp#>
</cfif>
If the function to be performed is to edit a record (based on a link from THIS page), then include the delete query page. If there is a failure, the delete query page will go directly to the add/edit page with a message. Otherwise, change the URL mode to something useless to avoid confusion, and let the rest of this page do its work.
<cfif isDefined("url.Mode") and (url.Mode is "Del")>
<cfinclude template="DelOne.cfm">
<cfset url.Mode="x">
</cfif>
Now display an add link, an edit link, and the table headers for the list. The add link tells the add/edit page what Content to use and tells it to work in Edit mode. The exit link merely returns to the menu. Assume that the key (Field1) exists. To avoid database integrity problems when a valid values table is considered in the context of how it will be used, you won't let the user edit the key. So, if the key field is the only field - the combined length of the other fields is zero - don't display the edit column. Otherwise, do display it. Display of the columns for the other fields is field-dependent. If the field description says the field has length, then display the column. Finish with a delete column.
<a href="OneForm.cfm?<cfoutput>Content=#form.Content#&Mode=Add</cfoutput>">Add</a> <a href="Menu.cfm">Exit</a> <cfinclude template="AllOne.cfm"> <table border=1> <cfoutput><tr> <cfif evaluate(Field2Max+FieldWMax+FieldXMax)> <td>EDIT</td> </cfif> <td>#Field1#</td> <cfif Field2Max> <td>#Field2#</td> </cfif> <cfif FieldWMax> <td>#FieldW#</td> </cfif> <cfif FieldXMax> <td>X if #FieldX#</td> </cfif> <td>DELETE</td> </tr></cfoutput>
Here's the list. Include the query on which the list will be based. As above, if a non-key field will be used, then an edit link will be needed. The edit link brings the user to the add/edit page, indicates edit mode, and supplies the key name and key value to be used. Assume Field1 will be used. If the length of other fields in the description is not zero, use the evaluate function to expose their content. The delete link comes back to this page in delete mode with the key name and value so the delete code above will be invoked with the proper parameters.
<cfoutput query="#AllQuery#"> <cfif evaluate(Field2Max+FieldWMax+FieldXMax)> <td><a href="OneForm.cfm?Content=#form.Content#&Mode=Edit&KeyName=#Field1#&Key=#urlEncodedFormat(evaluate(Field1))#">Edit</a></td> </cfif> <td>#evaluate(Field1)#</td> <cfif Field2Max> <td>#evaluate(Field2)#</td> </cfif> <cfif FieldWMax> <td>#evaluate(FieldW)#</td> </cfif> <cfif FieldXMax> <td>#evaluate(FieldX)#</td> </cfif> <td><a href="OneForm2.cfm?Content=#form.Content#&Mode=Del&KeyName=#Field1#&Key=#urlEncodedFormat(evaluate(Field1))#">Del</a></td> </tr> </cfoutput> </table>
This page needs Content and Mode. If the value of "Content" is provided in the URL, then pretend it came from a form for simplicity. Do the same for the Mode. Use Content to determine the name of the file containing the table description. Display the title from the description page.
<cfif not isDefined("form.Content")>
<cfif not isDefined("url.Content") and len(url.Content)>
Content must be provided: e.g., filename?Content={something}
<cfabort>
</cfif>
<cfset form.Content=url.Content>
</cfif>
<cfif not isDefined("form.Mode")>
<cfif not isDefined("url.Mode") and len(url.Mode)>
Mode must be provided.
<cfabort>
</cfif>
<cfset form.Mode=url.Mode>
</cfif>
<cfset Script="#form.Content#"&".cfm">
<cfinclude template="#script#">
<h3><cfoutput>#Title#</cfoutput></h3>
A message passed to this page indicates if the attempt to add or edit a record succeeded or failed. Success is routine. However, a message indicating failure should be formatted to catch the user's attention. Use a regular expression in the reFindNoCase function to look for a word beginning with "no" or "not". If the message contains this expression, give it more importance on the page.
The mode makes a difference. If the form is being used to add a record, then you want empty variables to be associated with the query. If it is being used to edit, you want those variables to come from the selected record. Include NotOne.cfm for a query that will return no rows but otherwise is named for the real query. Include OneOne when you want real values for this query.
<cfparam name="url.Message" default="">
<cfif reFindNoCase(" no[t]? ", Message)>
<!--- Message contains " no " or " not " --->
<h2><cfoutput>#url.Message#</cfoutput></h2>
<cfelse>
<h3><cfoutput>#url.Message#</cfoutput></h3>
</cfif>
<cfif form.Mode is "Add">
<cfinclude template="NotOne.cfm">
<cfelse>
<cfinclude template="OneOne.cfm">
</cfif>
For each field that has a length on the description page, the name of the variable containing its value has to be constructed from two separate halves: the name of the query and the name of the field. The name of the field must also be displayed. By performing both tasks at once, the same set of cfif statements can be used for both.
<table border=1> <cfset Field1Source="#OneQuery#.#Field1#"> <cfoutput><tr><td>#Field1#</td> <cfif Field2Max> <cfset Field2Source="#OneQuery#.#Field2#"> <td>#Field2#</td> </cfif> <cfif FieldWMax> <cfset FieldWSource="#OneQuery#.#FieldW#"> <td>#FieldW#</td> </cfif> <cfif FieldXMax> <cfset FieldXSource="#OneQuery#.#FieldX#"> <td>X if #FieldX#</td> </cfif> </tr></cfoutput>
Make the edit/add form post to the list page for its action. If the mode is edit, display Field1 (instead of letting the user change it) and use a hidden field to carry its value. Otherwise, use the cfinput tag. Set its required attribute to yes and provide a message; so, ColdFusion will write javascript that displays the message if the user tries to submit the field when it's empty. The maxlength attribute should be a little larger than the size attribute for most fonts; so, add 2 to the length given in the description and use the result as the maxlength for the field. Open additional fields for entry if the length in their description indicates that they are used. FieldX works the same way as Field1 and Field2 for data entry. The insert and update pages will replace any character entered for Field X with a capital X, but that makes no difference to this form.
<cfform action="OneForm2.cfm" name="OneForm" method="post"> <tr><td> <cfif form.Mode is "Add"> <cfinput name="#Field1#" type="text" maxlength="#Field1Max#" size="#evaluate(Field1Max+2)#" required="yes" message="#Field1# cannot be empty" value=#trim(evaluate(Field1Source))#> <cfelse> <cfoutput>#evaluate(Field1Source)#</cfoutput> <input type="hidden" name=<cfoutput>"#Field1#"</cfoutput> value=<cfoutput>#evaluate(Field1Source)#</cfoutput>> </cfif> </td> <cfif Field2Max> <td><cfinput name="#Field2#" type="text" maxlength="#Field2Max#" size="#evaluate(Field2Max+2)#" required="yes" message="#Field2# cannot be empty" value=#trim(evaluate(Field2Source))#></td> </cfif> <cfif FieldXMax> <td><cfinput name="#FieldX#" type="text" maxlength="1" size="2" value=#trim(evaluate(FieldXSource))#></td> </cfif>
The textarea tag will hold as much data as the browser version will let it. It is not a ColdFusion tag; so, wrap ColdFusion variables in a cfoutput tag to use them. For some applications, you would want to use physical wrap to preserve carriage returns. In this application, however, it's more appropriate to use virtual wrap so the result becomes one long string that other displays can eventually format differently if desired. A window 60 colums wide by 4 rows tall should be enough in this context.
<cfif FieldWMax> <td><textarea name=<cfoutput>"#FieldW#"</cfoutput> wrap="virtual" name="comments" cols="60" rows="4"><cfoutput>#evaluate(FieldWSource)#</cfoutput></textarea> </td> </cfif> </tr> </table>
The action page will need the Content to select the right table description and the mode to know what to do with the data. When adding or editing, they are passed in hidden form fields. If the user wants to list records, the List button changes the location to the list page and provides these fields in a URL. If the user wants to exit, the Exit button changes the location to the menu.
<input name="Content" type="hidden" value=<cfoutput>"#form.Content#"</cfoutput>> <input name="Mode" type="submit" value=<cfoutput>"#form.Mode#"</cfoutput>> <input name="List" type="submit" value="List" onClick="javascript:document.location='OneForm2.cfm?<cfoutput>Content=#form.Content#</cfoutput>'; return false;"> <input name="Exit" type="button" value="Exit" onClick="javascript:document.location='Menu.cfm'; return false;"> </cfform>