Shan’s Simple Examples: File uploads with Flex and ColdFusion

Someone in #flex was talking about how there were no good examples for doing uploads with Flex and ColdFusion. Sounded like an excellent topic to cover here. Now this example is a tiny bit more complex than previous examples, because I needed to cover two methods of functionality: single file uploads and multi-file uploads. Both processes are very similar, and in my code they actually share a method.

Here’s the MXML application:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" creationComplete="init()" layout="absolute">
    <mx:Script>
        <![CDATA[
            import mx.controls.Alert;
            private var myFileList:FileReferenceList;
            private var myFile:FileReference;
            private var uploadTarget:URLRequest = new URLRequest("upload.cfm");
           
            private function init():void {
                myFileList = new FileReferenceList();
                myFileList.addEventListener(Event.SELECT,fileListSelected);
               
                myFile = new FileReference();
                myFile.addEventListener(Event.SELECT,fileSelected);
            }
           
            private function fileListBrowse():void {
                // we only want to allow images to be uploaded
                var imagesFilter:FileFilter = new FileFilter("Images", "*.jpg;*.jpeg");
                myFileList.browse([imagesFilter]);
            }
           
            private function fileBrowse():void {
                // we only want to allow images to be uploaded
                var imagesFilter:FileFilter = new FileFilter("Images", "*.jpg;*.jpeg");
                myFile.browse([imagesFilter]);
            }
           
            private function fileListSelected(e:Event):void {
                // here we could do whatever we want, but we're just going to
                // upload right away
                uploadFromList();
            }
           
            private function fileSelected(e:Event):void {
                // here we could do whatever we want, but we're just going to
                // upload right away
                uploadFile(myFile);
            }
           
            private function uploadFromList():void {
                if (myFileList.fileList.length > 0) {
                    // if there are still files left to upload, continue
                    myFile = myFileList.fileList[0] as FileReference;
                    uploadFile(myFile);
                } else {
                    // if there are no more files, stop
                    return;
                }
            }
           
            // NOTE: this method is used for one or multiple files
            private function uploadFile(f:FileReference):void {
                // here we build out the other form items
                var urlVars:URLVariables = new URLVariables();
                urlVars.myFirstValue = 1;
                urlVars.myOtherValue = "something else";
               
                // add the form items to the request
                uploadTarget.data = urlVars;
                // set request the method to POST
                uploadTarget.method = URLRequestMethod.POST;
               
                // add the event listeners
                // DataEvent.UPLOAD_COMPLETE_DATA only fires if data is returned and the flash player supports it
                // otherwise, use Event.COMPLETE
                f.addEventListener(DataEvent.UPLOAD_COMPLETE_DATA,onDataComplete);
                f.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler);
                f.addEventListener(ProgressEvent.PROGRESS, progressHandler);
               
                // show the progress bar
                myProgress.visible = true;
               
                // start the upload
                f.upload(uploadTarget,"myUploadFieldName");
            }
           
            private function ioErrorHandler(e:IOError):void {
                // there was some error, you'll want to tell someone
                Alert.show("Error occured, oops!","I/O Error");
            }
           
            private function progressHandler(e:ProgressEvent):void {
                // update the ProgressBar
                myProgress.setProgress(e.bytesLoaded,e.bytesTotal);
            }
           
            private function onDataComplete(e:DataEvent):void {
                // here we could handle whatever was returned by the server. XML is probably your best bet.
                // var myResult:XML = XML(e.data); // would work for XML
                var myResult:String = e.data;
               
                // if there were multiple files, delete the one we uploaded and try another
                if (myFileList.fileList.length > 1) {
                    // remove what we just uploaded
                    myFileList.fileList.splice(0,1);
                    // go upload another another
                    uploadFromList();
                } else {
                    // if this was the last of a multiple file upload, remove it
                    if (myFileList.fileList.length > 0) {
                        myFileList.fileList.splice(0,1);
                    }
                   
                    // we're done uploading
                    myProgress.visible = false;
                    return;
                }
            }
        ]]>
    </mx:Script>
    <mx:VBox height="100%" width="100%" horizontalAlign="center" verticalAlign="middle">
        <mx:Button label="Choose One File to Upload..." click="fileBrowse()"/>
        <mx:Button label="Choose Multiple Files to Upload..." click="fileListBrowse()"/>
        <mx:ProgressBar id="myProgress" mode="manual" label="Uploading" visible="false"/>
    </mx:VBox>
