You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在两个独立Bootstrap模态框中实现Simple Ajax Uploader Dropzone并解决进度条失效与实例冲突问题

Fixing Independent Simple Ajax Uploader Instances in Bootstrap Modals with Working Progress Bars

I see exactly what's going on here—you're running into two key issues: global variable overriding and hardcoded DOM selection that breaks instance independence. Let's fix both problems step by step, so each modal's uploader works fully on its own with functional progress bars.

Why Your Current Code Fails

  • Global uploader Variable: You're using a single global uploader variable, so every time createUploader runs, it overwrites the previous instance. That's why only the last modal works.
  • Hardcoded Progress Container: In onSubmit, you're using document.getElementById('progressContainer') which always grabs the first element with that ID. Since IDs must be unique, your second modal's container isn't being used at all, and progress bars get added to the first modal by mistake.
  • Undefined file Variable: In onComplete, you reference file.previewElement which doesn't exist in Simple Ajax Uploader's context—this is leftover Dropzone code that breaks the success flow.

Corrected Full Code

HTML (Two Isolated Modals)

<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://btn.ninja/js/SimpleAjaxUploader.min.js"></script>

<!-- Modal 1 -->
<div class="modal fade" id="uploadModal1" tabindex="-1" aria-labelledby="uploadModal1Label" aria-hidden="true">
  <div class="modal-dialog">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title" id="uploadModal1Label">Upload Files - Modal 1</h5>
        <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
      </div>
      <div class="modal-body">
        <div class="col-12 mt-3">
          <div class="uploadContainer">
            <button type="button" class="col-12 btn bg-transparent border-0 link btnUpload">
              <div class="dotdash uploadDropzone">
                <h5>Drag & Drop</h5>
                <p>Your File Here or Browse to Upload</p>
                <small class="text-prime">Only PDF Files with max size of 20 MB</small>
              </div>
            </button>
            <div class="alert uploadMessage" style="display:none;"></div>
            <div class="row">
              <div class="col-12 uploadProgress"></div> <!-- No ID, use class for instance-specific selection -->
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</div>

<!-- Modal 2 -->
<div class="modal fade" id="uploadModal2" tabindex="-1" aria-labelledby="uploadModal2Label" aria-hidden="true">
  <div class="modal-dialog">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title" id="uploadModal2Label">Upload Files - Modal 2</h5>
        <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
      </div>
      <div class="modal-body">
        <div class="col-12 mt-3">
          <div class="uploadContainer">
            <button type="button" class="col-12 btn bg-transparent border-0 link btnUpload">
              <div class="dotdash uploadDropzone">
                <h5>Drag & Drop</h5>
                <p>Your File Here or Browse to Upload</p>
                <small class="text-prime">Only PDF Files with max size of 20 MB</small>
              </div>
            </button>
            <div class="alert uploadMessage" style="display:none;"></div>
            <div class="row">
              <div class="col-12 uploadProgress"></div> <!-- No ID, use class for instance-specific selection -->
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</div>

<!-- Buttons to open modals -->
<button type="button" class="btn btn-primary mt-3" data-bs-toggle="modal" data-bs-target="#uploadModal1">
  Open Modal 1
</button>
<button type="button" class="btn btn-secondary mt-3" data-bs-toggle="modal" data-bs-target="#uploadModal2">
  Open Modal 2
</button>

CSS (Tweaked for Better Progress Bar Alignment)

