Deutschsprachige FoxPro User Group



Developing Windows DNA Applications Using COMCodebook

By Beth Massi

Introduction

This document is meant to give you a detailed look of what pieces make up a DNA application written in COMCodebook as well as guide you through the process of creating a simple browser-based application from start to finish.  COMCodebook is a middle-tier framework written in Visual FoxPro 6 developed by Flash Creative Management, Inc. You can reach Flash Creative Management, Inc. on the internet at .

To get the most out of these lessons, you should be relatively familiar with Windows DNA theory as well as active server pages and Visual FoxPro, however, it is not necessary to have any working knowledge of COMCodebook specifically. For more information on Windows DNA, see the Microsoft internet papers , and . You should also have a look at the article “Windows DNA Development – A Pattern Language” by Yair Alan Griver in the Spring 2000 issue of Component Developer Magazine. For more information on Active Server Pages and Visual FoxPro, start at the Microsoft Visual Studio home page at .

First I will begin by briefly explaining the theory behind the COMCodebook middle-tier components. We will then move onto defining our project goals and designing the middle-tier. I will then show you how to step-by-step create a simple thin-client application with a parent-child form using active server pages and Dynamic HTML accessing the COMCodebook middle-tier components. Finally, we will scale our application up into Oracle8 and SQL-Server backends where I’ll show you how simple it is to modify the COMCodebook data source components to accommodate a different database.

Part 1 – The Theory Behind COMCodebook

COMCodebook’s Component Architecture

The Windows Distributed InterNet Application (DNA) model consists of three logical tiers: a user interface tier which presents information to the user and standardizes the user’s movements through the system, a middle or business tier which provides validation, business rule processing and standardization of the data, and the data tier which is our storage location for persisting information. You can use any database your organization requires, such as Oracle, SQL-Server, or VFP. The business tier is where the program logic, business rules and processes can run in the Microsoft Transaction Server (MTS) environment.  This is what we refer to as the “middle-tier”. The Presentation tier merely presents and collects the information in an interface to the user. In a thin-client scenario, Internet Information Server (IIS) can be used in conjunction with Active Server Pages (ASP) to serve the Dynamic HTML (DHTML) to the browsers. The middle and data tiers can be placed on multiple servers for scalability and security reasons, leading to a theoretically unlimited number of physical servers for any given system. It is for this reason that DNA applications are sometimes known as n-tier systems. In the diagram below the highlighted parts are the pieces of a Windows DNA Application using COMCodebook. Notice that COMCodebook does not dictate the front-end interfaces or back-end database, it is only a middle-tier framework. You have the flexibility to determine which database and what kind of user interface you want to develop. [pic]Microsoft Transaction Server Considerations

In the diagram above, you will notice that the middle-tier is sitting inside Microsoft Transaction Server. It is important to note a couple things. First, the COMCodebook components can run outside of Microsoft Transaction Server as well as inside. This provides you with the flexibility of deployment and scalability. Inside or outside MTS, there is no difference in your coding the middle tier. In other words, getting the components to run in MTS is transparent to you. The COMCodebook components know when you put them in MTS and take advantage of it immediately, without any re-compiling or code modifications on your part. If you decide you want to put your components inside MTS, you then have another option. You can have the components either support transactions or not support transactions. Running databases that are not XA or COM-TX compliant do not have transaction support with Microsoft Transaction Server, in which case you could still use MTS pooling, but would not use transaction support. Databases like Visual FoxPro or Access do not support MTS transactioning. Databases like Oracle, SQL-Server and DB2 support MTS transactioning. For more information on using MTS with VFP Components, read Microsoft Transaction Server for Visual FoxPro Developers by Randy Brown at .

Accessing Multiple Databases

Designing a system that speaks to multiple XA and/or COM-TX compliant databases is made extremely simple when using MTS and COMCodebook. You could design your application to have multiple data sources connect to multiple databases all managed with MTS transactions. This is a beautiful thing. Defining which connection classes the data source object will use is a part of developing each individual data source object. This makes it easy to define, say, SQL-Server data sources and Oracle data sources and save data to them at the same time all inside an MTS transaction.

COMCodebook Stereotypes

Stereotypes are how we categorize and describe the different components that make up the COMCodebook framework. The COMCodebook framework consists of the following:

|Stereotype |Description |

|Resources |Resources are the “containers of state” for our components. They are passed from tier to tier and component to component where they|

| |are modified as necessary. They will often take the form of ADO record sets or XML data streams. |

| |A resource is a container of state, the data marshaler. It contains properties but no methods. |

| |It does not live in MTS. |

|Resource  |the public interface used by the front-end developer. |

|Managers |They are the dispensers of resources and know which data sources to use. |

| |In MTS, they "Support Transactions" |

|Data Sources |Data Sources are components that handle creating, loading and updating Resources as necessary. |

| |They communicate with the backend database directly, typically running SQL statements. |

| |They provide a conceptual translation from a physical database to a logical view of the data. |

| |In MTS, they "Require a Transaction" |

|Resource Validators |Resource Validators are validation objects that contain the validation for a single Resource. |

| |A resource validator does any method work on the resource. |

| |In MTS, they are set to "Support Transactions" |

|Processes |Processes perform any processing that has to occur across multiple Resources, typically by calling into methods of particular |

| |resource validators. |

| |They typically only have one public method "Execute" |

| |In MTS, they are set to "Requires a New Transaction" |

|Resource Manager Proxy |A resource proxy may be needed in the presentation tier to translate the Resource objects into a format that is familiar to the |

| |front end developer. For instance, it may translate an XML stream into a VFP cursor for use in a thick VFP front-end. Basically, a |

| |proxy provides a place for a developer to put front-end specific hooks into the calls to a resource manager. It has the same |

| |methods as the Resource Manager. |

Let’s take a look at a simple example of how a business entity will work. The following table uses these objects:

rmAttorney - An Attorney resource manager

dsAttorney - An Attorney data source

rsAttorney - An Attorney resource

vldAttorney – An Attorney validation object

|Tier |Object |Method Call |Returns |

|Pres |rmAttorney |GetAttorneyByID(1) |  |

|rmAttorney |dsAttorney |GetAttorneyByID(1) |rsAttorney with data for Attorney #1 which is returned to rmAttorney and then to the |

| | | |UI |

|Pres |  |  |Modifies the data in rsAttorney |

|Pres |rmAttorney |Save(rsAttorney) |  |

|rmAttorney |vldAttorney |Validate(rsAttorney) |RsAttorney with any changes required by the validation and a validation success |

| | | |message |

|rmAttorney |dsAttorney |Save(rsAttorney) |Success message is passed back after the rsAttorney changes are updated to the |

| | | |server. |

As you can see, the presentation developer only needs to know about the public interface of the resource managers. The resource manager acts as a facade into validation objects, process objects and data sources. The data sources are the only objects that need to know about the structure of the database and any calling conventions for that database.   This is why we call it component architecture.  Each layer interacts with only the layer "next" to it.  So if you decide to offer SQL-Server, DB2, Sybase or any other XA-compliant database as a backend in the future, only the data sources need to be modified.  Conversely, if a new browser comes out that supports the next generation of HTML, only the presentation services need to be reprogrammed.  The business logic will always be intact.   If there is a bug in the business services, only the application server needs to be upgraded. Say bye-bye to workstation installs and upgrades. 

The Business Entity

The Business Entity is what we refer to as the set of components that define the behavior and rules of a resource. Remember that a resource is a container of state or data, like an ADO recordset. There raises a question of where the hierarchical relationship between tables that form a resource should be managed. It is possible to create various business entities that provide resources with different levels of hierarchy, and have the front-end application call whichever resource manager that is appropriate. It is also possible to have the resources be a single layer, with the application front-end describing the relationships necessary at the time, allowing the middle-tier to handle the formalities of retrieving and updating things in the proper order. The COMCodebook framework which has selected the latter approach, believing that it is more flexible over time.

In other words, the COMCodebook Business Entity is what we refer to as the set of components that define the behavior and rules of a single layer of data.  For instance, in the above example, the "Attorney" Business entity is used.  It consists of the Attorney Resource Manager, Attorney Data source, and Attorney Validator objects.  These objects only know how to get/save/add/delete data to the Attorney table in the database.  To get the data, we define interfaces to the business entities based on the methods of resource retrieval.   For example, we will have GetAttorneyByID(), GetAttorneyByName(), and GetAttorneyBySSN() among others defined as public functions in the resource managers that will return resources (ADO Recordsets).  The resource managers will then call the public GetAttorneyBy...() interfaces in the data sources which will execute the SQL Statements on the database and will return the resource to the resource manager.  The Validator objects are called before a save() of the resource manager and run through the data field by field (or row by row) in the resource to make sure it is valid.  This is where the field and row level business rules are located. 

The Resource Manager Controller (RMC)

You are probably asking yourself, “How do I connect the resources together in a hierarchical way? Where does the hierarchical relationship between the entities exist?” The example above only shows how a single business entity works.   Many relations between your data may exist.  Because the business entity only describes one single layer of data, many relationships between them will also exist. This means that many of your business entities have one-to-many or one-to-one relationships to each other.  How do we manage these relationships and finally present them to the user?  The answer is the Resource Manager Controller (RMC).  An RMC "controls" the update/delete/insert of many resources all at once by making method calls to all the resource managers. That way our presentation tier can act upon many resources at the same time.   When the presentation tier calls the save() of the RMC, it makes sure all the resources are valid by calling the validate() method of each resource manager and then executing their save() methods.  This creates a complex transaction because each of the individual save() methods of the resource manager's are wrapped in an MTS transaction as well. 

[pic]

The Resource Manager Proxy

Now that we understand how to display our data in a hierarchical way using the resource manager controllers, let's take a deeper look into the way the resources travel from middle-tier to a thick client front-end.  First we must understand how resources are translated on the thick client.  Since the resources are actually ADO Recordsets, in a VFP front-end there must be a way to translate the resources into a usable form, namely a VFP cursor. We do this with a Resource Manager Proxy (RMProxy).  A RMProxy will intercept the calls to/from the business tier to execute processing.  This is where we create the cursors to use on the forms or create ADO recordsets from our modified cursors to send back to the resource managers.  Additionally, there may be many Resource Manager Proxies on a form.  They are contained in the Resource Manager Proxy Controller.  It is this object that instantiates the remote RM Controller then sends environment information via XML to it, which sets up its environment of resource managers on the middle-tier.   Thus it is possible to present complex data entity relationships to the user for modification all at once. (Of course, the users don't know that they are complex.)

[pic]

The above diagram shows an example of how VFP thick-client translates resources. In a thin-client, a resource manager proxy is probably not necessary. This is because an active server pages application already knows how to display an ADO recordset resource. I’ll show you how to translate the ADO recordsets into HTML and back again in our simple application.

COMCodebook Code Layers

Before we develop our first application, it is necessary to understand that the COMCodebook framework code is structured into three layers, the a-layer, the i-layer and the c-layer. The c-layer is the “Codebook” layer. It is the layer where all framework code is written and from which all objects ultimately inherit from. This is the layer that Flash Creative Management provides for you. You should never have to modify this layer. The a-layer is the “application” layer. This is the layer where you spend your time. It is where all your application specific code resides. The i-layer is an intermediate layer that lies between the "c" and "a" layers. It is where company specific code is written, if necessary. It is a place where you, the Codebook developer, can safely modify the source to meet your needs and continue to take advantage of periodic "c" layer updates from Flash.

Part 2 - Our First COMCodebook Web-based Application

Now that we have discussed some of the theory behind COMCodebook, let’s dive head first into our first web-based application! To make things simple (and easy to download), the lessons will be using a Visual FoxPro database and will not run inside Microsoft Transaction Server since VFP does not support MTS transactions. However, lets remember that getting the components to run in MTS is transparent to you. (See: Microsoft Transaction Server Considerations in Part 1) If you are interested in more robust solutions, the final part of this document discusses scaling our application up into Oracle and SQL-Server databases and what minimal amount of code we need to change in the middle-tier to do so. Those sections assume you are familiar with the particular databases being discussed and focus only on the middle-tier modification and MTS configuration.

Project Specifications

The purpose of this application is to present users with a flexible way of searching for attorneys as well as a way to search for attorney notes, allowing them to add/edit/delete them. The user will be given a search form where they will type in their search criteria and will then be presented a list of attorneys and their notes that meet the search criteria. From there the user may edit an attorney record or add a new one. They will need to modify the Name, Firm, License Number and Address information of the attorney. Since there can be multiple notes per attorney, the user will also need to somehow browse a list of notes, preferably on the same page, and then be able to add/update/delete them as well.

Setting Up Our Development Environment

