tandem 0.2.0.rc → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (110) hide show
  1. data/lib/tandem/version.rb +1 -1
  2. data/vendor/assets/javascripts/tandem/jquery-fileupload/index.js +2 -0
  3. data/vendor/assets/javascripts/tandem/jquery-fileupload/jquery.fileupload.js +852 -0
  4. data/vendor/assets/javascripts/tandem/jquery-fileupload/jquery.iframe-transport.js +165 -0
  5. data/vendor/assets/javascripts/tandem/jquery.colorbox.js +888 -0
  6. data/vendor/assets/javascripts/tandem/jquery.ui.widget.js +282 -0
  7. data/vendor/assets/javascripts/tandem/modernizr-2.5.3.min.js +4 -0
  8. data/vendor/assets/javascripts/tandem/wymeditor/iframe/default/lbl-article.png +0 -0
  9. data/vendor/assets/javascripts/tandem/wymeditor/iframe/default/lbl-aside.png +0 -0
  10. data/vendor/assets/javascripts/tandem/wymeditor/iframe/default/lbl-blockquote.png +0 -0
  11. data/vendor/assets/javascripts/tandem/wymeditor/iframe/default/lbl-command.png +0 -0
  12. data/vendor/assets/javascripts/tandem/wymeditor/iframe/default/lbl-details.png +0 -0
  13. data/vendor/assets/javascripts/tandem/wymeditor/iframe/default/lbl-figcaption.png +0 -0
  14. data/vendor/assets/javascripts/tandem/wymeditor/iframe/default/lbl-figure.png +0 -0
  15. data/vendor/assets/javascripts/tandem/wymeditor/iframe/default/lbl-footer.png +0 -0
  16. data/vendor/assets/javascripts/tandem/wymeditor/iframe/default/lbl-h1.png +0 -0
  17. data/vendor/assets/javascripts/tandem/wymeditor/iframe/default/lbl-h2.png +0 -0
  18. data/vendor/assets/javascripts/tandem/wymeditor/iframe/default/lbl-h3.png +0 -0
  19. data/vendor/assets/javascripts/tandem/wymeditor/iframe/default/lbl-h4.png +0 -0
  20. data/vendor/assets/javascripts/tandem/wymeditor/iframe/default/lbl-h5.png +0 -0
  21. data/vendor/assets/javascripts/tandem/wymeditor/iframe/default/lbl-h6.png +0 -0
  22. data/vendor/assets/javascripts/tandem/wymeditor/iframe/default/lbl-header.png +0 -0
  23. data/vendor/assets/javascripts/tandem/wymeditor/iframe/default/lbl-hgroup.png +0 -0
  24. data/vendor/assets/javascripts/tandem/wymeditor/iframe/default/lbl-mark.png +0 -0
  25. data/vendor/assets/javascripts/tandem/wymeditor/iframe/default/lbl-meter.png +0 -0
  26. data/vendor/assets/javascripts/tandem/wymeditor/iframe/default/lbl-nav.png +0 -0
  27. data/vendor/assets/javascripts/tandem/wymeditor/iframe/default/lbl-p.png +0 -0
  28. data/vendor/assets/javascripts/tandem/wymeditor/iframe/default/lbl-pre.png +0 -0
  29. data/vendor/assets/javascripts/tandem/wymeditor/iframe/default/lbl-progress.png +0 -0
  30. data/vendor/assets/javascripts/tandem/wymeditor/iframe/default/lbl-rp.png +0 -0
  31. data/vendor/assets/javascripts/tandem/wymeditor/iframe/default/lbl-rt.png +0 -0
  32. data/vendor/assets/javascripts/tandem/wymeditor/iframe/default/lbl-ruby.png +0 -0
  33. data/vendor/assets/javascripts/tandem/wymeditor/iframe/default/lbl-section.png +0 -0
  34. data/vendor/assets/javascripts/tandem/wymeditor/iframe/default/lbl-summary.png +0 -0
  35. data/vendor/assets/javascripts/tandem/wymeditor/iframe/default/lbl-time.png +0 -0
  36. data/vendor/assets/javascripts/tandem/wymeditor/iframe/default/readme.md +1 -0
  37. data/vendor/assets/javascripts/tandem/wymeditor/iframe/default/wymiframe.css +98 -0
  38. data/vendor/assets/javascripts/tandem/wymeditor/iframe/default/wymiframe.css.scss +98 -0
  39. data/vendor/assets/javascripts/tandem/wymeditor/iframe/default/wymiframe.html +26 -0
  40. data/vendor/assets/javascripts/tandem/wymeditor/jquery.wymeditor.min.js +34 -0
  41. data/vendor/assets/javascripts/tandem/wymeditor/lang/bg.js +45 -0
  42. data/vendor/assets/javascripts/tandem/wymeditor/lang/ca.js +45 -0
  43. data/vendor/assets/javascripts/tandem/wymeditor/lang/cs.js +45 -0
  44. data/vendor/assets/javascripts/tandem/wymeditor/lang/cy.js +45 -0
  45. data/vendor/assets/javascripts/tandem/wymeditor/lang/de.js +45 -0
  46. data/vendor/assets/javascripts/tandem/wymeditor/lang/en.js +45 -0
  47. data/vendor/assets/javascripts/tandem/wymeditor/lang/es.js +45 -0
  48. data/vendor/assets/javascripts/tandem/wymeditor/lang/fa.js +46 -0
  49. data/vendor/assets/javascripts/tandem/wymeditor/lang/fi.js +44 -0
  50. data/vendor/assets/javascripts/tandem/wymeditor/lang/fr.js +45 -0
  51. data/vendor/assets/javascripts/tandem/wymeditor/lang/gl.js +45 -0
  52. data/vendor/assets/javascripts/tandem/wymeditor/lang/he.js +45 -0
  53. data/vendor/assets/javascripts/tandem/wymeditor/lang/hr.js +45 -0
  54. data/vendor/assets/javascripts/tandem/wymeditor/lang/hu.js +45 -0
  55. data/vendor/assets/javascripts/tandem/wymeditor/lang/it.js +45 -0
  56. data/vendor/assets/javascripts/tandem/wymeditor/lang/ja.js +44 -0
  57. data/vendor/assets/javascripts/tandem/wymeditor/lang/lt.js +45 -0
  58. data/vendor/assets/javascripts/tandem/wymeditor/lang/nb.js +45 -0
  59. data/vendor/assets/javascripts/tandem/wymeditor/lang/nl.js +45 -0
  60. data/vendor/assets/javascripts/tandem/wymeditor/lang/nn.js +45 -0
  61. data/vendor/assets/javascripts/tandem/wymeditor/lang/pl.js +45 -0
  62. data/vendor/assets/javascripts/tandem/wymeditor/lang/pt-br.js +45 -0
  63. data/vendor/assets/javascripts/tandem/wymeditor/lang/pt.js +45 -0
  64. data/vendor/assets/javascripts/tandem/wymeditor/lang/ru.js +45 -0
  65. data/vendor/assets/javascripts/tandem/wymeditor/lang/sv.js +46 -0
  66. data/vendor/assets/javascripts/tandem/wymeditor/lang/tr.js +45 -0
  67. data/vendor/assets/javascripts/tandem/wymeditor/lang/zh_cn.js +47 -0
  68. data/vendor/assets/javascripts/tandem/wymeditor/plugins/embed/jquery.wymeditor.embed.js +82 -0
  69. data/vendor/assets/javascripts/tandem/wymeditor/plugins/fullscreen/icon_fullscreen.gif +0 -0
  70. data/vendor/assets/javascripts/tandem/wymeditor/plugins/fullscreen/jquery.wymeditor.fullscreen.js +124 -0
  71. data/vendor/assets/javascripts/tandem/wymeditor/plugins/hovertools/jquery.wymeditor.hovertools.js +49 -0
  72. data/vendor/assets/javascripts/tandem/wymeditor/plugins/list/jquery.wymeditor.list.js +60 -0
  73. data/vendor/assets/javascripts/tandem/wymeditor/plugins/rdfa/jquery.wymeditor.rdfa.js +186 -0
  74. data/vendor/assets/javascripts/tandem/wymeditor/plugins/resizable/jquery.wymeditor.resizable.js +77 -0
  75. data/vendor/assets/javascripts/tandem/wymeditor/plugins/resizable/readme.txt +124 -0
  76. data/vendor/assets/javascripts/tandem/wymeditor/plugins/table/jquery.wymeditor.table.js +721 -0
  77. data/vendor/assets/javascripts/tandem/wymeditor/plugins/table/table_delete_column.png +0 -0
  78. data/vendor/assets/javascripts/tandem/wymeditor/plugins/table/table_delete_row.png +0 -0
  79. data/vendor/assets/javascripts/tandem/wymeditor/plugins/table/table_insert_column.png +0 -0
  80. data/vendor/assets/javascripts/tandem/wymeditor/plugins/table/table_insert_row.png +0 -0
  81. data/vendor/assets/javascripts/tandem/wymeditor/plugins/table/table_join_row.png +0 -0
  82. data/vendor/assets/javascripts/tandem/wymeditor/plugins/tidy/README +19 -0
  83. data/vendor/assets/javascripts/tandem/wymeditor/plugins/tidy/jquery.wymeditor.tidy.js +78 -0
  84. data/vendor/assets/javascripts/tandem/wymeditor/plugins/tidy/tidy.php +58 -0
  85. data/vendor/assets/javascripts/tandem/wymeditor/plugins/tidy/wand.png +0 -0
  86. data/vendor/assets/javascripts/tandem/wymeditor/skins/compact/icons.png +0 -0
  87. data/vendor/assets/javascripts/tandem/wymeditor/skins/compact/skin.css +134 -0
  88. data/vendor/assets/javascripts/tandem/wymeditor/skins/compact/skin.js +37 -0
  89. data/vendor/assets/javascripts/tandem/wymeditor/skins/default/icons.png +0 -0
  90. data/vendor/assets/javascripts/tandem/wymeditor/skins/default/skin.css +136 -0
  91. data/vendor/assets/javascripts/tandem/wymeditor/skins/default/skin.js +40 -0
  92. data/vendor/assets/javascripts/tandem/wymeditor/skins/minimal/images/bg.header.gif +0 -0
  93. data/vendor/assets/javascripts/tandem/wymeditor/skins/minimal/images/bg.selector.silver.gif +0 -0
  94. data/vendor/assets/javascripts/tandem/wymeditor/skins/minimal/images/bg.wymeditor.png +0 -0
  95. data/vendor/assets/javascripts/tandem/wymeditor/skins/minimal/images/icons.silver.gif +0 -0
  96. data/vendor/assets/javascripts/tandem/wymeditor/skins/minimal/skin.css +131 -0
  97. data/vendor/assets/javascripts/tandem/wymeditor/skins/minimal/skin.js +30 -0
  98. data/vendor/assets/javascripts/tandem/wymeditor/skins/silver/COPYING +674 -0
  99. data/vendor/assets/javascripts/tandem/wymeditor/skins/silver/README +27 -0
  100. data/vendor/assets/javascripts/tandem/wymeditor/skins/silver/images/bg.header.gif +0 -0
  101. data/vendor/assets/javascripts/tandem/wymeditor/skins/silver/images/bg.selector.silver.gif +0 -0
  102. data/vendor/assets/javascripts/tandem/wymeditor/skins/silver/images/bg.wymeditor.png +0 -0
  103. data/vendor/assets/javascripts/tandem/wymeditor/skins/silver/images/icons.silver.gif +0 -0
  104. data/vendor/assets/javascripts/tandem/wymeditor/skins/silver/skin.css +297 -0
  105. data/vendor/assets/javascripts/tandem/wymeditor/skins/silver/skin.js +61 -0
  106. data/vendor/assets/javascripts/tandem/wymeditor/skins/twopanels/icons.png +0 -0
  107. data/vendor/assets/javascripts/tandem/wymeditor/skins/twopanels/skin.css +134 -0
  108. data/vendor/assets/javascripts/tandem/wymeditor/skins/twopanels/skin.js +39 -0
  109. data/vendor/assets/javascripts/tandem/wymeditor/skins/wymeditor_icon.png +0 -0
  110. metadata +112 -4
