Flex trial sample application

Created

Modifying data in a database

You can also view the full source code by right-clicking the Flex application and selecting View Source.

Explanation

This sample project shows you how to connect a Flex application to server-side data to retrieve, update, add, and delete records in a database. To look at the code, right-click on the SWF in the browser and select View Source or download the sample files and view the source files in a text editor or follow the instructions to import the Flash Builder FXP.

Communicating with the server

Flex applications cannot connect directly to a remote database. Instead, they communicate with back-end servers using either direct socket connections or more commonly, through HTTP. HTTP requests are made to a remote data service written in your favorite web language (PHP, ColdFusion, Java, or any other server-side web technology) using one of the Flex framework's remote procedure call APIs: HTTPService, WebService, or RemoteObject. All three wrap Flash Player's HTTP connectivity, which in turn, uses the browser's HTTP library.

With HTTPService, you make HTTP requests to JSP, PHP, CFM, or XML files; RESTful web services; or other server files that returns text over HTTP. You specify the endpoint URL, listener functions (the callback functions to be invoked when the HTTPService request returns a successful or unsuccessful response), and a data type for the returned data (what type of data structure it should be translated into once received in the Flex application). You can specify the data to be handled as raw text and assigned to a String variable or converted to XML, E4X, or plain old ActionScript objects. If you get back JSON, you can use the Adobe Flex corelib package of classes to deserialize the JSON objects into ActionScript objects. To make calls to SOAP based web services, you can use the HTTPService API or the more specialized WebService API, which automatically handles the serialization and deserialization of SOAP formatted text to ActionScript data types and vice versa.

The third option for making remote procedure calls is to use the RemoteObject API. It makes a Flash Remoting request to a method of a server-side class (which can be a ColdFusion component) that returns binary Action Message Format (AMF) over HTTP. You specify an endpoint (which in this case is a server-side Flash Remoting class to handle the remoting request), a destination (the server-side class that has the methods you want to call), and listener functions to handle the responses. The data translation between the client and server-side data types is handled automatically.

This sample project uses Flash Remoting whose binary data transfer format enables applications to load data up to 10 times faster than with the more verbose, text-based formats such as XML, JSON, or SOAP. To see a comparison of AMF to other text-based serialization technologies, see James Ward's Census RIA Benchmark application.

Using Flash Remoting

Flash Remoting is a combination of client and server-side functionality that together provides a call-and-response model for accessing server-side objects from Flash Platform applications as if they were local objects. It provides transparent data transfer between ActionScript and server-side data types, handling the serialization into binary Action Message Format (AMF), deserialization, and data marshaling between the client and the server.

Flash Remoting uses client-side functionality built in to Flash Player and server-side functionality that must be installed on the application server. Flash Remoting is built in to some servers (like ColdFusion and Zend) but must be installed on other servers (as BlazeDS or LiveCycle Data Services on Java EE servers, WebORB or FluorineFX on .NET servers, the Zend Framework or amfphp on PHP servers, and more).

Setting up the server

Let's start by taking a look at the server-side files.

ColdFusion

Flash Remoting is built in ColdFusion servers and little setup is necessary. You need to make sure that Flash Remoting is enabled in the ColdFusion Administrator (Data & Services > Flex Integration > Enable Flash Remoting support) and then in your ColdFusion components, specify an access of remote for the functions you want to access from Flex applications. This sample project calls methods of EmployeeService.cfc located in /wwwroot/TestDrive/services/.

The files for configuring Flash Remoting files are located in /ColdFusion/wwwroot/WEB-INF/flex/. The main configuration file, services-config.xml, defines channels to specify how communication should take place between the client and server, including the protocol to use (for example, AMF over HTTP or HTTPS), the endpoints (the classes to handle and route the calls on the server), and other information for finetuning the calls (like specifying if query columns names and structure keys should be translated into ActionScript with lowercase or uppercase property names). You set the case by modifying the <property-case> tag as shown below.

