This page,
, is the most complex of the application, yet demonstrates another useful way of minimizing load on your server under high usage conditions. However, let's work through from the top and see how the page is built.webfinance.htm
The page heading containing the car model (and color if selected) is created in the same way as in the Details page. Following it is a set of HTML 'form' controls. They aren’t actually on a
because we won't be submitting anything. (If you build a page like this in Navigator you still have to include the <FORM>
tags or the controls won’t be displayed). Here's the result:<FORM>
And here's the HTML code for the controls section (the part below the first horizontal rule):
...
<HR>
<INPUT TYPE=RADIO ID="chkNumberMonths" NAME="CheckGroup" CHECKED>
I would like to pay $
<INPUT TYPE=TEXT ID="txtNumberMonths" VALUE="500" SIZE=3 MAXLENGTH=3>
each month.
<BR>
<INPUT TYPE=RADIO ID="chkMonthlyAmount" NAME="CheckGroup">
I would like to pay the total cost over
<INPUT TYPE=TEXT ID="txtMonthlyAmount" VALUE="36" SIZE=2 MAXLENGTH=3>
months.
<DIV ID="divResults">
Select the payment options you prefer and click <B>Calculate</B>.
</DIV>
<DIV ID="divInstructions">
You can accept or reject the results or try different payment options.
</DIV>
<HR>
<DIV STYLE="text-align:right; width:90%">
<INPUT TYPE="BUTTON" ID="cmdCalculate" VALUE="Calculate"
ONCLICK="calculateResults()"
TITLE="Calculate the options selected above">
<INPUT TYPE="BUTTON" ID="cmdAccept" VALUE="Accept"
DISABLED ONCLICK="acceptResults()"
TITLE="Accept the results displayed above">
<INPUT TYPE="BUTTON" ID="cmdCancel" VALUE="Cancel"
DISABLED ONCLICK="clearResults()"
TITLE="Reject the results displayed above">
</DIV>
...
Notice that we give the two
buttons the same RADIO
(so that only one can be selected at a time), but different NAME
s. We can easily use the ID
to tell which one was selected.ID
Calculating Finance Packages
The first part of the script in the page declares some variables that will be global just for this page, and then the
procedure that runs when the Calculate button is clicked: calculateResults
<SCRIPT LANGUAGE="VBScript">
'variable to hold last quotation.
'accepted quotes are stored in frameset page variables
intNumberMonths = 0
curMonthlyPayment = 0
curTotalPrice = 0
Sub calculateResults()
'runs when Calculate button is clicked
document.all("cmdAccept").Disabled = True 'disable the Accept button
document.all("cmdCancel").Disabled = True 'disable the Cancel button
If document.all("chkNumberMonths").checked Then
'calculate number of months to pay given payment amount
strResult = calculateMonths()
Else
'calculate monthly payment given number of months
strResult = calculatePayment()
End If
If InStr(strResult, "Sorry") > 0 Then 'couldn't calculate a result
strResult = "<B>Error</B>: " & strResult
Else
strResult = "<B>Quotation</B>: " & strResult 'quote succeeded
document.all("cmdAccept").Disabled = False 'enable Accept button
document.all("cmdCancel").Disabled = False 'enable Cancel button
End If
document.all("divResults").innerHTML = strResult 'display the results
End Sub
All that
does is check which option was selected, and call the appropriate procedure to return the result as a string. If the string contains the word 'Sorry' we know that the calculation could not be completed. At the same time, the code enables and disables the Accept and Cancel buttons depending on the outcome of the calculation.calculateResults
The two types of calculation involve two very different client-side techniques. The first of these is the one we originally saw back in Chapter 2. We provide a custom ActiveX control named
on our server which is downloaded and installed on the client when the page is opened.WCCFinance
The Client-side WCCFinance ActiveX Control
The
control is inserted into the client page using an WCCFinance
tag. The first time that the user opens this page the control is downloaded from our Web site. Any supporting files that it requires (such as the VB5 runtime library) are downloaded directly from the special Microsoft site designed for just this purpose—the VB5 Application Setup program looks after all these details automatically. After the user has visited the page once, the control will be available directly on their machine for subsequent visits so there will be no delays:<OBJECT>
<!-- our custom ActiveX control -->
<OBJECT ID="FinanceObject"
CLASSID="CLSID:CBFC0DA5-A499-11D1-882B-00201834E2A6"
CODEBASE="WCCFinance.cab#version=1,0,0,0">
</OBJECT>
The
routine that uses the control is similar to the code we saw in Chapter 2, but now performs some extra error checking:calculateMonths
Function calculateMonths()
'calculate number months given payment amount with custom control
On Error Resume Next
Set objFinance = document.all("FinanceObject") 'instantiate control
If Not IsObject(objFinance) Then 'couldn't instantiate
strMesg = "This calculation requires an ActiveX control that "
& "has not been properly installed" ..etc.
Msgbox strMesg, vbInformation, "Finance Control Error"
calculateMonths = "Sorry, cannot perform calculation."
Exit Function
End If
curPaymentValue = document.all("txtNumberMonths").Value
'check value is a number
If Not IsNumeric(curPaymentValue) Then
strMesg = "You must enter a number for the monthly amount."
Msgbox strMesg, vbInformation, "Data Entry Error"
document.all("txtNumberMonths").focus
document.all("txtNumberMonths").select
calculateMonths = "Sorry, cannot perform calculation."
Exit Function
End If
objFinance.TotalPrice = top.arrCars(5, intCarIndex) 'price of car
objFinance.InterestRate = top.interestRate / 12 'interest rate
objFinance.MonthlyPayment = curPaymentValue 'payment amount
intCalcMonths = objFinance.GetNumberPayments 'calculate result
If intCalcMonths > 0 Then 'control returns number > 0 if succeeded
'update page-level 'last quote' variables
intNumberMonths = intCalcMonths
curMonthlyPayment = curPaymentValue
curTotalPrice = curMonthlyPayment * intNumberMonths
strResult = "<B>" & intNumberMonths & "</B> payments of <B>" _
& FormatCurrency(curMonthlyPayment, 0) & "</B>. " _
& "Total payment of <B>" _
& FormatCurrency(curTotalPrice, 0) & "</B>."
Else
strResult = "Sorry, this is insufficient to meet the interest costs."
End If
calculateMonths = strResult
End Function
Once the values have been calculated, we update the local variables in this page (not the global variables in the main frameset) and return the results as a formatted string. If we can’t calculate a result we return a message including the word 'Sorry'. As you saw in the previous section, the return string is displayed in the page and the state of the buttons updated by the
procedure. calculateResults
The RDS Tabular Data Control
The second finance option is to specify the number of months to pay off the debt, and have the page calculate the payment. This isn’t a trivial task, and requires use of the standard
financial function—something that is not supported in VBScript or JavaScript.PMT
Instead, we've used Excel to create a simple comma-delimited text file containing the payments required for a $1000 loan over periods ranging from 6 months to 10 years at the interest rate (7.25%) hard-coded into the frameset page
. This text file, wroxcars.asp
, is on our server within the application directory: finance.txt
Months,Payment
6,171.19
7,147.30
8,129.37
9,115.44
10,104.29
11,95.17
12,87.57
...
117,13.00
118,12.94
119,12.87
120,12.80
In our application, we used a hard-coded interest rate for simplicity. There is no reason why you can’t store the rate on the server and retrieve it when you create the frameset page. However you will also have to provide a table (as shown above) for that rate, or modify the code to read from a multi-column table that contains all possible rates. As they say in all the best books, this is left for you to experiment with.
To get the data into our application, we use Remote Data Services (RDS) as we did in the Chapter 2 example. However we have a simple text file rather than a database table, and so we can use the light-weight Simple Tabular Data Control (TDC) on the client, instead of the Advanced Data Control (ADC) we used earlier. This loads the entire text file from the server, caches it on the client, and accesses it there. The whole process only requires one connection to upload the relatively small file, and never needs to go back to the server again—just what we want to happen.
The TDC is inserted with an
tag, and we also include a hidden bound control to make sure it retrieves the data file (sometimes it may not retrieve the data file if there are no bound controls on the page, although this is more likely to happen with the ADC rather than the TDC):<OBJECT>
<!-- the RDS Tabular Data Control -->
<OBJECT ID="FinanceData" WIDTH=1 HEIGHT=1
CLASSID="CLSID:333C7BC4-460F-11D0-BC04-0080C7055A83">
<PARAM NAME="FieldDelim" VALUE=",">
<PARAM NAME="DataURL" VALUE="finance.txt">
<PARAM NAME="UseHeader" VALUE=True>
</OBJECT>
<!-- may need a bound control to get TDC to retrieve records -->
<INPUT TYPE="TEXT" DATASRC="#FinanceData" DATAFLD="Months"
STYLE="visibility:hidden">
The routine that uses the TDC is fundamentally similar to the
routine, except that it also checks that the value for the number of months is within the range of the values in the text file. The following code is the WCCFinance
function in our Finance page that is called if the user specifies the number of months they want to spread the payments over:calculatePayments
Function calculatePayment ()
'calculate monthly payment given number of months using RDS text file
On Error Resume Next
intCalcMonths = document.all("txtMonthlyAmount").Value
'check value is a number
If Not IsNumeric(intCalcMonths) Then
... { create error message here }
Exit Function
End If
'check value is in range for values in text file
If (intCalcMonths < 6) Or (intCalcMonths > 120) Then
... { create error message here }
Exit Function
End If
'create local recordset from TDC control to get the payments list
Set oRs = document.all("FinanceData").Recordset
If Not IsObject(oRs) Then 'couldn't create the control
strMesg = "This calculation uses the ActiveX Tabular Data Control, "_
& "which has not been properly installed on your system."
Msgbox strMesg, vbInformation, "Finance Data Error"
calculatePayment = "Sorry, cannot perform calculation."
Exit Function
End If
...
Once we're sure we can do the calculation, we open the TDC's recordset and loop through the records looking for the number of months entered by the user, then collect the payment for a $1000 loan over that period. Afterwards it's just a matter of multiplying this up to match the price of the car, building the return string, and updating the page-level 'last quote' variables:
...
'loop through the records looking for required number of months
oRs.MoveFirst
curPaymentValue = 0
Do While Not oRs.EOF
If oRs("Months") = intCalcMonths Then
curPaymentValue = oRs("Payment")
End If
oRs.MoveNext
Loop
If curPaymentValue > 0 Then 'will be zero if not found
intNumberMonths = intCalcMonths
curMonthlyPayment = curPaymentValue _
* top.arrCars(5, intCarIndex) / 1000
curTotalPrice = curMonthlyPayment * intNumberMonths
strResult = "<B>" & intNumberMonths & "</B> monthly payments of <B>" _
& FormatCurrency(curMonthlyPayment, 0) & "</B>. " _
& "Total payment of <B>" _
& FormatCurrency(curTotalPrice, 0) & "</B>."
Else
strResult = "Sorry, cannot calculate for this number of months."
End If
calculatePayment = strResult
End Function
Accepting A Quote
Once we've done a successful quote, we allow the user to accept it by clicking the Accept button. This runs the
code, which updates the global variables in the frameset page, inserts a message into this page, and sets the state of the buttons to allow the user to cancel the quote if required:acceptResults
Sub acceptResults()
'update the global variables in the frameset page
top.financePayment = curMonthlyPayment
top.financeMonths = intNumberMonths
top.financeTotal = curTotalPrice
'enable/disable buttons as appropriate
document.all("cmdAccept").Disabled = True
document.all("cmdCancel").Disabled = False
'and display accepted quote
strResult = "<B>Accepted</B>: " _
& Mid(document.all("divResults").innerHTML, 19)
document.all("divInstructions").innerHTML = strResult
End Sub
Here's the result when they accept a quote:
Rejecting A Quote
To reject a quote we set the global frameset variables back to zero, disable both the Accept and Cancel buttons, and display the instructions again:
Sub clearResults()
'zero the global variables in the frameset page
top.financePayment = 0
top.financeMonths = 0
top.financeTotal = 0
'disable the buttons
document.all("cmdAccept").Disabled = True
document.all("cmdCancel").Disabled = True
'remove last quote and last accepted quote
strResult = "Select the payment options you prefer ..." etc.
document.all("divResults").innerHTML = strResult
strResult = "You can accept or reject the results or ..." etc.
document.all("divInstructions").innerHTML = strResult
End Sub