I have found solutions on how to add additional form data when submitting the file upload form. This question is how to upload the additional data if there is no file to upload.
I am using blueimp jquery-file-upload in a task management app in order to drag and drop files and attach them to a task.
The script is initialized and setup to not automatically upload when files are attached. On the fileuploadadd
callback I attach data.submit()
to my submit
event handler. This accomplishes that we submit the task data and the files in one POST request.
Until files are added I'm unable to get access to the file-upload data
to use the data.submit()
function. I came up with a work around by adding an empty file (and then removing it) on page load which would trigger the binding data.submit()
to the submit button. The problem is that the plugin is returning an error while trying to loop through an empty array of files. This problem would also occur if you added a file and then removed it before submitting the form.
I have been looking for a solution to this for a while and have looked high and low but couldn't find anything in the (IMHO) terrible documentation.
Have a look at my code below:
$('#post_task').fileupload({
autoUpload: false,
singleFileUploads: false,
disableImagePreview: true,
}).on('fileuploadadd', function (e, data) {
$.each(data.files, function (index, file) {
var filename = file.name,
filesize = bytesToSize(file.size)
ext = filename.substr(filename.lastIndexOf('.')+1,5),
icon = '<i class="sprite_file sprite_file-file_extension_'+ext+'"></i>',
node = $('<li/>').append($('<span/>').html(icon + filename + ' ' + filesize + '<a href="#">×</a>')).attr('data-index',index);
node.find('a').click(function(e){
e.preventDefault();
var $self = $(this),
$listItem = $self.parents('li'),
listIndex = $listItem.attr('data-index');
$listItem.remove();
$('#files li').attr('data-index',function(index){return index;});
data.files.splice(listIndex,listIndex);
console.log(data);
vardata = data;
});
$('#files').append(node);
});
$('#post_task').unbind('submit').submit(function(ev){
ev.preventDefault();
data.submit();
});
});
@Hirshy and @Luk, your solution is really quite elegant and works like a charm. The files input field does not even get sent to the server so it's easy to determine when a file is in the payload.
In my Angular app, I have a single view for both adding a new document and some attendant data and for editing the data and/or uploading a replacement document.
Here is my solution:
/*------------------------------------------------------------------------*/
/* Prepare file uploader. */
/* */
/* jQuery-File-Upload does not submit a form unless a file has been */
/* selected. To allow this, we manually add an empty file to be uploaded, */
/* which makes the submit handler available, and we replace the submit */
/* handler with one that will submit the form without a selected file. */
/* */
/* see: http://stackoverflow.com/q/21760757/2245849. */
/*------------------------------------------------------------------------*/
var uploadForm = $('#DocumentForm');
var fileInput = $('#DocumentForm input:file');
$scope.uploadOptions =
{
url: Services.Documents.uploadRoute,
autoUpload: false,
dropZone: uploadForm,
fileInput: fileInput,
replaceFileInput: false
};
/*---------------------------------------------------------------*/
/* Initialize the uploader. This must be done with the options */
/* or an error will be thrown when an empty file is added below. */
/* It is also necessary to initialize with the options here as */
/* well as in the element html or the results are unpredictable. */
/*---------------------------------------------------------------*/
uploadForm.fileupload($scope.uploadOptions);
/*--------------------------------------------------------------------*/
/* File processing is called in the default add handler and this */
/* handler is called after a successful add. It displays the file */
/* name in the drop zone, sets the document name for a new document, */
/* and sets the submit handler to submit the form with file and data. */
/* */
/* If editing a document, a dummy empty file object is manually */
/* added to make the submit handler available so the user can make */
/* data changes without uploading a new document. */
/*--------------------------------------------------------------------*/
uploadForm.bind("fileuploadprocessdone", function(e, data)
{
/*------------------------------------------------------------*/
/* Get the user selected file object and display the name. */
/* Set the document name to the file name if not already set. */
/*------------------------------------------------------------*/
if (data.files[0].name)
{
$scope.document.file = data.files[0];
if (!$scope.document.name)
$scope.document.name = $scope.document.file.name;
MessageService.clear();
}
/*--------------------------------------*/
/* If this is the dummy file add, reset */
/* 'acceptFileTypes' to global config. */
/*--------------------------------------*/
else
delete $scope.uploadOptions.acceptFileTypes;
/*------------------------------------------------------------*/
/* Set the submit handler. We have to do this every time a */
/* file is added because 'data' is not passed to the handler. */
/*------------------------------------------------------------*/
uploadForm.unbind('submit').submit(function(e)
{
e.preventDefault();
data.submit();
});
});
/*---------------------------------------------------------------------------*/
/* If we get here, the file could not be added to the process queue most */
/* likely because it is too large or not an allowed type. This is dispatched */
/* after the add event so clear the current file and show the error message. */
/*---------------------------------------------------------------------------*/
uploadForm.bind("fileuploadprocessfail", function(e, data)
{
$scope.document.file = null;
MessageService.notice(data.files[data.index].error);
});
/*-----------------------------------------------------------------*/
/* Add a dummy empty file if not a new document so the submit */
/* handler is set and the user does not have to upload a document. */
/*-----------------------------------------------------------------*/
if (!$scope.new_document)
{
$scope.uploadOptions.acceptFileTypes = null;
uploadForm.fileupload('add', { files: [{}] });
}
UPDATE
It turns out uploadForm.fileupload('add', { files: [''] });
will result in an exception being thrown in the browser if the server returns a failed status. JFU tries to assign data.files[0].error
and data.files[0] doesn't exist.
The problem is handled nicely by assigning an empty array instead of an empty string: uploadForm.fileupload('add', { files: [[]] });
I have updated the example above.
UPDATE 2/29/16
It turned out I did want to restrict the file types after all so I modified my script to clear 'acceptFileTypes' property before the dummy file add and reset it in the add handler. Also discovered I could not access the process errors in the add handler so replaced it with 'fileuploadprocessdone' and handled the error in 'fileuploadprocessfail'.
I have updated the example above but we're still using JFU 5.42.0.
IMPORTANT
I am using 5.42.0 which is a very old version of JFU. I did not write this code and my first attempt to upgrade failed. When I do upgrade, I'll update this solution.**
I just made two separate handlers like so:
$('#avatar').fileupload({
singleFileUploads: true,
multipart : true,
dataType : 'json',
autoUpload : false,
url : config.settings.api + 'services/user/updateByActivationKey',
type : 'POST',
add : function (e, data) {
submitbtn.on("click", function () {
console.log('submitting with image');
data.submit();
});
},
done : function (result) {
console.log(result);
if (!result.error) {
$('#modal-account-activated-success').modal('show');
$("#submitbtn").removeAttr('disabled');
$('#mercname').html('');
window.setTimeout(function () {
window.location.href = config.settings.user_url;
}, 3000);
} else {
//analytics.track('completeProfileError', {
// error : JSON.parse(result.responseText).error,
// activationKey: sessionStorage.getItem("activationkey")
//});
$('#modal-account-activated-error').modal('show');
$('#submitloader').html('');
$("#submitbtn").removeAttr('disabled');
}
}
,
fail : function (e) {
//analytics.track('completeProfileError', {
// error : e,
// activationKey: sessionStorage.getItem("activationkey")
//});
$('#errormessage').html(JSON.parse(e.responseText).error.messages[0]);
$('#modal-account-activated-error').modal('show');
$('#submitloader').html('');
$("#submitbtn").removeAttr('disabled');
}
});
//if no image was uploaded
submitbtn.on("click", function () {
if ($('#preview').html().length < 1) {
console.log('submitting without image');
$.ajax({
url : config.settings.api + 'services/user/updateByActivationKey',
type : 'POST',
data : JSON.stringify({
'email' : $("#email").val(),
'activationKey' : $("#activationKey").val(),
'firstName' : $("#firstname").val(),
'lastName' : $("#name").val(),
'password' : $("#password").val(),
'gender' : $("#gender").val(),
'birthdate' : $("#birthdate").val(),
'acceptedTermsAndConditions': $("#checkbox-accept-terms").val(),
'allowsDirectMarketing' : $("#checkbox-allow-marketing").val()
}),
beforeSend: function (xhr) {
xhr.setRequestHeader("Content-Type", "application/json");
},
success : function (result) {
console.log(result);
if (!result.error) {
$('#modal-account-activated-success').modal('show');
$("#submitbtn").removeAttr('disabled');
$('#mercname').html('');
window.setTimeout(function () {
window.location.href = config.settings.user_url;
}, 3000);
} else {
//analytics.track('completeProfileError', {
// error : JSON.parse(result.responseText).error,
// activationKey: sessionStorage.getItem("activationkey")
//});
$('#modal-account-activated-error').modal('show');
$('#submitloader').html('');
$("#submitbtn").removeAttr('disabled');
}
},
error : function (e) {
//analytics.track('completeProfileError', {
// error : e,
// activationKey: sessionStorage.getItem("activationkey")
//});
$('#errormessage').html(JSON.parse(e.responseText).error.messages[0]);
$('#modal-account-activated-error').modal('show');
$('#submitloader').html('');
$("#submitbtn").removeAttr('disabled');
}
})
}
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With