jquery-fileapi-rails 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,1050 @@
1
+ /**
2
+ * jQuery plugin for FileAPI v2+
3
+ * @auhtor RubaXa <trash@rubaxa.org>
4
+ */
5
+
6
+ /*jslint evil: true */
7
+ /*global jQuery, FileAPI*/
8
+ (function ($, api){
9
+ "use strict";
10
+
11
+ var
12
+ noop = $.noop
13
+ , oldJQ = !$.fn.prop
14
+ , propFn = oldJQ ? 'attr' : 'prop'
15
+
16
+ , _dataAttr = 'data-fileapi'
17
+ , _dataFileId = 'data-fileapi-id'
18
+ , _slice = [].slice
19
+ , _bind = function (ctx, fn) {
20
+ var args = _slice.call(arguments, 2);
21
+ return fn.bind ? fn.bind.apply(fn, [ctx].concat(args)) : function (){
22
+ return fn.apply(ctx, args.concat(_slice.call(arguments)));
23
+ };
24
+ }
25
+ ;
26
+
27
+
28
+
29
+ var Plugin = function (el, options){
30
+ this.$el = el = $(el).on('change.fileapi', _bind(this, this._onSelect));
31
+ this.el = el[0];
32
+
33
+ this._options = {}; // previous options
34
+ this.options = options = $.extend({
35
+ url: 0,
36
+ data: {}, // additional POST-data
37
+ accept: 0, // accept mime types, "*" — unlimited
38
+ multiple: false, // single or multiple mode upload mode
39
+ paramName: 0, // POST-parameter name
40
+ dataType: 'json',
41
+ duplicate: false, // ignore duplicate
42
+ chunkSize: 0, // or chunk size in bytes, eg: .5 * FileAPI.MB
43
+ chunkUploadRetry: 3, // number of retries during upload chunks (html5)
44
+
45
+ maxSize: 0, // max file size, 0 — unlimited
46
+ maxFiles: 0, // @todo: max uploaded files
47
+ imageSize: 0, // { minWidth: 320, minHeight: 240, maxWidth: 3840, maxHeight: 2160 }
48
+
49
+ sortFn: 0,
50
+ filterFn: 0,
51
+ autoUpload: false,
52
+ resetOnSelect: void 0,
53
+
54
+ lang: {
55
+ B: 'bytes'
56
+ , KB: 'KB'
57
+ , MB: 'MB'
58
+ , GB: 'GB'
59
+ , TB: 'TB'
60
+ },
61
+ sizeFormat: '0.00',
62
+
63
+ imageTransform: 0,
64
+ imageAutoOrientation: !!FileAPI.support.exif,
65
+
66
+ elements: {
67
+ ctrl: {
68
+ upload: '[data-fileapi="ctrl.upload"]',
69
+ reset: '[data-fileapi="ctrl.reset"]',
70
+ abort: '[data-fileapi="ctrl.abort"]'
71
+ },
72
+ empty: {
73
+ show: '[data-fileapi="empty.show"]',
74
+ hide: '[data-fileapi="empty.hide"]'
75
+ },
76
+ emptyQueue: {
77
+ show: '[data-fileapi="emptyQueue.show"]',
78
+ hide: '[data-fileapi="emptyQueue.hide"]'
79
+ },
80
+ active: {
81
+ show: '[data-fileapi="active.show"]',
82
+ hide: '[data-fileapi="active.hide"]'
83
+ },
84
+ size: '[data-fileapi="size"]',
85
+ name: '[data-fileapi="name"]',
86
+ progress: '[data-fileapi="progress"]',
87
+ file: {
88
+ tpl: '[data-fileapi="file.tpl"]',
89
+ progress: '[data-fileapi="file.progress"]',
90
+ active: {
91
+ show: '[data-fileapi="active.show"]',
92
+ hide: '[data-fileapi="active.hide"]'
93
+ },
94
+ preview: {
95
+ el: 0,
96
+ get: 0,
97
+ width: 0,
98
+ height: 0,
99
+ processing: 0
100
+ }
101
+ },
102
+ dnd: {
103
+ el: '[data-fileapi="dnd"]',
104
+ hover: 'dnd_hover'
105
+ }
106
+ },
107
+
108
+ onDrop: noop,
109
+ onDropHover: noop,
110
+
111
+ onSelect: noop,
112
+
113
+ onUpload: noop,
114
+ onProgress: noop,
115
+ onComplete: noop,
116
+
117
+ onFileUpload: noop,
118
+ onFileProgress: noop,
119
+ onFileComplete: noop
120
+ }, options);
121
+
122
+
123
+ if( !options.url ){
124
+ var url = this.$el.attr('action') || this.$el.find('form').attr('action');
125
+ if( url ){
126
+ options.url = url;
127
+ } else {
128
+ this._throw('url — is not defined');
129
+ }
130
+ }
131
+
132
+
133
+ this.$files = this.elem('list');
134
+ this.itemTplFn = $.fn.fileapi.tpl( $('<div/>').append( this.elem('file.tpl')).html() );
135
+
136
+
137
+ api.each(options, function (value, option){
138
+ this._setOption(option, value);
139
+ }, this);
140
+
141
+
142
+ this.$el
143
+ .on('reset.fileapi', _bind(this, this._onReset))
144
+ .on('submit.fileapi', _bind(this, this._onSubmit))
145
+ .on('upload.fileapi progress.fileapi complete.fileapi', _bind(this, this._onUploadEvent))
146
+ .on('fileupload.fileapi fileprogress.fileapi filecomplete.fileapi', _bind(this, this._onFileUploadEvent))
147
+ .on('click', '['+_dataAttr+']', _bind(this, this._onActionClick))
148
+ ;
149
+
150
+
151
+ // Controls
152
+ var ctrl = options.elements.ctrl;
153
+ if( ctrl ){
154
+ if( ctrl.reset ){
155
+ this.$el.on('click.fileapi', ctrl.reset, _bind(this, this._onReset));
156
+ }
157
+ if( ctrl.upload ){
158
+ this.$el.on('click.fileapi', ctrl.upload, _bind(this, this._onSubmit));
159
+ }
160
+ }
161
+
162
+ this.elem('dnd.el', true).dnd(_bind(this, this._onDropHover), _bind(this, this._onDrop));
163
+ this.$progress = this.elem('progress');
164
+
165
+ this._crop = {};
166
+ this._rotate = {}; // rotate deg
167
+
168
+ this.files = []; // all files
169
+ this.uploaded = []; // uploaded files
170
+
171
+ if( options.resetOnSelect === void 0 ){
172
+ options.resetOnSelect = !options.multiple;
173
+ }
174
+
175
+ this.clear();
176
+ };
177
+
178
+
179
+ Plugin.prototype = {
180
+ constructor: Plugin,
181
+
182
+ _throw: function (msg){
183
+ throw "jquery.fileapi: " + msg;
184
+ },
185
+
186
+ _getFiles: function (evt, fn){
187
+ var
188
+ opts = this.options
189
+ , maxSize = opts.maxSize
190
+ , filterFn = opts.filterFn
191
+ , files = api.getFiles(evt)
192
+ , data = {
193
+ all: files
194
+ , files: []
195
+ , other: []
196
+ , duplicate: opts.duplicate ? [] : this._extractDuplicateFiles(files)
197
+ }
198
+ , imageSize = opts.imageSize
199
+ , _this = this
200
+ ;
201
+
202
+ if( imageSize || filterFn ){
203
+ api.filterFiles(files, function (file, info){
204
+ var ok = true;
205
+ if( info && imageSize ){
206
+ ok = (!imageSize.minWidth || info.width >= imageSize.minWidth)
207
+ && (!imageSize.minHeight || info.height >= imageSize.minHeight)
208
+ && (!imageSize.maxWidth || info.height <= imageSize.maxWidth)
209
+ && (!imageSize.maxHeight || info.height <= imageSize.maxHeight)
210
+ ;
211
+ }
212
+ return ok && (!maxSize || file.size <= maxSize) && (!filterFn || filterFn(file, info));
213
+ }, function (succes, other){
214
+ data.files = succes;
215
+ data.other = other;
216
+
217
+ fn.call(_this, data);
218
+ });
219
+ } else {
220
+ api.each(files, function (file){
221
+ data[!maxSize || file.size <= maxSize ? 'files' : 'other'].push(file);
222
+ });
223
+
224
+ fn.call(_this, data);
225
+ }
226
+ },
227
+
228
+ _extractDuplicateFiles: function (list/**Array*/){
229
+ var duplicates = [], i = list.length, files = this.files, j;
230
+
231
+ while( i-- ){
232
+ j = files.length;
233
+ while( j-- ){
234
+ if( this._fileCompare(list[i], files[j]) ){
235
+ duplicates.push( list.splice(i, 1) );
236
+ break;
237
+ }
238
+ }
239
+ }
240
+
241
+ return duplicates;
242
+ },
243
+
244
+ _fileCompare: function (A/**File*/, B/**File*/){
245
+ return (A.size == B.size) && (A.name == B.name);
246
+ },
247
+
248
+ _getFormatedSize: function (size){
249
+ var opts = this.options, postfix = 'B';
250
+
251
+ if( size >= api.TB ){
252
+ size /= api[postfix = 'TB'];
253
+ }
254
+ else if( size >= api.GB ){
255
+ size /= api[postfix = 'GB'];
256
+ }
257
+ else if( size >= api.MB ){
258
+ size /= api[postfix = 'MB'];
259
+ }
260
+ else if( size >= api.KB ){
261
+ size /= api[postfix = 'KB'];
262
+ }
263
+
264
+ return opts.sizeFormat.replace(/^\d+([^\d]+)(\d*)/, function (_, separator, fix){
265
+ size = size.toFixed(fix.length);
266
+ return (size + '').replace('.', separator) +' '+ opts.lang[postfix];
267
+ });
268
+ },
269
+
270
+ _onSelect: function (evt){
271
+ this._getFiles(evt, _bind(this, function (data){
272
+ if( data.all.length && this.emit('select', data) !== false ){
273
+ this.add(data.files, this.options.resetOnSelect);
274
+ }
275
+ }));
276
+ },
277
+
278
+ _onActionClick: function (evt){
279
+ var
280
+ el = evt.currentTarget
281
+ , act = $.attr(el, _dataAttr)
282
+ , $item = $(el).closest('['+_dataFileId+']', this.$el)
283
+ , uid = $item.attr(_dataFileId)
284
+ , prevent = true
285
+ ;
286
+
287
+ if( 'remove' == act ){
288
+ $item.remove();
289
+ this.queue = api.filter(this.queue, function (file){ return api.uid(file) != uid; });
290
+ this.files = api.filter(this.files, function (file){ return api.uid(file) != uid; });
291
+ this._redraw();
292
+ }
293
+ else if( /^rotate/.test(act) ){
294
+ this.rotate(uid, (/ccw/.test(act) ? '-=90' : '+=90'));
295
+ }
296
+ else {
297
+ prevent = false;
298
+ }
299
+
300
+ if( prevent ){
301
+ evt.preventDefault();
302
+ }
303
+ },
304
+
305
+ _onSubmit: function (evt){
306
+ this.upload();
307
+ evt.preventDefault();
308
+ },
309
+
310
+ _onReset: function (evt){
311
+ this.clear();
312
+ evt.preventDefault();
313
+ },
314
+
315
+ _onDrop: function (files){
316
+ this._getFiles(files, function (data){
317
+ if( this.emit('drop', data) !== false ){
318
+ this.add(data.files);
319
+ }
320
+ });
321
+ },
322
+
323
+ _onDropHover: function (state, evt){
324
+ if( this.emit('dropHover', { state: state, event: evt }) !== false ){
325
+ var hover = this.option('elements.dnd.hover');
326
+ if( hover ){
327
+ $(evt.currentTarget).toggleClass(hover, state);
328
+ }
329
+ }
330
+ },
331
+
332
+ _getUploadEvent: function (extra){
333
+ var xhr = this.xhr, evt = {
334
+ xhr: xhr
335
+ , file: xhr.currentFile
336
+ , files: xhr.files
337
+ , widget: this
338
+ };
339
+ return $.extend(evt, extra);
340
+ },
341
+
342
+ _emitUploadEvent: function (prefix){
343
+ var evt = this._getUploadEvent();
344
+ this.emit(prefix+'upload', evt);
345
+ },
346
+
347
+ _emitProgressEvent: function (prefix, event){
348
+ var evt = this._getUploadEvent(event);
349
+ this.emit(prefix+'progress', evt);
350
+ },
351
+
352
+ _emitCompleteEvent: function (prefix, err){
353
+ var
354
+ xhr = this.xhr
355
+ , evt = this._getUploadEvent({
356
+ error: err
357
+ , status: xhr.status
358
+ , statusText: xhr.statusText
359
+ , result: xhr.responseText
360
+ })
361
+ ;
362
+
363
+ if( this.options.dataType == 'json' ){
364
+ evt.result = $.parseJSON(evt.result);
365
+ }
366
+
367
+ this.emit(prefix+'complete', evt);
368
+ },
369
+
370
+ _onUploadEvent: function (evt, ui){
371
+ var _this = this, $progress = this.$progress, type = evt.type;
372
+
373
+ if( type == 'progress' ){
374
+ $progress.stop().animate({ width: ui.loaded/ui.total*100 + '%' }, 300);
375
+ }
376
+ else if( type == 'upload' ){
377
+ $progress.width(0);
378
+ }
379
+ else {
380
+ var fn = function (){
381
+ $progress.dequeue();
382
+ _this.clear();
383
+ };
384
+
385
+ this.xhr = null;
386
+ this.active = false;
387
+
388
+ if( $progress.length ){
389
+ $progress.queue(fn);
390
+ } else {
391
+ fn();
392
+ }
393
+ }
394
+ },
395
+
396
+ _onFileUploadPrepare: function (file, opts){
397
+ var
398
+ uid = api.uid(file)
399
+ , deg = this._rotate[uid]
400
+ , crop = this._crop[uid]
401
+ ;
402
+
403
+ if( deg || crop ){
404
+ var trans = opts.imageTransform = opts.imageTransform || {};
405
+ if( $.isEmptyObject(trans) || _isOriginTransform(trans) ){
406
+ trans.crop = crop;
407
+ trans.rotate = deg;
408
+ }
409
+ else {
410
+ api.each(trans, function (opts){
411
+ opts.crop = crop;
412
+ opts.rotate = deg;
413
+ });
414
+ }
415
+ }
416
+ },
417
+
418
+ _onFileUploadEvent: function (evt, ui){
419
+ var
420
+ _this = this
421
+ , type = evt.type.substr(4)
422
+ , uid = api.uid(ui.file)
423
+ , $file = this.fileElem(uid)
424
+ , $progress = this._$fileprogress
425
+ ;
426
+
427
+ if( this.__fileId !== uid ){
428
+ this.__fileId = uid;
429
+ this._$fileprogress = $progress = $file.find(this.option('elements.file.progress'));
430
+ }
431
+
432
+ if( type == 'progress' ){
433
+ $progress.stop().animate({ width: ui.loaded/ui.total*100 + '%' }, 300);
434
+ }
435
+ else if( type == 'upload' || type == 'complete' ){
436
+ var fn = function (){
437
+ var elem = 'elements.file.'+ type;
438
+
439
+ if( type == 'upload' ){
440
+ $file.find('['+_dataAttr+'="remove"]').hide();
441
+ $progress.width(0);
442
+ }
443
+
444
+ $progress.dequeue();
445
+
446
+ $file.find(_this.option(elem + '.show')).show();
447
+ $file.find(_this.option(elem + '.hide')).hide();
448
+ };
449
+
450
+ if( $progress.length ){
451
+ $progress.queue(fn);
452
+ } else {
453
+ fn();
454
+ }
455
+
456
+ if( type == 'complete' ){
457
+ this.uploaded.push(ui.file);
458
+ delete this._rotate[uid];
459
+ }
460
+ }
461
+ },
462
+
463
+ _redraw: function (){
464
+ var
465
+ active = !!this.active
466
+ , files = this.files
467
+ , empty = !files.length && !active
468
+ , emptyQueue = !this.queue.length && !active
469
+ , name = []
470
+ , size = 0
471
+ , $files = this.$files
472
+ , offset = $files.children().length
473
+ , preview = this.option('elements.file.preview')
474
+ ;
475
+
476
+
477
+ api.each(files, function (file, i){
478
+ var uid = api.uid(file);
479
+
480
+ name.push(file.name);
481
+ size += file.size;
482
+
483
+ if( $files.length && !this.fileElem(uid).length ){
484
+ var html = this.itemTplFn({
485
+ $idx: offset + i
486
+ , uid: file.uid
487
+ , name: file.name
488
+ , type: file.type
489
+ , size: file.size
490
+ , sizeText: this._getFormatedSize(file.size)
491
+ });
492
+
493
+ $files.append( $(html).attr(_dataFileId, uid) );
494
+
495
+ if( preview.el ){
496
+ this._makeFilePreview(uid, file, preview);
497
+ }
498
+ }
499
+ }, this);
500
+
501
+
502
+ this.elem('name').text( name.join(', ') );
503
+ this.elem('size').text( this._getFormatedSize(size) );
504
+
505
+
506
+ if( this.__empty !== empty ){
507
+ this.__empty = empty;
508
+
509
+ this.elem('empty.show').toggle( empty );
510
+ this.elem('empty.hide').toggle( !empty );
511
+ }
512
+
513
+
514
+ if( this.__emptyQueue !== emptyQueue ){
515
+ this.__emptyQueue = empty;
516
+
517
+ this.elem('emptyQueue.show').toggle( emptyQueue );
518
+ this.elem('emptyQueue.hide').toggle( !emptyQueue );
519
+ }
520
+
521
+
522
+ if( this.__active !== active ){
523
+ this.__active = active;
524
+
525
+ this.elem('active.show').toggle( active );
526
+ this.elem('active.hide').toggle( !active );
527
+
528
+ this.$('.js-fileapi-wrapper,:file')
529
+ [active ? 'attr' : 'removeAttr']('aria-disabled', active)
530
+ [propFn]('disabled', active)
531
+ ;
532
+ }
533
+
534
+
535
+ // Upload & reset control
536
+ this.elem('ctrl.upload')
537
+ .add( this.elem('ctrl.reset') )
538
+ [empty || active ? 'attr' : 'removeAttr']('aria-disabled', empty || active)
539
+ [propFn]('disabled', empty || active)
540
+ ;
541
+ },
542
+
543
+ _makeFilePreview: function (uid, file, opts, global){
544
+ var
545
+ _this = this
546
+ , $el = global ? _this.$(opts.el) : _this.fileElem(uid).find(opts.el)
547
+ ;
548
+
549
+ if( /^image/.test(file.type) ){
550
+ api.log('_makeFilePreview:', uid, file);
551
+
552
+ var
553
+ image = api.Image(file)
554
+ , doneFn = function (){
555
+ image.get(function (err, img){
556
+ if( !_this._crop[uid] ){
557
+ if( err ){
558
+ opts.get && opts.get($el, file);
559
+ _this.emit('filePreviewError', { error: err, file: file });
560
+ } else {
561
+ $el.html(img);
562
+ }
563
+ }
564
+ });
565
+ }
566
+ ;
567
+
568
+ if( opts.width ){
569
+ image.preview(opts.width, opts.height);
570
+ }
571
+
572
+ if( opts.rotate ){
573
+ image.rotate(opts.rotate);
574
+ }
575
+
576
+ if( opts.processing ){
577
+ opts.processing(file, image, doneFn);
578
+ } else {
579
+ doneFn();
580
+ }
581
+ }
582
+ else {
583
+ opts.get && opts.get($el, file);
584
+ }
585
+ },
586
+
587
+ emit: function (name, arg){
588
+ var opts = this.options, evt = $.Event(name), res;
589
+ evt.widget = this;
590
+ name = $.camelCase('on-'+name.replace(/(file)(upload)/, '$1-$2'));
591
+ if( $.isFunction(opts[name]) ){
592
+ res = opts[name].call(this.el, evt, arg);
593
+ }
594
+ return (res !== false) && this.$el.triggerHandler(evt, arg);
595
+ },
596
+
597
+ /**
598
+ * Add files to queue
599
+ * @param {Array} files
600
+ * @param {Boolean} [reset]
601
+ */
602
+ add: function (files, reset){
603
+ files = [].concat(files);
604
+
605
+ if( files.length ){
606
+ var
607
+ opts = this.options
608
+ , sortFn = opts.sortFn
609
+ , preview = opts.elements.preview
610
+ ;
611
+
612
+ if( sortFn ){
613
+ files.sort(sortFn);
614
+ }
615
+
616
+ if( preview && preview.el ){
617
+ api.each(files, function (file){
618
+ this._makeFilePreview(api.uid(file), file, preview, true);
619
+ }, this);
620
+ }
621
+
622
+ if( this.xhr ){
623
+ this.xhr.append(files);
624
+ }
625
+
626
+ this.queue = reset ? files : this.queue.concat(files);
627
+ this.files = reset ? files : this.files.concat(files);
628
+
629
+ if( this.options.autoUpload ){
630
+ this.upload();
631
+ } else {
632
+ this._redraw();
633
+ }
634
+ }
635
+ },
636
+
637
+ /**
638
+ * Find element
639
+ * @param {String} sel
640
+ * @param {jQuery} [ctx]
641
+ * @return {jQuery}
642
+ */
643
+ $: function (sel, ctx){
644
+ if( typeof sel === 'string' ){
645
+ sel = /^#/.test(sel) ? sel : (ctx ? $(ctx) : this.$el).find(sel);
646
+ }
647
+ return $(sel);
648
+ },
649
+
650
+ /**
651
+ * @param {String} name
652
+ * @param {Boolean} [up]
653
+ * @return {jQuery}
654
+ */
655
+ elem: function (name, up){
656
+ var sel = this.option('elements.'+name);
657
+ if( sel === void 0 && up ){
658
+ sel = this.option('elements.'+name.substr(0, name.lastIndexOf('.')));
659
+ }
660
+ return this.$($.type(sel) != 'string' && $.isEmptyObject(sel) ? [] : sel);
661
+ },
662
+
663
+ /**
664
+ * @param {String} uid
665
+ * @return {jQuery}
666
+ */
667
+ fileElem: function (uid){
668
+ return this.$('['+_dataFileId+'="'+uid+'"]');
669
+ },
670
+
671
+ /**
672
+ * Get/set options
673
+ * @param {String} name
674
+ * @param {*} [value]
675
+ * @return {*}
676
+ */
677
+ option: function (name, value){
678
+ if( value !== void 0 && $.isPlainObject(value) ){
679
+ api.each(value, function (val, key){
680
+ this.option(name+'.'+key, val);
681
+ }, this);
682
+
683
+ return this;
684
+ }
685
+
686
+ var opts = this.options, val = opts[name], i = 0, len, part;
687
+
688
+ if( name.indexOf('.') != -1 ){
689
+ val = opts;
690
+ name = name.split('.');
691
+ len = name.length;
692
+
693
+ for( ; i < len; i++ ){
694
+ part = name[i];
695
+
696
+ if( (value !== void 0) && (len - i === 1) ){
697
+ val[part] = value;
698
+ break;
699
+ }
700
+ else if( val[part] === void 0 ){
701
+ val[part] = {};
702
+ }
703
+
704
+ val = val[part];
705
+ }
706
+ }
707
+ else if( value !== void 0 ){
708
+ opts[name] = value;
709
+ }
710
+
711
+ if( value !== void 0 ){
712
+ this._setOption(name, value, this._options[name]);
713
+ this._options[name] = value;
714
+ }
715
+
716
+ return value !== void 0 ? value : val;
717
+ },
718
+
719
+ _setOption: function (name, nVal){
720
+ switch( name ){
721
+ case 'accept':
722
+ case 'multiple':
723
+ case 'paramName':
724
+ if( name == 'paramName' ){ name = 'name'; }
725
+ if( nVal ){
726
+ this.$(':file')[propFn](name, nVal);
727
+ }
728
+ break;
729
+ }
730
+ },
731
+
732
+ serialize: function (){
733
+ var obj = {}, val;
734
+
735
+ this.$el.find(':input').each(function(name, node){
736
+ if(
737
+ (name = node.name) && !node.disabled
738
+ && (node.checked || /select|textarea|input/i.test(node.nodeName) && /checkbox|radio/i.test(node.type))
739
+ ){
740
+ val = $(node).val();
741
+ if( obj[name] !== void 0 ){
742
+ if( !obj[name].push ){
743
+ obj[name] = [obj[name]];
744
+ }
745
+
746
+ obj[name].push(val);
747
+ } else {
748
+ obj[name] = val;
749
+ }
750
+ }
751
+ });
752
+
753
+ return obj;
754
+ },
755
+
756
+ upload: function (){
757
+ if( !this.active ){
758
+ this.active = true;
759
+
760
+ var
761
+ $el = this.$el
762
+ , opts = this.options
763
+ , files = {}
764
+ , uploadOpts = {
765
+ url: opts.url
766
+ , data: $.extend({}, this.serialize(), opts.data)
767
+ , files: files
768
+ , chunkSize: opts.chunkSize|0
769
+ , chunkUploadRetry: opts.chunkUploadRetry|0
770
+ , prepare: _bind(this, this._onFileUploadPrepare)
771
+ , imageTransform: opts.imageTransform
772
+ }
773
+ ;
774
+
775
+ // Set files
776
+ files[$el.find(':file').attr('name') || 'files[]'] = this.queue;
777
+
778
+ // Add event listeners
779
+ api.each(['upload', 'progress', 'complete'], function (name){
780
+ uploadOpts[name] = _bind(this, this[$.camelCase('_emit-'+name+'Event')], '');
781
+ uploadOpts['file'+name] = _bind(this, this[$.camelCase('_emit-'+name+'Event')], 'file');
782
+ }, this);
783
+
784
+ // Start uploading
785
+ this.xhr = api.upload(uploadOpts);
786
+ this._redraw();
787
+ }
788
+ },
789
+
790
+ crop: function (file, coords){
791
+ var
792
+ uid = api.uid(file)
793
+ , opts = this.options
794
+ , preview = opts.multiple ? this.option('elements.file.preview') : opts.elements.preview
795
+ , $el = (opts.multiple ? this.fileElem(uid) : this.$el).find(preview && preview.el)
796
+ ;
797
+
798
+ if( $el.length ){
799
+ api.getInfo(file, _bind(this, function (err, info){
800
+ if( !err ){
801
+ // @todo error emit
802
+ if( !$el.find('div>div').length ){
803
+ $el.html(
804
+ $('<div><div></div></div>')
805
+ .css(preview)
806
+ .css('overflow', 'hidden')
807
+ );
808
+ }
809
+
810
+ if( this.__cropFile !== file ){
811
+ this.__cropFile = file;
812
+ api.Image(file).rotate(opts.imageAutoOrientation ? 'auto' : 0).get(function (err, img){
813
+ $el.find('>div>div').html($(img).width('100%').height('100%'));
814
+ }, 'exactFit');
815
+ }
816
+
817
+
818
+ var rx = preview.width/coords.w, ry = preview.height/coords.h;
819
+
820
+ $el.find('>div>div').css({
821
+ width: Math.round(rx * info.width)
822
+ , height: Math.round(ry * info.height)
823
+ , marginLeft: -Math.round(rx * coords.x)
824
+ , marginTop: -Math.round(ry * coords.y)
825
+ });
826
+ }
827
+ }));
828
+ }
829
+
830
+ this._crop[uid] = coords;
831
+ },
832
+
833
+ rotate: function (file, deg){
834
+ var
835
+ uid = typeof file == 'object' ? api.uid(file) : file
836
+ , opts = this.options
837
+ , preview = opts.multiple ? this.option('elements.file.preview') : opts.elements.preview
838
+ , $el = (opts.multiple ? this.fileElem(uid) : this.$el).find(preview && preview.el)
839
+ , _rotate = this._rotate
840
+ ;
841
+
842
+ if( /([+-])=/.test(deg) ){
843
+ deg = _rotate[uid] = (_rotate[uid] || 0) + (RegExp.$1 == '+' ? 1 : -1) * deg.substr(2);
844
+ } else {
845
+ _rotate[uid] = deg;
846
+ }
847
+
848
+ $el.css({
849
+ '-webkit-transform': 'rotate('+deg+'deg)'
850
+ , '-moz-transform': 'rotate('+deg+'deg)'
851
+ , 'transform': 'rotate('+deg+'deg)'
852
+ });
853
+ },
854
+
855
+ clear: function (){
856
+ this.queue = [];
857
+ this._redraw();
858
+ },
859
+
860
+ widget: function (){
861
+ return this;
862
+ },
863
+
864
+ destroy: function (){
865
+ this.$el
866
+ .off('.fileapi')
867
+ .removeData('fileapi')
868
+ ;
869
+ }
870
+ };
871
+
872
+
873
+ function _isOriginTransform(trans){
874
+ var key;
875
+ for( key in trans ){
876
+ if( trans.hasOwnProperty(key) ){
877
+ if( !(trans[key] instanceof Object || key === 'overlay') ){
878
+ return true;
879
+ }
880
+ }
881
+ }
882
+ return false;
883
+ }
884
+
885
+
886
+
887
+
888
+
889
+ /**
890
+ * @export
891
+ * @param {Object} options
892
+ * @param {String} [value]
893
+ */
894
+ $.fn.fileapi = function (options, value){
895
+ var plugin = this.data('fileapi');
896
+
897
+ if( plugin ){
898
+ if( options === 'widget' ){
899
+ return plugin;
900
+ }
901
+
902
+ if( typeof options == 'string' ){
903
+ var fn = plugin[options], res;
904
+ if( $.isFunction(fn) ){
905
+ res = fn.call(plugin, value, arguments[2]);
906
+ }
907
+ else if( fn === void 0 ){
908
+ res = this.option(options, value);
909
+ }
910
+ return res === void 0 ? this : res;
911
+ }
912
+ } else {
913
+ this.data('fileapi', new Plugin(this, options));
914
+ }
915
+
916
+ return this;
917
+ };
918
+
919
+
920
+ $.fn.fileapi.version = '0.1.2';
921
+ $.fn.fileapi.tpl = function (text){
922
+ var index = 0;
923
+ var source = "__b+='";
924
+
925
+ text.replace(/(?:&lt;|<)%([-=])?([\s\S]+?)%(?:&gt;|>)|$/g, function (match, mode, expr, offset){
926
+ source += text.slice(index, offset).replace(/[\r\n"']/g, function (match){ return '\\'+match; });
927
+
928
+ if( expr ){
929
+ if( mode ){
930
+ source += "'+\n((__x=("+ expr +"))==null?'':" + (mode == "-" ? "__esc(__x)" : "__x")+")\n+'";
931
+ } else {
932
+ source += "';\n"+ expr +"\n__b+='";
933
+ }
934
+ }
935
+
936
+ index = offset + match.length;
937
+ return match;
938
+ });
939
+
940
+ return new Function("ctx", "var __x,__b=''," +
941
+ "__esc=function(val){return typeof val=='string'?val.replace(/</g,'&lt;').replace(/\"/g,'&quot;'):val;};" +
942
+ "with(ctx||{}){\n"+ source +"';\n}return __b;");
943
+ };
944
+
945
+
946
+ /**
947
+ * FileAPI.Camera wrapper
948
+ * @param {Object|String} options
949
+ * @returns {jQuery}
950
+ */
951
+ $.fn.webcam = function (options){
952
+ var el = this, ret = el, $el = $(el), key = 'fileapi-camera', inst = $el.data(key);
953
+
954
+ if( inst === true ){
955
+ api.log("[webcam.warn] not ready.");
956
+ ret = null;
957
+ }
958
+ else if( options === 'widget' ){
959
+ ret = inst;
960
+ }
961
+ else if( options === 'destroy' ){
962
+ inst.stop();
963
+ $el.empty();
964
+ }
965
+ else if( inst ){
966
+ ret = inst[options]();
967
+ }
968
+ else if( inst === false ){
969
+ api.log("[webcam.error] вoes not work.");
970
+ ret = null;
971
+ }
972
+ else {
973
+ $el.data(key, true);
974
+ options = $.extend({ success: noop, error: noop }, options);
975
+
976
+ FileAPI.Camera.publish($el, options, function (err, cam){
977
+ $el.data(key, err ? false : cam);
978
+ options[err ? 'error' : 'success'].call(el, err || cam);
979
+ });
980
+ }
981
+
982
+ return ret;
983
+ };
984
+
985
+
986
+ /**
987
+ * Wrapper for JCrop
988
+ */
989
+ $.fn.cropper = function (opts){
990
+ var $el = this, file = opts.file;
991
+
992
+ if( typeof opts === 'string' ){
993
+ $el.first().Jcrop.apply($el, arguments);
994
+ }
995
+ else {
996
+ api.getInfo(file, function (err, info){
997
+ var Image = api.Image(file);
998
+
999
+ if( opts.maxSize ){
1000
+ Image.resize(opts.maxSize[0], opts.maxSize[1], 'max');
1001
+ }
1002
+
1003
+ Image.rotate('auto').get(function (err, img){
1004
+ var
1005
+ selection = opts.selection
1006
+ , minSide = Math.min(img.width, img.height)
1007
+
1008
+ , selWidth = minSide
1009
+ , selHeight = minSide / (opts.aspectRatio || 1)
1010
+ ;
1011
+
1012
+ if( selection ){
1013
+ if( /%/.test(selection) || (selection > 0 && selection < 1) ){
1014
+ selection = parseFloat(selection, 10) / (selection > 0 ? 1 : 100);
1015
+ selWidth *= selection;
1016
+ selHeight *= selection;
1017
+ }
1018
+
1019
+ var
1020
+ selLeft = (img.width - selWidth)/2
1021
+ , selTop = (img.height - selHeight)/2
1022
+ ;
1023
+
1024
+ opts.setSelect = [selLeft|0, selTop|0, (selLeft + selWidth)|0, (selTop + selHeight)|0];
1025
+ }
1026
+
1027
+ api.each(['onSelect', 'onChange'], function (name, fn){
1028
+ if( fn = opts[name] ){
1029
+ opts[name] = function (coords){
1030
+ var fw = info.width/img.width, fh = info.height/img.height;
1031
+ fn({
1032
+ x: (coords.x * fw + 0.5)|0
1033
+ , y: (coords.y * fh + 0.5)|0
1034
+ , w: (coords.w * fw + 0.5)|0
1035
+ , h: (coords.h * fh + 0.5)|0
1036
+ });
1037
+ };
1038
+ }
1039
+ });
1040
+
1041
+ var $inner = $('<div/>').css('lineHeight', 0).append( $(img).css('margin', 0) );
1042
+ $el.html($inner);
1043
+ $inner.Jcrop(opts).trigger('resize');
1044
+ });
1045
+ });
1046
+ }
1047
+
1048
+ return $el;
1049
+ };
1050
+ })(jQuery, FileAPI);