File Upload with Apollo and JavaScript May 21
Let’s say that you take a lot of pictures. You take pictures, but you don’t want to think about how to upload them. You’d really like an application to monitor a directory for new images, and then upload them to a server. That kind of rules out the browser - it’s not going to be watching a directory on your desktop for image files, and then automatically uploading them. What about Apollo though? Couldn’t an Apollo application monitor a directory for me? Sure! Let’s make it happen…
To preface this little experiment, the Apollo alpha doesn’t currently support the ability to run as a background process. To that end, this application does have to be running. It can be minimized, but it does have to be running. Oh, and I should mention that this application is written entirely in HTML, CSS and JavaScript. No Flash involved here, just Apollo DHTML goodness.

The UI for this application can be pretty simple - especially since we’re talking about an application that could eventually run in the background. To that end, my HTML (view) is little more than the basic HTML page with a single DIV. We’ll eventually use that DIV to hold a list of images and render that list with nifty upload status progress bars. Wait, progress bar of an image being uploaded? I know, it’s not something you generally see in HTML because, well, because it’s not available. That type of low-level event is available to Apollo. We’ll see more about this later, but for now let’s head back to the UI.
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Watcher</title>
<script src="jquery-1.1.2.js" type="text/javascript"></script>
<script src="watcher.js" type="text/javascript"></script>
<link href="watcher.css" rel="stylesheet" type="text/css" />
</head>
<body>
<div id="listing"></div>
</body>
</html>
I’m not a CSS guru. In fact, I’m rather appalled by the current state of affairs as it pertains to browser differences in CSS implementation. Since I’m building this application for Apollo however, I know that I’ll be using WebKit, and that makes my job a lot easier. One single browser that runs consistently across operating systems. That I can handle. I use some absolute positioning for the list DIV and a few other items, and then I sprinkle in some relative positioning for smaller textual regions.
.bar {
position: absolute;
height: 6px;
margin-top: 24px;
border: solid 1px #446EAC;
left: 28px;
right: 5px;
visibility: hidden;
background-color: #FFFFFF;
}
body {
background-color: #F6F5F4;
font-family: Arial, Helvetica, sans-serif;
font-size: 11px;
color: #000000;
}
.icon {
position: relative;
margin-left: 5px;
margin-top: 3px;
float: left;
}
.item {
height: 36px;
border-bottom: solid;
border-bottom-color: #808080;
border-bottom-width: 1px;
cursor: default;
background-color: #FFFFFF;
}
#listing {
border: solid;
border-color: #808080;
border-width: 1px;
background-color: #FFFFFF;
overflow: auto;
position: absolute;
left: 5px;
bottom: 5px;
right: 5px;
top: 5px;
}
.filename {
position: relative;
float: left;
margin-left: 4px;
margin-top: 5px;
clear: right;
}
.filename span {
color: #808080;
position: relative;
margin-left: 5px;
}
.pending {
position: relative;
margin-left: 27px;
margin-top: -1px;
color: #808080;
float: left;
clear: left;
}
.progress {
width: 0%;
height: 100%;
background-color: #AEDDF2;
}
If we can think about having CSS consistency across OS thanks to the WebKit engine in Apollo, then it stands to reason that we can think about consistency with JavaScript too. While that is true, I’m not a masochist, and I have elected to use jQuery as a JavaScript library for this application. I’d be interested in your thoughts on jQuery and it’s use in sample Apollo applications. Would you prefer samples be core JavaScript, or is a library acceptable? Which libraries would you prefer?
The first thing we’ll do when the application starts is check for the existence of a directory on the desktop. Apollo makes this easy, without having to think about where the desktop is located and on which operating system. The File object in Apollo has a variety of static properties to common OS locations that return a File reference. In this case we can use File.desktopDirectory and file.resolve() the specific directory on the desktop that we’re interested in watching.
// Watcher logic
$( document ).ready( function() {
// Check for directory
var watching = runtime.flash.filesystem.File.desktopDirectory;
watching = watching.resolve( dir );
// Create it if it doesn't exist
if( !watching.exists )
{
watching.createDirectory();
}
// Do the initial check and updating of the UI
update();
} );
Once the application is started and we’ve confirmed the presence of our image directory (or created it if it doesn’t exist), then we move on to checking for the presence of image files. The File object has a listDirectory() method that we can use to get the contents of the directory. We don’t want to upload anything other than image files however, so we’ll check the file extension. This isn’t particularly accurate, but it’s a good start. If the item in the list of files is an image, then we can build an item to render the details and then pop it into the list. In the case that there are images, we’ll start uploading the files, otherwise we’ll check back every five seconds via JavaScript’s setInterval() function.
// Checks the directory and updates the UI
function update()
{
// HTML block for list item
var itemo = "<div class="item">";
var icon = "<img src="image.png" width="18" height="18" class="icon" />";
var fileo = "<div class="filename">";
var sizeo = "<span>(";
var sizec = " KB)</span>";
var filec = "</div>";
var pending = "<div class="pending">Upload not yet started...</div>";
var baro = "<div class="bar">";
var progress = "<div class="progress"></div>";
var barc = "</div>";
var itemc = "</div>";
var inject = null;
// Get the list of image in the directory
var watching = runtime.flash.filesystem.File.desktopDirectory;
var list = null;
// Get the directory listing
watching = watching.resolve( dir );
list = watching.listDirectory();
// Remove existing item entries
$( "#listing" ).empty();
// Find the actual image files
for( var i = 0; i < list.length; i++ )
{
if( list[i].name.toLowerCase().indexOf( ".jpg" ) >= 0 )
{
// Build the item
inject = itemo +
icon +
fileo +
list[i].name +
sizeo +
formatSize( Math.round( list[i].size / 1000 ) ) +
sizec +
filec +
pending +
baro +
progress +
barc +
itemc;
// Inject the new item container
$( "#listing" ).append( inject );
}
}
// Add mouse handlers to any new items
$( ".item" ).mouseover( function() {
$( this ).css( "backgroundColor", "#D4D5D8" );
} );
$( ".item" ).mouseout( function() {
$( this ).css( "backgroundColor", "#FFFFFF" );
} );
// TODO: Show a means to remove an image when the mouse is over an item
// TODO: Have image thumbnail appear when mouse is over icon
// TODO: Show summary status bar with total upload progress
// Determine if uploading needs to occur
// Avoids refresh of list mid-upload
// Can check for more later, after upload has completed
if( $( ".item" ).size() > 0 )
{
upload( null );
} else {
// Check again at some point in the future
setTimeout( "update()", interval );
}
}
Rather than check the file system again before we’re done uploading, I’ve chosen to extract the file name from the list item and then infer the path (we know where that directory on the desktop is, right?). I figure that once we’re done with this batch of uploads that we’ll check the file system again later for more image files. There are some shortcomings to this approach, but I’m not trying to account for every use-case with this example.
You might initially be inclined to think that we’ll use an upload form in HTML to upload our files, but as mentioned previously, there’s no means provided to check the progress. As it turns out the File object has an upload() method on it. The only thing the upload() method requires is a reference to a URLRequest object. This is the object that most importantly contains the URL endpoint, but it might optionally contain some additional form data. The File.upload() will actually execute a multi-part form upload to the server, and you can treat it as such once it gets there.
For this example I’m using ColdFusion on the server. This is mostly because I have it readily available, and it’s only a single line of code to get the uploaded bytes and store them on disk. I like easy. You can replace this with whatever you prefer on the server.
The other important aspect to this is the additional event listeners I’ve put on the File object. The progress event let’s me know periodically that the upload is taking place. It passes an event object that among other data, contains bytesLoaded and bytesTotal properties. I can use this information to determine a percentage of the progress and then tell the CSS on the progress bar to size itself to that percent. The complete event will fire when the upload is finished, and then we can take additional action.
// Show progress for the image being uploaded
function progress( event )
{
// Determine overall percentage from event properties
var percent = Math.round( ( event.bytesLoaded / event.bytesTotal ) * 100 );
// Change CSS properties to show bar width based on percent
$( ".progress:first" ).css( "width", percent + "%" );
}
I’ve chosen to call back to the upload() function when the upload operation is completed. When that event fires, it too will pass an event object along. The upload() function checks for the presence of that event object. If the event object is present, then the application knows that the upload is complete, and can clean up the file from disk. Once again the File object provides a deleteFile() method we can use for this in a simple one-liner. If the upload() method gets called an no event object is provided, then the application knows that we need to start an upload operation.
// Perform the actual upload
function upload( event )
{
// Variables for managing the upload
// Image is a reference to a file on disk from the first item in the list
var img = extractAndResolve( $( ".item:first" ).text() );
var request = null;
// If there is no argument value, then upload the first item in the list
if( event == null )
{
// Show the upload progress bar and hide the waiting text
$( ".pending:first" ).css( "visibility", "hidden" );
$( ".bar:first" ).css( "visibility", "visible" );
// Destination of uploaded file
request = new runtime.flash.net.URLRequest( url );
// Attach the appropriate event listeners
img.addEventListener( runtime.flash.events.ProgressEvent.PROGRESS, progress );
img.addEventListener( runtime.flash.events.Event.COMPLETE, upload );
// Upload the file
img.upload( request, field, false );
} else {
// Remove the first image item from the list
$( ".item:first" ).remove();
// Remove the actual image from the disk
img.deleteFile();
// Move onto the next image if there are any available
if( $( ".item" ).size() > 0 )
{
upload( null );
} else {
// Keep checking for new images
setTimeout( "update()", interval );
}
}
}
A word of caution here is that I have chosen to remove the files after they’ve been uploaded. If you’re going to try this application out on your machine, make sure that you copy the files and not move them. This application has no mercy and will start uploading and deleting within seconds of your paste operation. User beware!
Once the upload operation is complete, and the image file has been removed from disk, the application checks for any additional items in the list. If there are none, the process of waiting five seconds and checking for more files starts all over. If there are additional items in the list (additional files), then the next item in the list will be processed (uploaded). I’ve chosen to always check the first item in the list, so uploading will happen from the top down.
That’s all there is to it! There’s a number of to-do’s that I’d like to add, and I’ve made comments to that point, which again is all JavaScript. Since I’m using my own server for the upload, I throttle the upload image size to no more than three megabytes (3 Mb). Feel free to point the URL to your own server, or better yet Amazon S3 and not worry about it. That was beyond my scope for this little experiment. You can access the source code and application file in the archive attached to this post.







