genbodev
4/8/2019 - 4:50 PM

Deffered. Отправка файлов

/* Нужную функцию superFunc вызываем в when, т.е. происходит её вызов, можно передать параметры  */
$.when(JSObj.superFunc(param1, param2)).
then(function () { // Можно .done(...)
  // Срабатывает при флаге resolve
}).
catch(function() { // Можно .fail(...)
  // Срабатывает при флаге reject
}).
always(function() {
  // Срабатывает всегда в конце (?)
});

var JSObj = {};
JSObj.superFunc(param1, param2) {
  
  var d = $.Deferred(); // <-Объявляем
  
  // Вот тут основной код, который нужно подождать, например, ajax
  $.post('/module/avia3side/SLContracts/deleteChildContracts', {param1: param1, param2: param2},
   function (data) {
     d.resolve(); // <-Успешное выполнение
   },
   'json');
   
  return d; // <-Возвращаем
}
/* 1. Есть массив с файлами files. Файлы нужно отправить на сервер строго последовательно, чтобы показать, например, индикатор загрузки для каждого из них  */
/* 2. Для начала подготовим функцию, которая и будет ожидаемая. Она будет отправлять файлы на сервер  */
/* 3. Мы разместим её в таймер, который будет через 500 проверять - занята функция отправки или нет  */

/* Цикличный таймер отдельно:  */
// Справка let timerId = setTimeout(func|code, [delay], [arg1], [arg2], ...) // Аргументы не работают в IE9-

var pause = 500;
var isBusy = false;
var timerId = setTimeout(function tick(formData) {
	if (isBusy === false) { // Свободно, можно загрузить файл
		console.log('Ну что ж.. я свободна, давай сюда свой файл и я его отправлю');
		isBusy = true; // Занимаем функцию делом
		var xhr = new XMLHttpRequest();
    xhr.open('GET', 'http://...');
    xhr.onload = function () { 
    	isBusy = false; // Файл загрузился - снимаем занятость и... 
      clearTimeout(timerId); // ...вырубаем текущий таймер
    }
    xhr.onerror = function () { // При ошибках...
    	console.log('У меня ошибка при отправке! Но я готова работать дальше!');
    	isBusy = false; // ...снимаем занятость и... 
      clearTimeout(timerId); // ...вырубаем текущий таймер
    }
    xhr.send(formData); // Отправляем объект с файлами
	} else { // Ждём паузу в 500 и снова пробуем
		console.log('Я ещё занята! Занимаюсь отправкой файла!');
		timerId = setTimeout(tick, pause, formData);
	}
}, pause, formData); // Аргументы (formData) будут самостоятельно размещаться в tick (см. выдержку из документации вверху)
// Сокращенно это будет так: setTimeout(function tick(formData) {}, pause, formData);, но кто же передаст formData изначально? Этим ниже займется функция request

// Теперь, если запускать этот код и давать ему задания - он их не будет принимать, если функция tick занята. ... 
// ... Задание выполнится только, если функция tick свободна

/* 4. Кто же будет ждать пока она освободится? Конечно инструмент deffered  */
/* 5. Смысл его работы, указан в листинге вверху. Напомню лишь то, что when может принимать не одну функцию, а целый массив!  */

$.when.apply($, deferred).done(function () { // deferred - тот самый массив
	console.log(arguments); // Аргументы из d.resolve(/*АРГУМЕНТЫ*/)
})
.fail(function (error) {
	console.log(error)
})
.always(function () {
  console.log('Запросов на сервер сделано: ' + deferred.length)
});

/* 6. Теперь нужно просто сделать этот массив функций  */

function request(formData) {
	// Тут запускаем наш таймер
}

