From 1474e2c59a434bdc59f204b0bdebc7e34cabedda Mon Sep 17 00:00:00 2001 From: ET-CS Date: Sun, 20 Jan 2013 04:13:37 +0200 Subject: [PATCH 01/72] fixed model unicode bug & admin.py added for admin panel control. --- fileupload/admin.py | 4 ++++ fileupload/models.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 fileupload/admin.py diff --git a/fileupload/admin.py b/fileupload/admin.py new file mode 100644 index 0000000..faa5cdc --- /dev/null +++ b/fileupload/admin.py @@ -0,0 +1,4 @@ +from fileupload.models import Picture +from django.contrib import admin + +admin.site.register(Picture) \ No newline at end of file diff --git a/fileupload/models.py b/fileupload/models.py index 64cf98b..c59bc93 100644 --- a/fileupload/models.py +++ b/fileupload/models.py @@ -12,7 +12,7 @@ class Picture(models.Model): slug = models.SlugField(max_length=50, blank=True) def __unicode__(self): - return self.file + return '%s' % (self.file) @models.permalink def get_absolute_url(self): From a225b8901d9f63c64838b38d7466890543da7a8e Mon Sep 17 00:00:00 2001 From: ET-CS Date: Sun, 20 Jan 2013 05:31:54 +0200 Subject: [PATCH 02/72] settings.py maps db path dynamically. --- settings.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/settings.py b/settings.py index 47396bc..4c50776 100644 --- a/settings.py +++ b/settings.py @@ -3,6 +3,8 @@ DEBUG = True TEMPLATE_DEBUG = DEBUG +SITE_ROOT = os.path.dirname(os.path.realpath(__file__)) + ADMINS = ( # ('Your Name', 'your_email@example.com'), ) @@ -12,7 +14,7 @@ DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'. - 'NAME': 'db', # Or path to database file if using sqlite3. + 'NAME': os.path.join(SITE_ROOT, 'db'), # Or path to database file if using sqlite3. 'USER': '', # Not used with sqlite3. 'PASSWORD': '', # Not used with sqlite3. 'HOST': '', # Set to empty string for localhost. Not used with sqlite3. From 4e55d73f19fe01857a237d82d6ac806c43858441 Mon Sep 17 00:00:00 2001 From: ET-CS Date: Sun, 20 Jan 2013 05:34:44 +0200 Subject: [PATCH 03/72] added setup.py added by h3 to his repository. as he commited: Added a god damn setup.py :D --- setup.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 setup.py diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..52497e9 --- /dev/null +++ b/setup.py @@ -0,0 +1,17 @@ +""" +django-jquery-file-upload +""" + +from setuptools import setup, find_packages + +setup ( + name = "django-jquery-file-upload", + version = "0.0.1", + url = "", + license = "The MIT License (MIT)", + description = "A minimal django project containing a minimal app with a working jquery file upload form based on the work by Sebastian Tschan: http://aquantum-demo.appspot.com/file-upload", + author = 'Sebastian Tschan / Sigurd Gartmann', + packages = find_packages(), + package_dir = {'': '.'}, + install_requires = [], +) \ No newline at end of file From 9472810607c618c15d00986259eada771ceff712 Mon Sep 17 00:00:00 2001 From: ET-CS Date: Sun, 20 Jan 2013 05:46:16 +0200 Subject: [PATCH 04/72] .gitignore updated to ignore db, *.pyc & venv --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 06f5f1d..c1d540a 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,6 @@ /lib /local /share +/venv +db +*.pyc \ No newline at end of file From 7cd6724707db1d755c4c77b9009023a5058f0a68 Mon Sep 17 00:00:00 2001 From: ET-CS Date: Sun, 20 Jan 2013 20:56:39 +0200 Subject: [PATCH 05/72] all .js files updated from the current original jQuery project --- fileupload/static/js/jquery.fileupload-fp.js | 38 +- fileupload/static/js/jquery.fileupload-ui.js | 328 +++++++---- fileupload/static/js/jquery.fileupload.js | 535 ++++++++++++------ .../static/js/jquery.iframe-transport.js | 28 +- .../static/js/jquery.postmessage-transport.js | 117 ++++ fileupload/static/js/jquery.ui.widget.js | 480 ++++++++++++---- fileupload/static/js/jquery.xdr-transport.js | 87 +++ fileupload/static/js/main.js | 25 +- 8 files changed, 1188 insertions(+), 450 deletions(-) create mode 100644 fileupload/static/js/jquery.postmessage-transport.js create mode 100644 fileupload/static/js/jquery.xdr-transport.js diff --git a/fileupload/static/js/jquery.fileupload-fp.js b/fileupload/static/js/jquery.fileupload-fp.js index 634fb5e..fdf18fb 100644 --- a/fileupload/static/js/jquery.fileupload-fp.js +++ b/fileupload/static/js/jquery.fileupload-fp.js @@ -1,5 +1,5 @@ /* - * jQuery File Upload File Processing Plugin 1.0 + * jQuery File Upload File Processing Plugin 1.2.1 * https://github.com/blueimp/jQuery-File-Upload * * Copyright 2012, Sebastian Tschan @@ -32,9 +32,9 @@ }(function ($, loadImage) { 'use strict'; - // The File Upload IP version extends the basic fileupload widget + // The File Upload FP version extends the fileupload widget // with file processing functionality: - $.widget('blueimpFP.fileupload', $.blueimp.fileupload, { + $.widget('blueimp.fileupload', $.blueimp.fileupload, { options: { // The list of file processing actions: @@ -70,7 +70,7 @@ processActions: { // Loads the image given via data.files and data.index - // as canvas element. + // as img element if the browser supports canvas. // Accepts the options fileTypes (regular expression) // and maxFileSize (integer) to limit the files to load: load: function (data, options) { @@ -85,28 +85,32 @@ options.fileTypes.test(file.type))) { loadImage( file, - function (canvas) { - data.canvas = canvas; + function (img) { + if (!img.src) { + return dfd.rejectWith(that, [data]); + } + data.img = img; dfd.resolveWith(that, [data]); - }, - {canvas: true} + } ); } else { dfd.rejectWith(that, [data]); } return dfd.promise(); }, - // Resizes the image given as data.canvas and updates - // data.canvas with the resized image. + // Resizes the image given as data.img and updates + // data.canvas with the resized image as canvas element. // Accepts the options maxWidth, maxHeight, minWidth and // minHeight to scale the given image: resize: function (data, options) { - if (data.canvas) { - var canvas = loadImage.scale(data.canvas, options); - if (canvas.width !== data.canvas.width || - canvas.height !== data.canvas.height) { + var img = data.img, + canvas; + options = $.extend({canvas: true}, options); + if (img) { + canvas = loadImage.scale(img, options); + if (canvas.width !== img.width || + canvas.height !== img.height) { data.canvas = canvas; - data.processed = true; } } return data; @@ -115,7 +119,7 @@ // inplace at data.index of data.files: save: function (data, options) { // Do nothing if no processing has happened: - if (!data.canvas || !data.processed) { + if (!data.canvas) { return data; } var that = this, @@ -208,7 +212,7 @@ }, _create: function () { - $.blueimp.fileupload.prototype._create.call(this); + this._super(); this._processing = 0; this._processingQueue = $.Deferred().resolveWith(this) .promise(); diff --git a/fileupload/static/js/jquery.fileupload-ui.js b/fileupload/static/js/jquery.fileupload-ui.js index f7fc3bf..e62cbab 100644 --- a/fileupload/static/js/jquery.fileupload-ui.js +++ b/fileupload/static/js/jquery.fileupload-ui.js @@ -1,5 +1,5 @@ /* - * jQuery File Upload User Interface Plugin 6.8.1 + * jQuery File Upload User Interface Plugin 7.2 * https://github.com/blueimp/jQuery-File-Upload * * Copyright 2010, Sebastian Tschan @@ -10,7 +10,7 @@ */ /*jslint nomen: true, unparam: true, regexp: true */ -/*global define, window, document, URL, webkitURL, FileReader */ +/*global define, window, URL, webkitURL, FileReader */ (function (factory) { 'use strict'; @@ -33,10 +33,9 @@ }(function ($, tmpl, loadImage) { 'use strict'; - // The UI version extends the FP (file processing) version or the basic - // file upload widget and adds complete user interface interaction: - var parentWidget = ($.blueimpFP || $.blueimp).fileupload; - $.widget('blueimpUI.fileupload', parentWidget, { + // The UI version extends the file upload widget + // and adds complete user interface interaction: + $.widget('blueimp.fileupload', $.blueimp.fileupload, { options: { // By default, files added to the widget are uploaded as soon @@ -52,7 +51,7 @@ minFileSize: undefined, // The regular expression for allowed file types, matches // against either file type or file name: - acceptFileTypes: /.+$/i, + acceptFileTypes: /^image\/(gif|jpeg|png)$/, // The regular expression to define for which files a preview // image is shown, matched against the file type: previewSourceFileTypes: /^image\/(gif|jpeg|png)$/, @@ -89,13 +88,13 @@ files = data.files; $(this).fileupload('process', data).done(function () { that._adjustMaxNumberOfFiles(-files.length); - data.isAdjusted = true; + data.maxNumberOfFilesAdjusted = true; data.files.valid = data.isValidated = that._validate(files); data.context = that._renderUpload(files).data('data', data); options.filesContainer[ options.prependFiles ? 'prepend' : 'append' ](data.context); - that._renderPreviews(files, data.context); + that._renderPreviews(data); that._forceReflow(data.context); that._transition(data.context).done( function () { @@ -112,8 +111,9 @@ send: function (e, data) { var that = $(this).data('fileupload'); if (!data.isValidated) { - if (!data.isAdjusted) { + if (!data.maxNumberOfFilesAdjusted) { that._adjustMaxNumberOfFiles(-data.files.length); + data.maxNumberOfFilesAdjusted = true; } if (!that._validate(data.files)) { return false; @@ -128,9 +128,10 @@ .find('.progress').addClass( !$.support.transition && 'progress-animated' ) + .attr('aria-valuenow', 100) .find('.bar').css( 'width', - parseInt(100, 10) + '%' + '100%' ); } return that._trigger('sent', e, data); @@ -138,11 +139,14 @@ // Callback for successful uploads: done: function (e, data) { var that = $(this).data('fileupload'), - template; + files = that._getFilesFromResponse(data), + template, + deferred; if (data.context) { data.context.each(function (index) { - var file = ($.isArray(data.result) && - data.result[index]) || {error: 'emptyResult'}; + var file = files[index] || + {error: 'Empty file upload result'}, + deferred = that._addFinishedDeferreds(); if (file.error) { that._adjustMaxNumberOfFiles(1); } @@ -150,26 +154,41 @@ function () { var node = $(this); template = that._renderDownload([file]) - .css('height', node.height()) .replaceAll(node); that._forceReflow(template); that._transition(template).done( function () { data.context = $(this); that._trigger('completed', e, data); + that._trigger('finished', e, data); + deferred.resolve(); } ); } ); }); } else { - template = that._renderDownload(data.result) + if (files.length) { + $.each(files, function (index, file) { + if (data.maxNumberOfFilesAdjusted && file.error) { + that._adjustMaxNumberOfFiles(1); + } else if (!data.maxNumberOfFilesAdjusted && + !file.error) { + that._adjustMaxNumberOfFiles(-1); + } + }); + data.maxNumberOfFilesAdjusted = true; + } + template = that._renderDownload(files) .appendTo(that.options.filesContainer); that._forceReflow(template); + deferred = that._addFinishedDeferreds(); that._transition(template).done( function () { data.context = $(this); that._trigger('completed', e, data); + that._trigger('finished', e, data); + deferred.resolve(); } ); } @@ -177,14 +196,18 @@ // Callback for failed (abort or error) uploads: fail: function (e, data) { var that = $(this).data('fileupload'), - template; - that._adjustMaxNumberOfFiles(data.files.length); + template, + deferred; + if (data.maxNumberOfFilesAdjusted) { + that._adjustMaxNumberOfFiles(data.files.length); + } if (data.context) { data.context.each(function (index) { if (data.errorThrown !== 'abort') { var file = data.files[index]; file.error = file.error || data.errorThrown || true; + deferred = that._addFinishedDeferreds(); that._transition($(this)).done( function () { var node = $(this); @@ -195,62 +218,80 @@ function () { data.context = $(this); that._trigger('failed', e, data); + that._trigger('finished', e, data); + deferred.resolve(); } ); } ); } else { + deferred = that._addFinishedDeferreds(); that._transition($(this)).done( function () { $(this).remove(); that._trigger('failed', e, data); + that._trigger('finished', e, data); + deferred.resolve(); } ); } }); } else if (data.errorThrown !== 'abort') { - that._adjustMaxNumberOfFiles(-data.files.length); data.context = that._renderUpload(data.files) .appendTo(that.options.filesContainer) .data('data', data); that._forceReflow(data.context); + deferred = that._addFinishedDeferreds(); that._transition(data.context).done( function () { data.context = $(this); that._trigger('failed', e, data); + that._trigger('finished', e, data); + deferred.resolve(); } ); } else { that._trigger('failed', e, data); + that._trigger('finished', e, data); + that._addFinishedDeferreds().resolve(); } }, // Callback for upload progress events: progress: function (e, data) { if (data.context) { - data.context.find('.bar').css( - 'width', - parseInt(data.loaded / data.total * 100, 10) + '%' - ); + var progress = parseInt(data.loaded / data.total * 100, 10); + data.context.find('.progress') + .attr('aria-valuenow', progress) + .find('.bar').css( + 'width', + progress + '%' + ); } }, // Callback for global upload progress events: progressall: function (e, data) { - var $this = $(this); - $this.find('.fileupload-progress') + var $this = $(this), + progress = parseInt(data.loaded / data.total * 100, 10), + globalProgressNode = $this.find('.fileupload-progress'), + extendedProgressNode = globalProgressNode + .find('.progress-extended'); + if (extendedProgressNode.length) { + extendedProgressNode.html( + $this.data('fileupload')._renderExtendedProgress(data) + ); + } + globalProgressNode + .find('.progress') + .attr('aria-valuenow', progress) .find('.bar').css( 'width', - parseInt(data.loaded / data.total * 100, 10) + '%' - ).end() - .find('.progress-extended').each(function () { - $(this).html( - $this.data('fileupload') - ._renderExtendedProgress(data) - ); - }); + progress + '%' + ); }, // Callback for uploads start, equivalent to the global ajaxStart event: start: function (e) { var that = $(this).data('fileupload'); + that._resetFinishedDeferreds(); that._transition($(this).find('.fileupload-progress')).done( function () { that._trigger('started', e); @@ -259,12 +300,19 @@ }, // Callback for uploads stop, equivalent to the global ajaxStop event: stop: function (e) { - var that = $(this).data('fileupload'); + var that = $(this).data('fileupload'), + deferred = that._addFinishedDeferreds(); + $.when.apply($, that._getFinishedDeferreds()) + .done(function () { + that._trigger('stopped', e); + }); that._transition($(this).find('.fileupload-progress')).done( function () { - $(this).find('.bar').css('width', '0%'); + $(this).find('.progress') + .attr('aria-valuenow', '0') + .find('.bar').css('width', '0%'); $(this).find('.progress-extended').html(' '); - that._trigger('stopped', e); + deferred.resolve(); } ); }, @@ -273,8 +321,8 @@ var that = $(this).data('fileupload'); if (data.url) { $.ajax(data); + that._adjustMaxNumberOfFiles(1); } - that._adjustMaxNumberOfFiles(1); that._transition(data.context).done( function () { $(this).remove(); @@ -284,6 +332,29 @@ } }, + _resetFinishedDeferreds: function () { + this._finishedUploads = []; + }, + + _addFinishedDeferreds: function (deferred) { + if (!deferred) { + deferred = $.Deferred(); + } + this._finishedUploads.push(deferred); + return deferred; + }, + + _getFinishedDeferreds: function () { + return this._finishedUploads; + }, + + _getFilesFromResponse: function (data) { + if (data.result && $.isArray(data.result.files)) { + return data.result.files; + } + return []; + }, + // Link handler, that allows to download files // by drag & drop of the links to the desktop: _enableDragToDesktop: function () { @@ -338,7 +409,7 @@ if (bits >= 1000) { return (bits / 1000).toFixed(2) + ' kbit/s'; } - return bits + ' bit/s'; + return bits.toFixed(2) + ' bit/s'; }, _formatTime: function (seconds) { @@ -375,22 +446,22 @@ // maxNumberOfFiles before validation, so we check if // maxNumberOfFiles is below 0 (instead of below 1): if (this.options.maxNumberOfFiles < 0) { - return 'maxNumberOfFiles'; + return 'Maximum number of files exceeded'; } // Files are accepted if either the file type or the file name // matches against the acceptFileTypes regular expression, as // only browsers with support for the File API report the type: if (!(this.options.acceptFileTypes.test(file.type) || this.options.acceptFileTypes.test(file.name))) { - return 'acceptFileTypes'; + return 'Filetype not allowed'; } if (this.options.maxFileSize && file.size > this.options.maxFileSize) { - return 'maxFileSize'; + return 'File is too big'; } if (typeof file.size === 'number' && file.size < this.options.minFileSize) { - return 'minFileSize'; + return 'File is too small'; } return null; }, @@ -434,7 +505,7 @@ that._transition(node).done(function () { dfd.resolveWith(node); }); - if (!$.contains(document.body, node[0])) { + if (!$.contains(that.document[0].body, node[0])) { // If the element is not part of the DOM, // transition events are not triggered, // so we have to resolve manually: @@ -449,18 +520,22 @@ )) || dfd.resolveWith(node)) && dfd; }, - _renderPreviews: function (files, nodes) { + _renderPreviews: function (data) { var that = this, options = this.options; - nodes.find('.preview span').each(function (index, element) { - var file = files[index]; + data.context.find('.preview span').each(function (index, element) { + var file = data.files[index]; if (options.previewSourceFileTypes.test(file.type) && ($.type(options.previewSourceMaxFileSize) !== 'number' || file.size < options.previewSourceMaxFileSize)) { that._processingQueue = that._processingQueue.pipe(function () { - var dfd = $.Deferred(); + var dfd = $.Deferred(), + ev = $.Event('previewdone', { + target: element + }); that._renderPreview(file, $(element)).done( function () { + that._trigger(ev.type, ev, data); dfd.resolveWith(that); } ); @@ -487,7 +562,7 @@ _startHandler: function (e) { e.preventDefault(); - var button = $(this), + var button = $(e.currentTarget), template = button.closest('.template-upload'), data = template.data('data'); if (data && data.submit && !data.jqXHR && data.submit()) { @@ -497,11 +572,11 @@ _cancelHandler: function (e) { e.preventDefault(); - var template = $(this).closest('.template-upload'), + var template = $(e.currentTarget).closest('.template-upload'), data = template.data('data') || {}; if (!data.jqXHR) { data.errorThrown = 'abort'; - e.data.fileupload._trigger('fail', e, data); + this._trigger('fail', e, data); } else { data.jqXHR.abort(); } @@ -509,18 +584,17 @@ _deleteHandler: function (e) { e.preventDefault(); - var button = $(this); - e.data.fileupload._trigger('destroy', e, { + var button = $(e.currentTarget); + this._trigger('destroy', e, $.extend({ context: button.closest('.template-download'), - url: button.attr('data-url'), - type: button.attr('data-type') || 'DELETE', - dataType: e.data.fileupload.options.dataType - }); + type: 'DELETE', + dataType: this.options.dataType + }, button.data())); }, _forceReflow: function (node) { - this._reflow = $.support.transition && - node.length && node[0].offsetWidth; + return $.support.transition && node.length && + node[0].offsetWidth; }, _transition: function (node) { @@ -546,75 +620,63 @@ _initButtonBarEventHandlers: function () { var fileUploadButtonBar = this.element.find('.fileupload-buttonbar'), - filesList = this.options.filesContainer, - ns = this.options.namespace; - fileUploadButtonBar.find('.start') - .bind('click.' + ns, function (e) { + filesList = this.options.filesContainer; + this._on(fileUploadButtonBar.find('.start'), { + click: function (e) { e.preventDefault(); filesList.find('.start button').click(); - }); - fileUploadButtonBar.find('.cancel') - .bind('click.' + ns, function (e) { + } + }); + this._on(fileUploadButtonBar.find('.cancel'), { + click: function (e) { e.preventDefault(); filesList.find('.cancel button').click(); - }); - fileUploadButtonBar.find('.delete') - .bind('click.' + ns, function (e) { + } + }); + this._on(fileUploadButtonBar.find('.delete'), { + click: function (e) { e.preventDefault(); filesList.find('.delete input:checked') .siblings('button').click(); fileUploadButtonBar.find('.toggle') .prop('checked', false); - }); - fileUploadButtonBar.find('.toggle') - .bind('change.' + ns, function (e) { + } + }); + this._on(fileUploadButtonBar.find('.toggle'), { + change: function (e) { filesList.find('.delete input').prop( 'checked', - $(this).is(':checked') + $(e.currentTarget).is(':checked') ); - }); + } + }); }, _destroyButtonBarEventHandlers: function () { - this.element.find('.fileupload-buttonbar button') - .unbind('click.' + this.options.namespace); - this.element.find('.fileupload-buttonbar .toggle') - .unbind('change.' + this.options.namespace); + this._off( + this.element.find('.fileupload-buttonbar button'), + 'click' + ); + this._off( + this.element.find('.fileupload-buttonbar .toggle'), + 'change.' + ); }, _initEventHandlers: function () { - parentWidget.prototype._initEventHandlers.call(this); - var eventData = {fileupload: this}; - this.options.filesContainer - .delegate( - '.start button', - 'click.' + this.options.namespace, - eventData, - this._startHandler - ) - .delegate( - '.cancel button', - 'click.' + this.options.namespace, - eventData, - this._cancelHandler - ) - .delegate( - '.delete button', - 'click.' + this.options.namespace, - eventData, - this._deleteHandler - ); + this._super(); + this._on(this.options.filesContainer, { + 'click .start button': this._startHandler, + 'click .cancel button': this._cancelHandler, + 'click .delete button': this._deleteHandler + }); this._initButtonBarEventHandlers(); }, _destroyEventHandlers: function () { - var options = this.options; this._destroyButtonBarEventHandlers(); - options.filesContainer - .undelegate('.start button', 'click.' + options.namespace) - .undelegate('.cancel button', 'click.' + options.namespace) - .undelegate('.delete button', 'click.' + options.namespace); - parentWidget.prototype._destroyEventHandlers.call(this); + this._off(this.options.filesContainer, 'click'); + this._super(); }, _enableFileInputButton: function () { @@ -631,7 +693,7 @@ _initTemplates: function () { var options = this.options; - options.templatesContainer = document.createElement( + options.templatesContainer = this.document[0].createElement( options.filesContainer.prop('nodeName') ); if (tmpl) { @@ -653,37 +715,75 @@ } }, + _stringToRegExp: function (str) { + var parts = str.split('/'), + modifiers = parts.pop(); + parts.shift(); + return new RegExp(parts.join('/'), modifiers); + }, + + _initRegExpOptions: function () { + var options = this.options; + if ($.type(options.acceptFileTypes) === 'string') { + options.acceptFileTypes = this._stringToRegExp( + options.acceptFileTypes + ); + } + if ($.type(options.previewSourceFileTypes) === 'string') { + options.previewSourceFileTypes = this._stringToRegExp( + options.previewSourceFileTypes + ); + } + }, + _initSpecialOptions: function () { - parentWidget.prototype._initSpecialOptions.call(this); + this._super(); this._initFilesContainer(); this._initTemplates(); + this._initRegExpOptions(); + }, + + _setOption: function (key, value) { + this._super(key, value); + if (key === 'maxNumberOfFiles') { + this._adjustMaxNumberOfFiles(0); + } }, _create: function () { - parentWidget.prototype._create.call(this); + this._super(); this._refreshOptionsList.push( 'filesContainer', 'uploadTemplateId', 'downloadTemplateId' ); - if (!$.blueimpFP) { + if (!this._processingQueue) { this._processingQueue = $.Deferred().resolveWith(this).promise(); this.process = function () { return this._processingQueue; }; } + this._resetFinishedDeferreds(); }, enable: function () { - parentWidget.prototype.enable.call(this); - this.element.find('input, button').prop('disabled', false); - this._enableFileInputButton(); + var wasDisabled = false; + if (this.options.disabled) { + wasDisabled = true; + } + this._super(); + if (wasDisabled) { + this.element.find('input, button').prop('disabled', false); + this._enableFileInputButton(); + } }, disable: function () { - this.element.find('input, button').prop('disabled', true); - this._disableFileInputButton(); - parentWidget.prototype.disable.call(this); + if (!this.options.disabled) { + this.element.find('input, button').prop('disabled', true); + this._disableFileInputButton(); + } + this._super(); } }); diff --git a/fileupload/static/js/jquery.fileupload.js b/fileupload/static/js/jquery.fileupload.js index 05a654b..96efeaf 100644 --- a/fileupload/static/js/jquery.fileupload.js +++ b/fileupload/static/js/jquery.fileupload.js @@ -1,5 +1,5 @@ /* - * jQuery File Upload Plugin 5.11.2 + * jQuery File Upload Plugin 5.19.8 * https://github.com/blueimp/jQuery-File-Upload * * Copyright 2010, Sebastian Tschan @@ -10,7 +10,7 @@ */ /*jslint nomen: true, unparam: true, regexp: true */ -/*global define, window, document, Blob, FormData, location */ +/*global define, window, document, File, Blob, FormData, location */ (function (factory) { 'use strict'; @@ -44,17 +44,16 @@ $.widget('blueimp.fileupload', { options: { - // The namespace used for event handler binding on the dropZone and - // fileInput collections. - // If not set, the name of the widget ("fileupload") is used. - namespace: undefined, - // The drop target collection, by the default the complete document. - // Set to null or an empty collection to disable drag & drop support: + // The drop target element(s), by the default the complete document. + // Set to null to disable drag & drop support: dropZone: $(document), - // The file input field collection, that is listened for change events. + // The paste target element(s), by the default the complete document. + // Set to null to disable paste support: + pasteZone: $(document), + // The file input field(s), that are listened to for change events. // If undefined, it is set to the file input fields inside // of the widget element on plugin initialization. - // Set to null or an empty collection to disable the change listener. + // Set to null to disable the change listener. fileInput: undefined, // By default, the file input field is replaced with a clone after // each input field change event. This is required for iframe transport @@ -159,13 +158,13 @@ // start: function (e) {}, // .bind('fileuploadstart', func); // Callback for uploads stop, equivalent to the global ajaxStop event: // stop: function (e) {}, // .bind('fileuploadstop', func); - // Callback for change events of the fileInput collection: + // Callback for change events of the fileInput(s): // change: function (e, data) {}, // .bind('fileuploadchange', func); - // Callback for paste events to the dropZone collection: + // Callback for paste events to the pasteZone(s): // paste: function (e, data) {}, // .bind('fileuploadpaste', func); - // Callback for drop events of the dropZone collection: + // Callback for drop events of the dropZone(s): // drop: function (e, data) {}, // .bind('fileuploaddrop', func); - // Callback for dragover events of the dropZone collection: + // Callback for dragover events of the dropZone(s): // dragover: function (e) {}, // .bind('fileuploaddragover', func); // The plugin options are used as settings object for the ajax calls. @@ -177,9 +176,9 @@ // A list of options that require a refresh after assigning a new value: _refreshOptionsList: [ - 'namespace', - 'dropZone', 'fileInput', + 'dropZone', + 'pasteZone', 'multipart', 'forceIframeTransport' ], @@ -210,10 +209,10 @@ if (typeof options.formData === 'function') { return options.formData(options.form); } - if ($.isArray(options.formData)) { + if ($.isArray(options.formData)) { return options.formData; } - if (options.formData) { + if (options.formData) { formData = []; $.each(options.formData, function (name, value) { formData.push({name: name, value: value}); @@ -301,29 +300,16 @@ // Ignore non-multipart setting if not supported: multipart = options.multipart || !$.support.xhrFileUpload, paramName = options.paramName[0]; - if (!multipart || options.blob) { - // For non-multipart uploads and chunked uploads, - // file meta data is not part of the request body, - // so we transmit this data as part of the HTTP headers. - // For cross domain requests, these headers must be allowed - // via Access-Control-Allow-Headers or removed using - // the beforeSend callback: - options.headers = $.extend(options.headers, { - 'X-File-Name': file.name, - 'X-File-Type': file.type, - 'X-File-Size': file.size - }); - if (!options.blob) { - // Non-chunked non-multipart upload: - options.contentType = file.type; - options.data = file; - } else if (!multipart) { - // Chunked non-multipart upload: - options.contentType = 'application/octet-stream'; - options.data = options.blob; - } + options.headers = options.headers || {}; + if (options.contentRange) { + options.headers['Content-Range'] = options.contentRange; } - if (multipart && $.support.xhrFormDataFileUpload) { + if (!multipart) { + options.headers['Content-Disposition'] = 'attachment; filename="' + + encodeURI(file.name) + '"'; + options.contentType = file.type; + options.data = options.blob || file; + } else if ($.support.xhrFormDataFileUpload) { if (options.postMessage) { // window.postMessage does not allow sending FormData // objects, so we just add the File/Blob objects to @@ -353,13 +339,17 @@ }); } if (options.blob) { + options.headers['Content-Disposition'] = 'attachment; filename="' + + encodeURI(file.name) + '"'; formData.append(paramName, options.blob, file.name); } else { $.each(options.files, function (index, file) { - // File objects are also Blob instances. + // Files are also Blob instances, but some browsers + // (Firefox 3.6) support the File API but not Blobs. // This check allows the tests to run with // dummy objects: - if (file instanceof Blob) { + if ((window.Blob && file instanceof Blob) || + (window.File && file instanceof File)) { formData.append( options.paramName[index] || paramName, file, @@ -436,6 +426,11 @@ // associated form, if available: if (!options.form || !options.form.length) { options.form = $(options.fileInput.prop('form')); + // If the given file input doesn't have an associated form, + // use the default widget file input's form: + if (!options.form.length) { + options.form = $(this.options.fileInput.prop('form')); + } } options.paramName = this._getParamName(options); if (!options.url) { @@ -444,9 +439,13 @@ // The HTTP request method must be "POST" or "PUT": options.type = (options.type || options.form.prop('method') || '') .toUpperCase(); - if (options.type !== 'POST' && options.type !== 'PUT') { + if (options.type !== 'POST' && options.type !== 'PUT' && + options.type !== 'PATCH') { options.type = 'POST'; } + if (!options.formAcceptCharset) { + options.formAcceptCharset = options.form.attr('accept-charset'); + } }, _getAJAXSettings: function (data) { @@ -480,6 +479,16 @@ return this._enhancePromise(promise); }, + // Parses the Range header from the server response + // and returns the uploaded bytes: + _getUploadedBytes: function (jqXHR) { + var range = jqXHR.getResponseHeader('Range'), + parts = range && range.split('-'), + upperBytesPos = parts && parts.length > 1 && + parseInt(parts[1], 10); + return upperBytesPos && upperBytesPos + 1; + }, + // Uploads a file in multiple, sequential requests // by splitting the file up in multiple blob chunks. // If the second parameter is true, only tests if the file @@ -491,13 +500,11 @@ fs = file.size, ub = options.uploadedBytes = options.uploadedBytes || 0, mcs = options.maxChunkSize || fs, - // Use the Blob methods with the slice implementation - // according to the W3C Blob API specification: - slice = file.webkitSlice || file.mozSlice || file.slice, - upload, - n, + slice = file.slice || file.webkitSlice || file.mozSlice, + dfd = $.Deferred(), + promise = dfd.promise(), jqXHR, - pipe; + upload; if (!(this._isXHRUpload(options) && slice && (ub || mcs < fs)) || options.data) { return false; @@ -506,62 +513,71 @@ return true; } if (ub >= fs) { - file.error = 'uploadedBytes'; + file.error = 'Uploaded bytes exceed file size'; return this._getXHRPromise( false, options.context, [null, 'error', file.error] ); } - // n is the number of blobs to upload, - // calculated via filesize, uploaded bytes and max chunk size: - n = Math.ceil((fs - ub) / mcs); - // The chunk upload method accepting the chunk number as parameter: + // The chunk upload method: upload = function (i) { - if (!i) { - return that._getXHRPromise(true, options.context); - } - // Upload the blobs in sequential order: - return upload(i -= 1).pipe(function () { - // Clone the options object for each chunk upload: - var o = $.extend({}, options); - o.blob = slice.call( - file, - ub + i * mcs, - ub + (i + 1) * mcs - ); - // Store the current chunk size, as the blob itself - // will be dereferenced after data processing: - o.chunkSize = o.blob.size; - // Process the upload data (the blob and potential form data): - that._initXHRData(o); - // Add progress listeners for this chunk upload: - that._initProgressListener(o); - jqXHR = ($.ajax(o) || that._getXHRPromise(false, o.context)) - .done(function () { - // Create a progress event if upload is done and - // no progress event has been invoked for this chunk: - if (!o.loaded) { - that._onProgress($.Event('progress', { - lengthComputable: true, - loaded: o.chunkSize, - total: o.chunkSize - }), o); - } - options.uploadedBytes = o.uploadedBytes += - o.chunkSize; - }); - return jqXHR; - }); + // Clone the options object for each chunk upload: + var o = $.extend({}, options); + o.blob = slice.call( + file, + ub, + ub + mcs, + file.type + ); + // Store the current chunk size, as the blob itself + // will be dereferenced after data processing: + o.chunkSize = o.blob.size; + // Expose the chunk bytes position range: + o.contentRange = 'bytes ' + ub + '-' + + (ub + o.chunkSize - 1) + '/' + fs; + // Process the upload data (the blob and potential form data): + that._initXHRData(o); + // Add progress listeners for this chunk upload: + that._initProgressListener(o); + jqXHR = ($.ajax(o) || that._getXHRPromise(false, o.context)) + .done(function (result, textStatus, jqXHR) { + ub = that._getUploadedBytes(jqXHR) || + (ub + o.chunkSize); + // Create a progress event if upload is done and + // no progress event has been invoked for this chunk: + if (!o.loaded) { + that._onProgress($.Event('progress', { + lengthComputable: true, + loaded: ub - o.uploadedBytes, + total: ub - o.uploadedBytes + }), o); + } + options.uploadedBytes = o.uploadedBytes = ub; + if (ub < fs) { + // File upload not yet complete, + // continue with the next chunk: + upload(); + } else { + dfd.resolveWith( + o.context, + [result, textStatus, jqXHR] + ); + } + }) + .fail(function (jqXHR, textStatus, errorThrown) { + dfd.rejectWith( + o.context, + [jqXHR, textStatus, errorThrown] + ); + }); }; - // Return the piped Promise object, enhanced with an abort method, - // which is delegated to the jqXHR object of the current upload, - // and jqXHR callbacks mapped to the equivalent Promise methods: - pipe = upload(n); - pipe.abort = function () { + this._enhancePromise(promise); + promise.abort = function () { return jqXHR.abort(); }; - return this._enhancePromise(pipe); + upload(); + return promise; }, _beforeSend: function (e, data) { @@ -631,18 +647,18 @@ _onSend: function (e, data) { var that = this, jqXHR, + aborted, slot, pipe, options = that._getAJAXSettings(data), - send = function (resolve, args) { + send = function () { that._sending += 1; // Set timer for bitrate progress calculation: options._bitrateTimer = new that._BitrateTimer(); jqXHR = jqXHR || ( - (resolve !== false && - that._trigger('send', e, options) !== false && - (that._chunkedUpload(options) || $.ajax(options))) || - that._getXHRPromise(false, options.context, args) + ((aborted || that._trigger('send', e, options) === false) && + that._getXHRPromise(false, options.context, aborted)) || + that._chunkedUpload(options) || $.ajax(options) ).done(function (result, textStatus, jqXHR) { that._onDone(result, textStatus, jqXHR, options); }).fail(function (jqXHR, textStatus, errorThrown) { @@ -659,9 +675,15 @@ options.limitConcurrentUploads > that._sending) { // Start the next queued upload, // that has not been aborted: - var nextSlot = that._slots.shift(); + var nextSlot = that._slots.shift(), + isPending; while (nextSlot) { - if (!nextSlot.isRejected()) { + // jQuery 1.6 doesn't provide .state(), + // while jQuery 1.8+ removed .isRejected(): + isPending = nextSlot.state ? + nextSlot.state() === 'pending' : + !nextSlot.isRejected(); + if (isPending) { nextSlot.resolve(); break; } @@ -686,12 +708,12 @@ // which is delegated to the jqXHR object of the current upload, // and jqXHR callbacks mapped to the equivalent Promise methods: pipe.abort = function () { - var args = [undefined, 'abort', 'abort']; + aborted = [undefined, 'abort', 'abort']; if (!jqXHR) { if (slot) { - slot.rejectWith(args); + slot.rejectWith(options.context, aborted); } - return send(false, args); + return send(); } return jqXHR.abort(); }; @@ -739,19 +761,12 @@ that._onSend(e, this); return this.jqXHR; }; - return (result = that._trigger('add', e, newData)); + result = that._trigger('add', e, newData); + return result; }); return result; }, - // File Normalization for Gecko 1.9.1 (Firefox 3.5) support: - _normalizeFile: function (index, file) { - if (file.name === undefined && file.size === undefined) { - file.name = file.fileName; - file.size = file.fileSize; - } - }, - _replaceFileInput: function (input) { var inputClone = input.clone(true); $('
').append(inputClone)[0].reset(); @@ -761,7 +776,7 @@ // Avoid memory leaks with the detached file input: $.cleanData(input.unbind('remove')); // Replace the original file input element in the fileInput - // collection with the clone, which has been copied including + // elements set with the clone, which has been copied including // event handlers: this.options.fileInput = this.options.fileInput.map(function (i, el) { if (el === input[0]) { @@ -776,31 +791,153 @@ } }, - _onChange: function (e) { - var that = e.data.fileupload, - data = { - files: $.each($.makeArray(e.target.files), that._normalizeFile), - fileInput: $(e.target), - form: $(e.target.form) - }; - if (!data.files.length) { + _handleFileTreeEntry: function (entry, path) { + var that = this, + dfd = $.Deferred(), + errorHandler = function (e) { + if (e && !e.entry) { + e.entry = entry; + } + // Since $.when returns immediately if one + // Deferred is rejected, we use resolve instead. + // This allows valid files and invalid items + // to be returned together in one set: + dfd.resolve([e]); + }, + dirReader; + path = path || ''; + if (entry.isFile) { + if (entry._file) { + // Workaround for Chrome bug #149735 + entry._file.relativePath = path; + dfd.resolve(entry._file); + } else { + entry.file(function (file) { + file.relativePath = path; + dfd.resolve(file); + }, errorHandler); + } + } else if (entry.isDirectory) { + dirReader = entry.createReader(); + dirReader.readEntries(function (entries) { + that._handleFileTreeEntries( + entries, + path + entry.name + '/' + ).done(function (files) { + dfd.resolve(files); + }).fail(errorHandler); + }, errorHandler); + } else { + // Return an empy list for file system items + // other than files or directories: + dfd.resolve([]); + } + return dfd.promise(); + }, + + _handleFileTreeEntries: function (entries, path) { + var that = this; + return $.when.apply( + $, + $.map(entries, function (entry) { + return that._handleFileTreeEntry(entry, path); + }) + ).pipe(function () { + return Array.prototype.concat.apply( + [], + arguments + ); + }); + }, + + _getDroppedFiles: function (dataTransfer) { + dataTransfer = dataTransfer || {}; + var items = dataTransfer.items; + if (items && items.length && (items[0].webkitGetAsEntry || + items[0].getAsEntry)) { + return this._handleFileTreeEntries( + $.map(items, function (item) { + var entry; + if (item.webkitGetAsEntry) { + entry = item.webkitGetAsEntry(); + if (entry) { + // Workaround for Chrome bug #149735: + entry._file = item.getAsFile(); + } + return entry; + } + return item.getAsEntry(); + }) + ); + } + return $.Deferred().resolve( + $.makeArray(dataTransfer.files) + ).promise(); + }, + + _getSingleFileInputFiles: function (fileInput) { + fileInput = $(fileInput); + var entries = fileInput.prop('webkitEntries') || + fileInput.prop('entries'), + files, + value; + if (entries && entries.length) { + return this._handleFileTreeEntries(entries); + } + files = $.makeArray(fileInput.prop('files')); + if (!files.length) { + value = fileInput.prop('value'); + if (!value) { + return $.Deferred().resolve([]).promise(); + } // If the files property is not available, the browser does not // support the File API and we add a pseudo File object with // the input value as name with path information removed: - data.files = [{name: e.target.value.replace(/^.*\\/, '')}]; - } - if (that.options.replaceFileInput) { - that._replaceFileInput(data.fileInput); + files = [{name: value.replace(/^.*\\/, '')}]; + } else if (files[0].name === undefined && files[0].fileName) { + // File normalization for Safari 4 and Firefox 3: + $.each(files, function (index, file) { + file.name = file.fileName; + file.size = file.fileSize; + }); } - if (that._trigger('change', e, data) === false || - that._onAdd(e, data) === false) { - return false; + return $.Deferred().resolve(files).promise(); + }, + + _getFileInputFiles: function (fileInput) { + if (!(fileInput instanceof $) || fileInput.length === 1) { + return this._getSingleFileInputFiles(fileInput); } + return $.when.apply( + $, + $.map(fileInput, this._getSingleFileInputFiles) + ).pipe(function () { + return Array.prototype.concat.apply( + [], + arguments + ); + }); + }, + + _onChange: function (e) { + var that = this, + data = { + fileInput: $(e.target), + form: $(e.target.form) + }; + this._getFileInputFiles(data.fileInput).always(function (files) { + data.files = files; + if (that.options.replaceFileInput) { + that._replaceFileInput(data.fileInput); + } + if (that._trigger('change', e, data) !== false) { + that._onAdd(e, data); + } + }); }, _onPaste: function (e) { - var that = e.data.fileupload, - cbd = e.originalEvent.clipboardData, + var cbd = e.originalEvent.clipboardData, items = (cbd && cbd.items) || [], data = {files: []}; $.each(items, function (index, item) { @@ -809,60 +946,57 @@ data.files.push(file); } }); - if (that._trigger('paste', e, data) === false || - that._onAdd(e, data) === false) { + if (this._trigger('paste', e, data) === false || + this._onAdd(e, data) === false) { return false; } }, _onDrop: function (e) { - var that = e.data.fileupload, + var that = this, dataTransfer = e.dataTransfer = e.originalEvent.dataTransfer, - data = { - files: $.each( - $.makeArray(dataTransfer && dataTransfer.files), - that._normalizeFile - ) - }; - if (that._trigger('drop', e, data) === false || - that._onAdd(e, data) === false) { - return false; + data = {}; + if (dataTransfer && dataTransfer.files && dataTransfer.files.length) { + e.preventDefault(); } - e.preventDefault(); + this._getDroppedFiles(dataTransfer).always(function (files) { + data.files = files; + if (that._trigger('drop', e, data) !== false) { + that._onAdd(e, data); + } + }); }, _onDragOver: function (e) { - var that = e.data.fileupload, - dataTransfer = e.dataTransfer = e.originalEvent.dataTransfer; - if (that._trigger('dragover', e) === false) { + var dataTransfer = e.dataTransfer = e.originalEvent.dataTransfer; + if (this._trigger('dragover', e) === false) { return false; } - if (dataTransfer) { - dataTransfer.dropEffect = dataTransfer.effectAllowed = 'copy'; + if (dataTransfer && $.inArray('Files', dataTransfer.types) !== -1) { + dataTransfer.dropEffect = 'copy'; + e.preventDefault(); } - e.preventDefault(); }, _initEventHandlers: function () { - var ns = this.options.namespace; if (this._isXHRUpload(this.options)) { - this.options.dropZone - .bind('dragover.' + ns, {fileupload: this}, this._onDragOver) - .bind('drop.' + ns, {fileupload: this}, this._onDrop) - .bind('paste.' + ns, {fileupload: this}, this._onPaste); + this._on(this.options.dropZone, { + dragover: this._onDragOver, + drop: this._onDrop + }); + this._on(this.options.pasteZone, { + paste: this._onPaste + }); } - this.options.fileInput - .bind('change.' + ns, {fileupload: this}, this._onChange); + this._on(this.options.fileInput, { + change: this._onChange + }); }, _destroyEventHandlers: function () { - var ns = this.options.namespace; - this.options.dropZone - .unbind('dragover.' + ns, this._onDragOver) - .unbind('drop.' + ns, this._onDrop) - .unbind('paste.' + ns, this._onPaste); - this.options.fileInput - .unbind('change.' + ns, this._onChange); + this._off(this.options.dropZone, 'dragover drop'); + this._off(this.options.pasteZone, 'paste'); + this._off(this.options.fileInput, 'change'); }, _setOption: function (key, value) { @@ -870,7 +1004,7 @@ if (refresh) { this._destroyEventHandlers(); } - $.Widget.prototype._setOption.call(this, key, value); + this._super(key, value); if (refresh) { this._initSpecialOptions(); this._initEventHandlers(); @@ -880,21 +1014,23 @@ _initSpecialOptions: function () { var options = this.options; if (options.fileInput === undefined) { - options.fileInput = this.element.is('input:file') ? - this.element : this.element.find('input:file'); + options.fileInput = this.element.is('input[type="file"]') ? + this.element : this.element.find('input[type="file"]'); } else if (!(options.fileInput instanceof $)) { options.fileInput = $(options.fileInput); } if (!(options.dropZone instanceof $)) { options.dropZone = $(options.dropZone); } + if (!(options.pasteZone instanceof $)) { + options.pasteZone = $(options.pasteZone); + } }, _create: function () { var options = this.options; // Initialize options set via HTML5 data-attributes: $.extend(options, $(this.element[0].cloneNode(false)).data()); - options.namespace = options.namespace || this.widgetName; this._initSpecialOptions(); this._slots = []; this._sequence = this._getXHRPromise(true); @@ -902,19 +1038,8 @@ this._initEventHandlers(); }, - destroy: function () { + _destroy: function () { this._destroyEventHandlers(); - $.Widget.prototype.destroy.call(this); - }, - - enable: function () { - $.Widget.prototype.enable.call(this); - this._initEventHandlers(); - }, - - disable: function () { - this._destroyEventHandlers(); - $.Widget.prototype.disable.call(this); }, // This method is exposed to the widget API and allows adding files @@ -922,21 +1047,61 @@ // must have a files property and can contain additional options: // .fileupload('add', {files: filesList}); add: function (data) { + var that = this; if (!data || this.options.disabled) { return; } - data.files = $.each($.makeArray(data.files), this._normalizeFile); - this._onAdd(null, data); + if (data.fileInput && !data.files) { + this._getFileInputFiles(data.fileInput).always(function (files) { + data.files = files; + that._onAdd(null, data); + }); + } else { + data.files = $.makeArray(data.files); + this._onAdd(null, data); + } }, // This method is exposed to the widget API and allows sending files // using the fileupload API. The data parameter accepts an object which - // must have a files property and can contain additional options: + // must have a files or fileInput property and can contain additional options: // .fileupload('send', {files: filesList}); // The method returns a Promise object for the file upload call. send: function (data) { if (data && !this.options.disabled) { - data.files = $.each($.makeArray(data.files), this._normalizeFile); + if (data.fileInput && !data.files) { + var that = this, + dfd = $.Deferred(), + promise = dfd.promise(), + jqXHR, + aborted; + promise.abort = function () { + aborted = true; + if (jqXHR) { + return jqXHR.abort(); + } + dfd.reject(null, 'abort', 'abort'); + return promise; + }; + this._getFileInputFiles(data.fileInput).always( + function (files) { + if (aborted) { + return; + } + data.files = files; + jqXHR = that._onSend(null, data).then( + function (result, textStatus, jqXHR) { + dfd.resolve(result, textStatus, jqXHR); + }, + function (jqXHR, textStatus, errorThrown) { + dfd.reject(jqXHR, textStatus, errorThrown); + } + ); + } + ); + return this._enhancePromise(promise); + } + data.files = $.makeArray(data.files); if (data.files.length) { return this._onSend(null, data); } diff --git a/fileupload/static/js/jquery.iframe-transport.js b/fileupload/static/js/jquery.iframe-transport.js index 04a5662..ed25895 100644 --- a/fileupload/static/js/jquery.iframe-transport.js +++ b/fileupload/static/js/jquery.iframe-transport.js @@ -1,5 +1,5 @@ /* - * jQuery Iframe Transport Plugin 1.4 + * jQuery Iframe Transport Plugin 1.6.1 * https://github.com/blueimp/jQuery-File-Upload * * Copyright 2011, Sebastian Tschan @@ -36,12 +36,26 @@ // equivalent to the return data of .serializeArray(), e.g.: // [{name: 'a', value: 1}, {name: 'b', value: 2}] $.ajaxTransport('iframe', function (options) { - if (options.async && (options.type === 'POST' || options.type === 'GET')) { + if (options.async) { var form, - iframe; + iframe, + addParamChar; return { send: function (_, completeCallback) { form = $('
'); + form.attr('accept-charset', options.formAcceptCharset); + addParamChar = /\?/.test(options.url) ? '&' : '?'; + // XDomainRequest only supports GET and POST: + if (options.type === 'DELETE') { + options.url = options.url + addParamChar + '_method=DELETE'; + options.type = 'POST'; + } else if (options.type === 'PUT') { + options.url = options.url + addParamChar + '_method=PUT'; + options.type = 'POST'; + } else if (options.type === 'PATCH') { + options.url = options.url + addParamChar + '_method=PATCH'; + options.type = 'POST'; + } // javascript:false as initial iframe src // prevents warning popups on HTTPS in IE6. // IE versions below IE8 cannot set the name property of @@ -154,16 +168,16 @@ $.ajaxSetup({ converters: { 'iframe text': function (iframe) { - return $(iframe[0].body).text(); + return iframe && $(iframe[0].body).text(); }, 'iframe json': function (iframe) { - return $.parseJSON($(iframe[0].body).text()); + return iframe && $.parseJSON($(iframe[0].body).text()); }, 'iframe html': function (iframe) { - return $(iframe[0].body).html(); + return iframe && $(iframe[0].body).html(); }, 'iframe script': function (iframe) { - return $.globalEval($(iframe[0].body).text()); + return iframe && $.globalEval($(iframe[0].body).text()); } } }); diff --git a/fileupload/static/js/jquery.postmessage-transport.js b/fileupload/static/js/jquery.postmessage-transport.js new file mode 100644 index 0000000..931b635 --- /dev/null +++ b/fileupload/static/js/jquery.postmessage-transport.js @@ -0,0 +1,117 @@ +/* + * jQuery postMessage Transport Plugin 1.1 + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2011, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/MIT + */ + +/*jslint unparam: true, nomen: true */ +/*global define, window, document */ + +(function (factory) { + 'use strict'; + if (typeof define === 'function' && define.amd) { + // Register as an anonymous AMD module: + define(['jquery'], factory); + } else { + // Browser globals: + factory(window.jQuery); + } +}(function ($) { + 'use strict'; + + var counter = 0, + names = [ + 'accepts', + 'cache', + 'contents', + 'contentType', + 'crossDomain', + 'data', + 'dataType', + 'headers', + 'ifModified', + 'mimeType', + 'password', + 'processData', + 'timeout', + 'traditional', + 'type', + 'url', + 'username' + ], + convert = function (p) { + return p; + }; + + $.ajaxSetup({ + converters: { + 'postmessage text': convert, + 'postmessage json': convert, + 'postmessage html': convert + } + }); + + $.ajaxTransport('postmessage', function (options) { + if (options.postMessage && window.postMessage) { + var iframe, + loc = $('').prop('href', options.postMessage)[0], + target = loc.protocol + '//' + loc.host, + xhrUpload = options.xhr().upload; + return { + send: function (_, completeCallback) { + var message = { + id: 'postmessage-transport-' + (counter += 1) + }, + eventName = 'message.' + message.id; + iframe = $( + '' + ).bind('load', function () { + $.each(names, function (i, name) { + message[name] = options[name]; + }); + message.dataType = message.dataType.replace('postmessage ', ''); + $(window).bind(eventName, function (e) { + e = e.originalEvent; + var data = e.data, + ev; + if (e.origin === target && data.id === message.id) { + if (data.type === 'progress') { + ev = document.createEvent('Event'); + ev.initEvent(data.type, false, true); + $.extend(ev, data); + xhrUpload.dispatchEvent(ev); + } else { + completeCallback( + data.status, + data.statusText, + {postmessage: data.result}, + data.headers + ); + iframe.remove(); + $(window).unbind(eventName); + } + } + }); + iframe[0].contentWindow.postMessage( + message, + target + ); + }).appendTo(document.body); + }, + abort: function () { + if (iframe) { + iframe.remove(); + } + } + }; + } + }); + +})); diff --git a/fileupload/static/js/jquery.ui.widget.js b/fileupload/static/js/jquery.ui.widget.js index 9da8673..886aff6 100644 --- a/fileupload/static/js/jquery.ui.widget.js +++ b/fileupload/static/js/jquery.ui.widget.js @@ -1,12 +1,12 @@ /* - * jQuery UI Widget 1.8.18+amd + * jQuery UI Widget 1.9.1+amd * https://github.com/blueimp/jQuery-File-Upload * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. + * Copyright 2012 jQuery Foundation and other contributors + * Released under the MIT license. * http://jquery.org/license * - * http://docs.jquery.com/UI/Widget + * http://api.jqueryui.com/jQuery.widget/ */ (function (factory) { @@ -19,40 +19,23 @@ } }(function( $, undefined ) { -// jQuery 1.4+ -if ( $.cleanData ) { - var _cleanData = $.cleanData; - $.cleanData = function( elems ) { - for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) { - try { - $( elem ).triggerHandler( "remove" ); - // http://bugs.jquery.com/ticket/8235 - } catch( e ) {} - } - _cleanData( elems ); - }; -} else { - var _remove = $.fn.remove; - $.fn.remove = function( selector, keepData ) { - return this.each(function() { - if ( !keepData ) { - if ( !selector || $.filter( selector, [ this ] ).length ) { - $( "*", this ).add( [ this ] ).each(function() { - try { - $( this ).triggerHandler( "remove" ); - // http://bugs.jquery.com/ticket/8235 - } catch( e ) {} - }); - } - } - return _remove.call( $(this), selector, keepData ); - }); - }; -} +var uuid = 0, + slice = Array.prototype.slice, + _cleanData = $.cleanData; +$.cleanData = function( elems ) { + for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) { + try { + $( elem ).triggerHandler( "remove" ); + // http://bugs.jquery.com/ticket/8235 + } catch( e ) {} + } + _cleanData( elems ); +}; $.widget = function( name, base, prototype ) { - var namespace = name.split( "." )[ 0 ], - fullName; + var fullName, existingConstructor, constructor, basePrototype, + namespace = name.split( "." )[ 0 ]; + name = name.split( "." )[ 1 ]; fullName = namespace + "-" + name; @@ -62,81 +45,167 @@ $.widget = function( name, base, prototype ) { } // create selector for plugin - $.expr[ ":" ][ fullName ] = function( elem ) { - return !!$.data( elem, name ); + $.expr[ ":" ][ fullName.toLowerCase() ] = function( elem ) { + return !!$.data( elem, fullName ); }; $[ namespace ] = $[ namespace ] || {}; - $[ namespace ][ name ] = function( options, element ) { + existingConstructor = $[ namespace ][ name ]; + constructor = $[ namespace ][ name ] = function( options, element ) { + // allow instantiation without "new" keyword + if ( !this._createWidget ) { + return new constructor( options, element ); + } + // allow instantiation without initializing for simple inheritance + // must use "new" keyword (the code above always passes args) if ( arguments.length ) { this._createWidget( options, element ); } }; - - var basePrototype = new base(); + // extend with the existing constructor to carry over any static properties + $.extend( constructor, existingConstructor, { + version: prototype.version, + // copy the object used to create the prototype in case we need to + // redefine the widget later + _proto: $.extend( {}, prototype ), + // track widgets that inherit from this widget in case this widget is + // redefined after a widget inherits from it + _childConstructors: [] + }); + + basePrototype = new base(); // we need to make the options hash a property directly on the new instance // otherwise we'll modify the options hash on the prototype that we're // inheriting from -// $.each( basePrototype, function( key, val ) { -// if ( $.isPlainObject(val) ) { -// basePrototype[ key ] = $.extend( {}, val ); -// } -// }); - basePrototype.options = $.extend( true, {}, basePrototype.options ); - $[ namespace ][ name ].prototype = $.extend( true, basePrototype, { + basePrototype.options = $.widget.extend( {}, basePrototype.options ); + $.each( prototype, function( prop, value ) { + if ( $.isFunction( value ) ) { + prototype[ prop ] = (function() { + var _super = function() { + return base.prototype[ prop ].apply( this, arguments ); + }, + _superApply = function( args ) { + return base.prototype[ prop ].apply( this, args ); + }; + return function() { + var __super = this._super, + __superApply = this._superApply, + returnValue; + + this._super = _super; + this._superApply = _superApply; + + returnValue = value.apply( this, arguments ); + + this._super = __super; + this._superApply = __superApply; + + return returnValue; + }; + })(); + } + }); + constructor.prototype = $.widget.extend( basePrototype, { + // TODO: remove support for widgetEventPrefix + // always use the name + a colon as the prefix, e.g., draggable:start + // don't prefix for widgets that aren't DOM-based + widgetEventPrefix: basePrototype.widgetEventPrefix || name + }, prototype, { + constructor: constructor, namespace: namespace, widgetName: name, - widgetEventPrefix: $[ namespace ][ name ].prototype.widgetEventPrefix || name, - widgetBaseClass: fullName - }, prototype ); + // TODO remove widgetBaseClass, see #8155 + widgetBaseClass: fullName, + widgetFullName: fullName + }); + + // If this widget is being redefined then we need to find all widgets that + // are inheriting from it and redefine all of them so that they inherit from + // the new version of this widget. We're essentially trying to replace one + // level in the prototype chain. + if ( existingConstructor ) { + $.each( existingConstructor._childConstructors, function( i, child ) { + var childPrototype = child.prototype; + + // redefine the child widget using the same prototype that was + // originally used, but inherit from the new version of the base + $.widget( childPrototype.namespace + "." + childPrototype.widgetName, constructor, child._proto ); + }); + // remove the list of existing child constructors from the old constructor + // so the old child constructors can be garbage collected + delete existingConstructor._childConstructors; + } else { + base._childConstructors.push( constructor ); + } - $.widget.bridge( name, $[ namespace ][ name ] ); + $.widget.bridge( name, constructor ); +}; + +$.widget.extend = function( target ) { + var input = slice.call( arguments, 1 ), + inputIndex = 0, + inputLength = input.length, + key, + value; + for ( ; inputIndex < inputLength; inputIndex++ ) { + for ( key in input[ inputIndex ] ) { + value = input[ inputIndex ][ key ]; + if ( input[ inputIndex ].hasOwnProperty( key ) && value !== undefined ) { + // Clone objects + if ( $.isPlainObject( value ) ) { + target[ key ] = $.isPlainObject( target[ key ] ) ? + $.widget.extend( {}, target[ key ], value ) : + // Don't extend strings, arrays, etc. with objects + $.widget.extend( {}, value ); + // Copy everything else by reference + } else { + target[ key ] = value; + } + } + } + } + return target; }; $.widget.bridge = function( name, object ) { + var fullName = object.prototype.widgetFullName; $.fn[ name ] = function( options ) { var isMethodCall = typeof options === "string", - args = Array.prototype.slice.call( arguments, 1 ), + args = slice.call( arguments, 1 ), returnValue = this; // allow multiple hashes to be passed on init options = !isMethodCall && args.length ? - $.extend.apply( null, [ true, options ].concat(args) ) : + $.widget.extend.apply( null, [ options ].concat(args) ) : options; - // prevent calls to internal methods - if ( isMethodCall && options.charAt( 0 ) === "_" ) { - return returnValue; - } - if ( isMethodCall ) { this.each(function() { - var instance = $.data( this, name ), - methodValue = instance && $.isFunction( instance[options] ) ? - instance[ options ].apply( instance, args ) : - instance; - // TODO: add this back in 1.9 and use $.error() (see #5972) -// if ( !instance ) { -// throw "cannot call methods on " + name + " prior to initialization; " + -// "attempted to call method '" + options + "'"; -// } -// if ( !$.isFunction( instance[options] ) ) { -// throw "no such method '" + options + "' for " + name + " widget instance"; -// } -// var methodValue = instance[ options ].apply( instance, args ); + var methodValue, + instance = $.data( this, fullName ); + if ( !instance ) { + return $.error( "cannot call methods on " + name + " prior to initialization; " + + "attempted to call method '" + options + "'" ); + } + if ( !$.isFunction( instance[options] ) || options.charAt( 0 ) === "_" ) { + return $.error( "no such method '" + options + "' for " + name + " widget instance" ); + } + methodValue = instance[ options ].apply( instance, args ); if ( methodValue !== instance && methodValue !== undefined ) { - returnValue = methodValue; + returnValue = methodValue && methodValue.jquery ? + returnValue.pushStack( methodValue.get() ) : + methodValue; return false; } }); } else { this.each(function() { - var instance = $.data( this, name ); + var instance = $.data( this, fullName ); if ( instance ) { instance.option( options || {} )._init(); } else { - $.data( this, name, new object( options, this ) ); + new object( options, this ); } }); } @@ -145,74 +214,126 @@ $.widget.bridge = function( name, object ) { }; }; -$.Widget = function( options, element ) { - // allow instantiation without initializing for simple inheritance - if ( arguments.length ) { - this._createWidget( options, element ); - } -}; +$.Widget = function( /* options, element */ ) {}; +$.Widget._childConstructors = []; $.Widget.prototype = { widgetName: "widget", widgetEventPrefix: "", + defaultElement: "
", options: { - disabled: false + disabled: false, + + // callbacks + create: null }, _createWidget: function( options, element ) { - // $.widget.bridge stores the plugin instance, but we do it anyway - // so that it's stored even before the _create function runs - $.data( element, this.widgetName, this ); + element = $( element || this.defaultElement || this )[ 0 ]; this.element = $( element ); - this.options = $.extend( true, {}, + this.uuid = uuid++; + this.eventNamespace = "." + this.widgetName + this.uuid; + this.options = $.widget.extend( {}, this.options, this._getCreateOptions(), options ); - var self = this; - this.element.bind( "remove." + this.widgetName, function() { - self.destroy(); - }); + this.bindings = $(); + this.hoverable = $(); + this.focusable = $(); + + if ( element !== this ) { + // 1.9 BC for #7810 + // TODO remove dual storage + $.data( element, this.widgetName, this ); + $.data( element, this.widgetFullName, this ); + this._on( this.element, { + remove: function( event ) { + if ( event.target === element ) { + this.destroy(); + } + } + }); + this.document = $( element.style ? + // element within the document + element.ownerDocument : + // element is window or document + element.document || element ); + this.window = $( this.document[0].defaultView || this.document[0].parentWindow ); + } this._create(); - this._trigger( "create" ); + this._trigger( "create", null, this._getCreateEventData() ); this._init(); }, - _getCreateOptions: function() { - return $.metadata && $.metadata.get( this.element[0] )[ this.widgetName ]; - }, - _create: function() {}, - _init: function() {}, + _getCreateOptions: $.noop, + _getCreateEventData: $.noop, + _create: $.noop, + _init: $.noop, destroy: function() { + this._destroy(); + // we can probably remove the unbind calls in 2.0 + // all event bindings should go through this._on() this.element - .unbind( "." + this.widgetName ) - .removeData( this.widgetName ); + .unbind( this.eventNamespace ) + // 1.9 BC for #7810 + // TODO remove dual storage + .removeData( this.widgetName ) + .removeData( this.widgetFullName ) + // support: jquery <1.6.3 + // http://bugs.jquery.com/ticket/9413 + .removeData( $.camelCase( this.widgetFullName ) ); this.widget() - .unbind( "." + this.widgetName ) + .unbind( this.eventNamespace ) .removeAttr( "aria-disabled" ) .removeClass( - this.widgetBaseClass + "-disabled " + + this.widgetFullName + "-disabled " + "ui-state-disabled" ); + + // clean up events and states + this.bindings.unbind( this.eventNamespace ); + this.hoverable.removeClass( "ui-state-hover" ); + this.focusable.removeClass( "ui-state-focus" ); }, + _destroy: $.noop, widget: function() { return this.element; }, option: function( key, value ) { - var options = key; + var options = key, + parts, + curOption, + i; if ( arguments.length === 0 ) { // don't return a reference to the internal hash - return $.extend( {}, this.options ); + return $.widget.extend( {}, this.options ); } - if (typeof key === "string" ) { - if ( value === undefined ) { - return this.options[ key ]; - } + if ( typeof key === "string" ) { + // handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } } options = {}; - options[ key ] = value; + parts = key.split( "." ); + key = parts.shift(); + if ( parts.length ) { + curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] ); + for ( i = 0; i < parts.length - 1; i++ ) { + curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {}; + curOption = curOption[ parts[ i ] ]; + } + key = parts.pop(); + if ( value === undefined ) { + return curOption[ key ] === undefined ? null : curOption[ key ]; + } + curOption[ key ] = value; + } else { + if ( value === undefined ) { + return this.options[ key ] === undefined ? null : this.options[ key ]; + } + options[ key ] = value; + } } this._setOptions( options ); @@ -220,10 +341,11 @@ $.Widget.prototype = { return this; }, _setOptions: function( options ) { - var self = this; - $.each( options, function( key, value ) { - self._setOption( key, value ); - }); + var key; + + for ( key in options ) { + this._setOption( key, options[ key ] ); + } return this; }, @@ -232,10 +354,10 @@ $.Widget.prototype = { if ( key === "disabled" ) { this.widget() - [ value ? "addClass" : "removeClass"]( - this.widgetBaseClass + "-disabled" + " " + - "ui-state-disabled" ) + .toggleClass( this.widgetFullName + "-disabled ui-state-disabled", !!value ) .attr( "aria-disabled", value ); + this.hoverable.removeClass( "ui-state-hover" ); + this.focusable.removeClass( "ui-state-focus" ); } return this; @@ -248,6 +370,88 @@ $.Widget.prototype = { return this._setOption( "disabled", true ); }, + _on: function( element, handlers ) { + var delegateElement, + instance = this; + // no element argument, shuffle and use this.element + if ( !handlers ) { + handlers = element; + element = this.element; + delegateElement = this.widget(); + } else { + // accept selectors, DOM elements + element = delegateElement = $( element ); + this.bindings = this.bindings.add( element ); + } + + $.each( handlers, function( event, handler ) { + function handlerProxy() { + // allow widgets to customize the disabled handling + // - disabled as an array instead of boolean + // - disabled class as method for disabling individual parts + if ( instance.options.disabled === true || + $( this ).hasClass( "ui-state-disabled" ) ) { + return; + } + return ( typeof handler === "string" ? instance[ handler ] : handler ) + .apply( instance, arguments ); + } + + // copy the guid so direct unbinding works + if ( typeof handler !== "string" ) { + handlerProxy.guid = handler.guid = + handler.guid || handlerProxy.guid || $.guid++; + } + + var match = event.match( /^(\w+)\s*(.*)$/ ), + eventName = match[1] + instance.eventNamespace, + selector = match[2]; + if ( selector ) { + delegateElement.delegate( selector, eventName, handlerProxy ); + } else { + element.bind( eventName, handlerProxy ); + } + }); + }, + + _off: function( element, eventName ) { + eventName = (eventName || "").split( " " ).join( this.eventNamespace + " " ) + this.eventNamespace; + element.unbind( eventName ).undelegate( eventName ); + }, + + _delay: function( handler, delay ) { + function handlerProxy() { + return ( typeof handler === "string" ? instance[ handler ] : handler ) + .apply( instance, arguments ); + } + var instance = this; + return setTimeout( handlerProxy, delay || 0 ); + }, + + _hoverable: function( element ) { + this.hoverable = this.hoverable.add( element ); + this._on( element, { + mouseenter: function( event ) { + $( event.currentTarget ).addClass( "ui-state-hover" ); + }, + mouseleave: function( event ) { + $( event.currentTarget ).removeClass( "ui-state-hover" ); + } + }); + }, + + _focusable: function( element ) { + this.focusable = this.focusable.add( element ); + this._on( element, { + focusin: function( event ) { + $( event.currentTarget ).addClass( "ui-state-focus" ); + }, + focusout: function( event ) { + $( event.currentTarget ).removeClass( "ui-state-focus" ); + } + }); + }, + _trigger: function( type, event, data ) { var prop, orig, callback = this.options[ type ]; @@ -272,11 +476,53 @@ $.Widget.prototype = { } this.element.trigger( event, data ); - - return !( $.isFunction(callback) && - callback.call( this.element[0], event, data ) === false || + return !( $.isFunction( callback ) && + callback.apply( this.element[0], [ event ].concat( data ) ) === false || event.isDefaultPrevented() ); } }; +$.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) { + $.Widget.prototype[ "_" + method ] = function( element, options, callback ) { + if ( typeof options === "string" ) { + options = { effect: options }; + } + var hasOptions, + effectName = !options ? + method : + options === true || typeof options === "number" ? + defaultEffect : + options.effect || defaultEffect; + options = options || {}; + if ( typeof options === "number" ) { + options = { duration: options }; + } + hasOptions = !$.isEmptyObject( options ); + options.complete = callback; + if ( options.delay ) { + element.delay( options.delay ); + } + if ( hasOptions && $.effects && ( $.effects.effect[ effectName ] || $.uiBackCompat !== false && $.effects[ effectName ] ) ) { + element[ method ]( options ); + } else if ( effectName !== method && element[ effectName ] ) { + element[ effectName ]( options.duration, options.easing, callback ); + } else { + element.queue(function( next ) { + $( this )[ method ](); + if ( callback ) { + callback.call( element[ 0 ] ); + } + next(); + }); + } + }; +}); + +// DEPRECATED +if ( $.uiBackCompat !== false ) { + $.Widget.prototype._getCreateOptions = function() { + return $.metadata && $.metadata.get( this.element[0] )[ this.widgetName ]; + }; +} + })); diff --git a/fileupload/static/js/jquery.xdr-transport.js b/fileupload/static/js/jquery.xdr-transport.js new file mode 100644 index 0000000..d769f45 --- /dev/null +++ b/fileupload/static/js/jquery.xdr-transport.js @@ -0,0 +1,87 @@ +/* + * jQuery XDomainRequest Transport Plugin 1.1.3 + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2011, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/MIT + * + * Based on Julian Aubourg's ajaxHooks xdr.js: + * https://github.com/jaubourg/ajaxHooks/ + */ + +/*jslint unparam: true */ +/*global define, window, XDomainRequest */ + +(function (factory) { + 'use strict'; + if (typeof define === 'function' && define.amd) { + // Register as an anonymous AMD module: + define(['jquery'], factory); + } else { + // Browser globals: + factory(window.jQuery); + } +}(function ($) { + 'use strict'; + if (window.XDomainRequest && !$.support.cors) { + $.ajaxTransport(function (s) { + if (s.crossDomain && s.async) { + if (s.timeout) { + s.xdrTimeout = s.timeout; + delete s.timeout; + } + var xdr; + return { + send: function (headers, completeCallback) { + var addParamChar = /\?/.test(s.url) ? '&' : '?'; + function callback(status, statusText, responses, responseHeaders) { + xdr.onload = xdr.onerror = xdr.ontimeout = $.noop; + xdr = null; + completeCallback(status, statusText, responses, responseHeaders); + } + xdr = new XDomainRequest(); + // XDomainRequest only supports GET and POST: + if (s.type === 'DELETE') { + s.url = s.url + addParamChar + '_method=DELETE'; + s.type = 'POST'; + } else if (s.type === 'PUT') { + s.url = s.url + addParamChar + '_method=PUT'; + s.type = 'POST'; + } else if (s.type === 'PATCH') { + s.url = s.url + addParamChar + '_method=PATCH'; + s.type = 'POST'; + } + xdr.open(s.type, s.url); + xdr.onload = function () { + callback( + 200, + 'OK', + {text: xdr.responseText}, + 'Content-Type: ' + xdr.contentType + ); + }; + xdr.onerror = function () { + callback(404, 'Not Found'); + }; + if (s.xdrTimeout) { + xdr.ontimeout = function () { + callback(0, 'timeout'); + }; + xdr.timeout = s.xdrTimeout; + } + xdr.send((s.hasContent && s.data) || null); + }, + abort: function () { + if (xdr) { + xdr.onerror = $.noop(); + xdr.abort(); + } + } + }; + } + }); + } +})); diff --git a/fileupload/static/js/main.js b/fileupload/static/js/main.js index 01c86fe..dbabd8f 100644 --- a/fileupload/static/js/main.js +++ b/fileupload/static/js/main.js @@ -1,5 +1,5 @@ /* - * jQuery File Upload Plugin JS Example 6.7 + * jQuery File Upload Plugin JS Example 7.0 * https://github.com/blueimp/jQuery-File-Upload * * Copyright 2010, Sebastian Tschan @@ -16,7 +16,11 @@ $(function () { 'use strict'; // Initialize the jQuery File Upload widget: - $('#fileupload').fileupload(); + $('#fileupload').fileupload({ + // Uncomment the following to send cross-domain cookies: + //xhrFields: {withCredentials: true}, + url: 'server/php/' + }); // Enable iframe cross-domain access via redirect option: $('#fileupload').fileupload( @@ -64,14 +68,15 @@ $(function () { } } else { // Load existing files: - $('#fileupload').each(function () { - var that = this; - $.getJSON(this.action, function (result) { - if (result && result.length) { - $(that).fileupload('option', 'done') - .call(that, null, {result: result}); - } - }); + $.ajax({ + // Uncomment the following to send cross-domain cookies: + //xhrFields: {withCredentials: true}, + url: $('#fileupload').fileupload('option', 'url'), + dataType: 'json', + context: $('#fileupload')[0] + }).done(function (result) { + $(this).fileupload('option', 'done') + .call(this, null, {result: result}); }); } From 6b987014a24f7944e2af4d9b06e7408415cfa26c Mon Sep 17 00:00:00 2001 From: ET-CS Date: Sun, 20 Jan 2013 21:10:07 +0200 Subject: [PATCH 06/72] xdr moved to cors folder inside .js --- fileupload/static/js/{ => cors}/jquery.postmessage-transport.js | 0 fileupload/static/js/{ => cors}/jquery.xdr-transport.js | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename fileupload/static/js/{ => cors}/jquery.postmessage-transport.js (100%) rename fileupload/static/js/{ => cors}/jquery.xdr-transport.js (100%) diff --git a/fileupload/static/js/jquery.postmessage-transport.js b/fileupload/static/js/cors/jquery.postmessage-transport.js similarity index 100% rename from fileupload/static/js/jquery.postmessage-transport.js rename to fileupload/static/js/cors/jquery.postmessage-transport.js diff --git a/fileupload/static/js/jquery.xdr-transport.js b/fileupload/static/js/cors/jquery.xdr-transport.js similarity index 100% rename from fileupload/static/js/jquery.xdr-transport.js rename to fileupload/static/js/cors/jquery.xdr-transport.js From 9b5d8cc77c0865cef1b126a590d62ceae6bcddbd Mon Sep 17 00:00:00 2001 From: ET-CS Date: Sun, 20 Jan 2013 21:27:46 +0200 Subject: [PATCH 07/72] leave the file after delete comment --- fileupload/models.py | 1 + 1 file changed, 1 insertion(+) diff --git a/fileupload/models.py b/fileupload/models.py index c59bc93..66fe2ed 100644 --- a/fileupload/models.py +++ b/fileupload/models.py @@ -22,6 +22,7 @@ def save(self, *args, **kwargs): self.slug = self.file.name super(Picture, self).save(*args, **kwargs) + # remove to leave file. def delete(self, *args, **kwargs): self.file.delete(False) super(Picture, self).delete(*args, **kwargs) From 569279bd6253fb4bd486930ae815f051767eea8c Mon Sep 17 00:00:00 2001 From: ET-CS Date: Sun, 20 Jan 2013 21:59:48 +0200 Subject: [PATCH 08/72] Django administration enabled --- settings.py | 2 +- urls.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/settings.py b/settings.py index 4c50776..68744b1 100644 --- a/settings.py +++ b/settings.py @@ -119,7 +119,7 @@ 'django.contrib.staticfiles', 'fileupload', # Uncomment the next line to enable the admin: - # 'django.contrib.admin', + 'django.contrib.admin', # Uncomment the next line to enable admin documentation: # 'django.contrib.admindocs', ) diff --git a/urls.py b/urls.py index d817b29..d061e88 100644 --- a/urls.py +++ b/urls.py @@ -1,8 +1,8 @@ from django.conf.urls.defaults import patterns, include, url # Uncomment the next two lines to enable the admin: -# from django.contrib import admin -# admin.autodiscover() +from django.contrib import admin +admin.autodiscover() urlpatterns = patterns('', # Examples: @@ -13,7 +13,7 @@ # url(r'^admin/doc/', include('django.contrib.admindocs.urls')), # Uncomment the next line to enable the admin: - # url(r'^admin/', include(admin.site.urls)), + url(r'^admin/', include(admin.site.urls)), ) import os From e76eff56f8de5921c6554a4447867a1820fa476a Mon Sep 17 00:00:00 2001 From: ET-CS Date: Mon, 21 Jan 2013 01:05:33 +0200 Subject: [PATCH 09/72] removed updated .js from original project. default / routing added. --- settings.py | 2 +- urls.py | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/settings.py b/settings.py index 4c50776..68744b1 100644 --- a/settings.py +++ b/settings.py @@ -119,7 +119,7 @@ 'django.contrib.staticfiles', 'fileupload', # Uncomment the next line to enable the admin: - # 'django.contrib.admin', + 'django.contrib.admin', # Uncomment the next line to enable admin documentation: # 'django.contrib.admindocs', ) diff --git a/urls.py b/urls.py index d817b29..535dd84 100644 --- a/urls.py +++ b/urls.py @@ -1,19 +1,22 @@ from django.conf.urls.defaults import patterns, include, url +from django.shortcuts import redirect # Uncomment the next two lines to enable the admin: -# from django.contrib import admin -# admin.autodiscover() +from django.contrib import admin +admin.autodiscover() urlpatterns = patterns('', # Examples: # url(r'^$', 'upload.views.home', name='home'), + + url(r'^$', redirect(‘url-name’)), url(r'^upload/', include('fileupload.urls')), # Uncomment the admin/doc line below to enable admin documentation: # url(r'^admin/doc/', include('django.contrib.admindocs.urls')), # Uncomment the next line to enable the admin: - # url(r'^admin/', include(admin.site.urls)), + url(r'^admin/', include(admin.site.urls)), ) import os From 31fab19786c3f7bd7759411a5aea562d7182aeb6 Mon Sep 17 00:00:00 2001 From: ET-CS Date: Mon, 21 Jan 2013 01:09:39 +0200 Subject: [PATCH 10/72] updated .js rolled-back --- .../js/cors/jquery.postmessage-transport.js | 117 ---- .../static/js/cors/jquery.xdr-transport.js | 87 --- fileupload/static/js/jquery.fileupload-fp.js | 38 +- fileupload/static/js/jquery.fileupload-ui.js | 328 ++++------- fileupload/static/js/jquery.fileupload.js | 535 ++++++------------ .../static/js/jquery.iframe-transport.js | 28 +- fileupload/static/js/jquery.ui.widget.js | 480 ++++------------ fileupload/static/js/main.js | 25 +- 8 files changed, 450 insertions(+), 1188 deletions(-) delete mode 100644 fileupload/static/js/cors/jquery.postmessage-transport.js delete mode 100644 fileupload/static/js/cors/jquery.xdr-transport.js diff --git a/fileupload/static/js/cors/jquery.postmessage-transport.js b/fileupload/static/js/cors/jquery.postmessage-transport.js deleted file mode 100644 index 931b635..0000000 --- a/fileupload/static/js/cors/jquery.postmessage-transport.js +++ /dev/null @@ -1,117 +0,0 @@ -/* - * jQuery postMessage Transport Plugin 1.1 - * https://github.com/blueimp/jQuery-File-Upload - * - * Copyright 2011, Sebastian Tschan - * https://blueimp.net - * - * Licensed under the MIT license: - * http://www.opensource.org/licenses/MIT - */ - -/*jslint unparam: true, nomen: true */ -/*global define, window, document */ - -(function (factory) { - 'use strict'; - if (typeof define === 'function' && define.amd) { - // Register as an anonymous AMD module: - define(['jquery'], factory); - } else { - // Browser globals: - factory(window.jQuery); - } -}(function ($) { - 'use strict'; - - var counter = 0, - names = [ - 'accepts', - 'cache', - 'contents', - 'contentType', - 'crossDomain', - 'data', - 'dataType', - 'headers', - 'ifModified', - 'mimeType', - 'password', - 'processData', - 'timeout', - 'traditional', - 'type', - 'url', - 'username' - ], - convert = function (p) { - return p; - }; - - $.ajaxSetup({ - converters: { - 'postmessage text': convert, - 'postmessage json': convert, - 'postmessage html': convert - } - }); - - $.ajaxTransport('postmessage', function (options) { - if (options.postMessage && window.postMessage) { - var iframe, - loc = $('').prop('href', options.postMessage)[0], - target = loc.protocol + '//' + loc.host, - xhrUpload = options.xhr().upload; - return { - send: function (_, completeCallback) { - var message = { - id: 'postmessage-transport-' + (counter += 1) - }, - eventName = 'message.' + message.id; - iframe = $( - '' - ).bind('load', function () { - $.each(names, function (i, name) { - message[name] = options[name]; - }); - message.dataType = message.dataType.replace('postmessage ', ''); - $(window).bind(eventName, function (e) { - e = e.originalEvent; - var data = e.data, - ev; - if (e.origin === target && data.id === message.id) { - if (data.type === 'progress') { - ev = document.createEvent('Event'); - ev.initEvent(data.type, false, true); - $.extend(ev, data); - xhrUpload.dispatchEvent(ev); - } else { - completeCallback( - data.status, - data.statusText, - {postmessage: data.result}, - data.headers - ); - iframe.remove(); - $(window).unbind(eventName); - } - } - }); - iframe[0].contentWindow.postMessage( - message, - target - ); - }).appendTo(document.body); - }, - abort: function () { - if (iframe) { - iframe.remove(); - } - } - }; - } - }); - -})); diff --git a/fileupload/static/js/cors/jquery.xdr-transport.js b/fileupload/static/js/cors/jquery.xdr-transport.js deleted file mode 100644 index d769f45..0000000 --- a/fileupload/static/js/cors/jquery.xdr-transport.js +++ /dev/null @@ -1,87 +0,0 @@ -/* - * jQuery XDomainRequest Transport Plugin 1.1.3 - * https://github.com/blueimp/jQuery-File-Upload - * - * Copyright 2011, Sebastian Tschan - * https://blueimp.net - * - * Licensed under the MIT license: - * http://www.opensource.org/licenses/MIT - * - * Based on Julian Aubourg's ajaxHooks xdr.js: - * https://github.com/jaubourg/ajaxHooks/ - */ - -/*jslint unparam: true */ -/*global define, window, XDomainRequest */ - -(function (factory) { - 'use strict'; - if (typeof define === 'function' && define.amd) { - // Register as an anonymous AMD module: - define(['jquery'], factory); - } else { - // Browser globals: - factory(window.jQuery); - } -}(function ($) { - 'use strict'; - if (window.XDomainRequest && !$.support.cors) { - $.ajaxTransport(function (s) { - if (s.crossDomain && s.async) { - if (s.timeout) { - s.xdrTimeout = s.timeout; - delete s.timeout; - } - var xdr; - return { - send: function (headers, completeCallback) { - var addParamChar = /\?/.test(s.url) ? '&' : '?'; - function callback(status, statusText, responses, responseHeaders) { - xdr.onload = xdr.onerror = xdr.ontimeout = $.noop; - xdr = null; - completeCallback(status, statusText, responses, responseHeaders); - } - xdr = new XDomainRequest(); - // XDomainRequest only supports GET and POST: - if (s.type === 'DELETE') { - s.url = s.url + addParamChar + '_method=DELETE'; - s.type = 'POST'; - } else if (s.type === 'PUT') { - s.url = s.url + addParamChar + '_method=PUT'; - s.type = 'POST'; - } else if (s.type === 'PATCH') { - s.url = s.url + addParamChar + '_method=PATCH'; - s.type = 'POST'; - } - xdr.open(s.type, s.url); - xdr.onload = function () { - callback( - 200, - 'OK', - {text: xdr.responseText}, - 'Content-Type: ' + xdr.contentType - ); - }; - xdr.onerror = function () { - callback(404, 'Not Found'); - }; - if (s.xdrTimeout) { - xdr.ontimeout = function () { - callback(0, 'timeout'); - }; - xdr.timeout = s.xdrTimeout; - } - xdr.send((s.hasContent && s.data) || null); - }, - abort: function () { - if (xdr) { - xdr.onerror = $.noop(); - xdr.abort(); - } - } - }; - } - }); - } -})); diff --git a/fileupload/static/js/jquery.fileupload-fp.js b/fileupload/static/js/jquery.fileupload-fp.js index fdf18fb..634fb5e 100644 --- a/fileupload/static/js/jquery.fileupload-fp.js +++ b/fileupload/static/js/jquery.fileupload-fp.js @@ -1,5 +1,5 @@ /* - * jQuery File Upload File Processing Plugin 1.2.1 + * jQuery File Upload File Processing Plugin 1.0 * https://github.com/blueimp/jQuery-File-Upload * * Copyright 2012, Sebastian Tschan @@ -32,9 +32,9 @@ }(function ($, loadImage) { 'use strict'; - // The File Upload FP version extends the fileupload widget + // The File Upload IP version extends the basic fileupload widget // with file processing functionality: - $.widget('blueimp.fileupload', $.blueimp.fileupload, { + $.widget('blueimpFP.fileupload', $.blueimp.fileupload, { options: { // The list of file processing actions: @@ -70,7 +70,7 @@ processActions: { // Loads the image given via data.files and data.index - // as img element if the browser supports canvas. + // as canvas element. // Accepts the options fileTypes (regular expression) // and maxFileSize (integer) to limit the files to load: load: function (data, options) { @@ -85,32 +85,28 @@ options.fileTypes.test(file.type))) { loadImage( file, - function (img) { - if (!img.src) { - return dfd.rejectWith(that, [data]); - } - data.img = img; + function (canvas) { + data.canvas = canvas; dfd.resolveWith(that, [data]); - } + }, + {canvas: true} ); } else { dfd.rejectWith(that, [data]); } return dfd.promise(); }, - // Resizes the image given as data.img and updates - // data.canvas with the resized image as canvas element. + // Resizes the image given as data.canvas and updates + // data.canvas with the resized image. // Accepts the options maxWidth, maxHeight, minWidth and // minHeight to scale the given image: resize: function (data, options) { - var img = data.img, - canvas; - options = $.extend({canvas: true}, options); - if (img) { - canvas = loadImage.scale(img, options); - if (canvas.width !== img.width || - canvas.height !== img.height) { + if (data.canvas) { + var canvas = loadImage.scale(data.canvas, options); + if (canvas.width !== data.canvas.width || + canvas.height !== data.canvas.height) { data.canvas = canvas; + data.processed = true; } } return data; @@ -119,7 +115,7 @@ // inplace at data.index of data.files: save: function (data, options) { // Do nothing if no processing has happened: - if (!data.canvas) { + if (!data.canvas || !data.processed) { return data; } var that = this, @@ -212,7 +208,7 @@ }, _create: function () { - this._super(); + $.blueimp.fileupload.prototype._create.call(this); this._processing = 0; this._processingQueue = $.Deferred().resolveWith(this) .promise(); diff --git a/fileupload/static/js/jquery.fileupload-ui.js b/fileupload/static/js/jquery.fileupload-ui.js index e62cbab..f7fc3bf 100644 --- a/fileupload/static/js/jquery.fileupload-ui.js +++ b/fileupload/static/js/jquery.fileupload-ui.js @@ -1,5 +1,5 @@ /* - * jQuery File Upload User Interface Plugin 7.2 + * jQuery File Upload User Interface Plugin 6.8.1 * https://github.com/blueimp/jQuery-File-Upload * * Copyright 2010, Sebastian Tschan @@ -10,7 +10,7 @@ */ /*jslint nomen: true, unparam: true, regexp: true */ -/*global define, window, URL, webkitURL, FileReader */ +/*global define, window, document, URL, webkitURL, FileReader */ (function (factory) { 'use strict'; @@ -33,9 +33,10 @@ }(function ($, tmpl, loadImage) { 'use strict'; - // The UI version extends the file upload widget - // and adds complete user interface interaction: - $.widget('blueimp.fileupload', $.blueimp.fileupload, { + // The UI version extends the FP (file processing) version or the basic + // file upload widget and adds complete user interface interaction: + var parentWidget = ($.blueimpFP || $.blueimp).fileupload; + $.widget('blueimpUI.fileupload', parentWidget, { options: { // By default, files added to the widget are uploaded as soon @@ -51,7 +52,7 @@ minFileSize: undefined, // The regular expression for allowed file types, matches // against either file type or file name: - acceptFileTypes: /^image\/(gif|jpeg|png)$/, + acceptFileTypes: /.+$/i, // The regular expression to define for which files a preview // image is shown, matched against the file type: previewSourceFileTypes: /^image\/(gif|jpeg|png)$/, @@ -88,13 +89,13 @@ files = data.files; $(this).fileupload('process', data).done(function () { that._adjustMaxNumberOfFiles(-files.length); - data.maxNumberOfFilesAdjusted = true; + data.isAdjusted = true; data.files.valid = data.isValidated = that._validate(files); data.context = that._renderUpload(files).data('data', data); options.filesContainer[ options.prependFiles ? 'prepend' : 'append' ](data.context); - that._renderPreviews(data); + that._renderPreviews(files, data.context); that._forceReflow(data.context); that._transition(data.context).done( function () { @@ -111,9 +112,8 @@ send: function (e, data) { var that = $(this).data('fileupload'); if (!data.isValidated) { - if (!data.maxNumberOfFilesAdjusted) { + if (!data.isAdjusted) { that._adjustMaxNumberOfFiles(-data.files.length); - data.maxNumberOfFilesAdjusted = true; } if (!that._validate(data.files)) { return false; @@ -128,10 +128,9 @@ .find('.progress').addClass( !$.support.transition && 'progress-animated' ) - .attr('aria-valuenow', 100) .find('.bar').css( 'width', - '100%' + parseInt(100, 10) + '%' ); } return that._trigger('sent', e, data); @@ -139,14 +138,11 @@ // Callback for successful uploads: done: function (e, data) { var that = $(this).data('fileupload'), - files = that._getFilesFromResponse(data), - template, - deferred; + template; if (data.context) { data.context.each(function (index) { - var file = files[index] || - {error: 'Empty file upload result'}, - deferred = that._addFinishedDeferreds(); + var file = ($.isArray(data.result) && + data.result[index]) || {error: 'emptyResult'}; if (file.error) { that._adjustMaxNumberOfFiles(1); } @@ -154,41 +150,26 @@ function () { var node = $(this); template = that._renderDownload([file]) + .css('height', node.height()) .replaceAll(node); that._forceReflow(template); that._transition(template).done( function () { data.context = $(this); that._trigger('completed', e, data); - that._trigger('finished', e, data); - deferred.resolve(); } ); } ); }); } else { - if (files.length) { - $.each(files, function (index, file) { - if (data.maxNumberOfFilesAdjusted && file.error) { - that._adjustMaxNumberOfFiles(1); - } else if (!data.maxNumberOfFilesAdjusted && - !file.error) { - that._adjustMaxNumberOfFiles(-1); - } - }); - data.maxNumberOfFilesAdjusted = true; - } - template = that._renderDownload(files) + template = that._renderDownload(data.result) .appendTo(that.options.filesContainer); that._forceReflow(template); - deferred = that._addFinishedDeferreds(); that._transition(template).done( function () { data.context = $(this); that._trigger('completed', e, data); - that._trigger('finished', e, data); - deferred.resolve(); } ); } @@ -196,18 +177,14 @@ // Callback for failed (abort or error) uploads: fail: function (e, data) { var that = $(this).data('fileupload'), - template, - deferred; - if (data.maxNumberOfFilesAdjusted) { - that._adjustMaxNumberOfFiles(data.files.length); - } + template; + that._adjustMaxNumberOfFiles(data.files.length); if (data.context) { data.context.each(function (index) { if (data.errorThrown !== 'abort') { var file = data.files[index]; file.error = file.error || data.errorThrown || true; - deferred = that._addFinishedDeferreds(); that._transition($(this)).done( function () { var node = $(this); @@ -218,80 +195,62 @@ function () { data.context = $(this); that._trigger('failed', e, data); - that._trigger('finished', e, data); - deferred.resolve(); } ); } ); } else { - deferred = that._addFinishedDeferreds(); that._transition($(this)).done( function () { $(this).remove(); that._trigger('failed', e, data); - that._trigger('finished', e, data); - deferred.resolve(); } ); } }); } else if (data.errorThrown !== 'abort') { + that._adjustMaxNumberOfFiles(-data.files.length); data.context = that._renderUpload(data.files) .appendTo(that.options.filesContainer) .data('data', data); that._forceReflow(data.context); - deferred = that._addFinishedDeferreds(); that._transition(data.context).done( function () { data.context = $(this); that._trigger('failed', e, data); - that._trigger('finished', e, data); - deferred.resolve(); } ); } else { that._trigger('failed', e, data); - that._trigger('finished', e, data); - that._addFinishedDeferreds().resolve(); } }, // Callback for upload progress events: progress: function (e, data) { if (data.context) { - var progress = parseInt(data.loaded / data.total * 100, 10); - data.context.find('.progress') - .attr('aria-valuenow', progress) - .find('.bar').css( - 'width', - progress + '%' - ); + data.context.find('.bar').css( + 'width', + parseInt(data.loaded / data.total * 100, 10) + '%' + ); } }, // Callback for global upload progress events: progressall: function (e, data) { - var $this = $(this), - progress = parseInt(data.loaded / data.total * 100, 10), - globalProgressNode = $this.find('.fileupload-progress'), - extendedProgressNode = globalProgressNode - .find('.progress-extended'); - if (extendedProgressNode.length) { - extendedProgressNode.html( - $this.data('fileupload')._renderExtendedProgress(data) - ); - } - globalProgressNode - .find('.progress') - .attr('aria-valuenow', progress) + var $this = $(this); + $this.find('.fileupload-progress') .find('.bar').css( 'width', - progress + '%' - ); + parseInt(data.loaded / data.total * 100, 10) + '%' + ).end() + .find('.progress-extended').each(function () { + $(this).html( + $this.data('fileupload') + ._renderExtendedProgress(data) + ); + }); }, // Callback for uploads start, equivalent to the global ajaxStart event: start: function (e) { var that = $(this).data('fileupload'); - that._resetFinishedDeferreds(); that._transition($(this).find('.fileupload-progress')).done( function () { that._trigger('started', e); @@ -300,19 +259,12 @@ }, // Callback for uploads stop, equivalent to the global ajaxStop event: stop: function (e) { - var that = $(this).data('fileupload'), - deferred = that._addFinishedDeferreds(); - $.when.apply($, that._getFinishedDeferreds()) - .done(function () { - that._trigger('stopped', e); - }); + var that = $(this).data('fileupload'); that._transition($(this).find('.fileupload-progress')).done( function () { - $(this).find('.progress') - .attr('aria-valuenow', '0') - .find('.bar').css('width', '0%'); + $(this).find('.bar').css('width', '0%'); $(this).find('.progress-extended').html(' '); - deferred.resolve(); + that._trigger('stopped', e); } ); }, @@ -321,8 +273,8 @@ var that = $(this).data('fileupload'); if (data.url) { $.ajax(data); - that._adjustMaxNumberOfFiles(1); } + that._adjustMaxNumberOfFiles(1); that._transition(data.context).done( function () { $(this).remove(); @@ -332,29 +284,6 @@ } }, - _resetFinishedDeferreds: function () { - this._finishedUploads = []; - }, - - _addFinishedDeferreds: function (deferred) { - if (!deferred) { - deferred = $.Deferred(); - } - this._finishedUploads.push(deferred); - return deferred; - }, - - _getFinishedDeferreds: function () { - return this._finishedUploads; - }, - - _getFilesFromResponse: function (data) { - if (data.result && $.isArray(data.result.files)) { - return data.result.files; - } - return []; - }, - // Link handler, that allows to download files // by drag & drop of the links to the desktop: _enableDragToDesktop: function () { @@ -409,7 +338,7 @@ if (bits >= 1000) { return (bits / 1000).toFixed(2) + ' kbit/s'; } - return bits.toFixed(2) + ' bit/s'; + return bits + ' bit/s'; }, _formatTime: function (seconds) { @@ -446,22 +375,22 @@ // maxNumberOfFiles before validation, so we check if // maxNumberOfFiles is below 0 (instead of below 1): if (this.options.maxNumberOfFiles < 0) { - return 'Maximum number of files exceeded'; + return 'maxNumberOfFiles'; } // Files are accepted if either the file type or the file name // matches against the acceptFileTypes regular expression, as // only browsers with support for the File API report the type: if (!(this.options.acceptFileTypes.test(file.type) || this.options.acceptFileTypes.test(file.name))) { - return 'Filetype not allowed'; + return 'acceptFileTypes'; } if (this.options.maxFileSize && file.size > this.options.maxFileSize) { - return 'File is too big'; + return 'maxFileSize'; } if (typeof file.size === 'number' && file.size < this.options.minFileSize) { - return 'File is too small'; + return 'minFileSize'; } return null; }, @@ -505,7 +434,7 @@ that._transition(node).done(function () { dfd.resolveWith(node); }); - if (!$.contains(that.document[0].body, node[0])) { + if (!$.contains(document.body, node[0])) { // If the element is not part of the DOM, // transition events are not triggered, // so we have to resolve manually: @@ -520,22 +449,18 @@ )) || dfd.resolveWith(node)) && dfd; }, - _renderPreviews: function (data) { + _renderPreviews: function (files, nodes) { var that = this, options = this.options; - data.context.find('.preview span').each(function (index, element) { - var file = data.files[index]; + nodes.find('.preview span').each(function (index, element) { + var file = files[index]; if (options.previewSourceFileTypes.test(file.type) && ($.type(options.previewSourceMaxFileSize) !== 'number' || file.size < options.previewSourceMaxFileSize)) { that._processingQueue = that._processingQueue.pipe(function () { - var dfd = $.Deferred(), - ev = $.Event('previewdone', { - target: element - }); + var dfd = $.Deferred(); that._renderPreview(file, $(element)).done( function () { - that._trigger(ev.type, ev, data); dfd.resolveWith(that); } ); @@ -562,7 +487,7 @@ _startHandler: function (e) { e.preventDefault(); - var button = $(e.currentTarget), + var button = $(this), template = button.closest('.template-upload'), data = template.data('data'); if (data && data.submit && !data.jqXHR && data.submit()) { @@ -572,11 +497,11 @@ _cancelHandler: function (e) { e.preventDefault(); - var template = $(e.currentTarget).closest('.template-upload'), + var template = $(this).closest('.template-upload'), data = template.data('data') || {}; if (!data.jqXHR) { data.errorThrown = 'abort'; - this._trigger('fail', e, data); + e.data.fileupload._trigger('fail', e, data); } else { data.jqXHR.abort(); } @@ -584,17 +509,18 @@ _deleteHandler: function (e) { e.preventDefault(); - var button = $(e.currentTarget); - this._trigger('destroy', e, $.extend({ + var button = $(this); + e.data.fileupload._trigger('destroy', e, { context: button.closest('.template-download'), - type: 'DELETE', - dataType: this.options.dataType - }, button.data())); + url: button.attr('data-url'), + type: button.attr('data-type') || 'DELETE', + dataType: e.data.fileupload.options.dataType + }); }, _forceReflow: function (node) { - return $.support.transition && node.length && - node[0].offsetWidth; + this._reflow = $.support.transition && + node.length && node[0].offsetWidth; }, _transition: function (node) { @@ -620,63 +546,75 @@ _initButtonBarEventHandlers: function () { var fileUploadButtonBar = this.element.find('.fileupload-buttonbar'), - filesList = this.options.filesContainer; - this._on(fileUploadButtonBar.find('.start'), { - click: function (e) { + filesList = this.options.filesContainer, + ns = this.options.namespace; + fileUploadButtonBar.find('.start') + .bind('click.' + ns, function (e) { e.preventDefault(); filesList.find('.start button').click(); - } - }); - this._on(fileUploadButtonBar.find('.cancel'), { - click: function (e) { + }); + fileUploadButtonBar.find('.cancel') + .bind('click.' + ns, function (e) { e.preventDefault(); filesList.find('.cancel button').click(); - } - }); - this._on(fileUploadButtonBar.find('.delete'), { - click: function (e) { + }); + fileUploadButtonBar.find('.delete') + .bind('click.' + ns, function (e) { e.preventDefault(); filesList.find('.delete input:checked') .siblings('button').click(); fileUploadButtonBar.find('.toggle') .prop('checked', false); - } - }); - this._on(fileUploadButtonBar.find('.toggle'), { - change: function (e) { + }); + fileUploadButtonBar.find('.toggle') + .bind('change.' + ns, function (e) { filesList.find('.delete input').prop( 'checked', - $(e.currentTarget).is(':checked') + $(this).is(':checked') ); - } - }); + }); }, _destroyButtonBarEventHandlers: function () { - this._off( - this.element.find('.fileupload-buttonbar button'), - 'click' - ); - this._off( - this.element.find('.fileupload-buttonbar .toggle'), - 'change.' - ); + this.element.find('.fileupload-buttonbar button') + .unbind('click.' + this.options.namespace); + this.element.find('.fileupload-buttonbar .toggle') + .unbind('change.' + this.options.namespace); }, _initEventHandlers: function () { - this._super(); - this._on(this.options.filesContainer, { - 'click .start button': this._startHandler, - 'click .cancel button': this._cancelHandler, - 'click .delete button': this._deleteHandler - }); + parentWidget.prototype._initEventHandlers.call(this); + var eventData = {fileupload: this}; + this.options.filesContainer + .delegate( + '.start button', + 'click.' + this.options.namespace, + eventData, + this._startHandler + ) + .delegate( + '.cancel button', + 'click.' + this.options.namespace, + eventData, + this._cancelHandler + ) + .delegate( + '.delete button', + 'click.' + this.options.namespace, + eventData, + this._deleteHandler + ); this._initButtonBarEventHandlers(); }, _destroyEventHandlers: function () { + var options = this.options; this._destroyButtonBarEventHandlers(); - this._off(this.options.filesContainer, 'click'); - this._super(); + options.filesContainer + .undelegate('.start button', 'click.' + options.namespace) + .undelegate('.cancel button', 'click.' + options.namespace) + .undelegate('.delete button', 'click.' + options.namespace); + parentWidget.prototype._destroyEventHandlers.call(this); }, _enableFileInputButton: function () { @@ -693,7 +631,7 @@ _initTemplates: function () { var options = this.options; - options.templatesContainer = this.document[0].createElement( + options.templatesContainer = document.createElement( options.filesContainer.prop('nodeName') ); if (tmpl) { @@ -715,75 +653,37 @@ } }, - _stringToRegExp: function (str) { - var parts = str.split('/'), - modifiers = parts.pop(); - parts.shift(); - return new RegExp(parts.join('/'), modifiers); - }, - - _initRegExpOptions: function () { - var options = this.options; - if ($.type(options.acceptFileTypes) === 'string') { - options.acceptFileTypes = this._stringToRegExp( - options.acceptFileTypes - ); - } - if ($.type(options.previewSourceFileTypes) === 'string') { - options.previewSourceFileTypes = this._stringToRegExp( - options.previewSourceFileTypes - ); - } - }, - _initSpecialOptions: function () { - this._super(); + parentWidget.prototype._initSpecialOptions.call(this); this._initFilesContainer(); this._initTemplates(); - this._initRegExpOptions(); - }, - - _setOption: function (key, value) { - this._super(key, value); - if (key === 'maxNumberOfFiles') { - this._adjustMaxNumberOfFiles(0); - } }, _create: function () { - this._super(); + parentWidget.prototype._create.call(this); this._refreshOptionsList.push( 'filesContainer', 'uploadTemplateId', 'downloadTemplateId' ); - if (!this._processingQueue) { + if (!$.blueimpFP) { this._processingQueue = $.Deferred().resolveWith(this).promise(); this.process = function () { return this._processingQueue; }; } - this._resetFinishedDeferreds(); }, enable: function () { - var wasDisabled = false; - if (this.options.disabled) { - wasDisabled = true; - } - this._super(); - if (wasDisabled) { - this.element.find('input, button').prop('disabled', false); - this._enableFileInputButton(); - } + parentWidget.prototype.enable.call(this); + this.element.find('input, button').prop('disabled', false); + this._enableFileInputButton(); }, disable: function () { - if (!this.options.disabled) { - this.element.find('input, button').prop('disabled', true); - this._disableFileInputButton(); - } - this._super(); + this.element.find('input, button').prop('disabled', true); + this._disableFileInputButton(); + parentWidget.prototype.disable.call(this); } }); diff --git a/fileupload/static/js/jquery.fileupload.js b/fileupload/static/js/jquery.fileupload.js index 96efeaf..05a654b 100644 --- a/fileupload/static/js/jquery.fileupload.js +++ b/fileupload/static/js/jquery.fileupload.js @@ -1,5 +1,5 @@ /* - * jQuery File Upload Plugin 5.19.8 + * jQuery File Upload Plugin 5.11.2 * https://github.com/blueimp/jQuery-File-Upload * * Copyright 2010, Sebastian Tschan @@ -10,7 +10,7 @@ */ /*jslint nomen: true, unparam: true, regexp: true */ -/*global define, window, document, File, Blob, FormData, location */ +/*global define, window, document, Blob, FormData, location */ (function (factory) { 'use strict'; @@ -44,16 +44,17 @@ $.widget('blueimp.fileupload', { options: { - // The drop target element(s), by the default the complete document. - // Set to null to disable drag & drop support: + // The namespace used for event handler binding on the dropZone and + // fileInput collections. + // If not set, the name of the widget ("fileupload") is used. + namespace: undefined, + // The drop target collection, by the default the complete document. + // Set to null or an empty collection to disable drag & drop support: dropZone: $(document), - // The paste target element(s), by the default the complete document. - // Set to null to disable paste support: - pasteZone: $(document), - // The file input field(s), that are listened to for change events. + // The file input field collection, that is listened for change events. // If undefined, it is set to the file input fields inside // of the widget element on plugin initialization. - // Set to null to disable the change listener. + // Set to null or an empty collection to disable the change listener. fileInput: undefined, // By default, the file input field is replaced with a clone after // each input field change event. This is required for iframe transport @@ -158,13 +159,13 @@ // start: function (e) {}, // .bind('fileuploadstart', func); // Callback for uploads stop, equivalent to the global ajaxStop event: // stop: function (e) {}, // .bind('fileuploadstop', func); - // Callback for change events of the fileInput(s): + // Callback for change events of the fileInput collection: // change: function (e, data) {}, // .bind('fileuploadchange', func); - // Callback for paste events to the pasteZone(s): + // Callback for paste events to the dropZone collection: // paste: function (e, data) {}, // .bind('fileuploadpaste', func); - // Callback for drop events of the dropZone(s): + // Callback for drop events of the dropZone collection: // drop: function (e, data) {}, // .bind('fileuploaddrop', func); - // Callback for dragover events of the dropZone(s): + // Callback for dragover events of the dropZone collection: // dragover: function (e) {}, // .bind('fileuploaddragover', func); // The plugin options are used as settings object for the ajax calls. @@ -176,9 +177,9 @@ // A list of options that require a refresh after assigning a new value: _refreshOptionsList: [ - 'fileInput', + 'namespace', 'dropZone', - 'pasteZone', + 'fileInput', 'multipart', 'forceIframeTransport' ], @@ -209,10 +210,10 @@ if (typeof options.formData === 'function') { return options.formData(options.form); } - if ($.isArray(options.formData)) { + if ($.isArray(options.formData)) { return options.formData; } - if (options.formData) { + if (options.formData) { formData = []; $.each(options.formData, function (name, value) { formData.push({name: name, value: value}); @@ -300,16 +301,29 @@ // Ignore non-multipart setting if not supported: multipart = options.multipart || !$.support.xhrFileUpload, paramName = options.paramName[0]; - options.headers = options.headers || {}; - if (options.contentRange) { - options.headers['Content-Range'] = options.contentRange; + if (!multipart || options.blob) { + // For non-multipart uploads and chunked uploads, + // file meta data is not part of the request body, + // so we transmit this data as part of the HTTP headers. + // For cross domain requests, these headers must be allowed + // via Access-Control-Allow-Headers or removed using + // the beforeSend callback: + options.headers = $.extend(options.headers, { + 'X-File-Name': file.name, + 'X-File-Type': file.type, + 'X-File-Size': file.size + }); + if (!options.blob) { + // Non-chunked non-multipart upload: + options.contentType = file.type; + options.data = file; + } else if (!multipart) { + // Chunked non-multipart upload: + options.contentType = 'application/octet-stream'; + options.data = options.blob; + } } - if (!multipart) { - options.headers['Content-Disposition'] = 'attachment; filename="' + - encodeURI(file.name) + '"'; - options.contentType = file.type; - options.data = options.blob || file; - } else if ($.support.xhrFormDataFileUpload) { + if (multipart && $.support.xhrFormDataFileUpload) { if (options.postMessage) { // window.postMessage does not allow sending FormData // objects, so we just add the File/Blob objects to @@ -339,17 +353,13 @@ }); } if (options.blob) { - options.headers['Content-Disposition'] = 'attachment; filename="' + - encodeURI(file.name) + '"'; formData.append(paramName, options.blob, file.name); } else { $.each(options.files, function (index, file) { - // Files are also Blob instances, but some browsers - // (Firefox 3.6) support the File API but not Blobs. + // File objects are also Blob instances. // This check allows the tests to run with // dummy objects: - if ((window.Blob && file instanceof Blob) || - (window.File && file instanceof File)) { + if (file instanceof Blob) { formData.append( options.paramName[index] || paramName, file, @@ -426,11 +436,6 @@ // associated form, if available: if (!options.form || !options.form.length) { options.form = $(options.fileInput.prop('form')); - // If the given file input doesn't have an associated form, - // use the default widget file input's form: - if (!options.form.length) { - options.form = $(this.options.fileInput.prop('form')); - } } options.paramName = this._getParamName(options); if (!options.url) { @@ -439,13 +444,9 @@ // The HTTP request method must be "POST" or "PUT": options.type = (options.type || options.form.prop('method') || '') .toUpperCase(); - if (options.type !== 'POST' && options.type !== 'PUT' && - options.type !== 'PATCH') { + if (options.type !== 'POST' && options.type !== 'PUT') { options.type = 'POST'; } - if (!options.formAcceptCharset) { - options.formAcceptCharset = options.form.attr('accept-charset'); - } }, _getAJAXSettings: function (data) { @@ -479,16 +480,6 @@ return this._enhancePromise(promise); }, - // Parses the Range header from the server response - // and returns the uploaded bytes: - _getUploadedBytes: function (jqXHR) { - var range = jqXHR.getResponseHeader('Range'), - parts = range && range.split('-'), - upperBytesPos = parts && parts.length > 1 && - parseInt(parts[1], 10); - return upperBytesPos && upperBytesPos + 1; - }, - // Uploads a file in multiple, sequential requests // by splitting the file up in multiple blob chunks. // If the second parameter is true, only tests if the file @@ -500,11 +491,13 @@ fs = file.size, ub = options.uploadedBytes = options.uploadedBytes || 0, mcs = options.maxChunkSize || fs, - slice = file.slice || file.webkitSlice || file.mozSlice, - dfd = $.Deferred(), - promise = dfd.promise(), + // Use the Blob methods with the slice implementation + // according to the W3C Blob API specification: + slice = file.webkitSlice || file.mozSlice || file.slice, + upload, + n, jqXHR, - upload; + pipe; if (!(this._isXHRUpload(options) && slice && (ub || mcs < fs)) || options.data) { return false; @@ -513,71 +506,62 @@ return true; } if (ub >= fs) { - file.error = 'Uploaded bytes exceed file size'; + file.error = 'uploadedBytes'; return this._getXHRPromise( false, options.context, [null, 'error', file.error] ); } - // The chunk upload method: + // n is the number of blobs to upload, + // calculated via filesize, uploaded bytes and max chunk size: + n = Math.ceil((fs - ub) / mcs); + // The chunk upload method accepting the chunk number as parameter: upload = function (i) { - // Clone the options object for each chunk upload: - var o = $.extend({}, options); - o.blob = slice.call( - file, - ub, - ub + mcs, - file.type - ); - // Store the current chunk size, as the blob itself - // will be dereferenced after data processing: - o.chunkSize = o.blob.size; - // Expose the chunk bytes position range: - o.contentRange = 'bytes ' + ub + '-' + - (ub + o.chunkSize - 1) + '/' + fs; - // Process the upload data (the blob and potential form data): - that._initXHRData(o); - // Add progress listeners for this chunk upload: - that._initProgressListener(o); - jqXHR = ($.ajax(o) || that._getXHRPromise(false, o.context)) - .done(function (result, textStatus, jqXHR) { - ub = that._getUploadedBytes(jqXHR) || - (ub + o.chunkSize); - // Create a progress event if upload is done and - // no progress event has been invoked for this chunk: - if (!o.loaded) { - that._onProgress($.Event('progress', { - lengthComputable: true, - loaded: ub - o.uploadedBytes, - total: ub - o.uploadedBytes - }), o); - } - options.uploadedBytes = o.uploadedBytes = ub; - if (ub < fs) { - // File upload not yet complete, - // continue with the next chunk: - upload(); - } else { - dfd.resolveWith( - o.context, - [result, textStatus, jqXHR] - ); - } - }) - .fail(function (jqXHR, textStatus, errorThrown) { - dfd.rejectWith( - o.context, - [jqXHR, textStatus, errorThrown] - ); - }); + if (!i) { + return that._getXHRPromise(true, options.context); + } + // Upload the blobs in sequential order: + return upload(i -= 1).pipe(function () { + // Clone the options object for each chunk upload: + var o = $.extend({}, options); + o.blob = slice.call( + file, + ub + i * mcs, + ub + (i + 1) * mcs + ); + // Store the current chunk size, as the blob itself + // will be dereferenced after data processing: + o.chunkSize = o.blob.size; + // Process the upload data (the blob and potential form data): + that._initXHRData(o); + // Add progress listeners for this chunk upload: + that._initProgressListener(o); + jqXHR = ($.ajax(o) || that._getXHRPromise(false, o.context)) + .done(function () { + // Create a progress event if upload is done and + // no progress event has been invoked for this chunk: + if (!o.loaded) { + that._onProgress($.Event('progress', { + lengthComputable: true, + loaded: o.chunkSize, + total: o.chunkSize + }), o); + } + options.uploadedBytes = o.uploadedBytes += + o.chunkSize; + }); + return jqXHR; + }); }; - this._enhancePromise(promise); - promise.abort = function () { + // Return the piped Promise object, enhanced with an abort method, + // which is delegated to the jqXHR object of the current upload, + // and jqXHR callbacks mapped to the equivalent Promise methods: + pipe = upload(n); + pipe.abort = function () { return jqXHR.abort(); }; - upload(); - return promise; + return this._enhancePromise(pipe); }, _beforeSend: function (e, data) { @@ -647,18 +631,18 @@ _onSend: function (e, data) { var that = this, jqXHR, - aborted, slot, pipe, options = that._getAJAXSettings(data), - send = function () { + send = function (resolve, args) { that._sending += 1; // Set timer for bitrate progress calculation: options._bitrateTimer = new that._BitrateTimer(); jqXHR = jqXHR || ( - ((aborted || that._trigger('send', e, options) === false) && - that._getXHRPromise(false, options.context, aborted)) || - that._chunkedUpload(options) || $.ajax(options) + (resolve !== false && + that._trigger('send', e, options) !== false && + (that._chunkedUpload(options) || $.ajax(options))) || + that._getXHRPromise(false, options.context, args) ).done(function (result, textStatus, jqXHR) { that._onDone(result, textStatus, jqXHR, options); }).fail(function (jqXHR, textStatus, errorThrown) { @@ -675,15 +659,9 @@ options.limitConcurrentUploads > that._sending) { // Start the next queued upload, // that has not been aborted: - var nextSlot = that._slots.shift(), - isPending; + var nextSlot = that._slots.shift(); while (nextSlot) { - // jQuery 1.6 doesn't provide .state(), - // while jQuery 1.8+ removed .isRejected(): - isPending = nextSlot.state ? - nextSlot.state() === 'pending' : - !nextSlot.isRejected(); - if (isPending) { + if (!nextSlot.isRejected()) { nextSlot.resolve(); break; } @@ -708,12 +686,12 @@ // which is delegated to the jqXHR object of the current upload, // and jqXHR callbacks mapped to the equivalent Promise methods: pipe.abort = function () { - aborted = [undefined, 'abort', 'abort']; + var args = [undefined, 'abort', 'abort']; if (!jqXHR) { if (slot) { - slot.rejectWith(options.context, aborted); + slot.rejectWith(args); } - return send(); + return send(false, args); } return jqXHR.abort(); }; @@ -761,12 +739,19 @@ that._onSend(e, this); return this.jqXHR; }; - result = that._trigger('add', e, newData); - return result; + return (result = that._trigger('add', e, newData)); }); return result; }, + // File Normalization for Gecko 1.9.1 (Firefox 3.5) support: + _normalizeFile: function (index, file) { + if (file.name === undefined && file.size === undefined) { + file.name = file.fileName; + file.size = file.fileSize; + } + }, + _replaceFileInput: function (input) { var inputClone = input.clone(true); $('
').append(inputClone)[0].reset(); @@ -776,7 +761,7 @@ // Avoid memory leaks with the detached file input: $.cleanData(input.unbind('remove')); // Replace the original file input element in the fileInput - // elements set with the clone, which has been copied including + // collection with the clone, which has been copied including // event handlers: this.options.fileInput = this.options.fileInput.map(function (i, el) { if (el === input[0]) { @@ -791,153 +776,31 @@ } }, - _handleFileTreeEntry: function (entry, path) { - var that = this, - dfd = $.Deferred(), - errorHandler = function (e) { - if (e && !e.entry) { - e.entry = entry; - } - // Since $.when returns immediately if one - // Deferred is rejected, we use resolve instead. - // This allows valid files and invalid items - // to be returned together in one set: - dfd.resolve([e]); - }, - dirReader; - path = path || ''; - if (entry.isFile) { - if (entry._file) { - // Workaround for Chrome bug #149735 - entry._file.relativePath = path; - dfd.resolve(entry._file); - } else { - entry.file(function (file) { - file.relativePath = path; - dfd.resolve(file); - }, errorHandler); - } - } else if (entry.isDirectory) { - dirReader = entry.createReader(); - dirReader.readEntries(function (entries) { - that._handleFileTreeEntries( - entries, - path + entry.name + '/' - ).done(function (files) { - dfd.resolve(files); - }).fail(errorHandler); - }, errorHandler); - } else { - // Return an empy list for file system items - // other than files or directories: - dfd.resolve([]); - } - return dfd.promise(); - }, - - _handleFileTreeEntries: function (entries, path) { - var that = this; - return $.when.apply( - $, - $.map(entries, function (entry) { - return that._handleFileTreeEntry(entry, path); - }) - ).pipe(function () { - return Array.prototype.concat.apply( - [], - arguments - ); - }); - }, - - _getDroppedFiles: function (dataTransfer) { - dataTransfer = dataTransfer || {}; - var items = dataTransfer.items; - if (items && items.length && (items[0].webkitGetAsEntry || - items[0].getAsEntry)) { - return this._handleFileTreeEntries( - $.map(items, function (item) { - var entry; - if (item.webkitGetAsEntry) { - entry = item.webkitGetAsEntry(); - if (entry) { - // Workaround for Chrome bug #149735: - entry._file = item.getAsFile(); - } - return entry; - } - return item.getAsEntry(); - }) - ); - } - return $.Deferred().resolve( - $.makeArray(dataTransfer.files) - ).promise(); - }, - - _getSingleFileInputFiles: function (fileInput) { - fileInput = $(fileInput); - var entries = fileInput.prop('webkitEntries') || - fileInput.prop('entries'), - files, - value; - if (entries && entries.length) { - return this._handleFileTreeEntries(entries); - } - files = $.makeArray(fileInput.prop('files')); - if (!files.length) { - value = fileInput.prop('value'); - if (!value) { - return $.Deferred().resolve([]).promise(); - } - // If the files property is not available, the browser does not - // support the File API and we add a pseudo File object with - // the input value as name with path information removed: - files = [{name: value.replace(/^.*\\/, '')}]; - } else if (files[0].name === undefined && files[0].fileName) { - // File normalization for Safari 4 and Firefox 3: - $.each(files, function (index, file) { - file.name = file.fileName; - file.size = file.fileSize; - }); - } - return $.Deferred().resolve(files).promise(); - }, - - _getFileInputFiles: function (fileInput) { - if (!(fileInput instanceof $) || fileInput.length === 1) { - return this._getSingleFileInputFiles(fileInput); - } - return $.when.apply( - $, - $.map(fileInput, this._getSingleFileInputFiles) - ).pipe(function () { - return Array.prototype.concat.apply( - [], - arguments - ); - }); - }, - _onChange: function (e) { - var that = this, + var that = e.data.fileupload, data = { + files: $.each($.makeArray(e.target.files), that._normalizeFile), fileInput: $(e.target), form: $(e.target.form) }; - this._getFileInputFiles(data.fileInput).always(function (files) { - data.files = files; - if (that.options.replaceFileInput) { - that._replaceFileInput(data.fileInput); - } - if (that._trigger('change', e, data) !== false) { - that._onAdd(e, data); - } - }); + if (!data.files.length) { + // If the files property is not available, the browser does not + // support the File API and we add a pseudo File object with + // the input value as name with path information removed: + data.files = [{name: e.target.value.replace(/^.*\\/, '')}]; + } + if (that.options.replaceFileInput) { + that._replaceFileInput(data.fileInput); + } + if (that._trigger('change', e, data) === false || + that._onAdd(e, data) === false) { + return false; + } }, _onPaste: function (e) { - var cbd = e.originalEvent.clipboardData, + var that = e.data.fileupload, + cbd = e.originalEvent.clipboardData, items = (cbd && cbd.items) || [], data = {files: []}; $.each(items, function (index, item) { @@ -946,57 +809,60 @@ data.files.push(file); } }); - if (this._trigger('paste', e, data) === false || - this._onAdd(e, data) === false) { + if (that._trigger('paste', e, data) === false || + that._onAdd(e, data) === false) { return false; } }, _onDrop: function (e) { - var that = this, + var that = e.data.fileupload, dataTransfer = e.dataTransfer = e.originalEvent.dataTransfer, - data = {}; - if (dataTransfer && dataTransfer.files && dataTransfer.files.length) { - e.preventDefault(); + data = { + files: $.each( + $.makeArray(dataTransfer && dataTransfer.files), + that._normalizeFile + ) + }; + if (that._trigger('drop', e, data) === false || + that._onAdd(e, data) === false) { + return false; } - this._getDroppedFiles(dataTransfer).always(function (files) { - data.files = files; - if (that._trigger('drop', e, data) !== false) { - that._onAdd(e, data); - } - }); + e.preventDefault(); }, _onDragOver: function (e) { - var dataTransfer = e.dataTransfer = e.originalEvent.dataTransfer; - if (this._trigger('dragover', e) === false) { + var that = e.data.fileupload, + dataTransfer = e.dataTransfer = e.originalEvent.dataTransfer; + if (that._trigger('dragover', e) === false) { return false; } - if (dataTransfer && $.inArray('Files', dataTransfer.types) !== -1) { - dataTransfer.dropEffect = 'copy'; - e.preventDefault(); + if (dataTransfer) { + dataTransfer.dropEffect = dataTransfer.effectAllowed = 'copy'; } + e.preventDefault(); }, _initEventHandlers: function () { + var ns = this.options.namespace; if (this._isXHRUpload(this.options)) { - this._on(this.options.dropZone, { - dragover: this._onDragOver, - drop: this._onDrop - }); - this._on(this.options.pasteZone, { - paste: this._onPaste - }); + this.options.dropZone + .bind('dragover.' + ns, {fileupload: this}, this._onDragOver) + .bind('drop.' + ns, {fileupload: this}, this._onDrop) + .bind('paste.' + ns, {fileupload: this}, this._onPaste); } - this._on(this.options.fileInput, { - change: this._onChange - }); + this.options.fileInput + .bind('change.' + ns, {fileupload: this}, this._onChange); }, _destroyEventHandlers: function () { - this._off(this.options.dropZone, 'dragover drop'); - this._off(this.options.pasteZone, 'paste'); - this._off(this.options.fileInput, 'change'); + var ns = this.options.namespace; + this.options.dropZone + .unbind('dragover.' + ns, this._onDragOver) + .unbind('drop.' + ns, this._onDrop) + .unbind('paste.' + ns, this._onPaste); + this.options.fileInput + .unbind('change.' + ns, this._onChange); }, _setOption: function (key, value) { @@ -1004,7 +870,7 @@ if (refresh) { this._destroyEventHandlers(); } - this._super(key, value); + $.Widget.prototype._setOption.call(this, key, value); if (refresh) { this._initSpecialOptions(); this._initEventHandlers(); @@ -1014,23 +880,21 @@ _initSpecialOptions: function () { var options = this.options; if (options.fileInput === undefined) { - options.fileInput = this.element.is('input[type="file"]') ? - this.element : this.element.find('input[type="file"]'); + options.fileInput = this.element.is('input:file') ? + this.element : this.element.find('input:file'); } else if (!(options.fileInput instanceof $)) { options.fileInput = $(options.fileInput); } if (!(options.dropZone instanceof $)) { options.dropZone = $(options.dropZone); } - if (!(options.pasteZone instanceof $)) { - options.pasteZone = $(options.pasteZone); - } }, _create: function () { var options = this.options; // Initialize options set via HTML5 data-attributes: $.extend(options, $(this.element[0].cloneNode(false)).data()); + options.namespace = options.namespace || this.widgetName; this._initSpecialOptions(); this._slots = []; this._sequence = this._getXHRPromise(true); @@ -1038,8 +902,19 @@ this._initEventHandlers(); }, - _destroy: function () { + destroy: function () { this._destroyEventHandlers(); + $.Widget.prototype.destroy.call(this); + }, + + enable: function () { + $.Widget.prototype.enable.call(this); + this._initEventHandlers(); + }, + + disable: function () { + this._destroyEventHandlers(); + $.Widget.prototype.disable.call(this); }, // This method is exposed to the widget API and allows adding files @@ -1047,61 +922,21 @@ // must have a files property and can contain additional options: // .fileupload('add', {files: filesList}); add: function (data) { - var that = this; if (!data || this.options.disabled) { return; } - if (data.fileInput && !data.files) { - this._getFileInputFiles(data.fileInput).always(function (files) { - data.files = files; - that._onAdd(null, data); - }); - } else { - data.files = $.makeArray(data.files); - this._onAdd(null, data); - } + data.files = $.each($.makeArray(data.files), this._normalizeFile); + this._onAdd(null, data); }, // This method is exposed to the widget API and allows sending files // using the fileupload API. The data parameter accepts an object which - // must have a files or fileInput property and can contain additional options: + // must have a files property and can contain additional options: // .fileupload('send', {files: filesList}); // The method returns a Promise object for the file upload call. send: function (data) { if (data && !this.options.disabled) { - if (data.fileInput && !data.files) { - var that = this, - dfd = $.Deferred(), - promise = dfd.promise(), - jqXHR, - aborted; - promise.abort = function () { - aborted = true; - if (jqXHR) { - return jqXHR.abort(); - } - dfd.reject(null, 'abort', 'abort'); - return promise; - }; - this._getFileInputFiles(data.fileInput).always( - function (files) { - if (aborted) { - return; - } - data.files = files; - jqXHR = that._onSend(null, data).then( - function (result, textStatus, jqXHR) { - dfd.resolve(result, textStatus, jqXHR); - }, - function (jqXHR, textStatus, errorThrown) { - dfd.reject(jqXHR, textStatus, errorThrown); - } - ); - } - ); - return this._enhancePromise(promise); - } - data.files = $.makeArray(data.files); + data.files = $.each($.makeArray(data.files), this._normalizeFile); if (data.files.length) { return this._onSend(null, data); } diff --git a/fileupload/static/js/jquery.iframe-transport.js b/fileupload/static/js/jquery.iframe-transport.js index ed25895..04a5662 100644 --- a/fileupload/static/js/jquery.iframe-transport.js +++ b/fileupload/static/js/jquery.iframe-transport.js @@ -1,5 +1,5 @@ /* - * jQuery Iframe Transport Plugin 1.6.1 + * jQuery Iframe Transport Plugin 1.4 * https://github.com/blueimp/jQuery-File-Upload * * Copyright 2011, Sebastian Tschan @@ -36,26 +36,12 @@ // equivalent to the return data of .serializeArray(), e.g.: // [{name: 'a', value: 1}, {name: 'b', value: 2}] $.ajaxTransport('iframe', function (options) { - if (options.async) { + if (options.async && (options.type === 'POST' || options.type === 'GET')) { var form, - iframe, - addParamChar; + iframe; return { send: function (_, completeCallback) { form = $('
'); - form.attr('accept-charset', options.formAcceptCharset); - addParamChar = /\?/.test(options.url) ? '&' : '?'; - // XDomainRequest only supports GET and POST: - if (options.type === 'DELETE') { - options.url = options.url + addParamChar + '_method=DELETE'; - options.type = 'POST'; - } else if (options.type === 'PUT') { - options.url = options.url + addParamChar + '_method=PUT'; - options.type = 'POST'; - } else if (options.type === 'PATCH') { - options.url = options.url + addParamChar + '_method=PATCH'; - options.type = 'POST'; - } // javascript:false as initial iframe src // prevents warning popups on HTTPS in IE6. // IE versions below IE8 cannot set the name property of @@ -168,16 +154,16 @@ $.ajaxSetup({ converters: { 'iframe text': function (iframe) { - return iframe && $(iframe[0].body).text(); + return $(iframe[0].body).text(); }, 'iframe json': function (iframe) { - return iframe && $.parseJSON($(iframe[0].body).text()); + return $.parseJSON($(iframe[0].body).text()); }, 'iframe html': function (iframe) { - return iframe && $(iframe[0].body).html(); + return $(iframe[0].body).html(); }, 'iframe script': function (iframe) { - return iframe && $.globalEval($(iframe[0].body).text()); + return $.globalEval($(iframe[0].body).text()); } } }); diff --git a/fileupload/static/js/jquery.ui.widget.js b/fileupload/static/js/jquery.ui.widget.js index 886aff6..9da8673 100644 --- a/fileupload/static/js/jquery.ui.widget.js +++ b/fileupload/static/js/jquery.ui.widget.js @@ -1,12 +1,12 @@ /* - * jQuery UI Widget 1.9.1+amd + * jQuery UI Widget 1.8.18+amd * https://github.com/blueimp/jQuery-File-Upload * - * Copyright 2012 jQuery Foundation and other contributors - * Released under the MIT license. + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * - * http://api.jqueryui.com/jQuery.widget/ + * http://docs.jquery.com/UI/Widget */ (function (factory) { @@ -19,23 +19,40 @@ } }(function( $, undefined ) { -var uuid = 0, - slice = Array.prototype.slice, - _cleanData = $.cleanData; -$.cleanData = function( elems ) { - for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) { - try { - $( elem ).triggerHandler( "remove" ); - // http://bugs.jquery.com/ticket/8235 - } catch( e ) {} - } - _cleanData( elems ); -}; +// jQuery 1.4+ +if ( $.cleanData ) { + var _cleanData = $.cleanData; + $.cleanData = function( elems ) { + for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) { + try { + $( elem ).triggerHandler( "remove" ); + // http://bugs.jquery.com/ticket/8235 + } catch( e ) {} + } + _cleanData( elems ); + }; +} else { + var _remove = $.fn.remove; + $.fn.remove = function( selector, keepData ) { + return this.each(function() { + if ( !keepData ) { + if ( !selector || $.filter( selector, [ this ] ).length ) { + $( "*", this ).add( [ this ] ).each(function() { + try { + $( this ).triggerHandler( "remove" ); + // http://bugs.jquery.com/ticket/8235 + } catch( e ) {} + }); + } + } + return _remove.call( $(this), selector, keepData ); + }); + }; +} $.widget = function( name, base, prototype ) { - var fullName, existingConstructor, constructor, basePrototype, - namespace = name.split( "." )[ 0 ]; - + var namespace = name.split( "." )[ 0 ], + fullName; name = name.split( "." )[ 1 ]; fullName = namespace + "-" + name; @@ -45,167 +62,81 @@ $.widget = function( name, base, prototype ) { } // create selector for plugin - $.expr[ ":" ][ fullName.toLowerCase() ] = function( elem ) { - return !!$.data( elem, fullName ); + $.expr[ ":" ][ fullName ] = function( elem ) { + return !!$.data( elem, name ); }; $[ namespace ] = $[ namespace ] || {}; - existingConstructor = $[ namespace ][ name ]; - constructor = $[ namespace ][ name ] = function( options, element ) { - // allow instantiation without "new" keyword - if ( !this._createWidget ) { - return new constructor( options, element ); - } - + $[ namespace ][ name ] = function( options, element ) { // allow instantiation without initializing for simple inheritance - // must use "new" keyword (the code above always passes args) if ( arguments.length ) { this._createWidget( options, element ); } }; - // extend with the existing constructor to carry over any static properties - $.extend( constructor, existingConstructor, { - version: prototype.version, - // copy the object used to create the prototype in case we need to - // redefine the widget later - _proto: $.extend( {}, prototype ), - // track widgets that inherit from this widget in case this widget is - // redefined after a widget inherits from it - _childConstructors: [] - }); - - basePrototype = new base(); + + var basePrototype = new base(); // we need to make the options hash a property directly on the new instance // otherwise we'll modify the options hash on the prototype that we're // inheriting from - basePrototype.options = $.widget.extend( {}, basePrototype.options ); - $.each( prototype, function( prop, value ) { - if ( $.isFunction( value ) ) { - prototype[ prop ] = (function() { - var _super = function() { - return base.prototype[ prop ].apply( this, arguments ); - }, - _superApply = function( args ) { - return base.prototype[ prop ].apply( this, args ); - }; - return function() { - var __super = this._super, - __superApply = this._superApply, - returnValue; - - this._super = _super; - this._superApply = _superApply; - - returnValue = value.apply( this, arguments ); - - this._super = __super; - this._superApply = __superApply; - - return returnValue; - }; - })(); - } - }); - constructor.prototype = $.widget.extend( basePrototype, { - // TODO: remove support for widgetEventPrefix - // always use the name + a colon as the prefix, e.g., draggable:start - // don't prefix for widgets that aren't DOM-based - widgetEventPrefix: basePrototype.widgetEventPrefix || name - }, prototype, { - constructor: constructor, +// $.each( basePrototype, function( key, val ) { +// if ( $.isPlainObject(val) ) { +// basePrototype[ key ] = $.extend( {}, val ); +// } +// }); + basePrototype.options = $.extend( true, {}, basePrototype.options ); + $[ namespace ][ name ].prototype = $.extend( true, basePrototype, { namespace: namespace, widgetName: name, - // TODO remove widgetBaseClass, see #8155 - widgetBaseClass: fullName, - widgetFullName: fullName - }); - - // If this widget is being redefined then we need to find all widgets that - // are inheriting from it and redefine all of them so that they inherit from - // the new version of this widget. We're essentially trying to replace one - // level in the prototype chain. - if ( existingConstructor ) { - $.each( existingConstructor._childConstructors, function( i, child ) { - var childPrototype = child.prototype; - - // redefine the child widget using the same prototype that was - // originally used, but inherit from the new version of the base - $.widget( childPrototype.namespace + "." + childPrototype.widgetName, constructor, child._proto ); - }); - // remove the list of existing child constructors from the old constructor - // so the old child constructors can be garbage collected - delete existingConstructor._childConstructors; - } else { - base._childConstructors.push( constructor ); - } + widgetEventPrefix: $[ namespace ][ name ].prototype.widgetEventPrefix || name, + widgetBaseClass: fullName + }, prototype ); - $.widget.bridge( name, constructor ); -}; - -$.widget.extend = function( target ) { - var input = slice.call( arguments, 1 ), - inputIndex = 0, - inputLength = input.length, - key, - value; - for ( ; inputIndex < inputLength; inputIndex++ ) { - for ( key in input[ inputIndex ] ) { - value = input[ inputIndex ][ key ]; - if ( input[ inputIndex ].hasOwnProperty( key ) && value !== undefined ) { - // Clone objects - if ( $.isPlainObject( value ) ) { - target[ key ] = $.isPlainObject( target[ key ] ) ? - $.widget.extend( {}, target[ key ], value ) : - // Don't extend strings, arrays, etc. with objects - $.widget.extend( {}, value ); - // Copy everything else by reference - } else { - target[ key ] = value; - } - } - } - } - return target; + $.widget.bridge( name, $[ namespace ][ name ] ); }; $.widget.bridge = function( name, object ) { - var fullName = object.prototype.widgetFullName; $.fn[ name ] = function( options ) { var isMethodCall = typeof options === "string", - args = slice.call( arguments, 1 ), + args = Array.prototype.slice.call( arguments, 1 ), returnValue = this; // allow multiple hashes to be passed on init options = !isMethodCall && args.length ? - $.widget.extend.apply( null, [ options ].concat(args) ) : + $.extend.apply( null, [ true, options ].concat(args) ) : options; + // prevent calls to internal methods + if ( isMethodCall && options.charAt( 0 ) === "_" ) { + return returnValue; + } + if ( isMethodCall ) { this.each(function() { - var methodValue, - instance = $.data( this, fullName ); - if ( !instance ) { - return $.error( "cannot call methods on " + name + " prior to initialization; " + - "attempted to call method '" + options + "'" ); - } - if ( !$.isFunction( instance[options] ) || options.charAt( 0 ) === "_" ) { - return $.error( "no such method '" + options + "' for " + name + " widget instance" ); - } - methodValue = instance[ options ].apply( instance, args ); + var instance = $.data( this, name ), + methodValue = instance && $.isFunction( instance[options] ) ? + instance[ options ].apply( instance, args ) : + instance; + // TODO: add this back in 1.9 and use $.error() (see #5972) +// if ( !instance ) { +// throw "cannot call methods on " + name + " prior to initialization; " + +// "attempted to call method '" + options + "'"; +// } +// if ( !$.isFunction( instance[options] ) ) { +// throw "no such method '" + options + "' for " + name + " widget instance"; +// } +// var methodValue = instance[ options ].apply( instance, args ); if ( methodValue !== instance && methodValue !== undefined ) { - returnValue = methodValue && methodValue.jquery ? - returnValue.pushStack( methodValue.get() ) : - methodValue; + returnValue = methodValue; return false; } }); } else { this.each(function() { - var instance = $.data( this, fullName ); + var instance = $.data( this, name ); if ( instance ) { instance.option( options || {} )._init(); } else { - new object( options, this ); + $.data( this, name, new object( options, this ) ); } }); } @@ -214,126 +145,74 @@ $.widget.bridge = function( name, object ) { }; }; -$.Widget = function( /* options, element */ ) {}; -$.Widget._childConstructors = []; +$.Widget = function( options, element ) { + // allow instantiation without initializing for simple inheritance + if ( arguments.length ) { + this._createWidget( options, element ); + } +}; $.Widget.prototype = { widgetName: "widget", widgetEventPrefix: "", - defaultElement: "
", options: { - disabled: false, - - // callbacks - create: null + disabled: false }, _createWidget: function( options, element ) { - element = $( element || this.defaultElement || this )[ 0 ]; + // $.widget.bridge stores the plugin instance, but we do it anyway + // so that it's stored even before the _create function runs + $.data( element, this.widgetName, this ); this.element = $( element ); - this.uuid = uuid++; - this.eventNamespace = "." + this.widgetName + this.uuid; - this.options = $.widget.extend( {}, + this.options = $.extend( true, {}, this.options, this._getCreateOptions(), options ); - this.bindings = $(); - this.hoverable = $(); - this.focusable = $(); - - if ( element !== this ) { - // 1.9 BC for #7810 - // TODO remove dual storage - $.data( element, this.widgetName, this ); - $.data( element, this.widgetFullName, this ); - this._on( this.element, { - remove: function( event ) { - if ( event.target === element ) { - this.destroy(); - } - } - }); - this.document = $( element.style ? - // element within the document - element.ownerDocument : - // element is window or document - element.document || element ); - this.window = $( this.document[0].defaultView || this.document[0].parentWindow ); - } + var self = this; + this.element.bind( "remove." + this.widgetName, function() { + self.destroy(); + }); this._create(); - this._trigger( "create", null, this._getCreateEventData() ); + this._trigger( "create" ); this._init(); }, - _getCreateOptions: $.noop, - _getCreateEventData: $.noop, - _create: $.noop, - _init: $.noop, + _getCreateOptions: function() { + return $.metadata && $.metadata.get( this.element[0] )[ this.widgetName ]; + }, + _create: function() {}, + _init: function() {}, destroy: function() { - this._destroy(); - // we can probably remove the unbind calls in 2.0 - // all event bindings should go through this._on() this.element - .unbind( this.eventNamespace ) - // 1.9 BC for #7810 - // TODO remove dual storage - .removeData( this.widgetName ) - .removeData( this.widgetFullName ) - // support: jquery <1.6.3 - // http://bugs.jquery.com/ticket/9413 - .removeData( $.camelCase( this.widgetFullName ) ); + .unbind( "." + this.widgetName ) + .removeData( this.widgetName ); this.widget() - .unbind( this.eventNamespace ) + .unbind( "." + this.widgetName ) .removeAttr( "aria-disabled" ) .removeClass( - this.widgetFullName + "-disabled " + + this.widgetBaseClass + "-disabled " + "ui-state-disabled" ); - - // clean up events and states - this.bindings.unbind( this.eventNamespace ); - this.hoverable.removeClass( "ui-state-hover" ); - this.focusable.removeClass( "ui-state-focus" ); }, - _destroy: $.noop, widget: function() { return this.element; }, option: function( key, value ) { - var options = key, - parts, - curOption, - i; + var options = key; if ( arguments.length === 0 ) { // don't return a reference to the internal hash - return $.widget.extend( {}, this.options ); + return $.extend( {}, this.options ); } - if ( typeof key === "string" ) { - // handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } } - options = {}; - parts = key.split( "." ); - key = parts.shift(); - if ( parts.length ) { - curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] ); - for ( i = 0; i < parts.length - 1; i++ ) { - curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {}; - curOption = curOption[ parts[ i ] ]; - } - key = parts.pop(); - if ( value === undefined ) { - return curOption[ key ] === undefined ? null : curOption[ key ]; - } - curOption[ key ] = value; - } else { - if ( value === undefined ) { - return this.options[ key ] === undefined ? null : this.options[ key ]; - } - options[ key ] = value; + if (typeof key === "string" ) { + if ( value === undefined ) { + return this.options[ key ]; } + options = {}; + options[ key ] = value; } this._setOptions( options ); @@ -341,11 +220,10 @@ $.Widget.prototype = { return this; }, _setOptions: function( options ) { - var key; - - for ( key in options ) { - this._setOption( key, options[ key ] ); - } + var self = this; + $.each( options, function( key, value ) { + self._setOption( key, value ); + }); return this; }, @@ -354,10 +232,10 @@ $.Widget.prototype = { if ( key === "disabled" ) { this.widget() - .toggleClass( this.widgetFullName + "-disabled ui-state-disabled", !!value ) + [ value ? "addClass" : "removeClass"]( + this.widgetBaseClass + "-disabled" + " " + + "ui-state-disabled" ) .attr( "aria-disabled", value ); - this.hoverable.removeClass( "ui-state-hover" ); - this.focusable.removeClass( "ui-state-focus" ); } return this; @@ -370,88 +248,6 @@ $.Widget.prototype = { return this._setOption( "disabled", true ); }, - _on: function( element, handlers ) { - var delegateElement, - instance = this; - // no element argument, shuffle and use this.element - if ( !handlers ) { - handlers = element; - element = this.element; - delegateElement = this.widget(); - } else { - // accept selectors, DOM elements - element = delegateElement = $( element ); - this.bindings = this.bindings.add( element ); - } - - $.each( handlers, function( event, handler ) { - function handlerProxy() { - // allow widgets to customize the disabled handling - // - disabled as an array instead of boolean - // - disabled class as method for disabling individual parts - if ( instance.options.disabled === true || - $( this ).hasClass( "ui-state-disabled" ) ) { - return; - } - return ( typeof handler === "string" ? instance[ handler ] : handler ) - .apply( instance, arguments ); - } - - // copy the guid so direct unbinding works - if ( typeof handler !== "string" ) { - handlerProxy.guid = handler.guid = - handler.guid || handlerProxy.guid || $.guid++; - } - - var match = event.match( /^(\w+)\s*(.*)$/ ), - eventName = match[1] + instance.eventNamespace, - selector = match[2]; - if ( selector ) { - delegateElement.delegate( selector, eventName, handlerProxy ); - } else { - element.bind( eventName, handlerProxy ); - } - }); - }, - - _off: function( element, eventName ) { - eventName = (eventName || "").split( " " ).join( this.eventNamespace + " " ) + this.eventNamespace; - element.unbind( eventName ).undelegate( eventName ); - }, - - _delay: function( handler, delay ) { - function handlerProxy() { - return ( typeof handler === "string" ? instance[ handler ] : handler ) - .apply( instance, arguments ); - } - var instance = this; - return setTimeout( handlerProxy, delay || 0 ); - }, - - _hoverable: function( element ) { - this.hoverable = this.hoverable.add( element ); - this._on( element, { - mouseenter: function( event ) { - $( event.currentTarget ).addClass( "ui-state-hover" ); - }, - mouseleave: function( event ) { - $( event.currentTarget ).removeClass( "ui-state-hover" ); - } - }); - }, - - _focusable: function( element ) { - this.focusable = this.focusable.add( element ); - this._on( element, { - focusin: function( event ) { - $( event.currentTarget ).addClass( "ui-state-focus" ); - }, - focusout: function( event ) { - $( event.currentTarget ).removeClass( "ui-state-focus" ); - } - }); - }, - _trigger: function( type, event, data ) { var prop, orig, callback = this.options[ type ]; @@ -476,53 +272,11 @@ $.Widget.prototype = { } this.element.trigger( event, data ); - return !( $.isFunction( callback ) && - callback.apply( this.element[0], [ event ].concat( data ) ) === false || + + return !( $.isFunction(callback) && + callback.call( this.element[0], event, data ) === false || event.isDefaultPrevented() ); } }; -$.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) { - $.Widget.prototype[ "_" + method ] = function( element, options, callback ) { - if ( typeof options === "string" ) { - options = { effect: options }; - } - var hasOptions, - effectName = !options ? - method : - options === true || typeof options === "number" ? - defaultEffect : - options.effect || defaultEffect; - options = options || {}; - if ( typeof options === "number" ) { - options = { duration: options }; - } - hasOptions = !$.isEmptyObject( options ); - options.complete = callback; - if ( options.delay ) { - element.delay( options.delay ); - } - if ( hasOptions && $.effects && ( $.effects.effect[ effectName ] || $.uiBackCompat !== false && $.effects[ effectName ] ) ) { - element[ method ]( options ); - } else if ( effectName !== method && element[ effectName ] ) { - element[ effectName ]( options.duration, options.easing, callback ); - } else { - element.queue(function( next ) { - $( this )[ method ](); - if ( callback ) { - callback.call( element[ 0 ] ); - } - next(); - }); - } - }; -}); - -// DEPRECATED -if ( $.uiBackCompat !== false ) { - $.Widget.prototype._getCreateOptions = function() { - return $.metadata && $.metadata.get( this.element[0] )[ this.widgetName ]; - }; -} - })); diff --git a/fileupload/static/js/main.js b/fileupload/static/js/main.js index dbabd8f..01c86fe 100644 --- a/fileupload/static/js/main.js +++ b/fileupload/static/js/main.js @@ -1,5 +1,5 @@ /* - * jQuery File Upload Plugin JS Example 7.0 + * jQuery File Upload Plugin JS Example 6.7 * https://github.com/blueimp/jQuery-File-Upload * * Copyright 2010, Sebastian Tschan @@ -16,11 +16,7 @@ $(function () { 'use strict'; // Initialize the jQuery File Upload widget: - $('#fileupload').fileupload({ - // Uncomment the following to send cross-domain cookies: - //xhrFields: {withCredentials: true}, - url: 'server/php/' - }); + $('#fileupload').fileupload(); // Enable iframe cross-domain access via redirect option: $('#fileupload').fileupload( @@ -68,15 +64,14 @@ $(function () { } } else { // Load existing files: - $.ajax({ - // Uncomment the following to send cross-domain cookies: - //xhrFields: {withCredentials: true}, - url: $('#fileupload').fileupload('option', 'url'), - dataType: 'json', - context: $('#fileupload')[0] - }).done(function (result) { - $(this).fileupload('option', 'done') - .call(this, null, {result: result}); + $('#fileupload').each(function () { + var that = this; + $.getJSON(this.action, function (result) { + if (result && result.length) { + $(that).fileupload('option', 'done') + .call(that, null, {result: result}); + } + }); }); } From f3d32a864682708146ca166240bc5c59c406f14d Mon Sep 17 00:00:00 2001 From: ET-CS Date: Mon, 21 Jan 2013 01:11:41 +0200 Subject: [PATCH 11/72] default url route added --- urls.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/urls.py b/urls.py index 535dd84..e57a6ec 100644 --- a/urls.py +++ b/urls.py @@ -9,7 +9,8 @@ # Examples: # url(r'^$', 'upload.views.home', name='home'), - url(r'^$', redirect(‘url-name’)), + + url(r'^$', redirect(‘url-name’)), url(r'^upload/', include('fileupload.urls')), # Uncomment the admin/doc line below to enable admin documentation: From 961f48168521e78f6b9b8d7f24ffd2c48b7882af Mon Sep 17 00:00:00 2001 From: ET-CS Date: Mon, 21 Jan 2013 01:18:16 +0200 Subject: [PATCH 12/72] fix invalid char. route fix. --- fileupload/models.py | 2 +- urls.py | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/fileupload/models.py b/fileupload/models.py index 66fe2ed..63a3b0e 100644 --- a/fileupload/models.py +++ b/fileupload/models.py @@ -12,7 +12,7 @@ class Picture(models.Model): slug = models.SlugField(max_length=50, blank=True) def __unicode__(self): - return '%s' % (self.file) + return self.file.name @models.permalink def get_absolute_url(self): diff --git a/urls.py b/urls.py index e57a6ec..3a8fe65 100644 --- a/urls.py +++ b/urls.py @@ -1,5 +1,5 @@ from django.conf.urls.defaults import patterns, include, url -from django.shortcuts import redirect +from django.http import HttpResponseRedirect # Uncomment the next two lines to enable the admin: from django.contrib import admin @@ -9,8 +9,7 @@ # Examples: # url(r'^$', 'upload.views.home', name='home'), - - url(r'^$', redirect(‘url-name’)), + url(r'^$', lambda x: HttpResponseRedirect('/upload/new/')), url(r'^upload/', include('fileupload.urls')), # Uncomment the admin/doc line below to enable admin documentation: From 720a6f09bc769ae6b2206d30e7edee2c6d84b5c7 Mon Sep 17 00:00:00 2001 From: Sigurd Gartmann Date: Sat, 26 Jan 2013 14:37:42 +0100 Subject: [PATCH 13/72] the unicode representation now uses the file's name --- fileupload/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fileupload/models.py b/fileupload/models.py index 64cf98b..eb5d0a5 100644 --- a/fileupload/models.py +++ b/fileupload/models.py @@ -12,7 +12,7 @@ class Picture(models.Model): slug = models.SlugField(max_length=50, blank=True) def __unicode__(self): - return self.file + return self.file.name @models.permalink def get_absolute_url(self): From a427ea2f6ddb1a79d37efff6817b0a04540ea209 Mon Sep 17 00:00:00 2001 From: Sigurd Gartmann Date: Wed, 3 Apr 2013 17:31:18 +0200 Subject: [PATCH 14/72] Also ignore environment placed in env dir --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 06f5f1d..25e7e00 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ /lib /local /share +/env From 3cc64c4d738101a618152b4ed54a7de1428a4bba Mon Sep 17 00:00:00 2001 From: Sigurd Gartmann Date: Wed, 3 Apr 2013 17:31:38 +0200 Subject: [PATCH 15/72] Show already uploaded files on page reload. With delete button as well. --- .../templates/fileupload/picture_form.html | 23 +++++++++++++++++++ fileupload/views.py | 5 ++++ 2 files changed, 28 insertions(+) diff --git a/fileupload/templates/fileupload/picture_form.html b/fileupload/templates/fileupload/picture_form.html index 89b764a..65a5be2 100644 --- a/fileupload/templates/fileupload/picture_form.html +++ b/fileupload/templates/fileupload/picture_form.html @@ -42,6 +42,29 @@

Django Jquery file upload demo

+ +
+ {% if pictures %} +

Already uploaded

+ + {% for picture in pictures %} + + + + + + {% endfor %} +
+ + {{ picture.slug }} + + + Delete + +
+

(Removing from this list is left as an exercise to the reader)

+ {% endif %} +