Fortunately, the TextTool user interface is a much more straightforward than the Java back-end. Flash players have been around for a long time, and my experience has been that the ports from one platform to another are relatively trouble free and surprisingly consistent. Additionally, having access to the advanced functionality of Flash 5 makes life much easier because we can leverage the elegance of true object-oriented programming.

TextTool is a simple application for entering, saving, recalling, and deleting short notes on any device that supports both Java and Flash. The entire interface consists primarily of a main area in which to enter text, three command buttons (Save, Load, and Delete), and three floating prompt boxes that expect input (filenames) from users. If you open the Flash file and look at the main timeline, you will find that the top layer (labeled "control") contains or includes most of the UI's ActionScript while the layers below it contain various graphical elements. You will also notice that everything exists on the first frame of the timeline (even all the movieclips use nothing but their first frames); because the application functions solely through ActionScript responding to user input, you can see that Flash 5 is as much a programming environment as it is an animation tool.

Figure 9.7. TextTools Flash user interface under development in the Flash authoring environment.


Let's start by looking at the UI's initialization code, which is any code in "control" that is not inside of a function.

 #include "ServerConfig.as"  #include "SaveFile.as"  #include "ReadFile.as"  #include "RemoveFile.as"  stop();  // Create a ServerConfig for everyone to share.  var config = new ServerConfig("","2000");  // Instantiate all our services. We can reuse the same instances.  var save = new SaveFile(config);  var read = new ReadFile(config);  var remove = new RemoveFile(config);  // Create a new listener. Each service will override the setResponse before  // passing it in.  var listener = new Object();  // Hide all the dialogue boxes.  hideAll(); 

Interestingly enough, the first thing TextTool does after you start it (other than include external ActionScript files) is to call the stop() function because the application's job is essentially to allow the user to enter as much text as he likes while waiting for him to tap a button. Once an event is detected, the appropriate prompt box becomes visible while any others, which might be visible, are hidden. The file name entered by the user is then used to either save text, load previously saved text, or delete previously save text. Each action is initiated through a call to a corresponding function in "control".

In the true spirit of object-orientated design, the UI configuration is encapsulated in a single ActionScript object called ServerConfig. Unlike its PocketConfig counterpart, however, its configuration is not loaded from an external file because the ServerConfig contains only two properties: server and port. Because the PocketServer is designed to run locally, the server needs to be (localhost) and the port can be whichever one you configured your PocketServer to listen on.


If it weren't for the fact that I don't want to unnecessarily complicate this example, I would probably load the configuration from an external XML file despite the fact that, at least initially, it would contain only two parameters. Externalizing configuration is almost always good application practice regardless of the size of your project because small applications often have a tendency to grow into much more involved ones.

The next piece of code that is executed in "control" loads and initializes the three objects that are the workhorses of the UI: SaveFile, ReadFile, and RemoveFile. Notice how they are loaded once at initialization time and then reused in much the same way as the PocketServices were loaded once and cached by the PocketServer. Even a generic object called "listener" is reused for maximum efficiency (more on the listener later). Finally, we call the hideAll() function that sets the visible property of all three prompt boxes to false until such time as they are needed.

Rather than taking you through the UI code one line at a time, I'm going to proceed by describing the object model I chose and how it relates to the PocketServer's object model. I will then take you through a single process (saving a file) in its entirely to demonstrate how all the various pieces fit together.

Because the PocketServer is modeled as a server, the processes that the UI uses to perform tasks against it are modeled as requests. In fact, as there are three primary actions the UI is capable of performing, there are precisely three different types of request objects to ask the PocketServer to perform them. Each request SaveFile, ReadFile, and RemoveFile inherits functionality from a base object called PocketServiceRequest. There is obviously a direct correlation between the three PocketServices on the back-end and the three PocketServiceRequests implemented in the UI.

Just as SaveFile, ReadFile, and RemoveFile inherit functionality from the base class PocketServiceRequest, PocketServiceRequest itself inherits from the native Flash 5 XML object. The XML object has the capability to open, read from, and write to sockets from the client (browser), which is how the communication occurs between the UI and the back-end. PocketServiceRequest also provides additional services too, such as functions to initialize and format data.


The Flash 5 XML ActionScript object can only open connections back to the server from which it was served if the Flash file it is in was initially retrieved through a URL (see Figure 9.8). Because the UI Flash file is loaded from the local file system in this case, you will not have to worry about such security restrictions.

Figure 9.8. The Flash 5 XML ActionScript object.


Understanding the SaveFile Object

To really give you a sound understanding of the architecture of the UI, let's follow the SaveFile PocketServiceRequest through from its initialization to the completion of a request. Initialization occurs with the following line of ActionScript from the top layer labeled "control":

 var save = new SaveFile(config); 


