@rails/ujs中等效于jQuery检测所有AJAX请求完成方法的实现咨询
Great question! Let's walk through how to replace that jQuery-based AJAX completion check with a @rails/ujs-compatible approach—perfect for your modal remote form + StimulusJS setup, whether you're working in production code or writing Capybara tests.
First, Understand the Original jQuery Logic
Your existing Ruby code checks jQuery.active, which tracks the number of in-flight AJAX requests managed by jQuery. When it hits 0, all requests are done. Since @rails/ujs (especially modern versions) doesn't rely on jQuery, we need a way to track requests using native browser APIs and @rails/ujs events.
Solution 1: For Capybara Tests (Matching Your Original Ruby Code)
If you're looking to replicate the test helper method, here's how to do it without jQuery:
Step 1: Add a Global Request Tracker to Your Frontend
First, add code to track all AJAX requests (including those from @rails/ujs and Stimulus) in your application's JavaScript:
// app/javascript/packs/application.js // Initialize a global counter for active AJAX requests window.activeAjaxRequests = 0; // Track @rails/ujs AJAX events document.addEventListener('ajax:beforeSend', () => { window.activeAjaxRequests++; }); document.addEventListener('ajax:complete', () => { window.activeAjaxRequests--; }); // Track fetch requests (common in Stimulus) const originalFetch = window.fetch; window.fetch = (...args) => { window.activeAjaxRequests++; return originalFetch(...args).finally(() => { window.activeAjaxRequests--; }); }; // Track raw XMLHttpRequest requests (if your Stimulus code uses this) const originalXHROpen = XMLHttpRequest.prototype.open; XMLHttpRequest.prototype.open = function(...args) { window.activeAjaxRequests++; this.addEventListener('loadend', () => { window.activeAjaxRequests--; }); originalXHROpen.apply(this, args); };
Step 2: Update Your Test Helper
Replace your existing finished_all_ajax_requests? method with this version that reads our global counter:
def finished_all_ajax_requests? # Wait for the counter to exist (in case it hasn't initialized yet) return false if page.evaluate_script('typeof window.activeAjaxRequests === "undefined"') page.evaluate_script('window.activeAjaxRequests').zero? end
This works exactly like your original jQuery check, but now it tracks all AJAX requests handled by @rails/ujs, Stimulus, and native browser APIs.
Solution 2: For Production/Stimulus Controller Logic
If you need to check for completed requests directly in your Stimulus controller (e.g., to close the modal after all AJAX finishes), here's a clean, encapsulated approach:
Create an AJAX Tracker Stimulus Controller
// app/javascript/controllers/ajax_tracker_controller.js import { Controller } from "@hotwired/stimulus"; export default class extends Controller { connect() { this.activeRequests = 0; // Track @rails/ujs events document.addEventListener('ajax:beforeSend', () => this.activeRequests++); document.addEventListener('ajax:complete', () => this.activeRequests--); // Wrap fetch to track Stimulus-initiated requests this.wrapFetch(); // Wrap XMLHttpRequest if needed this.wrapXHR(); } wrapFetch() { const originalFetch = window.fetch; window.fetch = (...args) => { this.activeRequests++; return originalFetch(...args).finally(() => this.activeRequests--); }; } wrapXHR() { const originalXHROpen = XMLHttpRequest.prototype.open; XMLHttpRequest.prototype.open = function(...args) { this.activeRequests++; this.addEventListener('loadend', () => this.activeRequests--); originalXHROpen.apply(this, args); }; } // Public method to check if all requests are done isAllFinished() { return this.activeRequests === 0; } }
Use the Tracker in Your Modal Form Controller
// app/javascript/controllers/modal_form_controller.js import { Controller } from "@hotwired/stimulus"; export default class extends Controller { static targets = ["form"]; connect() { // Get a reference to the AJAX tracker this.ajaxTracker = this.application.getControllerForElementAndIdentifier( document.body, "ajax-tracker" ); } // Example: Triggered when the remote form submits handleFormSubmit() { // Wait for all AJAX to finish before closing the modal this.waitForAllAjaxThenClose(); } waitForAllAjaxThenClose() { if (this.ajaxTracker.isAllFinished()) { this.closeModal(); } else { // Check again after a short delay (adjust timing as needed) setTimeout(() => this.waitForAllAjaxThenClose(), 100); } } closeModal() { this.element.classList.add('hidden'); // Add any other modal cleanup logic here } }
Don't forget to register the ajax-tracker controller in your HTML (e.g., on the <body> tag):
<body data-controller="ajax-tracker"> <!-- Your modal and form here --> </body>
Key Notes
- This approach covers all AJAX sources: @rails/ujs remote forms, Stimulus fetch/XHR calls, and even third-party scripts using native APIs.
- For tests, the global counter is straightforward and mirrors the simplicity of
jQuery.active. - For production code, using a Stimulus controller keeps your logic encapsulated and aligned with Hotwire best practices.
内容的提问来源于stack exchange,提问作者Artur79

