14.4 The Modules


With the specifications in place, we can focus on the implementation. We'll document first and code later, and we'll use OOP techniques for much of the application. One advantage of this approach is that the specifications dictate how the coding takes place. For example, we have specified that we will have users and scripts; these elements can be implemented as objects. This makes the coding process more applicable to real-world situations. We know the different properties of a user and the different properties of a script (outlined in the specifications), so these will be the properties of our objects. Even though we are using some OOP techniques and some objects, the application is not strictly an object-oriented application.

14.4.1 Structure

We will build the overall structure before we set out to code the functionality of the application. I've found that this is often the best way to approach a problem. You can think of it like drawing a picture: if you draw the outline first, it is a lot easier to color in, rather than color the picture and then try to draw the outline around it after the fact. In this way, comments and function skeletons make up your outline, and the actual code is used to " color in" the program. This has the added benefit that the comments are finished when your code is finished, rather than requiring you to add comments at the end of the project.

The skeleton code should be fully working code. Even placeholder functions should include return statements so that the program works as you code.

14.4.2 Database

The database is the first physical structure to create. The database needs to be in place and functional before the application can be built. The database structure is shown in Tables Table 14-1 through Table 14-5. Table 14-1 shows the Users table, which is used to manage user login.

Table 14-1. Users table

Column name

Datatype

Length

Notes

UserID

integer

4

Auto-numbering, primary key

Username

text

16

 

Password

text

12

 

FirstName

text

60

 

LastName

text

60

 

EmailAddress

text

255

 

HintQuestion

text

255

Prompt the user if password is forgotten

HintAnswer

text

20

Verify user response if password is forgotten

Table 14-2 shows the Categories table, which is used to group scripts into categories for easier searching and sorting once the repository grows larger.

Table 14-2. Categories table

Column name

Datatype

Length

Notes

CategoryID

integer

4

Autonumbering, primary key

CategoryDesc

text

60

 

Table 14-3 shows the Scripts table, which is used to manage the contributed scripts.

Table 14-3. Scripts table

Column name

Datatype

Length

Notes

ScriptID

integer

4

Autonumbering, primary key

ScriptName

text

60

 

ScriptDescription

text

255

 

ScriptCode

text

4095

 

LanguageID

integer

4

Foreign key to Languages table

CategoryID

integer

4

Foreign key to Categories table

UserID

integer

4

Foreign key to Users table

DateUploaded

date/time

8

Defaults to current date

DateModified

date/time

8

Defaults to current date

VersionMajor

integer

4

Defaults to 1

VersionMinor

integer

4

Defaults to 0

VersionMicro

integer

4

Defaults to 0

ScriptUniqueID

Unique identifier (UUID)

36

 

Table 14-4 shows the Languages table, which is used to track the programming languages in which scripts are written.

Table 14-4. Languages table

Column name

Datatype

Length

Notes

LanguageID

integer

4

Autonumbering, primary key

LanguageName

text

50

 

Table 14-5 shows the CompanyInfo table, which is used to provide contact information for contributors.

Table 14-5. CompanyInfo table

Column name

Datatype

Length

CompanyName

text

60

Address

text

127

City

text

60

State

text

2

Zip

text

9

Phone

text

50

Fax

text

50

ContactFirstName

text

50

ContactLastName

text

50

ContactEmail

text

127

PrivacyPolicy

text

1000

Description

text

1000

The database table specs have been shown in a generic fashion, to allow you to implement them in your own particular database. For example, the text datatypes are implemented as varchar or nvarchar fields in SQL Server or MySQL. Similarly, the DateUploaded field in the Scripts table is implemented as a datetime field, with a default value of getdate( ) in SQL Server or current_date( ) in MySQL. Other database implementations will vary.

The completed database diagram of table relationships is shown in Figure 14-1.

Figure 14-1. The completed database diagram shows table relationships
figs/frdg_1401.gif

14.4.3 Defining Server-Side Services

The server-side services are implemented with ColdFusion Components . The required services are shown in Tables Table 14-6 through Table 14-8.

Table 14-6 lists the service methods of the UserService service.

Table 14-6. The UserService service

Service method

Description

Arguments

Returns

loginUser( )

Validates username and password against the database. Sets the session if the login is successful, and sets the user's access level.

Username (string), Password (string)

Userid (numeric)

addUser( )

Adds a new user to the database. If the registration is successful, the user is also automatically logged in.

UserObject

UserObject

emailPassword( )

Emails a password to a user if he forgets his password.

EmailAddress

True

createUserObj( )

Package method that creates an object of type UserObject for passing back to ActionScript.

FirstName, LastName, EmailAddress, Username, Userpassword, HintQuestion, HintAnswer

UserObject

getEmail( )

Gets the user's hint question for retrieving a password.

EmailAddress

Hint question (string)

getScriptsForUser( )

Gets all scripts submitted by logged-in user.

UserID (numeric)

Recordset

Table 14-7 lists the service methods of the ScriptService service.

Table 14-7. The ScriptService service

Service method

Description

Arguments

Returns

addScript( )

Adds a script to the database.

ScriptObject

Script id (numeric)

updateScript( )

Updates an existing script in the database.

ScriptObject

ScriptObject

displayScript( )

Displays the script on the screen.

ScriptID (numeric)

ScriptObject

displayList( )

Displays a list of available scripts, with clickable links.

Search word (optional)

Recordset

getScript( )

Gets all information about a script to display.

ScriptID (numeric)

ScriptObject

createScriptObj( )

Package method that creates an object of type ScriptObject for passing back to ActionScript.

ScriptID, ScriptName, ScriptDescription, ScriptCode, LanguageID, CategoryID, UserID, DateUploaded, DateModified, VersionMajor, VersionMinor, VersionMicro, ScriptUniqueId

ScriptObject

DateTimeString( )

Package method that converts a Date object from ActionScript into a human-readable date/time string

Date object or string

Formatted date string

Table 14-8 lists the service methods of the SiteService service.

Table 14-8. The SiteService service

