Save Prestashop Custom fields on “Add to cart” – Part 2

After tackling text fields, in this second part we will see how to add ajax uploads for file customizations in Prestashop.

  • Version used: Prestashop 1.6

« Save Prestashop Custom fields on “Add to cart” – Part 1

Demo here  

 

Continuing from what we did in the previous tutorial, instead of modifying the ajax-cart.js file, this time we will handle product.js, located in the theme folder /js/. To make things easier we will also add atrigger button to fire the upload event, and modify our previously amended tools.js.

Before starting, it’s important to notice this kind of upload will not work for older browsers (like ie9), so you might want to add some kind of warning, or completely hide the ajax button in this case.

Handling ajax file uploads in Prestashop – product.js

First off, let’s create our small trigger. Locate product.tpl in your theme’s folder, open it up and right before this

						</div>
					{/if}
					{if $product->text_fields|intval}

Add the following

<a class="btn btn-default" href="javascript:void(0)" id="uploadTrigger">Upload</a>

If your theme looks different, make sure it’s inside this conditional, at least:

{if $product->uploadable_files|intval}
...
{/if}

Then, let’s deal with Javascript.

Open up product.js and make sure you put your cursor inside the very first $(document).ready(function() block. The first thing we need to do is create a list of files that the user might want to upload, as soon as the file inputs change:

	var files = new Array();
	$('.customizationUploadLine').find('input[type="file"]').on('change', prepareUpload);
	function prepareUpload(event)
	{
	  files.push({'name' : event.target.name, 'file' :event.target.files[0]});
	}

Explanation: we are taking advantage of the ‘files’ property of the change event, which holds the file(s) currently being added to the clicked input. We want to record modifications to all our file customization fields, and push the files array with each of the possible uploads. We will need each entry to be named as the form field, so we use a new object for each file, holding the correct name in the ‘name’ property, and the actual file content in the ‘file’ one.

The original method I used came from this tutorial, in case you want to have a reference of the source code: Ajax file uploads with jQuery

Now, let’s target the upload button’s click;

	$('#uploadTrigger').click(function(e) {
		if(files.length > 0)
		{
			// here will be out ajax
		} // end checking files length
		else alert('Nothing to upload!');
	});

We have to make sure we have at least one file to upload; otherwise it’s useless to continue with the call.

Since we do not know how long the file upload will take, it’s better to prevent the user from taking further actions before it’s completed. Let’s add some feedback:


			$('<div class="myoverlay"></div>').css({

				'position' : 'fixed',
				'top' : 0,
				'left' : 0,
				'background' : 'black',
				'background' : 'rgba(0,0,0,.5)',
				'z-index' : 5999,
				'width' : '100%',
				'height' : '100%',
				'cursor' : 'pointer'
			}).appendTo('body');
			
			$('<div class="uploadingfiles">Your files are being uploaded...<img src="'+baseUri+'themes/default-bootstrap/img/ajax-loader.gif"></div>')
				.css({
					'position' : 'absolute',
					'top' : '30%',
					'left' : '50%',
					'width' : '300px',
					'margin-left' : '-150px',
					'text-align' : 'center',
					'padding' : '10px',
					'background' : 'white'
				})
				.appendTo('.myoverlay');


Now the tricky part. We will use the new HTML5, supported with XHR2 (that is why it will not work on old browsers!). Right after appending the box, add the following:


			var data = new FormData();

		    $.each(files, function(key, obj)
		    {
		        data.append(obj.name, obj.file);
		    });

		    data.append('submitCustomizedDatas', 1);
		    data.append('ajax', 1);

Explanation: as mentioned, we need a FormData object to hold our data. Then, we iterate through each of the files, assigning them to the respective name. The append method works like data.append(key, data). Lastly, since in the previous tutorial we only ran the customization scripts when having two specific queries sent to the server, we also append them to our form data. The value here is not important, as long as it’s more than 0.

The ajax call

Here is our code so far:


	var files = new Array();
	$('.customizationUploadLine').find('input[type="file"]').on('change', prepareUpload);
	// Grab the files and set them to our variable
	function prepareUpload(event)
	{
	  files.push({'name' : event.target.name, 'file' :event.target.files[0]});
	}
	

	$('#uploadTrigger').click(function(e) {

	
		if(files.length > 0)
		{

			$('<div class="myoverlay"></div>').css({

				'position' : 'fixed',
				'top' : 0,
				'left' : 0,
				'background' : 'black',
				'background' : 'rgba(0,0,0,.5)',
				'z-index' : 5999,
				'width' : '100%',
				'height' : '100%',
				'cursor' : 'pointer'
			}).appendTo('body');
			
			$('<div class="uploadingfiles">Your files are being uploaded...<img src="'+baseUri+'themes/default-bootstrap/img/ajax-loader.gif"></div>')
				.css({
					'position' : 'absolute',
					'top' : '30%',
					'left' : '50%',
					'width' : '300px',
					'margin-left' : '-150px',
					'text-align' : 'center',
					'padding' : '10px',
					'background' : 'white'
				})
				.appendTo('.myoverlay');


			var data = new FormData();

		    $.each(files, function(key, obj)
		    {
		        data.append(obj.name, obj.file);
		    });

		    data.append('submitCustomizedDatas', 1);
		    data.append('ajax', 1);
		 

		} // end checking files length
		else alert('Nothing to upload!');
	});

Pretty long already! But we need to tackle the most important part: the ajax call. Right after appending ‘ajax’, add the following:

$.ajax({
    url: $('#customizationForm').attr('action'),
    type: 'POST',
    data: data,
    cache: false,
    dataType: 'json',
    processData: false,
    contentType: false, 
    success: function(data, textStatus, jqXHR)
    {
      
    },
    error: function(jqXHR, textStatus, errorThrown)
    {
    }
});

Explanation: two parts of this call deserve a better explanation; first, we are telling jQuery not to process data. Otherwise, the files array will be converted into a string, making it impossible to be further processed; then, we are setting contentType to false as well, to prevent the script to send over a normally encoded form (without files).

At this point, it’s worth testing out the script. It will inevitably break at some point, but you can see the upload being successful or not by refreshing the page when you see the call is complete. Make sure you keep an eye on the Network Tab in the browser’s console for this, to also troubleshoot any eventual error.

When it’s done, refresh the page. If everything went smoothly, the image will appear at the top of the file input!

Giving some feedback to the user

If we were the only ones using the site, we could as well jump to the next step. But since it’s rarely the case, let’s give our customers a bit of feedback on what’s up with files. In the “success” method of the ajax call, add the following:


if(typeof data.errors === 'undefined')
{

	$.each(files, function(key, obj)
    {
        $('input[name="'+obj.name+'"]').addClass('filled');
        previewFile($('input[name="'+obj.name+'"]'), obj.file);

    });
    $('.uploadingfiles').text('Upload Complete!');
}
else
{
   $('.uploadingfiles').text('Error while uploading, please refresh the page and try again');
}
$('.myoverlay').click(function(){$(this).remove()});

Explanation: first off, we must check that we don’t have any error coming back from the call. If we do, we replace our “uploading” text with the error string. If everything is ok (if(typeof data.errors === ‘undefined’)) we iterate through our files list, and add a preview above the list. Notice I am calling a function named previewFile, which we do not have yet!

You might have noticed I added a “filled” class as well to the input. Why? We will see this shortly, when amending tools.js.

Make sure you always add


$('.myoverlay').click(function(){$(this).remove()});

So that users can get rid of the overlay when needed.

Adding an image Preview

We are almost done! let’s create the previewFile function. Right before this:

	$('#uploadTrigger').click(function(e) {

Add

	function previewFile(target, file) {

		$('#uniform-'+target.attr('id')).before($('<img id="preview-'+target.attr('id')+'"/>'));
		var preview = $('#preview-'+target.attr('id'));
		var reader  = new FileReader();

		preview.attr('width', 64);

		reader.onloadend = function () {
			preview.attr('src', reader.result);
		}

		if (file) {
			reader.readAsDataURL(file);
		} else {
			preview.attr('src', "");
		}
	}

Explanation: we are taking advantage of the FileReader class. First, we prepend an image tag to our input’s parent, making sure the preview box is no larger than 64 pixels. Then, we replace out image source after the reader has actually read the file, if it exists, passing in the one we got before, out of the original “files” array. It’s a bit confusing, but you can basically copy/paste to have it working. Here is the source: Image preview for ajax file uploads

As a finishing touch, we might want to handle failed ajax requests as well:

 error: function(jqXHR, textStatus, errorThrown)
{
   $('.uploadingfiles').text('ERRORS: ' + errorThrown);
    $('.myoverlay').click(function(){$(this).remove()});
}

Yet again, tools.js!

Back to our beloved tools.js! Here is where we left off:

function checkCustomizations()
{
    var pattern = new RegExp(' ?filled ?');
 
    if (typeof customizationFields != 'undefined')
        for (var i = 0; i < customizationFields.length; i++)
        {
            if (parseInt(customizationFields[i][1]) == 1 && ($('#' + customizationFields[i][0]).val() == ''))
  			return false;
        }
    return true;
}

Now, this will make it impossible to save our current files. In order to be able to do it, we need to modify the function as follows

function checkCustomizations()
{
    var pattern = new RegExp(' ?filled ?');
 
    if (typeof customizationFields != 'undefined')
        for (var i = 0; i < customizationFields.length; i++)
        {
            if (parseInt(customizationFields[i][1]) == 1 && $('#' + customizationFields[i][0]).val() == '' && !pattern.test($('#' + customizationFields[i][0]).attr('class')))
				return false;
        }
    return true;
}

Explanation: remember when we added the ‘filled’ class? Now we also want to make sure the class is not there, to return false. This way, the field will validate if it hass the ‘filled’ class!

Save and try it out, it should work!

The final code for product.js:


	var files = new Array();
	$('.customizationUploadLine').find('input[type="file"]').on('change', prepareUpload);
	// Grab the files and set them to our variable
	function prepareUpload(event)
	{
	  files.push({'name' : event.target.name, 'file' :event.target.files[0]});
	}
	


	function previewFile(target, file) {

		$('#uniform-'+target.attr('id')).before($('<img id="preview-'+target.attr('id')+'"/>'));
		var preview = $('#preview-'+target.attr('id'));
		var reader  = new FileReader();

		preview.attr('width', 64);

		reader.onloadend = function () {
			preview.attr('src', reader.result);
		}

		if (file) {
			reader.readAsDataURL(file);
		} else {
			preview.attr('src', "");
		}
	}


	$('#uploadTrigger').click(function(e) {

	
		if(files.length > 0)
		{

			$('<div class="myoverlay"></div>').css({

				'position' : 'fixed',
				'top' : 0,
				'left' : 0,
				'background' : 'black',
				'background' : 'rgba(0,0,0,.5)',
				'z-index' : 5999,
				'width' : '100%',
				'height' : '100%',
				'cursor' : 'pointer'
			}).appendTo('body');
			
			$('<div class="uploadingfiles">Your files are being uploaded...<img src="'+baseUri+'themes/default-bootstrap/img/ajax-loader.gif"></div>')
				.css({
					'position' : 'absolute',
					'top' : '30%',
					'left' : '50%',
					'width' : '300px',
					'margin-left' : '-150px',
					'text-align' : 'center',
					'padding' : '10px',
					'background' : 'white'
				})
				.appendTo('.myoverlay');


			var data = new FormData();

		    $.each(files, function(key, obj)
		    {
		        data.append(obj.name, obj.file);
		    });

		    data.append('submitCustomizedDatas', 1);
		    data.append('ajax', 1);
		    $.ajax({
		        url: $('#customizationForm').attr('action'),
		        type: 'POST',
		        data: data,
		        cache: false,
		        dataType: 'json',
		        processData: false,
		        contentType: false,
		        success: function(data, textStatus, jqXHR)
		        {
		            if(typeof data.errors === 'undefined')
		            {
		            	$.each(files, function(key, obj)
					    {
					        $('input[name="'+obj.name+'"]').addClass('filled');
					        previewFile($('input[name="'+obj.name+'"]'), obj.file);

					    });
					    $('.uploadingfiles').text('Upload Complete!');
		            }
		            else
		            {
		               $('.uploadingfiles').text('Error while uploading, please refresh the page and try again');
		            }
		            $('.myoverlay').click(function(){$(this).remove()});
		        },
		        error: function(jqXHR, textStatus, errorThrown)
		        {
		           $('.uploadingfiles').text('ERRORS: ' + errorThrown);
		            $('.myoverlay').click(function(){$(this).remove()});
		        }
		    });

		} // end checking files length
		else alert('Nothing to upload!');
	});

Taking it a step further

Given that the previous works for you, we can try taking it a step further. If you really (really!) think your customers will never hit the upload button, you can automate the process and build it inside the ajax-cart as well. How? Opening ajax-cart.js, locate the previous code we added in part1:


		if(addedFromProductPage && $('#customizationForm').length > 0)
		{
			...
		}

Right before it, add:

	$('#uploadTrigger').click();

Then, inside product.js, make sure you also add

	async: false,

As property of the ajax call, otherwise the add to cart will run before the upload is completed!

You like the tuts and want to say "thank you"? Well, you can always feel free to donate:

  • Mateusz Brylew

    Hello,

    First of all thank you very much for this tutorial!

    I have a bit of a problem on my site. It doesn’t always happen, but sometimes when customer uploads an image it results in a white square picture with resolution of the uploaded file. At first miniature looks fine on the product page, but then on shopping cart it’s already white square and on the order details the same. I can’t see any pattern in terms of type of picture uploaded. With debug mode I get an error on upload:

    ERRORS: SyntaxError: JSON.parse:
    unexpected character at line 1 column1 of the JSON data

    Is there any chance you could help with this issue?

    Prestashop 1.6.0.13 , memory limit 512mb, filesize max. 80mb, post max size 130mb, input time -1, execution time 240

    Thanks very much
    Mateusz

  • Hanny

    This worked a treat for me – is there a way to make the ‘delete picture’ button also ajax?

    Currently it reloads the page with a deletePicture=1 appended to the URL. I’d really like to make this an ajax call so it dynamically deletes without reloading the page – what is the best way to accomplish that?

  • Mee

    Hi, not working for me… PS 1.6.0.14.
    Very long and complicated tutorial!!! WHY NOT JUST GIVE COMPLETE CODE????
    Also I saw in DEMO page
    http://demo.nemops.com/modules/blockcart/ajax-cart.js
    http://demo.nemops.com/js/tools.js

    HERE IS NO (!!!) CHANGES as in this tutorial :)

    So????
    Could you just paste complete parts of code?

  • dhiraj khursade

    It worked for me….
    But after implementing this tutorial I am facing problem in “View Larger” section in product page…. and also the thumbnails of product image are also not working.

  • http://www.hackingethics.com/ Ankur Gandhi

    Hi, Thanks for this code but I am getting one error:

    TypeError: data is null
    if(typeof data.errors === ‘undefined’)

    File is uploading properly when I click refresh but only problem is it is giving error in firebug and So first If condition of success is not true because data is null.

    Please help me in this.
    Ankur

Store Top Sales

You like the tuts and want to say "thank you"? Well, you can always feel free to donate: