The Finance Details Page

This page,

webfinance.htm
, 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.

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

<FORM>
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:

And here's the HTML code for the controls section (the part below the first horizontal rule):

...

<HR>

&nbsp; <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>

&nbsp; <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

RADIO
buttons the same
NAME
(so that only one can be selected at a time), but different
ID
s. We can easily use the
ID
to tell which one was selected.

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

calculateResults
procedure that runs when the Calculate button is clicked:

<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

calculateResults
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.

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

WCCFinance
on our server which is downloaded and installed on the client when the page is opened.

The Client-side WCCFinance ActiveX Control

The

WCCFinance
control is inserted into the client page using an
<OBJECT>
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:

<!-- our custom ActiveX control -->

<OBJECT ID="FinanceObject"

CLASSID="CLSID:CBFC0DA5-A499-11D1-882B-00201834E2A6"

CODEBASE="WCCFinance.cab#version=1,0,0,0">

</OBJECT>

The

calculateMonths
routine that uses the control is similar to the code we saw in Chapter 2, but now performs some extra error checking:

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

calculateResults
procedure.

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

PMT
financial function—something that is not supported in VBScript or JavaScript.

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

wroxcars.asp
. This text file,
finance.txt
, is on our server within the application directory:

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

<OBJECT>
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):

<!-- 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

WCCFinance
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
calculatePayments
function in our Finance page that is called if the user specifies the number of months they want to spread the payments over:

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

acceptResults
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:

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

© 1998 by Wrox Press. All rights reserved.