source: prototipo_portal_2018/WebContent/js/fileinput.js

Last change on this file was 9631190, checked in by antonioaraujob <antonioaraujob@…>, 9 years ago

Se agregan los archivos del portal de prueba al control de versiones.

  • Property mode set to 100755
File size: 96.4 KB
Line 
1/*!
2 * @copyright Copyright &copy; Kartik Visweswaran, Krajee.com, 2014 - 2015
3 * @version 4.1.8
4 *
5 * File input styled for Bootstrap 3.0 that utilizes HTML5 File Input's advanced
6 * features including the FileReader API.
7 *
8 * The plugin drastically enhances the HTML file input to preview multiple files on the client before
9 * upload. In addition it provides the ability to preview content of images, text, videos, audio, html,
10 * flash and other objects. It also offers the ability to upload and delete files using AJAX, and add
11 * files in batches (i.e. preview, append, or remove before upload).
12 *
13 * Author: Kartik Visweswaran
14 * Copyright: 2015, Kartik Visweswaran, Krajee.com
15 * For more JQuery plugins visit http://plugins.krajee.com
16 * For more Yii related demos visit http://demos.krajee.com
17 */
18(function ($) {
19    "use strict";
20    String.prototype.repl = function (from, to) {
21        return this.split(from).join(to);
22    };
23    var isIE = function (ver) {
24            var div = document.createElement("div"), status;
25            div.innerHTML = "<!--[if IE " + ver + "]><i></i><![endif]-->";
26            status = (div.getElementsByTagName("i").length === 1);
27            document.body.appendChild(div);
28            div.parentNode.removeChild(div);
29            return status;
30        },
31        previewCache = {
32            data: {},
33            init: function (obj) {
34                var content = obj.initialPreview, id = obj.id;
35                if (content.length > 0 && !isArray(content)) {
36                    content = content.split(obj.initialPreviewDelimiter);
37                }
38                previewCache.data[id] = {
39                    content: content,
40                    config: obj.initialPreviewConfig,
41                    tags: obj.initialPreviewThumbTags,
42                    delimiter: obj.initialPreviewDelimiter,
43                    template: obj.previewGenericTemplate,
44                    msg: obj.msgSelected,
45                    initId: obj.previewInitId,
46                    footer: obj.getLayoutTemplate('footer'),
47                    isDelete: obj.initialPreviewShowDelete,
48                    caption: obj.initialCaption,
49                    actions: function (showUpload, showDelete, disabled, url, key, index) {
50                        return obj.renderFileActions(showUpload, showDelete, disabled, url, key, index);
51                    }
52                };
53            },
54            fetch: function (id) {
55                return previewCache.data[id].content.filter(function (n) {
56                    return n !== undefined;
57                });
58            },
59            count: function (id) {
60                return !!previewCache.data[id] && !!previewCache.data[id].content ? previewCache.fetch(id).length : 0;
61            },
62            get: function (id, i, isDisabled) {
63                var ind = 'init_' + i, data = previewCache.data[id],
64                    previewId = data.initId + '-' + ind, out;
65                isDisabled = isDisabled === undefined ? true : isDisabled;
66                if (data.content[i] === undefined) {
67                    return '';
68                }
69                out = data.template
70                    .repl('{previewId}', previewId)
71                    .repl('{frameClass}', ' file-preview-initial')
72                    .repl('{fileindex}', ind)
73                    .repl('{content}', data.content[i])
74                    .repl('{footer}', previewCache.footer(id, i, isDisabled));
75                if (data.tags.length && data.tags[i]) {
76                    out = replaceTags(out, data.tags[i]);
77                }
78                return out;
79            },
80            add: function (id, content, config, append) {
81                var data = $.extend(true, {}, previewCache.data[id]), index;
82                if (!isArray(content)) {
83                    content = content.split(data.delimiter);
84                }
85                if (append) {
86                    index = data.content.push(content) - 1;
87                    data.config[index] = config;
88                } else {
89                    index = content.length;
90                    data.content = content;
91                    data.config = config;
92                }
93                previewCache.data[id] = data;
94                return index;
95            },
96            set: function (id, content, config, tags, append) {
97                var data = $.extend(true, {}, previewCache.data[id]), i;
98                if (!isArray(content)) {
99                    content = content.split(data.delimiter);
100                }
101                if (append) {
102                    for (i = 0; i < content.length; i++) {
103                        data.content.push(content[i]);
104                    }
105                    for (i = 0; i < config.length; i++) {
106                        data.config.push(config[i]);
107                    }
108                    for (i = 0; i < tags.length; i++) {
109                        data.tags.push(tags[i]);
110                    }
111                } else {
112                    data.content = content;
113                    data.config = config;
114                    data.tags = tags;
115                }
116                previewCache.data[id] = data;
117            },
118            unset: function (id, index) {
119                var chk = previewCache.count(id);
120                if (!chk) {
121                    return;
122                }
123                if (chk === 1) {
124                    previewCache.data[id].content = [];
125                    previewCache.data[id].config = [];
126                    return;
127                }
128                previewCache.data[id].content[index] = undefined;
129                previewCache.data[id].config[index] = undefined;
130            },
131            out: function (id) {
132                var html = '', data = previewCache.data[id], caption, len = previewCache.count(id);
133                if (len === 0) {
134                    return {content: '', caption: ''};
135                }
136                for (var i = 0; i < len; i++) {
137                    html += previewCache.get(id, i);
138                }
139                caption = data.msg.repl('{n}', len);
140                return {content: html, caption: caption};
141            },
142            footer: function (id, i, isDisabled) {
143                var data = previewCache.data[id];
144                isDisabled = isDisabled === undefined ? true : isDisabled;
145                if (data.config.length === 0 || isEmpty(data.config[i])) {
146                    return '';
147                }
148                var config = data.config[i],
149                    caption = isSet('caption', config) ? config.caption : '',
150                    width = isSet('width', config) ? config.width : 'auto',
151                    url = isSet('url', config) ? config.url : false,
152                    key = isSet('key', config) ? config.key : null,
153                    disabled = (url === false) && isDisabled,
154                    actions = data.isDelete ? data.actions(false, true, disabled, url, key, i) : '',
155                    footer = data.footer.repl('{actions}', actions);
156                return footer
157                    .repl('{caption}', caption)
158                    .repl('{width}', width)
159                    .repl('{indicator}', '')
160                    .repl('{indicatorTitle}', '');
161            }
162        },
163        PREVIEW_FRAMES = '.file-preview-frame:not(.file-preview-initial)',
164        getNum = function (num, def) {
165            def = def || 0;
166            if (typeof num === "number") {
167                return num;
168            }
169            if (typeof num === "string") {
170                num = parseFloat(num);
171            }
172            return isNaN(num) ? def : num;
173        },
174        hasFileAPISupport = function () {
175            return window.File && window.FileReader;
176        },
177        hasDragDropSupport = function () {
178            var $div = document.createElement('div');
179            return !isIE(9) && ($div.draggable !== undefined || ($div.ondragstart !== undefined && $div.ondrop !== undefined));
180        },
181        hasFileUploadSupport = function () {
182            return hasFileAPISupport && window.FormData;
183        },
184        addCss = function ($el, css) {
185            $el.removeClass(css).addClass(css);
186        },
187        STYLE_SETTING = 'style="width:{width};height:{height};"',
188        OBJECT_PARAMS = '      <param name="controller" value="true" />\n' +
189            '      <param name="allowFullScreen" value="true" />\n' +
190            '      <param name="allowScriptAccess" value="always" />\n' +
191            '      <param name="autoPlay" value="false" />\n' +
192            '      <param name="autoStart" value="false" />\n' +
193            '      <param name="quality" value="high" />\n',
194        DEFAULT_PREVIEW = '<div class="file-preview-other">\n' +
195            '       {previewFileIcon}\n' +
196            '   </div>',
197        defaultFileActionSettings = {
198            removeIcon: '<i class="glyphicon glyphicon-trash text-danger"></i>',
199            removeClass: 'btn btn-xs btn-default',
200            removeTitle: 'Remove file',
201            uploadIcon: '<i class="glyphicon glyphicon-upload text-info"></i>',
202            uploadClass: 'btn btn-xs btn-default',
203            uploadTitle: 'Upload file',
204            indicatorNew: '<i class="glyphicon glyphicon-hand-down text-warning"></i>',
205            indicatorSuccess: '<i class="glyphicon glyphicon-ok-sign file-icon-large text-success"></i>',
206            indicatorError: '<i class="glyphicon glyphicon-exclamation-sign text-danger"></i>',
207            indicatorLoading: '<i class="glyphicon glyphicon-hand-up text-muted"></i>',
208            indicatorNewTitle: 'Not uploaded yet',
209            indicatorSuccessTitle: 'Uploaded',
210            indicatorErrorTitle: 'Upload Error',
211            indicatorLoadingTitle: 'Uploading ...'
212        },
213        tMain1 = '{preview}\n' +
214            '<div class="kv-upload-progress hide"></div>\n' +
215            '<div class="input-group {class}">\n' +
216            '   {caption}\n' +
217            '   <div class="input-group-btn">\n' +
218            '       {remove}\n' +
219            '       {cancel}\n' +
220            '       {upload}\n' +
221            '       {browse}\n' +
222            '   </div>\n' +
223            '</div>',
224        tMain2 = '{preview}\n<div class="kv-upload-progress hide"></div>\n{remove}\n{cancel}\n{upload}\n{browse}\n',
225        tPreview = '<div class="file-preview {class}">\n' +
226            '    <div class="close fileinput-remove">&times;</div>\n' +
227            '    <div class="{dropClass}">\n' +
228            '    <div class="file-preview-thumbnails">\n' +
229            '    </div>\n' +
230            '    <div class="clearfix"></div>' +
231            '    <div class="file-preview-status text-center text-success"></div>\n' +
232            '    <div class="kv-fileinput-error"></div>\n' +
233            '    </div>\n' +
234            '</div>',
235        tIcon = '<span class="glyphicon glyphicon-file kv-caption-icon"></span>',
236        tCaption = '<div tabindex="-1" class="form-control file-caption {class}">\n' +
237            '   <span class="file-caption-ellipsis">&hellip;</span>\n' +
238            '   <div class="file-caption-name"></div>\n' +
239            '</div>',
240        tModal = '<div id="{id}" class="modal fade">\n' +
241            '  <div class="modal-dialog modal-lg">\n' +
242            '    <div class="modal-content">\n' +
243            '      <div class="modal-header">\n' +
244            '        <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>\n' +
245            '        <h3 class="modal-title">Detailed Preview <small>{title}</small></h3>\n' +
246            '      </div>\n' +
247            '      <div class="modal-body">\n' +
248            '        <textarea class="form-control" style="font-family:Monaco,Consolas,monospace; height: {height}px;" readonly>{body}</textarea>\n' +
249            '      </div>\n' +
250            '    </div>\n' +
251            '  </div>\n' +
252            '</div>',
253        tProgress = '<div class="progress">\n' +
254            '    <div class="{class}" role="progressbar"' +
255            ' aria-valuenow="{percent}" aria-valuemin="0" aria-valuemax="100" style="width:{percent}%;">\n' +
256            '        {percent}%\n' +
257            '     </div>\n' +
258            '</div>',
259        tFooter = '<div class="file-thumbnail-footer">\n' +
260            '    <div class="file-caption-name">{caption}</div>\n' +
261            '    {actions}\n' +
262            '</div>',
263        tActions = '<div class="file-actions">\n' +
264            '    <div class="file-footer-buttons">\n' +
265            '        {upload}{delete}{other}' +
266            '    </div>\n' +
267            '    <div class="file-upload-indicator" tabindex="-1" title="{indicatorTitle}">{indicator}</div>\n' +
268            '    <div class="clearfix"></div>\n' +
269            '</div>',
270        tActionDelete = '<button type="button" class="kv-file-remove {removeClass}" ' +
271            'title="{removeTitle}"{dataUrl}{dataKey}>{removeIcon}</button>\n',
272        tActionUpload = '<button type="button" class="kv-file-upload {uploadClass}" title="{uploadTitle}">' +
273            '   {uploadIcon}\n</button>\n',
274        tGeneric = '<div class="file-preview-frame{frameClass}" id="{previewId}" data-fileindex="{fileindex}">\n' +
275            '   {content}\n' +
276            '   {footer}\n' +
277            '</div>\n',
278        tHtml = '<div class="file-preview-frame{frameClass}" id="{previewId}" data-fileindex="{fileindex}">\n' +
279            '    <object data="{data}" type="{type}" width="{width}" height="{height}">\n' +
280            '       ' + DEFAULT_PREVIEW + '\n' +
281            '    </object>\n' +
282            '   {footer}\n' +
283            '</div>',
284        tImage = '<div class="file-preview-frame{frameClass}" id="{previewId}" data-fileindex="{fileindex}">\n' +
285            '   <img src="{data}" class="file-preview-image" title="{caption}" alt="{caption}" ' + STYLE_SETTING + '>\n' +
286            '   {footer}\n' +
287            '</div>\n',
288        tText = '<div class="file-preview-frame{frameClass}" id="{previewId}" data-fileindex="{fileindex}">\n' +
289            '   <div class="file-preview-text" title="{caption}" ' + STYLE_SETTING + '>\n' +
290            '       {data}\n' +
291            '   </div>\n' +
292            '   {footer}\n' +
293            '</div>',
294        tVideo = '<div class="file-preview-frame{frameClass}" id="{previewId}" data-fileindex="{fileindex}"' +
295            ' title="{caption}" ' + STYLE_SETTING + '>\n' +
296            '   <video width="{width}" height="{height}" controls>\n' +
297            '       <source src="{data}" type="{type}">\n' +
298            '       ' + DEFAULT_PREVIEW + '\n' +
299            '   </video>\n' +
300            '   {footer}\n' +
301            '</div>\n',
302        tAudio = '<div class="file-preview-frame{frameClass}" id="{previewId}" data-fileindex="{fileindex}"' +
303            ' title="{caption}" ' + STYLE_SETTING + '>\n' +
304            '   <audio controls>\n' +
305            '       <source src="' + '{data}' + '" type="{type}">\n' +
306            '       ' + DEFAULT_PREVIEW + '\n' +
307            '   </audio>\n' +
308            '   {footer}\n' +
309            '</div>',
310        tFlash = '<div class="file-preview-frame{frameClass}" id="{previewId}" data-fileindex="{fileindex}"' +
311            ' title="{caption}" ' + STYLE_SETTING + '>\n' +
312            '   <object type="application/x-shockwave-flash" width="{width}" height="{height}" data="{data}">\n' +
313            OBJECT_PARAMS + '       ' + DEFAULT_PREVIEW + '\n' +
314            '   </object>\n' +
315            '   {footer}\n' +
316            '</div>\n',
317        tObject = '<div class="file-preview-frame{frameClass}" id="{previewId}" data-fileindex="{fileindex}"' +
318            ' title="{caption}" ' + STYLE_SETTING + '>\n' +
319            '   <object data="{data}" type="{type}" width="{width}" height="{height}">\n' +
320            '       <param name="movie" value="{caption}" />\n' +
321            OBJECT_PARAMS + '         ' + DEFAULT_PREVIEW + '\n' +
322            '   </object>\n' +
323            '   {footer}\n' +
324            '</div>',
325        tOther = '<div class="file-preview-frame{frameClass}" id="{previewId}" data-fileindex="{fileindex}"' +
326            ' title="{caption}" ' + STYLE_SETTING + '>\n' +
327            '   ' + DEFAULT_PREVIEW + '\n' +
328            '   {footer}\n' +
329            '</div>',
330        defaultLayoutTemplates = {
331            main1: tMain1,
332            main2: tMain2,
333            preview: tPreview,
334            icon: tIcon,
335            caption: tCaption,
336            modal: tModal,
337            progress: tProgress,
338            footer: tFooter,
339            actions: tActions,
340            actionDelete: tActionDelete,
341            actionUpload: tActionUpload
342        },
343        defaultPreviewTemplates = {
344            generic: tGeneric,
345            html: tHtml,
346            image: tImage,
347            text: tText,
348            video: tVideo,
349            audio: tAudio,
350            flash: tFlash,
351            object: tObject,
352            other: tOther
353        },
354        defaultPreviewTypes = ['image', 'html', 'text', 'video', 'audio', 'flash', 'object'],
355        defaultPreviewSettings = {
356            image: {width: "auto", height: "160px"},
357            html: {width: "213px", height: "160px"},
358            text: {width: "160px", height: "160px"},
359            video: {width: "213px", height: "160px"},
360            audio: {width: "213px", height: "80px"},
361            flash: {width: "213px", height: "160px"},
362            object: {width: "160px", height: "160px"},
363            other: {width: "160px", height: "160px"}
364        },
365        defaultFileTypeSettings = {
366            image: function (vType, vName) {
367                return (vType !== undefined) ? vType.match('image.*') : vName.match(/\.(gif|png|jpe?g)$/i);
368            },
369            html: function (vType, vName) {
370                return (vType !== undefined) ? vType === 'text/html' : vName.match(/\.(htm|html)$/i);
371            },
372            text: function (vType, vName) {
373                return (vType !== undefined && vType.match('text.*')) || vName.match(/\.(txt|md|csv|nfo|php|ini)$/i);
374            },
375            video: function (vType, vName) {
376                return (vType !== undefined && vType.match(/\.video\/(ogg|mp4|webm)$/i)) || vName.match(/\.(og?|mp4|webm)$/i);
377            },
378            audio: function (vType, vName) {
379                return (vType !== undefined && vType.match(/\.audio\/(ogg|mp3|wav)$/i)) || vName.match(/\.(ogg|mp3|wav)$/i);
380            },
381            flash: function (vType, vName) {
382                return (vType !== undefined && vType === 'application/x-shockwave-flash') || vName.match(/\.(swf)$/i);
383            },
384            object: function () {
385                return true;
386            },
387            other: function () {
388                return true;
389            }
390        },
391        isEmpty = function (value, trim) {
392            return value === null || value === undefined || value.length === 0 || (trim && $.trim(value) === '');
393        },
394        isArray = function (a) {
395            return Array.isArray(a) || Object.prototype.toString.call(a) === '[object Array]';
396        },
397        isSet = function (needle, haystack) {
398            return (typeof haystack === 'object' && needle in haystack);
399        },
400        getElement = function (options, param, value) {
401            return (isEmpty(options) || isEmpty(options[param])) ? value : $(options[param]);
402        },
403        uniqId = function () {
404            return Math.round(new Date().getTime() + (Math.random() * 100));
405        },
406        htmlEncode = function (str) {
407            return String(str).repl('&', '&amp;')
408                .repl('"', '&quot;')
409                .repl("'", '&#39;')
410                .repl('<', '&lt;')
411                .repl('>', '&gt;');
412        },
413        replaceTags = function (str, tags) {
414            var out = str;
415            tags = tags || {};
416            $.each(tags, function (key, value) {
417                if (typeof value === "function") {
418                    value = value();
419                }
420                out = out.repl(key, value);
421            });
422            return out;
423        },
424        objUrl = window.URL || window.webkitURL,
425        FileInput = function (element, options) {
426            this.$element = $(element);
427            if (hasFileAPISupport() || isIE(9)) {
428                this.init(options);
429                this.listen();
430            } else {
431                this.$element.removeClass('file-loading');
432            }
433        };
434
435    FileInput.prototype = {
436        constructor: FileInput,
437        init: function (options) {
438            var self = this, $el = self.$element, t;
439            $.each(options, function (key, value) {
440                if (key === 'maxFileCount' || key === 'maxFileSize') {
441                    self[key] = getNum(value);
442                }
443                self[key] = value;
444            });
445            self.fileInputCleared = false;
446            self.fileBatchCompleted = true;
447            if (isEmpty(self.allowedPreviewTypes)) {
448                self.allowedPreviewTypes = defaultPreviewTypes;
449            }
450            self.uploadFileAttr = !isEmpty($el.attr('name')) ? $el.attr('name') : 'file_data';
451            self.reader = null;
452            self.formdata = {};
453            self.isIE9 = isIE(9);
454            self.isIE10 = isIE(10);
455            self.filestack = [];
456            self.ajaxRequests = [];
457            self.isError = false;
458            self.uploadAborted = false;
459            self.dropZoneEnabled = hasDragDropSupport() && self.dropZoneEnabled;
460            self.isDisabled = self.$element.attr('disabled') || self.$element.attr('readonly');
461            self.isUploadable = hasFileUploadSupport && !isEmpty(self.uploadUrl);
462            self.slug = typeof options.slugCallback === "function" ? options.slugCallback : self.slugDefault;
463            self.mainTemplate = self.showCaption ? self.getLayoutTemplate('main1') : self.getLayoutTemplate('main2');
464            self.captionTemplate = self.getLayoutTemplate('caption');
465            self.previewGenericTemplate = self.getPreviewTemplate('generic');
466            if (isEmpty(self.$element.attr('id'))) {
467                self.$element.attr('id', uniqId());
468            }
469            if (self.$container === undefined) {
470                self.$container = self.createContainer();
471            } else {
472                self.refreshContainer();
473            }
474            self.$progress = self.$container.find('.kv-upload-progress');
475            self.$btnUpload = self.$container.find('.kv-fileinput-upload');
476            self.$captionContainer = getElement(options, 'elCaptionContainer', self.$container.find('.file-caption'));
477            self.$caption = getElement(options, 'elCaptionText', self.$container.find('.file-caption-name'));
478            self.$previewContainer = getElement(options, 'elPreviewContainer', self.$container.find('.file-preview'));
479            self.$preview = getElement(options, 'elPreviewImage', self.$container.find('.file-preview-thumbnails'));
480            self.$previewStatus = getElement(options, 'elPreviewStatus', self.$container.find('.file-preview-status'));
481            self.$errorContainer = getElement(options, 'elErrorContainer',
482                self.$previewContainer.find('.kv-fileinput-error'));
483            if (!isEmpty(self.msgErrorClass)) {
484                addCss(self.$errorContainer, self.msgErrorClass);
485            }
486            self.$errorContainer.hide();
487            self.fileActionSettings = $.extend(defaultFileActionSettings, options.fileActionSettings);
488            self.previewInitId = "preview-" + uniqId();
489            self.id = self.$element.attr('id');
490            previewCache.init(self);
491            self.initPreview(true);
492            self.initPreviewDeletes();
493            self.options = options;
494            self.setFileDropZoneTitle();
495            self.uploadCount = 0;
496            self.uploadPercent = 0;
497            self.$element.removeClass('file-loading');
498            t = self.getLayoutTemplate('progress');
499            self.progressTemplate = t.replace('{class}', self.progressClass);
500            self.progressCompleteTemplate = t.replace('{class}', self.progressCompleteClass);
501            self.setEllipsis();
502        },
503        parseError: function (jqXHR, errorThrown, fileName) {
504            var self = this, errMsg = $.trim(errorThrown + ''),
505                dot = errMsg.slice(-1) === '.' ? '' : '.',
506                text = $(jqXHR.responseText).text();
507            if (self.showAjaxErrorDetails) {
508                text = $.trim(text.replace(/\n\s*\n/g, '\n'));
509                text = text.length > 0 ? '<pre>' + text + '</pre>' : '';
510                errMsg += dot + text;
511            } else {
512                errMsg += dot;
513            }
514            return fileName ? '<b>' + fileName + ': </b>' + jqXHR : errMsg;
515        },
516        raise: function (event, params) {
517            var self = this, e = $.Event(event), out;
518            if (params !== undefined) {
519                self.$element.trigger(e, params);
520            } else {
521                self.$element.trigger(e);
522            }
523            out = e.result || false;
524            if (!out) {
525                return;
526            }
527            switch (event) {
528                // ignore these events
529                case 'filebatchuploadcomplete':
530                case 'filebatchuploadsuccess':
531                case 'fileuploaded':
532                case 'fileclear':
533                case 'filecleared':
534                case 'filereset':
535                case 'fileerror':
536                case 'filefoldererror':
537                case 'fileuploaderror':
538                case 'filebatchuploaderror':
539                case 'filedeleteerror':
540                case 'filecustomerror':
541                    break;
542                // can trigger filecustomerror to abort upload
543                default:
544                    self.uploadAborted = out;
545                    break;
546            }
547        },
548        getLayoutTemplate: function (t) {
549            var self = this,
550                template = isSet(t, self.layoutTemplates) ? self.layoutTemplates[t] : defaultLayoutTemplates[t];
551            if (isEmpty(self.customLayoutTags)) {
552                return template;
553            }
554            return replaceTags(template, self.customLayoutTags);
555        },
556        getPreviewTemplate: function (t) {
557            var self = this,
558                template = isSet(t, self.previewTemplates) ? self.previewTemplates[t] : defaultPreviewTemplates[t];
559            template = template.repl('{previewFileIcon}', self.previewFileIcon);
560            if (isEmpty(self.customPreviewTags)) {
561                return template;
562            }
563            return replaceTags(template, self.customPreviewTags);
564        },
565        getOutData: function (jqXHR, responseData, filesData) {
566            var self = this;
567            jqXHR = jqXHR || {};
568            responseData = responseData || {};
569            filesData = filesData || self.filestack.slice(0) || {};
570            return {
571                form: self.formdata,
572                files: filesData,
573                extra: self.getExtraData(),
574                response: responseData,
575                reader: self.reader,
576                jqXHR: jqXHR
577            };
578        },
579        setEllipsis: function () {
580            var self = this, $capCont = self.$captionContainer, $cap = self.$caption,
581                $div = $cap.clone().css('height', 'auto').hide();
582            $capCont.parent().before($div);
583            $capCont.removeClass('kv-has-ellipsis');
584            if ($div.outerWidth() > $cap.outerWidth()) {
585                $capCont.addClass('kv-has-ellipsis');
586            }
587            $div.remove();
588        },
589        listen: function () {
590            var self = this, $el = self.$element, $cap = self.$captionContainer, $btnFile = self.$btnFile,
591                $form = $el.closest('form');
592            $el.on('change', $.proxy(self.change, self));
593            $(window).on('resize', function () {
594                self.setEllipsis();
595            });
596            $btnFile.off('click').on('click', function () {
597                self.raise('filebrowse');
598                if (self.isError && !self.isUploadable) {
599                    self.clear();
600                }
601                $cap.focus();
602            });
603            $form.off('reset').on('reset', $.proxy(self.reset, self));
604            self.$container.off('click')
605                .on('click', '.fileinput-remove:not([disabled])', $.proxy(self.clear, self))
606                .on('click', '.fileinput-cancel', $.proxy(self.cancel, self));
607            if (self.isUploadable && self.dropZoneEnabled && self.showPreview) {
608                self.initDragDrop();
609            }
610            if (!self.isUploadable) {
611                $form.on('submit', $.proxy(self.submitForm, self));
612            }
613            self.$container.find('.kv-fileinput-upload').off('click').on('click', function (e) {
614                if (!self.isUploadable) {
615                    return;
616                }
617                e.preventDefault();
618                if (!$(this).hasClass('disabled') && isEmpty($(this).attr('disabled'))) {
619                    self.upload();
620                }
621            });
622        },
623        submitForm: function () {
624            var self = this, $el = self.$element, files = $el.get(0).files;
625            if (files && files.length < self.minFileCount && self.minFileCount > 0) {
626                self.noFilesError({});
627                return false;
628            }
629            return !self.abort({});
630        },
631        abort: function (params) {
632            var self = this, data;
633            if (self.uploadAborted && typeof self.uploadAborted === "object" && self.uploadAborted.message !== undefined) {
634                if (self.uploadAborted.data !== undefined) {
635                    data = self.getOutData({}, self.uploadAborted.data);
636                } else {
637                    data = self.getOutData();
638                }
639                data = $.extend(data, params);
640                self.showUploadError(self.uploadAborted.message, data, 'filecustomerror');
641                return true;
642            }
643            return false;
644        },
645        noFilesError: function (params) {
646            var self = this, label = self.minFileCount > 1 ? self.filePlural : self.fileSingle,
647                msg = self.msgFilesTooLess.repl('{n}', self.minFileCount).repl('{files}', label),
648                $error = self.$errorContainer;
649            $error.html(msg);
650            self.isError = true;
651            self.updateFileDetails(0);
652            $error.fadeIn(800);
653            self.raise('fileerror', [params]);
654            self.clearFileInput();
655            addCss(self.$container, 'has-error');
656        },
657        setProgress: function (p) {
658            var self = this, pct = Math.min(p, 100),
659                template = pct < 100 ? self.progressTemplate : self.progressCompleteTemplate;
660            self.$progress.html(template.repl('{percent}', pct));
661        },
662        upload: function () {
663            var self = this, totLen = self.getFileStack().length, params = {},
664                i, outData, len, hasExtraData = !$.isEmptyObject(self.getExtraData());
665            if (totLen < self.minFileCount && self.minFileCount > 0) {
666                self.noFilesError(params);
667                return;
668            }
669            if (!self.isUploadable || self.isDisabled || (totLen === 0 && !hasExtraData)) {
670                return;
671            }
672            self.resetUpload();
673            self.$progress.removeClass('hide');
674            self.uploadCount = 0;
675            self.uploadPercent = 0;
676            self.lock();
677            self.setProgress(0);
678            if (totLen === 0 && hasExtraData) {
679                self.uploadExtraOnly();
680                return;
681            }
682            len = self.filestack.length;
683            self.hasInitData = false;
684            if (self.uploadAsync && self.showPreview) {
685                outData = self.getOutData();
686                self.raise('filebatchpreupload', [outData]);
687                self.fileBatchCompleted = false;
688                self.uploadCache = {content: [], config: [], tags: [], append: true};
689                for (i = 0; i < len; i += 1) {
690                    if (self.filestack[i] !== undefined) {
691                        self.uploadSingle(i, self.filestack, true);
692                    }
693                }
694                return;
695            }
696            self.uploadBatch();
697        },
698        lock: function () {
699            var self = this;
700            self.resetErrors();
701            self.disable();
702            if (self.showRemove) {
703                addCss(self.$container.find('.fileinput-remove'), 'hide');
704            }
705            if (self.showCancel) {
706                self.$container.find('.fileinput-cancel').removeClass('hide');
707            }
708            self.raise('filelock', [self.filestack, self.getExtraData()]);
709        },
710        unlock: function (reset) {
711            var self = this;
712            if (reset === undefined) {
713                reset = true;
714            }
715            self.enable();
716            if (self.showCancel) {
717                addCss(self.$container.find('.fileinput-cancel'), 'hide');
718            }
719            if (self.showRemove) {
720                self.$container.find('.fileinput-remove').removeClass('hide');
721            }
722            if (reset) {
723                self.resetFileStack();
724            }
725            self.raise('fileunlock', [self.filestack, self.getExtraData()]);
726        },
727        resetFileStack: function () {
728            var self = this, i = 0, newstack = [];
729            self.$preview.find(PREVIEW_FRAMES).each(function () {
730                var $thumb = $(this), ind = $thumb.attr('data-fileindex'),
731                    file = self.filestack[ind];
732                if (ind === -1) {
733                    return;
734                }
735                if (file !== undefined) {
736                    newstack[i] = file;
737                    $thumb.attr({
738                        'id': self.previewInitId + '-' + i,
739                        'data-fileindex': i
740                    });
741                    i += 1;
742                } else {
743                    $thumb.attr({
744                        'id': 'uploaded-' + uniqId(),
745                        'data-fileindex': '-1'
746                    });
747                }
748            });
749            self.filestack = newstack;
750        },
751        refresh: function (options) {
752            var self = this, $el = self.$element, $zone,
753                params = (arguments.length) ? $.extend(self.options, options) : self.options;
754            $el.off();
755            self.init(params);
756            $zone = self.$container.find('.file-drop-zone');
757            $zone.off('dragenter dragover drop');
758            $(document).off('dragenter dragover drop');
759            self.listen();
760            self.setFileDropZoneTitle();
761        },
762        initDragDrop: function () {
763            var self = this, $zone = self.$container.find('.file-drop-zone');
764            $zone.off('dragenter dragover drop');
765            $(document).off('dragenter dragover drop');
766            $zone.on('dragenter dragover', function (e) {
767                e.stopPropagation();
768                e.preventDefault();
769                if (self.isDisabled) {
770                    return;
771                }
772                addCss($(this), 'highlighted');
773            });
774            $zone.on('dragleave', function (e) {
775                e.stopPropagation();
776                e.preventDefault();
777                if (self.isDisabled) {
778                    return;
779                }
780                $(this).removeClass('highlighted');
781            });
782            $zone.on('drop', function (e) {
783                e.preventDefault();
784                if (self.isDisabled) {
785                    return;
786                }
787                self.change(e, 'dragdrop');
788                $(this).removeClass('highlighted');
789            });
790            $(document).on('dragenter dragover drop', function (e) {
791                e.stopPropagation();
792                e.preventDefault();
793            });
794        },
795        setFileDropZoneTitle: function () {
796            var self = this, $zone = self.$container.find('.file-drop-zone');
797            $zone.find('.' + self.dropZoneTitleClass).remove();
798            if (!self.isUploadable || !self.showPreview || $zone.length === 0 || self.getFileStack().length > 0 || !self.dropZoneEnabled) {
799                return;
800            }
801            if ($zone.find('.file-preview-frame').length === 0) {
802                $zone.prepend('<div class="' + self.dropZoneTitleClass + '">' + self.dropZoneTitle + '</div>');
803            }
804            self.$container.removeClass('file-input-new');
805            addCss(self.$container, 'file-input-ajax-new');
806        },
807        initFileActions: function () {
808            var self = this;
809            self.$preview.find('.kv-file-remove').each(function () {
810                var $el = $(this), $frame = $el.closest('.file-preview-frame'),
811                    ind = $frame.attr('data-fileindex'), n, cap;
812                $el.off('click').on('click', function () {
813                    $frame.fadeOut('slow', function () {
814                        self.filestack[ind] = undefined;
815                        self.clearObjects($frame);
816                        $frame.remove();
817                        var filestack = self.getFileStack(), len = filestack.length,
818                            chk = previewCache.count(self.id);
819                        self.clearFileInput();
820                        if (len === 0 && chk === 0) {
821                            self.reset();
822                        } else {
823                            n = chk + len;
824                            cap = n > 1 ? self.msgSelected.repl('{n}', n) : filestack[0].name;
825                            self.setCaption(cap);
826                        }
827                    });
828                });
829            });
830            self.$preview.find('.kv-file-upload').each(function () {
831                var $el = $(this);
832                $el.off('click').on('click', function () {
833                    var $frame = $el.closest('.file-preview-frame'),
834                        ind = $frame.attr('data-fileindex');
835                    self.uploadSingle(ind, self.filestack, false);
836                });
837            });
838        },
839        renderFileFooter: function (caption, width) {
840            var self = this, config = self.fileActionSettings, footer, out,
841                template = self.getLayoutTemplate('footer');
842            if (self.isUploadable) {
843                footer = template.repl('{actions}', self.renderFileActions(true, true, false, false, false, false));
844                out = footer.repl('{caption}', caption)
845                    .repl('{width}', width)
846                    .repl('{indicator}', config.indicatorNew)
847                    .repl('{indicatorTitle}', config.indicatorNewTitle);
848            } else {
849                out = template.repl('{actions}', '')
850                    .repl('{caption}', caption)
851                    .repl('{width}', width)
852                    .repl('{indicator}', '')
853                    .repl('{indicatorTitle}', '');
854            }
855            out = replaceTags(out, self.previewThumbTags);
856            return out;
857        },
858        renderFileActions: function (showUpload, showDelete, disabled, url, key, index) {
859            if (!showUpload && !showDelete) {
860                return '';
861            }
862            var self = this,
863                vUrl = url === false ? '' : ' data-url="' + url + '"',
864                vKey = key === false ? '' : ' data-key="' + key + '"',
865                btnDelete = self.getLayoutTemplate('actionDelete'),
866                btnUpload = '',
867                template = self.getLayoutTemplate('actions'),
868                otherButtons = self.otherActionButtons.repl('{dataKey}', vKey),
869                config = self.fileActionSettings,
870                removeClass = disabled ? config.removeClass + ' disabled' : config.removeClass;
871            btnDelete = btnDelete
872                .repl('{removeClass}', removeClass)
873                .repl('{removeIcon}', config.removeIcon)
874                .repl('{removeTitle}', config.removeTitle)
875                .repl('{dataUrl}', vUrl)
876                .repl('{dataKey}', vKey);
877            if (showUpload) {
878                btnUpload = self.getLayoutTemplate('actionUpload')
879                    .repl('{uploadClass}', config.uploadClass)
880                    .repl('{uploadIcon}', config.uploadIcon)
881                    .repl('{uploadTitle}', config.uploadTitle);
882            }
883            return template
884                .repl('{delete}', btnDelete)
885                .repl('{upload}', btnUpload)
886                .repl('{other}', otherButtons);
887        },
888        initPreview: function (isInit) {
889            var self = this, cap, out;
890            if (!previewCache.count(self.id)) {
891                self.$preview.html('');
892                self.setCaption('');
893                return;
894            }
895            out = previewCache.out(self.id);
896            cap = isInit && self.initialCaption ? self.initialCaption : out.caption;
897            self.$preview.html(out.content);
898            self.setCaption(cap);
899            if (!isEmpty(out.content)) {
900                self.$container.removeClass('file-input-new');
901            }
902        },
903        initPreviewDeletes: function () {
904            var self = this, deleteExtraData = self.deleteExtraData || {},
905                resetProgress = function () {
906                    if (self.$preview.find('.kv-file-remove').length === 0) {
907                        self.reset();
908                    }
909                };
910            self.$preview.find('.kv-file-remove').each(function () {
911                var $el = $(this), $frame = $el.closest('.file-preview-frame'),
912                    cache = previewCache.data[self.id], index, config, extraData,
913                    vUrl = $el.data('url') || self.deleteUrl, vKey = $el.data('key'), settings,
914                    params = {id: $el.attr('id'), key: vKey, extra: extraData};
915                if (typeof extraData === "function") {
916                    extraData = extraData();
917                }
918                if (vUrl === undefined || vKey === undefined) {
919                    return;
920                }
921                settings = $.extend({
922                    url: vUrl,
923                    type: 'DELETE',
924                    dataType: 'json',
925                    data: $.extend({key: vKey}, extraData),
926                    beforeSend: function (jqXHR) {
927                        addCss($frame, 'file-uploading');
928                        addCss($el, 'disabled');
929                        self.raise('filepredelete', [vKey, jqXHR, extraData]);
930                    },
931                    success: function (data, textStatus, jqXHR) {
932                        index = parseInt($frame.data('fileindex').replace('init_', ''));
933                        config = isEmpty(cache.config) && isEmpty(cache.config[index]) ? null : cache.config[index];
934                        extraData = isEmpty(config) || isEmpty(config.extra) ? deleteExtraData : config.extra;
935                        if (data === undefined || data.error === undefined) {
936                            previewCache.unset(self.id, index);
937                            self.raise('filedeleted', [vKey, jqXHR, extraData]);
938                        } else {
939                            params.jqXHR = jqXHR;
940                            params.response = data;
941                            self.showError(data.error, params, 'filedeleteerror');
942                            $frame.removeClass('file-uploading');
943                            $el.removeClass('disabled');
944                            resetProgress();
945                            return;
946                        }
947                        $frame.removeClass('file-uploading').addClass('file-deleted');
948                        $frame.fadeOut('slow', function () {
949                            self.clearObjects($frame);
950                            $frame.remove();
951                            resetProgress();
952                            if (!previewCache.count(self.id) && self.getFileStack().length === 0) {
953                                self.reset();
954                            }
955                        });
956                    },
957                    error: function (jqXHR, textStatus, errorThrown) {
958                        var errMsg = self.parseError(jqXHR, errorThrown);
959                        params.jqXHR = jqXHR;
960                        params.response = {};
961                        self.showError(errMsg, params, 'filedeleteerror');
962                        $frame.removeClass('file-uploading');
963                        resetProgress();
964                    }
965                }, self.ajaxDeleteSettings);
966                $el.off('click').on('click', function () {
967                    $.ajax(settings);
968                });
969            });
970        },
971        clearObjects: function ($el) {
972            $el.find('video audio').each(function () {
973                this.pause();
974                $(this).remove();
975            });
976            $el.find('img object div').each(function () {
977                $(this).remove();
978            });
979        },
980        clearFileInput: function () {
981            var self = this, $el = self.$element, $srcFrm, $tmpFrm, $tmpEl;
982            if (isEmpty($el.val())) {
983                return;
984            }
985            // Fix for IE ver < 11, that does not clear file inputs
986            // Requires a sequence of steps to prevent IE crashing but
987            // still allow clearing of the file input.
988            if (self.isIE9 || self.isIE10) {
989                $srcFrm = $el.closest('form');
990                $tmpFrm = $(document.createElement('form'));
991                $tmpEl = $(document.createElement('div'));
992                $el.before($tmpEl);
993                if ($srcFrm.length) {
994                    $srcFrm.after($tmpFrm);
995                } else {
996                    $tmpEl.after($tmpFrm);
997                }
998                $tmpFrm.append($el).trigger('reset');
999                $tmpEl.before($el).remove();
1000                $tmpFrm.remove();
1001            } else { // normal input clear behavior for other sane browsers
1002                $el.val('');
1003            }
1004            self.fileInputCleared = true;
1005        },
1006        resetUpload: function () {
1007            var self = this;
1008            self.uploadCache = {content: [], config: [], tags: [], append: true};
1009            self.uploadCount = 0;
1010            self.uploadPercent = 0;
1011            self.$btnUpload.removeAttr('disabled');
1012            self.setProgress(0);
1013            addCss(self.$progress, 'hide');
1014            self.resetErrors(false);
1015            self.uploadAborted = false;
1016            self.ajaxRequests = [];
1017        },
1018        cancel: function () {
1019            var self = this, xhr = self.ajaxRequests, len = xhr.length, i;
1020            if (len > 0) {
1021                for (i = 0; i < len; i += 1) {
1022                    xhr[i].abort();
1023                }
1024            }
1025            self.$preview.find(PREVIEW_FRAMES).each(function () {
1026                var $thumb = $(this), ind = $thumb.attr('data-fileindex');
1027                $thumb.removeClass('file-uploading');
1028                if (self.filestack[ind] !== undefined) {
1029                    $thumb.find('.kv-file-upload').removeClass('disabled').removeAttr('disabled');
1030                    $thumb.find('.kv-file-remove').removeClass('disabled').removeAttr('disabled');
1031                }
1032                self.unlock();
1033            });
1034        },
1035        clear: function () {
1036            var self = this, cap;
1037            self.$btnUpload.removeAttr('disabled');
1038            self.resetUpload();
1039            self.filestack = [];
1040            self.clearFileInput();
1041            self.resetErrors(true);
1042            self.raise('fileclear');
1043            if (!self.overwriteInitial && previewCache.count(self.id)) {
1044                self.showFileIcon();
1045                self.resetPreview();
1046                self.setEllipsis();
1047                self.initPreviewDeletes();
1048                self.$container.removeClass('file-input-new');
1049            } else {
1050                self.$preview.find(PREVIEW_FRAMES).each(function () {
1051                    self.clearObjects($(this));
1052                });
1053                self.$preview.html('');
1054                cap = (!self.overwriteInitial && self.initialCaption.length > 0) ? self.initialCaption : '';
1055                self.$caption.html(cap);
1056                self.setEllipsis();
1057                self.$caption.attr('title', '');
1058                addCss(self.$container, 'file-input-new');
1059            }
1060            if (self.$container.find('.file-preview-frame').length === 0) {
1061                self.initialCaption = '';
1062                self.$caption.html('');
1063                self.setEllipsis();
1064                self.$captionContainer.find('.kv-caption-icon').hide();
1065            }
1066            self.hideFileIcon();
1067            self.raise('filecleared');
1068            self.$captionContainer.focus();
1069            self.setFileDropZoneTitle();
1070        },
1071        resetPreview: function () {
1072            var self = this, out;
1073            if (previewCache.count(self.id)) {
1074                out = previewCache.out(self.id);
1075                self.$preview.html(out.content);
1076                self.setCaption(out.caption);
1077            } else {
1078                self.$preview.html('');
1079                self.$caption.html('');
1080            }
1081        },
1082        reset: function () {
1083            var self = this;
1084            self.clear();
1085            self.resetPreview();
1086            self.setEllipsis();
1087            self.$container.find('.fileinput-filename').text('');
1088            self.raise('filereset');
1089            if (self.initialPreview.length > 0) {
1090                self.$container.removeClass('file-input-new');
1091            }
1092            self.setFileDropZoneTitle();
1093            self.filestack = [];
1094            self.formdata = {};
1095        },
1096        disable: function () {
1097            var self = this;
1098            self.isDisabled = true;
1099            self.raise('filedisabled');
1100            self.$element.attr('disabled', 'disabled');
1101            self.$container.find(".kv-fileinput-caption").addClass("file-caption-disabled");
1102            self.$container.find(".btn-file, .fileinput-remove, .kv-fileinput-upload").attr("disabled", true);
1103            self.initDragDrop();
1104        },
1105        enable: function () {
1106            var self = this;
1107            self.isDisabled = false;
1108            self.raise('fileenabled');
1109            self.$element.removeAttr('disabled');
1110            self.$container.find(".kv-fileinput-caption").removeClass("file-caption-disabled");
1111            self.$container.find(".btn-file, .fileinput-remove, .kv-fileinput-upload").removeAttr("disabled");
1112            self.initDragDrop();
1113        },
1114        getExtraData: function () {
1115            var self = this, data = self.uploadExtraData;
1116            if (typeof self.uploadExtraData === "function") {
1117                data = self.uploadExtraData();
1118            }
1119            return data;
1120        },
1121        uploadExtra: function () {
1122            var self = this, data = self.getExtraData();
1123            if (data.length === 0) {
1124                return;
1125            }
1126            $.each(data, function (key, value) {
1127                self.formdata.append(key, value);
1128            });
1129        },
1130        initXhr: function (xhrobj, factor) {
1131            var self = this;
1132            if (xhrobj.upload) {
1133                xhrobj.upload.addEventListener('progress', function (event) {
1134                    var pct = 0, position = event.loaded || event.position, total = event.total;
1135                    if (event.lengthComputable) {
1136                        pct = Math.ceil(position / total * factor);
1137                    }
1138                    self.uploadPercent = Math.max(pct, self.uploadPercent);
1139                    self.setProgress(self.uploadPercent);
1140                }, false);
1141            }
1142            return xhrobj;
1143        },
1144        ajaxSubmit: function (fnBefore, fnSuccess, fnComplete, fnError) {
1145            var self = this, settings;
1146            self.uploadExtra();
1147            settings = $.extend({
1148                xhr: function () {
1149                    var xhrobj = $.ajaxSettings.xhr();
1150                    return self.initXhr(xhrobj, 98);
1151                },
1152                url: self.uploadUrl,
1153                type: 'POST',
1154                dataType: 'json',
1155                data: self.formdata,
1156                cache: false,
1157                processData: false,
1158                contentType: false,
1159                beforeSend: fnBefore,
1160                success: fnSuccess,
1161                complete: fnComplete,
1162                error: fnError
1163            }, self.ajaxSettings);
1164            self.ajaxRequests.push($.ajax(settings));
1165        },
1166        initUploadSuccess: function (out, $thumb, allFiles) {
1167            var self = this, append, data, index, $newThumb, content, config, tags;
1168            if (typeof out !== 'object' || $.isEmptyObject(out)) {
1169                return;
1170            }
1171            if (out.initialPreview !== undefined && out.initialPreview.length > 0) {
1172                self.hasInitData = true;
1173                content = out.initialPreview || [];
1174                config = out.initialPreviewConfig || [];
1175                tags = out.initialPreviewThumbTags || [];
1176                append = out.append === undefined || out.append ? true : false;
1177                self.overwriteInitial = false;
1178                if ($thumb !== undefined && !!allFiles) {
1179                    index = previewCache.add(self.id, content, config[0], tags[0], append);
1180                    data = previewCache.get(self.id, index, false);
1181                    $newThumb = $(data).hide();
1182                    $thumb.after($newThumb).fadeOut('slow', function () {
1183                        $newThumb.fadeIn('slow').css('display:inline-block');
1184                        self.initPreviewDeletes();
1185                    });
1186                } else {
1187                    if (allFiles) {
1188                        self.uploadCache.content.push(content[0]);
1189                        self.uploadCache.config.push(config[0]);
1190                        self.uploadCache.tags.push(tags[0]);
1191                        self.uploadCache.append = append;
1192                    } else {
1193                        previewCache.set(self.id, content, config, tags, append);
1194                        self.initPreview();
1195                        self.initPreviewDeletes();
1196                    }
1197                }
1198            }
1199        },
1200        uploadSingle: function (i, files, allFiles) {
1201            var self = this, total = self.getFileStack().length, formdata = new FormData(), outData,
1202                previewId = self.previewInitId + "-" + i, $thumb = $('#' + previewId + ':not(.file-preview-initial)'),
1203                pct, chkComplete, $btnUpload = $thumb.find('.kv-file-upload'), $btnDelete = $thumb.find('.kv-file-remove'),
1204                $indicator = $thumb.find('.file-upload-indicator'), config = self.fileActionSettings,
1205                hasPostData = self.filestack.length > 0 || !$.isEmptyObject(self.uploadExtraData),
1206                setIndicator, updateProgress, resetActions, fnBefore, fnSuccess, fnComplete, fnError,
1207                params = {id: previewId, index: i};
1208            self.formdata = formdata;
1209            if (total === 0 || !hasPostData || $btnUpload.hasClass('disabled') || self.abort(params)) {
1210                return;
1211            }
1212            chkComplete = function () {
1213                var $thumbs = self.$preview.find(PREVIEW_FRAMES + '.file-uploading');
1214                if ($thumbs.length > 0 && self.fileBatchCompleted) {
1215                    return;
1216                }
1217                previewCache.set(self.id, self.uploadCache.content, self.uploadCache.config, self.uploadCache.tags,
1218                    self.uploadCache.append);
1219                if (self.hasInitData) {
1220                    self.initPreview();
1221                    self.initPreviewDeletes();
1222                }
1223                self.setProgress(100);
1224                self.unlock();
1225                self.clearFileInput();
1226                self.raise('filebatchuploadcomplete', [self.filestack, self.getExtraData()]);
1227                self.fileBatchCompleted = true;
1228            };
1229            setIndicator = function (icon, msg) {
1230                $indicator.html(config[icon]);
1231                $indicator.attr('title', config[msg]);
1232            };
1233            updateProgress = function () {
1234                if (!allFiles || total === 0 || self.uploadPercent >= 100) {
1235                    return;
1236                }
1237                self.uploadCount += 1;
1238                pct = 80 + Math.ceil(self.uploadCount * 20 / total);
1239                self.uploadPercent = Math.max(pct, self.uploadPercent);
1240                self.setProgress(self.uploadPercent);
1241                self.initPreviewDeletes();
1242            };
1243            resetActions = function () {
1244                $btnUpload.removeAttr('disabled');
1245                $btnDelete.removeAttr('disabled');
1246                $thumb.removeClass('file-uploading');
1247            };
1248            fnBefore = function (jqXHR) {
1249                outData = self.getOutData(jqXHR);
1250                setIndicator('indicatorLoading', 'indicatorLoadingTitle');
1251                addCss($thumb, 'file-uploading');
1252                $btnUpload.attr('disabled', true);
1253                $btnDelete.attr('disabled', true);
1254                if (!allFiles) {
1255                    self.lock();
1256                }
1257                self.raise('filepreupload', [outData, previewId, i]);
1258                params = $.extend(params, outData);
1259                if (self.abort(params)) {
1260                    jqXHR.abort();
1261                    self.setProgress(100);
1262                }
1263            };
1264            fnSuccess = function (data, textStatus, jqXHR) {
1265                outData = self.getOutData(jqXHR, data);
1266                params = $.extend(params, outData);
1267                setTimeout(function () {
1268                    if (data.error === undefined) {
1269                        setIndicator('indicatorSuccess', 'indicatorSuccessTitle');
1270                        $btnUpload.hide();
1271                        $btnDelete.hide();
1272                        self.filestack[i] = undefined;
1273                        self.raise('fileuploaded', [outData, previewId, i]);
1274                        self.initUploadSuccess(data, $thumb, allFiles);
1275                        if (!allFiles) {
1276                            self.resetFileStack();
1277                        }
1278                    } else {
1279                        setIndicator('indicatorError', 'indicatorErrorTitle');
1280                        self.showUploadError(data.error, params);
1281                    }
1282                }, 100);
1283            };
1284            fnComplete = function () {
1285                setTimeout(function () {
1286                    updateProgress();
1287                    resetActions();
1288                    if (!allFiles) {
1289                        self.unlock(false);
1290                    } else {
1291                        setTimeout(function () {
1292                            chkComplete();
1293                        }, 500);
1294                    }
1295                }, 100);
1296            };
1297            fnError = function (jqXHR, textStatus, errorThrown) {
1298                var errMsg = self.parseError(jqXHR, errorThrown, (allFiles ? files[i].name : null));
1299                setIndicator('indicatorError', 'indicatorErrorTitle');
1300                params = $.extend(params, self.getOutData(jqXHR));
1301                self.showUploadError(errMsg, params);
1302            };
1303            formdata.append(self.uploadFileAttr, files[i]);
1304            formdata.append('file_id', i);
1305            self.ajaxSubmit(fnBefore, fnSuccess, fnComplete, fnError);
1306        },
1307        uploadBatch: function () {
1308            var self = this, files = self.filestack, total = files.length, config,
1309                hasPostData = self.filestack.length > 0 || !$.isEmptyObject(self.uploadExtraData),
1310                setIndicator, setAllUploaded, enableActions, fnBefore, fnSuccess, fnComplete, fnError,
1311                params = {};
1312            self.formdata = new FormData();
1313            if (total === 0 || !hasPostData || self.abort(params)) {
1314                return;
1315            }
1316            config = self.fileActionSettings;
1317            setIndicator = function (i, icon, msg) {
1318                var $indicator = $('#' + self.previewInitId + "-" + i).find('.file-upload-indicator');
1319                $indicator.html(config[icon]);
1320                $indicator.attr('title', config[msg]);
1321            };
1322            enableActions = function (i) {
1323                var $thumb = $('#' + self.previewInitId + "-" + i + ':not(.file-preview-initial)'),
1324                    $btnUpload = $thumb.find('.kv-file-upload'),
1325                    $btnDelete = $thumb.find('.kv-file-delete');
1326                $thumb.removeClass('file-uploading');
1327                $btnUpload.removeAttr('disabled');
1328                $btnDelete.removeAttr('disabled');
1329            };
1330            setAllUploaded = function () {
1331                $.each(files, function (key, data) {
1332                    self.filestack[key] = undefined;
1333                });
1334                self.clearFileInput();
1335            };
1336            fnBefore = function (jqXHR) {
1337                self.lock();
1338                var outData = self.getOutData(jqXHR);
1339                if (self.showPreview) {
1340                    self.$preview.find(PREVIEW_FRAMES).each(function () {
1341                        var $thumb = $(this), $btnUpload = $thumb.find('.kv-file-upload'), $btnDelete = $thumb.find('.kv-file-remove');
1342                        addCss($thumb, 'file-uploading');
1343                        $btnUpload.attr('disabled', true);
1344                        $btnDelete.attr('disabled', true);
1345                    });
1346                }
1347                self.raise('filebatchpreupload', [outData]);
1348                if (self.abort(outData)) {
1349                    jqXHR.abort();
1350                }
1351            };
1352            fnSuccess = function (data, textStatus, jqXHR) {
1353                var outData = self.getOutData(jqXHR, data),
1354                    keys = isEmpty(data.errorkeys) ? [] : data.errorkeys;
1355                if (data.error === undefined || isEmpty(data.error)) {
1356                    self.raise('filebatchuploadsuccess', [outData]);
1357                    setAllUploaded();
1358                    if (self.showPreview) {
1359                        self.$preview.find('.kv-file-upload').hide();
1360                        self.$preview.find('.kv-file-remove').hide();
1361                        self.$preview.find(PREVIEW_FRAMES).each(function () {
1362                            var $thumb = $(this), key = $thumb.attr('data-fileindex');
1363                            setIndicator(key, 'indicatorSuccess', 'indicatorSuccessTitle');
1364                            enableActions(key);
1365                        });
1366                        self.initUploadSuccess(data);
1367                    } else {
1368                        self.reset();
1369                    }
1370                } else {
1371                    if (self.showPreview) {
1372                        self.$preview.find(PREVIEW_FRAMES).each(function () {
1373                            var $thumb = $(this), key = parseInt($thumb.attr('data-fileindex'), 10);
1374                            enableActions(key);
1375                            if (keys.length === 0) {
1376                                setIndicator(key, 'indicatorError', 'indicatorErrorTitle');
1377                                return;
1378                            }
1379                            if ($.inArray(key, keys) !== -1) {
1380                                setIndicator(key, 'indicatorError', 'indicatorErrorTitle');
1381                            } else {
1382                                $thumb.find('.kv-file-upload').hide();
1383                                $thumb.find('.kv-file-remove').hide();
1384                                setIndicator(key, 'indicatorSuccess', 'indicatorSuccessTitle');
1385                                self.filestack[key] = undefined;
1386                            }
1387                        });
1388                        self.initUploadSuccess(data);
1389                    }
1390                    self.showUploadError(data.error, outData, 'filebatchuploaderror');
1391                }
1392            };
1393            fnComplete = function () {
1394                self.setProgress(100);
1395                self.unlock();
1396                self.raise('filebatchuploadcomplete', [self.filestack, self.getExtraData()]);
1397                self.clearFileInput();
1398            };
1399            fnError = function (jqXHR, textStatus, errorThrown) {
1400                var outData = self.getOutData(jqXHR), errMsg = self.parseError(jqXHR, errorThrown);
1401                self.showUploadError(errMsg, outData, 'filebatchuploaderror');
1402                self.uploadFileCount = total - 1;
1403                if (!self.showPreview) {
1404                    return;
1405                }
1406                self.$preview.find(PREVIEW_FRAMES).each(function () {
1407                    var $thumb = $(this), key = $thumb.attr('data-fileindex');
1408                    $thumb.removeClass('file-uploading');
1409                    if (self.filestack[key] !== undefined) {
1410                        setIndicator(key, 'indicatorError', 'indicatorErrorTitle');
1411                    }
1412                });
1413                self.$preview.find(PREVIEW_FRAMES).removeClass('file-uploading');
1414                self.$preview.find(PREVIEW_FRAMES + ' .kv-file-upload').removeAttr('disabled');
1415                self.$preview.find(PREVIEW_FRAMES + ' .kv-file-delete').removeAttr('disabled');
1416            };
1417            $.each(files, function (key, data) {
1418                if (!isEmpty(files[key])) {
1419                    self.formdata.append(self.uploadFileAttr, data);
1420                }
1421            });
1422            self.ajaxSubmit(fnBefore, fnSuccess, fnComplete, fnError);
1423        },
1424        uploadExtraOnly: function () {
1425            var self = this, params = {}, fnBefore, fnSuccess, fnComplete, fnError;
1426            self.formdata = new FormData();
1427            if (self.abort(params)) {
1428                return;
1429            }
1430            fnBefore = function (jqXHR) {
1431                self.lock();
1432                var outData = self.getOutData(jqXHR);
1433                self.raise('filebatchpreupload', [outData]);
1434                self.setProgress(50);
1435                params.data = outData;
1436                params.xhr = jqXHR;
1437                if (self.abort(params)) {
1438                    jqXHR.abort();
1439                    self.setProgress(100);
1440                }
1441            };
1442            fnSuccess = function (data, textStatus, jqXHR) {
1443                var outData = self.getOutData(jqXHR, data);
1444                if (data.error === undefined || isEmpty(data.error)) {
1445                    self.raise('filebatchuploadsuccess', [outData]);
1446                    self.clearFileInput();
1447                    self.initUploadSuccess(data);
1448                } else {
1449                    self.showUploadError(data.error, outData, 'filebatchuploaderror');
1450                }
1451            };
1452            fnComplete = function () {
1453                self.setProgress(100);
1454                self.unlock();
1455                self.raise('filebatchuploadcomplete', [self.filestack, self.getExtraData()]);
1456                self.clearFileInput();
1457            };
1458            fnError = function (jqXHR, textStatus, errorThrown) {
1459                var outData = self.getOutData(jqXHR), errMsg = self.parseError(jqXHR, errorThrown);
1460                params.data = outData;
1461                self.showUploadError(errMsg, outData, 'filebatchuploaderror');
1462            };
1463            self.ajaxSubmit(fnBefore, fnSuccess, fnComplete, fnError);
1464        },
1465        hideFileIcon: function () {
1466            if (this.overwriteInitial) {
1467                this.$captionContainer.find('.kv-caption-icon').hide();
1468            }
1469        },
1470        showFileIcon: function () {
1471            this.$captionContainer.find('.kv-caption-icon').show();
1472        },
1473        resetErrors: function (fade) {
1474            var self = this, $error = self.$errorContainer;
1475            self.isError = false;
1476            self.$container.removeClass('has-error');
1477            $error.html('');
1478            if (fade) {
1479                $error.fadeOut('slow');
1480            } else {
1481                $error.hide();
1482            }
1483        },
1484        showFolderError: function (folders) {
1485            var self = this, $error = self.$errorContainer;
1486            if (!folders) {
1487                return;
1488            }
1489            $error.html(self.msgFoldersNotAllowed.repl('{n}', folders));
1490            $error.fadeIn(800);
1491            addCss(self.$container, 'has-error');
1492            self.raise('filefoldererror', [folders]);
1493        },
1494        showUploadError: function (msg, params, event) {
1495            var self = this, $error = self.$errorContainer, ev = event || 'fileuploaderror';
1496            if ($error.find('ul').length === 0) {
1497                $error.html('<ul><li>' + msg + '</li></ul>');
1498            } else {
1499                $error.find('ul').append('<li>' + msg + '</li>');
1500            }
1501            $error.fadeIn(800);
1502            self.raise(ev, [params]);
1503            addCss(self.$container, 'has-error');
1504            return true;
1505        },
1506        showError: function (msg, params, event) {
1507            var self = this, $error = self.$errorContainer, ev = event || 'fileerror';
1508            params = params || {};
1509            params.reader = self.reader;
1510            $error.html(msg);
1511            $error.fadeIn(800);
1512            self.raise(ev, [params]);
1513            if (!self.isUploadable) {
1514                self.clearFileInput();
1515            }
1516            addCss(self.$container, 'has-error');
1517            self.$btnUpload.attr('disabled', true);
1518            return true;
1519        },
1520        errorHandler: function (evt, caption) {
1521            var self = this, err = evt.target.error;
1522            switch (err.code) {
1523                case err.NOT_FOUND_ERR:
1524                    self.showError(self.msgFileNotFound.repl('{name}', caption));
1525                    break;
1526                case err.SECURITY_ERR:
1527                    self.showError(self.msgFileSecured.repl('{name}', caption));
1528                    break;
1529                case err.NOT_READABLE_ERR:
1530                    self.showError(self.msgFileNotReadable.repl('{name}', caption));
1531                    break;
1532                case err.ABORT_ERR:
1533                    self.showError(self.msgFilePreviewAborted.repl('{name}', caption));
1534                    break;
1535                default:
1536                    self.showError(self.msgFilePreviewError.repl('{name}', caption));
1537            }
1538        },
1539        parseFileType: function (file) {
1540            var self = this, isValid, vType, cat, i;
1541            for (i = 0; i < defaultPreviewTypes.length; i += 1) {
1542                cat = defaultPreviewTypes[i];
1543                isValid = isSet(cat, self.fileTypeSettings) ? self.fileTypeSettings[cat] : defaultFileTypeSettings[cat];
1544                vType = isValid(file.type, file.name) ? cat : '';
1545                if (!isEmpty(vType)) {
1546                    return vType;
1547                }
1548            }
1549            return 'other';
1550        },
1551        previewDefault: function (file, previewId, isDisabled) {
1552            if (!this.showPreview) {
1553                return;
1554            }
1555            var self = this, data = objUrl.createObjectURL(file), $obj = $('#' + previewId),
1556                config = self.previewSettings.other,
1557                footer = self.renderFileFooter(file.name, config.width),
1558                previewOtherTemplate = self.getPreviewTemplate('other'),
1559                ind = previewId.slice(previewId.lastIndexOf('-') + 1),
1560                frameClass = '';
1561            if (isDisabled === true) {
1562                frameClass = ' btn disabled';
1563                footer += '<div class="file-other-error text-danger"><i class="glyphicon glyphicon-exclamation-sign"></i></div>';
1564            }
1565            self.$preview.append("\n" + previewOtherTemplate
1566                .repl('{previewId}', previewId)
1567                .repl('{frameClass}', frameClass)
1568                .repl('{fileindex}', ind)
1569                .repl('{caption}', self.slug(file.name))
1570                .repl('{width}', config.width)
1571                .repl('{height}', config.height)
1572                .repl('{type}', file.type)
1573                .repl('{data}', data)
1574                .repl('{footer}', footer));
1575            $obj.on('load', function () {
1576                objUrl.revokeObjectURL($obj.attr('data'));
1577            });
1578        },
1579        previewFile: function (file, theFile, previewId, data) {
1580            if (!this.showPreview) {
1581                return;
1582            }
1583            var self = this, cat = self.parseFileType(file), caption = self.slug(file.name), content, strText,
1584                types = self.allowedPreviewTypes, mimes = self.allowedPreviewMimeTypes,
1585                tmplt = self.getPreviewTemplate(cat),
1586                config = isSet(cat, self.previewSettings) ? self.previewSettings[cat] : defaultPreviewSettings[cat],
1587                wrapLen = parseInt(self.wrapTextLength, 10), wrapInd = self.wrapIndicator,
1588                chkTypes = types.indexOf(cat) >= 0, id, height,
1589                chkMimes = isEmpty(mimes) || (!isEmpty(mimes) && isSet(file.type, mimes)),
1590                footer = self.renderFileFooter(caption, config.width), modal = '',
1591                ind = previewId.slice(previewId.lastIndexOf('-') + 1);
1592            if (chkTypes && chkMimes) {
1593                if (cat === 'text') {
1594                    strText = htmlEncode(theFile.target.result);
1595                    objUrl.revokeObjectURL(data);
1596                    if (strText.length > wrapLen) {
1597                        id = 'text-' + uniqId();
1598                        height = window.innerHeight * 0.75;
1599                        modal = self.getLayoutTemplate('modal').repl('{id}', id)
1600                            .repl('{title}', caption)
1601                            .repl('{height}', height)
1602                            .repl('{body}', strText);
1603                        wrapInd = wrapInd
1604                            .repl('{title}', caption)
1605                            .repl('{dialog}', "$('#" + id + "').modal('show')");
1606                        strText = strText.substring(0, (wrapLen - 1)) + wrapInd;
1607                    }
1608                    content = tmplt.repl('{previewId}', previewId).repl('{caption}', caption)
1609                        .repl('{frameClass}', '')
1610                        .repl('{type}', file.type).repl('{width}', config.width)
1611                        .repl('{height}', config.height).repl('{data}', strText)
1612                        .repl('{footer}', footer).repl('{fileindex}', ind) + modal;
1613                } else {
1614                    content = tmplt.repl('{previewId}', previewId).repl('{caption}', caption)
1615                        .repl('{frameClass}', '')
1616                        .repl('{type}', file.type).repl('{data}', data)
1617                        .repl('{width}', config.width).repl('{height}', config.height)
1618                        .repl('{footer}', footer).repl('{fileindex}', ind);
1619                }
1620                self.$preview.append("\n" + content);
1621                self.autoSizeImage(previewId);
1622            } else {
1623                self.previewDefault(file, previewId);
1624            }
1625        },
1626        slugDefault: function (text) {
1627            return isEmpty(text) ? '' : text.split(/(\\|\/)/g).pop().replace(/[^\w\-.\\\/ ]+/g, '');
1628        },
1629        getFileStack: function () {
1630            var self = this;
1631            return self.filestack.filter(function (n) {
1632                return n !== undefined;
1633            });
1634        },
1635        readFiles: function (files) {
1636            this.reader = new FileReader();
1637            var self = this, $el = self.$element, $preview = self.$preview, reader = self.reader,
1638                $container = self.$previewContainer, $status = self.$previewStatus, msgLoading = self.msgLoading,
1639                msgProgress = self.msgProgress, previewInitId = self.previewInitId, numFiles = files.length,
1640                settings = self.fileTypeSettings, ctr = self.filestack.length,
1641                throwError = function (msg, file, previewId, index) {
1642                    var p1 = $.extend(self.getOutData({}, {}, files), {id: previewId, index: index}),
1643                        p2 = {id: previewId, index: index, file: file, files: files};
1644                    self.previewDefault(file, previewId, true);
1645                    return self.isUploadable ? self.showUploadError(msg, p1) : self.showError(msg, p2);
1646                };
1647
1648            function readFile(i) {
1649                if (isEmpty($el.attr('multiple'))) {
1650                    numFiles = 1;
1651                }
1652                if (i >= numFiles) {
1653                    if (self.isUploadable && self.filestack.length > 0) {
1654                        self.raise('filebatchselected', [self.getFileStack()]);
1655                    } else {
1656                        self.raise('filebatchselected', [files]);
1657                    }
1658                    $container.removeClass('loading');
1659                    $status.html('');
1660                    return;
1661                }
1662                var node = ctr + i, previewId = previewInitId + "-" + node, isText, file = files[i],
1663                    caption = self.slug(file.name), fileSize = (file.size || 0) / 1000, checkFile, fileExtExpr = '',
1664                    previewData = objUrl.createObjectURL(file), fileCount = 0, j, msg, typ, chk,
1665                    fileTypes = self.allowedFileTypes, strTypes = isEmpty(fileTypes) ? '' : fileTypes.join(', '),
1666                    fileExt = self.allowedFileExtensions, strExt = isEmpty(fileExt) ? '' : fileExt.join(', ');
1667                if (!isEmpty(fileExt)) {
1668                    fileExtExpr = new RegExp('\\.(' + fileExt.join('|') + ')$', 'i');
1669                }
1670                fileSize = fileSize.toFixed(2);
1671                if (self.maxFileSize > 0 && fileSize > self.maxFileSize) {
1672                    msg = self.msgSizeTooLarge.repl('{name}', caption)
1673                        .repl('{size}', fileSize)
1674                        .repl('{maxSize}', self.maxFileSize);
1675                    self.isError = throwError(msg, file, previewId, i);
1676                    return;
1677                }
1678                if (!isEmpty(fileTypes) && isArray(fileTypes)) {
1679                    for (j = 0; j < fileTypes.length; j += 1) {
1680                        typ = fileTypes[j];
1681                        checkFile = settings[typ];
1682                        chk = (checkFile !== undefined && checkFile(file.type, caption));
1683                        fileCount += isEmpty(chk) ? 0 : chk.length;
1684                    }
1685                    if (fileCount === 0) {
1686                        msg = self.msgInvalidFileType.repl('{name}', caption).repl('{types}', strTypes);
1687                        self.isError = throwError(msg, file, previewId, i);
1688                        return;
1689                    }
1690                }
1691                if (fileCount === 0 && !isEmpty(fileExt) && isArray(fileExt) && !isEmpty(fileExtExpr)) {
1692                    chk = caption.match(fileExtExpr);
1693                    fileCount += isEmpty(chk) ? 0 : chk.length;
1694                    if (fileCount === 0) {
1695                        msg = self.msgInvalidFileExtension.repl('{name}', caption).repl('{extensions}',
1696                            strExt);
1697                        self.isError = throwError(msg, file, previewId, i);
1698                        return;
1699                    }
1700                }
1701                if (!self.showPreview) {
1702                    self.filestack.push(file);
1703                    setTimeout(readFile(i + 1), 100);
1704                    self.raise('fileloaded', [file, previewId, i, reader]);
1705                    return;
1706                }
1707                if ($preview.length > 0 && FileReader !== undefined) {
1708                    $status.html(msgLoading.repl('{index}', i + 1).repl('{files}', numFiles));
1709                    $container.addClass('loading');
1710                    reader.onerror = function (evt) {
1711                        self.errorHandler(evt, caption);
1712                    };
1713                    reader.onload = function (theFile) {
1714                        self.previewFile(file, theFile, previewId, previewData);
1715                        self.initFileActions();
1716                    };
1717                    reader.onloadend = function () {
1718                        msg = msgProgress
1719                            .repl('{index}', i + 1).repl('{files}', numFiles)
1720                            .repl('{percent}', 50).repl('{name}', caption);
1721                        setTimeout(function () {
1722                            $status.html(msg);
1723                            objUrl.revokeObjectURL(previewData);
1724                        }, 100);
1725                        setTimeout(function () {
1726                            readFile(i + 1);
1727                            self.updateFileDetails(numFiles);
1728                        }, 100);
1729                        self.raise('fileloaded', [file, previewId, i, reader]);
1730                    };
1731                    reader.onprogress = function (data) {
1732                        if (data.lengthComputable) {
1733                            var fact = (data.loaded / data.total) * 100, progress = Math.ceil(fact);
1734                            msg = msgProgress.repl('{index}', i + 1).repl('{files}', numFiles)
1735                                .repl('{percent}', progress).repl('{name}', caption);
1736                            setTimeout(function () {
1737                                $status.html(msg);
1738                            }, 100);
1739                        }
1740                    };
1741                    isText = isSet('text', settings) ? settings.text : defaultFileTypeSettings.text;
1742                    if (isText(file.type, caption)) {
1743                        reader.readAsText(file, self.textEncoding);
1744                    } else {
1745                        reader.readAsArrayBuffer(file);
1746                    }
1747                } else {
1748                    self.previewDefault(file, previewId);
1749                    setTimeout(function () {
1750                        readFile(i + 1);
1751                        self.updateFileDetails(numFiles);
1752                    }, 100);
1753                    self.raise('fileloaded', [file, previewId, i, reader]);
1754                }
1755                self.filestack.push(file);
1756            }
1757
1758            readFile(0);
1759            self.updateFileDetails(numFiles, false);
1760        },
1761        updateFileDetails: function (numFiles) {
1762            var self = this, msgSelected = self.msgSelected, $el = self.$element, fileStack = self.getFileStack(),
1763                name = $el.val() || (fileStack.length && fileStack[0].name) || '', label = self.slug(name),
1764                n = self.isUploadable ? fileStack.length : numFiles,
1765                nFiles = previewCache.count(self.id) + n,
1766                log = n > 1 ? msgSelected.repl('{n}', nFiles) : label;
1767            if (self.isError) {
1768                self.$previewContainer.removeClass('loading');
1769                self.$previewStatus.html('');
1770                self.$captionContainer.find('.kv-caption-icon').hide();
1771            } else {
1772                self.showFileIcon();
1773            }
1774            self.setCaption(log, self.isError);
1775            self.$container.removeClass('file-input-new file-input-ajax-new');
1776            if (arguments.length === 1) {
1777                self.raise('fileselect', [numFiles, label]);
1778            }
1779            if (previewCache.count(self.id)) {
1780                self.initPreviewDeletes();
1781            }
1782        },
1783        change: function (e) {
1784            var self = this, $el = self.$element;
1785            if (!self.isUploadable && isEmpty($el.val()) && self.fileInputCleared) { // IE 11 fix
1786                self.fileInputCleared = false;
1787                return;
1788            }
1789            self.fileInputCleared = false;
1790            var tfiles, msg, total, $preview = self.$preview, isDragDrop = arguments.length > 1,
1791                files = isDragDrop ? e.originalEvent.dataTransfer.files : $el.get(0).files,
1792                isSingleUpload = isEmpty($el.attr('multiple')), i = 0, f, m, folders = 0,
1793                ctr = self.filestack.length, isAjaxUpload = self.isUploadable,
1794                throwError = function (mesg, file, previewId, index) {
1795                    var p1 = $.extend(self.getOutData({}, {}, files), {id: previewId, index: index}),
1796                        p2 = {id: previewId, index: index, file: file, files: files};
1797                    return self.isUploadable ? self.showUploadError(mesg, p1) : self.showError(mesg, p2);
1798                };
1799            self.reader = null;
1800            self.resetUpload();
1801            self.hideFileIcon();
1802            if (self.isUploadable) {
1803                self.$container.find('.file-drop-zone .' + self.dropZoneTitleClass).remove();
1804            }
1805            if (isDragDrop) {
1806                tfiles = [];
1807                while (files[i]) {
1808                    f = files[i];
1809                    if (!f.type && f.size % 4096 === 0) {
1810                        folders++;
1811                    } else {
1812                        tfiles.push(f);
1813                    }
1814                    i++;
1815                }
1816            } else {
1817                if (e.target.files === undefined) {
1818                    tfiles = e.target && e.target.value ? [
1819                        {name: e.target.value.replace(/^.+\\/, '')}
1820                    ] : [];
1821                } else {
1822                    tfiles = e.target.files;
1823                }
1824            }
1825            if (isEmpty(tfiles) || tfiles.length === 0) {
1826                if (!isAjaxUpload) {
1827                    self.clear();
1828                }
1829                self.showFolderError(folders);
1830                self.raise('fileselectnone');
1831                return;
1832            }
1833            self.resetErrors();
1834            if (!isAjaxUpload || (isSingleUpload && ctr > 0)) {
1835                if (!self.overwriteInitial && previewCache.count(self.id)) {
1836                    var out = previewCache.out(self.id);
1837                    $preview.html(out.content);
1838                    self.setCaption(out.caption);
1839                    self.initPreviewDeletes();
1840                } else {
1841                    $preview.html('');
1842                }
1843
1844                if (isSingleUpload && ctr > 0) {
1845                    self.filestack = [];
1846                }
1847            }
1848            total = self.isUploadable ? self.getFileStack().length + tfiles.length : tfiles.length;
1849            if (self.maxFileCount > 0 && total > self.maxFileCount) {
1850                msg = self.msgFilesTooMany.repl('{m}', self.maxFileCount).repl('{n}', total);
1851                self.isError = throwError(msg, null, null, null);
1852                self.$captionContainer.find('.kv-caption-icon').hide();
1853                self.$caption.html(self.msgValidationError);
1854                self.setEllipsis();
1855                self.$container.removeClass('file-input-new file-input-ajax-new');
1856                return;
1857            }
1858            if (!self.isIE9) {
1859                self.readFiles(tfiles);
1860            } else {
1861                self.updateFileDetails(1);
1862            }
1863            self.showFolderError(folders);
1864        },
1865        autoSizeImage: function (previewId) {
1866            var self = this, $preview = self.$preview,
1867                $thumb = $preview.find("#" + previewId),
1868                $img = $thumb.find('img'), w1, w2, $cap;
1869            if (!$img.length) {
1870                return;
1871            }
1872            $img.on('load', function () {
1873                w1 = $thumb.width();
1874                w2 = $preview.width();
1875                if (w1 > w2) {
1876                    $img.css('width', '100%');
1877                    $thumb.css('width', '97%');
1878                }
1879                $cap = $img.closest('.file-preview-frame').find('.file-caption-name');
1880                if ($cap.length) {
1881                    $cap.width($img.width());
1882                    $cap.attr('title', $cap.text());
1883                }
1884                self.raise('fileimageloaded', previewId);
1885            });
1886        },
1887        setCaption: function (content, isError) {
1888            var self = this, err = isError || false, title, out;
1889            if (isEmpty(content) || self.$caption.length === 0) {
1890                return;
1891            }
1892            if (err) {
1893                title = $('<div>' + self.msgValidationError + '</div>').text();
1894                out = '<span class="' + self.msgValidationErrorClass + '">' +
1895                self.msgValidationErrorIcon + title + '</span>';
1896            } else {
1897                title = $('<div>' + content + '</div>').text();
1898                out = title + self.getLayoutTemplate('icon');
1899            }
1900            self.$caption.html(out);
1901            self.$caption.attr('title', title);
1902            self.$captionContainer.find('.file-caption-ellipsis').attr('title', title);
1903            self.setEllipsis();
1904        },
1905        initBrowse: function ($container) {
1906            var self = this;
1907            self.$btnFile = $container.find('.btn-file');
1908            self.$btnFile.append(self.$element);
1909        },
1910        createContainer: function () {
1911            var self = this,
1912                $container = $(document.createElement("span"))
1913                    .attr({"class": 'file-input file-input-new'})
1914                    .html(self.renderMain());
1915            self.$element.before($container);
1916            self.initBrowse($container);
1917            return $container;
1918        },
1919        refreshContainer: function () {
1920            var self = this, $container = self.$container;
1921            $container.before(self.$element);
1922            $container.html(self.renderMain());
1923            self.initBrowse($container);
1924        },
1925        renderMain: function () {
1926            var self = this, dropCss = (self.isUploadable && self.dropZoneEnabled) ? ' file-drop-zone' : '',
1927                preview = self.showPreview ? self.getLayoutTemplate('preview').repl('{class}', self.previewClass)
1928                    .repl('{dropClass}', dropCss) : '',
1929                css = self.isDisabled ? self.captionClass + ' file-caption-disabled' : self.captionClass,
1930                caption = self.captionTemplate.repl('{class}', css + ' kv-fileinput-caption');
1931            return self.mainTemplate.repl('{class}', self.mainClass)
1932                .repl('{preview}', preview)
1933                .repl('{caption}', caption)
1934                .repl('{upload}', self.renderUpload())
1935                .repl('{remove}', self.renderRemove())
1936                .repl('{cancel}', self.renderCancel())
1937                .repl('{browse}', self.renderBrowse());
1938        },
1939        renderBrowse: function () {
1940            var self = this, css = self.browseClass + ' btn-file', status = '';
1941            if (self.isDisabled) {
1942                status = ' disabled ';
1943            }
1944            return '<div class="' + css + '"' + status + '> ' + self.browseIcon + self.browseLabel + ' </div>';
1945        },
1946        renderRemove: function () {
1947            var self = this, css = self.removeClass + ' fileinput-remove fileinput-remove-button', status = '';
1948            if (!self.showRemove) {
1949                return '';
1950            }
1951            if (self.isDisabled) {
1952                status = ' disabled ';
1953            }
1954            return '<button type="button" title="' + self.removeTitle + '" class="' + css + '"' + status + '>' + self.removeIcon + self.removeLabel + '</button>';
1955        },
1956        renderCancel: function () {
1957            var self = this, css = self.cancelClass + ' fileinput-cancel fileinput-cancel-button';
1958            if (!self.showCancel) {
1959                return '';
1960            }
1961            return '<button type="button" title="' + self.cancelTitle + '" class="hide ' + css + '">' + self.cancelIcon + self.cancelLabel + '</button>';
1962        },
1963        renderUpload: function () {
1964            var self = this, css = self.uploadClass + ' kv-fileinput-upload fileinput-upload-button', content = '', status = '';
1965            if (!self.showUpload) {
1966                return '';
1967            }
1968            if (self.isDisabled) {
1969                status = ' disabled ';
1970            }
1971            if (!self.isUploadable || self.isDisabled) {
1972                content = '<button type="submit" title="' + self.uploadTitle + '"class="' + css + '"' + status + '>' + self.uploadIcon + self.uploadLabel + '</button>';
1973            } else {
1974                content = '<a href="' + self.uploadUrl + '" title="' + self.uploadTitle + '" class="' + css + '"' + status + '>' + self.uploadIcon + self.uploadLabel + '</a>';
1975            }
1976            return content;
1977        }
1978    };
1979
1980    //FileInput plugin definition
1981    $.fn.fileinput = function (option) {
1982        if (!hasFileAPISupport() && !isIE(9)) {
1983            return;
1984        }
1985
1986        var args = Array.apply(null, arguments);
1987        args.shift();
1988        return this.each(function () {
1989            var $this = $(this),
1990                data = $this.data('fileinput'),
1991                options = typeof option === 'object' && option;
1992
1993            if (!data) {
1994                data = new FileInput(this, $.extend({}, $.fn.fileinput.defaults, options, $(this).data()));
1995                $this.data('fileinput', data);
1996            }
1997
1998            if (typeof option === 'string') {
1999                data[option].apply(data, args);
2000            }
2001        });
2002    };
2003
2004    $.fn.fileinput.defaults = {
2005        showCaption: true,
2006        showPreview: true,
2007        showRemove: true,
2008        showUpload: true,
2009        showCancel: true,
2010        mainClass: '',
2011        previewClass: '',
2012        captionClass: '',
2013        mainTemplate: null,
2014        initialCaption: '',
2015        initialPreview: [],
2016        initialPreviewDelimiter: '*$$*',
2017        initialPreviewConfig: [],
2018        initialPreviewThumbTags: [],
2019        previewThumbTags: {},
2020        initialPreviewShowDelete: true,
2021        deleteUrl: '',
2022        deleteExtraData: {},
2023        overwriteInitial: true,
2024        layoutTemplates: defaultLayoutTemplates,
2025        previewTemplates: defaultPreviewTemplates,
2026        allowedPreviewTypes: defaultPreviewTypes,
2027        allowedPreviewMimeTypes: null,
2028        allowedFileTypes: null,
2029        allowedFileExtensions: null,
2030        customLayoutTags: {},
2031        customPreviewTags: {},
2032        previewSettings: defaultPreviewSettings,
2033        fileTypeSettings: defaultFileTypeSettings,
2034        previewFileIcon: '<i class="glyphicon glyphicon-file"></i>',
2035        browseIcon: '<i class="glyphicon glyphicon-folder-open"></i> &nbsp;',
2036        browseClass: 'btn btn-primary',
2037        removeIcon: '<i class="glyphicon glyphicon-trash"></i> ',
2038        removeClass: 'btn btn-default',
2039        cancelIcon: '<i class="glyphicon glyphicon-ban-circle"></i> ',
2040        cancelClass: 'btn btn-default',
2041        uploadIcon: '<i class="glyphicon glyphicon-upload"></i> ',
2042        uploadClass: 'btn btn-default',
2043        uploadUrl: null,
2044        uploadAsync: true,
2045        uploadExtraData: {},
2046        maxFileSize: 0,
2047        minFileCount: 0,
2048        maxFileCount: 0,
2049        msgValidationErrorClass: 'text-danger',
2050        msgValidationErrorIcon: '<i class="glyphicon glyphicon-exclamation-sign"></i> ',
2051        msgErrorClass: 'file-error-message',
2052        progressClass: "progress-bar progress-bar-success progress-bar-striped active",
2053        progressCompleteClass: "progress-bar progress-bar-success",
2054        previewFileType: 'image',
2055        wrapTextLength: 250,
2056        wrapIndicator: ' <span class="wrap-indicator" title="{title}" onclick="{dialog}">[&hellip;]</span>',
2057        elCaptionContainer: null,
2058        elCaptionText: null,
2059        elPreviewContainer: null,
2060        elPreviewImage: null,
2061        elPreviewStatus: null,
2062        elErrorContainer: null,
2063        slugCallback: null,
2064        dropZoneEnabled: true,
2065        dropZoneTitleClass: 'file-drop-zone-title',
2066        fileActionSettings: {},
2067        otherActionButtons: '',
2068        textEncoding: 'UTF-8',
2069        ajaxSettings: {},
2070        ajaxDeleteSettings: {},
2071        showAjaxErrorDetails: true
2072    };
2073
2074    $.fn.fileinput.locales = {};
2075
2076    $.fn.fileinput.locales.en = {
2077        fileSingle: 'file',
2078        filePlural: 'files',
2079        browseLabel: 'Browse &hellip;',
2080        removeLabel: 'Remove',
2081        removeTitle: 'Clear selected files',
2082        cancelLabel: 'Cancel',
2083        cancelTitle: 'Abort ongoing upload',
2084        uploadLabel: 'Upload',
2085        uploadTitle: 'Upload selected files',
2086        msgSizeTooLarge: 'File "{name}" (<b>{size} KB</b>) exceeds maximum allowed upload size of <b>{maxSize} KB</b>. Please retry your upload!',
2087        msgFilesTooLess: 'You must select at least <b>{n}</b> {files} to upload. Please retry your upload!',
2088        msgFilesTooMany: 'Number of files selected for upload <b>({n})</b> exceeds maximum allowed limit of <b>{m}</b>. Please retry your upload!',
2089        msgFileNotFound: 'File "{name}" not found!',
2090        msgFileSecured: 'Security restrictions prevent reading the file "{name}".',
2091        msgFileNotReadable: 'File "{name}" is not readable.',
2092        msgFilePreviewAborted: 'File preview aborted for "{name}".',
2093        msgFilePreviewError: 'An error occurred while reading the file "{name}".',
2094        msgInvalidFileType: 'Invalid type for file "{name}". Only "{types}" files are supported.',
2095        msgInvalidFileExtension: 'Invalid extension for file "{name}". Only "{extensions}" files are supported.',
2096        msgValidationError: 'File Upload Error',
2097        msgLoading: 'Loading file {index} of {files} &hellip;',
2098        msgProgress: 'Loading file {index} of {files} - {name} - {percent}% completed.',
2099        msgSelected: '{n} files selected',
2100        msgFoldersNotAllowed: 'Drag & drop files only! {n} folder(s) dropped were skipped.',
2101        dropZoneTitle: 'Drag & drop files here &hellip;'
2102    };
2103
2104    $.extend($.fn.fileinput.defaults, $.fn.fileinput.locales.en);
2105
2106    $.fn.fileinput.Constructor = FileInput;
2107
2108    /**
2109     * Convert automatically file inputs with class 'file'
2110     * into a bootstrap fileinput control.
2111     */
2112    $(document).ready(function () {
2113        var $input = $('input.file[type=file]'), count = $input.attr('type') ? $input.length : 0;
2114        if (count > 0) {
2115            $input.fileinput();
2116        }
2117    });
2118})(window.jQuery);
Note: See TracBrowser for help on using the repository browser.