The services-config.xml file also includes a reference to the remoting-config.xml file, which is where you can define destinations, or mappings to specific ColdFusion components you want to call from Flex. (After changes to the configuration files, you must restart the server.) By default, remoting-config.xml contains one default destination (with an id of ColdFusion) that has its source property set to a wildcard so it can be used for any Flash Remoting calls to any ColdFusion components.

To make a call from Flex with RemoteObject, you specify a destination of ColdFusion and a source of TestDrive.services.employeeService, the fully qualified name of the ColdFusion component that you want to call from the webroot.

You can also add named destinations to remoting-config. For example, you could create a destination called employeeService that points to that same CFC:

... and then when making calls from Flex, you would specify a destination of employeeService (instead of ColFusion) and no source (because it is now included in the destination)

Java

To use Flash Remoting with Java, you need to install an implementation of Flash Remoting on the server. Adobe provides the open-source BlazeDS or the commercial LiveCycle Data Services. This sample uses BlazeDS and the BlazeDS files (JAR files and configuration files) are included in the Test Drive WAR file along with the Java classes to call from the Flex application. For more details, read the Flex and Java technology overview and the architecture overview.

This sample project calls methods of EmployeeService located in the testdrive webapp: /testdrive/WEB-INF/classes/services/. (The source files are also included for reference in /testdrive/webapps/WEB-INF/src/services/.) It uses a typical assembler design pattern and manipulates instances of an Employee data transfer object, which we will map to a corresponding client-side ActionScript object for passing data back and forth between the client and server. To be called from Flex, the Java class must have a no argument constructor.

The files for configuring Flash Remoting files are located in /testdrive/WEB-INF/flex/. The main configuration file, services-config.xml, defines channels to specify how communication should take place between the client and server, including the protocol to use (for example, AMF over HTTP or HTTPS), the endpoints (the classes to handle and route the calls on the server), and other information for finetuning the calls.

The services-config.xml file also includes a reference to the remoting-config.xml file, which is where you define destinations, or mappings to the specific Java classes you want to call from Flex. Here is the destination used for the sample projects called employeeService:

To make a call from Flex with RemoteObject, you specify a destination of employeeService.

PHP

To use Flash Remoting with PHP, you need to install an implementation of Flash Remoting on the server. This sample uses the Zend Framework. You must install the framework and then create a PHP file (in this sample, gateway.php) to use the framework and handle all the requests from Flex, including passing them to the appropriate PHP classes, handling all the data translation and serialization, and more. If you use the Create Data Service wizard in Flash Builder to create a data service to connect to a PHP class, the Zend framework is automatically installed on your server (if it is not already) and gateway.php and an amf-config.ini file created and placed on the server along with the SWF. Although this sample does not use service files created with the Flash Builder Data Service wizard, it does use the gateway.php and amf-config.ini files it creates. (The Flex Test Drive has a tutorial for creating the service with the wizard.)

This sample project calls methods of EmployeeService located in /htdocs/TestDrive/services/.

You specify directories that contain PHP classes to call from Flex in the amf-config.ini file.

;more code
amf.directories[]=TestDrive/servicesc

To make a call from Flex with RemoteObject, you specify a destination of zend, an endpoint of gateway.php, and a source of EmployeeService, the name of the PHP class to call.

Creating a RemoteObject

Now that we've looked at the server-side files, let's see how to call them from Flex. The first step is to define an instance of the RemoteObject class either in the Script block with ActionScript or in the Declarations tag with MXML; they are equivalent. The Declaration tag provides a way to define non-visual objects with MXML. (The green color of the tag indicates it is a compiler tag associated with compiler instructions and not an instance of a class.)

Depending upon what server you are using, the RemoteObject will have slightly different properties specified (the destination and possibly the source and/or endpoint). For ColdFusion and Java, the endpoint is set dynamically using the services-config.xml file and for ColdFusion, a source needs to be set if the default ColdFusion destination is used. After substituting the values from the constants set in the Script block of the sample code, the RemoteObject tag will look like one of these.

