Cancel, Abort and Retry individual file upload with ResumableJS - javascript

I've successfully managed to upload multiple files in chunks to a server using ResumableJS. During the upload process the user is able to see the overall upload progress and the individual file upload percentage. It's also possible to pause/resume the overall upload.
What i would like to now is allow the user to cancel/abort an individual file upload without interrupting the other file uploads.
In ResumableJS website there are some methods that allow to do what i want, but no examples on how to accomplish this.
I have tried the following:
onclick="ResumableFile.abort(); return(false);"
onclick="file.abort(); return(false);"
onclick="this.abort(); return(false);"
How may i abort a specific file upload without interrupting the overall file upload?
UPDATE: Here is my JS code:
var r = new Resumable({
target: 'FileHandler.ashx'
});
// Resumable.js isn't supported, fall back on a different method
if (!r.support)
{}
else
{
// Show a place for dropping/selecting files
$('.resumable-drop').show();
r.assignDrop($('.resumable-drop')[0]);
r.assignBrowse($('.resumable-browse')[0]);
// Handle file add event
r.on('fileAdded', function (file)
{
//// Add the file to the list
$('.resumable-list').append('<li class="resumable-file-' + file.uniqueIdentifier + '">Uploading <span class="resumable-file-name"></span> <span class="resumable-file-progress"></span> <button type="button" id="removeButton" onclick="abortFile();">Remove</button>');
$('.resumable-file-' + file.uniqueIdentifier + ' .resumable-file-name').html(file.fileName);
// Actually start the upload
r.upload();
});
//var file = new ResumableFile();
//$("#removeButton").on("click", function ()
//{
// console.log("abort!");
// file.abort();
//});
function abortFile()
{
console.log("abort!");
r.abort();
}
r.on('pause', function ()
{
// Show resume, hide pause main progress bar
});
r.on('complete', function ()
{
// Hide pause/resume when the upload has completed
});
r.on('fileSuccess', function (file, message)
{
// Reflect that the file upload has completed
});
r.on('fileError', function (file, message)
{
// Reflect that the file upload has resulted in error
});
r.on('fileProgress', function (file)
{
// Handle progress for both the file and the overall upload
});
}
With Ruben Rutten's help here is how i solved my issue:
// Handle file add event
r.on('fileAdded', function (file)
{
// Show progress bar
// Show pause, hide resume
//// Add the file to the list
$('.resumable-list').append('<li class="resumable-file-' + file.uniqueIdentifier + '">Uploading <span class="resumable-file-name"></span> <span class="resumable-file-progress"></span> <button type="button" class="removeButton" id="' + file.uniqueIdentifier + '">Remove</button>');
$('.resumable-file-' + file.uniqueIdentifier + ' .resumable-file-name').html(file.fileName);
///event to remove file from upload list
$(".removeButton").on("click", function ()
{
for (var i = 0; i < r.files.length; i++)
{
var identifier = $(this).attr("id");
if (r.files[i].uniqueIdentifier == identifier)
{
r.files[i].cancel();
$('.resumable-file-' + identifier).remove();
}
}
});
r.upload();
});

I know this is an old question, but this might help someone:
Looking at your solution, there is a more efficient way to obtain the resumable instance than iterating all of the files:
// event to remove file from upload list
$(".removeButton").on("click", function ()
{
var identifier = $(this).attr("id");
var file = resumable.getFromUniqueIdentifier(identifier);
file.cancel();
$('.resumable-file-' + identifier).remove();
});

If you have your JavaScript file / part, you create a var r = new Resumable(). You should create a function that aborts it, such as
function abortFile() {
r.abort();
}
Then on your button, use onclick="abortFile(); return false;"
I haven't tested it, but it should work.
In case you use jQuery, you could do the following:
<button id="cancelButton">Cancel</button>
<script type="text/javascript">
// Not sure if this works, just to test
var file = new ResumableFile();
$("#cancelButton").on("click", function() {
file.abort();
});
</script>

Related

<input type="file"> accept property will be ignored when drag and drop file, how can I prevent this?

