diff --git a/README.md b/README.md index e903052..1cfbae3 100644 --- a/README.md +++ b/README.md @@ -71,10 +71,18 @@ http://softwaremaniacs.org/soft/highlight/en/): These files are licensed under the 3-clause BSD license: https://github.com/isagalaev/highlight.js/blob/master/LICENSE. -The following file is taken from the library (see +The following file is the Jeditable jQuery plugin (see http://www.appelsiini.net/projects/jeditable): * public/javascript/jquery-jeditable.min.js This file is licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php. + +The following file is the jQuery filedrop plugin (see +https://github.com/weixiyen/jquery-filedrop): + +* public/javascript/jquery-filedrop.js + +This file is licensed under the MIT license: +http://www.opensource.org/licenses/mit-license.php. diff --git a/public/index.html b/public/index.html index 18fc986..595566b 100644 --- a/public/index.html +++ b/public/index.html @@ -8,6 +8,7 @@ + diff --git a/public/javascripts/jquery-filedrop.js b/public/javascripts/jquery-filedrop.js new file mode 100644 index 0000000..6cf702c --- /dev/null +++ b/public/javascripts/jquery-filedrop.js @@ -0,0 +1,448 @@ +/* + * Default text - jQuery plugin for html5 dragging files from desktop to browser + * + * Author: Weixi Yen + * + * Email: [Firstname][Lastname]@gmail.com + * + * Copyright (c) 2010 Resopollution + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/mit-license.php + * + * Project home: + * http://www.github.com/weixiyen/jquery-filedrop + * + * Version: 0.1.0 + * + * Features: + * Allows sending of extra parameters with file. + * Works with Firefox 3.6+ + * Future-compliant with HTML5 spec (will work with Webkit browsers and IE9) + * Usage: + * See README at project homepage + * + */ +;(function($) { + jQuery.event.props.push("dataTransfer"); + + var opts = {}, + default_opts = { + fallback_id: '', + url: '', + refresh: 1000, + paramname: 'userfile', + maxfiles: 25, // Ignored if queuefiles is set > 0 + maxfilesize: 1, // MB file size limit + queuefiles: 0, // Max files before queueing (for large volume uploads) + queuewait: 200, // Queue wait time if full + data: {}, + headers: {}, + drop: empty, + dragEnter: empty, + dragOver: empty, + dragLeave: empty, + docEnter: empty, + docOver: empty, + docLeave: empty, + beforeEach: empty, + afterAll: empty, + rename: empty, + error: function(err, file, i) { + alert(err); + }, + uploadStarted: empty, + uploadFinished: empty, + progressUpdated: empty, + speedUpdated: empty + }, + errors = ["BrowserNotSupported", "TooManyFiles", "FileTooLarge"], + doc_leave_timer, + stop_loop = false, + files_count = 0, + files; + + function empty() {} + + $.fn.filedrop = function(method) { + var methods = { + init : function(options) { + // Already initialized + if($(this).data('filedrop') != undefined || $(this).data('filedrop') != null) + return false; + + var tb = new filedropInstance(this, options); + tb.init(); + $(this).data('filedrop',tb); + }, + + destroy : function( ) { + $(this).data('filedrop').destroy(); + $(this).data('filedrop',null); + } + }; + + if ( methods[method] ) { + return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 )); + } else if ( typeof method === 'object' || ! method ) { + return methods.init.apply( this, arguments ); + } else { + $.error( 'Method ' + method + ' does not exist on jQuery.filedrop' ); + } + + + function filedropInstance(element, options) { + + this.init = function() { + opts = $.extend({}, default_opts, options); + + element.bind('drop.filedrop', drop).bind('dragenter.filedrop', dragEnter).bind('dragover.filedrop', dragOver).bind('dragleave.filedrop', dragLeave); + $(document).bind('drop.filedrop', docDrop).bind('dragenter.filedrop', docEnter).bind('dragover.filedrop', docOver).bind('dragleave.filedrop', docLeave); + + $('#' + opts.fallback_id).bind('change.filedrop', function(e) { + opts.drop(e); + files = e.target.files; + files_count = files.length; + upload(); + }); + }; + + this.destroy = function() { + element.unbind('.filedrop'); + $(document).unbind('.filedrop'); + $('#' + opts.fallback_id).unbind('.filedrop'); + }; + + function drop(e) { + opts.drop(e); + files = e.dataTransfer.files; + if (files === null || files === undefined) { + opts.error(errors[0]); + return false; + } + files_count = files.length; + upload(); + e.preventDefault(); + return false; + } + + function getBuilder(filename, filedata, mime, boundary) { + var dashdash = '--', + crlf = '\r\n', + builder = ''; + + if (opts.data) { + var params = $.param(opts.data).split(/&/); + + $.each(params, function() { + var pair = this.split(/=/, 2); + var name = decodeURI(pair[0]); + var val = decodeURI(pair[1]); + + builder += dashdash; + builder += boundary; + builder += crlf; + builder += 'Content-Disposition: form-data; name="' + name + '"'; + builder += crlf; + builder += crlf; + builder += val; + builder += crlf; + }); + } + + builder += dashdash; + builder += boundary; + builder += crlf; + builder += 'Content-Disposition: form-data; name="' + opts.paramname + '"'; + builder += '; filename="' + filename + '"'; + builder += crlf; + + builder += 'Content-Type: ' + mime; + builder += crlf; + builder += crlf; + + builder += filedata; + builder += crlf; + + builder += dashdash; + builder += boundary; + builder += dashdash; + builder += crlf; + return builder; + } + + function progress(e) { + if (e.lengthComputable) { + var percentage = Math.round((e.loaded * 100) / e.total); + if (this.currentProgress != percentage) { + + this.currentProgress = percentage; + opts.progressUpdated(this.index, this.file, this.currentProgress); + + var elapsed = new Date().getTime(); + var diffTime = elapsed - this.currentStart; + if (diffTime >= opts.refresh) { + var diffData = e.loaded - this.startData; + var speed = diffData / diffTime; // KB per second + opts.speedUpdated(this.index, this.file, speed); + this.startData = e.loaded; + this.currentStart = elapsed; + } + } + } + } + + // Respond to an upload + function upload() { + + stop_loop = false; + + if (!files) { + opts.error(errors[0]); + return false; + } + + var filesDone = 0, + filesRejected = 0; + + if (files_count > opts.maxfiles && opts.queuefiles === 0) { + opts.error(errors[1]); + return false; + } + + // Define queues to manage upload process + var workQueue = []; + var processingQueue = []; + var doneQueue = []; + + // Add everything to the workQueue + for (var i = 0; i < files_count; i++) { + workQueue.push(i); + } + + // Helper function to enable pause of processing to wait + // for in process queue to complete + var pause = function(timeout) { + setTimeout(process, timeout); + return; + } + + // Process an upload, recursive + var process = function() { + + var fileIndex; + + if (stop_loop) return false; + + // Check to see if are in queue mode + if (opts.queuefiles > 0 && processingQueue.length >= opts.queuefiles) { + + return pause(opts.queuewait); + + } else { + + // Take first thing off work queue + fileIndex = workQueue[0]; + workQueue.splice(0, 1); + + // Add to processing queue + processingQueue.push(fileIndex); + + } + + try { + if (beforeEach(files[fileIndex]) != false) { + if (fileIndex === files_count) return; + var reader = new FileReader(), + max_file_size = 1048576 * opts.maxfilesize; + + reader.index = fileIndex; + if (files[fileIndex].size > max_file_size) { + opts.error(errors[2], files[fileIndex], fileIndex); + // Remove from queue + processingQueue.forEach(function(value, key) { + if (value === fileIndex) processingQueue.splice(key, 1); + }); + filesRejected++; + return true; + } + reader.onloadend = send; + reader.readAsBinaryString(files[fileIndex]); + + } else { + filesRejected++; + } + } catch (err) { + // Remove from queue + processingQueue.forEach(function(value, key) { + if (value === fileIndex) processingQueue.splice(key, 1); + }); + opts.error(errors[0]); + return false; + } + + // If we still have work to do, + if (workQueue.length > 0) { + process(); + } + + }; + + var send = function(e) { + + var fileIndex = ((typeof(e.srcElement) === "undefined") ? e.target : e.srcElement).index + + // Sometimes the index is not attached to the + // event object. Find it by size. Hack for sure. + if (e.target.index == undefined) { + e.target.index = getIndexBySize(e.total); + } + + var xhr = new XMLHttpRequest(), + upload = xhr.upload, + file = files[e.target.index], + index = e.target.index, + start_time = new Date().getTime(), + boundary = '------multipartformboundary' + (new Date).getTime(), + builder; + + newName = rename(file.name); + mime = file.type + if (typeof newName === "string") { + builder = getBuilder(newName, e.target.result, mime, boundary); + } else { + builder = getBuilder(file.name, e.target.result, mime, boundary); + } + + upload.index = index; + upload.file = file; + upload.downloadStartTime = start_time; + upload.currentStart = start_time; + upload.currentProgress = 0; + upload.startData = 0; + upload.addEventListener("progress", progress, false); + + xhr.open("POST", opts.url, true); + xhr.setRequestHeader('content-type', 'multipart/form-data; boundary=' + boundary); + xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); + + // Add headers + $.each(opts.headers, function(k, v) { + xhr.setRequestHeader(k, v); + }); + + xhr.sendAsBinary(builder); + + opts.uploadStarted(index, file, files_count); + + xhr.onload = function() { + if (xhr.responseText) { + var now = new Date().getTime(), + timeDiff = now - start_time, + result = opts.uploadFinished(index, file, jQuery.parseJSON(xhr.responseText), timeDiff); + filesDone++; + + // Remove from processing queue + processingQueue.forEach(function(value, key) { + if (value === fileIndex) processingQueue.splice(key, 1); + }); + + // Add to donequeue + doneQueue.push(fileIndex); + + if (filesDone == files_count - filesRejected) { + afterAll(); + } + if (result === false) stop_loop = true; + } + }; + + } + + // Initiate the processing loop + process(); + + } + + function getIndexBySize(size) { + for (var i = 0; i < files_count; i++) { + if (files[i].size == size) { + return i; + } + } + + return undefined; + } + + function rename(name) { + return opts.rename(name); + } + + function beforeEach(file) { + return opts.beforeEach(file); + } + + function afterAll() { + return opts.afterAll(); + } + + function dragEnter(e) { + clearTimeout(doc_leave_timer); + e.preventDefault(); + opts.dragEnter(e); + } + + function dragOver(e) { + clearTimeout(doc_leave_timer); + e.preventDefault(); + opts.docOver(e); + opts.dragOver(e); + } + + function dragLeave(e) { + clearTimeout(doc_leave_timer); + opts.dragLeave(e); + e.stopPropagation(); + } + + function docDrop(e) { + e.preventDefault(); + opts.docLeave(e); + return false; + } + + function docEnter(e) { + clearTimeout(doc_leave_timer); + e.preventDefault(); + opts.docEnter(e); + return false; + } + + function docOver(e) { + clearTimeout(doc_leave_timer); + e.preventDefault(); + opts.docOver(e); + return false; + } + + function docLeave(e) { + doc_leave_timer = setTimeout(function() { + opts.docLeave(e); + }, 200); + } + + try { + if (XMLHttpRequest.prototype.sendAsBinary) return; + XMLHttpRequest.prototype.sendAsBinary = function(datastr) { + function byteValue(x) { + return x.charCodeAt(0) & 0xff; + } + var ords = Array.prototype.map.call(datastr, byteValue); + var ui8a = new Uint8Array(ords); + this.send(ui8a.buffer); + } + } catch (e) {} + + } // End of fileDropInstance() + }; // End of $.fn.filedrop() +})(jQuery); \ No newline at end of file