Jquery / Ajax Drag and Drop File Uploader Not Submitting Files - javascript

I have a drag and drop file uploader from this tutorial. The user can either click a button to choose files, or drag and drop. Either way, image previews are displayed, and then the images are uploaded when the form is submitted.
Choosing the files the old fashioned way works fine - the image previews are displayed and they are uploaded on form submission
But if I drag and drop an image, the previews are displayed, but when I submit the form they're not uploaded.
Code below:
var isAdvancedUpload = function() {
var div = document.createElement('div');
return (('draggable' in div) || ('ondragstart' in div && 'ondrop' in div)) && 'FormData' in window && 'FileReader' in window;
}();
var $form = $('ui form');
var $fileBox = $('.box');
var $input = $fileBox.find('input[type="file"]'),
$label = $fileBox.find('label');
showFiles = function(files) {
$label.text(files.length > 1 ? ($input.attr('data-multiple-caption') || '').replace('{count}', files.length) : files[0].name);
};
if (isAdvancedUpload) {
var droppedFiles = false;
$fileBox.addClass('has-advanced-upload');
$fileBox.on('drag dragstart dragend dragover dragenter dragleave drop', function(e) {
e.preventDefault();
e.stopPropagation();
});
$fileBox.on('dragover dragenter', function() {
$fileBox.addClass('is-dragover');
});
$fileBox.on('dragleave dragend drop', function() {
$fileBox.removeClass('is-dragover');
});
$fileBox.on('drop', function(e) {
droppedFiles = e.originalEvent.dataTransfer.files;
var $imgDiv = $('.selected-images');
$.each(droppedFiles, function(index, file) {
var img = document.createElement('img');
img.onload = function() {
window.URL.revokeObjectURL($.src);
};
img.height = 100;
img.src = window.URL.createObjectURL(file);
$imgDiv.append(img);
showFiles(droppedFiles);
});
});
}
$form.on('submit', function(e) {
if ($fileBox.hasClass('is-uploading')) return false;
$fileBox.addClass('is-uploading').removeClass('is-error');
if (isAdvancedUpload) {
e.preventDefault();
var ajaxData = new FormData($form.get(0));
if (droppedFiles) {
$.each(droppedFiles, function(i, file) {
ajaxData.append($input.attr('name'), file);
});
}
$.ajax({
url: $form.attr('action'),
type: $form.attr('method'),
data: ajaxData,
dataType: 'json',
cache: false,
contentType: false,
processData: false,
complete: function() {
$fileBox.removeClass('is-uploading');
},
success: function(data) {
$fileBox.addClass(data.success == true ? 'is-success' : 'is-error');
if (!data.success) $errorMsg.text(data.error);
},
error: function() {
console.log(data.error);
}
});
} else {
var iframeName = 'uploadiframe' + new Date().getTime();
$iframe = $('<iframe name="' + iframeName + '" style="display: none;"></iframe>');
$('body').append($iframe);
$form.attr('target', iframeName);
$iframe.one('load', function() {
var data = JSON.parse($iframe.contents().find('body').text());
$form
.removeClass('is-uploading')
.addClass(data.success == true ? 'is-success' : 'is-error')
.removeAttr('target');
if (!data.success) $errorMsg.text(data.error);
$form.removeAttr('target');
$iframe.remove();
});
};
});
$input.on('change', function(e) {
showFiles(e.target.files);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<form class="ui form" action="/signup" method="post" enctype="multipart/form-data">
<!-- text inputs -->
<div class="field">
<div class="box box__input">
<input class="box__file" type="file" name="photos" id="file" data-multiple-caption="{count} files selected" multiple />
<label for="file"><strong>Choose a file</strong><span class="box__dragndrop"> or drag it here</span>.</label>
<button class="box__button" type="submit">Upload</button>
</div>
<div class="box__uploading">Uploading…</div>
<div class="box__success">Done!</div>
<div class="box__error">Error! <span></span>.</div>
</div>
<!-- text inputs -->
</form>

I was also running into this issue. Found the solution finally.
This is because when files are dropped, the drop event is only showing the file info but are not set in the file input control. So, the file input remains empty (or has the selected files if selected manually earlier).
So, when form is submitted it only submits the selected files via manual method or empty.
To set the selected files in the file input set the files property of the file input control in drop event.
$("#file").prop("files", droppedFiles);
Drag-and-drop file uploading without AJAX, synchronously in the foreground?
Thanks to the above answer, i attempted it. Somehow i had the idea that the property files is read only, hence i was not trying it.

Related

Reload script (Poptrox) after ajax request

So I'm using an AJAX request to load data from the database into a DIV. At the same time, I'm using the same data inside the DIV in a different script (Poptrox) witch does some design stuff.
Without the AJAX request, poptrox works, but if I'm using it together with poptrox it doesn't do the designing anymore.
Is there a way to reload the poptrox-script after the ajax request?
NOTE: the AJAX-Code and the Poptrox-code are placed inside the "main.js"!
Thanks in advance!
php:
<script src="assets/js/jquery.min.js"></script>
<section class="wrapper">
<form action="kochbuch.html" method="get" id="searchfilter">
<input type="text" class="searchBox" name="searchBox" id="searchBox" placeholder="Search..">
<button type="submit" id="searchBtn"><i class="fas fa-search"></i></button>
</form>
</section>
<section class="wrapper">
<div id="gerichte"></div>
</section>
<script src="assets/js/main.js"></script>
js-ajax:
var queryString = window.location.search;
var urlParams = new URLSearchParams(queryString);
if (urlParams.has('searchBox')){
var searchBox = urlParams.get('searchBox');
$.ajax({
type: "POST",
url: "kochbuch/fetchdata.php",
data: {
"search_post_btn": 1,
"searchBox": searchBox,
},
dataType: "json",
success: function (response) {
$("#gerichte").html(response);
}
});
};
js-poptrox:
// Gerichte.
var $gerichte = $('#gerichte');
// Thumbs.
$gerichte.children('.thumb').each(function() {
var $this = $(this),
$image = $this.find('.image'), $image_img = $image.children('img'),
x;
// No image? Bail.
if ($image.length == 0)
return;
// Image.
// This sets the background of the "image" <span> to the image pointed to by its child
// <img> (which is then hidden). Gives us way more flexibility.
// Set background.
$image.css('background-image', 'url(' + $image_img.attr('src') + ')');
// Set background position.
if (x = $image_img.data('position'))
$image.css('background-position', x);
// Hide original img.
$image_img.hide();
});
// Poptrox.
$gerichte.poptrox({
baseZIndex: 20000,
caption: function($a) {
var s = '';
$a.nextAll().each(function() {
s += this.outerHTML;
});
return s;
},
fadeSpeed: 300,
onPopupClose: function() { $body.removeClass('modal-active'); },
onPopupOpen: function() { $body.addClass('modal-active'); },
overlayOpacity: 0,
popupCloserText: '',
popupHeight: 150,
popupLoaderText: '',
popupSpeed: 300,
popupWidth: 150,
selector: '.thumb > a.image',
usePopupCaption: true,
usePopupCloser: true,
usePopupDefaultStyling: false,
usePopupForceClose: true,
usePopupLoader: true,
usePopupNav: true,
windowMargin: 50
});
// Hack: Set margins to 0 when 'xsmall' activates.
breakpoints.on('<=xsmall', function() {
$main[0]._poptrox.windowMargin = 0;
});
breakpoints.on('>xsmall', function() {
$main[0]._poptrox.windowMargin = 50;
});
alright, i found one possible solution, but know the preloaded data ist formated:
the whole poptrox code has to be wrapped by this function:
$(document).ajaxStop(function () {
// function after ajax
});
If you know any better solution, feel free to submit :)
EDIT:
alright, my solution was as follows:
if (urlParams.has('searchBox')){
var searchBox = urlParams.get('searchBox');
} else {
var searchBox = '';
}
$.ajax({ .....

Unable to send array of uploaded files to controller in Laravel

I have a contact form that let user allow to upload multiple files. I'm sending all the files to my controller using ajax approach with formData. I want to get all the uploaded files into my controller from where I can process the files.
When I do dd(); it returns null.
Blade
<form method="POST" enctype="multipart/form-data")}}">
<div id="dropzoneLover" class="dropzone"></div>
<input type="file" class="form-control" name="documents[]" multiple/>
</form>
Javascript
<script>
Dropzone.autoDiscover = false;
$("#dropzoneLover").dropzone({
url: "{{route('form_for_car_lovers')}}",
autoProcessQueue: false,
uploadMultiple: true,
init: function() {
dzClosure = this;
document.getElementById("submit-all").addEventListener("click",
function(e) {
e.preventDefault();
e.stopPropagation();
dzClosure.processQueue();
}),
this.on("sendingmultiple", function(data, xhr, formData) {
formData.append("_token", "{{ csrf_token() }}");
$('input[name="documents[]"]').each(function (index, member)
{
var getDocs = $(member).val();
formData.append('documents[]', getDocs);
});
}),
}
});
</script>
Controller
public function myfiles()
{
$test = $request['documents'];
$test1 = $request->documents;
$test2 = $request->file('documents');
dd($test,$test1,$test2); // All variables returns null
}
You just need to append the actual file object in your formData like this below:
jQuery (this code is tested and works perfectly)
Dropzone.autoDiscover = false;
$("#dropzoneLover").dropzone({
url: "upload.php",
previewsContainer: ".dropzone-previews",
autoProcessQueue: false,
uploadMultiple:true,
init: function() {
var myDropzone = this;
//Click
document.getElementById("submit-all").addEventListener("click", function(e) {
e.preventDefault();
e.stopPropagation();
myDropzone.processQueue();
});
//On sending
myDropzone.on("sending", function(file, xhr, formData) {
formData.append("_token", "{{ csrf_token() }}");
var getDocuments = $('input[name="documents[]"]')[0].files
$.each(getDocuments, function(index, file) {
formData.append('documents[]', file);
});
//All the other formData goes below this
});
}
});
In your laravel controller
var_dump($request->file('documents'))
Here is another alternative approach.
I replaced my code
$('input[name="documents[]"]').each(function (index, member)
{
var getDocs = $(member).val();
formData.append('documents[]', getDocs);
});
with this
var getDocuments = document.getElementById('documents').files.length;
for (var x = 0; x < getDocuments; x++) {
formData.append("documents[]",
document.getElementById('documents').files[x]);
}
Fetch and Split the array of values into Controller like this
$docs = $request->documents;
foreach ($docs as $doc) {
$docname = $doc->getClientOriginalName();
$docNames[] = $docname; // Store all Images into array
$getuploadedImages = collect($docNames)->implode(',');
}
dd($getuploadedImages);

