├── .gitignore
├── README.md
├── __init__.py
├── manage.py
├── settings.py
├── static
├── css
│ └── jquery.fileupload-ui.css
└── js
│ ├── jquery.fileupload-ui.js
│ ├── jquery.fileupload.js
│ ├── jquery.iframe-transport.js
│ └── jquery_fix_csrf.js
├── templates
└── upload.html
├── upload
├── __init__.py
├── models.py
├── tests.py
├── urls.py
└── views.py
└── urls.py
/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 | /tmp
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Django-jQuery-File-Uploader-Integration-demo
2 | ============================================
3 |
4 | Please note that this demo by now is outdated however might
5 | provide some help to newcomers hence I'm not deleting it.
6 |
--------------------------------------------------------------------------------
/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/miki725/Django-jQuery-File-Uploader-Integration-demo/98ebf63caebeeb9c78c0207eb553dd3c8320087f/__init__.py
--------------------------------------------------------------------------------
/manage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | from django.core.management import execute_manager
3 | import imp
4 | try:
5 | imp.find_module('settings') # Assumed to be in the same directory.
6 | except ImportError:
7 | import sys
8 | sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n" % __file__)
9 | sys.exit(1)
10 |
11 | import settings
12 |
13 | if __name__ == "__main__":
14 | execute_manager(settings)
15 |
--------------------------------------------------------------------------------
/settings.py:
--------------------------------------------------------------------------------
1 | # Django settings for djangoUpload project.
2 |
3 | import os
4 | PROJECT_ROOT = os.path.abspath(os.path.dirname(os.path.abspath(__file__)))
5 |
6 | DEBUG = True
7 | TEMPLATE_DEBUG = DEBUG
8 |
9 | ADMINS = (
10 | # ('Your Name', 'your_email@example.com'),
11 | ('Miroslav Shubernetskiy', 'miroslav@miki725.com'),
12 | )
13 |
14 | MANAGERS = ADMINS
15 |
16 | DATABASES = {
17 | 'default': {
18 | 'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
19 | 'NAME': 'djangoupload.db', # Or path to database file if using sqlite3.
20 | 'USER': '', # Not used with sqlite3.
21 | 'PASSWORD': '', # Not used with sqlite3.
22 | 'HOST': '', # Set to empty string for localhost. Not used with sqlite3.
23 | 'PORT': '', # Set to empty string for default. Not used with sqlite3.
24 | }
25 | }
26 |
27 | # Local time zone for this installation. Choices can be found here:
28 | # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
29 | # although not all choices may be available on all operating systems.
30 | # On Unix systems, a value of None will cause Django to use the same
31 | # timezone as the operating system.
32 | # If running in a Windows environment this must be set to the same as your
33 | # system time zone.
34 | TIME_ZONE = 'America/New_York'
35 |
36 | # Language code for this installation. All choices can be found here:
37 | # http://www.i18nguy.com/unicode/language-identifiers.html
38 | LANGUAGE_CODE = 'en-us'
39 |
40 | SITE_ID = 1
41 |
42 | # If you set this to False, Django will make some optimizations so as not
43 | # to load the internationalization machinery.
44 | USE_I18N = True
45 |
46 | # If you set this to False, Django will not format dates, numbers and
47 | # calendars according to the current locale
48 | USE_L10N = True
49 |
50 | # Absolute filesystem path to the directory that will hold user-uploaded files.
51 | # Example: "/home/media/media.lawrence.com/media/"
52 | MEDIA_ROOT = ''
53 |
54 | # URL that handles the media served from MEDIA_ROOT. Make sure to use a
55 | # trailing slash.
56 | # Examples: "http://media.lawrence.com/media/", "http://example.com/media/"
57 | MEDIA_URL = ''
58 |
59 | # Absolute path to the directory static files should be collected to.
60 | # Don't put anything in this directory yourself; store your static files
61 | # in apps' "static/" subdirectories and in STATICFILES_DIRS.
62 | # Example: "/home/media/media.lawrence.com/static/"
63 | STATIC_ROOT = ''
64 |
65 | # URL prefix for static files.
66 | # Example: "http://media.lawrence.com/static/"
67 | STATIC_URL = '/static/'
68 |
69 | # URL prefix for admin static files -- CSS, JavaScript and images.
70 | # Make sure to use a trailing slash.
71 | # Examples: "http://foo.com/static/admin/", "/static/admin/".
72 | ADMIN_MEDIA_PREFIX = '/static/admin/'
73 |
74 | # Additional locations of static files
75 | STATICFILES_DIRS = (
76 | # Put strings here, like "/home/html/static" or "C:/www/django/static".
77 | # Always use forward slashes, even on Windows.
78 | # Don't forget to use absolute paths, not relative paths.
79 | os.path.join(PROJECT_ROOT, "static"),
80 |
81 | )
82 |
83 | # List of finder classes that know how to find static files in
84 | # various locations.
85 | STATICFILES_FINDERS = (
86 | 'django.contrib.staticfiles.finders.FileSystemFinder',
87 | 'django.contrib.staticfiles.finders.AppDirectoriesFinder',
88 | # 'django.contrib.staticfiles.finders.DefaultStorageFinder',
89 | )
90 |
91 | # Make this unique, and don't share it with anybody.
92 | # So this file is on github so I guess the secret is out!!!
93 | SECRET_KEY = 'gcs^9v-hbl9qwavnbn&@e794ir@tyrrnz(+0efshm!dzzo_xt+'
94 |
95 | # List of callables that know how to import templates from various sources.
96 | TEMPLATE_LOADERS = (
97 | 'django.template.loaders.filesystem.Loader',
98 | 'django.template.loaders.app_directories.Loader',
99 | # 'django.template.loaders.eggs.Loader',
100 | )
101 |
102 | MIDDLEWARE_CLASSES = (
103 | 'django.middleware.common.CommonMiddleware',
104 | 'django.contrib.sessions.middleware.SessionMiddleware',
105 | 'django.middleware.csrf.CsrfViewMiddleware',
106 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
107 | 'django.contrib.messages.middleware.MessageMiddleware',
108 | )
109 |
110 | ROOT_URLCONF = 'djangoUpload.urls'
111 |
112 | TEMPLATE_DIRS = (
113 | # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
114 | # Always use forward slashes, even on Windows.
115 | # Don't forget to use absolute paths, not relative paths.
116 | os.path.join(PROJECT_ROOT, "templates")
117 | )
118 |
119 | INSTALLED_APPS = (
120 | 'django.contrib.auth',
121 | 'django.contrib.contenttypes',
122 | 'django.contrib.sessions',
123 | 'django.contrib.sites',
124 | 'django.contrib.messages',
125 | 'django.contrib.staticfiles',
126 | # Uncomment the next line to enable the admin:
127 | # 'django.contrib.admin',
128 | # Uncomment the next line to enable admin documentation:
129 | # 'django.contrib.admindocs',
130 | )
131 |
132 | # A sample logging configuration. The only tangible logging
133 | # performed by this configuration is to send an email to
134 | # the site admins on every HTTP 500 error.
135 | # See http://docs.djangoproject.com/en/dev/topics/logging for
136 | # more details on how to customize your logging configuration.
137 | LOGGING = {
138 | 'version': 1,
139 | 'disable_existing_loggers': False,
140 | 'handlers': {
141 | 'mail_admins': {
142 | 'level': 'ERROR',
143 | 'class': 'django.utils.log.AdminEmailHandler'
144 | }
145 | },
146 | 'loggers': {
147 | 'django.request': {
148 | 'handlers': ['mail_admins'],
149 | 'level': 'ERROR',
150 | 'propagate': True,
151 | },
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/static/css/jquery.fileupload-ui.css:
--------------------------------------------------------------------------------
1 | @charset 'UTF-8';
2 | /*
3 | * jQuery File Upload UI Plugin CSS 5.0.6
4 | * https://github.com/blueimp/jQuery-File-Upload
5 | *
6 | * Copyright 2010, Sebastian Tschan
7 | * https://blueimp.net
8 | *
9 | * Licensed under the MIT license:
10 | * http://creativecommons.org/licenses/MIT/
11 | */
12 |
13 | .fileupload-buttonbar .ui-button input {
14 | position: absolute;
15 | top: 0;
16 | right: 0;
17 | margin: 0;
18 | border: solid transparent;
19 | border-width: 0 0 100px 200px;
20 | opacity: 0;
21 | filter: alpha(opacity=0);
22 | -o-transform: translate(250px, -50px) scale(1);
23 | -moz-transform: translate(-300px, 0) scale(4);
24 | direction: ltr;
25 | cursor: pointer;
26 | }
27 |
28 | .fileinput-button {
29 | overflow: hidden;
30 | }
31 |
32 | /* Fix for IE 6: */
33 | *html .fileinput-button {
34 | padding: 2px 0;
35 | }
36 |
37 | /* Fix for IE 7: */
38 | *+html .fileinput-button {
39 | padding: 2px 0;
40 | }
41 |
42 | .fileupload-buttonbar {
43 | padding: 0.2em 0.4em;
44 | }
45 |
46 | .fileupload-buttonbar .ui-button {
47 | vertical-align: middle;
48 | }
49 |
50 | .fileupload-content {
51 | padding: 0.2em 0.4em;
52 | border-top-width: 0;
53 | }
54 |
55 | .fileupload-content .ui-progressbar {
56 | width: 200px;
57 | height: 20px;
58 | }
59 |
60 | .fileupload-content .ui-progressbar-value {
61 | background: url(pbar-ani.gif);
62 | }
63 |
64 | .fileupload-content .fileupload-progressbar {
65 | width: 400px;
66 | margin: 10px 0;
67 | }
68 |
69 | .files {
70 | margin: 10px 0;
71 | border-collapse: collapse;
72 | }
73 |
74 | .files td {
75 | padding: 5px;
76 | border-spacing: 5px;
77 | }
78 |
79 | .files img {
80 | border: none;
81 | }
82 |
83 | .files .name {
84 | padding: 0 10px;
85 | }
86 |
87 | .files .size {
88 | padding: 0 10px 0 0;
89 | text-align: right;
90 | white-space: nowrap;
91 | }
92 |
93 | .ui-state-disabled .ui-state-disabled {
94 | opacity: 1;
95 | filter: alpha(opacity=100);
96 | }
97 |
98 | .ui-state-disabled input {
99 | cursor: default;
100 | }
--------------------------------------------------------------------------------
/static/js/jquery.fileupload-ui.js:
--------------------------------------------------------------------------------
1 | /*
2 | * jQuery File Upload User Interface Plugin 5.0.13
3 | * https://github.com/blueimp/jQuery-File-Upload
4 | *
5 | * Copyright 2010, Sebastian Tschan
6 | * https://blueimp.net
7 | *
8 | * Licensed under the MIT license:
9 | * http://creativecommons.org/licenses/MIT/
10 | */
11 |
12 | /*jslint nomen: true, unparam: true, regexp: true */
13 | /*global window, document, URL, webkitURL, FileReader, jQuery */
14 |
15 | (function ($) {
16 | 'use strict';
17 |
18 | // The UI version extends the basic fileupload widget and adds
19 | // a complete user interface based on the given upload/download
20 | // templates.
21 | $.widget('blueimpUI.fileupload', $.blueimp.fileupload, {
22 |
23 | options: {
24 | // By default, files added to the widget are uploaded as soon
25 | // as the user clicks on the start buttons. To enable automatic
26 | // uploads, set the following option to true:
27 | autoUpload: false,
28 | // The following option limits the number of files that are
29 | // allowed to be uploaded using this widget:
30 | maxNumberOfFiles: undefined,
31 | // The maximum allowed file size:
32 | maxFileSize: undefined,
33 | // The minimum allowed file size:
34 | minFileSize: 1,
35 | // The regular expression for allowed file types, matches
36 | // against either file type or file name:
37 | acceptFileTypes: /.+$/i,
38 | // The regular expression to define for which files a preview
39 | // image is shown, matched against the file type:
40 | previewFileTypes: /^image\/(gif|jpeg|png)$/,
41 | // The maximum width of the preview images:
42 | previewMaxWidth: 80,
43 | // The maximum height of the preview images:
44 | previewMaxHeight: 80,
45 | // By default, preview images are displayed as canvas elements
46 | // if supported by the browser. Set the following option to false
47 | // to always display preview images as img elements:
48 | previewAsCanvas: true,
49 | // The file upload template that is given as first argument to the
50 | // jQuery.tmpl method to render the file uploads:
51 | uploadTemplate: $('#template-upload'),
52 | // The file download template, that is given as first argument to the
53 | // jQuery.tmpl method to render the file downloads:
54 | downloadTemplate: $('#template-download'),
55 | // The expected data type of the upload response, sets the dataType
56 | // option of the $.ajax upload requests:
57 | dataType: 'json',
58 |
59 | // The add callback is invoked as soon as files are added to the fileupload
60 | // widget (via file input selection, drag & drop or add API call).
61 | // See the basic file upload widget for more information:
62 | add: function (e, data) {
63 | var that = $(this).data('fileupload');
64 | that._adjustMaxNumberOfFiles(-data.files.length);
65 | data.isAdjusted = true;
66 | data.isValidated = that._validate(data.files);
67 | data.context = that._renderUpload(data.files)
68 | .appendTo($(this).find('.files')).fadeIn(function () {
69 | // Fix for IE7 and lower:
70 | $(this).show();
71 | }).data('data', data);
72 | if ((that.options.autoUpload || data.autoUpload) &&
73 | data.isValidated) {
74 | data.jqXHR = data.submit();
75 | }
76 | },
77 | // Callback for the start of each file upload request:
78 | send: function (e, data) {
79 | if (!data.isValidated) {
80 | var that = $(this).data('fileupload');
81 | if (!data.isAdjusted) {
82 | that._adjustMaxNumberOfFiles(-data.files.length);
83 | }
84 | if (!that._validate(data.files)) {
85 | return false;
86 | }
87 | }
88 | if (data.context && data.dataType &&
89 | data.dataType.substr(0, 6) === 'iframe') {
90 | // Iframe Transport does not support progress events.
91 | // In lack of an indeterminate progress bar, we set
92 | // the progress to 100%, showing the full animated bar:
93 | data.context.find('.ui-progressbar').progressbar(
94 | 'value',
95 | parseInt(100, 10)
96 | );
97 | }
98 | },
99 | // Callback for successful uploads:
100 | done: function (e, data) {
101 | var that = $(this).data('fileupload');
102 | if (data.context) {
103 | data.context.each(function (index) {
104 | var file = ($.isArray(data.result) &&
105 | data.result[index]) || {error: 'emptyResult'};
106 | if (file.error) {
107 | that._adjustMaxNumberOfFiles(1);
108 | }
109 | $(this).fadeOut(function () {
110 | that._renderDownload([file])
111 | .css('display', 'none')
112 | .replaceAll(this)
113 | .fadeIn(function () {
114 | // Fix for IE7 and lower:
115 | $(this).show();
116 | });
117 | });
118 | });
119 | } else {
120 | that._renderDownload(data.result)
121 | .css('display', 'none')
122 | .appendTo($(this).find('.files'))
123 | .fadeIn(function () {
124 | // Fix for IE7 and lower:
125 | $(this).show();
126 | });
127 | }
128 | },
129 | // Callback for failed (abort or error) uploads:
130 | fail: function (e, data) {
131 | var that = $(this).data('fileupload');
132 | that._adjustMaxNumberOfFiles(data.files.length);
133 | if (data.context) {
134 | data.context.each(function (index) {
135 | $(this).fadeOut(function () {
136 | if (data.errorThrown !== 'abort') {
137 | var file = data.files[index];
138 | file.error = file.error || data.errorThrown
139 | || true;
140 | that._renderDownload([file])
141 | .css('display', 'none')
142 | .replaceAll(this)
143 | .fadeIn(function () {
144 | // Fix for IE7 and lower:
145 | $(this).show();
146 | });
147 | } else {
148 | data.context.remove();
149 | }
150 | });
151 | });
152 | } else if (data.errorThrown !== 'abort') {
153 | that._adjustMaxNumberOfFiles(-data.files.length);
154 | data.context = that._renderUpload(data.files)
155 | .css('display', 'none')
156 | .appendTo($(this).find('.files'))
157 | .fadeIn(function () {
158 | // Fix for IE7 and lower:
159 | $(this).show();
160 | }).data('data', data);
161 | }
162 | },
163 | // Callback for upload progress events:
164 | progress: function (e, data) {
165 | if (data.context) {
166 | data.context.find('.ui-progressbar').progressbar(
167 | 'value',
168 | parseInt(data.loaded / data.total * 100, 10)
169 | );
170 | }
171 | },
172 | // Callback for global upload progress events:
173 | progressall: function (e, data) {
174 | $(this).find('.fileupload-progressbar').progressbar(
175 | 'value',
176 | parseInt(data.loaded / data.total * 100, 10)
177 | );
178 | },
179 | // Callback for uploads start, equivalent to the global ajaxStart event:
180 | start: function () {
181 | $(this).find('.fileupload-progressbar')
182 | .progressbar('value', 0).fadeIn();
183 | },
184 | // Callback for uploads stop, equivalent to the global ajaxStop event:
185 | stop: function () {
186 | $(this).find('.fileupload-progressbar').fadeOut();
187 | },
188 | // Callback for file deletion:
189 | destroy: function (e, data) {
190 | var that = $(this).data('fileupload');
191 | if (data.url) {
192 | $.ajax(data)
193 | .success(function () {
194 | that._adjustMaxNumberOfFiles(1);
195 | $(this).fadeOut(function () {
196 | $(this).remove();
197 | });
198 | });
199 | } else {
200 | data.context.fadeOut(function () {
201 | $(this).remove();
202 | });
203 | }
204 | }
205 | },
206 |
207 | // Scales the given image (img HTML element)
208 | // using the given options.
209 | // Returns a canvas object if the canvas option is true
210 | // and the browser supports canvas, else the scaled image:
211 | _scaleImage: function (img, options) {
212 | options = options || {};
213 | var canvas = document.createElement('canvas'),
214 | scale = Math.min(
215 | (options.maxWidth || img.width) / img.width,
216 | (options.maxHeight || img.height) / img.height
217 | );
218 | if (scale >= 1) {
219 | scale = Math.max(
220 | (options.minWidth || img.width) / img.width,
221 | (options.minHeight || img.height) / img.height
222 | );
223 | }
224 | img.width = parseInt(img.width * scale, 10);
225 | img.height = parseInt(img.height * scale, 10);
226 | if (!options.canvas || !canvas.getContext) {
227 | return img;
228 | }
229 | canvas.width = img.width;
230 | canvas.height = img.height;
231 | canvas.getContext('2d')
232 | .drawImage(img, 0, 0, img.width, img.height);
233 | return canvas;
234 | },
235 |
236 | _createObjectURL: function (file) {
237 | var undef = 'undefined',
238 | urlAPI = (typeof window.createObjectURL !== undef && window) ||
239 | (typeof URL !== undef && URL) ||
240 | (typeof webkitURL !== undef && webkitURL);
241 | return urlAPI ? urlAPI.createObjectURL(file) : false;
242 | },
243 |
244 | _revokeObjectURL: function (url) {
245 | var undef = 'undefined',
246 | urlAPI = (typeof window.revokeObjectURL !== undef && window) ||
247 | (typeof URL !== undef && URL) ||
248 | (typeof webkitURL !== undef && webkitURL);
249 | return urlAPI ? urlAPI.revokeObjectURL(url) : false;
250 | },
251 |
252 | // Loads a given File object via FileReader interface,
253 | // invokes the callback with a data url:
254 | _loadFile: function (file, callback) {
255 | if (typeof FileReader !== 'undefined' &&
256 | FileReader.prototype.readAsDataURL) {
257 | var fileReader = new FileReader();
258 | fileReader.onload = function (e) {
259 | callback(e.target.result);
260 | };
261 | fileReader.readAsDataURL(file);
262 | return true;
263 | }
264 | return false;
265 | },
266 |
267 | // Loads an image for a given File object.
268 | // Invokes the callback with an img or optional canvas
269 | // element (if supported by the browser) as parameter:
270 | _loadImage: function (file, callback, options) {
271 | var that = this,
272 | url,
273 | img;
274 | if (!options || !options.fileTypes ||
275 | options.fileTypes.test(file.type)) {
276 | url = this._createObjectURL(file);
277 | img = $('').bind('load', function () {
278 | $(this).unbind('load');
279 | that._revokeObjectURL(url);
280 | callback(that._scaleImage(img[0], options));
281 | }).prop('src', url);
282 | if (!url) {
283 | this._loadFile(file, function (url) {
284 | img.prop('src', url);
285 | });
286 | }
287 | }
288 | },
289 |
290 | // Link handler, that allows to download files
291 | // by drag & drop of the links to the desktop:
292 | _enableDragToDesktop: function () {
293 | var link = $(this),
294 | url = link.prop('href'),
295 | name = decodeURIComponent(url.split('/').pop())
296 | .replace(/:/g, '-'),
297 | type = 'application/octet-stream';
298 | link.bind('dragstart', function (e) {
299 | try {
300 | e.originalEvent.dataTransfer.setData(
301 | 'DownloadURL',
302 | [type, name, url].join(':')
303 | );
304 | } catch (err) {}
305 | });
306 | },
307 |
308 | _adjustMaxNumberOfFiles: function (operand) {
309 | if (typeof this.options.maxNumberOfFiles === 'number') {
310 | this.options.maxNumberOfFiles += operand;
311 | if (this.options.maxNumberOfFiles < 1) {
312 | this._disableFileInputButton();
313 | } else {
314 | this._enableFileInputButton();
315 | }
316 | }
317 | },
318 |
319 | _formatFileSize: function (file) {
320 | if (typeof file.size !== 'number') {
321 | return '';
322 | }
323 | if (file.size >= 1000000000) {
324 | return (file.size / 1000000000).toFixed(2) + ' GB';
325 | }
326 | if (file.size >= 1000000) {
327 | return (file.size / 1000000).toFixed(2) + ' MB';
328 | }
329 | return (file.size / 1000).toFixed(2) + ' KB';
330 | },
331 |
332 | _hasError: function (file) {
333 | if (file.error) {
334 | return file.error;
335 | }
336 | // The number of added files is subtracted from
337 | // maxNumberOfFiles before validation, so we check if
338 | // maxNumberOfFiles is below 0 (instead of below 1):
339 | if (this.options.maxNumberOfFiles < 0) {
340 | return 'maxNumberOfFiles';
341 | }
342 | // Files are accepted if either the file type or the file name
343 | // matches against the acceptFileTypes regular expression, as
344 | // only browsers with support for the File API report the type:
345 | if (!(this.options.acceptFileTypes.test(file.type) ||
346 | this.options.acceptFileTypes.test(file.name))) {
347 | return 'acceptFileTypes';
348 | }
349 | if (this.options.maxFileSize &&
350 | file.size > this.options.maxFileSize) {
351 | return 'maxFileSize';
352 | }
353 | if (typeof file.size === 'number' &&
354 | file.size < this.options.minFileSize) {
355 | return 'minFileSize';
356 | }
357 | return null;
358 | },
359 |
360 | _validate: function (files) {
361 | var that = this,
362 | valid;
363 | $.each(files, function (index, file) {
364 | file.error = that._hasError(file);
365 | valid = !file.error;
366 | });
367 | return valid;
368 | },
369 |
370 | _uploadTemplateHelper: function (file) {
371 | file.sizef = this._formatFileSize(file);
372 | return file;
373 | },
374 |
375 | _renderUploadTemplate: function (files) {
376 | var that = this;
377 | return $.tmpl(
378 | this.options.uploadTemplate,
379 | $.map(files, function (file) {
380 | return that._uploadTemplateHelper(file);
381 | })
382 | );
383 | },
384 |
385 | _renderUpload: function (files) {
386 | var that = this,
387 | options = this.options,
388 | tmpl = this._renderUploadTemplate(files);
389 | if (!(tmpl instanceof $)) {
390 | return $();
391 | }
392 | tmpl.css('display', 'none');
393 | // .slice(1).remove().end().first() removes all but the first
394 | // element and selects only the first for the jQuery collection:
395 | tmpl.find('.progress div').slice(1).remove().end().first()
396 | .progressbar();
397 | tmpl.find('.start button').slice(
398 | this.options.autoUpload ? 0 : 1
399 | ).remove().end().first()
400 | .button({
401 | text: false,
402 | icons: {primary: 'ui-icon-circle-arrow-e'}
403 | });
404 | tmpl.find('.cancel button').slice(1).remove().end().first()
405 | .button({
406 | text: false,
407 | icons: {primary: 'ui-icon-cancel'}
408 | });
409 | tmpl.find('.preview').each(function (index, node) {
410 | that._loadImage(
411 | files[index],
412 | function (img) {
413 | $(img).hide().appendTo(node).fadeIn();
414 | },
415 | {
416 | maxWidth: options.previewMaxWidth,
417 | maxHeight: options.previewMaxHeight,
418 | fileTypes: options.previewFileTypes,
419 | canvas: options.previewAsCanvas
420 | }
421 | );
422 | });
423 | return tmpl;
424 | },
425 |
426 | _downloadTemplateHelper: function (file) {
427 | file.sizef = this._formatFileSize(file);
428 | return file;
429 | },
430 |
431 | _renderDownloadTemplate: function (files) {
432 | var that = this;
433 | return $.tmpl(
434 | this.options.downloadTemplate,
435 | $.map(files, function (file) {
436 | return that._downloadTemplateHelper(file);
437 | })
438 | );
439 | },
440 |
441 | _renderDownload: function (files) {
442 | var tmpl = this._renderDownloadTemplate(files);
443 | if (!(tmpl instanceof $)) {
444 | return $();
445 | }
446 | tmpl.css('display', 'none');
447 | tmpl.find('.delete button').button({
448 | text: false,
449 | icons: {primary: 'ui-icon-trash'}
450 | });
451 | tmpl.find('a').each(this._enableDragToDesktop);
452 | return tmpl;
453 | },
454 |
455 | _startHandler: function (e) {
456 | e.preventDefault();
457 | var tmpl = $(this).closest('.template-upload'),
458 | data = tmpl.data('data');
459 | if (data && data.submit && !data.jqXHR) {
460 | data.jqXHR = data.submit();
461 | $(this).fadeOut();
462 | }
463 | },
464 |
465 | _cancelHandler: function (e) {
466 | e.preventDefault();
467 | var tmpl = $(this).closest('.template-upload'),
468 | data = tmpl.data('data') || {};
469 | if (!data.jqXHR) {
470 | data.errorThrown = 'abort';
471 | e.data.fileupload._trigger('fail', e, data);
472 | } else {
473 | data.jqXHR.abort();
474 | }
475 | },
476 |
477 | _deleteHandler: function (e) {
478 | e.preventDefault();
479 | var button = $(this);
480 | e.data.fileupload._trigger('destroy', e, {
481 | context: button.closest('.template-download'),
482 | url: button.attr('data-url'),
483 | type: button.attr('data-type'),
484 | dataType: e.data.fileupload.options.dataType
485 | });
486 | },
487 |
488 | _initEventHandlers: function () {
489 | $.blueimp.fileupload.prototype._initEventHandlers.call(this);
490 | var filesList = this.element.find('.files'),
491 | eventData = {fileupload: this};
492 | filesList.find('.start button')
493 | .live(
494 | 'click.' + this.options.namespace,
495 | eventData,
496 | this._startHandler
497 | );
498 | filesList.find('.cancel button')
499 | .live(
500 | 'click.' + this.options.namespace,
501 | eventData,
502 | this._cancelHandler
503 | );
504 | filesList.find('.delete button')
505 | .live(
506 | 'click.' + this.options.namespace,
507 | eventData,
508 | this._deleteHandler
509 | );
510 | },
511 |
512 | _destroyEventHandlers: function () {
513 | var filesList = this.element.find('.files');
514 | filesList.find('.start button')
515 | .die('click.' + this.options.namespace);
516 | filesList.find('.cancel button')
517 | .die('click.' + this.options.namespace);
518 | filesList.find('.delete button')
519 | .die('click.' + this.options.namespace);
520 | $.blueimp.fileupload.prototype._destroyEventHandlers.call(this);
521 | },
522 |
523 | _initFileUploadButtonBar: function () {
524 | var fileUploadButtonBar = this.element.find('.fileupload-buttonbar'),
525 | filesList = this.element.find('.files'),
526 | ns = this.options.namespace;
527 | fileUploadButtonBar
528 | .addClass('ui-widget-header ui-corner-top');
529 | this.element.find('.fileinput-button').each(function () {
530 | var fileInput = $(this).find('input:file').detach();
531 | $(this).button({icons: {primary: 'ui-icon-plusthick'}})
532 | .append(fileInput);
533 | });
534 | fileUploadButtonBar.find('.start')
535 | .button({icons: {primary: 'ui-icon-circle-arrow-e'}})
536 | .bind('click.' + ns, function (e) {
537 | e.preventDefault();
538 | filesList.find('.start button').click();
539 | });
540 | fileUploadButtonBar.find('.cancel')
541 | .button({icons: {primary: 'ui-icon-cancel'}})
542 | .bind('click.' + ns, function (e) {
543 | e.preventDefault();
544 | filesList.find('.cancel button').click();
545 | });
546 | fileUploadButtonBar.find('.delete')
547 | .button({icons: {primary: 'ui-icon-trash'}})
548 | .bind('click.' + ns, function (e) {
549 | e.preventDefault();
550 | filesList.find('.delete button').click();
551 | });
552 | },
553 |
554 | _destroyFileUploadButtonBar: function () {
555 | this.element.find('.fileupload-buttonbar')
556 | .removeClass('ui-widget-header ui-corner-top');
557 | this.element.find('.fileinput-button').each(function () {
558 | var fileInput = $(this).find('input:file').detach();
559 | $(this).button('destroy')
560 | .append(fileInput);
561 | });
562 | this.element.find('.fileupload-buttonbar button')
563 | .unbind('click.' + this.options.namespace)
564 | .button('destroy');
565 | },
566 |
567 | _enableFileInputButton: function () {
568 | this.element.find('.fileinput-button input:file:disabled')
569 | .each(function () {
570 | var fileInput = $(this),
571 | button = fileInput.parent();
572 | fileInput.detach().prop('disabled', false);
573 | button.button('enable').append(fileInput);
574 | });
575 | },
576 |
577 | _disableFileInputButton: function () {
578 | this.element.find('.fileinput-button input:file:enabled')
579 | .each(function () {
580 | var fileInput = $(this),
581 | button = fileInput.parent();
582 | fileInput.detach().prop('disabled', true);
583 | button.button('disable').append(fileInput);
584 | });
585 | },
586 |
587 | _initTemplates: function () {
588 | // Handle cases where the templates are defined
589 | // after the widget library has been included:
590 | if (this.options.uploadTemplate instanceof $ &&
591 | !this.options.uploadTemplate.length) {
592 | this.options.uploadTemplate = $(
593 | this.options.uploadTemplate.selector
594 | );
595 | }
596 | if (this.options.downloadTemplate instanceof $ &&
597 | !this.options.downloadTemplate.length) {
598 | this.options.downloadTemplate = $(
599 | this.options.downloadTemplate.selector
600 | );
601 | }
602 | },
603 |
604 | _create: function () {
605 | $.blueimp.fileupload.prototype._create.call(this);
606 | this._initTemplates();
607 | this.element
608 | .addClass('ui-widget');
609 | this._initFileUploadButtonBar();
610 | this.element.find('.fileupload-content')
611 | .addClass('ui-widget-content ui-corner-bottom');
612 | this.element.find('.fileupload-progressbar')
613 | .hide().progressbar();
614 | },
615 |
616 | destroy: function () {
617 | this.element.find('.fileupload-progressbar')
618 | .progressbar('destroy');
619 | this.element.find('.fileupload-content')
620 | .removeClass('ui-widget-content ui-corner-bottom');
621 | this._destroyFileUploadButtonBar();
622 | this.element.removeClass('ui-widget');
623 | $.blueimp.fileupload.prototype.destroy.call(this);
624 | },
625 |
626 | enable: function () {
627 | $.blueimp.fileupload.prototype.enable.call(this);
628 | this.element.find(':ui-button').not('.fileinput-button')
629 | .button('enable');
630 | this._enableFileInputButton();
631 | },
632 |
633 | disable: function () {
634 | this.element.find(':ui-button').not('.fileinput-button')
635 | .button('disable');
636 | this._disableFileInputButton();
637 | $.blueimp.fileupload.prototype.disable.call(this);
638 | }
639 |
640 | });
641 |
642 | }(jQuery));
--------------------------------------------------------------------------------
/static/js/jquery.fileupload.js:
--------------------------------------------------------------------------------
1 | /*
2 | * jQuery File Upload Plugin 5.0.3
3 | * https://github.com/blueimp/jQuery-File-Upload
4 | *
5 | * Copyright 2010, Sebastian Tschan
6 | * https://blueimp.net
7 | *
8 | * Licensed under the MIT license:
9 | * http://creativecommons.org/licenses/MIT/
10 | */
11 |
12 | /*jslint nomen: true, unparam: true, regexp: true */
13 | /*global document, XMLHttpRequestUpload, Blob, File, FormData, location, jQuery */
14 |
15 | (function ($) {
16 | 'use strict';
17 |
18 | // The fileupload widget listens for change events on file input fields
19 | // defined via fileInput setting and drop events of the given dropZone.
20 | // In addition to the default jQuery Widget methods, the fileupload widget
21 | // exposes the "add" and "send" methods, to add or directly send files
22 | // using the fileupload API.
23 | // By default, files added via file input selection, drag & drop or
24 | // "add" method are uploaded immediately, but it is possible to override
25 | // the "add" callback option to queue file uploads.
26 | $.widget('blueimp.fileupload', {
27 |
28 | options: {
29 | // The namespace used for event handler binding on the dropZone and
30 | // fileInput collections.
31 | // If not set, the name of the widget ("fileupload") is used.
32 | namespace: undefined,
33 | // The drop target collection, by the default the complete document.
34 | // Set to null or an empty collection to disable drag & drop support:
35 | dropZone: $(document),
36 | // The file input field collection, that is listened for change events.
37 | // If undefined, it is set to the file input fields inside
38 | // of the widget element on plugin initialization.
39 | // Set to null or an empty collection to disable the change listener.
40 | fileInput: undefined,
41 | // By default, the file input field is replaced with a clone after
42 | // each input field change event. This is required for iframe transport
43 | // queues and allows change events to be fired for the same file
44 | // selection, but can be disabled by setting the following option to false:
45 | replaceFileInput: true,
46 | // The parameter name for the file form data (the request argument name).
47 | // If undefined or empty, the name property of the file input field is
48 | // used, or "files[]" if the file input name property is also empty:
49 | paramName: undefined,
50 | // By default, each file of a selection is uploaded using an individual
51 | // request for XHR type uploads. Set to false to upload file
52 | // selections in one request each:
53 | singleFileUploads: true,
54 | // Set the following option to true to issue all file upload requests
55 | // in a sequential order:
56 | sequentialUploads: false,
57 | // Set the following option to true to force iframe transport uploads:
58 | forceIframeTransport: false,
59 | // By default, XHR file uploads are sent as multipart/form-data.
60 | // The iframe transport is always using multipart/form-data.
61 | // Set to false to enable non-multipart XHR uploads:
62 | multipart: true,
63 | // To upload large files in smaller chunks, set the following option
64 | // to a preferred maximum chunk size. If set to 0, null or undefined,
65 | // or the browser does not support the required Blob API, files will
66 | // be uploaded as a whole.
67 | maxChunkSize: undefined,
68 | // When a non-multipart upload or a chunked multipart upload has been
69 | // aborted, this option can be used to resume the upload by setting
70 | // it to the size of the already uploaded bytes. This option is most
71 | // useful when modifying the options object inside of the "add" or
72 | // "send" callbacks, as the options are cloned for each file upload.
73 | uploadedBytes: undefined,
74 | // By default, failed (abort or error) file uploads are removed from the
75 | // global progress calculation. Set the following option to false to
76 | // prevent recalculating the global progress data:
77 | recalculateProgress: true,
78 |
79 | // Additional form data to be sent along with the file uploads can be set
80 | // using this option, which accepts an array of objects with name and
81 | // value properties, a function returning such an array, a FormData
82 | // object (for XHR file uploads), or a simple object.
83 | // The form of the first fileInput is given as parameter to the function:
84 | formData: function (form) {
85 | return form.serializeArray();
86 | },
87 |
88 | // The add callback is invoked as soon as files are added to the fileupload
89 | // widget (via file input selection, drag & drop or add API call).
90 | // If the singleFileUploads option is enabled, this callback will be
91 | // called once for each file in the selection for XHR file uplaods, else
92 | // once for each file selection.
93 | // The upload starts when the submit method is invoked on the data parameter.
94 | // The data object contains a files property holding the added files
95 | // and allows to override plugin options as well as define ajax settings.
96 | // Listeners for this callback can also be bound the following way:
97 | // .bind('fileuploadadd', func);
98 | // data.submit() returns a Promise object and allows to attach additional
99 | // handlers using jQuery's Deferred callbacks:
100 | // data.submit().done(func).fail(func).always(func);
101 | add: function (e, data) {
102 | data.submit();
103 | },
104 |
105 | // Other callbacks:
106 | // Callback for the start of each file upload request:
107 | // send: function (e, data) {}, // .bind('fileuploadsend', func);
108 | // Callback for successful uploads:
109 | // done: function (e, data) {}, // .bind('fileuploaddone', func);
110 | // Callback for failed (abort or error) uploads:
111 | // fail: function (e, data) {}, // .bind('fileuploadfail', func);
112 | // Callback for completed (success, abort or error) requests:
113 | // always: function (e, data) {}, // .bind('fileuploadalways', func);
114 | // Callback for upload progress events:
115 | // progress: function (e, data) {}, // .bind('fileuploadprogress', func);
116 | // Callback for global upload progress events:
117 | // progressall: function (e, data) {}, // .bind('fileuploadprogressall', func);
118 | // Callback for uploads start, equivalent to the global ajaxStart event:
119 | // start: function (e) {}, // .bind('fileuploadstart', func);
120 | // Callback for uploads stop, equivalent to the global ajaxStop event:
121 | // stop: function (e) {}, // .bind('fileuploadstop', func);
122 | // Callback for change events of the fileInput collection:
123 | // change: function (e, data) {}, // .bind('fileuploadchange', func);
124 | // Callback for drop events of the dropZone collection:
125 | // drop: function (e, data) {}, // .bind('fileuploaddrop', func);
126 | // Callback for dragover events of the dropZone collection:
127 | // dragover: function (e) {}, // .bind('fileuploaddragover', func);
128 |
129 | // The plugin options are used as settings object for the ajax calls.
130 | // The following are jQuery ajax settings required for the file uploads:
131 | processData: false,
132 | contentType: false,
133 | cache: false
134 | },
135 |
136 | // A list of options that require a refresh after assigning a new value:
137 | _refreshOptionsList: ['namespace', 'dropZone', 'fileInput'],
138 |
139 | _isXHRUpload: function (options) {
140 | var undef = 'undefined';
141 | return !options.forceIframeTransport &&
142 | typeof XMLHttpRequestUpload !== undef && typeof File !== undef &&
143 | (!options.multipart || typeof FormData !== undef);
144 | },
145 |
146 | _getFormData: function (options) {
147 | var formData;
148 | if (typeof options.formData === 'function') {
149 | return options.formData(options.form);
150 | } else if ($.isArray(options.formData)) {
151 | return options.formData;
152 | } else if (options.formData) {
153 | formData = [];
154 | $.each(options.formData, function (name, value) {
155 | formData.push({name: name, value: value});
156 | });
157 | return formData;
158 | }
159 | return [];
160 | },
161 |
162 | _getTotal: function (files) {
163 | var total = 0;
164 | $.each(files, function (index, file) {
165 | total += file.size || 1;
166 | });
167 | return total;
168 | },
169 |
170 | _onProgress: function (e, data) {
171 | if (e.lengthComputable) {
172 | var total = data.total || this._getTotal(data.files),
173 | loaded = parseInt(
174 | e.loaded / e.total * (data.chunkSize || total),
175 | 10
176 | ) + (data.uploadedBytes || 0);
177 | this._loaded += loaded - (data.loaded || data.uploadedBytes || 0);
178 | data.lengthComputable = true;
179 | data.loaded = loaded;
180 | data.total = total;
181 | // Trigger a custom progress event with a total data property set
182 | // to the file size(s) of the current upload and a loaded data
183 | // property calculated accordingly:
184 | this._trigger('progress', e, data);
185 | // Trigger a global progress event for all current file uploads,
186 | // including ajax calls queued for sequential file uploads:
187 | this._trigger('progressall', e, {
188 | lengthComputable: true,
189 | loaded: this._loaded,
190 | total: this._total
191 | });
192 | }
193 | },
194 |
195 | _initProgressListener: function (options) {
196 | var that = this,
197 | xhr = options.xhr ? options.xhr() : $.ajaxSettings.xhr();
198 | // Accesss to the native XHR object is required to add event listeners
199 | // for the upload progress event:
200 | if (xhr.upload && xhr.upload.addEventListener) {
201 | xhr.upload.addEventListener('progress', function (e) {
202 | that._onProgress(e, options);
203 | }, false);
204 | options.xhr = function () {
205 | return xhr;
206 | };
207 | }
208 | },
209 |
210 | _initXHRData: function (options) {
211 | var formData,
212 | file = options.files[0];
213 | if (!options.multipart || options.blob) {
214 | // For non-multipart uploads and chunked uploads,
215 | // file meta data is not part of the request body,
216 | // so we transmit this data as part of the HTTP headers.
217 | // For cross domain requests, these headers must be allowed
218 | // via Access-Control-Allow-Headers or removed using
219 | // the beforeSend callback:
220 | options.headers = $.extend(options.headers, {
221 | 'X-File-Name': file.name,
222 | 'X-File-Type': file.type,
223 | 'X-File-Size': file.size
224 | });
225 | if (!options.blob) {
226 | // Non-chunked non-multipart upload:
227 | options.contentType = file.type;
228 | options.data = file;
229 | } else if (!options.multipart) {
230 | // Chunked non-multipart upload:
231 | options.contentType = 'application/octet-stream';
232 | options.data = options.blob;
233 | }
234 | }
235 | if (options.multipart && typeof FormData !== 'undefined') {
236 | if (options.formData instanceof FormData) {
237 | formData = options.formData;
238 | } else {
239 | formData = new FormData();
240 | $.each(this._getFormData(options), function (index, field) {
241 | formData.append(field.name, field.value);
242 | });
243 | }
244 | if (options.blob) {
245 | formData.append(options.paramName, options.blob);
246 | } else {
247 | $.each(options.files, function (index, file) {
248 | // File objects are also Blob instances.
249 | // This check allows the tests to run with
250 | // dummy objects:
251 | if (file instanceof Blob) {
252 | formData.append(options.paramName, file);
253 | }
254 | });
255 | }
256 | options.data = formData;
257 | }
258 | // Blob reference is not needed anymore, free memory:
259 | options.blob = null;
260 | },
261 |
262 | _initIframeSettings: function (options) {
263 | // Setting the dataType to iframe enables the iframe transport:
264 | options.dataType = 'iframe ' + (options.dataType || '');
265 | // The iframe transport accepts a serialized array as form data:
266 | options.formData = this._getFormData(options);
267 | },
268 |
269 | _initDataSettings: function (options) {
270 | if (this._isXHRUpload(options)) {
271 | if (!this._chunkedUpload(options, true)) {
272 | if (!options.data) {
273 | this._initXHRData(options);
274 | }
275 | this._initProgressListener(options);
276 | }
277 | } else {
278 | this._initIframeSettings(options);
279 | }
280 | },
281 |
282 | _initFormSettings: function (options) {
283 | // Retrieve missing options from the input field and the
284 | // associated form, if available:
285 | if (!options.form || !options.form.length) {
286 | options.form = $(options.fileInput.prop('form'));
287 | }
288 | if (!options.paramName) {
289 | options.paramName = options.fileInput.prop('name') ||
290 | 'files[]';
291 | }
292 | if (!options.url) {
293 | options.url = options.form.prop('action') || location.href;
294 | }
295 | // The HTTP request method must be "POST" or "PUT":
296 | options.type = (options.type || options.form.prop('method') || '')
297 | .toUpperCase();
298 | if (options.type !== 'POST' && options.type !== 'PUT') {
299 | options.type = 'POST';
300 | }
301 | },
302 |
303 | _getAJAXSettings: function (data) {
304 | var options = $.extend({}, this.options, data);
305 | this._initFormSettings(options);
306 | this._initDataSettings(options);
307 | return options;
308 | },
309 |
310 | // Maps jqXHR callbacks to the equivalent
311 | // methods of the given Promise object:
312 | _enhancePromise: function (promise) {
313 | promise.success = promise.done;
314 | promise.error = promise.fail;
315 | promise.complete = promise.always;
316 | return promise;
317 | },
318 |
319 | // Creates and returns a Promise object enhanced with
320 | // the jqXHR methods abort, success, error and complete:
321 | _getXHRPromise: function (resolveOrReject, context, args) {
322 | var dfd = $.Deferred(),
323 | promise = dfd.promise();
324 | context = context || this.options.context || promise;
325 | if (resolveOrReject === true) {
326 | dfd.resolveWith(context, args);
327 | } else if (resolveOrReject === false) {
328 | dfd.rejectWith(context, args);
329 | }
330 | promise.abort = dfd.promise;
331 | return this._enhancePromise(promise);
332 | },
333 |
334 | // Uploads a file in multiple, sequential requests
335 | // by splitting the file up in multiple blob chunks.
336 | // If the second parameter is true, only tests if the file
337 | // should be uploaded in chunks, but does not invoke any
338 | // upload requests:
339 | _chunkedUpload: function (options, testOnly) {
340 | var that = this,
341 | file = options.files[0],
342 | fs = file.size,
343 | ub = options.uploadedBytes = options.uploadedBytes || 0,
344 | mcs = options.maxChunkSize || fs,
345 | // Use the Blob methods with the slice implementation
346 | // according to the W3C Blob API specification:
347 | slice = file.webkitSlice || file.mozSlice || file.slice,
348 | upload,
349 | n,
350 | jqXHR,
351 | pipe;
352 | if (!(this._isXHRUpload(options) && slice && (ub || mcs < fs)) ||
353 | options.data) {
354 | return false;
355 | }
356 | if (testOnly) {
357 | return true;
358 | }
359 | if (ub >= fs) {
360 | file.error = 'uploadedBytes';
361 | return this._getXHRPromise(false);
362 | }
363 | // n is the number of blobs to upload,
364 | // calculated via filesize, uploaded bytes and max chunk size:
365 | n = Math.ceil((fs - ub) / mcs);
366 | // The chunk upload method accepting the chunk number as parameter:
367 | upload = function (i) {
368 | if (!i) {
369 | return that._getXHRPromise(true);
370 | }
371 | // Upload the blobs in sequential order:
372 | return upload(i -= 1).pipe(function () {
373 | // Clone the options object for each chunk upload:
374 | var o = $.extend({}, options);
375 | o.blob = slice.call(
376 | file,
377 | ub + i * mcs,
378 | ub + (i + 1) * mcs
379 | );
380 | // Store the current chunk size, as the blob itself
381 | // will be dereferenced after data processing:
382 | o.chunkSize = o.blob.size;
383 | // Process the upload data (the blob and potential form data):
384 | that._initXHRData(o);
385 | // Add progress listeners for this chunk upload:
386 | that._initProgressListener(o);
387 | jqXHR = ($.ajax(o) || that._getXHRPromise(false, o.context))
388 | .done(function () {
389 | // Create a progress event if upload is done and
390 | // no progress event has been invoked for this chunk:
391 | if (!o.loaded) {
392 | that._onProgress($.Event('progress', {
393 | lengthComputable: true,
394 | loaded: o.chunkSize,
395 | total: o.chunkSize
396 | }), o);
397 | }
398 | options.uploadedBytes = o.uploadedBytes
399 | += o.chunkSize;
400 | });
401 | return jqXHR;
402 | });
403 | };
404 | // Return the piped Promise object, enhanced with an abort method,
405 | // which is delegated to the jqXHR object of the current upload,
406 | // and jqXHR callbacks mapped to the equivalent Promise methods:
407 | pipe = upload(n);
408 | pipe.abort = function () {
409 | return jqXHR.abort();
410 | };
411 | return this._enhancePromise(pipe);
412 | },
413 |
414 | _beforeSend: function (e, data) {
415 | if (this._active === 0) {
416 | // the start callback is triggered when an upload starts
417 | // and no other uploads are currently running,
418 | // equivalent to the global ajaxStart event:
419 | this._trigger('start');
420 | }
421 | this._active += 1;
422 | // Initialize the global progress values:
423 | this._loaded += data.uploadedBytes || 0;
424 | this._total += this._getTotal(data.files);
425 | },
426 |
427 | _onDone: function (result, textStatus, jqXHR, options) {
428 | if (!this._isXHRUpload(options)) {
429 | // Create a progress event for each iframe load:
430 | this._onProgress($.Event('progress', {
431 | lengthComputable: true,
432 | loaded: 1,
433 | total: 1
434 | }), options);
435 | }
436 | options.result = result;
437 | options.textStatus = textStatus;
438 | options.jqXHR = jqXHR;
439 | this._trigger('done', null, options);
440 | },
441 |
442 | _onFail: function (jqXHR, textStatus, errorThrown, options) {
443 | options.jqXHR = jqXHR;
444 | options.textStatus = textStatus;
445 | options.errorThrown = errorThrown;
446 | this._trigger('fail', null, options);
447 | if (options.recalculateProgress) {
448 | // Remove the failed (error or abort) file upload from
449 | // the global progress calculation:
450 | this._loaded -= options.loaded || options.uploadedBytes || 0;
451 | this._total -= options.total || this._getTotal(options.files);
452 | }
453 | },
454 |
455 | _onAlways: function (result, textStatus, jqXHR, errorThrown, options) {
456 | this._active -= 1;
457 | options.result = result;
458 | options.textStatus = textStatus;
459 | options.jqXHR = jqXHR;
460 | options.errorThrown = errorThrown;
461 | this._trigger('always', null, options);
462 | if (this._active === 0) {
463 | // The stop callback is triggered when all uploads have
464 | // been completed, equivalent to the global ajaxStop event:
465 | this._trigger('stop');
466 | // Reset the global progress values:
467 | this._loaded = this._total = 0;
468 | }
469 | },
470 |
471 | _onSend: function (e, data) {
472 | var that = this,
473 | jqXHR,
474 | pipe,
475 | options = that._getAJAXSettings(data),
476 | send = function (resolve, args) {
477 | jqXHR = jqXHR || (
478 | (resolve !== false &&
479 | that._trigger('send', e, options) !== false &&
480 | (that._chunkedUpload(options) || $.ajax(options))) ||
481 | that._getXHRPromise(false, options.context, args)
482 | ).done(function (result, textStatus, jqXHR) {
483 | that._onDone(result, textStatus, jqXHR, options);
484 | }).fail(function (jqXHR, textStatus, errorThrown) {
485 | that._onFail(jqXHR, textStatus, errorThrown, options);
486 | }).always(function (a1, a2, a3) {
487 | if (a3 && a3.done) {
488 | that._onAlways(a1, a2, a3, undefined, options);
489 | } else {
490 | that._onAlways(undefined, a2, a1, a3, options);
491 | }
492 | });
493 | return jqXHR;
494 | };
495 | this._beforeSend(e, options);
496 | if (this.options.sequentialUploads) {
497 | // Return the piped Promise object, enhanced with an abort method,
498 | // which is delegated to the jqXHR object of the current upload,
499 | // and jqXHR callbacks mapped to the equivalent Promise methods:
500 | pipe = (this._sequence = this._sequence.pipe(send, send));
501 | pipe.abort = function () {
502 | if (!jqXHR) {
503 | return send(false, [undefined, 'abort', 'abort']);
504 | }
505 | return jqXHR.abort();
506 | };
507 | return this._enhancePromise(pipe);
508 | }
509 | return send();
510 | },
511 |
512 | _onAdd: function (e, data) {
513 | var that = this,
514 | result = true,
515 | options = $.extend({}, this.options, data);
516 | if (options.singleFileUploads && this._isXHRUpload(options)) {
517 | $.each(data.files, function (index, file) {
518 | var newData = $.extend({}, data, {files: [file]});
519 | newData.submit = function () {
520 | return that._onSend(e, newData);
521 | };
522 | return (result = that._trigger('add', e, newData));
523 | });
524 | return result;
525 | } else if (data.files.length) {
526 | data = $.extend({}, data);
527 | data.submit = function () {
528 | return that._onSend(e, data);
529 | };
530 | return this._trigger('add', e, data);
531 | }
532 | },
533 |
534 | // File Normalization for Gecko 1.9.1 (Firefox 3.5) support:
535 | _normalizeFile: function (index, file) {
536 | if (file.name === undefined && file.size === undefined) {
537 | file.name = file.fileName;
538 | file.size = file.fileSize;
539 | }
540 | },
541 |
542 | _replaceFileInput: function (input) {
543 | var inputClone = input.clone(true);
544 | $('