You call a method of the server-side class by calling that method on this client-side object, for example, employeesService.getEmployees().

Remote procedure calls are asynchronous so you also need to specify listener functions, the callback functions to be invoked when a request returns a successful or unsuccessful response. For Flex remote procedure calls, you register to listen for result and fault events. Because a data service often has multiple methods that can be called and they can be called at different times in the application, the callback functions are usually specified on a per call basis instead of on the service object.

In this sample, a fault handler is specified for the RemoteObject but no result handler. This fault handler will be used for all service calls that do not have their own fault handler specified. The handler uses the Flex Alert component (which has a static show() method), to display a popup with the fault message. The FaultEvent object has a property called fault that is equal to an instance of the Fault class that has properties faultString and faultDetail.

Note: Instead of using the RemoteObject class directly as in this sample, you can also use Flash Builder's Create Data Service wizard to create a data service and then call methods of a generated service class that wraps the RemoteObject class. Flash Builder introspects the server-side class file and creates a corresponding client-side class with the same operations. (For this introspection and code generation to occur, RDS must be enabled on your server.) If the server returns strongly typed objects (which is the case for the Java classes in this sample), ActionScript classes are created that map to the corresponding objects manipulated by the methods of the class. If the server returns general objects (which is the case for the PHP class and CFC in this sample), you can configure the data service to create typed ActionScript objects whose properties match those for the general objects returned from the server. Of course, you can also write your PHP classes and CFC methods to return strongly typed objects instead of general objects as well. After creation, the data service, its methods, its argument types, and its return types are displayed in the Data/Services view and methods can be dragged and dropped on components in the application to add them. The Flex Test Drive tutorials create and use data services created with the Flash Builder Data Service wizard.

Retrieving data

Next, let's look at the calls to the server. We want to retrieve the employee and department data on application startup. To do this, you usually specify an event handler for the Application object's initialize or creationComplete events. Every Flex component has a series of events that are broadcast as the component is created, from the time it is instantiated until the time it is drawn on the Flash Player drawing surface. The initialize event is broadcast after the component has been created and all of its properties are set and those of its subobjects. creationComplete is broadcast later after the component and all its subobjects also have sizes and positions. Use creationComplete if any of the code references any sizes or positions.

In the sample code, a handler is specified for the Application's initialize event:

<s:Application initialize="init()" ...>

... and inside the handler, the getEmployees() and getDepartments() methods of the service object are called.

When this code is executed, Flash Player makes a call to the server. This happens asynchronously in the background; the user can still interact with the application.
When you make a service call, you need to specify what Flash Player should do when it gets a result or error back from the server. A fault handler was specified for the data service itself and will be used to display errors returned from calls to any of its operations in a popup box.

Using call responders to specify callbacks

To handle successful results, CallResponder objects are declared for each of the calls.

These need to be associated with the corresponding service call. When a service call is initiated, an instance of the AsyncToken class is created. To associate the CallResponder object with the service call, you set the CallResponder's token property equal to the AsyncToken generated at the time the service call is made. When data is returned from the server, it is handled by that CallResponder.

getEmployeesResult.token=employeeService.getEmployees();

A CallResponder object has a lastResult property that automatically gets populated with the data when it is returned to Flash Player from the server. It can be easily used by binding a control to it. In the application, the dataProvider property of a DataGrid component is bound to the lastResult property of the getDepartmentsResult CallResponder object. Whenever the value of getDepartmentsResult.lastResult changes, the DataGrid's dataProvider property is updated and the DataGrid repopulates itself with the new data.

By default, one column is created for each of the fields in the objects used to populate the DataGrid. You control what properties to display and in what order by setting the columns property equal to an ArrayList of GridColumn objects. For each DataGridColumn object, you specify what property it should display (dataField), the title for the column (headerText), its size, and more.

