I'm trying to use dropzone to upload large files directly to Azure storage using a SAS(Shared Access Signature). This is documented on the Azure side here: https://learn.microsoft.com/en-us/rest/api/storageservices/Put-Block
So I have to get the blockId(A Base64 string that identifies the chunk I'm sending) and put it in the url of the request sending that chunk.
Dropzone supports chunking now so I decided to use that. Unfortunately, implementations of it are hard to find.
I can change the url in the processing event but that's per file and I can't get chunk data from it.
I can send the blockId in the form data using params but can't seem to change the url from it.
Is it possible to add the blockId to my url? Or will I have to send it to my server first and upload from there? Thanks.
$("#videoSection").dropzone({
params: function (files, xhr, chunk) {
console.log(chunk);
xhr.setRequestHeader('x-ms-blob-type', 'BlockBlob');
//I can get the blockId from the chunk here
//this.options.url.replace("test") //doesn't work
//xhr.open(this.options.method, this.options.url.replace("test"), true); //doesn't work
return {"url": "test"}; //this returns form-data
},
url: "Create",
method: "PUT",
//headers: { "x-ms-blob-type": "BlockBlob" },
chunking: true,
chunkSize: 4000000,
forceChunking: true,
retryChunks: true,
retryChunksLimit: 3,
autoProcessQueue: false,
acceptedFiles: "video/*",
maxFiles: 1,
maxFilesize: 3000,
previewTemplate: $("#videoTemplate").html(),
dictDefaultMessage: "Drop Video Here",
init: function () {
this.on("processing", function (file) {
var blockId = 1;
#*this.options.url = "#(Config.Value.StoragePrefix)/#(user.Company.StorageName)/inspections/#Model.InspectionData.Inspection.Id/videos/"
+ file.name + "#Html.Raw(Model.SharedAccessSignature + "&comp=block&blockid=")" + blockId;*#
var progressBar = $(file.previewElement).find(".dropzoneProgressBar");
progressBar.show();
});
this.on("chunksUploaded", function (file, done) {
$.ajax({
type: "PUT",
url: "#(Config.Value.StoragePrefix)/#(user.Company.StorageName)/inspections/#Model.InspectionData.Inspection.Id/videos/"
+ file.name + "#Html.Raw(Model.SharedAccessSignature + "&comp=blocklist")",
success: function (data) {
done();
},
error: function (e) {
toastr.error(e);
console.log(e);
}
});
});
this.on("uploadprogress", function (file, progress, bytesSent) {
var progressBar = $(file.previewElement).find(".dropzoneProgressBar");
progress = bytesSent / file.size * 100;
progressBar.width(progress + "%");
});
this.on("success", function (file, response) {
var successCheckmark = $(file.previewElement).find(".dropSuccess");
successCheckmark.toggle();
var progressBar = $(file.previewElement).find(".dropzoneProgressBar");
progressBar.css("background-color", "green");
});
}
});
There is a problem with this line this.options.url.replace("test") //doesn't work. It should be this.options.url = this.options.url.replace("test", "newValue") //this should work. I am using a similar approach. The issue is that the params callback is called once XmlHttpRequest has been opened so you need to set the url for the first chunk before you start processing the file and in the params you need to set the url for the next chunk to be sent (use chunk.index + 1 for zero based indexing). I am not sure if this would work if you had parallelChunkUploads enabled - I haven't tested if the first chunk to be processed is the first one. Let me know if you need more information.
Related
I want to upload images with ajax. I convert the image to a base64 string. Posting the data works, but how do I get it on the server (I'm using Laravel 5.4)? I can do $request->all() what gives me an array with all the images base64 strings combined. I cannot do anything with that because whatever I do with that array will result in a 500 error.
This is my script to convert the images and post them.
let queue = [];
function addFile(input) {
let files = input.files,
j = files.length,
file;
for (i = 0; i < j; i += 1) {
let reader = new FileReader();
reader.onloadend = function (e) {
$('#upload-InnerPanel').append(
"<div class='upload-ItemPanel'><img class='upload-ImagePreview' src='" + e.target.result + "' > </div>");
queue.push(reader.result);
};
file = files[i];
reader.readAsDataURL(file);
}
}
$('#upload-ButtonSelect').on("click" , function () {
$('#upload-UploadInput').click();
});
$('#upload-UploadInput').change(function () {
addFile(this);
});
$('#upload-ButtonUpload').click(function () {
$.ajax({
url: "/admin/upload",
type: "POST",
data: queue,
processData: false,
error: function(xhr, status, error) {
let err = xhr.responseText;
//console.log(err);
$('#upload-InnerPanel').append("<iframe width='600' height='500' src='" + err +"'> </iframe>")
},
success: function (xhr) {
console.log(xhr);
},
headers: {
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
}
});
});
This is my controller:
public function upload(Request $request)
{
return var_dump($request->all());
}
That works, sort of, because My response is one long base64 in an array with just 1 item. Even if I add multiple images I just get one item in the array instead of three. It now combines them all in to one. Also, as I said. I cannot do anything with that array what does not result in a 500 error.
So my question is:
How do I get it to work so I can post multiple items instead of one and get the data on the backend?
You could add each file as a new input on your form, so that you have them separately on the back end.
In your addFile Javascript, instead of queue.push(reader.result), append a hidden input with the results:
reader.onloadend = function (e) {
// ... your code
// Update the form selector to suit your form
$('form').append('<input type="hidden" name="files[]" value="' + reader.result + '">');
};
Then in your ajax form submission:
$('#upload-ButtonUpload').click(function () {
// Again update the form selector to suit your form
var data = $('form').serialize();
$.ajax({
url: "/admin/upload",
type: "POST",
data: data,
// ... etc
Im uploading video and image files with progress bar using jquery ajax. Im using server side form validation and check duplicate entry. My issue is validation error message show after progress bar becomes complete. I want something like this...
if form validation is correct
then show progress bar
else if form validation is not correct
then only show error message of form validation
else if duplicate entry
then only show error message of duplicate entry
this is my js code:
$.ajax({
url: ajaxUrl,
type: 'POST',
dataType: 'json',
processData: false,
contentType: false,
async: true,
data: formData,
xhr: function () {
//upload Progress
var xhr = $.ajaxSettings.xhr();
if (xhr.upload) {
xhr.upload.addEventListener('progress', function (event) {
var percent = 0;
var position = event.loaded || event.position;
var total = event.total;
if (event.lengthComputable) {
percent = Math.ceil(position / total * 100);
}
var temp = event.loaded/event.total*100;
//update progressbar
$('div#upload-progress').text(percent + "%");
$('div#upload-progress').css("width", + temp + "%");
}, true);
}
return xhr;
},
complete: function(xhr) {
if(xhr.responseText) {
//console.log(xhr);
}
},
success: function(data, status){
if (data.hasOwnProperty('form-error')) {
$.each(data['form-error'], function (key, value) {
$("span#span_" + key).text(value);
})
} else {
// Form validation is correct
}
},
error : function(xhr, textStatus, errorThrown) {
var data = xhr.responseText;
myWindow = window.open("data:text/html," + encodeURIComponent(data), "parent", "width=1000, height=600");
myWindow.focus();
}
});
Can anyone give me any suggestion to how to do this?
If you're using the server to validate the file, that means that the file will have to be uploaded first before you can validate it, hence why you see the progress bar first. If you only want to check for duplicates (based on filename, size etc.), you need 2 ajax requests:
Send only the filename & size and validate it.
If valid, upload the file. (and validate again, of course)
Just my 2 cents.
I did made a AJAX file upload with a progress bar, you might want to take a look? I guess?
https://github.com/felixfong227/fileupload/blob/master/static/js/index.js
I keep getting the problem with downloaded zip file. All the time when I click on the archive it throws "Archive is either unknown format or damaged". I think the problem is with the coding (format of the content of the archive). Please help!
$.ajax({
url: '/Unloading/' + $("#radioVal:checked").val(),
type: "POST",
data: { 'dateTimeTo': $("#dateTimeTo").val(), 'dateTimeFrom': $("#dateTimeFrom").val() },
beforeSend: function() {$("#divLoading").show();},
success: function (result) {
$("#divLoading").hide();
if (result.length === 0) {
var message ="Error";
$("#dialog-message").text(message);
$("#dialog-message").dialog({
modal: true,
buttons: {
close: function() {
$(this).dialog("close");
}
}
});
} else {
var xmlstr, filename, bb;
filename = "UnloadedLeases.zip";
bb = new Blob([result], { type: "application/zip" }); // I think somewhere here is a problem with the coding
var pom = document.createElement('a');
pom.setAttribute("target", "_blank");
pom.setAttribute('href', window.URL.createObjectURL(bb));
pom.setAttribute("download", filename);
document.body.appendChild(pom);
pom.click();
document.body.removeChild(pom); //removing the element a from the page
}
},
As far as I know, $.ajax doesn't let you download binary content out of the box (it will try to decode your binary from UTF-8 and corrupt it). Either use a jQuery plugin (like jquery.binarytransport.js) or use a xhr directly (with responseType).
$.ajax({
url: '/Unloading/' + $("#radioVal:checked").val(),
type: "POST",
dataType: 'binary', // using jquery.binarytransport.js
// ...
success: function (result) {
// Default response type is blob
if (result.size === 0) { // instead of length for blobs
// ...
} else {
var bb = result; // already a blob
// ...
}
}
})
I have an issue trying to remove the files which I have passed dropzone.js after calling the database.
When I navigate to the page and upload a new image and then remove it without refreshing the page it all works as expected.
Below is my current upload method
myDropzone.on("success", function (file, response) {
console.log(response);
file.serverId = response;
});
This is what is inside the response after doing console.log(response);
Object { UniqueId="evgopvdjfs1w9sos3jt5"}
Which is correct.
Now when I press F5 and refresh the page I populate dropzone with the following snippet which returns me the image that I've just uploaded.
$.getJSON("/Person/GetPreviews/").done(function (data) {
if (data.Data != '') {
$.each(data.Data, function (index, item) {
var UniqueId = item.ImageName;
var mockFile = {
name: item.ImageName,
serverId: UniqueId // This is what I need to delete the image
};
console.log(mockFile);
// Call the default addedfile event handler
myDropzone.emit("addedfile", mockFile);
// And optionally show the thumbnail of the file:
myDropzone.emit("thumbnail", mockFile, item.ImageUrl);
myDropzone.files.push(mockFile);
});
}
});
Now when I do console.log(mockFile); the below is shown, again this is correct.
Object { name="evgopvdjfs1w9sos3jt5", UniqueId="evgopvdjfs1w9sos3jt5"}
Now when it comes to removing the file this is my current delete function
removedfile: function (file) {
console.log(file);
$.ajax({
type: 'POST',
url: '#Url.Action("DeleteUploadedFile", "Person", new {userId= #Model.UserId})',
data: "id=" + file.serverId['UniqueId'],
dataType: 'html',
});
var ref;
return (ref = file.previewElement) != null ? ref.parentNode.removeChild(file.previewElement) : void 0;
},
Now when I press remove file on the pre populated image from the database, it throws an error on
data: "id=" + file.serverId['UniqueId'],
saying its underfined, I personally cannot see how because both console.logs show the UniqueId it makes me wonder if I'm missing something when I'm pre populating dropzone with the images?
As you can see I have a console.log(file); in the delete function which when hit shows the following
Object { name="evgopvdjfs1w9sos3jt5", UniqueId="evgopvdjfs1w9sos3jt5"}
Can anyone see whats wrong?
So this question is a bit old. I came across it and also found this answer, which helped me:
http://www.stackoverflow.com/questions/24445724/add-existing-image-files-in-dropzone
This particular question was answered here and just points out a mix-up between serverId and UniqueId:
https://github.com/enyo/dropzone/issues/844
I have used the below code the image has been deleted but the thumbnail image still showing.
Dropzone.options.myDropzone = {
init: function() {
this.on("success", function(file, response) {
file.serverId = response;
});
this.on("removedfile", function(file) {
if (!file.serverId) { return; }
$.post("delete-file.php?id=" + file.serverId);
});
}
For deleting thumbnails you have to enable
addRemoveLinks: true,
and to use "removedfile" option in dropzonejs
removedfile: Called whenever a file is removed from the list. You can listen to this and delete the file from your server if you want to.
addRemoveLinks: true,
removedfile: function(file) {
var _ref;
return (_ref = file.previewElement) != null ? _ref.parentNode.removeChild(file.previewElement) : void 0;
}
I also added an ajax call for delete script and it looks like this:
addRemoveLinks: true,
removedfile: function(file) {
var name = file.name;
$.ajax({
type: 'POST',
url: 'delete.php',
data: "id="+name,
dataType: 'html'
});
var _ref;
return (_ref = file.previewElement) != null ? _ref.parentNode.removeChild(file.previewElement) : void 0;
}
It works on my side, so I hope it helps.
In addition to the best answer above - to remove spaces and replace with dashes and convert to lower case apply this js in the dropzone.js file:
name = name.replace(/\s+/g, '-').toLowerCase();
To the filename handling - I edited the dropzone.js file AND the above ajax call. This way, the client handles the file naming, and it AUTOMATICALLY gets sent to the PHP/server-side, so u don't have to redo it there, and it's URL safe pretty much.
In some instances the js changed depending on the function / naming:
dropzone.js - line 293 (approx):
node = _ref[_i];
node.textContent = this._renameFilename(file.name.replace(/\s+/g, '-').toLowerCase());
dropzone.js - line 746 (approx):
Dropzone.prototype._renameFilename = function(name) {
if (typeof this.options.renameFilename !== "function") {
return name.replace(/\s+/g, '-').toLowerCase();
}
return this.options.renameFilename(name.replace(/\s+/g, '-').toLowerCase());
};
I moved the whole removedFile section up to the top of dropzone.js like so:
autoQueue: true,
addRemoveLinks: true,
removedfile: function(file) {
var name = file.name;
name =name.replace(/\s+/g, '-').toLowerCase(); /*only spaces*/
$.ajax({
type: 'POST',
url: 'dropzone.php',
data: "id=" + name,
dataType: 'html',
success: function(data) {
$("#msg").html(data);
}
});
var _ref;
if (file.previewElement) {
if ((_ref = file.previewElement) != null) {
_ref.parentNode.removeChild(file.previewElement);
}
}
return this._updateMaxFilesReachedClass();
},
previewsContainer: null,
hiddenInputContainer: "body",
Note I also added in a message box in the html: (div id="msg"></div>) in the HTML that will allow server side error handling/deleting to post a message back to the user as well.
in dropzone.php:
if (isset($_POST['id']) {
//delete/unlink file
echo $_POST['id'] . ' deleted'; // send msg back to user
}
This is only an expansion with the working code on my side. I have 3 files, dropzone.html, dropzone.php, dropzone.js
Obviously, you would create a js function instead of repeating the code, but it suited me since the naming changes. You could pass the variables in a js function yourself to handle filename spaces/chars / etc.