Cincom

Refactoring the ASP solution


| Web Toolkit Tutorial Home | Table of Contents | Extending the use of Session Variables | ASP Model Summary |
In the previous phase of our application, it was possible for an employee to instanciate the Toyz class three separate times. Also, another page needs to be written, a search of employees who have taken a particular course. These improvements will necessitate the refactoring of our current solution.

In this last lesson of the ASP model, we will create just one instance of the Toyz class and use it in all of our SSP pages. Also, the page that lists employees who have taken a particular course will have to be written and then refactored. You will see why the refactoring is needed once the first solution has been written.

1. If VisualWorks is not already running, please start running it now, load the Web Toolkit parcel and start a Wave HTTP server. You should also file in the code from Toyz Inc 5 and make sure the initialize method of the Filestuff class sets the correct location of the directory variable (i.e. the directory that contains the 3 data files).

2. This phase of the application will be quite similar to the last phase but with some subtle changes making it more efficient. Perhaps the easiest place to start would be to copy all files used in the last phase (xxxxxxx5.zzz) to "version 6".
  • header5.inc --- header6.inc
  • footer5.inc --- footer6.inc
  • navigate5.inc --- navigate6.inc
  • login5.ssp --- login6.ssp
  • validate5.ssp --- validate6.ssp
  • home5.ssp --- home6.ssp
  • employeecourses5.inc --- employeecourses6.inc
  • allemployeecourses5.inc --- allemployeecourses6.inc
3. Of these files, some contain no changes at all except for changing references to files with a "5" in them to a "6". Those files are:
  • header6.inc
  • footer6.inc
  • navigate6.inc
  • login6.ssp
  • home6.ssp
As you can see, most of the changes are in the SSP pages that contain some "major logic". Edit these files and globally replace all fives with sixes.

4. Our first big change will be in the validate file. Start with validate5.ssp. If a valid user, create a session variable containing an instance of the Employee class. Also create an instance of the Toyz class and store that into a session variable. Redirect user to home6.ssp. All else remains the same. Save the file as validate6.ssp. The file should look like as follows:

<%
session timeout: 5.
session removeKey: 'signon'.
firstName := request anyFormValueAt: 'firstName'.
lastName := request anyFormValueAt: 'lastName'.
passWord := request anyFormValueAt: 'passWord'.

errorMessage := '111'.
toyz := Toyz new.
employees := toyz getEmployees.
employees do:
 [ :each |
 (passWord = each number) ifTrue:
  [
  (lastName = each lastName) ifTrue:
   [
   (firstName = each firstName) ifTrue:
     [
     session at: 'signon' put: each. ].
     session at: 'toyz' put: toyz. ].
     ].
   ].
  ].

session at: 'signon' ifAbsent:
[ response redirectTo: ('login4.ssp?msg=',errorMessage). ].
session at: 'signon' ifPresent:
[ :signon | response redirectTo: 'home4.ssp'. ].
%>

5. At this point, you should be able to sign on. The home page does not make use of the Toyz object. Our first test of this change would be with one of the other SSP pages. Start with the file employeecourses5.ssp. The code change is just one line of code. Change

toyz := Toyz new.

with

toyz := session at: 'toyz' ifAbsent: [Toyz new].

Although the ifAbsent: method isn't really necessary (a simple at: method would suffice since it wouldn't reach this line of code if the signon session had timed out), it does show you that this can be done should the need every arise in your own applications.

Save this file as employeecourses6.ssp and test it out.

6. Make the same change for allemployeecourses6.ssp and test it out.

7. Now for the page that displays a list of employees who have taken a particular course. This page will seem a little strange at first since it is referred to as a "postback" form page. What this means is that the page will submit itself to itself. The diagram below depicts this graphically.


Figure 1. A form that submits itself to itself

The users want a drop-down list of all avialable courses. They will select the course they want, click the submit button and see a list of employees who have taken that course. They intend to do this serveral times (over and over again). Because they want a drop-down list, the page has to be a form since a drop-down list is a form control. And since they want to submit the form over and over again, it does not make sense to take them to a separate page - keep sending them to this same page. This is what's called a postback form - it posts back to itself.

This form (page) will feature some new ideas which are outlined below.

<form action="listbycourse6.ssp" method="post">
The name of this file is listbycourse6.ssp and in order to create a postback form, simply specify the name of the file itself in the action attribute of the form tag.
<select name="cboCourse" size="1">
<option value="---">[ Select a course ]
<%
courses := toyz getCourses.
courses do:
[ :each |
response write: ('<option value=', each courseNumber, '>', each courseName).
].
%>
</select>

These lines of code will create a drop-down (selection) list. The first line is a standard HTML tag that informs the browser that a drop-down (selection) list is about to be created. The value of what the user choses goes into the named-pair of cboCourse. The first option tag after the select tag is commonly used for 2 purposes. The first is to give instructions to the user as to what to choose ([ Select a course ]). The second purpose is to initialize this choice with some "nonsense" value (---) so that if this value is extracted when the form is submitted, you can tell that they did not choose any real value while they were on the form.