@@ -1,3 +1,3 @@
1
1
  module Tandem
2
- VERSION = "0.2.0.rc"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -0,0 +1,2 @@
1
+ //= require tandem/jquery-fileupload/jquery.iframe-transport
2
+ //= require tandem/jquery-fileupload/jquery.fileupload
@@ -0,0 +1,852 @@
1
+ /*
2
+ * jQuery File Upload Plugin 5.8
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, document, XMLHttpRequestUpload, Blob, File, FormData, location */
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
+ './vendor/jquery.ui.widget',
22
+ './jquery.iframe-transport'
23
+ ], factory);
24
+ } else {
25
+ // Browser globals:
26
+ factory(window.jQuery);
27
+ }
28
+ }(function ($) {
29
+ 'use strict';
30
+
31
+ // The fileupload widget listens for change events on file input fields defined
32
+ // via fileInput setting and paste or drop events of the given dropZone.
33
+ // In addition to the default jQuery Widget methods, the fileupload widget
34
+ // exposes the "add" and "send" methods, to add or directly send files using
35
+ // the fileupload API.
36
+ // By default, files added via file input selection, paste, drag & drop or
37
+ // "add" method are uploaded immediately, but it is possible to override
38
+ // the "add" callback option to queue file uploads.
39
+ $.widget('blueimp.fileupload', {
40
+
41
+ options: {
42
+ // The namespace used for event handler binding on the dropZone and
43
+ // fileInput collections.
44
+ // If not set, the name of the widget ("fileupload") is used.
45
+ namespace: undefined,
46
+ // The drop target collection, by the default the complete document.
47
+ // Set to null or an empty collection to disable drag & drop support:
48
+ dropZone: $(document),
49
+ // The file input field collection, that is listened for change events.
50
+ // If undefined, it is set to the file input fields inside
51
+ // of the widget element on plugin initialization.
52
+ // Set to null or an empty collection to disable the change listener.
53
+ fileInput: undefined,
54
+ // By default, the file input field is replaced with a clone after
55
+ // each input field change event. This is required for iframe transport
56
+ // queues and allows change events to be fired for the same file
57
+ // selection, but can be disabled by setting the following option to false:
58
+ replaceFileInput: true,
59
+ // The parameter name for the file form data (the request argument name).
60
+ // If undefined or empty, the name property of the file input field is
61
+ // used, or "files[]" if the file input name property is also empty:
62
+ paramName: undefined,
63
+ // By default, each file of a selection is uploaded using an individual
64
+ // request for XHR type uploads. Set to false to upload file
65
+ // selections in one request each:
66
+ singleFileUploads: true,
67
+ // To limit the number of files uploaded with one XHR request,
68
+ // set the following option to an integer greater than 0:
69
+ limitMultiFileUploads: undefined,
70
+ // Set the following option to true to issue all file upload requests
71
+ // in a sequential order:
72
+ sequentialUploads: false,
73
+ // To limit the number of concurrent uploads,
74
+ // set the following option to an integer greater than 0:
75
+ limitConcurrentUploads: undefined,
76
+ // Set the following option to true to force iframe transport uploads:
77
+ forceIframeTransport: false,
78
+ // Set the following option to the location of a redirect url on the
79
+ // origin server, for cross-domain iframe transport uploads:
80
+ redirect: undefined,
81
+ // The parameter name for the redirect url, sent as part of the form
82
+ // data and set to 'redirect' if this option is empty:
83
+ redirectParamName: undefined,
84
+ // Set the following option to the location of a postMessage window,
85
+ // to enable postMessage transport uploads:
86
+ postMessage: undefined,
87
+ // By default, XHR file uploads are sent as multipart/form-data.
88
+ // The iframe transport is always using multipart/form-data.
89
+ // Set to false to enable non-multipart XHR uploads:
90
+ multipart: true,
91
+ // To upload large files in smaller chunks, set the following option
92
+ // to a preferred maximum chunk size. If set to 0, null or undefined,
93
+ // or the browser does not support the required Blob API, files will
94
+ // be uploaded as a whole.
95
+ maxChunkSize: undefined,
96
+ // When a non-multipart upload or a chunked multipart upload has been
97
+ // aborted, this option can be used to resume the upload by setting
98
+ // it to the size of the already uploaded bytes. This option is most
99
+ // useful when modifying the options object inside of the "add" or
100
+ // "send" callbacks, as the options are cloned for each file upload.
101
+ uploadedBytes: undefined,
102
+ // By default, failed (abort or error) file uploads are removed from the
103
+ // global progress calculation. Set the following option to false to
104
+ // prevent recalculating the global progress data:
105
+ recalculateProgress: true,
106
+
107
+ // Additional form data to be sent along with the file uploads can be set
108
+ // using this option, which accepts an array of objects with name and
109
+ // value properties, a function returning such an array, a FormData
110
+ // object (for XHR file uploads), or a simple object.
111
+ // The form of the first fileInput is given as parameter to the function:
112
+ formData: function (form) {
113
+ return form.serializeArray();
114
+ },
115
+
116
+ // The add callback is invoked as soon as files are added to the fileupload
117
+ // widget (via file input selection, drag & drop, paste or add API call).
118
+ // If the singleFileUploads option is enabled, this callback will be
119
+ // called once for each file in the selection for XHR file uplaods, else
120
+ // once for each file selection.
121
+ // The upload starts when the submit method is invoked on the data parameter.
122
+ // The data object contains a files property holding the added files
123
+ // and allows to override plugin options as well as define ajax settings.
124
+ // Listeners for this callback can also be bound the following way:
125
+ // .bind('fileuploadadd', func);
126
+ // data.submit() returns a Promise object and allows to attach additional
127
+ // handlers using jQuery's Deferred callbacks:
128
+ // data.submit().done(func).fail(func).always(func);
129
+ add: function (e, data) {
130
+ data.submit();
131
+ },
132
+
133
+ // Other callbacks:
134
+ // Callback for the submit event of each file upload:
135
+ // submit: function (e, data) {}, // .bind('fileuploadsubmit', func);
136
+ // Callback for the start of each file upload request:
137
+ // send: function (e, data) {}, // .bind('fileuploadsend', func);
138
+ // Callback for successful uploads:
139
+ // done: function (e, data) {}, // .bind('fileuploaddone', func);
140
+ // Callback for failed (abort or error) uploads:
141
+ // fail: function (e, data) {}, // .bind('fileuploadfail', func);
142
+ // Callback for completed (success, abort or error) requests:
143
+ // always: function (e, data) {}, // .bind('fileuploadalways', func);
144
+ // Callback for upload progress events:
145
+ // progress: function (e, data) {}, // .bind('fileuploadprogress', func);
146
+ // Callback for global upload progress events:
147
+ // progressall: function (e, data) {}, // .bind('fileuploadprogressall', func);
148
+ // Callback for uploads start, equivalent to the global ajaxStart event:
149
+ // start: function (e) {}, // .bind('fileuploadstart', func);
150
+ // Callback for uploads stop, equivalent to the global ajaxStop event:
151
+ // stop: function (e) {}, // .bind('fileuploadstop', func);
152
+ // Callback for change events of the fileInput collection:
153
+ // change: function (e, data) {}, // .bind('fileuploadchange', func);
154
+ // Callback for paste events to the dropZone collection:
155
+ // paste: function (e, data) {}, // .bind('fileuploadpaste', func);
156
+ // Callback for drop events of the dropZone collection:
157
+ // drop: function (e, data) {}, // .bind('fileuploaddrop', func);
158
+ // Callback for dragover events of the dropZone collection:
159
+ // dragover: function (e) {}, // .bind('fileuploaddragover', func);
160
+
161
+ // The plugin options are used as settings object for the ajax calls.
162
+ // The following are jQuery ajax settings required for the file uploads:
163
+ processData: false,
164
+ contentType: false,
165
+ cache: false
166
+ },
167
+
168
+ // A list of options that require a refresh after assigning a new value:
169
+ _refreshOptionsList: ['namespace', 'dropZone', 'fileInput'],
170
+
171
+ _isXHRUpload: function (options) {
172
+ var undef = 'undefined';
173
+ return !options.forceIframeTransport &&
174
+ typeof XMLHttpRequestUpload !== undef && typeof File !== undef &&
175
+ (!options.multipart || typeof FormData !== undef);
176
+ },
177
+
178
+ _getFormData: function (options) {
179
+ var formData;
180
+ if (typeof options.formData === 'function') {
181
+ return options.formData(options.form);
182
+ } else if ($.isArray(options.formData)) {
183
+ return options.formData;
184
+ } else if (options.formData) {
185
+ formData = [];
186
+ $.each(options.formData, function (name, value) {
187
+ formData.push({name: name, value: value});
188
+ });
189
+ return formData;
190
+ }
191
+ return [];
192
+ },
193
+
194
+ _getTotal: function (files) {
195
+ var total = 0;
196
+ $.each(files, function (index, file) {
197
+ total += file.size || 1;
198
+ });
199
+ return total;
200
+ },
201
+
202
+ _onProgress: function (e, data) {
203
+ if (e.lengthComputable) {
204
+ var total = data.total || this._getTotal(data.files),
205
+ loaded = parseInt(
206
+ e.loaded / e.total * (data.chunkSize || total),
207
+ 10
208
+ ) + (data.uploadedBytes || 0);
209
+ this._loaded += loaded - (data.loaded || data.uploadedBytes || 0);
210
+ data.lengthComputable = true;
211
+ data.loaded = loaded;
212
+ data.total = total;
213
+ // Trigger a custom progress event with a total data property set
214
+ // to the file size(s) of the current upload and a loaded data
215
+ // property calculated accordingly:
216
+ this._trigger('progress', e, data);
217
+ // Trigger a global progress event for all current file uploads,
218
+ // including ajax calls queued for sequential file uploads:
219
+ this._trigger('progressall', e, {
220
+ lengthComputable: true,
221
+ loaded: this._loaded,
222
+ total: this._total
223
+ });
224
+ }
225
+ },
226
+
227
+ _initProgressListener: function (options) {
228
+ var that = this,
229
+ xhr = options.xhr ? options.xhr() : $.ajaxSettings.xhr();
230
+ // Accesss to the native XHR object is required to add event listeners
231
+ // for the upload progress event:
232
+ if (xhr.upload) {
233
+ $(xhr.upload).bind('progress', function (e) {
234
+ var oe = e.originalEvent;
235
+ // Make sure the progress event properties get copied over:
236
+ e.lengthComputable = oe.lengthComputable;
237
+ e.loaded = oe.loaded;
238
+ e.total = oe.total;
239
+ that._onProgress(e, options);
240
+ });
241
+ options.xhr = function () {
242
+ return xhr;
243
+ };
244
+ }
245
+ },
246
+
247
+ _initXHRData: function (options) {
248
+ var formData,
249
+ file = options.files[0];
250
+ if (!options.multipart || options.blob) {
251
+ // For non-multipart uploads and chunked uploads,
252
+ // file meta data is not part of the request body,
253
+ // so we transmit this data as part of the HTTP headers.
254
+ // For cross domain requests, these headers must be allowed
255
+ // via Access-Control-Allow-Headers or removed using
256
+ // the beforeSend callback:
257
+ options.headers = $.extend(options.headers, {
258
+ 'X-File-Name': file.name,
259
+ 'X-File-Type': file.type,
260
+ 'X-File-Size': file.size
261
+ });
262
+ if (!options.blob) {
263
+ // Non-chunked non-multipart upload:
264
+ options.contentType = file.type;
265
+ options.data = file;
266
+ } else if (!options.multipart) {
267
+ // Chunked non-multipart upload:
268
+ options.contentType = 'application/octet-stream';
269
+ options.data = options.blob;
270
+ }
271
+ }
272
+ if (options.multipart && typeof FormData !== 'undefined') {
273
+ if (options.postMessage) {
274
+ // window.postMessage does not allow sending FormData
275
+ // objects, so we just add the File/Blob objects to
276
+ // the formData array and let the postMessage window
277
+ // create the FormData object out of this array:
278
+ formData = this._getFormData(options);
279
+ if (options.blob) {
280
+ formData.push({
281
+ name: options.paramName,
282
+ value: options.blob
283
+ });
284
+ } else {
285
+ $.each(options.files, function (index, file) {
286
+ formData.push({
287
+ name: options.paramName,
288
+ value: file
289
+ });
290
+ });
291
+ }
292
+ } else {
293
+ if (options.formData instanceof FormData) {
294
+ formData = options.formData;
295
+ } else {
296
+ formData = new FormData();
297
+ $.each(this._getFormData(options), function (index, field) {
298
+ formData.append(field.name, field.value);
299
+ });
300
+ }
301
+ if (options.blob) {
302
+ formData.append(options.paramName, options.blob, file.name);
303
+ } else {
304
+ $.each(options.files, function (index, file) {
305
+ // File objects are also Blob instances.
306
+ // This check allows the tests to run with
307
+ // dummy objects:
308
+ if (file instanceof Blob) {
309
+ formData.append(options.paramName, file, file.name);
310
+ }
311
+ });
312
+ }
313
+ }
314
+ options.data = formData;
315
+ }
316
+ // Blob reference is not needed anymore, free memory:
317
+ options.blob = null;
318
+ },
319
+
320
+ _initIframeSettings: function (options) {
321
+ // Setting the dataType to iframe enables the iframe transport:
322
+ options.dataType = 'iframe ' + (options.dataType || '');
323
+ // The iframe transport accepts a serialized array as form data:
324
+ options.formData = this._getFormData(options);
325
+ // Add redirect url to form data on cross-domain uploads:
326
+ if (options.redirect && $('<a></a>').prop('href', options.url)
327
+ .prop('host') !== location.host) {
328
+ options.formData.push({
329
+ name: options.redirectParamName || 'redirect',
330
+ value: options.redirect
331
+ });
332
+ }
333
+ },
334
+
335
+ _initDataSettings: function (options) {
336
+ if (this._isXHRUpload(options)) {
337
+ if (!this._chunkedUpload(options, true)) {
338
+ if (!options.data) {
339
+ this._initXHRData(options);
340
+ }
341
+ this._initProgressListener(options);
342
+ }
343
+ if (options.postMessage) {
344
+ // Setting the dataType to postmessage enables the
345
+ // postMessage transport:
346
+ options.dataType = 'postmessage ' + (options.dataType || '');
347
+ }
348
+ } else {
349
+ this._initIframeSettings(options, 'iframe');
350
+ }
351
+ },
352
+
353
+ _initFormSettings: function (options) {
354
+ // Retrieve missing options from the input field and the
355
+ // associated form, if available:
356
+ if (!options.form || !options.form.length) {
357
+ options.form = $(options.fileInput.prop('form'));
358
+ }
359
+ if (!options.paramName) {
360
+ options.paramName = options.fileInput.prop('name') ||
361
+ 'files[]';
362
+ }
363
+ if (!options.url) {
364
+ options.url = options.form.prop('action') || location.href;
365
+ }
366
+ // The HTTP request method must be "POST" or "PUT":
367
+ options.type = (options.type || options.form.prop('method') || '')
368
+ .toUpperCase();
369
+ if (options.type !== 'POST' && options.type !== 'PUT') {
370
+ options.type = 'POST';
371
+ }
372
+ },
373
+
374
+ _getAJAXSettings: function (data) {
375
+ var options = $.extend({}, this.options, data);
376
+ this._initFormSettings(options);
377
+ this._initDataSettings(options);
378
+ return options;
379
+ },
380
+
381
+ // Maps jqXHR callbacks to the equivalent
382
+ // methods of the given Promise object:
383
+ _enhancePromise: function (promise) {
384
+ promise.success = promise.done;
385
+ promise.error = promise.fail;
386
+ promise.complete = promise.always;
387
+ return promise;
388
+ },
389
+
390
+ // Creates and returns a Promise object enhanced with
391
+ // the jqXHR methods abort, success, error and complete:
392
+ _getXHRPromise: function (resolveOrReject, context, args) {
393
+ var dfd = $.Deferred(),
394
+ promise = dfd.promise();
395
+ context = context || this.options.context || promise;
396
+ if (resolveOrReject === true) {
397
+ dfd.resolveWith(context, args);
398
+ } else if (resolveOrReject === false) {
399
+ dfd.rejectWith(context, args);
400
+ }
401
+ promise.abort = dfd.promise;
402
+ return this._enhancePromise(promise);
403
+ },
404
+
405
+ // Uploads a file in multiple, sequential requests
406
+ // by splitting the file up in multiple blob chunks.
407
+ // If the second parameter is true, only tests if the file
408
+ // should be uploaded in chunks, but does not invoke any
409
+ // upload requests:
410
+ _chunkedUpload: function (options, testOnly) {
411
+ var that = this,
412
+ file = options.files[0],
413
+ fs = file.size,
414
+ ub = options.uploadedBytes = options.uploadedBytes || 0,
415
+ mcs = options.maxChunkSize || fs,
416
+ // Use the Blob methods with the slice implementation
417
+ // according to the W3C Blob API specification:
418
+ slice = file.webkitSlice || file.mozSlice || file.slice,
419
+ upload,
420
+ n,
421
+ jqXHR,
422
+ pipe;
423
+ if (!(this._isXHRUpload(options) && slice && (ub || mcs < fs)) ||
424
+ options.data) {
425
+ return false;
426
+ }
427
+ if (testOnly) {
428
+ return true;
429
+ }
430
+ if (ub >= fs) {
431
+ file.error = 'uploadedBytes';
432
+ return this._getXHRPromise(
433
+ false,
434
+ options.context,
435
+ [null, 'error', file.error]
436
+ );
437
+ }
438
+ // n is the number of blobs to upload,
439
+ // calculated via filesize, uploaded bytes and max chunk size:
440
+ n = Math.ceil((fs - ub) / mcs);
441
+ // The chunk upload method accepting the chunk number as parameter:
442
+ upload = function (i) {
443
+ if (!i) {
444
+ return that._getXHRPromise(true, options.context);
445
+ }
446
+ // Upload the blobs in sequential order:
447
+ return upload(i -= 1).pipe(function () {
448
+ // Clone the options object for each chunk upload:
449
+ var o = $.extend({}, options);
450
+ o.blob = slice.call(
451
+ file,
452
+ ub + i * mcs,
453
+ ub + (i + 1) * mcs
454
+ );
455
+ // Store the current chunk size, as the blob itself
456
+ // will be dereferenced after data processing:
457
+ o.chunkSize = o.blob.size;
458
+ // Process the upload data (the blob and potential form data):
459
+ that._initXHRData(o);
460
+ // Add progress listeners for this chunk upload:
461
+ that._initProgressListener(o);
462
+ jqXHR = ($.ajax(o) || that._getXHRPromise(false, o.context))
463
+ .done(function () {
464
+ // Create a progress event if upload is done and
465
+ // no progress event has been invoked for this chunk:
466
+ if (!o.loaded) {
467
+ that._onProgress($.Event('progress', {
468
+ lengthComputable: true,
469
+ loaded: o.chunkSize,
470
+ total: o.chunkSize
471
+ }), o);
472
+ }
473
+ options.uploadedBytes = o.uploadedBytes +=
474
+ o.chunkSize;
475
+ });
476
+ return jqXHR;
477
+ });
478
+ };
479
+ // Return the piped Promise object, enhanced with an abort method,
480
+ // which is delegated to the jqXHR object of the current upload,
481
+ // and jqXHR callbacks mapped to the equivalent Promise methods:
482
+ pipe = upload(n);
483
+ pipe.abort = function () {
484
+ return jqXHR.abort();
485
+ };
486
+ return this._enhancePromise(pipe);
487
+ },
488
+
489
+ _beforeSend: function (e, data) {
490
+ if (this._active === 0) {
491
+ // the start callback is triggered when an upload starts
492
+ // and no other uploads are currently running,
493
+ // equivalent to the global ajaxStart event:
494
+ this._trigger('start');
495
+ }
496
+ this._active += 1;
497
+ // Initialize the global progress values:
498
+ this._loaded += data.uploadedBytes || 0;
499
+ this._total += this._getTotal(data.files);
500
+ },
501
+
502
+ _onDone: function (result, textStatus, jqXHR, options) {
503
+ if (!this._isXHRUpload(options)) {
504
+ // Create a progress event for each iframe load:
505
+ this._onProgress($.Event('progress', {
506
+ lengthComputable: true,
507
+ loaded: 1,
508
+ total: 1
509
+ }), options);
510
+ }
511
+ options.result = result;
512
+ options.textStatus = textStatus;
513
+ options.jqXHR = jqXHR;
514
+ this._trigger('done', null, options);
515
+ },
516
+
517
+ _onFail: function (jqXHR, textStatus, errorThrown, options) {
518
+ options.jqXHR = jqXHR;
519
+ options.textStatus = textStatus;
520
+ options.errorThrown = errorThrown;
521
+ this._trigger('fail', null, options);
522
+ if (options.recalculateProgress) {
523
+ // Remove the failed (error or abort) file upload from
524
+ // the global progress calculation:
525
+ this._loaded -= options.loaded || options.uploadedBytes || 0;
526
+ this._total -= options.total || this._getTotal(options.files);
527
+ }
528
+ },
529
+
530
+ _onAlways: function (jqXHRorResult, textStatus, jqXHRorError, options) {
531
+ this._active -= 1;
532
+ options.textStatus = textStatus;
533
+ if (jqXHRorError && jqXHRorError.always) {
534
+ options.jqXHR = jqXHRorError;
535
+ options.result = jqXHRorResult;
536
+ } else {
537
+ options.jqXHR = jqXHRorResult;
538
+ options.errorThrown = jqXHRorError;
539
+ }
540
+ this._trigger('always', null, options);
541
+ if (this._active === 0) {
542
+ // The stop callback is triggered when all uploads have
543
+ // been completed, equivalent to the global ajaxStop event:
544
+ this._trigger('stop');
545
+ // Reset the global progress values:
546
+ this._loaded = this._total = 0;
547
+ }
548
+ },
549
+
550
+ _onSend: function (e, data) {
551
+ var that = this,
552
+ jqXHR,
553
+ slot,
554
+ pipe,
555
+ options = that._getAJAXSettings(data),
556
+ send = function (resolve, args) {
557
+ that._sending += 1;
558
+ jqXHR = jqXHR || (
559
+ (resolve !== false &&
560
+ that._trigger('send', e, options) !== false &&
561
+ (that._chunkedUpload(options) || $.ajax(options))) ||
562
+ that._getXHRPromise(false, options.context, args)
563
+ ).done(function (result, textStatus, jqXHR) {
564
+ that._onDone(result, textStatus, jqXHR, options);
565
+ }).fail(function (jqXHR, textStatus, errorThrown) {
566
+ that._onFail(jqXHR, textStatus, errorThrown, options);
567
+ }).always(function (jqXHRorResult, textStatus, jqXHRorError) {
568
+ that._sending -= 1;
569
+ that._onAlways(
570
+ jqXHRorResult,
571
+ textStatus,
572
+ jqXHRorError,
573
+ options
574
+ );
575
+ if (options.limitConcurrentUploads &&
576
+ options.limitConcurrentUploads > that._sending) {
577
+ // Start the next queued upload,
578
+ // that has not been aborted:
579
+ var nextSlot = that._slots.shift();
580
+ while (nextSlot) {
581
+ if (!nextSlot.isRejected()) {
582
+ nextSlot.resolve();
583
+ break;
584
+ }
585
+ nextSlot = that._slots.shift();
586
+ }
587
+ }
588
+ });
589
+ return jqXHR;
590
+ };
591
+ this._beforeSend(e, options);
592
+ if (this.options.sequentialUploads ||
593
+ (this.options.limitConcurrentUploads &&
594
+ this.options.limitConcurrentUploads <= this._sending)) {
595
+ if (this.options.limitConcurrentUploads > 1) {
596
+ slot = $.Deferred();
597
+ this._slots.push(slot);
598
+ pipe = slot.pipe(send);
599
+ } else {
600
+ pipe = (this._sequence = this._sequence.pipe(send, send));
601
+ }
602
+ // Return the piped Promise object, enhanced with an abort method,
603
+ // which is delegated to the jqXHR object of the current upload,
604
+ // and jqXHR callbacks mapped to the equivalent Promise methods:
605
+ pipe.abort = function () {
606
+ var args = [undefined, 'abort', 'abort'];
607
+ if (!jqXHR) {
608
+ if (slot) {
609
+ slot.rejectWith(args);
610
+ }
611
+ return send(false, args);
612
+ }
613
+ return jqXHR.abort();
614
+ };
615
+ return this._enhancePromise(pipe);
616
+ }
617
+ return send();
618
+ },
619
+
620
+ _onAdd: function (e, data) {
621
+ var that = this,
622
+ result = true,
623
+ options = $.extend({}, this.options, data),
624
+ limit = options.limitMultiFileUploads,
625
+ fileSet,
626
+ i;
627
+ if (!(options.singleFileUploads || limit) ||
628
+ !this._isXHRUpload(options)) {
629
+ fileSet = [data.files];
630
+ } else if (!options.singleFileUploads && limit) {
631
+ fileSet = [];
632
+ for (i = 0; i < data.files.length; i += limit) {
633
+ fileSet.push(data.files.slice(i, i + limit));
634
+ }
635
+ }
636
+ data.originalFiles = data.files;
637
+ $.each(fileSet || data.files, function (index, element) {
638
+ var files = fileSet ? element : [element],
639
+ newData = $.extend({}, data, {files: files});
640
+ newData.submit = function () {
641
+ newData.jqXHR = this.jqXHR =
642
+ (that._trigger('submit', e, this) !== false) &&
643
+ that._onSend(e, this);
644
+ return this.jqXHR;
645
+ };
646
+ return (result = that._trigger('add', e, newData));
647
+ });
648
+ return result;
649
+ },
650
+
651
+ // File Normalization for Gecko 1.9.1 (Firefox 3.5) support:
652
+ _normalizeFile: function (index, file) {
653
+ if (file.name === undefined && file.size === undefined) {
654
+ file.name = file.fileName;
655
+ file.size = file.fileSize;
656
+ }
657
+ },
658
+
659
+ _replaceFileInput: function (input) {
660
+ var inputClone = input.clone(true);
661
+ $('<form></form>').append(inputClone)[0].reset();
662
+ // Detaching allows to insert the fileInput on another form
663
+ // without loosing the file input value:
664
+ input.after(inputClone).detach();
665
+ // Avoid memory leaks with the detached file input:
666
+ $.cleanData(input.unbind('remove'));
667
+ // Replace the original file input element in the fileInput
668
+ // collection with the clone, which has been copied including
669
+ // event handlers:
670
+ this.options.fileInput = this.options.fileInput.map(function (i, el) {
671
+ if (el === input[0]) {
672
+ return inputClone[0];
673
+ }
674
+ return el;
675
+ });
676
+ // If the widget has been initialized on the file input itself,
677
+ // override this.element with the file input clone:
678
+ if (input[0] === this.element[0]) {
679
+ this.element = inputClone;
680
+ }
681
+ },
682
+
683
+ _onChange: function (e) {
684
+ var that = e.data.fileupload,
685
+ data = {
686
+ files: $.each($.makeArray(e.target.files), that._normalizeFile),
687
+ fileInput: $(e.target),
688
+ form: $(e.target.form)
689
+ };
690
+ if (!data.files.length) {
691
+ // If the files property is not available, the browser does not
692
+ // support the File API and we add a pseudo File object with
693
+ // the input value as name with path information removed:
694
+ data.files = [{name: e.target.value.replace(/^.*\\/, '')}];
695
+ }
696
+ if (that.options.replaceFileInput) {
697
+ that._replaceFileInput(data.fileInput);
698
+ }
699
+ if (that._trigger('change', e, data) === false ||
700
+ that._onAdd(e, data) === false) {
701
+ return false;
702
+ }
703
+ },
704
+
705
+ _onPaste: function (e) {
706
+ var that = e.data.fileupload,
707
+ cbd = e.originalEvent.clipboardData,
708
+ items = (cbd && cbd.items) || [],
709
+ data = {files: []};
710
+ $.each(items, function (index, item) {
711
+ var file = item.getAsFile && item.getAsFile();
712
+ if (file) {
713
+ data.files.push(file);
714
+ }
715
+ });
716
+ if (that._trigger('paste', e, data) === false ||
717
+ that._onAdd(e, data) === false) {
718
+ return false;
719
+ }
720
+ },
721
+
722
+ _onDrop: function (e) {
723
+ var that = e.data.fileupload,
724
+ dataTransfer = e.dataTransfer = e.originalEvent.dataTransfer,
725
+ data = {
726
+ files: $.each(
727
+ $.makeArray(dataTransfer && dataTransfer.files),
728
+ that._normalizeFile
729
+ )
730
+ };
731
+ if (that._trigger('drop', e, data) === false ||
732
+ that._onAdd(e, data) === false) {
733
+ return false;
734
+ }
735
+ e.preventDefault();
736
+ },
737
+
738
+ _onDragOver: function (e) {
739
+ var that = e.data.fileupload,
740
+ dataTransfer = e.dataTransfer = e.originalEvent.dataTransfer;
741
+ if (that._trigger('dragover', e) === false) {
742
+ return false;
743
+ }
744
+ if (dataTransfer) {
745
+ dataTransfer.dropEffect = dataTransfer.effectAllowed = 'copy';
746
+ }
747
+ e.preventDefault();
748
+ },
749
+
750
+ _initEventHandlers: function () {
751
+ var ns = this.options.namespace;
752
+ this.options.dropZone
753
+ .bind('dragover.' + ns, {fileupload: this}, this._onDragOver)
754
+ .bind('drop.' + ns, {fileupload: this}, this._onDrop)
755
+ .bind('paste.' + ns, {fileupload: this}, this._onPaste);
756
+ this.options.fileInput
757
+ .bind('change.' + ns, {fileupload: this}, this._onChange);
758
+ },
759
+
760
+ _destroyEventHandlers: function () {
761
+ var ns = this.options.namespace;
762
+ this.options.dropZone
763
+ .unbind('dragover.' + ns, this._onDragOver)
764
+ .unbind('drop.' + ns, this._onDrop)
765
+ .unbind('paste.' + ns, this._onPaste);
766
+ this.options.fileInput
767
+ .unbind('change.' + ns, this._onChange);
768
+ },
769
+
770
+ _setOption: function (key, value) {
771
+ var refresh = $.inArray(key, this._refreshOptionsList) !== -1;
772
+ if (refresh) {
773
+ this._destroyEventHandlers();
774
+ }
775
+ $.Widget.prototype._setOption.call(this, key, value);
776
+ if (refresh) {
777
+ this._initSpecialOptions();
778
+ this._initEventHandlers();
779
+ }
780
+ },
781
+
782
+ _initSpecialOptions: function () {
783
+ var options = this.options;
784
+ if (options.fileInput === undefined) {
785
+ options.fileInput = this.element.is('input:file') ?
786
+ this.element : this.element.find('input:file');
787
+ } else if (!(options.fileInput instanceof $)) {
788
+ options.fileInput = $(options.fileInput);
789
+ }
790
+ if (!(options.dropZone instanceof $)) {
791
+ options.dropZone = $(options.dropZone);
792
+ }
793
+ },
794
+
795
+ _create: function () {
796
+ var options = this.options,
797
+ dataOpts = $.extend({}, this.element.data());
798
+ dataOpts[this.widgetName] = undefined;
799
+ $.extend(options, dataOpts);
800
+ options.namespace = options.namespace || this.widgetName;
801
+ this._initSpecialOptions();
802
+ this._slots = [];
803
+ this._sequence = this._getXHRPromise(true);
804
+ this._sending = this._active = this._loaded = this._total = 0;
805
+ this._initEventHandlers();
806
+ },
807
+
808
+ destroy: function () {
809
+ this._destroyEventHandlers();
810
+ $.Widget.prototype.destroy.call(this);
811
+ },
812
+
813
+ enable: function () {
814
+ $.Widget.prototype.enable.call(this);
815
+ this._initEventHandlers();
816
+ },
817
+
818
+ disable: function () {
819
+ this._destroyEventHandlers();
820
+ $.Widget.prototype.disable.call(this);
821
+ },
822
+
823
+ // This method is exposed to the widget API and allows adding files
824
+ // using the fileupload API. The data parameter accepts an object which
825
+ // must have a files property and can contain additional options:
826
+ // .fileupload('add', {files: filesList});
827
+ add: function (data) {
828
+ if (!data || this.options.disabled) {
829
+ return;
830
+ }
831
+ data.files = $.each($.makeArray(data.files), this._normalizeFile);
832
+ this._onAdd(null, data);
833
+ },
834
+
835
+ // This method is exposed to the widget API and allows sending files
836
+ // using the fileupload API. The data parameter accepts an object which
837
+ // must have a files property and can contain additional options:
838
+ // .fileupload('send', {files: filesList});
839
+ // The method returns a Promise object for the file upload call.
840
+ send: function (data) {
841
+ if (data && !this.options.disabled) {
842
+ data.files = $.each($.makeArray(data.files), this._normalizeFile);
843
+ if (data.files.length) {
844
+ return this._onSend(null, data);
845
+ }
846
+ }
847
+ return this._getXHRPromise(false, data && data.context);
848
+ }
849
+
850
+ });
851
+
852
+ }));