|
<i><b>Originally posted by : tony (tbell@bell-consulting.com)</b></i><br />This is what I found. You'll have to edit it to fit you needs.<br />To see this in a better format please email me personally and I can send you the code and documentation in a word file.<br /><br />Tony<br /><br />Build dynamically expanding order entry pages<br />by Brad Wist <br />If you've read about the W3C's plans for the future of HTML forms, then you know that dynamically expanding rows are soon to be part of XHTML. However, with Internet time the way it is, you probably don't have the luxury of waiting half a year for the W3C to implement XForms. Fortunately, if your work is intranet-based, you can use VBScript to create a very flexible and extensible data entry system. In this article, we'll show you how to build an order entry page in which the rows grow to accommodate any amount of data. <br />Fixed row dilemma<br />Figure A shows a classic order entry page. <br />Figure A: We'll use DHTML to add a new row to the existing page. <br /><br />As you can see, a single buyer can enter in many orders. Most database programs such as Microsoft Access and SQL Server make setting up such an input form a breeze. However, in an ASP page, things get a little trickier. For instance, we could build a page with 10 lines, for 10 separate orders. But what happens if the wholesaler wants to order 11 items? The page would have to force them to a second order form, and then somehow tie the two pages together. While this is possible, a better alternative is to allow the form to dynamically grow, as required, so that it will hold as many lines as the buyer needs. <br />Expanding rows<br />In a nutshell, we'll create a server-side variable that contains an HTML template for a single row. We'll then use the InnerHTML method to dynamically add this blank row to a <span> area. Of course, each new row will need an ID number so we can differentiate it in the client-side script. We'll use the Replace method to insert the appropriate row number in place of a custom identifier in the HTML row template. <br />Server-side data retrieval<br />As our first step, we'll need to retrieve buyer and product data from our database for use in the Web page. Listing A shows the ASP code for this task. We've provided the OrderEntry MDB file in this month's download. <br />Listing A: Server-side data retrieval <br /><%@ Language=VBScript %><br /><%<br />option explicit<br />Response.Expires=0<br />dim sTable 'Name of the Main Table<br />dim aSubTables 'Array of Subtable Names<br />dim iSubCount 'Number of subtables<br />sTable = "Order"<br />aSubTables = Array("orderItem")<br />'----------<br />' Get the Number of Subtables for use later<br />' if there aren't any subtables, the ubound <br />' function will cause an error<br />'----------<br />on error resume next<br />iSubCount = ubound(aSubTables)<br />if Err.number<>0 then<br /> iSubCount=-1<br /> Err.Clear<br />end if<br />on error goto 0<br />%><br /><!-- #INCLUDE FILE="adovbs.inc" --><br /><% 'INCLUDE contains ADO constants<br />'----------------------------------------<br />' Pull the Lookup information from the DB<br />'----------------------------------------<br />dim adConn, rsShopper, rsProduct, sSQL, QQ, i<br /><br />QQ = chr(34)<br />set adConn = server.CreateObject("ADODB.Connection")<br />adConn.Open "Provider=Microsoft.Jet.OLEDB.3.51;" _<br /> & "Data Source=" & server.mappath("orderentry.mdb")<br />sSQL = "SELECT id, txtLastName, txtFirstName, " _<br /> & "txtCompany FROM buyers ORDER BY txtLastName, " _<br /> & "txtFirstName"<br />set rsShopper = server.CreateObject("ADODB.recordset")<br />rsShopper.Open sSql, adConn, adOpenForwardOnly, _<br /> adLockReadOnly, adcmdText<br />sSQL = "SELECT prodID, txtProdName, txtProdDesc, " _<br /> & "amPrice FROM product"<br />set rsProduct=server.CreateObject("ADODB.Recordset")<br />rsProduct.Open ssql, adconn, adOpenStatic, _<br /> adLockReadOnly, adCmdText<br />In addition, the server-side script creates the blank-row template, and adds the data from the database. Listing B shows the code for this operation. As you can see, this script defines the sServerRow variable. It builds a table row cell by cell. It also includes a select box, which holds a list of products. The value of each product in the select box consists of its ID along with its cost, which are separated by a dollar sign ($). <br />Listing B: Creating the HTML row template <br />'----------------------------------------<br />' Definition of the Dynamic Row<br />'----------------------------------------<br />dim sServerRow<br />sServerRow = "<table width='100%'><tr><td width='5%'>" _<br /> & ":#</td><td width='5%'><input name='_1qyProduct' " _<br /> & "maxlength='5' size='5' language='vbscript' " _<br /> & "onchange='DoTotal' value='0'></td><td width='50%'>" _<br /> & "<select name='_1idProduct' language='vbscript' " _<br /> & "onchange='DoPrice(:#)'>"<br />sServerRow = sServerRow & "<option value='0$0'>" _<br /> & "SELECT PRODUCT"<br />Do While Not rsProduct.EOF<br /> sServerRow = sServerRow & "<option value='" _<br /> & rsProduct("prodID") & "$" & rsProduct("amPrice") _<br /> & "'>" & rsProduct("txtProdName") & " (" _<br /> & rsProduct("txtProdDesc") & ")"<br /> rsProduct.MoveNext<br />Loop<br />sServerRow = sServerRow & "</select></td>" _<br /> & "<td width='20%'><input name='_1amPrice' " _<br /> & "maxlength='10' size='10'></td><td width='20%'>" _<br /> & "<input name='_1amCost' maxlength=10 size=10>" _<br /> & "</td></tr></table>"<br />%><br />Notice that we prefixed the name of each field in the row template with an underscore and a one (_1). This naming convention indicates that these fields belong to the first subtable on this page. <br />The basic HTML body<br />Listing C shows our Web page's <body> section. If you study the common area, you'll see that the page stores the table name and the number of subtables in the HTML form so that server-side script can enter the data into the database. However, that functionality is beyond the scope of this article, and we'll cover it in a future article. <br />Listing C: The order entry page's body section <br /><body><br /><form action="inputData.asp" id="frmInput" <br /> name="frmInput" method=post><br /><!-- ===== Start Common Area ===== --><br /><input type=hidden name="_!table" value="<%=sTable%>"><br /><%<br />if iSubCount >= 0 then<br /> For i = 0 to iSubCount<br />%><br /><input type="hidden" id="_!subtable<%= cstr(i+1) %>"<br /> value="<%=aSubTables(i)%>"><br /><%<br /> Next<br />End If<br />%><br /><!-- ===== End Common Area ===== --><br /><table><tr><td>Buyer:</td><br /> <td><select name="idShopper" onchange="DoReset"><br /><% <br /> Do While Not rsShopper.EOF<br /> Response.Write "<option VALUE=" & QQ _<br /> & rsShopper("id") & QQ & ">" _<br /> & rsShopper("txtLastName") & ", " _<br /> & rsShopper("txtFirstName") & "| " _<br /> & rsShopper("txtCompany")<br /> rsShopper.MoveNext<br /> Loop<br />rsShopper.close<br />rsProduct.close<br />set rsShopper=Nothing<br />set rsProduct=Nothing<br />set adConn = Nothing<br />%> </select></td></tr><br /></table><br /><br /><!-- ===== Start Product Area ===== --><br /><span id="AddRow" language="vbscript" onclick="AddRow"<br /> onmouseover="window.event.srcElement.style<br /> =>.cursor = 'hand'"<br /> onmouseout="window.event.srcElement.style<br /> =>.cursor=''"><h3>Add Row</h3></span><br /><table width=100%><br /><tr><th width=5%>#</th><br /> <th width=5%>Qty</th><br /> <th width=50%><p align=left>Product</th><br /> <th width=20%><p align=left>Price</th><br /> <th width=20%><p align=left>Cost</th></tr><br /></table><br /><span id="subtable"><%=replace(sServerRow,":#","1")%><br /></span><br /><!-- ===== End Product Area ===== --><br /><br /><!-- ===== Start Total Area ===== --><br /><table width="100%"><br /><tr><td width="80%"><p align="right">SubTotal: <br /> </p></td><br /> <td width="20%"><input name="amSubTotal" <br /> maxlength="10" size="10"></td></tr><br /><tr><td width="80%"><p align="right">Shipping: <br /> </p></td><br /> <td width="20%"><input name="amShipping" <br /> maxlength="10" size=10></td></tr><br /><tr><td width="80%"><p align="right">Tax: <br /> </p></td><br /> <td width="20%"><input name="amTaxes" <br /> maxlength="10" size="10"></td></tr><br /><tr><td width="80%"><p align="right">Total: <br /> </p></td><br /> <td width="20%"><input name="amTotal" maxlength="10"<br /> size="10"></td></tr></table><br /><!-- ===== End Total Area ===== --><br /><input type="submit" value="Submit Order"> <br /><input type="button" id="btnReset" onclick="DoReset"<br /> value="Reset"></form><br /></body><br />Notice that in the product area, we used the span's onmouseover and onmouseout events to change the cursor, providing a visible clue to the user that he can click this link. In addition, the span's click() event calls a client-side script routine called AddRow that adds a new HTML row template. Let's add the client-side script next. <br />Adding rows to the page<br />As we mentioned, to dynamically expand the page, adding order entry lines on the sheet as necessary, we'll use Dynamic HTML. Specifically, we'll expand subtable's innerHTML property with a new blank row. Listing D shows the code for this operation. Add it to the page's <head> section. <br />Listing D: The AddRow sub procedure <br /><script language="vbscript"><br />dim giRowNum, sRowOrig<br />sRowOrig = "<% =sServerRow %>"<br />giRowNum = 1<br /><br />Sub AddRow<br />on error resume next<br />iLen = window.frmInput.elements("_1qyProduct") _<br /> .length<br />if iLen Then<br /> blnQuantity = (window.frmInput.elements _<br /> ("_1qyProduct")(iLen-1).value > 0)<br /> blnProduct = (window.frmInput.elements _<br /> ("_1idProduct")(iLen-1).value <> "0$0")<br />else<br /> blnQuantity = (window.frmInput.elements _<br /> ("_1qyProduct").value > 0)<br /> blnProduct = (window.frmInput.elements _<br /> ("_1idProduct").value <> "0$0")<br />end if<br /><br />If blnProduct and blnQuantity Then<br /> dim sLocalRow<br /> giRowNum = giRowNum + 1<br /> sLocalRow = "<%=sServerRow%>"<br /> window.subtable.innerHTML = window.subtable _<br /> .innerHTML & replace(sLocalRow,":#",giRowNum)<br />End If<br />End Sub<br /></script><br />After initializing several variables, this script tracks how many rows you've added. The value uniquely identifies each row on the page, so that calculations can be performed dynamically, as we'll discuss in the next section. Another local variable, sLocalRow, stores the HTML row to insert. The routine then uses the innerHTML property to add the new row. Notice that the VBScript Replace() function inserts the current row number into the variable marker :# in the HTML row template. The code also ensures that the two fields, _1qyProduct (quantity) and _1idProduct (product) contain data before adding a new row. <br />Extracting selected product prices<br />Now that we have the code to dynamically build the page, we need code to extract the selected item's price. To do so, we'll call the DoPrice subroutine from _1idProduct's onchange() event, as shown in Listing E. This routine takes a parameter, i, which identifies the row for which to extract the price information. <br />Listing E: The DoPrice script <br /><script language="vbscript"><br />sub DoPrice(i)<br />dim v, l<br />v = window.event.srcElement.getAttribute("value")<br />on error resume next<br />l = window.frmInput.elements("_1amPrice").length <br />if Err.number then<br /> 'there is only one line<br /> Err.Clear<br /> if left(v,1)="0" then<br /> window.frmInput.elements("_1amPrice").value = ""<br /> exit sub<br /> end if<br /> if instr(v, "$") then<br /> v = mid(v, instr(v, "$")+1)<br /> window.frmInput.elements("_1amPrice").value = v<br /> if window.frmInput.elements("_1qyProduct") _<br /> .value = 0 then<br /> window.frmInput.elements("_1qyProduct") _<br /> .value = 1<br /> end if<br /> else<br /> window.frmInput.elements("_1amPrice").value = ""<br /> end if<br />else<br /> if left(v,1)="0" then<br /> window.frmInput.elements("_1amPrice")(i-1) _<br /> .value = ""<br /> exit sub<br /> end if<br /> if instr(v, "$") then<br /> v = mid(v, instr(v, "$")+1)<br /> window.frmInput.elements("_1amPrice")(i-1) _<br /> .value = v<br /> if window.frmInput.elements("_1qyProduct") _<br /> (i-1).value = 0 then<br /> window.frmInput.elements("_1qyProduct") _<br /> (i-1).value = 1<br /> end if <br /> else<br /> window.frmInput.elements("_1amPrice")(i-1) _<br /> .value = ""<br /> end if<br />end if<br />DoTotal<br />End Sub<br /></script><br />Initially, the code determines the value of the select box that triggered the event. Then, it checks if the form contains more than one row. To do so, the code checks the length of of the _1amPrice control collection. This property returns an error if the form contains only one _1amPrice text box. Otherwise, the property returns the number of text boxes in the collection. Once the code determines this number, it parses the current select box value and places it in the price text box. Next, the procedure initiates the DoTotal procedure, which calculates the subtotal and grand total. <br />Dynamic totals<br />Listing F shows the code to calculate totals on the fly. As you can see, it loops through the form's rows, calculating the appropriate values as it goes. <br />Listing F: The DoTotal procedure <br /><script language="vbscript"><br />sub DoTotal<br />dim i, j, LineTotal, SubTotal, Shipping, Tax<br />SubTotal=0<br />LineTotal=0<br />on error resume next<br />l = window.frmInput.elements("_1qyProduct").length<br />if Err.number then<br /> 'only one line<br /> LineTotal = csng(window.frmInput.elements<br /> ("_1qyProduct").value) * csng(window.frmInput _<br /> .elements("_1amPrice").value)<br /> window.frmInput.elements("_1amCost").value = LineTotal<br /> SubTotal = csng(SubTotal) + csng(LineTotal)<br /> Err.Clear<br />else<br /> for i = 0 to window.frmInput.elements("_1qyProduct") _<br /> .length-1<br /> LineTotal=csng(0)<br /> LineTotal = csng(window.frmInput.elements _<br /> ("_1qyProduct")(i).value) * csng(window.frmInput _<br /> .elements("_1amPrice")(i).value)<br /> window.frmInput.elements("_1amCost")(i) _<br /> .value = LineTotal<br /> SubTotal = SubTotal + LineTotal<br /> next<br />end if<br />window.frmInput.amSubTotal.value=clng(SubTotal*100 )/100<br />'apply shipping rules<br />Shipping = 5<br />window.frmInput.amShipping.value=clng(Shipping*100 )/100<br />'apply tax<br />Tax = SubTotal * 0.045<br />window.frmInput.amTaxes.value=clng(Tax*100)/100<br />'calculate total<br />window.frmInput.amTotal.value=clng( (csng(SubTotal) _<br /> + csng(Tax) + csng(Shipping)) *100)/100<br />end sub<br /></script><br />Resetting the page<br />Of course, in addition to adding rows to the page, we need a way to remove them, such as when a user changes from one buyer to another, or when he clicks the Reset button. To do so, we use a few simple lines of code <br />Sub DoReset<br />'*** Place code to save <br />' current data here!<br />window.subtable.innerHTML = _<br /> Replace(sRowOrig, ":#", "1")<br />giRowNum = 1<br />document.frmInput.reset<br />End Sub<br />Validating the data<br />Once you build the page, you'll need to adapt your form validation routines to take into account field collections. We adapted the ValidateDataTypes() function, which we covered in January 2000's article "Data validation strategies," to take such collections into account. We've included this updated validation code with this month's download. <br />Extending the order entry page<br />No doubt, you can see from our example that you can build a flexible, dynamic Web page that can expand as needed. In addition to the technique we've shown you in this article, you can expand this page even further to implement data entry Web pages that mimic those found in database programs<br /><br /><br /><br /><br />------------<br />KevinM at 5/15/2000 4:10:27 AM<br /><br />OK...<br />In a one to many db relationship in MS Access you woud typically have a main form and a continuous.cascading subform.<br />But how do you emaulate this on an asp page?<br />While it's easy enough to loop through the 'many' recordest and display them in a table, what's the best method in entering this data via an asp page and then retrieving it for updati g.<br />Do you have to do one line at a time or can you have a table with say 10 rows all with the text boxes with the same name.<br /><br />What I have at the moment is a table of 10 rows and each field ends in a number, i.e. in row one there is a field called Description1 and in the second row Description2 and so on up to 10. This doesn't seem the ideal way to do it although it does work. I would rather loop through one field name i.e . Description 10 times but am not sure if this would work and if each row would get entered correctly.<br /><br />I have also tried exporting the for from Access but it on displays the main form !<br /><br />Many Thanx<br /><br />Kevin M<br /><br />
|