통합검색

Javascript

input 파일 다중 업로드 (MultiFile plugin)

  • 2024.12.20 09:36:59
[!]html[/!]
 
<input type="file" name="attach" id="attach">
<div class="attaedbox"></div>
<script type="text/javascript">
$(':file[name=attach]').MultiFile({
    'max' : 5,
    'accept' : 'gif|jpg|jpeg|bmp|png|pdf',
    'maxfile' : 10240, //각 파일 최대 업로드 크기 (kb)
    'list' : $('.attaedbox'), //파일목록을 출력할 요소 지정가능
    'STRING' : { // error문구를 원하는대로 수정
        'remove' : "<a href=\"#\" class=\"remove\">제거</a>",
        'duplicate' : "$file 은 이미 선택된 파일입니다.",
        'denied' : "$ext 는(은) 업로드 할수 없는 파일확장자입니다.",
        'selected' :'$file 을 선택했습니다.',
        'toomuch' : "업로드할 수 있는 최대크기를 초과하였습니다.($size)",
        'toomany' : "업로드할 수 있는 최대 개수는 $max개 입니다.",
        'toobig' : "$file 은 크기가 매우 큽니다. (max $size)"
    }
});
</script>




[!]css[/!]
 
.MultiFile-wrap {min-height: 52px;display: flex;align-items: center;}
.MultiFile-label + .MultiFile-label {margin-top: 2px;}
.MultiFile-label {display: flex;align-items: center;}
.MultiFile-label > .remove {margin-right: 10px;width: 40px;height: 25px;line-height: 23px;box-sizing: border-box;border: 1px solid #ccc;border-radius: 3px;text-align: center;font-size: 12px;color: #333;background: #fff;}




[!]jquery[/!]​​​​​​​
 
/* jquery-multifile v2.2.2 @ 2020-04-16 06:05:29 */
window.jQuery&&function(d){"use strict";function g(e){return 1048576<e?(e/1048576).toFixed(1)+"Mb":1024==e?"1Mb":(e/1024).toFixed(1)+"Kb"}function h(e){return(e.files&&e.files.length?e.files:null)||[{name:e.value,size:0,type:((e.value||"").match(/[^\.]+$/i)||[""])[0]}]}d.fn.MultiFile=function(e){if(0==this.length)return this;if("string"==typeof arguments[0]){if(1<this.length){var i=arguments;return this.each(function(){d.fn.MultiFile.apply(d(this),i)})}return d.fn.MultiFile[arguments[0]].apply(this,d.makeArray(arguments).slice(1)||[])}"number"==typeof e&&(e={max:e});e=d.extend({},d.fn.MultiFile.options,e||{});d("form").not("MultiFile-intercepted").addClass("MultiFile-intercepted").submit(d.fn.MultiFile.disableEmpty),d.fn.MultiFile.options.autoIntercept&&(d.fn.MultiFile.intercept(d.fn.MultiFile.options.autoIntercept),d.fn.MultiFile.options.autoIntercept=null),this.not(".MultiFile-applied").addClass("MultiFile-applied").each(function(){window.MultiFile=(window.MultiFile||0)+1;var i=window.MultiFile,f={e:this,E:d(this),clone:d(this).clone()},c=d.extend({},d.fn.MultiFile.options,e||{},(d.metadata?f.E.metadata():d.meta?f.E.data():null)||{},{});0<c.max||(c.max=f.E.attr("maxlength")),0<c.max||(c.max=(String(f.e.className.match(/\b(max|limit)\-([0-9]+)\b/gi)||[""]).match(/[0-9]+/gi)||[""])[0],0<c.max?c.max=String(c.max).match(/[0-9]+/gi)[0]:c.max=-1),c.max=new Number(c.max),c.accept=c.accept||f.E.attr("accept")||"",c.accept&&c.accept.match(/\//)&&(c.accept=""),c.accept||(c.accept=f.e.className.match(/\b(accept\-[\w\|]+)\b/gi)||"",c.accept=new String(c.accept).replace(/^(accept|ext)\-/i,"")),c.maxsize=0<c.maxsize?c.maxsize:f.E.data("maxsize")||0,0<c.maxsize||(c.maxsize=(String(f.e.className.match(/\b(maxsize|maxload|size)\-([0-9]+)\b/gi)||[""]).match(/[0-9]+/gi)||[""])[0],0<c.maxsize?c.maxsize=String(c.maxsize).match(/[0-9]+/gi)[0]:c.maxsize=-1),c.maxfile=0<c.maxfile?c.maxfile:f.E.data("maxfile")||0,0<c.maxfile||(c.maxfile=(String(f.e.className.match(/\b(maxfile|filemax)\-([0-9]+)\b/gi)||[""]).match(/[0-9]+/gi)||[""])[0],0<c.maxfile?c.maxfile=String(c.maxfile).match(/[0-9]+/gi)[0]:c.maxfile=-1),1<c.maxfile&&(c.maxfile=1024*c.maxfile),1<c.maxsize&&(c.maxsize=1024*c.maxsize),!1!==c.multiple&&1<c.max&&f.E.attr("multiple","multiple").prop("multiple",!0),d.extend(f,c||{}),f.STRING=d.extend({},d.fn.MultiFile.options.STRING,f.STRING),d.extend(f,{n:0,slaves:[],files:[],instanceKey:f.e.id||"MultiFile"+String(i),generateID:function(e){return f.instanceKey+(0<e?"_F"+String(e):"")},trigger:function(e,t,a,i){var l,n=a[e]||a["on"+e];if(n)return i=i||a.files||h(this),d.each(i,function(e,i){l=n.apply(a.wrapper,[t,i.name,a,i])}),l}}),1<String(f.accept).length&&(f.accept=f.accept.replace(/\W+/g,"|").replace(/^\W|\W$/g,""),f.rxAccept=new RegExp("\\.("+(f.accept||"")+")$","gi")),f.wrapID=f.instanceKey,f.E.wrap('<div class="MultiFile-wrap" id="'+f.wrapID+'"></div>'),f.wrapper=d("#"+f.wrapID),f.e.name=f.e.name||"file"+i+"[]",f.list||(f.wrapper.append('<div class="MultiFile-list" id="'+f.wrapID+'_list"></div>'),f.list=d("#"+f.wrapID+"_list")),f.list=d(f.list),f.addSlave=function(u,m){var e;f.n++,u.MultiFile=f,u.id=u.name="",u.id=f.generateID(m),u.name=String(f.namePattern.replace(/\$name/gi,d(f.clone).attr("name")).replace(/\$id/gi,d(f.clone).attr("id")).replace(/\$g/gi,i).replace(/\$i/gi,m)),0<f.max&&f.files.length>f.max&&(e=u.disabled=!0),f.current=u,(u=d(u)).val("").attr("value","")[0].value="",u.addClass("MultiFile-applied"),u.change(function(e,i,t){d(this).blur();var r=this,a=f.files||[],l=this.files||[{name:this.value,size:0,type:((this.value||"").match(/[^\.]+$/i)||[""])[0]}],n=[],s=0,c=f.total_size||0,o=[];d.each(l,function(e,i){n[n.length]=i}),f.trigger("FileSelect",this,f,n),d.each(l,function(e,i){function a(e){return e.replace("$ext",String(l.match(/[^\.]+$/i)||"")).replace("$file",l.match(/[^\/\\]+$/gi)).replace("$size",g(t)+" > "+g(f.maxfile))}var l=i.name.replace(/^C:\\fakepath\\/gi,""),t=i.size;f.accept&&l&&!l.match(f.rxAccept)&&(o[o.length]=a(f.STRING.denied),f.trigger("FileInvalid",this,f,[i])),d(f.wrapper).find("input[type=file]").not(r).each(function(){d.each(h(this),function(e,i){var t;i.name&&(t=(i.name||"").replace(/^C:\\fakepath\\/gi,""),l!=t&&l!=t.substr(t.length-l.length)||(o[o.length]=a(f.STRING.duplicate),f.trigger("FileDuplicate",r,f,[i])))})}),0<f.maxfile&&0<t&&t>f.maxfile&&(o[o.length]=a(f.STRING.toobig),f.trigger("FileTooBig",this,f,[i]));var n=f.trigger("FileValidate",this,f,[i]);n&&""!=n&&(o[o.length]=a(n)),s+=i.size}),c+=s,n.size=s,n.total=c,n.total_length=n.length+a.length,0<f.max&&a.length+l.length>f.max&&(o[o.length]=f.STRING.toomany.replace("$max",f.max),f.trigger("FileTooMany",this,f,n)),0<f.maxsize&&c>f.maxsize&&(o[o.length]=f.STRING.toomuch.replace("$size",g(c)+" > "+g(f.maxsize)),f.trigger("FileTooMuch",this,f,n));var p=d(f.clone).clone();if(p.addClass("MultiFile"),0<o.length)return f.error(o.join("\n\n")),f.n--,f.addSlave(p[0],m),u.parent().prepend(p),u.remove(),!1;f.total_size=c,(l=a.concat(n)).size=c,l.size_label=g(c),f.files=l,d(this).css({position:"absolute",top:"-3000px"}),u.after(p),f.addSlave(p[0],m+1),f.addToList(this,m,n),f.trigger("afterFileSelect",this,f,n)}),d(u).data("MultiFile-wrap",f.wrapper),d(f.wrapper).data("MultiFile",f),e&&d(u).attr("disabled","disabled").prop("disabled",!0)},f.addToList=function(r,e,i){f.trigger("FileAppend",r,f,i);var s=d("<span/>");d.each(i,function(e,t){var i=String(t.name||"").replace(/[&<>'"]/g,function(e){return"&#"+e.charCodeAt()+";"}),a=f.STRING,l=a.label||a.file||a.name,n=a.title||a.tooltip||a.selected,a="image/"==t.type.substr(0,6)?'<img class="MultiFile-preview" style="'+f.previewCss+'"/>':"",a=d(('<span class="MultiFile-label" title="'+n+'"><span class="MultiFile-title">'+l+"</span>"+(f.preview||d(r).is(".with-preview")?a:"")+"</span>").replace(/\$(file|name)/gi,(i.match(/[^\/\\]+$/gi)||[i])[0]).replace(/\$(ext|extension|type)/gi,(i.match(/[^\.]+$/gi)||[""])[0]).replace(/\$(size)/gi,g(t.size||0)).replace(/\$(preview)/gi,a).replace(/\$(i)/gi,e));a.find("img.MultiFile-preview").each(function(){var i=this,e=new FileReader;e.readAsDataURL(t),e.onload=function(e){i.src=e.target.result}}),0<e&&c.separator&&s.append(c.separator),s.append(a);i=String(t.name||"");s[s.length]=('<span class="MultiFile-title" title="'+f.STRING.selected+'">'+f.STRING.file+"</span>").replace(/\$(file|name)/gi,(i.match(/[^\/\\]+$/gi)||[i])[0]).replace(/\$(ext|extension|type)/gi,(i.match(/[^\.]+$/gi)||[""])[0]).replace(/\$(size)/gi,g(t.size||0)).replace(/\$(i)/gi,e)});var t=d('<div class="MultiFile-label"></div>'),a=d('<a class="MultiFile-remove" href="#'+f.wrapID+'">'+f.STRING.remove+"</a>").click(function(){var e=h(r);f.trigger("FileRemove",r,f,e),f.n--,f.current.disabled=!1,d(r).remove(),d(this).parent().remove(),d(f.current).css({position:"",top:""}),d(f.current).reset().val("").attr("value","")[0].value="";var t=[],a=0;return d(f.wrapper).find("input[type=file]").each(function(){d.each(h(this),function(e,i){i.name&&(t[t.length]=i,a+=i.size)})}),f.files=t,f.total_size=a,f.size_label=g(a),d(f.wrapper).data("MultiFile",f),f.trigger("afterFileRemove",r,f,e),f.trigger("FileChange",f.current,f,t),!1});f.list.append(t.append(a," ",s)),f.trigger("afterFileAppend",r,f,i),f.trigger("FileChange",r,f,f.files)},f.MultiFile||f.addSlave(f.e,0),f.n++})},d.extend(d.fn.MultiFile,{data:function(){var e=d(this),e=e.is(".MultiFile-wrap")?e:e.data("MultiFile-wrap");if(!e||!e.length)return!console.error("Could not find MultiFile control wrapper");e=e.data("MultiFile");return e?e||{}:!console.error("Could not find MultiFile data in wrapper")},reset:function(){var e=this.MultiFile("data");return e&&d(e.list).find("a.MultiFile-remove").click(),d(this)},files:function(){var e=this.MultiFile("data");return e?e.files||[]:!console.log("MultiFile plugin not initialized")},size:function(){var e=this.MultiFile("data");return e?e.total_size||0:!console.log("MultiFile plugin not initialized")},count:function(){var e=this.MultiFile("data");return e?e.files&&e.files.length||0:!console.log("MultiFile plugin not initialized")},disableEmpty:function(e){e=("string"==typeof e?e:"")||"mfD";var i=[];return d("input:file.MultiFile").each(function(){""==d(this).val()&&(i[i.length]=this)}),window.clearTimeout(d.fn.MultiFile.reEnableTimeout),d.fn.MultiFile.reEnableTimeout=window.setTimeout(d.fn.MultiFile.reEnableEmpty,500),d(i).each(function(){this.disabled=!0}).addClass(e)},reEnableEmpty:function(e){return d("input:file."+(e=("string"==typeof e?e:"")||"mfD")).removeClass(e).each(function(){this.disabled=!1})},intercepted:{},intercept:function(e,i,t){var a,l;if((t=t||[]).constructor.toString().indexOf("Array")<0&&(t=[t]),"function"==typeof e)return d.fn.MultiFile.disableEmpty(),l=e.apply(i||window,t),setTimeout(function(){d.fn.MultiFile.reEnableEmpty()},1e3),l;e.constructor.toString().indexOf("Array")<0&&(e=[e]);for(var n=0;n<e.length;n++)(a=e[n]+"")&&function(e){d.fn.MultiFile.intercepted[e]=d.fn[e]||function(){},d.fn[e]=function(){return d.fn.MultiFile.disableEmpty(),l=d.fn.MultiFile.intercepted[e].apply(this,arguments),setTimeout(function(){d.fn.MultiFile.reEnableEmpty()},1e3),l}}(a)}}),d.fn.MultiFile.options={accept:"",max:-1,maxfile:-1,maxsize:-1,namePattern:"$name",preview:!1,previewCss:"max-height:100px; max-width:100px;",separator:", ",STRING:{remove:"x",denied:"You cannot select a $ext file.\nTry again...",file:"$file",selected:"File selected: $file",duplicate:"This file has already been selected:\n$file",toomuch:"The files selected exceed the maximum size permited ($size)",toomany:"Too many files selected (max: $max)",toobig:"$file is too big (max $size)"},autoIntercept:["submit","ajaxSubmit","ajaxForm","validate","valid"],error:function(e){"undefined"!=typeof console&&console.log(e),alert(e)}},d.fn.reset=d.fn.reset||function(){return this.each(function(){try{this.reset()}catch(e){}})},d(function(){d("input[type=file].multi").MultiFile()})}(jQuery);


/*
 ### jQuery Multiple File Selection Plugin v2.2.2 - 2016-06-16 ###
 * Home: https://multifile.fyneworks.com/
 * Code: https://github.com/fyneworks/multifile
 *
 * Licensed under http://en.wikipedia.org/wiki/MIT_License
 */
/*# AVOID COLLISIONS #*/
;
if (window.jQuery)(function ($) {
    "use strict";
    /*# AVOID COLLISIONS #*/
    // size label function (shows kb and mb where accordingly)
    function sl(x) {
        return x > 1048576 ? (x / 1048576).toFixed(1) + 'Mb' : (x==1024?'1Mb': (x / 1024).toFixed(1) + 'Kb' )
    };
    // utility function to return an array of
    function FILE_LIST(x){
        return ((x.files&&x.files.length) ? x.files : null) || [{
            name: x.value,
            size: 0,
            type: ((x.value || '').match(/[^\.]+$/i) || [''])[0]
        }];
    };
    // plugin initialization
    $.fn.MultiFile = function (options) {
        if (this.length == 0) return this; // quick fail
        // Handle API methods
        if (typeof arguments[0] == 'string') {
            // Perform API methods on individual elements
            if (this.length > 1) {
                var args = arguments;
                return this.each(function () {
                    $.fn.MultiFile.apply($(this), args);
                });
            };
            // Invoke API method handler (and return whatever it wants to return)
            return $.fn.MultiFile[arguments[0]].apply(this, $.makeArray(arguments).slice(1) || []);
        };
        // Accept number
        if (typeof options == 'number') {
            options = {max: options};
        };
        // Initialize options for this call
        var options = $.extend({} /* new object */ ,
            $.fn.MultiFile.options /* default options */ ,
            options || {} /* just-in-time options */
        );
        // Empty Element Fix!!!
        // this code will automatically intercept native form submissions
        // and disable empty file elements
        $('form')
            .not('MultiFile-intercepted')
            .addClass('MultiFile-intercepted')
            .submit($.fn.MultiFile.disableEmpty);
        //### http://plugins.jquery.com/node/1363
        // utility method to integrate this plugin with others...
        if ($.fn.MultiFile.options.autoIntercept) {
            $.fn.MultiFile.intercept($.fn.MultiFile.options.autoIntercept /* array of methods to intercept */ );
            $.fn.MultiFile.options.autoIntercept = null; /* only run this once */
        };
        // loop through each matched element
        this
            .not('.MultiFile-applied')
            .addClass('MultiFile-applied')
            .each(function () {
                //#####################################################################
                // MAIN PLUGIN FUNCTIONALITY - START
                //#####################################################################
                // BUG 1251 FIX: http://plugins.jquery.com/project/comments/add/1251
                // variable group_count would repeat itself on multiple calls to the plugin.
                // this would cause a conflict with multiple elements
                // changes scope of variable to global so id will be unique over n calls
                window.MultiFile = (window.MultiFile || 0) + 1;
                var group_count = window.MultiFile;
                // Copy parent attributes - Thanks to Jonas Wagner
                // we will use this one to create new input elements
                var MultiFile = {
                    e: this,
                    E: $(this),
                    clone: $(this).clone()
                };
                //===
                //# USE CONFIGURATION
                var o = $.extend({},
                    $.fn.MultiFile.options,
                    options || {}, ($.metadata ? MultiFile.E.metadata() : ($.meta ? MultiFile.E.data() : null)) || {}, /* metadata options */ {} /* internals */
                );
                // limit number of files that can be selected?
                if (!(o.max > 0) /*IsNull(MultiFile.max)*/ ) {
                    o.max = MultiFile.E.attr('maxlength');
                };
                if (!(o.max > 0) /*IsNull(MultiFile.max)*/ ) {
                    o.max = (String(MultiFile.e.className.match(/\b(max|limit)\-([0-9]+)\b/gi) || ['']).match(/[0-9]+/gi) || [''])[0];
                    if (!(o.max > 0)) o.max = -1;
                    else o.max = String(o.max).match(/[0-9]+/gi)[0];
                };
                o.max = new Number(o.max);
                // limit extensions?
                o.accept = o.accept || MultiFile.E.attr('accept') || '';
                // ignore mime-type accept attribute for browser handling
                // see https://github.com/fyneworks/multifile/issues/101
                if(o.accept && o.accept.match(/\//)) o.accept = '';
                if (!o.accept) {
                    o.accept = (MultiFile.e.className.match(/\b(accept\-[\w\|]+)\b/gi)) || '';
                    o.accept = new String(o.accept).replace(/^(accept|ext)\-/i, '');
                };
                // limit total pay load size
                o.maxsize = o.maxsize>0?o.maxsize:null || MultiFile.E.data('maxsize') || 0;
                if (!(o.maxsize > 0) /*IsNull(MultiFile.maxsize)*/ ) {
                    o.maxsize = (String(MultiFile.e.className.match(/\b(maxsize|maxload|size)\-([0-9]+)\b/gi) || ['']).match(/[0-9]+/gi) || [''])[0];
                    if (!(o.maxsize > 0)) o.maxsize = -1;
                    else o.maxsize = String(o.maxsize).match(/[0-9]+/gi)[0];
                };
                // limit individual file size
                o.maxfile = o.maxfile>0?o.maxfile:null || MultiFile.E.data('maxfile') || 0;
                if (!(o.maxfile > 0) /*IsNull(MultiFile.maxfile)*/ ) {
                    o.maxfile = (String(MultiFile.e.className.match(/\b(maxfile|filemax)\-([0-9]+)\b/gi) || ['']).match(/[0-9]+/gi) || [''])[0];
                    if (!(o.maxfile > 0)) o.maxfile = -1;
                    else o.maxfile = String(o.maxfile).match(/[0-9]+/gi)[0];
                };
                //===
                // size options are accepted in kylobytes, so multiple them by 1024
                if(o.maxfile>1) o.maxfile = o.maxfile * 1024;
                if(o.maxsize>1) o.maxsize = o.maxsize * 1024;
                //===
                // HTML5: enforce multiple selection to be enabled, except when explicitly disabled
                if (o.multiple !== false) {
                            if (o.max > 1) MultiFile.E.attr('multiple', 'multiple').prop('multiple', true);
                        }
                //===
                // APPLY CONFIGURATION
                $.extend(MultiFile, o || {});
                MultiFile.STRING = $.extend({}, $.fn.MultiFile.options.STRING, MultiFile.STRING);
                //===
                //#########################################
                // PRIVATE PROPERTIES/METHODS
                $.extend(MultiFile, {
                    n: 0, // How many elements are currently selected?
                    slaves: [],
                    files: [],
                    instanceKey: MultiFile.e.id || 'MultiFile' + String(group_count), // Instance Key?
                    generateID: function (z) {
                        return MultiFile.instanceKey + (z > 0 ? '_F' + String(z) : '');
                    },
                    trigger: function (event, element, MultiFile, files) {
                        var rv, handler = MultiFile[event] || MultiFile['on'+event] ;
                        if (handler){
                            files = files || MultiFile.files || FILE_LIST(this);
                            ;
                            $.each(files,function(i, file){
                                // execute function in element's context, so 'this' variable is current element
                                rv = handler.apply(MultiFile.wrapper, [element, file.name, MultiFile, file]);
                            });
                            return rv;
                        };
                    }
                });
                //===
                // Setup dynamic regular expression for extension validation
                // - thanks to John-Paul Bader: http://smyck.de/2006/08/11/javascript-dynamic-regular-expresions/
                if (String(MultiFile.accept).length > 1) {
                    MultiFile.accept = MultiFile.accept.replace(/\W+/g, '|').replace(/^\W|\W$/g, '');
                    MultiFile.rxAccept = new RegExp('\\.(' + (MultiFile.accept ? MultiFile.accept : '') + ')$', 'gi');
                };
                //===
                // Create wrapper to hold our file list
                MultiFile.wrapID = MultiFile.instanceKey;// + '_wrap'; // Wrapper ID?
                MultiFile.E.wrap('<div class="MultiFile-wrap" id="' + MultiFile.wrapID + '"></div>');
                MultiFile.wrapper = $('#' + MultiFile.wrapID + '');
                //===
                // MultiFile MUST have a name - default: file1[], file2[], file3[]
                MultiFile.e.name = MultiFile.e.name || 'file' + group_count + '[]';
                //===
                if (!MultiFile.list) {
                    // Create a wrapper for the list
                    // * OPERA BUG: NO_MODIFICATION_ALLOWED_ERR ('list' is a read-only property)
                    // this change allows us to keep the files in the order they were selected
                    MultiFile.wrapper.append('<div class="MultiFile-list" id="' + MultiFile.wrapID + '_list"></div>');
                    MultiFile.list = $('#' + MultiFile.wrapID + '_list');
                };
                MultiFile.list = $(MultiFile.list);
                //===
                // Bind a new element
                MultiFile.addSlave = function (slave, slave_count) {
                    //if(window.console) console.log('MultiFile.addSlave',slave_count);
                    // Keep track of how many elements have been displayed
                    MultiFile.n++;
                    // Add reference to master element
                    slave.MultiFile = MultiFile;
                    // BUG FIX: http://plugins.jquery.com/node/1495
                    // Clear identifying properties from clones
                    slave.id = slave.name = '';
                    // Define element's ID and name (upload components need this!)
                    //slave.id = slave.id || MultiFile.generateID(slave_count);
                    slave.id = MultiFile.generateID(slave_count);
                    //FIX for: http://code.google.com/p/jquery-multifile-plugin/issues/detail?id=23
                    //CHANGE v2.2.1 - change ID of all file elements, keep original ID in wrapper
                    // 2008-Apr-29: New customizable naming convention (see url below)
                    // http://groups.google.com/group/jquery-dev/browse_frm/thread/765c73e41b34f924#
                    slave.name = String(MultiFile.namePattern
                        /*master name*/
                        .replace(/\$name/gi, $(MultiFile.clone).attr('name'))
                        /*master id */
                        .replace(/\$id/gi, $(MultiFile.clone).attr('id'))
                        /*group count*/
                        .replace(/\$g/gi, group_count) //(group_count>0?group_count:''))
                        /*slave count*/
                        .replace(/\$i/gi, slave_count) //(slave_count>0?slave_count:''))
                    );
                    // If we've reached maximum number, disable input slave
                    var disable_slave;
                    if ((MultiFile.max > 0) && ((MultiFile.files.length) > (MultiFile.max))) {
                        slave.disabled = true;
                        disable_slave = true;
                    };
                    // Remember most recent slave
                    MultiFile.current = slave;
                    // We'll use jQuery from now on
                    slave = $(slave);
                    // Clear value
                    slave.val('').attr('value', '')[0].value = '';
                   
                    // Make slave field optional to avoid validation errors
                                        slave.attr('required', false);
                    // Stop plugin initializing on slaves
                    slave.addClass('MultiFile-applied');
                    // Triggered when a file is selected
                    slave.change(function (a, b, c) {
                        //if(window.console) console.log('MultiFile.slave.change',slave_count);
                        //if(window.console) console.log('MultiFile.slave.change',this.files);
                        // Lose focus to stop IE7 firing onchange again
                        $(this).blur();
                        //# NEW 2014-04-14 - accept multiple file selection, HTML5
                        var e = this,
                                prevs = MultiFile.files || [],
                                files = this.files || [{
                                    name: this.value,
                                    size: 0,
                                    type: ((this.value || '').match(/[^\.]+$/i) || [''])[0]
                                }],
                                newfs = [],
                                newfs_size = 0,
                                total_size = MultiFile.total_size || 0/*,
                                html5_multi_mode = this.files && $(this).attr('multiple')*/
                            ;
                        // recap
                        //console.log('START '+ prevs.length + ' files @ '+ sl(total_size) +'.', prevs);
                        //# Retrive value of selected file from element
                        var ERROR = []; //, v = String(this.value || '');
                        // make a normal array
                        $.each(files, function (i, file) {
                            newfs[newfs.length] = file;
                        });
                        //# Trigger Event! onFileSelect
                        MultiFile.trigger('FileSelect', this, MultiFile, newfs);
                        //# End Event!
                        // validate individual files
                        $.each(files, function (i, file) {
                            // pop local variables out of array/file object
                            var v = file.name.replace(/^C:\\fakepath\\/gi,''),
                                    s = file.size,
                                    p = function(z){
                                        return z
                                            .replace('$ext', String(v.match(/[^\.]+$/i) || ''))
                                            .replace('$file', v.match(/[^\/\\]+$/gi))
                                            .replace('$size', sl(s) + ' > ' + sl(MultiFile.maxfile))
                                    }
                            ;
                            // check extension
                            if (MultiFile.accept && v && !v.match(MultiFile.rxAccept)) {
                                ERROR[ERROR.length] = p(MultiFile.STRING.denied);
                                MultiFile.trigger('FileInvalid', this, MultiFile, [file]);
                            };
                            // Disallow duplicates
                            $(MultiFile.wrapper).find('input[type=file]').not(e).each(function(){
                                // go through each file in each slave
                                $.each(FILE_LIST(this), function (i, file) {
                                    if(file.name){
                                        //console.log('MultiFile.debug> Duplicate?', file.name, v);
                                        var x = (file.name || '').replace(/^C:\\fakepath\\/gi,'');
                                        if ( v == x || v == x.substr(x.length - v.length)) {
                                            ERROR[ERROR.length] = p(MultiFile.STRING.duplicate);
                                            MultiFile.trigger('FileDuplicate', e, MultiFile, [file]);
                                        };
                                    };
                                });
                            });
                            // limit the max size of individual files selected
                            if (MultiFile.maxfile>0 && s>0 && s>MultiFile.maxfile) {
                                ERROR[ERROR.length] = p(MultiFile.STRING.toobig);
                                MultiFile.trigger('FileTooBig', this, MultiFile, [file]);
                            };
                            // check extension
                            var customError = MultiFile.trigger('FileValidate', this, MultiFile, [file]);
                            if(customError && customError!=''){
                                ERROR[ERROR.length] = p(customError);
                            };
                            // add up size of files selected
                            newfs_size += file.size;
                        });
                        // add up total for all files selected (existing and new)
                        total_size += newfs_size;
                        // put some useful information in the file array
                        newfs.size = newfs_size;
                        newfs.total = total_size;
                        newfs.total_length = newfs.length + prevs.length;
                        // limit the number of files selected
                        if (MultiFile.max>0 && prevs.length + files.length > MultiFile.max) {
                            ERROR[ERROR.length] = MultiFile.STRING.toomany.replace('$max', MultiFile.max);
                            MultiFile.trigger('FileTooMany', this, MultiFile, newfs);
                        };
                        // limit the max size of files selected
                        if (MultiFile.maxsize > 0 && total_size > MultiFile.maxsize) {
                            ERROR[ERROR.length] = MultiFile.STRING.toomuch.replace('$size', sl(total_size) + ' > ' + sl(MultiFile.maxsize));
                            MultiFile.trigger('FileTooMuch', this, MultiFile, newfs);
                        };
                        // Create a new file input element
                        var newEle = $(MultiFile.clone).clone(); // Copy parent attributes - Thanks to Jonas Wagner
                        //# Let's remember which input we've generated so
                        // we can disable the empty ones before submission
                        // See: http://plugins.jquery.com/node/1495
                        newEle.addClass('MultiFile');
                        // Handle error
                        if (ERROR.length > 0) {
                            // Handle error
                            MultiFile.error(ERROR.join('\n\n'));
                            // 2007-06-24: BUG FIX - Thanks to Adrian Wróbel <adrian [dot] wrobel [at] gmail.com>
                            // Ditch the trouble maker and add a fresh new element
                            MultiFile.n--;
                            MultiFile.addSlave(newEle[0], slave_count);
                            slave.parent().prepend(newEle);
                            slave.remove();
                            return false;
                        }
                        else { // if no errors have been found
                            // remember total size
                            MultiFile.total_size = total_size;
                            // merge arrays
                            files = prevs.concat(newfs);
                            // put some useful information in the file array
                            files.size = total_size;
                            files.size_label = sl(total_size);
                            // recap
                            //console.log('NOW '+ files.length + ' files @ '+ sl(total_size) +'.', files);
                            // remember files
                            MultiFile.files = files;
                            // Hide this element (NB: display:none is evil!)
                            $(this).css({
                                position: 'absolute',
                                top: '-3000px'
                            });
                            // Add new element to the form
                            slave.after(newEle);
                            // Bind functionality
                            MultiFile.addSlave(newEle[0], slave_count + 1);
                            // Update list
                            MultiFile.addToList(this, slave_count, newfs);
                            //# Trigger Event! afterFileSelect
                            MultiFile.trigger('afterFileSelect', this, MultiFile, newfs);
                            //# End Event!
                        }; // no errors detected
                    }); // slave.change()
                    // point to wrapper
                    $(slave).data('MultiFile-wrap', MultiFile.wrapper);
                    // store contorl's settings and file info in wrapper
                    $(MultiFile.wrapper).data('MultiFile',MultiFile);
                    // disable?
                    if(disable_slave) $(slave).attr('disabled','disabled').prop('disabled',true);
                }; // MultiFile.addSlave
                // Bind a new element

                // Add a new file to the list
                MultiFile.addToList = function (slave, slave_count, files) {
                    //if(window.console) console.log('MultiFile.addToList',slave_count);
                    //# Trigger Event! onFileAppend
                    MultiFile.trigger('FileAppend', slave, MultiFile, files);
                    //# End Event!
                    var names = $('<span/>');
                    $.each(files, function (i, file) {
                        var v = String(file.name || '' ).replace(/[&<>'"]/g, function(c) { return '&#'+c.charCodeAt()+';'; }),
                                S = MultiFile.STRING,
                                n = S.label || S.file || S.name,
                                t = S.title || S.tooltip || S.selected,
                                p = file.type.substr(0,6) == 'image/' ? '<img class="MultiFile-preview" style="'+ MultiFile.previewCss+'"/>' : '',
                                label = $(
                                        (
                                            '<span class="MultiFile-label" title="' + t + '">'+
                                                '<span class="MultiFile-title">'+ n +'</span>'+
                                                (MultiFile.preview || $(slave).is('.with-preview') ? p : '' )+
                                            '</span>'
                                        )
                                        .replace(/\$(file|name)/gi, (v.match(/[^\/\\]+$/gi)||[v])[0])
                                        .replace(/\$(ext|extension|type)/gi, (v.match(/[^\.]+$/gi)||[''])[0])
                                        .replace(/\$(size)/gi, sl(file.size || 0))
                                        .replace(/\$(preview)/gi, p)
                                        .replace(/\$(i)/gi, i)
                                );
                        // now supports preview via locale string.
                        // just add an <img class='MultiFile-preview'/> anywhere within the "file" string
                        label.find('img.MultiFile-preview').each(function(){
                            var t = this;
                            var oFReader = new FileReader();
                            oFReader.readAsDataURL(file);
                            oFReader.onload = function (oFREvent) {
                                t.src = oFREvent.target.result;
                            };
                        });
                        // append file label to list
                        if(i>0 && !!o.separator) names.append(o.separator);
                        names.append(label);
          &n