Service method

Description

Arguments

Returns

about( )

Returns a short paragraph about the company from the database.

None

RecordSet

contactForm( )

Contacts the site administrator by email through a standard form.

UserID (numeric), Comment (string)

true

sendPage( )

Sends the page information to a friend.

UserID (numeric), Email address (string), Script ID (numeric)

true

getCategories( )

Retrieves a list of all categories for drop-down list.

None

RecordSet

getLanguages( )

Retrieves a list of all languages for drop-down list.

None

RecordSet

getUsers( )

Retrieves a list of all users for drop-down list.

None

RecordSet

Using Dreamweaver MX, you can create skeletons for all of the services. Dreamweaver MX allows you to create CFCs using an interface (shown in Figure 14-2), with function skeletons in place.

Figure 14-2. The Dreamweaver MX component interface
figs/frdg_1402.gif

Example 14-1 lists the skeleton code for the UserService service.

Example 14-1. Autogenerated skeleton code for the UserService service
 <!--- Generated by Dreamweaver MX 6.0.1722 [en] (Win32) - Wed Jan 29 19:07:39 GMT-0800  (Pacific Standard Time) 2003 ---> <cfcomponent displayName="UserService">   <cffunction name="loginUser" displayName="loginUser"     hint="Logs a user into the script repository"     access="remote" returnType="string" output="false">       <cfargument name="username" type="string" required="true">       <cfargument name="password" type="string" required="true">       <!--- loginUser body --->       <cfreturn >   </cffunction>   <cffunction name="addUser" displayName="addUser"     hint="Add a user to the database" access="remote"     returnType="string" output="false">       <cfargument name="Username" type="string" required="true">       <cfargument name="FirstName" type="string" required="true">       <cfargument name="LastName" type="string" required="true">       <cfargument name="EmailAddress" type="string" required="true">       <cfargument name="Password" type="string" required="true">       <cfargument name="HintQuestion" type="string" required="false">       <cfargument name="HintAnswer" type="string" required="false">       <!--- addUser body --->       <cfreturn >   </cffunction>   <cffunction name="emailPassword" displayName="emailPassword"     hint="Email a password to a user, given the email address"     access="remote" returnType="string" output="false">       <cfargument name="EmailAddress" type="string" required="true">       <cfargument name="HintQuestion" type="string" required="true">       <!--- emailPassword body --->       <cfreturn >   </cffunction>   <cffunction name="createUserObj" displayName="createUserObj"     hint="Create ActionScript object to hold user information"     access="package" returnType="struct" output="false">       <cfargument name="Username" type="string" required="true">       <cfargument name="FirstName" type="string" required="true">       <cfargument name="LastName" type="string" required="true">       <cfargument name="EmailAddress" type="string" required="true">       <cfargument name="Password" type="string" required="true">       <cfargument name="HintQuestion" type="string" required="false">       <cfargument name="HintAnswer" type="string" required="false">       <!--- createUserObj body --->       <cfreturn >   </cffunction>   <cffunction name="getEmail" displayName="getEmail"     hint="Retrieve the user's hint question given an email address"     access="remote" returnType="string" output="false">       <cfargument name="EmailAddress" type="string" required="true">       <!--- getEmail body --->       <cfreturn >   </cffunction>   <cffunction name="getScriptsForUser" displayName="getScriptsForUser"     hint="Retrieve the user's scripts to feed a combo box"     access="remote" returnType="recordset" output="false">       <cfargument name="UserID" type="string" required="true">       <!--- getEmail body --->       <cfreturn >   </cffunction> </cfcomponent> 

The methods of the CFC are each defined with all arguments and an empty return value. As you can see, the method bodies are empty, except for a comment. The code body will go there, but not yet. We'll fill in comments for each method, explaining what the method does, what is required, and what is returned. This will make it that much easier to write the methods afterwards, and the code will be fully commented. An example of a fully commented function skeleton is shown in Example 14-2. The component skeletons can be downloaded from the online Code Depot.

Example 14-2. The fully commented displayList( ) method skeleton
 <cffunction name="displayList"  access="remote"  returnType="query"  output="false"> <!---     Method: displayList     Version: 1.0.0     Author: Tom Muck     Arguments:       search      Optional search criteria     Return: query object of all script information. Properties are       ScriptID    ID number of the script (primary key)       Category    The category name       CategoryID  The categoryID       ScriptName  The name of the script     Description:       This service returns a complete list of scripts available or a list       that meets the search criteria --->     <!--- displayList body --->     <!--- End displayList body --->     <cfreturn /> </cffunction> 

Test mechanisms (also known as test harnesses ) can be set up as plain ColdFusion pages to test that each service and each method is working. Inside of the Dreamweaver MX environment, simply drag the CFC from the Components panel onto a .cfm page and insert a form and conditional logic to test the form, as in the test page shown in Example 14-3.

Example 14-3. A test page for the UserService service
 <cfparam name="form.test" default="" /> <html> <head> <title>User Service Test Page</title> <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> </head> <body> <form name="form1" method="post" action="">   <select name="test" id="test">     <option value="addUser">addUser</option>     <option value="emailPassword">emailPassword</option>     <option value="loginUser">loginUser</option>   </select>   <input type="submit" name="Submit" value="Submit"> </form> <cfif form.test EQ "addUser"> <cfinvoke   component="com.oreilly.frdg.ScriptRepository.UserService"   method="addUser"   returnvariable="addUserRet">     <cfinvokeargument name="Username" value="enter_value_here"/>     <cfinvokeargument name="FirstName" value="enter_value_here"/>     <cfinvokeargument name="LastName" value="enter_value_here"/>     <cfinvokeargument name="EmailAddress" value="enter_value_here"/>     <cfinvokeargument name="Password" value="enter_value_here"/>     <cfinvokeargument name="HintQuestion" value="enter_value_here"/>     <cfinvokeargument name="HintAnswer" value="enter_value_here"/> </cfinvoke> <cfoutput>#addUserRet#</cfoutput> </cfif> <cfif form.test eq "emailPassword"> <cfinvoke   component="com.oreilly.frdg.ScriptRepository.UserService"   method="emailPassword"   returnvariable="emailPasswordRet">     <cfinvokeargument name="EmailAddress" value="enter_value_here"/>     <cfinvokeargument name="HintAnswer" value="enter_value_here"/> </cfinvoke> <cfoutput>#emailPasswordRet#</cfoutput> </cfif> <cfif form.test eq "loginUser"><cfinvoke   component="com.oreilly.frdg.ScriptRepository.UserService"   method="loginUser"   returnvariable="loginUserRet">     <cfinvokeargument name="username" value="enter_value_here"/>     <cfinvokeargument name="password" value="enter_value_here"/> </cfinvoke> <cfoutput>#loginUserRet#</cfoutput> </cfif> </body> </html> 

