PageSourceSearch

https://borer.org.il/templates/versus/html/com_rsform/rsform/fileinput.js

js borer.org.il collected 2026-10-03 18:46:24 UTC 124,147 bytes, 2,662 lines download raw bytes

1/*!
2 * @copyright Copyright © Kartik Visweswaran, Krajee.com, 2014 - 2015
3 * @version 4.2.7
4 *
5 * File input styled for Bootstrap 3.0 that utilizes HTML5 File Input's advanced 
6 * features including the FileReader API. 
7 * 
8 * The plugin drastically enhances the HTML file input to preview multiple files on the client before
9 * upload. In addition it provides the ability to preview content of images, text, videos, audio, html, 
10 * flash and other objects. It also offers the ability to upload and delete files using AJAX, and add 
11 * files in batches (i.e. preview, append, or remove before upload).
12 * 
13 * Author: Kartik Visweswaran
14 * Copyright: 2015, Kartik Visweswaran, Krajee.com
15 * For more JQuery plugins visit http://plugins.krajee.com
16 * For more Yii related demos visit http://demos.krajee.com
17 */
18(function ($) {
19    "use strict";
20
21    $.fn.fileinputLocales = {};
22
23    var isIE, isEdge, handler, previewCache, getNum, hasFileAPISupport, hasDragDropSupport, hasFileUploadSupport, addCss,
24        STYLE_SETTING, OBJECT_PARAMS, DEFAULT_PREVIEW, defaultFileActionSettings, tMain1, tMain2, tPreview, tIcon, tClose,
25        tCaption, tBtnDefault, tBtnLink, tBtnBrowse, tModal, tProgress, tFooter, tActions, tActionDelete, tActionUpload,
26        tZoom, tGeneric, tHtml, tImage, tText, tVideo, tAudio, tFlash, tObject, tOther, defaultLayoutTemplates,
27        defaultPreviewTemplates, defaultPreviewTypes, defaultPreviewSettings, defaultFileTypeSettings, isEmpty, isArray,
28        isSet, getElement, uniqId, htmlEncode, replaceTags, objUrl, FileInput;
29
30    isIE = function (ver) {
31        // check for IE versions < 11
32        if (navigator.appName !== 'Microsoft Internet Explorer') {
33            return false;
34        }
35        if (ver === 10) {
36            return new RegExp('msie\\s' + ver, 'i').test(navigator.userAgent);
37        }
38        var div = document.createElement("div"), status;
39        div.innerHTML = "<!--[if IE " + ver + "]> <i></i> <![endif]-->";
40        status = div.getElementsByTagName("i").length;
41        document.body.appendChild(div);
42        div.parentNode.removeChild(div);
43        return status;
44    };
45    isEdge = function() {
46        return new RegExp('Edge\/[0-9]+', 'i').test(navigator.userAgent);
47    };
48    handler = function ($el, event, callback, skipNS) {
49        var ev = skipNS ? event : event + '.fileinput';
50        $el.off(ev).on(ev, callback);
51    };
52    previewCache = {
53        data: {},
54        init: function (obj) {
55            var content = obj.initialPreview, id = obj.id;
56            if (content.length > 0 && !isArray(content)) {
57                content = content.split(obj.initialPreviewDelimiter);
58            }
59            previewCache.data[id] = {
60                content: content,
61                config: obj.initialPreviewConfig,
62                tags: obj.initialPreviewThumbTags,
63                delimiter: obj.initialPreviewDelimiter,
64                template: obj.previewGenericTemplate,
65                msg: function (n) {
66                    return obj.getMsgSelected(n);
67                },
68                initId: obj.previewInitId,
69                footer: obj.getLayoutTemplate('footer').replace(/\{progress}/g, obj.renderThumbProgress()),
70                isDelete: obj.initialPreviewShowDelete,
71                caption: obj.initialCaption,
72                actions: function (showUpload, showDelete, disabled, url, key) {
73                    return obj.renderFileActions(showUpload, showDelete, disabled, url, key);
74                }
75            };
76        },
77        fetch: function (id) {
78            return previewCache.data[id].content.filter(function (n) {
79                return n !== null;
80            });
81        },
82        count: function (id, all) {
83            return !!previewCache.data[id] && !!previewCache.data[id].content ?
84                (all ? previewCache.data[id].content.length : previewCache.fetch(id).length) : 0;
85        },
86        get: function (id, i, isDisabled) {
87            var ind = 'init_' + i, data = previewCache.data[id], config = data.config[i],
88                previewId = data.initId + '-' + ind, out, $tmp, frameClass = ' file-preview-initial';
89            /** @namespace config.frameClass */
90            /** @namespace config.frameAttr */
91            isDisabled = isDisabled === undefined ? true : isDisabled;
92            if (data.content[i] === null) {
93                return '';
94            }
95            if (!isEmpty(config) && !isEmpty(config.frameClass)) {
96                frameClass += ' ' + config.frameClass;
97            }
98            out = data.template
99                .replace(/\{previewId}/g, previewId)
100                .replace(/\{frameClass}/g, frameClass)
101                .replace(/\{fileindex}/g, ind)
102                .replace(/\{content}/g, data.content[i])
103                .replace(/\{footer}/g, previewCache.footer(id, i, isDisabled));
104            if (data.tags.length && data.tags[i]) {
105                out = replaceTags(out, data.tags[i]);
106            }
107            if (!isEmpty(config) && !isEmpty(config.frameAttr)) {
108                $tmp = $(document.createElement('div')).html(out);
109                $tmp.find('.file-preview-initial').attr(config.frameAttr);
110                out = $tmp.html();
111                $tmp.remove();
112            }
113            return out;
114        },
115        add: function (id, content, config, tags, append) {
116            var data = $.extend(true, {}, previewCache.data[id]), index;
117            if (!isArray(content)) {
118                content = content.split(data.delimiter);
119            }
120            if (append) {
121                index = data.content.push(content) - 1;
122                data.config[index] = config;
123                data.tags[index] = tags;
124            } else {
125                index = content.length;
126                data.content = content;
127                data.config = config;
128                data.tags = tags;
129            }
130            previewCache.data[id] = data;
131            return index;
132        },
133        set: function (id, content, config, tags, append) {
134            var data = $.extend(true, {}, previewCache.data[id]), i, chk;
135            if (!content || !content.length) {
136                return;
137            }
138            if (!isArray(content)) {
139                content = content.split(data.delimiter);
140            }
141            chk = content.filter(function (n) {
142                return n !== null;
143            });
144            if (!chk.length) {
145                return;
146            }
147            if (data.content === undefined) {
148                data.content = [];
149            }
150            if (data.config === undefined) {
151                data.config = [];
152            }
153            if (data.tags === undefined) {
154                data.tags = [];
155            }
156            if (append) {
157                for (i = 0; i < content.length; i++) {
158                    if (content[i]) {
159                        data.content.push(content[i]);
160                    }
161                }
162                for (i = 0; i < config.length; i++) {
163                    if (config[i]) {
164                        data.config.push(config[i]);
165                    }
166                }
167                for (i = 0; i < tags.length; i++) {
168                    if (tags[i]) {
169                        data.tags.push(tags[i]);
170                    }
171                }
172            } else {
173                data.content = content;
174                data.config = config;
175                data.tags = tags;
176            }
177            previewCache.data[id] = data;
178        },
179        unset: function (id, index) {
180            var chk = previewCache.count(id);
181            if (!chk) {
182                return;
183            }
184            if (chk === 1) {
185                previewCache.data[id].content = [];
186                previewCache.data[id].config = [];
187                return;
188            }
189            previewCache.data[id].content[index] = null;
190            previewCache.data[id].config[index] = null;
191        },
192        out: function (id) {
193            var html = '', data = previewCache.data[id], caption, len = previewCache.count(id, true);
194            if (len === 0) {
195                return {content: '', caption: ''};
196            }
197            for (var i = 0; i < len; i++) {
198                html += previewCache.get(id, i);
199            }
200            caption = data.msg(previewCache.count(id));
201            return {content: html, caption: caption};
202        },
203        footer: function (id, i, isDisabled) {
204            var data = previewCache.data[id];
205            isDisabled = isDisabled === undefined ? true : isDisabled;
206            if (data.config.length === 0 || isEmpty(data.config[i])) {
207                return '';
208            }
209            var config = data.config[i],
210                caption = isSet('caption', config) ? config.caption : '',
211                width = isSet('width', config) ? config.width : 'auto',
212                url = isSet('url', config) ? config.url : false,
213                key = isSet('key', config) ? config.key : null,
214                disabled = (url === false) && isDisabled,
215                actions = data.isDelete ? data.actions(false, true, disabled, url, key) : '',
216                footer = data.footer.replace(/\{actions}/g, actions);
217            return footer.replace(/\{caption}/g, caption)
218                .replace(/\{width}/g, width)
219                .replace(/\{indicator}/g, '')
220                .replace(/\{indicatorTitle}/g, '');
221        }
222    };
223    getNum = function (num, def) {
224        def = def || 0;
225        if (typeof num === "number") {
226            return num;
227        }
228        if (typeof num === "string") {
229            num = parseFloat(num);
230        }
231        return isNaN(num) ? def : num;
232    };
233    hasFileAPISupport = function () {
234        return window.File && window.FileReader;
235    };
236    hasDragDropSupport = function () {
237        var div = document.createElement('div');
238        /** @namespace div.draggable */
239        /** @namespace div.ondragstart */
240        /** @namespace div.ondrop */
241        return !isIE(9) && !isEdge() && // Fix for MS Edge drag & drop support bug
242            (div.draggable !== undefined || (div.ondragstart !== undefined && div.ondrop !== undefined));
243    };
244    hasFileUploadSupport = function () {
245        return hasFileAPISupport() && window.FormData;
246    };
247    addCss = function ($el, css) {
248        $el.removeClass(css).addClass(css);
249    };
250    STYLE_SETTING = 'style="width:{width};height:{height};"';
251    OBJECT_PARAMS = '      <param name="controller" value="true" />\n' +
252    '      <param name="allowFullScreen" value="true" />\n' +
253    '      <param name="allowScriptAccess" value="always" />\n' +
254    '      <param name="autoPlay" value="false" />\n' +
255    '      <param name="autoStart" value="false" />\n' +
256    '      <param name="quality" value="high" />\n';
257    DEFAULT_PREVIEW = '<div class="file-preview-other">\n' +
258    '   <span class="{previewFileIconClass}">{previewFileIcon}</span>\n' +
259    '</div>';
260    defaultFileActionSettings = {
261        removeIcon: '<i class="icon icon-trash text-danger"></i>',
262        removeClass: 'btn btn-xs btn-default',
263        removeTitle: 'Remove file',
264        uploadIcon: '<i class="icon icon-upload-alt text-info"></i>',
265        uploadClass: 'btn btn-xs btn-default',
266        uploadTitle: 'Upload file',
267        indicatorNew: '<i class="icon icon-hand-down text-warning"></i>',
268        indicatorSuccess: '<i class="icon icon-ok-sign text-success"></i>',
269        indicatorError: '<i class="icon icon-exclamation-sign text-danger"></i>',
270        indicatorLoading: '<i class="icon icon-hand-up text-muted"></i>',
271        indicatorNewTitle: 'Not uploaded yet',
272        indicatorSuccessTitle: 'Uploaded',
273        indicatorErrorTitle: 'Upload Error',
274        indicatorLoadingTitle: 'Uploading ...'
275    };
276    tMain1 = '{preview}\n' +
277    '<div class="kv-upload-progress hide"></div>\n' +
278    '<div class="input-group {class}">\n' +
279    '   {caption}\n' +
280    '   <div class="input-group-btn">\n' +
281    '       {remove}\n' +
282    '       {cancel}\n' +
283    '       {upload}\n' +
284    '       {browse}\n' +
285    '   </div>\n' +
286    '</div>';
287    tMain2 = '{preview}\n<div class="kv-upload-progress hide"></div>\n{remove}\n{cancel}\n{upload}\n{browse}\n';
288    tPreview = '<div class="file-preview {class}">\n' +
289    '    {close}' +
290    '    <div class="{dropClass}">\n' +
291    '    <div class="file-preview-thumbnails">\n' +
292    '    </div>\n' +
293    '    <div class="clearfix"></div>' +
294    '    <div class="file-preview-status text-center text-success"></div>\n' +
295    '    <div class="kv-fileinput-error"></div>\n' +
296    '    </div>\n' +
297    '</div>';
298    tClose = '<div class="close fileinput-remove">&times;</div>\n';
299    tIcon = '<span class="icon icon-file kv-caption-icon"></span>';
300    tCaption = '<div tabindex="500" class="form-control file-caption {class}">\n' +
301    '   <div class="file-caption-name"></div>\n' +
302    '</div>\n';
303    tBtnDefault = '<button type="{type}" tabindex="500" title="{title}" class="{css}"{status}>{icon}{label}</button>';
304    tBtnLink = '<a href="{href}" tabindex="500" title="{title}" class="{css}"{status}>{icon}{label}</a>';
305    tBtnBrowse = '<div tabindex="500" class="{css}"{status}>{icon}{label}</div>';
306    tModal = '<div id="{id}" class="file-preview-detail-modal modal fade" tabindex="-1">\n' +
307    '  <div class="modal-dialog modal-lg">\n' +
308    '    <div class="modal-content">\n' +
309    '      <div class="modal-header">\n' +
310    '        <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>\n' +
311    '        <h3 class="modal-title">{heading} <small>{title}</small></h3>\n' +
312    '      </div>\n' +
313    '      <div class="modal-body">\n' +
314    '           <pre>{body}</pre>\n' +
315    '      </div>\n' +
316    '    </div>\n' +
317    '  </div>\n' +
318    '</div>';
319    tProgress = '<div class="progress">\n' +
320    '    <div class="{class}" role="progressbar"' +
321    ' aria-valuenow="{percent}" aria-valuemin="0" aria-valuemax="100" style="width:{percent}%;">\n' +
322    '        {percent}%\n' +
323    '     </div>\n' +
324    '</div>';
325    tFooter = '<div class="file-thumbnail-footer">\n' +
326    '    <div class="file-footer-caption" title="{caption}">{caption}</div>\n' +
327    '    {progress} {actions}\n' +
328    '</div>';
329    tActions = '<div class="file-actions">\n' +
330    '    <div class="file-footer-buttons">\n' +
331    '        {upload}{delete}{other}
331' +
332    '    </div>\n' +
333    '    <div class="file-upload-indicator" title="{indicatorTitle}">{indicator}</div>\n' +
334    '    <div class="clearfix"></div>\n' +
335    '</div>';
336    tActionDelete = '<button type="button" class="kv-file-remove {removeClass}" ' +
337    'title="{removeTitle}"{dataUrl}{dataKey}>{removeIcon}</button>\n';
338    tActionUpload = '<button type="button" class="kv-file-upload {uploadClass}" title="{uploadTitle}">' +
339    '   {uploadIcon}\n</button>\n';
340    tZoom = '<button type="button" class="btn btn-default btn-xs btn-block" title="{zoomTitle}: {caption}" onclick="{dialog}">\n' +
341    '   {zoomInd}\n' +
342    '</button>\n';
343    tGeneric = '<div class="file-preview-frame{frameClass}" id="{previewId}" data-fileindex="{fileindex}">\n' +
344    '   {content}\n' +
345    '   {footer}\n' +
346    '</div>\n';
347    tHtml = '<div class="file-preview-frame{frameClass}" id="{previewId}" data-fileindex="{fileindex}">\n' +
348    '    <object class="file-object" data="{data}" type="{type}" width="{width}" height="{height}">\n' +
349    '       ' + DEFAULT_PREVIEW + '\n' +
350    '    </object>\n' +
351    '   {footer}\n' +
352    '</div>';
353    tImage = '<div class="file-preview-frame{frameClass}" id="{previewId}" data-fileindex="{fileindex}">\n' +
354    '   <img src="{data}" class="file-preview-image" title="{caption}" alt="{caption}" ' + STYLE_SETTING + '>\n' +
355    '   {footer}\n' +
356    '</div>\n';
357    tText = '<div class="file-preview-frame{frameClass}" id="{previewId}" data-fileindex="{fileindex}">\n' +
358    '   <pre class="file-preview-text" title="{caption}" ' + STYLE_SETTING + '>{data}</pre>\n' +
359    '   {zoom}\n' +
360    '   {footer}\n' +
361    '</div>';
362    tVideo = '<div class="file-preview-frame{frameClass}" id="{previewId}" data-fileindex="{fileindex}"' +
363    ' title="{caption}" ' + STYLE_SETTING + '>\n' +
364    '   <video width="{width}" height="{height}" controls>\n' +
365    '       <source src="{data}" type="{type}">\n' +
366    '       ' + DEFAULT_PREVIEW + '\n' +
367    '   </video>\n' +
368    '   {footer}\n' +
369    '</div>\n';
370    tAudio = '<div class="file-preview-frame{frameClass}" id="{previewId}" data-fileindex="{fileindex}"' +
371    ' title="{caption}" ' + STYLE_SETTING + '>\n' +
372    '   <audio controls>\n' +
373    '       <source src="' + '{data}' + '" type="{type}">\n' +
374    '       ' + DEFAULT_PREVIEW + '\n' +
375    '   </audio>\n' +
376    '   {footer}\n' +
377    '</div>';
378    tFlash = '<div class="file-preview-frame{frameClass}" id="{previewId}" data-fileindex="{fileindex}"' +
379    ' title="{caption}" ' + STYLE_SETTING + '>\n' +
380    '   <object class="file-object" type="application/x-shockwave-flash" width="{width}" height="{height}" data="{data}">\n' +
381    OBJECT_PARAMS + '       ' + DEFAULT_PREVIEW + '\n' +
382    '   </object>\n' +
383    '   {footer}\n' +
384    '</div>\n';
385    tObject = '<div class="file-preview-frame{frameClass}" id="{previewId}" data-fileindex="{fileindex}"' +
386    ' title="{caption}" ' + STYLE_SETTING + '>\n' +
387    '   <object class="file-object" data="{data}" type="{type}" width="{width}" height="{height}">\n' +
388    '       <param name="movie" value="{caption}" />\n' +
389    OBJECT_PARAMS + '         ' + DEFAULT_PREVIEW + '\n' +
390    '   </object>\n' +
391    '   {footer}\n' +
392    '</div>';
393    tOther = '<div class="file-preview-frame{frameClass}" id="{previewId}" data-fileindex="{fileindex}"' +
394    ' title="{caption}" ' + STYLE_SETTING + '>\n' +
395    '   <div class="file-preview-other-frame">\n' +
396    '   ' + DEFAULT_PREVIEW + '\n' +
397    '   </div>\n' +
398    '   <div class="file-preview-other-footer">{footer}</div>\n' +
399    '</div>';
400    defaultLayoutTemplates = {
401        main1: tMain1,
402        main2: tMain2,
403        preview: tPreview,
404        close: tClose,
405        zoom: tZoom,
406        icon: tIcon,
407        caption: tCaption,
408        modal: tModal,
409        progress: tProgress,
410        footer: tFooter,
411        actions: tActions,
412        actionDelete: tActionDelete,
413        actionUpload: tActionUpload,
414        btnDefault: tBtnDefault,
415        btnLink: tBtnLink,
416        btnBrowse: tBtnBrowse
417    };
418    defaultPreviewTemplates = {
419        generic: tGeneric,
420        html: tHtml,
421        image: tImage,
422        text: tText,
423        video: tVideo,
424        audio: tAudio,
425        flash: tFlash,
426        object: tObject,
427        other: tOther
428    };
429    defaultPreviewTypes = ['image', 'html', 'text', 'video', 'audio', 'flash', 'object'];
430    defaultPreviewSettings = {
431        image: {width: "auto", height: "160px"},
432        html: {width: "213px", height: "160px"},
433        text: {width: "160px", height: "136px"},
434        video: {width: "213px", height: "160px"},
435        audio: {width: "213px", height: "80px"},
436        flash: {width: "213px", height: "160px"},
437        object: {width: "160px", height: "160px"},
438        other: {width: "160px", height: "160px"}
439    };
440    defaultFileTypeSettings = {
441        image: function (vType, vName) {
442            return (vType !== undefined) ? vType.match('image.*') : vName.match(/\.(gif|png|jpe?g)$/i);
443        },
444        html: function (vType, vName) {
445            return (vType !== undefined) ? vType === 'text/html' : vName.match(/\.(htm|html)$/i);
446        },
447        text: function (vType, vName) {
448            return (vType !== undefined && vType.match('text.*')) || vName.match(/\.(txt|md|csv|nfo|ini|json|php|js|css)$/i);
449        },
450        video: function (vType, vName) {
451            return (vType !== undefined && vType.match(/\.video\/(ogg|mp4|webm|3gp)$/i)) || vName.match(/\.(og?|mp4|webm|3gp)$/i);
452        },
453        audio: function (vType, vName) {
454            return (vType !== undefined && vType.match(/\.audio\/(ogg|mp3|wav)$/i)) || vName.match(/\.(ogg|mp3|wav)$/i);
455        },
456        flash: function (vType, vName) {
457            return (vType !== undefined && vType === 'application/x-shockwave-flash') || vName.match(/\.(swf)$/i);
458        },
459        object: function () {
460            return true;
461        },
462        other: function () {
463            return true;
464        }
465    };
466    isEmpty = function (value, trim) {
467        return value === undefined || value === null || value.length === 0 || (trim && $.trim(value) === '');
468    };
469    isArray = function (a) {
470        return Array.isArray(a) || Object.prototype.toString.call(a) === '[object Array]';
471    };
472    isSet = function (needle, haystack) {
473        return (typeof haystack === 'object' && needle in haystack);
474    };
475    getElement = function (options, param, value) {
476        return (isEmpty(options) || isEmpty(options[param])) ? value : $(options[param]);
477    };
478    uniqId = function () {
479        return Math.round(new Date().getTime() + (Math.random() * 100));
480    };
481    htmlEncode = function (str) {
482        return str.replace(/&/g, '&amp;')
483            .replace(/</g, '&lt;')
484            .replace(/>/g, '&gt;')
485            .replace(/"/g, '&quot;')
486            .replace(/'/g, '&apos;');
487    };
488    replaceTags = function (str, tags) {
489        var out = str;
490        if (!tags) {
491            return out;
492        }
493        $.each(tags, function (key, value) {
494            if (typeof value === "function") {
495                value = value();
496            }
497            out = out.split(key).join(value);
498        });
499        return out;
500    };
501    objUrl = window.URL || window.webkitURL;
502    FileInput = function (element, options) {
503        var self = this;
504        self.$element = $(element);
505        if (!self.validate()) {
506            return;
507        }
508        self.isPreviewable = hasFileAPISupport();
509        self.isIE9 = isIE(9);
510        self.isIE10 = isIE(10);
511        if (self.isPreviewable || self.isIE9) {
512            self.init(options);
513            self.listen();
514        } else {
515            self.$element.removeClass('file-loading');
516        }
517    };
518
519    FileInput.prototype = {
520        constructor: FileInput,
521        validate: function () {
522            var self = this, $exception;
523            if (self.$element.attr('type') === 'file') {
524                return true;
525            }
526            $exception = '<div class="help-block alert alert-warning">' +
527            '<h4>Invalid Input Type</h4>' +
528            'You must set an input <code>type = file</code> for <b>bootstrap-fileinput</b> plugin to initialize.' +
529            '</div>';
530            self.$element.after($exception);
531            return false;
532        },
533        init: function (options) {
534            var self = this, $el = self.$element, t;
535            $.each(options, function (key, value) {
536                switch (key) {
537                    case 'minFileCount':
538                    case 'maxFileCount':
539                    case 'maxFileSize':
540                        self[key] = getNum(value);
541                        break;
542                    default:
543                        self[key] = value;
544                        break;
545                }
546            });
547            self.fileInputCleared = false;
548            self.fileBatchCompleted = true;
549            if (!self.isPreviewable) {
550                self.showPreview = false;
551            }
552            self.uploadFileAttr = !isEmpty($el.attr('name')) ? $el.attr('name') : 'file_data';
553            self.reader = null;
554            self.formdata = {};
555            self.filestack = [];
556            self.filenames = [];
557            self.uploadCount = 0;
558            self.uploadStatus = {};
559            self.uploadLog = [];
560            self.uploadAsyncCount = 0;
561            self.loadedImages = [];
562            self.totalImagesCount = 0;
563            self.ajaxRequests = [];
564            self.isError = false;
565            self.ajaxAborted = false;
566            self.cancelling = false;
567            t = self.getLayoutTemplate('progress');
568            self.progressTemplate = t.replace('{class}', self.progressClass);
569            self.progressCompleteTemplate = t.replace('{class}', self.progressCompleteClass);
570            self.dropZoneEnabled = hasDragDropSupport() && self.dropZoneEnabled;
571            self.isDisabled = self.$element.attr('disabled') || self.$element.attr('readonly');
572            self.isUploadable = hasFileUploadSupport() && !isEmpty(self.uploadUrl);
573            self.slug = typeof options.slugCallback === "function" ? options.slugCallback : self.slugDefault;
574            self.mainTemplate = self.showCaption ? self.getLayoutTemplate('main1') : self.getLayoutTemplate('main2');
575            self.captionTemplate = self.getLayoutTemplate('caption');
576            self.previewGenericTemplate = self.getPreviewTemplate('generic');
577            if (self.resizeImage && (self.maxImageWidth || self.maxImageHeight)) {
578                self.imageCanvas = document.createElement('canvas');
579                self.imageCanvasContext = self.imageCanvas.getContext('2d');
580            }
581            if (isEmpty(self.$element.attr('id'))) {
582                self.$element.attr('id', uniqId());
583            }
584            if (self.$container === undefined) {
585                self.$container = self.createContainer();
586            } else {
587                self.refreshContainer();
588            }
589            self.$progress = self.$container.find('.kv-upload-progress');
590            self.$btnUpload = self.$container.find('.fileinput-upload');
591            self.$captionContainer = getElement(options, 'elCaptionContainer', self.$container.find('.file-caption'));
592            self.$caption = getElement(options, 'elCaptionText', self.$container.find('.file-caption-name'));
593            self.$previewContainer = getElement(options, 'elPreviewContainer', self.$container.find('.file-preview'));
594            self.$preview = getElement(options, 'elPreviewImage', self.$container.find('.file-preview-thumbnails'));
595            self.$previewStatus = getElement(options, 'elPreviewStatus', self.$container.find('.file-preview-status'));
596            self.$errorContainer = getElement(options, 'elErrorContainer',
597                self.$previewContainer.find('.kv-fileinput-error'));
598            if (!isEmpty(self.msgErrorClass)) {
599                addCss(self.$errorContainer, self.msgErrorClass);
600            }
601            self.$errorContainer.hide();
602            self.fileActionSettings = $.extend(defaultFileActionSettings, options.fileActionSettings);
603            self.previewInitId = "preview-" + uniqId();
604            self.id = self.$element.attr('id');
605            previewCache.init(self);
606            self.initPreview(true);
607            self.initPreviewDeletes();
608            self.options = options;
609            self.setFileDropZoneTitle();
610            self.$element.removeClass('file-loading');
611            if (self.$element.attr('disabled')) {
612                self.disable();
613            }
614        },
615        parseError: function (jqXHR, errorThrown, fileName) {
616            var self = this, errMsg = $.trim(errorThrown + ''),
617                dot = errMsg.slice(-1) === '.' ? '' : '.',
618                text = jqXHR.responseJSON !== undefined && jqXHR.responseJSON.error !== undefined ?
619                    jqXHR.responseJSON.error : jqXHR.responseText;
620            if (self.cancelling && self.msgUploadAborted) {
621                errMsg = self.msgUploadAborted;
622            }
623            if (self.showAjaxErrorDetails && text) {
624                text = $.trim(text.replace(/\n\s*\n/g, '\n'));
625                text = text.length > 0 ? '<pre>
625' + text + '</pre>' : '';
626                errMsg += dot + text;
627            } else {
628                errMsg += dot;
629            }
630            self.cancelling = false;
631            return fileName ? '<b>' + fileName + ': </b>' + errMsg : errMsg;
632        },
633        raise: function (event, params) {
634            var self = this, e = $.Event(event);
635            if (params !== undefined) {
636                self.$element.trigger(e, params);
637            } else {
638                self.$element.trigger(e);
639            }
640            if (e.isDefaultPrevented()) {
641                return false;
642            }
643            if (!e.result) {
644                return e.result;
645            }
646            switch (event) {
647                // ignore these events
648                case 'filebatchuploadcomplete':
649                case 'filebatchuploadsuccess':
650                case 'fileuploaded':
651                case 'fileclear':
652                case 'filecleared':
653                case 'filereset':
654                case 'fileerror':
655                case 'filefoldererror':
656                case 'fileuploaderror':
657                case 'filebatchuploaderror':
658                case 'filedeleteerror':
659                case 'filecustomerror':
660                case 'filesuccessremove':
661                    break;
662                // receive data response via `filecustomerror` event`
663                default:
664                    self.ajaxAborted = e.result;
665                    break;
666            }
667            return true;
668        },
669        getLayoutTemplate: function (t) {
670            var self = this,
671                template = isSet(t, self.layoutTemplates) ? self.layoutTemplates[t] : defaultLayoutTemplates[t];
672            if (isEmpty(self.customLayoutTags)) {
673                return template;
674            }
675            return replaceTags(template, self.customLayoutTags);
676        },
677        getPreviewTemplate: function (t) {
678            var self = this,
679                template = isSet(t, self.previewTemplates) ? self.previewTemplates[t] : defaultPreviewTemplates[t];
680            if (isEmpty(self.customPreviewTags)) {
681                return template;
682            }
683            return replaceTags(template, self.customPreviewTags);
684        },
685        parseFilePreviewIcon: function (content, fname) {
686            var self = this, ext, icn = self.previewFileIcon;
687            if (fname && fname.indexOf('.') > -1) {
688                ext = fname.split('.').pop();
689                if (self.previewFileIconSettings && self.previewFileIconSettings[ext]) {
690                    icn = self.previewFileIconSettings[ext];
691                }
692                if (self.previewFileExtSettings) {
693                    $.each(self.previewFileExtSettings, function (key, func) {
694                        if (self.previewFileIconSettings[key] && func(ext)) {
695                            icn = self.previewFileIconSettings[key];
696                        }
697                    });
698                }
699            }
700            if (content.indexOf('{previewFileIcon}') > -1) {
701                return content.replace(/\{previewFileIconClass}/g, self.previewFileIconClass).replace(/\{previewFileIcon}/g, icn);
702            }
703            return content;
704        },
705        getOutData: function (jqXHR, responseData, filesData) {
706            var self = this;
707            jqXHR = jqXHR || {};
708            responseData = responseData || {};
709            filesData = filesData || self.filestack.slice(0) || {};
710            return {
711                form: self.formdata,
712                files: filesData,
713                extra: self.getExtraData(),
714                response: responseData,
715                reader: self.reader,
716                jqXHR: jqXHR
717            };
718        },
719        listen: function () {
720            var self = this, $el = self.$element, $cap = self.$captionContainer, $btnFile = self.$btnFile,
721                $form = $el.closest('form'), $cont = self.$container;
722            handler($el, 'change', $.proxy(self.change, self));
723            handler($btnFile, 'click', function () {
724                self.raise('filebrowse');
725                if (self.isError && !self.isUploadable) {
726                    self.clear();
727                }
728                $cap.focus();
729            });
730            handler($form, 'reset', $.proxy(self.reset, self));
731            handler($cont.find('.fileinput-remove:not([disabled])'), 'click', $.proxy(self.clear, self));
732            handler($cont.find('.fileinput-cancel'), 'click', $.proxy(self.cancel, self));
733            if (self.isUploadable && self.dropZoneEnabled && self.showPreview) {
734                self.initDragDrop();
735            }
736            if (!self.isUploadable) {
737                handler($form, 'submit', $.proxy(self.submitForm, self));
738            }
739            handler(self.$container.find('.fileinput-upload'), 'click', function (e) {
740                var $btn = $(this), $form, isEnabled = !$btn.hasClass('disabled') && isEmpty($btn.attr('disabled'));
741                if (!self.isUploadable) {
742                    if (isEnabled && $btn.attr('type') !== 'submit') {
743                        $form = $btn.closest('form');
744                        // downgrade to normal form submit if possible
745                        if ($form.length) {
746                            $form.trigger('submit');
747                        }
748                        e.preventDefault();
749                    }
750                    return;
751                }
752                e.preventDefault();
753                if (isEnabled) {
754                    self.upload();
755                }
756            });
757        },
758        submitForm: function () {
759            var self = this, $el = self.$element, files = $el.get(0).files;
760            if (files && self.minFileCount > 0 && self.getFileCount(files.length) < self.minFileCount) {
761                self.noFilesError({});
762                return false;
763            }
764            return !self.abort({});
765        },
766        abort: function (params) {
767            var self = this, data;
768            if (self.ajaxAborted && typeof self.ajaxAborted === "object" && self.ajaxAborted.message !== undefined) {
769                data = $.extend(self.getOutData(), params);
770                data.abortData = self.ajaxAborted.data || {};
771                data.abortMessage = self.ajaxAborted.message;
772                self.cancel();
773                self.setProgress(100);
774                self.showUploadError(self.ajaxAborted.message, data, 'filecustomerror');
775                return true;
776            }
777            return false;
778        },
779        noFilesError: function (params) {
780            var self = this, label = self.minFileCount > 1 ? self.filePlural : self.fileSingle,
781                msg = self.msgFilesTooLess.replace('{n}', self.minFileCount).replace('{files}', label),
782                $error = self.$errorContainer;
783            self.addError(msg);
784            self.isError = true;
785            self.updateFileDetails(0);
786            $error.fadeIn(800);
787            self.raise('fileerror', [params]);
788            self.clearFileInput();
789            addCss(self.$container, 'has-error');
790        },
791        setProgress: function (p, $el) {
792            var self = this, pct = Math.min(p, 100),
793                template = pct < 100 ? self.progressTemplate : self.progressCompleteTemplate;
794            $el = $el || self.$progress;
795            if (!isEmpty(template)) {
796                $el.html(template.replace(/\{percent}/g, pct));
797            }
798        },
799        lock: function () {
800            var self = this;
801            self.resetErrors();
802            self.disable();
803            if (self.showRemove) {
804                addCss(self.$container.find('.fileinput-remove'), 'hide');
805            }
806            if (self.showCancel) {
807                self.$container.find('.fileinput-cancel').removeClass('hide');
808            }
809            self.raise('filelock', [self.filestack, self.getExtraData()]);
810        },
811        unlock: function (reset) {
812            var self = this;
813            if (reset === undefined) {
814                reset = true;
815            }
816            self.enable();
817            if (self.showCancel) {
818                addCss(self.$container.find('.fileinput-cancel'), 'hide');
819            }
820            if (self.showRemove) {
821                self.$container.find('.fileinput-remove').removeClass('hide');
822            }
823            if (reset) {
824                self.resetFileStack();
825            }
826            self.raise('fileunlock', [self.filestack, self.getExtraData()]);
827        },
828        resetFileStack: function () {
829            var self = this, i = 0, newstack = [];
830            self.getThumbs().each(function () {
831                var $thumb = $(this), ind = $thumb.attr('data-fileindex'),
832                    file = self.filestack[ind];
833                if (ind === -1) {
834                    return;
835                }
836                if (file !== undefined) {
837                    newstack[i] = file;
838                    $thumb.attr({
839                        'id': self.previewInitId + '-' + i,
840                        'data-fileindex': i
841                    });
842                    i += 1;
843                } else {
844                    $thumb.attr({
845                        'id': 'uploaded-' + uniqId(),
846                        'data-fileindex': '-1'
847                    });
848                }
849            });
850            self.filestack = newstack;
851            self.filenames = [];
852            $.each(self.filestack, function (i, file) {
853                self.filenames[i] = (file && file.name) ? self.slug(file.name) : '';
854            });
855        },
856        destroy: function () {
857            var self = this, $cont = self.$container;
858            $cont.find('.file-drop-zone').off();
859            self.$element.insertBefore($cont).off('.fileinput').removeData();
860            $cont.off().remove();
861        },
862        refresh: function (options) {
863            var self = this, $el = self.$element;
864            options = options ? $.extend(self.options, options) : self.options;
865            self.destroy();
866            $el.fileinput(options);
867            if ($el.val()) {
868                $el.trigger('change.fileinput');
869            }
870        },
871        initDragDrop: function () {
872            var self = this, $zone = self.$container.find('.file-drop-zone'),
873                allEvents = 'dragenter.fileinput dragover.fileinput drop.fileinput';
874            handler($zone, 'dragenter.fileinput dragover.fileinput', function (e) {
875                var hasFiles = $.inArray('Files', e.originalEvent.dataTransfer.types) > -1;
876                e.stopPropagation();
877                e.preventDefault();
878                if (self.isDisabled || !hasFiles) {
879                    e.originalEvent.dataTransfer.effectAllowed = 'none';
880                    e.originalEvent.dataTransfer.dropEffect = 'none';
881                    return;
882                }
883                addCss($(this), 'highlighted');
884            }, true);
885            handler($zone, 'dragleave', function (e) {
886                e.stopPropagation();
887                e.preventDefault();
888                if (self.isDisabled) {
889                    return;
890                }
891                $(this).removeClass('highlighted');
892            });
893            handler($zone, 'drop', function (e) {
894                e.preventDefault();
895                /** @namespace e.originalEvent.dataTransfer */
896                if (self.isDisabled || isEmpty(e.originalEvent.dataTransfer.files)) {
897                    return;
898                }
899                self.change(e, 'dragdrop');
900                $(this).removeClass('highlighted');
901            });
902            handler($(document), allEvents, function (e) {
903                e.stopPropagation();
904                e.preventDefault();
905            }, true);
906        },
907        setFileDropZoneTitle: function () {
908            var self = this, $zone = self.$container.find('.file-drop-zone');
909            $zone.find('.' + self.dropZoneTitleClass).remove();
910            if (!self.isUploadable || !self.showPreview || $zone.length === 0 || self.getFileStack().length > 0 || !self.dropZoneEnabled) {
911                return;
912            }
913            if ($zone.find('.file-preview-frame').length === 0 && isEmpty(self.defaultPreviewContent)) {
914                $zone.prepend('<div class="' + self.dropZoneTitleClass + '">' + self.dropZoneTitle + '</div>');
915            }
916            self.$container.removeClass('file-input-new');
917            addCss(self.$container, 'file-input-ajax-new');
918        },
919        errorsExist: function () {
920            var self = this, $err;
921            if (self.$errorContainer.find('li').length) {
922                return true;
923            }
924            $err = $(document.createElement('div')).html(self.$errorContainer.html());
925            $err.find('span.kv-error-close').remove();
926            $err.find('ul').remove();
927            return $.trim($err.text()).length ? true : false;
928        },
929        getMsgSelected: function (n) {
930            var self = this, strFiles = n === 1 ? self.fileSingle : self.filePlural;
931            return self.msgSelected.replace('{n}', n).replace('{files}', strFiles);
932        },
933        renderThumbProgress: function () {
934            return '<div class="file-thumb-progress hide">' + this.progressTemplate.replace(/\{percent}/g, '0') + '</div>';
935        },
936        renderFileFooter: function (caption, width) {
937            var self = this, config = self.fileActionSettings, footer, out, template = self.getLayoutTemplate('footer');
938            if (self.isUploadable) {
939                footer = template.replace(/\{actions}/g, self.renderFileActions(true, true, false, false, false));
940                out = footer.replace(/\{caption}/g, caption)
941                    .replace(/\{width}/g, width)
942                    .replace(/\{progress}/g, self.renderThumbProgress())
943                    .replace(/\{indicator}/g, config.indicatorNew)
944                    .replace(/\{indicatorTitle}/g, config.indicatorNewTitle);
945            } else {
946                out = template.replace(/\{actions}/g, '')
947                    .replace(/\{caption}/g, caption)
948                    .replace(/\{progress}/g, '')
949                    .replace(/\{width}/g, width)
950                    .replace(/\{indicator}/g, '')
951                    .replace(/\{indicatorTitle}/g, '');
952            }
953            out = replaceTags(out, self.previewThumbTags);
954            return out;
955        },
956        renderFileActions: function (showUpload, showDelete, disabled, url, key) {
957            if (!showUpload && !showDelete) {
958                return '';
959            }
960            var self = this,
961                vUrl = url === false ? '' : ' data-url="' + url + '"',
962                vKey = key === false ? '' : ' data-key="' + key + '"',
963                btnDelete = self.getLayoutTemplate('actionDelete'),
964                btnUpload = '',
965                template = self.getLayoutTemplate('actions'),
966                otherButtons = self.otherActionButtons.replace(/\{dataKey}/g, vKey),
967                config = self.fileActionSettings,
968                removeClass = disabled ? config.removeClass + ' disabled' : config.removeClass;
969            btnDelete = btnDelete
970                .replace(/\{removeClass}/g, removeClass)
971                .replace(/\{removeIcon}/g, config.removeIcon)
972                .replace(/\{removeTitle}/g, config.removeTitle)
973                .replace(/\{dataUrl}/g, vUrl)
974                .replace(/\{dataKey}/g, vKey);
975            if (showUpload) {
976                btnUpload = self.getLayoutTemplate('actionUpload')
977                    .replace(/\{uploadClass}/g, config.uploadClass)
978                    .replace(/\{uploadIcon}/g, config.uploadIcon)
979                    .replace(/\{uploadTitle}/g, config.uploadTitle);
980            }
981            return template
982                .replace(/\{delete}/g, btnDelete)
983                .replace(/\{upload}/g, btnUpload)
984                .replace(/\{other}/g, otherButtons);
985        },
986        setThumbStatus: function ($thumb, status) {
987            var self = this;
988            if (!self.showPreview) {
989                return;
990            }
991            var icon = 'indicator' + status, msg = icon + 'Title',
992                css = 'file-preview-' + status.toLowerCase(),
993                $indicator = $thumb.find('.file-upload-indicator'),
994                config = self.fileActionSettings;
995            $thumb.removeClass('file-preview-success file-preview-error file-preview-loading');
996            if (status === 'Error') {
997                $thumb.find('.kv-file-upload').attr('disabled', true);
998            }
999            $indicator.html(config[icon]);
1000            $indicator.attr('title', config[msg]);
1001            $thumb.addClass(css);
1002        },
1003        clearPreview: function () {
1004            var self = this, $thumbs = !self.showUploadedThumbs ? self.$preview.find('.file-preview-frame') :
1005                self.$preview.find('.file-preview-frame:not(.file-preview-success)');
1006            $thumbs.remove();
1007            if (!self.$preview.find('.file-preview-frame').length || !self.showPreview) {
1008                self.resetUpload();
1009            }
1010            self.validateDefaultPreview();
1011        },
1012        initPreview: function (isInit) {
1013            var self = this, cap = self.initialCaption || '', out;
1014            if (!previewCache.count(self.id)) {
1015                self.clearPreview();
1016                if (isInit) {
1017                    self.setCaption(cap);
1018                } else {
1019                    self.initCaption();
1020                }
1021                return;
1022            }
1023            out = previewCache.out(self.id);
1024            cap = isInit && self.initialCaption ? self.initialCaption : out.caption;
1025            self.$preview.html(out.content);
1026            self.setCaption(cap);
1027            if (!isEmpty(out.content)) {
1028                self.$container.removeClass('file-input-new');
1029            }
1030        },
1031        initPreviewDeletes: function () {
1032            var self = this, deleteExtraData = self.deleteExtraData || {},
1033                resetProgress = function () {
1034                    var hasFiles = self.isUploadable ? previewCache.count(self.id) : self.$element.get(0).files.length;
1035                    if (self.$preview.find('.kv-file-remove').length === 0 && !hasFiles) {
1036                        self.reset();
1037                        self.initialCaption = '';
1038                    }
1039                };
1040
1041            self.$preview.find('.kv-file-remove').each(function () {
1042                var $el = $(this), vUrl = $el.data('url') || self.deleteUrl, vKey = $el.data('key');
1043                if (isEmpty(vUrl) || vKey === undefined) {
1044                    return;
1045                }
1046                var $frame = $el.closest('.file-preview-frame'), cache = previewCache.data[self.id],
1047                    settings, params, index = $frame.data('fileindex'), config, extraData;
1048                index = parseInt(index.replace('init_', ''));
1049                config = isEmpty(cache.config) && isEmpty(cache.config[index]) ? null : cache.config[index];
1050                extraData = isEmpty(config) || isEmpty(config.extra) ? deleteExtraData : config.extra;
1051                if (typeof extraData === "function") {
1052                    extraData = extraData();
1053                }
1054                params = {id: $el.attr('id'), key: vKey, extra: extraData};
1055                settings = $.extend({
1056                    url: vUrl,
1057                    type: 'POST',
1058                    dataType: 'json',
1059                    data: $.extend({key: vKey}, extraData),
1060                    beforeSend: function (jqXHR) {
1061                        self.ajaxAborted = false;
1062                        self.raise('filepredelete', [vKey, jqXHR, extraData]);
1063                        if (self.ajaxAborted) {
1064                            jqXHR.abort();
1065                        } else {
1066                            addCss($frame, 'file-uploading');
1067                            addCss($el, 'disabled');
1068                        }
1069                    },
1070                    success: function (data, textStatus, jqXHR) {
1071                        var n, cap;
1072                        if (isEmpty(data) || isEmpty(data.error)) {
1073                            previewCache.unset(self.id, index);
1074                            n = previewCache.count(self.id);
1075                            cap = n > 0 ? self.getMsgSelected(n) : '';
1076                            self.raise('filedeleted', [vKey, jqXHR, extraData]);
1077                            self.setCaption(cap);
1078                        } else {
1079                            params.jqXHR = jqXHR;
1080                            params.response = data;
1081                            self.showError(data.error, params, 'filedeleteerror');
1082                            $frame.removeClass('file-uploading');
1083                            $el.removeClass('disabled');
1084                            resetProgress();
1085                            return;
1086                        }
1087                        $frame.removeClass('file-uploading').addClass('file-deleted');
1088                        $frame.fadeOut('slow', function () {
1089                            self.clearObjects($frame);
1090                            $frame.remove();
1091                            resetProgress();
1092                            if (!n && self.getFileStack().length === 0) {
1093                                self.setCaption('');
1094                                self.reset();
1095                            }
1096                        });
1097                    },
1098                    error: function (jqXHR, textStatus, errorThrown) {
1099                        var errMsg = self.parseError(jqXHR, errorThrown);
1100                        params.jqXHR = jqXHR;
1101                        params.response = {};
1102                        self.showError(errMsg, params, 'filedeleteerror');
1103                        $frame.removeClass('file-uploading');
1104                        resetProgress();
1105                    }
1106                }, self.ajaxDeleteSettings);
1107                handler($el, 'click', function () {
1108                    if (!self.validateMinCount()) {
1109                        return false;
1110                    }
1111                    $.ajax(settings);
1112                });
1113            });
1114        },
1115        clearObjects: function ($el) {
1116            $el.find('video audio').each(function () {
1117                this.pause();
1118                $(this).remove();
1119            });
1120            $el.find('img object div').each(function () {
1121                $(this).remove();
1122            });
1123        },
1124        clearFileInput: function () {
1125            var self = this, $el = self.$element, $srcFrm, $tmpFrm, $tmpEl;
1126            if (isEmpty($el.val())) {
1127                return;
1128            }
1129            // Fix for IE ver < 11, that does not clear file inputs
1130            // Requires a sequence of steps to prevent IE crashing but
1131            // still allow clearing of the file input.
1132            if (self.isIE9 || self.isIE10) {
1133                $srcFrm = $el.closest('form');
1134                $tmpFrm = $(document.createElement('form'));
1135                $tmpEl = $(document.createElement('div'));
1136                $el.before($tmpEl);
1137                if ($srcFrm.length) {
1138                    $srcFrm.after($tmpFrm);
1139                } else {
1140                    $tmpEl.after($tmpFrm);
1141                }
1142                $tmpFrm.append($el).trigger('reset');
1143                $tmpEl.before($el).remove();
1144                $tmpFrm.remove();
1145            } else { // normal input clear behavior for other sane browsers
1146                $el.val('');
1147            }
1148            self.fileInputCleared = true;
1149        },
1150        resetUpload: function () {
1151            var self = this;
1152            self.uploadCache = {content: [], config: [], tags: [], append: true};
1153            self.uploadCount = 0;
1154            self.uploadStatus = {};
1155            self.uploadLog = [];
1156            self.uploadAsyncCount = 0;
1157            self.loadedImages = [];
1158            self.totalImagesCount = 0;
1159            self.$btnUpload.removeAttr('disabled');
1160            self.setProgress(0);
1161            addCss(self.$progress, 'hide');
1162            self.resetErrors(false);
1163            self.ajaxAborted = false;
1164            self.ajaxRequests = [];
1165            self.resetCanvas();
1166        },
1167        resetCanvas: function () {
1168            var self = this;
1169            if (self.canvas && self.imageCanvasContext) {
1170                self.imageCanvasContext.clearRect(0, 0, self.canvas.width, self.canvas.height);
1171            }
1172        },
1173        cancel: function () {
1174            var self = this, xhr = self.ajaxRequests, len = xhr.length, i;
1175            if (len > 0) {
1176                for (i = 0; i < len; i += 1) {
1177                    self.cancelling = true;
1178                    xhr[i].abort();
1179                }
1180            }
1181            self.getThumbs().each(function () {
1182                var $thumb = $(this), ind = $thumb.attr('data-fileindex');
1183                $thumb.removeClass('file-uploading');
1184                if (self.filestack[ind] !== undefined) {
1185                    $thumb.find('.kv-file-upload').removeClass('disabled').removeAttr('disabled');
1186                    $thumb.find('.kv-file-remove').removeClass('disabled').removeAttr('disabled');
1187                }
1188                self.unlock();
1189            });
1190        },
1191        cleanMemory: function ($thumb) {
1192            var data = $thumb.is('img') ? $thumb.attr('src') : $thumb.find('source').attr('src');
1193            /** @namespace objUrl.revokeObjectURL */
1194            objUrl.revokeObjectURL(data);
1195        },
1196        hasInitialPreview: function () {
1197            var self = this;
1198            return !self.overwriteInitial && previewCache.count(self.id);
1199        },
1200        clear: function () {
1201            var self = this, cap;
1202            self.$btnUpload.removeAttr('disabled');
1203            self.getThumbs().find('video,audio,img').each(function () {
1204                self.cleanMemory($(this));
1205            });
1206            self.resetUpload();
1207            self.filestack = [];
1208            self.clearFileInput();
1209            self.resetErrors(true);
1210            self.raise('fileclear');
1211            if (self.hasInitialPreview()) {
1212                self.showFileIcon();
1213                self.resetPreview();
1214                self.initPreviewDeletes();
1215                self.$container.removeClass('file-input-new');
1216            } else {
1217                self.getThumbs().each(function () {
1218                    self.clearObjects($(this));
1219                });
1220                if (self.isUploadable) {
1221                    previewCache.data[self.id] = {};
1222                }
1223                self.$preview.html('');
1224                cap = (!self.overwriteInitial && self.initialCaption.length > 0) ? self.initialCaption : '';
1225                self.setCaption(cap);
1226                self.$caption.attr('title', '');
1227                addCss(self.$container, 'file-input-new');
1228                self.validateDefaultPreview();
1229            }
1230            if (self.$container.find('.file-preview-frame').length === 0) {
1231                if (!self.initCaption()) {
1232                    self.$captionContainer.find('.kv-caption-icon').hide();
1233                }
1234            }
1235            self.hideFileIcon();
1236            self.raise('filecleared');
1237            self.$captionContainer.focus();
1238            self.setFileDropZoneTitle();
1239        },
1240        resetPreview: function () {
1241            var self = this, out, cap;
1242            if (previewCache.count(self.id)) {
1243                out = previewCache.out(self.id);
1244                self.$preview.html(out.content);
1245                cap = self.initialCaption ? self.initialCaption : out.caption;
1246                self.setCaption(cap);
1247            } else {
1248                self.clearPreview();
1249                self.initCaption();
1250            }
1251        },
1252        clearDefaultPreview: function() {
1253            var self = this;
1254            self.$preview.find('.file-default-preview').remove();
1255        },
1256        validateDefaultPreview: function() {
1257            var self = this;
1258            if (!self.showPreview || isEmpty(self.defaultPreviewContent)) {
1259                return;
1260            }
1261            self.$preview.html('<div class="file-default-preview">' + self.defaultPreviewContent + '</div>');
1262            self.$container.removeClass('file-input-new');
1263        },
1264        resetPreviewThumbs: function (isAjax) {
1265            var self = this, out;
1266            if (isAjax) {
1267                self.clearPreview();
1268                self.filestack = [];
1269                return;
1270            }
1271            if (self.hasInitialPreview()) {
1272                out = previewCache.out(self.id);
1273                self.$preview.html(out.content);
1274                self.setCaption(out.caption);
1275                self.initPreviewDeletes();
1276            } else {
1277                self.clearPreview();
1278            }
1279        },
1280        reset: function () {
1281            var self = this;
1282            self.resetPreview();
1283            self.$container.find('.fileinput-filename').text('');
1284            self.raise('filereset');
1285            addCss(self.$container, 'file-input-new');
1286            if (self.$preview.find('.file-preview-frame').length || self.isUploadable && self.dropZoneEnabled) {
1287                self.$container.removeClass('file-input-new');
1288            }
1289            self.setFileDropZoneTitle();
1290            self.filestack = [];
1291            self.filenames = [];
1292            self.formdata = {};
1293        },
1294        disable: function () {
1295            var self = this;
1296            self.isDisabled = true;
1297            self.raise('filedisabled');
1298            self.$element.attr('disabled', 'disabled');
1299            self.$container.find(".kv-fileinput-caption").addClass("file-caption-disabled");
1300            self.$container.find(".btn-file, .fileinput-remove, .fileinput-upload, .file-preview-frame button").attr("disabled",
1301                true);
1302            self.initDragDrop();
1303        },
1304        enable: function () {
1305            var self = this;
1306            self.isDisabled = false;
1307            self.raise('fileenabled');
1308            self.$element.removeAttr('disabled');
1309            self.$container.find(".kv-fileinput-caption").removeClass("file-caption-disabled");
1310            self.$container.find(".btn-file, .fileinput-remove, .fileinput-upload, .file-preview-frame button").removeAttr("disabled");
1311            self.initDragDrop();
1312        },
1313        getThumbs: function (css) {
1314            css = css || '';
1315            return this.$preview.find('.file-preview-frame:not(.file-preview-initial)' + css);
1316        },
1317        getExtraData: function (previewId, index) {
1318            var self = this, data = self.uploadExtraData;
1319            if (typeof self.uploadExtraData === "function") {
1320                data = self.uploadExtraData(previewId, index);
1321            }
1322            return data;
1323        },
1324        uploadExtra: function (previewId, index) {
1325            var self = this, data = self.getExtraData(previewId, index);
1326            if (data.length === 0) {
1327                return;
1328            }
1329            $.each(data, function (key, value) {
1330                self.formdata.append(key, value);
1331            });
1332        },
1333        setAsyncUploadStatus: function (previewId, pct, total) {
1334            var self = this, sum = 0;
1335            self.setProgress(pct, $('#' + previewId).find('.file-thumb-progress'));
1336            self.uploadStatus[previewId] = pct;
1337            $.each(self.uploadStatus, function (key, value) {
1338                sum += value;
1339            });
1340            self.setProgress(Math.ceil(sum / total));
1341
1342        },
1343        initXhr: function (xhrobj, previewId, fileCount) {
1344            var self = this;
1345            if (xhrobj.upload) {
1346                xhrobj.upload.addEventListener('progress', function (event) {
1347                    var pct = 0, position = event.loaded || event.position, total = event.total;
1348                    /** @namespace event.lengthComputable */
1349                    if (event.lengthComputable) {
1350                        pct = Math.ceil(position / total * 100);
1351                    }
1352                    if (previewId) {
1353                        self.setAsyncUploadStatus(previewId, pct, fileCount);
1354                    } else {
1355                        self.setProgress(Math.ceil(pct));
1356                    }
1357                }, false);
1358            }
1359            return xhrobj;
1360        },
1361        ajaxSubmit: function (fnBefore, fnSuccess, fnComplete, fnError, previewId, index) {
1362            var self = this, settings;
1363            self.raise('filepreajax', [previewId, index]);
1364            self.uploadExtra(previewId, index);
1365            settings = $.extend({
1366                xhr: function () {
1367                    var xhrobj = $.ajaxSettings.xhr();
1368                    return self.initXhr(xhrobj, previewId, self.getFileStack().length);
1369                },
1370                url: self.uploadUrl,
1371                type: 'POST',
1372                dataType: 'json',
1373                data: self.formdata,
1374                cache: false,
1375                processData: false,
1376                contentType: false,
1377                beforeSend: fnBefore,
1378                success: fnSuccess,
1379                complete: fnComplete,
1380                error: fnError
1381            }, self.ajaxSettings);
1382            self.ajaxRequests.push($.ajax(settings));
1383        },
1384        initUploadSuccess: function (out, $thumb, allFiles) {
1385            var self = this, append, data, index, $newThumb, content, config, tags, i;
1386            if (!self.showPreview || typeof out !== 'object' || $.isEmptyObject(out)) {
1387                return;
1388            }
1389            if (out.initialPreview !== undefined && out.initialPreview.length > 0) {
1390                self.hasInitData = true;
1391                content = out.initialPreview || [];
1392                config = out.initialPreviewConfig || [];
1393                tags = out.initialPreviewThumbTags || [];
1394                append = out.append === undefined || out.append ? true : false;
1395                self.overwriteInitial = false;
1396                if ($thumb !== undefined) {
1397                    if (!allFiles) {
1398                        index = previewCache.add(self.id, content, config[0], tags[0], append);
1399                        data = previewCache.get(self.id, index, false);
1400                        $newThumb = $(data).hide();
1401                        $thumb.after($newThumb).fadeOut('slow', function () {
1402                            $newThumb.fadeIn('slow').css('display:inline-block');
1403                            self.initPreviewDeletes();
1404                            self.clearFileInput();
1405                            $thumb.remove();
1406                        });
1407                    } else {
1408                        i = $thumb.attr('data-fileindex');
1409                        self.uploadCache.content[i] = content[0];
1410                        self.uploadCache.config[i] = config[0];
1411                        self.uploadCache.tags[i] = tags[0];
1412                        self.uploadCache.append = append;
1413                    }
1414                } else {
1415                    previewCache.set(self.id, content, config, tags, append);
1416                    self.initPreview();
1417                    self.initPreviewDeletes();
1418                }
1419            }
1420        },
1421        initSuccessThumbs: function () {
1422            var self = this;
1423            if (!self.showPreview) {
1424                return;
1425            }
1426            self.getThumbs('.file-preview-success').each(function () {
1427                var $thumb = $(this), $remove = $thumb.find('.kv-file-remove');
1428                $remove.removeAttr('disabled');
1429                handler($remove, 'click', function () {
1430                    var out = self.raise('filesuccessremove', [$thumb.attr('id'), $thumb.data('fileindex')]);
1431                    self.cleanMemory($thumb);
1432                    if (out === false) {
1433                        return;
1434                    }
1435                    $thumb.fadeOut('slow', function () {
1436                        $thumb.remove();
1437                        if (!self.$preview.find('.file-preview-frame').length) {
1438                            self.reset();
1439                        }
1440                    });
1441                });
1442            });
1443        },
1444        checkAsyncComplete: function () {
1445            var self = this, previewId, i;
1446            for (i = 0; i < self.filestack.length; i++) {
1447                if (self.filestack[i]) {
1448                    previewId = self.previewInitId + "-" + i;
1449                    if ($.inArray(previewId, self.uploadLog) === -1) {
1450                        return false;
1451                    }
1452                }
1453            }
1454            return (self.uploadAsyncCount === self.uploadLog.length);
1455        },
1456        uploadSingle: function (i, files, allFiles) {
1457            var self = this, total = self.getFileStack().length, formdata = new FormData(), outData,
1458                previewId = self.previewInitId + "-" + i, $thumb, chkComplete, $btnUpload, $btnDelete,
1459                hasPostData = self.filestack.length > 0 || !$.isEmptyObject(self.uploadExtraData),
1460                fnBefore, fnSuccess, fnComplete, fnError, updateUploadLog, params = {id: previewId, index: i};
1461            self.formdata = formdata;
1462            if (self.showPreview) {
1463                $thumb = $('#' + previewId + ':not(.file-preview-initial)');
1464                $btnUpload = $thumb.find('.kv-file-upload');
1465                $btnDelete = $thumb.find('.kv-file-remove');
1466                $('#' + previewId).find('.file-thumb-progress').removeClass('hide');
1467            }
1468            if (total === 0 || !hasPostData || ($btnUpload && $btnUpload.hasClass('disabled')) || self.abort(params)) {
1469                return;
1470            }
1471            updateUploadLog = function (i, previewId) {
1472                self.filestack[i] = undefined;
1473                self.uploadLog.push(previewId);
1474                if (self.checkAsyncComplete()) {
1475                    self.fileBatchCompleted = true;
1476                }
1477            };
1478            chkComplete = function () {
1479                if (!self.fileBatchCompleted) {
1480                    return;
1481                }
1482                setTimeout(function () {
1483                    if (self.showPreview) {
1484                        previewCache.set(
1485                            self.id,
1486                            self.uploadCache.content,
1487                            self.uploadCache.config,
1488                            self.uploadCache.tags,
1489                            self.uploadCache.append
1490                        );
1491                        if (self.hasInitData) {
1492                            self.initPreview();
1493                            self.initPreviewDeletes();
1494                        }
1495                    }
1496                    self.unlock();
1497                    self.clearFileInput();
1498                    self.raise('filebatchuploadcomplete', [self.filestack, self.getExtraData()]);
1499                    self.uploadCount = 0;
1500                    self.uploadStatus = {};
1501                    self.uploadLog = [];
1502                    self.setProgress(100);
1503                }, 100);
1504            };
1505            fnBefore = function (jqXHR) {
1506                outData = self.getOutData(jqXHR);
1507                self.fileBatchCompleted = false;
1508                if (self.showPreview) {
1509                    if (!$thumb.hasClass('file-preview-success')) {
1510                        self.setThumbStatus($thumb, 'Loading');
1511                        addCss($thumb, 'file-uploading');
1512                    }
1513                    $btnUpload.attr('disabled', true);
1514                    $btnDelete.attr('disabled', true);
1515                }
1516                if (!allFiles) {
1517                    self.lock();
1518                }
1519                self.raise('filepreupload', [outData, previewId, i]);
1520                params = $.extend(params, outData);
1521                if (self.abort(params)) {
1522                    jqXHR.abort();
1523                    self.setProgress(100);
1524                }
1525            };
1526            fnSuccess = function (data, textStatus, jqXHR) {
1527                outData = self.getOutData(jqXHR, data);
1528                params = $.extend(params, outData);
1529                setTimeout(function () {
1530                    if (isEmpty(data) || isEmpty(data.error)) {
1531                        if (self.showPreview) {
1532                            self.setThumbStatus($thumb, 'Success');
1533                            $btnUpload.hide();
1534                            self.initUploadSuccess(data, $thumb, allFiles);
1535                        }
1536                        self.raise('fileuploaded', [outData, previewId, i]);
1537                        if (!allFiles) {
1538                            self.resetFileStack();
1539                            self.filestack[i] = undefined;
1540                            self.filenames[i] = undefined;
1541                        } else {
1542                            updateUploadLog(i, previewId);
1543                        }
1544                    } else {
1545                        self.setThumbStatus($thumb, 'Error');
1546                        self.showUploadError(data.error, params);
1547                        if (allFiles) {
1548                            updateUploadLog(i, previewId);
1549                        }
1550                    }
1551                }, 100);
1552            };
1553            fnComplete = function () {
1554                setTimeout(function () {
1555                    if (self.showPreview) {
1556                        $btnUpload.removeAttr('disabled');
1557                        $btnDelete.removeAttr('disabled');
1558                        $thumb.removeClass('file-uploading');
1559                    }
1560                    if (!allFiles) {
1561                        self.unlock(false);
1562                    } else {
1563                        chkComplete();
1564                    }
1565                    self.initSuccessThumbs();
1566                }, 100);
1567            };
1568            fnError = function (jqXHR, textStatus, errorThrown) {
1569                var errMsg = self.parseError(jqXHR, errorThrown, (allFiles ? files[i].name : null));
1570                setTimeout(function () {
1571                    if (allFiles) {
1572                        updateUploadLog(i, previewId);
1573                    }
1574                    self.uploadStatus[previewId] = 100;
1575                    self.setThumbStatus($thumb, 'Error');
1576                    params = $.extend(params, self.getOutData(jqXHR));
1577                    self.showUploadError(errMsg, params);
1578                }, 100);
1579            };
1580            formdata.append(self.uploadFileAttr, files[i], self.filenames[i]);
1581            formdata.append('file_id', i);
1582            self.ajaxSubmit(fnBefore, fnSuccess, fnComplete, fnError, previewId, i);
1583        },
1584        uploadBatch: function () {
1585            var self = this, files = self.filestack, total = files.length, params = {},
1586                hasPostData = self.filestack.length > 0 || !$.isEmptyObject(self.uploadExtraData),
1587                setAllUploaded, fnBefore, fnSuccess, fnComplete, fnError;
1588            self.formdata = new FormData();
1589            if (total === 0 || !hasPostData || self.abort(params)) {
1590                return;
1591            }
1592            setAllUploaded = function () {
1593                $.each(files, function (key) {
1594                    self.filestack[key] = undefined;
1595                });
1596                self.clearFileInput();
1597            };
1598            fnBefore = function (jqXHR) {
1599                self.lock();
1600                var outData = self.getOutData(jqXHR);
1601                if (self.showPreview) {
1602                    self.getThumbs().each(function () {
1603                        var $thumb = $(this), $btnUpload = $thumb.find('.kv-file-upload'),
1604                            $btnDelete = $thumb.find('.kv-file-remove');
1605                        if (!$thumb.hasClass('file-preview-success')) {
1606                            self.setThumbStatus($thumb, 'Loading');
1607                            addCss($thumb, 'file-uploading');
1608                        }
1609                        $btnUpload.attr('disabled', true);
1610                        $btnDelete.attr('disabled', true);
1611                    });
1612                }
1613                self.raise('filebatchpreupload', [outData]);
1614                if (self.abort(outData)) {
1615                    jqXHR.abort();
1616                    self.setProgress(100);
1617                }
1618            };
1619            fnSuccess = function (data, textStatus, jqXHR) {
1620                /** @namespace data.errorkeys */
1621                var outData = self.getOutData(jqXHR, data), $thumbs = self.getThumbs(), key = 0,
1622                    keys = isEmpty(data) || isEmpty(data.errorkeys) ? [] : data.errorkeys;
1623                if (isEmpty(data) || isEmpty(data.error)) {
1624                    self.raise('filebatchuploadsuccess', [outData]);
1625                    setAllUploaded();
1626                    if (self.showPreview) {
1627                        $thumbs.each(function () {
1628                            var $thumb = $(this), $btnUpload = $thumb.find('.kv-file-upload');
1629                            $thumb.find('.kv-file-upload').hide();
1630                            self.setThumbStatus($thumb, 'Success');
1631                            $thumb.removeClass('file-uploading');
1632                            $btnUpload.removeAttr('disabled');
1633                        });
1634                        self.initUploadSuccess(data);
1635                    } else {
1636                        self.reset();
1637                    }
1638                } else {
1639                    if (self.showPreview) {
1640                        $thumbs.each(function () {
1641                            var $thumb = $(this), $btnDelete = $thumb.find('.kv-file-remove'),
1642                                $btnUpload = $thumb.find('.kv-file-upload');
1643                            $thumb.removeClass('file-uploading');
1644                            $btnUpload.removeAttr('disabled');
1645                            $btnDelete.removeAttr('disabled');
1646                            if (keys.length === 0) {
1647                                self.setThumbStatus($thumb, 'Error');
1648                                return;
1649                            }
1650                            if ($.inArray(key, keys) !== -1) {
1651                                self.setThumbStatus($thumb, 'Error');
1652                            } else {
1653                                $thumb.find('.kv-file-upload').hide();
1654                                self.setThumbStatus($thumb, 'Success');
1655                                self.filestack[key] = undefined;
1656                            }
1657                            key++;
1658                        });
1659                        self.initUploadSuccess(data);
1660                    }
1661                    self.showUploadError(data.error, outData, 'filebatchuploaderror');
1662                }
1663            };
1664            fnComplete = function () {
1665                self.setProgress(100);
1666                self.unlock();
1667                self.initSuccessThumbs();
1668                self.clearFileInput();
1669                self.raise('filebatchuploadcomplete', [self.filestack, self.getExtraData()]);
1670            };
1671            fnError = function (jqXHR, textStatus, errorThrown) {
1672                var outData = self.getOutData(jqXHR), errMsg = self.parseError(jqXHR, errorThrown);
1673                self.showUploadError(errMsg, outData, 'filebatchuploaderror');
1674                self.uploadFileCount = total - 1;
1675                if (!self.showPreview) {
1676                    return;
1677                }
1678                self.getThumbs().each(function () {
1679                    var $thumb = $(this), key = $thumb.attr('data-fileindex');
1680                    $thumb.removeClass('file-uploading');
1681                    if (self.filestack[key] !== undefined) {
1682                        self.setThumbStatus($thumb, 'Error');
1683                    }
1684                });
1685                self.getThumbs().removeClass('file-uploading');
1686                self.getThumbs(' .kv-file-upload').removeAttr('disabled');
1687                self.getThumbs(' .kv-file-delete').removeAttr('disabled');
1688            };
1689            $.each(files, function (key, data) {
1690                if (!isEmpty(files[key])) {
1691                    self.formdata.append(self.uploadFileAttr, data, self.filenames[key]);
1692                }
1693            });
1694            self.ajaxSubmit(fnBefore, fnSuccess, fnComplete, fnError);
1695        },
1696        uploadExtraOnly: function () {
1697            var self = this, params = {}, fnBefore, fnSuccess, fnComplete, fnError;
1698            self.formdata = new FormData();
1699            if (self.abort(params)) {
1700                return;
1701            }
1702            fnBefore = function (jqXHR) {
1703                self.lock();
1704                var outData = self.getOutData(jqXHR);
1705                self.raise('filebatchpreupload', [outData]);
1706                self.setProgress(50);
1707                params.data = outData;
1708                params.xhr = jqXHR;
1709                if (self.abort(params)) {
1710                    jqXHR.abort();
1711                    self.setProgress(100);
1712                }
1713            };
1714            fnSuccess = function (data, textStatus, jqXHR) {
1715                var outData = self.getOutData(jqXHR, data);
1716                if (isEmpty(data) || isEmpty(data.error)) {
1717                    self.raise('filebatchuploadsuccess', [outData]);
1718                    self.clearFileInput();
1719                    self.initUploadSuccess(data);
1720                } else {
1721                    self.showUploadError(data.error, outData, 'filebatchuploaderror');
1722                }
1723            };
1724            fnComplete = function () {
1725                self.setProgress(100);
1726                self.unlock();
1727                self.clearFileInput();
1728                self.raise('filebatchuploadcomplete', [self.filestack, self.getExtraData()]);
1729            };
1730            fnError = function (jqXHR, textStatus, errorThrown) {
1731                var outData = self.getOutData(jqXHR), errMsg = self.parseError(jqXHR, errorThrown);
1732                params.data = outData;
1733                self.showUploadError(errMsg, outData, 'filebatchuploaderror');
1734            };
1735            self.ajaxSubmit(fnBefore, fnSuccess, fnComplete, fnError);
1736        },
1737        upload: function () {
1738            var self = this, totLen = self.getFileStack().length, params = {},
1739                i, outData, len, hasExtraData = !$.isEmptyObject(self.getExtraData());
1740            if (self.minFileCount > 0 && self.getFileCount(totLen) < self.minFileCount) {
1741                self.noFilesError(params);
1742                return;
1743            }
1744            if (!self.isUploadable || self.isDisabled || (totLen === 0 && !hasExtraData)) {
1745                return;
1746            }
1747            self.resetUpload();
1748            self.$progress.removeClass('hide');
1749            self.uploadCount = 0;
1750            self.uploadStatus = {};
1751            self.uploadLog = [];
1752            self.lock();
1753            self.setProgress(2);
1754            if (totLen === 0 && hasExtraData) {
1755                self.uploadExtraOnly();
1756                return;
1757            }
1758            len = self.filestack.length;
1759            self.hasInitData = false;
1760            if (self.uploadAsync) {
1761                outData = self.getOutData();
1762                self.raise('filebatchpreupload', [outData]);
1763                self.fileBatchCompleted = false;
1764                self.uploadCache = {content: [], config: [], tags: [], append: true};
1765                self.uploadAsyncCount = self.getFileStack().length;
1766                for (i = 0; i < len; i++) {
1767                    self.uploadCache.content[i] = null;
1768                    self.uploadCache.config[i] = null;
1769                    self.uploadCache.tags[i] = null;
1770                }
1771                for (i = 0; i < len; i++) {
1772                    if (self.filestack[i] !== undefined) {
1773                        self.uploadSingle(i, self.filestack, true);
1774                    }
1775                }
1776                return;
1777            }
1778            self.uploadBatch();
1779        },
1780        initFileActions: function () {
1781            var self = this;
1782            if (!self.showPreview) {
1783                return;
1784            }
1785            self.$preview.find('.kv-file-remove').each(function () {
1786                var $el = $(this), $frame = $el.closest('.file-preview-frame'), hasError,
1787                    id = $frame.attr('id'), ind = $frame.attr('data-fileindex'), n, cap, status;
1788                handler($el, 'click', function () {
1789                    status = self.raise('filepreremove', [id, ind]);
1790                    if (status === false || !self.validateMinCount()) {
1791                        return false;
1792                    }
1793                    hasError = $frame.hasClass('file-preview-error');
1794                    self.cleanMemory($frame);
1795                    $frame.fadeOut('slow', function () {
1796                        self.filestack[ind] = undefined;
1797                        self.filenames[ind] = '';
1798                        self.clearObjects($frame);
1799                        $frame.remove();
1800                        if (id && hasError) {
1801                            self.$errorContainer.find('li[data-file-id="' + id + '"]').fadeOut('fast', function () {
1802                                $(this).remove();
1803                                if (!self.errorsExist()) {
1804                                    self.resetErrors();
1805                                }
1806                            });
1807                        }
1808                        var filestack = self.getFileStack(true), len = filestack.length, chk = previewCache.count(self.id),
1809                            hasThumb = self.showPreview && self.$preview.find('.file-preview-frame').length;
1810                        self.clearFileInput();
1811                        if (len === 0 && chk === 0 && !hasThumb) {
1812                            self.reset();
1813                        } else {
1814                            n = chk + len;
1815                            cap = n > 1 ? self.getMsgSelected(n) : (filestack[0] ? filestack[0].name : '');
1816                            self.setCaption(cap);
1817                        }
1818                        self.raise('fileremoved', [id, ind]);
1819                    });
1820                });
1821            });
1822            self.$preview.find('.kv-file-upload').each(function () {
1823                var $el = $(this);
1824                handler($el, 'click', function () {
1825                    var $frame = $el.closest('.file-preview-frame'),
1826                        ind = $frame.attr('data-fileindex');
1827                    if (!$frame.hasClass('file-preview-error')) {
1828                        self.uploadSingle(ind, self.filestack, false);
1829                    }
1830                });
1831            });
1832        },
1833        hideFileIcon: function () {
1834            if (this.overwriteInitial) {
1835                this.$captionContainer.find('.kv-caption-icon').hide();
1836            }
1837        },
1838        showFileIcon: function () {
1839            this.$captionContainer.find('.kv-caption-icon').show();
1840        },
1841        addError: function (msg) {
1842            var self = this, $error = self.$errorContainer;
1843            if (msg && $error.length) {
1844                $error.html(self.errorCloseButton + msg);
1845                $error.find('.kv-error-close').off('click').on('click', function () {
1846                    $error.fadeOut('slow');
1847                });
1848            }
1849        },
1850        resetErrors: function (fade) {
1851            var self = this, $error = self.$errorContainer;
1852            self.isError = false;
1853            self.$container.removeClass('has-error');
1854            $error.html('');
1855            if (fade) {
1856                $error.fadeOut('slow');
1857            } else {
1858                $error.hide();
1859            }
1860        },
1861        showFolderError: function (folders) {
1862            var self = this, $error = self.$errorContainer;
1863            if (!folders) {
1864                return;
1865            }
1866            self.addError(self.msgFoldersNotAllowed.replace(/\{n}/g, folders));
1867            $error.fadeIn(800);
1868            addCss(self.$container, 'has-error');
1869            self.raise('filefoldererror', [folders]);
1870        },
1871        showUploadError: function (msg, params, event) {
1872            var self = this, $error = self.$errorContainer, ev = event || 'fileuploaderror',
1873                e = params && params.id ? '<li data-file-id="' + params.id + '">' + msg + '</li>' : '<li>' + msg + '</li>';
1874            if ($error.find('ul').length === 0) {
1875                self.addError('<ul>' + e + '</ul>');
1876            } else {
1877                $error.find('ul').append(e);
1878            }
1879            $error.fadeIn(800);
1880            self.raise(ev, [params]);
1881            self.$container.removeClass('file-input-new');
1882            addCss(self.$container, 'has-error');
1883            return true;
1884        },
1885        showError: function (msg, params, event) {
1886            var self = this, $error = self.$errorContainer, ev = event || 'fileerror';
1887            params = params || {};
1888            params.reader = self.reader;
1889            self.addError(msg);
1890            $error.fadeIn(800);
1891            self.raise(ev, [params]);
1892            if (!self.isUploadable) {
1893                self.clearFileInput();
1894            }
1895            self.$container.removeClass('file-input-new');
1896            addCss(self.$container, 'has-error');
1897            self.$btnUpload.attr('disabled', true);
1898            return true;
1899        },
1900        errorHandler: function (evt, caption) {
1901            var self = this, err = evt.target.error;
1902            /** @namespace err.NOT_FOUND_ERR */
1903            /** @namespace err.SECURITY_ERR */
1904            /** @namespace err.NOT_READABLE_ERR */
1905            if (err.code === err.NOT_FOUND_ERR) {
1906                self.showError(self.msgFileNotFound.replace('{name}', caption));
1907            } else if (err.code === err.SECURITY_ERR) {
1908                self.showError(self.msgFileSecured.replace('{name}', caption));
1909            } else if (err.code === err.NOT_READABLE_ERR) {
1910                self.showError(self.msgFileNotReadable.replace('{name}', caption));
1911            } else if (err.code === err.ABORT_ERR) {
1912                self.showError(self.msgFilePreviewAborted.replace('{name}', caption));
1913            } else {
1914                self.showError(self.msgFilePreviewError.replace('{name}', caption));
1915            }
1916        },
1917        parseFileType: function (file) {
1918            var self = this, isValid, vType, cat, i;
1919            for (i = 0;
1919 i < defaultPreviewTypes.length; i += 1) {
1920                cat = defaultPreviewTypes[i];
1921                isValid = isSet(cat, self.fileTypeSettings) ? self.fileTypeSettings[cat] : defaultFileTypeSettings[cat];
1922                vType = isValid(file.type, file.name) ? cat : '';
1923                if (!isEmpty(vType)) {
1924                    return vType;
1925                }
1926            }
1927            return 'other';
1928        },
1929        previewDefault: function (file, previewId, isDisabled) {
1930            if (!this.showPreview) {
1931                return;
1932            }
1933            var self = this, frameClass = '', fname = file ? file.name : '',
1934                /** @namespace objUrl.createObjectURL */
1935                data = objUrl.createObjectURL(file), ind = previewId.slice(previewId.lastIndexOf('-') + 1),
1936                config = self.previewSettings.other || defaultPreviewSettings.other,
1937                footer = self.renderFileFooter(file.name, config.width),
1938                previewOtherTemplate = self.parseFilePreviewIcon(self.getPreviewTemplate('other'), fname);
1939            if (isDisabled === true) {
1940                if (!self.isUploadable) {
1941                    footer += '<div class="file-other-error" title="' + self.fileActionSettings.indicatorErrorTitle +
1942                    '">' + self.fileActionSettings.indicatorError + '</div>';
1943                }
1944            }
1945            self.clearDefaultPreview();
1946            self.$preview.append("\n" + previewOtherTemplate
1947                .replace(/\{previewId}/g, previewId)
1948                .replace(/\{frameClass}/g, frameClass)
1949                .replace(/\{fileindex}/g, ind)
1950                .replace(/\{caption}/g, self.slug(file.name))
1951                .replace(/\{width}/g, config.width)
1952                .replace(/\{height}/g, config.height)
1953                .replace(/\{type}/g, file.type)
1954                .replace(/\{data}/g, data)
1955                .replace(/\{footer}/g, footer));
1956            if (isDisabled === true && self.isUploadable) {
1957                self.setThumbStatus($('#' + previewId), 'Error');
1958            }
1959        },
1960        previewFile: function (i, file, theFile, previewId, data) {
1961            if (!this.showPreview) {
1962                return;
1963            }
1964            var self = this, cat = self.parseFileType(file), fname = file ? file.name : '', caption = self.slug(fname),
1965                content, strText, types = self.allowedPreviewTypes, mimes = self.allowedPreviewMimeTypes,
1966                tmplt = self.getPreviewTemplate(cat), chkTypes = types && types.indexOf(cat) >= 0, id,
1967                config = isSet(cat, self.previewSettings) ? self.previewSettings[cat] : defaultPreviewSettings[cat],
1968                chkMimes = mimes && mimes.indexOf(file.type) !== -1,
1969                footer = self.renderFileFooter(caption, config.width), modal = '',
1970                ind = previewId.slice(previewId.lastIndexOf('-') + 1);
1971            if (chkTypes || chkMimes) {
1972                tmplt = self.parseFilePreviewIcon(tmplt, fname.split('.').pop());
1973                if (cat === 'text') {
1974                    strText = htmlEncode(theFile.target.result);
1975                    id = 'text-' + uniqId();
1976                    content = tmplt.replace(/\{zoom}/g, self.getLayoutTemplate('zoom'));
1977                    modal = self.getLayoutTemplate('modal').replace('{id}', id)
1978                        .replace(/\{title}/g, caption)
1979                        .replace(/\{body}/g, strText).replace(/\{heading}/g, self.msgZoomModalHeading);
1980                    content = content.replace(/\{previewId}/g, previewId).replace(/\{caption}/g, caption)
1981                        .replace(/\{width}/g, config.width).replace(/\{height}/g, config.height)
1982                        .replace(/\{frameClass}/g, '').replace(/\{zoomInd}/g, self.zoomIndicator)
1983                        .replace(/\{footer}/g, footer).replace(/\{fileindex}/g, ind)
1984                        .replace(/\{type}/g, file.type).replace(/\{zoomTitle}/g, self.msgZoomTitle)
1985                        .replace(/\{dialog}/g, "$('#" + id + "').modal('show')")
1986                        .replace(/\{data}/g, strText) + modal;
1987                } else {
1988                    content = tmplt.replace(/\{previewId}
1988/g, previewId).replace(/\{caption}/g, caption)
1989                        .replace(/\{frameClass}/g, '').replace(/\{type}/g, file.type).replace(/\{fileindex}/g, ind)
1990                        .replace(/\{width}/g, config.width).replace(/\{height}/g, config.height)
1991                        .replace(/\{footer}/g, footer).replace(/\{data}/g, data);
1992                }
1993                self.clearDefaultPreview();
1994                self.$preview.append("\n" + content);
1995                self.validateImage(i, previewId, caption, file.type);
1996            } else {
1997                self.previewDefault(file, previewId);
1998            }
1999        },
2000        slugDefault: function (text) {
2001            return isEmpty(text) ? '' : text.split(/(\\|\/)/g).pop().replace(/[^\w\u00C0-\u017F\-.\\\/ ]+/g, '');
2002        },
2003        getFileStack: function (skipNull) {
2004            var self = this;
2005            return self.filestack.filter(function (n) {
2006                return (skipNull ? n !== undefined : n !== undefined && n !== null);
2007            });
2008        },
2009        readFiles: function (files) {
2010            this.reader = new FileReader();
2011            var self = this, $el = self.$element, $preview = self.$preview, reader = self.reader,
2012                $container = self.$previewContainer, $status = self.$previewStatus, msgLoading = self.msgLoading,
2013                msgProgress = self.msgProgress, previewInitId = self.previewInitId, numFiles = files.length,
2014                settings = self.fileTypeSettings, ctr = self.filestack.length, readFile,
2015                throwError = function (msg, file, previewId, index) {
2016                    var p1 = $.extend(self.getOutData({}, {}, files), {id: previewId, index: index}),
2017                        p2 = {id: previewId, index: index, file: file, files: files};
2018                    self.previewDefault(file, previewId, true);
2019                    if (self.isUploadable) {
2020                        self.filestack.push(undefined);
2021                    }
2022                    setTimeout(readFile(index + 1), 100);
2023                    self.initFileActions();
2024                    return self.isUploadable ? self.showUploadError(msg, p1) : self.showError(msg, p2);
2025                };
2026
2027            self.loadedImages = [];
2028            self.totalImagesCount = 0;
2029
2030            $.each(files, function (key, file) {
2031                var cat = 'image',
2032                    func = isSet(cat, self.fileTypeSettings) ? self.fileTypeSettings[cat] : defaultFileTypeSettings[cat];
2033                if (func && func(file.type)) {
2034                    self.totalImagesCount++;
2035                }
2036                if (file && file.name) {
2037                    self.filenames[key] = file.name;
2038                }
2039            });
2040
2041            readFile = function (i) {
2042                if (isEmpty($el.attr('multiple'))) {
2043                    numFiles = 1;
2044                }
2045                if (i >= numFiles) {
2046                    if (self.isUploadable && self.filestack.length > 0) {
2047                        self.raise('filebatchselected', [self.getFileStack()]);
2048                    } else {
2049                        self.raise('filebatchselected', [files]);
2050                    }
2051                    $container.removeClass('loading');
2052                    $status.html('');
2053                    return;
2054                }
2055                var node = ctr + i, previewId = previewInitId + "-" + node, isText, file = files[i],
2056                    caption = self.slug(file.name), fileSize = (file.size || 0) / 1000, checkFile, fileExtExpr = '',
2057                    previewData = objUrl.createObjectURL(file), fileCount = 0, j, msg, typ, chk,
2058                    fileTypes = self.allowedFileTypes, strTypes = isEmpty(fileTypes) ? '' : fileTypes.join(', '),
2059                    fileExt = self.allowedFileExtensions, strExt = isEmpty(fileExt) ? '' : fileExt.join(', ');
2060                if (!isEmpty(fileExt)) {
2061                    fileExtExpr = new RegExp('\\.(' + fileExt.join('|') + ')$', 'i');
2062                }
2063                fileSize = fileSize.toFixed(2);
2064                if (self.maxFileSize > 0 && fileSize > self.maxFileSize) {
2065                    msg = self.msgSizeTooLarge.replace('{name}', caption)
2066                        .replace('{size}', fileSize)
2067                        .replace('{maxSize}', self.maxFileSize);
2068                    self.isError = throwError(msg, file, previewId, i);
2069                    return;
2070                }
2071                if (!isEmpty(fileTypes) && isArray(fileTypes)) {
2072                    for (j = 0; j < fileTypes.length; j += 1) {
2073                        typ = fileTypes[j];
2074                        checkFile = settings[typ];
2075                        chk = (checkFile !== undefined && checkFile(file.type, caption));
2076                        fileCount += isEmpty(chk) ? 0 : chk.length;
2077                    }
2078                    if (fileCount === 0) {
2079                        msg = self.msgInvalidFileType.replace('{name}', caption).replace('{types}', strTypes);
2080                        self.isError = throwError(msg, file, previewId, i);
2081                        return;
2082                    }
2083                }
2084                if (fileCount === 0 && !isEmpty(fileExt) && isArray(fileExt) && !isEmpty(fileExtExpr)) {
2085                    chk = caption.match(fileExtExpr);
2086                    fileCount += isEmpty(chk) ? 0 : chk.length;
2087                    if (fileCount === 0) {
2088                        msg = self.msgInvalidFileExtension.replace('{name}', caption).replace('{extensions}',
2089                            strExt);
2090                        self.isError = throwError(msg, file, previewId, i);
2091                        return;
2092                    }
2093                }
2094                if (!self.showPreview) {
2095                    self.filestack.push(file);
2096                    setTimeout(readFile(i + 1), 100);
2097                    self.raise('fileloaded', [file, previewId, i, reader]);
2098                    return;
2099                }
2100                if ($preview.length > 0 && FileReader !== undefined) {
2101                    $status.html(msgLoading.replace('{index}', i + 1).replace('{files}', numFiles));
2102                    $container.addClass('loading');
2103                    reader.onerror = function (evt) {
2104                        self.errorHandler(evt, caption);
2105                    };
2106                    reader.onload = function (theFile) {
2107                        self.previewFile(i, file, theFile, previewId, previewData);
2108                        self.initFileActions();
2109                    };
2110                    reader.onloadend = function () {
2111                        msg = msgProgress
2112                            .replace('{index}', i + 1).replace('{files}', numFiles)
2113                            .replace('{percent}', 50).replace('{name}', caption);
2114                        setTimeout(function () {
2115                            $status.html(msg);
2116                            self.updateFileDetails(numFiles);
2117                            readFile(i + 1);
2118                        }, 100);
2119                        self.raise('fileloaded', [file, previewId, i, reader]);
2120                    };
2121                    reader.onprogress = function (data) {
2122                        if (data.lengthComputable) {
2123                            var fact = (data.loaded / data.total) * 100, progress = Math.ceil(fact);
2124                            msg = msgProgress.replace('{index}', i + 1).replace('{files}', numFiles)
2125                                .replace('{percent}', progress).replace('{name}', caption);
2126                            setTimeout(function () {
2127                                $status.html(msg);
2128                            }, 100);
2129                        }
2130                    };
2131                    isText = isSet('text', settings) ? settings.text : defaultFileTypeSettings.text;
2132                    if (isText(file.type, caption)) {
2133                        reader.readAsText(file, self.textEncoding);
2134                    } else {
2135                        reader.readAsArrayBuffer(file);
2136                    }
2137                } else {
2138                    self.previewDefault(file, previewId);
2139                    setTimeout(function () {
2140                        readFile(i + 1);
2141                        self.updateFileDetails(numFiles);
2142                    }, 100);
2143                    self.raise('fileloaded', [file, previewId, i, reader]);
2144                }
2145                self.filestack.push(file);
2146            };
2147
2148            readFile(0);
2149            self.updateFileDetails(numFiles, false);
2150        },
2151        updateFileDetails: function (numFiles) {
2152            var self = this, $el = self.$element, fileStack = self.getFileStack(),
2153                name = $el.val() || (fileStack.length && fileStack[0].name) || '', label = self.slug(name),
2154                n = self.isUploadable ? fileStack.length : numFiles,
2155                nFiles = previewCache.count(self.id) + n,
2156                log = n > 1 ? self.getMsgSelected(nFiles) : label;
2157            if (self.isError) {
2158                self.$previewContainer.removeClass('loading');
2159                self.$previewStatus.html('');
2160                self.$captionContainer.find('.kv-caption-icon').hide();
2161            } else {
2162                self.showFileIcon();
2163            }
2164            self.setCaption(log, self.isError);
2165            self.$container.removeClass('file-input-new file-input-ajax-new');
2166            if (arguments.length === 1) {
2167                self.raise('fileselect', [numFiles, label]);
2168            }
2169            if (previewCache.count(self.id)) {
2170                self.initPreviewDeletes();
2171            }
2172        },
2173        validateMinCount: function () {
2174            var self = this, len = self.isUploadable ? self.getFileStack().length : self.$element.get(0).files.length;
2175            if (self.validateInitialCount && self.minFileCount > 0 && self.getFileCount(len - 1) < self.minFileCount) {
2176                self.noFilesError({});
2177                return false;
2178            }
2179            return true;
2180        },
2181        getFileCount: function (fileCount) {
2182            var self = this, addCount = 0;
2183            if (self.validateInitialCount && !self.overwriteInitial) {
2184                addCount = previewCache.count(self.id);
2185                fileCount += addCount;
2186            }
2187            return fileCount;
2188        },
2189        change: function (e) {
2190            var self = this, $el = self.$element;
2191            if (!self.isUploadable && isEmpty($el.val()) && self.fileInputCleared) { // IE 11 fix
2192                self.fileInputCleared = false;
2193                return;
2194            }
2195            self.fileInputCleared = false;
2196            var tfiles, msg, total, isDragDrop = arguments.length > 1,
2197                files = isDragDrop ? e.originalEvent.dataTransfer.files : $el.get(0).files,
2198                isSingleUpload = isEmpty($el.attr('multiple')), i = 0, f, n, folders = 0,
2199                ctr = self.filestack.length, isAjaxUpload = self.isUploadable, len,
2200                flagSingle = (isSingleUpload && ctr > 0),
2201                throwError = function (mesg, file, previewId, index) {
2202                    var p1 = $.extend(self.getOutData({}, {}, files), {id: previewId, index: index}),
2203                        p2 = {id: previewId, index: index, file: file, files: files};
2204                    return self.isUploadable ? self.showUploadError(mesg, p1) : self.showError(mesg, p2);
2205                };
2206            self.reader = null;
2207            self.resetUpload();
2208            self.hideFileIcon();
2209            if (self.isUploadable) {
2210                self.$container.find('.file-drop-zone .' + self.dropZoneTitleClass).remove();
2211            }
2212            if (isDragDrop) {
2213                tfiles = [];
2214                while (files[i]) {
2215                    f = files[i];
2216                    if (!f.type && f.size % 4096 === 0) {
2217                        folders++;
2218                    } else {
2219                        tfiles.push(f);
2220                    }
2221                    i++;
2222                }
2223            } else {
2224                if (e.target.files === undefined) {
2225                    tfiles = e.target && e.target.value ? [
2226                        {name: e.target.value.replace(/^.+\\/, '')}
2227                    ] : [];
2228                } else {
2229                    tfiles = e.target.files;
2230                }
2231            }
2232            if (isEmpty(tfiles) || tfiles.length === 0) {
2233                if (!isAjaxUpload) {
2234                    self.clear();
2235                }
2236                self.showFolderError(folders);
2237                self.raise('fileselectnone');
2238                return;
2239            }
2240            self.resetErrors();
2241            len = tfiles.length;
2242            total = self.isUploadable ? self.getFileStack().length + len : len;
2243            total = self.getFileCount(total);
2244            if (self.maxFileCount > 0 && total > self.maxFileCount) {
2245                if (!self.autoReplace || len > self.maxFileCount) {
2246                    n = (self.autoReplace && len > self.maxFileCount) ? len : total;
2247                    msg = self.msgFilesTooMany.replace('{m}', self.maxFileCount).replace('{n}', n);
2248                    self.isError = throwError(msg, null, null, null);
2249                    self.$captionContainer.find('.kv-caption-icon').hide();
2250                    self.setCaption('', true);
2251                    self.$container.removeClass('file-input-new file-input-ajax-new');
2252                    return;
2253                }
2254                if (total > self.maxFileCount) {
2255                    self.resetPreviewThumbs(isAjaxUpload);
2256                }
2257            } else {
2258                if (!isAjaxUpload || flagSingle) {
2259                    self.resetPreviewThumbs(false);
2260                    if (flagSingle) {
2261                        self.filestack = [];
2262                    }
2263                } else {
2264                    if (isAjaxUpload && ctr === 0 && (!previewCache.count(self.id) || self.overwriteInitial)) {
2265                        self.resetPreviewThumbs(true);
2266                    }
2267                }
2268            }
2269            if (self.isPreviewable) {
2270                self.readFiles(tfiles);
2271            } else {
2272                self.updateFileDetails(1);
2273            }
2274            self.showFolderError(folders);
2275        },
2276        checkDimensions: function (i, chk, $img, $thumb, fname, type, params) {
2277            var self = this, msg, dim, tag = chk === 'Small' ? 'min' : 'max',
2278                limit = self[tag + 'Image' + type], $imgEl, isValid;
2279            if (isEmpty(limit) || !$img.length) {
2280                return;
2281            }
2282            $imgEl = $img[0];
2283            dim = (type === 'Width') ? $imgEl.naturalWidth || $imgEl.width : $imgEl.naturalHeight || $imgEl.height;
2284            isValid = chk === 'Small' ? dim >= limit : dim <= limit;
2285            if (isValid) {
2286                return;
2287            }
2288            msg = self['msgImage' + type + chk].replace('{name}', fname).replace('{size}', limit);
2289            self.showUploadError(msg, params);
2290            self.setThumbStatus($thumb, 'Error');
2291            self.filestack[i] = null;
2292        },
2293        validateImage: function (i, previewId, fname, ftype) {
2294            var self = this, $preview = self.$preview, params, w1, w2,
2295                $thumb = $preview.find("#" + previewId), $img = $thumb.find('img');
2296            fname = fname || 'Untitled';
2297            if (!$img.length) {
2298                return;
2299            }
2300            handler($img, 'load', function () {
2301                w1 = $thumb.width();
2302                w2 = $preview.width();
2303                if (w1 > w2) {
2304                    $img.css('width', '100%');
2305                    $thumb.css('width', '97%');
2306                }
2307                params = {ind: i, id: previewId};
2308                self.checkDimensions(i, 'Small', $img, $thumb, fname, 'Width', params);
2309                self.checkDimensions(i, 'Small', $img, $thumb, fname, 'Height', params);
2310                if (!self.resizeImage) {
2311                    self.checkDimensions(i, 'Large', $img, $thumb, fname, 'Width', params);
2312                    self.checkDimensions(i, 'Large', $img, $thumb, fname, 'Height', params);
2313                }
2314                self.raise('fileimageloaded', [previewId]);
2315                self.loadedImages.push({ind: i, img: $img, thumb: $thumb, pid: previewId, typ: ftype});
2316                self.validateAllImages();
2317                objUrl.revokeObjectURL($img.attr('src'));
2318            });
2319        },
2320        validateAllImages: function () {
2321            var self = this, i, config, $img, $thumb, pid, ind, params = {}, errFunc;
2322            if (self.loadedImages.length !== self.totalImagesCount) {
2323                return;
2324            }
2325            self.raise('fileimagesloaded');
2326            if (!self.resizeImage) {
2327                return;
2328            }
2329            errFunc = self.isUploadable ? self.showUploadError : self.showError;
2330            for (i = 0; i < self.loadedImages.length; i++) {
2331                config = self.loadedImages[i];
2332                $img = config.img;
2333                $thumb = config.thumb;
2334                pid = config.pid;
2335                ind = config.ind;
2336                params = {id: pid, 'index': ind};
2337                if (!self.getResizedImage($img[0], config.typ, pid, ind)) {
2338                    errFunc(self.msgImageResizeError, params, 'fileimageresizeerror');
2339                    self.setThumbStatus($thumb, 'Error');
2340                    self.filestack[ind] = undefined;
2341                }
2342            }
2343            self.raise('fileimagesresized');
2344        },
2345        getResizedImage: function (image, type, pid, ind) {
2346            var self = this, width = image.naturalWidth, height = image.naturalHeight, ratio = 1,
2347                maxWidth = self.maxImageWidth || width, maxHeight = self.maxImageHeight || height,
2348                isValidImage = (width && height), chkWidth, chkHeight,
2349                canvas = self.imageCanvas, context = self.imageCanvasContext;
2350            if (!isValidImage) {
2351                return false;
2352            }
2353            if (width === maxWidth && height === maxHeight) {
2354                return true;
2355            }
2356            type = type || self.resizeDefaultImageType;
2357            chkWidth = width > maxWidth;
2358            chkHeight = height > maxHeight;
2359            if (self.resizePreference === 'width') {
2360                ratio = chkWidth ? maxWidth / width : (chkHeight ? maxHeight / height : 1);
2361            } else {
2362                ratio = chkHeight ? maxHeight / height : (chkWidth ? maxWidth / width : 1);
2363            }
2364            self.resetCanvas();
2365            width *= ratio;
2366            height *= ratio;
2367            canvas.width = width;
2368            canvas.height = height;
2369            try {
2370                context.drawImage(image, 0, 0, width, height);
2371                canvas.toBlob(function (blob) {
2372                    self.raise('fileimageresized', [pid, ind]);
2373                    self.filestack[ind] = blob;
2374                }, type, self.resizeQuality);
2375                return true;
2376            }
2377            catch (err) {
2378                return false;
2379            }
2380        },
2381        initCaption: function () {
2382            var self = this, cap = self.initialCaption || '';
2383            if (self.overwriteInitial || isEmpty(cap)) {
2384                self.$caption.html('');
2385                return false;
2386            }
2387            self.setCaption(cap);
2388            return true;
2389        },
2390        setCaption: function (content, isError) {
2391            var self = this, title, out;
2392            if (isError) {
2393                title = $('<div>' + self.msgValidationError + '</div>').text();
2394                out = '<span class="' + self.msgValidationErrorClass + '">' +
2395                self.msgValidationErrorIcon + title + '</span>';
2396            } else {
2397                if (isEmpty(content) || self.$caption.length === 0) {
2398                    return;
2399                }
2400                title = $('<div>' + content + '</div>').text();
2401                out = self.getLayoutTemplate('icon') + title;
2402            }
2403            self.$caption.html(out);
2404            self.$caption.attr('title', title);
2405            self.$captionContainer.find('.file-caption-ellipsis').attr('title', title);
2406        },
2407        initBrowse: function ($container) {
2408            var self = this;
2409            self.$btnFile = $container.find('.btn-file');
2410            self.$btnFile.append(self.$element);
2411        },
2412        createContainer: function () {
2413            var self = this,
2414                $container = $(document.createElement("div"))
2415                    .attr({"class": 'file-input file-input-new'})
2416                    .html(self.renderMain());
2417            self.$element.before($container);
2418            self.initBrowse($container);
2419            return $container;
2420        },
2421        refreshContainer: function () {
2422            var self = this, $container = self.$container;
2423            $container.before(self.$element);
2424            $container.html(self.renderMain());
2425            self.initBrowse($container);
2426        },
2427        renderMain: function () {
2428            var self = this, dropCss = (self.isUploadable && self.dropZoneEnabled) ? ' file-drop-zone' : '',
2429                close = !self.showClose ? '' : self.getLayoutTemplate('close'),
2430                preview = !self.showPreview ? '' : self.getLayoutTemplate('preview')
2431                    .replace(/\{class}/g, self.previewClass)
2432                    .replace(/\{dropClass}/g, dropCss),
2433                css = self.isDisabled ? self.captionClass + ' file-caption-disabled' : self.captionClass,
2434                caption = self.captionTemplate.replace(/\{class}/g, css + ' kv-fileinput-caption');
2435            return self.mainTemplate.replace(/\{class}/g, self.mainClass)
2436                .replace(/\{preview}/g, preview)
2437                .replace(/\{close}/g, close)
2438                .replace(/\{caption}/g, caption)
2439                .replace(/\{upload}/g, self.renderButton('upload'))
2440                .replace(/\{remove}/g, self.renderButton('remove'))
2441                .replace(/\{cancel}/g, self.renderButton('cancel'))
2442                .replace(/\{browse}/g, self.renderButton('browse'));
2443        },
2444        renderButton: function (type) {
2445            var self = this, tmplt = self.getLayoutTemplate('btnDefault'), css = self[type + 'Class'],
2446                title = self[type + 'Title'], icon = self[type + 'Icon'], label = self[type + 'Label'],
2447                status = self.isDisabled ? ' disabled' : '', btnType = 'button';
2448            switch (type) {
2449                case 'remove':
2450                    if (!self.showRemove) {
2451                        return '';
2452                    }
2453                    break;
2454                case 'cancel':
2455                    if (!self.showCancel) {
2456                        return '';
2457                    }
2458                    css += ' hide';
2459                    break;
2460                case 'upload':
2461                    if (!self.showUpload) {
2462                        return '';
2463                    }
2464                    if (self.isUploadable && !self.isDisabled) {
2465                        tmplt = self.getLayoutTemplate('btnLink').replace('{href}', self.uploadUrl);
2466                    } else {
2467                        btnType = 'submit';
2468                    }
2469                    break;
2470                case 'browse':
2471                    tmplt = self.getLayoutTemplate('btnBrowse');
2472                    break;
2473                default:
2474                    return '';
2475            }
2476            css += type === 'browse' ? ' btn-file' : ' fileinput-' + type + ' fileinput-' + type + '-button';
2477            return tmplt.replace('{type}', btnType)
2478                .replace('{css}', css)
2479                .replace('{title}', title)
2480                .replace('{status}', status)
2481                .replace('{icon}', icon)
2482                .replace('{label}', label);
2483        }
2484    }
2485    ;
2486
2487//FileInput plugin definition
2488    $.fn.fileinput = function (option) {
2489        if (!hasFileAPISupport() && !isIE(9)) {
2490            return;
2491        }
2492        var args = Array.apply(null, arguments), retvals = [];
2493        args.shift();
2494        this.each(function () {
2495            var $this = $(this), data = $this.data('fileinput'), defaults,
2496                options = typeof option === 'object' && option,
2497                lang = options.language || $this.data('language') || 'en';
2498
2499            if (!data) {
2500                defaults = $.extend({}, $.fn.fileinput.defaults);
2501                if (lang !== 'en' && !isEmpty($.fn.fileinputLocales[lang])) {
2502                    defaults = $.extend(defaults, $.fn.fileinputLocales[lang]);
2503                }
2504                data = new FileInput(this, $.extend(defaults, options, $this.data()));
2505                $this.data('fileinput', data);
2506            }
2507
2508            if (typeof option === 'string') {
2509                retvals.push(data[option].apply(data, args));
2510            }
2511        });
2512        switch (retvals.length) {
2513            case 0:
2514                return this;
2515            case 1:
2516                return retvals[0];
2517            default:
2518                return retvals;
2519        }
2520    };
2521
2522    $.fn.fileinput.defaults = {
2523        language: 'en',
2524        showCaption: true,
2525        showPreview: true,
2526        showRemove: true,
2527        showUpload: true,
2528        showCancel: true,
2529        showClose: true,
2530        showUploadedThumbs: true,
2531        autoReplace: false,
2532        mainClass: '',
2533        previewClass: '',
2534        captionClass: '',
2535        mainTemplate: null,
2536        initialCaption: '',
2537        initialPreview: [],
2538        initialPreviewDelimiter: '*$$*',
2539        initialPreviewConfig: [],
2540        initialPreviewThumbTags: [],
2541        previewThumbTags: {},
2542        initialPreviewShowDelete: true,
2543        deleteUrl: '',
2544        deleteExtraData: {},
2545        overwriteInitial: true,
2546        layoutTemplates: defaultLayoutTemplates,
2547        previewTemplates: defaultPreviewTemplates,
2548        allowedPreviewTypes: defaultPreviewTypes,
2549        allowedPreviewMimeTypes: null,
2550        allowedFileTypes: null,
2551        allowedFileExtensions: null,
2552        defaultPreviewContent: null,
2553        customLayoutTags: {},
2554        customPreviewTags: {},
2555        previewSettings: defaultPreviewSettings,
2556        fileTypeSettings: defaultFileTypeSettings,
2557        previewFileIcon: '<i class="icon icon-file"></i>',
2558        previewFileIconClass: 'file-icon-4x',
2559        previewFileIconSettings: {},
2560        previewFileExtSettings: {},
2561        browseIcon: '<i class="icon icon-upload-alt"></i> &nbsp;',
2562        browseClass: 'btn btn-primary',
2563        removeIcon: '<i class="icon icon-trash"></i> ',
2564        removeClass: 'btn btn-default',
2565        cancelIcon: '<i class="icon icon-ban-circle"></i> ',
2566        cancelClass: 'btn btn-default',
2567        uploadIcon: '<i class="icon icon-upload-alt"></i> ',
2568        uploadClass: 'btn btn-default',
2569        uploadUrl: null,
2570        uploadAsync: true,
2571        uploadExtraData: {},
2572        minImageWidth: null,
2573        minImageHeight: null,
2574        maxImageWidth: null,
2575        maxImageHeight: null,
2576        resizeImage: false,
2577        resizePreference: 'width',
2578        resizeQuality: 0.92,
2579        resizeDefaultImageType: 'image/jpeg',
2580        maxFileSize: 0,
2581        minFileCount: 0,
2582        maxFileCount: 0,
2583        validateInitialCount: false,
2584        msgValidationErrorClass: 'text-danger',
2585        msgValidationErrorIcon: '<i class="icon icon-exclamation-sign"></i> ',
2586        msgErrorClass: 'file-error-message',
2587        progressThumbClass: "progress-bar progress-bar-success progress-bar-striped active",
2588        progressClass: "progress-bar progress-bar-success progress-bar-striped active",
2589        progressCompleteClass: "progress-bar progress-bar-success",
2590        previewFileType: 'image',
2591        zoomIndicator: '<i class="icon icon-zoom-in"></i>',
2592        elCaptionContainer: null,
2593        elCaptionText: null,
2594        elPreviewContainer: null,
2595        elPreviewImage: null,
2596        elPreviewStatus: null,
2597        elErrorContainer: null,
2598        errorCloseButton: '<span class="close kv-error-close">&times;</span>',
2599        slugCallback: null,
2600        dropZoneEnabled: true,
2601        dropZoneTitleClass: 'file-drop-zone-title',
2602        fileActionSettings: {},
2603        otherActionButtons: '',
2604        textEncoding: 'UTF-8',
2605        ajaxSettings: {},
2606        ajaxDeleteSettings: {},
2607        showAjaxErrorDetails: true
2608    };
2609
2610    $.fn.fileinputLocales.en = {
2611        fileSingle: 'file',
2612        filePlural: 'files',
2613        browseLabel: 'Browse &hellip;',
2614        removeLabel: 'Remove',
2615        removeTitle: 'Clear selected files',
2616        cancelLabel: 'Cancel',
2617        cancelTitle: 'Abort ongoing upload',
2618        uploadLabel: 'Upload',
2619        uploadTitle: 'Upload selected files',
2620        msgZoomTitle: 'View details',
2621        msgZoomModalHeading: 'Detailed Preview',
2622        msgSizeTooLarge: 'File "{name}" (<b>{size} KB</b>) exceeds maximum allowed upload size of <b>{maxSize} KB</b>.',
2623        msgFilesTooLess: 'You must select at least <b>{n}</b> {files} to upload.',
2624        msgFilesTooMany: 'Number of files selected for upload <b>({n})</b> exceeds maximum allowed limit of <b>{m}</b>.',
2625        msgFileNotFound: 'File "{name}" not found!',
2626        msgFileSecured: 'Security restrictions prevent reading the file "{name}".',
2627        msgFileNotReadable: 'File "{name}" is not readable.',
2628        msgFilePreviewAborted: 'File preview aborted for "{name}".',
2629        msgFilePreviewError: 'An error occurred while reading the file "{name}".',
2630        msgInvalidFileType: 'Invalid type for file "{name}". Only "{types}" files are supported.',
2631        msgInvalidFileExtension: 'Invalid extension for file "{name}". Only "{extensions}" files are supported.',
2632        msgUploadAborted: 'The file upload was aborted',
2633        msgValidationError: 'File Upload Error',
2634        msgLoading: 'Loading file {index} of {files} &hellip;',
2635        msgProgress: 'Loading file {index} of {files} - {name} - {percent}% completed.',
2636        msgSelected: '{n} {files} selected',
2637        msgFoldersNotAllowed: 'Drag & drop files only! {n} folder(s) dropped were skipped.',
2638        msgImageWidthSmall: 'Width of image file "{name}" must be at least {size} px.',
2639        msgImageHeightSmall: 'Height of image file "{name}" must be at least {size} px.',
2640        msgImageWidthLarge: 'Width of image file "{name}" cannot exceed {size} px.',
2641        msgImageHeightLarge: 'Height of image file "{name}" cannot exceed {size} px.',
2642        msgImageResizeError: 'Could not get the image dimensions to resize.',
2643        msgImageResizeException: 'Error while resizing the image.<pre>{errors}</pre>',
2644        dropZoneTitle: 'Drag & drop files here &hellip;'
2645    };
2646
2647    $.extend($.fn.fileinput.defaults, $.fn.fileinputLocales.en);
2648
2649    $.fn.fileinput.Constructor = FileInput;
2650
2651    /**
2652     * Convert automatically file inputs with class 'file'
2653     * into a bootstrap fileinput control.
2654     */
2655    $(document).ready(function () {
2656        var $input = $('input.file[type=file]');
2657        if ($input.length) {
2658            $input.fileinput();
2659        }
2660    });
2661})
2662(window.jQuery);

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.