如何在两个独立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
uploaderVariable: You're using a single globaluploadervariable, so every timecreateUploaderruns, it overwrites the previous instance. That's why only the last modal works. - Hardcoded Progress Container: In
onSubmit, you're usingdocument.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
fileVariable: InonComplete, you referencefile.previewElementwhich 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
- Local Uploader Instances: We removed the global
uploadervariable and create a local instance insidecreateUploader, so each modal gets its own independent uploader. - 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. - Custom Progress Bar Binding: We set
progressBar: nullin the config, then create and bind a unique progress bar for each file directly inonSubmit, ensuring updates go to the correct modal. - Fixed Error Handling: We removed the invalid Dropzone code and added proper error messaging tied to each instance's message box.
- 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
相关产品推荐
相关产品推荐