If you build pages like these to test each server-side service, they will be invaluable in determining where problems might occur before you begin to bring Flash into the equation. Using a page like this in ColdFusion gives you full access to ColdFusion debugging and also allows you to easily manipulate the parameters and return values to test different situations.

14.4.4 Implementing Server-Side Services

With the server-side service skeletons in place, you can begin to flesh out the services. If you have built ColdFusion test pages as recommended, you can test the services one by one as you build them.

The services use a data source name called ScriptRepository , using the sample database available for download from http://www.flash-remoting.com. You must set this data source name up in your ColdFusion Administrator in order to create the server-side services.

14.4.4.1 The UserService service

The UserService service implements all methods that relate to users, such as logging in and retrieving passwords. You can easily add more methods to the service as the application becomes more advanced. In addition to the remote methods available to the Flash movie, there is a package method called createUserObj( ) that is used internally by some of the methods to create an object of type UserObject to pass back to ActionScript.

The completed code for the UserService remote service is shown in Example 14-4. Refer to Table 14-6 for a summary of the service methods for this service.

Example 14-4. The UserService service, implemented as UserService.cfc
 <!--- Generated by Dreamweaver MX 6.0.1722 [en] (Win32) - Wed Jan 29 19:07:39 GMT-0800  (Pacific Standard Time) 2003 ---> <cfcomponent displayName="UserService"> <!---   Service:  UserService   Package:   com/oreilly/frdg/ScriptRepository   Description: Services to interact with Users from the                ScriptRepository application --->   <cffunction name="createUserObj" displayName="createUserObj"     hint="Create ActionScript object to hold user information"     returnType="struct" access="package" output="false">     <!--- Create the ActionScript object --->     <cfobject type="java"       class="flashgateway.io.ASObject"        name="UserObject"        action="create" />     <!--- Create an instance of the object --->      <cfset o = UserObject.init( )>     <!--- Set the type to our custom UserObjectClass for deserialization --->      <cfset o.setType("UserObject")>     <cfset o.put("UserID", arguments[1]) />     <cfset o.put("Username", arguments[2]) />     <cfset o.put("Userpassword", arguments[3]) />     <cfset o.put("FirstName", arguments[4]) />     <cfset o.put("LastName", arguments[5]) />     <cfset o.put("Emailaddress", arguments[6]) />     <cfset o.put("HintQuestion", arguments[7]) />     <cfset o.put("isUserLogged", 1) />     <cfset o.put("inited", 1) />     <cfreturn o />   </cffunction>   <cffunction name="loginUser" displayName="loginUser"     hint="Logs a user into the script repository"     access="remote" returnType="any" output="false">   <!---     Method: loginUser     Version: 1.0.0     Author: Tom Muck     Arguments:       username  string of up to 16 characters       password  string of up to 12 characters     Return: user object     Description:       This service allows the user to log in to the application by       verifying the username/password in the database and returning       all of the properties of the user to the Flash movie.   --->     <cfargument name="username" type="string" required="true">     <cfargument name="userpassword" type="string" required="true">     <!--- loginUser body --->     <cftry>       <cfquery datasource="ScriptRepository"         name="rsUserLogin">         SELECT * FROM Users         WHERE Username =         <cfqueryparam cfsqltype="cf_sql_varchar" value="#username#">          AND Password =         <cfqueryparam cfsqltype="cf_sql_varchar" value="#userpassword#">       </cfquery>       <cfcatch type="Any">         <cfthrow message="There was a database error" />       </cfcatch>     </cftry>     <cfif rsUserLogin.RecordCount GT 0 >       <cfset UserObj = createUserObj(rsUserLogin.UserID,           rsUserLogin.Username,           rsUserLogin.Password,           rsUserLogin.FirstName,           rsUserLogin.LastName,           rsUserLogin.Emailaddress,           rsUserLogin.HintQuestion           ) />     <cfelse>       <cfthrow message="Not a valid user" />     </cfif>     <!--- end loginUser body --->     <cfreturn UserObj />   </cffunction>   <cffunction name="addUser" displayName="addUser"    hint="Add a user to the database" access="remote"    returnType="any" output="false">   <!---     Method: addUser     Version: 1.0.0     Author: Tom Muck     Arguments:       FirstName  string of up to 60 characters       LastName  string of up to 60 characters       EmailAddress  string of up to 127 characters       Username  string of up to 16 characters       Userpassword  string of up to 12 characters       HintQuestion  string of up to 255 characters       HintAnswer  string of up to 20 characters     Return: user object     Description:       This service allows a new user to be added to the database,       and automatically to log in to the application by       verifying the username/password in the database and returning       all of the properties of the user to the Flash movie.   --->     <cfargument name="Username" type="string" required="true">     <cfargument name="FirstName" type="string" required="true">     <cfargument name="LastName" type="string" required="true">     <cfargument name="EmailAddress" type="string" required="true">     <cfargument name="Userpassword" type="string" required="true">     <cfargument name="HintQuestion" type="string" required="false">     <cfargument name="HintAnswer" type="string" required="false">     <!--- addUser body --->     <cftry>       <cfquery datasource="ScriptRepository"         name="rsDoesUserExist">         SELECT * FROM Users          WHERE Username =         <cfqueryparam cfsqltype="cf_sql_varchar" value="#username#">       </cfquery>       <cfcatch type="Any">         <cfthrow message="There was a database error" />       </cfcatch>     </cftry>     <cfif rsDoesUserExist.RecordCount EQ 0>       <cftry>         <cfquery datasource="ScriptRepository"          name="rsAddUser">            INSERT INTO Users            (Username, Password, FirstName, LastName,            EmailAddress, HintQuestion, HintAnswer)            VALUES (         <cfqueryparam cfsqltype="cf_sql_varchar" value="#Username#">,         <cfqueryparam cfsqltype="cf_sql_varchar" value="#Userpassword#">,         <cfqueryparam cfsqltype="cf_sql_varchar" value="#FirstName#">,         <cfqueryparam cfsqltype="cf_sql_varchar" value="#LastName#">,         <cfqueryparam cfsqltype="cf_sql_varchar" value="#EmailAddress#">,         <cfqueryparam cfsqltype="cf_sql_varchar"           null="#HintQuestion EQ ''#" value="#HintQuestion#">,         <cfqueryparam cfsqltype="cf_sql_varchar"           null="#HintAnswer EQ ''#" value="#HintAnswer#">           )         </cfquery>         <cfcatch type="Any">           <cfthrow message="There was a database error" />         </cfcatch>       </cftry>     <cfelse>       <cfthrow message="Already a user with that username" />     </cfif>     <!--- End addUser body --->     <cfreturn this.loginUser('#username#','#userpassword#') />   </cffunction>   <cffunction name="getEmail" displayName="getEmail"     hint="Retrieve the user's hint question given an email address"     access="remote" returnType="string" output="false">   <!---     Method: getEmail     Version: 1.0.0     Author: Tom Muck     Arguments:       EmailAddress  string of up to 127 characters     Return: Hint question (string)     Description:       This service retrieves the user's hint question given an email       address   --->     <cfargument name="EmailAddress" type="string" required="true">     <!--- getEmail body --->      <cftry>       <cfquery datasource="ScriptRepository"        name="rsGetQuestion">          SELECT HintQuestion FROM Users          WHERE Emailaddress =          <cfqueryparam cfsqltype="cf_sql_varchar" value="#EmailAddress#">       </cfquery>       <cfcatch type="Any">         <cfthrow message="There was a database error" />       </cfcatch>     </cftry>     <cfif rsGetQuestion.RecordCount NEQ 1>       <cfthrow message="No match found in database" />     </cfif>     <!--- End getEmail body --->     <cfreturn rsGetQuestion.HintQuestion />   </cffunction>   <cffunction name="emailPassword" displayName="emailPassword"    hint="Email a password to a user, given the email address"    access="remote" returnType="boolean" output="false">   <!---     Method: emailPassword     Version: 1.0.0     Author: Tom Muck     Arguments:       EmailAddress  string of up to 127 characters       HintAnswer  string of up to 20 characters     Return: boolean of successful email of the password     Description:       This service allows a user to have his password emailed to him,       if the hint answer matches the user's hint in the database.   --->     <cfargument name="EmailAddress" type="string" required="true">     <cfargument name="HintAnswer" type="string" required="true">     <!--- emailPassword body --->     <cftry>       <cfquery datasource="ScriptRepository"        name="rsGetUser">          SELECT Username, Password FROM Users          WHERE Emailaddress =          <cfqueryparam cfsqltype="cf_sql_varchar" value="#EmailAddress#">          AND HintAnswer =          <cfqueryparam cfsqltype="cf_sql_varchar" value="#HintAnswer#">       </cfquery>        <cfcatch type="Any">         <cfthrow message="There was a database error" />       </cfcatch>     </cftry>     <cfif rsGetUser.RecordCount EQ 1>     <!---Only send the email if there is a matching record in the database --->       <cfmail from="admin@flash-remoting.com" to="#Emailaddress#"        subject="Requested information"> Your username is: #rsGetUser.username# Your password is: #rsGetUser.password# Please respond to admin@flash-remoting.com if you have received this message in error. Administrator       </cfmail>     <cfelse>       <cfreturn 0 />     </cfif>     <!--- End emailPassword body --->     <cfreturn 1 />   </cffunction>   <cffunction name="getScriptsForUser" access="remote"    returnType="query" output="false">     <!---     Method: getScriptsForUser     Version: 1.0.0     Author: Tom Muck     Arguments:       Username     username for currently logged-in user       Userpassword     password for currently logged-in user     Return:       query object of scriptid and scriptname information.     Description:       This service returns a complete list of scripts available       for the currently logged in user.   --->     <cfargument name="username" hint="Username of current user"      type="string"  default="" />     <cfargument name="userpassword" hint="Password of current user"      type="string"  default="" />     <!--- getScriptsForUser body --->     <cftry>       <cfquery name="rsScripts" datasource="ScriptRepository">         SELECT s.ScriptID, s.ScriptName         FROM Users u, Scripts s         WHERE u.Username =         <cfqueryparam cfsqltype="cf_sql_varchar" value="#username#">         AND u.Password =         <cfqueryparam cfsqltype="cf_sql_varchar" value="#userpassword#">         AND s.UserID = u.UserID         ORDER BY s.ScriptName       </cfquery>       <cfcatch type="any">         <cfthrow message="There was a database error" />       </cfcatch>     </cftry>     <!--- End getScriptsForUser body --->     <cfreturn rsScripts />   </cffunction> </cfcomponent> 

