dConstructing

Making SWFUpload work

When it comes to programming, I’m not the biggest fan of libraries. They’re packages of functions that you can use to make complex tasks more simple. That part I like, but what I don’t like is importing large chunks of data just to do a few simple things. What I hate most of all is libraries that don’t work. Too often I’ve found myself pouring through someone’s code to try to understand what it does and what it doesn’t do. Every now and then I come across a project that I just can’t complete without the help of a library. This was just that type of project.

Background

I built a web-based file repository system for a client a few months ago and they came back to me with a request. They had recently been uploading some large files to the site and wanted some sort of visual cue about the status of the upload. Not a simple throbber, but something that would indicate that the upload was in fact working and how far along it was. They wanted a progress bar.

The site was programmed in PHP, so I set about to find a way to create a progress bar in PHP. Here is my post about the struggles implementing a PHP upload progress bar. Next, I tried implementing something in PERL. The server wasn’t liking any CGI PERL scripts without some additional modules, so I had to scrap that. My last resort was Flash.

I don’t know Flash, so I had to find a prepackaged solution, but something that was customizable enough to let me integrate it the way I wanted to. Eventually I stumbled on SWFUpload (version 2.2.0.1), a JavaScript/Flash progress bar library that gives access to upload information, but leaves the user interface completely up to you.

Understanding SWFUpload

SWFUpload uses a small Flash file to handle file uploads. Flash provides feedback about a file as it’s being uploaded (unlike PHP), and SWFUpload is a handy wrapper that passes that information back to JavaScript so you can use it how you’d like. When you set up the SWFUpload object, you pass it a number of parameters so the Flash uploader treats your file the way you want it to. You can also set up a series of callback functions that can be used to trigger specific actions at various points along the path of an upload.

An upload with SWFUpload, step by step

I needed to submit some additional user input along with the file upload. Doing them at the same time is just not possible with SWFUpload. It took some time to figure out, but I finally got SWFUpload to do what I needed. Here’s what’s going on, step-by-step.

  1. The page loads
  2. JavaScript loads SWFUpload and puts it in place on the form.
  3. The user selects a file to upload.
  4. The user fills out additional information on the form.
  5. The user submits the form (clicks the button or presses the enter key).
  6. The form does not actually submit. The submit action triggers the file upload.
  7. When the file upload is complete, JavaScript places the new file ID in a hidden field in the form.
  8. JavaScript collects the form information does an AJAX post to a PHP script.
  9. PHP processes the form input and returns a result.
  10. If the result is good, JavaScript redirects to a new page.
  11. If the result is bad, JavaScript reconfigures the form’s submit button to trigger the AJAX post again.
  12. The user submits the form again, triggering an AJAX post.

Prepping the HTML

First I downloaded the Core .zip file and unzipped it on my computer. Then I copied swfupload.js and swfupload.swf to a location within the site files of the site I was working on. I included the JavaScript file with a line something like this:

<script type="text/javascript" src="http://www.example.com/path/to/js/swfupload.js"></script>

Then I created a form. This was just a normal form except for one small detail. The submit event was overwritten with JavaScript to trigger an upload instead. Normally I do this unobtrusively, but here’s what I did for testing purposes. Note, the form enctype doesn’t matter because the form never actually submits a file. You can leave out the action if you’d like too, but I leave it in because my AJAX poster was written to post to whatever URL the form’s action attribute is set to.

<form method="post" action="handle_form.php" id="create-file" onsubmit="return uploadFile(this, event);">
</form>

In that form I created an input box that would look like your standard HTML file selection box. There’s an empty text field, an empty <div> where SWFUpload would go (disguised as a button), and a hidden input field which SWFUpload would fill with an ID number once the upload was complete.

<input type="text" name="filename-text" id="filename-text" />
<div id="swfupload-container"></div>
<input type="hidden" name="hidFileID" id="hidFileID" value="" />

More form fields were created all around this upload box.

I also created this set up nested <div>s to reflect the upload progress. The outer <div> serves as a border. The width of the inner <div> increases as the download progresses to eventually fill the outer <div> with whatever color I specify in the CSS. These <div>s could go anywhere on the page, but I put mine within the form.