Change Dropzone maxFiles Dynamically

I'm trying to dynamically update the MaxFiles property each time a new image is uploaded/deleted.
By using the following code its not allowing any image to upload instead of limitize it to maxFiles. And it is not taking the value of the variable maxFile, but when i remove maxFile variable And put a number then it works fine.
got source code idea from this Answer.
!function ($) {
"use strict";
var Onyx = Onyx || {};
Onyx = {
init: function() {
var self = this,
obj;
for (obj in self) {
if ( self.hasOwnProperty(obj)) {
var _method = self[obj];
if ( _method.selector !== undefined && _method.init !== undefined ) {
if ( $(_method.selector).length > 0 ) {
_method.init();
}
}
}
}
},
userFilesDropzone: {
selector: 'form.dropzone',
init: function() {
var base = this,
container = $(base.selector);
base.initFileUploader(base, 'form.dropzone');
},
initFileUploader: function(base, target) {
var maxFile = $('.dropzone').attr('data-count');
var onyxDropzone = new Dropzone(target, {
url: ($(target).attr("action")) ? $(target).attr("action") : "data.php", // Check that our form has an action attr and if not, set one here
maxFiles: maxFile,
maxFilesize: 5,
acceptedFiles: ".JPG,.PNG,.JPEG",
// previewTemplate: previewTemplate,
// previewsContainer: "#previews",
clickable: true,
uploadMultiple: false,
});
onyxDropzone.on("success", function(file, response) {
let parsedResponse = JSON.parse(response);
file.upload_ticket = parsedResponse.file_link;
var imagecount = $('.dropzone').attr('data-count');
imagecount = imagecount - 1;
$('.dropzone').attr('data-count', imagecount);
});
},
}
}
}// JavaScript Document
function openImagePopup(id = null) {
$(".upload-images").show();
$.ajax({
url: 'fetch.php',
type: 'post',
data: {id: id},
dataType: 'json',
success:function(response) {
var imagecount = response.counts;
$('.dropzone').attr('data-count', imagecount);
}
});
}
HTML
<form action="data.php" class="dropzone files-container" data-count="">
<div class="fallback">
<input name="file" type="file" multiple />
</div>
<input type="hidden" id="imageId" name="imageId">
</form>
UPDATED ANSWER
Once instanciated, the Dropzone plugin will remains with the same options unless you change the instance inner options directly.
To change options of a Dropzone, you can do this with the following line:
$('.dropzone')[0].dropzone.options.maxFiles = newValue;
$('.dropzone')[0] returns the first dropzone DOM element
.dropzone.options return the underlying plugin instance options of the Dropzone. You can now change any options directly on this object.
In you case, you will have to change the function that initiate the popup like follow
function openImagePopup(id = null) {
$(".upload-images").show();
$.ajax({
url: 'fetch.php',
type: 'post',
data: {id: id},
dataType: 'json',
success:function(response) {
var imagecount = response.counts;
$('.dropzone')[0].dropzone.options.maxFiles = imagecount;
}
});
}
And change the dropzone onSuccess event like this:
onyxDropzone.on("success", function(file, response) {
let parsedResponse = JSON.parse(response);
file.upload_ticket = parsedResponse.file_link;
var imagecount = $('.dropzone')[0].dropzone.options.maxFiles - 1;
$('.dropzone')[0].dropzone.options.maxFiles = imagecount;
});
As you can see, You can also remove the data-count="" attribute on you element and reuse the value from the plugin instance options.maxFiles
After spending a couple of hours of trials and errors I realized using the maxFiles setting from Dropzone is not exactly what is expected in many cases. That setting will only limit uploading files through the explorer / drag&drop, but after reload more files can be uploaded. It also does not reflect any failures to the upload on the serrver side (e.g. file size too big).
Changing the value of the maxFiles setting of an already initialized Dropzone from outside ot it is impossible. For example reseting the number of allowed files after removing some images with ajax will not work.
To really control the number of files that can be uploaded to the server the counting must take place on the server. Then in the Dropzone, in the success function, we should handle the ajax response:
success: function (file, response) {
var response_data = jQuery.parseJSON(response);
if(!response_data.success) {
$(file.previewElement).addClass('dz-error');
$(file.previewElement).addClass('dz- complete');
$(file.previewElement).find('.dz-error-message').text(response_data.error);
}
}
The response is the feedback information provided by the script assigned to the action attribute of the Dropzone <form>, e.g. <form action="/uploader">.

