It's easy to imagine how to implement creating the header with a form that has a field for each attribute of the overall transaction, but what happens next? The header form creates a single header. The detail table will have a single row for each line of detail. How many lines of detail do you let the vendor add at once? Hint: the answer should NOT be only one line!
Although the database will be updated once for each line of detail, it doesn't always make sense from a communications and human factors standpoint for the user to enter it that way. If you create a form that only permits the user to enter a single line of detail, the user must receive and commit the form through the Internet perhaps 80 times: once for each line. If the invoice fits on a single form, a single commit, a single set of page requests, will suffice. Using a single form for multiple lines of data causes less waiting time for the user and a lower workload for the Web server. For really big invoices, continuation pages could be added as needed. This example won't do continuation pages, but it will demonstrate construction of forms and queries that handle multiple detail lines in one "gulp". Along the way, it will use two different methods of contructing field names on the fly: cfoutput and the evaluate function.
A form intended to insert multiple rows of database detail has many similar groups of fields: one group for each row. To make this easier, the fields in the first group might be suffixed with "1", the fields in the second group might be suffixed with "2", and so forth. The field names might be similar to the column names in the database. Here's an sample page to populate a form of 5 lines. Call it OrdAddF.cfm. You can make the form larger by changing the maximum loop value. The numberFormat function gives you consistent leading zeroes to format the row number.
<form name="add" action="OrdAddQ.cfm" method="post"> OrderID: new <br> Vendor: <input type="text" name="Vendor" size="30" value=""> <table> <tr><td></td><td>ITEM</td> <td>QTY</td><td>PRICE</td></tr> <cfloop index="Row" from="1" to="5"> <tr><cfoutput><td>#numberFormat(Row,000)#</td> <td><input type="text" name="Item_#Row#" value="" size="30"></td> <td><input type="text" name="Qty_#Row#" value="" size="10"></td> <td><input type="text" name="Price_#Row#" value="" size="12"></td> </cfoutput></tr> </cfloop> </table> <input type="submit" name="doit" value="Add"> </form>
OrdAddQ.cfm goes through an intermediate step to translate the fields for each row into the field names needed by the database. Looping through the potential rows, it builds field names for each row and does an insert if there is data for the item field. Because the insert will fail if numeric values are missing, it forces them to zero if not specified by the user.
<cfset try=0>
<cfloop condition="try lt 4">
<cftry>
<cftransaction>
<cfset Qerr=0>
<cfset try=try+1>
<!--- Header --->
<cfset key="HeadID">
<cfset table="Head">
<cfinclude template="GetMax1.cfm">
<cfquery name="AddHead" datasource="context">
insert into Head (HeadID, Vendor)
values (<cfoutput>#ID1#, '#form.Vendor#'</cfoutput>)
</cfquery>
<!--- Detail --->
<cfloop index="Row" from="1" to="5">
<cfset dummy="form.Item_"Row#>
<cfset Titem=evaluate(dummy)>
<cfif len(Titem)>
<cfset dummy="form.Qty_"Row#>
<cfset TQty=evaluate(dummy)>
<cfif not len(TQty)>
<cfset TQty=0>
</cfif>
<cfset dummy="form.Price_"Row#>
<cfset TPrice=evaluate(dummy)>
<cfif not len(TPrice)>
<cfset TPrice=0>
</cfif>
<cfset key2="DetailID">
<cfset table2="Detail">
<cfinclude template="getMax2.cfm">
<cfquery name="AddDetail" datasource="context">
insert into Detail (HeadID, DetailID, Item, Qty, Price)
values (#ID1#, #ID2#, '#Titem#', #Tqty#, #Tprice#)
</cfquery>
</cfif>
</cfloop>
</cftransaction>
<!--- Exit here when OK --->
<cfbreak>
<!--- Retry up to max times --->
<cfcatch type="database">
<cfif try gt 3>
<cfset Qerr=2>
<cfset Qmsg="database failed three times with good input; retry later or notify maintenance">
<cfbreak>
</cfif>
</cfcatch>
</cftry>
</cfloop>
<cfif #Qerr#>
<cfoutput>MyQuery Error #Qerr#: #Qmsg#<br>
Press your back button to continue.</cfoutput>
<cfabort>
</cfif>
<cfset ref="OrdSeeQ.cfm?HeadID=#ID1#">
<cflocation URL="#ref#">
In order to insert table rows, it's helpful to be able to determine the next available key for the table. GetMax1.cfm does this for the header table and can be used for other projects. Here, key is "HeadID" and table is "Head".
<cfquery name="getMax1" datasource="context"> select max(#key#) as Max1 from #table# </cfquery> <cfif len(getMax1.Max1)> <cfset ID1=getMax1.Max1+1> <cfelse> <cfset ID1=1> </cfif>
GetMax2.cfm does this for the detail table and can also be used for other projects. Here, key2 is "DetailID" and table2 is "Detail". ID1 comes from GetMax1.cfm and is the value of HeadID corresponding to this overall order.
<cfquery name="getMax2" datasource="context"> select max(#key2#) as Max2 from #table2# where #key# = #ID1# </cfquery> <cfif len(getMax2.Max2)> <cfset ID2=getMax2.Max2+1> <cfelse> <cfset ID2=1> </cfif>
<cfquery name="SeeHead" datasource="context"> select * from Head where HeadID = #URL.HeadID# </cfquery> <cfquery name="SeeDetail" datasource="context"> select * from Detail where HeadID = #URL.HeadID# </cfquery> <cfinclude template="OrdChgF.cfm">
OrdChgF.cfm displays the appropriate records and accepts changes. The HeadID is carried from the URL into a hidden field in the form. The query is unrolled to provide a row of input fields for each row of detail.
<form name="chg" action="OrdChgQ.cfm" method="post"> <cfoutput query="SeeHead"> OrdID: #HeadID#<br> <input type="hidden" name="HeadID" value="#HeadID#"> Vendor: <input type="text" name="Vendor" size="30" value="#Vendor#"> </cfoutput> <table> <tr><td></td><td>ITEM</td> <td>QTY</td><td>PRICE</td></tr> <cfset Row="1"> <cfoutput query="SeeDetail"> <tr><td>#numberFormat(Row,000)#</td> <td><input type="text" name="Item_#Row#" value="#Item#" size="30"></td> <td><input type="text" name="Qty_#Row#" value="#Qty#" size="10"></td> <td><input type="text" name="Price_#Row#" value="#Price#" size="12"> <input type="hidden" name="DetailID_#Row#" value="#DetailID#"></td> </tr> <cfset Row=Row+1> </cfoutput> </table> <input type="submit" name="doit" value="Change"> </form>
It goes through an intermediate step to translate the fields for each row into the field names needed by the database. Looping through the potential rows, it builds the field names for each row (up to an arbitrary maximum) until it creates a field name that doesn't exist (indicating that no more rows are present to be considered).
<cfset try=0>
<cfloop condition="try lt 4">
<cftry>
<cftransaction>
<cfset Qerr=0>
<cfset try=try+1>
<!--- Header --->
<cfquery name="chgHead" datasource="context">
update Head set
Vendor = '#trim(form.Vendor)#'
where HeadID = #form.HeadID#
</cfquery>
<!--- Detail --->
<cfloop index="Row" from="1" to="100">
<cfset dummy="form.Item_"Row#>
<cfif not isDefined(dummy)>
<cfbreak>
</cfif>
<cfset Titem=evaluate(dummy)>
<cfif len(Titem)>
<cfset dummy="form.Qty_"Row#>
<cfset TQty=evaluate(dummy)>
<cfif not len(TQty)>
<cfset TQty=0>
</cfif>
<cfset dummy="form.Price_"Row#>
<cfset TPrice=evaluate(dummy)>
<cfif not len(TPrice)>
<cfset TPrice=0>
</cfif>
<cfset dummy="form.DetailID_"Row#>
<cfset TDetailID=evaluate(dummy)>
<cfquery name="ChgDetail" datasource="context">
update Detail set
Item = '#Titem#',
Qty = #Tqty#,
Price= #TPrice#
where HeadID = #form.HeadID#
and DetailID = #TDetailID#
</cfquery>
</cfif>
</cfloop>
</cftransaction>
<!--- Exit here when OK --->
<cfbreak>
<!--- Retry up to max times --->
<cfcatch type="database">
<cfif try gt 3>
<cfset Qerr=2>
<cfset Qmsg="database failed three times with good input; retry later or notify maintenance">
<cfbreak>
</cfif>
</cfcatch>
</cftry>
</cfloop>
<cfif #Qerr#>
<cfoutput>MyQuery Error #Qerr#: #Qmsg#<br>
Press your back button to continue.</cfoutput>
<cfabort>
</cfif>
<cfset ref="OrdSeeQ.cfm?HeadID=#form.HeadID#">
<cflocation URL="#ref#">
Once you've assembled this example, go to OrdAddF.cfm to add an order and then update it as desired. This example ends by calling OrdSeeQ.cfm to invite another update. In real life, you would add templates to list, process, archive, and otherwise manage these orders at the order level.
So don't stop here. Add javascript to catch obvious errors as the user edits each field. Add templates needed to manage the orders you receive. Then use forms with multiple detail lines to reduce your communications overhead while improving service to your customer. =Marty=