Before you begin, you will need to download the latest version of the COMCodebook framework from the COMCodebook Web Site (that’s where you got this document, so hopefully you can find it… (). Please read the instructions on how to set up the sample application, TimeTrac, that ships with COMCodebook as an example and make sure it works for you. The software necessary to make the TimeTrac sample app work includes the following:

▪ The NT 4 Option Pack which includes Microsoft Transaction Server and Internet Information Server / Personal Web Server. Or Windows 2000 Professional or Server with IIS installed.

▪ Visual FoxPro 6 Service Pack 4 or higher ()

▪ ActiveX Data Objects (ADO) 2.5 or higher installed ().

▪ Microsoft Internet Explorer 5.0 or Higher ()

In our thin-client sample application, I will also use Visual InterDev 6 to develop the active server pages so you will want to make sure that is available as well. It is also necessary for developing this application to have Internet Information Server (NT Server) or Personal Web Server (NT Workstation) on your development machine which you can install from the NT 4 Option Pack if you are running NT 4.0 Server or Workstation. When you extract the COMCodebook framework, extract it to the root of your drive with sub-directories. This will put everything under a directory called FLASH. If you have extracted your framework to your D:\ then the directory structure should look similar to this:

[pic]

The AttorneyApp sample file shells are provided to you in the zip file AttorneyApp.zip. This sets up the application for development automatically for you. It contains all the data and project files necessary, but does not contain all of the code yet. We will write the code together in the lessons that follow. If however, you get lost or your code just doesn’t seem to be working, there is a complete set of zipped up code in the \Finished subfolder. Be sure to place your AttorneyApp folder under the Codebook folder by extracting this zip file to the same root drive that you extracted COMCodebook with subdirectories. Under the AttorneyApp folder you will see subfolders that will contain the data source, resource manager, validator and processes projects: \DataServices, \ResourceMgrs, \Validation and \Processes. Also, we will have \Include and \Libs folders. \Include is where the application include files will be placed and the \Libs directory is where all your a-layer programs will reside. We will also have an \Interface directory that will contain all the active server pages as well as a \jscripts and \images folder for JavaScript code we write and images we may use on the forms. \Interface will be our web directory. Finally, you will see a \Data folder which will contain the VFP database. So the structure of your AttorneyApp sub-folders should look like this:

[pic]

Notice that this is similar to the \WatsonDevelopment folder that is provided by Flash Creative Management as a sample application. That application is a thick-client, so it has additional folders we will not need.

Reviewing the Database

The simple sample VFP database is provided for you. It is located in the AttorneyApp\Data folder. Notice that the tables in the database contain more fields than we will need to display to the user on the forms. This is okay. The data sources do not have to be a mirror image of your tables. In fact, when we set up our flexible searching, we just might need to pull data from additional tables as well. This is what makes the data sources flexible on data retrieval.

|Attorney Table |Attorney Note Table |

|ATTORNEY_ID |ATTNOTE_ID |

|LICENSE_NUMBER |ATTORNEY_ID |

|FIRM_NAME |NOTE_TIME |

|LASTNAME |NOTE_LOCK |

|FIRSTNAME |NOTE_NAME |

|MI |USER_NAME |

|TITLE_ID |NOTE_TEXT |

|TITLE_DESC | |

|PRO_SUFFIX_ID | |

|PRO_SUFFIX_ABBV | |

|ADDRESS | |

|CITY | |

|STATE | |

|ZIP | |

|PHONE | |

|FAX | |

|TYPE | |

|TAX_1099 | |

|TAX_ID | |

There will be a parent-child relationship between attorney and attorney notes table on Attorney_ID.

Designing the Business Entities

Since we have two tables in this application, we will have to define two business entities; Attorney and AttorneyNote. This translates to two Resource Managers (Attorney and AttorneyNote), two data sources (AttorneyDataSource and AttornetNoteDataSource) and two validators (AttorneyValidator and AttorneyNoteValidator).

Data Sources and Resource Managers

To design the data sources and resource managers we need to ask ourselves, “How will our presentation services need to retrieve the Attorney and Attorney Note data?” When we answer this question we will be able to create the public Get() interfaces (methods) into our data sources. As mentioned earlier, to get the data, we define interfaces to the business entities based on the methods of resource retrieval.   How many Get() interfaces you create is up to you, however, you can almost always be sure you will need to create at least a Get..ByID() method that accepts the primary ID of the record you want and returns that particular record. In our situation, we will have a GetAttorneyByID( ) and a GetAttorneyNoteByID(). This means that we will be able to retrieve, by ID, a specific attorney or attorney note record.

In our application we are going to want to retrieve attorney notes related to an attorney somehow. We don’t want the user to have to pick through all the notes when they already have picked an attorney. So we are going to want to automatically retrieve all the notes related to an attorney when the user picks an attorney record. That means we will need a Get() method called GetAttorneyNoteByAttorneyID(). This method will accept the attorney_id and return a resource of all the attorney notes for that attorney.

Here are some more examples of Get() interfaces we may need:

|Attorney |AttorneyNote |

|GetAttorneyByID() |GetAttorneyNoteByID() |

|GetAttorneyByName() |GetAttorneyNoteByAttorneyID() |

|GetAttorneyBySearchCriteria() |GetAttorneyNoteBySearchCriteria() |

The last two interfaces are the most interesting. They are going to allow the client to pass in a resource like an ADO recordset or an XML data stream that contains search criteria parameters. This is how we will facilitate flexible searching in our interface. This is because we will want to present the user with many search criteria parameters. To create a Get() method for every possibility would be painful. This way we have one generic method to retrieve data from user searches. I’ll discuss the search criteria resources when we start coding the data sources.

Validators

The validators will be where we check to make sure each piece of data in a resource is valid when a save is performed. If the data is not valid, then a validation messanger object is created and passed back to the client. The messanger object can take the form of an XML stream which we can use for displaying validation messages on the HTML pages. I’ll show you how we can do this later.

The kind of validation we need in this application is simple. In the Attorney resource we will check to make sure the LastName is not empty. We will also check to make sure that the Attorney Note Note_Time field is a non-empty date.

Process Objects

Process objects or processes are components that work on multiple resources at the same time to execute processing. For instance, the Resource Manager Controller is a built-in process object. Its job is to validate and save multiple resources. In our simple attorney application we will not need any other process objects besides the Resource Manager Controller.

Part 3 - Coding the Data Sources

Now that we have reviewed the database and designed our business entities, we are ready to get to work! The first thing we are going to do is code our data sources. The data sources are

Let’s open Visual FoxPro and in the command window type:

CD “D:\FLASH\Codebook\AttorneyApp\Interface\”

DO StartCB

This will open all four of the middle-tier projects; AttData, AttValid, AttResMgr and AttProcess as well as set up your working directories. It is important that you type these two lines every time you work with or recompile your projects. If you don’t, VFP will not be able to find all the correct include files before building.

The Data Source Project

We are going to want to concentrate on the Data Sources first. The program files you see in the AttData project are what we need to create data sources. Most of them come from the COMCodebook Framework so you don’t even need to worry about them. The three programs that start with the letter “a” are the programs I have provided for you. The two important ones are “adatasources.prg” and “aparameterobjects.prg”. This is where we are going to start entering some code.

[pic]

As you can see, there are not a lot of programs to go through. All the classes in the COMCodebook

Framework are contained in .prg files. This not only makes it easier to work with source control software; it also allows you, as the programmer, to easily get the overall picture of the framework (without having to sift through methods of class libraries in the designer). As you work with the framework more and more, you will find that programming the middle-tier components is easy and quick. This allows you to concentrate harder on the processes and user interface.

Let’s open up the most important program file in our project, adatasources.prg. There you will see that I have already created the shell of the AttorneyDataSource class and have provided the full code for the AttorneyNoteDataSource class. As we work through the lesson I will be instructing you on where to enter the code in the AttorneyDataSource. I have already programmed the AttorneyNoteDataSource to save time, but you will find that its structure is exactly the same as the AttorneyDataSource.

The Data Sources inherit from iAbstractADODataSource (located in idatasources.prg) which inherits from cAbstractADODataSource (located in cdatasources.prg). Let’s start with the AttorneyDataSource. You’ll notice that there are six properties that you will need to pay special attention to:

Name

cTableName

cTableID

cSearchSyntaxClass

cADOComponentsParameterClass

cConfigurationObjectClass

The AttorneyDataSource’s name is “AttorneyDataSource”, simple enough. The cTableName and cTableID properties tell us which table this data source corresponds to on Save. The cTableID is used to automatically fill the primary key on an insert of a new record. We will talk about the cSearchSyntaxClass property a little later in the lesson. So in our AttorneyDataSource we will fill the first three properties as follows:

Name = "AttorneyDataSource"

cTableName = "ATTORNEY"

cTableID = "ATTORNEY_ID"

The last two properties are a bit more interesting.

The ADO Components Parameters

The cADOComponentsParameterClass property holds the name of the concrete parameter class you define in the aparameterobjects.prg. Let’s take a look at this file now. You will see that I have defined the ADO Component parameters for Attorney and AttorneyNotes:

*********************************************************************

DEFINE CLASS prm_AttorneyADOComponents AS iADOComponentsParameter

*********************************************************************

Name = "prm_AttorneyADOComponents"

lEnableServerAssignedKeys = .T.

nlocktype = 4

ncursortype = 1

ENDDEFINE

*********************************************************************

DEFINE CLASS prm_AttorneyNoteADOComponents AS iADOComponentsParameter

*********************************************************************

Name = "prm_AttorneyNoteADOComponents"

lEnableServerAssignedKeys = .T.

nlocktype = 4

ncursortype = 1

ENDDEFINE

The two interesting properties of the ADO resource are nLockType and nCursorType. Having nLockType set to 4 here translates to having the ADO recordset’s LockType property set to “optimistic batch updates”. In the same way, having nCursorType set to 1 here means that the ADO recordset use a keyset cursor. Take a look at CursorTypeEnum and LockTypeEnum in the MSDN library for more information.

Now that we have set up these two ADO Component Parameter classes, we will put their names in the AttorneyDataSource and AttorneyNoteDataSource’s cADOComponentsParameterClass property. Going back to our AttorneyDataSource in the adatasources.prg we will set:

cADOComponentsParameterClass = "prm_AttorneyADOComponents"

The Connection Parameter Class

The last property that is important to us is the cConfigurationObjectClass. This property holds the name of the concrete class that is used to connect to the database itself. Take a look at the aparameterobjects.prg again and scroll to the end of the file. There you’ll see that I have set up three connection parameter classes, prm_AttorneyApp_vfp, prm_AttorneyApp_SQL, and prm_AttorneyApp_Oracle. These three parameters are to be used to connect to VFP, SQL-Server and Oracle respectively. Although initially our example will only use the VFP connection, we will see how it is easy to set up your data sources to connect to different databases later. All you need to do is specify the corresponding connection classes in the data sources’ cConfigurationObjectClass property and write compliant SQL statements for that database. It’s that simple!

So back in our AttorneyDataSource we will set the cConfigurationObjectClass property like so:

cConfigurationObjectClass = "prm_AttorneyApp_vfp"

Setting Up the Connection Configuration File

Now we will need to set up the connection configuration file for the connection classes. This tells them how to connect to the database. To understand exactly what goes into a configuration file, let’s take a look up the hierarchy of the connection classes. Open up the cparameterobjects.prg and right-click to expose the shortcut menu. Select Procedure/Function List and select CAbstractADOConfigurationParameter.Init. This will bring your cursor to the abstract class that all the connection classes inherit from. Scroll up a little to see the property list for the class. Do you see the cConfigFile property? That property contains the name of the Connection Configuration file, in this case CONNCFG.DBF. If you wanted to change the name, then you would change it in your I-layer, not here.

Okay, so let’s modify our CONNCFG.DBF so that our data sources know where to get the data! The CONNCFG.DBF file is located in the same directory as the data source DLL (AttData.DLL). In our case it is in AttorneyApp\DataServices. Open up the CONNCFG.DBF in Visual FoxPro by clicking on it from explorer. You should see the following in a browse window:

I have entered three connection parameters, prm_AttorneyApp_vfp, prm_attorneyapp_sql and prm_attorneyapp_oracle. Since we will be using VFP databases, enter the location of the attorneyapp data in the Cdatasrc field (i.e. D:\FLASH\CODEBOOK\ATTORNEYAPP\DATA\ATTORNEYAPP.DBC)

If you keep scrolling to the left, you will see some additional fields. They are used with different types of databases. If you enter data in the fields that are not relevant to the particular database you are connecting to, the COMCodebook connection classes will ignore them. Here are the meanings of the fields relative to each database we’ll discuss:

|Field |VFP |SQL |ORACLE |

|Cindex |The name of the Configuration Parameter |The name of the Configuration Parameter |The name of the Configuration Parameter |

|Cconnectto |The Connection Timeout |The Connection Timeout | |

|Cdatasrc |The Location of the DBC |The SQL-Server Server name |The Oracle Service Name |

|Cinitcat | |The SQL-Server Database name | |

|Cpassword | |A valid database password |A valid database password |

|Csecinfo | |Security Information | |

|Cprovider |The OLE DB Provider Name (MSDASQL.1) |The OLE DB Provider Name (SQLOLEDB.1) |The OLE DB Provider Name (MSDAORA.1) |

|Cuserid | |A valid database user id |A valid database user id |

|Mspecial | | | |

This information is used by the COMCodebook connection classes to set up the valid connection strings to the particular databases. For more information on connection strings See article Q193332 in the Microsoft Knowledge Base.

The last field in the table is a memo field called Mspecial. This field is used to extend the connection strings if necessary. Since we cannot anticipate every database connection string syntax out there, this field is used for “special” cases. For Oracle, VFP, and SQL-Server this field is not necessary. For information on creating special connection strings, refer to the documentation in the CspecialConnectionString class in cparameterobjects.prg.

The Get() Interfaces

Are we having fun yet?! Now it is time to write our public Get() interfaces! Back in our adatasources.prg you will see that I have started the shell of all the AttorneyDataSource.Get() methods we want to code for our application. If you recall we designed three Get() interfaces for each data source:

|Attorney |AttorneyNote |

|GetAttorneyByID() |GetAttorneyNoteByID() |

|GetAttorneyByName() |GetAttorneyNoteByAttorneyID() |

|GetAttorneyBySearchCriteria() |GetAttorneyNoteBySearchCriteria() |

I have put comments in the areas where we will need to code the SQL statements to retrieve the data. You should also happen to notice that there is a GetProxy() method in the classes.

*------------------

FUNCTION GetProxy()

*------------------

LOCAL loADO

loADO = THIS.GetADOAggregateParameter()

loADO.oCommand.ActiveConnection = loADO.oConnection

*-- INSERT SQL HERE

loADO.mandText = [ ]

THIS.ExecuteSQLQuery( loADO )

RETURN loADO.oRecordSet

ENDFUNC

What is a GetProxy()? GetProxy() is your default multi-select interface, usually returning simply a Name and ID from the table. For instance, if the client needs to fill a pick-list or combobox, it could easily call the GetProxy() method to retrieve this information.

What shall we return from our AttorneyDataSource.GetProxy()? How about the Attorney LastName, FirstName and the Attorney_ID? So our CommandText would look like so:

loADO.mandText = [SELECT RTRIM(LastName)+ ", " +FirstName AS cDesc, Attorney_id AS ] +;

[ upID FROM Attorney ORDER BY Lastname, FirstName ]

Our next interface is the most important, GetAttorneyByID(). Here, the client will pass in the primary ID of the Attorney record and we will return the record they asked for in an ADO recordset resource. It is ultimately up to us as the data source programmers to return all the fields or only some of them. We could even rename them to more meaningful names. This goes right along with the theory that the presentation programmer is completely insulated from the actual database tables and fields themselves. They can only access the data how we want them to. Whether you want to expose the names of your fields or not is up to you. It also depends on how many long SQL statements you are willing to write! Take a look at the AttorneyDataSource.GetAttorneyByID():

*---------------------------------------

FUNCTION GetAttorneyByID( tuAttorneyID )

*---------------------------------------

LOCAL loADO, lopAttorneyID

loADO = THIS.GetADOAggregateParameter()

loADO.oCommand.ActiveConnection = loADO.oConnection

*-- INSERT SQL HERE

loADO.mandText = []

*-- CONSTRUCT YOUR PARAMETER(S)

lopAttorneyID = loADO.oCommand.CreateParameter( )

loADO.oCommand.Parameters.Append( lopAttorneyID )

THIS.ExecuteSQLQuery( loADO )

RETURN loADO.oRecordSet

ENDFUNC

In our simple application, since we will be writing the presentation as well, it is no secret to us what the names of our fields and tables are. In this case I am going to return all the fields with their real names exposed:

loADO.mandText = [SELECT Attorney.* FROM Attorney WHERE Attorney_ID = ? ]

lopAttorneyID = ;

loADO.oCommand.CreateParameter("Attorney_ID", adVarChar, adParamInput, 9, tuAttorneyID )

Here’s an interesting line of code we haven’t seen before. This is the way you construct ADO parameter objects to be able to have parameters in the SQL strings. We are creating an ADO Parameter object setting it up with the value of tuAttorneyID to append to the command object. If you are not familiar with ADO command and parameter objects, see “Command object (ADO)” in the MSDN Library.

The next interface is GetAttorneyByName(). This method is very similar to the GetAttorneyByID(), but this time we have two search parameters instead of just one (LastName and FirstName) and the resource we return may have more than one record in it. Take a look at the code we need to write highlighted in bold:

*---------------------------------

FUNCTION GetAttorneyByName( tcLastName, tcFirstName )

*---------------------------------

LOCAL loADO, lopLastName, lopFirstName

loADO = THIS.GetADOAggregateParameter()

loADO.oCommand.ActiveConnection = loADO.oConnection

*-- INSERT SQL HERE

loADO.mandText = [SELECT LastName, FirstName, Phone FROM Attorney WHERE LastName LIKE ? AND FirstName LIKE ? ORDER BY LastName, FirstName]

*-- CONSTRUCT YOUR PARAMETER(S)

lopLastName = loADO.oCommand.CreateParameter( "LastName", adVarChar, adParamInput, 20, ALLTRIM( tcLastName ) + "%" )

lopFirstName = loADO.oCommand.CreateParameter( "FirstName", adVarChar, adParamInput, 15, ALLTRIM( tcFirstName ) + "%" )

loADO.oCommand.Parameters.Append( lopLastName )

loADO.oCommand.Parameters.Append( lopFirstName )

THIS.ExecuteSQLQuery( loADO )

RETURN loADO.oRecordSet

ENDFUNC

Handling Search Criteria Resources

Now that we have finished our standard get methods, you should notice that even though we may have search parameters, they are hard coded to search specific fields in the tables. (i.e. Attorney_ID = ?) Also, the order that the records are retrieved is also fixed. How will we easily allow our presentation services to search on other fields and order the records returned? We need to think about how we can create flexible WHERE and ORDER BY clauses on our SELECT statements.

The answer is the Search Criteria Manager (SCM). Built into the COMCodebook framework is a standard method of retrieving search criteria parameters based on the data sources underlying table’s fields. If you recall, earlier we went through the important properties of the a-layer data sources. We skipped the cSearchSyntaxClass property. The cSearchSyntaxClass property of the data source holds the Search Syntax Class name that is used by the SCM. Since there are many diferent databases, there can be diferent syntax used by those databases to construct the clauses of the SELECT statements. We must tell the SCM which Syntax Object to use when constructing our clauses. The SCM provides the flexible searching we will need for our presentation services. There are two SCM’s provided for you in COMCodebook, CSearchCriteriaManagerXML and CSearchCriteriaManagerRS which are subclasses of CAbstractSearchCriteriaManager. Like all parts of COMCodebook, you can extend the SCM objects and write your own search manager objects. For more information on complex searching and a complete tour through the SCM object model, please see the Creating Flexible Searches in COMCodebook Datasources.doc found on the COMCodebook Download page.

The Search Criteria Manager does two things. First, it provides the client with a search criteria resource in the form of an ADO recordset or an XML stream by way of two public interfaces of the data source class; GetXMLSearchCriteriaResource() and GetRSSearchCriteriaResource(). The client can then fill in the appropriate field values and then send the resource back into the Get…BySearchCriteria() interface. The data source then calls upon the proper SCM again to do its second thing; construct the clauses of the data sources’ SELECT Statements by using the Syntax Object. This is done by calling the method CreateFilterFromResource() in the Get..BySearchCriteria() method. Take a look at your GetAttorneyBySearchCriteria() interface in the adatasources.prg and enter the SELECT statement shown below:

FUNCTION GetAttorneyBySearchCriteria( tuFilter )

*------------------

LOCAL loADO, lcSelect

loADO = THIS.GetADOAggregateParameter()

loADO.oCommand.ActiveConnection = loADO.oConnection

*-- INSERT DEFAULT SQL HERE

lcSelect = [SELECT Attorney.* FROM Attorney ]

If THIS.CreateFilterFromResource( tuFilter ) = FILE_OK

With this.oSearchClauses

loADO.mandText = lcSelect + .cWhere + .cOrder

EndWith

Else

loADO.mandText = lcSelect + [ ORDER BY Lastname, Firstname]

Endif

THIS.oSearchClauses = Null

THIS.ExecuteSQLQuery( loADO )

RETURN loADO.oRecordSet

ENDFUNC

The clauses that the Syntax Object constructs are placed into an object that you reference via the oSearchClauses property of the data source. We can then reference the WHERE and ORDER BY clauses of our select statement in the properties cWhere and cOrder. I know this might sound like a lot of work, but the beauty is that COMCodebook has practically done all of it for you. Besides programming the Get..SearchCriteria() method above, all we need to do is determine if the default set of fields is enough to provide to our presentation services. By default, if the client calls the Attorney.GetRSSearchCriteriaResource() then all the fields from the attorney table (and only attorney) will show up in our search criteria resource. An ADO recordset search criteria resource has the following character fields:

Table – The name of the base table

Name – Field Name

Type – ADO DataTypeEnum represented as a character

Value – Field Value

EndValue – Field End Value (for retrieving ranges of data)

Order – The order

Operator – The operator keyword; LIKE, CONTAINS, BETWEEN

Options – Order options like DESCENDING

By default, when the client calls up a search criteria resource, the fields Table, Name, Type and Operator are filled in. It is up to the presentation services to fill in the rest of the criteria (Value, EndValue, Operator, Order, Options) appropriately and pass it back into the Get…BySearchCriteria() interface. The way the particular Search Criteria Managers decompose the search criteria resource to create valid clauses depends on the Syntax Object being used. COMCodebook provides a standard CAbstractSearchSyntax class in csearchcriteria.prg and also sub-classes CVFPSearchSyntax, CSQLSearchSyntax and COracleSearchSyntax so you can immediately use the flexible searching with these databases. I should also mention that the search criteria resource structure is also extendible, like most things in COMCodebook. If you need to provide additional search criteria properties to your presentation services, you can take a look at the comments in the IAbstractSearchCriteriaManager class in isearchcriteria.prg that I have supplied.

In our application, both the attorney and attorney note data sources use a Visual FoxPro Database, so we will need to specify the SearchSyntax_VFP as the cSearchSyntaxClass:

cSearchSyntaxClass = "SearchSyntax_VFP"

Our AttorneyDataSource will not need to provide any additional search fields to the presentation services, however, take a look at the AttorneyNoteDataSource.AddSearchCriteriaInfo_Post method near the bottom of the adatasources.prg. There I have specified to add Attorney.LastName to the search so that we could look up notes on an attorney’s last name.

*------------------

PROTECTED FUNCTION AddSearchCriteriaInfo_Post( roRS )

*------------------

With This.oSearchReferences.oSearchCriteriaManager

.AddSearchElements( @roRS, "Attorney", "LastName", adChar )

Endwith

Return FILE_OK

Endfunc

The way we add records to our search criteria resource is by way of the Search Criteria Manager. A reference to this object is contained in the Search References object. The Search References object is a special parameter object defined in cparameterobjects.prg. What we are doing above is adding properties and their values of a single row or node of data to the SCM. We are sending the Search Criteria Resource, the table name, the field name, and the type of data to the SCM to add to our resource.

The last thing to do is to add the attorney’s last name to the SELECT statement in the GetAttorneyNoteBySearchCriteria().

lcSelect = [ SELECT Attnote.*, RTRIM(Attorney.LastName) + ', ' + Attorney.FirstName ] + ;

[ AS FullName FROM AttNote, Attorney ]

If This.CreateFilterFromResource( tuFilter ) = FILE_OK

With this.oSearchClauses

.cWhere = iif(empty(.cWhere), [ WHERE ], .cWhere + [ AND ])

.cWhere = .cWhere + [ Attnote.Attorney_Id = Attorney.Attorney_ID ]

loADO.mandText = lcSelect + .cWhere + .cOrder

Endwith

.

.

.

Now we can allow the presentation services to search and order on Attorney.LastName for attorney notes as well as any of the fields in the Attorney Note table! When we program the user interface, I’ll show you how we will work with the search resources themselves.

Getting GetPrimaryKey() to Work For You

GetPrimaryKey() is defined in the CAbstractADODataSource class in cdatasources.prg. Here is where the data sources retrieve primary keys for new records in their resources before they save them. The default is to call a stored procedure called sp_NewID to retrieve a primary key from the database. It executes the stored procedure through the ADO Command Object which returns the primary key value into an ADO Parameter Object. If the name of your primary key generator is different than sp_NewID and/or it accepts/returns different parameters, you can overwrite this method in your i or a-layers. If you will be developing applications against existing databases, you may need to enhance the GetPrimaryKey() function. For instance, if you needed to generate character primary keys, we could define a new GetPrimaryKey() method. For our purposes here, since we are working with a VFP database that is exactly what we are going to do. We will define a simpler GetPrimaryKey() method using a handy VFP SYS() function. Take a look at the bottom of the adatasources.prg. You will see a class definition for aAbstractADODataSource. There is the GetPrimaryKey() method we are going to use.

************************************************************************

DEFINE CLASS aAbstractADODataSource AS IAbstractADODataSource

************************************************************************

PROTECTED FUNCTION GetPrimaryKey( rcPrimaryKey, toADO )

LOCAL lnRetVal, lcPrimaryKey, lnStartIndex

lnRetVal = FILE_OK

lcPrimaryKey = RIGHT(SYS(2015), 9)

IF .NOT. ISNULL( lcPrimaryKey )

rcPrimaryKey = lcPrimaryKey

ELSE

lnRetVal = FILE_ERROR

THIS.RecordError( 999999 , ;

"GetPrimaryKey()" , ;

12 , ;

"Primary Key could not be generated." )

ENDIF

RETURN lnRetVal

ENDFUNC

ENDDEFINE

This method uses the VFP SYS(2015) function to generate a unique ID. The name that SYS(2015) returns is created from the system date and system time. Calling SYS(2015) more than once during the same millisecond interval will return a unique character string. Granted, this is not the most robust way of generating unique id’s, but if you were running these components all from the same server, you could be almost certain you would get a unique primary key every time.

Using Character Primary Keys

By default, COMCodebook assumes you are using integer primary keys in your database when deleting records, and in most cases you probably will. However, there may be times when you are using character fields as the primary keys of your tables as we are in our VFP database. Because of this assumption, you will need to override the default behavior of the data source method DefineDeleteSQL() which is responsible for generating the delete SQL statement syntax for the Delete() method. By default in the c-layer the method looks like this:

PROTECTED FUNCTION DefineDeleteSQL( tcID, rcDeleteSQL )

LOCAL lnRetVal

IF .NOT. EMPTY( THIS.cTableName ) AND .NOT. EMPTY( THIs.cTableID )

rcDeleteSQL = "DELETE FROM " + THIS.cTableName + " WHERE " + THIS.cTableID + " = " + tcID

lnRetVal = FILE_OK

ELSE

rcDeleteSQL = ""

lnRetVal = FILE_ERROR

ENDIF

RETURN lnRetVal

ENDFUNC

To make the delete work for character IDs the select statement must read:

rcDeleteSQL = "DELETE FROM " + THIS.cTableName + " WHERE " + ;

THIS.cTableID + " = '" + tcID + "'"

Open your adatasources.prg. Notice right below the GetPrimaryKey() method, I have defined a DefineDeleteSQL() method with this code that includes the single quotes necessary to construct a valid SQL statement. Using this function will allow our deletes to work with character primary keys. This is another great example of how we can enhance the framework easily to fit our needs.

In the last part of this document, we will discuss how to upsize our database into SQL Server and Oracle. We will use integer primary keys in those examples to demonstrate how to use the COMCodebook default behavior for generating primary IDs.

Done With Data Sources

Okay, I know that seemed like a lot of work, but it was your first run. And the data sources are the components with the most amount of code (besides the presentation layer). Among other things, we learned how to program our Get() interfaces as well as extend our search criteria resource. Next we will look at how easy it is to code the validators and the resource managers. They practically code themselves!

Part 4 - The Resource Managers, Validators and Processes

Coding the Resource Managers

Resource Managers are the presentation tier’s view into the business tier. They are business aware sets of components that provide the public interface used by the front-end developer. They know what data sources and validators to use. The resource manager project for our sample application is AttResMgr.PJX. Open up the project in Visual FoxPro:

[pic]

As you can see, there are a lot less files in here than the data source project. This is because the data sources are our “work-horses”. They are responsible for talking to the databases directly. The resource managers on the other hand are simply a façade into our business model. They provide a standard interface into any business resource that we need to access, and as such do not require much custom coding. Data sources require that we understand both the data structures and the resources that we need to access. Resource managers simply require that we know the name of a resource. Think of the data source as building a car from a kit, while the resource managers are equivalent to buying that car from a dealer. In the first case, you need to understand all the parts and how they should go together. The second case allows you to simply ask for the name of the car.

The Get() Interfaces

Programming the Get() interfaces of the resource managers is simple once the data sources are programmed. This is because the resource managers have all the same interfaces as the data sources. Let’s take a look at the GetProxy() method of the Attorney resource manager by opening the aresourcemanagers.prg:

*------------------

FUNCTION GetProxy()

*------------------

LOCAL loDataSource, loADORecordSet

loDataSource = .NULL. && The Data Source

loADORecordSet = .NULL. && The Resource

THIS.GetDataSourceObject( @loDataSource )

loADORecordSet = loDataSource.GetProxy()

RETURN loADORecordSet

ENDFUNC

As you can see all that the resource manager is really doing is calling the AttorneyDataSource.GetProxy(). Here is a little bit more complicated interface because we need to pass two parameters to the data source:

*---------------------------------

FUNCTION GetAttorneyByName( tcLastName, tcFirstName )

*---------------------------------

LOCAL loDataSource, loADORecordSet

loDataSource = .NULL. && The Data Source

loADORecordSet = .NULL. && The Resource

THIS.GetDataSourceObject( @loDataSource )

loADORecordSet = loDataSource.GetAttorneyByName( tcLastName, tcFirstName )

RETURN loADORecordSet

ENDFUNC

The resource manager Get() methods always accept the same number of parameters as the data source Get() methods and call the data source passing in those parameters. Sounds simple, right? Well it is. However, you might be asking yourself “How does the resource manager know what data source to call?“ To answer that we must look at how COMCodebook manages the resource manager’s collaborating objects.

Defining Participating Entities

All the objects used in a resource manager’s lifetime are what we call participating entities or collaborating objects. They are all the objects that work together to get things done. For instance, the resource manager needs to know what data source and which validatior objects to use when getting and saving resources.

Let’s look to the Attorney.DefineParticipatingEntities() method in adatasources.prg:

*-------------------------------------

PROTECTED FUNCTION DefineParticipatingEntities()

*-------------------------------------

DIMENSION THIS.aEntityReferences[3,4]

THIS.aEntityReferences[1,1] = "AttData.AttorneyDataSource"

THIS.aEntityReferences[1,4] = "AttorneyDataSource"

THIS.aEntityReferences[2,1] = "AttValid.AttorneyValidator"

THIS.aEntityReferences[2,4] = "AttorneyValidator"

ENDFUNC

What we are looking at here is the vital information the resource manager needs to call its data source and validator. The aEntityReferences array property holds the names of the data source and validator components along with other information that is filled in by the COMCodebook framework and used on save. The two columns you must fill in on the a-layer is column 1; the name of the class definition to use (i.e. AttData.AttorneyDataSource) and column 4; the alias used to reference this object (i.e. AttorneyDataSource). Methods in the resource manager like GetDataSourceObject() use this information to instantiate the necessary objects.

You should notice that we are once again placing a class’s name into a property. Remember we did this in our data source’s cSearchSyntaxClass, ADOComponentsParameterClass and cConfigurationObjectClass properties.This is handy for two reasons. For one, it keeps collaborating object names together in one section of code. Second and more importantly, it allows us to easily change which object to use without having to search for the line of CreateObject() code where we instantiate the object (which could possibly be far up the framework hierarchy). Consider this piece of code:

Obj = CreateObject("AttData.AttorneyDataSource")

As opposed to this:

lassName = "AttData.AttorneyDataSource"

Obj = CreateObject(lassName)

The two pieces of code do the exact same thing; they create a reference to an AttorneyDataSource object in the AttData.DLL. The benefit of the latter is that we can now separate our class name from the actual instantiation of it. You’ll see a big benefit for doing this when we talk about debugging in the next section. Debugging VFP components is a lot easier when they are contained in the same project. We could easily change our lassName in the example above to simply “AttorneyDataSource” which would tell VFP to instantiate a local VFP class instead. This way we could compile our data sources and resource managers together and debug them easier than if they were in separate DLLs. More on how to do this later.

Coding the Validators

The validators are responsible for field by field or row by row validation of a single resource. In our design of the validators we defined two rules.

1. In the Attorney resource we will check to make sure the LastName is not empty.

2. In the Attorney Note resource the Note_Time field must be non-empty date.

Let’s open up our AttValid project if it’s not already open.

[pic]

There is only one .prg file in this project that you have to worry about, avalidators.prg. Here is where we define the validation rules for the fields. Scroll down a little to the class definition for the AttorneyValidator. Type in your validation code:

*------------------------------------------------------

DEFINE CLASS AttorneyValidator AS aValidator OLEPUBLIC

*------------------------------------------------------

Name = "AttorneyValidator"

*------------------------------------------

FUNCTION vldLastName( tcValue, tlPreStatus, roMessage)

*------------------------------------------

LOCAL llRetVal

*-- Define our business rule here

llRetVal = .NOT. EMPTY( tcValue )

*--

If NOT llRetVal

If Vartype(roMessage) = "O"

roMessage.AddMessage("AttorneyEmptyLastName", THIS.Name )

Endif

Endif

RETURN llRetVal

ENDFUNC

ENDDEFINE

Since our simple application has simple validation rules, our class definitions are also simple. You see how the name of the function corresponds to the name of the resource’s field with the prefix “vld”? This is how the resource manager calls the corresponding field-by-field validation. Remember, this field name is not necessarily the same name as the field in the underlying table. It is the name your data source assigns this field via your select statements. In our case they happen to be the same. If the validation fails, (llRetVal = .F.) a message is added to the validation messanger object. This message is defined in the AttorneyApp\Validation\MSGSVC.DBF file. This VFP table defines the validation keys and messages for our application. It only contains two records because we only have two business rules. If we had more, we would define them here.

[pic]

For each record we need to fill in at least two pieces of information: the ckey field and the coriginal field. The ckey field is the name of the key that is being passed to the AddMessage() method. The coriginal field is the validation text that will be displayed when a validation fails. There is a field called cguivisual that is optional and is the name of a picture you may want to display. If you are interested in reading about the validators in more detail, please read the whitepaper “Validating Data In COMCodebook” by Michael Emmons.

If you scroll down a little, you’ll notice I already wrote the AttorneyNoteValidator class definition for you. The important validation line is:

llRetVal = .NOT. EMPTY( tdValue ) AND .NOT. ISNULL(tdValue)

Simple stuff! Even though the validators are simple to program, do not underestimate their role in the middle-tier. They save your database from rejecting records that don’t follow the rules. Performing these rules before the resources are even lined up to be saved saves you a trip to the database. That means better performance and scalability.

The Processes

The last project I want to take a look at is the Process project, Processes\AttProcess.pjx. This project contains the resource manager controller (RMC) process object. We talked a little bit about the RMC in the COMCodebook Theory section. Just remember that the RMC is a built-in process object that handles multiple-resource/multiple-record saves. We’ll get into how to use the RMC when we build our front end. For now, just be aware that we have a project for all our process objects. If we were to define more processes in our application, we could put them here. This simple attorney application is only going to use the built-in process object, the Resource Manager Controller. Take a look at the AttProcess.pjx file:

[pic]

The aprocess.prg contains our concrete class definition for the Resource Manager Controller, RMController. We do not need to do any additional coding in this project since we do not have any additional processes in our application.

Done With Resource Managers, Validators and Processes

Do you know what you just did? You just coded all the pieces that make up the middle-tier of our Attorney application. Congratulations! We learned how easy it is to create the resource managers once the data sources are written. We also saw how the collaborating objects’ names are stored in properties of the resource manager to make it easy to modify and debug. Next step is compiling and registering the components.

Part 5 – Compiling, Testing and Debugging

Compiling the Components

Now that we have put all our pieces together it is finally time to build the projects. First make sure your paths are set up correctly. In the VFP Command Window type:

CD “D:\FLASH\Codebook\AttorneyApp\Interface\”

DO StartCB

This will set up your development environment and open up all four projects, AttData, AttResMgr, AttValid, and AttProcess. Let’s build the data source project AttData first by clicking on the build button on the project manager. Under the Build Options, select Multi-threaded COM server (dll) then click OK.

[pic]

Go ahead and leave the default \DataServices directory to place the .DLL file into. Visual FoxPro will whiz through compiling all the prg’s into a DLL and register the COM component in your registry. Now build the AttResMgr, AttValid and then the AttProcess the same way you built AttData accepting the default directories to place the DLLs (\ResourceMgrs, \Validators and \Processes respectively). To show you that VFP registered your components, let’s take a look at the registry:

[pic]

As you can see, VFP has compiled all your code and registered all the components. Easy stuff!

Breaking Your Interface

I’d like to take a moment to mention the problems you will run into when compiling components where you have “broken your interface”. Visual Basic, Visual C++ and Java are “early-binding” or vtable-binding languages. This is unlike Visual FoxPro, or scripting languages like VBScript which are “late-binding”. Early-binding languages can strongly-type their variables. This means that the calls to functions on the components and their signatures are resolved and compiled into the application. This allows for faster access to the components in these languages because the calls are resolved at compile time. In a late-binding language like VFP or VBScript, the component function calls must be resolved at runtime.

When you remove a public method in a class definition in your component you will break the early-binding VB or VC++ client code. This can also happen when you change a public method’s calling syntax in your component, like adding or removing parameters. So if you have a VFP component being accessed from an early-binding language, you should not change the calling syntax for accessing any public method or property because of the risk of the dependencies the early-binding components have built on the original class definition. If you do happen to break an interface during development, and you probably will, you will have to modify and re-build all the components that rely on that interface to use the new calling syntax. This is up to you to remember. Visual FoxPro will happily re-compile your components without warning. You will receive obscure error messages from the calling code if the interface is broken. Visual Basic usually gives you a “Type Mismatch” error. If all else fails, re-compile all your components. For more information on vtable-binding see “Building, Versioning, and Maintaining Visual Basic Components” by Ivo Salmre in the Online MSDN library at .

Testing Your Components

Calling the Get() Interfaces

Whew! We are finally ready to test our components. Let’s run a simple test through the Visual FoxPro command window. Type:

oAttorney = CreateObject("AttResMgr.Attorney")

You just instantiated the Attorney resource manager. Now let’s request some data. Type:

rs = oAttorney.GetProxy()

The rs variable is your resource. In our case an ADO recordset was returned. To browse through the fields, type these lines of code:

? rs.Fields(0).Value

? rs.Fields(1).Value

rs.MoveNext

You can also refer to the field object by name. For instance rs.Fields(0).Value is the same as rs.Fields(“cDesc”).Value. Remember in our AttorneyDataSource.GetProxy() method we defined the fields returned as cDesc and upID. Play around with the other interfaces we defined like GetAttorneyByName(), and create an AttorneyNote resource and test it as well.

When you are finished with your resource manager you should set the reference to NULL like so:

oAttorney = Null

This will release the Attorney resource manager object.

Calling the Get..BySearchCriteria() Interface

Let’s try something a little harder by testing our GetAttorneyNoteBySearchCriteria() interface. To test it, we need to send it a search criteria resource. How do we do that? We must first decide what kind of resource we want, an ADO recordset or an XML stream. For testing, let’s get an XML stream. (I’ll show you how to manipulate an ADO search criteria recordset in ASP when we build our web-interface.) First we must instantiate the AttorneyNote resource manager. In the command window type:

oAttNote = CreateObject("AttResMgr.AttorneyNote")

Then we will call the GetXMLSearchCriteriaResource() interface:

XML = oAttNote.GetXMLSearchCriteriaResource()

This will return an XML stream with all the search criteria elements. To make manipulating the XML stream by hand easier, let’s save the XML string variable to a file by typing the following:

STRTOFILE(XML, "D:\attnote.xml")

VFP will save the contents of the XML stream to a file on your D:\ drive called “attnote.xml”. You can open the .xml file by clicking on it in Explorer. If you have Internet Explorer 5.0 or higher this should open the XML file in the browser. You can collapse and expand the FIELD nodes on the document by clicking the minus (-) and plus (+) signs on the nodes. Notice that all the fields from the ATTNOTE table are there plus the ATTORNEY.LastName that we added in our AttorneyNoteDataSource’s AddXMLSearchCriteriaChildNodes_Post() method in the adatasources.prg.

[pic]

Well this is great, but how do we enter some search criteria? Internet Explorer only displays the XML, it does not allow you to manually edit it. To edit it, open the file D:\AttNote.xml in notepad. There we can edit the XML stream by hand. Remember, we are only doing it by hand to test. If we were writing a user interface, it would have the code in it to parse the XML stream automatically filling in user search criteria. The Microsoft XML parser is an excellent tool for manipulating XML files. But for right now, lets type in some search criteria by hand. Scroll down to the last FIELD node, LastName. Lets put an “F” in the Value element like so:

Attorney

LastName

129

LIKE

F

This means we want all the notes for all the attorneys where their last name starts with the letter “F”.

Now save the file. Back in VFP, type the following into the command window:

XML = FILETOSTR("D:\attnote.xml")

This will place our changes into the XML search criteria resource variable. Now we must send this resource into the proper Get() interface like so:

rs = oAttNote.GetAttorneyNoteBySearchCriteria( XML )

This will return an ADO recordset with all the notes that belong to attorneys whose last names start with the letter “F”. Try fooling around with different combinations. For instance, put an order number in the ORDER element and change the operator to BETWEEN and specify an ENDVALUE. Make sure we don’t have any problems.

Now what if we want to see the SQL strings being generated? How would we output messages to go about debugging syntax errors if they arose? The next section on debugging will explain a nifty function COMCodebook gives us to do just that.

Debugging

Generating DebugLog() Messages

The COMCodebook framework has built into it a function to help troubleshoot errors you may occur while testing your components. This function is written into the cSession class at the top of the hierarchy so that you may use it anywhere in your code. This function is called DebugLog(). DebugLog() will accept any string parameter and output it to a log file. The log files will show up in your machine’s particular TEMP directory. The names of the logs are named after the classes that generated the call. Let’s put a DebugLog() call into one of our Get() interfaces to output the SQL string before it executes. In VFP, open up the adatasources.prg in your AttData project. Find the AttorneyNoteDataSource.GetAttorneyNoteBySearchCriteria() method and at the bottom of the function type the following bolded text:



THIS.DebugLog( loADO.mandText )

THIS.ExecuteSQLQuery( loADO )

RETURN loADO.oRecordSet

ENDFUNC

Save the file then re-build the project as a multi-threaded DLL again. Assuming that you still have a D:\AttNote.xml file with search criteria specified, let’s run the following bit of code. You can do it in the command window:

oAttNote = CreateObject("AttResMgr.AttorneyNote")

XML = FILETOSTR("D:\attnote.xml")

rs = oAttNote.GetAttorneyNoteBySearchCriteria( XML )

oAttNote = .NULL.

Now let’s open up Explorer and go out to your \TEMP directory. We should see a file called AttorneyNoteDataSource.LOG. Open it in Notepad.

03/06/00 06:31:59 PM: AttorneyNoteDataSource -

SELECT Attnote.*, Attorney.LastName, RTRIM(Attorney.LastName) + ', ' +

Attorney.FirstName AS FullName FROM AttNote, Attorney WHERE

Attorney.LastName LIKE 'F%' AND Attnote.Note_Id = Attorney.Attorney_ID

You will see the date, time and class name followed by your DebugLog() string. In our case we sent the SQL string. Notice the WHERE clause that was generated from the XML search criteria resource.

This function becomes extremely handy when you are debugging. Just remember to take the DebugLog() calls out before you send your components off to production. Repeatedly writing that extra information to disk can greatly impact the performance of your components.

Debugging Across Components

Sometimes a problem arises where DebugLog() just isn’t enough. There will come times when we really need to trace our code in the good old VFP debugger. Unfortunately, VFP isn’t so hot at debugging DLLs. As a matter of fact, it just plain won’t! What we usually have to do in this situation is compile our components as .APP or .EXE files and then trace like normal. We can put a main.prg file in our project and instantiate a local copy of the class we are trying to debug and trace through the code in the VFP debugger. But what happens when we instantiate other components? The tracing does not follow the code into the outside component (dll). Luckily, COMCodebook has made it easy for you to solve this problem.

We have talked a couple times about placing a class’s name into a property to make it easier to manage the creation of collaborating objects. Since we cannot easily debug VFP across components, we can specify local class names to instantiate instead. This way we can compile our data sources and resource managers together and debug them in the familiar VFP debugging environment.

For example, you might recall our DefineParticipatingEntities() method of our attorney resource manager contained this line of code:

THIS.aEntityReferences[1,1] = "AttData.AttorneyDataSource"

This refers to the component AttData contained in a separate DLL file. If we need to debug across our AttResMgr and AttData we would change the line to read:

THIS.aEntityReferences[1,1] = "AttorneyDataSource"

This tells the resource manager to instantiate a local VFP class instead of a component. Then we would compile the data source and resource manager code into one project and debug it like any other VFP program.

Done with the Middle-Tier

We made it! We have successfully programmed, compiled and tested our COMCodebook middle-tier components. In the sections that follow, we will design a web-based application that uses these components and I show you how easy it is to get and save data via active server pages. Remember that COMCodebook does not dictate the presentation, so you can create thick clients in any language like Visual FoxPro (see the TimeTrac Sample) or Visual Basic. I will, however, explore a thin-client interface using Internet Information Server and ASP.

Part 6 - Designing the Presentation

Defining the Forms Based on the User Process

We already briefly talked about our user’s process in the context of creating our Get() interfaces to the data sources. That process was to allow the user to flexibly search for attorney notes and also allow them to bring up the attorney form in order to modify the notes or the attorney itself. Our application is a pretty simple one. We will, however, still have to create a few different forms to facilitate the user process. Let’s start by scoping out what all our forms need to look like.

The Attorney Note Search Form

First we will need a search form. This is where we will bring the user first. That form will have three fields on it. The Attorney Last Name, the Note Subject and Note Keywords. Note Keywords will search for an instance of the word in all the notes using our Attorney Note search criteria resource.

When it is finished, the form should look something like this:

[pic]

From this form we will display our results of the query. That brings us to our next form, the Attorney List.

The Attorney List (Search Results)

The Attorney list is how we are going to display our results. We will want to display Last Name, Note Subject and Note Date. The Note subject will be a hyperlink that will pop up a smaller note window where they can view or modify the note. The attorney last name will be a hyperlink to the attorney form itself. We will also have a hyperlink to allow the user to navigate back to the search form.

The list will look something like this:

[pic]

The Attorney and Attorney Note Forms

The Attorney form will display the attorney information as well as display a grid of child notes in reverse chronological order. The user can click on a note in the grid to pull up the smaller note form or they can add a new note by clicking on a hyperlink on the grid that says “Add Note”. The user will also be able to add, modify and save the attorney parent form itself. From the attorney form we will have a hyperlink to allow the user to navigate back to the search form.

When we are finished, the Attorney form should look similar to this:

[pic]

And the Attorney Note form should look something like this:

The Conceptual Web Diagram

[pic]

It is always a good idea to create a web diagram of the forms that link to each other in a web application. There is a lot of software out there that helps you accomplish this, including FrontPage Explorer and Visual InterDev. We don’t have a very large application and I want to keep this example simple. I’d rather not complicate it with all the bells and whistles the FrontPage server extensions provide, so I will just draw the web diagram by hand.

First we will draw a general concept diagram. Basically, this means that we will only put the windows or screens our users will see as they navigate through the web interface. This makes it easier for the non-programmers to understand what the interface is generally going to do. Here is what I am proposing:

[pic]

This diagram points out all the windows the user will navigate through, starting with the Note search form. The boxes in bold represent the main browser window, while the smaller box (the Note Form) represents an additional window that will pop up. Why do we call this a conceptual diagram? Because this diagram represents all the browser “windows” or “screens” that the user will see, which may not necessarily correlate to the physical pages we will have to write.

The Physical Web Diagram

The physical web diagram more completely describes to the presentation programmer what web pages specifically must be created in order to achieve the goals of the conceptual model we created above. Based on the four windows we see above, we can start to plan how many pages that will translate to.

The Search Form

The search form will be where the users are directed to by default when they navigate their browsers to the application web site. For this reason, we will name that page Default.asp. This tells Internet Information Server (or Personal Web Server) to direct people to this page first. This page will be fairly simple, displaying three searchable fields, one submit button and one reset button. This page will simply submit its information to the search results form. No additional processing will be necessary.

The Attorney List (Search Results)

The attorney list form is a little more complicated. It will accept the values from the search form and call upon our middle tier to set up our search criteria resource. We must then populate the search criteria resource with the user’s search criteria submitted from the search form. It will then send that resource back into the middle tier to retrieve our result set. Since this page accesses our middle tier, it will contain ASP server-side code. We will call this form AttorneyList.asp.

The Attorney Note Form

The attorney note form is what I think of as a “real” form, meaning it has add, update and delete capability. We must perform different functionality based on user actions. To accomplish this we could have many different forms each performing one action (i.e. an Add form, a Delete form, an Edit form, etc.) or we could create one ASP page that handles all the actions. We can separate the visible part of the form from the code that performed the actions by using ASP Functions and Subroutines. This is much easier because you keep the presentation of the form in one web page. We could even go a step further and define the Functions and Subs in another included file. For the purposes of our simple application, we will include all our code that handles the attorney in one ASP page called AttorneyNote.asp. The AttorneyNote.asp is responsible for displaying, formatting and submitting the data, as well as for “processing” the data.

The Attorney Form

The attorney form is also a “real” form. It has add, update, delete functionality so we will need an Attorney.asp page just like our note form did. But this form also has something new; a list of child records. We will want to display the child note records as a list or a grid at the bottom of the form. How will we do this? We will use a DHTML tag called the inline frame to place content from another page into a specific position on a form. So what we call our attorney form will actually be three web pages. The Attorney.asp that contains all the parent record information, the AttorneyNoteGrid.asp which contains a list of child notes, and finally the page that holds them together with the inline frames. We’ll call this page cntAttorney.asp. This page will not have very much code in it. It is essentially just a container to hold the attorney (parent) and the notes (children) together. Note that this approach allows us to reuse either the main attorney page or the attorney note grid on another web page should such a need arise.

Here is what our physical web diagram now looks like. It has all the pages we are going to need to create our web interface.

[pic]

It is very important to design an application before jumping in and programming, especially in a web application, even a simple one like this. A web application has multiple pieces that need to be organized, documented and designed. COMCodebook makes this easier on you because most of the complicated middle-tier is already written for you, however, it is still up to you to program your databases, COM interfaces, process and user interfaces which can’t be done without a plan. In this section we designed the final piece, our user interface. Now it is time to start creating the HTML form templates.

Part 7 - Creating the Presentation Templates (HTML)

In creating our presentation templates, I will touch on the basics of creating Hyper Text Mark-up Language (HTML) forms. This is the most artistic part of creating our application. Here you will design the look and feel of the web interface. I will only briefly discuss some of the more important HTML tags we will come across. If you would like more information on the WC3 HTML 4.0 language specification please see . There is also a good HTML Tag reference at . We will use FrontPage to initially design the layout templates, and then I’ll show you how to create Cascading Style Sheets to breathe some style into our templates.

Hyper Text Mark-up Language

Learning HTML is just a matter of practice and familiarity of the tags. HTML is not a programming language, it’s just a way to format text on a page. Since it’s not a programming language it is very easy to pick up even by non-programmers. HTML is just text, therefore, you can use a simple ASCII editor to create HTML files that display in a browser. However, if you’re just learning, it is much easier to use an HTML editor. There are probably hundreds of editors out there. Even Microsoft Word can be used to edit HTML files. We are going to use Microsoft FrontPage. Of the editors I’ve tried, I like Microsoft FrontPage and good old notepad the best. (If you don’t have at least Microsoft FrontPage98 installed, it can be found on your Visual Studio 6 disk #2 in the \FRONTPAGE directory.)

Let’s then start by opening up FrontPage by clicking on the FrontPage icon on your Start/Programs menu. It will automatically open up the FrontPage Explorer. The FrontPage Explorer is used to connect to an existing FrontPage web or to create a new web.

[pic]

In the example above, FrontPage is connected to my web server named “eam”. We don’t have to create a FrontPage web to create a web application. And to keep this example simple, I don’t want to. (For more information on creating FrontPage Webs please see the FrontPage help file.) So if you opened a web on startup, lets just close it by selecting File/Close FrontPage Web. This will bring back up the Getting Started dialog box. From here just click “Cancel”. Now from the Tools menu select “Show FrontPage Editor”. (Note: Frontpage2000 automatically opens up with the Frontpage Editor) This is the tool we are going to use to create our templates. You might want to create a shortcut to the FrontPage Editor directly from your desktop.

[pic]

When you open the FrontPage Editor it will automatically put you in a new document called “New Page 1”. Notice the tabs on the bottom left-hand side of the screen, “Normal”, “HTML” and “Preview”. Select the HTML tab. This will flip you over to the HTML code itself so you can choose to design in a WYSIWYG format or as straight code. Play around with the editor in the normal view and then look at what FrontPage generates as the HTML code in the HTML view. This will help you learn HTML.

HTML Tags for Document Structure

I want to go over the important tags that make up the HTML document structure. As we discuss all the tags to follow, type the code examples into the HTML pane of the FrontPage editor and then flip to Normal view to see the visual output.

You should notice that HTML tags always come in begin-tag/end-tag pairs. Everything in between a begin-tag and end-tag is formatted to the specification of that particular tag.

HTML – Outermost Tag

The HTML tag identifies a document as an HTML document. All HTML documents should start with the tag and end with the tag.

HEAD - Document Header

The HEAD tag defines an HTML document header. The header contains information about the document rather than information to be displayed in the document. The web browser displays none of the information in the header, except for text contained by the TITLE tag which is placed on the browser’s title bar.

BODY - Main Content of Document

The BODY tag specifies the main content of a document. You should put all content that is to appear in the web page between the and tags.

FORM - Gathering User Input

The FORM tag creates an HTML form. The form can contain controls such as text fields, buttons, checkboxes, radio buttons, and selection lists that let users enter text and make choices. All controls on the form must be defined between the and tags. As well as these controls, the form can contain other elements such as headings, paragraphs, tables, and so on.

When the form is displayed in a web browser, the user can fill it out by making choices and entering text using the controls, and then submit the form by clicking a "Submit" button. There is an important attribute of the form called ACTION that specifies where the form values shall be submitted to when a submit button is clicked. For example:

This sets up the form to “post” or submit its values to the ProcessForm.asp file. We will see this a lot in our web application.

The HTML Formatting Tags

I don’t want to go through all the text and formatting tags there are in HTML for obvious reasons. There are probably about thirty of them at least. However, I do want to touch on a couple of important ones, and especially one you will be seeing in our forms the most often; the table.

TABLE – The Table Tag

The TABLE tag defines a table. Inside the TABLE tag, we use the TR tag to define rows in the table, and use the TD tag to define table cells.

column 1, row 1

column 2, row 1

column 1, row 2

column 2, row 2

This HTML Table will look like this in the browser:

We use tables quite often when designing user data-entry forms. They help us immensely when trying to position our labels, textboxes and other controls in nicely lined up columns on the form. There are many attributes you can apply to the TABLE tag. In our example above we are specifying a border attribute of width 1. For a complete list of attributes, see the TABLE tag in your HTML Reference.

H1 - H6 – The Standard Heading tags

The tags H1, H2, H3, H4, H5, and H6 display headings. Level 1 headings (H1) are the largest size headings, and level 6 headings (H6) are the smallest. Headings are usually displayed in a bolder, larger font than normal body text. When we talk about cascading style sheets we’ll see how we can control the styles of these heading tags and create new classes of headings. As a matter of fact, we can do this with any of the HTML tags we have available. I’ll show you how later in this section.

HTML Controls

There are really only four tags that generate controls. INPUT, SELECT/OPTION and TEXTAREA. The INPUT tag, however, has many different values for what is called a Type attribute on the tag. The Type attribute tells the browser what kind of INPUT control; a button, textbox, checkbox, radio button or a hidden field among others. We will discuss only few important controls that we will use in our application.

When a user clicks a submit button, the form is submitted, which means that the ACTION specified for the form is invoked. Like in the form example code above, the form will submit its values to the ProcessForm.asp file.

When a user presses a reset button, all elements in the form are reset to their default values.

A button appears in the form. You must specify client-side script code as the value of the ONCLICK attribute to determine what happens when the user clicks the button.

A textbox control is a single line text input field in which the user can enter text.

A hidden input is an invisible control whose main purpose is to contain data that the user does not enter. This data gets sent to the invoked active server page when the form is submitted. This allows you to deliver a value to the active server page without the user having to enter it or more importantly, modifying it. You should note, however, that it is not very hidden because the user can see it by viewing the document source. We will use hidden fields to carry record ID’s in our application. There is no use having the user see this information on their screen (since they probably don’t know what it is anyway) but it is also not a problem if they find out what the ID is by viewing the source.

The SELECT tag defines a drop-down list on an HTML form. A drop-down list displays a list of options from which the user can select an item. If the MUTLIPLE attribute is supplied, users can select multiple options from the list at a time. If the MULTIPLE attribute is not supplied users can select only one option in the list at a time. The SIZE attribute specifies how many options in the list are displayed at one time. If you specify the SIZE attribute, the list is displayed as a scrolling listbox that shows the specified number of options, regardless of whether the list allows single or multiple selection.

The OPTION tag specifies an option in a drop-down list or listbox. Use the OPTION tag inside a SELECT tag. When the form containing the drop-down list or listbox is submitted to the server, a name/value pair is sent for each selected option in the list. The value portion of an option is the value of the VALUE attribute, if it has one, otherwise, it is the text that follows the tag.

Anna

Tony

Beth

Jennifer

Garfield

Toby

The TEXTAREA tag defines an editbox control which is a multi-line input field. Editbox controls let the user enter words, phrases, or numbers. You can define the number of characters per line the editbox control can accommodate without scrolling by supplying the COLS attribute. You can specify that the number of lines that can appear without scrolling by supplying the ROWS attribute. Scrollbars appear in the editbox control if the text in the text area element exceeds the number of specified columns or rows.

This is a note

Wow! Lots of fun stuff! Now that we have gone over some of the HTML language, we are ready to create our first template. In your AttorneyApp\Interface directory I have provided for you the template web pages; Default.asp, AttorneyList.asp, cntAttorney.asp, Attorney.asp, AttorneyNoteGrid.asp and AttorneyNote.asp.

The HTML Templates

The Search Form Template

In FrontPage, open the D:\Flash\Codebook\AttorneyApp\Interface\Default.asp file. You should see the Attorney Note Search form in the FrontPage Editor main window.

[pic]

The dotted line indicates a FORM. This form consists of two tables one inside the other. The outside one has only one row and one column and is defined as 650 pixels wide and has a border of size 1. The inside table consists of two columns and five rows and its defined width is 100%. Since it is contained in the outer table and that size is fixed at 650, the inside table will be 100% of 650 pixels which is 650 pixels. That way if we decide to make our screens larger, say 800 pixels wide, then the inside table will stretch to 100% of 800 pixels.

. . .

. . .

This is the recurring technique I use to create a data entry form with a border. Since we need to display information to the user in an eye pleasing way, we will use the inside table to create rows of Label/Field pairs. To do this, in this case we have two columns. One for the labels and one for the fields. Other forms may require four columns so that Label/Field pairs can be placed next to each other.

We are going to want to add another field to this search form called “Note Key Words”. Place your cursor on the fourth row and type “Note Key Words:”. Then tab to the next column. From the menu select Insert/Form Field/One-Line Textbox. It will place the textbox in the table cell. Double-click on the textbox and specify the name as “note_text”. Take a look at your HTML.

. . .

Note Key Words:

. . .

I would also like to point out how the form is actually submitted back to the server. Take a look at the HTML view of the search form if you are not already there. You should see a line that reads:

The name attribute simply names the form. Keeping true to VFP, I happened to name my form “thisform”. But you could name it anything you want. The method and action attributes tell us that the form’s contents will be posted back to the server to the ASP page AttorneyList.asp. This is how the AttorneyList.asp form will be able to read the user’s input. We’ll see how to do this when we code the server-side script.

You should be getting a better feel for HTML now. I have created the templates for the rest of the forms for you in the /Templates directory. Notice that these forms are without any server-side ASP script in them. As you learn HTML and ASP in these lessons you can enter the code in them. Or you can just refer to the completed pages that I have already placed in your interface directory. These pages, however, contain ASP code in them and may not display correctly in the FrontPage editor. That’s why it is important to design your templates first and code your ASP last.

The Rest of the Form Templates

The rest of the form templates are in the \AttorneyApp\Interface\Templates directory provided for you. Please look through all the templates and get familiar with the HTML that is used to generate the look of the forms. You will notice that a couple of the forms do not have any visible controls on them. This is because most of that particular form will be created from server-side ASP script. For example, the Attorney List form that displays our search results is called AttorneyList.asp. There is not much to look at template wise with this form because most of it is going to be server-side ASP script. This form’s main job is to create the search criteria resource and fill it with the user’s search criteria that they filled out on the search form we just created. We will look at the ASP code on all our forms, including this one, when we start coding our form functionality in the next section.

After you look through all of your form templates, you will probably notice how dull they look. I purposely did not format any of the styles on these templates because that is exactly what our cascading style sheet is going to do!

Cascading Style Sheets (CSS)

Style sheets give you greater control over the presentation of your web documents. Using style sheets, you can specify many stylistic attributes of your web page, such as text color, margins, element alignments, font styles, font sizes, font weights and more. You can set margins and also specify borders for block-level elements. You can set the padding for elements that have borders, to indicate the distance between the element's content and its border. There is a wealth of information on how to create style sheets out on the Internet. I am only going to briefly cover how to create a simple style sheet in Visual InterDev. If you are interested in CSS, have a look at the W3C recommendation at .

There are two types of style sheet syntax. There are style sheets written in cascading style sheet (CSS) syntax and also style sheets written in JavaScript that use the document object model of the particular browser. When you define a style sheet, you must declare its type as either "text/CSS" or "text/JavaScript".

We will only discuss the CSS syntax in the following lesson since creating CSS files are easier because you can keep your styles in one file that all your HTML files can reference. In this way, style sheets allow you define the style for a particular HTML element once, and then use it over and over on any number of web pages. If you want to change how an element looks, you just change the style and the element automatically changes wherever it appears. Visual InterDev provides you with an interface to easily manipulate CSS files.

Like HTML, CSS files are just text. You can just as easily create cascading style sheets in notepad as you can in any other editor. However, I choose to use Visual InterDev for ease of use. You will see that the CSS editor is fairly intuitive. For more information on Visual InterDev’s CSS editor, see the CSS Editor Window under the Visual InterDev Documentation in the Online MSDN Library.

I have provided a sample style sheet file to get you started. Let’s open that file in Visual InterDev now.

[pic]

The window contains two main sections: the Selector tree and the property options. In the Selector tree, you can choose the HTML tag, class, or unique ID to modify. You can also add new HTML tags, classes, or unique IDs. You will see that I have included some different base tags as well as some classes that inherit from some of them. You can define styles for any of the HTML tags that exist. To add a new tag, just right click on the selector tree and select “Insert HTML tag” from the shortcut menu. To define a style, select the tag on in the Selector tree and then choose the stylistic attributes of that tag in the property options. The property options has many tabs on it which contains all of the styles you can specify for your tags.

Let’s select the BODY tag in the Selector tree and the “Font” tab on the property options to see the attributes that define a font’s style. In the example I’ve provided, I have chosen a Verdana 10pt Black Font on a white background for the BODY tag. The second and third choices for fonts follow as Arial then Times New Roman. This means if the browser does not have the Verdana font installed it will use Arial, and if it doesn’t have Arial then it will use Times New Roman.

Move around the selector tree and take a good look at all the styles I have defined to get you started. Don’t be afraid to pick new colors and define your own styles. If you switch the property options to the “Source” tab, you can view the source code generated by the attributes you have specified. This is the same thing you would see if you opened your CSS File in notepad.

BODY

{

BACKGROUND-COLOR: #ffffff;

COLOR: #000000;

FONT-FAMILY: verdana, arial, 'Times New Roman';

FONT-SIZE: 10pt

}



Now let’s take a look at the “Classes” section of the Selector tree. These are classes I have defined that inherit their styles from the base tags. For instance, select the class .notelist in the tree and click on the font tab on the property options. You will notice that I have not specified any additional fonts or font colors here. This means that .notelist will inherit from BODY. If you flip over to the Background tab you will see that I have specified a different background color than the BODY tag.

In many cases, styles on HTML tags are inherited from their container tags. This means that the styles of the outermost tags in the HTML document cascade to the inner tags. For instance, the tag of the HTML is the outermost tag of the HTML body. Tags contained inside the tag (which is almost everything) inherit their style from the tag.

For example, suppose a style has been assigned to the element as follows:

H1 {color:blue;}

The headline is important!

In this case, the child element takes on the style of its parent, which is the element, so the word is appears in blue. However, suppose you had previously set up a style specifying that elements should be displayed in red. In that case, the word is would be displayed in red, because properties set on the child override properties inherited from the parent.

Defining a style sheet is really an art and should not be taken lightly. Users are very influenced by the “look” of a system. They associate a nice looking application with a professional one. If you do not have a knack for user interface design or have artistic talent, leave defining the style sheets to someone who does. If such a person is not available, I’ve outlined a few simple guidelines to follow when designing your sheets.

1. Define your styles from outermost tag to innermost tag.

For example, start with the tag and work your way in.

2. Pick a background color or image first.

Choosing the tag’s background color first will help you decide what colors the rest of your text will be. If you choose a dark background color, select light foreground colors for the rest of your tags. If you select a light background color, pick a dark foreground.

3. Choose the body and paragraph text color, font and size next.

4. Decide if the text in tables needs to be different than your body text.

For instance, you could define a smaller font for everything in a table.

5. Define the style of the hyperlinks.

This can include three tags; A:Link, A:hover and A:visited. A:Link is the style of the hyperlinks. A:hover is what happens to the style when the mouse is over the hyperlink text. A:visited is the style of the visited hyperlinks.

6. Define your headings (H1-H6).

Your headings make a big impact on the layout of the form. Choose colors and font styles that will compliment your background. You can even vary the colors of your headings. For example, all your tags are Blue, but your tags are green.

7. Create additional classes if necessary.

I have included additional classes based on the type of forms we will create; Data Entry, Search Results List and the Note List.

Using the CSS File in Your Web Pages

Using the cascading style sheet on a page is easy using the LINK tag in the HTML header.

Attorney

The tag's attributes tell the browser to find an external style sheet, that the style sheet is a CSS file, and that the name of that file is style.css. (This assumes that your style sheet file is in the same place as your HTML file. If not, you need to supply the directory path in the href attribute.)

Now that you’ve linked in the style sheet, all the base tags you have defined will be applied to the tags in the HTML file. If you want to use a specific class you’ve defined in the cascading style sheet, then you use the “class” attribute on the specific tag.

This says to apply the styles defined in the notelist class. You can put the class attribute on any of the HTML tags. For instance, you can apply the style header1, defined in the sample style.CSS file, to a row of a table:

This is the header row

This is a plain old row

This will be what we would see in the browser:

[pic]

If I wanted to change the “header1” class to a red background, then I would modify my style.CSS file and the changes would be applied to all the TR tags of the class “header1”. You should be starting to realize how important CSS files can be in organizing the stylistic attributes of HTML tags away from the actual layout. Even more important, it moves the style into a separate file away from the ASP code that will end up in your web pages. This gives you a better ability to delegate the style of the site to one person, a graphic designer perhaps, and the programming of it to another.

Done with Designing Templates

In this section we learned how to design conceptual and physical web diagrams and the basics of HTML using the FrontPage Editor. We also learned how to manage the stylistic attributes of HTML tags using Cascading Style Sheets. Now that we have finished designing the look of our web application, let’s get back to programming!

Part 8 - Coding the Presentation with Server-Side Script

In this section we will deal with Active Server Pages (ASP) using VBScript and Dynamic HTML (DHTML) in Internet Explorer specifically. The MSDN Library is an excellent reference for DHTML and the IE Document Object Model. We will also explore some of the objects in the IIS Object Model. For more information please see Internet Information Services SDK under Platform SDK/Web Services in the MSDN library.

Overview of the Object Model for ASP Applications

Before we jump in head first programming our active server pages, let’s go over some of the built-in IIS objects we will use in our application. I’m not going to go over all of them, just the ones we are going to use. For more information see the MSDN library.

Server Object

The Server object provides access to methods and properties on the server. Most of these methods and properties serve as utility functions. The one we will be using specifically is the CreateObject() method which will instantiate our middle-tier components. This is how we obtain a reference to our Attorney Note resource manager from an ASP page:

Set obj = Server.CreateObject("AttResMgr.AttorneyNote")

Session Object

We probably won’t use this object in our sample application, however, I’d like to mention it because of its importance. You can use the Session object to store information needed for a particular user-session. As the user navigates around a site, variables can be stored in the Session object, which persists for the entire user-session. You can think of them as global variables for the application. The object will persist for as long as the session, which defaults to 20 minutes after the last user request. That means that the session will be automatically abandoned after 20 minutes of inactivity. One common use for the Session object is to store user information. For example, when a user logs into the system, you can store his/her real name to display on subsequent pages.

Session("UserFullName") = "Joe Shmoe"

Then when you wanted to display the user’s full name in a subsequent page somewhere in the application you would simply write something like this:

Hello ! Welcome back to this awesome web site!

Request Object

The Request object retrieves the values that the client browser passed to the server during an HTTP request. There are many parts to the HTTP request including: form values, cookies, query string values, and server or environment variables. The Request object packages the HTTP request into neat, easy to use collections. The two we’ll be using are the Form collection and the QueryString collection.

The Form collection retrieves the values of form elements posted to the HTTP request within a form using the POST method. Remember our Attorney Search form? When the user submits the form it is posted to the AttorneyList.asp. When we use the form collection on the AttorneyList.asp it will contain the elements on the search form, that is, everything between the tags. You can even have more than one form in an HTML document. You can reference the forms in the form collection as well as the elements if there is more than one.

To retrieve the value of the lastname textbox in our search form example we would write:

The QueryString collection retrieves the values of the variables in the HTTP query string. The HTTP query string is specified by the values following the question mark (?). Several different processes can generate a query string. For example, a hyperlink

Berg, James

generates a variable named ID with the value "12345".

In our example, to retrieve the value of the QueryString on the Attorney.asp form, we would write:

Response Object

You can use the Response object to send output to the client. There are many methods and properties on the Response object. I am only going to go over three of them: the Write and Redirect methods and the Expires property. The Write method writes a specified string to the current HTTP output. This is your means of sending data to the browser.

You can also write out variables:

Which is the same as:

The Redirect method is used when we want to point the browser to some other URL. The Redirect method causes the browser to attempt to connect to that different URL. We most often use this when we are processing a users’ request from say our Attorney.asp page and want to redirect them back to the Default.asp page.

The last thing I want to mention is the Expires property. The Expires property specifies the length of time before a page cached on a browser expires. If the user returns to the same page before it expires, the cached version is displayed. So what can basically happen to our data pages is that they won’t look current if we don’t force the page to expire immediately.

This forces the browser to request the page from the server again every time. We must use care when placing this code on pages because it will cause the server to get hit more often with requests. So never put this on static pages or pages that do not have to be immediately current.

Now that we have gone over the parts of the Object model that we’ll be using in our application, let’s set up our virtual directory in IIS!

Setting Up the Virtual Directory

The directory where we are going to write our active server pages is the \Interface directory under the \AttorneyApp folder. We will need to set this directory up as a virtual web directory. Open up the Internet Service Manager. If you are using an NT 4.0 system with Option Pack 4, this can be found in Start/Programs/Windows NT Option Pack 4/Personal Web Server (or Internet Information Services on NT Server)/Internet Service Manager. This will open the Microsoft Management Console (MMC):

[pic]

Right-click on “Default Web Site” and select New/Virtual Directory from the shortcut menu. The New Virtual Directory Wizard will appear. The first thing you must specify is the name of the virtual directory. This is what we will type in our browser when we navigate to the site. Type “attapp” in the space provided then click next. The next thing you must do is select the physical directory to use. This is our Flash\Codebook\AttorneyApp\Interface directory. You can browse for the directory by clicking the browse button. Once you have entered the physical path, click next. The last screen asks what permissions you want to set on the directory. Make sure the first three check boxes are checked; Allow Read Access, Allow Script Access, Allow Execute Access (includes Script Access). Click the Finish button and you’re all set! You should see your virtual directory in the left hand pane in the MMC and the file list in the right just like in the picture above. Now we are ready to start programming our active server pages!

The Attorney List (Search Results)

Let’s start by opening our attorney list in Visual InterDev. The file is located in your interface directory, FLASH\Codebook\AttorneyApp\Interface\AttorneyList.asp. You will notice that I have started writing the ASP code that instantiates the resource managers on the middle tier.

[pic]

The first lines of code declare variables and then create a reference to the Attorney Note resource manager in the variable obj. We then request from the resource manager a search criteria resource in the form of a recordset. You should recall how we extended our attorney note search criteria resource to include the Last Name from Attorney. That means we should get all the fields in the Attorney Note plus the Last Name of Attorney to search on.

Retrieving Data using the Search Criteria Resource

I have left a section blank where we will be writing code to run through our search criteria resource and fill it with the user’s criteria they filled in on the search page. Since the search page was posted to this page, we can access the field/value pairs from the Request.Form collection. Since the search criteria resource, oFilter, is really an ADO recordset, we access the oFilter object via ADO recordset methods. In particular, the Find method.

We must use the find method to search the rows of the recordset for the particular Name we need. We then set the Value to whatever is in our Form collection.

oFilter.Find "Name = 'lastname'",,1,1

This line of code says to search from the beginning of the recordset for a row with the Name column equal to “lastname”. Fortunately this query is NOT case sensitive, so it will find uppercase names as well. When we find it, ADO will position the record pointer on the row and we will have to set the Value like so:

oFilter("Value") = Request.Form("LastName")

We will do this for the other two fields as well. So our code for filling in the values should look like this:

oFilter.Find "Name = 'lastname'",,1,1

oFilter("Value") = Request.Form("LastName")

oFilter.Find "Name = 'note_name'",,1,1

oFilter("Value") = Request.Form("Note_Name")

oFilter.Find "Name = 'note_text'",,1,1

oFilter("Value") = Request.Form("note_text")

We also want to put this list in some kind of order. I suggest by descending note time order. In that case we’ll write the code:

oFilter.Find "Name = 'note_time'",,1,1

oFilter("Order") = "1"

oFilter("Option") = "DESC"

The next line that you see sends the oFilter search criteria resource into the resource manager which returns a recordset of the actual data that met our search criteria.

Set rs = obj.getAttorneyNoteBySearchCriteria( oFilter )

Finally, you always need to remember to release your object references as soon as possible by setting the variables to Nothing.

Set obj = Nothing

Set oFilter = Nothing

Displaying the Data

Now that we have the recordset of data in the variable “rs”, it is easy to generate the HTML table of data on the page.

Notice the code that generates the hyperlinks at the same time outputting the data row by row in a loop. To generate the popup window of the note, we will be using a small piece of JavaScript that will open a second smaller, stripped-down looking browser window. I’ll talk more about that later.

Now it is time to run our first bit of the application. Point your browser to the virtual directory you set up. For example, type this in the address bar:



You should see the search form in the browser. Type in some search criteria, say a last name starting with the letter “K”. Click the Search button. You should see this in your browser:

[pic]

WOW! Look how easy that was! You can right-click on the browser window and select View Source to see the HTML that was generated on the server and sent to the client. Play around with different search criteria and orders, options, etc. in your AttorneyList.asp page to get a feel for how this stuff works. Our next step is to display the Attorney Note form.

The Note Form

Retrieving the Data

The note form is where we take the user when they click on the note date and time in the list above. You should notice that the hyperlink that was generated includes the name of the form to go to and the QueryString needed once we get there. While in the Attorney List Search Results, right-click on the browser window to view the source:

3/9/00 10:40:20 AM

The openNote() JavaScript function is included in the jscripts subdirectory in a file called notelist.js. We’ll talk more about JavaScript later, however, I wanted to mention that this is the function that will execute when the user selects a hyperlink. The function will then open another smaller browser window and navigate it to the AttorneyNote.asp. Open up the AttorneyNote.asp in Visual InterDev. Take a look at the GetParameters subroutine. The first few lines pull the data from the QueryString.

cID = Trim(Request.Querystring("ID"))

cFK = Trim(Request.Querystring("FK"))

cAction = Trim(Request.Querystring("Action"))

cType = Trim(Request.Querystring("Type"))

We are looking for an ID, a foreign key, and/or a Type and an Action variable (add, delete, etc.). These four values can all be there or not all be there. If you refer to a QueryString variable that does not exist, it will return an empty string. If the variables are not contained in the QueryString, it is possible that the form is being submitted back to the server. In that case the Form values are checked.

If cID = "" and cAction "Add" then

cID = Request.Form("AttorneyNoteID")

End If

If we still do not have an ID, then we need to have at least an Attorney ID (FK) to add a note, so that is checked as well.

If cFK = "" Then

cFK = Request.Form("AttorneyID")

End if

When the Type variable is “List” we will not display the Add or Delete buttons on the note form. That means we will only allow the user to Save or Cancel if they were coming from the Search Results (AttorneyList.asp). However, we will give them all options if they are coming from the Attorney form.

Pop back up to the main program. You should see a case statement that defines diferent actions based on the value of the cAction variable. If we are editing a record for the first time, it will execute the Case Else section of code. Take a look at the GetAttorneyNoteResource() function. The first line of code calls the middle-tier and instantiates the AttorneyNote resource manager.

Set objNote = Server.CreateObject("AttResMgr.AttorneyNote")

The next thing to do is either get a new record or pull up an existing one. We do this based on whether we have an ID in our QueryString or not. In the end, our recordset contains an empty record or one with data. That’s one row either way. In the case of coming from the Attorney List we will be editing a record, so this line will execute:

Set rsNote = objNote.getAttorneyNotebyID(cID)

This simply returns a one record ADO recordset into rsNote.

Populating the HTML Controls With Our Data

The rest of the form uses the values in rsNote to set variables and display the note in the defined textarea. To place data in an HTML input field, we populate the value attribute with the value of our variable or recordset field value in the AssignValuesFromResource() ASP function. In the case of the Attorney note, we set the variable cDesc equal to the rsNote(“note_name”) if we are editing and then use that variable in the HTML input like so:

Take a look at how we populate the rest of the controls on the form. Now let’s run the form again and make sure it works. From the browser, select a note in the list. You should see something like this:

[pic]

You’re starting to realize how easy this really is, right? Our components are doing all of the work talking to the database and passing us resources in the form of ADO recordsets. All we really have to worry about here in the presentation services is the flow of the screens and displaying and collecting the data to/from the user.

The Attorney Form

Now that we have our Note form working from the search results, lets get our attorney form to work as well. When the user selects any of the Attorney’s names, we will bring them to the Attorney Form, which also displays a “grid” of the notes. There are three ASP forms involved in displaying this information to the user, the first of which is the Attorney.asp.

Retrieving the Data from the middle-tier

Getting data from the middle-tier is very straightforward in the Attorney Form because we only need to check if an ID was sent in the QueryString or not. Unlike the Note form, there is no parent key to worry about. Let’s open the Attorney.asp in Visual InterDev and take a look at the GetParameters() subroutine.

cID = Trim(Request.Querystring("ID"))

cAction = Trim(Request.Querystring("Action"))

We are only looking for an ID and/or an Action variable. The ID will tell us the primary key of the Attorney.

Now take a look at the GetAttorneyResource() function. This function is called when the user wants to edit an attorney for the first time or when the user has clicked save. The first line of code calls the middle-tier and instantiates the Attorney resource manager.

Set objAttorney = Server.CreateObject("AttResMgr.Attorney")

You should notice that this form is very similar to the note form. Just like the Note form, the next thing to do is either get a new record or pull up an existing one.

If cID = "" Then

'Adding

'Tell the resource manager to give me an empty recordset

Set rsAttorney = objAttorney.getEmptyResource()

else

'Editing

'Tell the resource manager to retrieve the Attorney record

Set rsAttorney = objAttorney.getAttorneyByID(cID)

End If

'Say bu-bye to the resource manager. It's done with its job.

Set objAttorney = Nothing

After the Case statement that checks the user action in the main program, you will see a call to the AssignValuesFromResource subroutine. This is where the form value variables are assigned if we are editing. This way we can access the values in our HTML like so:

Now take a look at the bottom of the form. You should notice there is a hidden variable located there. The hidden variable stores the primary ID of the attorney we are editing. Let’s remember that HTML inputs have an attribute of “type” that we can set to “hidden”. This creates a line of HTML like this:

We use hidden fields to pass information from form to form. In our case, the Attorney.asp form is submitted to itself. It will need to know the primary ID of the record we are editing in order to save the information. Hidden fields are not shown on the HTML form and the values cannot be modified, but they can be viewed via the “View Source” feature of the browser. That means you should never store passwords or other information you do not want the user to find out about. Storing primary IDs here is usually not a security problem because users rarely care about the primary IDs of the data.

Processing The Attorney

When the user selects any of the buttons on the top of the Attorney form, it will submit all the information between the tags back to the Attorney.asp form.

Let’s take a look at the Case statement in more detail. First, the cAction variable is retrieved. When the user is submitting the form back to the server, the cAction variable is assigned the value of Request.Form(“cmdAction”). You should see that the names of our buttons are all the same, cmdAction. This allows us to check the Request.Form() collection for the value of the cmdAction button in the GetParameters subroutine:

If cAction = "" then

cAction = Request.Form("cmdAction")

End If

Notice that we are also retrieving the value of the hidden field “AttorneyID”. If this ID is empty, we know that we are adding a new record, otherwise we are updating an existing one.

If cID = "" And cAction = "" Then

cAction = "AddNew"

End if

Saving the Attorney

I have written the saving code for the Attorney form for you. Later in this section you will have the chance to write the saving code for the Attorney Note form. Let’s briefly run through the Attorney.asp save code. Scroll down to the ASP Save() function.

The first thing to do is get the current record or a new one based on whether we have a primary key or not. That’s what the GetAttorneyResource function does. In that function we will call GetAttorneyByID() passing it the cID or we will call GetEmptyResource() which in our case returns a blank ADO recordset.

set objAttorney = Server.CreateObject("AttResMgr.Attorney")

if cID "" then

set rs = objAttorney.getAttorneyByID(cID) 'Came from Edit

else

set rs = objAttorney.getEmptyResource() 'Came From Add

end if

set objAttorney = Nothing

Now we have a resource. Next thing to do is fill the resource’s values with the form’s values that were submitted by the user. This is done in the AssignFormValuesToResource subroutine. Any fields that are between the HTML and tags are put into a collection of the Request called Form. The names in the collection are based on the name attributes you supplied for your HTML input controls. So here is how we set the Attorney last name in the resource to the value the user typed:

rs("LastName") = Trim(Request.Form("LastName"))

Trim() simply removes any beginning and trailing spaces from a string. The name attribute of the input on the Attorney.asp form is set to lastname. That is how we reference the lastname in the Form collection which happens to be case insensitive. Take a look at how the rest of the fields in the resource are assigned the Form collection’s values.

Now that we have our resource filled with the form values, we are ready to save it to the middle-tier. When saving a resource to the middle-tier there are a few steps involved on the client because of the nature of marshalling data from presentation services to the resource managers. If you recall, to facilitate saving multiple resources at the same time, for instance in a parent-child scenario, we use an object called a Resource Manager Controller (RMC). This process object acts as the controller object to the resource managers we are saving data to. For that reason, the RMC needs to know the resource managers involved in the save and their relationships to each other. The way we set up this information is in the form of an XML stream. We send this XML string of data into the RMC’s Set_Up_Environment() method which then tells the RMC which resource managers to instantiate as well as sets up the relationship between them in an array internal to the RMC.

Let’s take a look at what the Save XML can look like:

cXML = ""

cXML = cXML & ""

cXML = cXML & "CRMPNAME"

cXML = cXML & "C"

cXML = cXML & "30"

cXML = cXML & "0"

cXML = cXML & ""

cXML = cXML & ""

cXML = cXML & "CRMCLSNAME"

cXML = cXML & "C"

cXML = cXML & "30"

cXML = cXML & "0"

cXML = cXML & ""

cXML = cXML & ""

cXML = cXML & "CPARNTNAME"

cXML = cXML & "C"

cXML = cXML & "30"

cXML = cXML & "0"

cXML = cXML & ""

cXML = cXML & ""

cXML = cXML & "NSAVEORDER"

cXML = cXML & "N"

cXML = cXML & "2"

cXML = cXML & "0"

cXML = cXML & ""

cXML = cXML & ""

cXML = cXML & "CPARNTRELA"

cXML = cXML & "C"

cXML = cXML & "200"

cXML = cXML & "0"

cXML = cXML & ""

cXML = cXML & ""

cXML = cXML & "CPARNTRELA"

cXML = cXML & "C"

cXML = cXML & "200"

cXML = cXML & "0"

cXML = cXML & ""

cXML = cXML & ""

cXML = cXML & "CKEYFIELD"

cXML = cXML & "C"

cXML = cXML & "20"

cXML = cXML & "0"

cXML = cXML & ""

cXML = cXML & ""

cXML = cXML & ""

cXML = cXML & "oAttorney"

cXML = cXML & "AttResMgr.Attorney"

cXML = cXML & ""

cXML = cXML & "0.00"

cXML = cXML & ""

cXML = cXML & "T"

cXML = cXML & "Attorney_ID"

cXML = cXML & ""

cXML = cXML & ""

There are two parts of the Save’s XML stream, the table structure definition (in grey) and the data definition (in black). The first part describes the structure of the data we are sending into the RMController. This table structure definition is optional. If you do not send it into the RMController, the middle-tier will attach it to your XML stream automatically. The second part is the actual data. If you take a look at the Attorney XML string, you will notice that I only included the data and not the table definition. Since we are only saving to a single table, we only have one row of data. Let’s break down the elements of the XML stream one by one:

| |-This tag/endtag pair defines a Resource Manager Proxy environment |

| (optional) |-This tag/endtag pair defines the structure of the data we are going to send |

| (optional) |-This tag/endtag pair defines a single column of data; a field |

| (optional) |-This tag/endtag pair defines the field’s name |

| (optional) |-This tag/endtag pair defines the field’s type |

| (optional) |-This tag/endtag pair defines the field’s length |

| (optional) |-This tag/endtag pair defines the field’s number of decimal places |

| |-This tag/endtag pair defines a row of data being sent into the RMController |

| |-This tag/endtag pair defines the Resource Manager‘s name |

| |-This tag/endtag pair defines the Resource manager class name |

| |-This tag/endtag pair would define the parent of the Resource Manager |

| |-If saving multiple resources this describes the relationship to the parent resources |

| |-You can specify the order the resource is saved in when savng multiple saves |

| |-This tells us that this Resource Manager is the primary (top of the hierarchy) |

| |-The primary key of the table used on add. |

In our case the XML string is a simple one because we are only saving one resource, the Attorney resource. Since there are no children this makes the Attorney the Primary resource.

Now that we have this information, we will instantiate the RMController COM Object and pass the XML into the Set_Up_Environment() method. After that we can marshal the data. Marshalling the data simply means that the RMC will pass the resource to the corresponding resource manager.

set oRMControl = Server.CreateObject("AttProcess.RMController")

oRMControl.set_up_environment cXML

oRMControl.marshalldata rs, "oAttorney"

oAttorney corresponds to the name we gave our Attorney resource manager in the XML element crmpname.

The final section of code performs the actual save and checks to see whether we saved a new record or an existing one. It sets the value of cID to the return value of the save so we can use it to set the value of the AttorneyID hidden field when the form is sent back to the user. This makes it possible for the user to edit the record again after an add of a new one. If the save is successful, this will contain the primary ID of the record. If it fails it will contain FILE_CANCEL ( -3 ).

If cID = "" Then

cAction = "SaveNew"

End if

cID = oRMControl.Save( cXMLValidation )

cID = cStr(cID) 'Convert to a string representation

Set oRMControl = Nothing

If cID "-3"

If cAction "SaveNew" then

cAction = "Saved"

End If

Else

cAction = "Error"

End If

You will also notice that we are sending a variable into the save method, cXMLValidation. This variable is declared private in our main program at the top of the Attorney.asp as the empty string. By sending this string into the Save method by reference (VBScript always sends variables by reference), the middle tier can report any validation problems with the resource. Remember when we were constructing our validators we specified messages in the MSGSVC.DBF file. If the resource fails validation, the appropriate messages will be passed back to the client. By specifying this variable as a string, the messages will be passed back as an XML stream. We can then place the contents of this XML stream on the HTML page as an XML data island and bind a table to it. Data islands are XML data referenced or included in an HTML page. The XML data can be included within the HTML or it can be in an external file. In our case I’ve put the data island right on the HTML page.

This says that we want the Attorney.asp page to appear in a section of the page that is 600 pixels wide and 249 pixels high. If the page you are displaying inside of your inline frame is larger than the area you define here, scrollbars will appear on the main page. There are many attributes of the tag including name, source, height, width, frame border and more. For more information on the IFRAME tag see the Web Workshop or DHTML in the MSDN Library.

Designing a “Grid”

The next section of the cntAttorney.asp does a couple of things. First we check to see if we are adding a new attorney by seeing if our cID is empty or not. If it’s empty, we are not going to display the bottom frame that contains the note grid. If we are editing a record, we will first draw the column headings of the grid, then specify the inline frame that generates the contents of the grid. We also want the user to be able to add a note directly from here so we will generate a hyperlink “Add Note” that will open the note form in add mode. We want to draw the column headings of the grid outside of the so that when we scroll through the list of notes, the headings appear “fixed”. Here is the important piece of code that generates the inline frame that displays the AttorneyNoteGrid.asp page.

Response.Write(" ")

You will notice that once again the Attorney ID is being passed into the frame source, this time AttorneyNoteGrid.asp. This ID is needed to generate the proper note list. This frame is 600 pixels wide but only 75 pixels high. This will generate a grid of about three visible lines and then the frame will start to scroll. When we run these forms by selecting an Attorney name from the Attorney list we should get something that looks like this:

[pic]

WOW! This is actually three active server pages: cntAttorney.asp, Attorney.asp and AttorneyNoteGrid.asp. Now the user can work with the Attorney and the notes at the same time. If the user wants to edit a note, they will click the note date in the left hand column of the grid. If they wish to directly add a note to the grid, they can click the “Add Note” hyperlink on the grid heading.

Processing The Attorney Note

Now that we’ve got our Attorney forms working smoothly, let’s get into greater detail on how we add/update/delete information in the Note form. We do this in the AttorneyNote.asp. You will notice that the AttorneyNote.asp does not contain much code. That is because we are going to write it together! Let’s open up the AttorneyNote.asp page in Visual InterDev. The AttorneyNote.asp is very similar to the Attorney.asp. The processing part of the AttorneyNote.asp is basically just a CASE statement that checks for the cmdAction button the user clicked and then performs a function. There are the typical four functions; Save, Add, Delete and Cancel.

Saving the Attorney Note

Saving the attorney note is just like saving the Attorney Form. Lets open up the AttorneyNote.asp. You’ll notice that I have left the Save section of our CASE statement blank for you to write! If you recall, we went through how to save an Attorney in the sections above. Here I will show you how to step-by-step save our AttorneyNote form. Go to the ASP Save() function. You will see that I’ve broken it up into seven main steps.

1. Retrieve the resource.

In this case we will be using the AttorneyNote Resource Manager to retrieve our resource. I have created an ASP function called GetAttorneyNoteResource() that will either get an empty resource or retrieve an existing record. This is done by checking if the variable cID is empty or not. If it is, then we are saving a new record, otherwise we are saving an existing one. So type this code:

Set rsNote = GetAttorneyNoteResource()

Now we have a resource in the variable rsNote.

2. Fill the Resource with Form's values.

We need to fill the resource rsNote with the form values in the Request.Form collection. This is done in the ASP subroutine AssignFormValuesToResource. Here we also need to fill in the foreign key, which was the hidden field AttorneyID, and place it into the Note_ID field. You’ll notice that I created a variable called cFK at the top of the ASP page which is used in this subroutine. Type the code:

AssignFormValuesToResource

In that subroutine you’ll notice that we are checking to make sure our Date is in a valid date format before setting the ADO field value. If we do not, we will get an ADO error. Active server pages return all values as strings, so we have to convert the date string to a date format with the FormatDateTime function.

3. Create the XML string used to set up the environment on the middle tier.

We need to set up an XML string to tell the Resource Manager Controller (RMC) what resources we are going to save. Remember that the RMC is in charge of saving multiple resources by instantiating the resource managers and coordinating the validation and saves within an MTS transaction (if we are running in Microsoft Transaction Server). The way we communicate with the RMC is via an XML string. In our case it will be a simple one because we are only saving one resource, the AttorneyNote resource. That means there are no parents which makes the AttorneyNote the Primary resource. Notice that if we specified a parent in the cparntname element and the relationship in the cparntrela element then this would set up a parent-child hierarchy of resource managers on the middle tier.

cXML = cXML & ""

cXML = cXML & "oAttorneyNote"

cXML = cXML & "AttResMgr.AttorneyNote"

cXML = cXML & ""

cXML = cXML & "0.00"

cXML = cXML & ""

cXML = cXML & "T"

cXML = cXML & "Attnote_ID"

cXML = cXML & ""

4. Create an instance to the Resource Manager Controller on the middle-tier.

Set oRMControl = Server.CreateObject("AttProcess.RMController")

5. Set up the environment and marshal the data to the middle tier.

Now we need to send our XML environment information to the RMC we just instantiated. We do this by calling the Set_Up_Environment() method of the RMC. Next we well send the data in the resource, rs to the middle-tier by calling MarshallData(). Here’s the code you will need:

oRMControl.set_up_environment cXML

oRMControl.marshalldata rsNote, "oAttorneyNote"

oAttorneyNote corresponds to the name we gave our Attorney note resource manager in the XML element crmpname (see step 3 above).

6. Tell the RMController to save the data.

After the data is marshaled to the middle-tier, we are ready to save our data. We also pass in the cXMLValidation string we declared above in the main program as the empty string. If any of our data in the resource fails validation, the messages will be passed back as XML into this variable.

cID = oRMControl.save( cXMLValidation )

7. Release our objects.

Last but certainly not least, we need to set our object reference to Nothing so that we can release our RMC on the middle tier. You will need to type this code:

set oRMControl = Nothing

Simple enough! The next bit of code just checks if the save returned a negative number which would mean that an error occurred and we can display the XML messages to the user by binding an HTML table to our XML data island. This uses the same technique as our Attorney form.

You just wrote the saving code for the Attorney note! The save code is definitely one of the most important actions you will need to perform on the presentation tier.

Adding a New Attorney Note

When the user selects the Add button, we will need to redirect them back to the AttorneyNote form with an Attorney ID (foreign key). We retrieve the foreign key cFK from the QueryString value FK in our call to GetParameters at the top of the form. Remember, when loading the AttorneyNote.asp form, we check to see if we have an empty primary ID and at least a foreign key (Attorney ID) in the QueryString. If we do, then we can call know we are adding a new recrd and to just display the form to the user. So in the Case “Add” section of the AttorneyNote.asp form you will not need any code.

Deleting an Attorney Note

Deleting an attorney note involves three very simple steps:

1. Create a reference to the AttorneyNote resource manager on the middle-tier.

If the cID is not empty, we set a variable to reference the Attorney resource manager object:

Case "Delete"

if cID "" then

Set objNote = Server.CreateObject("AttResMgr.AttorneyNote")

2. Call the Delete() method passing in the primary ID, cID.

Now we call the delete method of the Attorney resource manager passing it the primary ID from the QueryString that we retrieved at the top of the form into the variable cID:

objNote.Delete( cID )

3. Release our resource manager object reference by setting it to Nothing.

Set objNote = Nothing

End If

That’s pretty easy. This code looks exactly the same as the Attorney delete code except for the names of the resource managers.

Using Server-Side Includes

We use Server-side include files when we want to insert contents of one file directly into another on the server before processing. This is handy when you want to define a standard set of code (HTML or ASP) that is used on many pages in you web. For instance, you may want to define one set of standard global server-side script functions that you can use in your active server pages. To include files in other ASP or HTML files we use the #include directive. This instructs the Web server to insert the contents of a file into the page before the ASP is processed. The included file can contain any content that is valid within an HTML document. For Example,

.

.

.

This will tell the web server to dump all the contents of the globalfuncs.asp page right before the tag of this document then start to process it. Notice that if the globalfuncs.asp page contains any HTML code in it, this page will be in bad form because we have not started the HTML document yet specified by the tag. However, we could take all of our functions and subroutines for our Attorney and Attorney note form and place them into separate files that were included on the ASP pages. This would separate our visual presentation from the ASP code we write. You can also use include files to include HTML into other HTML documents as long as the merged files create a valid HTML document.

Because the server includes files before processing script commands, you cannot use a script command to build the name of an included file. For example, the following script would not open the file header.inc because the server attempts to execute the #include directive before it assigns a file name to the variable name.

Let’s create a reference to an include file in our Attorney container form (cntAttorney.asp) that simply displays a nice title bar at the top of the page. If you look in your interface directory, you will see a file called header.inc. This file by itself is not a valid HTML file. If you open it in notepad, you will see that it only contains an HTML table not a complete HTML form:

Attorney Application Demo

Back to Search

The way we are going to use this is by including it a specific location in the cntAttorney.asp file. Open cntAttorney.asp in Visual InterDev. We will place the include file right after the tag:

Attorney

.

.

.

This will place the contents of the header.inc in the exact position the #include directive is in the cntAttorney.asp file. Select an Attorney from your search results attorney list and make sure you see the header at the top of the page.

[pic]

You can use this technique to create menus, borders or other consistent content on your web pages as well.

Done with Server-Side ASP Script

We have finally finished the server-side programming of our web-based application! We learned how to use our search criteria resources to make retrieving records from the middle-tier more flexible as well as program our add/update/delete/cancel functions. We saw how to put together multiple forms on one browser window using the tag. Finally, we learned a little bit about server-side include files. The next section will deal with controlling the browser using client-side JavaScript.

Part 9 – Controlling the Presentation with Client-Side JavaScript

In this section we will learn how to control our presentation with client-side JavaScript. I will show you how to manage and refresh your windows as well as do simple form field validation. We will use the DHTML Object Model of the browser and client-side JavaScript to accomplish our tasks. For more information on the DHTML in Internet Explorer, see DHTML Object Model in the MSDN Web Workshop at under DHTML, HTML & CSS. There is also a good JavaScript reference at .

Managing Browser Windows

In my opinion, window management in web applications is just as important as the presentation and graphics. It’s the worst when you navigate to a site that opens up twelve windows on your desktop without closing them! In the following sections, I’ll talk about how we are going to control the flow of our windows and how this will make the user’s experience with the application much better and easier to use. I have provided some JavaScript code for you in the jscripts subfolder of your interface directory (AttorneyApp\Interface\jscripts).

Opening an Additional Note Form Window

We want to automatically open up a smaller note window when the user selects a note from either our search results or our Note grid on the Attorney form. The way we do this is by calling a JavaScript function called openNote(). This function is located in the jscripts subfolder of your interface directory in the file called notelist.js. To open the file, right-click on it in Windows Explorer and select “Edit” from the shortcut menu. Here is the JavaScript function:

function openNote(goURL) {

window.open(goURL,'','width=550,height=365,scrollbars=yes,location=no,left=10,top=10,menubar=no,resizable=yes,toolbar=no,status=yes');

}

This function simply opens another instance of the browser window to the specified URL that is passed into the function in the goURL parameter. The window.open() method of the browser accepts as parameters the URL and a string of attributes that describe the look of the window. For instance, I have declared the size to be 550 pixels wide and 365 pixels high. I also specified to not display the toolbar (toolbar=no), menu (menubar=no) or the address bar (location=no). The way we call this function is in the hyperlink of the note list. For example, when we are generating the Search results we placed this line of code in our active server page:

Response.Write("" & rs("note_time") & "")

This will generate the HTML:

3/20/00 12:57:00 PM

Now when the user clicks on this hyperlink, the openNote() function will open a new browser window and navigate the contents to AttorneyNote.asp. To use JavaScript functions contained in files separate from the HTML files themselves, we place this code in the header of the HTML page.

Notice the source attribute specifies the relative path to the JavaScript file. This tells the browser to download the notelist.js file from the jscripts directory with the HTML page.

Closing the Note Form Window Automatically

Now we know how to automatically open up a smaller note window when the user selects a note from either our search results or our Note grid on the Attorney form. However, it’s up to us to close it when the user is done with their actions. If you open up the AttorneyNote.asp you will notice that there is a call to Response.Redirect(“closewindow.htm”) after almost every action. This sends a small HTML page down to the browser with a bit of JavaScript code. You can open the closewindow.htm file in Visual InterDev or even notepad will do just fine. Take a look at the HTML:

The active ingredient in this HTML form is the window.close() call in the onload event of the document. This causes the browser window to close automatically. You can close secondary browser windows with no complaint from the browser. A secondary window is what I call a window that opened from the main window. In our case, the Attorney form is in the main window and the Note form is opened into a secondary window. If you try to close the last primary browser window, the browser will usually put up a warning that in effect says, “The application is trying to close the browser, is this okay?”. So you want to try to avoid closing the main browser window. Trying to close the main browser window is just as annoying as it is to pop up twelve windows without closing them.

Refreshing the Note Grid (FrameNotes)

Refreshing the note grid independently of the Attorney form is made possible by the Dynamic HTML (DHTML) tag, . The inline frames allow us to separate the list of notes from the attorney parent form itself. This is useful when we need to refresh different parts of the form or allow the user to have pending changes to the parent while modifying the children. When the user changes or deletes a note, we want to refresh only the note grid on the attorney form, not the main Attorney frame. This is because if the user has pending changes in the Attorney frame, we do not want to reload that section of the form and lose all those changes. I have found that user’s really don’t like that. The way we refresh just the note grid to reflect the changes the user made on the note form is by calling a JavaScript function callerRefresh. This function is located in interface/jscripts/winman.js. To open the file right-click on it in Windows Explorer and select “Edit” from the shortcut menu. Here is the JavaScript function:

function callerRefresh(oReload){

if (oReload != null) oReload.location.reload(true)

return true ;

}

This simply reloads the window object that is passed into the function. The way we call this function is from the AttorneyNote.asp form in the onunload event. If you open the AttorneyNote.asp in Visual InterDev you will see that the onunload event call is generated on the server based on a few conditions:

>

The first thing to check is if we are coming from the “Add Note” link on the cntAttorney.asp form. If we are then we have to specifically name the “FrameNotes” to refresh. The name of the frame is specified in the name attribute of the tag. If we are editing a note, then that means we either came from the Search Results, or the Note grid. If we came from the search results, the cType variable will contain the word “List” and in that case we will not generate any code. Otherwise we are coming from the Note grid and the window.opener refers to the Note grid frame itself. Both of these window objects refer to the same notes grid, however depending on which frame we opened this Note window from, we refer to it in different ways. What this code does is refresh only the note grid (FrameNotes) while leaving the FrameAttorney alone.

Managing All of the Attorney Frames

Earlier I mentioned that we needed to pull a few tricks when refreshing our Attorney container form, particularly when going from Edit to Add mode. When the user clicks the “Add” button from the Attorney main form, the form submits back to the Attorney.asp and falls to the Case for “Add”. If you open up the Attorney.asp form again, you should notice that the case body for “Add” is empty. This is because our Attorney.asp does something special on an add because of the nature of the form. Since our attorney form is contained in a frame in the cntAttorney.asp form, it is necessary to “break out of the frame” when we need to Add a new attorney record. This is because when we go into Add mode from an Edit of an existing attorney, we need to “remove” the grid of notes at the bottom half of the form. The only way to do this is to refresh the parent/container window (cntAttorney). We do not want (or have) to display any child note records when adding a new Attorney. Not until the new attorney is saved do we then display the grid. There is no easy way to redirect the browser to a specific frame from the server using Response.Redirect(). We need to have the browser do it.

There is a piece of JavaScript in the middle of the Attorney.asp form that redirects the browser to the correct pages by setting the outermost frame (top.location) to the desired ASP page. We need to do this because since the Attorney.asp is going to be in a frame on the cntAttorney.asp page, if we redirect the user anywhere from inside the frame, only the frame contents will change. This means that if we were editing an attorney and notes, then decided to add a new attorney, the Attorney frame would change to add mode, but we would still see the notes from the previous record we were editing. Managing frames can be tricky. You can end up having frames within frames at runtime if you don’t watch out. If the Attorney frame redirected you to the cntAttorney.asp, you would end up with another attorney container (frames and all) inside the Attorney frame that called it!

So since we cannot redirect the user to a specific frame from the server’s Response.Redirect method, we will need to set up a piece of JavaScript code that is sent down to the client that sets the top.location equal to the redirect asp page. Take a look at the middle of the Attorney.asp page right at the start of the HTML. Notice how the server-side script is generating the client-side JavaScript:

In the end, the generated HTML form that is sent to the client (if the user selected “Add”) is this:

function breakframes(){

if ( top.location != location ) top.location.href = 'cntAttorney.asp?Action=Add' ;

}

This in essence redirects the main container page (cntAttorney.asp) to the specified location, cntAttorney.asp?Action=Add. If we had not redirected the container page and instead did a normal Response.Redirect from the Attorney.asp form (which is essentially contained in the Attorney frame) we would end up with another container page inside the Attorney frame. The cntAttorney.asp is then in charge of calling our Attorney.asp page with the proper QueryString information as we saw in an earlier section. This is a neat example of conditionally generating client-side script from the server.

Simple Validation with JavaScript

Now that you’ve seen how to use JavaScript to manage windows, I want to show you how we can use it to do simple data validation. We are going to write a simple validation script for our Attorney form so that we don’t have to bug our middle-tier AttorneyValidator if we don’t have to. This will avoid a round trip to the server. We are going to make sure that the Attorney lastname field is not empty. To do this, we will use some simple pattern matching to trim the string of blank spaces and then check if the result is equal to the empty string. If it is, then we will put up a message to the user and will not allow then to save the form. We will also force the state address field to upper case.

The first thing to do is to create a JavaScript file in our \jscripts subfolder. You can simply create a new text document and then rename the file with a .js extension. Let’s name the file vattorney.js. If you right-click the file, you can select edit from the shortcut menu and it should open in Notepad. We are going to call our function validate(). Type this in the editor:

function validate(thisform) {

}

We are going to pass this function the name of the form to validate. Functions in JavaScript always begin with an open brace ({) and end with a close brace (}). We are going to want to return a true or a false from this function. If we return a false, that means the lastname field is empty and the form will not submit. If we return true, everything is okay and we can go ahead and allow the user to submit the form. Let’s create a few variables, one for the return value (llRetVal) one for the value of the lastname (cLastName) and one for the value of the state (cState). Our function should now look like this:

function validate(thisform) {

var llRetVal = true;

var cLastName = thisform.lastname.value;

var cState = thisform.state.value;

return llRetVal

}

Now we can write an if statement that simply checks if the lastname is empty or not and put up an alert with the following code :

if (thisform.lastname.length == 0) {

alert("Please enter a last name.");

thisform.lastname.focus();

llRetVal = false;

}

This will also set focus back to the lastname field. The problem with this if statement is that if the user typed or left spaces in the lastname field, this expression will pass validation because the lastname field is not technically empty. What we can do is strip out the spaces first then compare the result to the empty string. To do this, we will use what we call a “Regular Expression” in JavaScript. This is how we do pattern matching. There is a great tutorial on the internet on Pattern Matching and Regular Expressions by Tomer Shiran and Yehuda Shiran at .

We are going to set up a simple regular expression that checks for spaces (including line feeds and tabs) anywhere in a string. To do this, we use the special character \s. To create our regular expression we will then type the code:

var re_LastName = /\s+/;

The patterns of regular expressions are always contained between the slashes (/ and /). The /s is a special character that matches white spaces and the + indicates to look for the white space character anywhere in the string. So we will need to call the JavaScript replace method on the cLastName string object to replace all occurrence of white space with the empty character like so:

if (cLastName.replace(re_LastName, "") == "") {

alert("Please enter a last name.");

thisform.lastname.focus();

llRetVal = false;

}

You could easily use regular expressions to create your own pattern matching functions like checking for valid phone number formats, US state codes or even using them to create a RTRIM(), LTRIM(), and ALLTRIM() functions. If we wanted to check our state against a list of valid state codes, we would create a regular expression like this:

var re_state = /^A[KLRZ]$|^C[AOT]$|^D[CE]$|^FL$|^GA$|^HI$|^I[ADLN]$|^K[SY]$|^LA$|^M[ADEINOST]$|^N[CDEHJMVY]$|^O[HKR]$|^PA$|^RI$|^S[CD]$|^T[NX]$|^UT$|^V[AT]$|^W[AIVY]$/i;

For our simple application, the only thing we are going to do is put the state code in upper case. We do this by calling the toUpperCase() method on the cState string object:

thisform.state.value = cState.toUpperCase();

Now our finished vattorney.js file should look like this:

function validate(thisform) {

var llRetVal = true;

var cLastName = thisform.lastname.value;

var cState = thisform.state.value;

var re_LastName = /\s+/;

if (cLastName.replace(re_LastName, "") == "") {

alert("Please enter a last name.");

thisform.lastname.focus();

llRetVal = false;

}

thisform.state.value = cState.toUpperCase();

return llRetVal

}

The way we call this function is from the onclick event of the save button on the Attorney.asp form. We will also need to add the reference to our vattorney.js script in the header of the HTML. Open up Attorney.asp in Visual InterDev. Add the bolded line to the HTML header:

Now we need to call the function from the onclick event of the save button. Add code to the save button control:

We send the name of our form into the validate function. The name of our form is specified in the tag which also happens to be “thisform”. Run the application and select an Attorney to edit. Blank out the lastname field and click the Save button. You should see an alert box:

[pic]

When you click OK, the focus will be on the lastname field and the form will not submit. Using JavaScript to do simple validation on the client is a good idea because you eliminate a trip to the middle-tier, saving time and precious server resources.

If you are new to JavaScript, you may be overwhelmed with even the small amount of code we had to write for our simple Attorney application. Controlling the browser is not a task that should be taken lightly. Fortunately, there is a wealth of information on browsers and JavaScript on the internet. If you need a function, chances are somebody already wrote it and you can find it out there somewhere. It does take time to learn a new language, however, as you develop more and more web-based applications, you will see that JavaScript and VBScript are easy to use and also very powerful.

Done with Presentation Services

You should now have a working ASP application written with COMCodebook Visual FoxPro middle-tier components and a Visual FoxPro database. You learned how to write HTML, CSS, server-side VBScript as well as client-side JavaScript to produce a working (and great looking!) web-based, internet application. We have been focusing hard on our presentation services for a while. Now that we’re done, I would like to switch gears and talk about scaling our database up to an Oracle or a SQL-Server backend and what is involved in using Microsoft Transaction Server with our components.

Part 10- Scaling Our Application Up

If you are interested in more robust solutions, you should read this section. I will discuss scaling up our application into Oracle8 and SQL-Server databases and what minimal amount of code we need to change in the middle-tier to do so. I will also discuss the process involved in putting our components into Microsoft Transaction Server. I will assume you are familiar with the particular databases being discussed and focus only on the middle-tier modifications and MTS configuration.

Using a SQL-Server Database

This section assumes that the reader is relatively familiar with SQL-Server database administration and has Administrator privileges to create databases in SQL-Server. For information on SQL-Server database administration, see your SQL-Server documentation.

Setting up the Database

This first thing you’re going to want is to create a database. You can make it small (1 Megabyte should be plenty). I used ATTAPP as the database name. If you use a different name, make sure when you see ATTAPP in this sample, to replace it with your database name.

To create the tables and data, there is a SQL script file called SqlServer.sql in your Flash\AttorneyApp\Data\ directory that contains the script necessary to create the tables, indexes, constraints and the stored procedure you will need. It will also insert some sample data. Make sure you have already set up your database and are executing the script logged in as a valid DBO.

If you are not interested in setting up an Oracle database, skip to the Modifying Our Data Sources section below.

Using an Oracle Database

This section assumes that the reader is familiar with Oracle database administration and has DBA privileges to create database instances on their Oracle server. This section merely suggests values for creating the AttorneyApp instance. It does not give step by step instruction on how to set up the instance since all Oracle server installations are unique. For information on exactly how to create an Oracle instance for your specific platform, see your Oracle documentation.

Setting up the Database

This first thing you’re going to need is create an Oracle Database instance. The simple AttorneyApp sample application is a small database so when creating the instance, you can specify the recommended “small” values in your INIT.ORA file. I used ATTN as the Oracle SID. If you use a different SID, make sure when you see ATTN in this sample, to replace it with your SID.

Example:

db_name = attndb

db_files = 100

db_file_multiblock_read_count = 8

db_block_buffers = 200

shared_pool_size = 3500000

log_checkpoint_interval = 10000

processes = 50

dml_locks = 100

log_buffer = 8192

sequence_cache_entries = 10

sequence_cache_hash_buckets = 10

Also, after creating the tablespaces, make sure you create enough rollback segments for your needs. The more concurrent transactions you want to execute against the AttorneyApp database, the more rollback segments or size of them you will need. I created 8 rollback segments that are initially 50K and max 250K that grow by 50K.

Example:

create public rollback segment rb1

storage(initial 50K next 50K optimal 250K)

tablespace rollback_data;

After the instance is up, don’t forget to modify the Oracle listener file to include the new AttorneyApp database. You may need to restart the listener service after you do this.

To create the tables and data, there is a SQL script file called Oracle.sql in your Flash\AttorneyApp\Data\ directory that contains the script necessary to create the tables, indexes, constraints and the stored procedure you will need. It will also insert some sample data. Make sure you have already set up your database instance and are executing the script logged in as a valid DBA.

CONTEXT Searching

If you would like to be able to search for note key words when searching for Attorney notes, you will need to implement Oracle CONTEXT searching and create an index on the Note_Text field in the Attnote table. This allows you to search a field of type LONG for the instance of a phrase using the CONTAINS() function in your select statements. For information on using this feature, see your Oracle documentation.

Setting Up the Oracle8 Client (Net8 Easy Config)

The application server must be configured to access the Oracle database. Your application server is the machine that is running the COMCodebook components (which is probably your development machine).

To configure the application server follow these steps:

1. Install the Oracle8 Client Software.

2. Open up the Net8 Easy Config.

3. Select the radio button "Add new Service" then type a name for your service. This service name is used in connection strings for the machine to refer to the particular database. It is used in the prm_AttorneyApp_Oracle class. (i.e. ATTAPP.WORLD) Click Next.

4. The next screen will ask you for the network protocol. This is usually TCP/IP. Click Next.

5. Now it will ask you for the host name and the port number. Type the name of your server for the host name and select the port number (usually 1521). Click Next.

6. This next screen asks you for the Database SID (ex. ATTN). Type yours in the textbox. Click Next.

7. The next screen has a button in the middle that says "Test Service". Click it and then type in a username and a password (i.e. DBO/SA) and click the test button. The status window will report any errors, but it should say "Connection Successful". Click the Done button.

8. Click the Next button and then click the Finish button. You're all set!

Modifying Our Data Sources

Now that we’ve got everything set up on our database and application servers, we are ready to make the code modifications necessary to connect to our Oracle/SQL database. Open up Visual FoxPro and in the Command Window type:

CD “D:\FLASH\Codebook\AttorneyApp\Interface\”

DO StartCB

This will open up all our projects and set up our file paths. We will only need to modify the data source project AttData. You will see that there is only a minimal amount of code changes necessary to convert the backend. These code changes are isolated to the data source component, as it should be. Only the data sources know the specifics of the database they are accessing.

There are three things we are going to need to do in our data sources:

1. Configure our data sources by specifying the Configuration, Search Criteria Manager and Component Parameter class properties to use the Oracle/SQL-Server specific objects.

2. Review our SQL Statements to make sure our syntax is Oracle/SQL-Server compliant.

3. Use the default behavior of the data sources when adding and deleting records by removing the overriding a-layer GetPrimaryKey() and DefineDeleteSQL() functions we had to write for our VFP data.

In most cases step three can be omitted if your primary key data types remain consistent between your VFP and Oracle/SQL databases. We will be using integer primary keys in our database which will demonstrate how to use the framework’s default behavior.

1. Configuring the Data Source Properties

Modifying Properties for SQL-Server

Open up the AttorneyDataSource in the adatasources.prg. The first thing to do is to set up our data source’s configuration classes to use the SQL-Server ones. Currently we are using our Visual FoxPro classes:

cADOComponentsParameterClass = "prm_AttorneyADOComponents_vfp"

cConfigurationObjectClass = "prm_AttorneyApp_vfp"

cSearchSyntaxClass = "SearchSyntax_vfp"

We need to change these three properties like so:

cADOComponentsParameterClass = "prm_AttorneyADOComponents_sql"

cConfigurationObjectClass = "prm_AttorneyApp_sql"

cSearchSyntaxClass = "SearchSyntax_sql"

You will also need to make this change in the AttorneyNoteDatasource:

cADOComponentsParameterClass = "prm_AttorneyNoteADOComponents_sql"

cConfigurationObjectClass = "prm_AttorneyApp_sql"

cSearchSyntaxClass = "SearchSyntax_sql"

The definition for the Components Parameter class and the Configuration Object Class is in the aparameterobjects.prg. In that file we define our concrete Component classes for Attorney and AttorneyNote:

*********************************************************************

DEFINE CLASS prm_AttorneyADOComponents_sql AS aadocomponentsparameter_sql

*********************************************************************

lEnableServerAssignedKeys = .T.

nlocktype = 4

ncursortype = 1

Name = "prm_AttorneyADOComponents_sql"

ENDDEFINE

*********************************************************************

DEFINE CLASS prm_AttorneyNoteADOComponents_sql AS aadocomponentsparameter_sql

*********************************************************************

lEnableServerAssignedKeys = .T.

Name = "prm_AttorneyNoteADOComponents_sql"

nlocktype = 4

ncursortype = 1

ENDDEFINE

In this same file are the definitions to the Configuration class. This class is responsible for setting up the connection to our database. The concrete class prm_AttorneyApp_sql defines the connection class we will need to use for SQL-Server which ultimately inherits from cOledbProviderForSQLServer:

*********************************************************************

DEFINE CLASS prm_AttorneyApp_sql AS isqloledbproviderforsqlserver

*********************************************************************

lEnableServerAssignedKeys = .T.

Name = "prm_AttorneyApp_sql"

ENDDEFINE

Modifying Properties for Oracle

Open up the AttorneyDataSource in the adatasources.prg. The first thing to do is to set up our data source’s configuration classes to use the Oracle ones. Currently we are using our Visual FoxPro classes:

cConfigurationObjectClass = "prm_AttorneyApp_vfp"

cSearchSyntaxClass = "SearchSyntax_vfp"

We need to change these two properties like so in the Attorney and AttorneyNote datasources:

cConfigurationObjectClass = "prm_AttorneyApp_oracle"

cSearchSyntaxClass = "SearchSyntax_oracle"

The definition for the Configuration Object Class is in the aparameterobjects.prg. Let’s take a look at that file now. The last definition describes the a-layer class for the configuration (connection) class. This class is responsible for setting up the connection to our database. The concrete class prm_AttorneyApp_Oracle defines the connection class we will need to use for Oracle which ultimately inherits from cOledbProviderForOracle:

*********************************************************************

DEFINE CLASS prm_AttorneyApp_oracle AS ioledbproviderfororacle

*********************************************************************

lEnableServerAssignedKeys = .T.

Name = "prm_AttorneyApp_oracle"

ENDDEFINE

Configuring the Connection Information

You should remember when we initially designed our data sources that we store the connection string information in a file called CONNCFG.DBF located in the \Dataservices directory along with the data source components (AttData.DLL) and project file. Open up the CONNCFG.DBF in Visual FoxPro by clicking on it from explorer. I have entered three connection parameters, prm_AttorneyApp_vfp, prm_attorneyapp_sql and prm_attorneyapp_oracle. Since we will be using a new database now, we will need to enter the specific properties of the connection string for those databases.

The SQL-Server Connection

Enter the SQL-Server server name in the cDatasrc field for the prm_attorneyapp_sql class (i.e. MYSERVER). Then enter the database name in the cInitCat field (i.e. ATTAPP). Next we enter the username and password information used to connect to the SQL-Server database in the cPassword and cUserID fields. Finally we can enter connection timeout and whether to use NT security by filling the cConnectto and cSecInfo fields. So here are the fields used in the SQL-Server Connection String:

|Cindex |Cconnectto |Cdatasrc |Cinitcat |Cpassword |Csecinfo |Cprovider |Cuserid |

|prm_attorneyapp_sql |60 |MYSERVER |ATTAPP | |FALSE |SQLOLEDB.1 |SA |

The Oracle Connection

Enter the Oracle service name in the cDatasrc field for the prm_attorneyapp_oracle class (i.e. ATTAPP.WORLD). Then enter the username and password information used to connect to the Oracle database in the cPassword and cUserID fields. Here are the fields used in the Oracle Connection String:

|Cindex |Cdatasrc |Cpassword |Cprovider |Cuserid |

|prm_attorneyapp_oracle |ATTAPP.WORLD |SA |MSDAORA.1 |DBO |

2. Reviewing Our SQL-SELECT Statements

SQL-Server SQL-SELECT Modifications

The next thing to do is to run through the SQL-SELECT statements in the Attorney and AttorneyNote data sources to make sure the queries are valid in SQL-Server. Fortunately, VFP and SQL-Server are close enough in their query language, we won't have to do anything! Next step is to modify the command parameters. (See Command Parameter Modifications below)

Oracle SQL-SELECT Modifications

Now we need to review the SQL-SELECT statements in the Attorney and AttorneyNote data sources to make sure the queries are valid in Oracle. The first method we will need to update is the AttorneyDatasource.GetProxy(). That interface defines a SQL statement that will not work in Oracle. The concatenation operator in Oracle is the double pipe (||). So the SELECT statement should read:

loADO.mandText = [ SELECT RTRIM(LastName) || ', ' || FirstName AS cDesc, ] + ;

[ Attorney_id AS upID FROM Attorney ORDER BY Lastname, FirstName ]

The same thing goes for the AttorneyNoteDataSource.GetAttorneyNoteBySearchCriteria() method. You will have to modify the queries like so:

loADO.mandText = [ SELECT Attnote.*, Attorney.LastName, ] + ;

[ RTRIM(Attorney.LastName) || ', ' || Attorney.FirstName ] + ;

[ AS FullName FROM AttNote, Attorney ] + ;

lcWhere + [ Attnote.Attorney_Id = Attorney.Attorney_ID ] + lcOrder

Command Parameter Modifications

Since we are going to also use integer primary keys with Oracle and SQL-Server, we will need to modify some of our SQL parameters. The first one is in AttorneyDataSource.GetAttorneyByID(). The lopAttorneyID parameter now reads:

lopAttorneyID = ;

loADO.oCommand.CreateParameter( "Attorney_ID", adVarchar, adParamInput, 9, tuAttorneyID)

Modify the lopAttorneyID parameter like so:

lopAttorneyID = ;

loADO.oCommand.CreateParameter( "Attorney_ID", adInteger, adParamInput, , tuAttorneyID)

This will facilitate our use of integer keys. We must also make this modification in the AttorneyNoteDataSource’s GetAttorneyNoteByID() and GetAttorneyNoteByAttorneyID() methods.

3. Using the Data Sources' Default Behavior

The last thing we need to do is comment out (or delete) the a-layer GetPrimaryKey() and DefineDeleteSQL() functions we had to use for our VFP data. We will want to use the default behavior COMCodebook gives us when adding new records and deleting old ones. These functions are located at the bottom of the adatasources.prg. Make sure you only remove the functions not the class definition. The aAbstractADODataSource is our abstract class the concrete data sources (AttorneyDataSource and AttorneyNoteDataSource) inherit from. In the end you should end up with this code remaining:

************************************************************************

DEFINE CLASS aAbstractADODataSource AS IAbstractADODataSource

************************************************************************

*!* PROTECTED FUNCTION GetPrimaryKey( rcPrimaryKey, toADO )

*!*

*!* .

*!* .

*!* .

*!*

*!* ENDFUNC

*!* **************************************

*!* PROTECTED FUNCTION DefineDeleteSQL( tcID, rcDeleteSQL )

*!*

*!* .

*!* .

*!* .

*!*

*!* ENDFUNC

ENDDEFINE

By removing these functions, we use the framework’s default definition of these methods located in the cAbstractADODataSource class. This will demonstrate the use of integer keys as well as calling our stored procedure (sp_NewID) to generate these keys when we save new records.

Compiling and Testing Our Data Sources

Now that we’ve made all the necessary modifications to the data source components, let’s rebuild and test them. Make sure, if you haven’t already, that you set up your directory paths by typing in the VFP command window :

CD “D:\FLASH\Codebook\AttorneyApp\Interface\”

DO StartCB

Now select the AttData project and click build. Select to build a multi-threaded DLL just like we did before. Navigate your browser to your Attorney Application (). Try performing a search on the notes and make sure the components are responding. You will notice a small slow-down in performance because of the use of larger database server instead of Visual FoxPro. Test the rest of the functionality of the application. Notice that we did not have to change the front-end because the new back-end database. In fact, all of our changes were isolated to the data source components. Now that’s cool! We just increased our scalability by adding a more robust database. But that’s not the only thing we can do. We can also place our components inside Microsoft Transaction Server.

Running the Components in Microsoft Transaction Server

Modifying the MTS Registry Settings for Oracle8

NOTE: If you are using the Oracle 7.3 Client software, you can skip this section.

If you are going to be using an Oracle8 database, we need to set up Microsoft Transaction Server to properly communicate with it. The way in which MTS handles transactions with Oracle is via a method that they call XA technology. Your Oracle8 database server should already be set up to handle this. However, if your components are not responding correctly, you can read the Microsoft KB Article Q193893 to make sure you have everything set up properly.

To modify the MTS registry settings for Oracle8:

1. Stop the Microsoft Distributed Transaction Coordinator (DTC) from the Microsoft Management Console (MMC) by right clicking on “My Computer” and selecting “Stop DTC”.

2. Open the registry editor on the MTS machine.

3. Under the following registry key are two string-named values that default to the names of the Oracle 7.3 Client software .dlls:

4. HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Transaction Server\Local Computer\My Computer

5. String-Named Values:

OracleXaLib "xa73.dll"

OracleSqlLib "sqllib18.dll"

6. Change these values to specify the names of the Oracle8 Client software .dlls:

OracleXaLib "xa80.dll"

OracleSqlLib "sqllib80.dll"

7. Exit the Registry Editor.

8. Re-start the Microsoft Distributed Transaction Coordinator (DTC) from the Microsoft Management Console (MMC) by right clicking on “My Computer” and selecting “Start DTC”.

Steps to Register the Components in an MTS Package

Always test your components to make sure they are running smoothly before you place them in the Microsoft Transaction Server environment. Besides scalibility, MTS also adds a level of complexity to your components and debugging is not easy in this environment. Fortunately, the developers of the COMCodebook framework have worked through most of these issues for you. To place the components in MTS under Windows NT4 follow these steps:

1. Open the MTS Management Console and create a new package by right clicking on My Computer/Packages Installed/ in the left-hand pane. From the shortcut menu select New/Package.

2. The Package wizard will display two options, import pre-built package or create new package. Select the create new empty package button.

3. The next step is to name the package. Type AttApp in the space provided. Click Next.

4. The wizard will then ask for you to set up the package identity, leave the default “Interactive User”. Click Finish.

5. Now select the Components folder by expanding your AttApp package you just created in the left-hand pane. Right-click on this folder and select “New” then “Component”.

6. When it asks you what you want to do, select the “Install component(s) that are already registered” button.

7. From the huge list of registered components, select all the AttData, AttResMgr, AttValid and AttProcess interfaces.

8. After they are successfully registered in MTS, select the AttData interfaces from the right pane and right-click. Select “Properties” and go to the “Transaction” tab. Select the “Requires a Transaction” option.

9. Select the AttValid and AttResMgr interfaces and right-click. Select “Properties” and go to the “Transaction” tab. Select the “Supports Transactions” option.

10. Select the AttProcess.RMController and right-click. Select “Properties” and go to the “Transaction” tab. Select the “Requires NewTransactions” option.

To place the components in Component Services under Windows 2000 follow these steps:

1. Open the Component Services Explorer under Control Panel/Administrative Tools.

2. Select Component Services/Computers/My Computer/COM+ Applications/ from the left hand pane.

3. Right click on the COM+Applications and select New/Application.

4. The Application wizard will appear. Click the next button on the welcome screen. The Application wizard will then display two options, install pre-built application(s) or create new empty application. Select the create new empty application button.

5. The next step is to name the application. Type AttApp in the space provided. Click Next.

6. The wizard will then ask for you to set up the application identity, leave the default “Interactive User”. Click Finish.

7. Now select the Components folder by expanding your AttApp package you just created in the left-hand pane. Right-click on this folder and select “New” then “Component”.

8. When it asks you what you want to do, select the “Install component(s) that are already registered” button.

9. From the huge list of registered components, select all the AttData, AttResMgr, AttValid and AttProcess interfaces.

10. After they are successfully registered, select the AttData interfaces from the right pane and right-click. Select “Properties” and go to the “Transaction” tab. Under the Transaction support section, select the “Required” option.

11. Select the AttValid and AttResMgr interfaces and right-click. Select “Properties” and go to the “Transaction” tab. Under the Transaction support section, select the “Supports” option.

12. Select the AttProcess.RMController and right-click. Select “Properties” and go to the “Transaction” tab. Under the Transaction support section, elect the “Requires New” option.

Now your components will run inside MTS (or Component Services under Win2000). Test this by going back to your browser and performing a search and modifying an Attorney. Now we have a completely scalable, n-tier application written with the Windows DNA theory in mind using a COMCodebook middle-tier. WOW, we did it!

Conclusion

I hope this document helped you understand how to develop n-tier applications using COMCodebook and Windows DNA. COMCodebook simply provides us with the "guts" of the middle tier, allowing us to choose whatever database and presentation we see fit. We developed a simple thin-client browser-based interface from scratch, learning HTML, CSS, JavaScript and ASP along the way. We also learned how easy it is to scale our application up into an Oracle and SQL-Server database as well as use Microsoft Transaction Services (Component Services) for our components. With careful planning and some practice, you should be able to develop and deploy scalable business solutions quickly and easily. I hope you are just as excited as I am about the future of n-tier business applications and wish you all the best of luck in your endeavors!

-----------------------

[pic]

................
................

In order to avoid copyright disputes, this page is only a partial summary.

Google Online Preview   Download