Заготовка JS класса в шаблоне компонента Битрикс

new InputFile(
            {
                elementContainer: container,
                element: document.getElementById('<?=$inputId?>'),
                maxSize: <?=$maxSize ?? 0?>,
                defaultFileName: '<?=CUtil::JSEscape($defaultFileName)?>',
                allowedExtensions: <?=CUtil::PhpToJSObject($extensions)?>,
                truncate: <?=$truncate ?? 0?>,
                isRequired: '<?=CUtil::JSEscape($required)?>',
                multiple: '<?=$multiple?>',
                multipleLimit: <?=$multipleLimit?>,
            }
        );
class InputFile {
    constructor(parameters) {
        this.defaultFileName = 'Загрузить'
        this.elementContainer = parameters.elementContainer;
        this.element = parameters.element; // Input
        this.truncate = parameters.truncate || 0; // После скольки символов обрезаем.
        this.isRequired = parameters.isRequired || false;
        this.multiple = parameters.multiple === 'Y';
        this.multipleLimit = parameters.multipleLimit || 0;

        // Дозволенные расширения.
        this.allowedExtensions = parameters.allowedExtensions || {} // Пустой объект - любые файлы принимаем.
        this.allowedExtensionsString = null
        if (this.allowedExtensions) {
            // Преобразуем объект в строку из ключей.
            this.allowedExtensionsString = Object.keys(this.allowedExtensions).join(", ");
        }

        // Максимальный размер файла в байтах (по умолчанию бесконечность).
        this.maxSize = parameters.maxSize || Infinity;
        this.maxSizeInMb = null
        if (this.maxSize !== Infinity) {
            this.maxSizeInMb = this.bytesToMB(this.maxSize);
        }

        this.nodeName = null // Заголовок.
        this.nodeInfo = null // Инфо о загруженном файле и перечень расширений.
        this.nodeError = null // Куда пихаем ошибки.
        this.nodeClearFile = null // Кнопка очистки input.

        if (this.elementContainer) {
            this.nodeName = this.elementContainer.querySelector('.input_file__name');
            this.nodeInfo = this.elementContainer.querySelector('.input_file__info');
            this.nodeError = this.elementContainer.querySelector('.input_file__error');
            this.nodeClearFile = this.elementContainer.querySelector('.input_file__delete');
        }

        if (this.nodeClearFile) {
            this.clearButtonHandler(this.nodeClearFile)
        }

        this.errors = {}

        this.inputHandler();
    }

    inputHandler() {
        this.element.addEventListener('change', (event) => {
            const files = event.target.files; // Получаем загруженные файлы.
            let filesQty = files.length

            if (filesQty > 0) {
                const file = files[0]; // Возьмем только первый файл.

                // Чистим ошибки.
                this.clearErrors();

                if (this.validator(file, filesQty)) { // Все ОК!
                    this.setFileName(this.truncateString(file.name))
                    this.setFileInfo(file)
                    this.removeErrorClass(this.elementContainer)
                } else {
                    this.setFileName(this.defaultFileName)
                    this.setFileInfo()
                }
                // Отображаем ошибки, если есть.
                this.displayErrors();
            }
        });
    }

    /**
     * Выводим название файла.
     */
    setFileName(name) {
        if (this.nodeName) {
            this.nodeName.textContent = name;
        }
    }

    /**
     * Выводим информацию о загружаемом файле.
     * Если def === true - скидываем на default.
     */
    setFileInfo(file = null) {
        if (this.nodeInfo) {
            let info = ''
            if (!file) {
                info = this.allowedExtensionsString
            } else {
                let size = this.bytesToMB(file.size)
                let extension = this.getFileExtension(file.name)

                if (size) {
                    info = size + ' Мб'
                }

                if (extension) {
                    info += ', ' + extension
                }
            }

            this.nodeInfo.textContent = info;
        }
    }

    /**
     * Получает расширение файла.
     * Возвращаем последнее значение после точки или пустую строку.
     */
    getFileExtension(fileName) {
        const parts = fileName.split('.');
        return parts.length > 1 ? parts.pop().toLowerCase() : '';
    }