The UserService service interacts with the Flash movie using a UserObject , a custom object that we set up in ActionScript using Object.registerClass . As discussed in Chapter 4, this method of transferring objects allows for seamless passing of data between client and server. ColdFusion supports a <cfobject> tag, which allows you to set up the object as a Java object of type flashgateway.io.ASObject .

There are a few things of note in the code. All queries that contain user-supplied parameters are set up with a <cfqueryparam> tag. This tag guards the application against SQL injection attacks, in which a malicious user sends SQL statements in a URL to attempt to damage your data or even gain control of your database.

Because we control the input from the Flash movie, you might think that the remote methods are safe, but that is not the case. A remote service that is set up for Flash Remoting is completely open to the outside world. A person can interact with your remote service if he knows the URL and the service name, which can easily be obtained from the Flash movie by decompiling it. A remote service can then be invoked through a URL, making all remote services that accept parameters open to attack.

The following query, from the getEmail( ) method, demonstrates the use of the <cfqueryparam> tag:

 <cfquery datasource="ScriptRepository"  name="rsGetQuestion">   SELECT HintQuestion FROM Users   WHERE Emailaddress =   <cfqueryparam cfsqltype="cf_sql_varchar" value="#EmailAddress#"> </cfquery> 

The <cfqueryparam> tag takes the place of the parameter within the query and throws an error if the datatype is not right. Therefore, the parameter is usable only as a proper parameter; crackers cannot inject SQL statements into the query.