</mx:Application>

The comments point out all the nuances you need to know about. It does the major features, including passing more data with the file, what you can do with the progress event, how to do an upload queue, rather than uploading all selected files at the same time, and how to handle responses from the upload script. This code will work with any backend processor, not just ColdFusion.

Here’s the ColdFusion file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<cfsetting enablecfoutputonly="true">
<!--- this will upload the file --->
<cffile action="upload" fileField = "myUploadFieldName" destination="#expandPath("./")#uploads/">

<!--- note that all formfields including FORM.myFirstValue and FORM.myOtherValue --->
<cfscript>
    myVal1 = FORM.myFirstValue;
    myVal2 = FORM.myOtherValue;
</cfscript>

<!--- here you can send stuff back to flex, only if the flash player is 9.0.28.0 or later --->
<cfoutput><myXML>
    <file uploadedAs="#cffile.serverFile#">
</myXML></cfoutput>

It doesn’t do much, but you can see how to get non-file data passed from flex, and how to send a response. Make sure that, when sending a response, you don’t send any whitespace before what you intend to send back to Flex. Also, note that your users need the Flash Player version 9.0.28.0 or later in order to send responses back to flex.

Shan’s Simple Examples: Start to Finish, using Ant with Flex for the first time

Disclaimer: This post is not about best practices or anything of that sort. This is just a recount of my first attempt at using Ant with an open-source Flex library project.

Here are the steps I went through (well, minus the steps that did nothing) to get my first Ant build script up & running;

Installing Ant

There were a few steps to get a Flex Builder standalone install up to speed to do stuff with Ant. Eclipse users might or might not need to do these steps.

  • Install Ant (Thanks, Judah!)
    1. In Flex Builder, go to Help > Software Updates > Find and Install
    2. Search for new features to install
    3. Select “The Eclipse Project Updates”
    4. Select The Eclipse Project Updates > Eclipse 3.3.2 > Eclipse Java Development Tools…
    5. Finish the installation process, including restarting Flex Builder
  • Install SVNAnt (Thanks, Phill!)
    1. Download the SVNAnt binary from http://subclipse.tigris.org/svnant.html
    2. Unzip the download, place all the .jar files in /lib/ in your Ant Install directory, which for Flex Builder is Flex Builder 3/plugins/org.apache.ant_${version_number}/lib/
  • Add needed libraries for FTP features
    1. Download the Apache Commons Net and Jakarta-ORO binaries
    2. Unzip both downloads, place all the .jar files in /lib/ in your Ant Install directory (same as above)
  • Finish configuring Ant
    1. Go to Window > Preferences > Ant > Runtime
    2. Click the Ant Home… button
    3. Choose your Ant Install directory (Flex Builder 3/plugins/org.apache.ant_${version_number}/). Be sure to NOT select the lib directory

Your Build Script

So you’ve got everything configured, now what? Let’s start with a build script. Explainations are in the comments. Keep in mind that these directory paths are for OS X, so Windows users will need to switch operating systems or use Windows paths. Probably easier to switch your OS.

  1. Create a build.xml in your project. Populate it with your build code (see my code exmaple below)
  2. Add the Ant view by going to Window > Other Views > Ant
  3. In the new Ant tab, right click and select Add Buildfiles. Select your build.xml file.
  4. Now, whenever you want to make a push, just hit the run button in the Ant tab.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
