2 ### jQuery Multiple File Upload Plugin v1.31 - 2009-01-17 ###
3 * Home: http://www.fyneworks.com/jquery/multiple-file-upload/
4 * Code: http://code.google.com/p/jquery-multifile-plugin/
6 * Dual licensed under the MIT and GPL licenses:
7 * http://www.opensource.org/licenses/mit-license.php
8 * http://www.gnu.org/licenses/gpl.html
12 /*# AVOID COLLISIONS #*/
13 ;if(window
.jQuery
) (function($){
14 /*# AVOID COLLISIONS #*/
16 // extend jQuery - $.MultiFile hook
18 MultiFile: function( o
/* Object */ ){
19 //return $("INPUT[type='file'].multi").MultiFile(o);
20 return $("input:file.multi").MultiFile(o
);
26 // extend $.MultiFile - default options
27 $.extend($.MultiFile
, {
30 // error handling function
34 message
: s
.replace(/\n/gi,'<br/>'),
36 border
:'none', padding
:'15px', size
:'12.0pt',
37 backgroundColor
:'#900', color
:'#fff',
38 opacity
:'.8','-webkit-border-radius': '10px','-moz-border-radius': '10px'
41 window
.setTimeout($.unblockUI
, 2000);
47 // namePattern: $name/$id (from master element), $i (slave count), $g (group count)
49 // STRING: collection lets you show messages in different languages
52 denied
:'You cannot select a $ext file.\nTry again...',
54 selected
:'File selected: $file',
55 duplicate
:'This file has already been selected:\n$file'
62 // extend $.MultiFile - global methods
63 $.extend($.MultiFile
, {
67 * This utility makes it easy to disable all 'empty' file elements in the document before submitting a form.
68 * It marks the affected elements so they can be easily re-enabled after the form submission or validation.
70 * Returns a jQuery collection of all affected elements.
74 * @cat Plugins/Multifile
75 * @author Diego A. (http://www.fyneworks.com/)
77 * @example $.MultiFile.disableEmpty();
78 * @param String class (optional) A string specifying a class to be applied to all affected elements - Default: 'mfD'.
80 disableEmpty: function(klass
){
82 $('input:file').each(function(){ if($(this).val()=='') o
[o
.length
] = this; });
83 return $(o
).each(function(){ this.disabled
= true }).addClass(klass
|| 'mfD');
88 * This method re-enables 'empty' file elements that were disabled (and marked) with the $.MultiFile.disableEmpty method.
90 * Returns a jQuery collection of all affected elements.
94 * @cat Plugins/Multifile
95 * @author Diego A. (http://www.fyneworks.com/)
97 * @example $.MultiFile.reEnableEmpty();
98 * @param String klass (optional) A string specifying the class that was used to mark affected elements - Default: 'mfD'.
100 reEnableEmpty: function(klass
){
101 klass
= klass
|| 'mfD';
102 return $('input:file.'+klass
).removeClass(klass
).each(function(){ this.disabled
= false });
107 * This method will intercept other jQuery plugins and disable empty file input elements prior to form submission
110 * @cat Plugins/Multifile
111 * @author Diego A. (http://www.fyneworks.com/)
113 * @example $.MultiFile.intercept();
114 * @param Array methods (optional) Array of method names to be intercepted
116 autoIntercept
: [ 'submit', 'ajaxSubmit', 'validate' /* array of methods to intercept */ ],
118 intercept: function(methods
, context
, args
){
119 var method
, value
; args
= args
|| [];
120 if(args
.constructor.toString().indexOf("Array")<0) args
= [ args
];
121 if(typeof(methods
)=='function'){
122 $.MultiFile
.disableEmpty();
123 value
= methods
.apply(context
|| window
, args
);
124 $.MultiFile
.reEnableEmpty();
127 if(methods
.constructor.toString().indexOf("Array")<0) methods
= [methods
];
128 for(var i
=0;i
<methods
.length
;i
++){
129 method
= methods
[i
]+''; // make sure that we have a STRING
130 if(method
) (function(method
){ // make sure that method is ISOLATED for the interception
131 $.MultiFile
.intercepted
[method
] = $.fn
[method
] || function(){};
132 $.fn
[method
] = function(){
133 $.MultiFile
.disableEmpty();
134 value
= $.MultiFile
.intercepted
[method
].apply(this, arguments
);
135 $.MultiFile
.reEnableEmpty();
138 })(method
); // MAKE SURE THAT method IS ISOLATED for the interception
145 // extend jQuery function library
148 // Use this function to clear values of file inputs
149 // But this doesn't always work: $(element).val('').attr('value', '')[0].value = '';
150 reset: function(){ return this.each(function(){ try{ this.reset(); }catch(e
){} }); },
152 // MultiFile function
153 MultiFile: function( options
/* Object */ ){
155 //### http://plugins.jquery.com/node/1363
156 // utility method to integrate this plugin with others...
157 if($.MultiFile
.autoIntercept
){
158 $.MultiFile
.intercept( $.MultiFile
.autoIntercept
/* array of methods to intercept */ );
159 $.MultiFile
.autoIntercept
= null; /* only run this once */
164 // Bind to each element in current jQuery object
165 return $(this).each(function(group_count
){
166 if(this._MultiFile
) return; this._MultiFile
= true;
168 // BUG 1251 FIX: http://plugins.jquery.com/project/comments/add/1251
169 // variable group_count would repeat itself on multiple calls to the plugin.
170 // this would cause a conflict with multiple elements
171 // changes scope of variable to global so id will be unique over n calls
172 window
.MultiFile
= (window
.MultiFile
|| 0) + 1;
173 group_count
= window
.MultiFile
;
175 // Copy parent attributes - Thanks to Jonas Wagner
176 // we will use this one to create new input elements
177 var MF
= {e
:this, E
:$(this), clone
:$(this).clone()};
181 //# USE CONFIGURATION
182 if(typeof options
=='number') options
= {max
:options
};
183 if(typeof options
=='string') options
= {accept
:options
};
187 ($.meta
? MF
.E
.data()/*NEW metadata plugin*/ :
188 ($.metadata
? MF
.E
.metadata()/*OLD metadata plugin*/ :
189 null/*metadata plugin not available*/)) || {}
191 // limit number of files that can be selected?
192 if(!(o
.max
>0) /*IsNull(MF.max)*/){
193 o
.max
= MF
.E
.attr('maxlength');
194 if(!(o
.max
>0) /*IsNull(MF.max)*/){
195 o
.max
= (String(MF
.e
.className
.match(/\b(max|limit)\-([0-9]+)\b/gi) || ['']).match(/[0-9]+/gi) || [''])[0];
196 if(!(o
.max
>0)) o
.max
= -1;
197 else o
.max
= String(o
.max
).match(/[0-9]+/gi)[0];
200 o
.max
= new Number(o
.max
);
202 o
.accept
= o
.accept
|| MF
.E
.attr('accept') || '';
204 o
.accept
= (MF
.e
.className
.match(/\b(accept\-[\w\|]+)\b/gi)) || '';
205 o
.accept
= new String(o
.accept
).replace(/^(accept|ext)\-/i,'');
210 // APPLY CONFIGURATION
211 $.extend(MF
, o
|| {});
212 MF
.STRING
= $.extend({},$.MultiFile
.options
.STRING
,MF
.STRING
);
216 //#########################################
217 // PRIVATE PROPERTIES/METHODS
219 n
: 0, // How many elements are currently selected?
220 slaves
: [], files
: [],
221 instanceKey
: MF
.e
.id
|| 'MultiFile'+String(group_count
), // Instance Key?
222 generateID: function(z
){ return MF
.instanceKey
+ (z
>0 ?'_F'+String(z
):''); },
223 trigger: function(event
, element
){
224 var handler
= MF
[event
], value
= $(element
).attr('value');
226 var returnValue
= handler(element
, value
, MF
);
227 if( returnValue
!=null ) return returnValue
;
235 // Setup dynamic regular expression for extension validation
236 // - thanks to John-Paul Bader: http://smyck.de/2006/08/11/javascript-dynamic-regular-expresions/
237 if(String(MF
.accept
).length
>1){
238 MF
.rxAccept
= new RegExp('\\.('+(MF
.accept
?MF
.accept
:'')+')$','gi');
243 // Create wrapper to hold our file list
244 MF
.wrapID
= MF
.instanceKey
+'_wrap'; // Wrapper ID?
245 MF
.E
.wrap('<div id="'+MF
.wrapID
+'"></div>');
246 MF
.wrapper
= $('#'+MF
.wrapID
+'');
250 // MF MUST have a name - default: file1[], file2[], file3[]
251 MF
.e
.name
= MF
.e
.name
|| 'file'+ group_count
+'[]';
256 // Create a wrapper for the list
257 // * OPERA BUG: NO_MODIFICATION_ALLOWED_ERR ('list' is a read-only property)
258 // this change allows us to keep the files in the order they were selected
259 MF
.wrapper
.append( '<span id="'+MF
.wrapID
+'_list"></span>' );
260 MF
.list
= $('#'+MF
.wrapID
+'_list');
262 MF
.list
= $(MF
.list
);
266 // Bind a new element
267 MF
.addSlave = function( slave
, slave_count
){
268 // Keep track of how many elements have been displayed
270 // Add reference to master element
273 slave
.i
= slave_count
;
275 // BUG FIX: http://plugins.jquery.com/node/1495
276 // Clear identifying properties from clones
277 if(slave
.i
>0) slave
.id
= slave
.name
= null;
279 // Define element's ID and name (upload components need this!)
280 slave
.id
= slave
.id
|| MF
.generateID(slave
.i
);
282 //slave.name = (slave.name || MF.E.attr('name') || 'file');// + (slave.i>0?slave.i:''); // same name as master element
283 // 2008-Apr-29: New customizable naming convention (see url below)
284 // http://groups.google.com/group/jquery-dev/browse_frm/thread/765c73e41b34f924#
285 slave
.name
= String(MF
.namePattern
286 /*master name*/.replace(/\$name
/gi
,MF
.E
.attr('name'))
287 /*master id */.replace(/\$id
/gi
, MF
.E
.attr('id'))
288 /*group count*/.replace(/\$g
/gi
, (group_count
>0?group_count
:''))
289 /*slave count*/.replace(/\$i
/gi
, (slave_count
>0?slave_count
:''))
293 $(slave
).val('').attr('value','')[0].value
= '';
295 // If we've reached maximum number, disable input slave
296 if( (MF
.max
> 0) && ((MF
.n
-1) > (MF
.max
)) )//{ // MF.n Starts at 1, so subtract 1 to find true count
297 slave
.disabled
= true;
300 // Remember most recent slave
301 MF
.current
= MF
.slaves
[slave
.i
] = slave
;
303 // now let's use jQuery
306 // Triggered when a file is selected
307 $(slave
).change(function(){
309 // Lose focus to stop IE7 firing onchange again
312 //# Trigger Event! onFileSelect
313 if(!MF
.trigger('onFileSelect', this, MF
)) return false;
316 //# Retrive value of selected file from element
317 var ERROR
= '', v
= String(this.value
|| ''/*.attr('value)*/);
320 if(MF
.accept
&& v
&& !v
.match(MF
.rxAccept
))//{
321 ERROR
= MF
.STRING
.denied
.replace('$ext', String(v
.match(/\.\w{1,4}$/gi)));
325 // Disallow duplicates
326 for(var f
in MF
.slaves
)//{
327 if(MF
.slaves
[f
] && MF
.slaves
[f
]!=this)//{
328 //console.log(MF.slaves[f],MF.slaves[f].value);
329 if(MF
.slaves
[f
].value
==v
)//{
330 ERROR
= MF
.STRING
.duplicate
.replace('$file', v
.match(/[^\/\\]+$/gi));
335 // Create a new file input element
336 //var newEle = $('<input name="'+(MF.E.attr('name') || '')+'" type="file"/>');
337 var newEle
= $(MF
.clone
).clone();// Copy parent attributes - Thanks to Jonas Wagner
338 //# Let's remember which input we've generated so
339 // we can disable the empty ones before submission
340 // See: http://plugins.jquery.com/node/1495
341 newEle
.addClass('MultiFile');
348 // Clear element value (DOES NOT WORK in some browsers)
349 //slave.reset().val('').attr('value', '')[0].value = '';
351 // 2007-06-24: BUG FIX - Thanks to Adrian Wróbel <adrian [dot] wrobel [at] gmail.com>
352 // Ditch the trouble maker and add a fresh new element
354 MF
.addSlave(newEle
[0], this.i
);
355 MF
.list
.before(newEle
);//slave.parent().prepend(newEle);
360 // Hide this element (NB: display:none is evil!)
361 $(this).css({ position
:'absolute', top
: '-3000px' });
363 // Add new element to the form
364 MF
.list
.before(newEle
);//.append(newEle);
365 //MF.wrapper.prepend(newEle);//.append(newEle);
368 MF
.addToList( this );
370 // Bind functionality
371 MF
.addSlave( newEle
[0], this.i
+1 );
373 //# Trigger Event! afterFileSelect
374 if(!MF
.trigger('afterFileSelect', this, MF
)) return false;
377 }); // slave.change()
380 // Bind a new element
384 // Add a new file to the list
385 MF
.addToList = function( slave
){
387 //# Trigger Event! onFileAppend
388 if(!MF
.trigger('onFileAppend', slave
, MF
)) return false;
391 // Create label elements
393 r
= $('<div></div>'),
394 v
= String(slave
.value
|| ''/*.attr('value)*/),
395 a
= $('<span class="file" title="'+MF
.STRING
.selected
.replace('$file', v
)+'">'+MF
.STRING
.file
.replace('$file', v
.match(/[^\/\\]+$/gi)[0])+'</span>'),
396 b
= $('<a href="#'+MF
.wrapID
+'">'+MF
.STRING
.remove
+'</a>');
400 r
.append(b
, ' ', a
)//.prepend(slave.i+': ')
405 //# Trigger Event! onFileRemove
406 if(!MF
.trigger('onFileRemove', slave
, MF
)) return false;
410 MF
.current
.disabled
= false;
412 // Remove element, remove label, point to current
413 MF
.slaves
[slave
.i
] = null;
415 $(this).parent().remove();
417 // Show most current element again (move into view) and clear selection
418 $(MF
.current
).css({ position
:'', top
: '' });
419 $(MF
.current
).reset().val('').attr('value', '')[0].value
= '';
421 //# Trigger Event! afterFileRemove
422 if(!MF
.trigger('afterFileRemove', slave
, MF
)) return false;
428 //# Trigger Event! afterFileAppend
429 if(!MF
.trigger('afterFileAppend', slave
, MF
)) return false;
433 // Add element to selected files list
437 // Bind functionality to the first element
438 if(!MF
.MF
) MF
.addSlave(MF
.e
, 0);
440 // Increment control count
441 //MF.I++; // using window.MultiFile
448 // MultiFile function
451 // extend jQuery function library
456 ### Default implementation ###
457 The plugin will attach itself to file inputs
458 with the class 'multi' when the page loads
460 $(function(){ $.MultiFile() });
464 /*# AVOID COLLISIONS #*/
466 /*# AVOID COLLISIONS #*/