Resize, preview and upload an image with AJAX - Complete example
Hello,
I wanted to do an Ajax script to resize and preview and image in an html form. Well, I didn't find how to do with Ajax, so I had to use php.
The issue is that JavaScript cannot manage a file field to get the selected file and send it via Ajax. The only way to send a file is submitting a form. To not send all data of the form, the file field must be a separate form from the rest of the data. So it can be automatically sent to a php script, which resizes the image, uploads it and then can display it in a iframe without reloading the page.
Then you have the resized image previewed in the form without submitting all data. And then you can send all data with Ajax, including the path of the previously uploaded image, which is stored as a variable in the iframe.
I use one html file and two php scripts, which in total are less than 20 KB.
What do you think about this approach?
Kind regards
HTML Code:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"><!--
Author: Alberto Moyano Sánchez
Date: 2009-05-24
Description: [...]
Notes and improvements: [...]
--><html><head><title>
Upload and preview an image with Ajax
</title><script language="JavaScript" type="text/javascript">
var loadingHtml = "Loading..."; // this could be an animated image
var imageLoadingHtml = "Image loading...";
var http = getXMLHTTPRequest();
//----------------------------------------------------------------
function uploadImage() {
var uploadedImageFrame = window.uploadedImage;
uploadedImageFrame.document.body.innerHTML = loadingHtml;
// VALIDATE FILE
var imagePath = uploadedImageFrame.imagePath;
if(imagePath == null){
imageForm.oldImageToDelete.value = "";
}
else {
imageForm.oldImageToDelete.value = imagePath;
}
imageForm.submit();
}
//----------------------------------------------------------------
function showImageUploadStatus() {
var uploadedImageFrame = window.uploadedImage;
if(uploadedImageFrame.document.body.innerHTML == loadingHtml){
divResult.innerHTML = imageLoadingHtml;
}
else {
var imagePath = uploadedImageFrame.imagePath;
if(imagePath == null){
divResult.innerHTML = "No uploaded image in this form.";
}
else {
divResult.innerHTML = "Loaded image: " + imagePath;
}
}
}
//----------------------------------------------------------------
function getXMLHTTPRequest() {
try {
xmlHttpRequest = new XMLHttpRequest();
}
catch(error1) {
try {
xmlHttpRequest = new ActiveXObject("Msxml2.XMLHTTP");
}
catch(error2) {
try {
xmlHttpRequest = new ActiveXObject("Microsoft.XMLHTTP");
}
catch(error3) {
xmlHttpRequest = false;
}
}
}
return xmlHttpRequest;
}
//----------------------------------------------------------------
function sendData() {
var url = "submitForm.php";
var parameters = "imageDescription=" + dataForm.imageDescription.value;
var imagePath = window.uploadedImage.imagePath;
if(imagePath != null){
parameters += "&uploadedImagePath=" + imagePath;
}
http.open("POST", url, true);
http.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
http.setRequestHeader("Content-length", parameters.length);
http.setRequestHeader("Connection", "close");
http.onreadystatechange = useHttpResponse;
http.send(parameters);
}
//----------------------------------------------------------------
function submitFormIfNotImageLoading(maxLoadingTime, checkingIntervalTime) {
if(window.uploadedImage.document.body.innerHTML == loadingHtml) {
if(maxLoadingTime <= 0) {
divResult.innerHTML = "The image loading has timed up. "
+ "Please, try again when the image is loaded.";
}
else {
divResult.innerHTML = imageLoadingHtml;
maxLoadingTime = maxLoadingTime - checkingIntervalTime;
var recursiveCall = "submitFormIfNotImageLoading("
+ maxLoadingTime + ", " + checkingIntervalTime + ")";
setTimeout(recursiveCall, checkingIntervalTime);
}
}
else {
sendData();
}
}
//----------------------------------------------------------------
function submitForm() {
var maxLoadingTime = 3000; // milliseconds
var checkingIntervalTime = 500; // milliseconds
submitFormIfNotImageLoading(maxLoadingTime, checkingIntervalTime);
}
//----------------------------------------------------------------
function useHttpResponse() {
if (http.readyState == 4) {
if (http.status == 200) {
divResult.innerHTML = http.responseText;
dataForm.reset();
imageForm.reset();
window.uploadedImage.document.body.innerHTML = "";
window.uploadedImage.imagePath = null;
}
}
}
</script></head><body><h3>
Form with preview of resized image with Ajax
</h3><form id="dataForm" name="dataForm">
Image description:
<input name="imageDescription" id="imageDescription" type="text"
size="30"/></form><form id="imageForm" name="imageForm" enctype="multipart/form-data"
action="uploadImage.php" method="POST" target="uploadedImage"><!-- Next field limits the maximum size of the selected file to 2MB.
If exceded the size, an error will be sent with the file. --><input type="hidden" name="MAX_FILE_SIZE" value="2000000" />
Image:
<input name="imageToUpload" id="imageToUpload" type="file"
onchange="uploadImage();" size="30"/><br />
Old uploaded image to delete (this field should be hidden):
<br /><input name="oldImageToDelete" id="oldImageToDelete" type="text"
size="50" /></form><iframe id="uploadedImage" name="uploadedImage" src=""
style="width:200px; height:200px;"
frameborder="0" marginheight="0" marginwidth="0"></iframe><br /><br /><form><input type="button" onclick="submitForm();" value="Submit" /></form><br /><form><input type="button" onclick="showImageUploadStatus();" value="Show image upload status" /></form><div id="divResult"></div></body></html>
<?php
/*
Author: Alberto Moyano Sánchez
Date: 2009-05-24
Description:
- The script receives a file and a text with method="post".
- The file is validated to check that is a supported type of image, has no
errors and is not larger than the maximum size allowed.
- If the dimensions of the image are larger than the maximum, it is resized.
- The image is uploaded to a temporary directory in the server.
- If the uploading was right, the script outputs an img html tag with the
uploaded image, and a JavaScript variable with the temporary path of the
uploaded image. If there was any error, the script outputs an error message.
- If a text is sent a together with the file, it means that there was an
image previously uploaded. This text is the path of the previously uploaded
image, and it is removed before the new image is uploaded.
*/
$pathToUpload = "uploads_temp/";
$fileFieldName = "imageToUpload";
// "image/pjpeg" is the type jpg for Internet Explorer
$allowedImageTypes = array("image/png", "image/jpg", "image/jpeg",
"image/pjpeg", "image/gif");
$maxImageSize = 2000000; // in bytes
//-----------------------------------------------------------------------------
function displayImage($imagePath) {
// BUG: the uppercase is very important. The extension of the image must
// be uppercase, if not, it is displayed distorted (Firefox 3.0.10)
echo "<img src='" . strtoupper($imagePath) . "' />";
echo "<script type = 'text/javascript'>";
echo "var imagePath = '" . $imagePath . "';";
echo "</script>";
// delay to test the uploading of the image
//sleep(6);
}
//-----------------------------------------------------------------------------
// the functions imagecreateXXX raise a fatal error when trying
// to create an image larger than 3 MB
function createImageFactory($imagePath, $imageType) {
$image = null;
if($imageType == "image/png") {
$image = imagecreatefrompng($imagePath);
}
else if($imageType == "image/gif") {
$image = imagecreatefromgif($imagePath);
}
else {
$image = imagecreatefromjpeg($imagePath);
}
return $image;
}
//-----------------------------------------------------------------------------
function saveImage($image, $imagePath, $imageType) {
$resultSave = false;
if($imageType == "image/png") {
$resultSave = imagepng($image, $imagePath);
}
else if($imageType == "image/gif") {
// if the gif is animated, the function imagegif doesn't keep the
// animation. The animation is lost in the process creating, resampling
// and saving the image
$resultSave = imagegif($image, $imagePath);
}
else {
$resultSave = imagejpeg($image, $imagePath, 80);
}
return $resultSave;
}
//-----------------------------------------------------------------------------
function createNewImage($width, $height, $imageType) {
$image = imagecreatetruecolor($width, $height);
// set the transparency if the image is png or gif
if(($imageType == "image/png") || ($imageType == "image/gif")) {
imagealphablending($image, false);
imagesavealpha($image, true);
$transparent = imagecolorallocatealpha($image, 255, 255, 255, 127);
imagefilledrectangle($image, 0, 0, $width, $height, $transparent);
}
return $image;
}
//-----------------------------------------------------------------------------
function uploadResizedImage($sourceImagePath, $destinationImagePath, $imageType) {
$resultSave = false;
list($width, $height) = getimagesize($sourceImagePath);
$maximumDimension = 200;
if(($maximumDimension >= $width) && ($maximumDimension >= $height)) {
// with the process of creating, resampling and saving the image, the
// animation of an animated gif is lost. In order to keep the possible
// animation, if the image is smaller than the maximum size, it is
// uploaded without any processing
$resultSave = move_uploaded_file($sourceImagePath, $destinationImagePath);
}
else {
// the function createImageFactory raises a fatal error when trying
// to create an image larger than 3 MB
$sourceImage = @createImageFactory($sourceImagePath, $imageType);
if($width > $height) {
$newWidth = round($maximumDimension);
$newHeight = round($height * ($newWidth / $width));
}
else {
$newHeight = round($maximumDimension);
$newWidth = round($width * ($newHeight / $height));
}
$destinationImage = createNewImage($newWidth, $newHeight, $imageType);
// the function imagecopyresampled is much better than the function
// imagecopyresized, which distorts the image when resizing
$resultResize = imagecopyresampled($destinationImage, $sourceImage,
0, 0, 0, 0, $newWidth, $newHeight, $width, $height);
return $resultSave;
}
//-----------------------------------------------------------------------------
// if there was an image uploaded before, remove it, because it is being
// substituted by another one
$oldImageToDelete = $_POST["oldImageToDelete"];
if(($oldImageToDelete != null) && (file_exists($oldImageToDelete))) {
unlink($oldImageToDelete);
}
// it must be checked first if there is an error with the file (e.g.
// larger than the maximum size allowed by the hidden field MAX_FILE_SIZE
// in HTML) than if type of the file is an allowed type, because if there
// is an error, the type of the file may not be available
if ($_FILES[$fileFieldName]["error"] > 0) {
echo "There is an error with the file.";
echo "<br />";
echo "Error code: " . $_FILES["imageToUpload"]["error"];
}
else if(!in_array($_FILES[$fileFieldName]["type"], $allowedImageTypes)) {
echo "The type of the file is not a supported image type.";
echo "<br />";
echo "FILE: " . $_FILES[$fileFieldName]["name"];
echo "<br />";
echo "File type: " . $_FILES[$fileFieldName]["type"];
}
else if($_FILES[$fileFieldName]["size"] > $maxImageSize) {
// the function createImageFactory, called by uploadResizedImage raises
// a fatal error when trying to create an image larger than 3 MB
echo "The uploaded file is larger than allowed.";
echo "<br />";
echo "Uploaded file size: " . round($_FILES[$fileFieldName]["size"] / 1000) . " KB";
echo "<br />";
echo "Max allowed size: " . round($maxImageSize / 1000) . " KB";
}
else {
$imagePath = $pathToUpload . basename($_FILES[$fileFieldName]["name"]);
if (file_exists($imagePath)) {
unlink($imagePath);
}
if(uploadResizedImage($_FILES[$fileFieldName]["tmp_name"], $imagePath, $_FILES[$fileFieldName]["type"])) {
displayImage($imagePath);
}
else {
echo "There was an error uploading the file" . $imagePath . ", please try again!";
echo "<br />";
echo "If the error continues, the uploaded file may not be accepted by the system.";
}
}
?>
submitForm.php
PHP Code:
<?php
/*
Author: Alberto Moyano Sánchez
Date: 2009-05-24
Description:
- The script receives some data with method="post".
- One of this data may be a path of an image that was previously uploaded
to a temporary directory in the server. If this datum exists, the script
move the image from the temporary directory to a final one.
- The script process the rest of the data and produces some output.
*/
$pathToMove = "uploads/";
That's going to be very annoying if they keep changing their minds or selecting the wrong file with the file being repeatedly uploaded when they don't want it to be. You also upload the final image twice, once when they select it and once when they submit it, which seems very inefficient. If they make a mistake once (there's no cancel), change their minds once and then submit they've uploaded up to 8Mb of data, I don't want to be doing that for a 2Mb file, I doubt other people will either.
I could also go into some things about your implementation, but won't bother given that what you're implementing is fundamentally flawed IMHO.
I suggest you look into another language like Flash or Java which can do this natively.
Thank you for the reply. I'd like to reply it too.
That's going to be very annoying if they keep changing their minds or selecting the wrong file with the file being repeatedly uploaded when they don't want it to be.
The user doesn't have to change his mind. He only selects a file and it is automatically processed. The file can be a wrong type, and it would not be uploaded. It is made with a JavaScript validation (in the uploadImage() function), which I have not included to test the php validation. Sorry, it is in the comments of the html file, which I removed because it was too long for a post.
You also upload the final image twice, once when they select it and once when they submit it, which seems very inefficient.
The file is uploaded only once: when it is selected. When the form is submitted, the file is moved from a temporary directory to the final one. Then it will be much faster because it is done at the server side (not uploading at all), and the image is already resized.
If they make a mistake once (there's no cancel), change their minds once and then submit they've uploaded up to 8Mb of data, I don't want to be doing that for a 2Mb file, I doubt other people will either.
There is a size limitation of 2MB, but it could be 3MB or 500KB. It must be validated with JavaScript previously to send the file. The idea of the system is not to be an image browser, but to upload an image for an avatar or something like this. So it is not expected that the user will upload an 8000KB image to resize it to 20KB.
I could also go into some things about your implementation, but won't bother given that what you're implementing is fundamentally flawed IMHO.
I suggest you look into another language like Flash or Java which can do this natively.
That is what I didn't want to do. I'm using the most simple languages, just plain html and JavaScript, and php at server side. No more.
Here are the complete explanatory comments of the html file. Here it is explained that there should be a JavaScript validation before automatically sending the selected file or where can be adjusted a delay for testing the delay time of uploading.
Regards
HTML Code:
<!--
Description:
- This is a script to upload a form dynamically with Ajax, which contains an
image and more data. The image is displayed in preview when it is selected.
- It consists of one html page and two php scripts. I wanted to do the
funtionality of the first php script, which resizes the selected image,
with Ajax instead of php. But it is not possible due to the limited action
of JavaScript over the file field and the filesystem, because of security
reasons.
- There are actually two forms, one for the image and another for the rest
of the data. (The reason is that JavaScript cannot take the selected file
of the file field. It is explained some points below.) There is also an
iframe which displays the image.
- The image form is automatically submited when a new file is selected. It is
sent to a php script which resizes the image, stores it in a temporary
directory and displays it. The target of the image form is the iframe, so
the image is displayed in it without need of reloading the whole page.
- The php script also writes in the iframe a control variable containing the
temporary path of the uploaded image. This variable is key to dynamically
upload the data form with Ajax.
- I wanted to do the resizing and previewing of the image with JavaScript,
saving the submitting of the image form and the processing of the image
at the server side. However it is not possible, because JavaScript cannot
interact with the filesystem due to security reasons.
- Another handicap is that JavaScript can barely manage the file field, due
to security reasons too. JavaScript can only read the name of the selected
file but not the whole path. JavaScript can neither take the selected file
to send it via Ajax. Thus, the file field cannot be sent via Ajax. The only
way it can be sent is submitting the whole form. Therefore must be a
separate form only for the file field, in order be able to submit only this
field and not the rest of the fields.
- The image form has another hidden field (for testing purpose it is visible)
to store the name of the previous selected file, so it can be removed by
the php script.
- The button to submit the data form takes the data fields and the control
variable of the iframe, which says if there is an uploaded image and what
its temporary path in the server is. A second php script is called, which
moves the uploaded image from the temporary directory to the final one,
and process the rest of the data.
Notes and improvements:
- This script has been tested with Firefox 3.0.10, Opera 9.64, Safari 4
and Internet Explorer 6.0. Everything works fine (with the exception of
the next point).
- The event "onchange" of the file field is well performed by all browsers
when selecting a file with the button "browse". Firefox and Safari don't
allow to write in the field, only to use the button to select a file.
Explorer and Opera allow to write in the text box of the field. Explorer
works fine and trhows the event "onchange" when the field loses the focus.
Opera throws the event "onchange" everytime a key is pressed when typing
in the field, what is not the desired behaviour.
- The file to upload must be validated with JavaScript previously to send
it in the function uploadImage().
- If a file is sent, which is larger than the maximum size especified with
the hidden field MAX_FILE_SIZE, PHP will throw an error, with error code
2. To avoid such message and a futile sending, the file must be previously
validated with JavaScript.
- In order to test the posible delays with the uploads, adjust the sleep
statement in the function displayImage() in the file uploadImage.php.
Adjust also the parameters in the JavaScript function submitForm().
- When an image is uploaded to the temporary directory to preview it, it is
checked if there was a previously previewed image, to remove it from the
temporary directory. However, the temporary directory must be emptied
from time to time, because there will be files from the previewed images
of forms that were finally not submited.
-->
Hello,
The user doesn't have to change his mind. He only selects a file and it is automatically processed. The file can be a wrong type, and it would not be uploaded. It is made with a JavaScript validation (in the uploadImage() function), which I have not included to test the php validation. Sorry, it is in the comments of the html file, which I removed because it was too long for a post.
I mean, what if you select an image, then change your mind and want to select a different one? It's still the right type so will be uploaded.
Originally Posted by AMS
The file is uploaded only once: when it is selected. When the form is submitted, the file is moved from a temporary directory to the final one. Then it will be much faster because it is done at the server side (not uploading at all), and the image is already resized.
Fair enough
Originally Posted by AMS
There is a size limitation of 2MB, but it could be 3MB or 500KB. It must be validated with JavaScript previously to send the file. The idea of the system is not to be an image browser, but to upload an image for an avatar or something like this. So it is not expected that the user will upload an 8000KB image to resize it to 20KB.
I mean, what if you select an image, then change your mind and want to select a different one? It's still the right type so will be uploaded.
That's true. At first I wanted to do the resizing and previewing with Ajax, and send the reduced image when the whole form was submitted. Well, this is not possible because Ajax cannot manage a file field. For this reason I had to do it at the server side with php. The good point is that after the image sending, the image management, like the preview, is done with the image already resized.
The user can think that he wants to change the image. Still I think it is better to preview the image, so the user can see it as soon at he has selected the file, and not when the whole form is submitted and he sees the already stored data. In that case, the wrong image whould be uploaded, and the user should look for the way to edit his uploaded data.
Originally Posted by Y_Less
I meant that 4 2Mb images is 8Mb total.
If the user wants to upload four 2MB images, he will do it, either if it is dynamically or by submitting the form. The difference is that, when it is done dynamically, every image is uploaded individually when it is selected, and meanwhile the user can do other things in the page. When it is done by submitting the form, the four images are uploaded together, and the user has to wait till the uploading process finish. I think it is better to upload 2MB four times than 8MB once, furthermore if it is not a blocking way.
Moreover, if there will be many images in the form and the previewing can become a problem, a button or radio button can be placed next to every file field, to individually enable/disable the functionality.
I love the script - I think it works great - I have a question though:
Have you tried playing around with having multiple image upload fields?
Ultimately I want to have a form that will collect text data (name, phone, etc.) and include this image uploader at the bottom, and allow for multiple image uploads. I would like to simplify things by having the user fill in all the text, choose their images, hit "Upload" ONCE, and have all the information (text and images) saved and stored.
What would I need to do to the code of this file to allow for more than one image at a time?
Yes, I use the script with multiple images, and it works quite good for me. The only thing you have to do is to set unique ids for the file fields. You can achieve this by only setting an index (fileField1, fileField2, etc.).
I have updated the php script and developed a jQuery plugin that makes the task simplier. I was planning to release it in a couple of months, as it takes some time to revise the code, write use instructions and develop a demo, and I don't have time for it right now. However, if you want, I can upload it here, with a few quick instructions.
That would be awesome! I have spent literally days upon days trying to find something like this and modifying what I could find to do what I want it to do (I am not a developer - more of a designer
I have tried to do it as complet as possible, but, as I already said, I have not much time right now. I hope to revise it and put a demo somewhere, so it can be seen in action
Though, I have made a simple demo with a little description, so who ever can install and try it out. If you have any question about the plugin, please ask
You can find everything attached to this post in two zip files (the maximum file size is 100 KB). Please, uncompress them and join both folders.
I have posted some features of the plugin below.
Hope it may be useful!
Features
Crossbrower. Firefox, Opera, Chrome, Safari and IE.
No garbage. The files in the temporary directory are deleted when the user exits or reloads the page
Descriptive filenames. The file names are constructed with a three random keywords of a given keywords list. If the filename already exists, characters are taken from the unique temporary filename generated by PHP.
Iframe factory. An iframe factory is used in order not to unnecessary duplicate iframes.
Loading spinner image. A loading spinner image is displayed while the uploading. It was taken from the website ajaxload.info.
Loading timeout. A jQuery dialog box is displayed while the saving. It contains a progress bar that shows the elapsed time if there are images loading. If the maximum loading time is reach, the saving process is canceled and an error message is displayed. To test this feature, it can be placed a timer in the php script. For example, place sleep(5); before the return statement, at the end of the script.
If you need client side handling of files before uploading and or batch uploading why not use a signed java applet or flash like they do on sites like facebook?
Bookmarks