The <cfqueryparam> tag has a counterpart if you are using stored procedures as well: <cfprocparam> . All queries and stored procedures that accept user-supplied parameters should use these tags.

Also, the queries in the page are all wrapped in try / catch blocks, in order to trap errors and simply throw them back to the Flash movie. We could put some other form of error handling within the block, such as writing to a log file or sending an email to a site administrator, but this method is the simplest.

The <cfmail> tag contains the body of the email message to be sent to the end user. For that reason, the text is aligned flush left, even though the code is nicely indented otherwise . The <cfmail> tag translates any spaces or line breaks within the body of the message literally, so the text format should be preserved inside the tag.

One last item deserves a mention: when a user is added to the database, the user is also automatically logged in, as shown in the following line from the addUser( ) method:

 <cfreturn this.loginUser('#username#','#userpassword#') /> 

Rather than simply return a Boolean value indicating whether the user was successfully added to the database, we log the user in using the UserService.loginUser( ) method and return the UserObject to the Flash movie. This improves the end user's experience because she doesn't need to log in as a separate step after registering.

14.4.4.2 The ScriptService service

The ScriptService service includes methods that relate to the storing of the scripts. The service contains many of the same types of features that the UserService.cfc file had, such as the package method that creates a ScriptObject object type, and the use of the <cfqueryparam> tags to guard against malicious user input.

The completed ScriptService service is shown in Example 14-5. Refer to Table 14-7 for a summary of the service methods for this service.

