0

I am building a JavaScript file uploader based on XHR. (Ajax in fact as I use jQuery) I use some HTML5 features as the Drag&Drop API, and - at the moment - the FileReader API.

I have some questions about the process, because there are some things I do not understand, theoretically.

I - voluntarily - choose to not use one of the existing and well designed plugins / standalone uploaders for some reasons :

  • I have some specific needs, by example to add a title and description (as in facebook) to the item
  • I would to implement it myself as a try for the HTML5 API's.

I've read a tutorial Upload Files with Ajax but I find strange to use the FileReader API to finally append the images to the FormData API.

More than it, I do not really would to have a form, I just drop the images in a drop-area, get the files content with the FileReader API then send it with an AJAX request to my server.

I am just interested to know if someone's got a better process idea, because at the moment :

  1. I get the dropped file(s) and read it's content (asynchronously, but take some time...)
  2. I send this content with an AJAX request to the server
  3. I write the content to a physical file on the server (take again some time)

I don't see how that could be optimized / faster, but it is silly to see the time for a big picture, and I just don't want to imagine it for a video by example... Is it possible by example to only send a reference of the file with Ajax, and manage the upload directly, instead that get the file content then wait that the user click on the send button to send it ?

By example, how does the facebook uploader work, they seems to solve the problem / use something to fix it ?

(If someone is interested to get it or to take a look, here are a small sample of what I have done at the moment)

/* HTML */
<div id='drop-area'>
    <h3 class='placeholder'>Drop your files there!</h3>
</div>

/* CSS */
#drop-area {
    position: relative;
    width: 100%;
    height: 400px;
    border: 1px dashed grey;
    border-radius: 10px;
}
#drop-area.drag-over {
    border: 1px solid cyan;
}
#drop-area .placeholder {
    position: absolute;
    top: 50%;
    left: 50%;
    width: 235px;
    height: 25px;
    margin: -12px 0 0 -117px;
    line-height: 25px;
    text-transform: uppercase;
}

/* JavaScript - jQuery way */
// This code is NOT AT ALL full-browser compatible !!
$(document).ready(function () {
    var i,
        $dropArea = $('drop-area'),
        fileReader,
        file,
        result,
        sendRequest = function () {
            $.ajax({
                url: 'upload.php',
                type: 'post',
                data: {
                    title: $('#title').val(),
                    description: $('#description').val(),
                    data: result, // base64 encoded data
                    type: file.type
                },
                processData: false, // No query string
                contentType: false // No data formatting
            }).fail(function (error, status) {
                console.log(error, status);
                alert('Fail...');
            }).done(function (response, output) {
                console.log(response, output);
                alert('Uploaded!');
            }).always(function () {
                $('#file-form-container').empty().detach();
            });
        },
        onFileFormSubmit = function (e) {
            e.preventDefault();
            sendRequest();
            return false;
        },
        displayForm = function (data) {
            $('body').append($('<div/>', {
                'id': 'file-form-container'
            }).append($('<form/>', {
                'action': '#',
                'id': 'file-form'
            }).append($('<img/>', {
                'src': data,
                'alt': 'File',
                'width': 100px
            })).append($('<input/>', {
                'id': 'title',
                'type': 'text',
                'name': 'title'
            })).append($('<textarea/>', {
                'id': 'description',
                'class': 'description',
                'name': 'description'
            })).append($('<input/>', {
                'type': 'submit',
                'name': 'submit',
                'value': 'Send!'
            }))));
        },
        onProgress = function (e) {
            console.log('progress', e);
            // Update a <progress> element...
        },
        onLoadEnd = function (e) {
            console.log('load end', e);
            result = e.target.result;
            displayForm(result);
        },
        onError = function (e) {
            console.log('error', e);
            // Display a message...
        }
        readFile = function (file) {
            fileReader = new FileReader();
            fileReader.onprogress = onProgress;
            fileReader.onerror = onError;
            fileReader.onloadend = onLoadEnd;
            fileReader.readAsDataURL(file);
        };

    $dropArea.on('dragenter', function (e) {
        $(this).addClass('drag-over');
    }).on('dragover', function (e) {
        e.preventDefault();
        e.originalEvent.dataTransfer.dropEffect = 'copy'; // Check the browser support if you want to fix the compatibility
    }).on('dragleave', function (e) {
        e.preventDefault();
        $(this).removeClass('drag-over');
    }).on('dragend', function (e) {
        e.preventDefault();
        $(this).removeClass('drag-over');
    }).on('drop', function (e) {
        // e || e.originalEvent ?
        e.preventDefault();
        e.stopPropagation();
        $(this).removeClass('drag-over');

        if (e.originalEvent.dataTransfer.files) {
            for (i = 0, i < e.originalEvent.dataTransfer.files.length; i++) {
                // Work with arrays to manage multiple files
                file = e.originalEvent.dataTransfer.files[i];
                readFile(file); // Read the file content then process the upload (callbacks)
            }
        }
    })
});

/* PHP - Symfony2 */
<?php

// Symfony 2 Controller Context

$em = $this->getDoctrine()->getEntityManager();

// get params
$request = $this->getRequest();

// Check for types, sizes, errors etc.
if ($request->get('data') && [...]) {
    [...]
}

// write the decoded data into a file
// fwrite(base64_decode(...));
// By the way, is there any OOP - MVC way to do that with Symfony2 ?
$path = ''; // define the file path


// Save the entity
$entity = new Picture();
$entity->setTitle($request->get('title'));
$entity->setDescription($request->get('description'));
$entity->setPath($path);
$em->persist($entity);
$em->flush();

return new JsonResponse(array($entity), 202);

?>
Flo Schild
  • 5,104
  • 4
  • 40
  • 55
  • Why are you "reading the content" of these files client-side? Why not just send the File object? Also, as you probably know, the File API is not supported in older browsers, (such as IE9 and earlier). Furthermore, if you want to learn how to do this, why don't you look at the source code of some of the existing plug-ins that already do all of this? Since you insist on re-inventing the wheel, why not study how the wheel has been successfully manufactured thus far? – Ray Nicholus Mar 06 '13 at 14:40
  • I know for the old-browsers support but it doesn't matter as the project is full webkit targeted. About the file object, is it a part of the HTML5 File API so ? I don't know so much about that. And about the plugins, in fact I didn't really see in existing plugins what I was looking for, but if you have a good example, I would like to know it ! EDIT: - Maybe Valums ? :) - – Flo Schild Mar 06 '13 at 14:59
  • Fine Uploader (valums/file-uploader) may provide some clues, yes. – Ray Nicholus Mar 06 '13 at 16:15

0 Answers0