I want to force the user to only select a CSV or excel file.
Please see this minimum example:
<input type="file" accept=".csv, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel">
Although I can use accept property to force the file selector not to select other files, I can still use drag and drop files directly from Finder, and it will works--the accept property will be ignored.
Is it possible to prevent this?
You will need to perform server-side validation, but you can make the user experience better by checking the type of the file against a Set of allowed types.
const allowedTypes = new Set(['application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'application/vnd.ms-excel']);
document.querySelector('input[type=file]').addEventListener('change', function(){
if(!allowedTypes.has(this.files[0].type)){
console.log("Not a CSV file");
this.value = '';//clear the input for invalid file
} else {
console.log("CSV file");
}
});
<input type="file" accept=".csv, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel">
Note that csv files has many MimeTypes so you should check for more than
"application/vnd.ms-excel" => .CSV Mimetypes, and you can check against that in client side
as well by comparing the type of the file against an array of your accepted types
that way you can add or delete the way that fits your needs
// the list of the accepted types since we need it always it's better to
// make it global instead of local to the onchange litener, and even you can
// add other types dynamically as well;
const acceptedTypes = ["text/csv", "text/x-csv", "application/x-csv", "application/csv", "text/x-comma-separated-values", "text/comma-separated-values", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "application/vnd.ms-excel"];
document.querySelector("[type='file']").onchange = function() {
if(!acceptedTypes.includes(this.files[0].type)) {
console.log("This file is not allowed for upload");
// if the file is not allowed then clear the value of the upload element
this.value = "";
}
};
And if you want this behaviour only when the user drags and drops the file then you can customize it like this
const acceptedTypes = ["text/csv", "text/x-csv", "application/x-csv", "application/csv", "text/x-comma-separated-values", "text/comma-separated-values", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "application/vnd.ms-excel"];
// global variable to hold if the user has dragged the file or not
let isDragged = false;
// the `ondragover` gets triggered before the `onchange` event so it works as expected
document.querySelector("[type='file']").ondragover = function() {
isDragged = true;
};
document.querySelector("[type='file']").onchange = function() {
// if there was no drag then do nothing
if(!isDragged) return;
if(!acceptedTypes.includes(this.files[0].type)) {
console.log("This file is not allowed for upload");
// if the file is not allowed then clear the value of the upload element
this.value = "";
}
isDragged = false;
};
You just have to add an event listener and function to either accept or reject the file:
fileInput.addEventListener("change", func);
var func = function() {
if(fileInput.files[0].type == /*insert your file types here*/) {
//accept file and do stuff with it
} else {
//reject and tell user that is was rejected and why
}
}
I am not sure this works with the drag & drop file input, but I know it works with the regular type. The change event is called whenever the file changes.

Feed FileReader from server side files

I´m starting to customize/improve an old audio editor project. I can import audio tracks to my canvas VIA drag&drop from my computer. The thing is that I also would like to use audio tracks already stored in the server just clicking over a list of available tracks... instead of use the <input type="file"> tags. How can I read the server side files with a FileReader?Ajax perhaps? Thanks in advance.
This is the code for the file reader:
Player.prototype.loadFile = function(file, el) {
//console.log(file);
var reader = new FileReader,
fileTypes = ['audio/mpeg', 'audio/mp3', 'audio/wave', 'audio/wav'],
that = this;
if (fileTypes.indexOf(file.type) < 0) {
throw('Unsupported file format!');
}
reader.onloadend = function(e) {
if (e.target.readyState == FileReader.DONE) { // DONE == 2
$('.progress').children().width('100%');
var onsuccess = function(audioBuffer) {
$(el).trigger('Audiee:fileLoaded', [audioBuffer, file]);
},
onerror = function() {
// on error - show alert modal
var tpl = (_.template(AlertT))({
message: 'Error while loading the file ' + file.name + '.'
}),
$tpl = $(tpl);
$tpl.on('hide', function() { $tpl.remove() })
.modal(); // show the modal window
// hide the new track modal
$('#newTrackModal').modal('hide');
};
that.context.decodeAudioData(e.target.result, onsuccess, onerror);
}
};
// NOTE: Maybe move to different module...
reader.onprogress = function(e) {
if (e.lengthComputable) {
$progress = $('.progress', '#newTrackModal');
if ($progress.hasClass('hide'))
$progress.fadeIn('fast');
// show loading progress
var loaded = Math.floor(e.loaded / e.total * 100);
$progress.children().width(loaded + '%');
}
};
reader.readAsArrayBuffer(file);
};
return Player;
Thanks for the suggestion micronn, I managed to make a bypass without touch the original code. The code as follows is the following:
jQuery('.file_in_server').click(function()
{
var url=jQuery(this).attr('src');//Get the server path with the mp3/wav file
var filename = url.replace(/^.*[\\\/]/, '');
var path="http://localhost/test/audio/tracks/"+filename;
var file = new File([""], filename); //I need this hack because the original function recives a buffer as well as the file sent from the web form, so I need it to send at least the filename
var get_track = new XMLHttpRequest();
get_track.open('GET',path,true);
get_track.responseType="arraybuffer";
get_track.onload = function(e)
{
if (this.status == 200) //When OK
{
Audiee.Player.context.decodeAudioData(this.response,function(buffer){ //Process the audio toward a buffer
jQuery('#menu-view ul.nav').trigger('Audiee:fileLoaded', [buffer, file]); //Send the buffer & file hack to the loading function
},function(){
alert("Error opening file");
jQuery('#newTrackModal').modal('hide');
});
}
};
get_track.send();
});
After this, in the fileLoaded function, the track is added to the editor.
var name = 'Pista ' + Audiee.Collections.Tracks.getIndexCount();
track = new TrackM({buffer: audioBuffer, file: file, name: name}); //being audioBuffer my buffer, file the fake file and name the fake file name
Audiee.Collections.Tracks.add(track);
And... thats it!

Listener for Download Dialog created Firefox

I have created a download manager and now I am creating its extensions. For firefox extension the problem is that I cannot find a decent way in which I can listen for creation of download and then stop the creation and redirect the url to my application. So here are two approaches I have already tried along with their drawbacks:
1. Using Downloads.jsm
const {Cu} = require("chrome");
Cu.import("resource://gre/modules/Downloads.jsm");
Cu.import("resource://gre/modules/Task.jsm");
let list;
let view = {
onDownloadAdded: download => {
Task.spawn(function () {
try {
// Send download.source.url to my application
yield download.finalize(true);
yield list.remove(download);
} catch (ex) {
console.error(ex);
}
});
}
};
Task.spawn(function () {
list = yield Downloads.getList(Downloads.ALL);
yield list.addView(view);
}).then(null, Cu.reportError);
exports.onUnload = function (reason) {
list.removeView(view);
};
Problem: The problem with this approach is that user have to 1st click save as dialog then click start button on my download manager which is totally unacceptable. If I have to use this approach I need some way to remove this save as dialog. And because I am canceling download when its added the download is shown as canceled in "Show all downloads" dialog in firefox.
2. Using http-on-modify-request
var dlExtensions = [".exe", ".iso"];
const {components, Cc, Ci} = require("chrome");
httpRequestObserver =
{
observe: function (aSubject, aTopic, aData) {
if (aTopic == "http-on-modify-request") {
let url;
aSubject.QueryInterface(Ci.nsIHttpChannel);
url = aSubject.URI.spec;
for (var x in dlExtensions) {
if (url.endsWith(dlExtensions[x])) {
aSubject.cancel(components.results.NS_BINDING_ABORTED);
// Send url to my application
break;
}
}
}
}
};
var observerService = components.classes["#mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
observerService.addObserver(httpRequestObserver, "http-on-modify-request", false);
Problem: This code works as expected but the problem is I have to define all download Extensions of file to download and there are too many of them.
Requirement: So basically I need a listener that listens for creation of download in firefox and a way in which I can stop regular download dialog appearing and make my code execute instead.(Note that the listener present in method 1 is not useable because it listens for addition of download in download list not for the creation of download that leads to creation of download dialog)

Non-ajax post using Dropzone.js

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);
}
}

