Thursday, April 03, 2014

Creating Image Upload Previewer with Javascript/PHP

This is a very common scenario on Web Applications that an user selects an image for uploading and he is shown the preview of that image immediately before the user clicks on the "Submit" button. 

How is it possible when the image file is not even uploaded to Server? From where the <img> element is showing the right image? How is it happening without refreshing the page?

Actually this involves both Server and Client side interactions which is the subject matter for this discussion. 

We have the following screen, 





and it changes to this immediately after the user selects an image from his Hard Disk.




To achieve our goal, let's chalk out the essential points : 

1. We need an <input type="file"> element within a <form> element.
2. The <form> element must be submitted for uploading/sending a file to Server. As form submission refreshes the page, we'll be using a little trick here.. we'll take help of an <iframe> element and submit our form onto this <iframe>. This will not refresh the page, but upload the file to the server.
3. When the image file is successfully uploaded, we just change the "src" property of an <img> element on our page to load the image and show the preview.

Let's check out the HTML we need.

<html>
<head>
<script src="jquery.min.js" ></script>
<script src="imageupload.js"></script>
</head>
<body>

<!-- FORM starts here -->
<form name="f" enctype="multipart/form-data" method="post" target="myiframe" action="imageupload.php">

Select Image files :: <br> <br>

<div style='padding-bottom:10px'>

<!-- FILE INPUT element -->
<input type="file" name = "file1" onchange="upload_and_show_image(this)"> 

<!-- <img> Tag to display the Preview -->
<img id='image1' border='0' src='' width="100" style='display:none'/> 

<!-- We have a SPAN element to show messages -->
<span id='message1'></span>

</div>

<!-- An Hidden Field -->
<input type='hidden' name='form_submit' value='1'>

</form>

<!-- THE IFRAME which is kept Hidden -->
<iframe name="myiframe" style="display:none"></iframe>

</body>
</html>

Notes : 
1. We have included JQuery Library for all our client-side working.

2. We have put all our client-side logic in imageupload.js file, which we'll be dissecting soon.

3. We have a Server Side PHP file imageupload.php where the form is actually submitted. This Server Side file handles the uploaded image and renames/stores it.

4. Check the declaration of <input> element :
   <input type="file" name = "file1" onchange="upload_and_show_image(this)"> 
   
   The javascript function "upload_and_show_image()" is called when user selects a new Image. We are capturing the "change" event on this element. The included script "imageupload.js" contents the whole upload_and_show_image() function definition.
   
5. Check the declaration of <iframe> element :
   <iframe name="myiframe" style="display:none"></iframe>
   
   We have given a name "myiframe" to it and it is kept hidden on our page.
   
6. Check the declaration of <form> element :
   <form name="f" enctype="multipart/form-data" method="post" target="myiframe" action="imageupload.php">
   
   For uploading a file, "enctype" property should have a value "multipart/form-data", "method" should be "post". The <form> will be submitted to a PHP page "imageupload.php". Check out its "target" property which holds "myiframe" i.e the <form> will be subitted to "imageupload.php" within an <iframe> having a name "myiframe". This way, ONLY the said <iframe> will be refreshed, not other parts of our page.
   
7. We have the <img> element which is kept hidden initially. When an Image is fully uploaded and available to browser for displaying, we change the "src" property of this element to load it on the browser.
   
   <img id='image1' border='0' src='' width="100" style='display:none'/> 
   
8.  We have a SPAN element where we can show error messages etc. 
    <span id='message1'></span>  

9. We also have a hidden value within the form. With this, we can track the form submission at the server end.
   
   <input type='hidden' name='form_submit' value='1'>
   

Let's check out the JS file "imageupload.js". What it does is shown point wise below :

1. Get the fileName. On some browser, we get whole path of the image like "c:\fakepath\sampleImage.jpg", we need to extract the name of only the image.

2. We would replace all the spaces with hyphens ("-") within the file name.

3. We also check for valid image extensions. If wrong files are selected, no preview will be available.

4. After we submit the form using Javascript, we create an Image object, load the image with it and when loading is completed, we change the "src" property of the <img> element on our page to show the preview.

Now the content ...

function upload_and_show_image(t)
{
  // Initially Hide the <img> element
  jQuery("#image1").hide();
  
  // get the Selected Image File's name 
  var filename = jQuery(t).val() ;
  filename = filename.toLowerCase()
  
  /// Take the actual File name
  /// Remove the path information 
  var pos = filename.lastIndexOf("\\");
  if(pos!= -1)
  {
    filename = filename.substr(pos+1);
  }
    
  // GET Extension
  var extn = filename.split('.').pop();
  
  // Remove all spaces with hyphens
  var fname = filename.split('.')[0].replace(/\s/g,"-");
  var filename_final = (fname + "." + extn);
  
  // Locate to the SPAN for a message
  var sp = jQuery("#message1");
  
  // Check for valid Image extension
  var valid_extn = ['jpg','jpeg','png','bmp','gif','tiff','wmf'];
  var matched = 0;
  for(var i in valid_extn) 
   if(valid_extn[i] == extn) matched = 1;
  
  if( !matched )
  {
     // Show an Error message in the SPAN element
     jQuery(sp).html("Please select a valid Image file").css("color","red");
     
     // RETURN
     return;
  }
  
  // Proceed, Clear the SPAN content
  jQuery(sp).html("");
  
  // Submit the form
  jQuery("form[name='f']").submit();
  
  // Create New Image  
  var i = new Image();
  i.src = filename;
  
  // Do some tasks when Image is loaded
  i.onload = function()
  { 
   // Change the 'src' property
   jQuery("#image1").attr("src", i.src);
 
    // Show the preview <img>
   jQuery("#image1").show();   
  }
  
}

