jquery-fileuploads-rails4 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +4 -0
  3. data/.gitmodules +3 -0
  4. data/Gemfile +4 -0
  5. data/Rakefile +40 -0
  6. data/Readme.md +92 -0
  7. data/demo/.gitignore +3 -0
  8. data/demo/Gemfile +8 -0
  9. data/demo/Rakefile +2 -0
  10. data/demo/Readme.md +12 -0
  11. data/demo/app/assets/javascripts/application.js.coffee +9 -0
  12. data/demo/app/controllers/application_controller.rb +7 -0
  13. data/demo/app/views/application/basic.html.erb +12 -0
  14. data/demo/app/views/application/create.html.erb +1 -0
  15. data/demo/app/views/application/ui.html.erb +1 -0
  16. data/demo/app/views/layouts/application.html.erb +13 -0
  17. data/demo/bin/bundle +3 -0
  18. data/demo/bin/rails +4 -0
  19. data/demo/bin/rake +4 -0
  20. data/demo/config.ru +4 -0
  21. data/demo/config/application.rb +21 -0
  22. data/demo/config/boot.rb +6 -0
  23. data/demo/config/environment.rb +5 -0
  24. data/demo/config/routes.rb +6 -0
  25. data/demo/log/.gitkeep +0 -0
  26. data/dependencies.json +4 -0
  27. data/jquery-fileuploads-rails4.gemspec +17 -0
  28. data/lib/jquery-fileuploads-rails4.rb +9 -0
  29. data/vendor/assets/javascripts/jquery.fileupload.js +1460 -0
  30. data/vendor/assets/javascripts/jquery.iframe-transport.js +214 -0
  31. data/vendor/legacy_assets/javascripts/jquery.fileupload-fp.js +219 -0
  32. data/vendor/legacy_assets/javascripts/jquery.fileupload-ip.js +160 -0
  33. data/vendor/legacy_assets/javascripts/jquery.fileupload-ui.js +702 -0
  34. data/vendor/legacy_assets/javascripts/jquery.postmessage-transport.js +117 -0
  35. data/vendor/legacy_assets/javascripts/jquery.xdr-transport.js +85 -0
  36. data/vendor/legacy_assets/stylesheets/jquery.fileupload-ui.css +84 -0
  37. metadata +107 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 9aab1fb6bbadf2b7c6a1c37a235b3fb97eb97892
