Drag&drop multiple file upload: updates and fixes

This commit is contained in:
Aysam Guerler
2013-09-19 22:53:34 -04:00
parent 1bdcd3ba94
commit cdf8c334b4
9 changed files with 506 additions and 230 deletions
+13 -1
View File
@@ -77,13 +77,25 @@ var GalaxyModal = Backbone.View.extend(
// link functions
$.each(options.buttons, function(name, value)
{
footer.append($('<button></button>').text(name).click(value)).append(" ");
footer.append($('<button id="' + String(name).toLowerCase() + '"></button>').text(name).click(value)).append(" ");
});
} else
// default close button
footer.append($('<button></button>').text('Close').click(function() { self.hide() })).append(" ");
},
// enable buttons
enable: function(name)
{
$(this.el).find('#' + String(name).toLowerCase()).prop('disabled', false);
},
// disable buttons
disable: function(name)
{
$(this.el).find('#' + String(name).toLowerCase()).prop('disabled', true);
},
/*
HTML TEMPLATES
*/
+245 -68
View File
@@ -1,5 +1,5 @@
/*
galaxy upload v1.0
galaxy upload
*/
// dependencies
@@ -14,12 +14,20 @@ var GalaxyUpload = Backbone.View.extend(
// button
button_show : null,
// file counter
file_counter: 0,
// upload mod
uploadbox: null,
// initialize
initialize : function()
{
// wait for galaxy history panel (workaround due to the use of iframes)
if (!Galaxy.currHistoryPanel)
{
var self = this;
window.setTimeout(function() { self.initialize() }, 500)
return;
}
// add activate icon
var self = this;
this.button_show = new mod_master.GalaxyMasterIcon (
@@ -37,87 +45,252 @@ var GalaxyUpload = Backbone.View.extend(
// events
events :
{
'mouseover' : 'event_mouseover',
'mouseleave' : 'event_mouseleave'
'mouseover' : 'event_mouseover',
'mouseleave' : 'event_mouseleave'
},
// mouse over
event_mouseover : function (e)
{
$('#galaxy-upload-box').addClass('highlight');
},
// mouse left
event_mouseleave : function (e)
{
$('#galaxy-upload-box').removeClass('highlight');
},
// start
event_start : function(index, file, message)
event_announce : function(index, file, message)
{
// make id
var id = '#galaxy-upload-file-' + index;
// hide info
this.uploadbox.info().hide();
// add tag
$('#galaxy-upload-box').append(this.template_file(id));
// make id
var id = '#upload-' + index;
// add upload item
$(this.el).append(this.template_file(id));
// scroll to bottom
$(this.el).scrollTop($(this.el).prop('scrollHeight'));
// access upload item
var it = this.get_upload_item(index);
// fade in
it.fadeIn();
// update title
$('#galaxy-upload-file-' + index).find('.title').html(file.name);
it.find('.title').html(file.name);
// configure select field
it.find('#extension').select2(
{
placeholder: 'Auto-detect',
width: 'copy',
ajax: {
url: "http://www.weighttraining.com/sm/search",
dataType: 'jsonp',
quietMillis: 100,
data: function(term, page)
{
return {
types: ["exercise"],
limit: -1,
term: term
};
},
results: function(data, page)
{
return { results: data.results.exercise }
}
},
formatResult: function(exercise)
{
return "<div class='select2-user-result'>" + exercise.term + "</div>";
},
formatSelection: function(exercise)
{
return exercise.term;
},
initSelection : function (element, callback)
{
var elementText = $(element).attr('data-init-text');
callback({"term":elementText});
}
});
// add functionality to remove button
var self = this;
it.find('.remove').on('click', function() { self.event_remove (index) });
// initialize progress
this.event_progress(index, file, 0);
// update counter
this.file_counter++;
this.refresh();
// update button status
this.modal.enable('Upload');
this.modal.enable('Reset');
},
// start
event_initialize : function(index, file, message)
{
// update on screen counter
this.button_show.number(message);
// get element
var it = this.get_upload_item(index);
// read in configuration
var data = {
source : "upload",
space_to_tabs : it.find('#space_to_tabs').is(':checked'),
extension : it.find('#extension').val()
}
// return additional data to be send with file
return data;
},
// progress
event_progress : function(index, file, message)
{
// get progress bar
var el = $('#galaxy-upload-file-' + index);
// get element
var it = this.get_upload_item(index);
// get value
var percentage = parseInt(message);
// update progress
el.find('.progress').css({ width : percentage + '%' });
it.find('.progress-bar').css({ width : percentage + '%' });
// update info
el.find('.info').html(percentage + '% of ' + this.size_to_string (file.size));
it.find('.info').html(percentage + '% of ' + this.size_to_string (file.size));
},
// end
event_success : function(index, file, message)
{
{
// get element
var it = this.get_upload_item(index);
// update progress frame
it.addClass('panel-success');
it.removeClass('panel-default');
// update galaxy history
Galaxy.currHistoryPanel.refresh();
// update counter
this.file_counter--;
this.refresh();
// make sure progress is shown correctly
this.event_progress(index, file, 100);
// update on screen counter
this.button_show.number('');
},
// end
event_error : function(index, file, message)
{
// get file box
var el = $('#galaxy-upload-file-' + index);
// get element
var it = this.get_upload_item(index);
// update progress frame
el.find('.progress-frame').addClass('failed');
it.addClass('panel-danger');
it.removeClass('panel-default');
// update error message
el.find('.error').html("<strong>Failed:</strong> " + message);
// update progress
// remove progress bar
it.find('.progress').remove();
// write error message
it.find('.error').html('<strong>Failed:</strong> ' + message);
// make sure progress is shown correctly
this.event_progress(index, file, 0);
// update counter
this.file_counter--;
this.refresh();
// update on screen counter
this.button_show.number('');
},
// start upload process
event_upload : function()
{
// hide configuration
$(this.el).find('.panel-body').hide();
// switch icon
$(this.el).find('.remove').each(function()
{
$(this).removeClass('fa-icon-trash');
$(this).addClass('fa-icon-caret-down');
});
// update button status
this.modal.disable('Upload');
// configure url
var current_history = Galaxy.currHistoryPanel.model.get('id');
this.uploadbox.configure({url : galaxy_config.root + "api/histories/" + current_history + "/contents"});
// initiate upload procedure in plugin
this.uploadbox.upload();
},
// remove all
event_reset : function()
{
// remove from screen
var panels = $(this.el).find('.panel');
var self = this;
panels.fadeOut({complete: function()
{
// remove panels
panels.remove();
// show on screen info
self.uploadbox.info().fadeIn();
}});
// update button status
this.modal.disable('Upload');
this.modal.disable('Reset');
// remove from queue
this.uploadbox.reset();
},
// remove item from upload list
event_remove : function(index)
{
// remove
var self = this;
var it = this.get_upload_item(index);
// fade out and update button status
it.fadeOut({complete: function()
{
// remove from screen
it.remove();
// remove from queue
self.uploadbox.remove(index);
// update reset button
if ($(self.el).find('.panel').length > 0)
self.modal.enable('Reset');
else {
// disable reset button
self.modal.disable('Reset');
// show on screen info
self.uploadbox.info().fadeIn();
}
// update upload button
if (self.uploadbox.length() > 0)
self.modal.enable('Upload');
else
self.modal.disable('Upload');
}});
},
// show/hide upload frame
@@ -126,14 +299,6 @@ var GalaxyUpload = Backbone.View.extend(
// prevent default
e.preventDefault();
// wait for galaxy history panel (workaround due to the use of iframes)
if (!Galaxy.currHistoryPanel)
{
var self = this;
window.setTimeout(function() { self.event_show(e) }, 200)
return;
}
// create modal
if (!this.modal)
{
@@ -142,44 +307,45 @@ var GalaxyUpload = Backbone.View.extend(
this.modal = new mod_modal.GalaxyModal(
{
title : 'Upload files from your local drive',
body : this.template(),
body : this.template('upload-box'),
buttons : {
'Close' : function() {self.modal.hide()}
'Select' : function() {self.uploadbox.select()},
'Upload' : function() {self.event_upload()},
'Reset' : function() {self.event_reset()},
'Close' : function() {self.modal.hide()}
}
});
// get current history
var current_history = Galaxy.currHistoryPanel.model.get('id');
// set element
this.setElement('#upload-box');
// file upload
var self = this;
$('#galaxy-upload-box').uploadbox(
this.uploadbox = this.$el.uploadbox(
{
url : galaxy_config.root + "api/histories/" + current_history + "/contents",
dragover : self.event_mouseover,
dragleave : self.event_mouseleave,
start : function(index, file, message) { self.event_start(index, file, message) },
announce : function(index, file, message) { self.event_announce(index, file, message) },
initialize : function(index, file, message) { return self.event_initialize(index, file, message) },
success : function(index, file, message) { self.event_success(index, file, message) },
progress : function(index, file, message) { self.event_progress(index, file, message) },
error : function(index, file, message) { self.event_error(index, file, message) },
data : {source : "upload"}
});
// set element
this.setElement('#galaxy-upload-box');
// update button status
this.modal.disable('Upload');
this.modal.disable('Reset');
}
// show modal
this.modal.show();
},
// update counter
refresh: function ()
// get upload item
get_upload_item: function(index)
{
if (this.file_counter > 0)
this.button_show.number(this.file_counter);
else
this.button_show.number('');
// get element
return $(this.el).find('#upload-' + index);
},
// to string
@@ -197,21 +363,32 @@ var GalaxyUpload = Backbone.View.extend(
},
// load html template
template: function()
template: function(id)
{
return '<form id="galaxy-upload-box" class="galaxy-upload-box"></form>';
return '<div id="' + id + '" class="upload-box"></div>';
},
// load html template
template_file: function(id)
{
return '<div id="' + id.substr(1) + '" class="file corner-soft shadow">' +
'<div class="title"></div>' +
'<div class="error"></div>' +
'<div class="progress-frame corner-soft">' +
'<div class="progress"></div>' +
return '<div id="' + id.substr(1) + '" class="panel panel-default">' +
'<div class="panel-heading">' +
'<h5 class="title"></h5>' +
'<h5 class="info"></h5>' +
'<div class="remove fa-icon-trash"></div>' +
'</div>' +
'<div class="panel-body">' +
'<div class="menu">' +
//'<input id="extension" type="hidden" width="10px"/>&nbsp;' +
'<span><input id="space_to_tabs" type="checkbox">Convert spaces to tabs</input></span>' +
'</div>' +
'</div>' +
'<div class="panel-footer">' +
'<div class="progress">' +
'<div class="progress-bar progress-bar-success"></div>' +
'</div>' +
'<h6 class="error"></h6>' +
'</div>' +
'<div class="info"></div>' +
'</div>';
}
});
+174 -84
View File
@@ -1,5 +1,5 @@
/*
galaxy upload lib v1.0 - uses FileReader, FormData and XMLHttpRequest
galaxy upload lib - uses FileReader, FormData and XMLHttpRequest
*/
;(function($)
{
@@ -11,67 +11,71 @@
{
url : '',
paramname : 'content',
maxfilesize : 2048,
data : {},
maxfilesize : 250,
dragover : function() {},
dragleave : function() {},
announce : function() {},
initialize : function() {},
start : function() {},
progress : function() {},
success : function() {},
error : function(index, file, message) { alert(message); },
error_browser : "Your browser does not support drag-and-drop file uploads.",
error_filesize : "This file is too large. Please use an FTP client to upload it.",
error_default : "The upload failed. Please make sure the file is available and accessible.",
text_default : "Drag&drop files here or click to browse your local drive.",
text_degrade : "Click here to browse your local drive. <br><br>Unfortunately, your browser does not support multiple file uploads or drag&drop.<br>Please upgrade to i.e. Firefox 4+, Chrome 7+, IE 10+, Opera 12+ or Safari 6+."
error_filesize : "This file is too large (>250MB). Please use an FTP client to upload it.",
error_default : "Please make sure the file is available.",
text_default : "Drag&drop files into this box or click 'Select' to select files!",
text_degrade : "Unfortunately, your browser does not support multiple file uploads or drag&drop.<br>Please upgrade to i.e. Firefox 4+, Chrome 7+, IE 10+, Opera 12+ or Safari 6+."
}
// global file queue
var queue = [];
// options
var opts = {};
// file queue
var queue = {};
// global counter for file being currently processed
var queue_index = -1;
// counter for file being currently processed
var queue_index = 0;
// global queue status
// queue length
var queue_length = 0;
// indicates if queue is currently running
var queue_status = false;
// element
var el = null;
// attach to element
$.fn.uploadbox = function(options)
{
// parse options
var opts = $.extend({}, default_opts, options);
opts = $.extend({}, default_opts, options);
// compatibility
var mode = window.File && window.FileReader && window.FormData && window.XMLHttpRequest;
// element
el = this;
// append upload button
this.append('<input id="uploadbox_input" type="file" style="display: none" multiple>');
this.append('<div id="uploadbox_info"></div>');
el.append('<input id="uploadbox_input" type="file" style="display: none" multiple>');
el.append('<div id="uploadbox_info"></div>');
// set info text
if (mode)
this.find('#uploadbox_info').html(opts.text_default);
el.find('#uploadbox_info').html(opts.text_default);
else
this.find('#uploadbox_info').html(opts.text_degrade);
el.find('#uploadbox_info').html(opts.text_degrade);
// attach events
this.on('drop', drop);
this.on('dragover', dragover);
this.on('dragleave', dragleave);
// attach click event
this.on('click', function(e)
{
e.stopPropagation();
$('#uploadbox_input').trigger(e);
});
el.on('drop', drop);
el.on('dragover', dragover);
el.on('dragleave', dragleave);
// attach change event
$('#uploadbox_input').change(function(e)
{
var files = e.target.files;
upload(files);
// add files to queue
add(e.target.files);
});
// drop event
@@ -81,11 +85,8 @@
if(!e.dataTransfer)
return;
// get files from event
var files = e.dataTransfer.files;
// start upload
upload(files);
// add files to queue
add(e.dataTransfer.files);
// prevent default
e.preventDefault();
@@ -98,14 +99,14 @@
function dragover(e)
{
e.preventDefault();
opts.dragover.call(this, e);
opts.dragover.call(e);
}
// drag leave
function dragleave(e)
{
e.stopPropagation();
opts.dragleave.call(this, e);
opts.dragleave.call(e);
}
// progress
@@ -117,39 +118,61 @@
}
// respond to an upload request
function upload(files)
function add(files)
{
// get current queue size
var queue_sofar = queue.length;
// add new files to queue
for (var index = 0; index < files.length; index++)
queue.push(files[index]);
for (var i = 0; i < files.length; i++)
{
// new identifier
var index = String(++queue_index);
// tell client about new uploads
for (var index = queue_sofar; index < queue.length; index++)
opts.start(index, queue[index], "");
// add to queue
queue[index] = files[i];
// initiate processing loop if process loop is not running already
if (!queue_status)
process();
// increase counter
queue_length++;
// announce
opts.announce(index, queue[index], "");
}
}
// remove entry from queue
function remove(index)
{
if (queue[index])
{
// remove from queue
delete queue[index];
// update counter
queue_length--;
}
}
// process an upload, recursive
function process()
{
// check if for files
if (queue_index + 1 == queue.length)
// get an identifier from the queue
var index = -1;
for (var key in queue)
{
queue_status = false;
return;
index = key;
break;
}
// set status
queue_status = true;
// validate
if (queue_length == 0)
return;
// identify current index
var index = ++queue_index;
// get current file from queue
var file = queue[index];
// remove from queue
remove(index)
// start
var data = opts.initialize(index, file, length);
// add file to queue
try
@@ -158,50 +181,45 @@
var reader = new FileReader();
// identify maximum file size
var file = queue[index];
var filesize = file.size;
var maxfilesize = 1048576 * opts.maxfilesize;
// set index
reader.index = index;
if (filesize < maxfilesize)
{
// link loadend is always called at the end
reader.onloadend = function(e)
// link load
reader.onload = function(e)
{
send(index, file)
send(index, file, data)
};
// link error
reader.onerror = function(e)
{
opts.error(index, file, opts.error_default);
queue_status = false;
error(index, file, opts.error_default);
};
// read data
reader.readAsDataURL(file);
} else {
// skip file
opts.error(index, file, opts.error_filesize);
// restart process
process();
error(index, file, opts.error_filesize);
}
} catch (err)
{
// parse error
opts.error(index, file, err);
error(index, file, err);
}
}
// send file
function send (index, file)
function send (index, file, data)
{
// construct form data
var formData = new FormData();
for (var key in opts.data)
formData.append(key, opts.data[key]);
for (var key in data)
formData.append(key, data[key]);
formData.append(opts.paramname, file, file.name);
// prepare request
@@ -237,22 +255,94 @@
// pass any error to the error option
if (xhr.status < 200 || xhr.status > 299)
{
// request error
opts.error(index, file, xhr.statusText + " (Server Code " + xhr.status + ")");
// format error
var text = xhr.statusText;
if (!xhr.statusText)
text = opts.error_default;
// reset status
queue_status = false;
} else {
// request error
error(index, file, text + " (Server Code " + xhr.status + ")");
} else
// parse response
opts.success(index, file, response);
// upload next file
process();
}
success(index, file, response);
}
}
// return
return this;
// success
function success (index, file, msg)
{
// parse message
opts.success(index, file, msg);
// restart process after success
process();
}
// error
function error (index, file, err)
{
// parse error
opts.error(index, file, err);
// restart process after error
process();
}
/*
public interface
*/
// open file browser for selection
function select()
{
$('#uploadbox_input').trigger('click');
}
// remove all entries from queue
function reset(index)
{
for (index in queue)
remove(index);
}
// initiate upload process
function upload()
{
if (!queue_status)
process();
}
// current queue length
function length()
{
return queue_length;
}
// set options
function configure(options)
{
// update current configuration
opts = $.extend({}, opts, options);
// return new configuration
return opts;
}
// visibility of on screen information
function info()
{
return el.find('#uploadbox_info');
}
// export functions
return {
'select' : select,
'remove' : remove,
'upload' : upload,
'reset' : reset,
'length' : length,
'configure' : configure,
'info' : info
};
}
})(jQuery);