for (var i = 0; i < files.length; i++) {
	formData = new FormData();
	formData.append("thefile", file);
	deferred.push(request(formData);
}

/* 7. Теперь в массиве deffered содержаться куча функций request  */
/* 8. Разместим их в when (сделали выше), а теперь нужно раставить "метки" resolve и reject, чтобы when знала, когда нужно запускать следующую функцию из массива  */

...
xhr.onload = function () { 
    	isBusy = false; // Файл загрузился - снимаем занятость и... 
      clearTimeout(timerId); // ...вырубаем текущий таймер
      d.resolve(xhr.response); // СТАВИМ МЕТКУ, ЧТО ФУНКЦИЯ ЗАВЕРШИЛАСЬ (УСПЕШНО)
}
xhr.onerror = function () { // При ошибках...
    	console.log('У меня ошибка при отправке! Но я готова работать дальше!');
    	isBusy = false; // ...снимаем занятость и... 
      clearTimeout(timerId); // ...вырубаем текущий таймер
			d.reject({status: this.status, statusText: xhr.statusText}); // СТАВИМ МЕТКУ, ЧТО ФУНКЦИЯ ЗАВЕРШИЛАСЬ (С ОШИБКОЙ)
}
...

/* 9. Теперь можно и настроить прогресс-бар (он будет просто циферками, например, "загружено 10 из 100")  */

...
xhr.upload.onprogress = function (event) {
	 document.getElementbyId('progress-bar').innerHTML = 'Загружено ' + event.loaded + ' из ' event.total;
};
...

/* 10. Навешиваем прочую мишуру: сообщение при ошибках, нормальный индикатор загрузки, передача не только файлов   */
Весь пример - ниже в файле FULL-deffered-example-file-load


/*PS НАДО БЫ ПРОВЕРИТЬ КАК СДЕЛАТЬ ОТЛОВ ОШИБКИ СО СТОРОНЫ СЕРВЕРА И ПРЕКРАЩЕНИЕ ДАЛЬНЕЙШИХ ЗАГРУЗОК*/
/* Т.е. есть 3 файла, средний - не картинка. Как прекратить загрузку третьего файла? */

/**
 * Обеспечивает вложение изображений в переписку
 * @author Elovskiy I.V. <elovskigor@gmail.com> <i.elovsky@starliner.ru>
 * @type {Object}
 */
var BroadcastAttachments = {

    init: function () {
        this.initAppVars();
        this.handlerStore();
        this.initAppListeners();
        this.initContentEditableDiv();
        this.addPasteLister();
        this.addRequirementsText();
    },
    /**
     * Инициализация переменных
     */
    initAppVars: function () {
        this.defaultFileNameText = _('Файл');
        this.defaultFileSizeText = 0 + ' ' + _('Б') + ' / ' + 0 + ' ' + _('Б');
        this.sizeLimit = 52428800;
        this.sizeLimitText = _('50МБ');
        this.allowedExtensions = ['jpeg', 'jpg', 'gif', 'png', 'pdf', 'doc', 'docx'];
        this.broadcastMsgElem = $('#broadcast_message');
        this.listeners = {};
        this.broadcastId = null;
        this.companyId = null;
        this.userRole = null;
        this.broadcastMethods = null;
        this.broadcastIndicatorElem = null;
        this.returnServerData = [];
        this.dropAreaIn = $('#msg_list_state_state0')[0];
        this.dropAreaOut = $('#msg_listfade')[0];
        this.dropAreaTarget = $('#broadcast_drop_area')[0];
        this.userAgent = detect.parse(navigator.userAgent);
        this.messageContainer = $('#msg_listbox .broadcast_jqi_message');
        this.buttonsContainer = $('#msg_listbox .broadcast_jqi_buttons');
        this.thumbsWrapperId = 'broadcast_file_uploader_thumbs_wrapper';
        // this.message = $('#msg_list .jqimessage');
        // this.buttons = $('#msg_list .jqibuttons');
        this.sendButton = $('#msg_list_state0_button_send');
        this.isUpSize = false;
    },
    /**
     * Хранилище для хендлеров
     */
    handlerStore: function () {
        this.handlers = {};
        this.handlers['clickThumbsHandlers'] = [];
        this.handlers['closeSwitchThumbsHandlers'] = [];
        this.handlers['zoomImgHandlers'] = [];
    },
    /**
     * Инициализация прослушек
     */
    initAppListeners: function () {
        this.addBtnListener();
        this.addInputListener();
        this.addDragDropListeners();
    },
    /**
     * Добавляет прослушку
     * @param element
     * @param event
     * @param handler
     * @param capture
     * @returns {number} Идентификатор прослушки
     */
    addListener: function (element, event, handler, capture) {
        var i = Object.keys(this.listeners).length;
        element.addEventListener(event, handler, capture);
        this.listeners[i] = {
            element: element,
            event: event,
            handler: handler,
            capture: capture
        };
        return i;
    },
    /**
     * Удаляет прослушку по идентификатору
     * @param id
     */
    removeListener: function (id) {
        var h;
        if (id in this.listeners) {
            h = this.listeners[id];
            h.element.removeEventListener(h.event, h.handler, h.capture);
            delete this.listeners[id];
        }
    },
    /**
     * Добавляет прослушку на кнопку
     */
    addBtnListener: function () {
        var attachImageBtn = document.getElementById('broadcast_attach_file_btn');
        this.handlers['attachImageBtn'] = this.addListener(attachImageBtn, 'click', BroadcastAttachments.actionAttach, false);
    },
    /**
     * Добавляет прослушку на инпут
     */
    addInputListener: function () {
        var inputFile = $('#broadcast_file_uploader input')[0];
        this.handlers['inputFile'] = this.addListener(inputFile, 'change', BroadcastAttachments.actionInput, false);
    },
    /**
     * Инициализация вспомогательного div-контейнера для IE (вставка из буфера)
     */
    initContentEditableDiv: function () {
        // Только для IE
        if (['IE'].indexOf(this.userAgent.browser.family) !== -1) {
            var div = $('<div />', {
                id: 'broadcast_message_ced',
                contenteditable: true,
                css: {
                    opacity: '0',
                    maxWidth: '0',
                    maxHeight: '0',
                    width: '0',
                    height: '0',
                    minWidth: '0',
                    minHeight: '0',
                    border: '0'
                }
            });
            $('#broadcast_attach_file_textarea_wrapper').append(div);
            this.contentEditableDiv = $('#broadcast_message_ced');
        }
    },
    /**
     * Обработка события paste
     */
    addPasteLister: function () {
        this.handlers['pasteHandler'] = this.addListener(this.broadcastMsgElem[0], 'paste', BroadcastAttachments.actionPaste, false);

        // Для IE
        if (['IE'].indexOf(this.userAgent.browser.family) !== -1) {
            this.handlers['contentEditableDivPasteHandler'] = this.addListener(this.contentEditableDiv[0], 'paste', BroadcastAttachments.actionPaste, false);
            $(this.broadcastMsgElem[0]).on('keydown', function (event) {
                if ((event.ctrlKey && event.keyCode === 86) || (event.shiftKey && event.keyCode === 45)) {
                    $(BroadcastAttachments.contentEditableDiv).empty();
                    $(BroadcastAttachments.contentEditableDiv).focus();
                }
            });
        }
    },
    /**
     * Вызовет клик по label (input type file) для открытия диалога выбора файлов
     */
    actionAttach: function () {
        $('#broadcast_file_uploader label').trigger('click');
    },
    /**
     * Обработка выбранных файлов
     * @param e
     */
    actionInput: function (e) {
        var files = e.target.files;
        if (files.length === 0) return;

        var result = BroadcastAttachments.checkRequirements(files);
        if (result.errorType === false && result.errorSize === false) {
            BroadcastAttachments.sendFiles(files);
        } else {
            BroadcastAttachments.createErrorWindow(result);
        }
    },
    /**
     * Создаст окно с ошибкой
     * @param errors
     */
    createErrorWindow: function (errors) {
        var buttons = {};
        buttons[_('ОК')] = true;
        var errorMsg = {
            errorType: _('Только изображения, pdf и doc/docx-файлы, пожалуйста'),
            errorSize: _('Размер файла не должен превышать') + ' ' + _(BroadcastAttachments.sizeLimitText)
        };
        var text = (errors.errorType === true) ? errorMsg['errorType'] : errorMsg['errorSize'];
        if (errors.errorText) {
            text = errors.errorText;
        }
        $.slPrompt(text, {
            close: true,
            title: _('Ошибка'),
            prefix: 'err_',
            buttons: buttons
        });
    },
    /**
     * Проверка файлов
     * Файл должен быть изображением и не более 50 Мб
     * @param files
     */
    checkRequirements: function (files) {
        var result = {};
        result.errorType = false;
        result.errorSize = false;
        var validTypes = [
            'application/pdf',
            'application/msword',
            'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
        ];
        for (var k = 0; k < files.length; k++) {
            // Только изображения
            if (!files[k].type.match('image.*')) {
                result.errorType = true;
            }
            // Если всё-таки pdf, doc, docx, то нет ошибки по типу файла
            if (validTypes.indexOf(files[k].type) !== -1) {
                result.errorType = false;
            }
            // Не более 50Мб
            if (files[k].size > BroadcastAttachments.sizeLimit) {
                result.errorSize = true;
            }
        }
        return result;
    },
    /**
     * Покажет лоадер, отправит файлы на сервер
     * @param files
     */
    sendFiles: function (files) {
        var pause = 500;
        BroadcastAttachments.showLoader();
        BroadcastAttachments.progressBarShow();
        BroadcastAttachments.sendDisable();
        var isBusy = false;

        function request(opts) {
            var d = $.Deferred();
            var timerId = setTimeout(function tick(opts) {
                if (isBusy === false) {
                    isBusy = true;
                    opts.customFunc(opts);
                    var xhr = new XMLHttpRequest();
                    xhr.open(opts.method, opts.url);
                    xhr.onload = function () {
                        isBusy = false;
                        clearTimeout(timerId);
                        if (this.status >= 200 && this.status < 300) {
                            d.resolve(xhr.response);
                        } else {
                            console.log({status: this.status, statusText: xhr.statusText});
                            d.reject({status: this.status, statusText: xhr.statusText});
                        }
                    };
                    xhr.upload.onprogress = function (event) {
                        opts.progressFunc(event);
                    };
                    xhr.onerror = function () {
                        isBusy = false;
                        clearTimeout(timerId);
                        console.log({status: this.status, statusText: xhr.statusText});
                        d.reject({status: this.status, statusText: xhr.statusText});
                    };

                    // headers: {'X-Subliminal-Message': 'Upvote-this-answer'}
                    if (opts.headers) {
                        Object.keys(opts.headers).forEach(function (key) {
                            xhr.setRequestHeader(key, opts.headers[key]);
                        });
                    }

                    // params: {param1: 'val1', param2: 'val2}' OR params = 'param1=val1&param2=val2' OR params = new FormData
                    var params = opts.params;
                    if (params && typeof params === 'object') {
                        if (params instanceof FormData !== true) {
                            params = Object.keys(params).map(function (key) {
                                return encodeURIComponent(key) + '=' + encodeURIComponent(params[key]);
                            }).join('&');
                        }
                    }
                    xhr.send(params);
                } else {
                    timerId = setTimeout(tick, pause, opts);
                }
            }, pause, opts);
            return d.promise();
        }

        var fileNameElem = document.getElementById("broadcast_loader_progress_file_name");
        var progressElem = document.getElementById("broadcast_loader_progress_line");
        var uploadedElem = document.getElementById("broadcast_loader_progress_file_size_uploaded");

        var deferred = [];
        var fileName;
        var formData;
        var bindFiles = {};
        var id;
        for (var i = 0; i < files.length; i++) {
            fileName = (files[i].name) ? files[i].name : 'image.png';
            id = BroadcastAttachments.randomString();
            bindFiles[id] = files[i];
            formData = new FormData();
            formData.append('id', id);
            formData.append('file_name', fileName);
            formData.append('is_rename', fileName);
            formData.append('size_limit', BroadcastAttachments.sizeLimit);
            $.each(BroadcastAttachments.allowedExtensions, function (index, value) {
                formData.append('allowed_extensions[]', value);
            });
            if (files[i].type.match('image.*')) {
                formData.append('is_image', '1');
            } else {
                formData.append('is_image', '0');
            }
            formData.append('path', BroadcastAttachments.companyId + '/broadcast/' + BroadcastAttachments.broadcastId);
            formData.append('qqfile', files[i], fileName);
            deferred.push(request({
                    method: 'POST',
                    url: '/File/ajaxUpload',
                    params: formData,
                    customFunc: function (opts) {
                        if (['IE'].indexOf(BroadcastAttachments.userAgent.browser.family) !== -1) {
                            fileNameElem.innerText = _('Отправка файла');
                        } else {
                            fileNameElem.innerText = opts.params.get('file_name');
                        }
                    },
                    progressFunc: function (event) {
                        uploadedElem.innerHTML = '(' + BroadcastAttachments.formatBytes(event.loaded) + ' / ' + BroadcastAttachments.formatBytes(event.total) + ')';
                        progressElem.setAttribute('max', event['total']);
                        progressElem.value = event.loaded;
                    }
                })
            );
        }
        $.when.apply($, deferred).done(function () {
            var isError = false;
            var serverResponse = '';
            for (var i = 0; i < arguments.length; i++) {
                serverResponse = JSON.parse(arguments[i]);
                if (serverResponse.error) {
                    isError = true;
                    break;
                } else {
                    BroadcastAttachments.returnServerData.push(serverResponse);
                }
            }
            BroadcastAttachments.hideLoader();
            BroadcastAttachments.clearInputFile();
            BroadcastAttachments.restoreLoaderDefaults();
            if (isError) {
                var errors = {errorText: serverResponse.error};
                BroadcastAttachments.createErrorWindow(errors);

            } else {
                BroadcastAttachments.createThumbs(bindFiles);
            }
        })
            .fail(function (error) {
                console.log(error)
            })
            .always(function () {
                // console.log('Запросов на сервер сделано: ' + deferred.length)
            });

    },
    /**
     * Покажет лоадер с индикатором загрузки
     */
    showLoader: function () {
        var textAreaWrapper = $('#broadcast_attach_file_textarea_wrapper');
        var loaderArea = $('#broadcast_loader_area_wrapper');
        var height = $(textAreaWrapper).height();
        $(loaderArea).height(height - (parseInt($(loaderArea).css('borderWidth')) * 2));
        $(textAreaWrapper).hide();
        $(loaderArea).css('display', '');
    },
    /**
     * Скроет лоадер
     */
    hideLoader: function () {
        var textAreaWrapper = $('#broadcast_attach_file_textarea_wrapper');
        var loaderArea = $('#broadcast_loader_area_wrapper');
        $(loaderArea).css('display', 'none');
        $(textAreaWrapper).show();
    },
    /**
     * Создает превью изображений
     * @param files
     */
    createThumbs: function (files) {
        var prevImgPath = '/modules/broadcast/images/paperclip-file-preview.png';

        function create(f, filename) {
            var d = $.Deferred();
            var reader = new FileReader();
            reader.onload = (function (theFile, theFileName) {
                return function (e) {
                    var div = document.createElement('div');
                    div.className = 'broadcast_file_uploader_thumb_wrapper';
                    div.dataset.filename = theFileName;
                    if (!theFile.type.match('image.*')) {
                        div.innerHTML = ['<div class="broadcast_file_uploader_img_wrapper" data-type="is_file"><div class="broadcast_file_uploader_thumb_close broadcast_file_uploader_thumb_close_hide"></div><img class="broadcast_file_uploader_img" title="', (theFile.name), '" src="', prevImgPath, '" /></div>'].join('');
                    } else {
                        div.innerHTML = ['<div class="broadcast_file_uploader_img_wrapper" data-type="is_image"><div class="broadcast_file_uploader_thumb_close broadcast_file_uploader_thumb_close_hide"></div><img class="broadcast_file_uploader_img" title="', (theFile.name), '" src="', e.target.result, '" /></div>'].join('');
                    }
                    var fileNameDiv = document.createElement('div');
                    fileNameDiv.className = 'broadcast_file_uploader_text';
                    fileNameDiv.innerText = theFile.name;
                    $(div).append(fileNameDiv);
                    document.getElementById('broadcast_file_uploader_thumbs_wrapper').insertBefore(div, null);
                    $('#broadcast_file_uploader_thumbs_wrapper').css('height', 75);
                    BroadcastAttachments.sizeCorrect('create');
                    d.resolve();
                };
            })(f, filename);
            // Чтение изображений в виде data URL.
            reader.readAsDataURL(f);
            return d.promise();
        }

        var deferred = [];
        for (var key in files) {
            if (files.hasOwnProperty(key)) {
                for (var i = 0; i < BroadcastAttachments.returnServerData.length; i++) {
                    if (BroadcastAttachments.returnServerData[i]['id'] === key) {
                        if (BroadcastAttachments.returnServerData[i]['success'] && BroadcastAttachments.returnServerData[i]['success'] === true) {
                            deferred[deferred.length] = create(files[key], BroadcastAttachments.returnServerData[i].filename);
                        }
                    }
                }

            }
        }
        $.when.apply($, deferred).done(function () {
            BroadcastAttachments.sendEnable();
            $('.broadcast_file_uploader_img_wrapper').each(function (index, value) {
                BroadcastAttachments.handlers['clickThumbsHandlers'].push(BroadcastAttachments.addListener(value, 'click', BroadcastAttachments.clickThumb, false));
                BroadcastAttachments.addListener(value, 'mouseover', BroadcastAttachments.showCloseThumb, false);
                BroadcastAttachments.addListener(value, 'mouseout', BroadcastAttachments.hideCloseThumb, false);
            });
            $('.broadcast_file_uploader_thumb_close').each(function (index, value) {
                BroadcastAttachments.addListener(value, 'click', BroadcastAttachments.removeThumb, false);
            });
        });
    },
    /**
     * Блокирует кнопку отправки
     */
    sendDisable: function () {
        $(this.sendButton).prop('disabled', true);
    },
    /**
     * Разблокирует кнопку отправки
     */
    sendEnable: function () {
        $(this.sendButton).prop('disabled', false);

    },
    /**
     * Добавляет действие на клик по превью файлов thumbs
     * @param e
     */
    clickThumb: function (e) {
        var elem = $(e.target);
        while ($(elem).hasClass('broadcast_file_uploader_img_wrapper') !== true) {
            elem = $(elem).parent();
        }
        var type = $(elem).data('type');
        var title = $(elem).find('img').attr('title');

        var path = '', filename = '';
        $.each(BroadcastAttachments.returnServerData, function (index, value) {
            if (value['original_filename'] === title) {
                path = '/File/show' + value['user_dir'] + '/';
                filename = value['filename'];
                return false;
            }
        });
        if (type === 'is_image') {
            var src = $(elem).find('img').attr('src');
            BroadcastAttachments.popupImg(src, false);
        } else {
            window.location = path + filename;
        }
    },
    /**
     * Покажет иконку удаления файла
     * @param e
     */
    showCloseThumb: function (e) {
        var elem = $(e.target);
        while ($(elem).hasClass('broadcast_file_uploader_img_wrapper') !== true) {
            elem = $(elem).parent();
        }
        $(elem).find('.broadcast_file_uploader_thumb_close').removeClass('broadcast_file_uploader_thumb_close_hide');
    },
    /**
     * Скроет иконку удаления файла
     * @param e
     */
    hideCloseThumb: function (e) {
        var elem = $(e.target);
        while ($(elem).hasClass('broadcast_file_uploader_img_wrapper') !== true) {
            elem = $(elem).parent();
        }
        $(elem).find('.broadcast_file_uploader_thumb_close').addClass('broadcast_file_uploader_thumb_close_hide');
    },
    /**
     * Удалит файл из списка для отправки и из зоны thumbs
     * @param e
     */
    removeThumb: function (e) {
        e.stopPropagation();
        e.preventDefault();
        var elem = $(e.target);
        while ($(elem).hasClass('broadcast_file_uploader_thumb_wrapper') !== true) {
            elem = $(elem).parent();
        }
        var sData = [];
        BroadcastAttachments.returnServerData.forEach(function (item, i, arr) {
            if (elem[0].dataset.filename !== item['filename']) {
                sData.push(item);
            }
        });
        BroadcastAttachments.returnServerData = sData;
        $(elem).remove();
        if ($('#broadcast_file_uploader_thumbs_wrapper').children().length == 0) {
            $('#broadcast_file_uploader_thumbs_wrapper').css('height', 0);
        }
        BroadcastAttachments.sizeCorrect('remove');
    },
    /**
     * Удаляет все прослушки с thumbs
     */
    removeThumbsListeners: function () {
        for (var i = 0; i < this.handlers['clickThumbsHandlers'].length; i++) {
            this.removeListener(this.handlers['clickThumbsHandlers'][i]);
        }
        this.handlers['clickThumbsHandlers'] = [];
    },
    /**
     * Добавляет прослушку на изображения в чате
     */
    addImgListeners: function () {
        $('.broadcast-message-img-div').each(function (index, value) {
            BroadcastAttachments.handlers['zoomImgHandlers'].push(BroadcastAttachments.addListener(value, 'click', BroadcastAttachments.addZoom, false));
        });
    },
    /**
     * Плавное появление изображений
     */
    addImgLazy: function () {
        $('.broadcast-message-img').each(function (index, value) {
            value.setAttribute('src', value.getAttribute('data-src'));
            value.onload = function () {
                value.removeAttribute('data-src');
            };
        });
    },
    /**
     * Добавляет "увеличение" для изображений в чате
     * @param e
     */
    addZoom: function (e) {
        var elem = $(e.target);
        while ($(elem).hasClass('broadcast-message-img-div') !== true) {
            elem = $(elem).parent();
        }
        var src = $(elem).find('.broadcast-message-img').attr('src');
        BroadcastAttachments.popupImg(src, true);
    },
    /**
     * Разметка для увеличенного изображения
     * @param src
     * @param isDownload
     */
    popupImg: function (src, isDownload) {
        var downloadDiv = [
            '<div class="broadcast_popup_control_download_wrapper">',
            '<a class="broadcast_popup_control_download_link" href="' + src + '" download>',
            '<div class="broadcast_popup_control_download_img">',
            '</div>',
            '<div class="broadcast_popup_control_download_text">',
            _('Скачать'),
            '</div>',
            '</a>',
            '</div>'
        ].join('');
        if (isDownload === false) {
            downloadDiv = '<div class="broadcast_popup_control_download_wrapper_empty"></div>';
        }
        var popup = [
            '<div class="broadcast_popup">',
            '<div class="broadcast_popup_control_wrapper">',
            downloadDiv,
            '<div class="broadcast_popup_control_close_wrapper">',
            '<div class="broadcast_popup_control_close_img">',
            '</div>',
            '</div>',
            '</div>',
            '<div class="broadcast_popup_bg">',
            '</div>',
            '<img class="broadcast_popup_img" alt="img" src="' + src + '" />',
            '</div>'
        ].join('');
        $('body').append(popup);
        var broadcastPopup = $('.broadcast_popup');
        var maxZ = BroadcastAttachments.getMaxZIndex();
        $(broadcastPopup).css('zIndex', maxZ + 1);
        var imageElement = $('.broadcast_popup_img');
        $(imageElement).css('zIndex', maxZ + 2);

        $(broadcastPopup).fadeIn(200); // Медленно выводим изображение

        // Закрытие
        var closeElement = $('.broadcast_popup_control_close_wrapper');
        var popupBgElement = $('.broadcast_popup_bg');

        [popupBgElement, closeElement].map(function (el) {
            $(el).on('click', function () {
                $(broadcastPopup).fadeOut(200); // Медленно убираем всплывающее окно
                setTimeout(function () {
                    $(broadcastPopup).remove(); // Удаляем разметку всплывающего окна
                }, 200);
            });
        });
    },
    /**
     * Возвращает максимальный zIndex
     * @returns {number}
     */
    getMaxZIndex: function () {
        var divs = document.getElementsByTagName('div');
        var highest = 0;
        for (var i = 0; i < divs.length; i++) {
            var zindex = divs[i].style.zIndex;
            if (zindex > highest) {
                highest = zindex;
            }
        }
        return highest;
    },
    /**
     * Очистит input от выбранных файлов
     */
    clearInputFile: function () {
        var inputFile = $('#broadcast_file_uploader input');
        $(inputFile).attr('type', '');
        $(inputFile).attr('type', 'file');
    },
    /**
     * Отправка сообщения смешанного типа
     * @param arr
     */
    sendMixedMessage: function (arr) {

        $.each(BroadcastAttachments.returnServerData, function (index, value) {
            var fileName = value['filename'];
            var filePath = value['user_dir'] + '/';
            var fullPath = '/File/show' + filePath;
            if (value['is_image'] === '1') {
                // path!!!
                arr[arr.length] = {message_type: 'image', message: fileName, path: fullPath, filename: fileName};
            } else {
                arr[arr.length] = {message_type: 'file', message: fileName, path: fullPath, filename: fileName};
            }
        });

        this.broadcastMethods._addMessage(_('Отправка данных'));
        this.broadcastMethods._send(this.broadcastIndicatorElem, this.broadcastId, arr);
        this.reset();
    },
    /**
     * Сброс в начальное состояние после отправки
     */
    reset: function () {
        $(this.broadcastMsgElem).val('');
        this.clearInputFile();
        BroadcastAttachments.returnServerData = [];
        var thumbArea = document.getElementById(this.thumbsWrapperId);
        while (thumbArea.firstChild) {
            thumbArea.removeChild(thumbArea.firstChild);
        }
        if ($('#broadcast_file_uploader_thumbs_wrapper').children().length == 0) {
            $('#broadcast_file_uploader_thumbs_wrapper').css('height', 0);
        }
        this.sizeCorrect('reset');
        this.removeThumbsListeners();
        this.restoreLoaderDefaults();
    },
    /**
     * Корректировка размеров в интерфейсе окна при наличии зоны thumbs
     */
    sizeCorrect: function (flag) {
        var jqimessageHeight = Math.floor($(BroadcastAttachments.messageContainer).outerHeight()),
            jqibuttonsHeight = Math.floor($(BroadcastAttachments.buttonsContainer).outerHeight()),
            jqiHeight = $('#msg_list').outerHeight(),
            thumbsWrapper = $('#broadcast_file_uploader_thumbs_wrapper'),
            thumbsWrapperHeight = 75; //высота оберки превью, задается жестко т.к. имеет прокрутку

        if (thumbsWrapper.children().length == 1 && flag == 'create') {
            $(BroadcastAttachments.messageContainer).css('height', jqimessageHeight - thumbsWrapperHeight);
        }
        // при удалении последнего превью корректируем высоту окна сообщения
        if (thumbsWrapper.children().length == 0 && flag == 'remove') {
            $(BroadcastAttachments.messageContainer).css('height', jqimessageHeight + thumbsWrapperHeight);
        }
        if (flag == 'reset' && jqimessageHeight < (jqiHeight - jqibuttonsHeight - thumbsWrapperHeight)) {
            $(BroadcastAttachments.messageContainer).css('height', jqimessageHeight + thumbsWrapperHeight);
        }
    },
    /**
     * Сбрасывает имя и размер в лоадере
     */
    restoreLoaderDefaults: function () {
        $('#broadcast_loader_progress_file_name').text(this.defaultFileNameText);
        $('#broadcast_loader_progress_file_size_uploaded').text(this.defaultFileSizeText);
    },
    /**
     * Перехватывает вставку из буфера обмена
     * @param e
     */
    actionPaste: function (e) {
        // Если поддерживается event.clipboardData (Chrome|Opera|Safari|Firefox)
        if (e.clipboardData) {
            // Получаем все содержимое буфера
            var items = e.clipboardData.items;
            if (items) {
                // Находим изображение
                for (var i = 0; i < items.length; i++) {
                    if (items[i].type.indexOf('image') !== -1) {
                        // Представляем изображение в виде файла
                        var blob = items[i].getAsFile();
                        var result = BroadcastAttachments.checkRequirements([blob]);
                        if (result.errorType === false && result.errorSize === false) {
                            BroadcastAttachments.sendFiles([blob]);
                        } else {
                            BroadcastAttachments.createErrorWindow(result);
                        }
                    }
                }
            }
        } else {
            // IE
            var waitToPastInterval = setTimeout(function () {
                if ($(BroadcastAttachments.contentEditableDiv).children().length > 0) {
                    clearInterval(waitToPastInterval);
                    // Поиск изображений
                    var image = $(BroadcastAttachments.contentEditableDiv).find('img');
                    if (image.length > 0) {
                        var dataURI = $(BroadcastAttachments.contentEditableDiv).find('img')[0].src;
                        var blob = BroadcastAttachments.dataURItoBlob(dataURI);
                        var result = BroadcastAttachments.checkRequirements([blob]);
                        if (result.errorType === false && result.errorSize === false) {
                            BroadcastAttachments.sendFiles([blob]);
                        } else {
                            BroadcastAttachments.createErrorWindow(result);
                        }
                    } else {
                        // Изображения не найдены, заберём текст
                        var text = window.clipboardData.getData("text");
                        $(BroadcastAttachments.broadcastMsgElem).val(text);
                        $(BroadcastAttachments.broadcastMsgElem).focus();
                    }
                }
                $(BroadcastAttachments.contentEditableDiv).empty();
            }, 1);
        }

    },
    /**
     * Конвертирует строку dataURI в blob-представление
     * @param dataURI
     * @returns {Blob}
     */
    dataURItoBlob: function (dataURI) {
        var byteString = atob(dataURI.split(',')[1]);
        var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];
        var ab = new ArrayBuffer(byteString.length);
        var ia = new Uint8Array(ab);
        for (var i = 0; i < byteString.length; i++) {
            ia[i] = byteString.charCodeAt(i);
        }
        return new Blob([ab], {type: mimeString});
    },
    /**
     * Добавляет прослушки для реализации drag&drop
     */
    addDragDropListeners: function () {

        BroadcastAttachments.addListener(BroadcastAttachments.dropAreaIn, 'dragover', BroadcastAttachments.actionFileDragHover, true);
        BroadcastAttachments.addListener(BroadcastAttachments.dropAreaOut, 'dragleave', BroadcastAttachments.actionFileDragHover, true);
        BroadcastAttachments.addListener(BroadcastAttachments.dropAreaTarget, 'drop', BroadcastAttachments.actionFileSelect, false);
    },
    /**
     * Действие при наведении файла на окно чата
     * @param e
     */
    actionFileDragHover: function (e) {
        e.stopPropagation();
        e.preventDefault();
        if (e.type === "dragover") {
            BroadcastAttachments.dropAreaShow();
        } else {
            BroadcastAttachments.dropAreaHide();
        }
    },
    /**
     * Действие при "сбросе" файла в окно чата
     * @param e
     */
    actionFileSelect: function (e) {
        BroadcastAttachments.actionFileDragHover(e);
        var files = e.target.files || e.dataTransfer.files;
        var result = BroadcastAttachments.checkRequirements(files);
        if (result.errorType === false && result.errorSize === false) {
            BroadcastAttachments.sendFiles(files);
        } else {
            BroadcastAttachments.createErrorWindow(result);
        }
    },
    /**
     * Покажет окно с текстом для перетаскивания файлов
     */
    dropAreaShow: function () {
        BroadcastAttachments.showLoader();
        BroadcastAttachments.progressBarHide();

    },
    /**
     * Скроет линию progress bar, покажет подсказку для перетаскивания файлов
     */
    progressBarHide: function () {
        $('#broadcast_drop_area_message_wrapper').show();
        $('#broadcast_loader_progress_wrapper').hide();
    },
    /**
     * Спрячет окно с текстом для перетаскивания файлов
     */
    dropAreaHide: function () {
        BroadcastAttachments.hideLoader();
        BroadcastAttachments.progressBarShow();
    },
    /**
     * Скроет подсказку для перетаскивания файлов, покажет progress bar
     */
    progressBarShow: function () {
        $('#broadcast_drop_area_message_wrapper').hide();
        $('#broadcast_loader_progress_wrapper').show();
    },
    /**
     * Конвертирует байты
     * @param bytes
     * @param decimals
     * @returns {string}
     */
    formatBytes: function (bytes, decimals) {
        if (parseInt(bytes) === 0) return '0' + _('Б');
        var k = 1024,
            dm = decimals <= 0 ? 0 : decimals || 2,
            sizes = [_('Б'), _('КБ'), _('МБ'), _('ГБ')],
            i = Math.floor(Math.log(bytes) / Math.log(k));
        return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
    },
    /**
     * Добавляет текст в окно для перетаскивания файлов
     */
    addRequirementsText: function () {
        $('#broadcast_drop_area_message_requirements').text(_('Разрешены файлы до') + ' ' + this.sizeLimitText + ', ' + _('в форматах') + ': ' + this.allowedExtensions.join(', '))
    },
    /**
     * Генератор случайной строки
     * @returns {string}
     */
    randomString: function () {
        var result = '';
        var words = 'qwertyuiopasdfghjklzxcvbnm';
        var max_position = words.length - 1;
        var i, position;
        for (i = 0; i < 5; ++i) {
            position = Math.floor(Math.random() * max_position);
            result = result + words.substring(position, position + 1);
        }
        return result;
    }
};
// https://www.codementor.io/tips/0743978201/my-way-of-chaining-together-numerous-jquery-deferred-objects-so-they-execute-in-the-right-order-without-the-need-for-nesting-callbacks

