There are at least two different approaches to programming in ActionScript 1.0: object-oriented programming and procedural programming. Both have their strengths and weaknesses. You could add a third group of programmers as well: those who program procedurally and use OOP concepts in their applications. This section will show some ways of doing things with Flash Remoting using these approaches. 12.7.1 Procedural ProgrammingProcedural programming, also known as top-down programming , uses techniques that have been around since the beginning of computer programming. With procedural programming, you write code from beginning to end and call functions when they're needed. Assembly language is an example of procedural programming. There is nothing inherently wrong with procedural programming, yet it has fallen out of favor with the advent of OOP. 12.7.1.1 Task-orientedProcedural programming focuses on the tasks . Using an example of the Products database from the earlier chapters, a procedural program asks the question "what has to be done?" and then proceeds to do it. For example, the code might follow like this (in pseudocode): 1. Initialize movie 2. Call remote methods to populate UI 3. Display results 4. Wait for user input 5. If "add" is clicked, show the addProduct screen 6. If "search" is clicked, call the remote method searchProducts( ) Each section of the program ( addProduct , searchProducts( ) , etc.) would contain more code that executes sequentially, with conditional logic to branch off into other areas of the program. ActionScript 1.0 promotes the use of procedural programming by the very nature of the ECMA-262 specification. ECMA-262 is not a true object-oriented specification, but it does allow for OOP. It's a very loose language in that it does not require entry points, strict datatyping, class definitions, or even variable declaration. That does not make procedural programming bad; it just means you have to structure your code to make it modular and maintain organization as you do so. One programming flaw in a program can have consequences further down the line. Because the code is executed sequentially, each line of code depends on what comes before it.
12.7.1.2 Event-drivenFlash also operates as an event-driven application, and event-driven applications are procedurally oriented. When the movie loads, all of the code in the movie is executed (depending on the timeline, of course). Flash then waits for user input. The user input triggers events that can be trapped with event handlers. These event handlers become named functions when you're using procedural programming: myButton_pb.setClickHandler("getProducts"); function getProducts( ) { myService.getProducts( ); } When you're using procedural programming in a Flash Remoting application, it becomes even more important to keep the code structured and clean. A procedural program can quickly turn into spaghetti code if the program lacks structure and organization. That said, a procedural program can also be well- constructed and function perfectly . 12.7.1.3 Result handlers in procedural programmingWhen dealing with remote services, you have several choices in how you handle the results. The simplest and most documented way of retrieving results is to name a function using the remote method name with an appended _Result or _Status . Generally, a procedural approach would utilize this method: myService.loginUser(user_txt.text, pwd_txt.text); loginUser_Result = function (result) { if (result == true) { trace("User logged"); } else { trace("User not logged"); } }; This method is simple, direct, and effective. It is self-documenting , because the remote method name is used in the naming of the callback function. However, it does become cumbersome when dealing with many remote calls. I would not discourage someone from using it, but I would not consider it a best practice. That said, there is nothing wrong with using this technique if you feel comfortable using it. 12.7.1.4 Procedural exampleExample 12-3 is an example of a procedural program with structure. Example 12-3. A procedural approach to the HelloUser program#include "NetServices.as" // Set up variables for the URL and service paths var myURL = "http://localhost/flashservices/gateway"; var servicePath = "com.oreilly.frdg.HelloUser"; // Connection hasn't been initialized; create connection and service objects if (initialized == null) { initialized = true; NetServices.setDefaultGatewayURL(myURL); var myConnection_conn = NetServices.createGatewayConnection( ); var service = myConnection_conn.getService(servicePath, this); } // Set up the callback function to handle mouseclicks submit_pb.setClickHandler("callSayHello"); // Call the service when the user clicks the Submit button function callSayHello ( ) { var user_name = userName_txt.text; if (user_name == "") { user_name = "User"; } service.sayHello(user_name); } // Set up onResult and onStatus event handlers function sayHello_Result (myResults) { results_txt.text = myResults; } function sayHello_Status (myError) { results_txt.text = myError.description; } // Set the system status to be handled by the method status handler as well System.onStatus = sayHello_Status; The procedural style mixes the user interface logic (inside the sayHello_Result( ) function) and is executed from the top down. Events that are triggered (such as when the submit_pb button is clicked) are handled by named functions. Events returned by a remote service are handled by functions, sayHello_Result( ) and sayHello_Status( ) , that are named after the calling method. A procedural program such as this can easily grow into spaghetti code if you are not careful. Even in this simple example, the results_txt field is referenced in several places. If something were to change in the interface, you would have to find all of your user interface references and change them manually. A better option is to use a custom responder object, as discussed in Chapter 4. Some of the more flexible options are shown in Section 12.7.2. 12.7.2 Object-Oriented ProgrammingObject-oriented programming (OOP) is at the opposite end of the programming spectrum from procedural programming. In true OOP, everything is an object. Code in the program does not exist if it is not part of an object. For that reason, Flash MX is not a true OOP environment; you don't have to create objects in order for the program to operate , although inline code is technically part of the current object where the code resides. Also, some of the key principles of OOP, such as data hiding (private, protected, and public members ), are not implemented in ActionScript. Even though strict OOP is not entirely possible with Flash MX, you can get pretty darn close by simply using OOP principles in your coding style.
12.7.2.1 Everything is an objectWith OOP, you will want to create objects for everything. The application itself is an object; the user of the application is an object; every button on the screen is an object; the connection to the remote server is an object; the user's email address can be an object. An object is an instance of a class. A class is the coded blueprint for an object. Imagine the classes as rubber stamps, and the objects as the imprints you make when you use each rubber stamp. How you organize your classes and tie them together is one of the keys to understanding how OOP works. OOP works in the exact opposite way that procedural programming works. In procedural programming, you ask yourself "What has to be done?" and then you do it. In OOP, you create abstract representations of each item in your application and ask yourself "How do they communicate?" Each class is created as a black box; you know what it does, you know what it needs, and you know what it returns. You don't have to know how it works, and you can remove it and substitute another black box with the same properties, methods, and events and the program will still work. Your class encapsulates the functionality and allows other classes to interact with it. 12.7.2.2 OOP in Flash RemotingIn Flash Remoting, there are several different ways you can encapsulate the functionality in objects:
12.7.2.3 How to create your objectsThere are several ways to implement OOP in Flash. Generally, the more abstract you make your classes, the easier the classes will be to understand for other programmers. I mean abstract in the sense of "evoking something's distilled essence," not "esoteric and obtuse." You should create classes that represent something meaningful. For example, your class should not be called RemoteService with methods that merely mirror your remote methods. This is obtuse and redundant, not abstract; it merely serves as a convenient way of accessing your services. An abstract class would be called Product , User , EmailAddress , or Search . These are human-readable objects that represent something meaningful to the application. Objects are typically modeled before a line of code is written. Modeling involves identifying the objects in your application and documenting how they communicate via the properties, methods, and events of each object. Modeling can be done in many ways: using a Universal Modeling Language (UML) diagram, 3 x 5 cards (one for each object), or plotted on paper. In an OOP application, the more modeling you do in advance of coding, the easier it will be to create the objects and complete the coding successfully. In Flash Remoting, you must identify how an object will receive the remote result and how it will handle the result using an OOP mentality . 12.7.2.4 Responder objects in OOPThroughout the book, I've shown a technique that makes sense in many situations ” utilizing a custom responder object, like this: function LoginResponder( ) { this.onResult = function (result) { if (result == true) { message_mc.message_txt.text = "User logged"; } else { message_mc.message_txt.text = "User not logged"; } }; this.onStatus = function (error) { trace(error.description); }; } myService.loginUser(new LoginResponder( ), user_txt.text, pwd_txt.text); or this: function LoginResponder ( ) { } LoginResponder.prototype.onResult = function (result) { if (result == true) { message_mc.message_txt.text = "User logged"; } else { message_mc.message_txt.text = "User not logged"; } }; LoginResponder.prototype.onStatus = function (error) { trace(error.description); }; myService.loginUser(new LoginResponder( ), user_txt.text, pwd_txt.text); A better technique, however, is to use a callback function or a broadcaster within the responder object. The previous code is tied to the user interface, which is not an object-oriented approach; the user interface elements are not separate from the LoginResponder object. If you pass a callback function to the object, the LoginResponder is separate from the UI. You might start with a Responder class: function Responder ( ) {} Responder.prototype.onResult = function (results) {trace(results);}; Responder.prototype.onStatus = function (error) {trace(error.description);}; Then, create a LoginResponder class for specific functionality: // LoginResponder extends Responder #include "Responder.as" function LoginResponder (myCallback) { this.prototype = new Responder( ); this.callback = myCallback; } LoginResponder.prototype.onResult = function (result) { if (result == true) { this.callback("User logged", result); } else { this.callback("User not logged", result); } }; doMessage = function (message) { message_mc.message_txt.text = message }; The preceding LoginResponder class defines a responder object that uses the callback function passed to it. You can use it from another class designed to gather information from the UI: myUserObject = new UserObject( ); myUserObject.loginUser("doMessage", user_txt.text, pwd_txt.text); Inside the UserObject class you would have a loginUser( ) method, which would call the remote service: #include "LoginResponder.as" UserObject.prototype.loginUser = function (callback, username, password) { this.service.loginUser(new LoginResponder(callback), username, password); }; 12.7.2.5 Problems with OOPThere are a few inconsistencies with Flash Remoting when working with objects. The asynchronous nature of Flash Remoting makes it difficult to create objects that separate UI and content from your remote results. Because the results are accessed within an onResult( ) method, you might be tempted to access interface elements from within the same method. This would break the principle of encapsulation , which basically says that objects should behave as black boxes. In a properly encapsulated object, the internal workings of the object don't rely on external items such as UI elements. You can overcome the obstacle by using a broadcaster inside the onResult( ) and onStatus( ) methods, or by passing a callback method to the object, which would be called inside of onResult( ) or onStatus( ) , as we'll see shortly. Another problem involves having a service object as part of a custom object that is sent to the server in a remote method. It is a natural tendency to want to encapsulate the object to be self-sufficient and exist as a unit. One way to do that is to have your remote service as a property of the object. Unfortunately, this causes the remote call to fail due to an internal fault with Flash Remoting. The Macromedia Pet Market blueprint application (http://www.macromedia.com/devnet/mx/blueprint) suffers from this problem, but the programmers worked around the issue by copying the object properties to another object before calling the remote service. Workarounds such as these are commonplace, as Flash Remoting is still in its infancy and has a few kinks to work out. 12.7.2.6 Callback exampleThis section demonstrates an example that uses callback functions and shows how the procedural code from Example 12-3 might be implemented as an OOP application. There are a few extra steps involved in turning a simple example into a full-fledged OOP application. You'll have to start with a new movie named HelloUserOOP.fla and follow these steps (the completed file is available at the online Code Depot):
Figure 12-1. Creating a new MovieClip for HelloUser
Objects communicating: that's what OOP is all about. The user interface knows nothing of the Result class. It knows only about the User object and how to communicate with it. It depends on the User object; however, any User object that provides the same properties, methods, and events could be substituted without a problem. You'll notice that the OOP code is much wordier than the simple procedural example that does the same thing. Even so, the initial time spent modeling your application and setting up your classes is regained when you implement the application and make modifications further down the road. Modifications come easy to an OOP application. 12.7.2.7 BroadcastersA broadcaster is based on the Observer pattern, another standard design pattern in programming. A broadcaster is implemented in ActionScript using the undocumented ASBroadcaster class. With this class, you can create objects that broadcast custom events inside your movie. After an event is broadcast, a listener that is listening for that particular event will respond.
Broadcasters fit right into the Flash Remoting framework because of the asynchronous nature of the technology. When you call a remote service, you don't wait for the response. The remote service method eventually returns a result to the responder function in the Flash client. The remote service is essentially a broadcaster, and your responder object is essentially a listener. This does not provide enough flexibility in handling results, however, so it makes sense to set up a custom broadcaster to convey the remote response to the part of your Flash movie that will benefit from it. You can set up a broadcaster inside of your responder to broadcast a custom event to the movie. The advantage of this approach is that, once the event is broadcast, you can have one or more listeners acting on the remote response. To create a broadcaster, pass an instance of the generic Object class to the static ASBroadcaster.initialize( ) method: var myBroadcaster = new Object( ); ASBroadcaster.initialize(myBroadcaster); This converts myBroadcaster into an ASBroadcaster object capable of broadcasting. Specify the custom event to broadcast using the broadcastMessage( ) method: myBroadcaster.broadcastMessage("onMyCustomEvent", "Hello there"); Finally, set up a listener object to listen for the custom event. Here, we create an object, myListener , with an anonymous function assigned to the onMyCustomEvent property: myListener = { onMyCustomEvent:function(message) { trace(message); } } Finally, add the listener to the object to myBroadcaster using the addListener( ) method: myBroadcaster.addListener(myListener); Example 12-4 utilizes a broadcaster to broadcast the onResult event from the server, rather than using a callback function. It uses the same HelloUserClass class as shown earlier in HelloUserOOP.fla , with no changes. The only changes are in the ActionScript code in the movie, as well as the two classes that were set up. Create a copy of the User.as file and rename it UserBroadcaster.as . Change the constructor and the sayHello( ) method as show in Example 12-4 (changes shown in bold). Example 12-4. UserBroadcaster class/* User class * public User Broadcaster constructor: new User Broadcaster ( ); // Default user with no arguments new User Broadcaster (name); // Set a default name property arguments: name: string properties: service: the remote service with which the user interacts name: the name of the user methods: getName: retrieve name property setName: set name property arguments: name: string getService: retrieve service object setService: set the remote service for the object arguments: connection: a NetConnection object servicePath: a path to a remote service sayHello:interface to remote method, sayHello( ) arguments: none Dependencies: com.oreilly.frdg. BroadcasterResponder */ #include "com/oreilly/frdg/ BroadcasterResponder.as " function User Broadcaster (name) { if (arguments) this.name = name; // Set this class up as a broadcaster ASBroadcaster.initialize(this); } User Broadcaster .prototype.getName = function ( ) { return this.name; }; // Set the name property only if the argument exists and is not blank User Broadcaster .prototype.setName = function (name) { if (name != "" && name != undefined) this.name = name; }; User Broadcaster .prototype.getService = function ( ) { return this.service; }; // Create remote service object as a property of User User Broadcaster .prototype.setService = function (connection, servicePath) { this.service = connection.getService(servicePath); }; UserBroadcaster.prototype.sayHello = function ( ) { this.getService( ).sayHello(new BroadcasterResponder("onSayHello", this), this.name); }; Let's compare the UserBroadcaster class in Example 12-4 with the User class from the earlier callback implementation. The main differences are the initialization of the class as an ASBroadcaster in the constructor and the fact that the sayHello( ) method now uses a different responder object: BroadcasterResponder . You pass a custom event (" onSayHello ") and the broadcaster object ( this ) to the responder function. The responder object notifies any listeners. The BroadcasterResponder responder function's definition is shown here: /* public BroadcasterResponder constructor: new BroadcasterResponder(event); arguments: event: the event that will be broadcast properties: none methods: onResult: method to handle remote results arguments: event: the event that will be broadcast onStatus: method to handle remote errors arguments: event: the event that the error occurred in Dependencies: none */ function BroadcasterResponder (event, broadcaster) { this.event = event; this.broadcaster = broadcaster; } // Set up onResult( ) and onStatus( ) handlers as // methods of the BroadcasterResponder class BroadcasterResponder.prototype.onResult = function (myResults) { this.broadcaster.broadcastMessage(this.event, myResults); }; BroadcasterResponder.prototype.onStatus = function (myError) { this.broadcaster.broadcastMessage(this.event + 'Error', myError); }; System.onStatus = BroadcasterResponder.prototype.onStatus; The BroadcasterResponder function accepts two arguments: the custom event that will fire when this responder is called, and the broadcaster that will broadcast the message (the UserBroadcaster object instance, in this case). The implementation is simple: when a successful result is returned from the server, the onResult( ) method is called and the broadcaster broadcasts the event (" onSayHello " in this case) and the actual results from the remote call to the movie. If an error is received by the onStatus( ) event handler, the name of the event becomes event + " Error ", or " onSayHelloError " in this case. Next, listeners need to be set up in the main movie: #include "com/oreilly/frdg/UserBroadcaster.as" if (initialized == undefined) { initialized = true; _global.app = new HelloUserClass("http://localhost/flashservices/gateway"); var servicePath = "com.oreilly.frdg.HelloUser"; app.myUser = new UserBroadcaster("User"); app.myUser.setService(app._conn, servicePath); } submit_pb.onRelease = function ( ) { app.myUser.setName(userName_txt.text); app.myUser.sayHello( ); }; // Listener object for the onSayHello event results_txt.onSayHello = function (message) { this.text = message; }; // Listener object for errors in onSayHello results_txt.onSayHelloError = function (message) { this.text = message.description; }; app.myUser.addListener(results_txt); The listener object is the results_txt TextField. Any object can serve as a listener, but the object must have a function set up to respond to your custom event. We simply create the necessary event handlers on the object (by setting the onSayHello and onSayHelloError properties to anonymous functions) and then add it as a listener to receive events fired off by the UserBroadcaster instance ( app.myUser ). Again, this technique is well suited to Flash Remoting. The Macromedia Pet Market blueprint application also uses custom broadcasters. One advantage, as mentioned earlier, is that you can add multiple listeners to the event. For example, you can add this code to create a built-in debugging listener: var debug = true; // var debug = false; // Uncomment this line to turn off debugging var debugListener = {onSayHello:function(message) { trace("User name: " + app.myUser.getName( )); trace("Results from server: " + message); }} if (debug) app.myUser.addListener(debugListener); The listener is "turned on" when the debug flag is set to true. Doing this, you can add listeners to all of your remote calls without having to dig into your code to make changes and put trace( ) statements all over the place. It can all be done from one place, because your listener is listening for the event. 12.7.3 Mixing Procedural and Object-Oriented CodeAnother common way to build an application is to mix procedural style with some OOP concepts. ActionScript 1.0 makes it easy to program in this way by not forcing the rules of OOP on you, as some other languages, such as ActionScript 2.0, require. The procedural example shown earlier could easily benefit from some of the techniques shown in the sections about OOP. For example, the code could implement callback functions in a custom responder object or a broadcaster. Chapter 14 shows a complete Flash MX application that is built procedurally using OOP concepts. |