Issues (4542)

js/fileinput.js (15 issues)

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