The lines in the code block (the percent tags) builds the list of options. These options come directly from the courses file - the getCourses method of the Toyz class will return a collection of Course objects. We iterate through the collection to build our list of options. Should the user choose a particular option, the value of the option gets paired with the name of the selection control (cboCourse).
<%
courseNumber := request anyFormValueAt: 'cboCourse'.
((courseNumber size) > 3) ifTrue:
[
courses := toyz getXrefs.
courses do:
[ :each |
(each courseNumber = courseNumber) ifTrue:
[
response write: '<tr>'.
response write: ('<td>',each employeeNumber,'</td>').
response write: ('<td>',each employeeName,'</td>').
response write: ('<td>',each courseNumber,'</td>').
response write: ('<td>',each courseName,'</td>').
response write: '</tr>'.
].
].
].
%>
This code block is very powerful. First, it extracts the value of cboCourse from the form. The next line (the test for the value of cboCourse) does two things. First, if the employee comes into the form for the first time, the value of cboCourse will be nil and our test will fail. Second, if the employee fails to choose an option, the value of cboCourse will be "---" (which does not have a size greater than 3) and it will fail as well. Naturally, this line of code was dependent on the fact that all courses have a course number of 3 letters and 3 numbers, a size of 6.

Should the test succeed, then we iterate through the collection of courses and see if any of them match the course number from our drop-down selection list. If so, then we will create a table to display it.
The code for this page will look something like this:


<%
title := 'Employees with a certain course'.
toyz := session at: 'toyz' ifAbsent: [Toyz new].
%>

<!-- #include file= "header6.inc" -->
<table width=90% border=0>
<tr>
<td width=200 align=center>
<!-- #include file= "navigation6.inc" -->
</td>
<td align=left>
<form action=listbycourse6.ssp method=post>
<table cellpadding=5 border=0>
<tr>
<td bgcolor=mediumblue colspan=2>
<font color=#ffffff><b>Please choose a course</b></font>
</td>
</tr>
<tr>
<td width=30% align=right>Course:</td>
<td width=70% align=left>
<select name=cboCourse size=1>
<option value="---">[ Select a course ]
<%
courses := toyz getCourses.
courses do:
[ :each |
response write: ('<option value=', each courseNumber, '>', each courseName).
].
%>
</select>
</td>
</tr>
<tr>
<td colspan=2 align=center>
<input type=submit name=action value=" Proceed ">
</td>
</tr>
</table>

<table border=1>
<%
courseNumber := request anyFormValueAt: 'cboCourse'.
((courseNumber size) > 3) ifTrue:
[
courses := toyz getXrefs.
courses do:
[ :each |
(each courseNumber = courseNumber) ifTrue:
[
response write: '<tr>'.
response write: ('<td>',each employeeNumber,'</td>').
response write: ('<td>',each employeeName,'</td>').
response write: ('<td>',each courseNumber,'</td>').
response write: ('<td>',each courseName,'</td>').
response write: '</tr>'.
].
].
].
%>
</table>

</td>
</tr>
</table>

<!-- #include file= "footer6.inc" -->

7. Now would be a good time to test this page. Go ahead. Try it a few times. Notice the results.


Figure 2. First time through - the drop-down list works perfectly


Figure 3. Results look fine but the drop-down list has doubled


Figure 4. The results have doubled and the drop-down list has tripled

What is going on with these crazy results?

Actually, nothing. In fact, the code is working just as it should have and is a verifcation that we are indeed re-using our session object of the Toyz class. In other words, it proves that the same instance of Toyz is being used in our application. Here's why.


Figure 5. The initialize method of the Toyz class

When we create an instance of the Toyz class, we create an instance of the FileStuff class.


Figure 6. The initialize method of the FileStuff class

When we create an instance of the FileStuff class, we create sorted collections for our instance variables of courses, employees and xrefs.


Figure 7. The getCourses method of the FileStuff class

Note that each time we execute this method, we keep adding to the collection. Since the session object of our Toyz class is the same instance of the original object, the list of courses is appended to the list that already exists. This explains why each time you submit the form on this page, the list keeps getting longer and longer.

This is also true for the getXrefs method and explains why the list of courses displayed keeps getting longer each time you submit the form.

How do we fix this?

One fix would be not to use a session variable for our instance of the Toyz class. If we wrote this page for phase 5 or lower, it would work. But the drawback would be that the web server would cause Smalltalk to work very hard by always creating instances of our classes. This is something we wanted to avoid.

The desired fix would be to modify our methods.
  • Clone the getCouses method of FileStuff to getCoursesFromFile
  • Change the getCourses method to check if the courses instance variable (collection initialized in the new method) is empty
  • If empty, load courses from the file
  • If not empty, just return the courses variable
  • Do the same for the getXrefs method and the xrefs instance variable
  • Do the same for the getEmplolyees method and the employees instance variable
8. File in the code from Toyz Inc 6 and make sure the initialize method of the Filestuff class sets the correct location of the directory variable (i.e. the directory that contains the 3 data files). Since all the "refactoring work" has been done on the custom objects, we can simply retest our pages without any further modifcation to them.


Figure 8. The drop-down list after several iterations of submitting this form

Congratulations! The ASP model of the TEACH application is complete

Our code (application) is now more efficient since we are able to reuse obects in the web environment just as we would have in a typical Smalltalk GUI application. Although there are some other coding techniques in the ASP model, the major concepts have been covered up to this point.

You now should be able to:
Make use of a session variable to hold an object instance and reuse it
Make changes to web pages by making changes to custom objects in the Smalltalk image

| Web Toolkit Tutorial Home | Table of Contents | Extending the use of Session Variables | ASP Model Summary |