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 am currently working on a project where I will need a script for uploading and resizing images such as yours. I have already started experimenting and seems to do exactly what I wnat it to, so I am happy.
The question is how would you feel if you some time in the future saw your script, more or less as adjustments needs to be implemented, in another project.
The project is a infoscreen system, based on php, mysql and javascript. I am building this mainly for my on workplace, but as I have ambitions I'm hoping this also could be useful for others. I plan to make it available as opensource. If you read Norwegian fairly well(as I suppose you don't), you can read about it here: http://blog.matsr.net/?page_id=128 or atleast take a look at the code so far.
on a side note, I don't have a specific time frame yet, but I am working as fast I can, learning while doing.
I would be happy if my script is useful for more people besides me. I have read and used things that other people have put on the internet, and that was also the idea when I put the image preview script.
So feel free to use, modify and distribute it as you want. The only thing I'd ask is to keep the name of the original author (me), if the script is basically the original one, i.e. it has not many modifications.
If you have any question about the script, feel free to ask it.
Sorry, I don't know Norwegian. As far as I can go ist ein bisschen Deutsch
I felt compelled to get back to you about this... I really like your script, but I am having trouble getting it work the way I need to(some serious path issues, probably easy to fix, for someone else ).
For the time being I have given up on making this work, and in stead trying to build from the ground up, well.. I have already stolen some of your code. (a part I had modified in your script aswell).
What I am trying to do is put the script in: ./admin/sys/submitform.php (and uploadimage.php)
and have them upload the images to ./images
also I have been trying to put the javascript in: ./admin/sys/js/img_upload.js
For some reasons it seems different browsers intrepret the paths differently(it works in chrome, but not IE or FF).
Well I suppose this is more of an update of the status on my project, havn't been able to work on it for awhile now.. progress is slow...
Have you tried the diferent forms of path? I mean:
../images
./images/
../images/
I don't know where your images directory is in relation with the directory where the active php is. Use "." to refer the current active directory, and ".." to go one level up.
HI - First of all, Alberto: THANKS. Made a ton of modifications, but this is the closest thing that I could find for what I was seeking.
I have a couple ideas/suggestions/needs/enhancement requests. They really should not be too hard, but I still have not been able to get some of them working using my own (not so good) newly acquired hacking skills!
1. Cross Browser functionality. I was finally able to accomplish this. Firefox had issues with setting paths. I ended up creating a soft link (luckily I am the UNIX admin on my server) to the actual path to the upload directory. That directory is in / (root) of the webserver. But I also had to have a softlink in ./ (dot slash) of the local directory of the template, which originates beyond / (root)... So... not a big deal, but sure would be nice if you considered this in your next release version.
2. Another cross browser issue I ran into... The Javascript in some browsers is VERY finicky. I had to add things like "document.getElementById" and window.uploadedImage" in front of a bunch of JS variables and thingies...
3. (here's 1st one I haven't figured out) I would like to ALSO save the ORIGINAL uploaded pictures... say, in a folder like /images/ORIG/... Right now you do the upload and resize in "one step"... I had another script that was easy to figure out this... but I have not yet figured out how to incorporate that in to this script...
4. (2nd mystery) although I have found a bunch of JS and PHP ways to do this, I haven't gotten the right combo, or the right place to make the modification, or something!.... but, I want the uploaded files that have any SPACES or funky characters (that I specify - like ~ or + or whatever might get through) to get those characters replaced by a dash or underscore (- or _)
I also separated the JAVASCRIPT portions of the form into a separate my_js_functions.js file which I call in... Makes it a little neater.
THANKS again for the good work on this. My website where you can see your script in action is: www.womendrinkingbeer.com
I know this code is not the best one one could wish, but I didn't find any better way with basic technologies.
I have also made some modifications, like placing all javascript functions in a separate file. The image form is now written by a javascript function. Thus it is very easy to have as many forms as required.
For the "document.getElementById" I am using now the Prototype framework, so just the "$" function is enough.
If you want to save the original file, you have just to call the function saveImage($image, $imagePath, $imageType) before resizing the image. The saving is performed by the function php imagepng($image, $imagePath), where $image is the uploaded image, and $imagePath, the file pathname where you want to save the image.
Your name? It's in your code. I kept it there, just as you requested... Alberto Moyano Sanchez - it's right there in your code snippets! And your are AMS...
Hope you weren't trying to stay anonymous!
Thanks for the help and responding to my post... especially since it's been a while since you last visited.
I'm not a coder - more of a hacker - but I've been learning a lot and picking up things here and there. I'm going to study the links and ideas you provided. I'll come back and ask more questions if I have any.
Bookmarks