initial commit
This commit is contained in:
+130
@@ -0,0 +1,130 @@
|
||||
# IIFE Capture-Phase Handler Kills Save Button
|
||||
|
||||
## Pattern
|
||||
|
||||
A page has an inline IIFE `<script>` block (not `type="module"`) that registers a capture-phase click handler on `document`:
|
||||
|
||||
```javascript
|
||||
document.addEventListener('click', function(e) {
|
||||
var closeBtn = e.target.closest('[aria-label="Close"],[data-close-modal]');
|
||||
if (closeBtn) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation(); // ← THIS IS THE KILLER
|
||||
var id = closeBtn.getAttribute('data-close-modal');
|
||||
if (id) { closeModal(id); return; }
|
||||
// ... fallback close logic ...
|
||||
}
|
||||
// ... backdrop click handling ...
|
||||
}, true); // ← capture phase
|
||||
```
|
||||
|
||||
Some JS module (e.g., `settings.js`) sets `data-close-modal` on close, cancel, AND save buttons:
|
||||
|
||||
```javascript
|
||||
saveSiteSettings.setAttribute('data-close-modal', 'site-settings-modal');
|
||||
```
|
||||
|
||||
## Symptom
|
||||
|
||||
- Close (X) and Cancel buttons work correctly — modal closes
|
||||
- Save button closes the modal but **settings are never persisted**
|
||||
- No console errors
|
||||
- The save function is defined and reachable (inline `onclick` or `addEventListener` exists)
|
||||
|
||||
## Root Cause
|
||||
|
||||
When the user clicks Save:
|
||||
|
||||
1. **Capture phase** — IIFE handler on `document` fires FIRST. Matches `[data-close-modal]` on the save button. Calls `closeModal()` immediately. Calls `e.stopPropagation()` which **prevents the event from ever reaching the target phase or bubble phase**.
|
||||
2. **Target phase** — NEVER REACHED. The inline `onclick="saveSiteSettings()"` never fires.
|
||||
3. **Bubble phase** — NEVER REACHED. Any `addEventListener('click', handler)` on the button never fires.
|
||||
|
||||
The modal closes (step 1 did that), but the save logic never executes.
|
||||
|
||||
## Why Close/Cancel Still Work
|
||||
|
||||
For close and cancel buttons, closing the modal IS the correct action. The IIFE intercepting and closing is fine — no additional behavior was needed.
|
||||
|
||||
## Fix (Three Options)
|
||||
|
||||
### Option A: Remove `data-close-modal` from save button (cleanest)
|
||||
|
||||
In the code that sets attributes (`ensureSiteSettingsCloseAttributes` or dashboard init):
|
||||
|
||||
```javascript
|
||||
// BEFORE (broken)
|
||||
setAttr(closeId); // ✓ fine
|
||||
setAttr(cancelId); // ✓ fine
|
||||
setAttr(saveId); // ✗ kills save handler
|
||||
|
||||
// AFTER (fixed)
|
||||
setAttr(closeId); // ✓
|
||||
setAttr(cancelId); // ✓
|
||||
// Do NOT set on saveId — let addEventListener handle it
|
||||
```
|
||||
|
||||
Then wire the save button via `addEventListener` (non-capture) so it fires after the IIFE ignores it:
|
||||
|
||||
```javascript
|
||||
saveBtn.addEventListener('click', async function() {
|
||||
// read form, save settings
|
||||
await saveAllSettings();
|
||||
closeModal('site-settings-modal');
|
||||
});
|
||||
```
|
||||
|
||||
The IIFE no longer matches the save button → capture phase passes through → target/bubble handlers fire normally.
|
||||
|
||||
### Option B: Add save handling to the IIFE itself
|
||||
|
||||
Insert a save-button check BEFORE the close-button check in the capture handler:
|
||||
|
||||
```javascript
|
||||
document.addEventListener('click', function(e) {
|
||||
// Check save button FIRST
|
||||
var saveBtn = e.target.closest('#save-site-settings');
|
||||
if (saveBtn) {
|
||||
e.stopPropagation();
|
||||
// Save directly or call window.saveSiteSettings()
|
||||
window.saveSiteSettings();
|
||||
window.closeModal('site-settings-modal');
|
||||
return;
|
||||
}
|
||||
// Then check close/cancel as before
|
||||
var closeBtn = e.target.closest('[aria-label="Close"],[data-close-modal]');
|
||||
// ...
|
||||
}, true);
|
||||
```
|
||||
|
||||
This works but couples the IIFE to specific button IDs. Option A is cleaner.
|
||||
|
||||
### Option C: Direct onclick property override (nuclear option)
|
||||
|
||||
Set `saveBtn.onclick` from the IIFE (firing in target phase, before bubbling):
|
||||
|
||||
```javascript
|
||||
var btn = document.getElementById('save-site-settings');
|
||||
if (btn) {
|
||||
btn.onclick = function(e) {
|
||||
e.stopImmediatePropagation();
|
||||
// save logic
|
||||
window.closeModal('site-settings-modal');
|
||||
return false;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
`stopImmediatePropagation()` prevents other listeners (from JS modules) on the same element from firing, avoiding duplicate saves.
|
||||
|
||||
## Diagnosis Checklist
|
||||
|
||||
1. Does the page have a capture-phase IIFE? Search for `addEventListener('click', ..., true)` or `},true)` in inline `<script>` blocks.
|
||||
2. Does the save button have `data-close-modal`? Check the HTML and any JS that sets attributes.
|
||||
3. Does the save button have an inline `onclick` or an `addEventListener` handler?
|
||||
4. If yes to all three → this is the bug.
|
||||
|
||||
## Related Pitfalls
|
||||
|
||||
- **Double save:** If you remove `data-close-modal` from save but BOTH the IIFE's new save handler AND the JS module's addEventListener fire, you get duplicate saves and duplicate notifications. Use `stopImmediatePropagation()` or check `hasAttribute('onclick')` to gate one of them.
|
||||
- **Module timing:** If the save handler is defined in a JS module (e.g., `window.saveSiteSettings` in `settings.js`), and the module loads after the IIFE, the IIFE may call it before it exists. Always check `typeof window.saveSiteSettings === 'function'` before calling.
|
||||
- **`stopPropagation()` vs `stopImmediatePropagation()`:** `stopPropagation()` in the target phase prevents bubbling but NOT other target-phase handlers on the same element. `stopImmediatePropagation()` prevents ALL remaining handlers on the element regardless of phase.
|
||||
Reference in New Issue
Block a user