s3_cors_fileupload 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. data/.document +5 -0
  2. data/.gitignore +51 -0
  3. data/.rspec +1 -0
  4. data/Gemfile +14 -0
  5. data/Gemfile.lock +39 -0
  6. data/LICENSE.txt +20 -0
  7. data/README.md +104 -0
  8. data/Rakefile +58 -0
  9. data/lib/generators/s3_cors_fileupload/install/USAGE +17 -0
  10. data/lib/generators/s3_cors_fileupload/install/install_generator.rb +51 -0
  11. data/lib/generators/s3_cors_fileupload/install/templates/amazon_s3.yml +17 -0
  12. data/lib/generators/s3_cors_fileupload/install/templates/create_source_files.rb +14 -0
  13. data/lib/generators/s3_cors_fileupload/install/templates/s3_uploads.js +94 -0
  14. data/lib/generators/s3_cors_fileupload/install/templates/s3_uploads_controller.rb +90 -0
  15. data/lib/generators/s3_cors_fileupload/install/templates/source_file.rb +53 -0
  16. data/lib/generators/s3_cors_fileupload/install/templates/views/_template_download.html.erb +29 -0
  17. data/lib/generators/s3_cors_fileupload/install/templates/views/_template_upload.html.erb +31 -0
  18. data/lib/generators/s3_cors_fileupload/install/templates/views/_template_uploaded.html.erb +25 -0
  19. data/lib/generators/s3_cors_fileupload/install/templates/views/index.html.erb +43 -0
  20. data/lib/s3_cors_fileupload.rb +2 -0
  21. data/lib/s3_cors_fileupload/rails.rb +8 -0
  22. data/lib/s3_cors_fileupload/rails/config.rb +27 -0
  23. data/lib/s3_cors_fileupload/rails/engine.rb +6 -0
  24. data/lib/s3_cors_fileupload/rails/form_helper.rb +91 -0
  25. data/lib/s3_cors_fileupload/rails/policy_helper.rb +48 -0
  26. data/lib/s3_cors_fileupload/version.rb +5 -0
  27. data/s3_cors_fileupload.gemspec +35 -0
  28. data/spec/s3_cors_fileupload/version_spec.rb +17 -0
  29. data/spec/s3_cors_fileupload_spec.rb +9 -0
  30. data/spec/spec_helper.rb +16 -0
  31. data/vendor/assets/images/loading.gif +0 -0
  32. data/vendor/assets/images/progressbar.gif +0 -0
  33. data/vendor/assets/javascripts/s3_cors_fileupload/index.js +6 -0
  34. data/vendor/assets/javascripts/s3_cors_fileupload/jquery.fileupload-ui.js +732 -0
  35. data/vendor/assets/javascripts/s3_cors_fileupload/jquery.fileupload.js +1106 -0
  36. data/vendor/assets/javascripts/s3_cors_fileupload/jquery.iframe-transport.js +172 -0
  37. data/vendor/assets/javascripts/s3_cors_fileupload/vendor/jquery.ui.widget.js +511 -0
  38. data/vendor/assets/javascripts/s3_cors_fileupload/vendor/load-image.js +122 -0
  39. data/vendor/assets/javascripts/s3_cors_fileupload/vendor/tmpl.js +87 -0
  40. data/vendor/assets/stylesheets/jquery.fileupload-ui.css.erb +85 -0
  41. metadata +205 -0