<div id="upload-progressbar-container">
    <div id="upload-progressbar">
    </div>
</div>

Every thing from here on was just writing the right JavaScript to get the .swf file to work the way I want it to.

Configuring SWFUpload

You want your SWFUpload object to be created after the page is fully loaded. For that reason, the JavaScript creation function starts off looking something like this:

var swfu;
window.onload = function () {
    var settings_object = {
        name: value,
        name2: value2
    };
    swfu = new SWFUpload(settings_object);
};

The rest of the configuration just involves getting all those name/value pairs right.

Firstly, let’s tell JavaScript where the swfupload.swf Flash file is located.

flash_url: 'http://www.example.com/path/to/swf/swfupload.swf'

Next, let’s tell JavaScript where we want Flash to send the uploaded file. This should be the location of a regular file upload handler page. It will receive a file just like it would with a regular HTML upload form

upload_url: 'http://www.example.com/upload_handler.php'

These next couple aren’t required, but I put them in. The first is the variable name of the file being uploaded (what you would see in PHP’s $_FILES array– $_FILES['Filedata']), and the second is a maximum upload file size.

file_post_name: 'Filedata' /* officially recommended to leave this as 'Filedata' */
file_size_limit: '300 MB'

Now we’ll tell JavaScript where to place SWFUpload. The first parameter is the ID of the <div> we’ve created for SWFUpload (note: SWFUpload will replace the <div> with the object, not fill the <div> with the object). The second parameter is the image we are using to represent SWFUpload. When this image is clicked, SWFUpload will prompt the user to select files to upload. The last two parameters are the height and width of the button. The image we are using is actually 4x the size of the button, and SWFUpload will reposition the button to reflect hover states and button presses.

button_placeholder_id: 'swfuploader-container'
button_image_url: 'http://www.example.com/path/to/img/button-upload.png'
button_width: '61'
button_height: '22'

Here is the image I am using for my button. It’s the one that was included in the SWFUpload package I downloaded. You can use it if you’d like.

Upload button for SWFUpload

SWFUpload also allows you to define custom parameters. I ended up using one, so I’ll show it here, but I’m sure I could have done it some other way.

custom_settings: {
    upload_successful: false
}

The last step is defining the callback functions. This is actually a pretty important step. I’ll show you how I defined my callback functions and summarize the JavaScript configuration and then go on to show you what my callback functions do.

file_queued_handler: fileQueued
upload_progress_handler: uploadProgress
upload_error_handler: uploadError
upload_success_handler: uploadSuccess
upload_complete_handler: uploadComplete

So here is the entirety of my JavaScript configuration:

var swfu;
window.onload = function () {
    var settings_object = {
        // Basic settings
        flash_url: 'http://www.example.com/path/to/swf/swfupload.swf',
        upload_url: 'http://www.example.com/upload_handler.php',
        file_post_name: 'Filedata', /* officially recommended to leave this as 'Filedata' */
        file_size_limit: '300 MB',

        // Button settings
        button_placeholder_id: 'swfuploader-container',
        button_image_url: 'http://www.example.com/path/to/img/button-upload.png',
        button_width: '61',
        button_height: '22',

        // Custom settings
        custom_settings: {
            upload_successful: false
        },

        // Callback functions
        file_queued_handler: fileQueued,
        upload_progress_handler: uploadProgress,
        upload_error_handler: uploadError,
        upload_success_handler: uploadSuccess,
        upload_complete_handler: uploadComplete
    };
    swfu = new SWFUpload(settings_object);
};

Now on to . . .

Callbacks and other functions

This is where it gets tricky. More callbacks are available in addition to the ones I’ve used here. Also the site already had the prototype library in use, so I used some prototype shortcuts to make things happen. Prototype is not necessary for SWFUpload to work though.

fileQueued – after the user clicks the Browse button and selects a file to upload

/**
 * This function places the name of the file in the empty text box we created earlier.
 * This serves no actual functional purpose. It's just a visual effect.
 */
function fileQueued(file) {
    $('filename-text').setValue(file.name);
}

