resumablejs-rails 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: cd2e7e4367f9bed114d813f61e0e91c579ad295a
4
+ data.tar.gz: 57680f77bb5313c964be2bbd2b485d67735c2f3f
5
+ SHA512:
6
+ metadata.gz: 99675ea0f713e987a81d7b2c62e95593dc17ec022a9aa74f251dcad10d08b7b8b856e509c4f0ad581a719d16a2cc65df62b97435e56adad0c1c1288b07a6d690
7
+ data.tar.gz: 8794a1c2e8d5df2f2331e05f40884678472ef747f426411d98f7098e651f5a4693b15b97a07766bb34238853c85b30c75739d4150a0c4373e8bd45e3f4da6543
data/README.md ADDED
@@ -0,0 +1,31 @@
1
+ # resumablejs-rails
2
+
3
+ resumable.js for the Rails Asset Pipeline
4
+
5
+ ## Installation
6
+
7
+ Add this to your application's Gemfile:
8
+
9
+ gem 'resumablejs-rails'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install resumablejs-rails
18
+
19
+ ## Usage
20
+
21
+ Add the following directive to your Javascript manifest file (application.js):
22
+
23
+ //= require resumable
24
+
25
+ ## Contributing
26
+
27
+ 1. Fork it
28
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
29
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
30
+ 4. Push to the branch (`git push origin my-new-feature`)
31
+ 5. Create new Pull Request
@@ -0,0 +1,8 @@
1
+ require "resumablejs-rails/version"
2
+
3
+ module Resumablejs
4
+ module Rails
5
+ class Engine < ::Rails::Engine
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,5 @@
1
+ module Resumablejs
2
+ module Rails
3
+ VERSION = "0.1.0"
4
+ end
5
+ end
@@ -0,0 +1,717 @@
1
+ /*
2
+ * MIT Licensed
3
+ * http://www.23developer.com/opensource
4
+ * http://github.com/23/resumable.js
5
+ * Steffen Tiedemann Christensen, steffen@23company.com
6
+ */
7
+
8
+ var Resumable = function(opts){
9
+ if ( !(this instanceof Resumable ) ) {
10
+ return new Resumable( opts );
11
+ }
12
+ // SUPPORTED BY BROWSER?
13
+ // Check if these features are support by the browser:
14
+ // - File object type
15
+ // - Blob object type
16
+ // - FileList object type
17
+ // - slicing files
18
+ this.support = (
19
+ (typeof(File)!=='undefined')
20
+ &&
21
+ (typeof(Blob)!=='undefined')
22
+ &&
23
+ (typeof(FileList)!=='undefined')
24
+ &&
25
+ (!!Blob.prototype.webkitSlice||!!Blob.prototype.mozSlice||Blob.prototype.slice||false)
26
+ );
27
+ if(!this.support) return(false);
28
+
29
+
30
+ // PROPERTIES
31
+ var $ = this;
32
+ $.files = [];
33
+ $.defaults = {
34
+ chunkSize:1*1024*1024,
35
+ forceChunkSize:false,
36
+ simultaneousUploads:3,
37
+ fileParameterName:'file',
38
+ throttleProgressCallbacks:0.5,
39
+ query:{},
40
+ headers:{},
41
+ preprocess:null,
42
+ method:'multipart',
43
+ prioritizeFirstAndLastChunk:false,
44
+ target:'/',
45
+ testChunks:true,
46
+ generateUniqueIdentifier:null,
47
+ maxChunkRetries:undefined,
48
+ chunkRetryInterval:undefined,
49
+ permanentErrors:[415, 500, 501],
50
+ maxFiles:undefined,
51
+ maxFilesErrorCallback:function (files, errorCount) {
52
+ var maxFiles = $.getOpt('maxFiles');
53
+ alert('Please upload ' + maxFiles + ' file' + (maxFiles === 1 ? '' : 's') + ' at a time.');
54
+ },
55
+ minFileSize:undefined,
56
+ minFileSizeErrorCallback:function(file, errorCount) {
57
+ alert(file.fileName +' is too small, please upload files larger than ' + $h.formatSize($.getOpt('minFileSize')) + '.');
58
+ },
59
+ maxFileSize:undefined,
60
+ maxFileSizeErrorCallback:function(file, errorCount) {
61
+ alert(file.fileName +' is too large, please upload files less than ' + $h.formatSize($.getOpt('maxFileSize')) + '.');
62
+ },
63
+ fileType: [],
64
+ fileTypeErrorCallback: function(file, errorCount) {
65
+ alert(file.fileName +' has type not allowed, please upload files of type ' + $.getOpt('fileType') + '.');
66
+ }
67
+ };
68
+ $.opts = opts||{};
69
+ $.getOpt = function(o) {
70
+ var $this = this;
71
+ // Get multiple option if passed an array
72
+ if(o instanceof Array) {
73
+ var options = {};
74
+ $h.each(o, function(option){
75
+ options[option] = $this.getOpt(option);
76
+ });
77
+ return options;
78
+ }
79
+ // Otherwise, just return a simple option
80
+ if ($this instanceof ResumableChunk) {
81
+ if (typeof $this.opts[o] !== 'undefined') { return $this.opts[o]; }
82
+ else { $this = $this.fileObj; }
83
+ }
84
+ if ($this instanceof ResumableFile) {
85
+ if (typeof $this.opts[o] !== 'undefined') { return $this.opts[o]; }
86
+ else { $this = $this.resumableObj; }
87
+ }
88
+ if ($this instanceof Resumable) {
89
+ if (typeof $this.opts[o] !== 'undefined') { return $this.opts[o]; }
90
+ else { return $this.defaults[o]; }
91
+ }
92
+ };
93
+
94
+ // EVENTS
95
+ // catchAll(event, ...)
96
+ // fileSuccess(file), fileProgress(file), fileAdded(file, event), fileRetry(file), fileError(file, message),
97
+ // complete(), progress(), error(message, file), pause()
98
+ $.events = [];
99
+ $.on = function(event,callback){
100
+ $.events.push(event.toLowerCase(), callback);
101
+ };
102
+ $.fire = function(){
103
+ // `arguments` is an object, not array, in FF, so:
104
+ var args = [];
105
+ for (var i=0; i<arguments.length; i++) args.push(arguments[i]);
106
+ // Find event listeners, and support pseudo-event `catchAll`
107
+ var event = args[0].toLowerCase();
108
+ for (var i=0; i<=$.events.length; i+=2) {
109
+ if($.events[i]==event) $.events[i+1].apply($,args.slice(1));
110
+ if($.events[i]=='catchall') $.events[i+1].apply(null,args);
111
+ }
112
+ if(event=='fileerror') $.fire('error', args[2], args[1]);
113
+ if(event=='fileprogress') $.fire('progress');
114
+ };
115
+
116
+
117
+ // INTERNAL HELPER METHODS (handy, but ultimately not part of uploading)
118
+ $h = {
119
+ stopEvent: function(e){
120
+ e.stopPropagation();
121
+ e.preventDefault();
122
+ },
123
+ each: function(o,callback){
124
+ if(typeof(o.length)!=='undefined') {
125
+ for (var i=0; i<o.length; i++) {
126
+ // Array or FileList
127
+ if(callback(o[i])===false) return;
128
+ }
129
+ } else {
130
+ for (i in o) {
131
+ // Object
132
+ if(callback(i,o[i])===false) return;
133
+ }
134
+ }
135
+ },
136
+ generateUniqueIdentifier:function(file){
137
+ var custom = $.getOpt('generateUniqueIdentifier');
138
+ if(typeof custom === 'function') {
139
+ return custom(file);
140
+ }
141
+ var relativePath = file.webkitRelativePath||file.fileName||file.name; // Some confusion in different versions of Firefox
142
+ var size = file.size;
143
+ return(size + '-' + relativePath.replace(/[^0-9a-zA-Z_-]/img, ''));
144
+ },
145
+ contains:function(array,test) {
146
+ var result = false;
147
+
148
+ $h.each(array, function(value) {
149
+ if (value == test) {
150
+ result = true;
151
+ return false;
152
+ }
153
+ return true;
154
+ });
155
+
156
+ return result;
157
+ },
158
+ formatSize:function(size){
159
+ if(size<1024) {
160
+ return size + ' bytes';
161
+ } else if(size<1024*1024) {
162
+ return (size/1024.0).toFixed(0) + ' KB';
163
+ } else if(size<1024*1024*1024) {
164
+ return (size/1024.0/1024.0).toFixed(1) + ' MB';
165
+ } else {
166
+ return (size/1024.0/1024.0/1024.0).toFixed(1) + ' GB';
167
+ }
168
+ }
169
+ };
170
+
171
+ var onDrop = function(event){
172
+ $h.stopEvent(event);
173
+ appendFilesFromFileList(event.dataTransfer.files, event);
174
+ };
175
+ var onDragOver = function(e) {
176
+ e.preventDefault();
177
+ };
178
+
179
+ // INTERNAL METHODS (both handy and responsible for the heavy load)
180
+ var appendFilesFromFileList = function(fileList, event){
181
+ // check for uploading too many files
182
+ var errorCount = 0;
183
+ var o = $.getOpt(['maxFiles', 'minFileSize', 'maxFileSize', 'maxFilesErrorCallback', 'minFileSizeErrorCallback', 'maxFileSizeErrorCallback', 'fileType', 'fileTypeErrorCallback']);
184
+ if (typeof(o.maxFiles)!=='undefined' && o.maxFiles<(fileList.length+$.files.length)) {
185
+ o.maxFilesErrorCallback(fileList, errorCount++);
186
+ return false;
187
+ }
188
+ var files = [];
189
+ $h.each(fileList, function(file){
190
+ file.name = file.fileName = file.fileName||file.name; // consistency across browsers for the error message
191
+
192
+ if (o.fileType.length > 0 && !$h.contains(o.fileType, file.type.split('/')[1])) {
193
+ o.fileTypeErrorCallback(file, errorCount++);
194
+ return false;
195
+ }
196
+
197
+ if (typeof(o.minFileSize)!=='undefined' && file.size<o.minFileSize) {
198
+ o.minFileSizeErrorCallback(file, errorCount++);
199
+ return false;
200
+ }
201
+ if (typeof(o.maxFileSize)!=='undefined' && file.size>o.maxFileSize) {
202
+ o.maxFileSizeErrorCallback(file, errorCount++);
203
+ return false;
204
+ }
205
+
206
+ // directories have size == 0
207
+ if (file.size > 0 && !$.getFromUniqueIdentifier($h.generateUniqueIdentifier(file))) {
208
+ var f = new ResumableFile($, file);
209
+ $.files.push(f);
210
+ files.push(f);
211
+ $.fire('fileAdded', f, event);
212
+ }
213
+ });
214
+ $.fire('filesAdded', files);
215
+ };
216
+
217
+ // INTERNAL OBJECT TYPES
218
+ function ResumableFile(resumableObj, file){
219
+ var $ = this;
220
+ $.opts = {};
221
+ $.getOpt = resumableObj.getOpt;
222
+ $._prevProgress = 0;
223
+ $.resumableObj = resumableObj;
224
+ $.file = file;
225
+ $.fileName = file.fileName||file.name; // Some confusion in different versions of Firefox
226
+ $.size = file.size;
227
+ $.relativePath = file.webkitRelativePath || $.fileName;
228
+ $.uniqueIdentifier = $h.generateUniqueIdentifier(file);
229
+ var _error = false;
230
+
231
+ // Callback when something happens within the chunk
232
+ var chunkEvent = function(event, message){
233
+ // event can be 'progress', 'success', 'error' or 'retry'
234
+ switch(event){
235
+ case 'progress':
236
+ $.resumableObj.fire('fileProgress', $);
237
+ break;
238
+ case 'error':
239
+ $.abort();
240
+ _error = true;
241
+ $.chunks = [];
242
+ $.resumableObj.fire('fileError', $, message);
243
+ break;
244
+ case 'success':
245
+ if(_error) return;
246
+ $.resumableObj.fire('fileProgress', $); // it's at least progress
247
+ if($.progress()==1) {
248
+ $.resumableObj.fire('fileSuccess', $, message);
249
+ }
250
+ break;
251
+ case 'retry':
252
+ $.resumableObj.fire('fileRetry', $);
253
+ break;
254
+ }
255
+ }
256
+
257
+ // Main code to set up a file object with chunks,
258
+ // packaged to be able to handle retries if needed.
259
+ $.chunks = [];
260
+ $.abort = function(){
261
+ // Stop current uploads
262
+ $h.each($.chunks, function(c){
263
+ if(c.status()=='uploading') c.abort();
264
+ });
265
+ $.resumableObj.fire('fileProgress', $);
266
+ }
267
+ $.cancel = function(){
268
+ // Reset this file to be void
269
+ var _chunks = $.chunks;
270
+ $.chunks = [];
271
+ // Stop current uploads
272
+ $h.each(_chunks, function(c){
273
+ if(c.status()=='uploading') {
274
+ c.abort();
275
+ $.resumableObj.uploadNextChunk();
276
+ }
277
+ });
278
+ $.resumableObj.removeFile($);
279
+ $.resumableObj.fire('fileProgress', $);
280
+ },
281
+ $.retry = function(){
282
+ $.bootstrap();
283
+ $.resumableObj.upload();
284
+ }
285
+ $.bootstrap = function(){
286
+ $.abort();
287
+ _error = false;
288
+ // Rebuild stack of chunks from file
289
+ $.chunks = [];
290
+ $._prevProgress = 0;
291
+ var round = $.getOpt('forceChunkSize') ? Math.ceil : Math.floor;
292
+ for (var offset=0; offset<Math.max(round($.file.size/$.getOpt('chunkSize')),1); offset++) {
293
+ $.chunks.push(new ResumableChunk($.resumableObj, $, offset, chunkEvent));
294
+ }
295
+ }
296
+ $.progress = function(){
297
+ if(_error) return(1);
298
+ // Sum up progress across everything
299
+ var ret = 0;
300
+ var error = false;
301
+ $h.each($.chunks, function(c){
302
+ if(c.status()=='error') error = true;
303
+ ret += c.progress(true); // get chunk progress relative to entire file
304
+ });
305
+ ret = (error ? 1 : (ret>0.999 ? 1 : ret))
306
+ ret = Math.max($._prevProgress, ret); // We don't want to lose percentages when an upload is paused
307
+ $._prevProgress = ret;
308
+ return(ret);
309
+ }
310
+
311
+ // Bootstrap and return
312
+ $.bootstrap();
313
+ return(this);
314
+ }
315
+
316
+ function ResumableChunk(resumableObj, fileObj, offset, callback){
317
+ var $ = this;
318
+ $.opts = {};
319
+ $.getOpt = resumableObj.getOpt;
320
+ $.resumableObj = resumableObj;
321
+ $.fileObj = fileObj;
322
+ $.fileObjSize = fileObj.size;
323
+ $.offset = offset;
324
+ $.callback = callback;
325
+ $.lastProgressCallback = (new Date);
326
+ $.tested = false;
327
+ $.retries = 0;
328
+ $.preprocessState = 0; // 0 = unprocessed, 1 = processing, 2 = finished
329
+
330
+ // Computed properties
331
+ var chunkSize = $.getOpt('chunkSize');
332
+ $.loaded = 0;
333
+ $.startByte = $.offset*chunkSize;
334
+ $.endByte = Math.min($.fileObjSize, ($.offset+1)*chunkSize);
335
+ if ($.fileObjSize-$.endByte < chunkSize && !$.getOpt('forceChunkSize')) {
336
+ // The last chunk will be bigger than the chunk size, but less than 2*chunkSize
337
+ $.endByte = $.fileObjSize;
338
+ }
339
+ $.xhr = null;
340
+
341
+ // test() makes a GET request without any data to see if the chunk has already been uploaded in a previous session
342
+ $.test = function(){
343
+ // Set up request and listen for event
344
+ $.xhr = new XMLHttpRequest();
345
+
346
+ var testHandler = function(e){
347
+ $.tested = true;
348
+ var status = $.status();
349
+ if(status=='success') {
350
+ $.callback(status, $.message());
351
+ $.resumableObj.uploadNextChunk();
352
+ } else {
353
+ $.send();
354
+ }
355
+ }
356
+ $.xhr.addEventListener("load", testHandler, false);
357
+ $.xhr.addEventListener("error", testHandler, false);
358
+
359
+ // Add data from the query options
360
+ var url = ""
361
+ var params = [];
362
+ var customQuery = $.getOpt('query');
363
+ if(typeof customQuery == "function") customQuery = customQuery($.fileObj, $);
364
+ $h.each(customQuery, function(k,v){
365
+ params.push([encodeURIComponent(k), encodeURIComponent(v)].join('='));
366
+ });
367
+ // Add extra data to identify chunk
368
+ params.push(['resumableChunkNumber', encodeURIComponent($.offset+1)].join('='));
369
+ params.push(['resumableChunkSize', encodeURIComponent($.getOpt('chunkSize'))].join('='));
370
+ params.push(['resumableCurrentChunkSize', encodeURIComponent($.endByte - $.startByte)].join('='));
371
+ params.push(['resumableTotalSize', encodeURIComponent($.fileObjSize)].join('='));
372
+ params.push(['resumableIdentifier', encodeURIComponent($.fileObj.uniqueIdentifier)].join('='));
373
+ params.push(['resumableFilename', encodeURIComponent($.fileObj.fileName)].join('='));
374
+ params.push(['resumableRelativePath', encodeURIComponent($.fileObj.relativePath)].join('='));
375
+ // Append the relevant chunk and send it
376
+ $.xhr.open("GET", $.getOpt('target') + '?' + params.join('&'));
377
+ // Add data from header options
378
+ $h.each($.getOpt('headers'), function(k,v) {
379
+ $.xhr.setRequestHeader(k, v);
380
+ });
381
+ $.xhr.send(null);
382
+ }
383
+
384
+ $.preprocessFinished = function(){
385
+ $.preprocessState = 2;
386
+ $.send();
387
+ }
388
+
389
+ // send() uploads the actual data in a POST call
390
+ $.send = function(){
391
+ var preprocess = $.getOpt('preprocess');
392
+ if(typeof preprocess === 'function') {
393
+ switch($.preprocessState) {
394
+ case 0: preprocess($); $.preprocessState = 1; return;
395
+ case 1: return;
396
+ case 2: break;
397
+ }
398
+ }
399
+ if($.getOpt('testChunks') && !$.tested) {
400
+ $.test();
401
+ return;
402
+ }
403
+
404
+ // Set up request and listen for event
405
+ $.xhr = new XMLHttpRequest();
406
+
407
+ // Progress
408
+ $.xhr.upload.addEventListener("progress", function(e){
409
+ if( (new Date) - $.lastProgressCallback > $.getOpt('throttleProgressCallbacks') * 1000 ) {
410
+ $.callback('progress');
411
+ $.lastProgressCallback = (new Date);
412
+ }
413
+ $.loaded=e.loaded||0;
414
+ }, false);
415
+ $.loaded = 0;
416
+ $.callback('progress');
417
+
418
+ // Done (either done, failed or retry)
419
+ var doneHandler = function(e){
420
+ var status = $.status();
421
+ if(status=='success'||status=='error') {
422
+ $.callback(status, $.message());
423
+ $.resumableObj.uploadNextChunk();
424
+ } else {
425
+ $.callback('retry', $.message());
426
+ $.abort();
427
+ $.retries++;
428
+ var retryInterval = $.getOpt('chunkRetryInterval');
429
+ if(retryInterval !== undefined) {
430
+ setTimeout($.send, retryInterval);
431
+ } else {
432
+ $.send();
433
+ }
434
+ }
435
+ };
436
+ $.xhr.addEventListener("load", doneHandler, false);
437
+ $.xhr.addEventListener("error", doneHandler, false);
438
+
439
+ // Set up the basic query data from Resumable
440
+ var query = {
441
+ resumableChunkNumber: $.offset+1,
442
+ resumableChunkSize: $.getOpt('chunkSize'),
443
+ resumableCurrentChunkSize: $.endByte - $.startByte,
444
+ resumableTotalSize: $.fileObjSize,
445
+ resumableIdentifier: $.fileObj.uniqueIdentifier,
446
+ resumableFilename: $.fileObj.fileName,
447
+ resumableRelativePath: $.fileObj.relativePath
448
+ }
449
+ // Mix in custom data
450
+ var customQuery = $.getOpt('query');
451
+ if(typeof customQuery == "function") customQuery = customQuery($.fileObj, $);
452
+ $h.each(customQuery, function(k,v){
453
+ query[k] = v;
454
+ });
455
+
456
+ // Add data from header options
457
+ $h.each($.getOpt('headers'), function(k,v) {
458
+ $.xhr.setRequestHeader(k, v);
459
+ });
460
+
461
+ var func = ($.fileObj.file.slice ? 'slice' : ($.fileObj.file.mozSlice ? 'mozSlice' : ($.fileObj.file.webkitSlice ? 'webkitSlice' : 'slice'))),
462
+ bytes = $.fileObj.file[func]($.startByte,$.endByte),
463
+ data = null,
464
+ target = $.getOpt('target');
465
+
466
+ if ($.getOpt('method') === 'octet') {
467
+ // Add data from the query options
468
+ data = bytes;
469
+ var params = [];
470
+ $h.each(query, function(k,v){
471
+ params.push([encodeURIComponent(k), encodeURIComponent(v)].join('='));
472
+ });
473
+ target += '?' + params.join('&');
474
+ } else {
475
+ // Add data from the query options
476
+ data = new FormData();
477
+ $h.each(query, function(k,v){
478
+ data.append(k,v);
479
+ });
480
+ data.append($.getOpt('fileParameterName'), bytes);
481
+ }
482
+
483
+ $.xhr.open('POST', target);
484
+ $.xhr.send(data);
485
+ }
486
+ $.abort = function(){
487
+ // Abort and reset
488
+ if($.xhr) $.xhr.abort();
489
+ $.xhr = null;
490
+ }
491
+ $.status = function(){
492
+ // Returns: 'pending', 'uploading', 'success', 'error'
493
+ if(!$.xhr) {
494
+ return('pending');
495
+ } else if($.xhr.readyState<4) {
496
+ // Status is really 'OPENED', 'HEADERS_RECEIVED' or 'LOADING' - meaning that stuff is happening
497
+ return('uploading');
498
+ } else {
499
+ if($.xhr.status==200) {
500
+ // HTTP 200, perfect
501
+ return('success');
502
+ } else if($h.contains($.getOpt('permanentErrors'), $.xhr.status) || $.retries >= $.getOpt('maxChunkRetries')) {
503
+ // HTTP 415/500/501, permanent error
504
+ return('error');
505
+ } else {
506
+ // this should never happen, but we'll reset and queue a retry
507
+ // a likely case for this would be 503 service unavailable
508
+ $.abort();
509
+ return('pending');
510
+ }
511
+ }
512
+ }
513
+ $.message = function(){
514
+ return($.xhr ? $.xhr.responseText : '');
515
+ }
516
+ $.progress = function(relative){
517
+ if(typeof(relative)==='undefined') relative = false;
518
+ var factor = (relative ? ($.endByte-$.startByte)/$.fileObjSize : 1);
519
+ var s = $.status();
520
+ switch(s){
521
+ case 'success':
522
+ case 'error':
523
+ return(1*factor);
524
+ case 'pending':
525
+ return(0*factor);
526
+ default:
527
+ return($.loaded/($.endByte-$.startByte)*factor);
528
+ }
529
+ }
530
+ return(this);
531
+ }
532
+
533
+ // QUEUE
534
+ $.uploadNextChunk = function(){
535
+ var found = false;
536
+
537
+ // In some cases (such as videos) it's really handy to upload the first
538
+ // and last chunk of a file quickly; this let's the server check the file's
539
+ // metadata and determine if there's even a point in continuing.
540
+ if ($.getOpt('prioritizeFirstAndLastChunk')) {
541
+ $h.each($.files, function(file){
542
+ if(file.chunks.length && file.chunks[0].status()=='pending' && file.chunks[0].preprocessState === 0) {
543
+ file.chunks[0].send();
544
+ found = true;
545
+ return(false);
546
+ }
547
+ if(file.chunks.length>1 && file.chunks[file.chunks.length-1].status()=='pending' && file.chunks[0].preprocessState === 0) {
548
+ file.chunks[file.chunks.length-1].send();
549
+ found = true;
550
+ return(false);
551
+ }
552
+ });
553
+ if(found) return(true);
554
+ }
555
+
556
+ // Now, simply look for the next, best thing to upload
557
+ $h.each($.files, function(file){
558
+ $h.each(file.chunks, function(chunk){
559
+ if(chunk.status()=='pending' && chunk.preprocessState === 0) {
560
+ chunk.send();
561
+ found = true;
562
+ return(false);
563
+ }
564
+ });
565
+ if(found) return(false);
566
+ });
567
+ if(found) return(true);
568
+
569
+ // The are no more outstanding chunks to upload, check is everything is done
570
+ $h.each($.files, function(file){
571
+ outstanding = false;
572
+ $h.each(file.chunks, function(chunk){
573
+ var status = chunk.status();
574
+ if(status=='pending' || status=='uploading' || chunk.preprocessState === 1) {
575
+ outstanding = true;
576
+ return(false);
577
+ }
578
+ });
579
+ if(outstanding) return(false);
580
+ });
581
+ if(!outstanding) {
582
+ // All chunks have been uploaded, complete
583
+ $.fire('complete');
584
+ }
585
+ return(false);
586
+ };
587
+
588
+
589
+ // PUBLIC METHODS FOR RESUMABLE.JS
590
+ $.assignBrowse = function(domNodes, isDirectory){
591
+ if(typeof(domNodes.length)=='undefined') domNodes = [domNodes];
592
+
593
+ // We will create an <input> and overlay it on the domNode
594
+ // (crappy, but since HTML5 doesn't have a cross-browser.browse() method we haven't a choice.
595
+ // FF4+ allows click() for this though: https://developer.mozilla.org/en/using_files_from_web_applications)
596
+ $h.each(domNodes, function(domNode) {
597
+ var input;
598
+ if(domNode.tagName==='INPUT' && domNode.type==='file'){
599
+ input = domNode;
600
+ } else {
601
+ input = document.createElement('input');
602
+ input.setAttribute('type', 'file');
603
+ // Place <input /> with the dom node an position the input to fill the entire space
604
+ domNode.style.display = 'inline-block';
605
+ domNode.style.position = 'relative';
606
+ input.style.position = 'absolute';
607
+ input.style.top = input.style.left = input.style.bottom = input.style.right = 0;
608
+ input.style.opacity = 0;
609
+ input.style.cursor = 'pointer';
610
+ domNode.appendChild(input);
611
+ }
612
+ var maxFiles = $.getOpt('maxFiles');
613
+ if (typeof(maxFiles)==='undefined'||maxFiles!=1){
614
+ input.setAttribute('multiple', 'multiple');
615
+ } else {
616
+ input.removeAttribute('multiple');
617
+ }
618
+ if(isDirectory){
619
+ input.setAttribute('webkitdirectory', 'webkitdirectory');
620
+ } else {
621
+ input.removeAttribute('webkitdirectory');
622
+ }
623
+ // When new files are added, simply append them to the overall list
624
+ input.addEventListener('change', function(e){
625
+ appendFilesFromFileList(e.target.files);
626
+ e.target.value = '';
627
+ }, false);
628
+ });
629
+ };
630
+ $.assignDrop = function(domNodes){
631
+ if(typeof(domNodes.length)=='undefined') domNodes = [domNodes];
632
+
633
+ $h.each(domNodes, function(domNode) {
634
+ domNode.addEventListener('dragover', onDragOver, false);
635
+ domNode.addEventListener('drop', onDrop, false);
636
+ });
637
+ };
638
+ $.unAssignDrop = function(domNodes) {
639
+ if (typeof(domNodes.length) == 'undefined') domNodes = [domNodes];
640
+
641
+ $h.each(domNodes, function(domNode) {
642
+ domNode.removeEventListener('dragover', onDragOver);
643
+ domNode.removeEventListener('drop', onDrop);
644
+ });
645
+ };
646
+ $.isUploading = function(){
647
+ var uploading = false;
648
+ $h.each($.files, function(file){
649
+ $h.each(file.chunks, function(chunk){
650
+ if(chunk.status()=='uploading') {
651
+ uploading = true;
652
+ return(false);
653
+ }
654
+ });
655
+ if(uploading) return(false);
656
+ });
657
+ return(uploading);
658
+ }
659
+ $.upload = function(){
660
+ // Make sure we don't start too many uploads at once
661
+ if($.isUploading()) return;
662
+ // Kick off the queue
663
+ $.fire('uploadStart');
664
+ for (var num=1; num<=$.getOpt('simultaneousUploads'); num++) {
665
+ $.uploadNextChunk();
666
+ }
667
+ };
668
+ $.pause = function(){
669
+ // Resume all chunks currently being uploaded
670
+ $h.each($.files, function(file){
671
+ file.abort();
672
+ });
673
+ $.fire('pause');
674
+ };
675
+ $.cancel = function(){
676
+ $h.each($.files, function(file){
677
+ file.cancel();
678
+ });
679
+ $.fire('cancel');
680
+ };
681
+ $.progress = function(){
682
+ var totalDone = 0;
683
+ var totalSize = 0;
684
+ // Resume all chunks currently being uploaded
685
+ $h.each($.files, function(file){
686
+ totalDone += file.progress()*file.size;
687
+ totalSize += file.size;
688
+ });
689
+ return(totalSize>0 ? totalDone/totalSize : 0);
690
+ };
691
+ $.addFile = function(file){
692
+ appendFilesFromFileList([file]);
693
+ };
694
+ $.removeFile = function(file){
695
+ var files = [];
696
+ $h.each($.files, function(f,i){
697
+ if(f!==file) files.push(f);
698
+ });
699
+ $.files = files;
700
+ };
701
+ $.getFromUniqueIdentifier = function(uniqueIdentifier){
702
+ var ret = false;
703
+ $h.each($.files, function(f){
704
+ if(f.uniqueIdentifier==uniqueIdentifier) ret = f;
705
+ });
706
+ return(ret);
707
+ };
708
+ $.getSize = function(){
709
+ var totalSize = 0;
710
+ $h.each($.files, function(file){
711
+ totalSize += file.size;
712
+ });
713
+ return(totalSize);
714
+ };
715
+
716
+ return(this);
717
+ }
metadata ADDED
@@ -0,0 +1,68 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: resumablejs-rails
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - beanie
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-05-13 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: railties
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>'
18
+ - !ruby/object:Gem::Version
19
+ version: '3.1'
20
+ - - <
21
+ - !ruby/object:Gem::Version
22
+ version: '5'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - '>'
28
+ - !ruby/object:Gem::Version
29
+ version: '3.1'
30
+ - - <
31
+ - !ruby/object:Gem::Version
32
+ version: '5'
33
+ description: resumable.js for the Rails Asset Pipeline
34
+ email:
35
+ - ich@abwesend.com
36
+ executables: []
37
+ extensions: []
38
+ extra_rdoc_files: []
39
+ files:
40
+ - lib/resumablejs-rails/version.rb
41
+ - lib/resumablejs-rails.rb
42
+ - vendor/assets/javascripts/resumable.js
43
+ - README.md
44
+ homepage: https://github.com/beanieboi/resumablejs-rails
45
+ licenses:
46
+ - MIT
47
+ metadata: {}
48
+ post_install_message:
49
+ rdoc_options: []
50
+ require_paths:
51
+ - lib
52
+ required_ruby_version: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - '>='
55
+ - !ruby/object:Gem::Version
56
+ version: '0'
57
+ required_rubygems_version: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ requirements: []
63
+ rubyforge_project:
64
+ rubygems_version: 2.0.0
65
+ signing_key:
66
+ specification_version: 4
67
+ summary: resumable.js for the Rails Asset Pipeline
68
+ test_files: []