<project name="MyProject" default="svn.live" basedir=".">
    <!-- set the taskdef's -->
    <taskdef resource="svntask.properties"/>
    <taskdef resource="flexTasks.tasks"/>
   
    <!-- set some properties -->
    <property name="TEMP_DIR" value="/tmp/MyProject_Temp/"/>
    <property name="FLEX_HOME" value="/Applications/Adobe Flex Builder 3/sdks/3.0.0/"/>
    <property name="APP_ROOT" value="~/Documents/Flex Builder 3/MyProject/"/>
   
    <target name="svn.live">
        <!-- clear out the temp dir -->
        <delete dir="${TEMP_DIR}"/>
       
        <!-- let's get the latest build from SVN to a temp directory -->
        <svn>
            <export srcUrl="http://svn.mysite.com/MyProject/trunk/" destPath="${TEMP_DIR}MyProject/"/>
        </svn>
        <echo>SVN Export Complete</echo>
       
        <!-- build the ASDocs from the source -->
        <exec executable="${FLEX_HOME}/bin/asdoc" failonerror="true">
            <arg line="-source-path ${TEMP_DIR}MyProject/"/>
            <arg line="-doc-sources ${TEMP_DIR}MyProject/"/>
            <arg line="-exclude-classes com.adobe.serialization.json.JSON com.adobe.serialization.json.JSONDecoder com.adobe.serialization.json.JSONEncoder com.adobe.serialization.json.JSONParseError com.adobe.serialization.json.JSONToken com.adobe.serialization.json.JSONTokenizer com.adobe.serialization.json.JSONTokenType"/>
            <arg line="-main-title 'My Big Project Docs'"/>
            <arg line="-output ${TEMP_DIR}MyProject/docs/"/>
        </exec>
        <echo>Docs Created</echo>
       
        <!-- build the latest swc from the source -->
        <exec executable="${FLEX_HOME}/bin/compc" failonerror="true">
            <arg line="-source-path ${TEMP_DIR}MyProject/"/>
            <arg line="-include-sources ${TEMP_DIR}MyProject/"/>
            <arg line="-output ${TEMP_DIR}MyProject/bin/MyProject.swc"/>
        </exec>
        <echo>SWC Built</echo>
       
        <!-- create zip files to upload -->
        <mkdir dir="${TEMP_DIR}zip/"/>
        <exec executable="zip" dir="${TEMP_DIR}">
            <arg line="-r ${TEMP_DIR}zip/MyProject.zip MyProject/"/>
        </exec>
        <echo>Source Zip Created</echo>
        <exec executable="zip" dir="${TEMP_DIR}MyProject/bin/">
            <arg line="${TEMP_DIR}/zip/MyProject.swc.zip MyProject.swc"/>
        </exec>
        <echo>SWC Zip Created</echo>
       
        <!-- upload files -->
        <ftp server="ftp.mysite.com"
            userid="username"
            password="password"
            passive="true"
            remotedir="path/to/upload"
        >
            <fileset dir="${TEMP_DIR}zip/"/>
        </ftp>
        <echo>Zip Files Uploaded</echo>
        <ftp server="ftp.mysite.com"
            userid="username"
            password="password"
            passive="true"
            remotedir="path/to/upload/docs"
        >
            <fileset dir="${TEMP_DIR}MyProject/docs/"/>
        </ftp>
        <echo>Zip Files Uploaded</echo>
    </target>
</project>

I’m not going to cover asdoc/compc/mxmlc command line options here, there’s plenty of great docs out there.
You can also read more about the svn task, exec task and ftp task at their respective documentation pages.

Now that you’ve got a start to finish example, go forth and Ant!

Shan’s Simple Examples: Using events (aka how to pass data from components)

There was some confusion in #flex yesterday. The question came up on how a child component could access the parent component. Eventually, it came down to they wanted to pass some data from the child back to the parent.

