2 ### jQuery Multiple File Upload Plugin v1.48 - 2013-02-19 ###
3 * Home: http://www.fyneworks.com/jquery/multiple-file-upload/
4 * Code: http://code.google.com/p/jquery-multifile-plugin/
6 * Licensed under http://en.wikipedia.org/wiki/MIT_License
10 /*# AVOID COLLISIONS #*/
11 ;if(window
.jQuery
) (function($){
12 /*# AVOID COLLISIONS #*/
14 // plugin initialization
15 $.fn
.MultiFile = function(options
){
16 if(this.length
==0) return this; // quick fail
19 if(typeof arguments
[0]=='string'){
20 // Perform API methods on individual elements
23 return this.each(function(){
24 $.fn
.MultiFile
.apply($(this), args
);
27 // Invoke API method handler
28 $.fn
.MultiFile
[arguments
[0]].apply(this, $.makeArray(arguments
).slice(1) || []);
33 // Initialize options for this call
34 var options
= $.extend(
36 $.fn
.MultiFile
.options
/* default options */,
37 options
|| {} /* just-in-time options */
40 // Empty Element Fix!!!
41 // this code will automatically intercept native form submissions
42 // and disable empty file elements
44 .not('MultiFile-intercepted')
45 .addClass('MultiFile-intercepted')
46 .submit($.fn
.MultiFile
.disableEmpty
);
48 //### http://plugins.jquery.com/node/1363
49 // utility method to integrate this plugin with others...
50 if($.fn
.MultiFile
.options
.autoIntercept
){
51 $.fn
.MultiFile
.intercept( $.fn
.MultiFile
.options
.autoIntercept
/* array of methods to intercept */ );
52 $.fn
.MultiFile
.options
.autoIntercept
= null; /* only run this once */
55 // loop through each matched element
57 .not('.MultiFile-applied')
58 .addClass('MultiFile-applied')
60 //#####################################################################
61 // MAIN PLUGIN FUNCTIONALITY - START
62 //#####################################################################
64 // BUG 1251 FIX: http://plugins.jquery.com/project/comments/add/1251
65 // variable group_count would repeat itself on multiple calls to the plugin.
66 // this would cause a conflict with multiple elements
67 // changes scope of variable to global so id will be unique over n calls
68 window
.MultiFile
= (window
.MultiFile
|| 0) + 1;
69 var group_count
= window
.MultiFile
;
71 // Copy parent attributes - Thanks to Jonas Wagner
72 // we will use this one to create new input elements
73 var MultiFile
= {e
:this, E
:$(this), clone
:$(this).clone()};
78 if(typeof options
=='number') options
= {max
:options
};
80 $.fn
.MultiFile
.options
,
82 ($.metadata
? MultiFile
.E
.metadata(): ($.meta
?MultiFile
.E
.data():null)) || {}, /* metadata options */
85 // limit number of files that can be selected?
86 if(!(o
.max
>0) /*IsNull(MultiFile.max)*/){
87 o
.max
= MultiFile
.E
.attr('maxlength');
89 if(!(o
.max
>0) /*IsNull(MultiFile.max)*/){
90 o
.max
= (String(MultiFile
.e
.className
.match(/\b(max|limit)\-([0-9]+)\b/gi) || ['']).match(/[0-9]+/gi) || [''])[0];
91 if(!(o
.max
>0)) o
.max
= -1;
92 else o
.max
= String(o
.max
).match(/[0-9]+/gi)[0];
94 o
.max
= new Number(o
.max
);
96 o
.accept
= o
.accept
|| MultiFile
.E
.attr('accept') || '';
98 o
.accept
= (MultiFile
.e
.className
.match(/\b(accept\-[\w\|]+)\b/gi)) || '';
99 o
.accept
= new String(o
.accept
).replace(/^(accept|ext)\-/i,'');
104 // APPLY CONFIGURATION
105 $.extend(MultiFile
, o
|| {});
106 MultiFile
.STRING
= $.extend({},$.fn
.MultiFile
.options
.STRING
,MultiFile
.STRING
);
110 //#########################################
111 // PRIVATE PROPERTIES/METHODS
112 $.extend(MultiFile
, {
113 n
: 0, // How many elements are currently selected?
114 slaves
: [], files
: [],
115 instanceKey
: MultiFile
.e
.id
|| 'MultiFile'+String(group_count
), // Instance Key?
116 generateID: function(z
){ return MultiFile
.instanceKey
+ (z
>0 ?'_F'+String(z
):''); },
117 trigger: function(event
, element
){
118 var handler
= MultiFile
[event
], value
= $(element
).attr('value');
120 var returnValue
= handler(element
, value
, MultiFile
);
121 if( returnValue
!=null ) return returnValue
;
129 // Setup dynamic regular expression for extension validation
130 // - thanks to John-Paul Bader: http://smyck.de/2006/08/11/javascript-dynamic-regular-expresions/
131 if(String(MultiFile
.accept
).length
>1){
132 MultiFile
.accept
= MultiFile
.accept
.replace(/\W+/g,'|').replace(/^\W|\W$/g,'');
133 MultiFile
.rxAccept
= new RegExp('\\.('+(MultiFile
.accept
?MultiFile
.accept
:'')+')$','gi');
138 // Create wrapper to hold our file list
139 MultiFile
.wrapID
= MultiFile
.instanceKey
+'_wrap'; // Wrapper ID?
140 MultiFile
.E
.wrap('<div class="MultiFile-wrap" id="'+MultiFile
.wrapID
+'"></div>');
141 MultiFile
.wrapper
= $('#'+MultiFile
.wrapID
+'');
145 // MultiFile MUST have a name - default: file1[], file2[], file3[]
146 MultiFile
.e
.name
= MultiFile
.e
.name
|| 'file'+ group_count
+'[]';
151 // Create a wrapper for the list
152 // * OPERA BUG: NO_MODIFICATION_ALLOWED_ERR ('list' is a read-only property)
153 // this change allows us to keep the files in the order they were selected
154 MultiFile
.wrapper
.append( '<div class="MultiFile-list" id="'+MultiFile
.wrapID
+'_list"></div>' );
155 MultiFile
.list
= $('#'+MultiFile
.wrapID
+'_list');
157 MultiFile
.list
= $(MultiFile
.list
);
161 // Bind a new element
162 MultiFile
.addSlave = function( slave
, slave_count
){
163 //if(window.console) console.log('MultiFile.addSlave',slave_count);
165 // Keep track of how many elements have been displayed
167 // Add reference to master element
168 slave
.MultiFile
= MultiFile
;
170 // BUG FIX: http://plugins.jquery.com/node/1495
171 // Clear identifying properties from clones
172 if(slave_count
>0) slave
.id
= slave
.name
= '';
174 // Define element's ID and name (upload components need this!)
175 //slave.id = slave.id || MultiFile.generateID(slave_count);
176 if(slave_count
>0) slave
.id
= MultiFile
.generateID(slave_count
);
177 //FIX for: http://code.google.com/p/jquery-multifile-plugin/issues/detail?id=23
179 // 2008-Apr-29: New customizable naming convention (see url below)
180 // http://groups.google.com/group/jquery-dev/browse_frm/thread/765c73e41b34f924#
181 slave
.name
= String(MultiFile
.namePattern
182 /*master name*/.replace(/\$name
/gi
,$(MultiFile
.clone
).attr('name'))
183 /*master id */.replace(/\$id
/gi
, $(MultiFile
.clone
).attr('id'))
184 /*group count*/.replace(/\$g
/gi, group_count)//(group_count
>0?group_count
:''))
185 /*slave count*/.replace(/\$i
/gi, slave_count)//(slave_count
>0?slave_count
:''))
188 // If we've reached maximum number, disable input slave
189 if( (MultiFile
.max
> 0) && ((MultiFile
.n
-1) > (MultiFile
.max
)) )//{ // MultiFile.n Starts at 1, so subtract 1 to find true count
190 slave
.disabled
= true;
193 // Remember most recent slave
194 MultiFile
.current
= MultiFile
.slaves
[slave_count
] = slave
;
196 // We'll use jQuery from now on
200 slave
.val('').attr('value','')[0].value
= '';
202 // Stop plugin initializing on slaves
203 slave
.addClass('MultiFile-applied');
205 // Triggered when a file is selected
206 slave
.change(function(){
207 //if(window.console) console.log('MultiFile.slave.change',slave_count);
209 // Lose focus to stop IE7 firing onchange again
212 //# Trigger Event! onFileSelect
213 if(!MultiFile
.trigger('onFileSelect', this, MultiFile
)) return false;
216 //# Retrive value of selected file from element
217 var ERROR
= '', v
= String(this.value
|| ''/*.attr('value)*/);
220 if(MultiFile
.accept
&& v
&& !v
.match(MultiFile
.rxAccept
))//{
221 ERROR
= MultiFile
.STRING
.denied
.replace('$ext', String(v
.match(/\.\w{1,4}$/gi)));
225 // Disallow duplicates
226 for(var f
in MultiFile
.slaves
)//{
227 if(MultiFile
.slaves
[f
] && MultiFile
.slaves
[f
]!=this)//{
228 //console.log(MultiFile.slaves[f],MultiFile.slaves[f].value);
229 if(MultiFile
.slaves
[f
].value
==v
)//{
230 ERROR
= MultiFile
.STRING
.duplicate
.replace('$file', v
.match(/[^\/\\]+$/gi));
235 // Create a new file input element
236 var newEle
= $(MultiFile
.clone
).clone();// Copy parent attributes - Thanks to Jonas Wagner
237 //# Let's remember which input we've generated so
238 // we can disable the empty ones before submission
239 // See: http://plugins.jquery.com/node/1495
240 newEle
.addClass('MultiFile');
245 MultiFile
.error(ERROR
);
247 // 2007-06-24: BUG FIX - Thanks to Adrian Wróbel <adrian [dot] wrobel [at] gmail.com>
248 // Ditch the trouble maker and add a fresh new element
250 MultiFile
.addSlave(newEle
[0], slave_count
);
251 slave
.parent().prepend(newEle
);
256 // Hide this element (NB: display:none is evil!)
257 $(this).css({ position
:'absolute', top
: '-3000px' });
259 // Add new element to the form
263 MultiFile
.addToList( this, slave_count
);
265 // Bind functionality
266 MultiFile
.addSlave( newEle
[0], slave_count
+1 );
268 //# Trigger Event! afterFileSelect
269 if(!MultiFile
.trigger('afterFileSelect', this, MultiFile
)) return false;
272 }); // slave.change()
274 // Save control to element
275 $(slave
).data('MultiFile', MultiFile
);
277 };// MultiFile.addSlave
278 // Bind a new element
282 // Add a new file to the list
283 MultiFile
.addToList = function( slave
, slave_count
){
284 //if(window.console) console.log('MultiFile.addToList',slave_count);
286 //# Trigger Event! onFileAppend
287 if(!MultiFile
.trigger('onFileAppend', slave
, MultiFile
)) return false;
290 // Create label elements
292 r
= $('<div class="MultiFile-label"></div>'),
293 v
= String(slave
.value
|| ''/*.attr('value)*/),
294 a
= $('<span class="MultiFile-title" title="'+MultiFile
.STRING
.selected
.replace('$file', v
)+'">'+MultiFile
.STRING
.file
.replace('$file', v
.match(/[^\/\\]+$/gi)[0])+'</span>'),
295 b
= $('<a class="MultiFile-remove" href="#'+MultiFile
.wrapID
+'">'+MultiFile
.STRING
.remove
+'</a>');
298 MultiFile
.list
.append(
305 //# Trigger Event! onFileRemove
306 if(!MultiFile
.trigger('onFileRemove', slave
, MultiFile
)) return false;
310 MultiFile
.current
.disabled
= false;
312 // Remove element, remove label, point to current
313 MultiFile
.slaves
[slave_count
] = null;
315 $(this).parent().remove();
317 // Show most current element again (move into view) and clear selection
318 $(MultiFile
.current
).css({ position
:'', top
: '' });
319 $(MultiFile
.current
).reset().val('').attr('value', '')[0].value
= '';
321 //# Trigger Event! afterFileRemove
322 if(!MultiFile
.trigger('afterFileRemove', slave
, MultiFile
)) return false;
328 //# Trigger Event! afterFileAppend
329 if(!MultiFile
.trigger('afterFileAppend', slave
, MultiFile
)) return false;
332 }; // MultiFile.addToList
333 // Add element to selected files list
337 // Bind functionality to the first element
338 if(!MultiFile
.MultiFile
) MultiFile
.addSlave(MultiFile
.e
, 0);
340 // Increment control count
341 //MultiFile.I++; // using window.MultiFile
344 // Save control to element
345 MultiFile
.E
.data('MultiFile', MultiFile
);
348 //#####################################################################
349 // MAIN PLUGIN FUNCTIONALITY - END
350 //#####################################################################
354 /*--------------------------------------------------------*/
357 ### Core functionality and API ###
359 $.extend($.fn
.MultiFile
, {
361 * This method removes all selected files
363 * Returns a jQuery collection of all affected elements.
367 * @cat Plugins/MultiFile
368 * @author Diego A. (http://www.fyneworks.com/)
370 * @example $.fn.MultiFile.reset();
373 var settings
= $(this).data('MultiFile');
374 //if(settings) settings.wrapper.find('a.MultiFile-remove').click();
375 if(settings
) settings
.list
.find('a.MultiFile-remove').click();
381 * This utility makes it easy to disable all 'empty' file elements in the document before submitting a form.
382 * It marks the affected elements so they can be easily re-enabled after the form submission or validation.
384 * Returns a jQuery collection of all affected elements.
388 * @cat Plugins/MultiFile
389 * @author Diego A. (http://www.fyneworks.com/)
391 * @example $.fn.MultiFile.disableEmpty();
392 * @param String class (optional) A string specifying a class to be applied to all affected elements - Default: 'mfD'.
394 disableEmpty: function(klass
){ klass
= (typeof(klass
)=='string'?klass
:'')||'mfD';
396 $('input:file.MultiFile').each(function(){ if($(this).val()=='') o
[o
.length
] = this; });
397 return $(o
).each(function(){ this.disabled
= true }).addClass(klass
);
402 * This method re-enables 'empty' file elements that were disabled (and marked) with the $.fn.MultiFile.disableEmpty method.
404 * Returns a jQuery collection of all affected elements.
406 * @name reEnableEmpty
408 * @cat Plugins/MultiFile
409 * @author Diego A. (http://www.fyneworks.com/)
411 * @example $.fn.MultiFile.reEnableEmpty();
412 * @param String klass (optional) A string specifying the class that was used to mark affected elements - Default: 'mfD'.
414 reEnableEmpty: function(klass
){ klass
= (typeof(klass
)=='string'?klass
:'')||'mfD';
415 return $('input:file.'+klass
).removeClass(klass
).each(function(){ this.disabled
= false });
420 * This method will intercept other jQuery plugins and disable empty file input elements prior to form submission
424 * @cat Plugins/MultiFile
425 * @author Diego A. (http://www.fyneworks.com/)
427 * @example $.fn.MultiFile.intercept();
428 * @param Array methods (optional) Array of method names to be intercepted
431 intercept: function(methods
, context
, args
){
432 var method
, value
; args
= args
|| [];
433 if(args
.constructor.toString().indexOf("Array")<0) args
= [ args
];
434 if(typeof(methods
)=='function'){
435 $.fn
.MultiFile
.disableEmpty();
436 value
= methods
.apply(context
|| window
, args
);
437 //SEE-http://code.google.com/p/jquery-multifile-plugin/issues/detail?id=27
438 setTimeout(function(){ $.fn
.MultiFile
.reEnableEmpty() },1000);
441 if(methods
.constructor.toString().indexOf("Array")<0) methods
= [methods
];
442 for(var i
=0;i
<methods
.length
;i
++){
443 method
= methods
[i
]+''; // make sure that we have a STRING
444 if(method
) (function(method
){ // make sure that method is ISOLATED for the interception
445 $.fn
.MultiFile
.intercepted
[method
] = $.fn
[method
] || function(){};
446 $.fn
[method
] = function(){
447 $.fn
.MultiFile
.disableEmpty();
448 value
= $.fn
.MultiFile
.intercepted
[method
].apply(this, arguments
);
449 //SEE http://code.google.com/p/jquery-multifile-plugin/issues/detail?id=27
450 setTimeout(function(){ $.fn
.MultiFile
.reEnableEmpty() },1000);
453 })(method
); // MAKE SURE THAT method IS ISOLATED for the interception
455 } // $.fn.MultiFile.intercept
459 /*--------------------------------------------------------*/
462 ### Default Settings ###
463 eg.: You can override default control like this:
464 $.fn.MultiFile.options.accept = 'gif|jpg';
466 $.fn
.MultiFile
.options
= { //$.extend($.fn.MultiFile, { options: {
467 accept
: '', // accepted file extensions
468 max
: -1, // maximum number of selectable files
470 // name to use for newly created elements
471 namePattern
: '$name', // same name by default (which creates an array)
472 /*master name*/ // use $name
473 /*master id */ // use $id
474 /*group count*/ // use $g
475 /*slave count*/ // use $i
476 /*other */ // use any combination of he above, eg.: $name_file$i
478 // STRING: collection lets you show messages in different languages
481 denied
:'You cannot select a $ext file.\nTry again...',
483 selected
:'File selected: $file',
484 duplicate
:'This file has already been selected:\n$file'
487 // name of methods that should be automcatically intercepted so the plugin can disable
488 // extra file elements that are empty before execution and automatically re-enable them afterwards
489 autoIntercept
: [ 'submit', 'ajaxSubmit', 'ajaxForm', 'validate', 'valid' /* array of methods to intercept */ ],
491 // error handling function
494 ERROR! blockUI is not currently working in IE
497 message: s.replace(/\n/gi,'<br/>'),
499 border:'none', padding:'15px', size:'12.0pt',
500 backgroundColor:'#900', color:'#fff',
501 opacity:'.8','-webkit-border-radius': '10px','-moz-border-radius': '10px'
504 window.setTimeout($.unblockUI, 2000);
506 else//{// save a byte!
513 /*--------------------------------------------------------*/
516 ### Additional Methods ###
517 Required functionality outside the plugin's scope
520 // Native input reset method - because this alone doesn't always work: $(element).val('').attr('value', '')[0].value = '';
521 $.fn
.reset = function(){ return this.each(function(){ try{ this.reset(); }catch(e
){} }); };
523 /*--------------------------------------------------------*/
526 ### Default implementation ###
527 The plugin will attach itself to file inputs
528 with the class 'multi' when the page loads
531 //$("input:file.multi").MultiFile();
532 $("input[type=file].multi").MultiFile();
537 /*# AVOID COLLISIONS #*/
539 /*# AVOID COLLISIONS #*/