A constructor is a function that automatically gets called wherever a new instance of an object is created. The function must have the same name as the object itself (in this case, SaveFile) and, as in the case of SaveFile, can take in arguments.

SaveFile's constructor contains the following code:

 this.super(serverConfig.getServer(), serverConfig.getPort());  this.setService("SaveFile"); 

The first line initializes the superclass PocketServiceRequest, passing it the server and port number from the ServerConfig. The second line tells the superclass which PocketService it is responsible for requesting from the PocketServer. From this point on, nothing happens until the Save button gets tapped, at which point the ActionScript associated with that button gets executed (see Figure 9.8):

 on (release) {    if (loadDialogue._visible = true) {        loadDialogue._visible = false;    }    if (deleteDialogue._visible = true) {        deleteDialogue._visible = false;    }    saveDialogue._visible = true;  } 

After making sure both the Load and Delete prompt boxes are hidden, the _visible property of the Save box is set to true, causing it to show. The user enters a filename and taps on the OK button, which does nothing but call the executeSave() function back in "control".

 function executeSave(){     var mainText = textArea.mainText;     var fileName = saveDialogue.genericDialogue.fileName;     if (!isValid(mainText) || !isValid(fileName)) {         return;     }           save.clearParameters();           save.setFileName(fileName);           save.setFileContent(mainText);           save.append("false");     listener.setResponse = saveResponse;     save.execute(listener);  } 

The first thing executeSave() does is copy the values of both the file prompt box and the main text area into local variables. It then uses a simple function called isValid() to determine whether the user entered useful data. If not, execution is halted by immediately returning; however, as long as at least one character was specified for each input, execution continues.


My implementation of isValid simply determines whether the filename is null or an empty string; however, you can provide a more robust, sophisticated implementation at your digression. The key is to encapsulate that logic in its own function and to reuse that function in as many places as possible so that changing it in one place changes the behavior of the every piece of code using it.

Because the SaveFile object is stateful and gets reused over and over, it is important that the clearParameters() method is used to reset any values that have been previously set so that we don't accidentally save old data or, more likely, some combination of the old data and new. We then configure the SaveFile object by telling it the name of the file to save, the content to save, and whether to append the new content to the old file (if a file by that name already exists) or overwrite it instead.


From the perspective of the back-end, it may seem pointless to even have the ability to append data to a file rather than overwrite it if the append property is going to be hard-coded to false in the client. Although TextTool does not really have a need to append text to files, when I wrote the SaveFile PocketService, I did so with the idea of keeping it generic. In other words, there is no reason why PocketServer cannot service requests from multiple clients, each with its own set of functionality and requirements, one of which possibly being to append text to a file rather than overwrite it.

Before we can call execute() on the SaveFile object, we have to prepare the listener we use to alert us to when SaveFile has either finished or quit because of an error. Using the callback approach as opposed to simply having the execute() function return an exit status, response code, Boolean, or error message is necessary because the SaveFile PocketServiceRequest behaves asynchronously. This means that the execute() function will return almost immediately rather than blocking until all its work has completed. I will explain asynchronous execution in more detail shortly.

We specify saveResponse() as our callback method by assigning it to setResponse() on the listener before passing the listener in to the execute() function. Essentially, we have told SaveFile to tell us that it is done by calling setResponse() on the listener, which points to the saveResponse() method contained in "control".

SaveFile.as is a relatively short and simple file because the majority of its functionality is contained in the PocketServiceRequest super class (several short files as opposed to fewer longer files are generally an indication of good object-oriented design). In fact, the execute() function is even contained in the PocketServiceRequest so that all three PocketServiceRequests don't have to implement their own.


It's important to remember that when we talk about sharing code in the context of object-oriented programming, we almost always mean sharing the source code as opposed to sharing the same instances of objects. In other words, although SaveFile, ReadFile, and RemoveFile all extend PocketServiceRequest and all use the execute() function contained therein, they are not calling the same instance of the execute() function, so there is no chance of concurrency issues.

 function execute(listener) {     this.listener = listener;     var req = "http://" +            this.server + ":" +            this.port + "/" +            this.service +            this.getQueryParameters();     this.load(req);  } 

execute() builds a URL and a query string that will be used to make a request to the PocketServer. Notice how it is kept generic (and hence reusable) by not having the service name or any portion of the query string hard-coded. Rather, it uses the service that was previously set through the setService() method on SaveFile's constructor, and a method called getQueryParameters() to build the query string from the properties that were set within executeSave() . Finally, execute() calls load() , passing in the URL and query string containing the data the user entered from the UI (the request should look similar to the one you made against the PocketServer from your browser). execute() then returns immediately, not blocking while waiting for load() to return.