jQuery File Upload in Rails 4.x Nested Form

I have a Horse model and a Photo model. I am using jQuery File Upload to resize (client side) images and save on Amazon s3 directly since I am using Heroku.
I have seen other questions similar that use carrierwave, paperclip, or that are very old. I am not sure why you would use carrierwave/paperclip, but I think based on what heroku says, I do not want to have images hitting the server potentially causing time-outs.
Heroku recommends using jQuery File Upload and shows js appending new file input with a value of the image's link (returned from amazon s3). I have this working when saving a photo separately. I now want to make it work in a nested form for Horse but js is not finding input (since it does not exist yet because it's nested I presume).
I am using Cocoon for nested forms (I am open to anything that will work better). I am not too familiar with javascript/jQuery but a far as I can tell, Cocoon 'hides' the nested element until I click to add it via the add_association.
haml view code:
= link_to_add_association 'add photo', f, :photos
html source before clicking 'add photo'
<div class='links'>
<a class="btn btn-default btn-sm add_fields" data-association-insertion-method="after" data-association="photo" data-associations="photos" data-association-insertion-template="<div class='nested-fields'>
<fieldset class="inputs"><ol><input type="file" name="horse[photos_attributes][new_photos][url]" id="horse_photos_attributes_new_photos_url" />
<input type="hidden" name="horse[photos_attributes][new_photos][_destroy]" id="horse_photos_attributes_new_photos__destroy" value="false" /><a class="remove_fields dynamic" href="#">remove photo</a>
</ol></fieldset>
</div>
" href="#">add photo</a>
How do I work with this input and how do I handle multiple file uploads as they are added correctly?
My current upload js:
$(function() {
if ($('#new_horse').length > 0) {
$.get( "/presign", function( s3params ) {
$('.direct-upload').find("input:file").each(function(i, elem) {
var fileInput = $(elem);
var form = $(fileInput.parents('form:first'));
var submitButton = form.find('input[type="submit"]');
var progressBar = $("<div class='bar'></div>");
var barContainer = $("<div class='progress'></div>").append(progressBar);
fileInput.fileupload({
fileInput: fileInput,
url: "http://" + s3params.url.host,
type: 'POST',
autoUpload: true,
formData: s3params.fields,
paramName: 'file', // S3 does not like nested name fields i.e. name="user[avatar_url]"
dataType: 'XML', // S3 returns XML if success_action_status is set to 201
disableImageResize: false,
imageQuality: 0.5,
disableImageResize: /Android(?!.*Chrome)|Opera/
.test(window.navigator && navigator.userAgent),
imageMaxWidth: 500,
imageMaxHeight: 1000,
imageOrientation: true, //auto rotates images
acceptFileTypes: /(\.|\/)(gif|jpe?g|png)$/i, //I added this to jquery.fileupload-validate: alert('Must Be JPG GIF or PNG Image')
replaceFileInput: false,
progressall: function (e, data) {
var progress = parseInt(data.loaded / data.total * 100, 10);
progressBar.css('width', progress + '%')
},
start: function (e) {
submitButton.prop('disabled', true);
fileInput.after(barContainer);
progressBar.
css('background', 'green').
css('display', 'block').
css('width', '0%').
text("Loading...");
},
done: function(e, data) {
submitButton.prop('disabled', false);
progressBar.text("Pre-uploading done... Please Save or Cancel");
// extract key and generate URL from response
var key = $(data.jqXHR.responseXML).find("Key").text();
var url = 'https://' + s3params.url.host +'/' + key;
// remove first input to prevent phantom upload delay
fileInput.remove();
// create new hidden input with image url
var input = $("<input />", { type:'hidden', name: fileInput.attr('name'), value: url })
var imgPreview = '<img src="' + url + '">';
form.append(input);
form.append(imgPreview);
},
fail: function(e, data) {
submitButton.prop('disabled', false);
progressBar.
css("background", "red").
text("Failed");
}
});
});
}, 'json');
}
});
I guess I should have looked at cocoon documentation first:
http://www.rubydoc.info/gems/cocoon#Callbacks__upon_insert_and_remove_of_items_
http://www.rubydoc.info/gems/cocoon/1.2.6
I modified my upload.js file to the following and it worked for multiple files in nested forms perfectly:
// added for file uploading
// https://devcenter.heroku.com/articles/direct-to-s3-image-uploads-in-rails
// Get our s3params from our endpoint
$(document).on('ready page:load', function () {
$('.direct-upload')
.on('cocoon:after-insert', function(e, photo) {
console.log('inside cocoon image function');
$.get( "/presign", function( s3params ) {
$('.direct-upload').find("input:file").each(function(i, elem) {
console.log('inside nested-fields photo input form');
var fileInput = $(elem);
var form = $(fileInput.parents('form:first'));
var submitButton = form.find('input[type="submit"]');
var progressBar = $("<div class='bar'></div>");
var barContainer = $("<div class='progress'></div>").append(progressBar);
fileInput.fileupload({
fileInput: fileInput,
url: "http://" + s3params.url.host,
type: 'POST',
autoUpload: true,
formData: s3params.fields,
paramName: 'file', // S3 does not like nested name fields i.e. name="user[avatar_url]"
dataType: 'XML', // S3 returns XML if success_action_status is set to 201
disableImageResize: false,
imageQuality: 0.5,
disableImageResize: /Android(?!.*Chrome)|Opera/
.test(window.navigator && navigator.userAgent),
imageMaxWidth: 500,
imageMaxHeight: 1000,
imageOrientation: true, //auto rotates images
acceptFileTypes: /(\.|\/)(gif|jpe?g|png)$/i, //I added this to jquery.fileupload-validate: alert('Must Be JPG GIF or PNG Image')
replaceFileInput: false,
previewMaxWidth: 100,
previewMaxHeight: 100,
previewCrop: true,
progressall: function (e, data) {
var progress = parseInt(data.loaded / data.total * 100, 10);
progressBar.css('width', progress + '%')
},
start: function (e) {
submitButton.prop('disabled', true);
fileInput.after(barContainer);
progressBar.
css('background', 'green').
css('display', 'block').
css('width', '0%').
text("Loading...");
},
done: function(e, data) {
submitButton.prop('disabled', false);
progressBar.text("Photo Uploaded");
// extract key and generate URL from response
var key = $(data.jqXHR.responseXML).find("Key").text();
var url = 'https://' + s3params.url.host +'/' + key;
// remove first input to prevent phantom upload delay
fileInput.remove();
// create new hidden input with image url
var input = $("<input />", { type:'hidden', name: fileInput.attr('name'), value: url })
var imgPreview = '<img src="' + url + '">';
form.append(input);
form.append(imgPreview);
},
fail: function(e, data) {
submitButton.prop('disabled', false);
progressBar.
css("background", "red").
text("Failed");
}
}, 'json'); //fileupload
}); //each file
}); //presign call
}); // function cocoon
}); // page ready
I guess Google and a well documented gem can replace knowledge of JS in the short term :-) I am sure it's not at tight as it could be so please offer any improvements.