    validator(file, filesQty) {
        let countErrors = 0;
        console.log(this.multiple, this.multipleLimit, filesQty)
        // Проверяем количество файлов
        if(this.multiple && this.multipleLimit !== 0) {
            if(!this.validateMultipleLimit(filesQty)) {
                this.errors.push(`Максимальное количество ${filesQty} шт.`);
                countErrors++
                return false; // Прерываем сразу, дальнейшие проверки не имеют смысла.
            }
        }

        if (!this.validateMaxSize(file)) {
            this.errors.push(`Ваш файл ${this.bytesToMB(file.size)} Мб, макс. размер ${this.maxSizeInMb} Мб.`);
            countErrors++
        }

        if (!this.validateFileExtension(file)) {
            countErrors++;
        }

        return countErrors === 0;
    }

    /**
     * Проверяем размер файла.
     */
    validateMaxSize(file) {
        return file.size <= this.maxSize;
    }

    /**
     * Проверяем на допустимое расширение файла.
     */
    validateFileExtension(file) {
        const fileExtension = this.getFileExtension(file.name); // Получаем расширение файла.

        console.log(fileExtension)
        console.log(this.allowedExtensions)

        // Проверяем, существует ли расширение файла в объекте разрешенных расширений.
        if (!(fileExtension in this.allowedExtensions)) {
            this.errors.push(`Недопустимое расширение файла: ${fileExtension}`);
            /*this.errors.push(`Недопустимое расширение файла: ${fileExtension}
            . Разрешены: ${Object.keys(this.allowedExtensions).join(', ')}.`);*/
            return false;
        }
        return true;
    }

    /**
     * Проверяем количество загруженных файлов.
     */
    validateMultipleLimit(filesQty) {
        return this.multipleLimit !== 0 && filesQty <= this.multipleLimit;
    }

    bytesToMB(bytes) {
        if (typeof bytes !== 'number' || bytes < 0) {
            throw new Error('Введите допустимое число байтов (ноль или больше).');
        }
        return (bytes / (1024 * 1024)).toFixed(2);
    }

    /**
     * Очищаем предыдущие ошибки.
     */
    clearErrors() {
        this.errors = [];
        if (this.nodeError) {
            this.nodeError.classList.remove('active');
            this.nodeError.textContent = '';
        }
    }

    /**
     * Отображаем ошибки в элементе.
     */
    displayErrors() {
        if (this.nodeError && this.errors.length > 0) {
            this.nodeError.innerHTML = this.errors.join('<br>');
            this.nodeError.classList.add('active');
        } else {
            this.nodeError.innerHTML = '';
            this.nodeError.classList.remove('active');
        }
    }

    /**
     * Обрезаем строку до заданной длины и добавляем три точки, если она длиннее.
     */
    truncateString(str) {
        if (this.truncate !== 0) {
            if (str.length > this.truncate) {
                return str.slice(0, this.truncate) + '...';
            }
        }

        return str;
    }

    /**
     * Очищаем input.
     */
    clearInput(element) {
        element.value = ''
        this.setFileName(this.defaultFileName)
        this.setFileInfo()
        // Если поле обязательное, добавляем класс с ошибкой.
        if(this.isRequired !== false) {
            this.addErrorClass(this.elementContainer)
        }
    }

    /**
     * Обработчик клика кнопки "Удалить"
     */
    clearButtonHandler(button) {
        button.addEventListener('click', (event) => {
            event.preventDefault()
            event.stopPropagation()
            let containerSelector = this.elementContainer.getAttribute('id');

            let targetInput = button.closest('#' + containerSelector).querySelector('input[type="file"]')

            if (targetInput && targetInput.value !== '') {
                this.clearInput(targetInput)
            }
        })
    }

    addErrorClass(element) {
        element.classList.add('_error')
    }

    removeErrorClass(element) {
        if (element.classList.contains('_error')) {
            element.classList.remove('_error')
        }
    }
}

Метод валидации Bootstrap

initValidation() {
        'use strict';

        this.form.addEventListener('submit', event => {
            event.preventDefault();
            event.stopPropagation();

            this.form.classList.add('was-validated');

            if (this.form.checkValidity()) {
                this.sendRequest()
            }
        }, false);
    }