var obj_def = {
	
	this.json = '';
	
	deferredChain: function (chain, context) {
        var d = $.Deferred(), i = 0;

        function onChainItemDone(context, chain, i, d) {
            i++;
            var innerChainItem;
            if (chain[i] && typeof chain[i] === 'function') {
                innerChainItem = chain[i].apply(context);
                innerChainItem.done(function () {
                    onChainItemDone(this, chain, i, d)
                })
            } else if (typeof chain[i] === 'object') {
                var innerChainFunc = chain[i]['func'];
                var innerChainArgs = chain[i]['args'] || [];
                innerChainItem = innerChainFunc.apply(context, innerChainArgs);
                innerChainItem.done(function () {
                    onChainItemDone(this, chain, i, d)
                })
            } else d.resolveWith(context);
        }

        var chainItem;
        if (!chain.length) {
            alert('Массив функций не найден');
            d.resolveWith(context);
        } else if (chain[i] && typeof chain[i] === 'function') {
            chainItem = chain[i].apply(context);
            chainItem.done(function () {
                onChainItemDone(this, chain, i, d)
            });
        } else if (chain[i] && typeof chain[i] === 'object') {
            var innerChainFunc = chain[i]['func'];
            var innerChainArgs = chain[i]['args'] || [];
            chainItem = innerChainFunc.apply(context, innerChainArgs);
            chainItem.done(function () {
                onChainItemDone(this, chain, i, d)
            });
        } else {
            $.error('В массиве должна быть фунция');
            d.resolveWith(context);
        }
        return d.promise();
    }
	
};

var demo = function (url) {
  var _d = $.Deferred();
  $.ajax({
      url: url
    })
    .done(function (json) {
      obj_def.json = json;
      _d.resolveWith(obj_def);
    })
    .fail(function () {
      _d.resolveWith(obj_def);
    });
  return _d.promise();
};

var chain = [demo('/ajax/getDemo'), demo('/ajax/getDemo2')];

obj_def.deferredChain(chain, obj_def);