uploadFile – When the user clicks the form’s submit button or presses the enter key. This is not a callback, but is hard-coded into the form.

/**
 * This function tells SWFUpload to start uploading the queued files.
 */
function uploadFile(form, e) {
    try {
        swfu.startUpload();
    } catch (ex) {
    }
    return false;
}

uploadProgress – Called at predefined intervals by SWFUpload while a file is being uploaded.

/**
 * This function reads progress information.
 * It reveals the progress bar and expands the inner div to reflect upload progress.
 */
function uploadProgress(file, bytesLoaded, bytesTotal) {
    try {
        var percent = Math.ceil((bytesLoaded / bytesTotal) * 100);
        $('upload-progressbar-container').setStyle({display:'block'});
        $('upload-progressbar').setStyle({width:percent+'%'});
    } catch (e) {
    }
}

uploadError – Called by SWFUpload if there is an error during file upload.

/**
 * This function is huge.
 */
function uploadError(file, errorCode, message) {
}

uploadSuccess – Called by SWFUpload when a file has been successfully uploaded.

/**
 * This function handles data that is returned from the PHP script.
 * That data is just whatever the PHP script echos to the page (no JSON or XML).
 * This function expects an ID representing the new file and places it
 * in the hidden field in the form.
 * It also sets the custom 'upload_successful' parameter to true for use later.
 */
function uploadSuccess(file, serverData, receivedResponse) {
    try {
        if (serverData === " ") {
            this.customSettings.upload_successful = false;
        } else {
            this.customSettings.upload_successful = true;
            $('hidFileID').setValue(serverData);
        }
    } catch (e) {
    }
}

uploadComplete – Called by SWFUpload every time an upload process ends, error or success.

/**
 * If the upload was completed successfully, this function calls the function
 * that submits the entire form via AJAX.
 * If the upload was unsuccessful, this function alerts an error message.
 */
function uploadComplete(file) {
    try {
        if (this.customSettings.upload_successful) {
            var form = $('create-file');
            submitForm(form); // Not described here.
            // The above function is not described here.
            // It is a function that receives a form element as input
            // and constructs POST data from all the user input found on that form.
            // It then submit that data via AJAX and calls fileUploaded();
        } else {
            alert('There was a problem with the upload.');
        }
    } catch (e) {
    }
}

fileUploaded – The AJAX form submission function calls this function when data has been received. This is not a SWFUpload callback function.

/**
 * This file decides what do to after the entire form has been submitted.
 * It expects a JSON object with a 'message' and 'status' value.
 * The message value is always displayed to the user.
 * If the server returns a successful status, the upload and processing of form
 * data is complete and the user is redirected to another page.
 * If the server returns an unsuccessful status, the form submit action
 * is reconfigured to try submitted the form again rather than try the entire upload.
 */
function fileUploaded(json) {
    alert(json.message);
    if (json.status == 1) {
        window.location = 'upload_successful.php';
    } else {
        form.setAttribute('onsubmit','submitForm(this);return false;');
    }
}

That’s all my functions. Leave a comment if you have any questions.

Complications

One of the things I like doing is passing along additional POST values when I submit a form so that I have some way of verifying that the form really should be processed. SWFUpload provides a method of passing along additional GET/POST parameters during the file upload, but I never could get it to work. Luckily I was able to include a session id variable in the URL specified in the upload_url parameter (essentially a GET variable). But, I couldn’t send any more than one parameter in the URL and I couldn’t send any sort of parameters (GET or POST) using the configuration parameter that SWFUpload has established for that purpose.

Lastly, in case it wasn’t clear earlier, when SWFUpload uploads a file you still have to program a page to do the receiving. You do that exactly the same way as you would receive a file that was uploaded with a traditional HTML upload form. If it’s unclear how that’s to be done, this is my favorite PHP file upload tutorial. The PHP $_FILES variables that are generated by a SWFUpload uploaded file are exactly the same as an HTML uploaded file, except the mime-type is different. The mime-type is always application/x-shockwave-flash so if you need to processes the file according to its mime-type you’ll have to find it some other way.

I hope this extra-long post has helped. Leave a comment if you have questions and I’ll try and provide guidance if I have any to give.