14.5 Flash Remoting Code


The ActionScript code used to contact the remote services is contained in several files described in the following sections. One caveat when working with Flash Remoting is that the remote calls are asynchronous. This creates problems when you're trying to separate the logic in your applications, as discussed in Chapter 12. In our application, we decided to keep the logic simple by implementing the UI logic from within our responder objects. In other words, when a remote call returns a result, the responder object takes care of the details of updating the display.

14.5.1 RemotingInit.as

The RemotingInit.as file contains all of the Flash Remoting initialization code needed for the movie. The code is identical to the code you've seen throughout the book, but in this case we are creating three distinct service objects: one for the UserService , one for the ScriptService , and one for the SiteService . The code is shown in Example 14-7.

Example 14-7. Remoting initialization in the RemotingInit.as file
 // Remoting initialization if (initialized == null) {   initialized = true;   NetServices.setDefaultGatewayUrl("http://localhost/flashservices/gateway");   my_conn = NetServices.createGatewayConnection( );   // Create the service objects   UserService = my_conn.getService(                         "com.oreilly.frdg.ScriptRepository.UserService");   ScriptService = my_conn.getService(                         "com.oreilly.frdg.ScriptRepository.ScriptService");   SiteService = my_conn.getService(                         "com.oreilly.frdg.ScriptRepository.SiteService"); } // Major error handler, usually a connection is bad, so the movie will fail System.onStatus = function (error) {   errorHandler("There was a connection failure"); }; // General error handler for entire movie function errorHandler (message, callbackFunction) {     alertBox("errorHandlerAlert", message, callbackFunction); }; 

The code also includes the general System.onStatus( ) method to handle any catastrophic errors, such as a connection failure. Also, a general error handler is defined that will be used in all onStatus( ) methods for responder objects in the movie. By centralizing the error handling we can easily customize the message presented to the user. During development, we are simply displaying the return message. During debugging, we might want to trace some debugging information. At some later point, we can change this function to present a more meaningful error to the user .

14.5.2 ScriptObject.as

The ScriptObject.as file contains the definition for the ScriptObject class. The complete ScriptObject.as file is shown in Example 14-8. As you can see, the client-side ScriptObject contains the same properties as the ScriptObject that was created in the server-side code in Example 14-5. The object is passed back and forth when necessary to simplify operations on both ends. When passed to the server, the properties become arguments of the ColdFusion function. When passed back to the client, the properties are copied to the current instance of the ScriptObject using the private _copyProperties( ) method.