Using the CallResponder lastResult property works fine if you just want to bind the results to a component as shown here, but many times you will want to execute some code when the results are returned. To do this, you register to listen for the result or fault events for the CallResponder. In the sample, a result handler is registered for the getEmployeesResult CallResponder.

This application manipulates employee data. When you bind controls to collections of objects (as we just saw in the deptDg DataGrid and will see next for the empDg DataGrid), the data should be strored in a Flex collection class. For example, instead of storing the employee data objects in an Array (a top level data type in Flash Player), you should use an ArrayList or ArrayCollection. The ArrayList and ArrayCollection classes basically wraps an Array so that when any value in any position of the array changes, an event is broadcast and any controls bound to it will be updated. If the objects were stored in an Array, even if it was made bindable, events would be broadcast only when the value of the entire array changed, for example, if it went from null to a value or was set to an entirely different value. Unlike an ArrayList, an ArrayCollection can also be sorted, filtered, and traversed with a cursor.

In this application, an employees ArrayCollection is defined in the Declarations block:

<s:ArrayCollection id="employees"/>

... which is equivalent to setting a public, bindable variable in ActionScript:

[Bindable]public var employees:ArrayCollection;

The dataProvider property of the empDg DataGrid is bound to this collection.

<mx:DataGrid id="empDg" dataProvider="{employees}" ...>

Inside this collection, we want to store strongly typed, bindable Employee objects, not general Objects. This way we will get code-hinting when manipulating these objects and compiler and runtime property and property type checking. These objects will also be bindable, so if any of their properties change, an event will be broadcast and any objects bound to it updated.

If you look in the valueObjects folder, you will see an Employee class definition.

If the server-side class returns strongly typed objects (as does the Java class in this sample), you need to include a RemoteClass metadata tag that maps this ActionScript class to the corresponding class on the server.

[RemoteClass(alias="services.Employee")]

This is the crucial information needed by Flash Player so it can map the server-side objects to the correct client-side objects automatically. In this case, the getEmployeeResult_resultHandler() only needs to populate the employees ArrayCollection with the data returned from the server because the data has already been translated to an ArrayCollection of Employee objects.

The data returned from a server-side method call is stored in the result property of the event object that is passed to the result event handler. The employees variable is data typed as an ArrayCollection but the result property of the event object is data typed as a general Object, so you have to cast event.result to an ArrayCollection to assign it to employees.

If Employee objects are not returned from the server (as is the case for the PHP class and CFC in this sample), getEmployeesResult_resultHandler() needs to create this collection of Employee objects manually. For PHP, you get an Array of Objects back from the server and for ColdFusion, an ArrayCollection of Objects. To convert the Objects into Employee objects, you need to loop through the collection, creating a new Employee instance for each item and populating its properties with the corresponding properties of the general Object, and then adding that Employee object to the collection. You could explicitly write each property assignment:

emp.id=data[i].id;
emp.firstname=data[i].firstname;
emp.lastname=data[i].lastname;
//do the same for all properties

... but this code instead uses the describeType() method (in the flash.utils package). describeType() returns an XML object that contains an accessor element (which has name, access, type, and declaredBy attributes) for each of object's properties. This is the ActionScript implementation of reflection.

For ColdFusion, the getEmployeesResult_resultHandler() appears as shown here.

When the user selects an employee in the DataGrid, that employee's details are displayed beneath it. In the Declarations block, you'll see an employee variable defined of type Employee (that is bindable by default).

The employee details will be displayed in the Label controls in the Form. A Flex Form container is purely a layout container, helping to easily line up multiple controls that have labels in front of them; it has no other purpose like in an HTML form. When you send data to the server which we will look at next, you specify explicity what objects to send; they have nothing to do with Form components.

Now that we have seen how to retrieve and display data from the server, let's look at how to modify this data on the server, updating, adding, and deleting records in the database. You may want to go look at your application server's class file (or CFC) now to familiarize yourself with the methods it contains before looking at the code to call these methods. If you installed the files, you can look at the source code on your server. Otherwise, you can right-click the SWF in the browser, select View Source, and browse to the appropriate PHP class, Java class, or CFC.