You may have noticed that there is actually no load() function defined in either SaveFile or PocketServiceRequest. The load() function we are actually calling belongs to the native Flash 5 XML object from which the PocketServiceRequest inherits. Although it is actually designed to load and parse XML files, we can still use it for its ability to make socket connections by overriding, or replacing, the XML specific methods with our own implementations. At the bottom of PocketServiceRequest, you'll find the following lines of code:

 PocketServiceRequest.prototype.__proto__          = XML.prototype;  PocketServiceRequest.prototype.addParameter       = addParameter;  PocketServiceRequest.prototype.setService         = setService;  PocketServiceRequest.prototype.getQueryParameters = getQueryParameters;  PocketServiceRequest.prototype.clearParameters    = clearParameters;  PocketServiceRequest.prototype.execute            = execute;  PocketServiceRequest.prototype.onData             = pr_onData; 

The first line is what establishes inheritance from the XML object. The second line through fifth lines are simply attaching various functions to the PocketServiceRequest. The last line, however, holds particular significance because onData() is not a function that I wrote. It is, rather, a function I inherited by extending XML. Because I'm primarily interested in using the XML object for its ability to make socket connections and therefore don't want it doing XML-specific processing on the response from the load() function, I replace onData() with my own custom implementation, which I call pr_onData .


I prefer the method of overriding (or replacing) the XML object's onData method to simply using loadVariables() for two reasons: First, I like the call-back paradigm the XML object uses, and second, I like that the server response is handed back simply as an argument rather than being assigned to ActionScript variables in another movieclip. From a programming perspective, it is far more intuitive and makes for cleaner code.

Flash Player calls onData() when the response from the server is finished loading. The argument that is passed to onData() is a string containing the actual response from the server. We pass the response on to the listener through the setResponse() function, and SaveFile has finished its work. The saveResponse() method back in "control" completes the process:

 function saveResponse(response) {     if (response.indexOf("r200") == 0) {        hideAll();     } else if (!isValid(response)) {         hideAll();         error(No response from server.);         return;     } else {        var msg = new String("There was an error saving your file. The server's");        msg = msg.concat("response was: \n");        msg = msg.concat(response);                 hideAll();                 error(msg);          }  } 

saveResponse() will do one of three things depending on the response from server. If it finds the substring "r200" in the response (200 being the standard response for "everything went smoothly on the server"), hideAll() is called, which will obscure the Save prompt box and let the user get back to writing. If the response from the server is empty, saveResponse() can assume that the server was, for some reason, unable to respond, so an appropriate error message is passed to the error() method, which displays the message in a bright red alert box. If the response does not contain the substring "r200" and is not empty, saveResponse() assumes that an error occurred on the server, so again, an appropriate error message is formulated and passed to the error() method; however, this time the error message in the error alert box will actually contain a Java stack trace from the server, which will help you track down the difficulty.


The r preceding the 200 (which stands for response) is there for no other reason than to compensate for an irksome bug I seem to have found in the beta version of PersonalJava. I found that if the first byte I tried to write to the socket output stream was a number (at least when wrapping the Output Stream in a PrintWriter), no data would end up getting written to the stream at all. Prepending a letter to the string ensures that all data gets written to the stream properly. Such are the joys of working with cutting-edge technologies.

Running the UI

The final step is to actually test the user interface. Running Flash on your Pocket PC device is fortunately far simpler than configuring and running Java. All you have to do is copy the appropriate files the textTool.swf and textTool.html over to your device and tap on textTool.html, which should look something like this:

 <html>  <head>  <title>textTool</title>  </head>  <body bgcolor="#ffffff">  <object class              width="220"       height="140">          <param name=movie value="textTool.swf">          <param name=quality value=high>         </object>  </body>  </html> 

Pocket Internet Explorer will automatically start loading textTool.html, which will, in turn, load textTool.swf. For testing and debugging, you may want to run textTool.swf on your PC before moving it over to your device, in which case you can use the Flash Player or the Flash plug-in with any browser that supports it. Once again, we see the tremendous flexibility and convenience platform-independence lends both Flash and Java.

Figure 9.9. AND 9.10 TextTool's Flash user interface running in the Flash Player on Windows and running inside of Pocket Internet Explorer on my iPAQ. Notice how they are essentially indistinguishable.


Macromedia Flash Enabled. Flash Design and Development for Devices
Macromedia Flash Enabled. Flash Design and Development for Devices
ISBN: 735711771
Year: 2002
Pages: 178

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