Saturday, 21 November 2015

Web Tutorial: Asynchronous File Upload

In any web interface, there will come a time when you'll need to upload files. Maybe it's for a gallery, or a form submission portal. Whatever the case may be, you may also have come across the trickiness of making this file transfer asynchronous.

Why is it necessary? 

It's not necessary, per se. An asynchronous file upload merely saves you the hassle of managing yet another page load. If you're fine with the classic synchronous way of doing it, hey, no sweat. But if you want to upload a file without having to reload your current page or go to a new page, read on.

Is this AJAX? 

No, this is not AJAX. It's definitely asynchronous though, in the sense that you don't reload the current page or go to a new page. But this doesn't return anything to your browser by way of a AJAX object either; so, no, it's not AJAX.

There is a way to do this using AJAX. It's possible now with HTML5. See the example at this link.

But until these specifications become more mainstream, you may want to use the workaround, which this web tutorial is all about! For this, you need a front-end HTML file and a back-end scripting file, which, for the purposes of this tutorial, will be written in PHP.

Here's the front-end code.

tt_afu.html
<!DOCTYPE html>
<html>
    <head>
        <title>Asynchronous File Upload</title>
    </head>
    <body>
        <form id="frmUpload" name="frmUpload" action="tt_afu.php" method="POST" enctype="multipart/form-data" target="">
                <label for="flUpload">File</label>
                <input type="file" name="flUpload" id="flUpload">
                <input type="hidden" name="hidUploadSize" id="hidUploadSize" value="50000000">
                <input type="submit" name="btSubmit" id="btSubmit" value="Upload!">
        </form>
    </body>
</html>

The front-end is supposed to look like this:


The file element is all you need to select whatever file you wish from your file system. The form tag has the following attributes:

action = "tt_afu.php" - this says that you are sending data to the tt_afu.php page.
method = "POST" - this tells the client that you are sending data via POST.
enctype= "multipart/form-data" - this tells the client that you are sending non-text data.
target = "" - this will come in handy later

And this is the back-end code for handling the file.

tt_afu.php
<?php
$strmessage="";
    
if (basename($_FILES["flUpload"]["name"])!="")
{
    $uploadsize= intval($_POST["hidUploadSize"]);

    $filetype = pathinfo($_FILES["flUpload"]["name"],PATHINFO_EXTENSION);
    $filetype=strtolower($filetype);
   
    if ($_FILES["flUpload"]["size"] > $uploadsize)
    {
        $strmessage = "Error was encountered while uploading file. File cannot exceed " . ($uploadsize/1000)  .  "kb";
    }
    else
    {
        $filecode=strtotime("now").rand();
        if (move_uploaded_file($_FILES["flUpload"]["tmp_name"], "uploads/" . $filecode . "." . $filetype))
        {
            $strmessage = "File successfully uploaded.";
        }
        else
        {
            $strmessage = "Error was encountered while uploading file.";
         }
    }
}
else
{
    $strmessage="No file selected.";
}

echo $strmessage;
?>

A brief explanation of what the code does, is in order. First we set a variable  $strmessage. This variable will be altered as the script is executed, and then its value displayed at the end of it. Now, the script checks for the uploaded file, which is stored in PHP's superglobal array $_FILES when the HTML form sends the selected file.