Example 14-5. The ScriptService service, implemented as ScriptService.cfc
 <!--- Generated by Dreamweaver MX 6.0.1722 [en] (Win32) - Wed Jan 29 19:32:00 GMT-0800  (Pacific Standard Time) 2003 ---> <cfcomponent displayName="ScriptService">   <!---   Service:  ScriptService   Package:   com/oreilly/frdg/ScriptRepository   Description: Utilizes a Script object to pass information back and forth     from the Flash movie.   --->   <cffunction name="createScriptObj" hint="Create ActionScript object to     hold script information" returnType="struct" access="package" output="false">     <!--- Create the ActionScript object --->     <cfobject type="java"      class="flashgateway.io.ASObject"      name="ScriptObject"      action="create" />     <!--- Create an instance of the object --->      <cfset o = ScriptObject.init( )>     <!--- Set the type to our custom UserObjectClass for deserialization --->     <cfset o.setType("ScriptObject")>     <cfset o.put("ScriptID", arguments[1]) />     <cfset o.put("ScriptName", arguments[2]) />     <cfset o.put("ScriptDescription", arguments[3]) />     <cfset o.put("ScriptCode", arguments[4]) />     <cfset o.put("LanguageID", arguments[5]) />     <cfset o.put("CategoryID", arguments[6]) />     <cfset o.put("UserID", arguments[7]) />     <cfset o.put("DateUploaded", this.DateTimeString(arguments[8])) />     <cfset o.put("DateModified", this.DateTimeString(arguments[9])) />     <cfset o.put("VersionMajor", arguments[10]) />     <cfset o.put("VersionMinor", arguments[11]) />     <cfset o.put("VersionMicro", arguments[12]) />     <cfset o.put("ScriptUniqueID", arguments[13]) />     <cfset o.put("inited",1) />     <cfreturn o />   </cffunction>   <cffunction name="addScript" access="remote" returnType="any" output="false">     <!---     Method: addScript     Version: 1.0.0     Author: Tom Muck     Arguments:       ScriptObj  a script object with all properties needed to add       the script to the database. Properties are:         ScriptName         ScriptDescription         ScriptCode         LanguageID         CategoryID         UserID         DateUploaded         DateModified         VersionMajor         VersionMinor         VersionMicro     Return: scriptid     Description:       This service allows a registered user to upload a script       to the database.   --->     <!--- <cfargument name="ScriptObj" type="struct" required="true"> --->     <!--- AddScript body --->   <!--- Create a unique ID to aid in retrieving the primary key --->   <cfset scriptuniqueid = CreateUUID( ) />   <cfset ScriptObj = this.createScriptObj(     0,     arguments.ScriptName,     arguments.ScriptDescription,     arguments.ScriptCode,     arguments.LanguageID,     arguments.CategoryID,     arguments.UserID,     arguments.DateUploaded,     arguments.DateModified,     arguments.VersionMajor,     arguments.VersionMinor,     arguments.VersionMicro,     scriptuniqueid     ) />   <!--- Insert the script into the database --->      <cftry>      <cfquery name="insertScript" datasource="ScriptRepository">       INSERT INTO Scripts (         ScriptName,       ScriptDescription,       ScriptCode,       LanguageID,       CategoryID,       UserID,       DateUploaded,       DateModified,       VersionMajor,       VersionMinor,       VersionMicro,       scriptuniqueid     )       VALUES (       <cfqueryparam cfsqltype="cf_sql_varchar"        null="#ScriptObj.ScriptName EQ ''#"        value="#ScriptObj.ScriptName#">,       <cfqueryparam cfsqltype="cf_sql_varchar"        null="#ScriptObj.ScriptDescription EQ ''#"        value="#ScriptObj.ScriptDescription#">,       <cfqueryparam cfsqltype="cf_sql_varchar"        null="#ScriptObj.ScriptCode EQ ''#"        value="#ScriptObj.ScriptCode#">,       <cfqueryparam cfsqltype="cf_sql_numeric"        null="#ScriptObj.LanguageID EQ ''#"        value="#ScriptObj.LanguageID#">,       <cfqueryparam cfsqltype="cf_sql_numeric"        null="#ScriptObj.CategoryID EQ ''#"        value="#ScriptObj.CategoryID#">,       <cfqueryparam cfsqltype="cf_sql_numeric"        null="#ScriptObj.UserID EQ ''#"        value="#ScriptObj.UserID#">,       <cfqueryparam cfsqltype="cf_sql_timestamp"        null="#ScriptObj.DateUploaded EQ ''#"        value="#ScriptObj.DateUploaded#">,       <cfqueryparam cfsqltype="cf_sql_timestamp"        null="#ScriptObj.DateModified EQ ''#"        value="#ScriptObj.DateModified#">,       <cfqueryparam cfsqltype="cf_sql_numeric"        null="#ScriptObj.VersionMajor EQ ''#"        value="#ScriptObj.VersionMajor#">,       <cfqueryparam cfsqltype="cf_sql_numeric"        null="#ScriptObj.VersionMinor EQ ''#"        value="#ScriptObj.VersionMinor#">,       <cfqueryparam cfsqltype="cf_sql_numeric"        null="#(ScriptObj.VersionMicro EQ '')#"        value="#ScriptObj.VersionMicro#">,       <cfqueryparam cfsqltype="cf_sql_varchar"        null="no"        value="#scriptuniqueid#">     )       </cfquery>     <cfquery name="rsScript" datasource="ScriptRepository">       SELECT ScriptID FROM Scripts     WHERE ScriptUniqueID = '#scriptuniqueid#'       </cfquery>       <cfcatch type="any">         <cfthrow message="There was a database error" />       </cfcatch>     </cftry>     <!--- End AddScript body --->     <cffile action="append" file="c:\log.txt" output=#this.objToString(ScriptObj)#>     <cfreturn ScriptObj />   </cffunction>   <cffunction name="updateScript" access="remote" returnType="any" output="false">     <!---     Method: updateScript     Version: 1.0.0     Author: Tom Muck     Arguments:       ScriptObj  a script object with all properties needed to add       the script to the database. Properties are:         ScriptID         ScriptDescription         ScriptCode         LanguageID         CategoryID         UserID         DateUploaded         DateModified         VersionMajor         VersionMinor         VersionMicro     Return: Updated ScriptObject     Description:       This service allows a registered user to change a script that       exists in the database.   --->   <!--- UpdateScript body --->   <cfset ScriptObj = this.createScriptObj(     arguments.ScriptID,     arguments.ScriptName,     arguments.ScriptDescription,     arguments.ScriptCode,     arguments.LanguageID,     arguments.CategoryID,     arguments.UserID,     arguments.DateUploaded,     arguments.DateModified,     arguments.VersionMajor,     arguments.VersionMinor,     arguments.VersionMicro,     0     ) />   <cftry>     <cfquery name="updateScript" datasource="ScriptRepository">          UPDATE Scripts SET ScriptName =          <cfqueryparam cfsqltype="cf_sql_varchar"           null="#ScriptObj.ScriptName EQ ''#"           value="#ScriptObj.ScriptName#">,          ScriptDescription =          <cfqueryparam cfsqltype="cf_sql_varchar"           null="#ScriptObj.ScriptDescription EQ ''#"           value="#ScriptObj.ScriptDescription#">,          ScriptCode =          <cfqueryparam cfsqltype="cf_sql_varchar"           null="#ScriptObj.ScriptCode EQ ''#"           value="#ScriptObj.ScriptCode#">,          LanguageID =          <cfqueryparam cfsqltype="cf_sql_numeric"           null="#ScriptObj.LanguageID EQ ''#"           value="#ScriptObj.LanguageID#">,          CategoryID =          <cfqueryparam cfsqltype="cf_sql_numeric"           null="#ScriptObj.CategoryID EQ ''#"           value="#ScriptObj.CategoryID#">,          UserID =          <cfqueryparam cfsqltype="cf_sql_numeric"           null="#ScriptObj.UserID EQ ''#"           value="#ScriptObj.UserID#">,          DateModified =          <cfqueryparam cfsqltype="cf_sql_timestamp"           null="#ScriptObj.DateModified EQ ''#"           value="#ScriptObj.DateModified#">,          VersionMajor =          <cfqueryparam cfsqltype="cf_sql_numeric"           null="#ScriptObj.VersionMajor EQ ''#"           value="#ScriptObj.VersionMajor#">,          VersionMinor =          <cfqueryparam cfsqltype="cf_sql_numeric"           null="#ScriptObj.VersionMinor EQ ''#"           value="#ScriptObj.VersionMinor#">,          VersionMicro =          <cfqueryparam cfsqltype="cf_sql_numeric"           null="#ScriptObj.VersionMicro EQ ''#"           value="#ScriptObj.VersionMicro#">          WHERE ScriptID   =          <cfqueryparam cfsqltype="cf_sql_numeric"           null="#ScriptObj.ScriptID EQ ''#"           value="#ScriptObj.ScriptID#">     </cfquery>       <cfcatch type="any">         <cfthrow message="There was a database error" />       </cfcatch>     </cftry>     <!--- End UpdateScript body --->     <cfreturn ScriptObj />   </cffunction>   <cffunction name="displayList" access="remote" returnType="query" output="false">     <!---     Method: displayList     Version: 1.0.0     Author: Tom Muck     Arguments:       search     Optional search criteria     Return: query object of all script information. Properties are       ScriptID  ID number of the script (primary key)       Category  The category name       CategoryID  The categoryID       ScriptName  The name of the script     Description:       This service returns a complete list of scripts available or a list       that meets the search criteria     --->   <cfargument name="search" hint="Search criteria for script listing"    type="string"  default="" />     <!--- DisplayList body --->   <cftry>       <cfquery name="rsScripts" datasource="ScriptRepository">         SELECT c.CategoryDesc         , c.CategoryID         , s.ScriptID         , s.ScriptName         FROM Categories c         INNER JOIN         Scripts s ON         c.CategoryID = s.CategoryID         <cfif search neq "">           WHERE s.ScriptDescription + s.ScriptName + c.CategoryDesc           LIKE           <cfqueryparam cfsqltype="cf_sql_varchar" value="%#search#%">         </cfif>         ORDER BY c.CategoryDesc, s.ScriptID       </cfquery>       <cfcatch type="any">         <cfthrow message="There was a database error" />       </cfcatch>     </cftry>     <!--- End DisplayList body --->     <cfreturn rsScripts />   </cffunction>   <cffunction name="getScript" access="remote" returnType="struct" output="false">     <!---     Method: getScript     Version: 1.0.0     Author: Tom Muck     Arguments:       ScriptID  ID number of the script to display     Return: ScriptObj     Description:       This service returns a script object to be displayed in the       Flash movie     --->     <cfargument name="ScriptID" type="any" required="true">     <!--- DisplayScript body --->   <cftry>       <cfquery name="rsScripts" datasource="ScriptRepository">     SELECT * FROM Scripts     WHERE ScriptID =     <cfqueryparam cfsqltype="cf_sql_numeric" value="#ScriptID#">       </cfquery>       <cfcatch type="any">         <cfthrow message="There was a database error" />       </cfcatch>     </cftry>   <cfif rsScripts.RecordCount EQ 1>    <cfset ScriptObj = createScriptObj(rsScripts.ScriptID,      rsScripts.ScriptName ,      rsScripts.ScriptDescription ,      rsScripts.ScriptCode,      rsScripts.LanguageID,      rsScripts.CategoryID,      rsScripts.UserID,      rsScripts.DateUploaded,      rsScripts.DateModified,      rsScripts.VersionMajor,      rsScripts.VersionMinor,      rsScripts.VersionMicro,      rsScripts.ScriptUniqueID      ) />    <cfelse>      <cfthrow message="No script with that ID" />    </cfif>     <!--- End getScript body --->     <cfreturn ScriptObj />   </cffunction>   <cffunction name="DateTimeString" access="package" hint="Convert a date/time data to a  string for display" returntype="string" >    <cfargument name="dateObj" type="any" required="false" />   <cfif isdate(dateObj)>     <cfset returnstring =      "#DateFormat(dateObj,'mm/dd/yyyy')# #TimeFormat(dateObj, 'hh:mm:ss tt')#" />   <cfelse>     <cfset returnstring = dateObj />   </cfif>   <cfreturn returnstring />   </cffunction> </cfcomponent> 
