resumablejs-rails 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: []