Last Thursday, I gave a talk at @JSSophia, the local Javascript User Group I co-founded with @FredGuillaume.
The group is just starting (2nd meeting), so there were only a few people, but as some of them looked quite interested by my talk, and some other couldn’t come due to personal or professional duties, I thought I could write a couple of blog posts about the same topic.
I choose File Upload Form example because it’s standalone, frequently used and it can be improved by many ways with HTML5 APIs.
It’s a good example to introduce some very important Javascript concepts:
- Using feature detection for progressive enhancement
- Using loosely coupled modules to architecture web applications.
This post focus on the first part of the talk.
It presents the feature detection technique.
I’ll cover the second part, loosely coupled modules, in another article.
If you’re in a hurry, or simply don’t want to read the whole post, you’ll find the slides embedded below and everything else on Github:
- The final uploader-thumbnail source code.
- The step by step demo I used during the talk.
- The slides.
The talk was in French so the slides are also written in French, even if it uses a lot of English keywords.
Initial Markup
The main idea in progressive enhancement is to provide an application that work in any context.
A good approach is to start development with features that will work (quite) everywhere, and progressively add more specific features to improve your application’s user experience in modern browsers.
Talking about file upload form, our starting point is a simple HTML markup.
1 | <form id="upload-form" action="" method="post" enctype="multipart/form-data"> |
It’s simple, works in every browser and, assuming the server behind do his job, it works without a single line of Javascript.
On the other hand, it requires a full page reload so the first thing to do to bring some hype in this is to allow uploading the file with an asynchronous request.
Feature detection
Uploading a file trough an asynchronous request isn’t that easy.
The FormData API perfectly fits our needs but it’s not well supported across all browsers (IE, I’m looking at you… See [Browser_compatibility](https://developer.mozilla.org/en/DOM/XMLHttpRequest/FormData#Browser compatibility) section).
Remember that our main concern is to provide the best user experience on each browser.
So how do we upload a file asynchronously in a browser that don’t support FormData
API?
Answer is by using an iframe
.
iframe file upload
Please note that I started by creating a jQuery plugin to make the code more easily reusable.
I also hid the submit button and the bound event ‘onchange’ on the input field to submit the form.
The following code snippets come from step1/upload.js file.
First, we have to listen to the submit
event to prepare the form:
1 | this.$form.on('submit.uploader', $.proxy(function(){ |
Next, let’s append an hidden iframe
to the form and define the target
attribute to match the iframe
id.
Once done, the form can be submitted as usual, the server’s answer will be loaded into the iframe
.
However, due to security concerns, we won’t be able to read the iframe
content once loaded, so we also need to create a callback function and to send the function name to the server as a URL parameter.
This way, the server script will be aware that we are using an iframe
and will be able to generate the appropriate response.
1 | prepareIframeUpload: function() { |
Knowing that the server response will be loaded as iframe
content, the server script has to generate this small piece of HTML.
It includes a script
tag, witch calls the callback
function on the parent window. The json result
is send as a parameter of that function.
1 | <script type="text/javascript"> |
Here we are.
Our script can send files asynchronously, without reloading the whole page, and it even works with old browsers.
Of course, we could decide to stop there, but we won’t because of the following:
- No Error handling: if something goes wrong while sending the file, or if the server don’t render the good response, the callback function will never be called, and we can’t handle the error.
You probably want to add a timeout to the script above to avoid waiting for an answer that would never come. - It’s not AJAX.
You probably already notice this point.
We are faking it.
The form is still sent as HTML form, we only changed his target.
The file is uploaded asynchronously, but without any XmlHttpRequest involved. - It’s dirty.
I’m OK as it stays a fall-back solution, but keeping it as the main implementation? Yuck!
FormData file upload
Time to do things the right way? OK. Let’s start by editing the submit event listener as following:
1 | this.$form.on('submit.uploader', $.proxy(function(){ |
The this.$upload
variable represents a jQuery object containing the input[type=file]
DOM node (see the complete step2/upload.js file for more details).
Here we have to check if the browser supports both File
and FormData
APIs.
If these two conditions are satisfied, we can go with the “HTML5” file upload.
Otherwise, we just fall-back to the iframe
hack… Simple isn’t it?
This is Feature Dectection and it’s one of the key concepts of modern web development.
It’s the only way we have to use the latest HTML5 features without breaking old browser’s support.
Now, we’re sure that we can use FormData
upload, we just need to implement the method as shown in the following code extract.
As you can read, it’s way simpler and less hacky compared to the iframe
method.
Server response and errors are handled the same way than with any other ajax request.
1 | upload: function(file) { |
We’re done for part 1.
Our upload form is fully functional for both modern and old browsers, and even with Javascript disabled.
In the 2nd part, I explain how to handle the thumbnail associated to the file input field.
It’s a very good example to introduce loosely coupled modules, and to show some other uses of the feature detection technique.
File Upload Form - Part 2: Loosely Coupled Modules
References:
- Browser detection using the user agent on MDC
- FormData API
- FileList API