Requirements      
Prerequisite knowledge Required products
Sample files
User level
Working knowledge of ActionScript 3.0 and Java, specifically, compiling and running Java servlets.
Flash Builder (Download trial)
Intermediate
       

 

 
Additional Requirements

 
Apache Commons FileUpload Package
File uploads enabled by Flex and Java provide a high level of interaction between the user and a web application. For example, people can use file upload to submit a résumé to a job posting or a home movie to a video sharing site. Flex offers a powerful library of tools to handle file uploads, while also providing feedback to the user on the upload’s progress.
 
For the server-side processing, a simple Java servlet can receive the request and write the file to storage. The request can contain additional data, providing more complex instructions to the servlet. In response, the servlet can generate XML for Flex to display the upload’s status.
 
This article shows how to develop a Flex and Java file upload mechanism for attaching images to an employee list. In the process, it demonstrates Flex’s ECMAScript for XML (E4X) language extension, custom components, classes, and events, as well as a Java-like properties file. In addition, the article explains packages in Java for generating XML.

 
Creating the Flex project

The sample Flex application for this article will display a DataGrid control containing a few employees, with the option to browse for and upload a photo for each person.  The Flex application sends to the Java servlet a POST request, which includes not just the image file but also the employee ID. The servlet uses the employee ID as the filename, deletes any previous pictures for that employee, writes the image to disk, and responds to Flex with an XML document. In turn, Flex displays the newly uploaded image along with the server’s response.
 
To start, create a new project in Flex Builder named FileUpload. Begin by adding a few components: a VBox, a Form, and a DataGrid. The Form will display the server response while the DataGrid will list each employee’s name, ID, department, location, and photo.
 
Give the components these properties:
 
<mx:VBox width="750"> <mx:Form width="720" borderColor="white" borderStyle="solid" > <mx:FormHeading label="Flex Upload"/> <mx:FormItem direction="horizontal" width="100%" label="Server Response:"> <mx:TextInput editable="false" id="txtServerResponse" /> </mx:FormItem> <mx:FormItem direction="horizontal" width="100%" label="Server Response (Raw):"> <mx:TextArea editable="false" id="txtServerResponseRaw" width="100%" height="150" /> </mx:FormItem> </mx:Form> <mx:DataGrid id="gridEmployees" variableRowHeight="true" wordWrap="true" rowHeight="100" height="336"> <mx:columns> <mx:DataGridColumn dataField="name" headerText="Name"/> <mx:DataGridColumn dataField="@id" headerText="ID" /> <mx:DataGridColumn dataField="department" headerText="Department" /> <mx:DataGridColumn dataField="location" headerText="Location" /> <mx:DataGridColumn headerText="Photo" id="imageCol" width="319" /> </mx:columns> </mx:DataGrid> </mx:VBox>
Note the last column in the grid with ID imageCol; this will hold the image upload component you’ll create later. For now, preview the app to see what you have (see Figure 1).
 
The Flex app without any data.

Figure 1. The Flex app without any data.

 
Using a custom XML configuration file
To simplify deployment to production, you can use a custom XML file to store server paths and other application-level variables. The values aren’t hardcoded inside the app; instead they’re in a text file that can be easily changed. You only have to modify this one file instead of changing your Flex app and recompiling.
 
Create an assets folder at the root of your app; right-click it and choose New > File. Name the file properties.xml. The app will use two values that change from development to production: the path to the servlet and the path to the server’s upload folder. They’re named UploadHandler and UploadFolder, respectively.
 
Type the following into the properties.xml file:
 
<properties> <property> <name>UploadHandler</name> <value>http://localhost/upload/UploadHandler</value> </property> <property> <name>UploadFolder</name> <value>http://localhost/upload/uploads/</value> </property> </properties>
The application reads the XML file using an HTTPService call from the Application container’s creationComplete handler. Add a script block to the app:
 
<mx:Script> <![CDATA[ import mx.controls.Alert; import mx.rpc.http.HTTPService; import mx.rpc.events.ResultEvent; import mx.rpc.events.FaultEvent; private var xmlProperties:XML; private var servicePropReader:HTTPService; public var uploadServletURL:String; public var uploadFolderURL:String; /** * CreationComplete event handler. Calls an initialization * method. */ private function handleCreationComplete():void { getProperties(); } /** * Reads the custom <code>properties.xml</code> file. * * <p>Fetches the data using the <code>HTTPService</code> call. * The file contains some variables that might change between the * development and production servers. This way, there's no need * to modify the app and recompile; simply change the values in * the XML file.</p> * * <p>The E4X (ECMAScript-for-XML) enables easy access to elements and attributes. * See link below for details.</p> * * @see mx.rpc.http.HTTPService * @see http://livedocs.adobe.com/flex/3/html/help.html?content=data_access_6.html */ private function getProperties():void { servicePropReader = new HTTPService(); servicePropReader.url = "assets/properties.xml"; // Set the URL. servicePropReader.resultFormat = "e4x"; // E4X for easy data access. servicePropReader.contentType = "application/xml"; /* Add event listeners for the result and fault events. */ servicePropReader.addEventListener(ResultEvent.RESULT, propertyReaderResultHandler); servicePropReader.addEventListener(FaultEvent.FAULT, propertyReaderFaultHandler); servicePropReader.send(); // Call the service at the specified URL. } /** * ResultEvent event handler for the servicePropReader HTTPService. * * <p>If we're in this handler, then the call was successful. We set * some global variables to values from the XML file. Note the E4X * methods of reading the XML elements and attributes. * * @param event ResultEvent object holds details on what triggered this event. * * @see mx.rpc.events.ResultEvent; */ private function propertyReaderResultHandler(event:ResultEvent):void { xmlProperties = XML(event.result); uploadServletURL = xmlProperties.property.(name=="UploadHandler").value; uploadFolderURL = xmlProperties.property.(name=="UploadFolder").value; } /** * Fault event handler for the servicePropReader HTTPService. * * <p>If we're in this handler, then the call failed. We pop up an * alert to display the error to the user.</p> * * @param event FaultEvent object holds details on what went wrong. * * @see mx.rpc.events.FaultEvent; */ private function propertyReaderFaultHandler(event:FaultEvent):void { Alert.show(event.fault.message,"Could not load properties XML file."); }
A few things to note in the above code: The resultFormat property of the HTTService call is set to “e4x”, short for the ECMAScript for XML (E4X) language extension. This format provides straightforward access to the XML document, without the need to convert it to an ArrayCollection. To fetch the value element of the node with the name equal to UploadHandler, you use the following code:
 
uploadServletUrl = xmlProperties.property.(name=="UploadHandler").value;
Also note that the variables’ types are set when they’re declared. For example, uploadServletUrl is of type String. This is an example of early (static) binding; the Flex compiler knows the properties and methods of the object at compile-time. This can have dramatic performance benefits. In contrast, with late (dynamic) binding, you declare the variable as a generic type (such as Object) and then set the specific type at runtime. Though more flexible, the latter technique has a performance penalty.
 
Don’t forget to change the Application tag to call the new handleCreationComplete() method:
 
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical" horizontalAlign="left" paddingLeft="50" paddingTop="20" creationComplete="handleCreationComplete();">
Don’t worry about the paths in the properties.xml file now; as you create the Java servlet, you can modify the paths. The Java servlet is relatively simple to create, too. It receives the file from Flex and writes it to disk.
 

 
Creating the Java servlet

Java code can be edited with something as simple as a text editor or as sophisticated as the free Eclipse IDE. The server code for this app can be compiled for the older Java 1.4 or the latest versions; I’ll cover the differences as the code is introduced.
 
The Java servlet uses a free, robust Java file utility package from the Apache Software Foundation called FileUpload. You can download the latest version of the package from http://commons.apache.org/downloads/download_fileupload.cgi.  It has a dependency on the included Apache Commons IO library. The JAR files need to be in the Java app’s lib folder. To learn more about the Apache FileUpload library, visit http://commons.apache.org/fileupload/.
 
Though not required for the simple XML response this servlet provides to Flex, learning an efficient XML generation technique can help you when you need to convert large data sets. The same mechanism can be applied to a few records or to thousands; the results are produced very quickly and efficiently.
 
To start writing the server code, create a new class file in your favorite Java editor, naming it UploadHandler.java. Type the following into the file (remember to change the package name to reflect your organization!):
 
package org.unctv.fileupload; import java.io.IOException; import java.io.PrintWriter; import java.io.File; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Iterator; import javax.servlet.*; import javax.servlet.http.*; import java.util.ListIterator; import java.util.List; // SAX and JAXP classes. import javax.imageio.ImageIO; import javax.imageio.ImageReader; import javax.imageio.stream.ImageInputStream; import org.xml.sax.*; import org.xml.sax.helpers.*; import javax.xml.transform.*; import javax.xml.transform.stream.*; import javax.xml.transform.sax.*; // File upload libraries. import org.apache.commons.fileupload.FileUploadException; import org.apache.commons.fileupload.servlet.ServletFileUpload; import org.apache.commons.fileupload.disk.DiskFileItemFactory; import org.apache.commons.fileupload.FileItem; import org.unctv.fileupload.FileFilter; /** * Receives the HTTP upload request from Flex and generates valid XML. * * @author Alex Cougarman */ public class UploadHandler extends HttpServlet { private static final String CONTENT_TYPE = "text/xml; charset=utf-8"; /** * Initialize of the servlet handler. * * @param config * @throws ServletException */ public void init(ServletConfig config) throws ServletException { super.init(config); } /** * Receives the POST request from Flex. The request contains form fields, * including an uploaded file. Writes the image file to the server using * the employee ID sent with the request and responds with some XML to * indicate status. It uses some simple exception handling for the most * common problems; modify it to meet your needs. * * @param request * @param response * @throws ServletException */ public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException { response.setContentType(CONTENT_TYPE); // Set the servlet's response type to XML. PrintWriter out = null; String uploadDirectory = this.getServletContext().getInitParameter("UploadDirectory"); // Get the upload directory from the web.xml file. ArrayList<String> allowedFormats = new ArrayList<String>(); // Allowed image format types are stored in an ArrayList. allowedFormats.add("jpeg"); allowedFormats.add("png"); allowedFormats.add("gif"); allowedFormats.add("jpg"); File disk = null; FileItem item = null; DiskFileItemFactory factory = new DiskFileItemFactory(); // We use the FileUpload package provided by Apache to process the request. String statusMessage = ""; String employeeID = ""; ListIterator iterator = null; List items = null; ServletFileUpload upload = new ServletFileUpload( factory ); // SAX 2.0 ContentHandler. TransformerHandler hd = null; try { out = response.getWriter(); StreamResult streamResult = new StreamResult(out); // Used for writing debug errors to the screen. SAXTransformerFactory tf = (SAXTransformerFactory) SAXTransformerFactory.newInstance(); // SAX XML parsing factory. items = upload.parseRequest(request); iterator = items.listIterator(); hd = tf.newTransformerHandler(); // Set the XML handler. Transformer serializer = hd.getTransformer(); // You'll serialize the data. serializer.setOutputProperty(OutputKeys.ENCODING,"UTF-8"); // You'll use UTF-8 for the XML encoding. serializer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM,"response.dtd"); // Set the doctype to the custom DTD. serializer.setOutputProperty(OutputKeys.INDENT,"yes"); // Though not required, you can provide automatic indentation of the XML. serializer.setOutputProperty(OutputKeys.METHOD,"xml"); // Identifies the method used for outputting the result tree. hd.setResult(streamResult); hd.startDocument(); // Start the XML document. AttributesImpl atts = new AttributesImpl(); // Declare and instantiate a new attributes object. hd.startElement("","","response",atts); // Start the main response element. while( iterator.hasNext() ) // Loop over the items in the request. { // Clear the XML attributes object. atts.clear(); item = (FileItem)iterator.next(); // If the current item is an HTML form field... if( item.isFormField() ) { /* * First item from Flex is Filename, automatically * added to the HTTP request header when uploading * a file. We'll set the variable when we iterate * to the correct URLVariable for the employeeID. * The last item is another field that Flex adds * automatically to the HTTP request header, * Upload, the value for the upload button. */ if (item.getFieldName().equalsIgnoreCase("employeeid")){ employeeID = item.getString(); // Get the value and store it. } atts.addAttribute("","","id","CDATA",item.getFieldName()); // Add the "id" attribute of the "field" element. hd.startElement("","","field",atts); // Start element and set its attribute. hd.characters(item.getString().toCharArray(),0,item.getString().length()); // Set the "field" tag's value. hd.endElement("","","field"); // Close the "field" tag. atts.clear(); // Clear the attributes object so it can be used again. } else { // If the item is a file... /* * Use an ImageInputStream to validate the file's format name. * This actually reads the image's internal file format, versus * reading the file extension, which isn't always reliable. */ ImageInputStream imageInputStream = ImageIO.createImageInputStream(item.getInputStream()); Iterator<ImageReader> imageReaders = ImageIO.getImageReaders(imageInputStream); ImageReader imageReader = null; if(imageReaders.hasNext()){ // Get the next (only) image. imageReader = imageReaders.next(); } /* * Non-image files will throw a NullPointerException on the next line. * This code uses a high-level Try/Catch block, but you can use a * more fine-grained exception handling mechanism. */ String imageFormat = imageReader.getFormatName(); String newFileName = employeeID + "." + imageFormat; // New image's filename, concatenation of employee ID and image format. if (allowedFormats.contains(imageFormat.toLowerCase())){ // If the image format is one of the allowed ones... /* * Custom FileFilter implements java.io.FilenameFilter. * See FileFilter.java. */ FileFilter fileFilter = new FileFilter(); // Declare and instantiate a FileFilter object. fileFilter.setEmployeeID(employeeID); // Set the employee ID and the allowed image types on the FileFilter. fileFilter.setImageTypes(allowedFormats); File fileList[] = (new File(uploadDirectory)).listFiles(fileFilter); // Get a filtered list of files from the upload directory. for (int i=0; i < fileList.length; i++){ // Delete any previous instances of the image file from the directory. (new File(fileList[i].getAbsolutePath())).delete(); } disk = new File(uploadDirectory + newFileName); // Instantiate a File object for the file to be written. item.write(disk); // Write the uploaded file to disk. /* * Get a Calendar object and fetch the current time from it. */ Calendar calendar = Calendar.getInstance(); SimpleDateFormat simpleDateFormat = new SimpleDateFormat("MM.dd.yy hh:mm:ss aaa"); statusMessage = "File successfully written to server at " + simpleDateFormat.format(calendar.getTime()); } /* * If you're processing multiple files, you'd place * these lines outside of the loop. */ imageReader.dispose(); imageInputStream.close(); atts.addAttribute("","","id","CDATA",newFileName); // Add the "id" attribute of the "file" element. hd.startElement("","","file",atts); // Start the "file" element. hd.characters(statusMessage.toCharArray(),0,statusMessage.length()); // Set the "file" element tag's value. hd.endElement("","","file"); // End the "file" element. /* * TODO: You can add code here to store the image name to a database. */ } } hd.endElement("","","response"); // End the "response" element. hd.endDocument(); // End the XML document. out.close(); // Close the output. } /* * Some very basic exception handling. * Modify as per your needs. */ catch (TransformerConfigurationException tcException) { out.println(tcException.getMessage()); } catch (FileUploadException fileUploadException) { out.println(fileUploadException.getMessage()); } catch (IOException ioException) { out.println(ioException.getMessage()); } catch (SAXException saxException) { out.println(saxException.getMessage()); } catch (NullPointerException exception) { out.println(exception.getMessage()); } catch (Exception e){ out.println(e.getMessage()); } } }
The UploadHandler.java code begins by importing some necessary classes. It proceeds to extend the HttpServlet class (thus the presence of the init() method). The doPost() method does the heavy lifting. It receives the Flex HTTP request and generates valid XML in response. But it doesn’t use the usual out.println() method to emit XML; it uses a more efficient means of producing the output, which I’ll cover shortly.
 
Here is some sample XML output of the servlet:
 
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE response SYSTEM "response.dtd"> <response> <field id="Filename">photo_1.png</field> <field id="employeeID">1234</field> <file id="1234.png">File successfully written to server at 04.30.09 01:50:57 PM</file> <field id="Upload">Submit Query</field> </response>
Note that Flex sends additional fields with the file upload. The first item from Flex is Filename, automatically added to the HTTP request header when uploading a file. The last item is another field that Flex adds automatically to the HTTP request header: Upload, the value for the upload button.
 
Also note the DOCTYPE declaration that references a custom Document Type Definition (DTD) file. In the Java app’s root folder, create a file called response.dtd and place the following content in it:
 
<!ELEMENT response ( field*, file* ) > <!ELEMENT field ( #PCDATA ) > <!ELEMENT file ( #PCDATA ) > <!ATTLIST file id CDATA #REQUIRED > <!ATTLIST field id CDATA #REQUIRED >
In plain English, the DTD is saying this:
 
·         The document must have an element called response, which can have 0 or more elements called field and file.
 
·         The field and file elements will contain parsed character data (PCDATA).
 
·         Both the file and field elements must contain an id attribute of type character data (CDATA).
 
The servlet generates valid XML using the DTD document. But how? In the UploadHandler.java code, you begin by setting the response’s content type to XML and declaring some variables used throughout the code. Then you fetch the path to the upload folder from the web.xml file and store it in the string variable uploadDirectory. Note that the code uses one large try..catch block to handle exceptions. You can modify this as per your requirements.
 
Now you can instantiate an ArrayList of strings to store the allowed image types and create some Apache FileUpload components. The TransformerHandler declaration begins the XML output processing. This class is part of the Java JAXP (Java API for XML Processing) package for parsing and producing valid XML. For Java 1.4.2 and earlier, you had to download the package separately; with JDK 5.0 and higher, it is an integrated part of Java. To learn more about JAXP, visit the JAXP page.
 
The SAXTransformerFactory object represents one of the parsing interfaces provided by JAXP. Because SAX (Simple API for XML) doesn’t create an in-memory representation of the XML document, it’s faster and more memory-efficient than the other JAXP interfaces.
 
Proceed by creating a list of the items in the upload request and fetch the list’s iterator. You can then create a Transformer object to serialize the data and set some output properties on it. Note that you set the DOCTYPE_SYSTEM to the response.dtd file to ensure the output is valid XML:
 
serializer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM,"response.dtd");
Then start the XML document with the startDocument() method and instantiate a new AttributesImpl object. This object adds attributes to the XML document’s elements.
 
The root node of the XML document is response, which has no attributes; thus you can pass the blank attributes object to the startDocument() method. Iterate over the request items list by first clearing the attributes object for reuse. Then convert the current item to a FileItem for processing.
 
If it’s an HTML form field and its name equals employeeid, you retrieve the employee ID, which Flex passes as part of the HTTP request. Add element attributes and start a field node. To set the tag’s text, use the characters() method and fetch the item’s getString() method:
 
hd.characters(item.getString().toCharArray(),0,item.getString().length());
End the element and clear the attributes.
 
If the item is something other than a form field, then it is the uploaded file. You only want images uploaded so use an ImageInputStream to read the file’s internal format name. You can also parse the uploaded file’s name and detect its extension, but that is less reliable. Comparing the results of getFormatName() on the ImageReader object to the allowedFormats ArrayList dependably limits the input types.
 
Set the filename in newFileName based on the employee ID and the image format. If you’ve received an allowed image type, you instantiate a FileFilter object based on the FileFilter.java custom class. This class has an accept() method that returns a Boolean to indicate if the file meets your criteria; in this case, it checks the file’s name to see if it begins with the employee ID and ends with one of the allowed image format extensions. You’ve only uploaded allowed types, so using the file’s extension is very dependable in this case.
 
The FileFilter object will be used to find and delete any previous images for the employee. Set the employee ID and the allowed image types on the FileFilter object and fetch the list of files from the upload directory based on the filter:
 
File fileList[] = (new File(uploadDirectory)).listFiles(fileFilter);
Iterate over the list and delete any files that meet the criteria. Now you can write the file to disk. To provide feedback to the user that the upload was successful, you set a status message and concatenate the date/time to it using an instance of the Calendar object:
 
Calendar calendar = Calendar.getInstance(); SimpleDateFormat simpleDateFormat = new SimpleDateFormat("MM.dd.yy hh:mm:ss aaa"); statusMessage = "File successfully written to server at " + simpleDateFormat.format(calendar.getTime());
Dispose of the ImageReader and close the ImageInputStream. Then add the attributes and write the file node to the XML document. Don’t forget to end the response element, end the document, and close the output.
 
The servlet uses a custom utility class to filter the files on the server’s upload directory and delete any previous pictures for the employee. This class is based on the java.io.FilenameFilter class. Create a new class in your Java app called FileFilter.java and enter the following code:
 
package org.unctv.fileupload; import java.io.*; import java.io.FilenameFilter; import java.util.ArrayList; import java.util.Iterator; /** * Implements java.io.FilenameFilter to enable filtering * of filenames. Has two custom variables to hold the * employee ID and the allowed image types. */ public class FileFilter implements FilenameFilter{ private String _employeeID = ""; private ArrayList _imageTypes; /** * Checks the filename based on it starting with the employee ID * and ending with the correct image type extension. * * @param file * @param name * @return boolean true if filename passes check, false otherwise */ public boolean accept(File file, String name){ boolean startsWith = false; boolean endsWith = false; startsWith = name.startsWith(getEmployeeID()); Iterator iterator = getImageTypes().iterator(); while (iterator.hasNext()){ if ( name.toLowerCase().endsWith((String) iterator.next())){ endsWith = true; break; } } return startsWith && endsWith; } public String getEmployeeID(){ return _employeeID; } public void setEmployeeID(String value){ _employeeID = value; } public ArrayList getImageTypes(){ return _imageTypes; } public void setImageTypes(ArrayList value){ _imageTypes = value; } }
Here is the critical section of code:
 
startsWith = name.startsWith(getEmployeeID()); Iterator iterator = getImageTypes().iterator(); while (iterator.hasNext()){ if (name.toLowerCase().endsWith((String) iterator.next())){ endsWith = true; break; } }
You’re setting two Boolean variables: startsWith and endsWith. You acquire an iterator from the _imageTypes ArrayList and loop to see if the filename ends with one of the allowed extensions. The accept() method returns true only if both variables are true.
 
Java servlets use the web.xml file, found in the WEB-INF folder. For this app, you can start with the following code and modify it to meet your needs:
 
<?xml version = '1.0' encoding = 'windows-1252'?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd" version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"> <description>web.xml file for Web Application</description> <context-param> <param-name>UploadDirectory</param-name> <param-value>/web/server/uploads/</param-value> </context-param> <servlet> <servlet-name>UploadHandler</servlet-name> <servlet-class>org.unctv.fileupload.UploadHandler</servlet-class> </servlet> <servlet-mapping> <servlet-name>UploadHandler</servlet-name> <url-pattern>/uploadhandler</url-pattern> </servlet-mapping> <session-config> <session-timeout>35</session-timeout> </session-config> <mime-mapping> <extension>html</extension> <mime-type>text/html</mime-type> </mime-mapping> <mime-mapping> <extension>txt</extension> <mime-type>text/plain</mime-type> </mime-mapping> </web-app>
Note that the servlet named UploadHandler is accessed via the URL /uploadhandler (directly from the root of your app); UploadHandler in turn references org.unctv.fileupload.UploadHandler, which represents the package hierarchy and will be different for your app.
 
You can now compile both Java classes and return to the Flex app.

 
Adding data to the Flex app

In Flex, to abstract the data from the view (presentation layer), you will use an XMLListCollection for the DataGrid control’s data provider. Data abstraction, an important concept in object-oriented programming, separates the properties of the data from its implementation. As a result, the implementation can change without affecting the properties (interface) visible to the data consumer (UI).
 
Modify the script block to read the XML employee data and bind it to the DataGrid:
 
<mx:Script> <![CDATA[ import mx.controls.Alert; import mx.collections.SortField; import mx.collections.Sort; import mx.collections.XMLListCollection; import mx.rpc.http.HTTPService; import mx.rpc.events.ResultEvent; import mx.rpc.events.FaultEvent; private var xmlEmployees:XML; private var xmlListEmployees:XMLList; [Bindable] private var xmlListColEmployees:XMLListCollection; // Object used for sorting the XMLListCollection. private var sortAsc:Sort; private var xmlProperties:XML; private var servicePropReader:HTTPService; public var uploadServletURL:String; public var uploadFolderURL:String; /** * CreationComplete event handler. Calls an initialization * method and sets an event listener. */ private function handleCreationComplete():void { getProperties(); } /** * Initialize the variables and create an XMLList and * XMLListCollection. Enable sorting on the collection. */ private function loadEmployeeList():void { // Load the XML data. xmlEmployees = <employees> <employee id="1234"> <name>Jane Smith</name> <department>IT</department> <location>Room 222</location> </employee> <employee id="5678"> <name>Joe Smith</name> <department>HR</department> <location>Room 123</location> </employee> <employee id="9012"> <name>Sally Johnson</name> <department>PR</department> <location>Room 195</location> </employee> </employees>; // Create the XMLList of employees. xmlListEmployees = xmlEmployees.employee; /* Create the XMLListCollection from the employee XMLList. */ xmlListColEmployees = new XMLListCollection(xmlListEmployees); // Create a Sort object. sortAsc = new Sort(); /* Create a SortField object and set the Sort object's fields property. */ sortAsc.fields = [new SortField("name", true)]; /* Set the sort property of the XMLListCollection to the Sort object. */ xmlListColEmployees.sort = sortAsc; /* Apply the sort by calling refresh. */ xmlListColEmployees.refresh(); } /** * Reads the custom <code>properties.xml</code> file. * * <p>Fetches the data using the <code>HTTPService</code> call. * The file contains some variables that might change between the * development and production servers. This way, there's no need * to modify the app and recompile; simply change the values in * the XML file.</p> * * <p>The E4X (ECMAScript-for-XML) enables easy access to elements and attributes. * See link below for details.</p> * * @see mx.rpc.http.HTTPService * @see http://livedocs.adobe.com/flex/3/html/help.html?content=data_access_6.html */ private function getProperties():void { servicePropReader = new HTTPService(); servicePropReader.url = "assets/xml/properties.xml"; // Set the URL. servicePropReader.resultFormat = "e4x"; // E4X for easy data access. servicePropReader.contentType = "application/xml"; /* Add event listeners for the result and fault events. */ servicePropReader.addEventListener(ResultEvent.RESULT, propertyReaderResultHandler); servicePropReader.addEventListener(FaultEvent.FAULT, propertyReaderFaultHandler); servicePropReader.send(); // Call the service at the specified URL. } /** * ResultEvent event handler for the servicePropReader HTTPService. * * <p>If we're in this handler, then the call was successful. We set * some global variables to values from the XML file. Note the E4X * methods of reading the XML elements and attributes.</p> * * @param event ResultEvent object holds details on what triggered this event. * * @see mx.rpc.events.ResultEvent; */ private function propertyReaderResultHandler(event:ResultEvent):void { xmlProperties = XML(event.result); uploadServletURL = xmlProperties.property.(name=="UploadHandler").value; uploadFolderURL = xmlProperties.property.(name=="UploadFolder").value; /* Load the XMLListCollection of employees here, to ensure we've read the properties.xml file before doing anything else. If you have paths to other servlets or resources in the properties.xml, this will ensure they are read into local variables before proceeding. */ this.loadEmployeeList(); } /** * Fault event handler for the servicePropReader HTTPService. * * <p>If we're in this handler, then the call failed. We pop up an * alert to display the error to the user.</p> * * @param event FaultEvent object holds details on what went wrong. * * @see mx.rpc.events.FaultEvent; */ private function propertyReaderFaultHandler(event:FaultEvent):void { Alert.show(event.fault.message,"Could not load properties XML file."); } ]]> </mx:Script>
In the above code, a Sort object instance named sortAsc (for sort ascending) provides sorting functionality for the XMLListCollection data. Calling the refresh() method triggers the function. Because the collection object is declared as [Bindable], the DataGrid will automatically receive the data.
 
Also note the call to the loadEmployeeList() method from the ResultEvent handler. Because of the asynchronous nature of Flex, this ensures that the app only sets the grid’s data when the properties.xml file has successfully loaded; all those variables are set properly before proceeding—thus preventing null reference exceptions.
 
Now modify the DataGrid to bind it to the XMLListCollection object:
 
<mx:DataGrid id="gridEmployees" dataProvider="{xmlListColEmployees}" variableRowHeight="true" wordWrap="true" rowHeight="100" height="336">
Run the app to see the employees loaded into the DataGrid (see Figure 2). You’ll notice a small up arrow in the “Name” column header, which indicates that the data has been sorted in ascending order on that column.
 
Application with the employee data.

Figure 2. Application with the employee data.

 
Creating the Flex upload component

Remember the imageCol DataGrid column you created earlier? You will place the custom itemRenderer for the file uploads in this column.
 
In Flex Builder, create a new component and name it ImageUpload. Base it on the HBox component and clear the width and height properties. Edit the ImageUpload.mxml file to add these components in the HBox:
 
<mx:Image id="imgPhoto" visible="true" autoLoad="true" width="100" height="100" source="{imageSrc}" ioError="imageIOErrorHandler(event)" /> <mx:VBox> <mx:TextInput id="txtFilename" editable="false" /> <mx:HBox> <mx:Button id="btnBrowse" label="Browse" click="browseHandler(event)" /> <mx:Button id="btnUpload" label="Upload" click="uploadHandler(event)" visible="false" /> </mx:HBox> <mx:ProgressBar id="pbrUploadProgress" indeterminate="false" mode="manual" fontWeight="bold" color="red" labelPlacement="bottom" trackHeight="9" visible="false" /> </mx:VBox>
The Image control will display the photo. You bind its source to a variable named imageSrc and handle the IO errors via the imageIOErrorHandler() method. Next to the image, a VBox will display a read-only TextInput. Two buttons side-by-side (inside the HBox control) provide the browse and upload capabilities. Note that the upload button is hidden for now. Rounding out the controls, a ProgressBar will display the upload status using an event listener on the FileReference object.
 
The functionality of the component comes from the following script block, which you can add inside the top-level HBox control:
 
<mx:Script> <![CDATA[ import mx.controls.Alert; private var file:FileReference; private var path:String; [Bindable] private var imageSrc:String; // Limit the file types in the Browse popup box to ".jpg" // so we can easily find it again when reloading page. private var imageTypes:FileFilter; /** * When component is created, this function is called. * It instantiates a FileReference object and sets its * listeners. * * @param event Event object contains details of the event triggering this handler. */ private function creationCompleteHandler(event:Event):void { file = new FileReference(); // Set the event listeners on the FileReference ojbect. file.addEventListener(Event.SELECT, selectHandler); file.addEventListener(Event.OPEN, openHandler); file.addEventListener(ProgressEvent.PROGRESS, progressHandler); file.addEventListener(IOErrorEvent.IO_ERROR, uploadIoErrorHandler); file.addEventListener(DataEvent.UPLOAD_COMPLETE_DATA, uploadCompleteHandler); // Set the upload path from the parent document. path = parentDocument.uploadFolderURL; // Instantiate a new FileFilter. imageTypes = new FileFilter("Images (*.jpg, *.jpeg, *.png, *.gif)", "*.jpg; *.jpeg; *.png; *.gif;"); } /** * Event handler for the "Browse" button's click. * * @param event Event object contains details of the event triggering this handler. */ private function browseHandler(event:Event):void { pbrUploadProgress.visible = false; btnUpload.visible = false; file.browse([imageTypes]); } /** * Event handler for when the user selects a file from disk. * * @param event Event object contains details of the event triggering this handler. */ private function selectHandler(event:Event):void { txtFilename.text = file.name; btnUpload.visible = true; } /** * Event handler for when the file is opened. * * @param event Event object contains details of the event triggering this handler. */ private function openHandler(event:Event):void { pbrUploadProgress.visible = true; pbrUploadProgress.label = "Uploading %3%% of image file."; } /** * Event handler for the "Upload" button. * Initiates file upload operation. * * @param event Event object contains details of the event triggering this handler. */ private function uploadHandler(event:Event):void { var url:String = parentDocument.uploadServletURL; var request:URLRequest = new URLRequest(url); request.method = URLRequestMethod.POST; var variables:URLVariables = new URLVariables(); variables.employeeID = data.@id; request.data = variables; pbrUploadProgress.visible = true; file.upload(request); } /** * Event handler for when the upload starts. * * @param event ProgressEvent object contains details of the event triggering this handler. */ private function progressHandler(event:ProgressEvent):void { pbrUploadProgress.setProgress(event.bytesLoaded, event.bytesTotal); } /** * Event handler for when the upload operation has completed. * Specifically manages response data. * * @param event DataEvent object contains details of the event triggering this handler. */ private function uploadCompleteHandler(event:DataEvent):void { try { var xmlResponse:XML = XML(event.data); pbrUploadProgress.label = "Upload complete."; imageSrc = path + xmlResponse.elements("file").attribute("id").toString(); } catch (error:Error) { pbrUploadProgress.label = "Exception occurred."; Alert.show("Exception details: " + error.message,"Exception occured."); } } /** * Override to set individual rows' values. */ override public function set data(value:Object):void { super.data = value; /* Set the imageSrc to a phoney value to trigger the image's IO error event handler. This is to demonstrate the event handler. */ imageSrc = "noimage.jpg"; } /** * Event handler for the image IO error. * * @param event IOErrorEvent object contains details of the event triggering this handler. */ private function imageIOErrorHandler(event:IOErrorEvent):void { txtFilename.text = "No Photo Found"; imgPhoto.source = path + "missing.jpg"; } /** * Event handler for the file upload IO error. * * @param event IOErrorEvent object contains details of the event triggering this handler. */ private function uploadIoErrorHandler(event:IOErrorEvent):void { pbrUploadProgress.label = "Encountered error."; Alert.show("IO Error: " + event.toString()); } ]]> </mx:Script>
In the above code, the creationCompleteHandler() method, called from the creationComplete event of the main HBox control, instantiates a FileReference object and sets its event listeners. The method also fetches the upload folder’s path from the parent document (set from the properties.xml file). You limit the file types the user can choose to the common Web image formats using a FileFilter object:
 
imageTypes = new FileFilter("Images (*.jpg, *.jpeg, *.png, *.gif)", "*.jpg; *.jpeg; *.png; *.gif;");
The first argument to the FileFilter constructor is what the user sees in the “Files of type:” dropdown control in the browse dialog box. The second argument specifies what files on the user’s system are shown in the file-browsing dialog box when the FileReference.browse() method is called.
 
The browseHandler() method is called when the Browse button is clicked. It sets the visible attributes of the ProgressBar and the upload Button to false before invoking the FileReference.browse() method with the FileFilter object as argument. File selection by the user calls the selectHandler() method, which sets the TextInput to the chosen filename and toggles the Upload button’s visibility to true.
 
When the file is opened for upload, you use the openHandler() event listener to set the ProgressBar’s visibility and label. Note the label’s text:
 
pbrUploadProgress.label = "Uploading %3%% of image file.";
The label property lets you include special characters in the label text string. The %3 string represents the percent loaded while %% corresponds to the % sign. For example, the above label would generate the following string:
 
Uploading 95% of image file.
The uploadHandler() event listener fetches the Java servlet’s path from the parent document and instantiates a URLRequest object with that address. You can use POST as the HTTP request method and pass the employee’s ID as a URLVariable to the URLRequest’s data property. Then toggle the visibility of the ProgressBar to true and call the FileReference.upload() method with the URLRequest argument. The method invocation uploads the selected file to the specified URL along with the employee ID.
 
To update the ProgressBar, you employ the progressHandler() event listener method. It receives the FileReference’s ProgressEvent and passes the bytesLoaded and bytesTotal values to the ProgressBar.setProgress() method to reflect the upload progress. 
 
When the upload completes successfully, you invoke the uploadCompleteHandler() method. Within a try..catch block, you store the servlet’s response as an XML object, set the ProgressBar’s label, and change the value of the imageSrc variable to the URL of the uploaded file. Because the variable is defined as [Bindable] and the Image control’s source is bound to it, the Image control will display the new photo upon completion of the upload.
 
If the upload fails with an input/output (IO) error, it will call the uploadIoErrorHandler() method to change the ProgressBar’s label and show an Alert box with the error.
 
The Image control can invoke an error handler when the specified file isn’t found. You define the imageIOErrorHandler() method as the IO error event listener:
 
<mx:Image id="imgPhoto" visible="true" autoLoad="true" width="100" height="100" source="{imageSrc}" ioError="imageIOErrorHandler(event)" />
The method sets the TextInput control’s text to an error message and displays a placeholder image called missing.jpg, found in the upload folder. To see this in action, override the set data() method of the itemRenderer, and set the imageSrc variable to “noimage.jpg”. Because there isn’t an image called noimage.jpg, it will call the method when the page first loads.
 
Now you can add this custom component to the DataGrid control’s imageCol column in the FileUpload.mxml file. Open this file and edit the DataGrid to reflect the following change:
 
<mx:DataGridColumn headerText="Photo" id="imageCol" width="319" itemRenderer="org.unctv.fileupload.ImageUpload" />
Note that you’re specifying the package hierarchy to the component in the itemRenderer property. Run the Flex app to see how the page looks (see Figure 3):
 
Application with the ImageUpload component.

Figure 3. Application with the ImageUpload component.

 
Creating a custom Flex event

One question remains: How do you let the parent document know the servlet’s response? Simple: Use a custom Flex event to communicate with the main Application.
 
You created a Form with some controls to display the reply. Now add the code necessary to fill it with the server response. Create a new class named UploadEvent; in the New ActionScript Class dialog box, provide the package name and choose flash.events.Event as the Superclass. Place the following code in the UploadEvent.as file:
 
package org.unctv.fileupload { import flash.events.Event; /** * Class extends the Event class to create a custom * event for the ImageUpload.mxml component. * * @see flash.events.Event */ public class UploadEvent extends Event { // Used for the event type. public static const UPLOAD_EVENT:String = "uploadevent"; private var _serverResponse:String = ""; private var _serverResponseRaw:String = ""; /** * Class constructor sets the default values for * the bubbles and cancelable parameters. */ public function UploadEvent(type:String, bubbles:Boolean=true, cancelable:Boolean=false) { super(type, bubbles, cancelable); } /** * Getter method for the _serverResponse member * variable. */ public function get ServerResponse():String { return _serverResponse; } /** * @private */ public function set ServerResponse(value:String):void { _serverResponse = value; } /** * Getter method for the _serverResponseRaw member * variable. */ public function get ServerResponseRaw():String { return _serverResponseRaw; } /** * @private */ public function set ServerResponseRaw(value:String):void { _serverResponseRaw = value; } } }
The event type is defined in the public static constant variable UPLOAD_EVENT. The two private member fields will hold the server response. In the class constructor, set the default values for the two Boolean variables in the argument declaration. The bubbles and cancelable arguments determine whether the event bubbles up through the object hierarchy and if the event can be cancelled, respectively:
 
public function UploadEvent(type:String, bubbles:Boolean=true, cancelable:Boolean=false) { super(type, bubbles, cancelable); }
You need to have the event bubble up the chain, out of the custom component and to the Application, where it can be detected and handled. Set the bubbles default value to true; the cancelable Boolean can be left as false. The super() method call invokes the superclass constructor of this class with the specified arguments. Finally, the getter and setter methods match the private member fields and provide a public interface for the class.
 
Now wire up the custom component to fire the event. Add the following metadata tag just above the script block in the ImageUpload.mxml file. This defines the MXML property for the event and the data type of the event object that this component emits:
 
<mx:Metadata> [Event(name="uploadevent", type="org.unctv.fileupload.UploadEvent")] </mx:Metadata>
Modify the ImageUpload component’s uploadCompleteHandler() method to reflect this code:
 
private function uploadCompleteHandler(event:DataEvent):void { try { var xmlResponse:XML = XML(event.data); pbrUploadProgress.label = "Upload complete."; // Create a custom event. var uploadEvent:UploadEvent = new UploadEvent("uploadevent"); // Fetch the file element of the servlet's XML response. uploadEvent.ServerResponse = xmlResponse.elements("file"); // Fetch the entire XML response. uploadEvent.ServerResponseRaw = event.data; // Dispatch the custom event. dispatchEvent(uploadEvent); imageSrc = path + xmlResponse.elements("file").attribute("id").toString(); } catch (error:Error) { pbrUploadProgress.label = "Exception occurred."; Alert.show("Exception details: " + error.message,"Exception occured."); } }
You declare a variable of type UploadEvent and instantiate it using the custom event’s constructor. Note that you only have to indicate the event’s name; the constructor will use the default values for the other two arguments (bubbles and cancelable) provided within the class.
 
Then you use the public properties of the custom event to store the server response. The servlet’s response XML includes the file element, with the status message indicating successful upload plus the date/time stamp. Use this value for the ServerResponse property of the custom event, while storing the entire servlet XML response in the ServerResponseRaw property.
 
Finally, call the dispatchEvent() method to trigger the event. Because there is no event handler defined within the ImageUpload component and the custom event is set to bubble, it will travel up the object hierarchy.
 
The main Application page, FileUpload.mxml, needs to listen to and capture this event. To do so, modify the FileUpload.mxml file’s handleCreationComplete() method to add an event listener to the Application:
 
private function handleCreationComplete():void { getProperties(); this.addEventListener(UploadEvent.UPLOAD_EVENT, uploadEventHandler); }
Next, add the event listener method, which sets the text of the two controls to the properties of the custom event:
 
/** * Custom event handler for the UploadEvent. * * <p>Sets some text fields from the event and invalidates * the DataGrid's display list to force a refresh.</p> * * @param event UploadEvent custom event */ private function uploadEventHandler(event:UploadEvent):void { txtServerResponse.text = event.ServerResponse; txtServerResponseRaw.text = event.ServerResponseRaw; }
You’re almost ready to see the app in action. One final step: Ensure the Flex app’s custom properties.xml file contains the correct paths to your Java servlet and the server’s upload location.
 
Run the Flex app, click the Browse button for any of the employees in the DataGrid, and select an image file from your hard drive (see Figure 4).
 
Selecting an image file.

Figure 4. Selecting an image file.

The Upload button becomes visible and the TextInput of the ImageUpload component displays the filename. Click the Upload button and notice the ProgressBar becomes visible and displays the upload status; when the upload completes, you’ll see the photo displayed for the employee (see Figure 5).
 
Successfully uploading the image file.

Figure 5. Successfully uploading the image file.

The employee photo is uploaded and the server response displays in the Form control above the DataGrid. Check the upload directory on the server to ensure the file has arrived successfully. And with that, you’re done!

 
Troubleshooting

A simple HTML page can help troubleshoot your Java servlet to ensure it’s working correctly. Create a page named upload.html in the root of your Java app and place this code in it:
 
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=windows-1252"> <title>Test Upload Servlet</title> </head> <body> <form action="./UploadHandler" method="post" enctype="multipart/form-data"> Enter an Employee ID: <input name="employeeID" type="text" value="1234" /><br /> Select an image (*.jpg, *.png, *.gif): <input name="txtFilename" type="file" /><br /> <input type="submit" value="Upload" /> </form> </body> </html>
Load the page in your browser; select an image file and click the Upload button. If you’re using Firefox, you will see something similar to the following output upon successful completion:
 
<response> <field id="employeeID">1234</field> <file id="1234.jpeg"> File successfully written to server at 04.30.09 02:46:31 PM </file> </response>
For Internet Explorer, you will have to choose View > Source or Page > View Source to see the full XML response. Otherwise, it will only display text like the following:
 
File successfully written to server at 04.30.09 02:46:31 PM

 
Where to go from here

This article showed you how to upload files with Flex and Java. Using the powerful RIA framework provided by Flex, you can send a file to the server with additional parameters and receive and process the server feedback. Flex makes it easy to add custom components and events. This customization provides greater flexibility for your app to meet your design requirements; instead of being limited to the components that ship with Flex, you can let your imagination and needs take you to new levels of ingenuity.
 
How can you modify this app to meet your business needs? Here are some ideas:
 
  • Add database code to your Java servlet so you can read the employee data. This app uses some static XML to simplify the code demonstrations, but a database can open up a whole new world of dynamic capabilities for you.
  • Write the employee’s image filename to the database in the Java code. Modify the servlet to read the employee information from the database and render it as XML for your Flex application. Then, when you load the Flex page, it will display the current images for the employees.
  • Use a URLLoader to emulate an upload of a file created entirely in memory. URLLoader encodes the image in memory (in the Flash Player), which you can then upload to the server. For example, you can use this  method to add a watermark to the photo.
  • Add a button to display only those employees without photos. You can send a parameter to the servlet to indicate what data you need.
  • Build search functionality for the app. Send the search parameters from Flex to the servlet and receive a more dynamic set of data.
  • Implement an authentication and authorization mechanism to allow only administrators to upload images. For example, you can implement the Java Authentication and Authorization Service to provide security.
  • Add a version of this app to your organization’s intranet. Your boss will be wondering why this wasn’t done before!
The following resources are also helpful:
 

 
Attributions

Special thanks to Kevin Hoyt, who wrote a great blog post about file uploads using Flex and Java.