Well, the “black box” method of component programming says that we should use custom events to send data from the component back to the parent. I whipped up an example that uses both a basic custom event and a custom event that passes back data.

One note, when you create your own event that passes back data, you not only need to add your custom attributes, but you also need to override the clone() method. Look at the MyCustomEvent.as file in the source.

You can view the example here. As always, right-click to view the source.

Shan’s Simple Examples: Keyboard shortcuts and TextInput

Someone on #flex asked about preventing text from appearing in a TextInput or TextArea, when the user is using a keyboard shortcut. After a little discussion, I came up with a working example. I decided to use shift-space to simplify things.

The first assumption is that you’re listening for keyboard shortcuts at the application level:

1
application.addEventListener(KeyboardEvent.KEY_UP,shortcutHandler);

Now, you’ve got your input box, and it’s listeners:

1
<mx:TextArea height="100%" width="95%" keyDown="shortcutPreprocess(event)" textInput="shortcutPreventer(event)"/>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
private function shortcutPreprocess(e:KeyboardEvent):void {
    if (e.shiftKey && e.keyCode == 32) {
        _isShiftSpace = true;
    } else {
        _isShiftSpace = false;
    }
}

private function shortcutPreventer(e:Event):void {
    if (_isShiftSpace) {
        e.stopImmediatePropagation();
        e.preventDefault();
    }
}

Note that I had to set a boolean in my shortcutPreprocess method, and then I check for the boolean in shortcutPreventer. The reason being is that the textInput event doesn’t pass in the KeyboardEvent in any way, so I don’t know if there was a key combo pressed.

Working example & source:
http://www.iotashan.com/examples/KeyboardShortcut/

Shan’s Simple Examples: Using an XML datasource with mx:ComboBox

Over the weekend I was writing a simple little app, and came across something that should have been trivial, but turned out to throw me for a loop momentarily. I needed to drive some data from XML instead of my tried-and-true ColdFusion components, and I realized I’d never done it before. So, here’s the example:

First off, you need to actually get the XML file using a HTTPService:

1
<mx:HTTPService id="sectionService" url="pet.xml" resultFormat="e4x" result="sectionResult(event)"/>

Looks simple enough, but to produce valid XML, I needed to have a root node around my other nodes:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?xml version="1.0" encoding="utf-8"?>
<varieties>
    <variety name="Standard"/>
    <variety name="Angora"/>
    <variety name="Satin"/>
    <variety name="Rex"/>
    <variety name="Texel"/>
    <variety name="Satin Texel"/>
    <variety name="Satin Rex"/>
    <variety name="Satin Angora"/>
    <variety name="Fuzzy"/>
    <variety name="Fuzzy Angora"/>
    <variety name="Fuzzy Hairless"/>
    <variety name="Fuzzy Hairless Angora"/>
    <variety name="Hairless"/>
</varieties>

Now, when I put this directly into a XMLList object, I got a ComboBox with one option… my entire XML packet. It took a few tries to realize that I needed to make the DataProvider only the “variety” nodes, and that was easy to do:

1
2
3
private function varietyResult(e:ResultEvent):void {
    varietyXML = XMLList(e.result.variety);
}

So, for everyone who likes to see the whole picture, like me, here’s the entire MXML code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" width="100%" height="100%" initialize="init()">
    <mx:HTTPService id="varietyService" url="varieties.xml" resultFormat="e4x" result="varietyResult(event)"/>
    <mx:Script>
        <![CDATA[
            import mx.rpc.events.ResultEvent;
           
            [Bindable] private var varietyXML:XMLList;
           
            private function init():void {
                varietyService.send();
            }
           
            private function varietyResult(e:ResultEvent):void {
                varietyXML = XMLList(e.result.variety);
            }
        ]]>
    </mx:Script>
    <mx:ComboBox id="variety" dataProvider="{varietyXML}" labelField="@name"/>
</mx:Application>