14.4.4.3 The SiteService service

The SiteService service contains methods to populate UI components, contact the site administrator, send messages to other users, and populate the About screen, as summarized in Table 14-8.

The complete server-side code for the service is shown in Example 14-6.

Example 14-6. The SiteService service, implemented as SiteService.cfc
 <!--- Generated by Dreamweaver MX 6.0.1722 [en] (Win32) - Thu Jan 30 21:49:32 GMT-0800  (Pacific Standard Time) 2003 ---> <cfcomponent displayName="SiteService" hint="General service for site methods"> <!---   Service:  SiteService   Package:   com/oreilly/frdg/ScriptRepository   Description: General utility methods for the site --->   <cffunction name="about" displayName="About"   hint="Short paragraph and info about the company" access="remote"   returnType="query" output="false">   <!---     Method: about     Version: 1.0.0     Author: Tom Muck     Arguments:       none     Return: a query object with the information about the site     Description:       This service sends the information about the site back to the caller   --->     <!--- about body --->     <cftry>       <cfquery datasource="ScriptRepository"          name="rsAbout">          SELECT TOP 1 * FROM CompanyInfo       </cfquery>       <cfcatch type="Any">         <cfthrow message="There was a database error" />       </cfcatch>     </cftry>     <!--- End about body --->     <cfreturn rsAbout />   </cffunction>   <cffunction name="contactForm" displayName="contactForm"    hint="Contact the site administrator by email through a standard form"    access="remote" returnType="string" output="false">    <cfargument name="emailaddress" type="string" default="" />    <cfargument name="userid" type="numeric" default=0 />    <cfargument name="comment" type="string" default="" />   <!---     Method: contactForm     Version: 1.0.0     Author: Tom Muck     Arguments:       none     Return: true     Description:       This service sends an email to the site administrator   --->     <!--- contactForm body --->     <cfmail to="admin@flash-remoting.com" from=#emailaddress#      subject="Comment: #left(comment,40)#..."      > UserID: #userid# Emailaddress: #emailaddress# Date/time: #DateFormat(now( ))# #TimeFormat(now( ))# Comment: #comment#     </cfmail>     <!--- End contactForm body --->     <cfreturn 1 />   </cffunction>   <cffunction name="sendPage" displayName="sendPage" hint="Send the page  information to a friend" access="remote" returnType="string" output="false">     <cfargument name="scriptid" required="true" type="numeric" />     <cfargument name="emailto" required="true" type="string" />     <cfargument name="emailfrom" required="true" type="string" />   <!---     Method: sendPage     Version: 1.0.0     Author: Tom Muck     Arguments:       none     Return: true     Description: This service sends an email to any email address with                   a link to a specific script   --->     <!--- sendPage body --->     <cfset link = this.about( ).DownloadLink />     <cfmail to=#emailto# from=#emailfrom#      subject="#emailfrom# thought you might be interested in this page"      bcc="emailrecord@flash-remoting.com"> This page was sent to you by #emailfrom#: #link#?id=#scriptid# Administrator, Flash-Remoting.com     </cfmail>     <!--- End sendPage body --->     <cfreturn 1 />   </cffunction>   <cffunction name="getCategories" access="remote" returntype="query" >   <!---     Method: getCategories     Version: 1.0.0     Author: Tom Muck     Arguments:       none     Return: recordset     Description:       This service returns a recordset for Categories to populate combo boxes   --->     <!--- getCategories body --->     <cfquery name="rsCategories" datasource="ScriptRepository" >      SELECT CategoryID, CategoryDesc FROM Categories     </cfquery>     <!--- END getCategories body --->     <cfreturn rsCategories />   </cffunction>   <cffunction name="getLanguages" access="remote" returntype="query" >   <!---     Method: getCategories     Version: 1.0.0     Author: Tom Muck     Arguments:       none     Return: recordset     Description:       This service returns a recordset for Categories to populate combo boxes   --->     <!--- getLanguages body --->     <cfquery name="rsLanguages" datasource="ScriptRepository" >      SELECT LanguageID, LanguageName FROM Languages     </cfquery>     <!--- End getLanguages body --->     <cfreturn rsLanguages />   </cffunction>   <cffunction name="getUsers" access="remote" returntype="query" >   <!---     Method: getUsers     Version: 1.0.0     Author: Tom Muck     Arguments:       none     Return: recordset     Description:       This service returns a recordset for Users to populate combo boxes   --->     <!--- getUsers body --->     <cfquery name="rsUsers" datasource="ScriptRepository" >      SELECT UserID, FirstName + ' ' + LastName as theName      FROM Users     </cfquery>     <!--- END getUsers body --->     <cfreturn rsUsers />   </cffunction> </cfcomponent> 