The above JS code is quite self-explanatory. The input (type 'file') gets full path name usually like "c:\abcd.jpg" (On windows system) etc. So, we need to extract only the file name out of it. And here, the lastIndexOf() and substr() function helps us to achieve it.

var pos = filename.lastIndexOf("\\");
filename = filename.substr(pos+1);

Now check the Server side code for handling the Image upload. Here is the content of "imageupload.php" file. 
  
<?php
// Check whether form is submitted
if(isset($_POST['form_submit']))
{
  // Check if FILE has been uploaded
  if( isset($_FILES['file1']['tmp_name']) && $_FILES['file1']['error'] == 0 )
  {
    
// Get the filename corrected
$file_name = strtolower($_FILES['file1']['name']);
$a = explode(".", $file_name );
$name = str_replace(" ","-",$a[0]);
$extn = $a[1];

// Check for Valid extensions
   $extns = array('jpg','jpeg','png','bmp','gif','tiff','wmf');
    
// Found
if( in_array( $extn, $extns) !== false )
{
     // Upload
    @move_uploaded_file( $_FILES['file1']['tmp_name'], "$name.$extn");
}
  }
}
?>

The above piece of HTML, JavaScript and PHP codes work together to effect this Image preview. 

But there is a problem with the above working model. Check out this section in JS file:

  // Submit the form
 jQuery("form[name='f']").submit();
  
 // Create New Image  
 var i = new Image();
 i.src = filename;

Here, we are submitting the form and then immediately we are creating an Image object in Javascript and changing its "src" property. Problem is, the Server may be quite busy while processing the uploaded file, renaming it and moving it etc or Server is taking time as the image has huge file size. But client side javascript does not wait for the server side PHP to finish its own task. So, sometimes, the Image loading (changing the 'src' property of 'img' tag) is done when the Image was not even created/moved by PHP code. As a result, the  <img> element (meant to show the preview) does not show the image and we get an error message "Network Error : file not found"  in browser console. An broken Image icon is displayed on browser. However if you right click on the broken-image icon and click "Reload Image" for 1 or more times, the image is shown perfectly (by that time the Image is available at the server-end). To work around with this, we may follow the following logic :: 

1. Submit the form
2. Run a timer, which fires a JS function check_for_image_existence() every 100 miliseconds. This function fires an Ajax request to server to query whether the image file is now existing. So, every 100 milliseconds, an Ajax call is fired.
3. If the Ajax response is satisfactory (Image exists), then we change the "src" property of the <img> element on our page. 

To achieve this, we need to change our JS file first as shown below, We are showing only the updated lines: 

     // Submit the form
  jQuery("form[name='f']").submit();
  
  /*  COMMENT OUT OLD CODE
  var i = new Image();
  i.src = filename;
  i.onload = function()
  { 
jQuery("#image1").attr("src", i.src);
jQuery("#image1").show();   
  }
  */
  
  // SET a Timer
  timer = setInterval( function(){ check_for_image_existence(); }, 100);
  
  /// The function which fires AJAX
  function check_for_image_existence()
  {
      
    // Ajax Call
    jQuery.ajax({
url  : "imageupload.php",
data : "check_file_existence=1&filename=" + filename_final,
dataType : "json",
type : "post",
cache : 'false',
success : function(data)
{
      // Success
      if(data.success == 1 )
      {
       // Change the "SRC" property
       jQuery("#image1").attr("src", filename_final );
       jQuery("#image1").show(); 
 
       // Clear the Interval
       clearInterval( timer );
      }
}   // success callBack ends  
     }); // jQuery.ajax ends
  } // Function ends 

The ajax call sends request to a PHP file "imageupload.php" with parameters "check_file_existence" and "filename". When the return response holds a value of "1" in "data.success" variable, we know that the file is available in Server and changing "src" can not just fail. We also remember to clear the timer.

Check the new Server side PHP code which can be appended to the existing code in "imageupload.php".

<?php
if( isset($_REQUEST['check_file_existence']) && $_REQUEST['check_file_existence'] == 1 )
{
  // Extract File name from Ajax request
  $filename = $_REQUEST['filename'];
  
  // Check if file exists
  if($filename && file_exists($filename))
    echo json_encode(array("success" => 1));
  else
    echo json_encode(array("success" => 0));  
}
?>

We receive the ajax response in json format.

The above fixes make the whole solution work perfectly now. It runs on all browsers also. Give it a try.

No comments: