/** * jQuery plugin for FileAPI v2+ * @auhtor RubaXa */ /*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( $('
').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( $('
') .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(/ 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 = $('
').css('lineHeight', 0).append( $(img).css('margin', 0) ); $el.html($inner); $inner.Jcrop(opts).trigger('resize'); }); }); } return $el; }; })(jQuery, FileAPI);