-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathhup.js
More file actions
709 lines (646 loc) · 26.5 KB
/
Copy pathhup.js
File metadata and controls
709 lines (646 loc) · 26.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
/**
* Copyright (c) 2013 Christopher Keefer. All Rights Reserved.
*
* jQuery plugin for reading in files or uploading them with the HTML5 file api and xhr2.
*/
"use strict";
(function($){
var filters = {},
fileTypes = [];
/**
* Populate the filters and fileTypes object and array, with the former containing a mapping between
* file extensions and their mime types, and the latter the mimetypes themselves.
*/
(function(mimetypes){
var mimes = mimetypes.split(/,/),
exts = [];
for (var i=0, len=mimes.length; i < len; i+=2)
{
fileTypes.push(mimes[i]);
exts = mimes[i+1].split(/ /);
for (var j=0, jlen = exts.length; j < jlen; j++)
{
filters[exts[j]] = mimes[i];
}
}
})(
"application/msword,doc dot,application/pdf,pdf,application/pgp-signature,pgp,application/postscript," +
"ps ai eps,application/rtf,rtf,application/vnd.ms-excel,xls xlb,application/vnd.ms-powerpoint," +
"ppt pps pot,application/zip,zip,application/x-shockwave-flash,swf swfl,application/x-javascript,js," +
"application/json,json,audio/mpeg,mpga mpega mp2 mp3,audio/x-wav,wav,audio/mp4,m4a,image/bmp,bmp," +
"image/gif,gif,image/jpeg,jpeg jpg jpe,image/photoshop,psd,image/png,png,image/svg+xml,svg svgz," +
"image/tiff,tiff tif,text/plain,asc txt text diff log,text/html,htm html xhtml,text/css,css,text/csv," +
"csv,text/rtf,rtf,video/mpeg,mpeg mpg mpe m2v,video/quicktime,qt mov,video/mp4,mp4,video/x-m4v,m4v," +
"video/x-flv,flv,video/x-ms-wmv,wmv,video/avi,avi,video/webm,webm,video/3gpp,3gp,video/3gpp2,3g2," +
"application/octet-stream,exe"
);
/**
* Construct html5 reader/uploader.
* @param {Object} options
* @constructor
*/
function Hup(options){
this.init(options);
}
/**
* Set options, listen for events on input element that indicate we should read/upload selected file(s).
* @param options
*/
Hup.prototype.init = function(options)
{
var that = this;
this.options = $.extend({
accept:[], // A string or array of extensions or mime-types to accept for reading/uploading
async:true, // Whether to send file(s) asynchronously
chunked:true, // Whether to send or read the file(s) in chunks
chunk_size:1048576, // Size of each chunk (default 1024*1024, 1 MiB)
input:'', // Input element - this is set automatically when using HUp in its jQuery plugin form.
make_dnd:false, // Whether to make the input element handle drag and drop - auto-true if not file input
max_file_size:0,// Max file size - 0 means no max size
read_method:'readAsDataURL', // the read method to use for reading in the file(s) - one of
// readAsText, readAsBinaryString, readAsDataURL or readAsArrayBuffer
type:'PUT', // Type of request to use for uploading
url:false // Url endpoint to send file to - if not specified or false, we read and return the file(s)
}, options);
this.input = $(this.options.input);
this.options.accept = this.acceptFilters(this.options.accept);
if (this.options.make_dnd || !this.isFileInput(this.input))
{
this.options.make_dnd = true;
this.input.off('dragover').on('dragover', function(event){
event = event.originalEvent;
that.handleDragover(event);
});
}
this.input.off('drop change').on('drop change', function(event){
event = event.originalEvent;
that.handleSelect(event);
});
};
/**
* Translate the accept string or array into an array of mime types, based on the mime types in filters.
* Input should look like the expected extensions:
* "swf, wmv, mp4" or ['swf', 'wmv', 'mp4']
* Or like mime type categories, or the mime types themselves:
* "application/*, application/pdf" or ['image/*', 'plain/text']
* @param accept
*/
Hup.prototype.acceptFilters = function(accept){
var mimes = [],
mime,
fileType;
// Ensure accept is an array of extensions or mime types
if (typeof accept === 'string' || accept instanceof String)
{
accept = accept.split(/,/);
}
for (var i=0, len = accept.length; i < len; i++)
{
mime = accept[i].trim().split(/\//);
if (mime.length > 1)
{
if (mime[1] === '*')
{
// Every mime-type that begins with mime[0] now needs to be pushed into the mimes array
for (var j=0, jlen = fileTypes.length; j < jlen; j++)
{
fileType = fileTypes[j].split(/\//);
if (mime[0] === fileType[0]) mimes.push(fileTypes[j]);
}
} else {
// Pass the mime type through unmolested
mimes.push(mime.join('/'));
}
} else {
// Only an extension has been specified - map to the mime type
if (mime[0] in filters) mimes.push(filters[mime[0]]);
}
}
return mimes;
};
/**
* Return whether the passed element is an input of type file.
* @param input Element to check.
* @returns {boolean}
*/
Hup.prototype.isFileInput = function(input){
return (input[0].tagName === 'INPUT' && /file/i.test(input[0].getAttribute('type')));
};
/**
* Handle the dragging of file(s) to a target, preventing the rejection of the dragover.
* @param event
*/
Hup.prototype.handleDragover = function(event){
event.preventDefault();
event.stopPropagation();
event.dataTransfer.dropEffect = 'copy';
};
/**
* Handle the selection of files to upload via an input or drag and drop to a target.
* @param event
*/
Hup.prototype.handleSelect = function(event){
var files;
if (this.options.make_dnd)
{
event.preventDefault();
event.stopPropagation();
files = event.dataTransfer.files;
}
else
{
files = event.target.files;
}
if (!files.length)
{
this.input.trigger(Hup.state.FILE_LIST_ERROR, {
state:Hup.state.FILE_LIST_ERROR,
error:'No files found in file list; no files were selected.'
});
return;
}
this.input.trigger(Hup.state.FILE_LIST_LOADED, {state:Hup.state.FILE_LIST_LOADED, files:files});
this.processFiles(files, this.options.url);
};
/**
* Process the files in the fileList, uploading them if a url is specified, otherwise reading them into
* memory and passing them on to be used in the browser.
* @param files
* @param upload
*/
Hup.prototype.processFiles = function(files, upload){
var that = this,
processed = 0,
accept = this.options.accept,
accepted = false,
maxSize = this.options.max_file_size,
fprocess;
this.fprocessors = [];
for (var i=0, len = files.length; i < len; i++)
{
// Check file against mime accept restrictions if any restrictions are set
if (accept.length)
{
accepted = false;
for (var j=0, jlen = accept.length; j < jlen; j++)
{
accepted = (files[i].type === accept[j]);
if (accepted) break;
}
if (!accepted)
{
this.input.trigger(Hup.state.FILE_TYPE_ERROR, {
state:Hup.state.FILE_TYPE_ERROR,
file_name:files[i].name,
error:'File type is '+files[i].type+', accepted types are '+accept.join(',')+'.'
});
continue;
}
}
// Check file against size restrictions
if (maxSize && files[i].size > maxSize)
{
this.input.trigger(Hup.state.FILE_SIZE_ERROR, {
state:Hup.state.FILE_SIZE_ERROR,
file_name:files[i].name,
error:'File size is '+files[i].size+', max file size is '+maxSize+'.'
});
continue;
}
// Create new DeferXhr or DeferReader and listen on its progression and completion to fire the appropriate
// events for interested listeners on our input
fprocess = (upload) ? new DeferXhr(this.options, files[i]) :
new DeferReader(this.options, files[i]);
fprocess.promise.progress(function(progress){
that.input.trigger(progress.state, progress);
}).done(function(res){
that.input.trigger(res.state, res);
processed++;
if (processed >= len)
{
if (upload)
{
that.input.trigger(Hup.state.FILE_UPLOAD_ALL, {state:Hup.state.FILE_UPLOAD_ALL, files:len});
return;
}
that.input.trigger(Hup.state.FILE_READ_ALL, {state:Hup.state.FILE_READ_ALL, files:len});
}
}).fail(function(res)
{
that.input.trigger(res.state, res);
});
this.fprocessors.push(fprocess);
}
};
/**
* Pause any in progress, chunked uploads/file reads. If pauseList is specified,
* elements should be either the names of the files or the index in which they were returned in the files
* list returned from the FILE_LIST_LOADED event. Can provide only a single string or number if only a single
* upload/read needs to be paused.
* @param {Array|number|string|boolean|undefined} pauseList
*/
Hup.prototype.pause = function(pauseList){
pauseList = (!pauseList) ? false : Array.isArray(pauseList) ? pauseList : [pauseList];
this.fprocessors.forEach(function(fprocess, idx){
if (!pauseList)
{
fprocess.pause();
return;
}
if (pauseList.indexOf(idx) !== -1 || pauseList.indexOf(fprocess.file.name) !== -1)
{
fprocess.pause();
}
});
};
/**
* Resume any in progress, paused, chunked uploads/file reads, following the same rules for pauseList as
* specified for pause.
* @see Hup.prototype.pause
* @param {Array|number|string|boolean|undefined} pauseList
*/
Hup.prototype.resume = function(pauseList){
pauseList = (!pauseList) ? false : Array.isArray(pauseList) ? pauseList : [pauseList];
this.fprocessors.forEach(function(fprocess, idx){
if (!pauseList)
{
fprocess.resume();
return;
}
if (pauseList.indexOf(idx) !== -1 || pauseList.indexOf(fprocess.file.name) !== -1)
{
fprocess.resume();
}
});
};
/**
* Convenience function for the reassembly of a file read in chunks as a data url, and returns a single
* dataURL base64 encoded string.
*
* Expects either an array containing all of the base64 strings to be used to reassemble the dataURL, in the
* correct order, or the strings can be specified as parameters, in the correct order for reassembly (that is,
* in the same order they were output by DeferReader).
* @param {Array|String} parts
* @returns {String}
*/
Hup.prototype.reassembleChunkedDataURL = function(parts){
var dataURL;
if (arguments.length > 1) parts = Array.prototype.slice.call(arguments);
dataURL = parts[0];
for (var i=1, len=parts.length; i < len; i++){
dataURL += parts[i].split(',')[1];
}
return dataURL;
};
/**
* Custom events we'll trigger on our input element at the appropriate times.
* @type {{FILE_LIST_ERROR: string, FILE_LIST_LOADED: string, FILE_TYPE_ERROR: string, FILE_SIZE_ERROR: string,
* FILE_READ_ERROR: string, FILE_READ_PROGRESS: string, FILE_READ_FINISHED: string, FILE_READ_ALL: string,
* FILE_UPLOAD_ERROR: string, FILE_UPLOAD_PROGRESS: string, FILE_UPLOAD_PAUSE: string,
* FILE_UPLOAD_RESUME: string, FILE_UPLOAD_FINISHED: string, FILE_UPLOAD_ALL: string}}
*/
Hup.state = {
FILE_LIST_ERROR:'fileListError',
FILE_LIST_LOADED:'fileListLoaded',
FILE_TYPE_ERROR:'fileTypeError',
FILE_SIZE_ERROR:'fileSizeError',
FILE_READ_ERROR:'fileReadError',
FILE_READ_PROGRESS:'fileReadProgress',
FILE_READ_FINISHED:'fileReadFinished',
FILE_READ_PAUSE:'fileReadPause',
FILE_READ_RESUME:'fileReadResume',
FILE_READ_ALL:'fileReadAll',
FILE_UPLOAD_ERROR:'fileUploadError',
FILE_UPLOAD_PROGRESS:'fileUploadProgress',
FILE_UPLOAD_PAUSE:'fileUploadPause',
FILE_UPLOAD_RESUME:'fileUploadResume',
FILE_UPLOAD_FINISHED:'fileUploadFinished',
FILE_UPLOAD_ALL:'fileUploadAll'
};
/**
* Deferred wrapper for xhr upload.
* @param options
* @param file
* @returns {Object} promise The deferred promise object.
* @constructor
*/
function DeferXhr(options, file){
this.defer = $.Deferred();
this.promise = this.defer.promise();
this.file = file;
this.options = options;
this.paused = false;
this.progress = 0;
this.time = {start:0, end:0, speed:0}; // Speed is measured in bytes per second
this.xhr = new XMLHttpRequest();
if (this.options.chunked)
{
this.start = 0;
this.end = Math.min(this.start+this.options.chunk_size, this.file.size);
}
this.xhr.addEventListener('load', this.complete.bind(this), false);
this.xhr.upload.addEventListener('progress', this.uploadProgress.bind(this), false);
this.xhr.upload.addEventListener('error', this.uploadError.bind(this), false);
this.upload();
return this;
}
/**
* Carry out the xhr upload, optionally chunked.
*/
DeferXhr.prototype.upload = function(){
this.time.start = +new Date();
this.xhr.open(this.options.type, this.options.url, this.options.async);
this.xhr.setRequestHeader('Accept', 'application/json');
this.xhr.setRequestHeader('X-File-Name', encodeURIComponent(this.file.name));
this.xhr.setRequestHeader('X-File-Type', this.file.type);
if (this.options.chunked)
{
this.xhr.overrideMimeType('application/octet-stream');
this.xhr.setRequestHeader('Content-Range', 'bytes '+this.start+"-"+this.end+"/"+this.file.size);
this.xhr.send(this.file.slice(this.start, this.end));
}
else
{
this.xhr.overrideMimeType((this.file.type || 'application/octet-stream'));
this.xhr.send(this.file);
}
};
/**
* Report on the upload progress, as a number between 0 and 1, modifying the progress if we're uploading a
* file in chunks to report on the progress as a percentage of file upload and total chunks uploaded.
* @param event
*/
DeferXhr.prototype.uploadProgress = function(event){
if (event.lengthComputable)
{
this.progress = (event.loaded/event.total);
if (this.options.chunked)
{
this.progress *= (this.end/this.file.size);
}
this.time.end = +new Date();
this.time.speed = (this.file.size*this.progress)/(this.time.end-this.time.start)*1000;
console.log('time:', this.time.end-this.time.start, 'speed:', this.time.speed, 'progress:', this.progress);
this.defer.notify({state:Hup.state.FILE_UPLOAD_PROGRESS, file_name:this.file.name, speed:this.time.speed,
progress:this.progress});
}
};
/**
* Call reject on the defer for this DeferXhr object, passing the details back to any subscribed event listeners.
* @param event
*/
DeferXhr.prototype.uploadError = function(event){
this.defer.reject({state:Hup.state.FILE_UPLOAD_ERROR, file_name:this.file.name, error:event});
};
/**
* Called when we've completed an upload (full file or chunk). If full file, or we've reached the last chunk,
* the upload is complete. Otherwise, we calculate the next chunk offsets and, if the upload isn't paused,
* upload it.
*/
DeferXhr.prototype.complete = function(){
this.time.end = +new Date();
if (!this.options.chunked || this.end == this.file.size)
{
this.uploadComplete();
return;
}
this.defer.notify({state:Hup.state.FILE_UPLOAD_PROGRESS, file_name:this.file.name,
response:this.parseResponse(), progress:this.progress});
this.start = this.end;
this.end = Math.min(this.start+this.options.chunk_size, this.file.size);
if (!this.paused)
{
this.upload();
}
};
/**
* Called when the full file has been uploaded.
*/
DeferXhr.prototype.uploadComplete = function(){
this.defer.resolve({state:Hup.state.FILE_UPLOAD_FINISHED, file_name:this.file.name,
file_size:this.file.size, file_type:this.file.type,
response:this.parseResponse()});
};
/**
* Try to parse the response as a JSON, and on failure return the error and the plaintext.
* @returns {Object}
*/
DeferXhr.prototype.parseResponse = function()
{
var response;
try{
response = JSON.parse(this.xhr.responseText);
}catch(e){
response = {error:e, text:this.xhr.responseText};
}
return response;
};
/**
* Pause the upload - that is, after the current chunk is finished uploading, cease uploading chunks until
* resume is called. For obvious reasons, this only works with chunked uploads.
* If the state of the deferred object is not pending (that is, is either already resolved or rejected),
* return early - we won't attempt to pause an upload that's finished or failed.
*/
DeferXhr.prototype.pause = function(){
if (this.defer.state() !== 'pending' || !this.options.chunked) return;
this.paused = true;
this.defer.notify({
state:Hup.state.FILE_UPLOAD_PAUSE, file_name:this.file.name,
current_range:{start:this.start, end:this.end, total:this.file.size}
});
};
/**
* Resume the upload if paused (works for chunked uploads only).
*/
DeferXhr.prototype.resume = function(){
if (this.options.chunked && this.paused)
{
this.paused = false;
this.defer.notify({
state:Hup.state.FILE_UPLOAD_RESUME, file_name:this.file.name,
current_range:{start:this.start, end:this.end, total:this.file.size}
});
this.upload();
}
};
/**
* Deferred wrapper for file reader.
* @param {Object} options
* @param {File|Blob} file
* @returns {Object} promise The Deferred promise object
* @constructor
*/
function DeferReader(options, file){
this.options = options;
this.defer = $.Deferred();
this.promise = this.defer.promise();
this.reader = new FileReader();
this.file = file;
this.read_method = this.options.read_method;
this.paused = false;
this.progress = 0;
if (this.options.chunked)
{
this.start = 0;
this.end = this.calculateChunkEnd();
}
this.listen();
this.readFile();
return this;
}
/**
* Calculate what the value of this.end should be when the file read is chunked, as special handling is needed
* when we're using the 'readAsDataURL' read_method to align reads on multiples of 6, as a result of how base64
* encoding works (that is, each character encodes 6 bits of information - if we fail to align the chunks with a
* multiple of 6, the base64 beyond the first chunk will end up represented in a way that cannot be trivially
* combined with the initial chunk).
*/
DeferReader.prototype.calculateChunkEnd = function(){
var end = Math.min(this.start+this.options.chunk_size, this.file.size);
if (this.read_method === 'readAsDataURL' && end !== this.file.size){
end -= end % 6;
}
return end;
};
/**
* Read the entire file or a slice thereof, depending on the value of options.chunked and chunk_size.
*/
DeferReader.prototype.readFile = function(){
if (this.options.chunked)
{
this.reader[this.read_method](this.file.slice(this.start, this.end, this.file.type));
return;
}
this.reader[this.read_method](this.file);
};
/**
* Report on the file read progress, as a number between 0 and 1, modifying the progress if we're reading a
* file in chunks to ensure that we're reporting the total percentage of the file read, not just the percentage
* of the current chunk read (see also readComplete).
* @param event
*/
DeferReader.prototype.readProgress = function(event){
var progress = this.progress;
if (event.lengthComputable)
{
progress = event.loaded/event.total;
if (this.options.chunked)
{
progress *= (this.end/this.file.size);
}
this.defer.notify({state:Hup.state.FILE_READ_PROGRESS, file_name:this.file.name, progress:progress});
}
this.progress = progress;
};
/**
* On read completion, if we're reading in chunks, if we've reached the last chunk, report on file read completion.
* If there are remaining chunks, report on progress and read the next chunk.
* Otherwise if we're reading the entire file in one go, report on file read completion.
* @param event
*/
DeferReader.prototype.readComplete = function(event){
if (event.target.readyState == FileReader.DONE && (!this.options.chunked || this.end == this.file.size))
{
this.defer.resolve({
state:Hup.state.FILE_READ_FINISHED, file_name:this.file.name, file_size:this.file.size,
file_type:this.file.type, read_method:this.read_method, read_result:event.target.result
});
return;
}
this.defer.notify({
state:Hup.state.FILE_READ_PROGRESS, file_name:this.file.name, progress:this.progress,
read_result:(event.target.readyState == FileReader.DONE) ? event.target.result : void 0
});
this.start = this.end;
this.end = this.calculateChunkEnd();
if (!this.paused)
{
this.readFile();
}
};
/**
* On file read error, attempt to create a meaningful error string, and return alongside the error code, reader
* state and the name of the file on which this error occurred.
* @param event
*/
DeferReader.prototype.readError = function(event){
var err = event.target.error,
errCode = event.target.error.code,
errMsg = 'Error attempting to read file "'+this.file.name+'": ';
switch(errCode)
{
case err.NOT_FOUND_ERR:
errMsg += "File could not be found.";
break;
case err.NOT_READABLE_ERR:
errMsg += "File is not readable.";
break;
case err.ABORT_ERR:
errMsg += "File read was aborted.";
break;
default:
errMsg += "An unexpected error occurred.";
break;
}
this.defer.reject({state:Hup.state.FILE_READ_ERROR, file_name:this.file.name, error:errMsg, code:errCode});
};
/**
* Listen for the various events of interest on the file reader, and return notification or resolution
* to deferred as appropriate.
*/
DeferReader.prototype.listen = function(){
this.reader.addEventListener('error', this.readError.bind(this), false);
this.reader.addEventListener('progress', this.readProgress.bind(this), false);
this.reader.addEventListener('loadend', this.readComplete.bind(this), false);
};
/**
* Pause this file reader if chunked by ceasing to read the file in after the current chunk is completed.
* If the defer has already been resolved or rejected, we return, making no attempt to pause a file
* read that has already finished or been rejected.
*/
DeferReader.prototype.pause = function(){
if (this.defer.state() !== 'pending' || !this.options.chunked) return;
this.paused = true;
this.defer.notify({
state:Hup.state.FILE_READ_PAUSE, file_name:this.file.name,
current_range:{start:this.start, end:this.end, total:this.file.size}});
};
/**
* Resume this file reader from the next chunk if it was previously paused and chunked.
*/
DeferReader.prototype.resume = function(){
if (this.options.chunked && this.paused)
{
this.paused = false;
this.defer.notify({
state:Hup.state.FILE_READ_RESUME, file_name:this.file.name,
current_range:{start:this.start, end:this.end, total:this.file.size}
});
this.readFile();
}
};
/**
* Entry point for calling the reader/uploader, with the element to be used as input specified.
* Usage:
* $('#input').hup({options}).on('events') --OR--
* $('.inputs').hup({options}).on('events')
* @param options
* @returns {Object} jQuery object reference for the given elements.
*/
$.fn.hup = function(options){
options = options || {};
return this.each(function(){
options.input = this;
var $this = $(this),
hup = $this.data('hup');
if (!hup)
{
$this.data('hup', new Hup(options));
}
else if (hup instanceof Hup)
{
hup.init(options);
}
});
};
})(jQuery);