Proyectos de Subversion LeadersLinked - Antes de SPA

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
6056 efrain 1
/*!
2
 * bootstrap-fileinput v5.1.3
3
 * http://plugins.krajee.com/file-input
4
 *
5
 * Author: Kartik Visweswaran
6
 * Copyright: 2014 - 2020, Kartik Visweswaran, Krajee.com
7
 *
8
 * Licensed under the BSD-3-Clause
9
 * https://github.com/kartik-v/bootstrap-fileinput/blob/master/LICENSE.md
10
 */
11
(function (factory) {
12
    'use strict';
13
    if (typeof define === 'function' && define.amd) {
14
        define(['jquery'], factory);
15
    } else {
16
        if (typeof module === 'object' && module.exports) {
17
            //noinspection NpmUsedModulesInstalled
18
            module.exports = factory(require('jquery'));
19
        } else {
20
            factory(window.jQuery);
21
        }
22
    }
23
}(function ($) {
24
    'use strict';
25
 
26
    $.fn.fileinputLocales = {};
27
    $.fn.fileinputThemes = {};
28
 
29
    String.prototype.setTokens = function (replacePairs) {
30
        var str = this.toString(), key, re;
31
        for (key in replacePairs) {
32
            if (replacePairs.hasOwnProperty(key)) {
33
                re = new RegExp('\{' + key + '\}', 'g');
34
                str = str.replace(re, replacePairs[key]);
35
            }
36
        }
37
        return str;
38
    };
39
 
40
    if (!Array.prototype.flatMap) { // polyfill flatMap
41
        Array.prototype.flatMap = function (lambda) {
42
            return [].concat(this.map(lambda));
43
        };
44
    }
45
 
46
    var $h, FileInput;
47
 
48
    // fileinput helper object for all global variables and internal helper methods
49
    $h = {
50
        FRAMES: '.kv-preview-thumb',
51
        SORT_CSS: 'file-sortable',
52
        INIT_FLAG: 'init-',
53
        OBJECT_PARAMS: '<param name="controller" value="true" />\n' +
54
            '<param name="allowFullScreen" value="true" />\n' +
55
            '<param name="allowScriptAccess" value="always" />\n' +
56
            '<param name="autoPlay" value="false" />\n' +
57
            '<param name="autoStart" value="false" />\n' +
58
            '<param name="quality" value="high" />\n',
59
        DEFAULT_PREVIEW: '<div class="file-preview-other">\n' +
60
            '<span class="{previewFileIconClass}">{previewFileIcon}</span>\n' +
61
            '</div>',
62
        MODAL_ID: 'kvFileinputModal',
63
        MODAL_EVENTS: ['show', 'shown', 'hide', 'hidden', 'loaded'],
64
        logMessages: {
65
            ajaxError: '{status}: {error}. Error Details: {text}.',
66
            badDroppedFiles: 'Error scanning dropped files!',
67
            badExifParser: 'Error loading the piexif.js library. {details}',
68
            badInputType: 'The input "type" must be set to "file" for initializing the "bootstrap-fileinput" plugin.',
69
            exifWarning: 'To avoid this warning, either set "autoOrientImage" to "false" OR ensure you have loaded ' +
70
                'the "piexif.js" library correctly on your page before the "fileinput.js" script.',
71
            invalidChunkSize: 'Invalid upload chunk size: "{chunkSize}". Resumable uploads are disabled.',
72
            invalidThumb: 'Invalid thumb frame with id: "{id}".',
73
            noResumableSupport: 'The browser does not support resumable or chunk uploads.',
74
            noUploadUrl: 'The "uploadUrl" is not set. Ajax uploads and resumable uploads have been disabled.',
75
            retryStatus: 'Retrying upload for chunk # {chunk} for {filename}... retry # {retry}.',
76
            chunkQueueError: 'Could not push task to ajax pool for chunk index # {index}.',
77
            resumableMaxRetriesReached: 'Maximum resumable ajax retries ({n}) reached.',
78
            resumableRetryError: 'Could not retry the resumable request (try # {n})... aborting.',
79
            resumableAborting: 'Aborting / cancelling the resumable request.'
80
 
81
        },
82
        objUrl: window.URL || window.webkitURL,
83
        now: function () {
84
            return new Date().getTime();
85
        },
86
        round: function (num) {
87
            num = parseFloat(num);
88
            return isNaN(num) ? 0 : Math.floor(Math.round(num));
89
        },
90
        getArray: function (obj) {
91
            var i, arr = [], len = obj && obj.length || 0;
92
            for (i = 0; i < len; i++) {
93
                arr.push(obj[i]);
94
            }
95
            return arr;
96
        },
97
        getFileRelativePath: function (file) {
98
            /** @namespace file.relativePath */
99
            /** @namespace file.webkitRelativePath */
100
            return String(file.newPath || file.relativePath || file.webkitRelativePath || $h.getFileName(file) || null);
101
 
102
        },
103
        getFileId: function (file, generateFileId) {
104
            var relativePath = $h.getFileRelativePath(file);
105
            if (typeof generateFileId === 'function') {
106
                return generateFileId(file);
107
            }
108
            if (!file) {
109
                return null;
110
            }
111
            if (!relativePath) {
112
                return null;
113
            }
114
            return (file.size + '_' + encodeURIComponent(relativePath).replace(/%/g, '_'));
115
        },
116
        getFrameSelector: function (id, selector) {
117
            selector = selector || '';
118
            return '[id="' + id + '"]' + selector;
119
        },
120
        getZoomSelector: function (id, selector) {
121
            return $h.getFrameSelector('zoom-' + id, selector);
122
        },
123
        getFrameElement: function ($element, id, selector) {
124
            return $element.find($h.getFrameSelector(id, selector));
125
        },
126
        getZoomElement: function ($element, id, selector) {
127
            return $element.find($h.getZoomSelector(id, selector));
128
        },
129
        getElapsed: function (seconds) {
130
            var delta = seconds, out = '', result = {}, structure = {
131
                year: 31536000,
132
                month: 2592000,
133
                week: 604800, // uncomment row to ignore
134
                day: 86400,   // feel free to add your own row
135
                hour: 3600,
136
                minute: 60,
137
                second: 1
138
            };
139
            $h.getObjectKeys(structure).forEach(function (key) {
140
                result[key] = Math.floor(delta / structure[key]);
141
                delta -= result[key] * structure[key];
142
            });
143
            $.each(result, function (key, value) {
144
                if (value > 0) {
145
                    out += (out ? ' ' : '') + value + key.substring(0, 1);
146
                }
147
            });
148
            return out;
149
        },
150
        debounce: function (func, delay) {
151
            var inDebounce;
152
            return function () {
153
                var args = arguments, context = this;
154
                clearTimeout(inDebounce);
155
                inDebounce = setTimeout(function () {
156
                    func.apply(context, args);
157
                }, delay);
158
            };
159
        },
160
        stopEvent: function (e) {
161
            e.stopPropagation();
162
            e.preventDefault();
163
        },
164
        getFileName: function (file) {
165
            /** @namespace file.fileName */
166
            return file ? (file.fileName || file.name || '') : ''; // some confusion in different versions of Firefox
167
        },
168
        createObjectURL: function (data) {
169
            if ($h.objUrl && $h.objUrl.createObjectURL && data) {
170
                return $h.objUrl.createObjectURL(data);
171
            }
172
            return '';
173
        },
174
        revokeObjectURL: function (data) {
175
            if ($h.objUrl && $h.objUrl.revokeObjectURL && data) {
176
                $h.objUrl.revokeObjectURL(data);
177
            }
178
        },
179
        compare: function (input, str, exact) {
180
            return input !== undefined && (exact ? input === str : input.match(str));
181
        },
182
        isIE: function (ver) {
183
            var div, status;
184
            // check for IE versions < 11
185
            if (navigator.appName !== 'Microsoft Internet Explorer') {
186
                return false;
187
            }
188
            if (ver === 10) {
189
                return new RegExp('msie\\s' + ver, 'i').test(navigator.userAgent);
190
            }
191
            div = document.createElement('div');
192
            div.innerHTML = '<!--[if IE ' + ver + ']> <i></i> <![endif]-->';
193
            status = div.getElementsByTagName('i').length;
194
            document.body.appendChild(div);
195
            div.parentNode.removeChild(div);
196
            return status;
197
        },
198
        canOrientImage: function ($el) {
199
            var $img = $(document.createElement('img')).css({width: '1px', height: '1px'}).insertAfter($el),
200
                flag = $img.css('image-orientation');
201
            $img.remove();
202
            return !!flag;
203
        },
204
        canAssignFilesToInput: function () {
205
            var input = document.createElement('input');
206
            try {
207
                input.type = 'file';
208
                input.files = null;
209
                return true;
210
            } catch (err) {
211
                return false;
212
            }
213
        },
214
        getDragDropFolders: function (items) {
215
            var i, item, len = items ? items.length : 0, folders = 0;
216
            if (len > 0 && items[0].webkitGetAsEntry()) {
217
                for (i = 0; i < len; i++) {
218
                    item = items[i].webkitGetAsEntry();
219
                    if (item && item.isDirectory) {
220
                        folders++;
221
                    }
222
                }
223
            }
224
            return folders;
225
        },
226
        initModal: function ($modal) {
227
            var $body = $('body');
228
            if ($body.length) {
229
                $modal.appendTo($body);
230
            }
231
        },
232
        isFunction: function (v) {
233
            return typeof v === 'function';
234
        },
235
        isEmpty: function (value, trim) {
236
            return value === undefined || value === null || (!$h.isFunction(
237
                value) && (value.length === 0 || (trim && $.trim(value) === '')));
238
        },
239
        isArray: function (a) {
240
            return Array.isArray(a) || Object.prototype.toString.call(a) === '[object Array]';
241
        },
242
        ifSet: function (needle, haystack, def) {
243
            def = def || '';
244
            return (haystack && typeof haystack === 'object' && needle in haystack) ? haystack[needle] : def;
245
        },
246
        cleanArray: function (arr) {
247
            if (!(arr instanceof Array)) {
248
                arr = [];
249
            }
250
            return arr.filter(function (e) {
251
                return (e !== undefined && e !== null);
252
            });
253
        },
254
        spliceArray: function (arr, index, reverseOrder) {
255
            var i, j = 0, out = [], newArr;
256
            if (!(arr instanceof Array)) {
257
                return [];
258
            }
259
            newArr = $.extend(true, [], arr);
260
            if (reverseOrder) {
261
                newArr.reverse();
262
            }
263
            for (i = 0; i < newArr.length; i++) {
264
                if (i !== index) {
265
                    out[j] = newArr[i];
266
                    j++;
267
                }
268
            }
269
            if (reverseOrder) {
270
                out.reverse();
271
            }
272
            return out;
273
        },
274
        getNum: function (num, def) {
275
            def = def || 0;
276
            if (typeof num === 'number') {
277
                return num;
278
            }
279
            if (typeof num === 'string') {
280
                num = parseFloat(num);
281
            }
282
            return isNaN(num) ? def : num;
283
        },
284
        hasFileAPISupport: function () {
285
            return !!(window.File && window.FileReader);
286
        },
287
        hasDragDropSupport: function () {
288
            var div = document.createElement('div');
289
            /** @namespace div.draggable */
290
            /** @namespace div.ondragstart */
291
            /** @namespace div.ondrop */
292
            return !$h.isIE(9) &&
293
                (div.draggable !== undefined || (div.ondragstart !== undefined && div.ondrop !== undefined));
294
        },
295
        hasFileUploadSupport: function () {
296
            return $h.hasFileAPISupport() && window.FormData;
297
        },
298
        hasBlobSupport: function () {
299
            try {
300
                return !!window.Blob && Boolean(new Blob());
301
            } catch (e) {
302
                return false;
303
            }
304
        },
305
        hasArrayBufferViewSupport: function () {
306
            try {
307
                return new Blob([new Uint8Array(100)]).size === 100;
308
            } catch (e) {
309
                return false;
310
            }
311
        },
312
        hasResumableUploadSupport: function () {
313
            /** @namespace Blob.prototype.webkitSlice */
314
            /** @namespace Blob.prototype.mozSlice */
315
            return $h.hasFileUploadSupport() && $h.hasBlobSupport() && $h.hasArrayBufferViewSupport() &&
316
                (!!Blob.prototype.webkitSlice || !!Blob.prototype.mozSlice || !!Blob.prototype.slice || false);
317
        },
318
        dataURI2Blob: function (dataURI) {
319
            var BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder ||
320
                window.MSBlobBuilder, canBlob = $h.hasBlobSupport(), byteStr, arrayBuffer, intArray, i, mimeStr, bb,
321
                canProceed = (canBlob || BlobBuilder) && window.atob && window.ArrayBuffer && window.Uint8Array;
322
            if (!canProceed) {
323
                return null;
324
            }
325
            if (dataURI.split(',')[0].indexOf('base64') >= 0) {
326
                byteStr = atob(dataURI.split(',')[1]);
327
            } else {
328
                byteStr = decodeURIComponent(dataURI.split(',')[1]);
329
            }
330
            arrayBuffer = new ArrayBuffer(byteStr.length);
331
            intArray = new Uint8Array(arrayBuffer);
332
            for (i = 0; i < byteStr.length; i += 1) {
333
                intArray[i] = byteStr.charCodeAt(i);
334
            }
335
            mimeStr = dataURI.split(',')[0].split(':')[1].split(';')[0];
336
            if (canBlob) {
337
                return new Blob([$h.hasArrayBufferViewSupport() ? intArray : arrayBuffer], {type: mimeStr});
338
            }
339
            bb = new BlobBuilder();
340
            bb.append(arrayBuffer);
341
            return bb.getBlob(mimeStr);
342
        },
343
        arrayBuffer2String: function (buffer) {
344
            if (window.TextDecoder) {
345
                return new TextDecoder('utf-8').decode(buffer);
346
            }
347
            var array = Array.prototype.slice.apply(new Uint8Array(buffer)), out = '', i = 0, len, c, char2, char3;
348
            len = array.length;
349
            while (i < len) {
350
                c = array[i++];
351
                switch (c >> 4) { // jshint ignore:line
352
                    case 0:
353
                    case 1:
354
                    case 2:
355
                    case 3:
356
                    case 4:
357
                    case 5:
358
                    case 6:
359
                    case 7:
360
                        // 0xxxxxxx
361
                        out += String.fromCharCode(c);
362
                        break;
363
                    case 12:
364
                    case 13:
365
                        // 110x xxxx   10xx xxxx
366
                        char2 = array[i++];
367
                        out += String.fromCharCode(((c & 0x1F) << 6) | (char2 & 0x3F)); // jshint ignore:line
368
                        break;
369
                    case 14:
370
                        // 1110 xxxx  10xx xxxx  10xx xxxx
371
                        char2 = array[i++];
372
                        char3 = array[i++];
373
                        out += String.fromCharCode(((c & 0x0F) << 12) | // jshint ignore:line
374
                            ((char2 & 0x3F) << 6) |  // jshint ignore:line
375
                            ((char3 & 0x3F) << 0)); // jshint ignore:line
376
                        break;
377
                }
378
            }
379
            return out;
380
        },
381
        isHtml: function (str) {
382
            var a = document.createElement('div');
383
            a.innerHTML = str;
384
            for (var c = a.childNodes, i = c.length; i--;) {
385
                if (c[i].nodeType === 1) {
386
                    return true;
387
                }
388
            }
389
            return false;
390
        },
391
        isSvg: function (str) {
392
            return str.match(/^\s*<\?xml/i) && (str.match(/<!DOCTYPE svg/i) || str.match(/<svg/i));
393
        },
394
        getMimeType: function (signature, contents, type) {
395
            switch (signature) {
396
                case 'ffd8ffe0':
397
                case 'ffd8ffe1':
398
                case 'ffd8ffe2':
399
                    return 'image/jpeg';
400
                case '89504e47':
401
                    return 'image/png';
402
                case '47494638':
403
                    return 'image/gif';
404
                case '49492a00':
405
                    return 'image/tiff';
406
                case '52494646':
407
                    return 'image/webp';
408
                case '66747970':
409
                    return 'video/3gp';
410
                case '4f676753':
411
                    return 'video/ogg';
412
                case '1a45dfa3':
413
                    return 'video/mkv';
414
                case '000001ba':
415
                case '000001b3':
416
                    return 'video/mpeg';
417
                case '3026b275':
418
                    return 'video/wmv';
419
                case '25504446':
420
                    return 'application/pdf';
421
                case '25215053':
422
                    return 'application/ps';
423
                case '504b0304':
424
                case '504b0506':
425
                case '504b0508':
426
                    return 'application/zip';
427
                case '377abcaf':
428
                    return 'application/7z';
429
                case '75737461':
430
                    return 'application/tar';
431
                case '7801730d':
432
                    return 'application/dmg';
433
                default:
434
                    switch (signature.substring(0, 6)) {
435
                        case '435753':
436
                            return 'application/x-shockwave-flash';
437
                        case '494433':
438
                            return 'audio/mp3';
439
                        case '425a68':
440
                            return 'application/bzip';
441
                        default:
442
                            switch (signature.substring(0, 4)) {
443
                                case '424d':
444
                                    return 'image/bmp';
445
                                case 'fffb':
446
                                    return 'audio/mp3';
447
                                case '4d5a':
448
                                    return 'application/exe';
449
                                case '1f9d':
450
                                case '1fa0':
451
                                    return 'application/zip';
452
                                case '1f8b':
453
                                    return 'application/gzip';
454
                                default:
455
                                    return contents && !contents.match(
456
                                        /[^\u0000-\u007f]/) ? 'application/text-plain' : type;
457
                            }
458
                    }
459
            }
460
        },
461
        addCss: function ($el, css) {
462
            $el.removeClass(css).addClass(css);
463
        },
464
        getElement: function (options, param, value) {
465
            return ($h.isEmpty(options) || $h.isEmpty(options[param])) ? value : $(options[param]);
466
        },
467
        createElement: function (str, tag) {
468
            tag = tag || 'div';
469
            return $($.parseHTML('<' + tag + '>' + str + '</' + tag + '>'));
470
        },
471
        uniqId: function () {
472
            return (new Date().getTime() + Math.floor(Math.random() * Math.pow(10, 15))).toString(36);
473
        },
474
        cspBuffer: {
475
            CSP_ATTRIB: 'data-csp-01928735', // a randomly named temporary attribute to store the CSP elem id
476
            domElementsStyles: {},
477
            stash: function (htmlString) {
478
                var self = this, outerDom = $.parseHTML('<div>' + htmlString + '</div>'), $el = $(outerDom);
479
                $el.find('[style]').each(function (key, elem) {
480
                    var $elem = $(elem), styleString = $elem.attr('style'), id = $h.uniqId(), styles = {};
481
                    if (styleString && styleString.length) {
482
                        if (styleString.indexOf(';') === -1) {
483
                            styleString += ';';
484
                        }
485
                        styleString.slice(0, styleString.length - 1).split(';').map(function (str) {
486
                            str = str.split(':');
487
                            if (str[0]) {
488
                                styles[str[0]] = str[1] ? str[1] : '';
489
                            }
490
                        });
491
                        self.domElementsStyles[id] = styles;
492
                        $elem.removeAttr('style').attr(self.CSP_ATTRIB, id);
493
                    }
494
                });
495
                $el.filter('*').removeAttr('style');                   // make sure all style attr are removed
496
                var values = Object.values ? Object.values(outerDom) : Object.keys(outerDom).map(function (itm) {
497
                    return outerDom[itm];
498
                });
499
                return values.flatMap(function (elem) {
500
                    return elem.innerHTML;
501
                }).join('');
502
            },
503
            apply: function (domElement) {
504
                var self = this, $el = $(domElement);
505
                $el.find('[' + self.CSP_ATTRIB + ']').each(function (key, elem) {
506
                    var $elem = $(elem), id = $elem.attr(self.CSP_ATTRIB), styles = self.domElementsStyles[id];
507
                    if (styles) {
508
                        $elem.css(styles);
509
                    }
510
                    $elem.removeAttr(self.CSP_ATTRIB);
511
                });
512
                self.domElementsStyles = {};
513
            }
514
        },
515
        setHtml: function ($elem, htmlString) {
516
            var buf = $h.cspBuffer;
517
            $elem.html(buf.stash(htmlString));
518
            buf.apply($elem);
519
            return $elem;
520
        },
521
        htmlEncode: function (str, undefVal) {
522
            if (str === undefined) {
523
                return undefVal || null;
524
            }
525
            return str.replace(/&/g, '&amp;')
526
                .replace(/</g, '&lt;')
527
                .replace(/>/g, '&gt;')
528
                .replace(/"/g, '&quot;')
529
                .replace(/'/g, '&apos;');
530
        },
531
        replaceTags: function (str, tags) {
532
            var out = str;
533
            if (!tags) {
534
                return out;
535
            }
536
            $.each(tags, function (key, value) {
537
                if (typeof value === 'function') {
538
                    value = value();
539
                }
540
                out = out.split(key).join(value);
541
            });
542
            return out;
543
        },
544
        cleanMemory: function ($thumb) {
545
            var data = $thumb.is('img') ? $thumb.attr('src') : $thumb.find('source').attr('src');
546
            $h.revokeObjectURL(data);
547
        },
548
        findFileName: function (filePath) {
549
            var sepIndex = filePath.lastIndexOf('/');
550
            if (sepIndex === -1) {
551
                sepIndex = filePath.lastIndexOf('\\');
552
            }
553
            return filePath.split(filePath.substring(sepIndex, sepIndex + 1)).pop();
554
        },
555
        checkFullScreen: function () {
556
            return document.fullscreenElement || document.mozFullScreenElement || document.webkitFullscreenElement ||
557
                document.msFullscreenElement;
558
        },
559
        toggleFullScreen: function (maximize) {
560
            var doc = document, de = doc.documentElement, isFullScreen = $h.checkFullScreen();
561
            if (de && maximize && !isFullScreen) {
562
                if (de.requestFullscreen) {
563
                    de.requestFullscreen();
564
                } else {
565
                    if (de.msRequestFullscreen) {
566
                        de.msRequestFullscreen();
567
                    } else {
568
                        if (de.mozRequestFullScreen) {
569
                            de.mozRequestFullScreen();
570
                        } else {
571
                            if (de.webkitRequestFullscreen) {
572
                                de.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT);
573
                            }
574
                        }
575
                    }
576
                }
577
            } else {
578
                if (isFullScreen) {
579
                    if (doc.exitFullscreen) {
580
                        doc.exitFullscreen();
581
                    } else {
582
                        if (doc.msExitFullscreen) {
583
                            doc.msExitFullscreen();
584
                        } else {
585
                            if (doc.mozCancelFullScreen) {
586
                                doc.mozCancelFullScreen();
587
                            } else {
588
                                if (doc.webkitExitFullscreen) {
589
                                    doc.webkitExitFullscreen();
590
                                }
591
                            }
592
                        }
593
                    }
594
                }
595
            }
596
        },
597
        moveArray: function (arr, oldIndex, newIndex, reverseOrder) {
598
            var newArr = $.extend(true, [], arr);
599
            if (reverseOrder) {
600
                newArr.reverse();
601
            }
602
            if (newIndex >= newArr.length) {
603
                var k = newIndex - newArr.length;
604
                while ((k--) + 1) {
605
                    newArr.push(undefined);
606
                }
607
            }
608
            newArr.splice(newIndex, 0, newArr.splice(oldIndex, 1)[0]);
609
            if (reverseOrder) {
610
                newArr.reverse();
611
            }
612
            return newArr;
613
        },
614
        closeButton: function (css) {
615
            css = css ? 'close ' + css : 'close';
616
            return '<button type="button" class="' + css + '" aria-label="Close">\n' +
617
                '  <span aria-hidden="true">&times;</span>\n' +
618
                '</button>';
619
        },
620
        getRotation: function (value) {
621
            switch (value) {
622
                case 2:
623
                    return 'rotateY(180deg)';
624
                case 3:
625
                    return 'rotate(180deg)';
626
                case 4:
627
                    return 'rotate(180deg) rotateY(180deg)';
628
                case 5:
629
                    return 'rotate(270deg) rotateY(180deg)';
630
                case 6:
631
                    return 'rotate(90deg)';
632
                case 7:
633
                    return 'rotate(90deg) rotateY(180deg)';
634
                case 8:
635
                    return 'rotate(270deg)';
636
                default:
637
                    return '';
638
            }
639
        },
640
        setTransform: function (el, val) {
641
            if (!el) {
642
                return;
643
            }
644
            el.style.transform = val;
645
            el.style.webkitTransform = val;
646
            el.style['-moz-transform'] = val;
647
            el.style['-ms-transform'] = val;
648
            el.style['-o-transform'] = val;
649
        },
650
        getObjectKeys: function (obj) {
651
            var keys = [];
652
            if (obj) {
653
                $.each(obj, function (key) {
654
                    keys.push(key);
655
                });
656
            }
657
            return keys;
658
        },
659
        getObjectSize: function (obj) {
660
            return $h.getObjectKeys(obj).length;
661
        },
662
        /**
663
         * Small dependency injection for the task manager
664
         * https://gist.github.com/fearphage/4341799
665
         */
666
        whenAll: function (array) {
667
            var s = [].slice, resolveValues = arguments.length === 1 && $h.isArray(array) ? array : s.call(arguments),
668
                deferred = $.Deferred(), i, failed = 0, value, length = resolveValues.length,
669
                remaining = length, rejectContexts, rejectValues, resolveContexts, updateFunc;
670
            rejectContexts = rejectValues = resolveContexts = Array(length);
671
            updateFunc = function (index, contexts, values) {
672
                return function () {
673
                    if (values !== resolveValues) {
674
                        failed++;
675
                    }
676
                    deferred.notifyWith(contexts[index] = this, values[index] = s.call(arguments));
677
                    if (!(--remaining)) {
678
                        deferred[(!failed ? 'resolve' : 'reject') + 'With'](contexts, values);
679
                    }
680
                };
681
            };
682
            for (i = 0; i < length; i++) {
683
                if ((value = resolveValues[i]) && $.isFunction(value.promise)) {
684
                    value.promise()
685
                        .done(updateFunc(i, resolveContexts, resolveValues))
686
                        .fail(updateFunc(i, rejectContexts, rejectValues));
687
                } else {
688
                    deferred.notifyWith(this, value);
689
                    --remaining;
690
                }
691
            }
692
            if (!remaining) {
693
                deferred.resolveWith(resolveContexts, resolveValues);
694
            }
695
            return deferred.promise();
696
        }
697
    };
698
    FileInput = function (element, options) {
699
        var self = this;
700
        self.$element = $(element);
701
        self.$parent = self.$element.parent();
702
        if (!self._validate()) {
703
            return;
704
        }
705
        self.isPreviewable = $h.hasFileAPISupport();
706
        self.isIE9 = $h.isIE(9);
707
        self.isIE10 = $h.isIE(10);
708
        if (self.isPreviewable || self.isIE9) {
709
            self._init(options);
710
            self._listen();
711
        }
712
        self.$element.removeClass('file-loading');
713
    };
714
 
715
    FileInput.prototype = {
716
        constructor: FileInput,
717
        _cleanup: function () {
718
            var self = this;
719
            self.reader = null;
720
            self.clearFileStack();
721
            self.fileBatchCompleted = true;
722
            self.isError = false;
723
            self.isDuplicateError = false;
724
            self.isPersistentError = false;
725
            self.cancelling = false;
726
            self.paused = false;
727
            self.lastProgress = 0;
728
            self._initAjax();
729
        },
730
        _isAborted: function () {
731
            var self = this;
732
            return self.cancelling || self.paused;
733
        },
734
        _initAjax: function () {
735
            var self = this, tm = self.taskManager = {
736
                pool: {},
737
                addPool: function (id) {
738
                    return (tm.pool[id] = new tm.TasksPool(id));
739
                },
740
                getPool: function (id) {
741
                    return tm.pool[id];
742
                },
743
                addTask: function (id, logic) { // add standalone task directly from task manager
744
                    return new tm.Task(id, logic);
745
                },
746
                TasksPool: function (id) {
747
                    var tp = this;
748
                    tp.id = id;
749
                    tp.cancelled = false;
750
                    tp.cancelledDeferrer = $.Deferred();
751
                    tp.tasks = {};
752
                    tp.addTask = function (id, logic) {
753
                        return (tp.tasks[id] = new tm.Task(id, logic));
754
                    };
755
                    tp.size = function () {
756
                        return $h.getObjectSize(tp.tasks);
757
                    };
758
                    tp.run = function (maxThreads) {
759
                        var i = 0, failed = false, task, tasksList = $h.getObjectKeys(tp.tasks).map(function (key) {
760
                            return tp.tasks[key];
761
                        }), tasksDone = [], deferred = $.Deferred(), enqueue, callback;
762
 
763
                        if (tp.cancelled) {
764
                            tp.cancelledDeferrer.resolve();
765
                            return deferred.reject();
766
                        }
767
                        // if run all at once
768
                        if (!maxThreads) {
769
                            var tasksDeferredList = $h.getObjectKeys(tp.tasks).map(function (key) {
770
                                return tp.tasks[key].deferred;
771
                            });
772
                            // when all are done
773
                            $h.whenAll(tasksDeferredList).done(function () {
774
                                var argv = $h.getArray(arguments);
775
                                if (!tp.cancelled) {
776
                                    deferred.resolve.apply(null, argv);
777
                                    tp.cancelledDeferrer.reject();
778
                                } else {
779
                                    deferred.reject.apply(null, argv);
780
                                    tp.cancelledDeferrer.resolve();
781
                                }
782
                            }).fail(function () {
783
                                var argv = $h.getArray(arguments);
784
                                deferred.reject.apply(null, argv);
785
                                if (!tp.cancelled) {
786
                                    tp.cancelledDeferrer.reject();
787
                                } else {
788
                                    tp.cancelledDeferrer.resolve();
789
                                }
790
                            });
791
                            // run all tasks
792
                            $.each(tp.tasks, function (id) {
793
                                task = tp.tasks[id];
794
                                task.run();
795
                            });
796
                            return deferred;
797
                        }
798
                        enqueue = function (task) {
799
                            $.when(task.deferred)
800
                                .fail(function () {
801
                                    failed = true;
802
                                    callback.apply(null, arguments);
803
                                })
804
                                .always(callback);
805
                        };
806
                        callback = function () {
807
                            var argv = $h.getArray(arguments);
808
                            // notify a task just ended
809
                            deferred.notify(argv);
810
                            tasksDone.push(argv);
811
                            if (tp.cancelled) {
812
                                deferred.reject.apply(null, tasksDone);
813
                                tp.cancelledDeferrer.resolve();
814
                                return;
815
                            }
816
                            if (tasksDone.length === tp.size()) {
817
                                if (failed) {
818
                                    deferred.reject.apply(null, tasksDone);
819
                                } else {
820
                                    deferred.resolve.apply(null, tasksDone);
821
                                }
822
                            }
823
                            // if there are any tasks remaining
824
                            if (tasksList.length) {
825
                                task = tasksList.shift();
826
                                enqueue(task);
827
                                task.run();
828
                            }
829
                        };
830
                        // run the first "maxThreads" tasks
831
                        while (tasksList.length && i++ < maxThreads) {
832
                            task = tasksList.shift();
833
                            enqueue(task);
834
                            task.run();
835
                        }
836
                        return deferred;
837
                    };
838
                    tp.cancel = function () {
839
                        tp.cancelled = true;
840
                        return tp.cancelledDeferrer;
841
                    };
842
                },
843
                Task: function (id, logic) {
844
                    var tk = this;
845
                    tk.id = id;
846
                    tk.deferred = $.Deferred();
847
                    tk.logic = logic;
848
                    tk.context = null;
849
                    tk.run = function () {
850
                        var argv = $h.getArray(arguments);
851
                        argv.unshift(tk.deferred);     // add deferrer as first argument
852
                        logic.apply(tk.context, argv); // run task
853
                        return tk.deferred;            // return deferrer
854
                    };
855
                    tk.runWithContext = function (context) {
856
                        tk.context = context;
857
                        return tk.run();
858
                    };
859
                }
860
            };
861
            self.ajaxQueue = [];
862
            self.ajaxRequests = [];
863
            self.ajaxAborted = false;
864
        },
865
        _init: function (options, refreshMode) {
866
            var self = this, f, $el = self.$element, $cont, t, tmp;
867
            self.options = options;
868
            self.canOrientImage = $h.canOrientImage($el);
869
            $.each(options, function (key, value) {
870
                switch (key) {
871
                    case 'minFileCount':
872
                    case 'maxFileCount':
873
                    case 'maxTotalFileCount':
874
                    case 'minFileSize':
875
                    case 'maxFileSize':
876
                    case 'maxFilePreviewSize':
877
                    case 'resizeImageQuality':
878
                    case 'resizeIfSizeMoreThan':
879
                    case 'progressUploadThreshold':
880
                    case 'initialPreviewCount':
881
                    case 'zoomModalHeight':
882
                    case 'minImageHeight':
883
                    case 'maxImageHeight':
884
                    case 'minImageWidth':
885
                    case 'maxImageWidth':
886
                        self[key] = $h.getNum(value);
887
                        break;
888
                    default:
889
                        self[key] = value;
890
                        break;
891
                }
892
            });
893
            if (self.maxTotalFileCount > 0 && self.maxTotalFileCount < self.maxFileCount) {
894
                self.maxTotalFileCount = self.maxFileCount;
895
            }
896
            if (self.rtl) { // swap buttons for rtl
897
                tmp = self.previewZoomButtonIcons.prev;
898
                self.previewZoomButtonIcons.prev = self.previewZoomButtonIcons.next;
899
                self.previewZoomButtonIcons.next = tmp;
900
            }
901
            // validate chunk threads to not exceed maxAjaxThreads
902
            if (!isNaN(self.maxAjaxThreads) && self.maxAjaxThreads < self.resumableUploadOptions.maxThreads) {
903
                self.resumableUploadOptions.maxThreads = self.maxAjaxThreads;
904
            }
905
            self._initFileManager();
906
            if (typeof self.autoOrientImage === 'function') {
907
                self.autoOrientImage = self.autoOrientImage();
908
            }
909
            if (typeof self.autoOrientImageInitial === 'function') {
910
                self.autoOrientImageInitial = self.autoOrientImageInitial();
911
            }
912
            if (!refreshMode) {
913
                self._cleanup();
914
            }
915
            self.duplicateErrors = [];
916
            self.$form = $el.closest('form');
917
            self._initTemplateDefaults();
918
            self.uploadFileAttr = !$h.isEmpty($el.attr('name')) ? $el.attr('name') : 'file_data';
919
            t = self._getLayoutTemplate('progress');
920
            self.progressTemplate = t.replace('{class}', self.progressClass);
921
            self.progressInfoTemplate = t.replace('{class}', self.progressInfoClass);
922
            self.progressPauseTemplate = t.replace('{class}', self.progressPauseClass);
923
            self.progressCompleteTemplate = t.replace('{class}', self.progressCompleteClass);
924
            self.progressErrorTemplate = t.replace('{class}', self.progressErrorClass);
925
            self.isDisabled = $el.attr('disabled') || $el.attr('readonly');
926
            if (self.isDisabled) {
927
                $el.attr('disabled', true);
928
            }
929
            self.isClickable = self.browseOnZoneClick && self.showPreview &&
930
                (self.dropZoneEnabled || !$h.isEmpty(self.defaultPreviewContent));
931
            self.isAjaxUpload = $h.hasFileUploadSupport() && !$h.isEmpty(self.uploadUrl);
932
            self.dropZoneEnabled = $h.hasDragDropSupport() && self.dropZoneEnabled;
933
            if (!self.isAjaxUpload) {
934
                self.dropZoneEnabled = self.dropZoneEnabled && $h.canAssignFilesToInput();
935
            }
936
            self.slug = typeof options.slugCallback === 'function' ? options.slugCallback : self._slugDefault;
937
            self.mainTemplate = self.showCaption ? self._getLayoutTemplate('main1') : self._getLayoutTemplate('main2');
938
            self.captionTemplate = self._getLayoutTemplate('caption');
939
            self.previewGenericTemplate = self._getPreviewTemplate('generic');
940
            if (!self.imageCanvas && self.resizeImage && (self.maxImageWidth || self.maxImageHeight)) {
941
                self.imageCanvas = document.createElement('canvas');
942
                self.imageCanvasContext = self.imageCanvas.getContext('2d');
943
            }
944
            if ($h.isEmpty($el.attr('id'))) {
945
                $el.attr('id', $h.uniqId());
946
            }
947
            self.namespace = '.fileinput_' + $el.attr('id').replace(/-/g, '_');
948
            if (self.$container === undefined) {
949
                self.$container = self._createContainer();
950
            } else {
951
                self._refreshContainer();
952
            }
953
            $cont = self.$container;
954
            self.$dropZone = $cont.find('.file-drop-zone');
955
            self.$progress = $cont.find('.kv-upload-progress');
956
            self.$btnUpload = $cont.find('.fileinput-upload');
957
            self.$captionContainer = $h.getElement(options, 'elCaptionContainer', $cont.find('.file-caption'));
958
            self.$caption = $h.getElement(options, 'elCaptionText', $cont.find('.file-caption-name'));
959
            if (!$h.isEmpty(self.msgPlaceholder)) {
960
                f = $el.attr('multiple') ? self.filePlural : self.fileSingle;
961
                self.$caption.attr('placeholder', self.msgPlaceholder.replace('{files}', f));
962
            }
963
            self.$captionIcon = self.$captionContainer.find('.file-caption-icon');
964
            self.$previewContainer = $h.getElement(options, 'elPreviewContainer', $cont.find('.file-preview'));
965
            self.$preview = $h.getElement(options, 'elPreviewImage', $cont.find('.file-preview-thumbnails'));
966
            self.$previewStatus = $h.getElement(options, 'elPreviewStatus', $cont.find('.file-preview-status'));
967
            self.$errorContainer = $h.getElement(options, 'elErrorContainer',
968
                self.$previewContainer.find('.kv-fileinput-error'));
969
            self._validateDisabled();
970
            if (!$h.isEmpty(self.msgErrorClass)) {
971
                $h.addCss(self.$errorContainer, self.msgErrorClass);
972
            }
973
            if (!refreshMode) {
974
                self._resetErrors();
975
                self.$errorContainer.hide();
976
                self.previewInitId = 'thumb-' + $el.attr('id');
977
                self._initPreviewCache();
978
                self._initPreview(true);
979
                self._initPreviewActions();
980
                if (self.$parent.hasClass('file-loading')) {
981
                    self.$container.insertBefore(self.$parent);
982
                    self.$parent.remove();
983
                }
984
            } else {
985
                if (!self._errorsExist()) {
986
                    self.$errorContainer.hide();
987
                }
988
            }
989
            self._setFileDropZoneTitle();
990
            if ($el.attr('disabled')) {
991
                self.disable();
992
            }
993
            self._initZoom();
994
            if (self.hideThumbnailContent) {
995
                $h.addCss(self.$preview, 'hide-content');
996
            }
997
        },
998
        _initFileManager: function () {
999
            var self = this;
1000
            self.uploadStartTime = $h.now();
1001
            self.fileManager = {
1002
                stack: {},
1003
                filesProcessed: [],
1004
                errors: [],
1005
                loadedImages: {},
1006
                totalImages: 0,
1007
                totalFiles: null,
1008
                totalSize: null,
1009
                uploadedSize: 0,
1010
                stats: {},
1011
                initStats: function (id) {
1012
                    var data = {started: $h.now()};
1013
                    if (id) {
1014
                        self.fileManager.stats[id] = data;
1015
                    } else {
1016
                        self.fileManager.stats = data;
1017
                    }
1018
                },
1019
                getUploadStats: function (id, loaded, total) {
1020
                    var fm = self.fileManager,
1021
                        started = id ? fm.stats[id] && fm.stats[id].started || $h.now() : self.uploadStartTime;
1022
                    var elapsed = ($h.now() - started) / 1000,
1023
                        speeds = ['B/s', 'KB/s', 'MB/s', 'GB/s', 'TB/s', 'PB/s', 'EB/s', 'ZB/s', 'YB/s'],
1024
                        bps = elapsed ? loaded / elapsed : 0, bitrate = self._getSize(bps, speeds),
1025
                        pendingBytes = total - loaded,
1026
                        out = {
1027
                            fileId: id,
1028
                            started: started,
1029
                            elapsed: elapsed,
1030
                            loaded: loaded,
1031
                            total: total,
1032
                            bps: bps,
1033
                            bitrate: bitrate,
1034
                            pendingBytes: pendingBytes
1035
                        };
1036
                    if (id) {
1037
                        fm.stats[id] = out;
1038
                    } else {
1039
                        fm.stats = out;
1040
                    }
1041
                    return out;
1042
                },
1043
                exists: function (id) {
1044
                    return $.inArray(id, self.fileManager.getIdList()) !== -1;
1045
                },
1046
                count: function () {
1047
                    return self.fileManager.getIdList().length;
1048
                },
1049
                total: function () {
1050
                    var fm = self.fileManager;
1051
                    if (!fm.totalFiles) {
1052
                        fm.totalFiles = fm.count();
1053
                    }
1054
                    return fm.totalFiles;
1055
                },
1056
                getTotalSize: function () {
1057
                    var fm = self.fileManager;
1058
                    if (fm.totalSize) {
1059
                        return fm.totalSize;
1060
                    }
1061
                    fm.totalSize = 0;
1062
                    $.each(self.fileManager.stack, function (id, f) {
1063
                        var size = parseFloat(f.size);
1064
                        fm.totalSize += isNaN(size) ? 0 : size;
1065
                    });
1066
                    return fm.totalSize;
1067
                },
1068
                add: function (file, id) {
1069
                    if (!id) {
1070
                        id = self.fileManager.getId(file);
1071
                    }
1072
                    if (!id) {
1073
                        return;
1074
                    }
1075
                    self.fileManager.stack[id] = {
1076
                        file: file,
1077
                        name: $h.getFileName(file),
1078
                        relativePath: $h.getFileRelativePath(file),
1079
                        size: file.size,
1080
                        nameFmt: self._getFileName(file, ''),
1081
                        sizeFmt: self._getSize(file.size)
1082
                    };
1083
                },
1084
                remove: function ($thumb) {
1085
                    var id = $thumb.attr('data-fileid');
1086
                    if (id) {
1087
                        self.fileManager.removeFile(id);
1088
                    }
1089
                },
1090
                removeFile: function (id) {
1091
                    delete self.fileManager.stack[id];
1092
                    delete self.fileManager.loadedImages[id];
1093
                },
1094
                move: function (idFrom, idTo) {
1095
                    var result = {}, stack = self.fileManager.stack;
1096
                    if (!idFrom && !idTo || idFrom === idTo) {
1097
                        return;
1098
                    }
1099
                    $.each(stack, function (k, v) {
1100
                        if (k !== idFrom) {
1101
                            result[k] = v;
1102
                        }
1103
                        if (k === idTo) {
1104
                            result[idFrom] = stack[idFrom];
1105
                        }
1106
                    });
1107
                    self.fileManager.stack = result;
1108
                },
1109
                list: function () {
1110
                    var files = [];
1111
                    $.each(self.fileManager.stack, function (k, v) {
1112
                        if (v && v.file) {
1113
                            files.push(v.file);
1114
                        }
1115
                    });
1116
                    return files;
1117
                },
1118
                isPending: function (id) {
1119
                    return $.inArray(id, self.fileManager.filesProcessed) === -1 && self.fileManager.exists(id);
1120
                },
1121
                isProcessed: function () {
1122
                    var filesProcessed = true, fm = self.fileManager;
1123
                    $.each(fm.stack, function (id) {
1124
                        if (fm.isPending(id)) {
1125
                            filesProcessed = false;
1126
                        }
1127
                    });
1128
                    return filesProcessed;
1129
                },
1130
                clear: function () {
1131
                    var fm = self.fileManager;
1132
                    self.isDuplicateError = false;
1133
                    self.isPersistentError = false;
1134
                    fm.totalFiles = null;
1135
                    fm.totalSize = null;
1136
                    fm.uploadedSize = 0;
1137
                    fm.stack = {};
1138
                    fm.errors = [];
1139
                    fm.filesProcessed = [];
1140
                    fm.stats = {};
1141
                    fm.clearImages();
1142
                },
1143
                clearImages: function () {
1144
                    self.fileManager.loadedImages = {};
1145
                    self.fileManager.totalImages = 0;
1146
                },
1147
                addImage: function (id, config) {
1148
                    self.fileManager.loadedImages[id] = config;
1149
                },
1150
                removeImage: function (id) {
1151
                    delete self.fileManager.loadedImages[id];
1152
                },
1153
                getImageIdList: function () {
1154
                    return $h.getObjectKeys(self.fileManager.loadedImages);
1155
                },
1156
                getImageCount: function () {
1157
                    return self.fileManager.getImageIdList().length;
1158
                },
1159
                getId: function (file) {
1160
                    return self._getFileId(file);
1161
                },
1162
                getIndex: function (id) {
1163
                    return self.fileManager.getIdList().indexOf(id);
1164
                },
1165
                getThumb: function (id) {
1166
                    var $thumb = null;
1167
                    self._getThumbs().each(function () {
1168
                        var $t = $(this);
1169
                        if ($t.attr('data-fileid') === id) {
1170
                            $thumb = $t;
1171
                        }
1172
                    });
1173
                    return $thumb;
1174
                },
1175
                getThumbIndex: function ($thumb) {
1176
                    var id = $thumb.attr('data-fileid');
1177
                    return self.fileManager.getIndex(id);
1178
                },
1179
                getIdList: function () {
1180
                    return $h.getObjectKeys(self.fileManager.stack);
1181
                },
1182
                getFile: function (id) {
1183
                    return self.fileManager.stack[id] || null;
1184
                },
1185
                getFileName: function (id, fmt) {
1186
                    var file = self.fileManager.getFile(id);
1187
                    if (!file) {
1188
                        return '';
1189
                    }
1190
                    return fmt ? (file.nameFmt || '') : file.name || '';
1191
                },
1192
                getFirstFile: function () {
1193
                    var ids = self.fileManager.getIdList(), id = ids && ids.length ? ids[0] : null;
1194
                    return self.fileManager.getFile(id);
1195
                },
1196
                setFile: function (id, file) {
1197
                    if (self.fileManager.getFile(id)) {
1198
                        self.fileManager.stack[id].file = file;
1199
                    } else {
1200
                        self.fileManager.add(file, id);
1201
                    }
1202
                },
1203
                setProcessed: function (id) {
1204
                    self.fileManager.filesProcessed.push(id);
1205
                },
1206
                getProgress: function () {
1207
                    var total = self.fileManager.total(), filesProcessed = self.fileManager.filesProcessed.length;
1208
                    if (!total) {
1209
                        return 0;
1210
                    }
1211
                    return Math.ceil(filesProcessed / total * 100);
1212
 
1213
                },
1214
                setProgress: function (id, pct) {
1215
                    var f = self.fileManager.getFile(id);
1216
                    if (!isNaN(pct) && f) {
1217
                        f.progress = pct;
1218
                    }
1219
                }
1220
            };
1221
        },
1222
        _setUploadData: function (fd, config) {
1223
            var self = this;
1224
            $.each(config, function (key, value) {
1225
                var param = self.uploadParamNames[key] || key;
1226
                if ($h.isArray(value)) {
1227
                    fd.append(param, value[0], value[1]);
1228
                } else {
1229
                    fd.append(param, value);
1230
                }
1231
            });
1232
        },
1233
        _initResumableUpload: function () {
1234
            var self = this, opts = self.resumableUploadOptions, logs = $h.logMessages, rm, fm = self.fileManager;
1235
            if (!self.enableResumableUpload) {
1236
                return;
1237
            }
1238
            if (opts.fallback !== false && typeof opts.fallback !== 'function') {
1239
                opts.fallback = function (s) {
1240
                    s._log(logs.noResumableSupport);
1241
                    s.enableResumableUpload = false;
1242
                };
1243
            }
1244
            if (!$h.hasResumableUploadSupport() && opts.fallback !== false) {
1245
                opts.fallback(self);
1246
                return;
1247
            }
1248
            if (!self.uploadUrl && self.enableResumableUpload) {
1249
                self._log(logs.noUploadUrl);
1250
                self.enableResumableUpload = false;
1251
                return;
1252
 
1253
            }
1254
            opts.chunkSize = parseFloat(opts.chunkSize);
1255
            if (opts.chunkSize <= 0 || isNaN(opts.chunkSize)) {
1256
                self._log(logs.invalidChunkSize, {chunkSize: opts.chunkSize});
1257
                self.enableResumableUpload = false;
1258
                return;
1259
            }
1260
            rm = self.resumableManager = {
1261
                init: function (id, f, index) {
1262
                    rm.logs = [];
1263
                    rm.stack = [];
1264
                    rm.error = '';
1265
                    rm.id = id;
1266
                    rm.file = f.file;
1267
                    rm.fileName = f.name;
1268
                    rm.fileIndex = index;
1269
                    rm.completed = false;
1270
                    rm.lastProgress = 0;
1271
                    if (self.showPreview) {
1272
                        rm.$thumb = fm.getThumb(id) || null;
1273
                        rm.$progress = rm.$btnDelete = null;
1274
                        if (rm.$thumb && rm.$thumb.length) {
1275
                            rm.$progress = rm.$thumb.find('.file-thumb-progress');
1276
                            rm.$btnDelete = rm.$thumb.find('.kv-file-remove');
1277
                        }
1278
                    }
1279
                    rm.chunkSize = opts.chunkSize * 1024;
1280
                    rm.chunkCount = rm.getTotalChunks();
1281
                },
1282
                setAjaxError: function (jqXHR, textStatus, errorThrown, isTest) {
1283
                    if (jqXHR.responseJSON && jqXHR.responseJSON.error) {
1284
                        errorThrown = jqXHR.responseJSON.error.toString();
1285
                    }
1286
                    if (!isTest) {
1287
                        rm.error = errorThrown;
1288
                    }
1289
                    if (opts.showErrorLog) {
1290
                        self._log(logs.ajaxError, {
1291
                            status: jqXHR.status,
1292
                            error: errorThrown,
1293
                            text: jqXHR.responseText || ''
1294
                        });
1295
                    }
1296
                },
1297
                reset: function () {
1298
                    rm.stack = [];
1299
                    rm.chunksProcessed = {};
1300
                },
1301
                setProcessed: function (status) {
1302
                    var id = rm.id, msg, $thumb = rm.$thumb, $prog = rm.$progress, hasThumb = $thumb && $thumb.length,
1303
                        params = {id: hasThumb ? $thumb.attr('id') : '', index: fm.getIndex(id), fileId: id};
1304
                    rm.completed = true;
1305
                    rm.lastProgress = 0;
1306
                    if (hasThumb) {
1307
                        $thumb.removeClass('file-uploading');
1308
                    }
1309
                    if (status === 'success') {
1310
                        fm.uploadedSize += rm.file.size;
1311
                        if (self.showPreview) {
1312
                            self._setProgress(101, $prog);
1313
                            self._setThumbStatus($thumb, 'Success');
1314
                            self._initUploadSuccess(rm.chunksProcessed[id].data, $thumb);
1315
                        }
1316
                        fm.removeFile(id);
1317
                        delete rm.chunksProcessed[id];
1318
                        self._raise('fileuploaded', [params.id, params.index, params.fileId]);
1319
                        if (fm.isProcessed()) {
1320
                            self._setProgress(101);
1321
                        }
1322
                    } else {
1323
                        if (status !== 'cancel') {
1324
                            if (self.showPreview) {
1325
                                self._setThumbStatus($thumb, 'Error');
1326
                                self._setPreviewError($thumb, true);
1327
                                self._setProgress(101, $prog, self.msgProgressError);
1328
                                self._setProgress(101, self.$progress, self.msgProgressError);
1329
                                self.cancelling = true;
1330
                            }
1331
                            if (!self.$errorContainer.find('li[data-file-id="' + params.fileId + '"]').length) {
1332
                                msg = self.msgResumableUploadRetriesExceeded.setTokens({
1333
                                    file: rm.fileName,
1334
                                    max: opts.maxRetries,
1335
                                    error: rm.error
1336
                                });
1337
                                self._showFileError(msg, params);
1338
                            }
1339
                        }
1340
                    }
1341
                    if (fm.isProcessed()) {
1342
                        rm.reset();
1343
                    }
1344
                },
1345
                check: function () {
1346
                    var status = true;
1347
                    $.each(rm.logs, function (index, value) {
1348
                        if (!value) {
1349
                            status = false;
1350
                            return false;
1351
                        }
1352
                    });
1353
                },
1354
                processedResumables: function () {
1355
                    var logs = rm.logs, i, count = 0;
1356
                    if (!logs || !logs.length) {
1357
                        return 0;
1358
                    }
1359
                    for (i = 0; i < logs.length; i++) {
1360
                        if (logs[i] === true) {
1361
                            count++;
1362
                        }
1363
                    }
1364
                    return count;
1365
                },
1366
                getUploadedSize: function () {
1367
                    var size = rm.processedResumables() * rm.chunkSize;
1368
                    return size > rm.file.size ? rm.file.size : size;
1369
                },
1370
                getTotalChunks: function () {
1371
                    var chunkSize = parseFloat(rm.chunkSize);
1372
                    if (!isNaN(chunkSize) && chunkSize > 0) {
1373
                        return Math.ceil(rm.file.size / chunkSize);
1374
                    }
1375
                    return 0;
1376
                },
1377
                getProgress: function () {
1378
                    var chunksProcessed = rm.processedResumables(), total = rm.chunkCount;
1379
                    if (total === 0) {
1380
                        return 0;
1381
                    }
1382
                    return Math.ceil(chunksProcessed / total * 100);
1383
                },
1384
                checkAborted: function (intervalId) {
1385
                    if (self._isAborted()) {
1386
                        clearInterval(intervalId);
1387
                        self.unlock();
1388
                    }
1389
                },
1390
                upload: function () {
1391
                    var ids = fm.getIdList(), flag = 'new', intervalId;
1392
                    intervalId = setInterval(function () {
1393
                        var id;
1394
                        rm.checkAborted(intervalId);
1395
                        if (flag === 'new') {
1396
                            self.lock();
1397
                            flag = 'processing';
1398
                            id = ids.shift();
1399
                            fm.initStats(id);
1400
                            if (fm.stack[id]) {
1401
                                rm.init(id, fm.stack[id], fm.getIndex(id));
1402
                                rm.processUpload();
1403
                            }
1404
                        }
1405
                        if (!fm.isPending(id) && rm.completed) {
1406
                            flag = 'new';
1407
                        }
1408
                        if (fm.isProcessed()) {
1409
                            var $initThumbs = self.$preview.find('.file-preview-initial');
1410
                            if ($initThumbs.length) {
1411
                                $h.addCss($initThumbs, $h.SORT_CSS);
1412
                                self._initSortable();
1413
                            }
1414
                            clearInterval(intervalId);
1415
                            self._clearFileInput();
1416
                            self.unlock();
1417
                            setTimeout(function () {
1418
                                var data = self.previewCache.data;
1419
                                if (data) {
1420
                                    self.initialPreview = data.content;
1421
                                    self.initialPreviewConfig = data.config;
1422
                                    self.initialPreviewThumbTags = data.tags;
1423
                                }
1424
                                self._raise('filebatchuploadcomplete', [
1425
                                    self.initialPreview,
1426
                                    self.initialPreviewConfig,
1427
                                    self.initialPreviewThumbTags,
1428
                                    self._getExtraData()
1429
                                ]);
1430
                            }, self.processDelay);
1431
                        }
1432
                    }, self.processDelay);
1433
                },
1434
                uploadResumable: function () {
1435
                    var i, pool, tm = self.taskManager, total = rm.chunkCount;
1436
                    pool = tm.addPool(rm.id);
1437
                    for (i = 0; i < total; i++) {
1438
                        rm.logs[i] = !!(rm.chunksProcessed[rm.id] && rm.chunksProcessed[rm.id][i]);
1439
                        if (!rm.logs[i]) {
1440
                            rm.pushAjax(i, 0);
1441
                        }
1442
                    }
1443
                    pool.run(opts.maxThreads)
1444
                        .done(function () {
1445
                            rm.setProcessed('success');
1446
                        })
1447
                        .fail(function () {
1448
                            rm.setProcessed(pool.cancelled ? 'cancel' : 'error');
1449
                        });
1450
                },
1451
                processUpload: function () {
1452
                    var fd, f, id = rm.id, fnBefore, fnSuccess, fnError, fnComplete, outData;
1453
                    if (!opts.testUrl) {
1454
                        rm.uploadResumable();
1455
                        return;
1456
                    }
1457
                    fd = new FormData();
1458
                    f = fm.stack[id];
1459
                    self._setUploadData(fd, {
1460
                        fileId: id,
1461
                        fileName: f.fileName,
1462
                        fileSize: f.size,
1463
                        fileRelativePath: f.relativePath,
1464
                        chunkSize: rm.chunkSize,
1465
                        chunkCount: rm.chunkCount
1466
                    });
1467
                    fnBefore = function (jqXHR) {
1468
                        outData = self._getOutData(fd, jqXHR);
1469
                        self._raise('filetestbeforesend', [id, fm, rm, outData]);
1470
                    };
1471
                    fnSuccess = function (data, textStatus, jqXHR) {
1472
                        outData = self._getOutData(fd, jqXHR, data);
1473
                        var pNames = self.uploadParamNames, chunksUploaded = pNames.chunksUploaded || 'chunksUploaded',
1474
                            params = [id, fm, rm, outData];
1475
                        if (!data[chunksUploaded] || !$h.isArray(data[chunksUploaded])) {
1476
                            self._raise('filetesterror', params);
1477
                        } else {
1478
                            if (!rm.chunksProcessed[id]) {
1479
                                rm.chunksProcessed[id] = {};
1480
                            }
1481
                            $.each(data[chunksUploaded], function (key, index) {
1482
                                rm.logs[index] = true;
1483
                                rm.chunksProcessed[id][index] = true;
1484
                            });
1485
                            rm.chunksProcessed[id].data = data;
1486
                            self._raise('filetestsuccess', params);
1487
                        }
1488
                        rm.uploadResumable();
1489
                    };
1490
                    fnError = function (jqXHR, textStatus, errorThrown) {
1491
                        outData = self._getOutData(fd, jqXHR);
1492
                        self._raise('filetestajaxerror', [id, fm, rm, outData]);
1493
                        rm.setAjaxError(jqXHR, textStatus, errorThrown, true);
1494
                        rm.uploadResumable();
1495
                    };
1496
                    fnComplete = function () {
1497
                        self._raise('filetestcomplete', [id, fm, rm, self._getOutData(fd)]);
1498
                    };
1499
                    self._ajaxSubmit(fnBefore, fnSuccess, fnComplete, fnError, fd, id, rm.fileIndex, opts.testUrl);
1500
                },
1501
                pushAjax: function (index, retry) {
1502
                    var tm = self.taskManager, pool = tm.getPool(rm.id);
1503
                    pool.addTask(pool.size() + 1, function (deferrer) {
1504
                        // use fifo chunk stack
1505
                        var arr = rm.stack.shift(), index;
1506
                        index = arr[0];
1507
                        if (!rm.chunksProcessed[rm.id] || !rm.chunksProcessed[rm.id][index]) {
1508
                            rm.sendAjax(index, arr[1], deferrer);
1509
                        } else {
1510
                            self._log(logs.chunkQueueError, {index: index});
1511
                        }
1512
                    });
1513
                    rm.stack.push([index, retry]);
1514
                },
1515
                sendAjax: function (index, retry, deferrer) {
1516
                    var f, chunkSize = rm.chunkSize, id = rm.id, file = rm.file, $thumb = rm.$thumb,
1517
                        msgs = $h.logMessages, $btnDelete = rm.$btnDelete, logError = function (msg, tokens) {
1518
                            if (tokens) {
1519
                                msg = msg.setTokens(tokens);
1520
                            }
1521
                            msg = 'Error processing resumable ajax request. ' + msg;
1522
                            self._log(msg);
1523
                            deferrer.reject(msg);
1524
                        };
1525
                    if (rm.chunksProcessed[id] && rm.chunksProcessed[id][index]) {
1526
                        return;
1527
                    }
1528
                    if (retry > opts.maxRetries) {
1529
                        logError(msgs.resumableMaxRetriesReached, {n: opts.maxRetries});
1530
                        rm.setProcessed('error');
1531
                        return;
1532
                    }
1533
                    var fd, outData, fnBefore, fnSuccess, fnError, fnComplete, slice = file.slice ? 'slice' :
1534
                        (file.mozSlice ? 'mozSlice' : (file.webkitSlice ? 'webkitSlice' : 'slice')),
1535
                        blob = file[slice](chunkSize * index, chunkSize * (index + 1));
1536
                    fd = new FormData();
1537
                    f = fm.stack[id];
1538
                    self._setUploadData(fd, {
1539
                        chunkCount: rm.chunkCount,
1540
                        chunkIndex: index,
1541
                        chunkSize: chunkSize,
1542
                        chunkSizeStart: chunkSize * index,
1543
                        fileBlob: [blob, rm.fileName],
1544
                        fileId: id,
1545
                        fileName: rm.fileName,
1546
                        fileRelativePath: f.relativePath,
1547
                        fileSize: file.size,
1548
                        retryCount: retry
1549
                    });
1550
                    if (rm.$progress && rm.$progress.length) {
1551
                        rm.$progress.show();
1552
                    }
1553
                    fnBefore = function (jqXHR) {
1554
                        outData = self._getOutData(fd, jqXHR);
1555
                        if (self.showPreview) {
1556
                            if (!$thumb.hasClass('file-preview-success')) {
1557
                                self._setThumbStatus($thumb, 'Loading');
1558
                                $h.addCss($thumb, 'file-uploading');
1559
                            }
1560
                            $btnDelete.attr('disabled', true);
1561
                        }
1562
                        self._raise('filechunkbeforesend', [id, index, retry, fm, rm, outData]);
1563
                    };
1564
                    fnSuccess = function (data, textStatus, jqXHR) {
1565
                        if (self._isAborted()) {
1566
                            logError(msgs.resumableAborting);
1567
                            return;
1568
                        }
1569
                        outData = self._getOutData(fd, jqXHR, data);
1570
                        var paramNames = self.uploadParamNames, chunkIndex = paramNames.chunkIndex || 'chunkIndex',
1571
                            params = [id, index, retry, fm, rm, outData];
1572
                        if (data.error) {
1573
                            if (opts.showErrorLog) {
1574
                                self._log(logs.retryStatus, {
1575
                                    retry: retry + 1,
1576
                                    filename: rm.fileName,
1577
                                    chunk: index
1578
                                });
1579
                            }
1580
                            rm.pushAjax(index, retry + 1);
1581
                            rm.error = data.error;
1582
                            self._raise('filechunkerror', params);
1583
                        } else {
1584
                            rm.logs[data[chunkIndex]] = true;
1585
                            if (!rm.chunksProcessed[id]) {
1586
                                rm.chunksProcessed[id] = {};
1587
                            }
1588
                            rm.chunksProcessed[id][data[chunkIndex]] = true;
1589
                            rm.chunksProcessed[id].data = data;
1590
                            deferrer.resolve.call(null, data);
1591
                            self._raise('filechunksuccess', params);
1592
                            rm.check();
1593
                        }
1594
                    };
1595
                    fnError = function (jqXHR, textStatus, errorThrown) {
1596
                        if (self._isAborted()) {
1597
                            logError(msgs.resumableAborting);
1598
                            return;
1599
                        }
1600
                        outData = self._getOutData(fd, jqXHR);
1601
                        rm.setAjaxError(jqXHR, textStatus, errorThrown);
1602
                        self._raise('filechunkajaxerror', [id, index, retry, fm, rm, outData]);
1603
                        rm.pushAjax(index, retry + 1);                        // push another task
1604
                        logError(msgs.resumableRetryError, {n: retry - 1}); // resolve the current task
1605
                    };
1606
                    fnComplete = function () {
1607
                        if (!self._isAborted()) {
1608
                            self._raise('filechunkcomplete', [id, index, retry, fm, rm, self._getOutData(fd)]);
1609
                        }
1610
                    };
1611
                    self._ajaxSubmit(fnBefore, fnSuccess, fnComplete, fnError, fd, id, rm.fileIndex);
1612
                }
1613
            };
1614
            rm.reset();
1615
        },
1616
        _initTemplateDefaults: function () {
1617
            var self = this, tMain1, tMain2, tPreview, tFileIcon, tClose, tCaption, tBtnDefault, tBtnLink, tBtnBrowse,
1618
                tModalMain, tModal, tProgress, tSize, tFooter, tActions, tActionDelete, tActionUpload, tActionDownload,
1619
                tActionZoom, tActionDrag, tIndicator, tTagBef, tTagBef1, tTagBef2, tTagAft, tGeneric, tHtml, tImage,
1620
                tText, tOffice, tGdocs, tVideo, tAudio, tFlash, tObject, tPdf, tOther, tStyle, tZoomCache, vDefaultDim,
1621
                tStats, tModalLabel;
1622
            tMain1 = '{preview}\n' +
1623
                '<div class="kv-upload-progress kv-hidden"></div><div class="clearfix"></div>\n' +
1624
                '<div class="input-group {class}">\n' +
1625
                '  {caption}\n' +
1626
                '<div class="input-group-btn input-group-append">\n' +
1627
                '      {remove}\n' +
1628
                '      {cancel}\n' +
1629
                '      {pause}\n' +
1630
                '      {upload}\n' +
1631
                '      {browse}\n' +
1632
                '    </div>\n' +
1633
                '</div>';
1634
            tMain2 = '{preview}\n<div class="kv-upload-progress kv-hidden"></div>\n<div class="clearfix"></div>\n' +
1635
                '{remove}\n{cancel}\n{upload}\n{browse}\n';
1636
            tPreview = '<div class="file-preview {class}">\n' +
1637
                '  {close}' +
1638
                '  <div class="{dropClass} clearfix">\n' +
1639
                '    <div class="file-preview-thumbnails clearfix">\n' +
1640
                '    </div>\n' +
1641
                '    <div class="file-preview-status text-center text-success"></div>\n' +
1642
                '    <div class="kv-fileinput-error"></div>\n' +
1643
                '  </div>\n' +
1644
                '</div>';
1645
            tClose = $h.closeButton('fileinput-remove');
1646
            tFileIcon = '<i class="glyphicon glyphicon-file"></i>';
1647
            // noinspection HtmlUnknownAttribute
1648
            tCaption = '<div class="file-caption form-control {class}" tabindex="500">\n' +
1649
                '  <span class="file-caption-icon"></span>\n' +
1650
                '  <input class="file-caption-name">\n' +
1651
                '</div>';
1652
            //noinspection HtmlUnknownAttribute
1653
            tBtnDefault = '<button type="{type}" tabindex="500" title="{title}" class="{css}" ' +
1654
                '{status}>{icon} {label}</button>';
1655
            //noinspection HtmlUnknownTarget,HtmlUnknownAttribute
1656
            tBtnLink = '<a href="{href}" tabindex="500" title="{title}" class="{css}" {status}>{icon} {label}</a>';
1657
            //noinspection HtmlUnknownAttribute
1658
            tBtnBrowse = '<div tabindex="500" class="{css}" {status}>{icon} {label}</div>';
1659
            tModalLabel =  $h.MODAL_ID + 'Label';
1660
            tModalMain = '<div id="' + $h.MODAL_ID + '" class="file-zoom-dialog modal fade" ' +
1661
                'tabindex="-1" aria-labelledby="' + tModalLabel + '"></div>';
1662
            tModal = '<div class="modal-dialog modal-lg{rtl}" role="document">\n' +
1663
                '  <div class="modal-content">\n' +
1664
                '    <div class="modal-header">\n' +
1665
                '      <h5 class="modal-title" id="' + tModalLabel + '">{heading}</h5>\n' +
1666
                '      <span class="kv-zoom-title"></span>\n' +
1667
                '      <div class="kv-zoom-actions">{toggleheader}{fullscreen}{borderless}{close}</div>\n' +
1668
                '    </div>\n' +
1669
                '    <div class="modal-body">\n' +
1670
                '      <div class="floating-buttons"></div>\n' +
1671
                '      <div class="kv-zoom-body file-zoom-content {zoomFrameClass}"></div>\n' + '{prev} {next}\n' +
1672
                '    </div>\n' +
1673
                '  </div>\n' +
1674
                '</div>\n';
1675
            tProgress = '<div class="progress">\n' +
1676
                '    <div class="{class}" role="progressbar"' +
1677
                ' aria-valuenow="{percent}" aria-valuemin="0" aria-valuemax="100" style="width:{percent}%;">\n' +
1678
                '        {status}\n' +
1679
                '     </div>\n' +
1680
                '</div>{stats}';
1681
            tStats = '<div class="text-info file-upload-stats">' +
1682
                '<span class="pending-time">{pendingTime}</span> ' +
1683
                '<span class="upload-speed">{uploadSpeed}</span>' +
1684
                '</div>';
1685
            tSize = ' <samp>({sizeText})</samp>';
1686
            tFooter = '<div class="file-thumbnail-footer">\n' +
1687
                '    <div class="file-footer-caption" title="{caption}">\n' +
1688
                '        <div class="file-caption-info">{caption}</div>\n' +
1689
                '        <div class="file-size-info">{size}</div>\n' +
1690
                '    </div>\n' +
1691
                '    {progress}\n{indicator}\n{actions}\n' +
1692
                '</div>';
1693
            tActions = '<div class="file-actions">\n' +
1694
                '    <div class="file-footer-buttons">\n' +
1695
                '        {download} {upload} {delete} {zoom} {other}' +
1696
                '    </div>\n' +
1697
                '</div>\n' +
1698
                '{drag}\n' +
1699
                '<div class="clearfix"></div>';
1700
            //noinspection HtmlUnknownAttribute
1701
            tActionDelete = '<button type="button" class="kv-file-remove {removeClass}" ' +
1702
                'title="{removeTitle}" {dataUrl}{dataKey}>{removeIcon}</button>\n';
1703
            tActionUpload = '<button type="button" class="kv-file-upload {uploadClass}" title="{uploadTitle}">' +
1704
                '{uploadIcon}</button>';
1705
            tActionDownload = '<a class="kv-file-download {downloadClass}" title="{downloadTitle}" ' +
1706
                'href="{downloadUrl}" download="{caption}" target="_blank">{downloadIcon}</a>';
1707
            tActionZoom = '<button type="button" class="kv-file-zoom {zoomClass}" ' +
1708
                'title="{zoomTitle}">{zoomIcon}</button>';
1709
            tActionDrag = '<span class="file-drag-handle {dragClass}" title="{dragTitle}">{dragIcon}</span>';
1710
            tIndicator = '<div class="file-upload-indicator" title="{indicatorTitle}">{indicator}</div>';
1711
            tTagBef = '<div class="file-preview-frame {frameClass}" id="{previewId}" data-fileindex="{fileindex}"' +
1712
                ' data-fileid="{fileid}" data-template="{template}"';
1713
            tTagBef1 = tTagBef + '><div class="kv-file-content">\n';
1714
            tTagBef2 = tTagBef + ' title="{caption}"><div class="kv-file-content">\n';
1715
            tTagAft = '</div>{footer}\n{zoomCache}</div>\n';
1716
            tGeneric = '{content}\n';
1717
            tStyle = ' {style}';
1718
            tHtml = '<div class="kv-preview-data file-preview-html" title="{caption}"' + tStyle + '>{data}</div>\n';
1719
            tImage = '<img src="{data}" class="file-preview-image kv-preview-data" title="{title}" ' +
1720
                'alt="{alt}"' + tStyle + '>\n';
1721
            tText = '<textarea class="kv-preview-data file-preview-text" title="{caption}" readonly' + tStyle + '>' +
1722
                '{data}</textarea>\n';
1723
            tOffice = '<iframe class="kv-preview-data file-preview-office" ' +
1724
                'src="https://view.officeapps.live.com/op/embed.aspx?src={data}"' + tStyle + '></iframe>';
1725
            tGdocs = '<iframe class="kv-preview-data file-preview-gdocs" ' +
1726
                'src="https://docs.google.com/gview?url={data}&embedded=true"' + tStyle + '></iframe>';
1727
            tVideo = '<video class="kv-preview-data file-preview-video" controls' + tStyle + '>\n' +
1728
                '<source src="{data}" type="{type}">\n' + $h.DEFAULT_PREVIEW + '\n</video>\n';
1729
            tAudio = '<!--suppress ALL --><audio class="kv-preview-data file-preview-audio" controls' + tStyle + '>\n<source src="{data}" ' +
1730
                'type="{type}">\n' + $h.DEFAULT_PREVIEW + '\n</audio>\n';
1731
            tFlash = '<embed class="kv-preview-data file-preview-flash" src="{data}" type="application/x-shockwave-flash"' + tStyle + '>\n';
1732
            tPdf = '<embed class="kv-preview-data file-preview-pdf" src="{data}" type="application/pdf"' + tStyle + '>\n';
1733
            tObject = '<object class="kv-preview-data file-preview-object file-object {typeCss}" ' +
1734
                'data="{data}" type="{type}"' + tStyle + '>\n' + '<param name="movie" value="{caption}" />\n' +
1735
                $h.OBJECT_PARAMS + ' ' + $h.DEFAULT_PREVIEW + '\n</object>\n';
1736
            tOther = '<div class="kv-preview-data file-preview-other-frame"' + tStyle + '>\n' + $h.DEFAULT_PREVIEW + '\n</div>\n';
1737
            tZoomCache = '<div class="kv-zoom-cache" style="display:none">{zoomContent}</div>';
1738
            vDefaultDim = {width: '100%', height: '100%', 'min-height': '480px'};
1739
            if (self._isPdfRendered()) {
1740
                tPdf = self.pdfRendererTemplate.replace('{renderer}', self._encodeURI(self.pdfRendererUrl));
1741
            }
1742
            self.defaults = {
1743
                layoutTemplates: {
1744
                    main1: tMain1,
1745
                    main2: tMain2,
1746
                    preview: tPreview,
1747
                    close: tClose,
1748
                    fileIcon: tFileIcon,
1749
                    caption: tCaption,
1750
                    modalMain: tModalMain,
1751
                    modal: tModal,
1752
                    progress: tProgress,
1753
                    stats: tStats,
1754
                    size: tSize,
1755
                    footer: tFooter,
1756
                    indicator: tIndicator,
1757
                    actions: tActions,
1758
                    actionDelete: tActionDelete,
1759
                    actionUpload: tActionUpload,
1760
                    actionDownload: tActionDownload,
1761
                    actionZoom: tActionZoom,
1762
                    actionDrag: tActionDrag,
1763
                    btnDefault: tBtnDefault,
1764
                    btnLink: tBtnLink,
1765
                    btnBrowse: tBtnBrowse,
1766
                    zoomCache: tZoomCache
1767
                },
1768
                previewMarkupTags: {
1769
                    tagBefore1: tTagBef1,
1770
                    tagBefore2: tTagBef2,
1771
                    tagAfter: tTagAft
1772
                },
1773
                previewContentTemplates: {
1774
                    generic: tGeneric,
1775
                    html: tHtml,
1776
                    image: tImage,
1777
                    text: tText,
1778
                    office: tOffice,
1779
                    gdocs: tGdocs,
1780
                    video: tVideo,
1781
                    audio: tAudio,
1782
                    flash: tFlash,
1783
                    object: tObject,
1784
                    pdf: tPdf,
1785
                    other: tOther
1786
                },
1787
                allowedPreviewTypes: ['image', 'html', 'text', 'video', 'audio', 'flash', 'pdf', 'object'],
1788
                previewTemplates: {},
1789
                previewSettings: {
1790
                    image: {width: 'auto', height: 'auto', 'max-width': '100%', 'max-height': '100%'},
1791
                    html: {width: '213px', height: '160px'},
1792
                    text: {width: '213px', height: '160px'},
1793
                    office: {width: '213px', height: '160px'},
1794
                    gdocs: {width: '213px', height: '160px'},
1795
                    video: {width: '213px', height: '160px'},
1796
                    audio: {width: '100%', height: '30px'},
1797
                    flash: {width: '213px', height: '160px'},
1798
                    object: {width: '213px', height: '160px'},
1799
                    pdf: {width: '100%', height: '160px', 'position': 'relative'},
1800
                    other: {width: '213px', height: '160px'}
1801
                },
1802
                previewSettingsSmall: {
1803
                    image: {width: 'auto', height: 'auto', 'max-width': '100%', 'max-height': '100%'},
1804
                    html: {width: '100%', height: '160px'},
1805
                    text: {width: '100%', height: '160px'},
1806
                    office: {width: '100%', height: '160px'},
1807
                    gdocs: {width: '100%', height: '160px'},
1808
                    video: {width: '100%', height: 'auto'},
1809
                    audio: {width: '100%', height: '30px'},
1810
                    flash: {width: '100%', height: 'auto'},
1811
                    object: {width: '100%', height: 'auto'},
1812
                    pdf: {width: '100%', height: '160px'},
1813
                    other: {width: '100%', height: '160px'}
1814
                },
1815
                previewZoomSettings: {
1816
                    image: {width: 'auto', height: 'auto', 'max-width': '100%', 'max-height': '100%'},
1817
                    html: vDefaultDim,
1818
                    text: vDefaultDim,
1819
                    office: {width: '100%', height: '100%', 'max-width': '100%', 'min-height': '480px'},
1820
                    gdocs: {width: '100%', height: '100%', 'max-width': '100%', 'min-height': '480px'},
1821
                    video: {width: 'auto', height: '100%', 'max-width': '100%'},
1822
                    audio: {width: '100%', height: '30px'},
1823
                    flash: {width: 'auto', height: '480px'},
1824
                    object: {width: 'auto', height: '100%', 'max-width': '100%', 'min-height': '480px'},
1825
                    pdf: vDefaultDim,
1826
                    other: {width: 'auto', height: '100%', 'min-height': '480px'}
1827
                },
1828
                mimeTypeAliases: {
1829
                    'video/quicktime': 'video/mp4'
1830
                },
1831
                fileTypeSettings: {
1832
                    image: function (vType, vName) {
1833
                        return ($h.compare(vType, 'image.*') && !$h.compare(vType, /(tiff?|wmf)$/i) ||
1834
                            $h.compare(vName, /\.(gif|png|jpe?g)$/i));
1835
                    },
1836
                    html: function (vType, vName) {
1837
                        return $h.compare(vType, 'text/html') || $h.compare(vName, /\.(htm|html)$/i);
1838
                    },
1839
                    office: function (vType, vName) {
1840
                        return $h.compare(vType, /(word|excel|powerpoint|office)$/i) ||
1841
                            $h.compare(vName, /\.(docx?|xlsx?|pptx?|pps|potx?)$/i);
1842
                    },
1843
                    gdocs: function (vType, vName) {
1844
                        return $h.compare(vType, /(word|excel|powerpoint|office|iwork-pages|tiff?)$/i) ||
1845
                            $h.compare(vName,
1846
                                /\.(docx?|xlsx?|pptx?|pps|potx?|rtf|ods|odt|pages|ai|dxf|ttf|tiff?|wmf|e?ps)$/i);
1847
                    },
1848
                    text: function (vType, vName) {
1849
                        return $h.compare(vType, 'text.*') || $h.compare(vName, /\.(xml|javascript)$/i) ||
1850
                            $h.compare(vName, /\.(txt|md|csv|nfo|ini|json|php|js|css)$/i);
1851
                    },
1852
                    video: function (vType, vName) {
1853
                        return $h.compare(vType, 'video.*') && ($h.compare(vType, /(ogg|mp4|mp?g|mov|webm|3gp)$/i) ||
1854
                            $h.compare(vName, /\.(og?|mp4|webm|mp?g|mov|3gp)$/i));
1855
                    },
1856
                    audio: function (vType, vName) {
1857
                        return $h.compare(vType, 'audio.*') && ($h.compare(vName, /(ogg|mp3|mp?g|wav)$/i) ||
1858
                            $h.compare(vName, /\.(og?|mp3|mp?g|wav)$/i));
1859
                    },
1860
                    flash: function (vType, vName) {
1861
                        return $h.compare(vType, 'application/x-shockwave-flash', true) || $h.compare(vName,
1862
                            /\.(swf)$/i);
1863
                    },
1864
                    pdf: function (vType, vName) {
1865
                        return $h.compare(vType, 'application/pdf', true) || $h.compare(vName, /\.(pdf)$/i);
1866
                    },
1867
                    object: function () {
1868
                        return true;
1869
                    },
1870
                    other: function () {
1871
                        return true;
1872
                    }
1873
                },
1874
                fileActionSettings: {
1875
                    showRemove: true,
1876
                    showUpload: true,
1877
                    showDownload: true,
1878
                    showZoom: true,
1879
                    showDrag: true,
1880
                    removeIcon: '<i class="glyphicon glyphicon-trash"></i>',
1881
                    removeClass: 'btn btn-sm btn-kv btn-default btn-outline-secondary',
1882
                    removeErrorClass: 'btn btn-sm btn-kv btn-danger',
1883
                    removeTitle: 'Remove file',
1884
                    uploadIcon: '<i class="glyphicon glyphicon-upload"></i>',
1885
                    uploadClass: 'btn btn-sm btn-kv btn-default btn-outline-secondary',
1886
                    uploadTitle: 'Upload file',
1887
                    uploadRetryIcon: '<i class="glyphicon glyphicon-repeat"></i>',
1888
                    uploadRetryTitle: 'Retry upload',
1889
                    downloadIcon: '<i class="glyphicon glyphicon-download"></i>',
1890
                    downloadClass: 'btn btn-sm btn-kv btn-default btn-outline-secondary',
1891
                    downloadTitle: 'Download file',
1892
                    zoomIcon: '<i class="glyphicon glyphicon-zoom-in"></i>',
1893
                    zoomClass: 'btn btn-sm btn-kv btn-default btn-outline-secondary',
1894
                    zoomTitle: 'View Details',
1895
                    dragIcon: '<i class="glyphicon glyphicon-move"></i>',
1896
                    dragClass: 'text-info',
1897
                    dragTitle: 'Move / Rearrange',
1898
                    dragSettings: {},
1899
                    indicatorNew: '<i class="glyphicon glyphicon-plus-sign text-warning"></i>',
1900
                    indicatorSuccess: '<i class="glyphicon glyphicon-ok-sign text-success"></i>',
1901
                    indicatorError: '<i class="glyphicon glyphicon-exclamation-sign text-danger"></i>',
1902
                    indicatorLoading: '<i class="glyphicon glyphicon-hourglass text-muted"></i>',
1903
                    indicatorPaused: '<i class="glyphicon glyphicon-pause text-primary"></i>',
1904
                    indicatorNewTitle: 'Not uploaded yet',
1905
                    indicatorSuccessTitle: 'Uploaded',
1906
                    indicatorErrorTitle: 'Upload Error',
1907
                    indicatorLoadingTitle: 'Uploading &hellip;',
1908
                    indicatorPausedTitle: 'Upload Paused'
1909
                }
1910
            };
1911
            $.each(self.defaults, function (key, setting) {
1912
                if (key === 'allowedPreviewTypes') {
1913
                    if (self.allowedPreviewTypes === undefined) {
1914
                        self.allowedPreviewTypes = setting;
1915
                    }
1916
                    return;
1917
                }
1918
                self[key] = $.extend(true, {}, setting, self[key]);
1919
            });
1920
            self._initPreviewTemplates();
1921
        },
1922
        _initPreviewTemplates: function () {
1923
            var self = this, tags = self.previewMarkupTags, tagBef, tagAft = tags.tagAfter;
1924
            $.each(self.previewContentTemplates, function (key, value) {
1925
                if ($h.isEmpty(self.previewTemplates[key])) {
1926
                    tagBef = tags.tagBefore2;
1927
                    if (key === 'generic' || key === 'image' || key === 'html' || key === 'text') {
1928
                        tagBef = tags.tagBefore1;
1929
                    }
1930
                    if (self._isPdfRendered() && key === 'pdf') {
1931
                        tagBef = tagBef.replace('kv-file-content', 'kv-file-content kv-pdf-rendered');
1932
                    }
1933
                    self.previewTemplates[key] = tagBef + value + tagAft;
1934
                }
1935
            });
1936
        },
1937
        _initPreviewCache: function () {
1938
            var self = this;
1939
            self.previewCache = {
1940
                data: {},
1941
                init: function () {
1942
                    var content = self.initialPreview;
1943
                    if (content.length > 0 && !$h.isArray(content)) {
1944
                        content = content.split(self.initialPreviewDelimiter);
1945
                    }
1946
                    self.previewCache.data = {
1947
                        content: content,
1948
                        config: self.initialPreviewConfig,
1949
                        tags: self.initialPreviewThumbTags
1950
                    };
1951
                },
1952
                count: function (skipNull) {
1953
                    if (!self.previewCache.data || !self.previewCache.data.content) {
1954
                        return 0;
1955
                    }
1956
                    if (skipNull) {
1957
                        var chk = self.previewCache.data.content.filter(function (n) {
1958
                            return n !== null;
1959
                        });
1960
                        return chk.length;
1961
                    }
1962
                    return self.previewCache.data.content.length;
1963
                },
1964
                get: function (i, isDisabled) {
1965
                    var ind = $h.INIT_FLAG + i, data = self.previewCache.data, config = data.config[i],
1966
                        content = data.content[i], out, $tmp, cat, ftr,
1967
                        fname, ftype, frameClass, asData = $h.ifSet('previewAsData', config, self.initialPreviewAsData),
1968
                        a = config ? {title: config.title || null, alt: config.alt || null} : {title: null, alt: null},
1969
                        parseTemplate = function (cat, dat, fname, ftype, ftr, ind, fclass, t) {
1970
                            var fc = ' file-preview-initial ' + $h.SORT_CSS + (fclass ? ' ' + fclass : ''),
1971
                                id = self.previewInitId + '-' + ind,
1972
                                fileId = config && config.fileId || id;
1973
                            /** @namespace config.zoomData */
1974
                            return self._generatePreviewTemplate(cat, dat, fname, ftype, id, fileId, false, null, fc,
1975
                                ftr, ind, t, a, config && config.zoomData || dat);
1976
                        };
1977
                    if (!content || !content.length) {
1978
                        return '';
1979
                    }
1980
                    isDisabled = isDisabled === undefined ? true : isDisabled;
1981
                    cat = $h.ifSet('type', config, self.initialPreviewFileType || 'generic');
1982
                    fname = $h.ifSet('filename', config, $h.ifSet('caption', config));
1983
                    ftype = $h.ifSet('filetype', config, cat);
1984
                    ftr = self.previewCache.footer(i, isDisabled, (config && config.size || null));
1985
                    frameClass = $h.ifSet('frameClass', config);
1986
                    if (asData) {
1987
                        out = parseTemplate(cat, content, fname, ftype, ftr, ind, frameClass);
1988
                    } else {
1989
                        out = parseTemplate('generic', content, fname, ftype, ftr, ind, frameClass, cat)
1990
                            .setTokens({'content': data.content[i]});
1991
                    }
1992
                    if (data.tags.length && data.tags[i]) {
1993
                        out = $h.replaceTags(out, data.tags[i]);
1994
                    }
1995
                    /** @namespace config.frameAttr */
1996
                    if (!$h.isEmpty(config) && !$h.isEmpty(config.frameAttr)) {
1997
                        $tmp = $h.createElement(out);
1998
                        $tmp.find('.file-preview-initial').attr(config.frameAttr);
1999
                        out = $tmp.html();
2000
                        $tmp.remove();
2001
                    }
2002
                    return out;
2003
                },
2004
                clean: function (data) {
2005
                    data.content = $h.cleanArray(data.content);
2006
                    data.config = $h.cleanArray(data.config);
2007
                    data.tags = $h.cleanArray(data.tags);
2008
                    self.previewCache.data = data;
2009
                },
2010
                add: function (content, config, tags, append) {
2011
                    var data = self.previewCache.data, index;
2012
                    if (!content || !content.length) {
2013
                        return 0;
2014
                    }
2015
                    index = content.length - 1;
2016
                    if (!$h.isArray(content)) {
2017
                        content = content.split(self.initialPreviewDelimiter);
2018
                    }
2019
                    if (append && data.content) {
2020
                        index = data.content.push(content[0]) - 1;
2021
                        data.config[index] = config;
2022
                        data.tags[index] = tags;
2023
                    } else {
2024
                        data.content = content;
2025
                        data.config = config;
2026
                        data.tags = tags;
2027
                    }
2028
                    self.previewCache.clean(data);
2029
                    return index;
2030
                },
2031
                set: function (content, config, tags, append) {
2032
                    var data = self.previewCache.data, i, chk;
2033
                    if (!content || !content.length) {
2034
                        return;
2035
                    }
2036
                    if (!$h.isArray(content)) {
2037
                        content = content.split(self.initialPreviewDelimiter);
2038
                    }
2039
                    chk = content.filter(function (n) {
2040
                        return n !== null;
2041
                    });
2042
                    if (!chk.length) {
2043
                        return;
2044
                    }
2045
                    if (data.content === undefined) {
2046
                        data.content = [];
2047
                    }
2048
                    if (data.config === undefined) {
2049
                        data.config = [];
2050
                    }
2051
                    if (data.tags === undefined) {
2052
                        data.tags = [];
2053
                    }
2054
                    if (append) {
2055
                        for (i = 0; i < content.length; i++) {
2056
                            if (content[i]) {
2057
                                data.content.push(content[i]);
2058
                            }
2059
                        }
2060
                        for (i = 0; i < config.length; i++) {
2061
                            if (config[i]) {
2062
                                data.config.push(config[i]);
2063
                            }
2064
                        }
2065
                        for (i = 0; i < tags.length; i++) {
2066
                            if (tags[i]) {
2067
                                data.tags.push(tags[i]);
2068
                            }
2069
                        }
2070
                    } else {
2071
                        data.content = content;
2072
                        data.config = config;
2073
                        data.tags = tags;
2074
                    }
2075
                    self.previewCache.clean(data);
2076
                },
2077
                unset: function (index) {
2078
                    var chk = self.previewCache.count(), rev = self.reversePreviewOrder;
2079
                    if (!chk) {
2080
                        return;
2081
                    }
2082
                    if (chk === 1) {
2083
                        self.previewCache.data.content = [];
2084
                        self.previewCache.data.config = [];
2085
                        self.previewCache.data.tags = [];
2086
                        self.initialPreview = [];
2087
                        self.initialPreviewConfig = [];
2088
                        self.initialPreviewThumbTags = [];
2089
                        return;
2090
                    }
2091
                    self.previewCache.data.content = $h.spliceArray(self.previewCache.data.content, index, rev);
2092
                    self.previewCache.data.config = $h.spliceArray(self.previewCache.data.config, index, rev);
2093
                    self.previewCache.data.tags = $h.spliceArray(self.previewCache.data.tags, index, rev);
2094
                    var data = $.extend(true, {}, self.previewCache.data);
2095
                    self.previewCache.clean(data);
2096
                },
2097
                out: function () {
2098
                    var html = '', caption, len = self.previewCache.count(), i, content;
2099
                    if (len === 0) {
2100
                        return {content: '', caption: ''};
2101
                    }
2102
                    for (i = 0; i < len; i++) {
2103
                        content = self.previewCache.get(i);
2104
                        html = self.reversePreviewOrder ? (content + html) : (html + content);
2105
                    }
2106
                    caption = self._getMsgSelected(len);
2107
                    return {content: html, caption: caption};
2108
                },
2109
                footer: function (i, isDisabled, size) {
2110
                    var data = self.previewCache.data || {};
2111
                    if ($h.isEmpty(data.content)) {
2112
                        return '';
2113
                    }
2114
                    if ($h.isEmpty(data.config) || $h.isEmpty(data.config[i])) {
2115
                        data.config[i] = {};
2116
                    }
2117
                    isDisabled = isDisabled === undefined ? true : isDisabled;
2118
                    var config = data.config[i], caption = $h.ifSet('caption', config), a,
2119
                        width = $h.ifSet('width', config, 'auto'), url = $h.ifSet('url', config, false),
2120
                        key = $h.ifSet('key', config, null), fileId = $h.ifSet('fileId', config, null),
2121
                        fs = self.fileActionSettings, initPreviewShowDel = self.initialPreviewShowDelete || false,
2122
                        downloadInitialUrl = !self.initialPreviewDownloadUrl ? '' :
2123
                            self.initialPreviewDownloadUrl + '?key=' + key + (fileId ? '&fileId=' + fileId : ''),
2124
                        dUrl = config.downloadUrl || downloadInitialUrl,
2125
                        dFil = config.filename || config.caption || '',
2126
                        initPreviewShowDwl = !!(dUrl),
2127
                        sDel = $h.ifSet('showRemove', config, initPreviewShowDel),
2128
                        sDwl = $h.ifSet('showDownload', config, $h.ifSet('showDownload', fs, initPreviewShowDwl)),
2129
                        sZm = $h.ifSet('showZoom', config, $h.ifSet('showZoom', fs, true)),
2130
                        sDrg = $h.ifSet('showDrag', config, $h.ifSet('showDrag', fs, true)),
2131
                        dis = (url === false) && isDisabled;
2132
                    sDwl = sDwl && config.downloadUrl !== false && !!dUrl;
2133
                    a = self._renderFileActions(config, false, sDwl, sDel, sZm, sDrg, dis, url, key, true, dUrl, dFil);
2134
                    return self._getLayoutTemplate('footer').setTokens({
2135
                        'progress': self._renderThumbProgress(),
2136
                        'actions': a,
2137
                        'caption': caption,
2138
                        'size': self._getSize(size),
2139
                        'width': width,
2140
                        'indicator': ''
2141
                    });
2142
                }
2143
            };
2144
            self.previewCache.init();
2145
        },
2146
        _isPdfRendered: function () {
2147
            var self = this, useLib = self.usePdfRenderer,
2148
                flag = typeof useLib === 'function' ? useLib() : !!useLib;
2149
            return flag && self.pdfRendererUrl;
2150
        },
2151
        _handler: function ($el, event, callback) {
2152
            var self = this, ns = self.namespace, ev = event.split(' ').join(ns + ' ') + ns;
2153
            if (!$el || !$el.length) {
2154
                return;
2155
            }
2156
            $el.off(ev).on(ev, callback);
2157
        },
2158
        _encodeURI: function (vUrl) {
2159
            var self = this;
2160
            return self.encodeUrl ? encodeURI(vUrl) : vUrl;
2161
        },
2162
        _log: function (msg, tokens) {
2163
            var self = this, id = self.$element.attr('id');
2164
            if (!self.showConsoleLogs) {
2165
                return;
2166
            }
2167
            if (id) {
2168
                msg = '"' + id + '": ' + msg;
2169
            }
2170
            msg = 'bootstrap-fileinput: ' + msg;
2171
            if (typeof tokens === 'object') {
2172
                msg = msg.setTokens(tokens);
2173
            }
2174
            if (window.console && typeof window.console.log !== 'undefined') {
2175
                window.console.log(msg);
2176
            } else {
2177
                window.alert(msg);
2178
            }
2179
        },
2180
        _validate: function () {
2181
            var self = this, status = self.$element.attr('type') === 'file';
2182
            if (!status) {
2183
                self._log($h.logMessages.badInputType);
2184
            }
2185
            return status;
2186
        },
2187
        _errorsExist: function () {
2188
            var self = this, $err, $errList = self.$errorContainer.find('li');
2189
            if ($errList.length) {
2190
                return true;
2191
            }
2192
            $err = $h.createElement(self.$errorContainer.html());
2193
            $err.find('.kv-error-close').remove();
2194
            $err.find('ul').remove();
2195
            return !!$.trim($err.text()).length;
2196
        },
2197
        _errorHandler: function (evt, caption) {
2198
            var self = this, err = evt.target.error, showError = function (msg) {
2199
                self._showError(msg.replace('{name}', caption));
2200
            };
2201
            /** @namespace err.NOT_FOUND_ERR */
2202
            /** @namespace err.SECURITY_ERR */
2203
            /** @namespace err.NOT_READABLE_ERR */
2204
            if (err.code === err.NOT_FOUND_ERR) {
2205
                showError(self.msgFileNotFound);
2206
            } else {
2207
                if (err.code === err.SECURITY_ERR) {
2208
                    showError(self.msgFileSecured);
2209
                } else {
2210
                    if (err.code === err.NOT_READABLE_ERR) {
2211
                        showError(self.msgFileNotReadable);
2212
                    } else {
2213
                        if (err.code === err.ABORT_ERR) {
2214
                            showError(self.msgFilePreviewAborted);
2215
                        } else {
2216
                            showError(self.msgFilePreviewError);
2217
                        }
2218
                    }
2219
                }
2220
            }
2221
        },
2222
        _addError: function (msg) {
2223
            var self = this, $error = self.$errorContainer;
2224
            if (msg && $error.length) {
2225
                $h.setHtml($error, self.errorCloseButton + msg);
2226
                self._handler($error.find('.kv-error-close'), 'click', function () {
2227
                    setTimeout(function () {
2228
                        if (self.showPreview && !self.getFrames().length) {
2229
                            self.clear();
2230
                        }
2231
                        $error.fadeOut('slow');
2232
                    }, self.processDelay);
2233
                });
2234
            }
2235
        },
2236
        _setValidationError: function (css) {
2237
            var self = this;
2238
            css = (css ? css + ' ' : '') + 'has-error';
2239
            self.$container.removeClass(css).addClass('has-error');
2240
            $h.addCss(self.$captionContainer, 'is-invalid');
2241
        },
2242
        _resetErrors: function (fade) {
2243
            var self = this, $error = self.$errorContainer;
2244
            if (self.isPersistentError) {
2245
                return;
2246
            }
2247
            self.isError = false;
2248
            self.$container.removeClass('has-error');
2249
            self.$captionContainer.removeClass('is-invalid');
2250
            $error.html('');
2251
            if (fade) {
2252
                $error.fadeOut('slow');
2253
            } else {
2254
                $error.hide();
2255
            }
2256
        },
2257
        _showFolderError: function (folders) {
2258
            var self = this, $error = self.$errorContainer, msg;
2259
            if (!folders) {
2260
                return;
2261
            }
2262
            if (!self.isAjaxUpload) {
2263
                self._clearFileInput();
2264
            }
2265
            msg = self.msgFoldersNotAllowed.replace('{n}', folders);
2266
            self._addError(msg);
2267
            self._setValidationError();
2268
            $error.fadeIn(self.fadeDelay);
2269
            self._raise('filefoldererror', [folders, msg]);
2270
        },
2271
        _showFileError: function (msg, params, event) {
2272
            var self = this, $error = self.$errorContainer, ev = event || 'fileuploaderror',
2273
                fId = params && params.fileId || '', e = params && params.id ?
2274
                '<li data-thumb-id="' + params.id + '" data-file-id="' + fId + '">' + msg + '</li>' : '<li>' + msg + '</li>';
2275
 
2276
            if ($error.find('ul').length === 0) {
2277
                self._addError('<ul>' + e + '</ul>');
2278
            } else {
2279
                $error.find('ul').append(e);
2280
            }
2281
            $error.fadeIn(self.fadeDelay);
2282
            self._raise(ev, [params, msg]);
2283
            self._setValidationError('file-input-new');
2284
            return true;
2285
        },
2286
        _showError: function (msg, params, event) {
2287
            var self = this, $error = self.$errorContainer, ev = event || 'fileerror';
2288
            params = params || {};
2289
            params.reader = self.reader;
2290
            self._addError(msg);
2291
            $error.fadeIn(self.fadeDelay);
2292
            self._raise(ev, [params, msg]);
2293
            if (!self.isAjaxUpload) {
2294
                self._clearFileInput();
2295
            }
2296
            self._setValidationError('file-input-new');
2297
            self.$btnUpload.attr('disabled', true);
2298
            return true;
2299
        },
2300
        _noFilesError: function (params) {
2301
            var self = this, label = self.minFileCount > 1 ? self.filePlural : self.fileSingle,
2302
                msg = self.msgFilesTooLess.replace('{n}', self.minFileCount).replace('{files}', label),
2303
                $error = self.$errorContainer;
2304
            msg = '<li>' + msg + '</li>';
2305
            if ($error.find('ul').length === 0) {
2306
                self._addError('<ul>' + msg + '</ul>');
2307
            } else {
2308
                $error.find('ul').append(msg);
2309
            }
2310
            self.isError = true;
2311
            self._updateFileDetails(0);
2312
            $error.fadeIn(self.fadeDelay);
2313
            self._raise('fileerror', [params, msg]);
2314
            self._clearFileInput();
2315
            self._setValidationError();
2316
        },
2317
        _parseError: function (operation, jqXHR, errorThrown, fileName) {
2318
            /** @namespace jqXHR.responseJSON */
2319
            var self = this, errMsg = $.trim(errorThrown + ''), textPre, errText, text;
2320
            errText = jqXHR.responseJSON && jqXHR.responseJSON.error ? jqXHR.responseJSON.error.toString() : '';
2321
            text = errText ? errText : jqXHR.responseText;
2322
            if (self.cancelling && self.msgUploadAborted) {
2323
                errMsg = self.msgUploadAborted;
2324
            }
2325
            if (self.showAjaxErrorDetails && text) {
2326
                if (errText) {
2327
                    errMsg = $.trim(errText + '');
2328
                } else {
2329
                    text = $.trim(text.replace(/\n\s*\n/g, '\n'));
2330
                    textPre = text.length ? '<pre>' + text + '</pre>' : '';
2331
                    errMsg += errMsg ? textPre : text;
2332
                }
2333
            }
2334
            if (!errMsg) {
2335
                errMsg = self.msgAjaxError.replace('{operation}', operation);
2336
            }
2337
            self.cancelling = false;
2338
            return fileName ? '<b>' + fileName + ': </b>' + errMsg : errMsg;
2339
        },
2340
        _parseFileType: function (type, name) {
2341
            var self = this, isValid, vType, cat, i, types = self.allowedPreviewTypes || [];
2342
            if (type === 'application/text-plain') {
2343
                return 'text';
2344
            }
2345
            for (i = 0; i < types.length; i++) {
2346
                cat = types[i];
2347
                isValid = self.fileTypeSettings[cat];
2348
                vType = isValid(type, name) ? cat : '';
2349
                if (!$h.isEmpty(vType)) {
2350
                    return vType;
2351
                }
2352
            }
2353
            return 'other';
2354
        },
2355
        _getPreviewIcon: function (fname) {
2356
            var self = this, ext, out = null;
2357
            if (fname && fname.indexOf('.') > -1) {
2358
                ext = fname.split('.').pop();
2359
                if (self.previewFileIconSettings) {
2360
                    out = self.previewFileIconSettings[ext] || self.previewFileIconSettings[ext.toLowerCase()] || null;
2361
                }
2362
                if (self.previewFileExtSettings) {
2363
                    $.each(self.previewFileExtSettings, function (key, func) {
2364
                        if (self.previewFileIconSettings[key] && func(ext)) {
2365
                            out = self.previewFileIconSettings[key];
2366
                            //noinspection UnnecessaryReturnStatementJS
2367
                            return;
2368
                        }
2369
                    });
2370
                }
2371
            }
2372
            return out || self.previewFileIcon;
2373
        },
2374
        _parseFilePreviewIcon: function (content, fname) {
2375
            var self = this, icn = self._getPreviewIcon(fname), out = content;
2376
            if (out.indexOf('{previewFileIcon}') > -1) {
2377
                out = out.setTokens({'previewFileIconClass': self.previewFileIconClass, 'previewFileIcon': icn});
2378
            }
2379
            return out;
2380
        },
2381
        _raise: function (event, params) {
2382
            var self = this, e = $.Event(event);
2383
            if (params !== undefined) {
2384
                self.$element.trigger(e, params);
2385
            } else {
2386
                self.$element.trigger(e);
2387
            }
2388
            if (e.isDefaultPrevented() || e.result === false) {
2389
                return false;
2390
            }
2391
            switch (event) {
2392
                // ignore these events
2393
                case 'filebatchuploadcomplete':
2394
                case 'filebatchuploadsuccess':
2395
                case 'fileuploaded':
2396
                case 'fileclear':
2397
                case 'filecleared':
2398
                case 'filereset':
2399
                case 'fileerror':
2400
                case 'filefoldererror':
2401
                case 'fileuploaderror':
2402
                case 'filebatchuploaderror':
2403
                case 'filedeleteerror':
2404
                case 'filecustomerror':
2405
                case 'filesuccessremove':
2406
                    break;
2407
                // receive data response via `filecustomerror` event`
2408
                default:
2409
                    if (!self.ajaxAborted) {
2410
                        self.ajaxAborted = e.result;
2411
                    }
2412
                    break;
2413
            }
2414
            return true;
2415
        },
2416
        _listenFullScreen: function (isFullScreen) {
2417
            var self = this, $modal = self.$modal, $btnFull, $btnBord;
2418
            if (!$modal || !$modal.length) {
2419
                return;
2420
            }
2421
            $btnFull = $modal && $modal.find('.btn-fullscreen');
2422
            $btnBord = $modal && $modal.find('.btn-borderless');
2423
            if (!$btnFull.length || !$btnBord.length) {
2424
                return;
2425
            }
2426
            $btnFull.removeClass('active').attr('aria-pressed', 'false');
2427
            $btnBord.removeClass('active').attr('aria-pressed', 'false');
2428
            if (isFullScreen) {
2429
                $btnFull.addClass('active').attr('aria-pressed', 'true');
2430
            } else {
2431
                $btnBord.addClass('active').attr('aria-pressed', 'true');
2432
            }
2433
            if ($modal.hasClass('file-zoom-fullscreen')) {
2434
                self._maximizeZoomDialog();
2435
            } else {
2436
                if (isFullScreen) {
2437
                    self._maximizeZoomDialog();
2438
                } else {
2439
                    $btnBord.removeClass('active').attr('aria-pressed', 'false');
2440
                }
2441
            }
2442
        },
2443
        _listen: function () {
2444
            var self = this, $el = self.$element, $form = self.$form, $cont = self.$container, fullScreenEv, $cap, fn;
2445
            self._handler($el, 'click', function (e) {
2446
                if ($el.hasClass('file-no-browse')) {
2447
                    if ($el.data('zoneClicked')) {
2448
                        $el.data('zoneClicked', false);
2449
                    } else {
2450
                        e.preventDefault();
2451
                    }
2452
                }
2453
            });
2454
            self._handler($el, 'change', $.proxy(self._change, self));
2455
            if (self.showBrowse) {
2456
                self._handler(self.$btnFile, 'click', $.proxy(self._browse, self));
2457
            }
2458
            $cap = $cont.find('.file-caption-name');
2459
            fn = function () {
2460
                return false;
2461
            };
2462
            self._handler($cont.find('.fileinput-remove:not([disabled])'), 'click', $.proxy(self.clear, self));
2463
            self._handler($cont.find('.fileinput-cancel'), 'click', $.proxy(self.cancel, self));
2464
            self._handler($cont.find('.fileinput-pause'), 'click', $.proxy(self.pause, self));
2465
            self._handler($cap, 'keydown', fn);
2466
            self._handler($cap, 'paste', fn);
2467
            self._initDragDrop();
2468
            self._handler($form, 'reset', $.proxy(self.clear, self));
2469
            if (!self.isAjaxUpload) {
2470
                self._handler($form, 'submit', $.proxy(self._submitForm, self));
2471
            }
2472
            self._handler(self.$container.find('.fileinput-upload'), 'click', $.proxy(self._uploadClick, self));
2473
            self._handler($(window), 'resize', function () {
2474
                self._listenFullScreen(screen.width === window.innerWidth && screen.height === window.innerHeight);
2475
            });
2476
            fullScreenEv = 'webkitfullscreenchange mozfullscreenchange fullscreenchange MSFullscreenChange';
2477
            self._handler($(document), fullScreenEv, function () {
2478
                self._listenFullScreen($h.checkFullScreen());
2479
            });
2480
            self._autoFitContent();
2481
            self._initClickable();
2482
            self._refreshPreview();
2483
        },
2484
        _autoFitContent: function () {
2485
            var width = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth,
2486
                self = this, config = width < 400 ? (self.previewSettingsSmall || self.defaults.previewSettingsSmall) :
2487
                (self.previewSettings || self.defaults.previewSettings), sel;
2488
            $.each(config, function (cat, settings) {
2489
                sel = '.file-preview-frame .file-preview-' + cat;
2490
                self.$preview.find(sel + '.kv-preview-data,' + sel + ' .kv-preview-data').css(settings);
2491
            });
2492
        },
2493
        _scanDroppedItems: function (item, files, path) {
2494
            path = path || '';
2495
            var self = this, i, dirReader, readDir, errorHandler = function (e) {
2496
                self._log($h.logMessages.badDroppedFiles);
2497
                self._log(e);
2498
            };
2499
            if (item.isFile) {
2500
                item.file(function (file) {
2501
                    if (path) {
2502
                        file.newPath = path + file.name;
2503
                    }
2504
                    files.push(file);
2505
                }, errorHandler);
2506
            } else {
2507
                if (item.isDirectory) {
2508
                    dirReader = item.createReader();
2509
                    readDir = function () {
2510
                        dirReader.readEntries(function (entries) {
2511
                            if (entries && entries.length > 0) {
2512
                                for (i = 0; i < entries.length; i++) {
2513
                                    self._scanDroppedItems(entries[i], files, path + item.name + '/');
2514
                                }
2515
                                // recursively call readDir() again, since browser can only handle first 100 entries.
2516
                                readDir();
2517
                            }
2518
                            return null;
2519
                        }, errorHandler);
2520
                    };
2521
                    readDir();
2522
                }
2523
            }
2524
 
2525
        },
2526
        _initDragDrop: function () {
2527
            var self = this, $zone = self.$dropZone;
2528
            if (self.dropZoneEnabled && self.showPreview) {
2529
                self._handler($zone, 'dragenter dragover', $.proxy(self._zoneDragEnter, self));
2530
                self._handler($zone, 'dragleave', $.proxy(self._zoneDragLeave, self));
2531
                self._handler($zone, 'drop', $.proxy(self._zoneDrop, self));
2532
                self._handler($(document), 'dragenter dragover drop', self._zoneDragDropInit);
2533
            }
2534
        },
2535
        _zoneDragDropInit: function (e) {
2536
            e.stopPropagation();
2537
            e.preventDefault();
2538
        },
2539
        _zoneDragEnter: function (e) {
2540
            var self = this, dataTransfer = e.originalEvent.dataTransfer,
2541
                hasFiles = $.inArray('Files', dataTransfer.types) > -1;
2542
            self._zoneDragDropInit(e);
2543
            if (self.isDisabled || !hasFiles) {
2544
                e.originalEvent.dataTransfer.effectAllowed = 'none';
2545
                e.originalEvent.dataTransfer.dropEffect = 'none';
2546
                return;
2547
            }
2548
            if (self._raise('fileDragEnter', {'sourceEvent': e, 'files': dataTransfer.types.Files})) {
2549
                $h.addCss(self.$dropZone, 'file-highlighted');
2550
            }
2551
        },
2552
        _zoneDragLeave: function (e) {
2553
            var self = this;
2554
            self._zoneDragDropInit(e);
2555
            if (self.isDisabled) {
2556
                return;
2557
            }
2558
            if (self._raise('fileDragLeave', {'sourceEvent': e})) {
2559
                self.$dropZone.removeClass('file-highlighted');
2560
            }
2561
 
2562
        },
2563
        _zoneDrop: function (e) {
2564
            /** @namespace e.originalEvent.dataTransfer */
2565
            var self = this, i, $el = self.$element, dataTransfer = e.originalEvent.dataTransfer,
2566
                files = dataTransfer.files, items = dataTransfer.items, folders = $h.getDragDropFolders(items),
2567
                processFiles = function () {
2568
                    if (!self.isAjaxUpload) {
2569
                        self.changeTriggered = true;
2570
                        $el.get(0).files = files;
2571
                        setTimeout(function () {
2572
                            self.changeTriggered = false;
2573
                            $el.trigger('change' + self.namespace);
2574
                        }, self.processDelay);
2575
                    } else {
2576
                        self._change(e, files);
2577
                    }
2578
                    self.$dropZone.removeClass('file-highlighted');
2579
                };
2580
            e.preventDefault();
2581
            if (self.isDisabled || $h.isEmpty(files)) {
2582
                return;
2583
            }
2584
            if (!self._raise('fileDragDrop', {'sourceEvent': e, 'files': files})) {
2585
                return;
2586
            }
2587
            if (folders > 0) {
2588
                if (!self.isAjaxUpload) {
2589
                    self._showFolderError(folders);
2590
                    return;
2591
                }
2592
                files = [];
2593
                for (i = 0; i < items.length; i++) {
2594
                    var item = items[i].webkitGetAsEntry();
2595
                    if (item) {
2596
                        self._scanDroppedItems(item, files);
2597
                    }
2598
                }
2599
                setTimeout(function () {
2600
                    processFiles();
2601
                }, 500);
2602
            } else {
2603
                processFiles();
2604
            }
2605
        },
2606
        _uploadClick: function (e) {
2607
            var self = this, $btn = self.$container.find('.fileinput-upload'), $form,
2608
                isEnabled = !$btn.hasClass('disabled') && $h.isEmpty($btn.attr('disabled'));
2609
            if (e && e.isDefaultPrevented()) {
2610
                return;
2611
            }
2612
            if (!self.isAjaxUpload) {
2613
                if (isEnabled && $btn.attr('type') !== 'submit') {
2614
                    $form = $btn.closest('form');
2615
                    // downgrade to normal form submit if possible
2616
                    if ($form.length) {
2617
                        $form.trigger('submit');
2618
                    }
2619
                    e.preventDefault();
2620
                }
2621
                return;
2622
            }
2623
            e.preventDefault();
2624
            if (isEnabled) {
2625
                self.upload();
2626
            }
2627
        },
2628
        _submitForm: function () {
2629
            var self = this;
2630
            return self._isFileSelectionValid() && !self._abort({});
2631
        },
2632
        _clearPreview: function () {
2633
            var self = this,
2634
                $thumbs = self.showUploadedThumbs ? self.getFrames(':not(.file-preview-success)') : self.getFrames();
2635
            $thumbs.each(function () {
2636
                var $thumb = $(this);
2637
                $thumb.remove();
2638
            });
2639
            if (!self.getFrames().length || !self.showPreview) {
2640
                self._resetUpload();
2641
            }
2642
            self._validateDefaultPreview();
2643
        },
2644
        _initSortable: function () {
2645
            var self = this, $el = self.$preview, settings, selector = '.' + $h.SORT_CSS, $cont, $body = $('body'),
2646
                $html = $('html'), rev = self.reversePreviewOrder, Sortable = window.Sortable, beginGrab, endGrab;
2647
            if (!Sortable || $el.find(selector).length === 0) {
2648
                return;
2649
            }
2650
            $cont = $body.length ? $body : ($html.length ? $html : self.$container);
2651
            beginGrab = function () {
2652
                $cont.addClass('file-grabbing');
2653
            };
2654
            endGrab = function () {
2655
                $cont.removeClass('file-grabbing');
2656
            };
2657
            settings = {
2658
                handle: '.drag-handle-init',
2659
                dataIdAttr: 'data-fileid',
2660
                animation: 600,
2661
                draggable: selector,
2662
                scroll: false,
2663
                forceFallback: true,
2664
                onChoose: beginGrab,
2665
                onStart: beginGrab,
2666
                onUnchoose: endGrab,
2667
                onEnd: endGrab,
2668
                onSort: function (e) {
2669
                    var oldIndex = e.oldIndex, newIndex = e.newIndex, i = 0, len = self.initialPreviewConfig.length,
2670
                        exceedsLast = len > 0 && newIndex >= len, $item = $(e.item), $first;
2671
                    if (exceedsLast) {
2672
                        newIndex = len - 1;
2673
                    }
2674
                    self.initialPreview = $h.moveArray(self.initialPreview, oldIndex, newIndex, rev);
2675
                    self.initialPreviewConfig = $h.moveArray(self.initialPreviewConfig, oldIndex, newIndex, rev);
2676
                    self.previewCache.init();
2677
                    self.getFrames('.file-preview-initial').each(function () {
2678
                        $(this).attr('data-fileindex', $h.INIT_FLAG + i);
2679
                        i++;
2680
                    });
2681
                    if (exceedsLast) {
2682
                        $first = self.getFrames(':not(.file-preview-initial):first');
2683
                        if ($first.length) {
2684
                            $item.slideUp(function () {
2685
                                $item.insertBefore($first).slideDown();
2686
                            });
2687
                        }
2688
                    }
2689
                    self._raise('filesorted', {
2690
                        previewId: $item.attr('id'),
2691
                        'oldIndex': oldIndex,
2692
                        'newIndex': newIndex,
2693
                        stack: self.initialPreviewConfig
2694
                    });
2695
                },
2696
            };
2697
            $.extend(true, settings, self.fileActionSettings.dragSettings);
2698
            if (self.sortable) {
2699
                self.sortable.destroy();
2700
            }
2701
            self.sortable = Sortable.create($el[0], settings);
2702
        },
2703
        _setPreviewContent: function (content) {
2704
            var self = this;
2705
            $h.setHtml(self.$preview, content);
2706
            self._autoFitContent();
2707
        },
2708
        _initPreviewImageOrientations: function () {
2709
            var self = this, i = 0, canOrientImage = self.canOrientImage;
2710
            if (!self.autoOrientImageInitial && !canOrientImage) {
2711
                return;
2712
            }
2713
            self.getFrames('.file-preview-initial').each(function () {
2714
                var $thumb = $(this), $img, $zoomImg, id, config = self.initialPreviewConfig[i];
2715
                /** @namespace config.exif */
2716
                if (config && config.exif && config.exif.Orientation) {
2717
                    id = $thumb.attr('id');
2718
                    $img = $thumb.find('>.kv-file-content img');
2719
                    $zoomImg = self._getZoom(id, ' >.kv-file-content img');
2720
                    if (canOrientImage) {
2721
                        $img.css('image-orientation', (self.autoOrientImageInitial ? 'from-image' : 'none'));
2722
                    } else {
2723
                        self.setImageOrientation($img, $zoomImg, config.exif.Orientation, $thumb);
2724
                    }
2725
                }
2726
                i++;
2727
            });
2728
        },
2729
        _initPreview: function (isInit) {
2730
            var self = this, cap = self.initialCaption || '', out;
2731
            if (!self.previewCache.count(true)) {
2732
                self._clearPreview();
2733
                if (isInit) {
2734
                    self._setCaption(cap);
2735
                } else {
2736
                    self._initCaption();
2737
                }
2738
                return;
2739
            }
2740
            out = self.previewCache.out();
2741
            cap = isInit && self.initialCaption ? self.initialCaption : out.caption;
2742
            self._setPreviewContent(out.content);
2743
            self._setInitThumbAttr();
2744
            self._setCaption(cap);
2745
            self._initSortable();
2746
            if (!$h.isEmpty(out.content)) {
2747
                self.$container.removeClass('file-input-new');
2748
            }
2749
            self._initPreviewImageOrientations();
2750
        },
2751
        _getZoomButton: function (type) {
2752
            var self = this, label = self.previewZoomButtonIcons[type], css = self.previewZoomButtonClasses[type],
2753
                title = ' title="' + (self.previewZoomButtonTitles[type] || '') + '" ',
2754
                params = title + (type === 'close' ? ' data-dismiss="modal" aria-hidden="true"' : '');
2755
            if (type === 'fullscreen' || type === 'borderless' || type === 'toggleheader') {
2756
                params += ' data-toggle="button" aria-pressed="false" autocomplete="off"';
2757
            }
2758
            return '<button type="button" class="' + css + ' btn-' + type + '"' + params + '>' + label + '</button>';
2759
        },
2760
        _getModalContent: function () {
2761
            var self = this;
2762
            return self._getLayoutTemplate('modal').setTokens({
2763
                'rtl': self.rtl ? ' kv-rtl' : '',
2764
                'zoomFrameClass': self.frameClass,
2765
                'heading': self.msgZoomModalHeading,
2766
                'prev': self._getZoomButton('prev'),
2767
                'next': self._getZoomButton('next'),
2768
                'toggleheader': self._getZoomButton('toggleheader'),
2769
                'fullscreen': self._getZoomButton('fullscreen'),
2770
                'borderless': self._getZoomButton('borderless'),
2771
                'close': self._getZoomButton('close')
2772
            });
2773
        },
2774
        _listenModalEvent: function (event) {
2775
            var self = this, $modal = self.$modal, getParams = function (e) {
2776
                return {
2777
                    sourceEvent: e,
2778
                    previewId: $modal.data('previewId'),
2779
                    modal: $modal
2780
                };
2781
            };
2782
            $modal.on(event + '.bs.modal', function (e) {
2783
                var $btnFull = $modal.find('.btn-fullscreen'), $btnBord = $modal.find('.btn-borderless');
2784
                if ($modal.data('fileinputPluginId') === self.$element.attr('id')) {
2785
                    self._raise('filezoom' + event, getParams(e));
2786
                }
2787
                if (event === 'shown') {
2788
                    $btnBord.removeClass('active').attr('aria-pressed', 'false');
2789
                    $btnFull.removeClass('active').attr('aria-pressed', 'false');
2790
                    if ($modal.hasClass('file-zoom-fullscreen')) {
2791
                        self._maximizeZoomDialog();
2792
                        if ($h.checkFullScreen()) {
2793
                            $btnFull.addClass('active').attr('aria-pressed', 'true');
2794
                        } else {
2795
                            $btnBord.addClass('active').attr('aria-pressed', 'true');
2796
                        }
2797
                    }
2798
                }
2799
            });
2800
        },
2801
        _initZoom: function () {
2802
            var self = this, $dialog, modalMain = self._getLayoutTemplate('modalMain'), modalId = '#' + $h.MODAL_ID;
2803
            if (!self.showPreview) {
2804
                return;
2805
            }
2806
            self.$modal = $(modalId);
2807
            if (!self.$modal || !self.$modal.length) {
2808
                $dialog = $h.createElement($h.cspBuffer.stash(modalMain)).insertAfter(self.$container);
2809
                self.$modal = $(modalId).insertBefore($dialog);
2810
                $h.cspBuffer.apply(self.$modal);
2811
                $dialog.remove();
2812
            }
2813
            $h.initModal(self.$modal);
2814
            self.$modal.html($h.cspBuffer.stash(self._getModalContent()));
2815
            $h.cspBuffer.apply(self.$modal);
2816
            $.each($h.MODAL_EVENTS, function (key, event) {
2817
                self._listenModalEvent(event);
2818
            });
2819
        },
2820
        _initZoomButtons: function () {
2821
            var self = this, previewId = self.$modal.data('previewId') || '', $first, $last,
2822
                thumbs = self.getFrames().toArray(), len = thumbs.length, $prev = self.$modal.find('.btn-prev'),
2823
                $next = self.$modal.find('.btn-next');
2824
            if (thumbs.length < 2) {
2825
                $prev.hide();
2826
                $next.hide();
2827
                return;
2828
            } else {
2829
                $prev.show();
2830
                $next.show();
2831
            }
2832
            if (!len) {
2833
                return;
2834
            }
2835
            $first = $(thumbs[0]);
2836
            $last = $(thumbs[len - 1]);
2837
            $prev.removeAttr('disabled');
2838
            $next.removeAttr('disabled');
2839
            if ($first.length && $first.attr('id') === previewId) {
2840
                $prev.attr('disabled', true);
2841
            }
2842
            if ($last.length && $last.attr('id') === previewId) {
2843
                $next.attr('disabled', true);
2844
            }
2845
        },
2846
        _maximizeZoomDialog: function () {
2847
            var self = this, $modal = self.$modal, $head = $modal.find('.modal-header:visible'),
2848
                $foot = $modal.find('.modal-footer:visible'), $body = $modal.find('.modal-body'),
2849
                h = $(window).height(), diff = 0;
2850
            $modal.addClass('file-zoom-fullscreen');
2851
            if ($head && $head.length) {
2852
                h -= $head.outerHeight(true);
2853
            }
2854
            if ($foot && $foot.length) {
2855
                h -= $foot.outerHeight(true);
2856
            }
2857
            if ($body && $body.length) {
2858
                diff = $body.outerHeight(true) - $body.height();
2859
                h -= diff;
2860
            }
2861
            $modal.find('.kv-zoom-body').height(h);
2862
        },
2863
        _resizeZoomDialog: function (fullScreen) {
2864
            var self = this, $modal = self.$modal, $btnFull = $modal.find('.btn-fullscreen'),
2865
                $btnBord = $modal.find('.btn-borderless');
2866
            if ($modal.hasClass('file-zoom-fullscreen')) {
2867
                $h.toggleFullScreen(false);
2868
                if (!fullScreen) {
2869
                    if (!$btnFull.hasClass('active')) {
2870
                        $modal.removeClass('file-zoom-fullscreen');
2871
                        self.$modal.find('.kv-zoom-body').css('height', self.zoomModalHeight);
2872
                    } else {
2873
                        $btnFull.removeClass('active').attr('aria-pressed', 'false');
2874
                    }
2875
                } else {
2876
                    if (!$btnFull.hasClass('active')) {
2877
                        $modal.removeClass('file-zoom-fullscreen');
2878
                        self._resizeZoomDialog(true);
2879
                        if ($btnBord.hasClass('active')) {
2880
                            $btnBord.removeClass('active').attr('aria-pressed', 'false');
2881
                        }
2882
                    }
2883
                }
2884
            } else {
2885
                if (!fullScreen) {
2886
                    self._maximizeZoomDialog();
2887
                    return;
2888
                }
2889
                $h.toggleFullScreen(true);
2890
            }
2891
            $modal.focus();
2892
        },
2893
        _setZoomContent: function ($frame, animate) {
2894
            var self = this, $content, tmplt, body, title, $body, $dataEl, config, previewId = $frame.attr('id'),
2895
                $zoomPreview = self._getZoom(previewId), $modal = self.$modal, $tmp,
2896
                $btnFull = $modal.find('.btn-fullscreen'), $btnBord = $modal.find('.btn-borderless'), cap, size,
2897
                $btnTogh = $modal.find('.btn-toggleheader');
2898
            tmplt = $zoomPreview.attr('data-template') || 'generic';
2899
            $content = $zoomPreview.find('.kv-file-content');
2900
            body = $content.length ? $content.html() : '';
2901
            cap = $frame.data('caption') || '';
2902
            size = $frame.data('size') || '';
2903
            title = cap + ' ' + size;
2904
            $modal.find('.kv-zoom-title').attr('title', $('<div/>').html(title).text()).html(title);
2905
            $body = $modal.find('.kv-zoom-body');
2906
            $modal.removeClass('kv-single-content');
2907
            if (animate) {
2908
                $tmp = $body.addClass('file-thumb-loading').clone().insertAfter($body);
2909
                $h.setHtml($body, body).hide();
2910
                $tmp.fadeOut('fast', function () {
2911
                    $body.fadeIn('fast', function () {
2912
                        $body.removeClass('file-thumb-loading');
2913
                    });
2914
                    $tmp.remove();
2915
                });
2916
            } else {
2917
                $h.setHtml($body, body);
2918
            }
2919
            config = self.previewZoomSettings[tmplt];
2920
            if (config) {
2921
                $dataEl = $body.find('.kv-preview-data');
2922
                $h.addCss($dataEl, 'file-zoom-detail');
2923
                $.each(config, function (key, value) {
2924
                    $dataEl.css(key, value);
2925
                    if (($dataEl.attr('width') && key === 'width') || ($dataEl.attr('height') && key === 'height')) {
2926
                        $dataEl.removeAttr(key);
2927
                    }
2928
                });
2929
            }
2930
            $modal.data('previewId', previewId);
2931
            self._handler($modal.find('.btn-prev'), 'click', function () {
2932
                self._zoomSlideShow('prev', previewId);
2933
            });
2934
            self._handler($modal.find('.btn-next'), 'click', function () {
2935
                self._zoomSlideShow('next', previewId);
2936
            });
2937
            self._handler($btnFull, 'click', function () {
2938
                self._resizeZoomDialog(true);
2939
            });
2940
            self._handler($btnBord, 'click', function () {
2941
                self._resizeZoomDialog(false);
2942
            });
2943
            self._handler($btnTogh, 'click', function () {
2944
                var $header = $modal.find('.modal-header'), $floatBar = $modal.find('.modal-body .floating-buttons'),
2945
                    ht, $actions = $header.find('.kv-zoom-actions'), resize = function (height) {
2946
                        var $body = self.$modal.find('.kv-zoom-body'), h = self.zoomModalHeight;
2947
                        if ($modal.hasClass('file-zoom-fullscreen')) {
2948
                            h = $body.outerHeight(true);
2949
                            if (!height) {
2950
                                h = h - $header.outerHeight(true);
2951
                            }
2952
                        }
2953
                        $body.css('height', height ? h + height : h);
2954
                    };
2955
                if ($header.is(':visible')) {
2956
                    ht = $header.outerHeight(true);
2957
                    $header.slideUp('slow', function () {
2958
                        $actions.find('.btn').appendTo($floatBar);
2959
                        resize(ht);
2960
                    });
2961
                } else {
2962
                    $floatBar.find('.btn').appendTo($actions);
2963
                    $header.slideDown('slow', function () {
2964
                        resize();
2965
                    });
2966
                }
2967
                $modal.focus();
2968
            });
2969
            self._handler($modal, 'keydown', function (e) {
2970
                var key = e.which || e.keyCode, $prev = $(this).find('.btn-prev'), $next = $(this).find('.btn-next'),
2971
                    vId = $(this).data('previewId'), vPrevKey = self.rtl ? 39 : 37, vNextKey = self.rtl ? 37 : 39;
2972
                if (key === vPrevKey && $prev.length && !$prev.attr('disabled')) {
2973
                    self._zoomSlideShow('prev', vId);
2974
                }
2975
                if (key === vNextKey && $next.length && !$next.attr('disabled')) {
2976
                    self._zoomSlideShow('next', vId);
2977
                }
2978
            });
2979
        },
2980
        _showModal: function ($frame) {
2981
            var self = this, $modal = self.$modal;
2982
            if (!$frame || !$frame.length) {
2983
                return;
2984
            }
2985
            $h.initModal($modal);
2986
            $h.setHtml($modal, self._getModalContent());
2987
            self._setZoomContent($frame);
2988
            $modal.data('fileinputPluginId', self.$element.attr('id'));
2989
            $modal.modal('show');
2990
            self._initZoomButtons();
2991
        },
2992
        _zoomPreview: function ($btn) {
2993
            var self = this, $frame;
2994
            if (!$btn.length) {
2995
                throw 'Cannot zoom to detailed preview!';
2996
            }
2997
            $frame = $btn.closest($h.FRAMES);
2998
            self._showModal($frame);
2999
        },
3000
        _zoomSlideShow: function (dir, previewId) {
3001
            var self = this, $btn = self.$modal.find('.kv-zoom-actions .btn-' + dir), $targFrame, i, $thumb,
3002
                thumbsData = self.getFrames().toArray(), thumbs = [], len = thumbsData.length, out;
3003
            if ($btn.attr('disabled')) {
3004
                return;
3005
            }
3006
            for (i = 0; i < len; i++) {
3007
                $thumb = $(thumbsData[i]);
3008
                if ($thumb && $thumb.length && $thumb.find('.kv-file-zoom:visible').length) {
3009
                    thumbs.push(thumbsData[i]);
3010
                }
3011
            }
3012
            len = thumbs.length;
3013
            for (i = 0; i < len; i++) {
3014
                if ($(thumbs[i]).attr('id') === previewId) {
3015
                    out = dir === 'prev' ? i - 1 : i + 1;
3016
                    break;
3017
                }
3018
            }
3019
            if (out < 0 || out >= len || !thumbs[out]) {
3020
                return;
3021
            }
3022
            $targFrame = $(thumbs[out]);
3023
            if ($targFrame.length) {
3024
                self._setZoomContent($targFrame, true);
3025
            }
3026
            self._initZoomButtons();
3027
            self._raise('filezoom' + dir, {'previewId': previewId, modal: self.$modal});
3028
        },
3029
        _initZoomButton: function () {
3030
            var self = this;
3031
            self.$preview.find('.kv-file-zoom').each(function () {
3032
                var $el = $(this);
3033
                self._handler($el, 'click', function () {
3034
                    self._zoomPreview($el);
3035
                });
3036
            });
3037
        },
3038
        _inputFileCount: function () {
3039
            return this.$element[0].files.length;
3040
        },
3041
        _refreshPreview: function () {
3042
            var self = this, files;
3043
            if ((!self._inputFileCount() && !self.isAjaxUpload) || !self.showPreview || !self.isPreviewable) {
3044
                return;
3045
            }
3046
            if (self.isAjaxUpload) {
3047
                if (self.fileManager.count() > 0) {
3048
                    files = $.extend(true, {}, self.fileManager.stack);
3049
                    self.fileManager.clear();
3050
                    self._clearFileInput();
3051
                } else {
3052
                    files = self.$element[0].files;
3053
                }
3054
            } else {
3055
                files = self.$element[0].files;
3056
            }
3057
            if (files && files.length) {
3058
                self.readFiles(files);
3059
                self._setFileDropZoneTitle();
3060
            }
3061
        },
3062
        _clearObjects: function ($el) {
3063
            $el.find('video audio').each(function () {
3064
                this.pause();
3065
                $(this).remove();
3066
            });
3067
            $el.find('img object div').each(function () {
3068
                $(this).remove();
3069
            });
3070
        },
3071
        _clearFileInput: function () {
3072
            var self = this, $el = self.$element, $srcFrm, $tmpFrm, $tmpEl;
3073
            if (!self._inputFileCount()) {
3074
                return;
3075
            }
3076
            $srcFrm = $el.closest('form');
3077
            $tmpFrm = $(document.createElement('form'));
3078
            $tmpEl = $(document.createElement('div'));
3079
            $el.before($tmpEl);
3080
            if ($srcFrm.length) {
3081
                $srcFrm.after($tmpFrm);
3082
            } else {
3083
                $tmpEl.after($tmpFrm);
3084
            }
3085
            $tmpFrm.append($el).trigger('reset');
3086
            $tmpEl.before($el).remove();
3087
            $tmpFrm.remove();
3088
        },
3089
        _resetUpload: function () {
3090
            var self = this;
3091
            self.uploadStartTime = $h.now();
3092
            self.uploadCache = [];
3093
            self.$btnUpload.removeAttr('disabled');
3094
            self._setProgress(0);
3095
            self._hideProgress();
3096
            self._resetErrors(false);
3097
            self._initAjax();
3098
            self.fileManager.clearImages();
3099
            self._resetCanvas();
3100
            if (self.overwriteInitial) {
3101
                self.initialPreview = [];
3102
                self.initialPreviewConfig = [];
3103
                self.initialPreviewThumbTags = [];
3104
                self.previewCache.data = {
3105
                    content: [],
3106
                    config: [],
3107
                    tags: []
3108
                };
3109
            }
3110
        },
3111
        _resetCanvas: function () {
3112
            var self = this;
3113
            if (self.canvas && self.imageCanvasContext) {
3114
                self.imageCanvasContext.clearRect(0, 0, self.canvas.width, self.canvas.height);
3115
            }
3116
        },
3117
        _hasInitialPreview: function () {
3118
            var self = this;
3119
            return !self.overwriteInitial && self.previewCache.count(true);
3120
        },
3121
        _resetPreview: function () {
3122
            var self = this, out, cap, $div, hasSuc = self.showUploadedThumbs, hasErr = !self.removeFromPreviewOnError,
3123
                includeProcessed = (hasSuc || hasErr) && self.isDuplicateError;
3124
            if (self.previewCache.count(true)) {
3125
                out = self.previewCache.out();
3126
                if (includeProcessed) {
3127
                    $div = $h.createElement('').insertAfter(self.$container);
3128
                    self.getFrames().each(function () {
3129
                        var $thumb = $(this);
3130
                        if ((hasSuc && $thumb.hasClass('file-preview-success')) ||
3131
                            (hasErr && $thumb.hasClass('file-preview-error'))) {
3132
                            $div.append($thumb);
3133
                        }
3134
                    });
3135
                }
3136
                self._setPreviewContent(out.content);
3137
                self._setInitThumbAttr();
3138
                cap = self.initialCaption ? self.initialCaption : out.caption;
3139
                self._setCaption(cap);
3140
                if (includeProcessed) {
3141
                    $div.contents().appendTo(self.$preview);
3142
                    $div.remove();
3143
                }
3144
            } else {
3145
                self._clearPreview();
3146
                self._initCaption();
3147
            }
3148
            if (self.showPreview) {
3149
                self._initZoom();
3150
                self._initSortable();
3151
            }
3152
            self.isDuplicateError = false;
3153
        },
3154
        _clearDefaultPreview: function () {
3155
            var self = this;
3156
            self.$preview.find('.file-default-preview').remove();
3157
        },
3158
        _validateDefaultPreview: function () {
3159
            var self = this;
3160
            if (!self.showPreview || $h.isEmpty(self.defaultPreviewContent)) {
3161
                return;
3162
            }
3163
            self._setPreviewContent('<div class="file-default-preview">' + self.defaultPreviewContent + '</div>');
3164
            self.$container.removeClass('file-input-new');
3165
            self._initClickable();
3166
        },
3167
        _resetPreviewThumbs: function (isAjax) {
3168
            var self = this, out;
3169
            if (isAjax) {
3170
                self._clearPreview();
3171
                self.clearFileStack();
3172
                return;
3173
            }
3174
            if (self._hasInitialPreview()) {
3175
                out = self.previewCache.out();
3176
                self._setPreviewContent(out.content);
3177
                self._setInitThumbAttr();
3178
                self._setCaption(out.caption);
3179
                self._initPreviewActions();
3180
            } else {
3181
                self._clearPreview();
3182
            }
3183
        },
3184
        _getLayoutTemplate: function (t) {
3185
            var self = this, template = self.layoutTemplates[t];
3186
            if ($h.isEmpty(self.customLayoutTags)) {
3187
                return template;
3188
            }
3189
            return $h.replaceTags(template, self.customLayoutTags);
3190
        },
3191
        _getPreviewTemplate: function (t) {
3192
            var self = this, templates = self.previewTemplates, template = templates[t] || templates.other;
3193
            if ($h.isEmpty(self.customPreviewTags)) {
3194
                return template;
3195
            }
3196
            return $h.replaceTags(template, self.customPreviewTags);
3197
        },
3198
        _getOutData: function (formdata, jqXHR, responseData, filesData) {
3199
            var self = this;
3200
            jqXHR = jqXHR || {};
3201
            responseData = responseData || {};
3202
            filesData = filesData || self.fileManager.list();
3203
            return {
3204
                formdata: formdata,
3205
                files: filesData,
3206
                filenames: self.filenames,
3207
                filescount: self.getFilesCount(),
3208
                extra: self._getExtraData(),
3209
                response: responseData,
3210
                reader: self.reader,
3211
                jqXHR: jqXHR
3212
            };
3213
        },
3214
        _getMsgSelected: function (n) {
3215
            var self = this, strFiles = n === 1 ? self.fileSingle : self.filePlural;
3216
            return n > 0 ? self.msgSelected.replace('{n}', n).replace('{files}', strFiles) : self.msgNoFilesSelected;
3217
        },
3218
        _getFrame: function (id, skipWarning) {
3219
            var self = this, $frame = $h.getFrameElement(self.$preview, id);
3220
            if (self.showPreview && !skipWarning && !$frame.length) {
3221
                self._log($h.logMessages.invalidThumb, {id: id});
3222
            }
3223
            return $frame;
3224
        },
3225
        _getZoom: function (id, selector) {
3226
            var self = this, $frame = $h.getZoomElement(self.$preview, id, selector);
3227
            if (self.showPreview && !$frame.length) {
3228
                self._log($h.logMessages.invalidThumb, {id: id});
3229
            }
3230
            return $frame;
3231
        },
3232
        _getThumbs: function (css) {
3233
            css = css || '';
3234
            return this.getFrames(':not(.file-preview-initial)' + css);
3235
        },
3236
        _getThumbId: function (fileId) {
3237
            var self = this;
3238
            return self.previewInitId + '-' + fileId;
3239
        },
3240
        _getExtraData: function (fileId, index) {
3241
            var self = this, data = self.uploadExtraData;
3242
            if (typeof self.uploadExtraData === 'function') {
3243
                data = self.uploadExtraData(fileId, index);
3244
            }
3245
            return data;
3246
        },
3247
        _initXhr: function (xhrobj, fileId) {
3248
            var self = this, fm = self.fileManager, func = function (event) {
3249
                var pct = 0, total = event.total, loaded = event.loaded || event.position,
3250
                    stats = fm.getUploadStats(fileId, loaded, total);
3251
                /** @namespace event.lengthComputable */
3252
                if (event.lengthComputable && !self.enableResumableUpload) {
3253
                    pct = $h.round(loaded / total * 100);
3254
                }
3255
                if (fileId) {
3256
                    self._setFileUploadStats(fileId, pct, stats);
3257
                } else {
3258
                    self._setProgress(pct, null, null, self._getStats(stats));
3259
                }
3260
                self._raise('fileajaxprogress', [stats]);
3261
            };
3262
            if (xhrobj.upload) {
3263
                if (self.progressDelay) {
3264
                    func = $h.debounce(func, self.progressDelay);
3265
                }
3266
                xhrobj.upload.addEventListener('progress', func, false);
3267
            }
3268
            return xhrobj;
3269
        },
3270
        _initAjaxSettings: function () {
3271
            var self = this;
3272
            self._ajaxSettings = $.extend(true, {}, self.ajaxSettings);
3273
            self._ajaxDeleteSettings = $.extend(true, {}, self.ajaxDeleteSettings);
3274
        },
3275
        _mergeAjaxCallback: function (funcName, srcFunc, type) {
3276
            var self = this, settings = self._ajaxSettings, flag = self.mergeAjaxCallbacks, targFunc;
3277
            if (type === 'delete') {
3278
                settings = self._ajaxDeleteSettings;
3279
                flag = self.mergeAjaxDeleteCallbacks;
3280
            }
3281
            targFunc = settings[funcName];
3282
            if (flag && typeof targFunc === 'function') {
3283
                if (flag === 'before') {
3284
                    settings[funcName] = function () {
3285
                        targFunc.apply(this, arguments);
3286
                        srcFunc.apply(this, arguments);
3287
                    };
3288
                } else {
3289
                    settings[funcName] = function () {
3290
                        srcFunc.apply(this, arguments);
3291
                        targFunc.apply(this, arguments);
3292
                    };
3293
                }
3294
            } else {
3295
                settings[funcName] = srcFunc;
3296
            }
3297
        },
3298
        _ajaxSubmit: function (fnBefore, fnSuccess, fnComplete, fnError, formdata, fileId, index, vUrl) {
3299
            var self = this, settings, defaults, data, ajaxTask;
3300
            if (!self._raise('filepreajax', [formdata, fileId, index])) {
3301
                return;
3302
            }
3303
            formdata.append('initialPreview', JSON.stringify(self.initialPreview));
3304
            formdata.append('initialPreviewConfig', JSON.stringify(self.initialPreviewConfig));
3305
            formdata.append('initialPreviewThumbTags', JSON.stringify(self.initialPreviewThumbTags));
3306
            self._initAjaxSettings();
3307
            self._mergeAjaxCallback('beforeSend', fnBefore);
3308
            self._mergeAjaxCallback('success', fnSuccess);
3309
            self._mergeAjaxCallback('complete', fnComplete);
3310
            self._mergeAjaxCallback('error', fnError);
3311
            vUrl = vUrl || self.uploadUrlThumb || self.uploadUrl;
3312
            if (typeof vUrl === 'function') {
3313
                vUrl = vUrl();
3314
            }
3315
            data = self._getExtraData(fileId, index) || {};
3316
            if (typeof data === 'object') {
3317
                $.each(data, function (key, value) {
3318
                    formdata.append(key, value);
3319
                });
3320
            }
3321
            defaults = {
3322
                xhr: function () {
3323
                    var xhrobj = $.ajaxSettings.xhr();
3324
                    return self._initXhr(xhrobj, fileId);
3325
                },
3326
                url: self._encodeURI(vUrl),
3327
                type: 'POST',
3328
                dataType: 'json',
3329
                data: formdata,
3330
                cache: false,
3331
                processData: false,
3332
                contentType: false
3333
            };
3334
            settings = $.extend(true, {}, defaults, self._ajaxSettings);
3335
            ajaxTask = self.taskManager.addTask(fileId + '-' + index, function () {
3336
                var self = this.self, config, xhr;
3337
                config = self.ajaxQueue.shift();
3338
                xhr = $.ajax(config);
3339
                self.ajaxRequests.push(xhr);
3340
            });
3341
            self.ajaxQueue.push(settings);
3342
            ajaxTask.runWithContext({self: self});
3343
        },
3344
        _mergeArray: function (prop, content) {
3345
            var self = this, arr1 = $h.cleanArray(self[prop]), arr2 = $h.cleanArray(content);
3346
            self[prop] = arr1.concat(arr2);
3347
        },
3348
        _initUploadSuccess: function (out, $thumb, allFiles) {
3349
            var self = this, append, data, index, $div, $newCache, content, config, tags, id, i;
3350
            if (!self.showPreview || typeof out !== 'object' || $.isEmptyObject(out)) {
3351
                self._resetCaption();
3352
                return;
3353
            }
3354
            if (out.initialPreview !== undefined && out.initialPreview.length > 0) {
3355
                self.hasInitData = true;
3356
                content = out.initialPreview || [];
3357
                config = out.initialPreviewConfig || [];
3358
                tags = out.initialPreviewThumbTags || [];
3359
                append = out.append === undefined || out.append;
3360
                if (content.length > 0 && !$h.isArray(content)) {
3361
                    content = content.split(self.initialPreviewDelimiter);
3362
                }
3363
                if (content.length) {
3364
                    self._mergeArray('initialPreview', content);
3365
                    self._mergeArray('initialPreviewConfig', config);
3366
                    self._mergeArray('initialPreviewThumbTags', tags);
3367
                }
3368
                if ($thumb !== undefined) {
3369
                    if (!allFiles) {
3370
                        index = self.previewCache.add(content[0], config[0], tags[0], append);
3371
                        data = self.previewCache.get(index, false);
3372
                        $div = $h.createElement(data).hide().appendTo($thumb);
3373
                        $newCache = $div.find('.kv-zoom-cache');
3374
                        if ($newCache && $newCache.length) {
3375
                            $newCache.appendTo($thumb);
3376
                        }
3377
                        $thumb.fadeOut('slow', function () {
3378
                            var $newThumb = $div.find('.file-preview-frame');
3379
                            if ($newThumb && $newThumb.length) {
3380
                                $newThumb.insertBefore($thumb).fadeIn('slow').css('display:inline-block');
3381
                            }
3382
                            self._initPreviewActions();
3383
                            self._clearFileInput();
3384
                            $thumb.remove();
3385
                            $div.remove();
3386
                            self._initSortable();
3387
                        });
3388
                    } else {
3389
                        id = $thumb.attr('id');
3390
                        i = self._getUploadCacheIndex(id);
3391
                        if (i !== null) {
3392
                            self.uploadCache[i] = {
3393
                                id: id,
3394
                                content: content[0],
3395
                                config: config[0] || [],
3396
                                tags: tags[0] || [],
3397
                                append: append
3398
                            };
3399
                        }
3400
                    }
3401
                } else {
3402
                    self.previewCache.set(content, config, tags, append);
3403
                    self._initPreview();
3404
                    self._initPreviewActions();
3405
                }
3406
            }
3407
            self._resetCaption();
3408
        },
3409
        _getUploadCacheIndex: function (id) {
3410
            var self = this, i, len = self.uploadCache.length, config;
3411
            for (i = 0; i < len; i++) {
3412
                config = self.uploadCache[i];
3413
                if (config.id === id) {
3414
                    return i;
3415
                }
3416
            }
3417
            return null;
3418
        },
3419
        _initSuccessThumbs: function () {
3420
            var self = this;
3421
            if (!self.showPreview) {
3422
                return;
3423
            }
3424
            self._getThumbs($h.FRAMES + '.file-preview-success').each(function () {
3425
                var $thumb = $(this), $remove = $thumb.find('.kv-file-remove');
3426
                $remove.removeAttr('disabled');
3427
                self._handler($remove, 'click', function () {
3428
                    var id = $thumb.attr('id'),
3429
                        out = self._raise('filesuccessremove', [id, $thumb.attr('data-fileindex')]);
3430
                    $h.cleanMemory($thumb);
3431
                    if (out === false) {
3432
                        return;
3433
                    }
3434
                    $thumb.fadeOut('slow', function () {
3435
                        $thumb.remove();
3436
                        if (!self.getFrames().length) {
3437
                            self.reset();
3438
                        }
3439
                    });
3440
                });
3441
            });
3442
        },
3443
        _updateInitialPreview: function () {
3444
            var self = this, u = self.uploadCache;
3445
            if (self.showPreview) {
3446
                $.each(u, function (key, setting) {
3447
                    self.previewCache.add(setting.content, setting.config, setting.tags, setting.append);
3448
                });
3449
                if (self.hasInitData) {
3450
                    self._initPreview();
3451
                    self._initPreviewActions();
3452
                }
3453
            }
3454
        },
3455
        _uploadSingle: function (i, id, isBatch) {
3456
            var self = this, fm = self.fileManager, count = fm.count(), formdata = new FormData(), outData,
3457
                previewId = self._getThumbId(id), $thumb, chkComplete, $btnUpload, $btnDelete,
3458
                hasPostData = count > 0 || !$.isEmptyObject(self.uploadExtraData), uploadFailed, $prog, fnBefore,
3459
                errMsg, fnSuccess, fnComplete, fnError, updateUploadLog, op = self.ajaxOperations.uploadThumb,
3460
                fileObj = fm.getFile(id), params = {id: previewId, index: i, fileId: id},
3461
                fileName = self.fileManager.getFileName(id, true);
3462
            if (self.enableResumableUpload) { // not enabled for resumable uploads
3463
                return;
3464
            }
3465
            if (self.showPreview) {
3466
                $thumb = self.fileManager.getThumb(id);
3467
                $prog = $thumb.find('.file-thumb-progress');
3468
                $btnUpload = $thumb.find('.kv-file-upload');
3469
                $btnDelete = $thumb.find('.kv-file-remove');
3470
                $prog.show();
3471
            }
3472
            if (count === 0 || !hasPostData || (self.showPreview && $btnUpload && $btnUpload.hasClass('disabled')) ||
3473
                self._abort(params)) {
3474
                return;
3475
            }
3476
            updateUploadLog = function () {
3477
                if (!uploadFailed) {
3478
                    fm.removeFile(id);
3479
                } else {
3480
                    fm.errors.push(id);
3481
                }
3482
                fm.setProcessed(id);
3483
                if (fm.isProcessed()) {
3484
                    self.fileBatchCompleted = true;
3485
                    chkComplete();
3486
                }
3487
            };
3488
            chkComplete = function () {
3489
                var $initThumbs;
3490
                if (!self.fileBatchCompleted) {
3491
                    return;
3492
                }
3493
                setTimeout(function () {
3494
                    var triggerReset = fm.count() === 0, errCount = fm.errors.length;
3495
                    self._updateInitialPreview();
3496
                    self.unlock(triggerReset);
3497
                    if (triggerReset) {
3498
                        self._clearFileInput();
3499
                    }
3500
                    $initThumbs = self.$preview.find('.file-preview-initial');
3501
                    if (self.uploadAsync && $initThumbs.length) {
3502
                        $h.addCss($initThumbs, $h.SORT_CSS);
3503
                        self._initSortable();
3504
                    }
3505
                    self._raise('filebatchuploadcomplete', [fm.stack, self._getExtraData()]);
3506
                    if (!self.retryErrorUploads || errCount === 0) {
3507
                        fm.clear();
3508
                    }
3509
                    self._setProgress(101);
3510
                    self.ajaxAborted = false;
3511
                }, self.processDelay);
3512
            };
3513
            fnBefore = function (jqXHR) {
3514
                outData = self._getOutData(formdata, jqXHR);
3515
                fm.initStats(id);
3516
                self.fileBatchCompleted = false;
3517
                if (!isBatch) {
3518
                    self.ajaxAborted = false;
3519
                }
3520
                if (self.showPreview) {
3521
                    if (!$thumb.hasClass('file-preview-success')) {
3522
                        self._setThumbStatus($thumb, 'Loading');
3523
                        $h.addCss($thumb, 'file-uploading');
3524
                    }
3525
                    $btnUpload.attr('disabled', true);
3526
                    $btnDelete.attr('disabled', true);
3527
                }
3528
                if (!isBatch) {
3529
                    self.lock();
3530
                }
3531
                if (fm.errors.indexOf(id) !== -1) {
3532
                    delete fm.errors[id];
3533
                }
3534
                self._raise('filepreupload', [outData, previewId, i]);
3535
                $.extend(true, params, outData);
3536
                if (self._abort(params)) {
3537
                    jqXHR.abort();
3538
                    if (!isBatch) {
3539
                        self._setThumbStatus($thumb, 'New');
3540
                        $thumb.removeClass('file-uploading');
3541
                        $btnUpload.removeAttr('disabled');
3542
                        $btnDelete.removeAttr('disabled');
3543
                        self.unlock();
3544
                    }
3545
                    self._setProgressCancelled();
3546
                }
3547
            };
3548
            fnSuccess = function (data, textStatus, jqXHR) {
3549
                var pid = self.showPreview && $thumb.attr('id') ? $thumb.attr('id') : previewId;
3550
                outData = self._getOutData(formdata, jqXHR, data);
3551
                $.extend(true, params, outData);
3552
                setTimeout(function () {
3553
                    if ($h.isEmpty(data) || $h.isEmpty(data.error)) {
3554
                        if (self.showPreview) {
3555
                            self._setThumbStatus($thumb, 'Success');
3556
                            $btnUpload.hide();
3557
                            self._initUploadSuccess(data, $thumb, isBatch);
3558
                            self._setProgress(101, $prog);
3559
                        }
3560
                        self._raise('fileuploaded', [outData, pid, i]);
3561
                        if (!isBatch) {
3562
                            self.fileManager.remove($thumb);
3563
                        } else {
3564
                            updateUploadLog();
3565
                        }
3566
                    } else {
3567
                        uploadFailed = true;
3568
                        errMsg = self._parseError(op, jqXHR, self.msgUploadError, self.fileManager.getFileName(id));
3569
                        self._showFileError(errMsg, params);
3570
                        self._setPreviewError($thumb, true);
3571
                        if (!self.retryErrorUploads) {
3572
                            $btnUpload.hide();
3573
                        }
3574
                        if (isBatch) {
3575
                            updateUploadLog();
3576
                        }
3577
                        self._setProgress(101, self._getFrame(pid).find('.file-thumb-progress'),
3578
                            self.msgUploadError);
3579
                    }
3580
                }, self.processDelay);
3581
            };
3582
            fnComplete = function () {
3583
                if (self.showPreview) {
3584
                    $btnUpload.removeAttr('disabled');
3585
                    $btnDelete.removeAttr('disabled');
3586
                    $thumb.removeClass('file-uploading');
3587
                }
3588
                if (!isBatch) {
3589
                    self.unlock(false);
3590
                    self._clearFileInput();
3591
                } else {
3592
                    chkComplete();
3593
                }
3594
                self._initSuccessThumbs();
3595
            };
3596
            fnError = function (jqXHR, textStatus, errorThrown) {
3597
                errMsg = self._parseError(op, jqXHR, errorThrown, self.fileManager.getFileName(id));
3598
                uploadFailed = true;
3599
                setTimeout(function () {
3600
                    var $prog;
3601
                    if (isBatch) {
3602
                        updateUploadLog();
3603
                    }
3604
                    self.fileManager.setProgress(id, 100);
3605
                    self._setPreviewError($thumb, true);
3606
                    if (!self.retryErrorUploads) {
3607
                        $btnUpload.hide();
3608
                    }
3609
                    $.extend(true, params, self._getOutData(formdata, jqXHR));
3610
                    self._setProgress(101, self.$progress, self.msgAjaxProgressError.replace('{operation}', op));
3611
                    $prog = self.showPreview && $thumb ? $thumb.find('.file-thumb-progress') : '';
3612
                    self._setProgress(101, $prog, self.msgUploadError);
3613
                    self._showFileError(errMsg, params);
3614
                }, self.processDelay);
3615
            };
3616
            self._setFileData(formdata, fileObj.file, fileName, id);
3617
            self._setUploadData(formdata, {fileId: id});
3618
            self._ajaxSubmit(fnBefore, fnSuccess, fnComplete, fnError, formdata, id, i);
3619
        },
3620
        _setFileData: function (formdata, file, fileName, fileId) {
3621
            var self = this, preProcess = self.preProcessUpload;
3622
            if (preProcess && typeof preProcess === 'function') {
3623
                formdata.append(self.uploadFileAttr, preProcess(fileId, file));
3624
            } else {
3625
                formdata.append(self.uploadFileAttr, file, fileName);
3626
            }
3627
        },
3628
        _uploadBatch: function () {
3629
            var self = this, fm = self.fileManager, total = fm.total(), params = {}, fnBefore, fnSuccess, fnError,
3630
                fnComplete, hasPostData = total > 0 || !$.isEmptyObject(self.uploadExtraData), errMsg,
3631
                setAllUploaded, formdata = new FormData(), op = self.ajaxOperations.uploadBatch;
3632
            if (total === 0 || !hasPostData || self._abort(params)) {
3633
                return;
3634
            }
3635
            setAllUploaded = function () {
3636
                self.fileManager.clear();
3637
                self._clearFileInput();
3638
            };
3639
            fnBefore = function (jqXHR) {
3640
                self.lock();
3641
                fm.initStats();
3642
                var outData = self._getOutData(formdata, jqXHR);
3643
                self.ajaxAborted = false;
3644
                if (self.showPreview) {
3645
                    self._getThumbs().each(function () {
3646
                        var $thumb = $(this), $btnUpload = $thumb.find('.kv-file-upload'),
3647
                            $btnDelete = $thumb.find('.kv-file-remove');
3648
                        if (!$thumb.hasClass('file-preview-success')) {
3649
                            self._setThumbStatus($thumb, 'Loading');
3650
                            $h.addCss($thumb, 'file-uploading');
3651
                        }
3652
                        $btnUpload.attr('disabled', true);
3653
                        $btnDelete.attr('disabled', true);
3654
                    });
3655
                }
3656
                self._raise('filebatchpreupload', [outData]);
3657
                if (self._abort(outData)) {
3658
                    jqXHR.abort();
3659
                    self._getThumbs().each(function () {
3660
                        var $thumb = $(this), $btnUpload = $thumb.find('.kv-file-upload'),
3661
                            $btnDelete = $thumb.find('.kv-file-remove');
3662
                        if ($thumb.hasClass('file-preview-loading')) {
3663
                            self._setThumbStatus($thumb, 'New');
3664
                            $thumb.removeClass('file-uploading');
3665
                        }
3666
                        $btnUpload.removeAttr('disabled');
3667
                        $btnDelete.removeAttr('disabled');
3668
                    });
3669
                    self._setProgressCancelled();
3670
                }
3671
            };
3672
            fnSuccess = function (data, textStatus, jqXHR) {
3673
                /** @namespace data.errorkeys */
3674
                var outData = self._getOutData(formdata, jqXHR, data), key = 0,
3675
                    $thumbs = self._getThumbs(':not(.file-preview-success)'),
3676
                    keys = $h.isEmpty(data) || $h.isEmpty(data.errorkeys) ? [] : data.errorkeys;
3677
 
3678
                if ($h.isEmpty(data) || $h.isEmpty(data.error)) {
3679
                    self._raise('filebatchuploadsuccess', [outData]);
3680
                    setAllUploaded();
3681
                    if (self.showPreview) {
3682
                        $thumbs.each(function () {
3683
                            var $thumb = $(this);
3684
                            self._setThumbStatus($thumb, 'Success');
3685
                            $thumb.removeClass('file-uploading');
3686
                            $thumb.find('.kv-file-upload').hide().removeAttr('disabled');
3687
                        });
3688
                        self._initUploadSuccess(data);
3689
                    } else {
3690
                        self.reset();
3691
                    }
3692
                    self._setProgress(101);
3693
                } else {
3694
                    if (self.showPreview) {
3695
                        $thumbs.each(function () {
3696
                            var $thumb = $(this);
3697
                            $thumb.removeClass('file-uploading');
3698
                            $thumb.find('.kv-file-upload').removeAttr('disabled');
3699
                            $thumb.find('.kv-file-remove').removeAttr('disabled');
3700
                            if (keys.length === 0 || $.inArray(key, keys) !== -1) {
3701
                                self._setPreviewError($thumb, true);
3702
                                if (!self.retryErrorUploads) {
3703
                                    $thumb.find('.kv-file-upload').hide();
3704
                                    self.fileManager.remove($thumb);
3705
                                }
3706
                            } else {
3707
                                $thumb.find('.kv-file-upload').hide();
3708
                                self._setThumbStatus($thumb, 'Success');
3709
                                self.fileManager.remove($thumb);
3710
                            }
3711
                            if (!$thumb.hasClass('file-preview-error') || self.retryErrorUploads) {
3712
                                key++;
3713
                            }
3714
                        });
3715
                        self._initUploadSuccess(data);
3716
                    }
3717
                    errMsg = self._parseError(op, jqXHR, self.msgUploadError);
3718
                    self._showFileError(errMsg, outData, 'filebatchuploaderror');
3719
                    self._setProgress(101, self.$progress, self.msgUploadError);
3720
                }
3721
            };
3722
            fnComplete = function () {
3723
                self.unlock();
3724
                self._initSuccessThumbs();
3725
                self._clearFileInput();
3726
                self._raise('filebatchuploadcomplete', [self.fileManager.stack, self._getExtraData()]);
3727
            };
3728
            fnError = function (jqXHR, textStatus, errorThrown) {
3729
                var outData = self._getOutData(formdata, jqXHR);
3730
                errMsg = self._parseError(op, jqXHR, errorThrown);
3731
                self._showFileError(errMsg, outData, 'filebatchuploaderror');
3732
                self.uploadFileCount = total - 1;
3733
                if (!self.showPreview) {
3734
                    return;
3735
                }
3736
                self._getThumbs().each(function () {
3737
                    var $thumb = $(this);
3738
                    $thumb.removeClass('file-uploading');
3739
                    if (self.fileManager.getFile($thumb.attr('data-fileid'))) {
3740
                        self._setPreviewError($thumb);
3741
                    }
3742
                });
3743
                self._getThumbs().removeClass('file-uploading');
3744
                self._getThumbs(' .kv-file-upload').removeAttr('disabled');
3745
                self._getThumbs(' .kv-file-delete').removeAttr('disabled');
3746
                self._setProgress(101, self.$progress, self.msgAjaxProgressError.replace('{operation}', op));
3747
            };
3748
            var ctr = 0;
3749
            $.each(self.fileManager.stack, function (key, data) {
3750
                if (!$h.isEmpty(data.file)) {
3751
                    self._setFileData(formdata, data.file, (data.nameFmt || ('untitled_' + ctr)), key);
3752
                }
3753
                ctr++;
3754
            });
3755
            self._ajaxSubmit(fnBefore, fnSuccess, fnComplete, fnError, formdata);
3756
        },
3757
        _uploadExtraOnly: function () {
3758
            var self = this, params = {}, fnBefore, fnSuccess, fnComplete, fnError, formdata = new FormData(), errMsg,
3759
                op = self.ajaxOperations.uploadExtra;
3760
            if (self._abort(params)) {
3761
                return;
3762
            }
3763
            fnBefore = function (jqXHR) {
3764
                self.lock();
3765
                var outData = self._getOutData(formdata, jqXHR);
3766
                self._raise('filebatchpreupload', [outData]);
3767
                self._setProgress(50);
3768
                params.data = outData;
3769
                params.xhr = jqXHR;
3770
                if (self._abort(params)) {
3771
                    jqXHR.abort();
3772
                    self._setProgressCancelled();
3773
                }
3774
            };
3775
            fnSuccess = function (data, textStatus, jqXHR) {
3776
                var outData = self._getOutData(formdata, jqXHR, data);
3777
                if ($h.isEmpty(data) || $h.isEmpty(data.error)) {
3778
                    self._raise('filebatchuploadsuccess', [outData]);
3779
                    self._clearFileInput();
3780
                    self._initUploadSuccess(data);
3781
                    self._setProgress(101);
3782
                } else {
3783
                    errMsg = self._parseError(op, jqXHR, self.msgUploadError);
3784
                    self._showFileError(errMsg, outData, 'filebatchuploaderror');
3785
                }
3786
            };
3787
            fnComplete = function () {
3788
                self.unlock();
3789
                self._clearFileInput();
3790
                self._raise('filebatchuploadcomplete', [self.fileManager.stack, self._getExtraData()]);
3791
            };
3792
            fnError = function (jqXHR, textStatus, errorThrown) {
3793
                var outData = self._getOutData(formdata, jqXHR);
3794
                errMsg = self._parseError(op, jqXHR, errorThrown);
3795
                params.data = outData;
3796
                self._showFileError(errMsg, outData, 'filebatchuploaderror');
3797
                self._setProgress(101, self.$progress, self.msgAjaxProgressError.replace('{operation}', op));
3798
            };
3799
            self._ajaxSubmit(fnBefore, fnSuccess, fnComplete, fnError, formdata);
3800
        },
3801
        _deleteFileIndex: function ($frame) {
3802
            var self = this, ind = $frame.attr('data-fileindex'), rev = self.reversePreviewOrder;
3803
            if (ind.substring(0, 5) === $h.INIT_FLAG) {
3804
                ind = parseInt(ind.replace($h.INIT_FLAG, ''));
3805
                self.initialPreview = $h.spliceArray(self.initialPreview, ind, rev);
3806
                self.initialPreviewConfig = $h.spliceArray(self.initialPreviewConfig, ind, rev);
3807
                self.initialPreviewThumbTags = $h.spliceArray(self.initialPreviewThumbTags, ind, rev);
3808
                self.getFrames().each(function () {
3809
                    var $nFrame = $(this), nInd = $nFrame.attr('data-fileindex');
3810
                    if (nInd.substring(0, 5) === $h.INIT_FLAG) {
3811
                        nInd = parseInt(nInd.replace($h.INIT_FLAG, ''));
3812
                        if (nInd > ind) {
3813
                            nInd--;
3814
                            $nFrame.attr('data-fileindex', $h.INIT_FLAG + nInd);
3815
                        }
3816
                    }
3817
                });
3818
            }
3819
        },
3820
        _resetCaption: function () {
3821
            var self = this;
3822
            setTimeout(function () {
3823
                var cap, n, chk = self.previewCache.count(true), len = self.fileManager.count(), file,
3824
                    incomplete = ':not(.file-preview-success):not(.file-preview-error)',
3825
                    hasThumb = self.showPreview && self.getFrames(incomplete).length;
3826
                if (len === 0 && chk === 0 && !hasThumb) {
3827
                    self.reset();
3828
                } else {
3829
                    n = chk + len;
3830
                    if (n > 1) {
3831
                        cap = self._getMsgSelected(n);
3832
                    } else {
3833
                        file = self.fileManager.getFirstFile();
3834
                        cap = file ? file.nameFmt : '_';
3835
                    }
3836
                    self._setCaption(cap);
3837
                }
3838
            }, self.processDelay);
3839
        },
3840
        _initFileActions: function () {
3841
            var self = this;
3842
            if (!self.showPreview) {
3843
                return;
3844
            }
3845
            self._initZoomButton();
3846
            self.getFrames(' .kv-file-remove').each(function () {
3847
                var $el = $(this), $frame = $el.closest($h.FRAMES), hasError, id = $frame.attr('id'),
3848
                    ind = $frame.attr('data-fileindex'), status;
3849
                self._handler($el, 'click', function () {
3850
                    status = self._raise('filepreremove', [id, ind]);
3851
                    if (status === false || !self._validateMinCount()) {
3852
                        return false;
3853
                    }
3854
                    hasError = $frame.hasClass('file-preview-error');
3855
                    $h.cleanMemory($frame);
3856
                    $frame.fadeOut('slow', function () {
3857
                        self.fileManager.remove($frame);
3858
                        self._clearObjects($frame);
3859
                        $frame.remove();
3860
                        if (id && hasError) {
3861
                            self.$errorContainer.find('li[data-thumb-id="' + id + '"]').fadeOut('fast', function () {
3862
                                $(this).remove();
3863
                                if (!self._errorsExist()) {
3864
                                    self._resetErrors();
3865
                                }
3866
                            });
3867
                        }
3868
                        self._clearFileInput();
3869
                        self._resetCaption();
3870
                        self._raise('fileremoved', [id, ind]);
3871
                    });
3872
                });
3873
            });
3874
            self.getFrames(' .kv-file-upload').each(function () {
3875
                var $el = $(this);
3876
                self._handler($el, 'click', function () {
3877
                    var $frame = $el.closest($h.FRAMES), fileId = $frame.attr('data-fileid');
3878
                    self._hideProgress();
3879
                    if ($frame.hasClass('file-preview-error') && !self.retryErrorUploads) {
3880
                        return;
3881
                    }
3882
                    self._uploadSingle(self.fileManager.getIndex(fileId), fileId, false);
3883
                });
3884
            });
3885
        },
3886
        _initPreviewActions: function () {
3887
            var self = this, $preview = self.$preview, deleteExtraData = self.deleteExtraData || {},
3888
                btnRemove = $h.FRAMES + ' .kv-file-remove', settings = self.fileActionSettings,
3889
                origClass = settings.removeClass, errClass = settings.removeErrorClass,
3890
                resetProgress = function () {
3891
                    var hasFiles = self.isAjaxUpload ? self.previewCache.count(true) : self._inputFileCount();
3892
                    if (!self.getFrames().length && !hasFiles) {
3893
                        self._setCaption('');
3894
                        self.reset();
3895
                        self.initialCaption = '';
3896
                    }
3897
                };
3898
            self._initZoomButton();
3899
            $preview.find(btnRemove).each(function () {
3900
                var $el = $(this), vUrl = $el.data('url') || self.deleteUrl, vKey = $el.data('key'), errMsg, fnBefore,
3901
                    fnSuccess, fnError, op = self.ajaxOperations.deleteThumb;
3902
                if ($h.isEmpty(vUrl) || vKey === undefined) {
3903
                    return;
3904
                }
3905
                if (typeof vUrl === 'function') {
3906
                    vUrl = vUrl();
3907
                }
3908
                var $frame = $el.closest($h.FRAMES), cache = self.previewCache.data, settings, params, config,
3909
                    fileName, extraData, index = $frame.attr('data-fileindex');
3910
                index = parseInt(index.replace($h.INIT_FLAG, ''));
3911
                config = $h.isEmpty(cache.config) && $h.isEmpty(cache.config[index]) ? null : cache.config[index];
3912
                extraData = $h.isEmpty(config) || $h.isEmpty(config.extra) ? deleteExtraData : config.extra;
3913
                fileName = config && (config.filename || config.caption) || '';
3914
                if (typeof extraData === 'function') {
3915
                    extraData = extraData();
3916
                }
3917
                params = {id: $el.attr('id'), key: vKey, extra: extraData};
3918
                fnBefore = function (jqXHR) {
3919
                    self.ajaxAborted = false;
3920
                    self._raise('filepredelete', [vKey, jqXHR, extraData]);
3921
                    if (self._abort()) {
3922
                        jqXHR.abort();
3923
                    } else {
3924
                        $el.removeClass(errClass);
3925
                        $h.addCss($frame, 'file-uploading');
3926
                        $h.addCss($el, 'disabled ' + origClass);
3927
                    }
3928
                };
3929
                fnSuccess = function (data, textStatus, jqXHR) {
3930
                    var n, cap;
3931
                    if (!$h.isEmpty(data) && !$h.isEmpty(data.error)) {
3932
                        params.jqXHR = jqXHR;
3933
                        params.response = data;
3934
                        errMsg = self._parseError(op, jqXHR, self.msgDeleteError, fileName);
3935
                        self._showFileError(errMsg, params, 'filedeleteerror');
3936
                        $frame.removeClass('file-uploading');
3937
                        $el.removeClass('disabled ' + origClass).addClass(errClass);
3938
                        resetProgress();
3939
                        return;
3940
                    }
3941
                    $frame.removeClass('file-uploading').addClass('file-deleted');
3942
                    $frame.fadeOut('slow', function () {
3943
                        index = parseInt(($frame.attr('data-fileindex')).replace($h.INIT_FLAG, ''));
3944
                        self.previewCache.unset(index);
3945
                        self._deleteFileIndex($frame);
3946
                        n = self.previewCache.count(true);
3947
                        cap = n > 0 ? self._getMsgSelected(n) : '';
3948
                        self._setCaption(cap);
3949
                        self._raise('filedeleted', [vKey, jqXHR, extraData]);
3950
                        self._clearObjects($frame);
3951
                        $frame.remove();
3952
                        resetProgress();
3953
                    });
3954
                };
3955
                fnError = function (jqXHR, textStatus, errorThrown) {
3956
                    var errMsg = self._parseError(op, jqXHR, errorThrown, fileName);
3957
                    params.jqXHR = jqXHR;
3958
                    params.response = {};
3959
                    self._showFileError(errMsg, params, 'filedeleteerror');
3960
                    $frame.removeClass('file-uploading');
3961
                    $el.removeClass('disabled ' + origClass).addClass(errClass);
3962
                    resetProgress();
3963
                };
3964
                self._initAjaxSettings();
3965
                self._mergeAjaxCallback('beforeSend', fnBefore, 'delete');
3966
                self._mergeAjaxCallback('success', fnSuccess, 'delete');
3967
                self._mergeAjaxCallback('error', fnError, 'delete');
3968
                settings = $.extend(true, {}, {
3969
                    url: self._encodeURI(vUrl),
3970
                    type: 'POST',
3971
                    dataType: 'json',
3972
                    data: $.extend(true, {}, {key: vKey}, extraData)
3973
                }, self._ajaxDeleteSettings);
3974
                self._handler($el, 'click', function () {
3975
                    if (!self._validateMinCount()) {
3976
                        return false;
3977
                    }
3978
                    self.ajaxAborted = false;
3979
                    self._raise('filebeforedelete', [vKey, extraData]);
3980
                    if (self.ajaxAborted instanceof Promise) {
3981
                        self.ajaxAborted.then(function (result) {
3982
                            if (!result) {
3983
                                $.ajax(settings);
3984
                            }
3985
                        });
3986
                    } else {
3987
                        if (!self.ajaxAborted) {
3988
                            $.ajax(settings);
3989
                        }
3990
                    }
3991
                });
3992
            });
3993
        },
3994
        _hideFileIcon: function () {
3995
            var self = this;
3996
            if (self.overwriteInitial) {
3997
                self.$captionContainer.removeClass('icon-visible');
3998
            }
3999
        },
4000
        _showFileIcon: function () {
4001
            var self = this;
4002
            $h.addCss(self.$captionContainer, 'icon-visible');
4003
        },
4004
        _getSize: function (bytes, sizes) {
4005
            var self = this, size = parseFloat(bytes), i, func = self.fileSizeGetter, out;
4006
            if (!$.isNumeric(bytes) || !$.isNumeric(size)) {
4007
                return '';
4008
            }
4009
            if (typeof func === 'function') {
4010
                out = func(size);
4011
            } else {
4012
                if (size === 0) {
4013
                    out = '0.00 B';
4014
                } else {
4015
                    i = Math.floor(Math.log(size) / Math.log(1024));
4016
                    if (!sizes) {
4017
                        sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
4018
                    }
4019
                    out = (size / Math.pow(1024, i)).toFixed(2) * 1 + ' ' + sizes[i];
4020
                }
4021
            }
4022
            return self._getLayoutTemplate('size').replace('{sizeText}', out);
4023
        },
4024
        _getFileType: function (ftype) {
4025
            var self = this;
4026
            return self.mimeTypeAliases[ftype] || ftype;
4027
        },
4028
        _generatePreviewTemplate: function (
4029
            cat,
4030
            data,
4031
            fname,
4032
            ftype,
4033
            previewId,
4034
            fileId,
4035
            isError,
4036
            size,
4037
            frameClass,
4038
            foot,
4039
            ind,
4040
            templ,
4041
            attrs,
4042
            zoomData
4043
        ) {
4044
            var self = this, caption = self.slug(fname), prevContent, zoomContent = '', styleAttribs = '',
4045
                screenW = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth,
4046
                config, title = caption, alt = caption, typeCss = 'type-default', getContent,
4047
                footer = foot || self._renderFileFooter(cat, caption, size, 'auto', isError),
4048
                forcePrevIcon = self.preferIconicPreview, forceZoomIcon = self.preferIconicZoomPreview,
4049
                newCat = forcePrevIcon ? 'other' : cat;
4050
            config = screenW < 400 ? (self.previewSettingsSmall[newCat] || self.defaults.previewSettingsSmall[newCat]) :
4051
                (self.previewSettings[newCat] || self.defaults.previewSettings[newCat]);
4052
            if (config) {
4053
                $.each(config, function (key, val) {
4054
                    styleAttribs += key + ':' + val + ';';
4055
                });
4056
            }
4057
            getContent = function (c, d, zoom, frameCss) {
4058
                var id = zoom ? 'zoom-' + previewId : previewId, tmplt = self._getPreviewTemplate(c),
4059
                    css = (frameClass || '') + ' ' + frameCss;
4060
                if (self.frameClass) {
4061
                    css = self.frameClass + ' ' + css;
4062
                }
4063
                if (zoom) {
4064
                    css = css.replace(' ' + $h.SORT_CSS, '');
4065
                }
4066
                tmplt = self._parseFilePreviewIcon(tmplt, fname);
4067
                if (c === 'text') {
4068
                    d = $h.htmlEncode(d);
4069
                }
4070
                if (cat === 'object' && !ftype) {
4071
                    $.each(self.defaults.fileTypeSettings, function (key, func) {
4072
                        if (key === 'object' || key === 'other') {
4073
                            return;
4074
                        }
4075
                        if (func(fname, ftype)) {
4076
                            typeCss = 'type-' + key;
4077
                        }
4078
                    });
4079
                }
4080
                if (!$h.isEmpty(attrs)) {
4081
                    if (attrs.title !== undefined && attrs.title !== null) {
4082
                        title = attrs.title;
4083
                    }
4084
                    if (attrs.alt !== undefined && attrs.alt !== null) {
4085
                        title = attrs.alt;
4086
                    }
4087
                }
4088
                return tmplt.setTokens({
4089
                    'previewId': id,
4090
                    'caption': caption,
4091
                    'title': title,
4092
                    'alt': alt,
4093
                    'frameClass': css,
4094
                    'type': self._getFileType(ftype),
4095
                    'fileindex': ind,
4096
                    'fileid': fileId || '',
4097
                    'typeCss': typeCss,
4098
                    'footer': footer,
4099
                    'data': d,
4100
                    'template': templ || cat,
4101
                    'style': styleAttribs ? 'style="' + styleAttribs + '"' : ''
4102
                });
4103
            };
4104
            ind = ind || previewId.slice(previewId.lastIndexOf('-') + 1);
4105
            if (self.fileActionSettings.showZoom) {
4106
                zoomContent = getContent((forceZoomIcon ? 'other' : cat), zoomData ? zoomData : data, true,
4107
                    'kv-zoom-thumb');
4108
            }
4109
            zoomContent = '\n' + self._getLayoutTemplate('zoomCache').replace('{zoomContent}', zoomContent);
4110
            if (typeof self.sanitizeZoomCache === 'function') {
4111
                zoomContent = self.sanitizeZoomCache(zoomContent);
4112
            }
4113
            prevContent = getContent((forcePrevIcon ? 'other' : cat), data, false, 'kv-preview-thumb');
4114
            return prevContent.setTokens({zoomCache: zoomContent});
4115
        },
4116
        _addToPreview: function ($preview, content) {
4117
            var self = this, $el;
4118
            content = $h.cspBuffer.stash(content);
4119
            $el = self.reversePreviewOrder ? $preview.prepend(content) : $preview.append(content);
4120
            $h.cspBuffer.apply($preview);
4121
            return $el;
4122
        },
4123
        _previewDefault: function (file, isDisabled) {
4124
            var self = this, $preview = self.$preview;
4125
            if (!self.showPreview) {
4126
                return;
4127
            }
4128
            var fname = $h.getFileName(file), ftype = file ? file.type : '', content, size = file.size || 0,
4129
                caption = self._getFileName(file, ''), isError = isDisabled === true && !self.isAjaxUpload,
4130
                data = $h.createObjectURL(file), fileId = self.fileManager.getId(file),
4131
                previewId = self._getThumbId(fileId);
4132
            self._clearDefaultPreview();
4133
            content = self._generatePreviewTemplate('other', data, fname, ftype, previewId, fileId, isError, size);
4134
            self._addToPreview($preview, content);
4135
            self._setThumbAttr(previewId, caption, size);
4136
            if (isDisabled === true && self.isAjaxUpload) {
4137
                self._setThumbStatus(self._getFrame(previewId), 'Error');
4138
            }
4139
        },
4140
        _previewFile: function (i, file, theFile, data, fileInfo) {
4141
            if (!this.showPreview) {
4142
                return;
4143
            }
4144
            var self = this, fname = $h.getFileName(file), ftype = fileInfo.type, caption = fileInfo.name,
4145
                cat = self._parseFileType(ftype, fname), content, $preview = self.$preview, fsize = file.size || 0,
4146
                iData = (cat === 'text' || cat === 'html' || cat === 'image') ? theFile.target.result : data,
4147
                fileId = self.fileManager.getId(file), previewId = self._getThumbId(fileId);
4148
            /** @namespace window.DOMPurify */
4149
            if (cat === 'html' && self.purifyHtml && window.DOMPurify) {
4150
                iData = window.DOMPurify.sanitize(iData);
4151
            }
4152
            content = self._generatePreviewTemplate(cat, iData, fname, ftype, previewId, fileId, false, fsize);
4153
            self._clearDefaultPreview();
4154
            self._addToPreview($preview, content);
4155
            var $thumb = self._getFrame(previewId);
4156
            self._validateImageOrientation($thumb.find('img'), file, previewId, fileId, caption, ftype, fsize, iData);
4157
            self._setThumbAttr(previewId, caption, fsize);
4158
            self._initSortable();
4159
        },
4160
        _setThumbAttr: function (id, caption, size) {
4161
            var self = this, $frame = self._getFrame(id);
4162
            if ($frame.length) {
4163
                size = size && size > 0 ? self._getSize(size) : '';
4164
                $frame.data({'caption': caption, 'size': size});
4165
            }
4166
        },
4167
        _setInitThumbAttr: function () {
4168
            var self = this, data = self.previewCache.data, len = self.previewCache.count(true), config,
4169
                caption, size, previewId;
4170
            if (len === 0) {
4171
                return;
4172
            }
4173
            for (var i = 0; i < len; i++) {
4174
                config = data.config[i];
4175
                previewId = self.previewInitId + '-' + $h.INIT_FLAG + i;
4176
                caption = $h.ifSet('caption', config, $h.ifSet('filename', config));
4177
                size = $h.ifSet('size', config);
4178
                self._setThumbAttr(previewId, caption, size);
4179
            }
4180
        },
4181
        _slugDefault: function (text) {
4182
            // noinspection RegExpRedundantEscape
4183
            return $h.isEmpty(text, true) ? '' : String(text).replace(/[\[\]\/\{}:;#%=\(\)\*\+\?\\\^\$\|<>&"']/g, '_');
4184
        },
4185
        _updateFileDetails: function (numFiles, skipRaiseEvent) {
4186
            var self = this, $el = self.$element, label, n, log, nFiles, file,
4187
                name = ($h.isIE(9) && $h.findFileName($el.val())) || ($el[0].files[0] && $el[0].files[0].name);
4188
            if (!name && self.fileManager.count() > 0) {
4189
                file = self.fileManager.getFirstFile();
4190
                label = file.nameFmt;
4191
            } else {
4192
                label = name ? self.slug(name) : '_';
4193
            }
4194
            n = self.isAjaxUpload ? self.fileManager.count() : numFiles;
4195
            nFiles = self.previewCache.count(true) + n;
4196
            log = n === 1 ? label : self._getMsgSelected(nFiles);
4197
            if (self.isError) {
4198
                self.$previewContainer.removeClass('file-thumb-loading');
4199
                self.$previewStatus.html('');
4200
                self.$captionContainer.removeClass('icon-visible');
4201
            } else {
4202
                self._showFileIcon();
4203
            }
4204
            self._setCaption(log, self.isError);
4205
            self.$container.removeClass('file-input-new file-input-ajax-new');
4206
            if (!skipRaiseEvent) {
4207
                self._raise('fileselect', [numFiles, label]);
4208
            }
4209
            if (self.previewCache.count(true)) {
4210
                self._initPreviewActions();
4211
            }
4212
        },
4213
        _setThumbStatus: function ($thumb, status) {
4214
            var self = this;
4215
            if (!self.showPreview) {
4216
                return;
4217
            }
4218
            var icon = 'indicator' + status, msg = icon + 'Title',
4219
                css = 'file-preview-' + status.toLowerCase(),
4220
                $indicator = $thumb.find('.file-upload-indicator'),
4221
                config = self.fileActionSettings;
4222
            $thumb.removeClass('file-preview-success file-preview-error file-preview-paused file-preview-loading');
4223
            if (status === 'Success') {
4224
                $thumb.find('.file-drag-handle').remove();
4225
            }
4226
            $h.setHtml($indicator, config[icon]);
4227
            $indicator.attr('title', config[msg]);
4228
            $thumb.addClass(css);
4229
            if (status === 'Error' && !self.retryErrorUploads) {
4230
                $thumb.find('.kv-file-upload').attr('disabled', true);
4231
            }
4232
        },
4233
        _setProgressCancelled: function () {
4234
            var self = this;
4235
            self._setProgress(101, self.$progress, self.msgCancelled);
4236
        },
4237
        _setProgress: function (p, $el, error, stats) {
4238
            var self = this;
4239
            $el = $el || self.$progress;
4240
            if (!$el.length) {
4241
                return;
4242
            }
4243
            var pct = Math.min(p, 100), out, pctLimit = self.progressUploadThreshold,
4244
                t = p <= 100 ? self.progressTemplate : self.progressCompleteTemplate,
4245
                template = pct < 100 ? self.progressTemplate :
4246
                    (error ? (self.paused ? self.progressPauseTemplate : self.progressErrorTemplate) : t);
4247
            if (p >= 100) {
4248
                stats = '';
4249
            }
4250
            if (!$h.isEmpty(template)) {
4251
                if (pctLimit && pct > pctLimit && p <= 100) {
4252
                    out = template.setTokens({'percent': pctLimit, 'status': self.msgUploadThreshold});
4253
                } else {
4254
                    out = template.setTokens({'percent': pct, 'status': (p > 100 ? self.msgUploadEnd : pct + '%')});
4255
                }
4256
                stats = stats || '';
4257
                out = out.setTokens({stats: stats});
4258
                $h.setHtml($el, out);
4259
                if (error) {
4260
                    $h.setHtml($el.find('[role="progressbar"]'), error);
4261
                }
4262
            }
4263
        },
4264
        _hasFiles: function () {
4265
            var el = this.$element[0];
4266
            return !!(el && el.files && el.files.length);
4267
        },
4268
        _setFileDropZoneTitle: function () {
4269
            var self = this, $zone = self.$container.find('.file-drop-zone'), title = self.dropZoneTitle, strFiles;
4270
            if (self.isClickable) {
4271
                strFiles = $h.isEmpty(self.$element.attr('multiple')) ? self.fileSingle : self.filePlural;
4272
                title += self.dropZoneClickTitle.replace('{files}', strFiles);
4273
            }
4274
            $zone.find('.' + self.dropZoneTitleClass).remove();
4275
            if (!self.showPreview || $zone.length === 0 || self.fileManager.count() > 0 || !self.dropZoneEnabled ||
4276
                self.previewCache.count() > 0 || (!self.isAjaxUpload && self._hasFiles())) {
4277
                return;
4278
            }
4279
            if ($zone.find($h.FRAMES).length === 0 && $h.isEmpty(self.defaultPreviewContent)) {
4280
                $zone.prepend('<div class="' + self.dropZoneTitleClass + '">' + title + '</div>');
4281
            }
4282
            self.$container.removeClass('file-input-new');
4283
            $h.addCss(self.$container, 'file-input-ajax-new');
4284
        },
4285
        _getStats: function (stats) {
4286
            var self = this, pendingTime, t;
4287
            if (!self.showUploadStats || !stats || !stats.bitrate) {
4288
                return '';
4289
            }
4290
            t = self._getLayoutTemplate('stats');
4291
            pendingTime = (!stats.elapsed || !stats.bps) ? self.msgCalculatingTime :
4292
                self.msgPendingTime.setTokens({time: $h.getElapsed(Math.ceil(stats.pendingBytes / stats.bps))});
4293
 
4294
            return t.setTokens({
4295
                uploadSpeed: stats.bitrate,
4296
                pendingTime: pendingTime
4297
            });
4298
        },
4299
        _setResumableProgress: function (pct, stats, $thumb) {
4300
            var self = this, rm = self.resumableManager, obj = $thumb ? rm : self,
4301
                $prog = $thumb ? $thumb.find('.file-thumb-progress') : null;
4302
            if (obj.lastProgress === 0) {
4303
                obj.lastProgress = pct;
4304
            }
4305
            if (pct < obj.lastProgress) {
4306
                pct = obj.lastProgress;
4307
            }
4308
            self._setProgress(pct, $prog, null, self._getStats(stats));
4309
            obj.lastProgress = pct;
4310
        },
4311
        _toggleResumableProgress: function (template, message) {
4312
            var self = this, $progress = self.$progress;
4313
            if ($progress && $progress.length) {
4314
                $h.setHtml($progress, template.setTokens({
4315
                    percent: 101,
4316
                    status: message,
4317
                    stats: ''
4318
                }));
4319
            }
4320
        },
4321
        _setFileUploadStats: function (id, pct, stats) {
4322
            var self = this, $prog = self.$progress;
4323
            if (!self.showPreview && (!$prog || !$prog.length)) {
4324
                return;
4325
            }
4326
            var fm = self.fileManager, rm = self.resumableManager, $thumb = fm.getThumb(id), pctTot,
4327
                totUpSize = 0, totSize = fm.getTotalSize(), totStats = $.extend(true, {}, stats);
4328
            if (self.enableResumableUpload) {
4329
                var loaded = stats.loaded, currUplSize = rm.getUploadedSize(), currTotSize = rm.file.size, totLoaded;
4330
                loaded += currUplSize;
4331
                totLoaded = fm.uploadedSize + loaded;
4332
                pct = $h.round(100 * loaded / currTotSize);
4333
                stats.pendingBytes = currTotSize - currUplSize;
4334
                self._setResumableProgress(pct, stats, $thumb);
4335
                pctTot = Math.floor(100 * totLoaded / totSize);
4336
                totStats.pendingBytes = totSize - totLoaded;
4337
                self._setResumableProgress(pctTot, totStats);
4338
            } else {
4339
                fm.setProgress(id, pct);
4340
                $prog = $thumb && $thumb.length ? $thumb.find('.file-thumb-progress') : null;
4341
                self._setProgress(pct, $prog, null, self._getStats(stats));
4342
                $.each(fm.stats, function (id, cfg) {
4343
                    totUpSize += cfg.loaded;
4344
                });
4345
                totStats.pendingBytes = totSize - totUpSize;
4346
                pctTot = $h.round(totUpSize / totSize * 100);
4347
                self._setProgress(pctTot, null, null, self._getStats(totStats));
4348
            }
4349
        },
4350
        _validateMinCount: function () {
4351
            var self = this, len = self.isAjaxUpload ? self.fileManager.count() : self._inputFileCount();
4352
            if (self.validateInitialCount && self.minFileCount > 0 && self._getFileCount(len - 1) < self.minFileCount) {
4353
                self._noFilesError({});
4354
                return false;
4355
            }
4356
            return true;
4357
        },
4358
        _getFileCount: function (fileCount, includeInitial) {
4359
            var self = this, addCount = 0;
4360
            if (includeInitial === undefined) {
4361
                includeInitial = self.validateInitialCount && !self.overwriteInitial;
4362
            }
4363
            if (includeInitial) {
4364
                addCount = self.previewCache.count(true);
4365
                fileCount += addCount;
4366
            }
4367
            return fileCount;
4368
        },
4369
        _getFileId: function (file) {
4370
            return $h.getFileId(file, this.generateFileId);
4371
        },
4372
        _getFileName: function (file, defaultValue) {
4373
            var self = this, fileName = $h.getFileName(file);
4374
            return fileName ? self.slug(fileName) : defaultValue;
4375
        },
4376
        _getFileNames: function (skipNull) {
4377
            var self = this;
4378
            return self.filenames.filter(function (n) {
4379
                return (skipNull ? n !== undefined : n !== undefined && n !== null);
4380
            });
4381
        },
4382
        _setPreviewError: function ($thumb, keepFile) {
4383
            var self = this, removeFrame = self.removeFromPreviewOnError && !self.retryErrorUploads;
4384
            if (!keepFile || removeFrame) {
4385
                self.fileManager.remove($thumb);
4386
            }
4387
            if (!self.showPreview) {
4388
                return;
4389
            }
4390
            if (removeFrame) {
4391
                $thumb.remove();
4392
                return;
4393
            } else {
4394
                self._setThumbStatus($thumb, 'Error');
4395
            }
4396
            self._refreshUploadButton($thumb);
4397
        },
4398
        _refreshUploadButton: function ($thumb) {
4399
            var self = this, $btn = $thumb.find('.kv-file-upload'), cfg = self.fileActionSettings,
4400
                icon = cfg.uploadIcon, title = cfg.uploadTitle;
4401
            if (!$btn.length) {
4402
                return;
4403
            }
4404
            if (self.retryErrorUploads) {
4405
                icon = cfg.uploadRetryIcon;
4406
                title = cfg.uploadRetryTitle;
4407
            }
4408
            $btn.attr('title', title);
4409
            $h.setHtml($btn, icon);
4410
        },
4411
        _checkDimensions: function (i, chk, $img, $thumb, fname, type, params) {
4412
            var self = this, msg, dim, tag = chk === 'Small' ? 'min' : 'max', limit = self[tag + 'Image' + type],
4413
                $imgEl, isValid;
4414
            if ($h.isEmpty(limit) || !$img.length) {
4415
                return;
4416
            }
4417
            $imgEl = $img[0];
4418
            dim = (type === 'Width') ? $imgEl.naturalWidth || $imgEl.width : $imgEl.naturalHeight || $imgEl.height;
4419
            isValid = chk === 'Small' ? dim >= limit : dim <= limit;
4420
            if (isValid) {
4421
                return;
4422
            }
4423
            msg = self['msgImage' + type + chk].setTokens({'name': fname, 'size': limit});
4424
            self._showFileError(msg, params);
4425
            self._setPreviewError($thumb);
4426
        },
4427
        _getExifObj: function (data) {
4428
            var self = this, exifObj, error = $h.logMessages.exifWarning;
4429
            if (data.slice(0, 23) !== 'data:image/jpeg;base64,' && data.slice(0, 22) !== 'data:image/jpg;base64,') {
4430
                exifObj = null;
4431
                return;
4432
            }
4433
            try {
4434
                exifObj = window.piexif ? window.piexif.load(data) : null;
4435
            } catch (err) {
4436
                exifObj = null;
4437
                error = err && err.message || '';
4438
            }
4439
            if (!exifObj) {
4440
                self._log($h.logMessages.badExifParser, {details: error});
4441
            }
4442
            return exifObj;
4443
        },
4444
        setImageOrientation: function ($img, $zoomImg, value, $thumb) {
4445
            var self = this, invalidImg = !$img || !$img.length, invalidZoomImg = !$zoomImg || !$zoomImg.length, $mark,
4446
                isHidden = false, $div, zoomOnly = invalidImg && $thumb && $thumb.attr('data-template') === 'image', ev;
4447
            if (invalidImg && invalidZoomImg) {
4448
                return;
4449
            }
4450
            ev = 'load.fileinputimageorient';
4451
            if (zoomOnly) {
4452
                $img = $zoomImg;
4453
                $zoomImg = null;
4454
                $img.css(self.previewSettings.image);
4455
                $div = $(document.createElement('div')).appendTo($thumb.find('.kv-file-content'));
4456
                $mark = $(document.createElement('span')).insertBefore($img);
4457
                $img.css('visibility', 'hidden').removeClass('file-zoom-detail').appendTo($div);
4458
            } else {
4459
                isHidden = !$img.is(':visible');
4460
            }
4461
            $img.off(ev).on(ev, function () {
4462
                if (isHidden) {
4463
                    self.$preview.removeClass('hide-content');
4464
                    $thumb.find('.kv-file-content').css('visibility', 'hidden');
4465
                }
4466
                var img = $img[0], zoomImg = $zoomImg && $zoomImg.length ? $zoomImg[0] : null,
4467
                    h = img.offsetHeight, w = img.offsetWidth, r = $h.getRotation(value);
4468
                if (isHidden) {
4469
                    $thumb.find('.kv-file-content').css('visibility', 'visible');
4470
                    self.$preview.addClass('hide-content');
4471
                }
4472
                $img.data('orientation', value);
4473
                if (zoomImg) {
4474
                    $zoomImg.data('orientation', value);
4475
                }
4476
                if (value < 5) {
4477
                    $h.setTransform(img, r);
4478
                    $h.setTransform(zoomImg, r);
4479
                    return;
4480
                }
4481
                var offsetAngle = Math.atan(w / h), origFactor = Math.sqrt(Math.pow(h, 2) + Math.pow(w, 2)),
4482
                    scale = !origFactor ? 1 : (h / Math.cos(Math.PI / 2 + offsetAngle)) / origFactor,
4483
                    s = ' scale(' + Math.abs(scale) + ')';
4484
                $h.setTransform(img, r + s);
4485
                $h.setTransform(zoomImg, r + s);
4486
                if (zoomOnly) {
4487
                    $img.css('visibility', 'visible').insertAfter($mark).addClass('file-zoom-detail');
4488
                    $mark.remove();
4489
                    $div.remove();
4490
                }
4491
            });
4492
        },
4493
        _validateImageOrientation: function ($img, file, previewId, fileId, caption, ftype, fsize, iData) {
4494
            var self = this, exifObj, value, autoOrientImage = self.autoOrientImage, selector;
4495
            if (self.canOrientImage) {
4496
                $img.css('image-orientation', (autoOrientImage ? 'from-image' : 'none'));
4497
                return;
4498
            }
4499
            selector = $h.getZoomSelector(previewId, ' img');
4500
            exifObj = autoOrientImage ? self._getExifObj(iData) : null;
4501
            value = exifObj ? exifObj['0th'][piexif.ImageIFD.Orientation] : null; // jshint ignore:line
4502
            if (!value) {
4503
                self._validateImage(previewId, fileId, caption, ftype, fsize, iData, exifObj);
4504
                return;
4505
            }
4506
            self.setImageOrientation($img, $(selector), value, self._getFrame(previewId));
4507
            self._raise('fileimageoriented', {'$img': $img, 'file': file});
4508
            self._validateImage(previewId, fileId, caption, ftype, fsize, iData, exifObj);
4509
        },
4510
        _validateImage: function (previewId, fileId, fname, ftype, fsize, iData, exifObj) {
4511
            var self = this, $preview = self.$preview, params, w1, w2, $thumb = self._getFrame(previewId),
4512
                i = $thumb.attr('data-fileindex'), $img = $thumb.find('img');
4513
            fname = fname || 'Untitled';
4514
            $img.one('load', function () {
4515
                w1 = $thumb.width();
4516
                w2 = $preview.width();
4517
                if (w1 > w2) {
4518
                    $img.css('width', '100%');
4519
                }
4520
                params = {ind: i, id: previewId, fileId: fileId};
4521
                self._checkDimensions(i, 'Small', $img, $thumb, fname, 'Width', params);
4522
                self._checkDimensions(i, 'Small', $img, $thumb, fname, 'Height', params);
4523
                if (!self.resizeImage) {
4524
                    self._checkDimensions(i, 'Large', $img, $thumb, fname, 'Width', params);
4525
                    self._checkDimensions(i, 'Large', $img, $thumb, fname, 'Height', params);
4526
                }
4527
                self._raise('fileimageloaded', [previewId]);
4528
                self.fileManager.addImage(fileId, {
4529
                    ind: i,
4530
                    img: $img,
4531
                    thumb: $thumb,
4532
                    pid: previewId,
4533
                    typ: ftype,
4534
                    siz: fsize,
4535
                    validated: false,
4536
                    imgData: iData,
4537
                    exifObj: exifObj
4538
                });
4539
                $thumb.data('exif', exifObj);
4540
                self._validateAllImages();
4541
            }).one('error', function () {
4542
                self._raise('fileimageloaderror', [previewId]);
4543
            }).each(function () {
4544
                if (this.complete) {
4545
                    $(this).trigger('load');
4546
                } else {
4547
                    if (this.error) {
4548
                        $(this).trigger('error');
4549
                    }
4550
                }
4551
            });
4552
        },
4553
        _validateAllImages: function () {
4554
            var self = this, counter = {val: 0}, numImgs = self.fileManager.getImageCount(), fsize,
4555
                minSize = self.resizeIfSizeMoreThan;
4556
            if (numImgs !== self.fileManager.totalImages) {
4557
                return;
4558
            }
4559
            self._raise('fileimagesloaded');
4560
            if (!self.resizeImage) {
4561
                return;
4562
            }
4563
            $.each(self.fileManager.loadedImages, function (id, config) {
4564
                if (!config.validated) {
4565
                    fsize = config.siz;
4566
                    if (fsize && fsize > minSize * 1000) {
4567
                        self._getResizedImage(id, config, counter, numImgs);
4568
                    }
4569
                    config.validated = true;
4570
                }
4571
            });
4572
        },
4573
        _getResizedImage: function (id, config, counter, numImgs) {
4574
            var self = this, img = $(config.img)[0], width = img.naturalWidth, height = img.naturalHeight, blob,
4575
                ratio = 1, maxWidth = self.maxImageWidth || width, maxHeight = self.maxImageHeight || height,
4576
                isValidImage = !!(width && height), chkWidth, chkHeight, canvas = self.imageCanvas, dataURI,
4577
                context = self.imageCanvasContext, type = config.typ, pid = config.pid, ind = config.ind,
4578
                $thumb = config.thumb, throwError, msg, exifObj = config.exifObj, exifStr, file, params, evParams;
4579
            throwError = function (msg, params, ev) {
4580
                if (self.isAjaxUpload) {
4581
                    self._showFileError(msg, params, ev);
4582
                } else {
4583
                    self._showError(msg, params, ev);
4584
                }
4585
                self._setPreviewError($thumb);
4586
            };
4587
            file = self.fileManager.getFile(id);
4588
            params = {id: pid, 'index': ind, fileId: id};
4589
            evParams = [id, pid, ind];
4590
            if (!file || !isValidImage || (width <= maxWidth && height <= maxHeight)) {
4591
                if (isValidImage && file) {
4592
                    self._raise('fileimageresized', evParams);
4593
                }
4594
                counter.val++;
4595
                if (counter.val === numImgs) {
4596
                    self._raise('fileimagesresized');
4597
                }
4598
                if (!isValidImage) {
4599
                    throwError(self.msgImageResizeError, params, 'fileimageresizeerror');
4600
                    return;
4601
                }
4602
            }
4603
            type = type || self.resizeDefaultImageType;
4604
            chkWidth = width > maxWidth;
4605
            chkHeight = height > maxHeight;
4606
            if (self.resizePreference === 'width') {
4607
                ratio = chkWidth ? maxWidth / width : (chkHeight ? maxHeight / height : 1);
4608
            } else {
4609
                ratio = chkHeight ? maxHeight / height : (chkWidth ? maxWidth / width : 1);
4610
            }
4611
            self._resetCanvas();
4612
            width *= ratio;
4613
            height *= ratio;
4614
            canvas.width = width;
4615
            canvas.height = height;
4616
            try {
4617
                context.drawImage(img, 0, 0, width, height);
4618
                dataURI = canvas.toDataURL(type, self.resizeQuality);
4619
                if (exifObj) {
4620
                    exifStr = window.piexif.dump(exifObj);
4621
                    dataURI = window.piexif.insert(exifStr, dataURI);
4622
                }
4623
                blob = $h.dataURI2Blob(dataURI);
4624
                self.fileManager.setFile(id, blob);
4625
                self._raise('fileimageresized', evParams);
4626
                counter.val++;
4627
                if (counter.val === numImgs) {
4628
                    self._raise('fileimagesresized', [undefined, undefined]);
4629
                }
4630
                if (!(blob instanceof Blob)) {
4631
                    throwError(self.msgImageResizeError, params, 'fileimageresizeerror');
4632
                }
4633
            } catch (err) {
4634
                counter.val++;
4635
                if (counter.val === numImgs) {
4636
                    self._raise('fileimagesresized', [undefined, undefined]);
4637
                }
4638
                msg = self.msgImageResizeException.replace('{errors}', err.message);
4639
                throwError(msg, params, 'fileimageresizeexception');
4640
            }
4641
        },
4642
        _showProgress: function () {
4643
            var self = this;
4644
            if (self.$progress && self.$progress.length) {
4645
                self.$progress.show();
4646
            }
4647
        },
4648
        _hideProgress: function () {
4649
            var self = this;
4650
            if (self.$progress && self.$progress.length) {
4651
                self.$progress.hide();
4652
            }
4653
        },
4654
        _initBrowse: function ($container) {
4655
            var self = this, $el = self.$element;
4656
            if (self.showBrowse) {
4657
                self.$btnFile = $container.find('.btn-file').append($el);
4658
            } else {
4659
                $el.appendTo($container).attr('tabindex', -1);
4660
                $h.addCss($el, 'file-no-browse');
4661
            }
4662
        },
4663
        _initClickable: function () {
4664
            var self = this, $zone, $tmpZone;
4665
            if (!self.isClickable) {
4666
                return;
4667
            }
4668
            $zone = self.$dropZone;
4669
            if (!self.isAjaxUpload) {
4670
                $tmpZone = self.$preview.find('.file-default-preview');
4671
                if ($tmpZone.length) {
4672
                    $zone = $tmpZone;
4673
                }
4674
            }
4675
 
4676
            $h.addCss($zone, 'clickable');
4677
            $zone.attr('tabindex', -1);
4678
            self._handler($zone, 'click', function (e) {
4679
                var $tar = $(e.target);
4680
                if (!$(self.elErrorContainer + ':visible').length &&
4681
                    (!$tar.parents('.file-preview-thumbnails').length || $tar.parents(
4682
                        '.file-default-preview').length)) {
4683
                    self.$element.data('zoneClicked', true).trigger('click');
4684
                    $zone.blur();
4685
                }
4686
            });
4687
        },
4688
        _initCaption: function () {
4689
            var self = this, cap = self.initialCaption || '';
4690
            if (self.overwriteInitial || $h.isEmpty(cap)) {
4691
                self.$caption.val('');
4692
                return false;
4693
            }
4694
            self._setCaption(cap);
4695
            return true;
4696
        },
4697
        _setCaption: function (content, isError) {
4698
            var self = this, title, out, icon, n, cap, file;
4699
            if (!self.$caption.length) {
4700
                return;
4701
            }
4702
            self.$captionContainer.removeClass('icon-visible');
4703
            if (isError) {
4704
                title = $('<div>' + self.msgValidationError + '</div>').text();
4705
                n = self.fileManager.count();
4706
                if (n) {
4707
                    file = self.fileManager.getFirstFile();
4708
                    cap = n === 1 && file ? file.nameFmt : self._getMsgSelected(n);
4709
                } else {
4710
                    cap = self._getMsgSelected(self.msgNo);
4711
                }
4712
                out = $h.isEmpty(content) ? cap : content;
4713
                icon = '<span class="' + self.msgValidationErrorClass + '">' + self.msgValidationErrorIcon + '</span>';
4714
            } else {
4715
                if ($h.isEmpty(content)) {
4716
                    return;
4717
                }
4718
                title = $('<div>' + content + '</div>').text();
4719
                out = title;
4720
                icon = self._getLayoutTemplate('fileIcon');
4721
            }
4722
            self.$captionContainer.addClass('icon-visible');
4723
            self.$caption.attr('title', title).val(out);
4724
            $h.setHtml(self.$captionIcon, icon);
4725
        },
4726
        _createContainer: function () {
4727
            var self = this, attribs = {'class': 'file-input file-input-new' + (self.rtl ? ' kv-rtl' : '')},
4728
                $container = $h.createElement($h.cspBuffer.stash(self._renderMain()));
4729
            $h.cspBuffer.apply($container);
4730
            $container.insertBefore(self.$element).attr(attribs);
4731
            self._initBrowse($container);
4732
            if (self.theme) {
4733
                $container.addClass('theme-' + self.theme);
4734
            }
4735
            return $container;
4736
        },
4737
        _refreshContainer: function () {
4738
            var self = this, $container = self.$container, $el = self.$element;
4739
            $el.insertAfter($container);
4740
            $h.setHtml($container, self._renderMain());
4741
            self._initBrowse($container);
4742
            self._validateDisabled();
4743
        },
4744
        _validateDisabled: function () {
4745
            var self = this;
4746
            self.$caption.attr({readonly: self.isDisabled});
4747
        },
4748
        _renderMain: function () {
4749
            var self = this,
4750
                dropCss = self.dropZoneEnabled ? ' file-drop-zone' : 'file-drop-disabled',
4751
                close = !self.showClose ? '' : self._getLayoutTemplate('close'),
4752
                preview = !self.showPreview ? '' : self._getLayoutTemplate('preview')
4753
                    .setTokens({'class': self.previewClass, 'dropClass': dropCss}),
4754
                css = self.isDisabled ? self.captionClass + ' file-caption-disabled' : self.captionClass,
4755
                caption = self.captionTemplate.setTokens({'class': css + ' kv-fileinput-caption'});
4756
            return self.mainTemplate.setTokens({
4757
                'class': self.mainClass + (!self.showBrowse && self.showCaption ? ' no-browse' : ''),
4758
                'preview': preview,
4759
                'close': close,
4760
                'caption': caption,
4761
                'upload': self._renderButton('upload'),
4762
                'remove': self._renderButton('remove'),
4763
                'cancel': self._renderButton('cancel'),
4764
                'pause': self._renderButton('pause'),
4765
                'browse': self._renderButton('browse')
4766
            });
4767
 
4768
        },
4769
        _renderButton: function (type) {
4770
            var self = this, tmplt = self._getLayoutTemplate('btnDefault'), css = self[type + 'Class'],
4771
                title = self[type + 'Title'], icon = self[type + 'Icon'], label = self[type + 'Label'],
4772
                status = self.isDisabled ? ' disabled' : '', btnType = 'button';
4773
            switch (type) {
4774
                case 'remove':
4775
                    if (!self.showRemove) {
4776
                        return '';
4777
                    }
4778
                    break;
4779
                case 'cancel':
4780
                    if (!self.showCancel) {
4781
                        return '';
4782
                    }
4783
                    css += ' kv-hidden';
4784
                    break;
4785
                case 'pause':
4786
                    if (!self.showPause) {
4787
                        return '';
4788
                    }
4789
                    css += ' kv-hidden';
4790
                    break;
4791
                case 'upload':
4792
                    if (!self.showUpload) {
4793
                        return '';
4794
                    }
4795
                    if (self.isAjaxUpload && !self.isDisabled) {
4796
                        tmplt = self._getLayoutTemplate('btnLink').replace('{href}', self.uploadUrl);
4797
                    } else {
4798
                        btnType = 'submit';
4799
                    }
4800
                    break;
4801
                case 'browse':
4802
                    if (!self.showBrowse) {
4803
                        return '';
4804
                    }
4805
                    tmplt = self._getLayoutTemplate('btnBrowse');
4806
                    break;
4807
                default:
4808
                    return '';
4809
            }
4810
 
4811
            css += type === 'browse' ? ' btn-file' : ' fileinput-' + type + ' fileinput-' + type + '-button';
4812
            if (!$h.isEmpty(label)) {
4813
                label = ' <span class="' + self.buttonLabelClass + '">' + label + '</span>';
4814
            }
4815
            return tmplt.setTokens({
4816
                'type': btnType, 'css': css, 'title': title, 'status': status, 'icon': icon, 'label': label
4817
            });
4818
        },
4819
        _renderThumbProgress: function () {
4820
            var self = this;
4821
            return '<div class="file-thumb-progress kv-hidden">' +
4822
                self.progressInfoTemplate.setTokens({percent: 101, status: self.msgUploadBegin, stats: ''}) +
4823
                '</div>';
4824
        },
4825
        _renderFileFooter: function (cat, caption, size, width, isError) {
4826
            var self = this, config = self.fileActionSettings, rem = config.showRemove, drg = config.showDrag,
4827
                upl = config.showUpload, zoom = config.showZoom, out, params,
4828
                template = self._getLayoutTemplate('footer'), tInd = self._getLayoutTemplate('indicator'),
4829
                ind = isError ? config.indicatorError : config.indicatorNew,
4830
                title = isError ? config.indicatorErrorTitle : config.indicatorNewTitle,
4831
                indicator = tInd.setTokens({'indicator': ind, 'indicatorTitle': title});
4832
            size = self._getSize(size);
4833
            params = {type: cat, caption: caption, size: size, width: width, progress: '', indicator: indicator};
4834
            if (self.isAjaxUpload) {
4835
                params.progress = self._renderThumbProgress();
4836
                params.actions = self._renderFileActions(params, upl, false, rem, zoom, drg, false, false, false);
4837
            } else {
4838
                params.actions = self._renderFileActions(params, false, false, false, zoom, drg, false, false, false);
4839
            }
4840
            out = template.setTokens(params);
4841
            out = $h.replaceTags(out, self.previewThumbTags);
4842
            return out;
4843
        },
4844
        _renderFileActions: function (
4845
            cfg,
4846
            showUpl,
4847
            showDwn,
4848
            showDel,
4849
            showZoom,
4850
            showDrag,
4851
            disabled,
4852
            url,
4853
            key,
4854
            isInit,
4855
            dUrl,
4856
            dFile
4857
        ) {
4858
            var self = this;
4859
            if (!cfg.type && isInit) {
4860
                cfg.type = 'image';
4861
            }
4862
            if (self.enableResumableUpload) {
4863
                showUpl = false;
4864
            } else {
4865
                if (typeof showUpl === 'function') {
4866
                    showUpl = showUpl(cfg);
4867
                }
4868
            }
4869
            if (typeof showDwn === 'function') {
4870
                showDwn = showDwn(cfg);
4871
            }
4872
            if (typeof showDel === 'function') {
4873
                showDel = showDel(cfg);
4874
            }
4875
            if (typeof showZoom === 'function') {
4876
                showZoom = showZoom(cfg);
4877
            }
4878
            if (typeof showDrag === 'function') {
4879
                showDrag = showDrag(cfg);
4880
            }
4881
            if (!showUpl && !showDwn && !showDel && !showZoom && !showDrag) {
4882
                return '';
4883
            }
4884
            var vUrl = url === false ? '' : ' data-url="' + url + '"', btnZoom = '', btnDrag = '', css,
4885
                vKey = key === false ? '' : ' data-key="' + key + '"', btnDelete = '', btnUpload = '', btnDownload = '',
4886
                template = self._getLayoutTemplate('actions'), config = self.fileActionSettings,
4887
                otherButtons = self.otherActionButtons.setTokens({'dataKey': vKey, 'key': key}),
4888
                removeClass = disabled ? config.removeClass + ' disabled' : config.removeClass;
4889
            if (showDel) {
4890
                btnDelete = self._getLayoutTemplate('actionDelete').setTokens({
4891
                    'removeClass': removeClass,
4892
                    'removeIcon': config.removeIcon,
4893
                    'removeTitle': config.removeTitle,
4894
                    'dataUrl': vUrl,
4895
                    'dataKey': vKey,
4896
                    'key': key
4897
                });
4898
            }
4899
            if (showUpl) {
4900
                btnUpload = self._getLayoutTemplate('actionUpload').setTokens({
4901
                    'uploadClass': config.uploadClass,
4902
                    'uploadIcon': config.uploadIcon,
4903
                    'uploadTitle': config.uploadTitle
4904
                });
4905
            }
4906
            if (showDwn) {
4907
                btnDownload = self._getLayoutTemplate('actionDownload').setTokens({
4908
                    'downloadClass': config.downloadClass,
4909
                    'downloadIcon': config.downloadIcon,
4910
                    'downloadTitle': config.downloadTitle,
4911
                    'downloadUrl': dUrl || self.initialPreviewDownloadUrl
4912
                });
4913
                btnDownload = btnDownload.setTokens({'filename': dFile, 'key': key});
4914
            }
4915
            if (showZoom) {
4916
                btnZoom = self._getLayoutTemplate('actionZoom').setTokens({
4917
                    'zoomClass': config.zoomClass,
4918
                    'zoomIcon': config.zoomIcon,
4919
                    'zoomTitle': config.zoomTitle
4920
                });
4921
            }
4922
            if (showDrag && isInit) {
4923
                css = 'drag-handle-init ' + config.dragClass;
4924
                btnDrag = self._getLayoutTemplate('actionDrag').setTokens({
4925
                    'dragClass': css,
4926
                    'dragTitle': config.dragTitle,
4927
                    'dragIcon': config.dragIcon
4928
                });
4929
            }
4930
            return template.setTokens({
4931
                'delete': btnDelete,
4932
                'upload': btnUpload,
4933
                'download': btnDownload,
4934
                'zoom': btnZoom,
4935
                'drag': btnDrag,
4936
                'other': otherButtons
4937
            });
4938
        },
4939
        _browse: function (e) {
4940
            var self = this;
4941
            if (e && e.isDefaultPrevented() || !self._raise('filebrowse')) {
4942
                return;
4943
            }
4944
            if (self.isError && !self.isAjaxUpload) {
4945
                self.clear();
4946
            }
4947
            if (self.focusCaptionOnBrowse) {
4948
                self.$captionContainer.focus();
4949
            }
4950
        },
4951
        _change: function (e) {
4952
            var self = this;
4953
            if (self.changeTriggered) {
4954
                return;
4955
            }
4956
            var $el = self.$element, isDragDrop = arguments.length > 1, isAjaxUpload = self.isAjaxUpload,
4957
                tfiles, files = isDragDrop ? arguments[1] : $el[0].files, ctr = self.fileManager.count(),
4958
                total, initCount, len, isSingleUpl = $h.isEmpty($el.attr('multiple')),
4959
                maxCount = !isAjaxUpload && isSingleUpl ? 1 : self.maxFileCount, maxTotCount = self.maxTotalFileCount,
4960
                inclAll = maxTotCount > 0 && maxTotCount > maxCount, flagSingle = (isSingleUpl && ctr > 0),
4961
                throwError = function (mesg, file, previewId, index) {
4962
                    var p1 = $.extend(true, {}, self._getOutData(null, {}, {}, files), {id: previewId, index: index}),
4963
                        p2 = {id: previewId, index: index, file: file, files: files};
4964
                    self.isPersistentError = true;
4965
                    return isAjaxUpload ? self._showFileError(mesg, p1) : self._showError(mesg, p2);
4966
                },
4967
                maxCountCheck = function (n, m, all) {
4968
                    var msg = all ? self.msgTotalFilesTooMany : self.msgFilesTooMany;
4969
                    msg = msg.replace('{m}', m).replace('{n}', n);
4970
                    self.isError = throwError(msg, null, null, null);
4971
                    self.$captionContainer.removeClass('icon-visible');
4972
                    self._setCaption('', true);
4973
                    self.$container.removeClass('file-input-new file-input-ajax-new');
4974
                };
4975
            self.reader = null;
4976
            self._resetUpload();
4977
            self._hideFileIcon();
4978
            if (self.dropZoneEnabled) {
4979
                self.$container.find('.file-drop-zone .' + self.dropZoneTitleClass).remove();
4980
            }
4981
            if (!isAjaxUpload) {
4982
                if (e.target && e.target.files === undefined) {
4983
                    files = e.target.value ? [{name: e.target.value.replace(/^.+\\/, '')}] : [];
4984
                } else {
4985
                    files = e.target.files || {};
4986
                }
4987
            }
4988
            tfiles = files;
4989
            if ($h.isEmpty(tfiles) || tfiles.length === 0) {
4990
                if (!isAjaxUpload) {
4991
                    self.clear();
4992
                }
4993
                self._raise('fileselectnone');
4994
                return;
4995
            }
4996
            self._resetErrors();
4997
            len = tfiles.length;
4998
            initCount = isAjaxUpload ? (self.fileManager.count() + len) : len;
4999
            total = self._getFileCount(initCount, inclAll ? false : undefined);
5000
            if (maxCount > 0 && total > maxCount) {
5001
                if (!self.autoReplace || len > maxCount) {
5002
                    maxCountCheck((self.autoReplace && len > maxCount ? len : total), maxCount);
5003
                    return;
5004
                }
5005
                if (total > maxCount) {
5006
                    self._resetPreviewThumbs(isAjaxUpload);
5007
                }
5008
            } else {
5009
                if (inclAll) {
5010
                    total = self._getFileCount(initCount, true);
5011
                    if (maxTotCount > 0 && total > maxTotCount) {
5012
                        if (!self.autoReplace || len > maxCount) {
5013
                            maxCountCheck((self.autoReplace && len > maxTotCount ? len : total), maxTotCount, true);
5014
                            return;
5015
                        }
5016
                        if (total > maxCount) {
5017
                            self._resetPreviewThumbs(isAjaxUpload);
5018
                        }
5019
                    }
5020
                }
5021
                if (!isAjaxUpload || flagSingle) {
5022
                    self._resetPreviewThumbs(false);
5023
                    if (flagSingle) {
5024
                        self.clearFileStack();
5025
                    }
5026
                } else {
5027
                    if (isAjaxUpload && ctr === 0 && (!self.previewCache.count(true) || self.overwriteInitial)) {
5028
                        self._resetPreviewThumbs(true);
5029
                    }
5030
                }
5031
            }
5032
            self.readFiles(tfiles);
5033
        },
5034
        _abort: function (params) {
5035
            var self = this, data;
5036
            if (self.ajaxAborted && typeof self.ajaxAborted === 'object' && self.ajaxAborted.message !== undefined) {
5037
                data = $.extend(true, {}, self._getOutData(null), params);
5038
                data.abortData = self.ajaxAborted.data || {};
5039
                data.abortMessage = self.ajaxAborted.message;
5040
                self._setProgress(101, self.$progress, self.msgCancelled);
5041
                self._showFileError(self.ajaxAborted.message, data, 'filecustomerror');
5042
                self.cancel();
5043
                return true;
5044
            }
5045
            return !!self.ajaxAborted;
5046
        },
5047
        _resetFileStack: function () {
5048
            var self = this, i = 0;
5049
            self._getThumbs().each(function () {
5050
                var $thumb = $(this), ind = $thumb.attr('data-fileindex'), pid = $thumb.attr('id');
5051
                if (ind === '-1' || ind === -1) {
5052
                    return;
5053
                }
5054
                if (!self.fileManager.getFile($thumb.attr('data-fileid'))) {
5055
                    $thumb.attr({'data-fileindex': i});
5056
                    i++;
5057
                } else {
5058
                    $thumb.attr({'data-fileindex': '-1'});
5059
                }
5060
                self._getZoom(pid).attr({
5061
                    'data-fileindex': $thumb.attr('data-fileindex')
5062
                });
5063
            });
5064
        },
5065
        _isFileSelectionValid: function (cnt) {
5066
            var self = this;
5067
            cnt = cnt || 0;
5068
            if (self.required && !self.getFilesCount()) {
5069
                self.$errorContainer.html('');
5070
                self._showFileError(self.msgFileRequired);
5071
                return false;
5072
            }
5073
            if (self.minFileCount > 0 && self._getFileCount(cnt) < self.minFileCount) {
5074
                self._noFilesError({});
5075
                return false;
5076
            }
5077
            return true;
5078
        },
5079
        _canPreview: function (file) {
5080
            var self = this;
5081
            if (!file || !self.showPreview || !self.$preview || !self.$preview.length) {
5082
                return false;
5083
            }
5084
            var name = file.name || '', type = file.type || '', size = (file.size || 0) / 1000,
5085
                cat = self._parseFileType(type, name), allowedTypes, allowedMimes, allowedExts, skipPreview,
5086
                types = self.allowedPreviewTypes, mimes = self.allowedPreviewMimeTypes,
5087
                exts = self.allowedPreviewExtensions || [], dTypes = self.disabledPreviewTypes,
5088
                dMimes = self.disabledPreviewMimeTypes, dExts = self.disabledPreviewExtensions || [],
5089
                maxSize = self.maxFilePreviewSize && parseFloat(self.maxFilePreviewSize) || 0,
5090
                expAllExt = new RegExp('\\.(' + exts.join('|') + ')$', 'i'),
5091
                expDisExt = new RegExp('\\.(' + dExts.join('|') + ')$', 'i');
5092
            allowedTypes = !types || types.indexOf(cat) !== -1;
5093
            allowedMimes = !mimes || mimes.indexOf(type) !== -1;
5094
            allowedExts = !exts.length || $h.compare(name, expAllExt);
5095
            skipPreview = (dTypes && dTypes.indexOf(cat) !== -1) || (dMimes && dMimes.indexOf(type) !== -1) ||
5096
                (dExts.length && $h.compare(name, expDisExt)) || (maxSize && !isNaN(maxSize) && size > maxSize);
5097
            return !skipPreview && (allowedTypes || allowedMimes || allowedExts);
5098
        },
5099
        addToStack: function (file, id) {
5100
            this.fileManager.add(file, id);
5101
        },
5102
        clearFileStack: function () {
5103
            var self = this;
5104
            self.fileManager.clear();
5105
            self._initResumableUpload();
5106
            if (self.enableResumableUpload) {
5107
                if (self.showPause === null) {
5108
                    self.showPause = true;
5109
                }
5110
                if (self.showCancel === null) {
5111
                    self.showCancel = false;
5112
                }
5113
            } else {
5114
                self.showPause = false;
5115
                if (self.showCancel === null) {
5116
                    self.showCancel = true;
5117
                }
5118
            }
5119
            return self.$element;
5120
        },
5121
        getFileStack: function () {
5122
            return this.fileManager.stack;
5123
        },
5124
        getFileList: function () {
5125
            return this.fileManager.list();
5126
        },
5127
        getFilesCount: function (includeInitial) {
5128
            var self = this, len = self.isAjaxUpload ? self.fileManager.count() : self._inputFileCount();
5129
            if (includeInitial) {
5130
                len += self.previewCache.count(true);
5131
            }
5132
            return self._getFileCount(len);
5133
        },
5134
        readFiles: function (files) {
5135
            this.reader = new FileReader();
5136
            var self = this, reader = self.reader, $container = self.$previewContainer,
5137
                $status = self.$previewStatus, msgLoading = self.msgLoading, msgProgress = self.msgProgress,
5138
                previewInitId = self.previewInitId, numFiles = files.length, settings = self.fileTypeSettings,
5139
                readFile, fileTypes = self.allowedFileTypes, typLen = fileTypes ? fileTypes.length : 0,
5140
                fileExt = self.allowedFileExtensions, strExt = $h.isEmpty(fileExt) ? '' : fileExt.join(', '),
5141
                throwError = function (msg, file, previewId, index, fileId) {
5142
                    var $thumb, p1 = $.extend(true, {}, self._getOutData(null, {}, {}, files),
5143
                        {id: previewId, index: index, fileId: fileId}),
5144
                        p2 = {id: previewId, index: index, fileId: fileId, file: file, files: files};
5145
                    self._previewDefault(file, true);
5146
                    $thumb = self._getFrame(previewId, true);
5147
                    if (self.isAjaxUpload) {
5148
                        setTimeout(function () {
5149
                            readFile(index + 1);
5150
                        }, self.processDelay);
5151
                    } else {
5152
                        self.unlock();
5153
                        numFiles = 0;
5154
                    }
5155
                    if (self.removeFromPreviewOnError && $thumb.length) {
5156
                        $thumb.remove();
5157
                    } else {
5158
                        self._initFileActions();
5159
                        $thumb.find('.kv-file-upload').remove();
5160
                    }
5161
                    self.isPersistentError = true;
5162
                    self.isError = self.isAjaxUpload ? self._showFileError(msg, p1) : self._showError(msg, p2);
5163
                    self._updateFileDetails(numFiles);
5164
                };
5165
            self.fileManager.clearImages();
5166
            $.each(files, function (key, file) {
5167
                var func = self.fileTypeSettings.image;
5168
                if (func && func(file.type)) {
5169
                    self.fileManager.totalImages++;
5170
                }
5171
            });
5172
            readFile = function (i) {
5173
                var $error = self.$errorContainer, errors, fm = self.fileManager;
5174
                if (i >= numFiles) {
5175
                    self.unlock();
5176
                    if (self.duplicateErrors.length) {
5177
                        errors = '<li>' + self.duplicateErrors.join('</li><li>') + '</li>';
5178
                        if ($error.find('ul').length === 0) {
5179
                            $h.setHtml($error, self.errorCloseButton + '<ul>' + errors + '</ul>');
5180
                        } else {
5181
                            $error.find('ul').append(errors);
5182
                        }
5183
                        $error.fadeIn(self.fadeDelay);
5184
                        self._handler($error.find('.kv-error-close'), 'click', function () {
5185
                            $error.fadeOut(self.fadeDelay);
5186
                        });
5187
                        self.duplicateErrors = [];
5188
                    }
5189
                    if (self.isAjaxUpload) {
5190
                        self._raise('filebatchselected', [fm.stack]);
5191
                        if (fm.count() === 0 && !self.isError) {
5192
                            self.reset();
5193
                        }
5194
                    } else {
5195
                        self._raise('filebatchselected', [files]);
5196
                    }
5197
                    $container.removeClass('file-thumb-loading');
5198
                    $status.html('');
5199
                    return;
5200
                }
5201
                self.lock(true);
5202
                var file = files[i], id = self._getFileId(file), previewId = previewInitId + '-' + id, fSizeKB, j, msg,
5203
                    fnText = settings.text, fnImage = settings.image, fnHtml = settings.html, typ, chk, typ1, typ2,
5204
                    caption = self._getFileName(file, ''), fileSize = (file && file.size || 0) / 1000,
5205
                    fileExtExpr = '', previewData = $h.createObjectURL(file), fileCount = 0,
5206
                    strTypes = '', fileId, canLoad, fileReaderAborted = false,
5207
                    func, knownTypes = 0, isText, isHtml, isImage, txtFlag, processFileLoaded = function () {
5208
                        var msg = msgProgress.setTokens({
5209
                            'index': i + 1,
5210
                            'files': numFiles,
5211
                            'percent': 50,
5212
                            'name': caption
5213
                        });
5214
                        setTimeout(function () {
5215
                            $status.html(msg);
5216
                            self._updateFileDetails(numFiles);
5217
                            readFile(i + 1);
5218
                        }, self.processDelay);
5219
                        if (self._raise('fileloaded', [file, previewId, id, i, reader]) && self.isAjaxUpload) {
5220
                            fm.add(file);
5221
                        }
5222
                    };
5223
                if (!file) {
5224
                    return;
5225
                }
5226
                fileId = fm.getId(file);
5227
                if (typLen > 0) {
5228
                    for (j = 0; j < typLen; j++) {
5229
                        typ1 = fileTypes[j];
5230
                        typ2 = self.msgFileTypes[typ1] || typ1;
5231
                        strTypes += j === 0 ? typ2 : ', ' + typ2;
5232
                    }
5233
                }
5234
                if (caption === false) {
5235
                    readFile(i + 1);
5236
                    return;
5237
                }
5238
                if (caption.length === 0) {
5239
                    msg = self.msgInvalidFileName.replace('{name}', $h.htmlEncode($h.getFileName(file), '[unknown]'));
5240
                    throwError(msg, file, previewId, i, fileId);
5241
                    return;
5242
                }
5243
                if (!$h.isEmpty(fileExt)) {
5244
                    fileExtExpr = new RegExp('\\.(' + fileExt.join('|') + ')$', 'i');
5245
                }
5246
                fSizeKB = fileSize.toFixed(2);
5247
                if (self.isAjaxUpload && fm.exists(fileId) || self._getFrame(previewId, true).length) {
5248
                    var p2 = {id: previewId, index: i, fileId: fileId, file: file, files: files};
5249
                    msg = self.msgDuplicateFile.setTokens({name: caption, size: fSizeKB});
5250
                    if (self.isAjaxUpload) {
5251
                        self.duplicateErrors.push(msg);
5252
                        self.isDuplicateError = true;
5253
                        self._raise('fileduplicateerror', [file, fileId, caption, fSizeKB, previewId, i]);
5254
                        readFile(i + 1);
5255
                        self._updateFileDetails(numFiles);
5256
                    } else {
5257
                        self._showError(msg, p2);
5258
                        self.unlock();
5259
                        numFiles = 0;
5260
                        self._clearFileInput();
5261
                        self.reset();
5262
                        self._updateFileDetails(numFiles);
5263
                    }
5264
                    return;
5265
                }
5266
                if (self.maxFileSize > 0 && fileSize > self.maxFileSize) {
5267
                    msg = self.msgSizeTooLarge.setTokens({
5268
                        'name': caption,
5269
                        'size': fSizeKB,
5270
                        'maxSize': self.maxFileSize
5271
                    });
5272
                    throwError(msg, file, previewId, i, fileId);
5273
                    return;
5274
                }
5275
                if (self.minFileSize !== null && fileSize <= $h.getNum(self.minFileSize)) {
5276
                    msg = self.msgSizeTooSmall.setTokens({
5277
                        'name': caption,
5278
                        'size': fSizeKB,
5279
                        'minSize': self.minFileSize
5280
                    });
5281
                    throwError(msg, file, previewId, i, fileId);
5282
                    return;
5283
                }
5284
                if (!$h.isEmpty(fileTypes) && $h.isArray(fileTypes)) {
5285
                    for (j = 0; j < fileTypes.length; j += 1) {
5286
                        typ = fileTypes[j];
5287
                        func = settings[typ];
5288
                        fileCount += !func || (typeof func !== 'function') ? 0 : (func(file.type,
5289
                            $h.getFileName(file)) ? 1 : 0);
5290
                    }
5291
                    if (fileCount === 0) {
5292
                        msg = self.msgInvalidFileType.setTokens({name: caption, types: strTypes});
5293
                        throwError(msg, file, previewId, i, fileId);
5294
                        return;
5295
                    }
5296
                }
5297
                if (fileCount === 0 && !$h.isEmpty(fileExt) && $h.isArray(fileExt) && !$h.isEmpty(fileExtExpr)) {
5298
                    chk = $h.compare(caption, fileExtExpr);
5299
                    fileCount += $h.isEmpty(chk) ? 0 : chk.length;
5300
                    if (fileCount === 0) {
5301
                        msg = self.msgInvalidFileExtension.setTokens({name: caption, extensions: strExt});
5302
                        throwError(msg, file, previewId, i, fileId);
5303
                        return;
5304
                    }
5305
                }
5306
                if (!self._canPreview(file)) {
5307
                    canLoad = self.isAjaxUpload && self._raise('filebeforeload', [file, i, reader]);
5308
                    if (self.isAjaxUpload && canLoad) {
5309
                        fm.add(file);
5310
                    }
5311
                    if (self.showPreview && canLoad) {
5312
                        $container.addClass('file-thumb-loading');
5313
                        self._previewDefault(file);
5314
                        self._initFileActions();
5315
                    }
5316
                    setTimeout(function () {
5317
                        if (canLoad) {
5318
                            self._updateFileDetails(numFiles);
5319
                        }
5320
                        readFile(i + 1);
5321
                        self._raise('fileloaded', [file, previewId, id, i]);
5322
                    }, 10);
5323
                    return;
5324
                }
5325
                isText = fnText(file.type, caption);
5326
                isHtml = fnHtml(file.type, caption);
5327
                isImage = fnImage(file.type, caption);
5328
                $status.html(msgLoading.replace('{index}', i + 1).replace('{files}', numFiles));
5329
                $container.addClass('file-thumb-loading');
5330
                reader.onerror = function (evt) {
5331
                    self._errorHandler(evt, caption);
5332
                };
5333
                reader.onload = function (theFile) {
5334
                    var hex, fileInfo, uint, byte, bytes = [], contents, mime, readTextImage = function (textFlag) {
5335
                        var newReader = new FileReader();
5336
                        newReader.onerror = function (theFileNew) {
5337
                            self._errorHandler(theFileNew, caption);
5338
                        };
5339
                        newReader.onload = function (theFileNew) {
5340
                            if (self.isAjaxUpload && !self._raise('filebeforeload', [file, i, reader])) {
5341
                                fileReaderAborted = true;
5342
                                self._resetCaption();
5343
                                reader.abort();
5344
                                $status.html('');
5345
                                $container.removeClass('file-thumb-loading');
5346
                                self.enable();
5347
                                return;
5348
                            }
5349
                            self._previewFile(i, file, theFileNew, previewData, fileInfo);
5350
                            self._initFileActions();
5351
                            processFileLoaded();
5352
                        };
5353
                        if (textFlag) {
5354
                            newReader.readAsText(file, self.textEncoding);
5355
                        } else {
5356
                            newReader.readAsDataURL(file);
5357
                        }
5358
                    };
5359
                    fileInfo = {'name': caption, 'type': file.type};
5360
                    $.each(settings, function (k, f) {
5361
                        if (k !== 'object' && k !== 'other' && typeof f === 'function' && f(file.type, caption)) {
5362
                            knownTypes++;
5363
                        }
5364
                    });
5365
                    if (knownTypes === 0) { // auto detect mime types from content if no known file types detected
5366
                        uint = new Uint8Array(theFile.target.result);
5367
                        for (j = 0; j < uint.length; j++) {
5368
                            byte = uint[j].toString(16);
5369
                            bytes.push(byte);
5370
                        }
5371
                        hex = bytes.join('').toLowerCase().substring(0, 8);
5372
                        mime = $h.getMimeType(hex, '', '');
5373
                        if ($h.isEmpty(mime)) { // look for ascii text content
5374
                            contents = $h.arrayBuffer2String(reader.result);
5375
                            mime = $h.isSvg(contents) ? 'image/svg+xml' : $h.getMimeType(hex, contents, file.type);
5376
                        }
5377
                        fileInfo = {'name': caption, 'type': mime};
5378
                        isText = fnText(mime, '');
5379
                        isHtml = fnHtml(mime, '');
5380
                        isImage = fnImage(mime, '');
5381
                        txtFlag = isText || isHtml;
5382
                        if (txtFlag || isImage) {
5383
                            readTextImage(txtFlag);
5384
                            return;
5385
                        }
5386
                    }
5387
                    if (self.isAjaxUpload && !self._raise('filebeforeload', [file, i, reader])) {
5388
                        fileReaderAborted = true;
5389
                        self._resetCaption();
5390
                        reader.abort();
5391
                        $status.html('');
5392
                        $container.removeClass('file-thumb-loading');
5393
                        self.enable();
5394
                        return;
5395
                    }
5396
                    self._previewFile(i, file, theFile, previewData, fileInfo);
5397
                    self._initFileActions();
5398
                    processFileLoaded();
5399
                };
5400
                reader.onprogress = function (data) {
5401
                    if (data.lengthComputable) {
5402
                        var fact = (data.loaded / data.total) * 100, progress = Math.ceil(fact);
5403
                        msg = msgProgress.setTokens({
5404
                            'index': i + 1,
5405
                            'files': numFiles,
5406
                            'percent': progress,
5407
                            'name': caption
5408
                        });
5409
                        setTimeout(function () {
5410
                            if (!fileReaderAborted) {
5411
                                $status.html(msg);
5412
                            }
5413
                        }, self.processDelay);
5414
                    }
5415
                };
5416
                if (isText || isHtml) {
5417
                    reader.readAsText(file, self.textEncoding);
5418
                } else {
5419
                    if (isImage) {
5420
                        reader.readAsDataURL(file);
5421
                    } else {
5422
                        reader.readAsArrayBuffer(file);
5423
                    }
5424
                }
5425
            };
5426
 
5427
            readFile(0);
5428
            self._updateFileDetails(numFiles, true);
5429
        },
5430
        lock: function (selectMode) {
5431
            var self = this, $container = self.$container;
5432
            self._resetErrors();
5433
            self.disable();
5434
            if (!selectMode && self.showCancel) {
5435
                $container.find('.fileinput-cancel').show();
5436
            }
5437
            if (!selectMode && self.showPause) {
5438
                $container.find('.fileinput-pause').show();
5439
            }
5440
            self._raise('filelock', [self.fileManager.stack, self._getExtraData()]);
5441
            return self.$element;
5442
        },
5443
        unlock: function (reset) {
5444
            var self = this, $container = self.$container;
5445
            if (reset === undefined) {
5446
                reset = true;
5447
            }
5448
            self.enable();
5449
            $container.removeClass('is-locked');
5450
            if (self.showCancel) {
5451
                $container.find('.fileinput-cancel').hide();
5452
            }
5453
            if (self.showPause) {
5454
                $container.find('.fileinput-pause').hide();
5455
            }
5456
            if (reset) {
5457
                self._resetFileStack();
5458
            }
5459
            self._raise('fileunlock', [self.fileManager.stack, self._getExtraData()]);
5460
            return self.$element;
5461
        },
5462
        resume: function () {
5463
            var self = this, flag = false, rm = self.resumableManager;
5464
            if (!self.enableResumableUpload) {
5465
                return self.$element;
5466
            }
5467
            if (self.paused) {
5468
                self._toggleResumableProgress(self.progressPauseTemplate, self.msgUploadResume);
5469
            } else {
5470
                flag = true;
5471
            }
5472
            self.paused = false;
5473
            if (flag) {
5474
                self._toggleResumableProgress(self.progressInfoTemplate, self.msgUploadBegin);
5475
            }
5476
            setTimeout(function () {
5477
                rm.upload();
5478
            }, self.processDelay);
5479
            return self.$element;
5480
        },
5481
        pause: function () {
5482
            var self = this, rm = self.resumableManager, xhr = self.ajaxRequests, len = xhr.length, i,
5483
                pct = rm.getProgress(), actions = self.fileActionSettings, tm = self.taskManager,
5484
                pool = tm.getPool(rm.id);
5485
            if (!self.enableResumableUpload) {
5486
                return self.$element;
5487
            } else {
5488
                if (pool) {
5489
                    pool.cancel();
5490
                }
5491
            }
5492
            self._raise('fileuploadpaused', [self.fileManager, rm]);
5493
            if (len > 0) {
5494
                for (i = 0; i < len; i += 1) {
5495
                    self.paused = true;
5496
                    xhr[i].abort();
5497
                }
5498
            }
5499
            if (self.showPreview) {
5500
                self._getThumbs().each(function () {
5501
                    var $thumb = $(this), fileId = $thumb.attr('data-fileid'), t = self._getLayoutTemplate('stats'),
5502
                        stats, $indicator = $thumb.find('.file-upload-indicator');
5503
                    $thumb.removeClass('file-uploading');
5504
                    if ($indicator.attr('title') === actions.indicatorLoadingTitle) {
5505
                        self._setThumbStatus($thumb, 'Paused');
5506
                        stats = t.setTokens({pendingTime: self.msgPaused, uploadSpeed: ''});
5507
                        self.paused = true;
5508
                        self._setProgress(pct, $thumb.find('.file-thumb-progress'), pct + '%', stats);
5509
                    }
5510
                    if (!self.fileManager.getFile(fileId)) {
5511
                        $thumb.find('.kv-file-remove').removeClass('disabled').removeAttr('disabled');
5512
                    }
5513
                });
5514
            }
5515
            self._setProgress(101, self.$progress, self.msgPaused);
5516
            return self.$element;
5517
        },
5518
        cancel: function () {
5519
            var self = this, xhr = self.ajaxRequests,
5520
                rm = self.resumableManager, tm = self.taskManager,
5521
                pool = rm ? tm.getPool(rm.id) : undefined, len = xhr.length, i;
5522
 
5523
            if (self.enableResumableUpload && pool) {
5524
                pool.cancel().done(function () {
5525
                    self._setProgressCancelled();
5526
                });
5527
                rm.reset();
5528
                self._raise('fileuploadcancelled', [self.fileManager, rm]);
5529
            } else {
5530
                self._raise('fileuploadcancelled', [self.fileManager]);
5531
            }
5532
            self._initAjax();
5533
            if (len > 0) {
5534
                for (i = 0; i < len; i += 1) {
5535
                    self.cancelling = true;
5536
                    xhr[i].abort();
5537
                }
5538
            }
5539
            self._getThumbs().each(function () {
5540
                var $thumb = $(this), fileId = $thumb.attr('data-fileid'), $prog = $thumb.find('.file-thumb-progress');
5541
                $thumb.removeClass('file-uploading');
5542
                self._setProgress(0, $prog);
5543
                $prog.hide();
5544
                if (!self.fileManager.getFile(fileId)) {
5545
                    $thumb.find('.kv-file-upload').removeClass('disabled').removeAttr('disabled');
5546
                    $thumb.find('.kv-file-remove').removeClass('disabled').removeAttr('disabled');
5547
                }
5548
                self.unlock();
5549
            });
5550
            setTimeout(function () {
5551
                self._setProgressCancelled();
5552
            }, self.processDelay);
5553
            return self.$element;
5554
        },
5555
        clear: function () {
5556
            var self = this, cap;
5557
            if (!self._raise('fileclear')) {
5558
                return;
5559
            }
5560
            self.$btnUpload.removeAttr('disabled');
5561
            self._getThumbs().find('video,audio,img').each(function () {
5562
                $h.cleanMemory($(this));
5563
            });
5564
            self._clearFileInput();
5565
            self._resetUpload();
5566
            self.clearFileStack();
5567
            self.isDuplicateError = false;
5568
            self.isPersistentError = false;
5569
            self._resetErrors(true);
5570
            if (self._hasInitialPreview()) {
5571
                self._showFileIcon();
5572
                self._resetPreview();
5573
                self._initPreviewActions();
5574
                self.$container.removeClass('file-input-new');
5575
            } else {
5576
                self._getThumbs().each(function () {
5577
                    self._clearObjects($(this));
5578
                });
5579
                if (self.isAjaxUpload) {
5580
                    self.previewCache.data = {};
5581
                }
5582
                self.$preview.html('');
5583
                cap = (!self.overwriteInitial && self.initialCaption.length > 0) ? self.initialCaption : '';
5584
                self.$caption.attr('title', '').val(cap);
5585
                $h.addCss(self.$container, 'file-input-new');
5586
                self._validateDefaultPreview();
5587
            }
5588
            if (self.$container.find($h.FRAMES).length === 0) {
5589
                if (!self._initCaption()) {
5590
                    self.$captionContainer.removeClass('icon-visible');
5591
                }
5592
            }
5593
            self._hideFileIcon();
5594
            if (self.focusCaptionOnClear) {
5595
                self.$captionContainer.focus();
5596
            }
5597
            self._setFileDropZoneTitle();
5598
            self._raise('filecleared');
5599
            return self.$element;
5600
        },
5601
        reset: function () {
5602
            var self = this;
5603
            if (!self._raise('filereset')) {
5604
                return;
5605
            }
5606
            self.lastProgress = 0;
5607
            self._resetPreview();
5608
            self.$container.find('.fileinput-filename').text('');
5609
            $h.addCss(self.$container, 'file-input-new');
5610
            if (self.getFrames().length) {
5611
                self.$container.removeClass('file-input-new');
5612
            }
5613
            self.clearFileStack();
5614
            self._setFileDropZoneTitle();
5615
            return self.$element;
5616
        },
5617
        disable: function () {
5618
            var self = this, $container = self.$container;
5619
            self.isDisabled = true;
5620
            self._raise('filedisabled');
5621
            self.$element.attr('disabled', 'disabled');
5622
            $container.addClass('is-locked');
5623
            $h.addCss($container.find('.btn-file'), 'disabled');
5624
            $container.find('.kv-fileinput-caption').addClass('file-caption-disabled');
5625
            $container.find('.fileinput-remove, .fileinput-upload, .file-preview-frame button')
5626
                .attr('disabled', true);
5627
            self._initDragDrop();
5628
            return self.$element;
5629
        },
5630
        enable: function () {
5631
            var self = this, $container = self.$container;
5632
            self.isDisabled = false;
5633
            self._raise('fileenabled');
5634
            self.$element.removeAttr('disabled');
5635
            $container.removeClass('is-locked');
5636
            $container.find('.kv-fileinput-caption').removeClass('file-caption-disabled');
5637
            $container.find('.fileinput-remove, .fileinput-upload, .file-preview-frame button')
5638
                .removeAttr('disabled');
5639
            $container.find('.btn-file').removeClass('disabled');
5640
            self._initDragDrop();
5641
            return self.$element;
5642
        },
5643
        upload: function () {
5644
            var self = this, fm = self.fileManager, totLen = fm.count(), i, outData,
5645
                hasExtraData = !$.isEmptyObject(self._getExtraData());
5646
            if (!self.isAjaxUpload || self.isDisabled || !self._isFileSelectionValid(totLen)) {
5647
                return;
5648
            }
5649
            self.lastProgress = 0;
5650
            self._resetUpload();
5651
            if (totLen === 0 && !hasExtraData) {
5652
                self._showFileError(self.msgUploadEmpty);
5653
                return;
5654
            }
5655
            self.cancelling = false;
5656
            self._showProgress();
5657
            self.lock();
5658
            if (totLen === 0 && hasExtraData) {
5659
                self._setProgress(2);
5660
                self._uploadExtraOnly();
5661
                return;
5662
            }
5663
            if (self.enableResumableUpload) {
5664
                return self.resume();
5665
            }
5666
            if (self.uploadAsync || self.enableResumableUpload) {
5667
                outData = self._getOutData(null);
5668
                self._raise('filebatchpreupload', [outData]);
5669
                self.fileBatchCompleted = false;
5670
                self.uploadCache = [];
5671
                $.each(self.getFileStack(), function (id) {
5672
                    var previewId = self._getThumbId(id);
5673
                    self.uploadCache.push({id: previewId, content: null, config: null, tags: null, append: true});
5674
                });
5675
                self.$preview.find('.file-preview-initial').removeClass($h.SORT_CSS);
5676
                self._initSortable();
5677
            }
5678
            self._setProgress(2);
5679
            self.hasInitData = false;
5680
            if (self.uploadAsync) {
5681
                i = 0;
5682
                $.each(fm.stack, function (id) {
5683
                    self._uploadSingle(i, id, true);
5684
                    i++;
5685
                });
5686
                return;
5687
            }
5688
            self._uploadBatch();
5689
            return self.$element;
5690
        },
5691
        destroy: function () {
5692
            var self = this, $form = self.$form, $cont = self.$container, $el = self.$element, ns = self.namespace;
5693
            $(document).off(ns);
5694
            $(window).off(ns);
5695
            if ($form && $form.length) {
5696
                $form.off(ns);
5697
            }
5698
            if (self.isAjaxUpload) {
5699
                self._clearFileInput();
5700
            }
5701
            self._cleanup();
5702
            self._initPreviewCache();
5703
            $el.insertBefore($cont).off(ns).removeData();
5704
            $cont.off().remove();
5705
            return $el;
5706
        },
5707
        refresh: function (options) {
5708
            var self = this, $el = self.$element;
5709
            if (typeof options !== 'object' || $h.isEmpty(options)) {
5710
                options = self.options;
5711
            } else {
5712
                options = $.extend(true, {}, self.options, options);
5713
            }
5714
            self._init(options, true);
5715
            self._listen();
5716
            return $el;
5717
        },
5718
        zoom: function (frameId) {
5719
            var self = this, $frame = self._getFrame(frameId);
5720
            self._showModal($frame);
5721
        },
5722
        getExif: function (frameId) {
5723
            var self = this, $frame = self._getFrame(frameId);
5724
            return $frame && $frame.data('exif') || null;
5725
        },
5726
        getFrames: function (cssFilter) {
5727
            var self = this, $frames;
5728
            cssFilter = cssFilter || '';
5729
            $frames = self.$preview.find($h.FRAMES + cssFilter);
5730
            if (self.reversePreviewOrder) {
5731
                $frames = $($frames.get().reverse());
5732
            }
5733
            return $frames;
5734
        },
5735
        getPreview: function () {
5736
            var self = this;
5737
            return {
5738
                content: self.initialPreview,
5739
                config: self.initialPreviewConfig,
5740
                tags: self.initialPreviewThumbTags
5741
            };
5742
        }
5743
    };
5744
 
5745
    $.fn.fileinput = function (option) {
5746
        if (!$h.hasFileAPISupport() && !$h.isIE(9)) {
5747
            return;
5748
        }
5749
        var args = Array.apply(null, arguments), retvals = [];
5750
        args.shift();
5751
        this.each(function () {
5752
            var self = $(this), data = self.data('fileinput'), options = typeof option === 'object' && option,
5753
                theme = options.theme || self.data('theme'), l = {}, t = {},
5754
                lang = options.language || self.data('language') || $.fn.fileinput.defaults.language || 'en', opt;
5755
            if (!data) {
5756
                if (theme) {
5757
                    t = $.fn.fileinputThemes[theme] || {};
5758
                }
5759
                if (lang !== 'en' && !$h.isEmpty($.fn.fileinputLocales[lang])) {
5760
                    l = $.fn.fileinputLocales[lang] || {};
5761
                }
5762
                opt = $.extend(true, {}, $.fn.fileinput.defaults, t, $.fn.fileinputLocales.en, l, options, self.data());
5763
                data = new FileInput(this, opt);
5764
                self.data('fileinput', data);
5765
            }
5766
 
5767
            if (typeof option === 'string') {
5768
                retvals.push(data[option].apply(data, args));
5769
            }
5770
        });
5771
        switch (retvals.length) {
5772
            case 0:
5773
                return this;
5774
            case 1:
5775
                return retvals[0];
5776
            default:
5777
                return retvals;
5778
        }
5779
    };
5780
 
5781
    var IFRAME_ATTRIBS = 'class="kv-preview-data file-preview-pdf" src="{renderer}?file={data}" {style}';
5782
 
5783
    $.fn.fileinput.defaults = {
5784
        language: 'en',
5785
        showCaption: true,
5786
        showBrowse: true,
5787
        showPreview: true,
5788
        showRemove: true,
5789
        showUpload: true,
5790
        showUploadStats: true,
5791
        showCancel: null,
5792
        showPause: null,
5793
        showClose: true,
5794
        showUploadedThumbs: true,
5795
        showConsoleLogs: false,
5796
        browseOnZoneClick: false,
5797
        autoReplace: false,
5798
        autoOrientImage: function () { // applicable for JPEG images only and non ios safari
5799
            var ua = window.navigator.userAgent, webkit = !!ua.match(/WebKit/i),
5800
                iOS = !!ua.match(/iP(od|ad|hone)/i), iOSSafari = iOS && webkit && !ua.match(/CriOS/i);
5801
            return !iOSSafari;
5802
        },
5803
        autoOrientImageInitial: true,
5804
        required: false,
5805
        rtl: false,
5806
        hideThumbnailContent: false,
5807
        encodeUrl: true,
5808
        focusCaptionOnBrowse: true,
5809
        focusCaptionOnClear: true,
5810
        generateFileId: null,
5811
        previewClass: '',
5812
        captionClass: '',
5813
        frameClass: 'krajee-default',
5814
        mainClass: 'file-caption-main',
5815
        mainTemplate: null,
5816
        purifyHtml: true,
5817
        fileSizeGetter: null,
5818
        initialCaption: '',
5819
        initialPreview: [],
5820
        initialPreviewDelimiter: '*$$*',
5821
        initialPreviewAsData: false,
5822
        initialPreviewFileType: 'image',
5823
        initialPreviewConfig: [],
5824
        initialPreviewThumbTags: [],
5825
        previewThumbTags: {},
5826
        initialPreviewShowDelete: true,
5827
        initialPreviewDownloadUrl: '',
5828
        removeFromPreviewOnError: false,
5829
        deleteUrl: '',
5830
        deleteExtraData: {},
5831
        overwriteInitial: true,
5832
        sanitizeZoomCache: function (content) {
5833
            var $container = $h.createElement(content);
5834
            $container.find('input,textarea,select,datalist,form,.file-thumbnail-footer').remove();
5835
            return $container.html();
5836
        },
5837
        previewZoomButtonIcons: {
5838
            prev: '<i class="glyphicon glyphicon-triangle-left"></i>',
5839
            next: '<i class="glyphicon glyphicon-triangle-right"></i>',
5840
            toggleheader: '<i class="glyphicon glyphicon-resize-vertical"></i>',
5841
            fullscreen: '<i class="glyphicon glyphicon-fullscreen"></i>',
5842
            borderless: '<i class="glyphicon glyphicon-resize-full"></i>',
5843
            close: '<i class="glyphicon glyphicon-remove"></i>'
5844
        },
5845
        previewZoomButtonClasses: {
5846
            prev: 'btn btn-navigate',
5847
            next: 'btn btn-navigate',
5848
            toggleheader: 'btn btn-sm btn-kv btn-default btn-outline-secondary',
5849
            fullscreen: 'btn btn-sm btn-kv btn-default btn-outline-secondary',
5850
            borderless: 'btn btn-sm btn-kv btn-default btn-outline-secondary',
5851
            close: 'btn btn-sm btn-kv btn-default btn-outline-secondary'
5852
        },
5853
        previewTemplates: {},
5854
        previewContentTemplates: {},
5855
        preferIconicPreview: false,
5856
        preferIconicZoomPreview: false,
5857
        allowedFileTypes: null,
5858
        allowedFileExtensions: null,
5859
        allowedPreviewTypes: undefined,
5860
        allowedPreviewMimeTypes: null,
5861
        allowedPreviewExtensions: null,
5862
        disabledPreviewTypes: undefined,
5863
        disabledPreviewExtensions: ['msi', 'exe', 'com', 'zip', 'rar', 'app', 'vb', 'scr'],
5864
        disabledPreviewMimeTypes: null,
5865
        defaultPreviewContent: null,
5866
        customLayoutTags: {},
5867
        customPreviewTags: {},
5868
        previewFileIcon: '<i class="glyphicon glyphicon-file"></i>',
5869
        previewFileIconClass: 'file-other-icon',
5870
        previewFileIconSettings: {},
5871
        previewFileExtSettings: {},
5872
        buttonLabelClass: 'hidden-xs',
5873
        browseIcon: '<i class="glyphicon glyphicon-folder-open"></i>&nbsp;',
5874
        browseClass: 'btn btn-primary',
5875
        removeIcon: '<i class="glyphicon glyphicon-trash"></i>',
5876
        removeClass: 'btn btn-default btn-secondary',
5877
        cancelIcon: '<i class="glyphicon glyphicon-ban-circle"></i>',
5878
        cancelClass: 'btn btn-default btn-secondary',
5879
        pauseIcon: '<i class="glyphicon glyphicon-pause"></i>',
5880
        pauseClass: 'btn btn-default btn-secondary',
5881
        uploadIcon: '<i class="glyphicon glyphicon-upload"></i>',
5882
        uploadClass: 'btn btn-default btn-secondary',
5883
        uploadUrl: null,
5884
        uploadUrlThumb: null,
5885
        uploadAsync: true,
5886
        uploadParamNames: {
5887
            chunkCount: 'chunkCount',
5888
            chunkIndex: 'chunkIndex',
5889
            chunkSize: 'chunkSize',
5890
            chunkSizeStart: 'chunkSizeStart',
5891
            chunksUploaded: 'chunksUploaded',
5892
            fileBlob: 'fileBlob',
5893
            fileId: 'fileId',
5894
            fileName: 'fileName',
5895
            fileRelativePath: 'fileRelativePath',
5896
            fileSize: 'fileSize',
5897
            retryCount: 'retryCount'
5898
        },
5899
        maxAjaxThreads: 5,
5900
        fadeDelay: 800,
5901
        processDelay: 100,
5902
        queueDelay: 10, // must be lesser than process delay
5903
        progressDelay: 0, // must be lesser than process delay
5904
        enableResumableUpload: false,
5905
        resumableUploadOptions: {
5906
            fallback: null,
5907
            testUrl: null, // used for checking status of chunks/ files previously / partially uploaded
5908
            chunkSize: 2 * 1024, // in KB
5909
            maxThreads: 4,
5910
            maxRetries: 3,
5911
            showErrorLog: true
5912
        },
5913
        uploadExtraData: {},
5914
        zoomModalHeight: 480,
5915
        minImageWidth: null,
5916
        minImageHeight: null,
5917
        maxImageWidth: null,
5918
        maxImageHeight: null,
5919
        resizeImage: false,
5920
        resizePreference: 'width',
5921
        resizeQuality: 0.92,
5922
        resizeDefaultImageType: 'image/jpeg',
5923
        resizeIfSizeMoreThan: 0, // in KB
5924
        minFileSize: -1,
5925
        maxFileSize: 0,
5926
        maxFilePreviewSize: 25600, // 25 MB
5927
        minFileCount: 0,
5928
        maxFileCount: 0,
5929
        maxTotalFileCount: 0,
5930
        validateInitialCount: false,
5931
        msgValidationErrorClass: 'text-danger',
5932
        msgValidationErrorIcon: '<i class="glyphicon glyphicon-exclamation-sign"></i> ',
5933
        msgErrorClass: 'file-error-message',
5934
        progressThumbClass: 'progress-bar progress-bar-striped active progress-bar-animated',
5935
        progressClass: 'progress-bar bg-success progress-bar-success progress-bar-striped active progress-bar-animated',
5936
        progressInfoClass: 'progress-bar bg-info progress-bar-info progress-bar-striped active progress-bar-animated',
5937
        progressCompleteClass: 'progress-bar bg-success progress-bar-success',
5938
        progressPauseClass: 'progress-bar bg-primary progress-bar-primary progress-bar-striped active progress-bar-animated',
5939
        progressErrorClass: 'progress-bar bg-danger progress-bar-danger',
5940
        progressUploadThreshold: 99,
5941
        previewFileType: 'image',
5942
        elCaptionContainer: null,
5943
        elCaptionText: null,
5944
        elPreviewContainer: null,
5945
        elPreviewImage: null,
5946
        elPreviewStatus: null,
5947
        elErrorContainer: null,
5948
        errorCloseButton: $h.closeButton('kv-error-close'),
5949
        slugCallback: null,
5950
        dropZoneEnabled: true,
5951
        dropZoneTitleClass: 'file-drop-zone-title',
5952
        fileActionSettings: {},
5953
        otherActionButtons: '',
5954
        textEncoding: 'UTF-8',
5955
        preProcessUpload: null,
5956
        ajaxSettings: {},
5957
        ajaxDeleteSettings: {},
5958
        showAjaxErrorDetails: true,
5959
        mergeAjaxCallbacks: false,
5960
        mergeAjaxDeleteCallbacks: false,
5961
        retryErrorUploads: true,
5962
        reversePreviewOrder: false,
5963
        usePdfRenderer: function () {
5964
            var isIE11 = !!window.MSInputMethodContext && !!document.documentMode;
5965
            return !!navigator.userAgent.match(/(iPod|iPhone|iPad|Android)/i) || isIE11;
5966
        },
5967
        pdfRendererUrl: '',
5968
        pdfRendererTemplate: '<iframe ' + IFRAME_ATTRIBS + '></iframe>'
5969
    };
5970
 
5971
    // noinspection HtmlUnknownAttribute
5972
    $.fn.fileinputLocales.en = {
5973
        fileSingle: 'file',
5974
        filePlural: 'files',
5975
        browseLabel: 'Browse &hellip;',
5976
        removeLabel: 'Remove',
5977
        removeTitle: 'Clear all unprocessed files',
5978
        cancelLabel: 'Cancel',
5979
        cancelTitle: 'Abort ongoing upload',
5980
        pauseLabel: 'Pause',
5981
        pauseTitle: 'Pause ongoing upload',
5982
        uploadLabel: 'Upload',
5983
        uploadTitle: 'Upload selected files',
5984
        msgNo: 'No',
5985
        msgNoFilesSelected: 'No files selected',
5986
        msgCancelled: 'Cancelled',
5987
        msgPaused: 'Paused',
5988
        msgPlaceholder: 'Select {files} ...',
5989
        msgZoomModalHeading: 'Detailed Preview',
5990
        msgFileRequired: 'You must select a file to upload.',
5991
        msgSizeTooSmall: 'File "{name}" (<b>{size} KB</b>) is too small and must be larger than <b>{minSize} KB</b>.',
5992
        msgSizeTooLarge: 'File "{name}" (<b>{size} KB</b>) exceeds maximum allowed upload size of <b>{maxSize} KB</b>.',
5993
        msgFilesTooLess: 'You must select at least <b>{n}</b> {files} to upload.',
5994
        msgFilesTooMany: 'Number of files selected for upload <b>({n})</b> exceeds maximum allowed limit of <b>{m}</b>.',
5995
        msgTotalFilesTooMany: 'You can upload a maximum of <b>{m}</b> files (<b>{n}</b> files detected).',
5996
        msgFileNotFound: 'File "{name}" not found!',
5997
        msgFileSecured: 'Security restrictions prevent reading the file "{name}".',
5998
        msgFileNotReadable: 'File "{name}" is not readable.',
5999
        msgFilePreviewAborted: 'File preview aborted for "{name}".',
6000
        msgFilePreviewError: 'An error occurred while reading the file "{name}".',
6001
        msgInvalidFileName: 'Invalid or unsupported characters in file name "{name}".',
6002
        msgInvalidFileType: 'Invalid type for file "{name}". Only "{types}" files are supported.',
6003
        msgInvalidFileExtension: 'Invalid extension for file "{name}". Only "{extensions}" files are supported.',
6004
        msgFileTypes: {
6005
            'image': 'image',
6006
            'html': 'HTML',
6007
            'text': 'text',
6008
            'video': 'video',
6009
            'audio': 'audio',
6010
            'flash': 'flash',
6011
            'pdf': 'PDF',
6012
            'object': 'object'
6013
        },
6014
        msgUploadAborted: 'The file upload was aborted',
6015
        msgUploadThreshold: 'Processing &hellip;',
6016
        msgUploadBegin: 'Initializing &hellip;',
6017
        msgUploadEnd: 'Done',
6018
        msgUploadResume: 'Resuming upload &hellip;',
6019
        msgUploadEmpty: 'No valid data available for upload.',
6020
        msgUploadError: 'Upload Error',
6021
        msgDeleteError: 'Delete Error',
6022
        msgProgressError: 'Error',
6023
        msgValidationError: 'Validation Error',
6024
        msgLoading: 'Loading file {index} of {files} &hellip;',
6025
        msgProgress: 'Loading file {index} of {files} - {name} - {percent}% completed.',
6026
        msgSelected: '{n} {files} selected',
6027
        msgFoldersNotAllowed: 'Drag & drop files only! {n} folder(s) dropped were skipped.',
6028
        msgImageWidthSmall: 'Width of image file "{name}" must be at least {size} px.',
6029
        msgImageHeightSmall: 'Height of image file "{name}" must be at least {size} px.',
6030
        msgImageWidthLarge: 'Width of image file "{name}" cannot exceed {size} px.',
6031
        msgImageHeightLarge: 'Height of image file "{name}" cannot exceed {size} px.',
6032
        msgImageResizeError: 'Could not get the image dimensions to resize.',
6033
        msgImageResizeException: 'Error while resizing the image.<pre>{errors}</pre>',
6034
        msgAjaxError: 'Something went wrong with the {operation} operation. Please try again later!',
6035
        msgAjaxProgressError: '{operation} failed',
6036
        msgDuplicateFile: 'File "{name}" of same size "{size} KB" has already been selected earlier. Skipping duplicate selection.',
6037
        msgResumableUploadRetriesExceeded: 'Upload aborted beyond <b>{max}</b> retries for file <b>{file}</b>! Error Details: <pre>{error}</pre>',
6038
        msgPendingTime: '{time} remaining',
6039
        msgCalculatingTime: 'calculating time remaining',
6040
        ajaxOperations: {
6041
            deleteThumb: 'file delete',
6042
            uploadThumb: 'file upload',
6043
            uploadBatch: 'batch file upload',
6044
            uploadExtra: 'form data upload'
6045
        },
6046
        dropZoneTitle: 'Drag & drop files here &hellip;',
6047
        dropZoneClickTitle: '<br>(or click to select {files})',
6048
        previewZoomButtonTitles: {
6049
            prev: 'View previous file',
6050
            next: 'View next file',
6051
            toggleheader: 'Toggle header',
6052
            fullscreen: 'Toggle full screen',
6053
            borderless: 'Toggle borderless mode',
6054
            close: 'Close detailed preview'
6055
        }
6056
    };
6057
 
6058
    $.fn.fileinput.Constructor = FileInput;
6059
 
6060
    /**
6061
     * Convert automatically file inputs with class 'file' into a bootstrap fileinput control.
6062
     */
6063
    $(document).ready(function () {
6064
        var $input = $('input.file[type=file]');
6065
        if ($input.length) {
6066
            $input.fileinput();
6067
        }
6068
    });
6069
}));