Example 14-8. The ScriptObject class
 /* ScriptObject  an object with all properties needed to add the script to the database. Properties:   ScriptID   ScriptName   ScriptDescription   ScriptCode   LanguageID   CategoryID   UserID   DateUploaded   DateModified   VersionMajor   VersionMinor   VersionMicro   ScriptUniqueID Methods:   init          initialize the object.   addScript     call the remote method to add a script to the DB   updateScript  call the remote method to update a script in the DB   test          debugging method that is used to make sure object is returning                 from the remote method as an object of type   ScriptObject   onResult      Responder method for the object's remote calls   onStatus      Responder error method for object's remote calls   _copyProperties  "Private" method to copy the properties from remote call                 to the current instance of a   ScriptObject   toString      debugging method to display the current script's properties */ function ScriptObject (id) {   if (!this.inited)     this.init(arguments); } ScriptObject.prototype.init = function (args) {   this.inited = true;    // Instance is initialized   this.ScriptID          = (args[0] != undefined) ? Number(args[0]) : 0;   this.ScriptName        = (args[1] != undefined) ? args[1] : "";   this.ScriptDescription = (args[2] != undefined) ? args[2] : "";   this.ScriptCode        = (args[3] != undefined) ? args[3] : "";   this.LanguageID        = (args[4] != undefined) ? Number(args[4]) : 0;   this.CategoryID        = (args[5] != undefined) ? Number(args[5]) : 0;   this.UserID            = (args[6] != undefined) ? Number(args[6]) : 0;   this.DateUploaded      = (args[7] != undefined) ? args[7] : "";   this.DateModified      = (args[8] != undefined) ? args[8] : "";   this.VersionMajor =        (args[9] != undefined && args[9] != "") ? Number(args[9]) : 0;   this.VersionMinor =        (args[10] != undefined && args[10] != "") ? Number(args[10]) : 0;   this.VersionMicro =        (args[11] != undefined && args[11] != "") ? Number(args[11]) : 0;   this.ScriptUniqueID = (args[12] != undefined) ? args[12] : ""; }; Object.registerClass("ScriptObject", ScriptObject); // Define a   toString( )   function for reading the object ScriptObject.prototype.toString = function ( ) {   var temp = "inited:  "         +  this.inited + '\n';   temp += "ScriptID:  "          +  this.ScriptID + '\n';   temp += "ScriptName:  "        +  this.ScriptName + '\n';   temp += "ScriptDescription:  " +  this.ScriptDescription + '\n';   temp += "ScriptCode:  "        +  this.ScriptCode + '\n';   temp += "LanguageID:  "        +  this.LanguageID + '\n';   temp += "CategoryID:  "        +  this.CategoryID + '\n';   temp += "UserID:  "            +  this.UserID + '\n';   temp += "DateUploaded:  "      +  this.DateUploaded + '\n';   temp += "DateModified:  "      +  this.DateModified + '\n';   temp += "VersionMajor:  "      +  this.VersionMajor + '\n';   temp += "VersionMinor:  "      +  this.VersionMinor + '\n';   temp += "VersionMicro:  "      +  this.VersionMicro + '\n';   temp += "ScriptUniqueID:  "    +  this.ScriptUniqueID;   return temp; }; // Methods are simple interfaces to the remote methods ScriptObject.prototype.addScript = function (service) {   service.addScript(this, this); }; ScriptObject.prototype.updateScript = function (service) {   service.updateScript(this, this); }; // Debugging function to let us know that the object returned from // the server was registered properly. In responder function, do: //   result.test( )   ScriptObject.prototype.test = function ( ) {   trace("ScriptObject successful") }; //  Copy properties from an object to this (current instance of obj) ScriptObject.prototype._copyProperties = function (from) {   for (var prop in from) {     if (this[prop] != from[prop]) this[prop] = from[prop];   } }; //  Responder function ScriptObject.prototype.onResult = function (result) {   var exists = false;   if (this.ScriptID) exists = true;  // If this is an update,   ScriptID   exists     // Put all properties from the result into the instance of the   ScriptObject   this._copyProperties(result);   alertBox("alertModify", "Successful.", scriptRepositoryRefresh(this, exists)); }; // Responder error handler ScriptObject.prototype.onStatus = function (error) {   errorHandler(error.description); }; ScriptObject.prototype.validate = function ( ) {   var errorMsg = "";   if (this.ScriptName == "")   errorMsg += "Script name must not be empty\n";   if (this.ScriptDescription == "")       errorMsg += "Script description must not be empty\n";   if (this.ScriptCode == "")   errorMsg += "Script code must not be empty\n";   if (this.LanguageID == 0)    errorMsg += "Must choose a script language\n";   if (this.CategoryID == 0)    errorMsg += "Must choose a script category\n";   if (this.DateUploaded == "") errorMsg += "Must choose date uploaded\n";   if (this.DateModified == "") errorMsg += "Must include date modified\n";   if (this.VersionMajor == "")      errorMsg += "Script version must be in format x.x.x\n";   if (this.VersionMinor == "")      errorMsg += "Script version must be in format x.x.x\n";   if (this.VersionMicro == "")      errorMsg += "Script version must be in format x.x.x\n";   return errorMsg; } 

The script is commented inline, but a few areas warrant further explanation. The calls to the remote service use a rather cryptic syntax, passing this as both the first and second parameters:

 ScriptObject.prototype.addScript = function (service) {   service.addScript(this, this); }; 

This code passes the first argument of the current ScriptObject instance ( this ) as a responder object, because the object has onResult( ) and onStatus( ) methods declared on it. Flash will strip off the first argument to use as the responder, and the second argument ( this ) becomes the argument sent to the remote service. It is the current instance of the ScriptObject as well; all properties of the object are passed to the service.

The responder method, onResult( ) , copies the properties of the result to the same instance of the ScriptObject that made the remote call. It then calls the scriptRepositoryRefresh( ) function as a callback function that will add the script to the Tree component (and to the scriptCache property shown in Example 14-9). The exists variable tells the callback function that the current script is either a new script ( exists is false ) or an updated script ( exists is true ).

The validate( ) method of the object is a general-purpose validation routine for the properties of the ScriptObject . Rather than validate the individual text fields, we wait until the call to the remote service to validate the text. By doing this, the complexities of validating various text fields and combo boxes throughout the application are eliminated; validation can be done all at once easily by invoking the validate( ) method of the object.

The init( ) method, as previously shown in Chapter 4, allows the object passed from the remote method to retain all of its properties upon instantiation, which occurs behind the scenes immediately upon return and before your ActionScript code can act on the object.

14.5.3 ScriptRepository.as

The ScriptRepository.as file is the only one included directly in our Flash movie. All other ActionScript documents are included from this file. The movie is initialized from here, and the user interface is populated from the remote methods.

The complete ScriptRepository.as file is shown in Example 14-9.

Example 14-9. The ScriptRepository.as file
 // Flash Remoting include #include "NetServices.as" // NetDebug for debugging purposes #include "NetDebug.as" // Data provider for UI components #include "DataGlue.as" // Initialize Flash Remoting #include "RemotingInit.as" // Site utility functions #include "SiteUtilityFunctions.as" //   UserObject   class #include "UserObject.as" //   ScriptObject   class #include "ScriptObject.as" // User interface stuff #include "UI.as" // Set up a cache for scripts to avoid hitting the remote service again var scriptCache = new Object( ); // Initialize the user and some other globals _global.currentUser = new UserObject( ); _global.currentScript = 0;   // If there is a current script shown, it will be here _global.downloadLink = "http://www.flash-remoting.com"; // General responder object for methods that return nothing //   onResult( )   method displays a message in an alert box //   onStatus( )   method simply calls the error handler function GeneralResponder (theName, theMessage, callbackFunction) {   this.onResult = function (result) {     alertBox(theName, theMessage, callbackFunction);   };   this.onStatus = function (error) {     errorHandler(error.description);   }; } // Set up a default "script" that contains generic labels for the interface scriptCache["0"] = new ScriptObject(             0,                    // scriptid             "Script name...",     // ScriptName             "Description...",     // ScriptDescription             "Code...",            // ScriptCode             0,                    // LanguageID             0,                    // CategoryID             0,                    // UserID             "Date uploaded...",   // DateUploaded             "Date modified...",   // DateModified             "",                   // Version major             "",                   // Version minor             "",                   // Version micro             "");                  // ScriptUniqueID // Display the dummy script displayIt(0, cnt_main_mc.cnt_view_mc); // Search the scripts database function searchScripts (searchWord) {   ScriptService.displayList(new ScriptListingResponder(                             cnt_main_mc.cnt_view_mc.scripttree_tree,                             'containing ' + searchWord),searchWord);   workingAlert( );   // Display a "...working" box } // Get all of the scripts function getAllScripts ( ) {   ScriptService.displayList(new ScriptListingResponder(                             cnt_main_mc.cnt_view_mc.scripttree_tree));   workingAlert( );   // Display a "...working" box } // Upon initialization, get all scripts for tree // Tree control named scripttree_tree // Call the remote service to display scripts getAllScripts( ); // Responder object to populate tree with script names and IDs function ScriptListingResponder (theTree, rootNode) {   // Serves double duty for searches and all scripts.   // Pass in the tree reference and a string containing the rootNode text.   if (rootNode == undefined  rootNode == "") rootNode = "All Scripts";   // Set a root node and open it   var myRootNode = new FTreeNode(rootNode).setIsOpen(true);   theTree.setRootNode(myRootNode);   // Responder   onResult(   ) method lists the scripts and removes the "working" box.   // The   listScripts( )   function is defined within this object.   this.onResult = function (result_rs) {     listScripts(result_rs, theTree);     theTree.refresh( );     workingBox_mc.removeMovieClip( );   };   this.onStatus = function (error) {     errorHandler(error.description)   };   function listScripts (my_rs, theTree) {     // Populate the tree     // Set up a nested repeat using   CategoryID   .     // CategoryIDs are in order, so when it changes, start a new node.     var CatID = "";     var catNode;     var rootNode = theTree.getRootNode( );     for (var i=0; i < my_rs.getLength( ); i++) {       if (my_rs.getItemAt(i).CategoryID != CatID) {         catNode = new FTreeNode(my_rs.getItemAt(i).CategoryDesc,         my_rs.getItemAt(i).CategoryID);         rootNode.addNode(catNode);       }       catNode.addNode(new FTreeNode(my_rs.getItemAt(i).ScriptName,         my_rs.getItemAt(i).ScriptID));       CatID = my_rs.getItemAt(i).CategoryID;     }   } } // Set up change handler for the Tree component cnt_main_mc.cnt_view_mc.scripttree_tree.setChangeHandler("displayScript", _root); // Display the script in the interface function displayScript (tree) {   var theNode = tree.getSelectedNode( );   var theScriptId = theNode.data;   _global.currentScript = theScriptId;   if (!theNode.isBranch( )) {     if (findItem(scriptCache, theScriptId)) {       displayIt(theScriptId, cnt_main_mc.cnt_view_mc);     } else {       putScriptInCacheAndDisplayIt(theScriptId, cnt_main_mc.cnt_view_mc);     }   } else {     _global.currentScript = 0; // no current script     // Display the dummy script     displayIt(0, cnt_main_mc.cnt_view_mc);   } } // Set up change handler for the   scriptname_cb   in //   cnt_main_mc.cnt_modify_mc   (Modify screen) cnt_main_mc.cnt_modify_mc.scriptname_cb.setChangeHandler(                                           "displayScriptUpdate", _root); // Display the script in the interface function displayScriptUpdate (cb) {   var theScript = cb.getSelectedItem( );   var theScriptId = theScript.data;   _global.currentScript = theScriptId;   if (findItem(scriptCache, theScriptId)) {     displayIt(theScriptId, cnt_main_mc.cnt_modify_mc);   } else {     putScriptInCacheAndDisplayIt(theScriptId, cnt_main_mc.cnt_modify_mc);   } } // Display routines for scripts // Scripts are cached the first time they are accessed from the remote DB // Cache the script first, then display it function putScriptInCacheAndDisplayIt (theScriptId, theMovieClip, refreshTree) {   var temp = new ScriptObject(theScriptId);   // Script is not cached, so get it from the remote service   ScriptService.getScript(        new ScriptDisplayResponder(theMovieClip,refreshTree), temp); } // Default responder for the remote method   getScript( )   . // The movie clip is passed to the object so that // the display will work in View and Modify screens function ScriptDisplayResponder (theMovieClip, refreshTree) {   this.onResult = function (result) {     // Get the script from the remote method     scriptCache[result.ScriptID] = result;     // Have to display from the responder function     displayIt(result.ScriptId, theMovieClip);     if (refreshTree)       setTheScriptNode(cnt_main_mc.cnt_view_mc.scripttree_tree, result, true);   }; } function displayIt (theScriptId, theMovieClip) {   // Set text fields   theMovieClip.scriptname_txt.text = scriptCache[theScriptId].ScriptName;   theMovieClip.scriptdesc_txt.text = scriptCache[theScriptId].ScriptDescription;   theMovieClip.scriptcode_txt.text = scriptCache[theScriptId].ScriptCode;   theMovieClip.scriptdateuploaded_txt.text =                                      scriptCache[theScriptId].DateUploaded;   theMovieClip.scriptdatemodified_txt.text =                                      scriptCache[theScriptId].DateModified;   theMovieClip.scriptversion_txt.text  =                 scriptCache[theScriptId].VersionMajor + '.' +                 scriptCache[theScriptId].VersionMinor + '.' +                 scriptCache[theScriptId].VersionMicro;   // Pick values in combo boxes   theMovieClip.scriptlanguage_cb.pickValue(scriptCache[theScriptId].LanguageID);   theMovieClip.scriptcategory_cb.pickValue(scriptCache[theScriptId].CategoryID);   theMovieClip.scriptuser_cb.pickValue(scriptCache[theScriptId].UserID);   return; } // Refresh the script tree function scriptRepositoryRefresh (ScriptObj, exists) {   scriptCache[ScriptObj.ScriptID] = ScriptObj;   setTheScriptNode(cnt_main_mc.cnt_view_mc.scripttree_tree, ScriptObj, exists);   displayScript(cnt_main_mc.cnt_view_mc.scripttree_tree); } // Open a specific node of the tree. // If the node does not exist in the tree, add it. function setTheScriptNode (theTree, theScript, exists) {   var theParent = theTree.getRootNode( );   var theCategories = theParent.getChildNodes( );   var theParentNodeId = theScript.CategoryID;   var theChildNodeId = theScript.ScriptID;   for (var i=0; i < theCategories.length; i++) {     if (theCategories[i].getData( ) == theParentNodeId) break;   }   if (!exists) {   // New script -- add the node to the main display tree     theCategories[i].addNode(new FTreeNode(theScript.ScriptName,                                             theScript.ScriptID));   }   theCategories[i].setIsOpen(true);   theTree.refresh( );   var theNodes = theCategories[i].getChildNodes( );   for (var j=0; j < theNodes.length; j++) {     if (theNodes[j].getData( ) == theChildNodeId) break;   }   theTree.setSelectedNode(theNodes[j]); } // findItem:  method for the cache array to find an //            item with a   ScriptID   that matches function findItem (theArray, theItem) {   for (var i in theArray) {     if (theArray[i].ScriptID == theItem) {       return true;     }   }   return false; } 

The script is commented inline, but a few points are worth mentioning. A scriptCache property, which contains a generic Object instance, is set up as a cache for all displayed scripts. When a user clicks an item in the tree, the remote method is called and returns a ScriptObject . If the user clicks off the tree item and then back on again, further calls to the remote method are unnecessary, because the ScriptObject is stored in the cache. The zeroth element of the object become the descriptive label whenever a user has clicked on a folder in the tree; no script is shown. The code is written to be self-documenting (if it finds the items in the cache, it displays it; otherwise , it both adds it to the cache and displays it):

 if (findItem(scriptCache, theScriptId)) {   displayIt(theScriptId, cnt_main_mc.cnt_modify_mc); } else {   putScriptInCacheAndDisplayIt(theScriptId, cnt_main_mc.cnt_modify_mc); } 

The findItem( ) method is also declared in the ScriptRepository.as file. This function finds a script in the scriptCache object by iterating through the objects that are part of the scriptCache and comparing the theItem parameter (the second argument to the function) to the ScriptID property of each scriptCache element.

The searchScripts( ) and getAllScripts( ) methods utilize the same responder object. If searching for a phrase, the phrase is shown in the root of the tree ("scripts containing..."). If the user clicks the Show All Scripts button, "All Scripts" will be shown as the root node of the tree.

The displayIt( ) function also serves double duty: the displayScript( ) and displayScriptUpdate( ) methods use it to refresh the main script and update script movie clips, respectively. User interface elements share the same names on the different movie clips, so we are able to reference them by passing the movie clip name to the function and using it as a prefix.

14.5.4 UserObject.as

The UserObject , like the ScriptObject , is a class definition that defines a class of an object that we will pass back and forth between client and server.

The complete UserObject class is shown in Example 14-10.

Example 14-10. The UserObject class definition
 /* UserObject    Properties:      UserID        numeric      Username      string      userpassword  string      FirstName     string      LastName      string      Emailaddress  string      HintQuestion  string      HintAnswer    string    Methods:      init       initialize the object      toString      loginUser      addUser      emailPassword      _copyProperties      onResult      onStatus */ function UserObject ( ) {   if (!this.inited)     this.init(arguments); } UserObject.prototype.init = function (args) {   this.inited = true; // Instance is initialized   this.UserID          = (args[0] != undefined) ? args[0] : "";   this.Username        = (args[1] != undefined) ? args[1] : "";   this.Userpassword    = (args[2] != undefined) ? args[2] : "";   this.FirstName       = (args[3] != undefined) ? args[3] : "";   this.LastName        = (args[4] != undefined) ? args[4] : "";   this.Emailaddress    = (args[5] != undefined) ? args[5] : "";   this.HintQuestion    = (args[6] != undefined) ? args[6] : "";   this.HintAnswer      = (args[7] != undefined) ? args[7] : "";   this.PasswordConfirm = (args[8] != undefined) ? args[8] : "";   this.isUserLogged    = (args[9] != undefined) ? args[9] : false; }; // Register the object for use by Flash Remoting remote methods Object.registerClass("UserObject", UserObject); // Define a   toString( )   function for reading the object UserObject.prototype.toString = function ( ) {   var temp = "inited:  "       +  this.inited + '\n';   temp += "UserID:  "          +  this.UserID + '\n';   temp += "Username:  "        +  this.Username + '\n';   temp += "Userpassword:  "    +  this.Userpassword + '\n';   temp += "FirstName:  "       +  this.FirstName + '\n';   temp += "LastName:  "        +  this.LastName + '\n';   temp += "Emailaddress:  "    +  this.Emailaddress + '\n';   temp += "HintQuestion:  "    +  this.HintQuestion + '\n';   temp += "HintAnswer:  "      +  this.HintAnswer + '\n';   temp += "PasswordConfirm:  " +  this.PasswordConfirm;   temp += "isUserLogged:  "    +  this.isUserLogged;   return temp; }; // Call the remote   loginUser( )   service UserObject.prototype.loginUser = function (service) {   service.loginUser(this, this); }; // Call the remote   addUser( )   service  UserObject.prototype.addUser = function (service) {   if (this.Userpassword != this.PasswordConfirm) {     errorHandler("Passwords don't match");   this.isUserLogged = false;   }   service.addUser(this, this); }; // Get scripts for user UserObject.prototype.getScriptsForUser = function (service, box) {   service.getScriptsForUser(new ComboBoxResponder(box), this); }; // Debugging function to let us know that the object returned from  // the server was registered properly. In responder function, do: //   result.test( )   UserObject.prototype.test = function ( ) {   trace("UserObject successful") }; // Copy properties from an object to   this   UserObject.prototype._copyProperties = function (from) {   for (var prop in from) {     if (this[prop] != from[prop]) this[prop] = from[prop];   } }; // Responder method UserObject.prototype.onResult = function (result) {   if (result.isUserLogged == true) {     // Put all properties from the result into the instance of the   UserObject   this._copyProperties(result);     alertBox("userAlert", "Welcome " + result.FirstName);   } else {     trace("fail");   } }; // Call a global error handler for the movie UserObject.prototype.onStatus = function (error) {   errorHandler(error.description); }; // Validation function for properties of the   UserObject   UserObject.prototype.validate = function ( ) {   var errorMsg = "";   if (this.Username == "")     errorMsg += "Username must not be empty\n";   if (this.Userpassword == "") errorMsg += "Password must not be empty\n";   if (this.FirstName == "")    errorMsg += "First name must not be empty\n";   if (this.LastName == "")     errorMsg += "Last name must not be empty\n";   if (!isValidEmail(this.Emailaddress))       errorMsg += "Email address must be valid\n";   if (this.HintQuestion == "") errorMsg += "Hint question must not be empty\n";   if (this.HintAnswer == "")   errorMsg += "Hint answer must not be empty\n";   if (this.PasswordConfirm == "")       errorMsg += "Password confirmation must not be empty\n";   return errorMsg; }; 

You can see that the UserObject is constructed in a fashion similar to the ScriptObject . The object contains an init( ) method to allow objects returned from the server to retain their properties, a _copyProperties( ) method to copy the properties from the object returned from the server to the current instance that called the remote service, toString( ) and test( ) methods for debugging, and onResult( ) and onStatus( ) methods that give it the ability to act as a responder object. The validate( ) method of the UserObject works like its counterpart in the ScriptObject , with the exception that it calls a named function, isValidEmail( ) , to validate an email address.

14.5.5 SiteUtilityFunctions.as

The calls to the site services are implemented as a set of named functions in the SiteUtilityFunctions.as file. The complete ActionScript code is shown in Example 14-11.

Example 14-11. The site utility functions
 // General responder object for methods that return nothing: //   onResult( )   method displays a message in an alert box //   onStatus( )   method simply calls the error handler function GeneralResponder (theName, theMessage, callbackFunction) {   this.onResult = function (result) {     alertBox(theName, theMessage, callbackFunction);   };   this.onStatus = function (error) {     errorHandler(error.description);   }; } // Contact form event --   contactForm( )   calls remote method   contactForm( )   function contactForm (from, userid, message) {   if (message.length == 0) {     errorHandler("Must enter a message");     return;   }   // Call the remote service   SiteService.contactForm(     new GeneralResponder("contactAlert",     "Email was sent: Thank you for contacting us"),     from,     // Email from field     userid,   // User ID     message   // Message to send   );   workingAlert( );   // Display a "...working" box   return; } // Send Page event --   sendPage( )   calls remote method   sendPage( )   function sendPage (scriptid, to, from) {   // Call the remote service   SiteService.sendPage(     new GeneralResponder("sendpageAlert", "Email was sent", setSendPageText),        // Responder function fires alert       scriptid,    // Script ID       to,          // Email to field       from         // Email from field     );   workingAlert( );  // Display a "...working" box   return; } // Callback function to reset the text for the "send page to a friend" text field function setSendPageText ( ) {   cnt_main_mc.cnt_view_mc.sendto_txt.doDefault( ); } // Set up About box on load SiteService.about(new AboutResponder( )); function AboutResponder ( ) {   this.onResult = function (result_rs) {     cnt_main_mc.cnt_about_mc.aboutname_txt.text =        result_rs.getItemAt(0).CompanyName;     cnt_main_mc.cnt_about_mc.aboutdesc_txt.text =        result_rs.getItemAt(0).Description;     // Set up the default download link for scripts stored in the remote database     _global.downloadLink = result_rs.getItemAt(0).DownloadLink;   };   this.onStatus = function (error) {     errorHandler(error.description);   }; } // Specialized responder object for retrieving the hint question var GetEmailResponder = new Object( ); GetEmailResponder.onResult = function (result) {   if (_global.currentUser.HintQuestion == "")     _global.currentUser.HintQuestion = result;   retrieveBox("getHintAnswerBox", "Your hint Question", result,               "Your answer", getAnswer); }; GetEmailResponder.onStatus = function (error) {   errorHandler(error.description); }; // Callback function for   getEmail( )   function getQuestion (theField) {   _global.currentUser.Emailaddress = theField;   UserService.getEmail(GetEmailResponder, theField);   workingAlert( );    // Display a "...working" box } // Callback function for   emailPassword( )   function getAnswer (theField) {   var temp = SharedObject.getLocal("tries");   if (temp.data.tries < temp.data.triesLimit &&       temp.date.datetime < new Date( ).getMilliseconds( ) + temp.data.timeLimit) {     UserService.emailPassword(EmailPasswordResponder,                   _global.currentUser.Emailaddress,                   theField);     workingBox( );    // show the "...working" message   } else {     alertBox("badRetrieve",       "You've tried more than " + temp.data.triesLimit + " times within " +       temp.data.hours + " hours.\n" +       "Try again later or contact the site administrator.");   } } // Specialized responder object for   emailPassword( )   var EmailPasswordResponder = new Object( ); EmailPasswordResponder.onResult = function (result) {   if (result == true) {     alertBox("goodRetrieveBox", "Your password has been sent");   } else {     retrieveBox("tryAgainBox", "Wrong answer. Try Again: ",       _global.currentUser.HintQuestion, "", getAnswer);     var temp = SharedObject.getLocal("tries");     temp.data.tries ++;   } }; EmailPasswordResponder.onStatus = function (error) {   errorHandler(error.description); }; // Call the remote service   emailPassword( ).   // This function interacts with alert boxes and message boxes in the main movie. function emailPassword ( ) {   // Set up limits for how many tries we will allow and store in   SharedObject   var hours = 24;   var timeLimit = hours * 60 * 60 * 1000; // 1 day in milliseconds   var triesLimit = 5;                //   triesLimit   within the   timeLimit   specified   var temp = SharedObject.getLocal("tries");   // Set up the   SharedObject   if it hasn't been set up yet   if (!temp.data.tries) {     temp.data.tries      = 0;     temp.data.triesLimit = triesLimit;     temp.data.datetime   = new Date( );     temp.data.timeLimit  = timeLimit;     temp.data.hours      = hours;   }   // Step 1: Get email address   retrieveBox("getEmail", "Enter your email address", "", "",getQuestion); } // Validate an email address (simple client-side validation): returns true or false function isValidEmail (theString) {   var isValid = (    (theString.lastIndexOf('.') < theString.length - 2) &&  // must have dot    (theString.indexOf('@') != -1) &&                       // must have @    (theString.indexOf('@') == theString.lastIndexOf('@'))  // must not have two @@    )   return isValid; } // Put a   Date   object into human-readable date format (US format) function doDateFormat (dateObj_date) {   var d = dateObj_date.getDay( );   var m = dateObj_date.getMonth( );   var y = dateObj_date.getFullYear( );   var h = dateObj_date.getHours( );   var mn = dateObj_date.getMinutes( );   mn = (mn < 10) ? '0' + mn : mn;   var s = dateObj_date.getSeconds( );   s = (s < 10) ? '0' + s : s;   return m + '/' + d + '/' + y + ' ' + h + ':' + mn + ':' + s; } 

The SiteUtilityFunctions.as file takes care of calls to contactForm( ) , sendPage( ) , and about( ) in the SiteService remote service. It also takes care of the email password functionality, which is one of the more complicated aspects of the application. In a typical HTML-based application, the steps can be followed like this:

  1. User clicks the "email me my password" link and a page loads in with an email address box. User fills in email address and clicks Submit.

  2. The remote service finds the user's email address in the database and returns a question. A new page loads in with a hint question and an answer box. User fills in answer and clicks Submit again.

  3. The hint answer is checked in the database and, if correct, the username and password are mailed to the user. A new page loads, telling the user that the password has been mailed.

In the Flash Remoting application, we can't implement functionality that follows steps like this, but that is not a bad thing ”it gives the application more of an immediate feel. Each call to the remote service is going to be handled by a responder object, but how do you call three remote services in a row that depend on the response from the previous call? You can't call them like this:

 getQuestion(emailAddress); getAnswer(hintAnswer); emailPassword( ); 

If you were to execute this code, you would have an error because the three functions would fire immediately, even before the response was returned from the first function call.

Instead, we've created specialized responder objects that take care of calling the next remote method. It works like this:

  1. The user clicks the email password link and the emailPassword( ) function fires, displaying the dialog box that prompts the user for an email address.

  2. The prompt box uses a callback function, getQuestion( ) , which calls the remote method getEmail( ) using the GetEmailResponder object.

  3. The "...working" dialog box pops up while the question is being retrieved. Within the GetEmailResponder.onResult( ) method, the hint question is shown in a second prompt box.

  4. The prompt box ( retrieveBox( ) ) function takes a callback function ( getAnswer( ) ) as an argument. This way, when the user clicks OK, we can call another remote method: emailPassword( ) .

  5. If the hint answer matches the answer in the database, the username and password are mailed to the user. If not, getAnswer( ) is called again, but a counter limits the number of attempts (for security reasons). After five unsuccessful attempts, the user is locked out for 24 hours. The number of tries and the length of lockout are variables that you can change.

Finally, the SiteUtilityFunctions.as file contains a few utility functions for email validation and date formatting.

14.5.6 UI.as

The UI.as file contains several additions to the built-in objects and components of Flash MX, and a responder object to simplify populating a combo box with a recordset result. The complete code for UI.as is shown in Example 14-12.

Example 14-12. The UI.as file contains code for GUI elements
 // Set up a responder object to handle recordsets for ComboBoxes. // This responder assumes that data is coming in with ID column  // in [0] position and description column in the [1] position. //   cbName   is the fully-qualified name of the ComboBox. //   zeroElement   is an optional argument that contains a zeroeth element // of a descriptive label, like "--Categories--" function ComboBoxResponder (cbName, zeroElement) {   this.onResult = function (result_rs) {     var fields = result_rs.getColumnNames( );     // If there is a descriptive text to put in the Combo box     // put it in the 0 position of the recordset.     if (zeroElement != null) {       var temp = {};       result_rs.addItemAt(0, temp);       result_rs.setField(0,fields[0], 0);       result_rs.setField(0,fields[1],zeroElement);     }     var idField = '#' + fields[0] + '#';     var descField = '#' + fields[1] + '#';     DataGlue.bindFormatStrings(cbName, result_rs, descField, idField);   };   this.onStatus = errorHandler; } // Call the remote service to get all script IDs and names for scripts // created by the current user. function getUserScripts ( ) {   ScriptService.getScriptsForUser(     new ComboBoxResponder(     cnt_main_mc.cnt_modify_mc.scriptname_cb, "-Scripts-"),     _global.currentUser.username,     _global.currentUser.password   ) } //   pickValue( )   : New method for ComboBoxes to be able to pick a value. FComboBoxClass.prototype.pickValue = function (value) {   for (var i=0; i<this.getLength( ); i++) {     if (this.getItemAt(i).data == value) {       this.setSelectedIndex(i);       break;     }   } }; //   setAutoBlank( )   : New method for the   TextField   object. // Set the field to blank when cursor is placed in field. // NOTE: if passing   false   to the function to turn feature off, // need to redefine any   onSetFocus( )   functionality. TextField.prototype.setAutoBlank = function (value) {   if (value) {     this.onSetFocus = function ( ) {this.text = "";}   } else {     this.onSetFocus = null;   } }; //   defaultText   : Allow for default text to be placed in a text field. TextField.prototype.defaultText = null; TextField.prototype.setDefaultText = function (value) {   this.defaultText = value; }; TextField.prototype.getDefaultText = function ( ) {   return this.defaultText; }; TextField.prototype.addProperty("defaultText",                                 this.getDefaultText,                                 this.setDefaultText); //   doDefault( )   : Set the field text to   defaultText   . TextField.prototype.doDefault = function ( ) {   this.text = this.defaultText; }; 

The ComboBoxResponder object is used by all combo boxes in the movie that are fed by remote recordsets. The recordsets are assumed to contain a number field and a description field. There are four combo boxes that use this responder object.

The pickValue( ) method is added to the FComboBox class to add the functionality to all combo boxes in the movie. With this method, you can now pass a number to the combo box to have that particular record shown. For example, if you have a list of six categories in the Categories_cb combo box and you want the fourth item, you bring it into focus like this:

 Categories_cb.pickValue(3); 

There are two additions to the TextField class as well. We've added an autoblank feature, which allows you to create a TextField that automatically becomes blank when you place your cursor in it. Turn on this functionality like this:

 myTextfield.setAutoBlank(true); 

We've also added a defaultText property to the TextField class. This property stores the default text for that particular field. Restore the default text for the text field using the custom doDefault( ) method:

 myTextField.doDefault( ); 

14.5.7 Flash User Interface Code

Many of the remote methods are called from the Flash interface. The ActionScript code for the interface is fairly elaborate and too long to reprint here in full, but a few of the key ActionScript snippets should be explained. (The full version can be downloaded from the online Code Depot.)

There are two custom message boxes that are built from movie clips rather than components, because one of the Macromedia components that would have been necessary is a commercial component (the Advanced Message Box). The message boxes are both set up to accept a callback function, which would be fired upon the user clicking the OK button. The alertBox( ) function is shown in Example 14-13.

Example 14-13. Custom alert box movie clip is used extensively in the movie
 // Display Alert Box // Arguments: //   theName:    name for the box //   theMessage: text message to display //   callback:   callback function when OK is clicked function alertBox (theName, theMessage, callbackFunction, hideOK) {   if (workingBox_mc)             // If there is a "...working" box, remove it     workingBox_mc.removeMovieClip( );   _root.attachMovie("alertbox_mc", theName, 1);   var thisBox = _root[theName];   thisBox._x = (Stage.width - thisBox._width)/2;   thisBox._y = (Stage.height - thisBox._height)/2;   thisBox.message_txt.text = theMessage;   if (!hideOK) {     // ok button     thisBox.ok_btn.onRollOver = overState;     thisBox.ok_btn.onRollOut = outState;     thisBox.ok_btn.onPress = function ( ) {       thisBox.onUnload = callbackFunction;       thisBox.removeMovieClip( );     };   } else {     thisBox.ok_btn._visible = false;   } }; 

The workingAlert( ) function also shares this alertBox( ) function and displays a "...working" message to the user. This is used by many remote methods in the application. The retrieveBox( ) function displays a similar box, but it allows for user input, as shown in Figure 14-4.

Figure 14-4. The retrieveBox( ) function calls a custom movie clip to retrieve information
figs/frdg_1404.gif

Remote methods are called from the onRelease events of the buttons in the interface. Example 14-14 shows the code for the Upload Script button ( scriptupload_btn ).

Example 14-14. The Upload Script button calls
 //   scriptupload_btn   cnt_main_mc.cnt_upload_mc.scriptupload_btn.onRollOver = overState; cnt_main_mc.cnt_upload_mc.scriptupload_btn.onRollOut = outState; cnt_main_mc.cnt_upload_mc.scriptupload_btn.onRelease = function (mc) {   var tempScript = new ScriptObject(null,               cnt_main_mc.cnt_upload_mc.scriptname_txt.text,               cnt_main_mc.cnt_upload_mc.scriptdesc_txt.text,               cnt_main_mc.cnt_upload_mc.scriptcode_txt.text,               cnt_main_mc.cnt_upload_mc.scriptlanguage_cb.getSelectedItem( ).data,               cnt_main_mc.cnt_upload_mc.scriptcategory_cb.getSelectedItem( ).data,               _global.currentUser.UserID,               cnt_main_mc.cnt_upload_mc.scriptdateuploaded_txt.text,               cnt_main_mc.cnt_upload_mc.scriptdatemodified_txt.text,               1,               0,               0);   // Make sure the script is filled in   var errorMessage = tempScript.validate( );   if (errorMessage == "") {     tempScript.addScript(ScriptService);     workingAlert( );     mainScreen( );   } else {     alertBox("validationError",errorMessage);   } }; 

In the scriptupload_btn button's onRelease( ) event handler, a temporary ScriptObject is created using the text from the interface elements as the arguments to create the object. The tempScript variable contains the new ScriptObject , and the remote addScript( ) method is called through this object. The "working" alert box is shown until it is removed by the appropriate responder function.

The "send this page to a friend" functionality is made possible by the use of the FlashVars attribute in the <object> and <embed> tags on the ColdFusion page that houses the movie. If an id variable is passed to the page, Flash will pick up the variable and execute the following code:

 if (scriptid != null && scriptid != "" && scriptid != "undefined") {     putScriptInCacheAndDisplayIt(scriptid, cnt_main_mc.cnt_view_mc, true); } 

We simply pass to the putScriptInCacheAndDisplayIt( ) function the scriptid variable, the main display movie clip, and the value true to signal a refresh of the Tree component.

The HTML and ColdFusion code required for this functionality is shown in Example 14-15. The ColdFusion logic is highlighted in bold. Similar functionality can be created in PHP, ASP.NET, or Java pages as well.

Example 14-15. HTML and ColdFusion code to pass URL variables
 <OBJECT classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/ swflash.cab#version=6,0,65,0"  WIDTH="100%" HEIGHT="100%" ALIGN="">  <cfif isdefined("url.scriptid")><param name="flashvars"    value="scriptid=<cfoutput>#url.scriptid#</cfoutput>"></cfif>  <PARAM NAME=movie VALUE="ScriptRepository.swf"> <PARAM NAME=quality VALUE=high> <PARAM NAME=bgcolor VALUE=#D9EFB4> <EMBED src="ScriptRepository.swf" WIDTH="100%" HEIGHT="100%" ALIGN="" quality=high bgcolor=#D9EFB4 TYPE="application/x-shockwave-flash"  PLUGINSPAGE="http://www.macromedia.com/go/getflashplayer"  <cfif isdefined("url.scriptid")> flashvars="scriptid=<cfoutput>#url.scriptid#</cfoutput>"</cfif> ></EMBED>  </OBJECT> 

You can also simply append variables to the end of the URL, but this technique is known to be buggy in several versions of the Player. Using FlashVars is a better approach when you can control the output of the HTML tags with server-side logic.



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