@@ -0,0 +1,5 @@
1
+ module S3CorsFileupload
2
+ VERSION = '0.1.1'
3
+ JQUERY_FILEUPLOAD_VERSION = '5.19'
4
+ JQUERY_FILEUPLOAD_UI_VERSION = '6.10'
5
+ end
@@ -0,0 +1,35 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/s3_cors_fileupload/version', __FILE__)
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = "s3_cors_fileupload"
6
+ s.version = S3CorsFileupload::VERSION
7
+ s.platform = Gem::Platform::RUBY
8
+ s.authors = ["Ben Atkins"]
9
+ s.email = ["benatkins@fullbridge.com"]
10
+ s.homepage = "http://github.com/fullbridge-batkins/s3_cors_fileupload"
11
+ s.summary = "File uploads for Rails ~> 3.1 to AWS-S3 via CORS using the jQuery-File-Upload script"
12
+ s.description = "A Ruby gem for providing File uploads for Rails ~> 3.1 to AWS-S3 via CORS using the jQuery-File-Upload script"
13
+ s.licenses = ["MIT"]
14
+
15
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
16
+ s.rubygems_version = "1.8.24"
17
+
18
+ s.add_dependency('rails', ['~> 3.1'])
19
+ s.add_dependency('jquery-rails', ['>= 2.0'])
20
+ s.add_dependency('aws-s3', ['~> 0.6']) # :require => 'aws/s3'
21
+
22
+ s.add_development_dependency('rake', [">= 0.8.7"])
23
+ s.add_development_dependency('bundler', [">= 0"])
24
+ s.add_development_dependency('rdoc', ["~> 3.12"])
25
+ s.add_development_dependency('jeweler', ["~> 1.8.4"])
26
+
27
+ s.files = `git ls-files`.split("\n")
28
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
29
+ s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
30
+ s.require_paths = ['lib']
31
+ s.extra_rdoc_files = [
32
+ "LICENSE.txt",
33
+ "README.md"
34
+ ]
35
+ end
@@ -0,0 +1,17 @@
1
+ require 'spec_helper'
2
+
3
+ describe S3CorsFileupload do
4
+
5
+ describe "Constants" do
6
+ describe :VERSION do
7
+ it { S3CorsFileupload.const_defined?(:VERSION).should be_true }
8
+ end
9
+ describe :JQUERY_FILEUPLOAD_VERSION do
10
+ it { S3CorsFileupload.const_defined?(:JQUERY_FILEUPLOAD_VERSION).should be_true }
11
+ end
12
+ describe :JQUERY_FILEUPLOAD_UI_VERSION do
13
+ it { S3CorsFileupload.const_defined?(:JQUERY_FILEUPLOAD_UI_VERSION).should be_true }
14
+ end
15
+ end
16
+
17
+ end
@@ -0,0 +1,9 @@
1
+ require 'spec_helper'
2
+
3
+ describe S3CorsFileupload do
4
+
5
+ it { should be_instance_of(Module) }
6
+
7
+ # we will need to write some additional specs for the module within the context of a Rails app,
8
+ # since much of the functionality doesn't get required unless Rails is defined.
9
+ end
@@ -0,0 +1,16 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+
4
+ require File.expand_path('../../lib/s3_cors_fileupload', __FILE__)
5
+
6
+
7
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
8
+ RSpec.configure do |config|
9
+ config.treat_symbols_as_metadata_keys_with_true_values = true
10
+
11
+ # Run specs in random order to surface order dependencies. If you find an
12
+ # order dependency and want to debug it, you can fix the order by providing
13
+ # the seed, which is printed after each run.
14
+ # --seed 1234
15
+ config.order = 'random'
16
+ end
@@ -0,0 +1,6 @@
1
+ //= require s3_cors_fileupload/vendor/jquery.ui.widget
2
+ //= require s3_cors_fileupload/vendor/tmpl
3
+ //= require s3_cors_fileupload/vendor/load-image
4
+ //= require s3_cors_fileupload/jquery.iframe-transport
5
+ //= require s3_cors_fileupload/jquery.fileupload
6
+ //= require s3_cors_fileupload/jquery.fileupload-ui
@@ -0,0 +1,732 @@
1
+ /*
2
+ * jQuery File Upload User Interface Plugin 6.10
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://www.opensource.org/licenses/MIT
10
+ */
11
+
12
+ /*jslint nomen: true, unparam: true, regexp: true */
13
+ /*global define, window, URL, webkitURL, FileReader */
14
+
15
+ (function (factory) {
16
+ 'use strict';
17
+ if (typeof define === 'function' && define.amd) {
18
+ // Register as an anonymous AMD module:
19
+ define([
20
+ 'jquery',
21
+ 'tmpl',
22
+ 'load-image',
23
+ './jquery.fileupload-fp'
24
+ ], factory);
25
+ } else {
26
+ // Browser globals:
27
+ factory(
28
+ window.jQuery,
29
+ window.tmpl,
30
+ window.loadImage
31
+ );
32
+ }
33
+ }(function ($, tmpl, loadImage) {
34
+ 'use strict';
35
+
36
+ // The UI version extends the file upload widget
37
+ // and adds complete user interface interaction:
38
+ $.widget('blueimp.fileupload', $.blueimp.fileupload, {
39
+
40
+ options: {
41
+ // By default, files added to the widget are uploaded as soon
42
+ // as the user clicks on the start buttons. To enable automatic
43
+ // uploads, set the following option to true:
44
+ autoUpload: false,
45
+ // The following option limits the number of files that are
46
+ // allowed to be uploaded using this widget:
47
+ maxNumberOfFiles: undefined,
48
+ // The maximum allowed file size:
49
+ maxFileSize: undefined,
50
+ // The minimum allowed file size:
51
+ minFileSize: undefined,
52
+ // The regular expression for allowed file types, matches
53
+ // against either file type or file name:
54
+ acceptFileTypes: /.+$/i,
55
+ // The regular expression to define for which files a preview
56
+ // image is shown, matched against the file type:
57
+ previewSourceFileTypes: /^image\/(gif|jpeg|png)$/,
58
+ // The maximum file size of images that are to be displayed as preview:
59
+ previewSourceMaxFileSize: 5000000, // 5MB
60
+ // The maximum width of the preview images:
61
+ previewMaxWidth: 80,
62
+ // The maximum height of the preview images:
63
+ previewMaxHeight: 80,
64
+ // By default, preview images are displayed as canvas elements
65
+ // if supported by the browser. Set the following option to false
66
+ // to always display preview images as img elements:
67
+ previewAsCanvas: true,
68
+ // The ID of the upload template:
69
+ uploadTemplateId: 'template-upload',
70
+ // The ID of the download template:
71
+ downloadTemplateId: 'template-download',
72
+ // The container for the list of files. If undefined, it is set to
73
+ // an element with class "files" inside of the widget element:
74
+ filesContainer: undefined,
75
+ // By default, files are appended to the files container.
76
+ // Set the following option to true, to prepend files instead:
77
+ prependFiles: false,
78
+ // The expected data type of the upload response, sets the dataType
79
+ // option of the $.ajax upload requests:
80
+ dataType: 'json',
81
+
82
+ // The add callback is invoked as soon as files are added to the fileupload
83
+ // widget (via file input selection, drag & drop or add API call).
84
+ // See the basic file upload widget for more information:
85
+ add: function (e, data) {
86
+ var that = $(this).data('fileupload'),
87
+ options = that.options,
88
+ files = data.files;
89
+ $(this).fileupload('process', data).done(function () {
90
+ that._adjustMaxNumberOfFiles(-files.length);
91
+ data.maxNumberOfFilesAdjusted = true;
92
+ data.files.valid = data.isValidated = that._validate(files);
93
+ data.context = that._renderUpload(files).data('data', data);
94
+ options.filesContainer[
95
+ options.prependFiles ? 'prepend' : 'append'
96
+ ](data.context);
97
+ that._renderPreviews(files, data.context);
98
+ that._forceReflow(data.context);
99
+ that._transition(data.context).done(
100
+ function () {
101
+ if ((that._trigger('added', e, data) !== false) &&
102
+ (options.autoUpload || data.autoUpload) &&
103
+ data.autoUpload !== false && data.isValidated) {
104
+ data.submit();
105
+ }
106
+ }
107
+ );
108
+ });
109
+ },
110
+ // Callback for the start of each file upload request:
111
+ send: function (e, data) {
112
+ var that = $(this).data('fileupload');
113
+ if (!data.isValidated) {
114
+ if (!data.maxNumberOfFilesAdjusted) {
115
+ that._adjustMaxNumberOfFiles(-data.files.length);
116
+ data.maxNumberOfFilesAdjusted = true;
117
+ }
118
+ if (!that._validate(data.files)) {
119
+ return false;
120
+ }
121
+ }
122
+ if (data.context && data.dataType &&
123
+ data.dataType.substr(0, 6) === 'iframe') {
124
+ // Iframe Transport does not support progress events.
125
+ // In lack of an indeterminate progress bar, we set
126
+ // the progress to 100%, showing the full animated bar:
127
+ data.context
128
+ .find('.progress').addClass(
129
+ !$.support.transition && 'progress-animated'
130
+ )
131
+ .attr('aria-valuenow', 100)
132
+ .find('.bar').css(
133
+ 'width',
134
+ '100%'
135
+ );
136
+ }
137
+ return that._trigger('sent', e, data);
138
+ },
139
+ // Callback for successful uploads:
140
+ done: function (e, data) {
141
+ var that = $(this).data('fileupload'),
142
+ template;
143
+ if (data.context) {
144
+ data.context.each(function (index) {
145
+ var file = ($.isArray(data.result) &&
146
+ data.result[index]) ||
147
+ {error: 'Empty file upload result'};
148
+ if (file.error) {
149
+ that._adjustMaxNumberOfFiles(1);
150
+ }
151
+ that._transition($(this)).done(
152
+ function () {
153
+ var node = $(this);
154
+ template = that._renderDownload([file])
155
+ .replaceAll(node);
156
+ that._forceReflow(template);
157
+ that._transition(template).done(
158
+ function () {
159
+ data.context = $(this);
160
+ that._trigger('completed', e, data);
161
+ }
162
+ );
163
+ }
164
+ );
165
+ });
166
+ } else {
167
+ if ($.isArray(data.result)) {
168
+ $.each(data.result, function (index, file) {
169
+ if (data.maxNumberOfFilesAdjusted && file.error) {
170
+ that._adjustMaxNumberOfFiles(1);
171
+ } else if (!data.maxNumberOfFilesAdjusted &&
172
+ !file.error) {
173
+ that._adjustMaxNumberOfFiles(-1);
174
+ }
175
+ });
176
+ data.maxNumberOfFilesAdjusted = true;
177
+ }
178
+ template = that._renderDownload(data.result)
179
+ .appendTo(that.options.filesContainer);
180
+ that._forceReflow(template);
181
+ that._transition(template).done(
182
+ function () {
183
+ data.context = $(this);
184
+ that._trigger('completed', e, data);
185
+ }
186
+ );
187
+ }
188
+ },
189
+ // Callback for failed (abort or error) uploads:
190
+ fail: function (e, data) {
191
+ var that = $(this).data('fileupload'),
192
+ template;
193
+ if (data.maxNumberOfFilesAdjusted) {
194
+ that._adjustMaxNumberOfFiles(data.files.length);
195
+ }
196
+ if (data.context) {
197
+ data.context.each(function (index) {
198
+ if (data.errorThrown !== 'abort') {
199
+ var file = data.files[index];
200
+ file.error = file.error || data.errorThrown ||
201
+ true;
202
+ that._transition($(this)).done(
203
+ function () {
204
+ var node = $(this);
205
+ template = that._renderDownload([file])
206
+ .replaceAll(node);
207
+ that._forceReflow(template);
208
+ that._transition(template).done(
209
+ function () {
210
+ data.context = $(this);
211
+ that._trigger('failed', e, data);
212
+ }
213
+ );
214
+ }
215
+ );
216
+ } else {
217
+ that._transition($(this)).done(
218
+ function () {
219
+ $(this).remove();
220
+ that._trigger('failed', e, data);
221
+ }
222
+ );
223
+ }
224
+ });
225
+ } else if (data.errorThrown !== 'abort') {
226
+ data.context = that._renderUpload(data.files)
227
+ .appendTo(that.options.filesContainer)
228
+ .data('data', data);
229
+ that._forceReflow(data.context);
230
+ that._transition(data.context).done(
231
+ function () {
232
+ data.context = $(this);
233
+ that._trigger('failed', e, data);
234
+ }
235
+ );
236
+ } else {
237
+ that._trigger('failed', e, data);
238
+ }
239
+ },
240
+ // Callback for upload progress events:
241
+ progress: function (e, data) {
242
+ if (data.context) {
243
+ var progress = parseInt(data.loaded / data.total * 100, 10);
244
+ data.context.find('.progress')
245
+ .attr('aria-valuenow', progress)
246
+ .find('.bar').css(
247
+ 'width',
248
+ progress + '%'
249
+ );
250
+ }
251
+ },
252
+ // Callback for global upload progress events:
253
+ progressall: function (e, data) {
254
+ var $this = $(this),
255
+ progress = parseInt(data.loaded / data.total * 100, 10),
256
+ globalProgressNode = $this.find('.fileupload-progress'),
257
+ extendedProgressNode = globalProgressNode
258
+ .find('.progress-extended');
259
+ if (extendedProgressNode.length) {
260
+ extendedProgressNode.html(
261
+ $this.data('fileupload')._renderExtendedProgress(data)
262
+ );
263
+ }
264
+ globalProgressNode
265
+ .find('.progress')
266
+ .attr('aria-valuenow', progress)
267
+ .find('.bar').css(
268
+ 'width',
269
+ progress + '%'
270
+ );
271
+ },
272
+ // Callback for uploads start, equivalent to the global ajaxStart event:
273
+ start: function (e) {
274
+ var that = $(this).data('fileupload');
275
+ that._transition($(this).find('.fileupload-progress')).done(
276
+ function () {
277
+ that._trigger('started', e);
278
+ }
279
+ );
280
+ },
281
+ // Callback for uploads stop, equivalent to the global ajaxStop event:
282
+ stop: function (e) {
283
+ var that = $(this).data('fileupload');
284
+ that._transition($(this).find('.fileupload-progress')).done(
285
+ function () {
286
+ $(this).find('.progress')
287
+ .attr('aria-valuenow', '0')
288
+ .find('.bar').css('width', '0%');
289
+ $(this).find('.progress-extended').html(' ');
290
+ that._trigger('stopped', e);
291
+ }
292
+ );
293
+ },
294
+ // Callback for file deletion:
295
+ destroy: function (e, data) {
296
+ var that = $(this).data('fileupload');
297
+ if (data.url) {
298
+ $.ajax(data);
299
+ that._adjustMaxNumberOfFiles(1);
300
+ }
301
+ that._transition(data.context).done(
302
+ function () {
303
+ $(this).remove();
304
+ that._trigger('destroyed', e, data);
305
+ }
306
+ );
307
+ }
308
+ },
309
+
310
+ // Link handler, that allows to download files
311
+ // by drag & drop of the links to the desktop:
312
+ _enableDragToDesktop: function () {
313
+ var link = $(this),
314
+ url = link.prop('href'),
315
+ name = link.prop('download'),
316
+ type = 'application/octet-stream';
317
+ link.bind('dragstart', function (e) {
318
+ try {
319
+ e.originalEvent.dataTransfer.setData(
320
+ 'DownloadURL',
321
+ [type, name, url].join(':')
322
+ );
323
+ } catch (err) {}
324
+ });
325
+ },
326
+
327
+ _adjustMaxNumberOfFiles: function (operand) {
328
+ if (typeof this.options.maxNumberOfFiles === 'number') {
329
+ this.options.maxNumberOfFiles += operand;
330
+ if (this.options.maxNumberOfFiles < 1) {
331
+ this._disableFileInputButton();
332
+ } else {
333
+ this._enableFileInputButton();
334
+ }
335
+ }
336
+ },
337
+
338
+ _formatFileSize: function (bytes) {
339
+ if (typeof bytes !== 'number') {
340
+ return '';
341
+ }
342
+ if (bytes >= 1000000000) {
343
+ return (bytes / 1000000000).toFixed(2) + ' GB';
344
+ }
345
+ if (bytes >= 1000000) {
346
+ return (bytes / 1000000).toFixed(2) + ' MB';
347
+ }
348
+ return (bytes / 1000).toFixed(2) + ' KB';
349
+ },
350
+
351
+ _formatBitrate: function (bits) {
352
+ if (typeof bits !== 'number') {
353
+ return '';
354
+ }
355
+ if (bits >= 1000000000) {
356
+ return (bits / 1000000000).toFixed(2) + ' Gbit/s';
357
+ }
358
+ if (bits >= 1000000) {
359
+ return (bits / 1000000).toFixed(2) + ' Mbit/s';
360
+ }
361
+ if (bits >= 1000) {
362
+ return (bits / 1000).toFixed(2) + ' kbit/s';
363
+ }
364
+ return bits + ' bit/s';
365
+ },
366
+
367
+ _formatTime: function (seconds) {
368
+ var date = new Date(seconds * 1000),
369
+ days = parseInt(seconds / 86400, 10);
370
+ days = days ? days + 'd ' : '';
371
+ return days +
372
+ ('0' + date.getUTCHours()).slice(-2) + ':' +
373
+ ('0' + date.getUTCMinutes()).slice(-2) + ':' +
374
+ ('0' + date.getUTCSeconds()).slice(-2);
375
+ },
376
+
377
+ _formatPercentage: function (floatValue) {
378
+ return (floatValue * 100).toFixed(2) + ' %';
379
+ },
380
+
381
+ _renderExtendedProgress: function (data) {
382
+ return this._formatBitrate(data.bitrate) + ' | ' +
383
+ this._formatTime(
384
+ (data.total - data.loaded) * 8 / data.bitrate
385
+ ) + ' | ' +
386
+ this._formatPercentage(
387
+ data.loaded / data.total
388
+ ) + ' | ' +
389
+ this._formatFileSize(data.loaded) + ' / ' +
390
+ this._formatFileSize(data.total);
391
+ },
392
+
393
+ _hasError: function (file) {
394
+ if (file.error) {
395
+ return file.error;
396
+ }
397
+ // The number of added files is subtracted from
398
+ // maxNumberOfFiles before validation, so we check if
399
+ // maxNumberOfFiles is below 0 (instead of below 1):
400
+ if (this.options.maxNumberOfFiles < 0) {
401
+ return 'Maximum number of files exceeded';
402
+ }
403
+ // Files are accepted if either the file type or the file name
404
+ // matches against the acceptFileTypes regular expression, as
405
+ // only browsers with support for the File API report the type:
406
+ if (!(this.options.acceptFileTypes.test(file.type) ||
407
+ this.options.acceptFileTypes.test(file.name))) {
408
+ return 'Filetype not allowed';
409
+ }
410
+ if (this.options.maxFileSize &&
411
+ file.size > this.options.maxFileSize) {
412
+ return 'File is too big';
413
+ }
414
+ if (typeof file.size === 'number' &&
415
+ file.size < this.options.minFileSize) {
416
+ return 'File is too small';
417
+ }
418
+ return null;
419
+ },
420
+
421
+ _validate: function (files) {
422
+ var that = this,
423
+ valid = !!files.length;
424
+ $.each(files, function (index, file) {
425
+ file.error = that._hasError(file);
426
+ if (file.error) {
427
+ valid = false;
428
+ }
429
+ });
430
+ return valid;
431
+ },
432
+
433
+ _renderTemplate: function (func, files) {
434
+ if (!func) {
435
+ return $();
436
+ }
437
+ var result = func({
438
+ files: files,
439
+ formatFileSize: this._formatFileSize,
440
+ options: this.options
441
+ });
442
+ if (result instanceof $) {
443
+ return result;
444
+ }
445
+ return $(this.options.templatesContainer).html(result).children();
446
+ },
447
+
448
+ _renderPreview: function (file, node) {
449
+ var that = this,
450
+ options = this.options,
451
+ dfd = $.Deferred();
452
+ return ((loadImage && loadImage(
453
+ file,
454
+ function (img) {
455
+ node.append(img);
456
+ that._forceReflow(node);
457
+ that._transition(node).done(function () {
458
+ dfd.resolveWith(node);
459
+ });
460
+ if (!$.contains(that.document[0].body, node[0])) {
461
+ // If the element is not part of the DOM,
462
+ // transition events are not triggered,
463
+ // so we have to resolve manually:
464
+ dfd.resolveWith(node);
465
+ }
466
+ },
467
+ {
468
+ maxWidth: options.previewMaxWidth,
469
+ maxHeight: options.previewMaxHeight,
470
+ canvas: options.previewAsCanvas
471
+ }
472
+ )) || dfd.resolveWith(node)) && dfd;
473
+ },
474
+
475
+ _renderPreviews: function (files, nodes) {
476
+ var that = this,
477
+ options = this.options;
478
+ nodes.find('.preview span').each(function (index, element) {
479
+ var file = files[index];
480
+ if (options.previewSourceFileTypes.test(file.type) &&
481
+ ($.type(options.previewSourceMaxFileSize) !== 'number' ||
482
+ file.size < options.previewSourceMaxFileSize)) {
483
+ that._processingQueue = that._processingQueue.pipe(function () {
484
+ var dfd = $.Deferred();
485
+ that._renderPreview(file, $(element)).done(
486
+ function () {
487
+ dfd.resolveWith(that);
488
+ }
489
+ );
490
+ return dfd.promise();
491
+ });
492
+ }
493
+ });
494
+ return this._processingQueue;
495
+ },
496
+
497
+ _renderUpload: function (files) {
498
+ return this._renderTemplate(
499
+ this.options.uploadTemplate,
500
+ files
501
+ );
502
+ },
503
+
504
+ _renderDownload: function (files) {
505
+ return this._renderTemplate(
506
+ this.options.downloadTemplate,
507
+ files
508
+ ).find('a[download]').each(this._enableDragToDesktop).end();
509
+ },
510
+
511
+ _startHandler: function (e) {
512
+ e.preventDefault();
513
+ var button = $(e.currentTarget),
514
+ template = button.closest('.template-upload'),
515
+ data = template.data('data');
516
+ if (data && data.submit && !data.jqXHR && data.submit()) {
517
+ button.prop('disabled', true);
518
+ }
519
+ },
520
+
521
+ _cancelHandler: function (e) {
522
+ e.preventDefault();
523
+ var template = $(e.currentTarget).closest('.template-upload'),
524
+ data = template.data('data') || {};
525
+ if (!data.jqXHR) {
526
+ data.errorThrown = 'abort';
527
+ this._trigger('fail', e, data);
528
+ } else {
529
+ data.jqXHR.abort();
530
+ }
531
+ },
532
+
533
+ _deleteHandler: function (e) {
534
+ e.preventDefault();
535
+ var button = $(e.currentTarget);
536
+ this._trigger('destroy', e, {
537
+ context: button.closest('.template-download'),
538
+ url: button.attr('data-url'),
539
+ type: button.attr('data-type') || 'DELETE',
540
+ dataType: this.options.dataType
541
+ });
542
+ },
543
+
544
+ _forceReflow: function (node) {
545
+ return $.support.transition && node.length &&
546
+ node[0].offsetWidth;
547
+ },
548
+
549
+ _transition: function (node) {
550
+ var dfd = $.Deferred();
551
+ if ($.support.transition && node.hasClass('fade')) {
552
+ node.bind(
553
+ $.support.transition.end,
554
+ function (e) {
555
+ // Make sure we don't respond to other transitions events
556
+ // in the container element, e.g. from button elements:
557
+ if (e.target === node[0]) {
558
+ node.unbind($.support.transition.end);
559
+ dfd.resolveWith(node);
560
+ }
561
+ }
562
+ ).toggleClass('in');
563
+ } else {
564
+ node.toggleClass('in');
565
+ dfd.resolveWith(node);
566
+ }
567
+ return dfd;
568
+ },
569
+
570
+ _initButtonBarEventHandlers: function () {
571
+ var fileUploadButtonBar = this.element.find('.fileupload-buttonbar'),
572
+ filesList = this.options.filesContainer;
573
+ this._on(fileUploadButtonBar.find('.start'), {
574
+ click: function (e) {
575
+ e.preventDefault();
576
+ filesList.find('.start button').click();
577
+ }
578
+ });
579
+ this._on(fileUploadButtonBar.find('.cancel'), {
580
+ click: function (e) {
581
+ e.preventDefault();
582
+ filesList.find('.cancel button').click();
583
+ }
584
+ });
585
+ this._on(fileUploadButtonBar.find('.delete'), {
586
+ click: function (e) {
587
+ e.preventDefault();
588
+ filesList.find('.delete input:checked')
589
+ .siblings('button').click();
590
+ fileUploadButtonBar.find('.toggle')
591
+ .prop('checked', false);
592
+ }
593
+ });
594
+ this._on(fileUploadButtonBar.find('.toggle'), {
595
+ change: function (e) {
596
+ filesList.find('.delete input').prop(
597
+ 'checked',
598
+ $(e.currentTarget).is(':checked')
599
+ );
600
+ }
601
+ });
602
+ },
603
+
604
+ _destroyButtonBarEventHandlers: function () {
605
+ this._off(
606
+ this.element.find('.fileupload-buttonbar button'),
607
+ 'click'
608
+ );
609
+ this._off(
610
+ this.element.find('.fileupload-buttonbar .toggle'),
611
+ 'change.'
612
+ );
613
+ },
614
+
615
+ _initEventHandlers: function () {
616
+ this._super();
617
+ this._on(this.options.filesContainer, {
618
+ 'click .start button': this._startHandler,
619
+ 'click .cancel button': this._cancelHandler,
620
+ 'click .delete button': this._deleteHandler
621
+ });
622
+ this._initButtonBarEventHandlers();
623
+ },
624
+
625
+ _destroyEventHandlers: function () {
626
+ this._destroyButtonBarEventHandlers();
627
+ this._off(this.options.filesContainer, 'click');
628
+ this._super();
629
+ },
630
+
631
+ _enableFileInputButton: function () {
632
+ this.element.find('.fileinput-button input')
633
+ .prop('disabled', false)
634
+ .parent().removeClass('disabled');
635
+ },
636
+
637
+ _disableFileInputButton: function () {
638
+ this.element.find('.fileinput-button input')
639
+ .prop('disabled', true)
640
+ .parent().addClass('disabled');
641
+ },
642
+
643
+ _initTemplates: function () {
644
+ var options = this.options;
645
+ options.templatesContainer = this.document[0].createElement(
646
+ options.filesContainer.prop('nodeName')
647
+ );
648
+ if (tmpl) {
649
+ if (options.uploadTemplateId) {
650
+ options.uploadTemplate = tmpl(options.uploadTemplateId);
651
+ }
652
+ if (options.downloadTemplateId) {
653
+ options.downloadTemplate = tmpl(options.downloadTemplateId);
654
+ }
655
+ }
656
+ },
657
+
658
+ _initFilesContainer: function () {
659
+ var options = this.options;
660
+ if (options.filesContainer === undefined) {
661
+ options.filesContainer = this.element.find('.files');
662
+ } else if (!(options.filesContainer instanceof $)) {
663
+ options.filesContainer = $(options.filesContainer);
664
+ }
665
+ },
666
+
667
+ _stringToRegExp: function (str) {
668
+ var parts = str.split('/'),
669
+ modifiers = parts.pop();
670
+ parts.shift();
671
+ return new RegExp(parts.join('/'), modifiers);
672
+ },
673
+
674
+ _initRegExpOptions: function () {
675
+ var options = this.options;
676
+ if ($.type(options.acceptFileTypes) === 'string') {
677
+ options.acceptFileTypes = this._stringToRegExp(
678
+ options.acceptFileTypes
679
+ );
680
+ }
681
+ if ($.type(options.previewSourceFileTypes) === 'string') {
682
+ options.previewSourceFileTypes = this._stringToRegExp(
683
+ options.previewSourceFileTypes
684
+ );
685
+ }
686
+ },
687
+
688
+ _initSpecialOptions: function () {
689
+ this._super();
690
+ this._initFilesContainer();
691
+ this._initTemplates();
692
+ this._initRegExpOptions();
693
+ },
694
+
695
+ _create: function () {
696
+ this._super();
697
+ this._refreshOptionsList.push(
698
+ 'filesContainer',
699
+ 'uploadTemplateId',
700
+ 'downloadTemplateId'
701
+ );
702
+ if (!this._processingQueue) {
703
+ this._processingQueue = $.Deferred().resolveWith(this).promise();
704
+ this.process = function () {
705
+ return this._processingQueue;
706
+ };
707
+ }
708
+ },
709
+
710
+ enable: function () {
711
+ var wasDisabled = false;
712
+ if (this.options.disabled) {
713
+ wasDisabled = true;
714
+ }
715
+ this._super();
716
+ if (wasDisabled) {
717
+ this.element.find('input, button').prop('disabled', false);
718
+ this._enableFileInputButton();
719
+ }
720
+ },
721
+
722
+ disable: function () {
723
+ if (!this.options.disabled) {
724
+ this.element.find('input, button').prop('disabled', true);
725
+ this._disableFileInputButton();
726
+ }
727
+ this._super();
728
+ }
729
+
730
+ });
731
+
732
+ }));