Using the DataGrid to update data

One way to let users update the data is to make the DataGrid editable and then to send the changes to the server. You make the DataGrid editable by setting its editable property to true and then registering to listen for its gridItemEditorSessionSave event which is broadcast when the user makes changes to a cell value.

Inside the handler, you need to send the modified Employee instance to the server: call the updateEmployee() method and pass to it the selected item in the DataGrid.

employeeService.updateEmployee(employee);

For ColdFusion, you need to modify this slightly. When you pass an object to a Flash Remoting request, it is treated as a group of named arguments and passed to the CFC method as individual arguments, not as a single object argument. To actually pass a single object as an argument, you must create an Object instance with the name of the argument expected by the CFC, in this case an argument called item, and pass that to the server-side method. The {} here are are an ActonScript short hand notation for creating an Object; multiple name/value pairs would be separated by commas.

employeeService.updateEmployee({item:employee});

In this case, you're not going to do anything after the data is successfully updated so you don't need to specify a CallResponder to handle the results.

In this application, updates are sent to the server every time the user changes data in one DataGrid cell. If a lot of changes are going to be made, you may want to wait and submit all changes to the server at once.

Using a form to update data

Only some of the employee data is displayed in the DataGrid, so you may also want to provide a form for the user to modify all of the employee data.
When an employee is selected and the EmployeeDetails state displayed, an update button is enabled.

When the user clicks this button in the Form container, the employee object is updated with the values the user entered in the input controls and then passed to the data service's updateEmployee() method.

Similar functionionality will be used to add a new employee next, so this function has been written to handle both cases. If an existing employee is being updated, employee.id will be non-zero, so the server-side updateEmployee() method is called. Otherwise, the createEmployee() method is called. As before, the code for ColdFusion is slightly different to pass an object to a server-side method.

In this case, we do want to do something after the data has successfully been sent to the server, so a new CallResponder is defined with a result handler:

When the user clicks this button, the application switches to its EmployeeAdd state, which shows the same form as used to update an employee, but now the submit button is labeled Add instead of Update.

When the user clicks this button, the application switches to its EmployeeAdd state, which shows the same form as used to update an employee, but now the submit button is labeled Add instead of Update.

The employee object is also set equal to a new Employee instance (employee could currently contain the employee selected in the DataGrid) so the input controls in the Form container that are bound to it will all be set to the Employee class's default values.

The same handler is called when the user clicks the button in the form:

At this point the new employee is saved in the database, but not in the collection of data being displayed in the DataGrid. You need to assign the newly generated id to employee and add employee to the data displayed in the DataGrid.

If you look in the TestDrive server-side service file, you will see that the createEmployee() method returns an integer equal to the id of the new employee inserted in the database. The data returned from a server-side method call is stored in the result property of the event object that is passed to the result event handler. The id property of employee is data typed as an integer and the result property of the event object is data typed as a general Object, so you have to cast event.result to an integer to set id equal to it.

employee.id=event.result as int;

You use the addItem() method to add the new employee to the employees ArrayCollection.

employees.addItem(employee);

Note: In this example, you are writing code to update both the server-side data (stored in the database) and the client-side data (stored in the DataGrid dataProvider). Flash Builder also has a data management feature you can use to help synchronize client and server-side data.

Then, you select it in the DataGrid:

empDg.setSelectedIndex (employees.getItemIndex(employee));

... and then scroll to it so it is displayed:

empDg.ensureCellIsVisible(empDg.selectedIndex);

The result event handler for adding an employee appears as shown here.

The server-side method expects one argument, the id of the employee to delete.

In the result handler, the employee is removed from the employees ArrayCollection using its removeItemAt() method and the application is switched to its Employees state (there is no longer any employee selected to show details for).