Issues (4542)

js/fileinput.js (1 issue)

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);
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')) {
1530
                        self.setThumbStatus($thumb, 'Loading');
1531
                        addCss($thumb, 'file-uploading');
1532
                    }
1533
                    $btnUpload.attr('disabled', true);
1534
                    $btnDelete.attr('disabled', true);
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');
1553
                            $btnUpload.hide();
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');
1575
                        $btnDelete.removeAttr('disabled');
1576
                        $thumb.removeClass('file-uploading');
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');
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
                    });
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;
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,
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;
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
}));