How Do I Send The File Data Without Form With Ajax On Server?

I've coded a javascript code which nicely collects every file user wants to upload. But things turned when I added drag/drop file option.
By default, I had a code which monitored input[type='file'] change event handler and once it was detected, actions were performed and files were sent to server for upload.
But since drag/drop doesn't change the input[type='file'] value and neither I can change it programmatically due to security reasons, I'm struck how do I send files which are dragged and dropped on the site.
Here's some of my code:
document.getElementById('drop').addEventListener('drop', function (e) {
e = e || window.event;
e.preventDefault();
var dt = e.dataTransfer;
var files = dt.files;
for (var i=0; i<files.length; i++) {
var file = files[i];
var reader = new FileReader();
reader.readAsDataURL(file);
addEventHandler(reader, 'loadend', function(e, file) {
var bin = this.result;
var filename = file.name;
var filesize = (file.size/1048576).toFixed(2) + ' MB';
alert(' '+filename+' '+filesize+' '); // DEBUGGING ONLY
console.log("YEAY");
if(filecheck(filename)) { // Additional Function
step2(filesize, filename, bin); // Additional Function
$('.btn').click(function() { // Button to be clicked to start upload
$('#main_img_upload').submit(); // Form with that input[type='file']
});
}
else {
alert("Wrong File");
return false;
}
}.bindToEventHandler(file), false);
}
return false;
});
Obviously, it starts upload but server doesn't receive anything as no change has been made to form. But I have all the necessary details (name of file, size of file, etc..)
Any help would be appreciated.
Try out this code.
data.append("FileName", files[0]);
$.ajax({
url: "../",
type: "POST",
processData: false,
contentType: false,
data: data,
success: function (data) {
if (data) {
}
},
error: function (er) {
MSGBox(er);
}
});
}

Categories

Resources