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 :
- I get the dropped file(s) and read it's content (asynchronously, but take some time...)
- I send this content with an AJAX request to the server
- 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);
?>