jquery.fileapi
Version:
jQuery plugin for FileAPI (multiupload, image upload, crop, resize and etc.)
1,383 lines (1,135 loc) • 32.6 kB
JavaScript
/**
* jQuery plugin for FileAPI v2+
* @auhtor RubaXa <trash@rubaxa.org>
*/
/*jslint evil: true */
/*global jQuery, FileAPI*/
(function ($, api){
"use strict";
var
noop = $.noop
, oldJQ = !$.fn.prop
, propFn = oldJQ ? 'attr' : 'prop'
, removePropFn = oldJQ ? 'removeAttr' : 'removeProp'
, _dataAttr = 'data-fileapi'
, _dataFileId = 'data-fileapi-id'
, _slice = [].slice
, _each = api.each
, _extend = api.extend
, _bind = function (ctx, fn) {
var args = _slice.call(arguments, 2);
return fn.bind ? fn.bind.apply(fn, [ctx].concat(args)) : function (){
return fn.apply(ctx, args.concat(_slice.call(arguments)));
};
}
, _optDataAttr = function (name){
return '['+_dataAttr+'="'+name+'"]';
}
, _isID = function (selector){
return selector.indexOf('#') === 0;
}
;
var Plugin = function (el, options){
this.$el = el = $(el).on('change.fileapi', 'input[type="file"]', _bind(this, this._onSelect));
this.el = el[0];
this._options = {}; // previous options
this.options = { // current options
url: 0,
data: {}, // additional POST-data
accept: 0, // accept mime types, "*" — unlimited
multiple: false, // single or multiple mode upload mode
paramName: 0, // POST-parameter name
dataType: 'json',
duplicate: false, // ignore duplicate
uploadRetry: 0,
networkDownRetryTimeout: 5000,
chunkSize: 0, // or chunk size in bytes, eg: .5 * FileAPI.MB
chunkUploadRetry: 3, // number of retries during upload chunks (html5)
chunkNetworkDownRetryTimeout: 2000,
maxSize: 0, // max file size, 0 — unlimited
maxFiles: 0, // 0 unlimited
imageSize: 0, // { minWidth: 320, minHeight: 240, maxWidth: 3840, maxHeight: 2160 }
sortFn: 0,
filterFn: 0,
autoUpload: false,
clearOnSelect: void 0,
clearOnComplete: void 0,
lang: {
B: 'bytes'
, KB: 'KB'
, MB: 'MB'
, GB: 'GB'
, TB: 'TB'
},
sizeFormat: '0.00',
imageOriginal: true,
imageTransform: 0,
imageAutoOrientation: !!FileAPI.support.exif,
elements: {
ctrl: {
upload: _optDataAttr('ctrl.upload'),
reset: _optDataAttr('ctrl.reset'),
abort: _optDataAttr('ctrl.abort')
},
empty: {
show: _optDataAttr('empty.show'),
hide: _optDataAttr('empty.hide')
},
emptyQueue: {
show: _optDataAttr('emptyQueue.show'),
hide: _optDataAttr('emptyQueue.hide')
},
active: {
show: _optDataAttr('active.show'),
hide: _optDataAttr('active.hide')
},
size: _optDataAttr('size'),
name: _optDataAttr('name'),
progress: _optDataAttr('progress'),
list: _optDataAttr('list'),
file: {
tpl: _optDataAttr('file.tpl'),
progress: _optDataAttr('file.progress'),
active: {
show: _optDataAttr('file.active.show'),
hide: _optDataAttr('file.active.hide')
},
preview: {
el: 0,
get: 0,
width: 0,
height: 0,
processing: 0
},
abort: _optDataAttr('file.abort'),
remove: _optDataAttr('file.remove'),
rotate: _optDataAttr('file.rotate')
},
dnd: {
el: _optDataAttr('dnd'),
hover: 'dnd_hover',
fallback: _optDataAttr('dnd.fallback')
}
},
onDrop: noop,
onDropHover: noop,
onSelect: noop,
onBeforeUpload: noop,
onUpload: noop,
onProgress: noop,
onComplete: noop,
onFilePrepare: noop,
onFileUpload: noop,
onFileProgress: noop,
onFileComplete: noop,
onFileRemove: null,
onFileRemoveCompleted: null
};
$.extend(true, this.options, options); // deep extend
options = this.options;
this.option('elements.file.preview.rotate', options.imageAutoOrientation);
if( !options.url ){
var url = this.$el.attr('action') || this.$el.find('form').attr('action');
if( url ){
options.url = url;
} else {
this._throw('url — is not defined');
}
}
this.$files = this.$elem('list');
this.$fileTpl = this.$elem('file.tpl');
this.itemTplFn = $.fn.fileapi.tpl( $('<div/>').append( this.$elem('file.tpl') ).html() );
_each(options, function (value, option){
this._setOption(option, value);
}, this);
this.$el
.on('reset.fileapi', _bind(this, this._onReset))
.on('submit.fileapi', _bind(this, this._onSubmit))
.on('upload.fileapi progress.fileapi complete.fileapi', _bind(this, this._onUploadEvent))
.on('fileupload.fileapi fileprogress.fileapi filecomplete.fileapi', _bind(this, this._onFileUploadEvent))
.on('click', '['+_dataAttr+']', _bind(this, this._onActionClick))
;
// Controls
var ctrl = options.elements.ctrl;
if( ctrl ){
this._listen('click', ctrl.reset, _bind(this, this._onReset));
this._listen('click', ctrl.upload, _bind(this, this._onSubmit));
this._listen('click', ctrl.abort, _bind(this, this._onAbort));
}
// Drag'n'Drop
var dnd = FileAPI.support.dnd;
this.$elem('dnd.el', true).toggle(dnd);
this.$elem('dnd.fallback').toggle(!dnd);
if( dnd ){
this.$elem('dnd.el', true).dnd(_bind(this, this._onDropHover), _bind(this, this._onDrop));
}
this.$progress = this.$elem('progress');
if( options.clearOnSelect === void 0 ){
options.clearOnSelect = !options.multiple;
}
this.clear();
if( $.isArray(options.files) ){
this.files = $.map(options.files, function (file){
if( $.type(file) === 'string' ){
file = { src: file, size: 0 };
}
file.name = file.name || file.src.split('/').pop();
file.type = file.type || /\.(jpe?g|png|bmp|gif|tiff?)/i.test(file.src) && 'image/' + file.src.split('.').pop(); // @todo: use FileAPI.getMimeType (v2.1+)
file.complete = true;
return file;
});
this._redraw();
}
};
Plugin.prototype = {
constructor: Plugin,
_throw: function (msg){
throw "jquery.fileapi: " + msg;
},
_getFiles: function (evt, fn){
var
opts = this.options
, maxSize = opts.maxSize
, maxFiles = opts.maxFiles
, filterFn = opts.filterFn
, countFiles = this.files.length
, files = api.getFiles(evt)
, data = {
all: files
, files: []
, other: []
, duplicate: opts.duplicate ? [] : this._extractDuplicateFiles(files)
}
, imageSize = opts.imageSize
, _this = this
;
if( imageSize || filterFn ){
api.filterFiles(files, function (file, info){
if( info && imageSize ){
_checkFileByCriteria(file, 'minWidth', imageSize, info);
_checkFileByCriteria(file, 'minHeight', imageSize, info);
_checkFileByCriteria(file, 'maxWidth', imageSize, info);
_checkFileByCriteria(file, 'maxHeight', imageSize, info);
}
_checkFileByCriteria(file, 'maxSize', maxSize, file.size);
return !file.errors && (!filterFn || filterFn(file, info));
}, function (success, rejected){
_extractFilesOverLimit(maxFiles, countFiles, success, rejected);
data.other = rejected;
data.files = success;
fn.call(_this, data);
});
} else {
_each(files, function (file){
_checkFileByCriteria(file, 'maxSize', maxSize, file.size);
data[file.errors ? 'other' : 'files'].push(file);
});
_extractFilesOverLimit(maxFiles, countFiles, data.files, data.other);
fn.call(_this, data);
}
},
_extractDuplicateFiles: function (list/**Array*/){
var duplicates = [], i = list.length, files = this.files, j;
while( i-- ){
j = files.length;
while( j-- ){
if( this._fileCompare(list[i], files[j]) ){
duplicates.push( list.splice(i, 1) );
break;
}
}
}
return duplicates;
},
_fileCompare: function (A/**File*/, B/**File*/){
return (A.size == B.size) && (A.name == B.name);
},
_getFormatedSize: function (size){
var opts = this.options, postfix = 'B';
if( size >= api.TB ){
size /= api[postfix = 'TB'];
}
else if( size >= api.GB ){
size /= api[postfix = 'GB'];
}
else if( size >= api.MB ){
size /= api[postfix = 'MB'];
}
else if( size >= api.KB ){
size /= api[postfix = 'KB'];
}
return opts.sizeFormat.replace(/^\d+([^\d]+)(\d*)/, function (_, separator, fix){
size = (parseFloat(size) || 0).toFixed(fix.length);
return (size + '').replace('.', separator) +' '+ opts.lang[postfix];
});
},
_onSelect: function (evt){
if( this.options.clearOnSelect ){
this.queue = [];
this.files = [];
}
this._getFiles(evt, _bind(this, function (data){
if( data.all.length && this.emit('select', data) !== false ){
this.add(data.files);
}
// Reset input
FileAPI.reset(evt.target);
}));
},
_onActionClick: function (evt){
var
el = evt.currentTarget
, act = $.attr(el, _dataAttr)
, uid = $(el).closest('['+_dataFileId+']', this.$el).attr(_dataFileId)
, file = this._getFile(uid)
, prevent = true
;
if( !this.$file(uid).attr('disabled') ){
if( 'file.remove' == act ){
if( file && this.emit('fileRemove' + (file.complete ? 'Completed' : ''), file) ){
this.remove(uid);
}
}
else if( /^file\.rotate/.test(act) ){
this.rotate(uid, (/ccw/.test(act) ? '-=90' : '+=90'));
}
else {
prevent = false;
}
}
if( prevent ){
evt.preventDefault();
}
},
_listen: function (name, selector, fn){
selector && _each($.trim(selector).split(','), function (selector){
selector = $.trim(selector);
if( _isID(selector) ){
$(selector).on(name+'.fileapi', fn);
} else {
this.$el.on(name+'.fileapi', selector, fn);
}
}, this);
},
_onSubmit: function (evt){
evt.preventDefault();
this.upload();
},
_onReset: function (evt){
evt.preventDefault();
this.clear(true);
},
_onAbort: function (evt){
evt.preventDefault();
this.abort();
},
_onDrop: function (files){
this._getFiles(files, function (data){
if( this.emit('drop', data) !== false ){
this.add(data.files);
}
});
},
_onDropHover: function (state, evt){
if( this.emit('dropHover', { state: state, event: evt }) !== false ){
var hover = this.option('elements.dnd.hover');
if( hover ){
$(evt.currentTarget).toggleClass(hover, state);
}
}
},
_getFile: function (uid){
return api.filter(this.files, function (file){ return api.uid(file) == uid; })[0];
},
_getUploadEvent: function (xhr, extra){
xhr = this.xhr || xhr;
var evt = {
xhr: xhr
, file: this.xhr.currentFile
, files: this.xhr.files
, widget: this
};
return _extend(evt, extra);
},
_emitUploadEvent: function (prefix, file, xhr){
var evt = this._getUploadEvent(xhr);
this.emit(prefix+'Upload', evt);
},
_emitProgressEvent: function (prefix, event, file, xhr){
var evt = this._getUploadEvent(xhr, event);
this.emit(prefix+'Progress', evt);
},
_emitCompleteEvent: function (prefix, err, xhr, file){
var evt = this._getUploadEvent(xhr, {
error: err
, status: xhr.status
, statusText: xhr.statusText
, result: xhr.responseText
});
if( prefix == 'file' ){
file.complete = true;
}
if( this.options.dataType == 'json' ){
try {
evt.result = $.parseJSON(evt.result);
} catch (err){
evt.error = err;
}
}
this.emit(prefix+'Complete', evt);
},
_onUploadEvent: function (evt, ui){
var _this = this, $progress = _this.$progress, type = evt.type;
if( type == 'progress' ){
$progress.stop().animate({ width: ui.loaded/ui.total*100 + '%' }, 300);
}
else if( type == 'upload' ){
// Начало загрузки
$progress.width(0);
}
else {
// Завершение загрузки
var fn = function (){
$progress.dequeue();
_this[_this.options.clearOnComplete ? 'clear' : 'dequeue']();
};
this.xhr = null;
this.active = false;
if( $progress.length ){
$progress.queue(fn);
} else {
fn();
}
}
},
_onFileUploadPrepare: function (file, opts){
var
uid = api.uid(file)
, deg = this._rotate[uid]
, crop = this._crop[uid]
, resize = this._resize[uid]
, evt = this._getUploadEvent(this.xhr)
;
if( deg || crop || resize ){
var trans = $.extend(true, {}, opts.imageTransform || {});
deg = (deg != null) ? deg : (this.options.imageAutoOrientation ? 'auto' : void 0);
if( $.isEmptyObject(trans) || _isOriginTransform(trans) ){
_extend(trans, resize);
trans.crop = crop;
trans.rotate = deg;
}
else {
_each(trans, function (opts){
_extend(opts, resize);
opts.crop = crop;
opts.rotate = deg;
});
}
opts.imageTransform = trans;
}
evt.file = file;
evt.options = opts;
this.emit('filePrepare', evt);
},
_onFileUploadEvent: function (evt, ui){
var
_this = this
, type = evt.type.substr(4)
, uid = api.uid(ui.file)
, $file = this.$file(uid)
, $progress = this._$fileprogress
, opts = this.options
;
if( this.__fileId !== uid ){
this.__fileId = uid;
this._$fileprogress = $progress = this.$elem('file.progress', $file);
}
if( type == 'progress' ){
$progress.stop().animate({ width: ui.loaded/ui.total*100 + '%' }, 300);
}
else if( type == 'upload' || type == 'complete' ){
var fn = function (){
var
elem = 'file.'+ type
, $remove = _this.$elem('file.remove', $file)
;
if( type == 'upload' ){
$remove.hide();
$progress.width(0);
} else if( opts.onFileRemoveCompleted ){
$remove.show();
}
$progress.dequeue();
_this.$elem(elem + '.show', $file).show();
_this.$elem(elem + '.hide', $file).hide();
};
if( $progress.length ){
$progress.queue(fn);
} else {
fn();
}
if( type == 'complete' ){
this.uploaded.push(ui.file);
delete this._rotate[uid];
}
}
},
_redraw: function (clearFiles/**Boolean*/, clearPreview/**Boolean*/){
var
files = this.files
, active = !!this.active
, empty = !files.length && !active
, emptyQueue = !this.queue.length && !active
, name = []
, size = 0
, $files = this.$files
, offset = $files.children().length
, preview = this.option('elements.preview')
, filePreview = this.option('elements.file.preview')
;
if( clearFiles ){
this.$files.empty();
}
if( clearPreview && preview && preview.el && !this.queue.length ) {
this.$(preview.el).empty();
}
_each(files, function (file, i){
var uid = api.uid(file);
name.push(file.name);
size += file.complete ? 0 : file.size;
if( preview && preview.el ){
this._makeFilePreview(uid, file, preview, true);
}
else if( $files.length && !this.$file(uid).length ){
var
html = this.itemTplFn({
$idx: offset + i
, uid: uid
, name: file.name
, type: file.type
, size: file.size
, complete: !!file.complete
, sizeText: this._getFormatedSize(file.size)
})
, $file = $(html).attr(_dataFileId, uid)
;
file.$el = $file;
$files.append( $file );
if( file.complete ){
this.$elem('file.upload.hide', $file).hide();
this.$elem('file.complete.hide', $file).hide();
}
if( filePreview.el ){
this._makeFilePreview(uid, file, filePreview);
}
}
}, this);
this.$elem('name').text( name.join(', ') );
this.$elem('size').text( !emptyQueue ? this._getFormatedSize(size) : '' );
this.$elem('empty.show').toggle( empty );
this.$elem('empty.hide').toggle( !empty );
this.$elem('emptyQueue.show').toggle( emptyQueue );
this.$elem('emptyQueue.hide').toggle( !emptyQueue );
this.$elem('active.show').toggle( active );
this.$elem('active.hide').toggle( !active );
this.$('.js-fileapi-wrapper,:file')
[active ? 'attr' : 'removeAttr']('aria-disabled', active)
[propFn]('disabled', active)
;
// Upload control
this._disableElem('ctrl.upload', emptyQueue || active);
// Reset control
this._disableElem('ctrl.reset', emptyQueue || active);
// Abort control
this._disableElem('ctrl.abort', !active);
},
_disableElem: function (name, state){
this.$elem(name)
[state ? 'attr' : 'removeAttr']('aria-disabled', 'disabled')
[propFn]('disabled', state)
;
},
_makeFilePreview: function (uid, file, opts, global){
var
_this = this
, $el = global ? _this.$(opts.el) : _this.$file(uid).find(opts.el)
;
if( !_this._crop[uid] ){
if( /^image/.test(file.type) ){
var
image = api.Image(file.src || file)
, doneFn = function (){
image.get(function (err, img){
if( !_this._crop[uid] ){
if( err ){
opts.get && opts.get($el, file);
_this.emit('previewError', [err, file]);
} else {
$el.html(img);
}
}
});
}
;
if( opts.width ){
image.preview(opts.width, opts.height);
}
if( opts.rotate ){
image.rotate('auto');
}
if( opts.processing ){
opts.processing(file, image, doneFn);
} else {
doneFn();
}
}
else {
opts.get && opts.get($el, file);
}
}
},
emit: function (name, arg){
var opts = this.options, evt = $.Event(name.toLowerCase()), res;
evt.widget = this;
name = $.camelCase('on-'+name);
if( $.isFunction(opts[name]) ){
res = opts[name].call(this.el, evt, arg);
}
this.$el.triggerHandler(evt, arg);
return (res !== false) && !evt.isDefaultPrevented();
},
/**
* Add files to queue
* @param {Array} files
* @param {Boolean} [clear]
*/
add: function (files, clear){
files = [].concat(files);
if( files.length ){
var
opts = this.options
, sortFn = opts.sortFn
;
if( sortFn ){
files.sort(sortFn);
}
if( this.xhr ){
this.xhr.append(files);
}
this.queue = clear ? files : this.queue.concat(files);
this.files = clear ? files : this.files.concat(files);
if( this.active ){
this.xhr.append(files);
this._redraw(clear);
}
else {
this._redraw(clear);
if( this.options.autoUpload ){
this.upload();
}
}
}
},
/**
* Find element
* @param {String} sel
* @param {jQuery} [ctx]
* @return {jQuery}
*/
$: function (sel, ctx){
if( typeof sel === 'string' ){
sel = /^#/.test(sel) ? sel : (ctx ? $(ctx) : this.$el).find(sel);
}
return $(sel);
},
/**
* @param {String} name
* @param {Boolean|jQuery} [up]
* @param {jQuery} [ctx]
* @return {jQuery}
*/
$elem: function (name, up, ctx){
if( up && up.jquery ){
ctx = up;
up = false;
}
var sel = this.option('elements.'+name);
if( sel === void 0 && up ){
sel = this.option('elements.'+name.substr(0, name.lastIndexOf('.')));
}
return this.$($.type(sel) != 'string' && $.isEmptyObject(sel) ? [] : sel, ctx);
},
/**
* @param {String} uid
* @return {jQuery}
*/
$file: function (uid){
return this.$('['+_dataFileId+'="'+uid+'"]');
},
/**
* Get/set options
* @param {String} name
* @param {*} [value]
* @return {*}
*/
option: function (name, value){
if( value !== void 0 && $.isPlainObject(value) ){
_each(value, function (val, key){
this.option(name+'.'+key, val);
}, this);
return this;
}
var opts = this.options, val = opts[name], i = 0, len, part;
if( name.indexOf('.') != -1 ){
val = opts;
name = name.split('.');
len = name.length;
for( ; i < len; i++ ){
part = name[i];
if( (value !== void 0) && (len - i === 1) ){
val[part] = value;
break;
}
else if( !val[part] ){
val[part] = {};
}
val = val[part];
}
}
else if( value !== void 0 ){
opts[name] = value;
}
if( value !== void 0 ){
this._setOption(name, value, this._options[name]);
this._options[name] = value;
}
return value !== void 0 ? value : val;
},
_setOption: function (name, nVal){
switch( name ){
case 'accept':
case 'multiple':
this.$(':file')[nVal ? propFn : removePropFn](name, nVal);
break;
case 'paramName':
nVal && this.$(':file')[propFn]('name', nVal);
break;
}
},
serialize: function (){
var obj = {}, val;
this.$el.find(':input').each(function(name, node){
if(
(name = node.name) && !node.disabled
&& (node.checked || /select|textarea|input/i.test(node.nodeName) && !/checkbox|radio|file/i.test(node.type))
){
val = $(node).val();
if( obj[name] !== void 0 ){
if( !obj[name].push ){
obj[name] = [obj[name]];
}
obj[name].push(val);
} else {
obj[name] = val;
}
}
});
return obj;
},
upload: function (){
if( !this.active && this.emit('beforeUpload', { widget: this, files: this.queue }) ){
this.active = true;
var
$el = this.$el
, opts = this.options
, files = {}
, uploadOpts = {
url: opts.url
, data: _extend({}, this.serialize(), opts.data)
, headers: opts.headers
, files: files
, uploadRetry: opts.uploadRetry
, networkDownRetryTimeout: opts.networkDownRetryTimeout
, chunkSize: opts.chunkSize
, chunkUploadRetry: opts.chunkUploadRetry
, chunkNetworkDownRetryTimeout: opts.chunkNetworkDownRetryTimeout
, prepare: _bind(this, this._onFileUploadPrepare)
, imageOriginal: opts.imageOriginal
, imageTransform: opts.imageTransform
, imageAutoOrientation: opts.imageAutoOrientation
}
;
// Set files
files[$el.find(':file').attr('name') || 'files[]'] = this.queue;
// Add event listeners
_each(['Upload', 'Progress', 'Complete'], function (name){
var lowerName = name.toLowerCase();
uploadOpts[lowerName] = _bind(this, this['_emit'+name+'Event'], '');
uploadOpts['file'+lowerName] = _bind(this, this['_emit'+name+'Event'], 'file');
}, this);
// Start uploading
this.xhr = api.upload(uploadOpts);
this._redraw();
}
},
abort: function (text){
if( this.active && this.xhr ){
this.xhr.abort(text);
}
},
crop: function (file, coords){
var
uid = api.uid(file)
, opts = this.options
, preview = opts.multiple ? this.option('elements.file.preview') : opts.elements.preview
, $el = (opts.multiple ? this.$file(uid) : this.$el).find(preview && preview.el)
;
if( $el.length ){
api.getInfo(file, _bind(this, function (err, info){
if( err ){
this.emit('previewError', [err, file]);
} else {
if( !$el.find('div>div').length ){
$el.html(
$('<div><div></div></div>')
.css(preview)
.css('overflow', 'hidden')
);
}
if( this.__cropFile !== file ){
this.__cropFile = file;
api.Image(file).rotate(opts.imageAutoOrientation ? 'auto' : 0).get(function (err, img){
$el.find('>div>div').html($(img).width('100%').height('100%'));
}, 'exactFit');
}
var
pw = preview.width, ph = preview.height
, mx = pw, my = ph
, rx = pw/coords.rw, ry = ph/coords.rh
;
if( preview.keepAspectRatio ){
if (rx > 1 && ry > 1){ // image is smaller than preview (no scale)
rx = ry = 1;
my = coords.h;
mx = coords.w;
} else { // image is bigger than preview (scale)
if( rx < ry ){
ry = rx;
my = pw * coords.rh / coords.rw;
} else {
rx = ry;
mx = ph * coords.rw / coords.rh;
}
}
}
$el.find('>div>div').css({
width: Math.round(rx * info[coords.flip ? 'height' : 'width'])
, height: Math.round(ry * info[coords.flip ? 'width' : 'height'])
, marginLeft: -Math.round(rx * coords.rx)
, marginTop: -Math.round(ry * coords.ry)
});
if( preview.keepAspectRatio ){ // create side gaps
$el.find('>div').css({
width: Math.round(mx)
, height: Math.round(my)
, marginLeft: mx < pw ? Math.round((pw - mx) / 2) : 0
, marginTop: my < ph ? Math.round((ph - my) / 2) : 0
});
}
}
}));
}
this._crop[uid] = coords;
},
resize: function (file, width, height, type){
this._resize[api.uid(file)] = {
type: type
, width: width
, height: height
};
},
rotate: function (file, deg){
var
uid = $.type(file) == 'string' ? file : api.uid(file)
, opts = this.options
, preview = opts.multiple ? this.option('elements.file.preview') : opts.elements.preview
, $el = (opts.multiple ? this.$file(uid) : this.$el).find(preview && preview.el)
, _rotate = this._rotate
;
file = this._getFile(uid);
api.getInfo(file, function (err, info) {
var orientation = info && info.exif && info.exif.Orientation,
startDeg = opts.imageAutoOrientation && api.Image.exifOrientation[orientation] || 0;
if (_rotate[uid] == null) {
_rotate[uid] = startDeg || 0;
}
if( /([+-])=/.test(deg) ){
_rotate[uid] = deg = (_rotate[uid] + (RegExp.$1 == '+' ? 1 : -1) * deg.substr(2));
} else {
_rotate[uid] = deg;
}
// Store deg
file.rotate = deg;
// Fix exif.rotate.auto
deg -= startDeg;
$el.css({
'-webkit-transform': 'rotate('+deg+'deg)'
, '-moz-transform': 'rotate('+deg+'deg)'
, 'transform': 'rotate('+deg+'deg)'
});
});
},
remove: function (file){
var uid = typeof file == 'object' ? api.uid(file) : file;
this.$file(uid).remove();
this.queue = api.filter(this.queue, function (file){ return api.uid(file) != uid; });
this.files = api.filter(this.files, function (file){ return api.uid(file) != uid; });
this._redraw();
},
clear: function (all) {
this._crop = {};
this._resize = {};
this._rotate = {}; // rotate deg
this.queue = [];
this.files = []; // all files
this.uploaded = []; // uploaded files
all = all === void 0 ? true : all;
this._redraw(all, all);
},
dequeue: function (){
this.queue = [];
this._redraw();
},
widget: function (){
return this;
},
toString: function (){
return '[jQuery.FileAPI object]';
},
destroy: function (){
this.$files.empty().append(this.$fileTpl);
this.$el
.off('.fileapi')
.removeData('fileapi')
;
_each(this.options.elements.ctrl, function (selector){
_isID(selector) && $(selector).off('click.fileapi');
});
}
};
function _isOriginTransform(trans){
var key;
for( key in trans ){
if( trans.hasOwnProperty(key) ){
if( !(trans[key] instanceof Object || key === 'overlay') ){
return true;
}
}
}
return false;
}
function _checkFileByCriteria(file, name, imageSize, info){
if( imageSize && info ){
var excepted = imageSize > 0 ? imageSize : imageSize[name],
actual = info > 0 ? info : info[name.substr(3).toLowerCase()],
val = (excepted - actual),
isMax = /max/.test(name)
;
if( (isMax && val < 0) || (!isMax && val > 0) ){
if( !file.errors ){
file.errors = {};
}
file.errors[name] = Math.abs(val);
}
}
}
function _extractFilesOverLimit(limit, countFiles, files, other){
if( limit ){
var delta = files.length - (limit - countFiles);
if( delta > 0 ){
_each(files.splice(0, delta), function (file, i){
_checkFileByCriteria(file, 'maxFiles', -1, i);
other.push(file);
});
}
}
}
/**
* @export
* @param {Object} options
* @param {String} [value]
*/
$.fn.fileapi = function (options, value){
var plugin = this.data('fileapi');
if( plugin ){
if( options === 'widget' ){
return plugin;
}
if( typeof options == 'string' ){
var fn = plugin[options], res;
if( $.isFunction(fn) ){
res = fn.apply(plugin, _slice.call(arguments, 1));
}
else if( fn === void 0 ){
res = plugin.option(options, value);
}
else if( options === 'files' ){
res = fn;
}
return res === void 0 ? this : res;
}
} else if( options == null || typeof options == 'object' ){
this.data('fileapi', new Plugin(this, options));
}
return this;
};
$.fn.fileapi.version = '0.4.11';
$.fn.fileapi.tpl = function (text){
var index = 0;
var source = "__b+='";
text.replace(/(?:<|<)%([-=])?([\s\S]+?)%(?:>|>)|$/g, function (match, mode, expr, offset){
source += text.slice(index, offset).replace(/[\r\n"']/g, function (match){ return '\\'+match; });
if( expr ){
if( mode ){
source += "'+\n((__x=("+ expr +"))==null?'':" + (mode == "-" ? "__esc(__x)" : "__x")+")\n+'";
} else {
source += "';\n"+ expr +"\n__b+='";
}
}
index = offset + match.length;
return match;
});
return new Function("ctx", "var __x,__b=''," +
"__esc=function(val){return typeof val=='string'?val.replace(/</g,'<').replace(/\"/g,'"'):val;};" +
"with(ctx||{}){\n"+ source +"';\n}return __b;");
};
/**
* FileAPI.Camera wrapper
* @param {Object|String} options
* @returns {jQuery}
*/
$.fn.webcam = function (options){
var el = this, ret = el, $el = $(el), key = 'fileapi-camera', inst = $el.data(key);
if( inst === true ){
api.log("[webcam.warn] not ready.");
ret = null;
}
else if( options === 'widget' ){
ret = inst;
}
else if( options === 'destroy' ){
inst.stop();
$el.empty();
}
else if( inst ){
ret = inst[options]();
}
else if( inst === false ){
api.log("[webcam.error] does not work.");
ret = null;
}
else {
$el.data(key, true);
options = _extend({ success: noop, error: noop }, options);
FileAPI.Camera.publish($el, options, function (err, cam){
$el.data(key, err ? false : cam);
options[err ? 'error' : 'success'].call(el, err || cam);
});
}
return ret;
};
/**
* Wrapper for JCrop
*/
$.fn.cropper = function (opts){
var $el = this, file = opts.file;
if( typeof opts === 'string' ){
$el.first().Jcrop.apply($el, arguments);
}
else {
var
minSize = opts.minSize || [0, 0],
ratio = (opts.aspectRatio || minSize[0]/minSize[1])
;
if( $.isArray(opts.minSize) && opts.aspectRatio === void 0 && ratio > 0 ){
opts.aspectRatio = ratio;
}
api.getInfo(file, function (err, info){
var
Image = api.Image(file)
, maxSize = opts.maxSize
, deg = file.rotate
;
if( maxSize ){
Image.resize(
Math.max(maxSize[0], minSize[0])
, Math.max(maxSize[1], minSize[1])
, 'max'
);
}
Image.rotate(deg === void 0 ? 'auto' : deg).get(function (err, img){
var
selection = opts.selection
, minSide = Math.min(img.width, img.height)
, selWidth = minSide
, selHeight = minSide / ratio
, deg = FileAPI.Image.exifOrientation[info.exif && info.exif.Orientation] || 0
;
if( selection ){
if( /%/.test(selection) || (selection > 0 && selection < 1) ){
selection = parseFloat(selection, 10) / (selection > 0 ? 1 : 100);
selWidth *= selection;
selHeight *= selection;
}
var
selLeft = (img.width - selWidth)/2
, selTop = (img.height - selHeight)/2
;
opts.setSelect = [selLeft|0, selTop|0, (selLeft + selWidth)|0, (selTop + selHeight)|0];
}
_each(['onSelect', 'onChange'], function (name, fn){
if( fn = opts[name] ){
opts[name] = function (coords){
var
flip = deg % 180
, ow = info.width
, oh = info.height
, fx = coords.x/img.width
, fy = coords.y/img.height
, fw = coords.w/img.width
, fh = coords.h/img.height
, x = ow * (flip ? fy : fx)
, y = oh * (flip ? 1 - (coords.x + coords.w)/img.width : fy)
, w = ow * (flip ? fh : fw)
, h = oh * (flip ? fw : fh)
;
fn({
x: x
, y: y
, w: w
, h: h
, rx: fx * (flip ? oh : ow)
, ry: fy * (flip ? ow : oh)
, rw: fw * (flip ? oh : ow)
, rh: fh * (flip ? ow : oh)
, lx: coords.x // local coords
, ly: coords.y
, lw: coords.w
, lh: coords.h
, lx2: coords.x2
, ly2: coords.y2
, deg: deg
, flip: flip
});
};
}
});
var $inner = $('<div/>').css('lineHeight', 0).append( $(img).css('margin', 0) );
$el.html($inner);
$inner.Jcrop(opts).trigger('resize');
});
});
}
return $el;
};
})(jQuery, FileAPI);