PageSourceSearch

https://enertrak.com/vendors/fileinput/js/fileinput.js

js enertrak.com collected 2026-10-04 02:00:30 UTC 158,744 bytes, 3,386 lines download raw bytes

1/*!
2 * bootstrap-fileinput v4.3.6
3 * http://plugins.krajee.com/file-input
4 *
5 * Author: Kartik Visweswaran
6 * Copyright: 2014 - 2016, Kartik Visweswaran, Krajee.com
7 *
8 * Licensed under the BSD 3-Clause
9 * https://github.com/kartik-v/bootstrap-fileinput/blob/master/LICENSE.md
10 */
11(function (factory) {
12    "use strict";
13    //noinspection JSUnresolvedVariable
14    if (typeof define === 'function' && define.amd) { // jshint ignore:line
15        // AMD. Register as an anonymous module.
16        define(['jquery'], factory); // jshint ignore:line
17    } else { // noinspection JSUnresolvedVariable
18        if (typeof module === 'object' && module.exports) { // jshint ignore:line
19            // Node/CommonJS
20            // noinspection JSUnresolvedVariable
21            module.exports = factory(require('jquery')); // jshint ignore:line
22        } else {
23            // Browser globals
24            factory(window.jQuery);
25        }
26    }
27}(function ($) {
28    "use strict";
29
30    $.fn.fileinputLocales = {};
31    $.fn.fileinputThemes = {};
32
33    var NAMESPACE, MODAL_ID, STYLE_SETTING, OBJECT_PARAMS, DEFAULT_PREVIEW, objUrl, compare, isIE, handler,
34        previewCache, getNum, hasFileAPISupport, hasDragDropSupport, hasFileUploadSupport, addCss, tMain1, tMain2,
35        tPreview, tFileIcon, tClose, tCaption, tBtnDefault, tBtnLink, tBtnBrowse, tModalMain, tModal, tProgress, tSize,
36        tFooter, tActions, tActionDelete, tActionUpload, tActionZoom, tActionDrag, tTagBef, tTagBef1, tTagBef2, tTagAft,
37        tGeneric, tHtml, tImage, tText, tVideo, tAudio, tFlash, tObject, tPdf, tOther, defaultFileActionSettings,
38        defaultLayoutTemplates, defaultPreviewTemplates, defaultPreviewZoomSettings, defaultPreviewTypes, getElement,
39        defaultPreviewSettings, defaultFileTypeSettings, isEmpty, isArray, ifSet, uniqId, htmlEncode, replaceTags,
40        cleanMemory, findFileName, checkFullScreen, toggleFullScreen, moveArray, FileInput;
41
42    NAMESPACE = '.fileinput';
43    MODAL_ID = 'kvFileinputModal';
44    STYLE_SETTING = 'style="width:{width};height:{height};"';
45    OBJECT_PARAMS = '<param name="controller" value="true" />\n' +
46        '<param name="allowFullScreen" value="true" />\n' +
47        '<param name="allowScriptAccess" value="always" />\n' +
48        '<param name="autoPlay" value="false" />\n' +
49        '<param name="autoStart" value="false" />\n' +
50        '<param name="quality" value="high" />\n';
51    DEFAULT_PREVIEW = '<div class="file-preview-other">\n' +
52        '<span class="{previewFileIconClass}">{previewFileIcon}</span>\n' +
53        '</div>';
54    //noinspection JSUnresolvedVariable
55    objUrl = window.URL || window.webkitURL;
56    compare = function (input, str, exact) {
57        return input !== undefined && (exact ? input === str : input.match(str));
58    };
59    isIE = function (ver) {
60        // check for IE versions < 11
61        if (navigator.appName !== 'Microsoft Internet Explorer') {
62            return false;
63        }
64        if (ver === 10) {
65            return new RegExp('msie\\s' + ver, 'i').test(navigator.userAgent);
66        }
67        var div = document.createElement("div"), status;
68        div.innerHTML = "<!--[if IE " + ver + "]> <i></i> <![endif]-->";
69        status = div.getElementsByTagName("i").length;
70        document.body.appendChild(div);
71        div.parentNode.removeChild(div);
72        return status;
73    };
74    handler = function ($el, event, callback, skipNS) {
75        var ev = skipNS ? event : event.split(' ').join(NAMESPACE + ' ') + NAMESPACE;
76        $el.off(ev).on(ev, callback);
77    };
78    previewCache = {
79        data: {},
80        init: function (obj) {
81            var content = obj.initialPreview, id = obj.id;
82            if (content.length > 0 && !isArray(content)) {
83                content = content.split(obj.initialPreviewDelimiter);
84            }
85            previewCache.data[id] = {
86                content: content,
87                config: obj.initialPreviewConfig,
88                tags: obj.initialPreviewThumbTags,
89                delimiter: obj.initialPreviewDelimiter,
90                previewFileType: obj.initialPreviewFileType,
91                previewAsData: obj.initialPreviewAsData,
92                template: obj.previewGenericTemplate,
93                showZoom: obj.fileActionSettings.showZoom,
94                showDrag: obj.fileActionSettings.showDrag,
95                getSize: function (size) {
96                    return obj._getSize(size);
97                },
98                parseTemplate: function (cat, data, fname, ftype, pId, ftr, ind) {
99                    var fc = ' file-preview-initial';
100                    return obj._generatePreviewTemplate(cat, data, fname, ftype, pId, false, null, fc, ftr, ind);
101                },
102                msg: function (n) {
103                    return obj._getMsgSelected(n);
104                },
105                initId: obj.previewInitId,
106                footer: obj._getLayoutTemplate('footer').replace(/\{progress}/g, obj._renderThumbProgress()),
107                isDelete: obj.initialPreviewShowDelete,
108                caption: obj.initialCaption,
109                actions: function (showUpload, showDelete, showZoom, showDrag, disabled, url, key) {
110                    return obj._renderFileActions(showUpload, showDelete, showZoom, showDrag, disabled, url, key, true);
111                }
112            };
113        },
114        fetch: function (id) {
115            return previewCache.data[id].content.filter(function (n) {
116                return n !== null;
117            });
118        },
119        count: function (id, all) {
120            return !!previewCache.data[id] && !!previewCache.data[id].content ?
121                (all ? previewCache.data[id].content.length : previewCache.fetch(id).length) : 0;
122        },
123        get: function (id, i, isDisabled) {
124            var ind = 'init_' + i, data = previewCache.data[id], config = data.config[i], content = data.content[i],
125                previewId = data.initId + '-' + ind, out, $tmp, frameClass = ' file-preview-initial', cat, cap, ftr,
126                ftype, asData = ifSet('previewAsData', config, data.previewAsData);
127            isDisabled = isDisabled === undefined ? true : isDisabled;
128            /** @namespace config.frameAttr */
129            /** @namespace config.frameClass */
130            /** @namespace config.filetype */
131            if (!content) {
132                return '';
133            }
134            if (config && config.frameClass) {
135                frameClass += ' ' + config.frameClass;
136            }
137            if (asData) {
138                cat = data.previewAsData ? ifSet('type', config, data.previewFileType || 'generic') : 'generic';
139                cap = ifSet('caption', config);
140                ftr = previewCache.footer(id, i, isDisabled, (config && config.size || null));
141                ftype = ifSet('filetype', config, cat);
142                out = data.parseTemplate(cat, content, cap, ftype, previewId, ftr, ind, null);
143            } else {
144                out = data.template
145                    .replace(/\{previewId}/g, previewId).replace(/\{frameClass}/g, frameClass)
146                    .replace(/\{fileindex}
146/g, ind).replace(/\{content}/g, data.content[i])
147                    .replace(/\{template}/g, ifSet('type', config, data.previewFileType))
148                    .replace(/\{footer}/g, previewCache.footer(id, i, isDisabled, (config && config.size || null)));
149            }
150            if (data.tags.length && data.tags[i]) {
151                out = replaceTags(out, data.tags[i]);
152            }
153            if (!isEmpty(config) && !isEmpty(config.frameAttr)) {
154                $tmp = $(document.createElement('div')).html(out);
155                $tmp.find('.file-preview-initial').attr(config.frameAttr);
156                out = $tmp.html();
157                $tmp.remove();
158            }
159            return out;
160        },
161        add: function (id, content, config, tags, append) {
162            var data = $.extend(true, {}, previewCache.data[id]), index;
163            if (!isArray(content)) {
164                content = content.split(data.delimiter);
165            }
166            if (append) {
167                index = data.content.push(content) - 1;
168                data.config[index] = config;
169                data.tags[index] = tags;
170            } else {
171                index = content.length - 1;
172                data.content = content;
173                data.config = config;
174                data.tags = tags;
175            }
176            previewCache.data[id] = data;
177            return index;
178        },
179        set: function (id, content, config, tags, append) {
180            var data = $.extend(true, {}, previewCache.data[id]), i, chk;
181            if (!content || !content.length) {
182                return;
183            }
184            if (!isArray(content)) {
185                content = content.split(data.delimiter);
186            }
187            chk = content.filter(function (n) {
188                return n !== null;
189            });
190            if (!chk.length) {
191                return;
192            }
193            if (data.content === undefined) {
194                data.content = [];
195            }
196            if (data.config === undefined) {
197                data.config = [];
198            }
199            if (data.tags === undefined) {
200                data.tags = [];
201            }
202            if (append) {
203                for (i = 0; i < content.length; i++) {
204                    if (content[i]) {
205                        data.content.push(content[i]);
206                    }
207                }
208                for (i = 0; i < config.length; i++) {
209                    if (config[i]) {
210                        data.config.push(config[i]);
211                    }
212                }
213                for (i = 0; i < tags.length; i++) {
214                    if (tags[i]) {
215                        data.tags.push(tags[i]);
216                    }
217                }
218            } else {
219                data.content = content;
220                data.config = config;
221                data.tags = tags;
222            }
223            previewCache.data[id] = data;
224        },
225        unset: function (obj, index) {
226            var chk = previewCache.count(obj.id);
227            if (!chk) {
228                return;
229            }
230            if (chk === 1) {
231                previewCache.data[obj.id].content = [];
232                previewCache.data[obj.id].config = [];
233                previewCache.data[obj.id].tags = [];
234                obj.initialPreview = [];
235                obj.initialPreviewConfig = [];
236                obj.initialPreviewThumbTags = [];
237                return;
238            }
239
240            previewCache.data[obj.id].content[index] = null;
241            previewCache.data[obj.id].config[index] = null;
242            previewCache.data[obj.id].tags[index] = null;
243        },
244        out: function (id) {
245            var html = '', data = previewCache.data[id], caption, len = previewCache.count(id, true);
246            if (len === 0) {
247                return {content: '', caption: ''};
248            }
249            for (var i = 0; i < len; i++) {
250                html += previewCache.get(id, i);
251            }
252            caption = data.msg(previewCache.count(id));
253            return {content: '<div class="file-initial-thumbs">' + html + '</div>', caption: caption};
254        },
255        footer: function (id, i, isDisabled, size) {
256            var data = previewCache.data[id];
257            isDisabled = isDisabled === undefined ? true : isDisabled;
258            if (data.config.length === 0 || isEmpty(data.config[i])) {
259                return '';
260            }
261            var config = data.config[i], caption = ifSet('caption', config), width = ifSet('width', config, 'auto'),
262                url = ifSet('url', config, false), key = ifSet('key', config, null),
263                showDel = ifSet('showDelete', config, true), showZoom = ifSet('showZoom', config, data.showZoom),
264                showDrag = ifSet('showDrag', config, data.showDrag), disabled = (url === false) && isDisabled,
265                actions = data.isDelete ? data.actions(false, showDel, showZoom, showDrag, disabled, url, key) : '',
266                footer = data.footer.replace(/\{actions}/g, actions);
267            return footer.replace(/\{caption}/g, caption).replace(/\{size}/g, data.getSize(size))
268                .replace(/\{width}/g, width).replace(/\{indicator}/g, '').replace(/\{indicatorTitle}/g, '');
269        }
270    };
271    getNum = function (num, def) {
272        def = def || 0;
273        if (typeof num === "number") {
274            return num;
275        }
276        if (typeof num === "string") {
277            num = parseFloat(num);
278        }
279        return isNaN(num) ? def : num;
280    };
281    hasFileAPISupport = function () {
282        return !!(window.File && window.FileReader);
283    };
284    hasDragDropSupport = function () {
285        var div = document.createElement('div');
286        /** @namespace div.draggable */
287        /** @namespace div.ondragstart */
288        /** @namespace div.ondrop */
289        return !isIE(9) && (div.draggable !== undefined || (div.ondragstart !== undefined && div.ondrop !== undefined));
290    };
291    hasFileUploadSupport = function () {
292        return hasFileAPISupport() && window.FormData;
293    };
294    addCss = function ($el, css) {
295        $el.removeClass(css).addClass(css);
296    };
297    defaultFileActionSettings = {
298        showRemove: true,
299        showUpload: true,
300        showZoom: true,
301        showDrag: true,
302        removeIcon: '<i class="glyphicon glyphicon-trash text-danger"></i>',
303        removeClass: 'btn btn-xs btn-default',
304        removeTitle: 'Remove file',
305        uploadIcon: '<i class="glyphicon glyphicon-upload text-info"></i>',
306        uploadClass: 'btn btn-xs btn-default',
307        uploadTitle: 'Upload file',
308        zoomIcon: '<i class="glyphicon glyphicon-zoom-in"></i>',
309        zoomClass: 'btn btn-xs btn-default',
310        zoomTitle: 'View Details',
311        dragIcon: '<i class="glyphicon glyphicon-menu-hamburger"></i>',
312        dragClass: 'text-info',
313        dragTitle: 'Move / Rearrange',
314        dragSettings: {},
315        indicatorNew: '<i class="glyphicon glyphicon-hand-down text-warning"></i>',
316        indicatorSuccess: '<i class="glyphicon glyphicon-ok-sign text-success"></i>',
317        indicatorError: '<i class="glyphicon glyphicon-exclamation-sign text-danger"></i>',
318        indicatorLoading: '<i class="glyphicon glyphicon-hand-up text-muted"></i>',
319        indicatorNewTitle: 'Not uploaded yet',
320        indicatorSuccessTitle: 'Uploaded',
321        indicatorErrorTitle: 'Upload Error',
322        indicatorLoadingTitle: 'Uploading ...'
323    };
324    tMain1 = '{preview}\n' +
325        '<div class="kv-upload-progress hide"></div>\n' +
326        '<div class="input-group {class}">\n' +
327        '   {caption}\n' +
328        '   <div class="input-group-btn">\n' +
329        '       {remove}\n' +
330        '       {cancel}\n' +
331        '       {upload}\n' +
332        '       {browse}\n' +
333        '   </div>\n' +
334        '</div>';
335    tMain2 = '{preview}\n<div class="kv-upload-progress hide"></div>\n{remove}\n{cancel}\n{upload}\n{browse}\n';
336    tPreview = '<div class="file-preview {class}">\n' +
337        '    {close}' +
338        '    <div class="{dropClass}">\n' +
339        '    <div class="file-preview-thumbnails">\n' +
340        '    </div>\n' +
341        '    <div class="clearfix"></div>' +
342        '    <div class="file-preview-status text-center text-success"></div>\n' +
343        '    <div class="kv-fileinput-error"></div>\n' +
344        '    </div>\n' +
345        '</div>';
346    tClose = '<div class="close fileinput-remove">&times;</div>\n';
347    tFileIcon = '<i class="glyphicon glyphicon-file kv-caption-icon"></i>';
348    tCaption = '<div tabindex="500" class="form-control file-caption {class}">\n' +
349        '   <div class="file-caption-name"></div>\n' +
350        '</div>\n';
351    //noinspection HtmlUnknownAttribute
352    tBtnDefault = '<button type="{type}" tabindex="500" title="{title}" class="{css}" {status}>{icon} {label}</button>';
353    //noinspection HtmlUnknownAttribute
354    tBtnLink = '<a href="{href}" tabindex="500" title="{title}" class="{css}" {status}>{icon} {label}</a>';
355    //noinspection HtmlUnknownAttribute
356    tBtnBrowse = '<div tabindex="500" class="{css}" {status}>{icon} {label}</div>';
357    tModalMain = '<div id="' + MODAL_ID + '" class="file-zoom-dialog modal fade" tabindex="-1" aria-labelledby="' +
358        MODAL_ID + 'Label"></div>';
359    tModal = '<div class="modal-dialog modal-lg" role="document">\n' +
360        '  <div class="modal-content">\n' +
361        '    <div class="modal-header">\n' +
362        '      <div class="kv-zoom-actions pull-right">{toggleheader}{fullscreen}{borderless}{close}</div>\n' +
363        '      <h3 class="modal-title">{heading} <small><span class="kv-zoom-title"></span></small></h3>\n' +
364        '    </div>\n' +
365        '    <div class="modal-body">\n' +
366        '      <div class="floating-buttons"></div>\n' +
367        '      <div class="kv-zoom-body file-zoom-content"></div>\n' + '{prev} {next}\n' +
368        '    </div>\n' +
369        '  </div>\n' +
370        '</div>\n';
371    tProgress = '<div class="progress">\n' +
372        '    <div class="{class}" role="progressbar"' +
373        ' aria-valuenow="{percent}" aria-valuemin="0" aria-valuemax="100" style="width:{percent}%;">\n' +
374        '        {percent}%\n' +
375        '     </div>\n' +
376        '</div>';
377    tSize = ' <br><samp>({sizeText})</samp>';
378    tFooter = '<div class="file-thumbnail-footer">\n' +
379        '    <div class="file-footer-caption" title="{caption}">{caption}{size}</div>\n' +
380        '    {progress} {actions}\n' +
381        '</div>';
382    tActions = '<div class="file-actions">\n' +
383        '    <div class="file-footer-buttons">\n' +
384        '        {upload} {delete} {zoom} {other}
384' +
385        '    </div>\n' +
386        '    {drag}\n' +
387        '    <div class="file-upload-indicator" title="{indicatorTitle}">{indicator}</div>\n' +
388        '    <div class="clearfix"></div>\n' +
389        '</div>';
390    //noinspection HtmlUnknownAttribute
391    tActionDelete = '<button type="button" class="kv-file-remove {removeClass}" ' + 'title="{removeTitle}" {dataUrl}{dataKey}>{removeIcon}</button>\n';
392    tActionUpload = '<button type="button" class="kv-file-upload {uploadClass}" title="{uploadTitle}">' +
393        '{uploadIcon}</button>';
394    tActionZoom = '<button type="button" class="kv-file-zoom {zoomClass}" title="{zoomTitle}">{zoomIcon}</button>';
395    tActionDrag = '<span class="file-drag-handle {dragClass}" title="{dragTitle}">{dragIcon}</span>';
396    tTagBef = '<div class="file-preview-frame{frameClass}" id="{previewId}" data-fileindex="{fileindex}"' +
397        ' data-template="{template}"';
398    tTagBef1 = tTagBef + '><div class="kv-file-content">\n';
399    tTagBef2 = tTagBef + ' title="{caption}" ' + STYLE_SETTING + '><div class="kv-file-content">\n';
400    tTagAft = '</div>{footer}\n</div>\n';
401    tGeneric = '{content}\n';
402    tHtml = '<div class="kv-preview-data file-preview-html" title="{caption}" ' + STYLE_SETTING + '>{data}</div>\n';
403    tImage = '<img src="{data}" class="kv-preview-data file-preview-image" title="{caption}" alt="{caption}" ' +
404        STYLE_SETTING + '>\n';
405    tText = '<textarea class="kv-preview-data file-preview-text" title="{caption}" readonly ' + STYLE_SETTING +
406        '>{data}</textarea>\n';
407    tVideo = '<video class="kv-preview-data" width="{width}" height="{height}" controls>\n' +
408        '<source src="{data}" type="{type}">\n' + DEFAULT_PREVIEW + '\n</video>\n';
409    tAudio = '<audio class="kv-preview-data" controls>\n<source src="' + '{data}' + '" type="{type}">\n' +
410        DEFAULT_PREVIEW + '\n</audio>\n';
411    tFlash = '<object class="kv-preview-data file-object" type="application/x-shockwave-flash" ' +
412        'width="{width}" height="{height}" data="{data}">\n' + OBJECT_PARAMS + ' ' + DEFAULT_PREVIEW + '\n</object>\n';
413    tObject = '<object class="kv-preview-data file-object" data="{data}" type="{type}" width="{width}" height="{height}">\n' +
414        '<param name="movie" value="{caption}" />\n' + OBJECT_PARAMS + ' ' + DEFAULT_PREVIEW + '\n</object>\n';
415    tPdf = '<embed class="kv-preview-data" src="{data}" width="{width}" height="{height}" type="application/pdf">\n';
416    tOther = '<div class="kv-preview-data file-preview-other-frame">\n' + DEFAULT_PREVIEW + '\n</div>\n';
417    defaultLayoutTemplates = {
418        main1: tMain1,
419        main2: tMain2,
420        preview: tPreview,
421        close: tClose,
422        fileIcon: tFileIcon,
423        caption: tCaption,
424        modalMain: tModalMain,
425        modal: tModal,
426        progress: tProgress,
427        size: tSize,
428        footer: tFooter,
429        actions: tActions,
430        actionDelete: tActionDelete,
431        actionUpload: tActionUpload,
432        actionZoom: tActionZoom,
433        actionDrag: tActionDrag,
434        btnDefault: tBtnDefault,
435        btnLink: tBtnLink,
436        btnBrowse: tBtnBrowse
437    };
438    defaultPreviewTemplates = {
439        generic: tTagBef1 + tGeneric + tTagAft,
440        html: tTagBef1 + tHtml + tTagAft,
441        image: tTagBef1 + tImage + tTagAft,
442        text: tTagBef1 + tText + tTagAft,
443        video: tTagBef2 + tVideo + tTagAft,
444        audio: tTagBef2 + tAudio + tTagAft,
445        flash: tTagBef2 + tFlash + tTagAft,
446        object: tTagBef2 + tObject + tTagAft,
447        pdf: tTagBef2 + tPdf + tTagAft,
448        other: tTagBef2 + tOther + tTagAft
449    };
450    defaultPreviewTypes = ['image', 'html', 'text', 'video', 'audio', 'flash', 'pdf', 'object'];
451    defaultPreviewSettings = {
452        image: {width: "auto", height: "160px"},
453        html: {width: "213px", height: "160px"},
454        text: {width: "213px", height: "160px"},
455        video: {width: "213px", height: "160px"},
456        audio: {width: "213px", height: "80px"},
457        flash: {width: "213px", height: "160px"},
458        object: {width: "160px", height: "160px"},
459        pdf: {width: "160px", height: "160px"},
460        other: {width: "160px", height: "160px"}
461    };
462    defaultPreviewZoomSettings = {
463        image: {width: "auto", height: "auto", 'max-width': "100%",'max-height': "100%"},
464        html: {width: "100%", height: "100%", 'min-height': "480px"},
465        text: {width: "100%", height: "100%", 'min-height': "480px"},
466        video: {width: "auto", height: "100%", 'max-width': "100%"},
467        audio: {width: "100%", height: "30px"},
468        flash: {width: "auto", height: "480px"},
469        object: {width: "auto", height: "100%", 'min-height': "480px"},
470        pdf: {width: "100%", height: "100%", 'min-height': "480px"},
471        other: {width: "auto", height: "100%", 'min-height': "480px"}
472    };
473    defaultFileTypeSettings = {
474        image: function (vType, vName) {
475            return compare(vType, 'image.*') || compare(vName, /\.(gif|png|jpe?g)$/i);
476        },
477        html: function (vType, vName) {
478            return compare(vType, 'text/html') || compare(vName, /\.(htm|html)$/i);
479        },
480        text: function (vType, vName) {
481            return compare(vType, 'text.*') || compare(vName, /\.(xml|javascript)$/i) ||
482                compare(vName, /\.(txt|md|csv|nfo|ini|json|php|js|css)$/i);
483        },
484        video: function (vType, vName) {
485            return compare(vType, 'video.*') && (compare(vType, /(ogg|mp4|mp?g|mov|webm|3gp)$/i) ||
486                compare(vName, /\.(og?|mp4|webm|mp?g|mov|3gp)$/i));
487        },
488        audio: function (vType, vName) {
489            return compare(vType, 'audio.*') && (compare(vName, /(ogg|mp3|mp?g|wav)$/i) ||
490                compare(vName, /\.(og?|mp3|mp?g|wav)$/i));
491        },
492        flash: function (vType, vName) {
493            return compare(vType, 'application/x-shockwave-flash', true) || compare(vName, /\.(swf)$/i);
494        },
495        pdf: function (vType, vName) {
496            return compare(vType, 'application/pdf', true) || compare(vName, /\.(pdf)$/i);
497        },
498        object: function () {
499            return true;
500        },
501        other: function () {
502            return true;
503        }
504    };
505    isEmpty = function (value, trim) {
506        return value === undefined || value === null || value.length === 0 || (trim && $.trim(value) === '');
507    };
508    isArray = function (a) {
509        return Array.isArray(a) || Object.prototype.toString.call(a) === '[object Array]';
510    };
511    ifSet = function (needle, haystack, def) {
512        def = def || '';
513        return (haystack && typeof haystack === 'object' && needle in haystack) ? haystack[needle] : def;
514    };
515    getElement = function (options, param, value) {
516        return (isEmpty(options) || isEmpty(options[param])) ? value : $(options[param]);
517    };
518    uniqId = function () {
519        return Math.round(new Date().getTime() + (Math.random() * 100));
520    };
521    htmlEncode = function (str) {
522        return str.replace(/&/g, '&amp;')
523            .replace(/</g, '&lt;')
524            .replace(/>/g, '&gt;')
525            .replace(/"/g, '&quot;')
526            .replace(/'/g, '&apos;');
527    };
528    replaceTags = function (str, tags) {
529        var out = str;
530        if (!tags) {
531            return out;
532        }
533        $.each(tags, function (key, value) {
534            if (typeof value === "function") {
535                value = value();
536            }
537            out = out.split(key).join(value);
538        });
539        return out;
540    };
541    cleanMemory = function ($thumb) {
542        var data = $thumb.is('img') ? $thumb.attr('src') : $thumb.find('source').attr('src');
543        /** @namespace objUrl.revokeObjectURL */
544        objUrl.revokeObjectURL(data);
545    };
546    findFileName = function (filePath) {
547        var sepIndex = filePath.lastIndexOf('/');
548        if (sepIndex === -1) {
549            sepIndex = filePath.lastIndexOf('\\');
550        }
551        return filePath.split(filePath.substring(sepIndex, sepIndex + 1)).pop();
552    };
553    checkFullScreen = function () {
554        //noinspection JSUnresolvedVariable
555        return document.fullscreenElement || document.mozFullScreenElement || document.webkitFullscreenElement ||
556            document.msFullscreenElement;
557    };
558    toggleFullScreen = function (maximize) {
559        if (maximize && !checkFullScreen()) {
560            /** @namespace document.documentElement.requestFullscreen */
561            /** @namespace document.documentElement.msRequestFullscreen */
562            /** @namespace document.documentElement.mozRequestFullScreen */
563            /** @namespace document.documentElement.webkitRequestFullscreen */
564            /** @namespace Element.ALLOW_KEYBOARD_INPUT */
565            if (document.documentElement.requestFullscreen) {
566                document.documentElement.requestFullscreen();
567            } else if (document.documentElement.msRequestFullscreen) {
568                document.documentElement.msRequestFullscreen();
569            } else if (document.documentElement.mozRequestFullScreen) {
570                document.documentElement.mozRequestFullScreen();
571            } else if (document.documentElement.webkitRequestFullscreen) {
572                document.documentElement.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT);
573            }
574        } else {
575            /** @namespace document.exitFullscreen */
576            /** @namespace document.msExitFullscreen */
577            /** @namespace document.mozCancelFullScreen */
578            /** @namespace document.webkitExitFullscreen */
579            if (document.exitFullscreen) {
580                document.exitFullscreen();
581            } else if (document.msExitFullscreen) {
582                document.msExitFullscreen();
583            } else if (document.mozCancelFullScreen) {
584                document.mozCancelFullScreen();
585            } else if (document.webkitExitFullscreen) {
586                document.webkitExitFullscreen();
587            }
588        }
589    };
590    moveArray = function (arr, oldIndex, newIndex) {
591        if (newIndex >= arr.length) {
592            var k = newIndex - arr.length;
593            while ((k--) + 1) {
594                arr.push(undefined);
595            }
596        }
597        arr.splice(newIndex, 0, arr.splice(oldIndex, 1)[0]);
598        return arr;
599    };
600    FileInput = function (element, options) {
601        var self = this;
602        self.$element = $(element);
603        if (!self._validate()) {
604            return;
605        }
606        self.isPreviewable = hasFileAPISupport();
607        self.isIE9 = isIE(9);
608        self.isIE10 = isIE(10);
609        if (self.isPreviewable || self.isIE9) {
610            self._init(options);
611            self._listen();
612        } else {
613            self.$element.removeClass('file-loading');
614        }
615    };
616    FileInput.prototype = {
617        constructor: FileInput,
618        _init: function (options) {
619            var self = this, $el = self.$element, t;
620            $.each(options, function (key, value) {
621                switch (key) {
622                    case 'minFileCount':
623                    case 'maxFileCount':
624                    case 'maxFileSize':
625                        self[key] = getNum(value);
626                        break;
627                    default:
628                        self[key] = value;
629                        break;
630                }
631            });
632            self.fileInputCleared = false;
633            self.fileBatchCompleted = true;
634            if (!self.isPreviewable) {
635                self.showPreview = false;
636            }
637            self.uploadFileAttr = !isEmpty($el.attr('name')) ? $el.attr('name') : 'file_data';
638            self.reader = null;
639            self.formdata = {};
640            self.clearStack();
641            self.uploadCount = 0;
642            self.uploadStatus = {};
643            self.uploadLog = [];
644            self.uploadAsyncCount = 0;
645            self.loadedImages = [];
646            self.totalImagesCount = 0;
647            self.ajaxRequests = [];
648            self.isError = false;
649            self.ajaxAborted = false;
650            self.cancelling = false;
651            t = self._getLayoutTemplate('progress');
652            self.progressTemplate = t.replace('{class}', self.progressClass);
653            self.progressCompleteTemplate = t.replace('{class}', self.progressCompleteClass);
654            self.progressErrorTemplate = t.replace('{class}', self.progressErrorClass);
655            self.dropZoneEnabled = hasDragDropSupport() && self.dropZoneEnabled;
656            self.isDisabled = self.$element.attr('disabled') || self.$element.attr('readonly');
657            self.isUploadable = hasFileUploadSupport() && !isEmpty(self.uploadUrl);
658            self.isClickable = self.browseOnZoneClick && self.showPreview &&
659                (self.isUploadable && self.dropZoneEnabled || !isEmpty(self.defaultPreviewContent));
660            self.slug = typeof options.slugCallback === "function" ? options.slugCallback : self._slugDefault;
661            self.mainTemplate = self.showCaption ? self._getLayoutTemplate('main1') : self._getLayoutTemplate('main2');
662            self.captionTemplate = self._getLayoutTemplate('caption');
663            self.previewGenericTemplate = self._getPreviewTemplate('generic');
664            if (self.resizeImage && (self.maxImageWidth || self.maxImageHeight)) {
665                self.imageCanvas = document.createElement('canvas');
666                self.imageCanvasContext = self.imageCanvas.getContext('2d');
667            }
668            if (isEmpty(self.$element.attr('id'))) {
669                self.$element.attr('id', uniqId());
670            }
671            if (self.$container === undefined) {
672                self.$container = self._createContainer();
673            } else {
674                self._refreshContainer();
675            }
676            self.$dropZone = self.$container.find('.file-drop-zone');
677            self.$progress = self.$container.find('.kv-upload-progress');
678            self.$btnUpload = self.$container.find('.fileinput-upload');
679            self.$captionContainer = getElement(options, 'elCaptionContainer', self.$container.find('.file-caption'));
680            self.$caption = getElement(options, 'elCaptionText', self.$container.find('.file-caption-name'));
681            self.$previewContainer = getElement(options, 'elPreviewContainer', self.$container.find('.file-preview'));
682            self.$preview = getElement(options, 'elPreviewImage', self.$container.find('.file-preview-thumbnails'));
683            self.$previewStatus = getElement(options, 'elPreviewStatus', self.$container.find('.file-preview-status'));
684            self.$errorContainer = getElement(options, 'elErrorContainer',
685                self.$previewContainer.find('.kv-fileinput-error'));
686            if (!isEmpty(self.msgErrorClass)) {
687                addCss(self.$errorContainer, self.msgErrorClass);
688            }
689            self.$errorContainer.hide();
690            self.fileActionSettings = $.extend(true, defaultFileActionSettings, options.fileActionSettings);
691            self.previewInitId = "preview-" + uniqId();
692            self.id = self.$element.attr('id');
693            previewCache.init(self);
694            self._initPreview(true);
695            self._initPreviewActions();
696            self.options = options;
697            self._setFileDropZoneTitle();
698            self.$element.removeClass('file-loading');
699            if (self.$element.attr('disabled')) {
700                self.disable();
701            }
702            self._initZoom();
703        },
704        _validate: function () {
705            var self = this, $exception;
706            if (self.$element.attr('type') === 'file') {
707                return true;
708            }
709            $exception = '<div class="help-block alert alert-warning">' +
710                '<h4>Invalid Input Type</h4>' +
711                'You must set an input <code>type = file</code> for <b>bootstrap-fileinput</b> plugin to initialize.' +
712                '</div>';
713            self.$element.after($exception);
714            return false;
715        },
716        _errorsExist: function () {
717            var self = this, $err;
718            if (self.$errorContainer.find('li').length) {
719                return true;
720            }
721            $err = $(document.createElement('div')).html(self.$errorContainer.html());
722            $err.find('span.kv-error-close').remove();
723            $err.find('ul').remove();
724            return $.trim($err.text()).length ? true : false;
725        },
726        _errorHandler: function (evt, caption) {
727            var self = this, err = evt.target.error;
728            /** @namespace err.NOT_FOUND_ERR */
729            /** @namespace err.SECURITY_ERR */
730            /** @namespace err.NOT_READABLE_ERR */
731            if (err.code === err.NOT_FOUND_ERR) {
732                self._showError(self.msgFileNotFound.replace('{name}', caption));
733            } else if (err.code === err.SECURITY_ERR) {
734                self._showError(self.msgFileSecured.replace('{name}', caption));
735            } else if (err.code === err.NOT_READABLE_ERR) {
736                self._showError(self.msgFileNotReadable.replace('{name}', caption));
737            } else if (err.code === err.ABORT_ERR) {
738                self._showError(self.msgFilePreviewAborted.replace('{name}', caption));
739            } else {
740                self._showError(self.msgFilePreviewError.replace('{name}', caption));
741            }
742        },
743        _addError: function (msg) {
744            var self = this, $error = self.$errorContainer;
745            if (msg && $error.length) {
746                $error.html(self.errorCloseButton + msg);
747                handler($error.find('.kv-error-close'), 'click', function () {
748                    $error.fadeOut('slow');
749                });
750            }
751        },
752        _resetErrors: function (fade) {
753            var self = this, $error = self.$errorContainer;
754            self.isError = false;
755            self.$container.removeClass('has-error');
756            $error.html('');
757            if (fade) {
758                $error.fadeOut('slow');
759            } else {
760                $error.hide();
761            }
762        },
763        _showFolderError: function (folders) {
764            var self = this, $error = self.$errorContainer, msg;
765            if (!folders) {
766                return;
767            }
768            msg = self.msgFoldersNotAllowed.replace(/\{n}/g, folders);
769            self._addError(msg);
770            addCss(self.$container, 'has-error');
771            $error.fadeIn(800);
772            self._raise('filefoldererror', [folders, msg]);
773        },
774        _showUploadError: function (msg, params, event) {
775            var self = this, $error = self.$errorContainer, ev = event || 'fileuploaderror', e = params && params.id ?
776            '<li data-file-id="' + params.id + '">' + msg + '</li>' : '<li>' + msg + '</li>';
777            if ($error.find('ul').length === 0) {
778                self._addError('<ul>' + e + '</ul>');
779            } else {
780                $error.find('ul').append(e);
781            }
782            $error.fadeIn(800);
783            self._raise(ev, [params, msg]);
784            self.$container.removeClass('file-input-new');
785            addCss(self.$container, 'has-error');
786            return true;
787        },
788        _showError: function (msg, params, event) {
789            var self = this, $error = self.$errorContainer, ev = event || 'fileerror';
790            params = params || {};
791            params.reader = self.reader;
792            self._addError(msg);
793            $error.fadeIn(800);
794            self._raise(ev, [params, msg]);
795            if (!self.isUploadable) {
796                self._clearFileInput();
797            }
798            self.$container.removeClass('file-input-new');
799            addCss(self.$container, 'has-error');
800            self.$btnUpload.attr('disabled', true);
801            return true;
802        },
803        _noFilesError: function (params) {
804            var self = this, label = self.minFileCount > 1 ? self.filePlural : self.fileSingle,
805                msg = self.msgFilesTooLess.replace('{n}', self.minFileCount).replace('{files}', label),
806                $error = self.$errorContainer;
807            self._addError(msg);
808            self.isError = true;
809            self._updateFileDetails(0);
810            $error.fadeIn(800);
811            self._raise('fileerror', [params, msg]);
812            self._clearFileInput();
813            addCss(self.$container, 'has-error');
814        },
815        _parseError: function (jqXHR, errorThrown, fileName) {
816            /** @namespace jqXHR.responseJSON */
817            var self = this, errMsg = $.trim(errorThrown + ''),
818                dot = errMsg.slice(-1) === '.' ? '' : '.',
819                text = jqXHR.responseJSON !== undefined && jqXHR.responseJSON.error !== undefined ?
820                    jqXHR.responseJSON.error : jqXHR.responseText;
821            if (self.cancelling && self.msgUploadAborted) {
822                errMsg = self.msgUploadAborted;
823            }
824            if (self.showAjaxErrorDetails && text) {
825                text = $.trim(text.replace(/\n\s*\n/g, '\n'));
826                text = text.length > 0 ? '<pre>
826' + text + '</pre>' : '';
827                errMsg += dot + text;
828            } else {
829                errMsg += dot;
830            }
831            self.cancelling = false;
832            return fileName ? '<b>' + fileName + ': </b>' + errMsg : errMsg;
833        },
834        _parseFileType: function (file) {
835            var self = this, isValid, vType, cat, i;
836            for (i = 0; i < defaultPreviewTypes.length; i += 1) {
837                cat = defaultPreviewTypes[i];
838                isValid = ifSet(cat, self.fileTypeSettings, defaultFileTypeSettings[cat]);
839                vType = isValid(file.type, file.name) ? cat : '';
840                if (!isEmpty(vType)) {
841                    return vType;
842                }
843            }
844            return 'other';
845        },
846        _parseFilePreviewIcon: function (content, fname) {
847            var self = this, proceed, ext, icn = self.previewFileIcon;
848            if (fname && fname.indexOf('.') > -1) {
849                ext = fname.split('.').pop();
850                if (self.previewFileIconSettings && self.previewFileIconSettings[ext]) {
851                    icn = self.previewFileIconSettings[ext];
852                }
853                if (self.previewFileExtSettings) {
854                    $.each(self.previewFileExtSettings, function (key, func) {
855                        if (self.previewFileIconSettings[key] && func(ext)) {
856                            icn = self.previewFileIconSettings[key];
857                            return;
858                        }
859                        proceed = true;
860                    });
861                }
862            }
863            if (content.indexOf('{previewFileIcon}') > -1) {
864                return content.replace(/\{previewFileIconClass}/g, self.previewFileIconClass).replace(
865                    /\{previewFileIcon}/g, icn);
866            }
867            return content;
868        },
869        _raise: function (event, params) {
870            var self = this, e = $.Event(event);
871            if (params !== undefined) {
872                self.$element.trigger(e, params);
873            } else {
874                self.$element.trigger(e);
875            }
876            if (e.isDefaultPrevented()) {
877                return false;
878            }
879            if (!e.result) {
880                return e.result;
881            }
882            switch (event) {
883                // ignore these events
884                case 'filebatchuploadcomplete':
885                case 'filebatchuploadsuccess':
886                case 'fileuploaded':
887                case 'fileclear':
888                case 'filecleared':
889                case 'filereset':
890                case 'fileerror':
891                case 'filefoldererror':
892                case 'fileuploaderror':
893                case 'filebatchuploaderror':
894                case 'filedeleteerror':
895                case 'filecustomerror':
896                case 'filesuccessremove':
897                    break;
898                // receive data response via `filecustomerror` event`
899                default:
900                    self.ajaxAborted = e.result;
901                    break;
902            }
903            return true;
904        },
905        _listenFullScreen: function (isFullScreen) {
906            var self = this, $modal = self.$modal, $btnFull, $btnBord;
907            if (!$modal || !$modal.length) {
908                return;
909            }
910            $btnFull = $modal && $modal.find('.btn-fullscreen');
911            $btnBord = $modal && $modal.find('.btn-borderless');
912            if (!$btnFull.length || !$btnBord.length) {
913                return;
914            }
915            $btnFull.removeClass('active').attr('aria-pressed', 'false');
916            $btnBord.removeClass('active').attr('aria-pressed', 'false');
917            if (isFullScreen) {
918                $btnFull.addClass('active').attr('aria-pressed', 'true');
919            } else {
920                $btnBord.addClass('active').attr('aria-pressed', 'true');
921            }
922            if ($modal.hasClass('file-zoom-fullscreen')) {
923                self._maximizeZoomDialog();
924            } else {
925                if (isFullScreen) {
926                    self._maximizeZoomDialog();
927                } else {
928                    $btnBord.removeClass('active').attr('aria-pressed', 'false');
929                }
930            }
931        },
932        _listen: function () {
933            var self = this, $el = self.$element, $form = $el.closest('form'), $cont = self.$container;
934            handler($el, 'change', $.proxy(self._change, self));
935            if (self.showBrowse) {
936                handler(self.$btnFile, 'click', $.proxy(self._browse, self));
937            }
938            handler($form, 'reset', $.proxy(self.reset, self));
939            handler($cont.find('.fileinput-remove:not([disabled])'), 'click', $.proxy(self.clear, self));
940            handler($cont.find('.fileinput-cancel'), 'click', $.proxy(self.cancel, self));
941            self._initDragDrop();
942            if (!self.isUploadable) {
943                handler($form, 'submit', $.proxy(self._submitForm, self));
944            }
945            handler(self.$container.find('.fileinput-upload'), 'click', $.proxy(self._uploadClick, self));
946            handler($(window), 'resize', function () {
947                self._listenFullScreen(screen.width === window.innerWidth && screen.height === window.innerHeight);
948            });
949            handler($(document), 'webkitfullscreenchange mozfullscreenchange fullscreenchange MSFullscreenChange',
950                function () {
951                    self._listenFullScreen(checkFullScreen());
952                });
953            self._initClickable();
954        },
955        _initClickable: function () {
956            var self = this, $zone;
957            if (!self.isClickable) {
958                return;
959            }
960            $zone = self.isUploadable ? self.$dropZone : self.$preview.find('.file-default-preview');
961            addCss($zone, 'clickable');
962            $zone.attr('tabindex', -1);
963            handler($zone, 'click', function (e) {
964                var $target = $(e.target);
965                if (!$target.parents('.file-preview-thumbnails').length || $target.parents(
966                        '.file-default-preview').length) {
967                    self.$element.trigger('click');
968                    $zone.blur();
969                }
970            });
971        },
972        _initDragDrop: function () {
973            var self = this, $zone = self.$dropZone;
974            if (self.isUploadable && self.dropZoneEnabled && self.showPreview) {
975                handler($zone, 'dragenter dragover', $.proxy(self._zoneDragEnter, self));
976                handler($zone, 'dragleave', $.proxy(self._zoneDragLeave, self));
977                handler($zone, 'drop', $.proxy(self._zoneDrop, self));
978                handler($(document), 'dragenter dragover drop', self._zoneDragDropInit);
979            }
980        },
981        _zoneDragDropInit: function (e) {
982            e.stopPropagation();
983            e.preventDefault();
984        },
985        _zoneDragEnter: function (e) {
986            var self = this, hasFiles = $.inArray('Files', e.originalEvent.dataTransfer.types) > -1;
987            self._zoneDragDropInit(e);
988            if (self.isDisabled || !hasFiles) {
989                e.originalEvent.dataTransfer.effectAllowed = 'none';
990                e.originalEvent.dataTransfer.dropEffect = 'none';
991                return;
992            }
993            addCss(self.$dropZone, 'file-highlighted');
994        },
995        _zoneDragLeave: function (e) {
996            var self = this;
997            self._zoneDragDropInit(e);
998            if (self.isDisabled) {
999                return;
1000            }
1001            self.$dropZone.removeClass('file-highlighted');
1002        },
1003        _zoneDrop: function (e) {
1004            var self = this;
1005            e.preventDefault();
1006            /** @namespace e.originalEvent.dataTransfer */
1007            if (self.isDisabled || isEmpty(e.originalEvent.dataTransfer.files)) {
1008                return;
1009            }
1010            self._change(e, 'dragdrop');
1011            self.$dropZone.removeClass('file-highlighted');
1012        },
1013        _uploadClick: function (e) {
1014            var self = this, $btn = self.$container.find('.fileinput-upload'), $form,
1015                isEnabled = !$btn.hasClass('disabled') && isEmpty($btn.attr('disabled'));
1016            if (e && e.isDefaultPrevented()) {
1017                return;
1018            }
1019            if (!self.isUploadable) {
1020                if (isEnabled && $btn.attr('type') !== 'submit') {
1021                    $form = $btn.closest('form');
1022                    // downgrade to normal form submit if possible
1023                    if ($form.length) {
1024                        $form.trigger('submit');
1025                    }
1026                    e.preventDefault();
1027                }
1028                return;
1029            }
1030            e.preventDefault();
1031            if (isEnabled) {
1032                self.upload();
1033            }
1034        },
1035        _submitForm: function () {
1036            var self = this, $el = self.$element, files = $el.get(0).files;
1037            if (files && self.minFileCount > 0 && self._getFileCount(files.length) < self.minFileCount) {
1038                self._noFilesError({});
1039                return false;
1040            }
1041            return !self._abort({});
1042        },
1043        _clearPreview: function () {
1044            var self = this, $thumbs = !self.showUploadedThumbs ? self.$preview.find('.file-preview-frame') :
1045                self.$preview.find('.file-preview-frame:not(.file-preview-success)');
1046            $thumbs.remove();
1047            if (!self.$preview.find('.file-preview-frame').length || !self.showPreview) {
1048                self._resetUpload();
1049            }
1050            self._validateDefaultPreview();
1051        },
1052        _initSortable: function () {
1053            var self = this, $preview = self.$preview, $el, settings;
1054            if (!window.KvSortable) {
1055                return;
1056            }
1057            $el = $preview.find('.file-initial-thumbs');
1058            //noinspection JSUnusedGlobalSymbols
1059            settings = {
1060                handle: '.drag-handle-init',
1061                dataIdAttr: 'data-preview-id',
1062                draggable: '.file-preview-initial',
1063                onSort: function (e) {
1064                    var oldIndex = e.oldIndex, newIndex = e.newIndex;
1065                    self.initialPreview = moveArray(self.initialPreview, oldIndex, newIndex);
1066                    self.initialPreviewConfig = moveArray(self.initialPreviewConfig, oldIndex, newIndex);
1067                    previewCache.init(self);
1068                    for(var i = 0; i < self.initialPreviewConfig.length; i++) {
1069                        if(self.initialPreviewConfig[i] !== null) {
1070                            var key = self.initialPreviewConfig[i].key;
1071                            var $frame = $(".kv-file-remove[data-key='" + key + "']");
1072                            $frame = $frame.closest('.file-preview-frame');
1073                            $frame.attr('data-fileindex', 'init_'+i);
1074                            $frame.data('fileindex', 'init_'+i);
1075                        }
1076                    }
1077                    self._raise('filesorted', {
1078                        previewId: $(e.item).attr('id'),
1079                        'oldIndex': oldIndex,
1080                        'newIndex': newIndex,
1081                        stack: self.initialPreviewConfig
1082                    });
1083                }
1084            };
1085            if ($el.data('kvsortable')) {
1086                $el.kvsortable('destroy');
1087            }
1088            $.extend(true, settings, self.fileActionSettings.dragSettings);
1089            $el.kvsortable(settings);
1090        },
1091        _initPreview: function (isInit) {
1092            var self = this, cap = self.initialCaption || '', out;
1093            if (!previewCache.count(self.id)) {
1094                self._clearPreview();
1095                if (isInit) {
1096                    self._setCaption(cap);
1097                } else {
1098                    self._initCaption();
1099                }
1100                return;
1101            }
1102            out = previewCache.out(self.id);
1103            cap = isInit && self.initialCaption ? self.initialCaption : out.caption;
1104            self.$preview.html(out.content);
1105            self._setCaption(cap);
1106            self._initSortable();
1107            if (!isEmpty(out.content)) {
1108                self.$container.removeClass('file-input-new');
1109            }
1110        },
1111        _getZoomButton: function (type) {
1112            var self = this, label = self.previewZoomButtonIcons[type], css = self.previewZoomButtonClasses[type],
1113                title = ' title="' + (self.previewZoomButtonTitles[type] || '') + '" ',
1114                params = title + (type === 'close' ? ' data-dismiss="modal" aria-hidden="true"' : '');
1115            if (type === 'fullscreen' || type === 'borderless' || type === 'toggleheader') {
1116                params += ' data-toggle="button" aria-pressed="false" autocomplete="off"';
1117            }
1118            return '<button type="button" class="' + css + ' btn-' + type + '"' + params + '>' + label + '</button>';
1119        },
1120        _getModalContent: function () {
1121            var self = this;
1122            return self._getLayoutTemplate('modal')
1123                .replace(/\{heading}/g, self.msgZoomModalHeading)
1124                .replace(/\{prev}/g, self._getZoomButton('prev'))
1125                .replace(/\{next}/g, self._getZoomButton('next'))
1126                .replace(/\{toggleheader}/g, self._getZoomButton('toggleheader'))
1127                .replace(/\{fullscreen}/g, self._getZoomButton('fullscreen'))
1128                .replace(/\{borderless}/g, self._getZoomButton('borderless'))
1129                .replace(/\{close}/g, self._getZoomButton('close'));
1130        },
1131        _listenModalEvent: function (event) {
1132            var self = this, $modal = self.$modal, getParams = function (e) {
1133                return {
1134                    sourceEvent: e,
1135                    previewId: $modal.data('previewId'),
1136                    modal: $modal
1137                };
1138            };
1139            $modal.on(event + '.bs.modal', function (e) {
1140                var $btnFull = $modal.find('.btn-fullscreen'), $btnBord = $modal.find('.btn-borderless');
1141                self._raise('filezoom' + event, getParams(e));
1142                if (event === 'shown') {
1143                    $btnBord.removeClass('active').attr('aria-pressed', 'false');
1144                    $btnFull.removeClass('active').attr('aria-pressed', 'false');
1145                    if ($modal.hasClass('file-zoom-fullscreen')) {
1146                        self._maximizeZoomDialog();
1147                        if (checkFullScreen()) {
1148                            $btnFull.addClass('active').attr('aria-pressed', 'true');
1149                        } else {
1150                            $btnBord.addClass('active').attr('aria-pressed', 'true');
1151                        }
1152                    }
1153                }
1154            });
1155        },
1156        _initZoom: function () {
1157            var self = this, $dialog, modalMain = self._getLayoutTemplate('modalMain'), modalId = '#' + MODAL_ID;
1158            self.$modal = $(modalId);
1159            if (!self.$modal || !self.$modal.length) {
1160                $dialog = $(document.createElement('div')).html(modalMain).insertAfter(self.$container);
1161                self.$modal = $('#' + MODAL_ID).insertBefore($dialog);
1162                $dialog.remove();
1163            }
1164            self.$modal.html(self._getModalContent());
1165            self._listenModalEvent('show');
1166            self._listenModalEvent('shown');
1167            self._listenModalEvent('hide');
1168            self._listenModalEvent('hidden');
1169            self._listenModalEvent('loaded');
1170        },
1171        _initZoomButtons: function () {
1172            var self = this, previewId = self.$modal.data('previewId') || '', $first, $last,
1173                frames = self.$preview.find('.file-preview-frame').toArray(), len = frames.length,
1174                $prev = self.$modal.find('.btn-prev'), $next = self.$modal.find('.btn-next');
1175
1176            if (!len) {
1177                return;
1178            }
1179            $first = $(frames[0]);
1180            $last = $(frames[len - 1]);
1181            $prev.removeAttr('disabled');
1182            $next.removeAttr('disabled');
1183            if ($first.length && $first.attr('id') === previewId) {
1184                $prev.attr('disabled', true);
1185            }
1186            if ($last.length && $last.attr('id') === previewId) {
1187                $next.attr('disabled', true);
1188            }
1189        },
1190        _maximizeZoomDialog: function () {
1191            var self = this, $modal = self.$modal, $head = $modal.find('.modal
1191-header:visible'),
1192                $foot = $modal.find('.modal-footer:visible'), $body = $modal.find('.modal-body'),
1193                h = $(window).height(), diff = 0;
1194            $modal.addClass('file-zoom-fullscreen');
1195            if ($head && $head.length) {
1196                h -= $head.outerHeight(true);
1197            }
1198            if ($foot && $foot.length) {
1199                h -= $foot.outerHeight(true);
1200            }
1201            if ($body && $body.length) {
1202                diff = $body.outerHeight(true) - $body.height();
1203                h -= diff;
1204            }
1205            $modal.find('.kv-zoom-body').height(h);
1206        },
1207        _resizeZoomDialog: function (fullScreen) {
1208            var self = this, $modal = self.$modal, $btnFull = $modal.find('.btn-fullscreen'),
1209                $btnBord = $modal.find('.btn-borderless');
1210            if ($modal.hasClass('file-zoom-fullscreen')) {
1211                toggleFullScreen(false);
1212                if (!fullScreen) {
1213                    if (!$btnFull.hasClass('active')) {
1214                        $modal.removeClass('file-zoom-fullscreen');
1215                        self.$modal.find('.kv-zoom-body').css('height', self.zoomModalHeight);
1216                    } else {
1217                        $btnFull.removeClass('active').attr('aria-pressed', 'false');
1218                    }
1219                } else {
1220                    if (!$btnFull.hasClass('active')) {
1221                        $modal.removeClass('file-zoom-fullscreen');
1222                        self._resizeZoomDialog(true);
1223                        if ($btnBord.hasClass('active')) {
1224                            $btnBord.removeClass('active').attr('aria-pressed', 'false');
1225                        }
1226                    }
1227                }
1228            } else {
1229                if (!fullScreen) {
1230                    self._maximizeZoomDialog();
1231                    return;
1232                }
1233                toggleFullScreen(true);
1234            }
1235            $modal.focus();
1236        },
1237        _setZoomContent: function ($preview, animate) {
1238            var self = this, $content, tmplt, body, title, $body, $dataEl, config, previewId = $preview.attr('id'),
1239                $modal = self.$modal, $prev = $modal.find('.btn-prev'), $next = $modal.find('.btn-next'), $tmp,
1240                $btnFull = $modal.find('.btn-fullscreen'), $btnBord = $modal.find('.btn-borderless'),
1241                $btnTogh = $modal.find('.btn-toggleheader');
1242            tmplt = $preview.data('template') || 'generic';
1243            $content = $preview.find('.kv-file-content');
1244            body = $content.length ? $content.html() : '';
1245            title = $preview.find('.file-footer-caption').text() || '';
1246            $modal.find('.kv-zoom-title').html(title);
1247            $body = $modal.find('.kv-zoom-body');
1248            if (animate) {
1249                $tmp = $body.clone().insertAfter($body);
1250                $body.html(body).hide();
1251                $tmp.fadeOut('fast', function () {
1252                    $body.fadeIn('fast');
1253                    $tmp.remove();
1254                });
1255            } else {
1256                $body.html(body);
1257            }
1258            config = self.previewZoomSettings[tmplt];
1259            if (config) {
1260                $dataEl = $body.find('.kv-preview-data');
1261                addCss($dataEl, 'file-zoom-detail');
1262                $.each(config, function (key, value) {
1263                    $dataEl.css(key, value);
1264                    if (($dataEl.attr('width') && key === 'width') || ($dataEl.attr('height') && key === 'height')) {
1265                        $dataEl.removeAttr(key);
1266                    }
1267                });
1268            }
1269            $modal.data('previewId', previewId);
1270            handler($prev, 'click', function () {
1271                self._zoomSlideShow('prev', previewId);
1272            });
1273            handler($next, 'click', function () {
1274                self._zoomSlideShow('next', previewId);
1275            });
1276            handler($btnFull, 'click', function () {
1277                self._resizeZoomDialog(true);
1278            });
1279            handler($btnBord, 'click', function () {
1280                self._resizeZoomDialog(false);
1281            });
1282            handler($btnTogh, 'click', function () {
1283                var $header = $modal.find('.modal-header'), $floatBar = $modal.find('.modal-body .floating-buttons'),
1284                    ht, $actions = $header.find('.kv-zoom-actions'), resize = function (height) {
1285                        var $body = self.$modal.find('.kv-zoom-body'), h = self.zoomModalHeight;
1286                        if ($modal.hasClass('file-zoom-fullscreen')) {
1287                            h = $body.outerHeight(true);
1288                            if (!height) {
1289                                h = h - $header.outerHeight(true);
1290                            }
1291                        }
1292                        $body.css('height', height ? h + height : h);
1293                    };
1294                if ($header.is(':visible')) {
1295                    ht = $header.outerHeight(true);
1296                    $header.slideUp('slow', function () {
1297                        $actions.find('.btn').appendTo($floatBar);
1298                        resize(ht);
1299                    });
1300                } else {
1301                    $floatBar.find('.btn').appendTo($actions);
1302                    $header.slideDown('slow', function () {
1303                        resize();
1304                    });
1305                }
1306                $modal.focus();
1307            });
1308            handler($modal, 'keydown', function (e) {
1309                var key = e.which || e.keyCode;
1310                if (key === 37 && !$prev.attr('disabled')) {
1311                    self._zoomSlideShow('prev', previewId);
1312                }
1313                if (key === 39 && !$next.attr('disabled')) {
1314                    self._zoomSlideShow('next', previewId);
1315                }
1316            });
1317        },
1318        _zoomPreview: function ($btn) {
1319            var self = this, $preview;
1320            if (!$btn.length) {
1321                throw 'Cannot zoom to detailed preview!';
1322            }
1323            self.$modal.html(self._getModalContent());
1324            $preview = $btn.closest('.file-preview-frame');
1325            self._setZoomContent($preview);
1326            self.$modal.modal('show');
1327            self._initZoomButtons();
1328        },
1329        _zoomSlideShow: function (dir, previewId) {
1330            var self = this, $btn = self.$modal.find('.kv-zoom-actions .btn-' + dir), $targFrame, i,
1331                frames = self.$preview.find('.file-preview-frame').toArray(), len = frames.length, out;
1332            if ($btn.attr('disabled')) {
1333                return;
1334            }
1335            for (i = 0; i < len; i++) {
1336                if ($(frames[i]).attr('id') === previewId) {
1337                    out = dir === 'prev' ? i - 1 : i + 1;
1338                    break;
1339                }
1340            }
1341            if (out < 0 || out >= len || !frames[out]) {
1342                return;
1343            }
1344            $targFrame = $(frames[out]);
1345            if ($targFrame.length) {
1346                self._setZoomContent($targFrame, true);
1347            }
1348            self._initZoomButtons();
1349            self._raise('filezoom' + dir, {'previewId': previewId, modal: self.$modal});
1350        },
1351        _initZoomButton: function () {
1352            var self = this;
1353            self.$preview.find('.kv-file-zoom').each(function () {
1354                var $el = $(this);
1355                handler($el, 'click', function () {
1356                    self._zoomPreview($el);
1357                });
1358            });
1359        },
1360        _initPreviewActions: function () {
1361            var self = this, deleteExtraData = self.deleteExtraData || {},
1362                resetProgress = function () {
1363                    var hasFiles = self.isUploadable ? previewCache.count(self.id) : self.$element.get(0).files.length;
1364                    if (self.$preview.find('.kv-file-remove').length === 0 && !hasFiles) {
1365                        self.reset();
1366                        self.initialCaption = '';
1367                    }
1368                };
1369            self._initZoomButton();
1370            self.$preview.find('.kv-file-remove').each(function () {
1371                var $el = $(this), vUrl = $el.data('url') || self.deleteUrl, vKey = $el.data('key');
1372                if (isEmpty(vUrl) || vKey === undefined) {
1373                    return;
1374                }
1375                var $frame = $el.closest('.file-preview-frame'), cache = previewCache.data[self.id],
1376                    settings, params, index = $frame.data('fileindex'), config, extraData;
1377                index = parseInt(index.replace('init_', ''));
1378                config = isEmpty(cache.config) && isEmpty(cache.config[index]) ? null : cache.config[index];
1379                extraData = isEmpty(config) || isEmpty(config.extra) ? deleteExtraData : config.extra;
1380                if (typeof extraData === "function") {
1381                    extraData = extraData();
1382                }
1383                params = {id: $el.attr('id'), key: vKey, extra: extraData};
1384                settings = $.extend(true, {}, {
1385                    url: vUrl,
1386                    type: 'POST',
1387                    dataType: 'json',
1388                    data: $.extend(true, {}, {key: vKey}, extraData),
1389                    beforeSend: function (jqXHR) {
1390                        self.ajaxAborted = false;
1391                        self._raise('filepredelete', [vKey, jqXHR, extraData]);
1392                        if (self.ajaxAborted) {
1393                            jqXHR.abort();
1394                        } else {
1395                            addCss($frame, 'file-uploading');
1396                            addCss($el, 'disabled');
1397                        }
1398                    },
1399                    success: function (data, textStatus, jqXHR) {
1400                        var n, cap;
1401                        if (isEmpty(data) || isEmpty(data.error)) {
1402                            previewCache.init(self);
1403                            index = parseInt(($frame.data('fileindex')).replace('init_', ''));
1404                            previewCache.unset(self, index);
1405                            n = previewCache.count(self.id);
1406                            cap = n > 0 ? self._getMsgSelected(n) : '';
1407                            self._raise('filedeleted', [vKey, jqXHR, extraData]);
1408                            self._setCaption(cap);
1409                        } else {
1410                            params.jqXHR = jqXHR;
1411                            params.response = data;
1412                            self._showError(data.error, params, 'filedeleteerror');
1413                            $frame.removeClass('file-uploading');
1414                            $el.removeClass('disabled');
1415                            resetProgress();
1416                            return;
1417                        }
1418                        $frame.removeClass('file-uploading').addClass('file-deleted');
1419                        $frame.fadeOut('slow', function () {
1420                            self._clearObjects($frame);
1421                            $frame.remove();
1422                            resetProgress();
1423                            if (!n && self.getFileStack().length === 0) {
1424                                self._setCaption('');
1425                                self.reset();
1426                            }
1427                        });
1428                    },
1429                    error: function (jqXHR, textStatus, errorThrown) {
1430                        var errMsg = self._parseError(jqXHR, errorThrown);
1431                        params.jqXHR = jqXHR;
1432                        params.response = {};
1433                        self._showError(errMsg, params, 'filedeleteerror');
1434                        $frame.removeClass('file-uploading');
1435                        resetProgress();
1436                    }
1437                }, self.ajaxDeleteSettings);
1438                handler($el, 'click', function () {
1439                    if (!self._validateMinCount()) {
1440                        return false;
1441                    }
1442                    $.ajax(settings);
1443                });
1444            });
1445        },
1446        _clearObjects: function ($el) {
1447            $el.find('video audio').each(function () {
1448                this.pause();
1449                $(this).remove();
1450            });
1451            $el.find('img object div').each(function () {
1452                $(this).remove();
1453            });
1454        },
1455        _clearFileInput: function () {
1456            var self = this, $el = self.$element, $srcFrm, $tmpFrm, $tmpEl;
1457            self.fileInputCleared = true;
1458            if (isEmpty($el.val())) {
1459                return;
1460            }
1461            // Fix for IE ver < 11, that does not clear file inputs. Requires a sequence of steps to prevent IE
1462            // crashing but still allow clearing of the file input.
1463            if (self.isIE9 || self.isIE10) {
1464                $srcFrm = $el.closest('form');
1465                $tmpFrm = $(document.createElement('form'));
1466                $tmpEl = $(document.createElement('div'));
1467                $el.before($tmpEl);
1468                if ($srcFrm.length) {
1469                    $srcFrm.after($tmpFrm);
1470                } else {
1471                    $tmpEl.after($tmpFrm);
1472                }
1473                $tmpFrm.append($el).trigger('reset');
1474                $tmpEl.before($el).remove();
1475                $tmpFrm.remove();
1476            } else { // normal input clear behavior for other sane browsers
1477                $el.val('');
1478            }
1479        },
1480        _resetUpload: function () {
1481            var self = this;
1482            self.uploadCache = {content: [], config: [], tags: [], append: true};
1483            self.uploadCount = 0;
1484            self.uploadStatus = {};
1485            self.uploadLog = [];
1486            self.uploadAsyncCount = 0;
1487            self.loadedImages = [];
1488            self.totalImagesCount = 0;
1489            self.$btnUpload.removeAttr('disabled');
1490            self._setProgress(0);
1491            addCss(self.$progress, 'hide');
1492            self._resetErrors(false);
1493            self.ajaxAborted = false;
1494            self.ajaxRequests = [];
1495            self._resetCanvas();
1496        },
1497        _resetCanvas: function () {
1498            var self = this;
1499            if (self.canvas && self.imageCanvasContext) {
1500                self.imageCanvasContext.clearRect(0, 0, self.canvas.width, self.canvas.height);
1501            }
1502        },
1503        _hasInitialPreview: function () {
1504            var self = this;
1505            return !self.overwriteInitial && previewCache.count(self.id);
1506        },
1507        _resetPreview: function () {
1508            var self = this, out, cap;
1509            if (previewCache.count(self.id)) {
1510                out = previewCache.out(self.id);
1511                self.$preview.html(out.content);
1512                cap = self.initialCaption ? self.initialCaption : out.caption;
1513                self._setCaption(cap);
1514            } else {
1515                self._clearPreview();
1516                self._initCaption();
1517            }
1518            if (self.showPreview) {
1519                self._initZoom();
1520                self._initSortable();
1521            }
1522        },
1523        _clearDefaultPreview: function () {
1524            var self = this;
1525            self.$preview.find('.file-default-preview').remove();
1526        },
1527        _validateDefaultPreview: function () {
1528            var self = this;
1529            if (!self.showPreview || isEmpty(self.defaultPreviewContent)) {
1530                return;
1531            }
1532            self.$preview.html('<div class="file-default-preview">' + self.defaultPreviewContent + '</div>');
1533            self.$container.removeClass('file-input-new');
1534            self._initClickable();
1535        },
1536        _resetPreviewThumbs: function (isAjax) {
1537            var self = this, out;
1538            if (isAjax) {
1539                self._clearPreview();
1540                self.clearStack();
1541                return;
1542            }
1543            if (self._hasInitialPreview()) {
1544                out = previewCache.out(self.id);
1545                self.$preview.html(out.content);
1546                self._setCaption(out.caption);
1547                self._initPreviewActions();
1548            } else {
1549                self._clearPreview();
1550            }
1551        },
1552        _getLayoutTemplate: function (t) {
1553            var self = this,
1554                template = ifSet(t, self.layoutTemplates, defaultLayoutTemplates[t]);
1555            if (isEmpty(self.customLayoutTags)) {
1556                return template;
1557            }
1558            return replaceTags(template, self.customLayoutTags);
1559        },
1560        _getPreviewTemplate: function (t) {
1561            var self = this,
1562                template = ifSet(t, self.previewTemplates, defaultPreviewTemplates[t]);
1563            if (isEmpty(self.customPreviewTags)) {
1564                return template;
1565            }
1566            return replaceTags(template, self.customPreviewTags);
1567        },
1568        _getOutData: function (jqXHR, responseData, filesData) {
1569            var self = this;
1570            jqXHR = jqXHR || {};
1571            responseData = responseData || {};
1572            filesData = filesData || self.filestack.slice(0) || {};
1573            return {
1574                form: self.formdata,
1575                files: filesData,
1576                filenames: self.filenames,
1577                filescount: self.getFilesCount(),
1578                extra: self._getExtraData(),
1579                response: responseData,
1580                reader: self.reader,
1581                jqXHR: jqXHR
1582            };
1583        },
1584        _getMsgSelected: function (n) {
1585            var self = this, strFiles = n === 1 ? self.fileSingle : self.filePlural;
1586            return n > 0 ? self.msgSelected.replace('{n}', n).replace('{files}', strFiles) : self.msgNoFilesSelected;
1587        },
1588        _getThumbs: function (css) {
1589            css = css || '';
1590            return this.$preview.find('.file-preview-frame:not(.file-preview-initial)' + css);
1591        },
1592        _getExtraData: function (previewId, index) {
1593            var self = this, data = self.uploadExtraData;
1594            if (typeof self.uploadExtraData === "function") {
1595                data = self.uploadExtraData(previewId, index);
1596            }
1597            return data;
1598        },
1599        _initXhr: function (xhrobj, previewId, fileCount) {
1600            var self = this;
1601            if (xhrobj.upload) {
1602                xhrobj.upload.addEventListener('progress', function (event) {
1603                    var pct = 0, total = event.total, position = event.loaded || event.position;
1604                    /** @namespace event.lengthComputable */
1605                    if (event.lengthComputable) {
1606                        pct = Math.floor(position / total * 100);
1607                    }
1608                    if (previewId) {
1609                        self._setAsyncUploadStatus(previewId, pct, fileCount);
1610                    } else {
1611                        self._setProgress(pct);
1612                    }
1613                }, false);
1614            }
1615            return xhrobj;
1616        },
1617        _ajaxSubmit: function (fnBefore, fnSuccess, fnComplete, fnError, previewId, index) {
1618            var self = this, settings;
1619            self._raise('filepreajax', [previewId, index]);
1620            self._uploadExtra(previewId, index);
1621            settings = $.extend(true, {}, {
1622                xhr: function () {
1623                    var xhrobj = $.ajaxSettings.xhr();
1624                    return self._initXhr(xhrobj, previewId, self.getFileStack().length);
1625                },
1626                url: self.uploadUrl,
1627                type: 'POST',
1628                dataType: 'json',
1629                data: self.formdata,
1630                cache: false,
1631                processData: false,
1632                contentType: false,
1633                beforeSend: fnBefore,
1634                success: fnSuccess,
1635                complete: fnComplete,
1636                error: fnError
1637            }, self.ajaxSettings);
1638            self.ajaxRequests.push($.ajax(settings));
1639        },
1640        _initUploadSuccess: function (out, $thumb, allFiles) {
1641            var self = this, append, data, index, $newThumb, content, config, tags, i,
1642                mergeArray = function (prop, content) {
1643                    if (!(self[prop] instanceof Array)) {
1644                        self[prop] = [];
1645                    }
1646                    if (content && content.length) {
1647                        self[prop] = self[prop].concat(content);
1648                    }
1649                };
1650            if (!self.showPreview || typeof out !== 'object' || $.isEmptyObject(out)) {
1651                return;
1652            }
1653            if (out.initialPreview !== undefined && out.initialPreview.length > 0) {
1654                self.hasInitData = true;
1655                content = out.initialPreview || [];
1656                config = out.initialPreviewConfig || [];
1657                tags = out.initialPreviewThumbTags || [];
1658                append = out.append === undefined || out.append ? true : false;
1659                if (content.length > 0 && !isArray(content)) {
1660                    content = content.split(self.initialPreviewDelimiter);
1661                }
1662                self.overwriteInitial = false;
1663                mergeArray('initialPreview', content);
1664                mergeArray('initialPreviewConfig', config);
1665                mergeArray('initialPreviewThumbTags', tags);
1666                if ($thumb !== undefined) {
1667                    if (!allFiles) {
1668                        index = previewCache.add(self.id, content, config[0], tags[0], append);
1669                        data = previewCache.get(self.id, index, false);
1670                        $newThumb = $(data).hide();
1671                        $thumb.after($newThumb).fadeOut('slow', function () {
1672                            $newThumb.fadeIn('slow').css('display:inline-block');
1673                            self._initPreviewActions();
1674                            self._clearFileInput();
1675                            $thumb.remove();
1676                        });
1677                    } else {
1678                        i = $thumb.attr('data-fileindex');
1679                        self.uploadCache.content[i] = content[0];
1680                        self.uploadCache.config[i] = config[0] || [];
1681                        self.uploadCache.tags[i] = tags[0] || [];
1682                        self.uploadCache.append = append;
1683                    }
1684                } else {
1685                    previewCache.set(self.id, content, config, tags, append);
1686                    self._initPreview();
1687                    self._initPreviewActions();
1688                }
1689            }
1690        },
1691        _initSuccessThumbs: function () {
1692            var self = this;
1693            if (!self.showPreview) {
1694                return;
1695            }
1696            self._getThumbs('.file-preview-success').each(function () {
1697                var $thumb = $(this), $remove = $thumb.find('.kv-file-remove');
1698                $remove.removeAttr('disabled');
1699                handler($remove, 'click', function () {
1700                    var out = self._raise('filesuccessremove', [$thumb.attr('id'), $thumb.data('fileindex')]);
1701                    cleanMemory($thumb);
1702                    if (out === false) {
1703                        return;
1704                    }
1705                    $thumb.fadeOut('slow', function () {
1706                        $thumb.remove();
1707                        if (!self.$preview.find('.file-preview-frame').length) {
1708                            self.reset();
1709                        }
1710                    });
1711                });
1712            });
1713        },
1714        _checkAsyncComplete: function () {
1715            var self = this, previewId, i;
1716            for (i = 0; i < self.filestack.length; i++) {
1717                if (self.filestack[i]) {
1718                    previewId = self.previewInitId + "-" + i;
1719                    if ($.inArray(previewId, self.uploadLog) === -1) {
1720                        return false;
1721                    }
1722                }
1723            }
1724            return (self.uploadAsyncCount === self.uploadLog.length);
1725        },
1726        _uploadExtra: function (previewId, index) {
1727            var self = this, data = self._getExtraData(previewId, index);
1728            if (data.length === 0) {
1729                return;
1730            }
1731            $.each(data, function (key, value) {
1732                self.formdata.append(key, value);
1733            });
1734        },
1735        _uploadSingle: function (i, files, allFiles) {
1736            var self = this, total = self.getFileStack().length, formdata = new FormData(), outData,
1737                previewId = self.previewInitId + "-" + i, $thumb, chkComplete, $btnUpload, $btnDelete,
1738                hasPostData = self.filestack.length > 0 || !$.isEmptyObject(self.uploadExtraData),
1739                fnBefore, fnSuccess, fnComplete, fnError, updateUploadLog, params = {id: previewId, index: i};
1740            self.formdata = formdata;
1741            if (self.showPreview) {
1742                $thumb = $('#' + previewId + ':not(.file-preview-initial)');
1743                $btnUpload = $thumb.find('.kv-file-upload');
1744                $btnDelete = $thumb.find('.kv-file-remove');
1745                $('#' + previewId).find('.file-thumb-progress').removeClass('hide');
1746            }
1747            if (total === 0 || !hasPostData || ($btnUpload && $btnUpload.hasClass('disabled')) || self._abort(params)) {
1748                return;
1749            }
1750            updateUploadLog = function (i, previewId) {
1751                self.updateStack(i, undefined);
1752                self.uploadLog.push(previewId);
1753                if (self._checkAsyncComplete()) {
1754                    self.fileBatchCompleted = true;
1755                }
1756            };
1757            chkComplete = function () {
1758                var u = self.uploadCache;
1759                if (!self.fileBatchCompleted) {
1760                    return;
1761                }
1762                setTimeout(function () {
1763                    if (self.showPreview) {
1764                        previewCache.set(self.id, u.content, u.config, u.tags, u.append);
1765                        if (self.hasInitData) {
1766                            self._initPreview();
1767                            self._initPreviewActions();
1768                        }
1769                    }
1770                    self.unlock();
1771                    self._clearFileInput();
1772                    self._raise('filebatchuploadcomplete', [self.filestack, self._getExtraData()]);
1773                    self.uploadCount = 0;
1774                    self.uploadStatus = {};
1775                    self.uploadLog = [];
1776                    self._setProgress(101);
1777                }, 100);
1778            };
1779            fnBefore = function (jqXHR) {
1780                outData = self._getOutData(jqXHR);
1781                self.fileBatchCompleted = false;
1782                if (self.showPreview) {
1783                    if (!$thumb.hasClass('file-preview-success')) {
1784                        self._setThumbStatus($thumb, 'Loading');
1785                        addCss($thumb, 'file-uploading');
1786                    }
1787                    $btnUpload.attr('disabled', true);
1788                    $btnDelete.attr('disabled', true);
1789                }
1790                if (!allFiles) {
1791                    self.lock();
1792                }
1793                self._raise('filepreupload', [outData, previewId, i]);
1794                $.extend(true, params, outData);
1795                if (self._abort(params)) {
1796                    jqXHR.abort();
1797                    self._setProgressCancelled();
1798                }
1799            };
1800            fnSuccess = function (data, textStatus, jqXHR) {
1801                var pid = self.showPreview && $thumb.attr('id') ? $thumb.attr('id') : previewId;
1802                outData = self._getOutData(jqXHR, data);
1803                $.extend(true, params, outData);
1804                setTimeout(function () {
1805                    if (isEmpty(data) || isEmpty(data.error)) {
1806                        if (self.showPreview) {
1807                            self._setThumbStatus($thumb, 'Success');
1808                            $btnUpload.hide();
1809                            self._initUploadSuccess(data, $thumb, allFiles);
1810                        }
1811                        self._raise('fileuploaded', [outData, pid, i]);
1812                        if (!allFiles) {
1813                            self.updateStack(i, undefined);
1814                        } else {
1815                            updateUploadLog(i, pid);
1816                        }
1817                    } else {
1818                        self._showUploadError(data.error, params);
1819                        self._setPreviewError($thumb, i);
1820                        if (allFiles) {
1821                            updateUploadLog(i, pid);
1822                        }
1823                    }
1824                }, 100);
1825            };
1826            fnComplete = function () {
1827                setTimeout(function () {
1828                    if (self.showPreview) {
1829                        $btnUpload.removeAttr('disabled');
1830                        $btnDelete.removeAttr('disabled');
1831                        $thumb.removeClass('file-uploading');
1832                        self._setProgress(101, $('#' + previewId).find('.file-thumb-progress'));
1833                    }
1834                    if (!allFiles) {
1835                        self.unlock(false);
1836                        self._clearFileInput();
1837                    } else {
1838                        chkComplete();
1839                    }
1840                    self._initSuccessThumbs();
1841                }, 100);
1842            };
1843            fnError = function (jqXHR, textStatus, errorThrown) {
1844                var errMsg = self._parseError(jqXHR, errorThrown, (allFiles ? files[i].name : null));
1845                setTimeout(function () {
1846                    if (allFiles) {
1847                        updateUploadLog(i, previewId);
1848                    }
1849                    self.uploadStatus[previewId] = 100;
1850                    self._setPreviewError($thumb, i);
1851                    $.extend(true, params, self._getOutData(jqXHR));
1852                    self._showUploadError(errMsg, params);
1853                }, 100);
1854            };
1855            formdata.append(self.uploadFileAttr, files[i], self.filenames[i]);
1856            formdata.append('file_id', i);
1857            self._ajaxSubmit(fnBefore, fnSuccess, fnComplete, fnError, previewId, i);
1858        },
1859        _uploadBatch: function () {
1860            var self = this, files = self.filestack, total = files.length, params = {}, fnBefore, fnSuccess, fnError,
1861                fnComplete, hasPostData = self.filestack.length > 0 || !$.isEmptyObject(self.uploadExtraData),
1862                setAllUploaded;
1863            self.formdata = new FormData();
1864            if (total === 0 || !hasPostData || self._abort(params)) {
1865                return;
1866            }
1867            setAllUploaded = function () {
1868                $.each(files, function (key) {
1869                    self.updateStack(key, undefined);
1870                });
1871                self._clearFileInput();
1872            };
1873            fnBefore = function (jqXHR) {
1874                self.lock();
1875                var outData = self._getOutData(jqXHR);
1876                if (self.showPreview) {
1877                    self._getThumbs().each(function () {
1878                        var $thumb = $(this), $btnUpload = $thumb.find('.kv-file-upload'),
1879                            $btnDelete = $thumb.find('.kv-file-remove');
1880                        if (!$thumb.hasClass('file-preview-success')) {
1881                            self._setThumbStatus($thumb, 'Loading');
1882                            addCss($thumb, 'file-uploading');
1883                        }
1884                        $btnUpload.attr('disabled', true);
1885                        $btnDelete.attr('disabled', true);
1886                    });
1887                }
1888                self._raise('filebatchpreupload', [outData]);
1889                if (self._abort(outData)) {
1890                    jqXHR.abort();
1891                    self._setProgressCancelled();
1892                }
1893            };
1894            fnSuccess = function (data, textStatus, jqXHR) {
1895                /** @namespace data.errorkeys */
1896                var outData = self._getOutData(jqXHR, data), $thumbs = self._getThumbs(':not(.file-preview-error)'), key = 0,
1897                    keys = isEmpty(data) || isEmpty(data.errorkeys) ? [] : data.errorkeys;
1898                if (isEmpty(data) || isEmpty(data.error)) {
1899                    self._raise('filebatchuploadsuccess', [outData]);
1900                    setAllUploaded();
1901                    if (self.showPreview) {
1902                        $thumbs.each(function () {
1903                            var $thumb = $(this), $btnUpload = $thumb.find('.kv-file-upload');
1904                            $thumb.find('.kv-file-upload').hide();
1905                            self._setThumbStatus($thumb, 'Success');
1906                            $thumb.removeClass('file-uploading');
1907                            $btnUpload.removeAttr('disabled');
1908                        });
1909                        self._initUploadSuccess(data);
1910                    } else {
1911                        self.reset();
1912                    }
1913                } else {
1914                    if (self.showPreview) {
1915                        $thumbs.each(function () {
1916                            var $thumb = $(this), $btnDelete = $thumb.find('.kv-file-remove'),
1917                                $btnUpload = $thumb.find('.kv-file-upload');
1918                            $thumb.removeClass('file-uploading');
1919                            $btnUpload.removeAttr('disabled');
1920                            $btnDelete.removeAttr('disabled');
1921                            if (keys.length === 0) {
1922                                self._setPreviewError($thumb);
1923                                return;
1924                            }
1925                            if ($.inArray(key, keys) !== -1) {
1926                                self._setPreviewError($thumb);
1927                            } else {
1928                                $thumb.find('.kv-file-upload').hide();
1929                                self._setThumbStatus($thumb, 'Success');
1930                                self.updateStack(key, undefined);
1931                            }
1932                            key++;
1933                        });
1934                        self._initUploadSuccess(data);
1935                    }
1936                    self._showUploadError(data.error, outData, 'filebatchuploaderror');
1937                }
1938            };
1939            fnComplete = function () {
1940                self._setProgress(101);
1941                self.unlock();
1942                self._initSuccessThumbs();
1943                self._clearFileInput();
1944                self._raise('filebatchuploadcomplete', [self.filestack, self._getExtraData()]);
1945            };
1946            fnError = function (jqXHR, textStatus, errorThrown) {
1947                var outData = self._getOutData(jqXHR), errMsg = self._parseError(jqXHR, errorThrown);
1948                self._showUploadError(errMsg, outData, 'filebatchuploaderror');
1949                self.uploadFileCount = total - 1;
1950                if (!self.showPreview) {
1951                    return;
1952                }
1953                self._getThumbs().each(function () {
1954                    var $thumb = $(this), key = $thumb.attr('data-fileindex');
1955                    $thumb.removeClass('file-uploading');
1956                    if (self.filestack[key] !== undefined) {
1957                        self._setPreviewError($thumb);
1958                    }
1959                });
1960                self._getThumbs().removeClass('file-uploading');
1961                self._getThumbs(' .kv-file-upload').removeAttr('disabled');
1962                self._getThumbs(' .kv-file-delete').removeAttr('disabled');
1963            };
1964            $.each(files, function (key, data) {
1965                if (!isEmpty(files[key])) {
1966                    self.formdata.append(self.uploadFileAttr, data, self.filenames[key]);
1967                }
1968            });
1969            self._ajaxSubmit(fnBefore, fnSuccess, fnComplete, fnError);
1970        },
1971        _uploadExtraOnly: function () {
1972            var self = this, params = {}, fnBefore, fnSuccess, fnComplete, fnError;
1973            self.formdata = new FormData();
1974            if (self._abort(params)) {
1975                return;
1976            }
1977            fnBefore = function (jqXHR) {
1978                self.lock();
1979                var outData = self._getOutData(jqXHR);
1980                self._raise('filebatchpreupload', [outData]);
1981                self._setProgress(50);
1982                params.data = outData;
1983                params.xhr = jqXHR;
1984                if (self._abort(params)) {
1985                    jqXHR.abort();
1986                    self._setProgressCancelled();
1987                }
1988            };
1989            fnSuccess = function (data, textStatus, jqXHR) {
1990                var outData = self._getOutData(jqXHR, data);
1991                if (isEmpty(data) || isEmpty(data.error)) {
1992                    self._raise('filebatchuploadsuccess', [outData]);
1993                    self._clearFileInput();
1994                    self._initUploadSuccess(data);
1995                } else {
1996                    self._showUploadError(data.error, outData, 'filebatchuploaderror');
1997                }
1998            };
1999            fnComplete = function () {
2000                self._setProgress(101);
2001                self.unlock();
2002                self._clearFileInput();
2003                self._raise('filebatchuploadcomplete', [self.filestack, self._getExtraData()]);
2004            };
2005            fnError = function (jqXHR, textStatus, errorThrown) {
2006                var outData = self._getOutData(jqXHR), errMsg = self._parseError(jqXHR, errorThrown);
2007                params.data = outData;
2008                self._showUploadError(errMsg, outData, 'filebatchuploaderror');
2009            };
2010            self._ajaxSubmit(fnBefore, fnSuccess, fnComplete, fnError);
2011        },
2012        _initFileActions: function () {
2013            var self = this;
2014            if (!self.showPreview) {
2015                return;
2016            }
2017            self._initZoomButton();
2018            self.$preview.find('.kv-file-remove').each(function () {
2019                var $el = $(this), $frame = $el.closest('.file-preview-frame'), hasError,
2020                    id = $frame.attr('id'), ind = $frame.attr('data-fileindex'), n, cap, status;
2021                handler($el, 'click', function () {
2022                    status = self._raise('filepreremove', [id, ind]);
2023                    if (status === false || !self._validateMinCount()) {
2024                        return false;
2025                    }
2026                    hasError = $frame.hasClass('file-preview-error');
2027                    cleanMemory($frame);
2028                    $frame.fadeOut('slow', function () {
2029                        self.updateStack(ind, undefined);
2030                        self._clearObjects($frame);
2031                        $frame.remove();
2032                        if (id && hasError) {
2033                            self.$errorContainer.find('li[data-file-id="' + id + '"]').fadeOut('fast', function () {
2034                                $(this).remove();
2035                                if (!self._errorsExist()) {
2036                                    self._resetErrors();
2037                                }
2038                            });
2039                        }
2040                        self._clearFileInput();
2041                        var filestack = self.getFileStack(true), chk = previewCache.count(self.id),
2042                            len = filestack.length,
2043                            hasThumb = self.showPreview && self.$preview.find('.file-preview-frame').length;
2044                        if (len === 0 && chk === 0 && !hasThumb) {
2045                            self.reset();
2046                        } else {
2047                            n = chk + len;
2048                            cap = n > 1 ? self._getMsgSelected(n) : (filestack[0] ? self._getFileNames()[0] : '');
2049                            self._setCaption(cap);
2050                        }
2051                        self._raise('fileremoved', [id, ind]);
2052                    });
2053                });
2054            });
2055            self.$preview.find('.kv-file-upload').each(function () {
2056                var $el = $(this);
2057                handler($el, 'click', function () {
2058                    var $frame = $el.closest('.file-preview-frame'),
2059                        ind = $frame.attr('data-fileindex');
2060                    if (!$frame.hasClass('file-preview-error')) {
2061                        self._uploadSingle(ind, self.filestack, false);
2062                    }
2063                });
2064            });
2065        },
2066        _hideFileIcon: function () {
2067            if (this.overwriteInitial) {
2068                this.$captionContainer.find('.kv-caption-icon').hide();
2069            }
2070        },
2071        _showFileIcon: function () {
2072            this.$captionContainer.find('.kv-caption-icon').show();
2073        },
2074        _getSize: function (bytes) {
2075            var self = this, size = parseFloat(bytes);
2076            if (!bytes || !size || isNaN(bytes) || isNaN(size)) {
2077                return self._getLayoutTemplate('size').replace('{sizeText}', '0.00 KB');
2078            }
2079            var  i, func = self.fileSizeGetter, sizes, out;
2080            if (typeof func === 'function') {
2081                out = func(bytes);
2082            } else {
2083                i = Math.floor(Math.log(size) / Math.log(1024));
2084                sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
2085                out = (size / Math.pow(1024, i)).toFixed(2) * 1 + ' ' + sizes[i];
2086            }
2087            return self._getLayoutTemplate('size').replace('{sizeText}', out);
2088        },
2089        _generatePreviewTemplate: function (cat, data, fname, ftype, previewId, isError, size, frameClass, foot, ind) {
2090            var self = this, tmplt = self._getPreviewTemplate(cat), content, sText, css = frameClass || '',
2091                config = ifSet(cat, self.previewSettings, defaultPreviewSettings[cat]), caption = self.slug(fname),
2092                footer = foot || self._renderFileFooter(caption, size, config.width, isError);
2093            ind = ind || previewId.slice(previewId.lastIndexOf('-') + 1);
2094            tmplt = self._parseFilePreviewIcon(tmplt, fname);
2095            if (cat === 'text' || cat === 'html') {
2096                sText = cat === 'text' ? htmlEncode(data) : data;
2097                content = tmplt.replace(/\{previewId}/g, previewId).replace(/\{caption}/g, caption)
2098                    .replace(/\{width}/g, config.width).replace(/\{height}/g, config.height)
2099                    .replace(/\{frameClass}/g, css).replace(/\{cat}/g, ftype)
2100                    .replace(/\{footer}/g, footer).replace(/\{fileindex}/g, ind)
2101                    .replace(/\{data}/g, sText).replace(/\{template}/g, cat);
2102            } else {
2103                content = tmplt.replace(/\{previewId}
2103/g, previewId).replace(/\{caption}/g, caption)
2104                    .replace(/\{frameClass}/g, css).replace(/\{type}/g, ftype).replace(/\{fileindex}/g, ind)
2105                    .replace(/\{width}/g, config.width).replace(/\{height}/g, config.height)
2106                    .replace(/\{footer}/g, footer).replace(/\{data}/g, data).replace(/\{template}/g, cat);
2107            }
2108            return content;
2109        },
2110        _previewDefault: function (file, previewId, isDisabled) {
2111            var self = this, $preview = self.$preview, $previewLive = $preview.find('.file-live-thumbs');
2112            if (!self.showPreview) {
2113                return;
2114            }
2115            var fname = file ? file.name : '', ftype = file ? file.type : '', content,
2116                isError = isDisabled === true && !self.isUploadable, data = objUrl.createObjectURL(file);
2117            self._clearDefaultPreview();
2118            content = self._generatePreviewTemplate('other', data, fname, ftype, previewId, isError, file.size);
2119            if (!$previewLive.length) {
2120                $previewLive = $(document.createElement('div')).addClass('file-live-thumbs').appendTo($preview);
2121            }
2122            $previewLive.append("\n" + content);
2123            if (isDisabled === true && self.isUploadable) {
2124                self._setThumbStatus($('#' + previewId), 'Error');
2125            }
2126        },
2127        _previewFile: function (i, file, theFile, previewId, data) {
2128            if (!this.showPreview) {
2129                return;
2130            }
2131            var self = this, cat = self._parseFileType(file), fname = file ? file.name : '', caption = self.slug(fname),
2132                types = self.allowedPreviewTypes, mimes = self.allowedPreviewMimeTypes, $preview = self.$preview,
2133                chkTypes = types && types.indexOf(cat) >= 0, $previewLive = $preview.find('.file-live-thumbs'),
2134                iData = (cat === 'text' || cat === 'html' || cat === 'image') ? theFile.target.result : data, content,
2135                chkMimes = mimes && mimes.indexOf(file.type) !== -1;
2136            if (!$previewLive.length) {
2137                $previewLive = $(document.createElement('div')).addClass('file-live-thumbs').appendTo($preview);
2138            }
2139            /** @namespace window.DOMPurify */
2140            if (cat === 'html' && self.purifyHtml && window.DOMPurify) {
2141                iData = window.DOMPurify.sanitize(iData);
2142            }
2143            if (chkTypes || chkMimes) {
2144                content = self._generatePreviewTemplate(cat, iData, fname, file.type, previewId, false, file.size);
2145                self._clearDefaultPreview();
2146                $previewLive.append("\n" + content);
2147                self._validateImage(i, previewId, caption, file.type);
2148            } else {
2149                self._previewDefault(file, previewId);
2150            }
2151            self._initSortable();
2152        },
2153        _slugDefault: function (text) {
2154            return isEmpty(text) ? '' : String(text).replace(/[\-\[\]\/\{}:;#%=\(\)\*\+\?\\\^\$\|<>&"']/g, '_');
2155        },
2156        _readFiles: function (files) {
2157            this.reader = new FileReader();
2158            var self = this, $el = self.$element, $preview = self.$preview, reader = self.reader,
2159                $container = self.$previewContainer, $status = self.$previewStatus, msgLoading = self.msgLoading,
2160                msgProgress = self.msgProgress, previewInitId = self.previewInitId, numFiles = files.length,
2161                settings = self.fileTypeSettings, ctr = self.filestack.length, readFile,
2162                maxPreviewSize = self.maxFilePreviewSize && parseFloat(self.maxFilePreviewSize),
2163                canPreview = $preview.length && (!maxPreviewSize || isNaN(maxPreviewSize)),
2164                throwError = function (msg, file, previewId, index) {
2165                    var p1 = $.extend(true, {}, self._getOutData({}, {}, files), {id: previewId, index: index}),
2166                        p2 = {id: previewId, index: index, file: file, files: files};
2167                    self._previewDefault(file, previewId, true);
2168                    if (self.isUploadable) {
2169                        self.addToStack(undefined);
2170                        setTimeout(function () {
2171                            readFile(index + 1);
2172                        }, 100);
2173                    }
2174                    self._initFileActions();
2175                    if (self.removeFromPreviewOnError) {
2176                        $('#' + previewId).remove();
2177                    }
2178                    return self.isUploadable ? self._showUploadError(msg, p1) : self._showError(msg, p2);
2179                };
2180
2181            self.loadedImages = [];
2182            self.totalImagesCount = 0;
2183
2184            $.each(files, function (key, file) {
2185                var func = self.fileTypeSettings.image || defaultFileTypeSettings.image;
2186                if (func && func(file.type)) {
2187                    self.totalImagesCount++;
2188                }
2189            });
2190            readFile = function (i) {
2191                if (isEmpty($el.attr('multiple'))) {
2192                    numFiles = 1;
2193                }
2194                if (i >= numFiles) {
2195                    if (self.isUploadable && self.filestack.length > 0) {
2196                        self._raise('filebatchselected', [self.getFileStack()]);
2197                    } else {
2198                        self._raise('filebatchselected', [files]);
2199                    }
2200                    $container.removeClass('file-thumb-loading');
2201                    $status.html('');
2202                    return;
2203                }
2204                var node = ctr + i, previewId = previewInitId + "-" + node, isText, isImage, file = files[i], f
2204SizeKB,
2205                    caption = file.name ? self.slug(file.name) : '', fileSize = (file.size || 0) / 1000, checkFile,
2206                    fileExtExpr = '', previewData = objUrl.createObjectURL(file), fileCount = 0, j, msg, typ, chk,
2207                    fileTypes = self.allowedFileTypes, strTypes = isEmpty(fileTypes) ? '' : fileTypes.join(', '),
2208                    fileExt = self.allowedFileExtensions, strExt = isEmpty(fileExt) ? '' : fileExt.join(', ');
2209
2210                if (caption === false) {
2211                    readFile(i + 1);
2212                    return;
2213                }
2214                if (caption.length === 0) {
2215                    msg = self.msgInvalidFileName.replace('{name}', htmlEncode(file.name));
2216                    self.isError = throwError(msg, file, previewId, i);
2217                    return;
2218                }
2219                if (!isEmpty(fileExt)) {
2220                    fileExtExpr = new RegExp('\\.(' + fileExt.join('|') + ')$', 'i');
2221                }
2222                fSizeKB = fileSize.toFixed(2);
2223                if (self.maxFileSize > 0 && fileSize > self.maxFileSize) {
2224                    msg = self.msgSizeTooLarge.replace('{name}', caption).replace('{size}', fSizeKB)
2225                        .replace('{maxSize}', self.maxFileSize);
2226                    self.isError = throwError(msg, file, previewId, i);
2227                    return;
2228                }
2229                if (self.minFileSize !== null && fileSize <= getNum(self.minFileSize)) {
2230                    msg = self.msgSizeTooSmall.replace('{name}', caption).replace('{size}', fSizeKB)
2231                        .replace('{minSize}', self.minFileSize);
2232                    self.isError = throwError(msg, file, previewId, i);
2233                    return;
2234                }
2235                if (!isEmpty(fileTypes) && isArray(fileTypes)) {
2236                    for (j = 0; j < fileTypes.length; j += 1) {
2237                        typ = fileTypes[j];
2238                        checkFile = settings[typ];
2239                        chk = (checkFile !== undefined && checkFile(file.type, caption));
2240                        fileCount += isEmpty(chk) ? 0 : chk.length;
2241                    }
2242                    if (fileCount === 0) {
2243                        msg = self.msgInvalidFileType.replace('{name}', caption).replace('{types}', strTypes);
2244                        self.isError = throwError(msg, file, previewId, i);
2245                        return;
2246                    }
2247                }
2248                if (fileCount === 0 && !isEmpty(fileExt) && isArray(fileExt) && !isEmpty(fileExtExpr)) {
2249                    chk = compare(caption, fileExtExpr);
2250                    fileCount += isEmpty(chk) ? 0 : chk.length;
2251                    if (fileCount === 0) {
2252                        msg = self.msgInvalidFileExtension.replace('{name}', caption).replace('{extensions}', strExt);
2253                        self.isError = throwError(msg, file, previewId, i);
2254                        return;
2255                    }
2256                }
2257                if (!self.showPreview) {
2258                    self.addToStack(file);
2259                    setTimeout(function () {
2260                        readFile(i + 1);
2261                    }, 100);
2262                    self._raise('fileloaded', [file, previewId, i, reader]);
2263                    return;
2264                }
2265                if (!canPreview && fileSize > maxPreviewSize) {
2266                    self.addToStack(file);
2267                    $container.addClass('file-thumb-loading');
2268                    self._previewDefault(file, previewId);
2269                    self._initFileActions();
2270                    self._updateFileDetails(numFiles);
2271                    readFile(i + 1);
2272                    return;
2273                }
2274                if ($preview.length && FileReader !== undefined) {
2275                    $status.html(msgLoading.replace('{index}', i + 1).replace('{files}', numFiles));
2276                    $container.addClass('file-thumb-loading');
2277                    reader.onerror = function (evt) {
2278                        self._errorHandler(evt, caption);
2279                    };
2280                    reader.onload = function (theFile) {
2281                        self._previewFile(i, file, theFile, previewId, previewData);
2282                        self._initFileActions();
2283                    };
2284                    reader.onloadend = function () {
2285                        msg = msgProgress.replace('{index}', i + 1).replace('{files}', numFiles)
2286                            .replace('{percent}', 50).replace('{name}', caption);
2287                        setTimeout(function () {
2288                            $status.html(msg);
2289                            self._updateFileDetails(numFiles);
2290                            readFile(i + 1);
2291                        }, 100);
2292                        self._raise('fileloaded', [file, previewId, i, reader]);
2293                    };
2294                    reader.onprogress = function (data) {
2295                        if (data.lengthComputable) {
2296                            var fact = (data.loaded / data.total) * 100, progress = Math.ceil(fact);
2297                            msg = msgProgress.replace('{index}', i + 1).replace('{files}', numFiles)
2298                                .replace('{percent}', progress).replace('{name}', caption);
2299                            setTimeout(function () {
2300                                $status.html(msg);
2301                            }, 100);
2302                        }
2303                    };
2304                    isText = ifSet('text', settings, defaultFileTypeSettings.text);
2305                    isImage = ifSet('image', settings, defaultFileTypeSettings.image);
2306
2307                    if (isText(file.type, caption)) {
2308                        reader.readAsText(file, self.textEncoding);
2309                    } else {
2310                        if (isImage(file.type, caption)) {
2311                            reader.readAsDataURL(file);
2312                        } else {
2313                            reader.readAsArrayBuffer(file);
2314                        }
2315                    }
2316                } else {
2317                    self._previewDefault(file, previewId);
2318                    setTimeout(function () {
2319                        readFile(i + 1);
2320                        self._updateFileDetails(numFiles);
2321                    }, 100);
2322                    self._raise('fileloaded', [file, previewId, i, reader]);
2323                }
2324                self.addToStack(file);
2325            };
2326
2327            readFile(0);
2328            self._updateFileDetails(numFiles, false);
2329        },
2330        _updateFileDetails: function (numFiles) {
2331            var self = this, $el = self.$element, fileStack = self.getFileStack(),
2332                name = (isIE(9) && findFileName($el.val())) ||
2333                    ($el[0].files[0] && $el[0].files[0].name) || (fileStack.length && fileStack[0].name) || '',
2334                label = self.slug(name), n = self.isUploadable ? fileStack.length : numFiles,
2335                nFiles = previewCache.count(self.id) + n, log = n > 1 ? self._getMsgSelected(nFiles) : label;
2336            if (self.isError) {
2337                self.$previewContainer.removeClass('file-thumb-loading');
2338                self.$previewStatus.html('');
2339                self.$captionContainer.find('.kv-caption-icon').hide();
2340            } else {
2341                self._showFileIcon();
2342            }
2343            self._setCaption(log, self.isError);
2344            self.$container.removeClass('file-input-new file-input-ajax-new');
2345            if (arguments.length === 1) {
2346                self._raise('fileselect', [numFiles, label]);
2347            }
2348            if (previewCache.count(self.id)) {
2349                self._initPreviewActions();
2350            }
2351        },
2352        _setThumbStatus: function ($thumb, status) {
2353            var self = this;
2354            if (!self.showPreview) {
2355                return;
2356            }
2357            var icon = 'indicator' + status, msg = icon + 'Title',
2358                css = 'file-preview-' + status.toLowerCase(),
2359                $indicator = $thumb.find('.file-upload-indicator'),
2360                config = self.fileActionSettings;
2361            $thumb.removeClass('file-preview-success file-preview-error file-preview-loading');
2362            if (status === 'Error') {
2363                $thumb.find('.kv-file-upload').attr('disabled', true);
2364            }
2365            if (status === 'Success') {
2366                $thumb.find('.file-drag-handle').remove();
2367                $indicator.css('margin-left', 0);
2368            }
2369            $indicator.html(config[icon]);
2370            $indicator.attr('title', config[msg]);
2371            $thumb.addClass(css);
2372        },
2373        _setProgressCancelled: function () {
2374            var self = this;
2375            self._setProgress(101, self.$progress, self.msgCancelled);
2376        },
2377        _setProgress: function (p, $el, error) {
2378            var self = this, pct = Math.min(p, 100), template = pct < 100 ? self.progressTemplate :
2379                    (error ? self.progressErrorTemplate : (p <= 100 ? self.progressTemplate : self.progressCompleteTemplate)),
2380                pctLimit = self.progressUploadThreshold;
2381            $el = $el || self.$progress;
2382            if (!isEmpty(template)) {
2383                if (pctLimit && pct > pctLimit && p <= 100) {
2384                    var out = template.replace('{percent}', pctLimit).replace('{percent}', pctLimit).replace('{percent}%', self.msgUploadThreshold);
2385                    $el.html(out);
2386                } else {
2387                    $el.html(template.replace(/\{percent}/g, pct));
2388                }
2389                if (error) {
2390                    $el.find('[role="progressbar"]').html(error);
2391                }
2392            }
2393        },
2394        _setFileDropZoneTitle: function () {
2395            var self = this, $zone = self.$container.find('.file-drop-zone'), title = self.dropZoneTitle, strFiles;
2396            if (self.isClickable) {
2397                strFiles = isEmpty(self.$element.attr('multiple')) ? self.fileSingle : self.filePlural;
2398                title += self.dropZoneClickTitle.replace('{files}', strFiles);
2399            }
2400            $zone.find('.' + self.dropZoneTitleClass).remove();
2401            if (!self.isUploadable || !self.showPreview || $zone.length === 0 || self.getFileStack().length > 0 || !self.dropZoneEnabled) {
2402                return;
2403            }
2404            if ($zone.find('.file-preview-frame').length === 0 && isEmpty(self.defaultPreviewContent)) {
2405                $zone.prepend('<div class="' + self.dropZoneTitleClass + '">' + title + '</div>');
2406            }
2407            self.$container.removeClass('file-input-new');
2408            addCss(self.$container, 'file-input-ajax-new');
2409        },
2410        _setAsyncUploadStatus: function (previewId, pct, total) {
2411            var self = this, sum = 0;
2412            self._setProgress(pct, $('#' + previewId).find('.file-thumb-progress'));
2413            self.uploadStatus[previewId] = pct;
2414            $.each(self.uploadStatus, function (key, value) {
2415                sum += value;
2416            });
2417            self._setProgress(Math.floor(sum / total));
2418
2419        },
2420        _validateMinCount: function () {
2421            var self = this, len = self.isUploadable ? self.getFileStack().length : self.$element.get(0).files.length;
2422            if (self.validateInitialCount && self.minFileCount > 0 && self._getFileCount(len - 1) < self.minFileCount) {
2423                self._noFilesError({});
2424                return false;
2425            }
2426            return true;
2427        },
2428        _getFileCount: function (fileCount) {
2429            var self = this, addCount = 0;
2430            if (self.validateInitialCount && !self.overwriteInitial) {
2431                addCount = previewCache.count(self.id);
2432                fileCount += addCount;
2433            }
2434            return fileCount;
2435        },
2436        _getFileName: function (file) {
2437            return file && file.name ? this.slug(file.name) : undefined;
2438        },
2439        _getFileNames: function (skipNull) {
2440            var self = this;
2441            return self.filenames.filter(function (n) {
2442                return (skipNull ? n !== undefined : n !== undefined && n !== null);
2443            });
2444        },
2445        _setPreviewError: function ($thumb, i, val) {
2446            var self = this;
2447            if (i !== undefined) {
2448                self.updateStack(i, val);
2449            }
2450            if (self.removeFromPreviewOnError) {
2451                $thumb.remove();
2452            } else {
2453                self._setThumbStatus($thumb, 'Error');
2454            }
2455        },
2456        _checkDimensions: function (i, chk, $img, $thumb, fname, type, params) {
2457            var self = this, msg, dim, tag = chk === 'Small' ? 'min' : 'max', limit = self[tag + 'Image' + type],
2458                $imgEl, isValid;
2459            if (isEmpty(limit) || !$img.length) {
2460                return;
2461            }
2462            $imgEl = $img[0];
2463            dim = (type === 'Width') ? $imgEl.naturalWidth || $imgEl.width : $imgEl.naturalHeight || $imgEl.height;
2464            isValid = chk === 'Small' ? dim >= limit : dim <= limit;
2465            if (isValid) {
2466                return;
2467            }
2468            msg = self['msgImage' + type + chk].replace('{name}', fname).replace('{size}', limit);
2469            self._showUploadError(msg, params);
2470            self._setPreviewError($thumb, i, null);
2471        },
2472        _validateImage: function (i, previewId, fname, ftype) {
2473            var self = this, $preview = self.$preview, params, w1, w2,
2474                $thumb = $preview.find("#" + previewId), $img = $thumb.find('img');
2475            fname = fname || 'Untitled';
2476            if (!$img.length) {
2477                return;
2478            }
2479            handler($img, 'load', function () {
2480                w1 = $thumb.width();
2481                w2 = $preview.width();
2482                if (w1 > w2) {
2483                    $img.css('width', '100%');
2484                    $thumb.css('width', '97%');
2485                }
2486                params = {ind: i, id: previewId};
2487                self._checkDimensions(i, 'Small', $img, $thumb, fname, 'Width', params);
2488                self._checkDimensions(i, 'Small', $img, $thumb, fname, 'Height', params);
2489                if (!self.resizeImage) {
2490                    self._checkDimensions(i, 'Large', $img, $thumb, fname, 'Width', params);
2491                    self._checkDimensions(i, 'Large', $img, $thumb, fname, 'Height', params);
2492                }
2493                self._raise('fileimageloaded', [previewId]);
2494                self.loadedImages.push({ind: i, img: $img, thumb: $thumb, pid: previewId, typ: ftype});
2495                self._validateAllImages();
2496            });
2497        },
2498        _validateAllImages: function () {
2499            var self = this, i, config, $img, $thumb, pid, ind, params = {}, errFunc;
2500            if (self.loadedImages.length !== self.totalImagesCount) {
2501                return;
2502            }
2503            self._raise('fileimagesloaded');
2504            if (!self.resizeImage) {
2505                return;
2506            }
2507            errFunc = self.isUploadable ? self._showUploadError : self._showError;
2508            var counter = {val: 0};
2509            for (i = 0; i < self.loadedImages.length; i++) {
2510                config = self.loadedImages[i];
2511                $img = config.img;
2512                $thumb = config.thumb;
2513                pid = config.pid;
2514                ind = config.ind;
2515                params = {id: pid, 'index': ind};
2516                if (!self._getResizedImage($img[0], config.typ, pid, ind, counter, self.loadedImages.length)) {
2517                    errFunc(self.msgImageResizeError, params, 'fileimageresizeerror');
2518                    self._setPreviewError($thumb, ind);
2519                }
2520            }
2521        },
2522        _getResizedImage: function (image, type, pid, ind, counter, num_imgs) {
2523            var self = this, width = image.naturalWidth, height = image.naturalHeight, ratio = 1,
2524                maxWidth = self.maxImageWidth || width, maxHeight = self.maxImageHeight || height,
2525                isValidImage = (width && height), chkWidth, chkHeight,
2526                canvas = self.imageCanvas, context = self.imageCanvasContext;
2527            if (!isValidImage) {
2528                counter.val++;
2529                if (counter.val === num_imgs) {
2530                    self._raise('fileimagesresized');
2531                }
2532                return false;
2533            }
2534            if (width === maxWidth && height === maxHeight) {
2535                self._raise('fileimageresized', [pid, ind]);
2536                counter.val++;
2537                if (counter.val === num_imgs) {
2538                    self._raise('fileimagesresized');
2539                }
2540                return true;
2541            }
2542            type = type || self.resizeDefaultImageType;
2543            chkWidth = width > maxWidth;
2544            chkHeight = height > maxHeight;
2545            if (self.resizePreference === 'width') {
2546                ratio = chkWidth ? maxWidth / width : (chkHeight ? maxHeight / height : 1);
2547            } else {
2548                ratio = chkHeight ? maxHeight / height : (chkWidth ? maxWidth / width : 1);
2549            }
2550            self._resetCanvas();
2551            width *= ratio;
2552            height *= ratio;
2553            canvas.width = width;
2554            canvas.height = height;
2555            try {
2556                context.drawImage(image, 0, 0, width, height);
2557                canvas.toBlob(function (blob) {
2558                    self.filestack[ind] = blob;
2559                    self._raise('fileimageresized', [pid, ind]);
2560                    counter.val++;
2561                    if (counter.val === num_imgs) {
2562                        self._raise('fileimagesresized', [undefined, undefined]);
2563                    }
2564                }, type, self.resizeQuality);
2565                return true;
2566            }
2567            catch (err) {
2568                counter.val++;
2569                if (counter.val === num_imgs) {
2570                    self._raise('fileimagesresized', [undefined, undefined]);
2571                }
2572                return false;
2573            }
2574        },
2575        _initBrowse: function ($container) {
2576            var self = this;
2577            if (self.showBrowse) {
2578                self.$btnFile = $container.find('.btn-file');
2579                self.$btnFile.append(self.$element);
2580            } else {
2581                self.$element.hide();
2582            }
2583        },
2584        _initCaption: function () {
2585            var self = this, cap = self.initialCaption || '';
2586            if (self.overwriteInitial || isEmpty(cap)) {
2587                self.$caption.html('');
2588                return false;
2589            }
2590            self._setCaption(cap);
2591            return true;
2592        },
2593        _setCaption: function (content, isError) {
2594            var self = this, title, out, n, cap, stack = self.getFileStack();
2595            if (!self.$caption.length) {
2596                return;
2597            }
2598            if (isError) {
2599                title = $('<div>' + self.msgValidationError + '</div>').text();
2600                n = stack.length;
2601                if (n) {
2602                    cap = n === 1 && stack[0] ? self._getFileNames()[0] : self._getMsgSelected(n);
2603                } else {
2604                    cap = self._getMsgSelected(self.msgNo);
2605                }
2606                out = '<span class="' + self.msgValidationErrorClass + '">' + self.msgValidationErrorIcon +
2607                    (isEmpty(content) ? cap : content) + '</span>';
2608            } else {
2609                if (isEmpty(content)) {
2610                    return;
2611                }
2612                title = $('<div>' + content + '</div>').text();
2613                out = self._getLayoutTemplate('fileIcon') + title;
2614            }
2615            self.$caption.html(out);
2616            self.$caption.attr('title', title);
2617            self.$captionContainer.find('.file-caption-ellipsis').attr('title', title);
2618        },
2619        _createContainer: function () {
2620            var self = this, $container = $(document.createElement("div"))
2621                .attr({"class": 'file-input file-input-new'})
2622                .html(self._renderMain());
2623            self.$element.before($container);
2624            self._initBrowse($container);
2625            if (self.theme) {
2626                $container.addClass('theme-' + self.theme);
2627            }
2628            return $container;
2629        },
2630        _refreshContainer: function () {
2631            var self = this, $container = self.$container;
2632            $container.before(self.$element);
2633            $container.html(self._renderMain());
2634            self._initBrowse($container);
2635        },
2636        _renderMain: function () {
2637            var self = this, dropCss = (self.isUploadable && self.dropZoneEnabled) ? ' file-drop-zone' : 'file-drop-disabled',
2638                close = !self.showClose ? '' : self._getLayoutTemplate('close'),
2639                preview = !self.showPreview ? '' : self._getLayoutTemplate('preview')
2640                    .replace(/\{class}/g, self.previewClass)
2641                    .replace(/\{dropClass}/g, dropCss),
2642                css = self.isDisabled ? self.captionClass + ' file-caption-disabled' : self.captionClass,
2643                caption = self.captionTemplate.replace(/\{class}/g, css + ' kv-fileinput-caption');
2644            return self.mainTemplate.replace(/\{class}/g, self.mainClass +
2645                (!self.showBrowse && self.showCaption ? ' no-browse' : ''))
2646                .replace(/\{preview}/g, preview)
2647                .replace(/\{close}/g, close)
2648                .replace(/\{caption}/g, caption)
2649                .replace(/\{upload}/g, self._renderButton('upload'))
2650                .replace(/\{remove}/g, self._renderButton('remove'))
2651                .replace(/\{cancel}/g, self._renderButton('cancel'))
2652                .replace(/\{browse}/g, self._renderButton('browse'));
2653        },
2654        _renderButton: function (type) {
2655            var self = this, tmplt = self._getLayoutTemplate('btnDefault'), css = self[type + 'Class'],
2656                title = self[type + 'Title'], icon = self[type + 'Icon'], label = self[type + 'Label'],
2657                status = self.isDisabled ? ' disabled' : '', btnType = 'button';
2658            switch (type) {
2659                case 'remove':
2660                    if (!self.showRemove) {
2661                        return '';
2662                    }
2663                    break;
2664                case 'cancel':
2665                    if (!self.showCancel) {
2666                        return '';
2667                    }
2668                    css += ' hide';
2669                    break;
2670                case 'upload':
2671                    if (!self.showUpload) {
2672                        return '';
2673                    }
2674                    if (self.isUploadable && !self.isDisabled) {
2675                        tmplt = self._getLayoutTemplate('btnLink').replace('{href}', self.uploadUrl);
2676                    } else {
2677                        btnType = 'submit';
2678                    }
2679                    break;
2680                case 'browse':
2681                    if (!self.showBrowse) {
2682                        return '';
2683                    }
2684                    tmplt = self._getLayoutTemplate('btnBrowse');
2685                    break;
2686                default:
2687                    return '';
2688            }
2689
2690            css += type === 'browse' ? ' btn-file' : ' fileinput-' + type + ' fileinput-' + type + '-button';
2691            if (!isEmpty(label)) {
2692                label = ' <span class="' + self.buttonLabelClass + '">' + label + '</span>';
2693            }
2694            return tmplt.replace('{type}', btnType).replace('{css}', css).replace('{title}', title)
2695                .replace('{status}', status).replace('{icon}', icon).replace('{label}', label);
2696        },
2697        _renderThumbProgress: function () {
2698            return '<div class="file-thumb-progress hide">' + this.progressTemplate.replace(/\{percent}/g,
2699                    '0') + '</div>';
2700        },
2701        _renderFileFooter: function (caption, size, width, isError) {
2702            var self = this, config = self.fileActionSettings, rem = config.showRemove, drg = config.showDrag,
2703                upl = config.showUpload, zoom = config.showZoom, out, template = self._getLayoutTemplate('footer'),
2704                indicator = isError ? config.indicatorError : config.indicatorNew,
2705                title = isError ? config.indicatorErrorTitle : config.indicatorNewTitle;
2706            size = self._getSize(size);
2707            if (self.isUploadable) {
2708                out = template.replace(/\{actions}/g, self._renderFileActions(upl, rem, zoom, drg, false, false, false))
2709                    .replace(/\{caption}/g, caption).replace(/\{size}/g, size).replace(/\{width}/g, width)
2710                    .replace(/\{progress}/g, self._renderThumbProgress()).replace(/\{indicator}/g, indicator)
2711                    .replace(/\{indicatorTitle}/g, title);
2712            } else {
2713                out = template.replace(/\{actions}/g,
2714                    self._renderFileActions(false, false, zoom, drg, false, false, false))
2715                    .replace(/\{caption}/g, caption).replace(/\{size}/g, size).replace(/\{width}/g, width)
2716                    .replace(/\{progress}/g, '').replace(/\{indicator}/g, indicator)
2717                    .replace(/\{indicatorTitle}/g, title);
2718            }
2719            out = replaceTags(out, self.previewThumbTags);
2720            return out;
2721        },
2722        _renderFileActions: function (showUpload, showDelete, showZoom, showDrag, disabled, url, key, isInit) {
2723            if (!showUpload && !showDelete && !showZoom && !showDrag) {
2724                return '';
2725            }
2726            var self = this,
2727                vUrl = url === false ? '' : ' data-url="' + url + '"',
2728                vKey = key === false ? '' : ' data-key="' + key + '"',
2729                btnDelete = '', btnUpload = '', btnZoom = '', btnDrag = '', css,
2730                template = self._getLayoutTemplate('actions'), config = self.fileActionSettings,
2731                otherButtons = self.otherActionButtons.replace(/\{dataKey}/g, vKey),
2732                removeClass = disabled ? config.removeClass + ' disabled' : config.removeClass;
2733            if (showDelete) {
2734                btnDelete = self._getLayoutTemplate('actionDelete')
2735                    .replace(/\{removeClass}/g, removeClass)
2736                    .replace(/\{removeIcon}/g, config.removeIcon)
2737                    .replace(/\{removeTitle}/g, config.removeTitle)
2738                    .replace(/\{dataUrl}/g, vUrl)
2739                    .replace(/\{dataKey}/g, vKey);
2740            }
2741            if (showUpload) {
2742                btnUpload = self._getLayoutTemplate('actionUpload')
2743                    .replace(/\{uploadClass}/g, config.uploadClass)
2744                    .replace(/\{uploadIcon}/g, config.uploadIcon)
2745                    .replace(/\{uploadTitle}/g, config.uploadTitle);
2746            }
2747            if (showZoom) {
2748                btnZoom = self._getLayoutTemplate('actionZoom')
2749                    .replace(/\{zoomClass}/g, config.zoomClass)
2750                    .replace(/\{zoomIcon}/g, config.zoomIcon)
2751                    .replace(/\{zoomTitle}/g, config.zoomTitle);
2752            }
2753            if (showDrag && isInit) {
2754                css = 'drag-handle-init ' + config.dragClass;
2755                btnDrag = self._getLayoutTemplate('actionDrag').replace(/\{dragClass}/g, css)
2756                    .replace(/\{dragTitle}/g, config.dragTitle)
2757                    .replace(/\{dragIcon}/g, config.dragIcon);
2758            }
2759            return template.replace(/\{delete}/g, btnDelete)
2760                .replace(/\{upload}/g, btnUpload)
2761                .replace(/\{zoom}/g, btnZoom)
2762                .replace(/\{drag}/g, btnDrag)
2763                .replace(/\{other}/g, otherButtons);
2764        },
2765        _browse: function (e) {
2766            var self = this;
2767            self._raise('filebrowse');
2768            if (e && e.isDefaultPrevented()) {
2769                return;
2770            }
2771            if (self.isError && !self.isUploadable) {
2772                self.clear();
2773            }
2774            self.$captionContainer.focus();
2775        },
2776        _change: function (e) {
2777            var self = this, $el = self.$element;
2778            if (!self.isUploadable && isEmpty($el.val()) && self.fileInputCleared) { // IE 11 fix
2779                self.fileInputCleared = false;
2780                return;
2781            }
2782            self.fileInputCleared = false;
2783            var tfiles, msg, total, isDragDrop = arguments.length > 1, isAjaxUpload = self.isUploadable, i = 0, f, n, len,
2784                files = isDragDrop ? e.originalEvent.dataTransfer.files : $el.get(0).files, ctr = self.filestack.length,
2785                isSingleUpload = isEmpty($el.attr('multiple')), flagSingle = (isSingleUpload && ctr > 0), folders = 0,
2786                throwError = function (mesg, file, previewId, index) {
2787                    var p1 = $.extend(true, {}, self._getOutData({}, {}, files), {id: previewId, index: index}),
2788                        p2 = {id: previewId, index: index, file: file, files: files};
2789                    return self.isUploadable ? self._showUploadError(mesg, p1) : self._showError(mesg, p2);
2790                };
2791            self.reader = null;
2792            self._resetUpload();
2793            self._hideFileIcon();
2794            if (self.isUploadable) {
2795                self.$container.find('.file-drop-zone .' + self.dropZoneTitleClass).remove();
2796            }
2797            if (isDragDrop) {
2798                tfiles = [];
2799                while (files[i]) {
2800                    f = files[i];
2801                    if (!f.type && f.size % 4096 === 0) {
2802                        folders++;
2803                    } else {
2804                        tfiles.push(f);
2805                    }
2806                    i++;
2807                }
2808            } else {
2809                if (e.target.files === undefined) {
2810                    tfiles = e.target && e.target.value ? [
2811                        {name: e.target.value.replace(/^.+\\/, '')}
2812                    ] : [];
2813                } else {
2814                    tfiles = e.target.files;
2815                }
2816            }
2817            if (isEmpty(tfiles) || tfiles.length === 0) {
2818                if (!isAjaxUpload) {
2819                    self.clear();
2820                }
2821                self._showFolderError(folders);
2822                self._raise('fileselectnone');
2823                return;
2824            }
2825            self._resetErrors();
2826            len = tfiles.length;
2827            total = self._getFileCount(self.isUploadable ? (self.getFileStack().length + len) : len);
2828            if (self.maxFileCount > 0 && total > self.maxFileCount) {
2829                if (!self.autoReplace || len > self.maxFileCount) {
2830                    n = (self.autoReplace && len > self.maxFileCount) ? len : total;
2831                    msg = self.msgFilesTooMany.replace('{m}', self.maxFileCount).replace('{n}', n);
2832                    self.isError = throwError(msg, null, null, null);
2833                    self.$captionContainer.find('.kv-caption-icon').hide();
2834                    self._setCaption('', true);
2835                    self.$container.removeClass('file-input-new file-input-ajax-new');
2836                    return;
2837                }
2838                if (total > self.maxFileCount) {
2839                    self._resetPreviewThumbs(isAjaxUpload);
2840                }
2841            } else {
2842                if (!isAjaxUpload || flagSingle) {
2843                    self._resetPreviewThumbs(false);
2844                    if (flagSingle) {
2845                        self.clearStack();
2846                    }
2847                } else {
2848                    if (isAjaxUpload && ctr === 0 && (!previewCache.count(self.id) || self.overwriteInitial)) {
2849                        self._resetPreviewThumbs(true);
2850                    }
2851                }
2852            }
2853            if (self.isPreviewable) {
2854                self._readFiles(tfiles);
2855            } else {
2856                self._updateFileDetails(1);
2857            }
2858            self._showFolderError(folders);
2859        },
2860        _abort: function (params) {
2861            var self = this, data;
2862            if (self.ajaxAborted && typeof self.ajaxAborted === "object" && self.ajaxAborted.message !== undefined) {
2863                data = $.extend(true, {}, self._getOutData(), params);
2864                data.abortData = self.ajaxAborted.data || {};
2865                data.abortMessage = self.ajaxAborted.message;
2866                self.cancel();
2867                self._setProgress(101, self.$progress, self.msgCancelled);
2868                self._showUploadError(self.ajaxAborted.message, data, 'filecustomerror');
2869                return true;
2870            }
2871            return false;
2872        },
2873        _resetFileStack: function () {
2874            var self = this, i = 0, newstack = [], newnames = [];
2875            self._getThumbs().each(function () {
2876                var $thumb = $(this), ind = $thumb.attr('data-fileindex'),
2877                    file = self.filestack[ind];
2878                if (ind === -1) {
2879                    return;
2880                }
2881                if (file !== undefined) {
2882                    newstack[i] = file;
2883                    newnames[i] = self._getFileName(file);
2884                    $thumb.attr({
2885                        'id': self.previewInitId + '-' + i,
2886                        'data-fileindex': i
2887                    });
2888                    i++;
2889                } else {
2890                    $thumb.attr({
2891                        'id': 'uploaded-' + uniqId(),
2892                        'data-fileindex': '-1'
2893                    });
2894                }
2895            });
2896            self.filestack = newstack;
2897            self.filenames = newnames;
2898        },
2899        clearStack: function () {
2900            var self = this;
2901            self.filestack = [];
2902            self.filenames = [];
2903            return self.$element;
2904        },
2905        updateStack: function (i, file) {
2906            var self = this;
2907            self.filestack[i] = file;
2908            self.filenames[i] = self._getFileName(file);
2909            return self.$element;
2910        },
2911        addToStack: function (file) {
2912            var self = this;
2913            self.filestack.push(file);
2914            self.filenames.push(self._getFileName(file));
2915            return self.$element;
2916        },
2917        getFileStack: function (skipNull) {
2918            var self = this;
2919            return self.filestack.filter(function (n) {
2920                return (skipNull ? n !== undefined : n !== undefined && n !== null);
2921            });
2922        },
2923        getFilesCount: function () {
2924            var self = this, len = self.isUploadable ? self.getFileStack().length : self.$element.get(0).files.length;
2925            return self._getFileCount(len);
2926        },
2927        lock: function () {
2928            var self = this;
2929            self._resetErrors();
2930            self.disable();
2931            if (self.showRemove) {
2932                addCss(self.$container.find('.fileinput-remove'), 'hide');
2933            }
2934            if (self.showCancel) {
2935                self.$container.find('.fileinput-cancel').removeClass('hide');
2936            }
2937            self._raise('filelock', [self.filestack, self._getExtraData()]);
2938            return self.$element;
2939        },
2940        unlock: function (reset) {
2941            var self = this;
2942            if (reset === undefined) {
2943                reset = true;
2944            }
2945            self.enable();
2946            if (self.showCancel) {
2947                addCss(self.$container.find('.fileinput-cancel'), 'hide');
2948            }
2949            if (self.showRemove) {
2950                self.$container.find('.fileinput-remove').removeClass('hide');
2951            }
2952            if (reset) {
2953                self._resetFileStack();
2954            }
2955            self._raise('fileunlock', [self.filestack, self._getExtraData()]);
2956            return self.$element;
2957        },
2958        cancel: function () {
2959            var self = this, xhr = self.ajaxRequests, len = xhr.length, i;
2960            if (len > 0) {
2961                for (i = 0; i < len; i += 1) {
2962                    self.cancelling = true;
2963                    xhr[i].abort();
2964                }
2965            }
2966            self._setProgressCancelled();
2967            self._getThumbs().each(function () {
2968                var $thumb = $(this), ind = $thumb.attr('data-fileindex');
2969                $thumb.removeClass('file-uploading');
2970                if (self.filestack[ind] !== undefined) {
2971                    $thumb.find('.kv-file-upload').removeClass('disabled').removeAttr('disabled');
2972                    $thumb.find('.kv-file-remove').removeClass('disabled').removeAttr('disabled');
2973                }
2974                self.unlock();
2975            });
2976            return self.$element;
2977        },
2978        clear: function () {
2979            var self = this, cap;
2980            self.$btnUpload.removeAttr('disabled');
2981            self._getThumbs().find('video,audio,img').each(function () {
2982                cleanMemory($(this));
2983            });
2984            self._resetUpload();
2985            self.clearStack();
2986            self._clearFileInput();
2987            self._resetErrors(true);
2988            self._raise('fileclear');
2989            if (self._hasInitialPreview()) {
2990                self._showFileIcon();
2991                self._resetPreview();
2992                self._initPreviewActions();
2993                self.$container.removeClass('file-input-new');
2994            } else {
2995                self._getThumbs().each(function () {
2996                    self._clearObjects($(this));
2997                });
2998                if (self.isUploadable) {
2999                    previewCache.data[self.id] = {};
3000                }
3001                self.$preview.html('');
3002                cap = (!self.overwriteInitial && self.initialCaption.length > 0) ? self.initialCaption : '';
3003                self.$caption.html(cap);
3004                self.$caption.attr('title', '');
3005                addCss(self.$container, 'file-input-new');
3006                self._validateDefaultPreview();
3007            }
3008            if (self.$container.find('.file-preview-frame').length === 0) {
3009                if (!self._initCaption()) {
3010                    self.$captionContainer.find('.kv-caption-icon').hide();
3011                }
3012            }
3013            self._hideFileIcon();
3014            self._raise('filecleared');
3015            self.$captionContainer.focus();
3016            self._setFileDropZoneTitle();
3017            return self.$element;
3018        },
3019        reset: function () {
3020            var self = this;
3021            self._resetPreview();
3022            self.$container.find('.fileinput-filename').text('');
3023            self._raise('filereset');
3024            addCss(self.$container, 'file-input-new');
3025            if (self.$preview.find('.file-preview-frame').length || self.isUploadable && self.dropZoneEnabled) {
3026                self.$container.removeClass('file-input-new');
3027            }
3028            self._setFileDropZoneTitle();
3029            self.clearStack();
3030            self.formdata = {};
3031            return self.$element;
3032        },
3033        disable: function () {
3034            var self = this;
3035            self.isDisabled = true;
3036            self._raise('filedisabled');
3037            self.$element.attr('disabled', 'disabled');
3038            self.$container.find(".kv-fileinput-caption").addClass("file-caption-disabled");
3039            self.$container.find(".btn-file, .fileinput-remove, .fileinput-upload, .file-preview-frame button").attr(
3040                "disabled",
3041                true);
3042            self._initDragDrop();
3043            return self.$element;
3044        },
3045        enable: function () {
3046            var self = this;
3047            self.isDisabled = false;
3048            self._raise('fileenabled');
3049            self.$element.removeAttr('disabled');
3050            self.$container.find(".kv-fileinput-caption").removeClass("file-caption-disabled");
3051            self.$container.find(
3052                ".btn-file, .fileinput-remove, .fileinput-upload, .file-preview-frame button").removeAttr("disabled");
3053            self._initDragDrop();
3054            return self.$element;
3055        },
3056        upload: function () {
3057            var self = this, totLen = self.getFileStack().length, params = {},
3058                i, outData, len, hasExtraData = !$.isEmptyObject(self._getExtraData());
3059            if (self.minFileCount > 0 && self._getFileCount(totLen) < self.minFileCount) {
3060                self._noFilesError(params);
3061                return;
3062            }
3063            if (!self.isUploadable || self.isDisabled || (totLen === 0 && !hasExtraData)) {
3064                return;
3065            }
3066            self._resetUpload();
3067            self.$progress.removeClass('hide');
3068            self.uploadCount = 0;
3069            self.uploadStatus = {};
3070            self.uploadLog = [];
3071            self.lock();
3072            self._setProgress(2);
3073            if (totLen === 0 && hasExtraData) {
3074                self._uploadExtraOnly();
3075                return;
3076            }
3077            len = self.filestack.length;
3078            self.hasInitData = false;
3079            if (self.uploadAsync) {
3080                outData = self._getOutData();
3081                self._raise('filebatchpreupload', [outData]);
3082                self.fileBatchCompleted = false;
3083                self.uploadCache = {content: [], config: [], tags: [], append: true};
3084                self.uploadAsyncCount = self.getFileStack().length;
3085                for (i = 0; i < len; i++) {
3086                    self.uploadCache.content[i] = null;
3087                    self.uploadCache.config[i] = null;
3088                    self.uploadCache.tags[i] = null;
3089                }
3090                for (i = 0; i < len; i++) {
3091                    if (self.filestack[i] !== undefined) {
3092                        self._uploadSingle(i, self.filestack, true);
3093                    }
3094                }
3095                return;
3096            }
3097            self._uploadBatch();
3098            return self.$element;
3099        },
3100        destroy: function () {
3101            var self = this, $cont = self.$container;
3102            $cont.find('.file-drop-zone').off();
3103            self.$element.insertBefore($cont).off(NAMESPACE).removeData();
3104            $cont.off().remove();
3105            return self.$element;
3106        },
3107        refresh: function (options) {
3108            var self = this, $el = self.$element;
3109            options = options ? $.extend(true, {}, self.options, options) : self.options;
3110            self.destroy();
3111            $el.fileinput(options);
3112            if ($el.val()) {
3113                $el.trigger('change.fileinput');
3114            }
3115            return $el;
3116        }
3117    };
3118
3119    $.fn.fileinput = function (option) {
3120        if (!hasFileAPISupport() && !isIE(9)) {
3121            return;
3122        }
3123        var args = Array.apply(null, arguments), retvals = [];
3124        args.shift();
3125        this.each(function () {
3126            var self = $(this), data = self.data('fileinput'), options = typeof option === 'object' && option,
3127                theme = options.theme || self.data('theme'), l = {}, t = {},
3128                lang = $('html').attr('lang') || options.language || self.data('language') || 'en', opts;
3129            if (!data) {
3130                if (theme) {
3131                    t = $.fn.fileinputThemes[theme] || {};
3132                }
3133                if (lang !== 'en' && !isEmpty($.fn.fileinputLocales[lang])) {
3134                    l = $.fn.fileinputLocales[lang] || {};
3135                }
3136                opts = $.extend(true, {}, $.fn.fileinput.defaults, t, $.fn.fileinputLocales.en, l, options,
3137                    self.data());
3138                data = new FileInput(this, opts);
3139                self.data('fileinput', data);
3140            }
3141			
3142            if (typeof option === 'string') {
3143                retvals.push(data[option].apply(data, args));
3144            }
3145        });
3146        switch (retvals.length) {
3147            case 0:
3148                return this;
3149            case 1:
3150                return retvals[0];
3151            default:
3152                return retvals;
3153        }
3154    };
3155
3156    $.fn.fileinput.defaults = {
3157        language: 'en',
3158        showCaption: true,
3159        showBrowse: true,
3160        showPreview: true,
3161        showRemove: true,
3162        showUpload: true,
3163        showCancel: true,
3164        showClose: true,
3165        showUploadedThumbs: true,
3166        browseOnZoneClick: false,
3167        autoReplace: false,
3168        previewClass: '',
3169        captionClass: '',
3170        mainClass: 'file-caption-main',
3171        mainTemplate: null,
3172        purifyHtml: true,
3173        fileSizeGetter: null,
3174        initialCaption: '',
3175        initialPreview: [],
3176        initialPreviewDelimiter: '*$$*',
3177        initialPreviewAsData: false,
3178        initialPreviewFileType: 'image',
3179        initialPreviewConfig: [],
3180        initialPreviewThumbTags: [],
3181        previewThumbTags: {},
3182        initialPreviewShowDelete: true,
3183        removeFromPreviewOnError: false,
3184        deleteUrl: '',
3185        deleteExtraData: {},
3186        overwriteInitial: true,
3187        layoutTemplates: defaultLayoutTemplates,
3188        previewTemplates: defaultPreviewTemplates,
3189        previewZoomSettings: defaultPreviewZoomSettings,
3190        previewZoomButtonIcons: {
3191            prev: '<i class="glyphicon glyphicon-triangle-left"></i>',
3192            next: '<i class="glyphicon glyphicon-triangle-right"></i>',
3193            toggleheader: '<i class="glyphicon glyphicon-resize-vertical"></i>',
3194            fullscreen: '<i class="glyphicon glyphicon-fullscreen"></i>',
3195            borderless: '<i class="glyphicon glyphicon-resize-full"></i>',
3196            close: '<i class="glyphicon glyphicon-remove"></i>'
3197        },
3198        previewZoomButtonClasses: {
3199            prev: 'btn btn-navigate',
3200            next: 'btn btn-navigate',
3201            toggleheader: 'btn btn-default btn-header-toggle',
3202            fullscreen: 'btn btn-default',
3203            borderless: 'btn btn-default',
3204            close: 'btn btn-default'
3205        },
3206        allowedPreviewTypes: defaultPreviewTypes,
3207        allowedPreviewMimeTypes: null,
3208        allowedFileTypes: null,
3209        allowedFileExtensions: null,
3210        defaultPreviewContent: null,
3211        customLayoutTags: {},
3212        customPreviewTags: {},
3213        previewSettings: defaultPreviewSettings,
3214        fileTypeSettings: defaultFileTypeSettings,
3215        previewFileIcon: '<i class="glyphicon glyphicon-file"></i>',
3216        previewFileIconClass: 'file-other-icon',
3217        previewFileIconSettings: {},
3218        previewFileExtSettings: {},
3219        buttonLabelClass: 'hidden-xs',
3220        browseIcon: '<i class="glyphicon glyphicon-folder-open"></i>&nbsp;',
3221        browseClass: 'btn btn-primary',
3222        removeIcon: '<i class="glyphicon glyphicon-trash"></i>',
3223        removeClass: 'btn btn-default',
3224        cancelIcon: '<i class="glyphicon glyphicon-ban-circle"></i>',
3225        cancelClass: 'btn btn-default',
3226        uploadIcon: '<i class="glyphicon glyphicon-upload"></i>',
3227        uploadClass: 'btn btn-default',
3228        uploadUrl: null,
3229        uploadAsync: true,
3230        uploadExtraData: {},
3231        zoomModalHeight: 480,
3232        minImageWidth: null,
3233        minImageHeight: null,
3234        maxImageWidth: null,
3235        maxImageHeight: null,
3236        resizeImage: false,
3237        resizePreference: 'width',
3238        resizeQuality: 0.92,
3239        resizeDefaultImageType: 'image/jpeg',
3240        minFileSize: 0,
3241        maxFileSize: 0,
3242        maxFilePreviewSize: 25600, // 25 MB
3243        minFileCount: 0,
3244        maxFileCount: 0,
3245        validateInitialCount: false,
3246        msgValidationErrorClass: 'text-danger',
3247        msgValidationErrorIcon: '<i class="glyphicon glyphicon-exclamation-sign"></i> ',
3248        msgErrorClass: 'file-error-message',
3249        progressThumbClass: "progress-bar progress-bar-success progress-bar-striped active",
3250        progressClass: "progress-bar progress-bar-success progress-bar-striped active",
3251        progressCompleteClass: "progress-bar progress-bar-success",
3252        progressErrorClass: "progress-bar progress-bar-danger",
3253        progressUploadThreshold: 99,
3254        previewFileType: 'image',
3255        elCaptionContainer: null,
3256        elCaptionText: null,
3257        elPreviewContainer: null,
3258        elPreviewImage: null,
3259        elPreviewStatus: null,
3260        elErrorContainer: null,
3261        errorCloseButton: '<span class="close kv-error-close">&times;</span>',
3262        slugCallback: null,
3263        dropZoneEnabled: true,
3264        dropZoneTitleClass: 'file-drop-zone-title',
3265        fileActionSettings: {},
3266        otherActionButtons: '',
3267        textEncoding: 'UTF-8',
3268        ajaxSettings: {},
3269        ajaxDeleteSettings: {},
3270        showAjaxErrorDetails: true
3271    };
3272
3273    $.fn.fileinputLocales.en = {
3274        fileSingle: 'file',
3275        filePlural: 'files',
3276        browseLabel: 'Browse &hellip;',
3277        removeLabel: 'Remove',
3278        removeTitle: 'Clear selected files',
3279        cancelLabel: 'Cancel',
3280        cancelTitle: 'Abort ongoing upload',
3281        uploadLabel: 'Upload',
3282        uploadTitle: 'Upload selected files',
3283        msgNo: 'No',
3284        msgNoFilesSelected: 'No files selected',
3285        msgCancelled: 'Cancelled',
3286        msgZoomModalHeading: 'Detailed Preview',
3287        msgSizeTooSmall: 'File "{name}" (<b>{size} KB</b>) is too small and must be larger than <b>{minSize} KB</b>.',
3288        msgSizeTooLarge: 'File "{name}" (<b>{size} KB</b>) exceeds maximum allowed upload size of <b>{maxSize} KB</b>.',
3289        msgFilesTooLess: 'You must select at least <b>{n}</b> {files} to upload.',
3290        msgFilesTooMany: 'Number of files selected for upload <b>({n})</b> exceeds maximum allowed limit of <b>{m}</b>.',
3291        msgFileNotFound: 'File "{name}" not found!',
3292        msgFileSecured: 'Security restrictions prevent reading the file "{name}".',
3293        msgFileNotReadable: 'File "{name}" is not readable.',
3294        msgFilePreviewAborted: 'File preview aborted for "{name}".',
3295        msgFilePreviewError: 'An error occurred while reading the file "{name}".',
3296        msgInvalidFileName: 'Invalid or unsupported characters in file name "{name}".',
3297        msgInvalidFileType: 'Invalid type for file "{name}". Only "{types}" files are supported.',
3298        msgInvalidFileExtension: 'Invalid extension for file "{name}". Only "{extensions}" files are supported.',
3299        msgUploadAborted: 'The file upload was aborted',
3300        msgUploadThreshold: 'Processing...',
3301        msgValidationError: 'Validation Error',
3302        msgLoading: 'Loading file {index} of {files} &hellip;',
3303        msgProgress: 'Loading file {index} of {files} - {name} - {percent}% completed.',
3304        msgSelected: '{n} {files} selected',
3305        msgFoldersNotAllowed: 'Drag & drop files only! {n} folder(s) dropped were skipped.',
3306        msgImageWidthSmall: 'Width of image file "{name}" must be at least {size} px.',
3307        msgImageHeightSmall: 'Height of image file "{name}" must be at least {size} px.',
3308        msgImageWidthLarge: 'Width of image file "{name}" cannot exceed {size} px.',
3309        msgImageHeightLarge: 'Height of image file "{name}" cannot exceed {size} px.',
3310        msgImageResizeError: 'Could not get the image dimensions to resize.',
3311        msgImageResizeException: 'Error while resizing the image.<pre>{errors}</pre>',
3312        dropZoneTitle: 'Drag & drop files here &hellip;',
3313        dropZoneClickTitle: '<br>(or click to select {files})',
3314        previewZoomButtonTitles: {
3315            prev: 'View previous file',
3316            next: 'View next file',
3317            toggleheader: 'Toggle header',
3318            fullscreen: 'Toggle full screen',
3319            borderless: 'Toggle borderless mode',
3320            close: 'Close detailed preview'
3321        }
3322    };
3323	
3324    $.fn.fileinputLocales.fr = {
3325        fileSingle: 'fichier',
3326        filePlural: 'fichiers',
3327        browseLabel: 'Parcourir &hellip;',
3328        removeLabel: 'Supprimer',
3329        removeTitle: 'Effacer',
3330        cancelLabel: 'Annuler',
3331        cancelTitle: 'Annuler le chargement',
3332        uploadLabel: 'Télécharger',
3333        uploadTitle: 'Télécharger',
3334        msgNo: 'Non',
3335        msgNoFilesSelected: 'Aucun fichier sélectionné',
3336        msgCancelled: 'Annulé',
3337        msgZoomModalHeading: 'Appercu',
3338        msgSizeTooSmall: 'Fichier "{name}" (<b>{size} KB</b>) est trop petit et doit avoir minimum <b>{minSize} KB</b>.',
3339        msgSizeTooLarge: 'Fichier "{name}" (<b>{size} KB</b>) excède la taille de <b>{maxSize} KB</b>.',
3340        msgFilesTooLess: 'Vous devez sélectionner au moins <b>{n}</b> {files} à charger.',
3341        msgFilesTooMany: 'Le nombre <b>({n})</b> de fichier a charger excède le nombre maximale alloué de <b>{m}</b>.',
3342        msgFileNotFound: 'Fichier "{name}" non trouvé!',
3343        msgFileSecured: 'Security restrictions prevent reading the file "{name}".',
3344        msgFileNotReadable: 'File "{name}" is not readable.',
3345        msgFilePreviewAborted: 'File preview aborted for "{name}".',
3346        msgFilePreviewError: 'Une erreur est survenue pendant la lecture du fichier "{name}".',
3347        msgInvalidFileName: 'Caractères invalide dans le nom du fichier "{name}".',
3348        msgInvalidFileType: 'Type de fichier invalide "{name}". Seulement "{types}" sont acceptés.',
3349        msgInvalidFileExtension: 'Fichier de type invalide "{name}". Seulement les fichiers de type "{extensions}" sont acceptés.',
3350        msgUploadAborted: 'Le chargement a été interompu',
3351        msgUploadThreshold: 'Chargement...',
3352        msgValidationError: 'Erreur de validation',
3353        msgLoading: 'Chargement {index} de {files} &hellip;',
3354        msgProgress: 'Chargement {index} de {files} - {name} - {percent}% complété.',
3355        msgSelected: '{n} {files} sélectionné(s)',
3356        msgFoldersNotAllowed: 'Drag & drop files only! {n} folder(s) dropped were skipped.',
3357        msgImageWidthSmall: 'Width of image file "{name}" must be at least {size} px.',
3358        msgImageHeightSmall: 'Height of image file "{name}" must be at least {size} px.',
3359        msgImageWidthLarge: 'Width of image file "{name}" cannot exceed {size} px.',
3360        msgImageHeightLarge: 'Height of image file "{name}" cannot exceed {size} px.',
3361        msgImageResizeError: 'Could not get the image dimensions to resize.',
3362        msgImageResizeException: 'Error while resizing the image.<pre>{errors}</pre>',
3363        dropZoneTitle: 'Drag & drop files here &hellip;',
3364        dropZoneClickTitle: '<br>(ou cliquez pour sélectionner {files})',
3365        previewZoomButtonTitles: {
3366            prev: 'Fichier précédent',
3367            next: 'Fichier suivant',
3368            toggleheader: 'Masquer/afficher entête',
3369            fullscreen: 'Mode plein écran',
3370            borderless: 'Modem sans bordures',
3371            close: 'Fermer l\'appercu'
3372        }
3373    };	
3374
3375    $.fn.fileinput.Constructor = FileInput;
3376
3377    /**
3378     * Convert automatically file inputs with class 'file' into a bootstrap fileinput control.
3379     */
3380    $(document).ready(function () {
3381        var $input = $('input.file[type=file]');
3382        if ($input.length) {
3383            $input.fileinput();
3384        }
3385    });
3386}));

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.