jQuery Загрузка нескольких изображений с предварительным просмотром и удалением - PullRequest
0 голосов
/ 19 июня 2020

Во время выбора изображения, если мы выберем 1 или несколько изображений (1.jpeg, 2.jpeg) и закроем диалоговое окно, а затем снова откроем для выбора других файлов (3.jpeg, 4.jpeg) и закроем снова, тогда предварительный просмотр показывает предыдущие и новые изображения (1.jpeg, 2.jpeg, 3.jpeg и 4.jpeg), что нормально в GoogleCrome,

, но в Firefox он работает только при первом выборе, выберите больше изображений или удалите одно изображение с пустым массивом.

После некоторых тестов я обнаружил, что все работает нормально, но только в Chrome. Эта ошибка возникает только в Edge. В Firefox он просто не будет работать, но и ошибок не выдаст. В Safari тоже не работает.

Полный репозиторий: https://github.com/christianbayer/image-uploader

** Это js Плагин **

(function ($) {

$.fn.imageUploader = function (options) {

    // Default settings
    let defaults = {
        preloaded: [],
        imagesInputName: 'images',
        preloadedInputName: 'preloaded',
        label: 'Drag & Drop files here or click to browse',
        extensions: ['.jpg', '.jpeg', '.png', '.gif', '.svg'],
        mimes: ['image/jpeg', 'image/png', 'image/gif', 'image/svg+xml'],
        maxSize: undefined,
        maxFiles: undefined,
    };

    // Get instance
    let plugin = this;

    // Will keep the files
    let dataTransfer = new DataTransfer();

    // The file input
    let $input;

    // Set empty settings
    plugin.settings = {};

    // Plugin constructor
    plugin.init = function () {

        // Define settings
        plugin.settings = $.extend(plugin.settings, defaults, options);

        // Run through the elements
        plugin.each(function (i, wrapper) {

            // Create the container
            let $container = createContainer();

            // Append the container to the wrapper
            $(wrapper).append($container);

            // Set some bindings
            $container.on("dragover", fileDragHover.bind($container));
            $container.on("dragleave", fileDragHover.bind($container));
            $container.on("drop", fileSelectHandler.bind($container));

            // If there are preloaded images
            if (plugin.settings.preloaded.length) {

                // Change style
                $container.addClass('has-files');

                // Get the upload images container
                let $uploadedContainer = $container.find('.uploaded');

                // Set preloaded images preview
                for (let i = 0; i < plugin.settings.preloaded.length; i++) {
                    $uploadedContainer.append(createImg(plugin.settings.preloaded[i].src, plugin.settings.preloaded[i].id, true));
                }

            }

        });

    };

    let createContainer = function () {

        // Create the image uploader container
        let $container = $('<div>', {class: 'image-uploader'});

        // Create the input type file and append it to the container
        $input = $('<input>', {
            type: 'file',
            id: plugin.settings.imagesInputName + '-' + random(),
            name: plugin.settings.imagesInputName + '[]',
            accept: plugin.settings.extensions.join(','),
            multiple: 'multiple'
        }).appendTo($container);

        // Create the uploaded images container and append it to the container
        let $uploadedContainer = $('<div>', {class: 'uploaded'}).appendTo($container),

            // Create the text container and append it to the container
            $textContainer = $('<div>', {
                class: 'upload-text'
            }).appendTo($container),

            // Create the icon and append it to the text container
            $i = $('<i>', {class: 'iui-cloud-upload'}).appendTo($textContainer),

            // Create the text and append it to the text container
            $span = $('<span>', {text: plugin.settings.label}).appendTo($textContainer);


        // Listen to container click and trigger input file click
        $container.on('click', function (e) {
            // Prevent browser default event and stop propagation
            prevent(e);

            // Trigger input click
            $input.trigger('click');
        });

        // Stop propagation on input click
        $input.on("click", function (e) {
            e.stopPropagation();
        });

        // Listen to input files changed
        $input.on('change', fileSelectHandler.bind($container));

        return $container;
    };


    let prevent = function (e) {
        // Prevent browser default event and stop propagation
        e.preventDefault();
        e.stopPropagation();
    };

    let createImg = function (src, id, preloaded) {

        // Create the upladed image container
        let $container = $('<div>', {class: 'uploaded-image'}),

            // Create the img tag
            $img = $('<img>', {src: src}).appendTo($container),

            // Create the delete button
            $button = $('<button>', {class: 'delete-image'}).appendTo($container),

            // Create the delete icon
            $i = $('<i>', {class: 'iui-close'}).appendTo($button);


        // If the image is preloaded
        if (preloaded) {

            // Set a identifier
            $container.attr('data-preloaded', true);

            // Create the preloaded input and append it to the container
            let $preloaded = $('<input>', {
                type: 'hidden',
                name: plugin.settings.preloadedInputName + '[]',
                value: id
            }).appendTo($container)

        } else {

            // Set the index
            $container.attr('data-index', id);

        }

        // Stop propagation on click
        $container.on("click", function (e) {
            // Prevent browser default event and stop propagation
            prevent(e);
        });

        // Set delete action
        $button.on("click", function (e) {

            // Prevent browser default event and stop propagation
            prevent(e);

            // Get the parent element
            let $parent = $container.parent();

            // If is not a preloaded image
            if ($container.data('preloaded') === true) {

                // Remove from preloaded array
                plugin.settings.preloaded = plugin.settings.preloaded.filter(function (p) {
                    return p.id !== id;
                });

            } else {

                // Get the image index
                let index = parseInt($container.data('index'));

                // Update other indexes
                $parent.find('.uploaded-image[data-index]').each(function (i, cont) {
                    if (i > index) {
                        $(cont).attr('data-index', i - 1);
                    }
                });

                // Remove the file from input
                dataTransfer.items.remove(index);

                // Update input files
                $input.prop('files', dataTransfer.files);
            }

            // Remove this image from the container
            $container.remove();

            // If there is no more uploaded files
            if (!$parent.children().length) {

                // Remove the 'has-files' class
                $parent.parent().removeClass('has-files');

            }

        });

        return $container;
    };

    let fileDragHover = function (e) {

        // Prevent browser default event and stop propagation
        prevent(e);

        // Change the container style
        if (e.type === "dragover") {
            $(this).addClass('drag-over');
        } else {
            $(this).removeClass('drag-over');
        }
    };

    let fileSelectHandler = function (e) {

        // Prevent browser default event and stop propagation
        prevent(e);

        // Get the jQuery element instance
        let $container = $(this);

        // Get the files as an array of files
        let files = Array.from(e.target.files || e.originalEvent.dataTransfer.files);

        // Will keep only the valid files
        let validFiles = [];

        // Run through the files
        $(files).each(function (i, file) {
            // Run the validations
            if (plugin.settings.extensions && !validateExtension(file)) {
                return;
            }
            if (plugin.settings.mimes && !validateMIME(file)) {
                return;
            }
            if (plugin.settings.maxSize && !validateMaxSize(file)) {
                return;
            }
            if (plugin.settings.maxFiles && !validateMaxFiles(validFiles.length, file)) {
                return;
            }
            validFiles.push(file);
            console.log(validFiles);
        });

        // If there is at least one valid file
        if (validFiles.length) {
            // Change the container style
            $container.removeClass('drag-over');

            // Makes the upload
            setPreview($container, validFiles);
        } else {

            // Update input files (it is now empty due to a default browser action)
            $input.prop('files', dataTransfer.files);

        }
    };

    let validateExtension = function (file) {

        if (plugin.settings.extensions.indexOf(file.name.replace(new RegExp('^.*\\.'), '.')) < 0) {
            alert(`The file "${file.name}" does not match with the accepted file extensions: "${plugin.settings.extensions.join('", "')}"`);

            return false;
        }

        return true;
    };

    let validateMIME = function (file) {

        if (plugin.settings.mimes.indexOf(file.type) < 0) {
            alert(`The file "${file.name}" does not match with the accepted mime types: "${plugin.settings.mimes.join('", "')}"`);

            return false;
        }

        return true;
    };

    let validateMaxSize = function (file) {

        if (file.size > plugin.settings.maxSize) {
            alert(`The file "${file.name}" exceeds the maximum size of ${plugin.settings.maxSize / 1024 / 1024}Mb`);

            return false;
        }

        return true;

    };

    let validateMaxFiles = function (index, file) {

        if ((index + dataTransfer.items.length + plugin.settings.preloaded.length) >= plugin.settings.maxFiles) {
            alert(`The file "${file.name}" could not be added because the limit of ${plugin.settings.maxFiles} files was reached`);

            return false;
        }

        return true;

    };

    let setPreview = function ($container, files) {

        // Add the 'has-files' class
        $container.addClass('has-files');

        // Get the upload images container
        let $uploadedContainer = $container.find('.uploaded'),

            // Get the files input
            $input = $container.find('input[type="file"]');

        // Run through the files
        $(files).each(function (i, file) {

            // Add it to data transfer
            dataTransfer.items.add(file);

            // Set preview
            $uploadedContainer.append(createImg(URL.createObjectURL(file), dataTransfer.items.length - 1), false);

        });

        // Update input files
        $input.prop('files', dataTransfer.files);

    };

    // Generate a random id
    let random = function () {
        return Date.now() + Math.floor((Math.random() * 100) + 1);
    };

    this.init();

    // Return the instance
    return this;
};

} (jQuery));

и файл HTML выглядит так:

    <form method="POST" enctype="multipart/form-data">

        <div class="input-field">
            <input type="text" name="name-1" id="name-1">
            <label for="name-1">Name</label>
        </div>

        <div class="input-field">
            <input type="text" name="description-1" id="description-1">
            <label for="description-1">Description</label>
        </div>

        <div class="input-field">
            <label class="active">Photos</label>
            <div class="input-images-1" style="padding-top: .5rem;"></div>
        </div>

        <button>Submit and display data</button>

    </form>

и в теге тела: тег скрипта:

<script>
    $(function () {
        $('.input-images-1').imageUploader({
            // preloaded: preloaded,
            imagesInputName: 'filesName',
            preloadedInputName: 'old',
            maxSize: 2 * 1024 * 1024,
            maxFiles: 4
        });

    });
</script>
...