Joel May 23
What I would like to see (so what I think I am going to do), this make a wordpress post automagically. We keep a family photo blog, and this would make it really easy to update. Thanks for the example Kevin.
Andy matthews May 25
Holy SCHMOKES Batman! This is awesome. Great job Kevin!!!
Drinkspiller May 25
Regarding your question about using vanilla JavaScript or a library, count me among those pleased to see jQuery used in the example. I tend to prefer it for the same concise code you mentioned for the CFML upload. It seems reasonable to assume developers familiar with this scope of technologies (Javascript, HTML, CSS, Actionscript, CFML/PHP/.NET) will be able to follow along regardless of which JS library is used. While I’m not dogmatic about it, I’ve settled on jQuery after having used it, YUI, Prototype and on numerous production sites.
FWIW, I’m a bit of an AS2 geek and have yet to so much as get my feet wet with AS3/Apollo. Your blog is a nice find. Keep the examples comin’!
Cheers!
Anthony May 25
How about a zip file containing this project? I think I could digest stuff easier seeing stuff all together. But this is EXACTLY what I have been dying for so thank you! Once you have the stuff in your todo list I can see this being one of the most useful applications written in apollo to date!
Thanks again and nice work!
Kevin Hoyt May 25
Anthony,
The source and application file are attached at the end of the post, just before the start of the comments in Zip format. The URL for the file is:
http://blog.kevinhoyt.org/wp-content/watcher.zip
Hope this helps,
Kevin
WDPX May 29
Awesome Idea. This is, what I was looking for quite some time. I definately need to implement this in my software Aiyoota!-CMS.
Thanx!
grildcheese May 31
background: trying to embed the Watcher functionality in another Apollo application and compile as .swf. Saw Adobe documentation
var container:Sprite;
var html:HTMLControl;
html.width = 400;
html.height = 600;
var urlReq:URLRequest = new URLRequest(”app-resource:/wherever/watcher.html”);
html.loadHTMLFromURLRequest(urlReq);
container.addChild(html);
still getting error
var watching = runtime.flash.filesystem.File.desktopDirectory;
ReferenceError: Can’t find variable: runtime
at app-resource:/watcher.js : 19
obviously I don’t know what I am doing but could someone post on how they included watcher in an Apollo application.
thanks for sharing this resource.
Kevin Hoyt May 31
Mr. Cheese,
In this case the main HTML file (watcher.html) is designed to be *the* application, and doesn’t expect to be embedded in anything else.
The “runtime.flash.filesystem.File.desktopDirectory” message is a property that is used from HTML-based Apollo applications, and makes explicit reference to its container. In the case of using “watcher.html” inside the Flex HTMLControl, the HTML is no longer the root of the application, so those references no longer resolve and you get the error.
If you want this functionality in a Flex application, you should port it over using the AS3 equivalents.
Hope this helps,
Kevin
grildcheese Jun 4
Mr. Hoyt,
Please, call me Grild. Your application works when embedded in another Apollo application using the mx:HTML class, I just needed to set the exposeRuntime=”true” , It would make more sense to port it actionscript though. Thanks again.
nilesh Jun 16
nice example, would it also be possible to do Multi -uploads at once? vs one upload at a time? or what would be the factor to base that on? AIR or ?
was trying to find your email, you hide it well, was not able to find it yet, hehe please drop me a email , have some questions about a project i been working on need small advice.
-Nilesh
grildcheese Jun 27
The Flex 3 documentation on HTML class shows changes in the class. exposeRuntime is no longer necessary and has been removed. An Air application (formerly Apollo) compiles fine and uploads dutifully.
Linda Rawson Jul 3
Great demo! Just what I was looking for.
Can you tell me what would be required from a user. Say a user worked at some computer security lockdown. What would the user need to run this application? For instance at most military installations they cannot run flash applications. Would they need flash to run this demo? Do they need admin privleges of any sort?
Thanks!
Linda Rawson
http://www.sensorytech.net
tts Jul 5
Hi Kevin,
Really cool. I’ve been building on this for another project and trying to get upto speed on AIR…not to mention JavaScript. I’m mostly a PHP guy.
What I’m having trouble with is figuring out the calls to upload(). The upldate() function calls it and it also calls itself. Then there’s also the timer going off calling upldate() which happens in both update() and upload().
Is this OK with Javascript? It looks like a lot of recursion is happening to me.
And when I run it, the allocated memory seems to increase over time too.
Could you elaborate on this “polling” process?
best,
- tim
grildcheese Jul 6
Hi Linda,
To install an AIR application you do not need administrator privileges but you do need to install the AIR Runtime. An AIR application does not subvert system security settings but it can run with full local filesystem access so in any lockdown situation they would not allow installation of AIR applications and it would be prevented by any reasonably strict lockdown policy. There is a discussion in Kevin’s book “Adobe AIR for Javascript Developers” which was freely downloadable recently.
tts Jul 7
It doesn’t seem to work with PHP. Anyone know how I can tell what headers are being sent? PHP requires that the enctype be set to multipart/form. Can this be set using the URLRequestHeader or is it a given that this info is being passed?
thanks,
- tim
tts Jul 8
Well the PHP thing was my bad. But I found something else of import. When I start the uploader, and the uploads dir is empty. Then I add some images, I get error #2158, file or directory is in use. I’m thinking Windows hasn’t quite finished copying the files before the upload method tries for it. Maybe if I could catch this error, I could move to the next in the queue or something, but I don’t know how to catch an error like this. Can anyone point me in that direction?
cheers,
- tim
aw Oct 24
Greetings from Beijing, hey, Kevin Hoyt
It’s really an interesting project.
Chris Leeman Dec 16
There are a couple of deprecated items being used here, since this app was built when it was still ‘Apollo’.
watching.resolve(dir) should be ‘watching.resolvePath(dir)’
‘listDirectory()’ should be ‘getDirectoryListing()’
This app does not seem to run in AIR without the use of ‘parent/child sandbox bridges’. I’m guessing this was not required in Apollo, but now it it since they’ve tightened up the security model…
Seeing ‘Error: Parsing Disallowed’ is an indication that eval() is being used via an external js library (like JQuery, used here).
Kevin Hoyt Dec 17
Chris,
Yes, this application like many on my blog were written long before the current AIR Beta 3. I haven’t gone back and update most of these older examples, so your pointers are much appreciated!
Thanks for reading,
Kevin
Chris Leeman Dec 20
No problem Kevin, just wanted to mention it for those who try to get the sample working in newer versions or AIR. Thanks for taking the time to do it though, it’s been a great help!
One quick note… you mention the use of ’setInterval’ for repeated checking of the upload dir, but in the code you use ’setTimeout’. Shouldn’t it be ’setInterval’ for repeated checking?
mike kidder Jan 8
Does anyone have a working version of this that works in Beta3 release? If you can post a link, it would be appreciated.
tom Feb 8
great uploader! thank you
Flex Development Mar 13
I downloaded to test but not able to install, its saying not work with this version Adobe AIR.
I have downloaded the later Adobe AIR, its not supporting that.
arama motoru May 3
Mr. Hoyt,
Please, call me Grild. Your application works when embedded in another Apollo application using the mx:HTML class, I just needed to set the exposeRuntime=”true” , It would make more sense to port it actionscript though. Thanks again…