Removing a file in modern browsers

Problem
I am currently using ( https://github.com/blueimp/jQuery-File-Upload/wiki ) this jQuery HTML5 Uploader.
The basic version, no ui.
The big problem is, that I looked everywhere (Mozilla Developer Network, SO, Google, etc.) and found no solution for removing a files already added via dragNdrop or manually via the file input dialogue.
Why do I want to achieve removing a file?
Because it seems that HTML5 has a kind of "bug".
If you drop / select a file (file input has set multiple) upload it, and then drop / select another file you magically have now the new file twice and it gets uploaded twice.
To prevent this magic file caching the use would have to refresh the page, which is not what someone wants to have for his modern AJAX web app.
What I have tried so far:
.reset()
.remove()
Reset Button
Setting .val() to ''
This seems to be a general HTML5 JS problem not jQuery specific.
Theory
Might it be, that $j('#post').click (I bind / re-bind a lot of times different callbacks), stacks the callbacks methods so that each time the updateFileupload function is called an additional callback is set.
The actual problem would now not rely anymore on the HTML5 upload, it would now rely on my could, miss-binding the .click action on my submit button (id=#post).
If we now call .unbind before each .click there shouldn't be any duplicated callback binding.
Code
Function containing the upload code:
function updateFileupload (type) {
var destination = "";
switch(type)
{
case upload_type.file:
destination = '/wall/uploadfile/id/<?=$this->id?>';
break;
case upload_type.image:
destination = '/wall/upload/id/<?=$this->id?>';
break;
}
$j('#fileupload').fileupload({
dataType: 'json',
url: destination,
singleFileUploads: false,
autoUpload: false,
dropZone: $k(".dropZone"),
done: function (e, data) {
console.log("--:--");
console.log(data.result);
upload_result = data.result;
console.log(upload_result);
console.log("--:--");
console.log(type);
if(type == upload_type.image)
{
var imageName = upload_result.real;
console.log(imageName);
$k.get('/wall/addpicture/id/<?=$this->id ?>/name'+imageName, function(data){
if(data > 0){
console.log("I made it through!");
if(!data.id)
{
$k('#imgUpload').html('');
//$k('#imgPreview').fadeOut();
$k('#newPost').val('');
$k.get("/wall/entry/id/"+data, function(html){
$k('#postList').prepend(html);
});
}
}
});
}
},
send: function(e, data){
var files = data.files;
var duplicates = Array(); // Iterate over all entries and check whether any entry matches the current and add it to duplicates for deletion
for(var i=0; i<data.files.length;i++)
{
for(var j=0;j<data.files.length-1;j++)
{
if(files[i].name == files[j].name && i != j)
{
duplicates.push(j);
}
}
}
if(duplicates.length > 0)
{
for(var i=0;i<duplicates.length;i++)
files.splice(i, 1);
}
console.log("Duplicates");
console.log(duplicates);
},
drop: function(e, data){
console.log("outside");
// $k.each(data.files, function(index, file){
// $k('#imageListDummy').after('<li class="file-small-info-box">'+file.name+'</li>');
// console.log(file);
//
// });
},
add: function(e, data){
upload_data = data;
console.log(data);
$k.each(data.files, function(index, file){
$k('#imageListDummy').after('<li class="file-small-info-box">'+file.name+'</li>');
console.log(file);
});
$j('#post').click(function(event){
upload_data.submit();
if(type == upload_type.image)
{
var file = upload_data.files[0];
console.log("I am here");
console.log(file);
var img = document.createElement("img");
img.src = window.URL.createObjectURL(file);
img.height = 64;
img.width = 64;
img.onload = function(e) {
window.URL.revokeObjectURL(this.src);
}
document.getElementById('imgPreview').appendChild(img);
$k('#imgPreview').show();
}
clickPostCallback(event);
});
$j('#showSubmit').show();
}
});
}
It could be more a browser security issue.
Current file uploads specs don't allow javascript (or anything as far as I know) to tamper with the value of the file field even if to remove it.
So I would imagine any good file uploader would create multiple file upload fields so you can remove the entire field rather than play with the value?
This is speculation though.
Updated answer to Updated Question:
Shouldn't click() only be bound once? you shouldn't need to rebind a click event to a single element '#post' (unless this element changes, in which case it should really be a class). You can place the click() event binding outside of the options for file upload, as long as it's contained in a $(function(){} so it's when the DOM's ready.
Aside from that I'm trying to read the code without any HTML and no experience in multiple file uploading. The best thing to do is try and re-create it on jsfiddle.net, that way others can go in and play around with the code without affecting you and your likely to find the problem while putting the code in there anyway :)

Categories