.uploadContainer {
    position: relative;
}
.uploadDropzone {
    content: "";
    border: 2px dashed #DADBDF;
    padding: 20px;
    text-align: center;
    border-radius: 5px;
}
.uploadDropzone .cloud {
    font-size: 48px;
    line-height: 1;
    opacity: .5;
}
.uploadMessage {
    margin-top: .5rem;
}
.uploadProgress {
    margin-top: .5rem;
    background-color: #fff;
    border: thin solid #aaa;
    border-radius: 3px;
}
.uploadProgress:empty {
    background: none !important;
    margin: 0 !important;
    border: none !important;
}
.wrapper {
    width: auto;
}
.uploadProgress .wrapper {
    position: static;
    background-color: #fff;
    white-space: nowrap;
    border-bottom: thin solid #d2d2d2;
    line-height: 35px;
    padding: 0 0 0 10px;
}
.uploadProgress .wrapper:last-child {
    border-bottom: 0;
}
.uploadProgress .wrapper:after {
    content: "";
    display: table;
    clear: both;
}
.uploadProgress .name {
    display: block;
    float: left;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.uploadProgress .size {
    display: block;
    float: left;
    white-space: nowrap;
    color: #aaa;
    padding-left: 5px;
}
.uploadProgress .size:before {
    content: "(";
}
.uploadProgress .size:after {
    content: ")";
}
.uploadProgress .progress {
    display: block;
    float: right;
    margin-right: 10px;
    width: 120px;
    height: 20px;
    margin-top: 7px;
}
.uploadProgress .progress-bar {
    height: 100%;
}
.uploadProgress .deleteAttachment {
    display: block;
    float: right;
    padding: 0;
    background-color: transparent !important;
    border: 0 !important;
    background-image: none;
    line-height: inherit;
}
.deleteAttachment:after {
    content: '\00D7';
    display: block;
    line-height: inherit;
    width: 35px;
    text-align: center;
    color: rgba(0, 0, 0, .3);
    font-family: sans-serif;
    font-size: 18px;
    font-weight: bold;
}
.uploadProgress .wrapper.success {
    color: green;
}
.uploadProgress .wrapper.error {
    color: red;
}
.uploadProgress .error .progress-bar {
    background-color: gold;
}
.uploadProgress .success .progress-bar {
    background-color: green;
}

JavaScript (Fixed Instance Isolation & Progress Bars)

// No global uploader variable - each instance is created locally
function createUploader(index, elem) {
    const $elem = $(elem);
    const type = $elem.data("type");
    const $dropzone = $elem.find('.uploadDropzone');
    const $btn = $elem.find('.btnUpload');
    const $progBox = $elem.find('.uploadProgress'); // Target THIS container's progress box
    const $msgBox = $elem.find('.uploadMessage');
    const maxsize = 20000; // 20MB

    function showUploadMessage(msg, result) {
        $msgBox.text(msg).addClass(result).show();
    }

    function hideUploadMessage() {
        $msgBox.text('').removeClass('error unknown success').hide();
    }

    // Create a LOCAL uploader instance for this container
    const uploader = new ss.SimpleUpload({
        button: $btn[0], // Pass native DOM element
        dropzone: $dropzone[0],
        url: 'your_php_file.php', // Replace with your actual upload endpoint
        name: 'uploadfile',
        progressBar: null, // We'll set custom progress bars per file
        multipart: true,
        disabledClass: 'disabled',
        responseType: 'json',
        allowedExtensions: ["pdf"],
        maxSize: maxsize,
        multiple: true,
        multipleSelect: true,
        maxUploads: 10,
        queue: false,
        startXHR: function() {},
        onSubmit: function(filename, fileSize) {
            hideUploadMessage();

            // Build custom progress wrapper for this file
            const wrapper = document.createElement('div');
            wrapper.className = 'row ps-0 wrapper';
            
            const fileNameEl = document.createElement('div');
            fileNameEl.className = 'col-6 pe-0 name';
            fileNameEl.innerHTML = `<span>${filename}</span>`;
            
            const fileSizeEl = document.createElement('div');
            fileSizeEl.className = 'col-2 d-flex justify-content-center size';
            fileSizeEl.textContent = this.formatSize(fileSize); // Use built-in size formatter
            
            const progressContainer = document.createElement('div');
            progressContainer.className = 'col-3 progress me-0';
            
            const progressBar = document.createElement('div');
            progressBar.className = 'progress-bar';
            
            const closeBtn = document.createElement('button');
            closeBtn.className = 'col-1 d-flex justify-content-end deleteAttachment';
            closeBtn.setAttribute('aria-label', 'Delete file');

            // Assemble and add to THIS instance's progress container
            progressContainer.appendChild(progressBar);
            wrapper.appendChild(fileNameEl);
            wrapper.appendChild(fileSizeEl);
            wrapper.appendChild(progressContainer);
            wrapper.appendChild(closeBtn);
            $progBox[0].appendChild(wrapper);

            // Bind elements to this upload instance
            $(wrapper).data('file', filename);
            this.setProgressBar(progressBar);
            this.setAbortBtn(closeBtn);
        },
        onSizeError: function(filename, fileSize) {
            const msg = `${filename} exceeds the maximum size of ${this.formatSize(maxsize)}.`;
            showUploadMessage(msg, 'error');
        },
        onExtError: function(filename) {
            const msg = `${filename} is not a valid PDF file. Please select only PDF files.`;
            showUploadMessage(msg, 'error');
        },
        onComplete: function(filename, response) {
            const $wrapper = $progBox.find('.wrapper').filterByData('file', filename);
            $wrapper.addClass('success');

            if (!response) {
                showUploadMessage('Unable to upload file(s).', 'error');
                $wrapper.addClass('error');
                return;
            }

            if (response.success === true) {
                // Add your post-success logic here (e.g., save file reference to database)
                console.log(`${filename} uploaded successfully!`);
            } else {
                const msg = response.msg || 'Sorry! Unable to upload this file.';
                showUploadMessage(msg, 'error');
                $wrapper.addClass('error');
            }
        },
        onError: function(filename) {
            const $wrapper = $progBox.find('.wrapper').filterByData('file', filename);
            $wrapper.addClass('error');
            showUploadMessage('Unable to upload file(s) to server. Please try again later.', 'error');
        }
    });

    // Store the uploader instance on the container for later access (e.g., aborting uploads)
    $elem.data('uploader', uploader);
}

// Initialize all upload containers
$('.uploadContainer').each(createUploader);

// Delete attachment handler
$(document).on('click', '.deleteAttachment', function(e) {
    e.preventDefault();
    const $wrapper = $(this).closest('.wrapper');
    const filename = $wrapper.data('file');
    
    // Abort the upload if it's still in progress
    const $uploadContainer = $wrapper.closest('.uploadContainer');
    const uploader = $uploadContainer.data('uploader');
    if (uploader && uploader.getQueue().some(item => item.filename === filename)) {
        uploader.abort(filename);
    }
    
    $wrapper.remove();
    
    // Add your server-side deletion logic here
    // $.post('delete-file.php', { filename: filename }, function(response) {
    //     console.log('File deleted from server:', response);
    // });
});

// Helper: Filter elements by data attribute (jQuery doesn't have this built-in)
$.fn.filterByData = function(key, value) {
    return this.filter(function() {
        return $(this).data(key) === value;
    });
};

Key Fixes Explained

  1. Local Uploader Instances: We removed the global uploader variable and create a local instance inside createUploader, so each modal gets its own independent uploader.
  2. Instance-Specific DOM Selection: Instead of hardcoding a progress container ID, we use $elem.find('.uploadProgress') to target only the container inside the current uploader's modal.
  3. Custom Progress Bar Binding: We set progressBar: null in the config, then create and bind a unique progress bar for each file directly in onSubmit, ensuring updates go to the correct modal.
  4. Fixed Error Handling: We removed the invalid Dropzone code and added proper error messaging tied to each instance's message box.
  5. Upload Abort Support: We store each uploader instance on its container, so we can abort in-progress uploads when a user deletes a file.

Testing the Solution

  • Open each modal independently—uploads, progress bars, and error messages will be fully isolated to the modal you're using.
  • Progress bars will update correctly as files upload, since each file's bar is bound to the correct upload instance.
  • Deleting a file will only affect the modal it's in, and will abort the upload if it's still in progress.

内容的提问来源于stack exchange,提问作者Omer Butt

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.28 20:17:39