Files
hermes-config/skills/software-development/web-ui-repair/references/iife-capture-save-button-conflict.md
T
2026-07-12 10:17:17 -04:00

131 lines
5.1 KiB
Markdown

# 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.