4
+ data.tar.gz: bf083aec3cc26a19ee394612a103b1e5fb8ccd27
5
+ SHA512:
6
+ metadata.gz: ae786e899b8d0d46b872ae285306b039b4eb78f747b8746b99e183b8fa75197a29fed7aff26e8a33ca986b7566ee1a5bf3a235043d3428ca308399b37d650402
7
+ data.tar.gz: 62330dadc5f46b737c315474ab80e139931d8d584a5a8095f8cdf180431b30086499cd44ea54207646c51af26f332f420acba296bc47a42172e402a5e3015e51
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/.gitmodules ADDED
@@ -0,0 +1,3 @@
1
+ [submodule "jQuery-File-Upload"]
2
+ path = jQuery-File-Upload
3
+ url = git://github.com/blueimp/jQuery-File-Upload.git
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in jquery.fileupload-rails.gemspec
4
+ gemspec
data/Rakefile ADDED
@@ -0,0 +1,40 @@
1
+ require 'json'
2
+ require 'bundler/gem_tasks'
3
+
4
+ DEPENDENCY_HASH = JSON.load(File.read('dependencies.json'))
5
+ PROJECT_NAME = "jQuery-File-Upload"
6
+
7
+ task :submodule do
8
+ sh 'git submodule update --init' unless File.exist?("#{PROJECT_NAME}/README.md")
9
+ end
10
+
11
+ desc "Remove the vendor directory"
12
+ task :clean do
13
+ rm_rf 'vendor/assets'
14
+ end
15
+
16
+ desc "Generate the JavaScript assets"
17
+ task :javascripts => :submodule do
18
+ target_dir = "vendor/assets/javascripts"
19
+ mkdir_p target_dir
20
+ Rake.rake_output_message 'Generating javascripts'
21
+ DEPENDENCY_HASH.each do |name, dep_modules|
22
+ path = "#{PROJECT_NAME}/js/#{name}.js"
23
+ File.open("#{target_dir}/#{name}.js", "w") do |out|
24
+ dep_modules.each do |mod|
25
+ out.write("//= require #{mod}\n")
26
+ end
27
+ out.write("\n") unless dep_modules.empty?
28
+ source_code = File.read(path)
29
+ out.write(source_code)
30
+ end
31
+ end
32
+ end
33
+
34
+ desc "Clean and then generate everything (default)"
35
+ task :assets => [:clean, :javascripts]
36
+
37
+ task :build => :assets
38
+
39
+ task :default => :assets
40
+
data/Readme.md ADDED
@@ -0,0 +1,92 @@
1
+ # jQuery File Upload for Rails
2
+
3
+ [jQuery File Upload][1] is a cross-browser javascript library for asynchronus Flash-free file uploading
4
+ by Sebastian Tschan (@blueimp). This gem packages it for the asset pipeline in Rails.
5
+
6
+ You should see the original project page for reference & documentation.
7
+ There are no instructions here on how to use the library itself.
8
+
9
+ ## Usage
10
+
11
+ Add a line to your Gemfile.
12
+
13
+ gem 'jquery.fileupload-rails'
14
+
15
+ Now you can require the javascript library in application.js:
16
+
17
+ //= require jquery.fileupload
18
+
19
+ Included (no need to require):
20
+
21
+ * jQuery Iframe Transport for IE support.
22
+ * jQuery UI widget from [jquery-ui-rails][2]
23
+
24
+ Example Rails application can be found in "demo" directory.
25
+
26
+ ## Upgrading 0.1 to 1.0
27
+
28
+ You can remove all dependencies of the plugin from you manifest. Before:
29
+
30
+ //= require jquery.ui
31
+ //= require jquery.iframe-transport
32
+ //= require jquery.fileupload
33
+
34
+ After:
35
+
36
+ //= require jquery.fileupload
37
+
38
+ If you downloaded jquery.ui assets into your project, delete them and use [jquery-ui-rails][2] gem instead.
39
+
40
+ ## Changelog
41
+
42
+ 1.11.0. Core 5.42.0.
43
+
44
+ 1.10.1. Core 5.41.1.
45
+
46
+ 1.10.0. Compatibility with new jQuery UI Rails 5.0 (jQuery UI 1.11).
47
+
48
+ 1.9.0. Core 5.41.0.
49
+
50
+ 1.8.1. Core 5.40.1.
51
+
52
+ 1.8.0. Core 5.40.0, updated demo app.
53
+
54
+ 1.7.0. Core 5.34.0.
55
+
56
+ 1.6.1. Core 5.32.5, jQuery UI Rails 4 compatibility.
57
+
58
+ 1.6.0. Core 5.32.2.
59
+
60
+ 1.5.1. Core 5.31.
61
+
62
+ 1.5.0. Core 5.30.
63
+
64
+ 1.4.1. Core 5.28.8.
65
+
66
+ 1.4.0. Core 5.28.4.
67
+
68
+ 1.3.0. Core 5.26.
69
+
70
+ 1.2.0. Core 5.21.1, demo instructions.
71
+
72
+ 1.1.1. Core 5.19.4, jQuery UI 1.9, added licensing info.
73
+
74
+ 1.0.0. Core 5.18.
75
+
76
+ Now rake task generates assets from official repo and adds dependencies automatically.
77
+ That means you can just require jquery.fileupload, no extra requires needed.
78
+
79
+ 0.1.2. Fixed CSS that makes SASS 3.2 raise an error on rake assets:precompile
80
+
81
+ 0.1.1. Core 5.11.2, UI 6.9.1, minor gemspec change.
82
+
83
+ 0.1.0. Core 5.9.0, UI 6.6.2, added readme.
84
+
85
+ 0.0.1. Core 5.5.2, UI 5.1.1.
86
+
87
+ [1]: https://github.com/blueimp/jQuery-File-Upload
88
+ [2]: https://github.com/joliss/jquery-ui-rails
89
+
90
+ ## License
91
+ jQuery File Upload as well as this gem are released under the [MIT license](http://www.opensource.org/licenses/MIT).
92
+ # jquery-fileuploads-rails4
data/demo/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ /.bundle
2
+ /log/*.log
3
+ /tmp
data/demo/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'rails', '4.1.4'
4
+
5
+ gem 'coffee-rails', '~> 4.0.0'
6
+ gem 'jquery-rails', '~> 3.1.0'
7
+ gem 'jquery-ui-rails', '~> 5.0.0'
8
+ gem 'jquery.fileupload-rails', path: '..'
data/demo/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require File.expand_path('../config/application', __FILE__)
2
+ Rails.application.load_tasks
data/demo/Readme.md ADDED
@@ -0,0 +1,12 @@
1
+ Start up the app:
2
+
3
+ bundle install
4
+ rails server
5
+
6
+ And head over to the home page: http://localhost:3000
7
+
8
+ Files to look at:
9
+
10
+ app/assets/javascripts/application.js.coffee
11
+ app/controllers/application_controller.rb
12
+ app/views/application/*
@@ -0,0 +1,9 @@
1
+ #= require jquery
2
+ #= require jquery_ujs
3
+ #= require jquery.fileupload
4
+
5
+ $ ->
6
+ $('#basic').fileupload
7
+ done: (e, data)->
8
+ console.log "Done", data.result
9
+ $("body").append(data.result)
@@ -0,0 +1,7 @@
1
+ class ApplicationController < ActionController::Base
2
+ protect_from_forgery
3
+
4
+ def create
5
+ render layout: false, content_type: "text/html"
6
+ end
7
+ end
@@ -0,0 +1,12 @@
1
+ <p>
2
+ You can test the most basic functionality of jQuery File Upload on this page.<br />
3
+ First, open the log in Web Inspector. You shouldn't see any errors there.<br />
4
+ Try to upload one file, multiple files. You will see a Ruby array of uploaded files below.<br />
5
+ </p>
6
+ <p>
7
+ All browsers except Opera upload only one file per request.<br />
8
+ That means you will probably see an array of one element for each uploaded file.<br />
9
+ </p>
10
+ <%= form_tag "/", method: "post", id: "basic" do %>
11
+ <div><%= file_field_tag "files[]", multiple: true %></div>
12
+ <% end %>
@@ -0,0 +1 @@
1
+ <div style="margin: 1em 0"><%= debug params[:files] %></div>
@@ -0,0 +1 @@
1
+ <%= javascript_include_tag "jquery.fileupload-ui" %>
@@ -0,0 +1,13 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Demo</title>
5
+ <%= javascript_include_tag "application" %>
6
+ <%= csrf_meta_tags %>
7
+ </head>
8
+ <body>
9
+
10
+ <%= yield %>
11
+
12
+ </body>
13
+ </html>
data/demo/bin/bundle ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
3
+ load Gem.bin_path('bundler', 'bundle')
data/demo/bin/rails ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ APP_PATH = File.expand_path('../../config/application', __FILE__)
3
+ require_relative '../config/boot'
4
+ require 'rails/commands'
data/demo/bin/rake ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ require_relative '../config/boot'
3
+ require 'rake'
4
+ Rake.application.run
data/demo/config.ru ADDED
@@ -0,0 +1,4 @@
1
+ # This file is used by Rack-based servers to start the application.
2
+
3
+ require ::File.expand_path('../config/environment', __FILE__)
4
+ run Demo::Application
@@ -0,0 +1,21 @@
1
+ require File.expand_path('../boot', __FILE__)
2
+
3
+ require 'action_controller/railtie'
4
+ require 'sprockets/railtie'
5
+ Bundler.require
6
+
7
+ module Demo
8
+ class Application < Rails::Application
9
+ config.cache_classes = false
10
+ config.eager_load = false
11
+ config.consider_all_requests_local = true
12
+
13
+ config.session_store :disabled
14
+ config.secret_key_base = "no"
15
+ config.active_support.deprecation = :log
16
+ config.action_dispatch.best_standards_support = :builtin
17
+
18
+ config.assets.enabled = true
19
+ config.assets.debug = true
20
+ end
21
+ end
@@ -0,0 +1,6 @@
1
+ require 'rubygems'
2
+
3
+ # Set up gems listed in the Gemfile.
4
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
5
+
6
+ require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE'])
@@ -0,0 +1,5 @@
1
+ # Load the rails application
2
+ require File.expand_path('../application', __FILE__)
3
+
4
+ # Initialize the rails application
5
+ Demo::Application.initialize!
@@ -0,0 +1,6 @@
1
+ Demo::Application.routes.draw do
2
+ get '/' => 'application#basic'
3
+ post '/' => 'application#create'
4
+
5
+ get ':action', controller: "application"
6
+ end
data/demo/log/.gitkeep ADDED
File without changes
data/dependencies.json ADDED
@@ -0,0 +1,4 @@
1
+ {
2
+ "jquery.fileupload": ["jquery-ui/widget", "jquery.iframe-transport"],
3
+ "jquery.iframe-transport": []
4
+ }
@@ -0,0 +1,17 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = "jquery-fileuploads-rails4"
5
+ s.version = "0.1.0"
6
+ s.author = "Karthik K"
7
+ s.email = "bello@skript.com"
8
+ s.homepage = "https://github.com/skcript/jquery-fileuploads-rails4"
9
+ s.summary = %q{Use jQuery File Upload plugin with Rails 4}
10
+ s.description = %q{This gem packages jQuery File Upload plugin and it's dependencies for Rails asset pipeline.}
11
+ s.license = "MIT"
12
+
13
+ s.files = File.read('Manifest.txt').split("\n")
14
+
15
+ s.add_dependency 'railties', '~> 4.1'
16
+ s.add_dependency 'jquery-ui-rails', '~> 5.0'
17
+ end
@@ -0,0 +1,9 @@
1
+ require "jquery-ui-rails"
2
+
3
+ module JqueryFileUpload
4
+ module Rails
5
+ class Rails::Engine < ::Rails::Engine
6
+ paths['vendor/assets'] << 'vendor/legacy_assets'
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,1460 @@
1
+ //= require jquery-ui/widget
2
+ //= require jquery.iframe-transport
3
+
4
+ /*
5
+ * jQuery File Upload Plugin 5.42.0
6
+ * https://github.com/blueimp/jQuery-File-Upload
7
+ *
8
+ * Copyright 2010, Sebastian Tschan
9
+ * https://blueimp.net
10
+ *
11
+ * Licensed under the MIT license:
12
+ * http://www.opensource.org/licenses/MIT
13
+ */
14
+
15
+ /* jshint nomen:false */
16
+ /* global define, window, document, location, Blob, FormData */
17
+
18
+ (function (factory) {
19
+ 'use strict';
20
+ if (typeof define === 'function' && define.amd) {
21
+ // Register as an anonymous AMD module:
22
+ define([
23
+ 'jquery',
24
+ 'jquery.ui.widget'
25
+ ], factory);
26
+ } else {
27
+ // Browser globals:
28
+ factory(window.jQuery);
29
+ }
30
+ }(function ($) {
31
+ 'use strict';
32
+
33
+ // Detect file input support, based on
34
+ // http://viljamis.com/blog/2012/file-upload-support-on-mobile/
35
+ $.support.fileInput = !(new RegExp(
36
+ // Handle devices which give false positives for the feature detection:
37
+ '(Android (1\\.[0156]|2\\.[01]))' +
38
+ '|(Windows Phone (OS 7|8\\.0))|(XBLWP)|(ZuneWP)|(WPDesktop)' +
39
+ '|(w(eb)?OSBrowser)|(webOS)' +
40
+ '|(Kindle/(1\\.0|2\\.[05]|3\\.0))'
41
+ ).test(window.navigator.userAgent) ||
42
+ // Feature detection for all other devices:
43
+ $('<input type="file">').prop('disabled'));
44
+
45
+ // The FileReader API is not actually used, but works as feature detection,
46
+ // as some Safari versions (5?) support XHR file uploads via the FormData API,
47
+ // but not non-multipart XHR file uploads.
48
+ // window.XMLHttpRequestUpload is not available on IE10, so we check for
49
+ // window.ProgressEvent instead to detect XHR2 file upload capability:
50
+ $.support.xhrFileUpload = !!(window.ProgressEvent && window.FileReader);
51
+ $.support.xhrFormDataFileUpload = !!window.FormData;
52
+
53
+ // Detect support for Blob slicing (required for chunked uploads):
54
+ $.support.blobSlice = window.Blob && (Blob.prototype.slice ||
55
+ Blob.prototype.webkitSlice || Blob.prototype.mozSlice);
56
+
57
+ // Helper function to create drag handlers for dragover/dragenter/dragleave:
58
+ function getDragHandler(type) {
59
+ var isDragOver = type === 'dragover';
60
+ return function (e) {
61
+ e.dataTransfer = e.originalEvent && e.originalEvent.dataTransfer;
62
+ var dataTransfer = e.dataTransfer;
63
+ if (dataTransfer && $.inArray('Files', dataTransfer.types) !== -1 &&
64
+ this._trigger(
65
+ type,
66
+ $.Event(type, {delegatedEvent: e})
67
+ ) !== false) {
68
+ e.preventDefault();
69
+ if (isDragOver) {
70
+ dataTransfer.dropEffect = 'copy';
71
+ }
72
+ }
73
+ };
74
+ }
75
+
76
+ // The fileupload widget listens for change events on file input fields defined
77
+ // via fileInput setting and paste or drop events of the given dropZone.
78
+ // In addition to the default jQuery Widget methods, the fileupload widget
79
+ // exposes the "add" and "send" methods, to add or directly send files using
80
+ // the fileupload API.
81
+ // By default, files added via file input selection, paste, drag & drop or
82
+ // "add" method are uploaded immediately, but it is possible to override
83
+ // the "add" callback option to queue file uploads.
84
+ $.widget('blueimp.fileupload', {
85
+
86
+ options: {
87
+ // The drop target element(s), by the default the complete document.
88
+ // Set to null to disable drag & drop support:
89
+ dropZone: $(document),
90
+ // The paste target element(s), by the default undefined.
91
+ // Set to a DOM node or jQuery object to enable file pasting:
92
+ pasteZone: undefined,
93
+ // The file input field(s), that are listened to for change events.
94
+ // If undefined, it is set to the file input fields inside
95
+ // of the widget element on plugin initialization.
96
+ // Set to null to disable the change listener.
97
+ fileInput: undefined,
98
+ // By default, the file input field is replaced with a clone after
99
+ // each input field change event. This is required for iframe transport
100
+ // queues and allows change events to be fired for the same file
101
+ // selection, but can be disabled by setting the following option to false:
102
+ replaceFileInput: true,
103
+ // The parameter name for the file form data (the request argument name).
104
+ // If undefined or empty, the name property of the file input field is
105
+ // used, or "files[]" if the file input name property is also empty,
106
+ // can be a string or an array of strings:
107
+ paramName: undefined,
108
+ // By default, each file of a selection is uploaded using an individual
109
+ // request for XHR type uploads. Set to false to upload file
110
+ // selections in one request each:
111
+ singleFileUploads: true,
112
+ // To limit the number of files uploaded with one XHR request,
113
+ // set the following option to an integer greater than 0:
114
+ limitMultiFileUploads: undefined,
115
+ // The following option limits the number of files uploaded with one
116
+ // XHR request to keep the request size under or equal to the defined
117
+ // limit in bytes:
118
+ limitMultiFileUploadSize: undefined,
119
+ // Multipart file uploads add a number of bytes to each uploaded file,
120
+ // therefore the following option adds an overhead for each file used
121
+ // in the limitMultiFileUploadSize configuration:
122
+ limitMultiFileUploadSizeOverhead: 512,
123
+ // Set the following option to true to issue all file upload requests
124
+ // in a sequential order:
125
+ sequentialUploads: false,
126
+ // To limit the number of concurrent uploads,
127
+ // set the following option to an integer greater than 0:
128
+ limitConcurrentUploads: undefined,
129
+ // Set the following option to true to force iframe transport uploads:
130
+ forceIframeTransport: false,
131
+ // Set the following option to the location of a redirect url on the
132
+ // origin server, for cross-domain iframe transport uploads:
133
+ redirect: undefined,
134
+ // The parameter name for the redirect url, sent as part of the form
135
+ // data and set to 'redirect' if this option is empty:
136
+ redirectParamName: undefined,
137
+ // Set the following option to the location of a postMessage window,
138
+ // to enable postMessage transport uploads:
139
+ postMessage: undefined,
140
+ // By default, XHR file uploads are sent as multipart/form-data.
141
+ // The iframe transport is always using multipart/form-data.
142
+ // Set to false to enable non-multipart XHR uploads:
143
+ multipart: true,
144
+ // To upload large files in smaller chunks, set the following option
145
+ // to a preferred maximum chunk size. If set to 0, null or undefined,
146
+ // or the browser does not support the required Blob API, files will
147
+ // be uploaded as a whole.
148
+ maxChunkSize: undefined,
149
+ // When a non-multipart upload or a chunked multipart upload has been
150
+ // aborted, this option can be used to resume the upload by setting
151
+ // it to the size of the already uploaded bytes. This option is most
152
+ // useful when modifying the options object inside of the "add" or
153
+ // "send" callbacks, as the options are cloned for each file upload.
154
+ uploadedBytes: undefined,
155
+ // By default, failed (abort or error) file uploads are removed from the
156
+ // global progress calculation. Set the following option to false to
157
+ // prevent recalculating the global progress data:
158
+ recalculateProgress: true,
159
+ // Interval in milliseconds to calculate and trigger progress events:
160
+ progressInterval: 100,
161
+ // Interval in milliseconds to calculate progress bitrate:
162
+ bitrateInterval: 500,
163
+ // By default, uploads are started automatically when adding files:
164
+ autoUpload: true,
165
+
166
+ // Error and info messages:
167
+ messages: {
168
+ uploadedBytes: 'Uploaded bytes exceed file size'
169
+ },
170
+
171
+ // Translation function, gets the message key to be translated
172
+ // and an object with context specific data as arguments:
173
+ i18n: function (message, context) {
174
+ message = this.messages[message] || message.toString();
175
+ if (context) {
176
+ $.each(context, function (key, value) {
177
+ message = message.replace('{' + key + '}', value);
178
+ });
179
+ }
180
+ return message;
181
+ },
182
+
183
+ // Additional form data to be sent along with the file uploads can be set
184
+ // using this option, which accepts an array of objects with name and
185
+ // value properties, a function returning such an array, a FormData
186
+ // object (for XHR file uploads), or a simple object.
187
+ // The form of the first fileInput is given as parameter to the function:
188
+ formData: function (form) {
189
+ return form.serializeArray();
190
+ },
191
+
192
+ // The add callback is invoked as soon as files are added to the fileupload
193
+ // widget (via file input selection, drag & drop, paste or add API call).
194
+ // If the singleFileUploads option is enabled, this callback will be
195
+ // called once for each file in the selection for XHR file uploads, else
196
+ // once for each file selection.
197
+ //
198
+ // The upload starts when the submit method is invoked on the data parameter.
199
+ // The data object contains a files property holding the added files
200
+ // and allows you to override plugin options as well as define ajax settings.
201
+ //
202
+ // Listeners for this callback can also be bound the following way:
203
+ // .bind('fileuploadadd', func);
204
+ //
205
+ // data.submit() returns a Promise object and allows to attach additional
206
+ // handlers using jQuery's Deferred callbacks:
207
+ // data.submit().done(func).fail(func).always(func);
208
+ add: function (e, data) {
209
+ if (e.isDefaultPrevented()) {
210
+ return false;
211
+ }
212
+ if (data.autoUpload || (data.autoUpload !== false &&
213
+ $(this).fileupload('option', 'autoUpload'))) {
214
+ data.process().done(function () {
215
+ data.submit();
216
+ });
217
+ }
218
+ },
219
+
220
+ // Other callbacks:
221
+
222
+ // Callback for the submit event of each file upload:
223
+ // submit: function (e, data) {}, // .bind('fileuploadsubmit', func);
224
+
225
+ // Callback for the start of each file upload request:
226
+ // send: function (e, data) {}, // .bind('fileuploadsend', func);
227
+
228
+ // Callback for successful uploads:
229
+ // done: function (e, data) {}, // .bind('fileuploaddone', func);
230
+
231
+ // Callback for failed (abort or error) uploads:
232
+ // fail: function (e, data) {}, // .bind('fileuploadfail', func);
233
+
234
+ // Callback for completed (success, abort or error) requests:
235
+ // always: function (e, data) {}, // .bind('fileuploadalways', func);
236
+
237
+ // Callback for upload progress events:
238
+ // progress: function (e, data) {}, // .bind('fileuploadprogress', func);
239
+
240
+ // Callback for global upload progress events:
241
+ // progressall: function (e, data) {}, // .bind('fileuploadprogressall', func);
242
+
243
+ // Callback for uploads start, equivalent to the global ajaxStart event:
244
+ // start: function (e) {}, // .bind('fileuploadstart', func);
245
+
246
+ // Callback for uploads stop, equivalent to the global ajaxStop event:
247
+ // stop: function (e) {}, // .bind('fileuploadstop', func);
248
+
249
+ // Callback for change events of the fileInput(s):
250
+ // change: function (e, data) {}, // .bind('fileuploadchange', func);
251
+
252
+ // Callback for paste events to the pasteZone(s):
253
+ // paste: function (e, data) {}, // .bind('fileuploadpaste', func);
254
+
255
+ // Callback for drop events of the dropZone(s):
256
+ // drop: function (e, data) {}, // .bind('fileuploaddrop', func);
257
+
258
+ // Callback for dragover events of the dropZone(s):
259
+ // dragover: function (e) {}, // .bind('fileuploaddragover', func);
260
+
261
+ // Callback for the start of each chunk upload request:
262
+ // chunksend: function (e, data) {}, // .bind('fileuploadchunksend', func);
263
+
264
+ // Callback for successful chunk uploads:
265
+ // chunkdone: function (e, data) {}, // .bind('fileuploadchunkdone', func);
266
+
267
+ // Callback for failed (abort or error) chunk uploads:
268
+ // chunkfail: function (e, data) {}, // .bind('fileuploadchunkfail', func);
269
+
270
+ // Callback for completed (success, abort or error) chunk upload requests:
271
+ // chunkalways: function (e, data) {}, // .bind('fileuploadchunkalways', func);
272
+
273
+ // The plugin options are used as settings object for the ajax calls.
274
+ // The following are jQuery ajax settings required for the file uploads:
275
+ processData: false,
276
+ contentType: false,
277
+ cache: false
278
+ },
279
+
280
+ // A list of options that require reinitializing event listeners and/or
281
+ // special initialization code:
282
+ _specialOptions: [
283
+ 'fileInput',
284
+ 'dropZone',
285
+ 'pasteZone',
286
+ 'multipart',
287
+ 'forceIframeTransport'
288
+ ],
289
+
290
+ _blobSlice: $.support.blobSlice && function () {
291
+ var slice = this.slice || this.webkitSlice || this.mozSlice;
292
+ return slice.apply(this, arguments);
293
+ },
294
+
295
+ _BitrateTimer: function () {
296
+ this.timestamp = ((Date.now) ? Date.now() : (new Date()).getTime());
297
+ this.loaded = 0;
298
+ this.bitrate = 0;
299
+ this.getBitrate = function (now, loaded, interval) {
300
+ var timeDiff = now - this.timestamp;
301
+ if (!this.bitrate || !interval || timeDiff > interval) {
302
+ this.bitrate = (loaded - this.loaded) * (1000 / timeDiff) * 8;
303
+ this.loaded = loaded;
304
+ this.timestamp = now;
305
+ }
306
+ return this.bitrate;
307
+ };
308
+ },
309
+
310
+ _isXHRUpload: function (options) {
311
+ return !options.forceIframeTransport &&
312
+ ((!options.multipart && $.support.xhrFileUpload) ||
313
+ $.support.xhrFormDataFileUpload);
314
+ },
315
+
316
+ _getFormData: function (options) {
317
+ var formData;
318
+ if ($.type(options.formData) === 'function') {
319
+ return options.formData(options.form);
320
+ }
321
+ if ($.isArray(options.formData)) {
322
+ return options.formData;
323
+ }
324
+ if ($.type(options.formData) === 'object') {
325
+ formData = [];
326
+ $.each(options.formData, function (name, value) {
327
+ formData.push({name: name, value: value});
328
+ });
329
+ return formData;
330
+ }
331
+ return [];
332
+ },
333
+
334
+ _getTotal: function (files) {
335
+ var total = 0;
336
+ $.each(files, function (index, file) {
337
+ total += file.size || 1;
338
+ });
339
+ return total;
340
+ },
341
+
342
+ _initProgressObject: function (obj) {
343
+ var progress = {
344
+ loaded: 0,
345
+ total: 0,
346
+ bitrate: 0
347
+ };
348
+ if (obj._progress) {
349
+ $.extend(obj._progress, progress);
350
+ } else {
351
+ obj._progress = progress;
352
+ }
353
+ },
354
+
355
+ _initResponseObject: function (obj) {
356
+ var prop;
357
+ if (obj._response) {
358
+ for (prop in obj._response) {
359
+ if (obj._response.hasOwnProperty(prop)) {
360
+ delete obj._response[prop];
361
+ }
362
+ }
363
+ } else {
364
+ obj._response = {};
365
+ }
366
+ },
367
+
368
+ _onProgress: function (e, data) {
369
+ if (e.lengthComputable) {
370
+ var now = ((Date.now) ? Date.now() : (new Date()).getTime()),
371
+ loaded;
372
+ if (data._time && data.progressInterval &&
373
+ (now - data._time < data.progressInterval) &&
374
+ e.loaded !== e.total) {
375
+ return;
376
+ }
377
+ data._time = now;
378
+ loaded = Math.floor(
379
+ e.loaded / e.total * (data.chunkSize || data._progress.total)
380
+ ) + (data.uploadedBytes || 0);
381
+ // Add the difference from the previously loaded state
382
+ // to the global loaded counter:
383
+ this._progress.loaded += (loaded - data._progress.loaded);
384
+ this._progress.bitrate = this._bitrateTimer.getBitrate(
385
+ now,
386
+ this._progress.loaded,
387
+ data.bitrateInterval
388
+ );
389
+ data._progress.loaded = data.loaded = loaded;
390
+ data._progress.bitrate = data.bitrate = data._bitrateTimer.getBitrate(
391
+ now,
392
+ loaded,
393
+ data.bitrateInterval
394
+ );
395
+ // Trigger a custom progress event with a total data property set
396
+ // to the file size(s) of the current upload and a loaded data
397
+ // property calculated accordingly:
398
+ this._trigger(
399
+ 'progress',
400
+ $.Event('progress', {delegatedEvent: e}),
401
+ data
402
+ );
403
+ // Trigger a global progress event for all current file uploads,
404
+ // including ajax calls queued for sequential file uploads:
405
+ this._trigger(
406
+ 'progressall',
407
+ $.Event('progressall', {delegatedEvent: e}),
408
+ this._progress
409
+ );
410
+ }
411
+ },
412
+
413
+ _initProgressListener: function (options) {
414
+ var that = this,
415
+ xhr = options.xhr ? options.xhr() : $.ajaxSettings.xhr();
416
+ // Accesss to the native XHR object is required to add event listeners
417
+ // for the upload progress event:
418
+ if (xhr.upload) {
419
+ $(xhr.upload).bind('progress', function (e) {
420
+ var oe = e.originalEvent;
421
+ // Make sure the progress event properties get copied over:
422
+ e.lengthComputable = oe.lengthComputable;
423
+ e.loaded = oe.loaded;
424
+ e.total = oe.total;
425
+ that._onProgress(e, options);
426
+ });
427
+ options.xhr = function () {
428
+ return xhr;
429
+ };
430
+ }
431
+ },
432
+
433
+ _isInstanceOf: function (type, obj) {
434
+ // Cross-frame instanceof check
435
+ return Object.prototype.toString.call(obj) === '[object ' + type + ']';
436
+ },
437
+
438
+ _initXHRData: function (options) {
439
+ var that = this,
440
+ formData,
441
+ file = options.files[0],
442
+ // Ignore non-multipart setting if not supported:
443
+ multipart = options.multipart || !$.support.xhrFileUpload,
444
+ paramName = $.type(options.paramName) === 'array' ?
445
+ options.paramName[0] : options.paramName;
446
+ options.headers = $.extend({}, options.headers);
447
+ if (options.contentRange) {
448
+ options.headers['Content-Range'] = options.contentRange;
449
+ }
450
+ if (!multipart || options.blob || !this._isInstanceOf('File', file)) {
451
+ options.headers['Content-Disposition'] = 'attachment; filename="' +
452
+ encodeURI(file.name) + '"';
453
+ }
454
+ if (!multipart) {
455
+ options.contentType = file.type || 'application/octet-stream';
456
+ options.data = options.blob || file;
457
+ } else if ($.support.xhrFormDataFileUpload) {
458
+ if (options.postMessage) {
459
+ // window.postMessage does not allow sending FormData
460
+ // objects, so we just add the File/Blob objects to
461
+ // the formData array and let the postMessage window
462
+ // create the FormData object out of this array:
463
+ formData = this._getFormData(options);
464
+ if (options.blob) {
465
+ formData.push({
466
+ name: paramName,
467
+ value: options.blob
468
+ });
469
+ } else {
470
+ $.each(options.files, function (index, file) {
471
+ formData.push({
472
+ name: ($.type(options.paramName) === 'array' &&
473
+ options.paramName[index]) || paramName,
474
+ value: file
475
+ });
476
+ });
477
+ }
478
+ } else {
479
+ if (that._isInstanceOf('FormData', options.formData)) {
480
+ formData = options.formData;
481
+ } else {
482
+ formData = new FormData();
483
+ $.each(this._getFormData(options), function (index, field) {
484
+ formData.append(field.name, field.value);
485
+ });
486
+ }
487
+ if (options.blob) {
488
+ formData.append(paramName, options.blob, file.name);
489
+ } else {
490
+ $.each(options.files, function (index, file) {
491
+ // This check allows the tests to run with
492
+ // dummy objects:
493
+ if (that._isInstanceOf('File', file) ||
494
+ that._isInstanceOf('Blob', file)) {
495
+ formData.append(
496
+ ($.type(options.paramName) === 'array' &&
497
+ options.paramName[index]) || paramName,
498
+ file,
499
+ file.uploadName || file.name
500
+ );
501
+ }
502
+ });
503
+ }
504
+ }
505
+ options.data = formData;
506
+ }
507
+ // Blob reference is not needed anymore, free memory:
508
+ options.blob = null;
509
+ },
510
+
511
+ _initIframeSettings: function (options) {
512
+ var targetHost = $('<a></a>').prop('href', options.url).prop('host');
513
+ // Setting the dataType to iframe enables the iframe transport:
514
+ options.dataType = 'iframe ' + (options.dataType || '');
515
+ // The iframe transport accepts a serialized array as form data:
516
+ options.formData = this._getFormData(options);
517
+ // Add redirect url to form data on cross-domain uploads:
518
+ if (options.redirect && targetHost && targetHost !== location.host) {
519
+ options.formData.push({
520
+ name: options.redirectParamName || 'redirect',
521
+ value: options.redirect
522
+ });
523
+ }
524
+ },
525
+
526
+ _initDataSettings: function (options) {
527
+ if (this._isXHRUpload(options)) {
528
+ if (!this._chunkedUpload(options, true)) {
529
+ if (!options.data) {
530
+ this._initXHRData(options);
531
+ }
532
+ this._initProgressListener(options);
533
+ }
534
+ if (options.postMessage) {
535
+ // Setting the dataType to postmessage enables the
536
+ // postMessage transport:
537
+ options.dataType = 'postmessage ' + (options.dataType || '');
538
+ }
539
+ } else {
540
+ this._initIframeSettings(options);
541
+ }
542
+ },
543
+
544
+ _getParamName: function (options) {
545
+ var fileInput = $(options.fileInput),
546
+ paramName = options.paramName;
547
+ if (!paramName) {
548
+ paramName = [];
549
+ fileInput.each(function () {
550
+ var input = $(this),
551
+ name = input.prop('name') || 'files[]',
552
+ i = (input.prop('files') || [1]).length;
553
+ while (i) {
554
+ paramName.push(name);
555
+ i -= 1;
556
+ }
557
+ });
558
+ if (!paramName.length) {
559
+ paramName = [fileInput.prop('name') || 'files[]'];
560
+ }
561
+ } else if (!$.isArray(paramName)) {
562
+ paramName = [paramName];
563
+ }
564
+ return paramName;
565
+ },
566
+
567
+ _initFormSettings: function (options) {
568
+ // Retrieve missing options from the input field and the
569
+ // associated form, if available:
570
+ if (!options.form || !options.form.length) {
571
+ options.form = $(options.fileInput.prop('form'));
572
+ // If the given file input doesn't have an associated form,
573
+ // use the default widget file input's form:
574
+ if (!options.form.length) {
575
+ options.form = $(this.options.fileInput.prop('form'));
576
+ }
577
+ }
578
+ options.paramName = this._getParamName(options);
579
+ if (!options.url) {
580
+ options.url = options.form.prop('action') || location.href;
581
+ }
582
+ // The HTTP request method must be "POST" or "PUT":
583
+ options.type = (options.type ||
584
+ ($.type(options.form.prop('method')) === 'string' &&
585
+ options.form.prop('method')) || ''
586
+ ).toUpperCase();
587
+ if (options.type !== 'POST' && options.type !== 'PUT' &&
588
+ options.type !== 'PATCH') {
589
+ options.type = 'POST';
590
+ }
591
+ if (!options.formAcceptCharset) {
592
+ options.formAcceptCharset = options.form.attr('accept-charset');
593
+ }
594
+ },
595
+
596
+ _getAJAXSettings: function (data) {
597
+ var options = $.extend({}, this.options, data);
598
+ this._initFormSettings(options);
599
+ this._initDataSettings(options);
600
+ return options;
601
+ },
602
+
603
+ // jQuery 1.6 doesn't provide .state(),
604
+ // while jQuery 1.8+ removed .isRejected() and .isResolved():
605
+ _getDeferredState: function (deferred) {
606
+ if (deferred.state) {
607
+ return deferred.state();
608
+ }
609
+ if (deferred.isResolved()) {
610
+ return 'resolved';
611
+ }
612
+ if (deferred.isRejected()) {
613
+ return 'rejected';
614
+ }
615
+ return 'pending';
616
+ },
617
+
618
+ // Maps jqXHR callbacks to the equivalent
619
+ // methods of the given Promise object:
620
+ _enhancePromise: function (promise) {
621
+ promise.success = promise.done;
622
+ promise.error = promise.fail;
623
+ promise.complete = promise.always;
624
+ return promise;
625
+ },
626
+
627
+ // Creates and returns a Promise object enhanced with
628
+ // the jqXHR methods abort, success, error and complete:
629
+ _getXHRPromise: function (resolveOrReject, context, args) {
630
+ var dfd = $.Deferred(),
631
+ promise = dfd.promise();
632
+ context = context || this.options.context || promise;
633
+ if (resolveOrReject === true) {
634
+ dfd.resolveWith(context, args);
635
+ } else if (resolveOrReject === false) {
636
+ dfd.rejectWith(context, args);
637
+ }
638
+ promise.abort = dfd.promise;
639
+ return this._enhancePromise(promise);
640
+ },
641
+
642
+ // Adds convenience methods to the data callback argument:
643
+ _addConvenienceMethods: function (e, data) {
644
+ var that = this,
645
+ getPromise = function (args) {
646
+ return $.Deferred().resolveWith(that, args).promise();
647
+ };
648
+ data.process = function (resolveFunc, rejectFunc) {
649
+ if (resolveFunc || rejectFunc) {
650
+ data._processQueue = this._processQueue =
651
+ (this._processQueue || getPromise([this])).pipe(
652
+ function () {
653
+ if (data.errorThrown) {
654
+ return $.Deferred()
655
+ .rejectWith(that, [data]).promise();
656
+ }
657
+ return getPromise(arguments);
658
+ }
659
+ ).pipe(resolveFunc, rejectFunc);
660
+ }
661
+ return this._processQueue || getPromise([this]);
662
+ };
663
+ data.submit = function () {
664
+ if (this.state() !== 'pending') {
665
+ data.jqXHR = this.jqXHR =
666
+ (that._trigger(
667
+ 'submit',
668
+ $.Event('submit', {delegatedEvent: e}),
669
+ this
670
+ ) !== false) && that._onSend(e, this);
671
+ }
672
+ return this.jqXHR || that._getXHRPromise();
673
+ };
674
+ data.abort = function () {
675
+ if (this.jqXHR) {
676
+ return this.jqXHR.abort();
677
+ }
678
+ this.errorThrown = 'abort';
679
+ that._trigger('fail', null, this);
680
+ return that._getXHRPromise(false);
681
+ };
682
+ data.state = function () {
683
+ if (this.jqXHR) {
684
+ return that._getDeferredState(this.jqXHR);
685
+ }
686
+ if (this._processQueue) {
687
+ return that._getDeferredState(this._processQueue);
688
+ }
689
+ };
690
+ data.processing = function () {
691
+ return !this.jqXHR && this._processQueue && that
692
+ ._getDeferredState(this._processQueue) === 'pending';
693
+ };
694
+ data.progress = function () {
695
+ return this._progress;
696
+ };
697
+ data.response = function () {
698
+ return this._response;
699
+ };
700
+ },
701
+
702
+ // Parses the Range header from the server response
703
+ // and returns the uploaded bytes:
704
+ _getUploadedBytes: function (jqXHR) {
705
+ var range = jqXHR.getResponseHeader('Range'),
706
+ parts = range && range.split('-'),
707
+ upperBytesPos = parts && parts.length > 1 &&
708
+ parseInt(parts[1], 10);
709
+ return upperBytesPos && upperBytesPos + 1;
710
+ },
711
+
712
+ // Uploads a file in multiple, sequential requests
713
+ // by splitting the file up in multiple blob chunks.
714
+ // If the second parameter is true, only tests if the file
715
+ // should be uploaded in chunks, but does not invoke any
716
+ // upload requests:
717
+ _chunkedUpload: function (options, testOnly) {
718
+ options.uploadedBytes = options.uploadedBytes || 0;
719
+ var that = this,
720
+ file = options.files[0],
721
+ fs = file.size,
722
+ ub = options.uploadedBytes,
723
+ mcs = options.maxChunkSize || fs,
724
+ slice = this._blobSlice,
725
+ dfd = $.Deferred(),
726
+ promise = dfd.promise(),
727
+ jqXHR,
728
+ upload;
729
+ if (!(this._isXHRUpload(options) && slice && (ub || mcs < fs)) ||
730
+ options.data) {
731
+ return false;
732
+ }
733
+ if (testOnly) {
734
+ return true;
735
+ }
736
+ if (ub >= fs) {
737
+ file.error = options.i18n('uploadedBytes');
738
+ return this._getXHRPromise(
739
+ false,
740
+ options.context,
741
+ [null, 'error', file.error]
742
+ );
743
+ }
744
+ // The chunk upload method:
745
+ upload = function () {
746
+ // Clone the options object for each chunk upload:
747
+ var o = $.extend({}, options),
748
+ currentLoaded = o._progress.loaded;
749
+ o.blob = slice.call(
750
+ file,
751
+ ub,
752
+ ub + mcs,
753
+ file.type
754
+ );
755
+ // Store the current chunk size, as the blob itself
756
+ // will be dereferenced after data processing:
757
+ o.chunkSize = o.blob.size;
758
+ // Expose the chunk bytes position range:
759
+ o.contentRange = 'bytes ' + ub + '-' +
760
+ (ub + o.chunkSize - 1) + '/' + fs;
761
+ // Process the upload data (the blob and potential form data):
762
+ that._initXHRData(o);
763
+ // Add progress listeners for this chunk upload:
764
+ that._initProgressListener(o);
765
+ jqXHR = ((that._trigger('chunksend', null, o) !== false && $.ajax(o)) ||
766
+ that._getXHRPromise(false, o.context))
767
+ .done(function (result, textStatus, jqXHR) {
768
+ ub = that._getUploadedBytes(jqXHR) ||
769
+ (ub + o.chunkSize);
770
+ // Create a progress event if no final progress event
771
+ // with loaded equaling total has been triggered
772
+ // for this chunk:
773
+ if (currentLoaded + o.chunkSize - o._progress.loaded) {
774
+ that._onProgress($.Event('progress', {
775
+ lengthComputable: true,
776
+ loaded: ub - o.uploadedBytes,
777
+ total: ub - o.uploadedBytes
778
+ }), o);
779
+ }
780
+ options.uploadedBytes = o.uploadedBytes = ub;
781
+ o.result = result;
782
+ o.textStatus = textStatus;
783
+ o.jqXHR = jqXHR;
784
+ that._trigger('chunkdone', null, o);
785
+ that._trigger('chunkalways', null, o);
786
+ if (ub < fs) {
787
+ // File upload not yet complete,
788
+ // continue with the next chunk:
789
+ upload();
790
+ } else {
791
+ dfd.resolveWith(
792
+ o.context,
793
+ [result, textStatus, jqXHR]
794
+ );
795
+ }
796
+ })
797
+ .fail(function (jqXHR, textStatus, errorThrown) {
798
+ o.jqXHR = jqXHR;
799
+ o.textStatus = textStatus;
800
+ o.errorThrown = errorThrown;
801
+ that._trigger('chunkfail', null, o);
802
+ that._trigger('chunkalways', null, o);
803
+ dfd.rejectWith(
804
+ o.context,
805
+ [jqXHR, textStatus, errorThrown]
806
+ );
807
+ });
808
+ };
809
+ this._enhancePromise(promise);
810
+ promise.abort = function () {
811
+ return jqXHR.abort();
812
+ };
813
+ upload();
814
+ return promise;
815
+ },
816
+
817
+ _beforeSend: function (e, data) {
818
+ if (this._active === 0) {
819
+ // the start callback is triggered when an upload starts
820
+ // and no other uploads are currently running,
821
+ // equivalent to the global ajaxStart event:
822
+ this._trigger('start');
823
+ // Set timer for global bitrate progress calculation:
824
+ this._bitrateTimer = new this._BitrateTimer();
825
+ // Reset the global progress values:
826
+ this._progress.loaded = this._progress.total = 0;
827
+ this._progress.bitrate = 0;
828
+ }
829
+ // Make sure the container objects for the .response() and
830
+ // .progress() methods on the data object are available
831
+ // and reset to their initial state:
832
+ this._initResponseObject(data);
833
+ this._initProgressObject(data);
834
+ data._progress.loaded = data.loaded = data.uploadedBytes || 0;
835
+ data._progress.total = data.total = this._getTotal(data.files) || 1;
836
+ data._progress.bitrate = data.bitrate = 0;
837
+ this._active += 1;
838
+ // Initialize the global progress values:
839
+ this._progress.loaded += data.loaded;
840
+ this._progress.total += data.total;
841
+ },
842
+
843
+ _onDone: function (result, textStatus, jqXHR, options) {
844
+ var total = options._progress.total,
845
+ response = options._response;
846
+ if (options._progress.loaded < total) {
847
+ // Create a progress event if no final progress event
848
+ // with loaded equaling total has been triggered:
849
+ this._onProgress($.Event('progress', {
850
+ lengthComputable: true,
851
+ loaded: total,
852
+ total: total
853
+ }), options);
854
+ }
855
+ response.result = options.result = result;
856
+ response.textStatus = options.textStatus = textStatus;
857
+ response.jqXHR = options.jqXHR = jqXHR;
858
+ this._trigger('done', null, options);
859
+ },
860
+
861
+ _onFail: function (jqXHR, textStatus, errorThrown, options) {
862
+ var response = options._response;
863
+ if (options.recalculateProgress) {
864
+ // Remove the failed (error or abort) file upload from
865
+ // the global progress calculation:
866
+ this._progress.loaded -= options._progress.loaded;
867
+ this._progress.total -= options._progress.total;
868
+ }
869
+ response.jqXHR = options.jqXHR = jqXHR;
870
+ response.textStatus = options.textStatus = textStatus;
871
+ response.errorThrown = options.errorThrown = errorThrown;
872
+ this._trigger('fail', null, options);
873
+ },
874
+
875
+ _onAlways: function (jqXHRorResult, textStatus, jqXHRorError, options) {
876
+ // jqXHRorResult, textStatus and jqXHRorError are added to the
877
+ // options object via done and fail callbacks
878
+ this._trigger('always', null, options);
879
+ },
880
+
881
+ _onSend: function (e, data) {
882
+ if (!data.submit) {
883
+ this._addConvenienceMethods(e, data);
884
+ }
885
+ var that = this,
886
+ jqXHR,
887
+ aborted,
888
+ slot,
889
+ pipe,
890
+ options = that._getAJAXSettings(data),
891
+ send = function () {
892
+ that._sending += 1;
893
+ // Set timer for bitrate progress calculation:
894
+ options._bitrateTimer = new that._BitrateTimer();
895
+ jqXHR = jqXHR || (
896
+ ((aborted || that._trigger(
897
+ 'send',
898
+ $.Event('send', {delegatedEvent: e}),
899
+ options
900
+ ) === false) &&
901
+ that._getXHRPromise(false, options.context, aborted)) ||
902
+ that._chunkedUpload(options) || $.ajax(options)
903
+ ).done(function (result, textStatus, jqXHR) {
904
+ that._onDone(result, textStatus, jqXHR, options);
905
+ }).fail(function (jqXHR, textStatus, errorThrown) {
906
+ that._onFail(jqXHR, textStatus, errorThrown, options);
907
+ }).always(function (jqXHRorResult, textStatus, jqXHRorError) {
908
+ that._onAlways(
909
+ jqXHRorResult,
910
+ textStatus,
911
+ jqXHRorError,
912
+ options
913
+ );
914
+ that._sending -= 1;
915
+ that._active -= 1;
916
+ if (options.limitConcurrentUploads &&
917
+ options.limitConcurrentUploads > that._sending) {
918
+ // Start the next queued upload,
919
+ // that has not been aborted:
920
+ var nextSlot = that._slots.shift();
921
+ while (nextSlot) {
922
+ if (that._getDeferredState(nextSlot) === 'pending') {
923
+ nextSlot.resolve();
924
+ break;
925
+ }
926
+ nextSlot = that._slots.shift();
927
+ }
928
+ }
929
+ if (that._active === 0) {
930
+ // The stop callback is triggered when all uploads have
931
+ // been completed, equivalent to the global ajaxStop event:
932
+ that._trigger('stop');
933
+ }
934
+ });
935
+ return jqXHR;
936
+ };
937
+ this._beforeSend(e, options);
938
+ if (this.options.sequentialUploads ||
939
+ (this.options.limitConcurrentUploads &&
940
+ this.options.limitConcurrentUploads <= this._sending)) {
941
+ if (this.options.limitConcurrentUploads > 1) {
942
+ slot = $.Deferred();
943
+ this._slots.push(slot);
944
+ pipe = slot.pipe(send);
945
+ } else {
946
+ this._sequence = this._sequence.pipe(send, send);
947
+ pipe = this._sequence;
948
+ }
949
+ // Return the piped Promise object, enhanced with an abort method,
950
+ // which is delegated to the jqXHR object of the current upload,
951
+ // and jqXHR callbacks mapped to the equivalent Promise methods:
952
+ pipe.abort = function () {
953
+ aborted = [undefined, 'abort', 'abort'];
954
+ if (!jqXHR) {
955
+ if (slot) {
956
+ slot.rejectWith(options.context, aborted);
957
+ }
958
+ return send();
959
+ }
960
+ return jqXHR.abort();
961
+ };
962
+ return this._enhancePromise(pipe);
963
+ }
964
+ return send();
965
+ },
966
+
967
+ _onAdd: function (e, data) {
968
+ var that = this,
969
+ result = true,
970
+ options = $.extend({}, this.options, data),
971
+ files = data.files,
972
+ filesLength = files.length,
973
+ limit = options.limitMultiFileUploads,
974
+ limitSize = options.limitMultiFileUploadSize,
975
+ overhead = options.limitMultiFileUploadSizeOverhead,
976
+ batchSize = 0,
977
+ paramName = this._getParamName(options),
978
+ paramNameSet,
979
+ paramNameSlice,
980
+ fileSet,
981
+ i,
982
+ j = 0;
983
+ if (limitSize && (!filesLength || files[0].size === undefined)) {
984
+ limitSize = undefined;
985
+ }
986
+ if (!(options.singleFileUploads || limit || limitSize) ||
987
+ !this._isXHRUpload(options)) {
988
+ fileSet = [files];
989
+ paramNameSet = [paramName];
990
+ } else if (!(options.singleFileUploads || limitSize) && limit) {
991
+ fileSet = [];
992
+ paramNameSet = [];
993
+ for (i = 0; i < filesLength; i += limit) {
994
+ fileSet.push(files.slice(i, i + limit));
995
+ paramNameSlice = paramName.slice(i, i + limit);
996
+ if (!paramNameSlice.length) {
997
+ paramNameSlice = paramName;
998
+ }
999
+ paramNameSet.push(paramNameSlice);
1000
+ }
1001
+ } else if (!options.singleFileUploads && limitSize) {
1002
+ fileSet = [];
1003
+ paramNameSet = [];
1004
+ for (i = 0; i < filesLength; i = i + 1) {
1005
+ batchSize += files[i].size + overhead;
1006
+ if (i + 1 === filesLength ||
1007
+ ((batchSize + files[i + 1].size + overhead) > limitSize) ||
1008
+ (limit && i + 1 - j >= limit)) {
1009
+ fileSet.push(files.slice(j, i + 1));
1010
+ paramNameSlice = paramName.slice(j, i + 1);
1011
+ if (!paramNameSlice.length) {
1012
+ paramNameSlice = paramName;
1013
+ }
1014
+ paramNameSet.push(paramNameSlice);
1015
+ j = i + 1;
1016
+ batchSize = 0;
1017
+ }
1018
+ }
1019
+ } else {
1020
+ paramNameSet = paramName;
1021
+ }
1022
+ data.originalFiles = files;
1023
+ $.each(fileSet || files, function (index, element) {
1024
+ var newData = $.extend({}, data);
1025
+ newData.files = fileSet ? element : [element];
1026
+ newData.paramName = paramNameSet[index];
1027
+ that._initResponseObject(newData);
1028
+ that._initProgressObject(newData);
1029
+ that._addConvenienceMethods(e, newData);
1030
+ result = that._trigger(
1031
+ 'add',
1032
+ $.Event('add', {delegatedEvent: e}),
1033
+ newData
1034
+ );
1035
+ return result;
1036
+ });
1037
+ return result;
1038
+ },
1039
+
1040
+ _replaceFileInput: function (data) {
1041
+ var input = data.fileInput,
1042
+ inputClone = input.clone(true);
1043
+ // Add a reference for the new cloned file input to the data argument:
1044
+ data.fileInputClone = inputClone;
1045
+ $('<form></form>').append(inputClone)[0].reset();
1046
+ // Detaching allows to insert the fileInput on another form
1047
+ // without loosing the file input value:
1048
+ input.after(inputClone).detach();
1049
+ // Avoid memory leaks with the detached file input:
1050
+ $.cleanData(input.unbind('remove'));
1051
+ // Replace the original file input element in the fileInput
1052
+ // elements set with the clone, which has been copied including
1053
+ // event handlers:
1054
+ this.options.fileInput = this.options.fileInput.map(function (i, el) {
1055
+ if (el === input[0]) {
1056
+ return inputClone[0];
1057
+ }
1058
+ return el;
1059
+ });
1060
+ // If the widget has been initialized on the file input itself,
1061
+ // override this.element with the file input clone:
1062
+ if (input[0] === this.element[0]) {
1063
+ this.element = inputClone;
1064
+ }
1065
+ },
1066
+
1067
+ _handleFileTreeEntry: function (entry, path) {
1068
+ var that = this,
1069
+ dfd = $.Deferred(),
1070
+ errorHandler = function (e) {
1071
+ if (e && !e.entry) {
1072
+ e.entry = entry;
1073
+ }
1074
+ // Since $.when returns immediately if one
1075
+ // Deferred is rejected, we use resolve instead.
1076
+ // This allows valid files and invalid items
1077
+ // to be returned together in one set:
1078
+ dfd.resolve([e]);
1079
+ },
1080
+ successHandler = function (entries) {
1081
+ that._handleFileTreeEntries(
1082
+ entries,
1083
+ path + entry.name + '/'
1084
+ ).done(function (files) {
1085
+ dfd.resolve(files);
1086
+ }).fail(errorHandler);
1087
+ },
1088
+ readEntries = function () {
1089
+ dirReader.readEntries(function (results) {
1090
+ if (!results.length) {
1091
+ successHandler(entries);
1092
+ } else {
1093
+ entries = entries.concat(results);
1094
+ readEntries();
1095
+ }
1096
+ }, errorHandler);
1097
+ },
1098
+ dirReader, entries = [];
1099
+ path = path || '';
1100
+ if (entry.isFile) {
1101
+ if (entry._file) {
1102
+ // Workaround for Chrome bug #149735
1103
+ entry._file.relativePath = path;
1104
+ dfd.resolve(entry._file);
1105
+ } else {
1106
+ entry.file(function (file) {
1107
+ file.relativePath = path;
1108
+ dfd.resolve(file);
1109
+ }, errorHandler);
1110
+ }
1111
+ } else if (entry.isDirectory) {
1112
+ dirReader = entry.createReader();
1113
+ readEntries();
1114
+ } else {
1115
+ // Return an empy list for file system items
1116
+ // other than files or directories:
1117
+ dfd.resolve([]);
1118
+ }
1119
+ return dfd.promise();
1120
+ },
1121
+
1122
+ _handleFileTreeEntries: function (entries, path) {
1123
+ var that = this;
1124
+ return $.when.apply(
1125
+ $,
1126
+ $.map(entries, function (entry) {
1127
+ return that._handleFileTreeEntry(entry, path);
1128
+ })
1129
+ ).pipe(function () {
1130
+ return Array.prototype.concat.apply(
1131
+ [],
1132
+ arguments
1133
+ );
1134
+ });
1135
+ },
1136
+
1137
+ _getDroppedFiles: function (dataTransfer) {
1138
+ dataTransfer = dataTransfer || {};
1139
+ var items = dataTransfer.items;
1140
+ if (items && items.length && (items[0].webkitGetAsEntry ||
1141
+ items[0].getAsEntry)) {
1142
+ return this._handleFileTreeEntries(
1143
+ $.map(items, function (item) {
1144
+ var entry;
1145
+ if (item.webkitGetAsEntry) {
1146
+ entry = item.webkitGetAsEntry();
1147
+ if (entry) {
1148
+ // Workaround for Chrome bug #149735:
1149
+ entry._file = item.getAsFile();
1150
+ }
1151
+ return entry;
1152
+ }
1153
+ return item.getAsEntry();
1154
+ })
1155
+ );
1156
+ }
1157
+ return $.Deferred().resolve(
1158
+ $.makeArray(dataTransfer.files)
1159
+ ).promise();
1160
+ },
1161
+
1162
+ _getSingleFileInputFiles: function (fileInput) {
1163
+ fileInput = $(fileInput);
1164
+ var entries = fileInput.prop('webkitEntries') ||
1165
+ fileInput.prop('entries'),
1166
+ files,
1167
+ value;
1168
+ if (entries && entries.length) {
1169
+ return this._handleFileTreeEntries(entries);
1170
+ }
1171
+ files = $.makeArray(fileInput.prop('files'));
1172
+ if (!files.length) {
1173
+ value = fileInput.prop('value');
1174
+ if (!value) {
1175
+ return $.Deferred().resolve([]).promise();
1176
+ }
1177
+ // If the files property is not available, the browser does not
1178
+ // support the File API and we add a pseudo File object with
1179
+ // the input value as name with path information removed:
1180
+ files = [{name: value.replace(/^.*\\/, '')}];
1181
+ } else if (files[0].name === undefined && files[0].fileName) {
1182
+ // File normalization for Safari 4 and Firefox 3:
1183
+ $.each(files, function (index, file) {
1184
+ file.name = file.fileName;
1185
+ file.size = file.fileSize;
1186
+ });
1187
+ }
1188
+ return $.Deferred().resolve(files).promise();
1189
+ },
1190
+
1191
+ _getFileInputFiles: function (fileInput) {
1192
+ if (!(fileInput instanceof $) || fileInput.length === 1) {
1193
+ return this._getSingleFileInputFiles(fileInput);
1194
+ }
1195
+ return $.when.apply(
1196
+ $,
1197
+ $.map(fileInput, this._getSingleFileInputFiles)
1198
+ ).pipe(function () {
1199
+ return Array.prototype.concat.apply(
1200
+ [],
1201
+ arguments
1202
+ );
1203
+ });
1204
+ },
1205
+
1206
+ _onChange: function (e) {
1207
+ var that = this,
1208
+ data = {
1209
+ fileInput: $(e.target),
1210
+ form: $(e.target.form)
1211
+ };
1212
+ this._getFileInputFiles(data.fileInput).always(function (files) {
1213
+ data.files = files;
1214
+ if (that.options.replaceFileInput) {
1215
+ that._replaceFileInput(data);
1216
+ }
1217
+ if (that._trigger(
1218
+ 'change',
1219
+ $.Event('change', {delegatedEvent: e}),
1220
+ data
1221
+ ) !== false) {
1222
+ that._onAdd(e, data);
1223
+ }
1224
+ });
1225
+ },
1226
+
1227
+ _onPaste: function (e) {
1228
+ var items = e.originalEvent && e.originalEvent.clipboardData &&
1229
+ e.originalEvent.clipboardData.items,
1230
+ data = {files: []};
1231
+ if (items && items.length) {
1232
+ $.each(items, function (index, item) {
1233
+ var file = item.getAsFile && item.getAsFile();
1234
+ if (file) {
1235
+ data.files.push(file);
1236
+ }
1237
+ });
1238
+ if (this._trigger(
1239
+ 'paste',
1240
+ $.Event('paste', {delegatedEvent: e}),
1241
+ data
1242
+ ) !== false) {
1243
+ this._onAdd(e, data);
1244
+ }
1245
+ }
1246
+ },
1247
+
1248
+ _onDrop: function (e) {
1249
+ e.dataTransfer = e.originalEvent && e.originalEvent.dataTransfer;
1250
+ var that = this,
1251
+ dataTransfer = e.dataTransfer,
1252
+ data = {};
1253
+ if (dataTransfer && dataTransfer.files && dataTransfer.files.length) {
1254
+ e.preventDefault();
1255
+ this._getDroppedFiles(dataTransfer).always(function (files) {
1256
+ data.files = files;
1257
+ if (that._trigger(
1258
+ 'drop',
1259
+ $.Event('drop', {delegatedEvent: e}),
1260
+ data
1261
+ ) !== false) {
1262
+ that._onAdd(e, data);
1263
+ }
1264
+ });
1265
+ }
1266
+ },
1267
+
1268
+ _onDragOver: getDragHandler('dragover'),
1269
+
1270
+ _onDragEnter: getDragHandler('dragenter'),
1271
+
1272
+ _onDragLeave: getDragHandler('dragleave'),
1273
+
1274
+ _initEventHandlers: function () {
1275
+ if (this._isXHRUpload(this.options)) {
1276
+ this._on(this.options.dropZone, {
1277
+ dragover: this._onDragOver,
1278
+ drop: this._onDrop,
1279
+ // event.preventDefault() on dragenter is required for IE10+:
1280
+ dragenter: this._onDragEnter,
1281
+ // dragleave is not required, but added for completeness:
1282
+ dragleave: this._onDragLeave
1283
+ });
1284
+ this._on(this.options.pasteZone, {
1285
+ paste: this._onPaste
1286
+ });
1287
+ }
1288
+ if ($.support.fileInput) {
1289
+ this._on(this.options.fileInput, {
1290
+ change: this._onChange
1291
+ });
1292
+ }
1293
+ },
1294
+
1295
+ _destroyEventHandlers: function () {
1296
+ this._off(this.options.dropZone, 'dragenter dragleave dragover drop');
1297
+ this._off(this.options.pasteZone, 'paste');
1298
+ this._off(this.options.fileInput, 'change');
1299
+ },
1300
+
1301
+ _setOption: function (key, value) {
1302
+ var reinit = $.inArray(key, this._specialOptions) !== -1;
1303
+ if (reinit) {
1304
+ this._destroyEventHandlers();
1305
+ }
1306
+ this._super(key, value);
1307
+ if (reinit) {
1308
+ this._initSpecialOptions();
1309
+ this._initEventHandlers();
1310
+ }
1311
+ },
1312
+
1313
+ _initSpecialOptions: function () {
1314
+ var options = this.options;
1315
+ if (options.fileInput === undefined) {
1316
+ options.fileInput = this.element.is('input[type="file"]') ?
1317
+ this.element : this.element.find('input[type="file"]');
1318
+ } else if (!(options.fileInput instanceof $)) {
1319
+ options.fileInput = $(options.fileInput);
1320
+ }
1321
+ if (!(options.dropZone instanceof $)) {
1322
+ options.dropZone = $(options.dropZone);
1323
+ }
1324
+ if (!(options.pasteZone instanceof $)) {
1325
+ options.pasteZone = $(options.pasteZone);
1326
+ }
1327
+ },
1328
+
1329
+ _getRegExp: function (str) {
1330
+ var parts = str.split('/'),
1331
+ modifiers = parts.pop();
1332
+ parts.shift();
1333
+ return new RegExp(parts.join('/'), modifiers);
1334
+ },
1335
+
1336
+ _isRegExpOption: function (key, value) {
1337
+ return key !== 'url' && $.type(value) === 'string' &&
1338
+ /^\/.*\/[igm]{0,3}$/.test(value);
1339
+ },
1340
+
1341
+ _initDataAttributes: function () {
1342
+ var that = this,
1343
+ options = this.options,
1344
+ clone = $(this.element[0].cloneNode(false));
1345
+ // Initialize options set via HTML5 data-attributes:
1346
+ $.each(
1347
+ clone.data(),
1348
+ function (key, value) {
1349
+ var dataAttributeName = 'data-' +
1350
+ // Convert camelCase to hyphen-ated key:
1351
+ key.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
1352
+ if (clone.attr(dataAttributeName)) {
1353
+ if (that._isRegExpOption(key, value)) {
1354
+ value = that._getRegExp(value);
1355
+ }
1356
+ options[key] = value;
1357
+ }
1358
+ }
1359
+ );
1360
+ },
1361
+
1362
+ _create: function () {
1363
+ this._initDataAttributes();
1364
+ this._initSpecialOptions();
1365
+ this._slots = [];
1366
+ this._sequence = this._getXHRPromise(true);
1367
+ this._sending = this._active = 0;
1368
+ this._initProgressObject(this);
1369
+ this._initEventHandlers();
1370
+ },
1371
+
1372
+ // This method is exposed to the widget API and allows to query
1373
+ // the number of active uploads:
1374
+ active: function () {
1375
+ return this._active;
1376
+ },
1377
+
1378
+ // This method is exposed to the widget API and allows to query
1379
+ // the widget upload progress.
1380
+ // It returns an object with loaded, total and bitrate properties
1381
+ // for the running uploads:
1382
+ progress: function () {
1383
+ return this._progress;
1384
+ },
1385
+
1386
+ // This method is exposed to the widget API and allows adding files
1387
+ // using the fileupload API. The data parameter accepts an object which
1388
+ // must have a files property and can contain additional options:
1389
+ // .fileupload('add', {files: filesList});
1390
+ add: function (data) {
1391
+ var that = this;
1392
+ if (!data || this.options.disabled) {
1393
+ return;
1394
+ }
1395
+ if (data.fileInput && !data.files) {
1396
+ this._getFileInputFiles(data.fileInput).always(function (files) {
1397
+ data.files = files;
1398
+ that._onAdd(null, data);
1399
+ });
1400
+ } else {
1401
+ data.files = $.makeArray(data.files);
1402
+ this._onAdd(null, data);
1403
+ }
1404
+ },
1405
+
1406
+ // This method is exposed to the widget API and allows sending files
1407
+ // using the fileupload API. The data parameter accepts an object which
1408
+ // must have a files or fileInput property and can contain additional options:
1409
+ // .fileupload('send', {files: filesList});
1410
+ // The method returns a Promise object for the file upload call.
1411
+ send: function (data) {
1412
+ if (data && !this.options.disabled) {
1413
+ if (data.fileInput && !data.files) {
1414
+ var that = this,
1415
+ dfd = $.Deferred(),
1416
+ promise = dfd.promise(),
1417
+ jqXHR,
1418
+ aborted;
1419
+ promise.abort = function () {
1420
+ aborted = true;
1421
+ if (jqXHR) {
1422
+ return jqXHR.abort();
1423
+ }
1424
+ dfd.reject(null, 'abort', 'abort');
1425
+ return promise;
1426
+ };
1427
+ this._getFileInputFiles(data.fileInput).always(
1428
+ function (files) {
1429
+ if (aborted) {
1430
+ return;
1431
+ }
1432
+ if (!files.length) {
1433
+ dfd.reject();
1434
+ return;
1435
+ }
1436
+ data.files = files;
1437
+ jqXHR = that._onSend(null, data);
1438
+ jqXHR.then(
1439
+ function (result, textStatus, jqXHR) {
1440
+ dfd.resolve(result, textStatus, jqXHR);
1441
+ },
1442
+ function (jqXHR, textStatus, errorThrown) {
1443
+ dfd.reject(jqXHR, textStatus, errorThrown);
1444
+ }
1445
+ );
1446
+ }
1447
+ );
1448
+ return this._enhancePromise(promise);
1449
+ }
1450
+ data.files = $.makeArray(data.files);
1451
+ if (data.files.length) {
1452
+ return this._onSend(null, data);
1453
+ }
1454
+ }
1455
+ return this._getXHRPromise(false, data && data.context);
1456
+ }
1457
+
1458
+ });
1459
+
1460
+ }));