You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
554 lines
17 KiB
554 lines
17 KiB
4 years ago
|
/**
|
||
|
<b>Ace file input element</b>. Custom, simple file input element to style browser's default file input.
|
||
|
*/
|
||
|
(function($ , undefined) {
|
||
|
var multiplible = 'multiple' in document.createElement('INPUT');
|
||
|
var hasFileList = 'FileList' in window;//file list enabled in modern browsers
|
||
|
var hasFileReader = 'FileReader' in window;
|
||
|
var hasFile = 'File' in window;
|
||
|
|
||
|
var Ace_File_Input = function(element , settings) {
|
||
|
var self = this;
|
||
|
this.settings = $.extend({}, $.fn.ace_file_input.defaults, settings);
|
||
|
|
||
|
this.$element = $(element);
|
||
|
this.element = element;
|
||
|
this.disabled = false;
|
||
|
this.can_reset = true;
|
||
|
|
||
|
|
||
|
this.$element
|
||
|
.off('change.ace_inner_call')
|
||
|
.on('change.ace_inner_call', function(e , ace_inner_call){
|
||
|
if(ace_inner_call === true) return;//this change event is called from above drop event and extra checkings are taken care of there
|
||
|
return handle_on_change.call(self);
|
||
|
//if(ret === false) e.preventDefault();
|
||
|
});
|
||
|
|
||
|
var parent_label = this.$element.closest('label').css({'display':'block'})
|
||
|
var tagName = parent_label.length == 0 ? 'label' : 'span';//if not inside a "LABEL" tag, use "LABEL" tag, otherwise use "SPAN"
|
||
|
this.$element.wrap('<'+tagName+' class="ace-file-input" />');
|
||
|
|
||
|
this.apply_settings();
|
||
|
this.reset_input_field();//for firefox as it keeps selected file after refresh
|
||
|
}
|
||
|
Ace_File_Input.error = {
|
||
|
'FILE_LOAD_FAILED' : 1,
|
||
|
'IMAGE_LOAD_FAILED' : 2,
|
||
|
'THUMBNAIL_FAILED' : 3
|
||
|
};
|
||
|
|
||
|
|
||
|
Ace_File_Input.prototype.apply_settings = function() {
|
||
|
var self = this;
|
||
|
|
||
|
this.multi = this.$element.attr('multiple') && multiplible;
|
||
|
this.well_style = this.settings.style == 'well';
|
||
|
|
||
|
if(this.well_style) this.$element.parent().addClass('ace-file-multiple');
|
||
|
else this.$element.parent().removeClass('ace-file-multiple');
|
||
|
|
||
|
|
||
|
this.$element.parent().find(':not(input[type=file])').remove();//remove all except our input, good for when changing settings
|
||
|
this.$element.after('<span class="ace-file-container" data-title="'+this.settings.btn_choose+'"><span class="ace-file-name" data-title="'+this.settings.no_file+'">'+(this.settings.no_icon ? '<i class="'+ ace.vars['icon'] + this.settings.no_icon+'"></i>' : '')+'</span></span>');
|
||
|
this.$label = this.$element.next();
|
||
|
this.$container = this.$element.closest('.ace-file-input');
|
||
|
|
||
|
var remove_btn = !!this.settings.icon_remove;
|
||
|
if(remove_btn) {
|
||
|
var btn =
|
||
|
$('<a class="remove" href="#"><i class="'+ ace.vars['icon'] + this.settings.icon_remove+'"></i></a>')
|
||
|
.appendTo(this.$element.parent());
|
||
|
|
||
|
btn.on(ace.click_event, function(e){
|
||
|
e.preventDefault();
|
||
|
if( !self.can_reset ) return false;
|
||
|
|
||
|
var ret = true;
|
||
|
if(self.settings.before_remove) ret = self.settings.before_remove.call(self.element);
|
||
|
if(!ret) return false;
|
||
|
|
||
|
var r = self.reset_input();
|
||
|
return false;
|
||
|
});
|
||
|
}
|
||
|
|
||
|
|
||
|
if(this.settings.droppable && hasFileList) {
|
||
|
enable_drop_functionality.call(this);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Ace_File_Input.prototype.show_file_list = function($files) {
|
||
|
var files = typeof $files === "undefined" ? this.$element.data('ace_input_files') : $files;
|
||
|
if(!files || files.length == 0) return;
|
||
|
|
||
|
//////////////////////////////////////////////////////////////////
|
||
|
|
||
|
if(this.well_style) {
|
||
|
this.$label.find('.ace-file-name').remove();
|
||
|
if(!this.settings.btn_change) this.$label.addClass('hide-placeholder');
|
||
|
}
|
||
|
this.$label.attr('data-title', this.settings.btn_change).addClass('selected');
|
||
|
|
||
|
for (var i = 0; i < files.length; i++) {
|
||
|
var filename = typeof files[i] === "string" ? files[i] : $.trim( files[i].name );
|
||
|
var index = filename.lastIndexOf("\\") + 1;
|
||
|
if(index == 0)index = filename.lastIndexOf("/") + 1;
|
||
|
filename = filename.substr(index);
|
||
|
|
||
|
var fileIcon = 'fa fa-file';
|
||
|
var format = 'file';
|
||
|
|
||
|
if((/\.(jpe?g|png|gif|svg|bmp|tiff?)$/i).test(filename)) {
|
||
|
fileIcon = 'fa fa-picture-o file-image';
|
||
|
format = 'image';
|
||
|
}
|
||
|
else if((/\.(mpe?g|flv|mov|avi|swf|mp4|mkv|webm|wmv|3gp)$/i).test(filename)) {
|
||
|
fileIcon = 'fa fa-film file-video';
|
||
|
format = 'video';
|
||
|
}
|
||
|
else if((/\.(mp3|ogg|wav|wma|amr|aac)$/i).test(filename)) {
|
||
|
fileIcon = 'fa fa-music file-audio';
|
||
|
format = 'audio';
|
||
|
}
|
||
|
|
||
|
|
||
|
if(!this.well_style) this.$label.find('.ace-file-name').attr({'data-title':filename}).find(ace.vars['.icon']).attr('class', ace.vars['icon'] + fileIcon);
|
||
|
else {
|
||
|
this.$label.append('<span class="ace-file-name" data-title="'+filename+'"><i class="'+ ace.vars['icon'] + fileIcon+'"></i></span>');
|
||
|
var type = $.trim(files[i].type);
|
||
|
var can_preview = hasFileReader && this.settings.thumbnail
|
||
|
&&
|
||
|
( (type.length > 0 && type.match('image')) || (type.length == 0 && format == 'image') )//the second one is for Android's default browser which gives an empty text for file.type
|
||
|
if(can_preview) {
|
||
|
var self = this;
|
||
|
$.when(preview_image.call(this, files[i])).fail(function(result){
|
||
|
//called on failure to load preview
|
||
|
if(self.settings.preview_error) self.settings.preview_error.call(self, filename, result.code);
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
Ace_File_Input.prototype.reset_input = function() {
|
||
|
this.reset_input_ui();
|
||
|
this.reset_input_field();
|
||
|
}
|
||
|
|
||
|
Ace_File_Input.prototype.reset_input_ui = function() {
|
||
|
this.$label.attr({'data-title':this.settings.btn_choose, 'class':'ace-file-container'})
|
||
|
.find('.ace-file-name:first').attr({'data-title':this.settings.no_file , 'class':'ace-file-name'})
|
||
|
.find(ace.vars['.icon']).attr('class', ace.vars['icon'] + this.settings.no_icon)
|
||
|
.prev('img').remove();
|
||
|
if(!this.settings.no_icon) this.$label.find(ace.vars['.icon']).remove();
|
||
|
|
||
|
this.$label.find('.ace-file-name').not(':first').remove();
|
||
|
|
||
|
this.reset_input_data();
|
||
|
}
|
||
|
Ace_File_Input.prototype.reset_input_field = function() {
|
||
|
//http://stackoverflow.com/questions/1043957/clearing-input-type-file-using-jquery/13351234#13351234
|
||
|
this.$element.wrap('<form>').parent().get(0).reset();
|
||
|
this.$element.unwrap();
|
||
|
|
||
|
//strangely when reset is called on this temporary inner form
|
||
|
//only **IE9/10** trigger 'reset' on the outer form as well
|
||
|
//and as we have mentioned to reset input on outer form reset
|
||
|
//it causes infinite recusrsion by coming back to reset_input_field
|
||
|
//thus calling reset again and again and again
|
||
|
//so because when "reset" button of outer form is hit, file input is automatically reset
|
||
|
//we just reset_input_ui to avoid recursion
|
||
|
}
|
||
|
Ace_File_Input.prototype.reset_input_data = function() {
|
||
|
if(this.$element.data('ace_input_files')) {
|
||
|
this.$element.removeData('ace_input_files');
|
||
|
this.$element.removeData('ace_input_method');
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Ace_File_Input.prototype.enable_reset = function(can_reset) {
|
||
|
this.can_reset = can_reset;
|
||
|
}
|
||
|
|
||
|
Ace_File_Input.prototype.disable = function() {
|
||
|
this.disabled = true;
|
||
|
this.$element.attr('disabled', 'disabled').addClass('disabled');
|
||
|
}
|
||
|
Ace_File_Input.prototype.enable = function() {
|
||
|
this.disabled = false;
|
||
|
this.$element.removeAttr('disabled').removeClass('disabled');
|
||
|
}
|
||
|
|
||
|
Ace_File_Input.prototype.files = function() {
|
||
|
return $(this).data('ace_input_files') || null;
|
||
|
}
|
||
|
Ace_File_Input.prototype.method = function() {
|
||
|
return $(this).data('ace_input_method') || '';
|
||
|
}
|
||
|
|
||
|
Ace_File_Input.prototype.update_settings = function(new_settings) {
|
||
|
this.settings = $.extend({}, this.settings, new_settings);
|
||
|
this.apply_settings();
|
||
|
}
|
||
|
|
||
|
Ace_File_Input.prototype.loading = function(is_loading) {
|
||
|
if(is_loading === false) {
|
||
|
this.$container.find('.ace-file-overlay').remove();
|
||
|
this.element.removeAttribute('readonly');
|
||
|
}
|
||
|
else {
|
||
|
var inside = typeof is_loading === 'string' ? is_loading : '<i class="overlay-content fa fa-spin fa-spinner orange2 fa-2x"></i>';
|
||
|
var loader = this.$container.find('.ace-file-overlay');
|
||
|
if(loader.length == 0) {
|
||
|
loader = $('<div class="ace-file-overlay"></div>').appendTo(this.$container);
|
||
|
loader.on('click tap', function(e) {
|
||
|
e.stopImmediatePropagation();
|
||
|
e.preventDefault();
|
||
|
return false;
|
||
|
});
|
||
|
|
||
|
this.element.setAttribute('readonly' , 'true');//for IE
|
||
|
}
|
||
|
loader.empty().append(inside);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
var enable_drop_functionality = function() {
|
||
|
var self = this;
|
||
|
var dropbox = this.$element.parent();
|
||
|
dropbox
|
||
|
.off('dragenter')
|
||
|
.on('dragenter', function(e){
|
||
|
e.preventDefault();
|
||
|
e.stopPropagation();
|
||
|
})
|
||
|
.off('dragover')
|
||
|
.on('dragover', function(e){
|
||
|
e.preventDefault();
|
||
|
e.stopPropagation();
|
||
|
})
|
||
|
.off('drop')
|
||
|
.on('drop', function(e){
|
||
|
e.preventDefault();
|
||
|
e.stopPropagation();
|
||
|
|
||
|
var dt = e.originalEvent.dataTransfer;
|
||
|
var file_list = dt.files;
|
||
|
if(!self.multi && file_list.length > 1) {//single file upload, but dragged multiple files
|
||
|
var tmpfiles = [];
|
||
|
tmpfiles.push(file_list[0]);
|
||
|
file_list = tmpfiles;//keep only first file
|
||
|
}
|
||
|
|
||
|
|
||
|
file_list = processFiles.call(self, file_list, true);//true means files have been selected, not dropped
|
||
|
if(file_list === false) return false;
|
||
|
|
||
|
self.$element.data('ace_input_method', 'drop');
|
||
|
self.$element.data('ace_input_files', file_list);//save files data to be used later by user
|
||
|
|
||
|
self.show_file_list(file_list);
|
||
|
|
||
|
self.$element.triggerHandler('change' , [true]);//true means ace_inner_call
|
||
|
return true;
|
||
|
});
|
||
|
}
|
||
|
|
||
|
|
||
|
var handle_on_change = function() {
|
||
|
var file_list = this.element.files || [this.element.value];/** make it an array */
|
||
|
|
||
|
file_list = processFiles.call(this, file_list, false);//false means files have been selected, not dropped
|
||
|
if(file_list === false) return false;
|
||
|
|
||
|
this.$element.data('ace_input_method', 'select');
|
||
|
this.$element.data('ace_input_files', file_list);
|
||
|
|
||
|
this.show_file_list(file_list);
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
var preview_image = function(file) {
|
||
|
var self = this;
|
||
|
var $span = self.$label.find('.ace-file-name:last');//it should be out of onload, otherwise all onloads may target the same span because of delays
|
||
|
|
||
|
var deferred = new $.Deferred
|
||
|
var reader = new FileReader();
|
||
|
reader.onload = function (e) {
|
||
|
$span.prepend("<img class='middle' style='display:none;' />");
|
||
|
var img = $span.find('img:last').get(0);
|
||
|
|
||
|
$(img).one('load', function() {
|
||
|
//if image loaded successfully
|
||
|
var size = 50;
|
||
|
if(self.settings.thumbnail == 'large') size = 150;
|
||
|
else if(self.settings.thumbnail == 'fit') size = $span.width();
|
||
|
$span.addClass(size > 50 ? 'large' : '');
|
||
|
|
||
|
var thumb = get_thumbnail(img, size, file.type);
|
||
|
if(thumb == null) {
|
||
|
//if making thumbnail fails
|
||
|
$(this).remove();
|
||
|
deferred.reject({code:Ace_File_Input.error['THUMBNAIL_FAILED']});
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
var w = thumb.w, h = thumb.h;
|
||
|
if(self.settings.thumbnail == 'small') {w=h=size;};
|
||
|
$(img).css({'background-image':'url('+thumb.src+')' , width:w, height:h})
|
||
|
.data('thumb', thumb.src)
|
||
|
.attr({src:'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg=='})
|
||
|
.show()
|
||
|
|
||
|
///////////////////
|
||
|
deferred.resolve();
|
||
|
}).one('error', function() {
|
||
|
//for example when a file has image extenstion, but format is something else
|
||
|
$span.find('img').remove();
|
||
|
deferred.reject({code:Ace_File_Input.error['IMAGE_LOAD_FAILED']});
|
||
|
});
|
||
|
|
||
|
img.src = e.target.result;
|
||
|
}
|
||
|
reader.onerror = function (e) {
|
||
|
deferred.reject({code:Ace_File_Input.error['FILE_LOAD_FAILED']});
|
||
|
}
|
||
|
reader.readAsDataURL(file);
|
||
|
|
||
|
return deferred.promise();
|
||
|
}
|
||
|
|
||
|
var get_thumbnail = function(img, size, type) {
|
||
|
var w = img.width, h = img.height;
|
||
|
|
||
|
//**IE10** is not giving correct width using img.width so we use $(img).width()
|
||
|
w = w > 0 ? w : $(img).width()
|
||
|
h = h > 0 ? h : $(img).height()
|
||
|
|
||
|
if(w > size || h > size) {
|
||
|
if(w > h) {
|
||
|
h = parseInt(size/w * h);
|
||
|
w = size;
|
||
|
} else {
|
||
|
w = parseInt(size/h * w);
|
||
|
h = size;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
var dataURL
|
||
|
try {
|
||
|
var canvas = document.createElement('canvas');
|
||
|
canvas.width = w; canvas.height = h;
|
||
|
var context = canvas.getContext('2d');
|
||
|
context.drawImage(img, 0, 0, img.width, img.height, 0, 0, w, h);
|
||
|
dataURL = canvas.toDataURL(/*type == 'image/jpeg' ? type : 'image/png', 10*/)
|
||
|
} catch(e) {
|
||
|
dataURL = null;
|
||
|
}
|
||
|
if(! dataURL) return null;
|
||
|
|
||
|
|
||
|
//there was only one image that failed in firefox completely randomly! so let's double check things
|
||
|
if( !( /^data\:image\/(png|jpe?g|gif);base64,[0-9A-Za-z\+\/\=]+$/.test(dataURL)) ) dataURL = null;
|
||
|
if(! dataURL) return null;
|
||
|
|
||
|
|
||
|
return {src: dataURL, w:w, h:h};
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
var processFiles = function(file_list, dropped) {
|
||
|
var ret = checkFileList.call(this, file_list, dropped);
|
||
|
if(ret === -1) {
|
||
|
this.reset_input();
|
||
|
return false;
|
||
|
}
|
||
|
if( !ret || ret.length == 0 ) {
|
||
|
if( !this.$element.data('ace_input_files') ) this.reset_input();
|
||
|
//if nothing selected before, reset because of the newly unacceptable (ret=false||length=0) selection
|
||
|
//otherwise leave the previous selection intact?!!!
|
||
|
return false;
|
||
|
}
|
||
|
if (ret instanceof Array || (hasFileList && ret instanceof FileList)) file_list = ret;
|
||
|
|
||
|
|
||
|
ret = true;
|
||
|
if(this.settings.before_change) ret = this.settings.before_change.call(this.element, file_list, dropped);
|
||
|
if(ret === -1) {
|
||
|
this.reset_input();
|
||
|
return false;
|
||
|
}
|
||
|
if(!ret || ret.length == 0) {
|
||
|
if( !this.$element.data('ace_input_files') ) this.reset_input();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
//inside before_change you can return a modified File Array as result
|
||
|
if (ret instanceof Array || (hasFileList && ret instanceof FileList)) file_list = ret;
|
||
|
|
||
|
return file_list;
|
||
|
}
|
||
|
|
||
|
|
||
|
var getExtRegex = function(ext) {
|
||
|
if(!ext) return null;
|
||
|
if(typeof ext === 'string') ext = [ext];
|
||
|
if(ext.length == 0) return null;
|
||
|
return new RegExp("\.(?:"+ext.join('|')+")$", "i");
|
||
|
}
|
||
|
var getMimeRegex = function(mime) {
|
||
|
if(!mime) return null;
|
||
|
if(typeof mime === 'string') mime = [mime];
|
||
|
if(mime.length == 0) return null;
|
||
|
return new RegExp("^(?:"+mime.join('|').replace(/\//g, "\\/")+")$", "i");
|
||
|
}
|
||
|
var checkFileList = function(files, dropped) {
|
||
|
var allowExt = getExtRegex(this.settings.allowExt);
|
||
|
|
||
|
var denyExt = getExtRegex(this.settings.denyExt);
|
||
|
|
||
|
var allowMime = getMimeRegex(this.settings.allowMime);
|
||
|
|
||
|
var denyMime = getMimeRegex(this.settings.denyMime);
|
||
|
|
||
|
var maxSize = this.settings.maxSize || false;
|
||
|
|
||
|
if( !(allowExt || denyExt || allowMime || denyMime || maxSize) ) return true;//no checking required
|
||
|
|
||
|
|
||
|
var safe_files = [];
|
||
|
var error_list = {}
|
||
|
for(var f = 0; f < files.length; f++) {
|
||
|
var file = files[f];
|
||
|
|
||
|
//file is either a string(file name) or a File object
|
||
|
var filename = !hasFile ? file : file.name;
|
||
|
if( allowExt && !allowExt.test(filename) ) {
|
||
|
//extension not matching whitelist, so drop it
|
||
|
if(!('ext' in error_list)) error_list['ext'] = [];
|
||
|
error_list['ext'].push(filename);
|
||
|
|
||
|
continue;
|
||
|
} else if( denyExt && denyExt.test(filename) ) {
|
||
|
//extension is matching blacklist, so drop it
|
||
|
if(!('ext' in error_list)) error_list['ext'] = [];
|
||
|
error_list['ext'].push(filename);
|
||
|
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
var type;
|
||
|
if( !hasFile ) {
|
||
|
//in browsers that don't support FileReader API
|
||
|
safe_files.push(file);
|
||
|
continue;
|
||
|
}
|
||
|
else if((type = $.trim(file.type)).length > 0) {
|
||
|
//there is a mimetype for file so let's check against are rules
|
||
|
if( allowMime && !allowMime.test(type) ) {
|
||
|
//mimeType is not matching whitelist, so drop it
|
||
|
if(!('mime' in error_list)) error_list['mime'] = [];
|
||
|
error_list['mime'].push(filename);
|
||
|
continue;
|
||
|
}
|
||
|
else if( denyMime && denyMime.test(type) ) {
|
||
|
//mimeType is matching blacklist, so drop it
|
||
|
if(!('mime' in error_list)) error_list['mime'] = [];
|
||
|
error_list['mime'].push(filename);
|
||
|
continue;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if( maxSize && file.size > maxSize ) {
|
||
|
//file size is not acceptable
|
||
|
if(!('size' in error_list)) error_list['size'] = [];
|
||
|
error_list['size'].push(filename);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
safe_files.push(file)
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
if(safe_files.length == files.length) return files;//return original file list if all are valid
|
||
|
|
||
|
/////////
|
||
|
var error_count = {'ext': 0, 'mime': 0, 'size': 0}
|
||
|
if( 'ext' in error_list ) error_count['ext'] = error_list['ext'].length;
|
||
|
if( 'mime' in error_list ) error_count['mime'] = error_list['mime'].length;
|
||
|
if( 'size' in error_list ) error_count['size'] = error_list['size'].length;
|
||
|
|
||
|
var event
|
||
|
this.$element.trigger(
|
||
|
event = new $.Event('file.error.ace'),
|
||
|
{
|
||
|
'file_count': files.length,
|
||
|
'invalid_count' : files.length - safe_files.length,
|
||
|
'error_list' : error_list,
|
||
|
'error_count' : error_count,
|
||
|
'dropped': dropped
|
||
|
}
|
||
|
);
|
||
|
if ( event.isDefaultPrevented() ) return -1;//it will reset input
|
||
|
//////////
|
||
|
|
||
|
return safe_files;//return safe_files
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
///////////////////////////////////////////
|
||
|
$.fn.ace_file_input = function (option,value) {
|
||
|
var retval;
|
||
|
|
||
|
var $set = this.each(function () {
|
||
|
var $this = $(this);
|
||
|
var data = $this.data('ace_file_input');
|
||
|
var options = typeof option === 'object' && option;
|
||
|
|
||
|
if (!data) $this.data('ace_file_input', (data = new Ace_File_Input(this, options)));
|
||
|
if (typeof option === 'string') retval = data[option](value);
|
||
|
});
|
||
|
|
||
|
return (retval === undefined) ? $set : retval;
|
||
|
};
|
||
|
|
||
|
|
||
|
$.fn.ace_file_input.defaults = {
|
||
|
style: false,
|
||
|
no_file: 'No File ...',
|
||
|
no_icon: 'fa fa-upload',
|
||
|
btn_choose: 'Choose',
|
||
|
btn_change: 'Change',
|
||
|
icon_remove: 'fa fa-times',
|
||
|
droppable: false,
|
||
|
thumbnail: false,//large, fit, small
|
||
|
|
||
|
allowExt: null,
|
||
|
denyExt: null,
|
||
|
allowMime: null,
|
||
|
denyMime: null,
|
||
|
maxSize: false,
|
||
|
|
||
|
//callbacks
|
||
|
before_change: null,
|
||
|
before_remove: null,
|
||
|
preview_error: null
|
||
|
}
|
||
|
|
||
|
|
||
|
})(window.jQuery);
|