I would like to have a simple drop zone to upload image via AJAX and jQuery. I have found some plugins but they are way too customized for what's needed, and I cannot get any of them working properly.
I also would like the drop zone to be clickable, in order to manually choose a file from the OS file dialog.
I found this script, that works fine but where the drop zone is not clickable:
// ---------------------------- drop zone to upload image : '#dropfile'
$(document).on('dragenter', '#dropfile', function() {
return false;
});
$(document).on('dragover', '#dropfile', function(e){
e.preventDefault();
e.stopPropagation();
return false;
});
$(document).on('dragleave', '#dropfile', function(e) {
e.preventDefault();
e.stopPropagation();
return false;
});
$(document).on('drop', '#dropfile', function(e) {
if(e.originalEvent.dataTransfer){
if(e.originalEvent.dataTransfer.files.length) {
// Stop the propagation of the event
e.preventDefault();
e.stopPropagation();
// Main function to upload
upload(e.originalEvent.dataTransfer.files);
}
}
return false;
});
function upload(files) {
var f = files[0] ;
// Only process image files.
if (!f.type.match('image/jpeg')) {
alert(‘The file must be a jpeg image’) ;
return false ;
}
var reader = new FileReader();
// When the image is loaded, run handleReaderLoad function
reader.onload = handleReaderLoad;
// Read in the image file as a data URL.
reader.readAsDataURL(f);
}
function handleReaderLoad(evt) {
var pic = {};
pic.file = evt.target.result.split(',')[1];
var str = jQuery.param(pic);
$.ajax({
type: 'POST',
url: ‘url_to_php_script.php’,
data: str,
success: function(data) {
//do_something(data) ;
}
});
}
So I added an invisible file type input, but image data seems to be sent twice. I suppose it's due a bad event propagation with the original drop zone:
// ---------------------------- clickable drop zone with invisible file input '#inputFile'
$('#dropfile).on('click', function() {
$('input#inputFile').trigger('click');
$('input#inputFile').change(function(e) {
upload($('input#inputFile')[0].files);
});
});
I tried to add these lines but data is always sent twice:
$('#dropfile).on('click', function() {
$('input#inputFile').trigger('click');
$('input#inputFile').change(function(e) {
upload($('input#inputFile')[0].files);
// -------------- stop sending data twice ???
e.preventDefault();
e.stopPropagation();
return false;
});
});
I still don't know why data is sent twice but I found a better script here:
https://makitweb.com/drag-and-drop-file-upload-with-jquery-and-ajax/
Related
I'm having some troubles with javascript.
I'm trying to do a desktop app with NW.JS. I have a .xml file which I drag and drop in my app then it run a function to read the XML do some stuff and save a new file in .csv
It's work fine but now i would be able to update a progress bar during the function...
I tried setInterval and setTimeOut() but I'mhaving always the same result : nothing append until the function is finished.
here is my code
//Same as $(document).ready();
function ready(fn) {
if (document.readyState != 'loading'){
fn();
} else {
document.addEventListener('DOMContentLoaded', fn);
}
}
//When the page has loaded, run this code
ready(function(){
// prevent default behavior from changing page on dropped file
window.ondragover = function(e) { e.preventDefault(); return false };
// NOTE: ondrop events WILL NOT WORK if you do not "preventDefault" in the ondragover event!!
window.ondrop = function(e) { e.preventDefault(); return false };
var holder = document.getElementById('holder');
holder.ondragover = function () { this.className = 'hover'; return false; };
holder.ondragleave = function () { this.className = ''; return false; };
holder.ondrop = function (e) {
e.preventDefault();
var file = e.dataTransfer.files[0],
reader = new FileReader();
reader.onload = function (event) {
########I'm doing stuff here to convert file and i want to update the progressbar##########
};
reader.readAsText(file);
//reader.readAsDataURL(file);
return false;
};
});
Thanks for your help
best regards,
After trying the same code in NWJS and Electron, I found the problem to be that any long-running process in the 'main' Chromium process blocks rendering. The solution is to spawn a child process that communicates via Node's IPC. More details in this answer.
I am using Jquery Form Plugin to upload files using onchange event listener on the file upload input element to upload the file as soon as it is selected.
I want to allow only one file at a time to be uploaded and adding the rest in a queue to be uploaded once the existing upload is complete.
I tried making this happen by using a variable and setting it false in beforeSubmit and then switching it back to true once the upload is complete. However, it keeps turning true automatically.
Here is my Javascript code:
var allowUpload = true;
console.log('initial');
var options = {
beforeSubmit: beforeSubmit,
uploadProgress: OnProgress,
success: afterSuccess,
resetForm: true
};
$('#upload').change(function() {
console.log(allowUpload);
if(allowUpload)
{
console.log('onchange:' + allowUpload);
$('#uploadForm').ajaxSubmit(options);
}
return false;
});
function afterSuccess(data)
{
allowUpload = true;
console.log('aftersuccess' + allowUpload);
}
function beforeSubmit(data)
{
allowUpload = false;
console.log(allowUpload);
}
I have decided to update this question as I still can't change the imageId
This is my current dropzone implementation
$("div#UploadImage").dropzone({
url: '#Url.Action("SaveUploadedPhoto", "Listing", new { advertId = #Model.AdvertId })',
addRemoveLinks: true,
init: function () {
var myDropzone = this;
// First change the button to actually tell Dropzone to process the queue.
$("input[type=submit]").on("click", function (e) {
// Make sure that the form isn't actually being sent.
e.preventDefault();
e.stopPropagation();
myDropzone.processQueue();
});
myDropzone.on("success", function (file, rep) {
console.log(rep.UniqueId);
fileList[i] = { "serverFileName": rep.UniqueId, "fileName": rep.UniqueId, "fileId": rep.UniqueId, "name": rep.UniqueId };
i++;
console.log(fileList);
});
myDropzone.on("removedfile", function (file) {
var rmvFile = "";
for (f = 0; f < fileList.length; f++) {
if (fileList[f].fileName == file.name) {
rmvFile = fileList[f].serverFileName;
}
}
if (rmvFile) {
$.ajax({
url: '#Url.Action("DeleteUploadedFile", "Listing")',
type: "POST",
data: { "id": rmvFile }
});
}
});
},
maxFilesize: 2,
maxFiles: 12
});
When I upload an image, we pass the image to a third party company which returns a uniqueId for that image, this uniqueId then gets saved in my database, I then pass this uniqueId back to the view which will become the uploaded image name, I'm trying to do this in the "success" function in the above implementation of dropzone, when I do
console.log(rep.UniqueId);
I can see the value and its correct.
When I press "Remove image" on dropzone the "removedFile" function is called, this doesn't fire to the controller because rmvFile is the old image name and it doesn't find a match within the for loop, I know this because when I do
console.log(rmvFile);
It shows the old name, now my question is what am I doing wrong here? how can I change the uploaded image name or id to the uniqueId which is returned after the uploaded has been completed? so I can successfully delete it as and when it required.
I have browsed the web, tried situation's which has resulted in how my success method currently looks.
I've managed to get this working now, after trying many different solutions.
I have found doing the following passes in the uniqueId for the image
file.serverId['UniqueId'],
This error message pop up in the terminal while I try to handle file dropping in chrome packaged app:
Can't open same-window link to "path/to/some/file.txt"; try target="_blank".
This is the code I was using:
$.fn.extend({
filedrop: function (options) {
var defaults = {
callback : null
}
options = $.extend(defaults, options)
return this.each(function() {
var files = []
var $this = $(this)
// Stop default browser actions
$this.bind('dragover dragleave', function(event) {
event.stopPropagation()
event.preventDefault()
})
// Catch drop event
$this.bind('drop', function(event) {
// Stop default browser actions
event.stopPropagation()
event.preventDefault()
// Get all files that are dropped
files = event.originalEvent.target.files || event.originalEvent.dataTransfer.files
// Convert uploaded file to data URL and pass trought callback
if(options.callback) {
var reader = new FileReader()
reader.onload = function(event) {
options.callback(event.target.result)
}
reader.readAsDataURL(files[0])
}
return false
})
})
}
})
// Event listener filedropper
$('.dropbox').filedrop({
callback : function(fileEncryptedData) {
console.log(fileEncryptedData);
}
})
Is this something to do with chrome app permission or something in my code. There's already e.stopPropagation in there so i have no idea why.
I'm wondering if there's any way to make Dropzone.js (http://dropzonejs.com) work with a standard browser POST instead of AJAX.
Some way to inject the inputs type=file in the DOM right before submit maybe?
No. You cannot manually set the value of a <input type='file'> for security reasons. When you use Javascript drag and drop features you're surpassing the file input altogether. Once a file is fetched from the user's computer the only way to submit the file to the server is via AJAX.
Workarounds: You could instead serialize the file or otherwise stringify it and append it to the form as a string, and then unserialize it on the server side.
var base64Image;
var reader = new FileReader();
reader.addEventListener("load", function () {
base64Image = reader.result;
// append the base64 encoded image to a form and submit
}, false);
reader.readAsDataURL(file);
Perhaps you're using dropzone.js because file inputs are ugly and hard to style? If that is the case, this Dropzone.js alternative may work for you. It allows you to create custom styled inputs that can be submitted with a form. It supports drag and drop too, but with drag and drop you cannot submit the form the way you want. Disclaimer: I am author of aforementioned library.
So, if I understood correctly you want to append some data (input=file) before submit your form which has dropzone activated, right?
If so, I had to do almost the same thing and I got it through listening events. If you just upload one file, you should listen to "sending" event, but if you want to enable multiple uploads you should listen to "sendingmultiple". Here is a piece of my code that I used to make this work:
Dropzone.options.myAwesomeForm = {
acceptedFiles: "image/*",
autoProcessQueue: false,
uploadMultiple: true,
parallelUploads: 100,
maxFiles: 100,
init: function() {
var myDropzone = this;
[..some code..]
this.on("sendingmultiple", function(files, xhr, formData) {
var attaches = $("input[type=file]").filter(function (){
return this.files.length > 0;
});
var numAttaches = attaches.length;
if( numAttaches > 0 ) {
for(var i = 0; i < numAttaches; i++){
formData.append(attaches[i].name, attaches[i].files[0]);
$(attaches[i]).remove();
}
}
});
[..some more code..]
}
}
And that's it. I hope you find it helpful :)
PS: Sorry if there's any grammar mistakes but English is not my native language
For future visitors
I've added this to dropzone options:
addedfile: function (file) {
var _this = this,
attachmentsInputContainer = $('#attachment_images');
file.previewElement = Dropzone.createElement(this.options.previewTemplate);
file.previewTemplate = file.previewElement;
this.previewsContainer.appendChild(file.previewElement);
file.previewElement.querySelector("[data-dz-name]").textContent = file.name;
file.previewElement.querySelector("[data-dz-size]").innerHTML = this.filesize(file.size);
if (this.options.addRemoveLinks) {
file._removeLink = Dropzone.createElement("<a class=\"dz-remove\" href=\"javascript:undefined;\">" + this.options.dictRemoveFile + "</a>");
file._removeLink.addEventListener("click", function (e) {
e.preventDefault();
e.stopPropagation();
if (file.status === Dropzone.UPLOADING) {
return Dropzone.confirm(_this.options.dictCancelUploadConfirmation, function () {
return _this.removeFile(file);
});
} else {
if (_this.options.dictRemoveFileConfirmation) {
return Dropzone.confirm(_this.options.dictRemoveFileConfirmation, function () {
return _this.removeFile(file);
});
} else {
return _this.removeFile(file);
}
}
});
file.previewElement.appendChild(file._removeLink);
}
attachmentsInputContainer.find('input').remove();
attachmentsInputContainer.append(Dropzone.instances[0].hiddenFileInput).find('input').attr('name', 'files');
return this._updateMaxFilesReachedClass();
},
This is default implementation of dropzone's addedfile option with 3 insertions.
Declared variable attachmentsInputContainer. This is invisible block. Something like
<div id="attachment_images" style="display:none;"></div>
Here I store future input with selected images
Then in the end of function remove previously added input(if any) from block and add new
attachmentsInputContainer.find('input').remove();
attachmentsInputContainer.append(Dropzone.instances[0].hiddenFileInput).find('input').attr('name', 'files');
And now, when you send form via simple submit button, input[name="files"] with values will be send.
I've made this hack because I append files to post that maybe not created yet
This is what I used for my past projects,
function makeDroppable(element, callback) {
var input = document.createElement('input');
input.setAttribute('type', 'file');
input.setAttribute('multiple', true);
input.style.display = 'none';
input.addEventListener('change', triggerCallback);
element.appendChild(input);
element.addEventListener('dragover', function(e) {
e.preventDefault();
e.stopPropagation();
element.classList.add('dragover');
});
element.addEventListener('dragleave', function(e) {
e.preventDefault();
e.stopPropagation();
element.classList.remove('dragover');
});
element.addEventListener('drop', function(e) {
e.preventDefault();
e.stopPropagation();
element.classList.remove('dragover');
triggerCallback(e);
});
element.addEventListener('click', function() {
input.value = null;
input.click();
});
function triggerCallback(e) {
var files;
if(e.dataTransfer) {
files = e.dataTransfer.files;
} else if(e.target) {
files = e.target.files;
}
callback.call(null, files);
}
}