After all server-side services are in full working order and have been tested , we can begin work on the Flash Remoting client-side code.

14.4.5 Client-Side ActionScript

The Flash user interface for the application was designed and implemented by a designer who worked from a short specification that described the interfaces needed and the fields needed in each interface. The design specification sheet can be seen in Appendix C. We won't concern ourselves with the actual design or implementation of the ActionScript code to make the interface work. The preliminary .fla (bare-bones interface) and completed Flash movies can be downloaded from the online Code Depot.

My thanks to Edoardo "Edo" Zubler who created and implemented the interface design. Edo maintains a site at http://www.aftershape.com.

The ActionScript Flash Remoting code for the application features some of the techniques shown in Chapter 12 and is described here.

14.4.5.1 Objects

An object-based approach to building the interface makes it easier to expand the application in the future. It also makes it easier to reuse modules from the application in other applications. The objects we'll use are:

ScriptObject

A ScriptObject object is a simple representation of a user's script, for both uploading and downloading. The ScriptObject class contains the methods for interacting with the ScriptService service. Methods correspond to the server-side methods of the ScriptServices service.

UserObject

The UserObject object contains the properties of the currently logged-in user. The UserObject class contains all methods for interacting with the UserService service. Methods correspond to the server-side methods of the UserServices service.

Each object is self-contained and includes all code necessary to deal with the service, including the methods. If the object has to be sent to the server, Flash automatically takes care of stripping off the methods before sending the object.

14.4.5.2 Interface

The Flash movie contains several interfaces to implement all the functionality required of the application. The main interface is shown in Figure 14-3.

Figure 14-3. The Flash interface for the Script Repository
figs/frdg_1403.gif

The following screen definitions resulted from the designer's feedback on the initial user interface specification (see Appendix C):

Main

The main application screen, which lists the different options available and displays all scripts. Listing scripts in a Tree component allows all users to view available scripts and drill down to a description page for a particular script. The Search box allows a user to specify criteria to narrow the list of displayed scripts. A detail page appears to the right of the Tree on the same screen. Also present are a Download Script button to allow a script to be downloaded, and a send-to-a-friend feature to send the current detail page's link to an email address. The menu items always remain within reach via a sliding menu.

Login

The login screen allows registered users to log in. It contains username and password fields and a button.

Register

Allows a new user to register, with personal information and a username/password combination. If the registration is successful, the user is also automatically logged in.

Upload Script

A blank form to allow a registered user to upload a script to the database.

Modify Script

A form that mimics the Upload Script form and is pre-filled in with information about a script that needs to be updated. A drop-down list of available scripts allows a user to view all scripts that he has uploaded, so he can choose which one to modify.

Contact Form

Allows a user to contact the site administrator.

About

A screen showing a short description of the site.

Alert Box

This page delivers messages to the user, with a simple OK button. The box allows an optional callback function to be executed upon clicking OK.

Retrieve Box

A general box that presents a label and a text field so that the user can enter a value and pass it to the application. The box allows an optional callback function to be executed upon clicking OK.

Working

A progress box that tells the user that the application is working.

The user interface has been designed independently of the server-side and Flash Remoting code. The user interface, as implemented by the designer, is entirely functional, with dummy methods to act as placeholders for eventual Flash Remoting methods. Fitting the Flash Remoting code into the existing interface will be a simple operation.

14.4.5.3 ActionScript code

The Flash Remoting code and ActionScript code not related to the user interface is placed into these include files:

ScriptRepository.as

Main ActionScript code file, which initializes the Flash Remoting connections and other objects needed for the application and includes all of the other necessary files.

RemotingInit.as

Initializes the Flash Remoting URL and the service objects.

UserObject.as

Class file that contains the UserObject class and all methods that interact with the UserService service.

ScriptObject.as

Class file that contains the ScriptObject class and all methods that interact with the ScriptService service.

SiteUtilityFunctions.as

ActionScript functions that implement some of the services for the site, such as emailing the site administrator.

NetServices.as

Flash Remoting file (included with Flash Remoting components from Macromedia).

UI.as

User interface utility functions and extensions to some basic Flash MX components.



Flash Remoting
Flash Remoting: The Definitive Guide
ISBN: 059600401X
EAN: 2147483647
Year: 2003
Pages: 239
Authors: Tom Muck

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net