You can learn more about the superglobal $_FILES here. (http://php.net/manual/en/reserved.variables.files.php)

If a file is present, we extract the file extension from the $_FILES array.

Next is an If-else statement to check if the file size exceeds the limit provided in the hidUploadSize text box. If there's no problem on that end, a random filename is generated using strtotime("now").rand(). This is completely arbitrary on my part, and you should feel free to modify it.

Now the script attempts to transfer the file into the sub-directory "Uploads", using the extsting file extension and the newly generated filename. The value of $strmessage changes depending on whether or not it succeeds.

Your code, when executed by uploading a file through tt_afu.html, should look something like this. Nothing fancy, just black text on white background.



All right! You have the front-end and back-end. What's next?

First - you test. Make sure you have the "Uploads" sub-directory created. Run your code. Does your file get uploaded? If not, do the appropriate error messages appear? So your code is working. But remember - you want it asynchronous. Which also means it's time to make a few changes!

Don't worry, the changes are small. First, modify your front-end like so.
<!DOCTYPE html>
<html>
    <head>
        <title>Asynchronous File Upload</title>
    </head>
    <body>
        <form id="frmUpload" name="frmUpload" action="tt_afu.php" method="POST" enctype="multipart/form-data" target="ifrm_upload">
                <label for="flUpload">File</label>
                <input type="file" name="flUpload" id="flUpload">
                <input type="hidden" name="hidUploadSize" id="hidUploadSize" value="50000000">
                <input type="submit" name="btSubmit" id="btSubmit" value="Upload!">
        </form>

        <iframe id="ifrm_upload" name="ifrm_upload" style="display:none">

        </iframe>
    </body>
</html>

This creates the ifm_upload iframe, whose display property is set to none, effectively hiding it from the user's eyes. Then in the form tag, set the form to post data to the iframe, using the target property. Told you it would come in useful!

Test your code again. Does it still work? You'll notice that your file gets successfully uploaded, but now error messages no longer work. Don't sweat it. They're still there, but since they're now within the ifm_upload iframe which is hidden, you won't see them. Go on, change the code, upload another file and see what happens.


        <iframe id="ifrm_upload" name="ifrm_upload" style="display:block">

        </iframe>

Now take a look. Now that your iframe is visible, the messages appear! It's pretty ugly though.


Unlike AJAX, this method does not return data to the front-end. We need a workaround. So what we do is modify the back-end with some JavaScript!
<?php
$strmessage="";
    
if (basename($_FILES["flUpload"]["name"])!="")
{
    $uploadsize= intval($_POST["hidUploadSize"]);

    $filetype = pathinfo($_FILES["flUpload"]["name"],PATHINFO_EXTENSION);
    $filetype=strtolower($filetype);
  
    if ($_FILES["flUpload"]["size"] > $uploadsize)
    {
        $strmessage = "Error was encountered while uploading file. File cannot exceed " . ($uploadsize/1000) .

"kb";
    }
    else
    {
        $filecode=strtotime("now").rand();
        if (move_uploaded_file($_FILES["flUpload"]["tmp_name"], "uploads/" . $filecode . "." . $filetype))
        {
            $strmessage = "File successfully uploaded.";
        }
        else
        {
            $strmessage = "Error was encountered while uploading file.";
            }
    }
}
else
{
    $strmessage="No file selected.";
}
?>

<script>
    parent.document.getElementById("pnlMessage").innerHTML="<?php echo $strmessage;?>";
</script>

parent, in this case, refers to your tt_upload.html file. And of course, this means you now have to create the pnlMessage div in your front-end, to hold the error message (if any). Turn your iframe invisible again as well.
<!DOCTYPE html>
<html>
    <head>
        <title>Asynchronous File Upload</title>
    </head>
    <body>
        <div id="pnlMessage" style="color:#FF0000">&nbsp;</div>
        <form id="frmUpload" name="frmUpload" action="tt_afu.php" method="POST" enctype="multipart/form-data" target="ifrm_upload">
            <label for="flUpload">File</label>
            <input type="file" name="flUpload" id="flUpload">
            <input type="hidden" name="hidUploadSize" id="hidUploadSize" value="50000000">
            <input type="submit" name="btSubmit" id="btSubmit" value="Upload!">
        </form>

        <iframe id="ifrm_upload" name="ifrm_upload" style="display:none">

        </iframe>
    </body>
</html>

Run the code one more time. Does the message appear in red above your form?


Congratulations! You have an Asynchronous File Upload!

It's a cheap and dirty fix. Almost like cheating, really, but it works. And it's been working for a very long time. Until you feel like doing it the HTML5 way, this is a great fallback!

Confused yet? Worry not, it'll sync in.
T___T

No comments:

Post a Comment