# JavaScript Skill

**When:** Any frontend JS task. Foundation skill always applies.

---

## Global Naming

4+ char prefix mandatory. Use `PresszoneForumApp`, `presszoneForumData`. Never `FPZ`, `pz`, `fpz`.

Access globals safely: `window.presszoneForumData?.nonce ?? ''` — never bare `window.presszoneForumData.nonce`.

---

## IIFE Pattern

Wrap all code in `(function() { 'use strict'; ... })()`. Read config via `window.presszoneForumData || {}` inside. Expose public API on `window.PresszoneForumApp`. Auto-init: check `document.readyState === 'loading'` → `addEventListener('DOMContentLoaded', init)` else call `init()` directly.

---

## Module Communication

Dispatch `new CustomEvent('fpz:ui-opening', { detail: { source: '...' } })` on `document`. Other modules listen with `document.addEventListener('fpz:ui-opening', handler)`.

---

## XSS Prevention

Never `element.innerHTML = userInput`. Use `element.textContent = userInput`. To build HTML with user data: create element via `document.createElement`, set `.textContent`, then `appendChild`. Only use `innerHTML` with server-sanitized content — add `// SECURITY: server-sanitized` comment.

---

## Event Handling

Use delegation: `document.addEventListener('click', e => { if (e.target.matches('.presszone-forum-btn')) handle(e); })`. Never attach to every element — memory leak when elements removed.

Cleanup: store `{ element, event, handler }` tuples, call `removeEventListener` on destroy.

---

## AJAX / Fetch

REST: `fetch(CONFIG.restUrl + endpoint, { method, headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': CONFIG.nonce }, credentials: 'same-origin', body: JSON.stringify(data) })`. Always include nonce + credentials.

WP AJAX: `FormData` to `CONFIG.ajaxUrl`, append `action` + `nonce` + payload fields.

Always `try/catch`, always show error via `Toast.js` on failure.

---

## DOM

Prefer `querySelector`/`querySelectorAll`. Use `classList.add/remove/toggle/contains`. Use `element.dataset.postId` for data attributes. Set `aria-expanded`, `disabled` as properties not strings where possible.

---

## Forms

`e.preventDefault()` always. Validate before submit. Serialize with `new FormData(form)`. If TinyMCE present, call `tinymce.triggerSave()` before reading values.

---

## Async

Use `async/await` + `try/catch/finally`. Show loading state in `try`, hide in `finally`. Parallel: `Promise.all(ids.map(id => apiRequest(id)))`.

---

## Debounce / Throttle

Debounce search inputs ~300ms. Throttle scroll/resize ~100ms. Implement inline or reuse existing util — no library imports.

---

## LocalStorage

Wrap in `try/catch` — private browsing can throw. `JSON.parse`/`JSON.stringify` all values. Key: `'presszone-dark-mode'`.

---

## Polling

Use `setInterval` in a class with `start()`/`stop()`. Always call `stop()` on widget close/destroy. Never leave intervals running.

---

## Shared Dropdown Component

`window.PresszoneForumDropdowns` — auto-discovers `.presszone-forum-actions-dropdown` wrappers.

HTML contract: wrapper `.presszone-forum-actions-dropdown` > trigger button `.presszone-forum-actions-dropdown__trigger` (with `aria-expanded="false"` `aria-haspopup="true"`) + menu div `.presszone-forum-actions-dropdown__menu` (role="menu") > items `.presszone-forum-actions-dropdown__item` (role="menuitem"). Danger variant: add `--danger` modifier.

Handles: toggle, outside-click close, Escape + focus return, ArrowUp/Down, `aria-expanded` sync, mutual exclusion.

Navbar user dropdown: managed by `navbar.js` — uses same item classes but NOT a `.presszone-forum-actions-dropdown` wrapper (needs custom animation + `presszone-forum:ui-opening` dispatch).

---

## Common Mistakes

| Mistake | Fix |
|---------|-----|
| Short global (`FPZ`) | `PresszoneForumApp` (4+ chars) |
| Bare global access | Optional chain + fallback |
| `innerHTML` + user data | `textContent` or `createElement` |
| No nonce in request | Add `X-WP-Nonce` header |
| Missing `credentials` | `credentials: 'same-origin'` |
| Event on every element | Event delegation |
| No listener cleanup | Store + `removeEventListener` |
| No `e.preventDefault()` | Always on form submit |
| `var` usage | `const`/`let` only |
| Unhandled rejection | `try/catch` or `.catch()` |
| Interval without cleanup | `clearInterval` on destroy |
| Mutating state arrays/objects | Spread copies: `[...arr]`, `{...obj}` |
| No toast on error | Use `Toast.js` for user-facing errors |
| TinyMCE not saved | `tinymce.triggerSave()` before submit |

---

## Related Skills

`ajax-skill`, `rest-api-skill`, `frontend-architecture-skill`, `accessibility-skill`
