dom-primitive-bindings 1.0.0 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +101 -64
- package/docs/architecture.md +111 -0
- package/docs/binding-spec.md +86 -0
- package/docs/dist/min.js +7 -6
- package/docs/dist/v2/min.js +64 -0
- package/docs/dist/v3/min.js +64 -0
- package/docs/index.html +102 -79
- package/docs/stories/01-the-listener-trap.html +189 -0
- package/docs/stories/02-the-delegation-epiphany.html +185 -0
- package/docs/stories/03-the-staged-pipeline.html +162 -0
- package/docs/stories/04-declarative-json-bindings.html +159 -0
- package/package.json +3 -3
- package/src/index.js +2 -2
- package/src/{v1 → v3}/register/index.js +2 -1
- package/src/{v1 → v3}/register/meta.js +1 -2
- /package/src/{v1 → v3}/dispatch/executeBinding.js +0 -0
- /package/src/{v1 → v3}/dispatch/getClientData.js +0 -0
- /package/src/{v1 → v3}/dispatch/getStartInput.js +0 -0
- /package/src/{v1 → v3}/dispatch/index.js +0 -0
- /package/src/{v1 → v3}/guards/bindings/index.js +0 -0
- /package/src/{v1 → v3}/guards/bindings/validateArray.js +0 -0
- /package/src/{v1 → v3}/guards/container/index.js +0 -0
- /package/src/{v1 → v3}/guards/container/resolveElement.js +0 -0
- /package/src/{v1 → v3}/guards/index.js +0 -0
- /package/src/{v1 → v3}/index.js +0 -0
- /package/src/{v1 → v3}/listeners/attachListener.js +0 -0
- /package/src/{v1 → v3}/listeners/index.js +0 -0
- /package/src/{v1 → v3}/prepare/groupBindings.js +0 -0
- /package/src/{v1 → v3}/prepare/index.js +0 -0
package/docs/index.html
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<head>
|
|
5
5
|
<meta charset="UTF-8">
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
|
-
<title>DOM Primitive Bindings (
|
|
7
|
+
<title>DOM Primitive Bindings (v3) — Architecture & Story Hub</title>
|
|
8
8
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
9
9
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css" rel="stylesheet">
|
|
10
10
|
<style>
|
|
@@ -20,13 +20,22 @@
|
|
|
20
20
|
padding: 56px 0;
|
|
21
21
|
margin-bottom: 40px;
|
|
22
22
|
}
|
|
23
|
-
.
|
|
23
|
+
.story-card {
|
|
24
24
|
background: #ffffff;
|
|
25
25
|
border: 1px solid #e2e8f0;
|
|
26
26
|
border-radius: 12px;
|
|
27
|
-
|
|
27
|
+
transition: all 0.2s ease-in-out;
|
|
28
|
+
text-decoration: none;
|
|
29
|
+
color: inherit;
|
|
30
|
+
display: block;
|
|
28
31
|
height: 100%;
|
|
29
32
|
}
|
|
33
|
+
.story-card:hover {
|
|
34
|
+
transform: translateY(-3px);
|
|
35
|
+
box-shadow: 0 10px 20px rgba(0,0,0,0.06);
|
|
36
|
+
border-color: #34d399;
|
|
37
|
+
color: inherit;
|
|
38
|
+
}
|
|
30
39
|
pre {
|
|
31
40
|
background-color: #0f172a;
|
|
32
41
|
color: #f1f5f9;
|
|
@@ -51,18 +60,18 @@
|
|
|
51
60
|
<div class="container">
|
|
52
61
|
<div class="d-flex align-items-center justify-content-between flex-wrap gap-3">
|
|
53
62
|
<div>
|
|
54
|
-
<span class="badge bg-warning text-dark mb-2">
|
|
63
|
+
<span class="badge bg-warning text-dark mb-2">v3 · Single Source of Truth</span>
|
|
55
64
|
<h1 class="display-6 fw-bold mb-2">DOM Primitive Bindings</h1>
|
|
56
65
|
<p class="lead mb-0 text-white opacity-75">
|
|
57
|
-
|
|
66
|
+
Browser-native, single-listener container event delegation driven by declarative JSON.
|
|
58
67
|
</p>
|
|
59
68
|
</div>
|
|
60
69
|
<div class="d-flex gap-2">
|
|
61
|
-
<a href="../samples/
|
|
62
|
-
<i class="bi bi-play-circle me-1"></i> Interactive Samples
|
|
70
|
+
<a href="../samples/v3/index.html" class="btn btn-outline-light btn-sm">
|
|
71
|
+
<i class="bi bi-play-circle me-1"></i> Interactive Samples (v3)
|
|
63
72
|
</a>
|
|
64
|
-
<a href="
|
|
65
|
-
<i class="bi bi-
|
|
73
|
+
<a href="https://www.npmjs.com/package/dom-primitive-bindings" class="btn btn-warning btn-sm fw-bold">
|
|
74
|
+
<i class="bi bi-box-seam me-1"></i> npm Package
|
|
66
75
|
</a>
|
|
67
76
|
</div>
|
|
68
77
|
</div>
|
|
@@ -71,113 +80,127 @@
|
|
|
71
80
|
|
|
72
81
|
<main class="container pb-5" style="max-width: 1000px;">
|
|
73
82
|
|
|
74
|
-
<!-- Section 1: The
|
|
75
|
-
<section class="
|
|
76
|
-
<
|
|
77
|
-
<
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
</p>
|
|
85
|
-
</section>
|
|
83
|
+
<!-- Section 1: The Interactive Story Chapters -->
|
|
84
|
+
<section class="mb-5">
|
|
85
|
+
<div class="d-flex align-items-center justify-content-between mb-4">
|
|
86
|
+
<div>
|
|
87
|
+
<h2 class="h4 fw-bold text-dark mb-1">
|
|
88
|
+
<i class="bi bi-book-half text-success me-2"></i> The Event Delegation Story
|
|
89
|
+
</h2>
|
|
90
|
+
<p class="text-muted small mb-0">Follow the 4-part journey from brittle individual listeners to declarative, unbreakable JSON delegation:</p>
|
|
91
|
+
</div>
|
|
92
|
+
</div>
|
|
86
93
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
94
|
+
<div class="row g-4">
|
|
95
|
+
<!-- Chapter 1 -->
|
|
96
|
+
<div class="col-md-6">
|
|
97
|
+
<a href="./stories/01-the-listener-trap.html" class="story-card p-4">
|
|
98
|
+
<div class="d-flex align-items-center justify-content-between mb-2">
|
|
99
|
+
<span class="badge bg-danger">Chapter 1</span>
|
|
100
|
+
<i class="bi bi-arrow-right text-muted"></i>
|
|
101
|
+
</div>
|
|
102
|
+
<h3 class="h5 fw-bold text-dark mb-2">The Everyday Habit & The Listener Trap</h3>
|
|
103
|
+
<p class="small text-muted mb-0">
|
|
104
|
+
Why attaching listeners to individual elements causes memory leaks, double-trigger cascades, and dead-zones on dynamic elements.
|
|
105
|
+
</p>
|
|
106
|
+
</a>
|
|
107
|
+
</div>
|
|
92
108
|
|
|
93
|
-
|
|
109
|
+
<!-- Chapter 2 -->
|
|
94
110
|
<div class="col-md-6">
|
|
95
|
-
<
|
|
96
|
-
<
|
|
97
|
-
<
|
|
98
|
-
|
|
111
|
+
<a href="./stories/02-the-delegation-epiphany.html" class="story-card p-4">
|
|
112
|
+
<div class="d-flex align-items-center justify-content-between mb-2">
|
|
113
|
+
<span class="badge bg-primary">Chapter 2</span>
|
|
114
|
+
<i class="bi bi-arrow-right text-muted"></i>
|
|
115
|
+
</div>
|
|
116
|
+
<h3 class="h5 fw-bold text-dark mb-2">The Delegation Epiphany</h3>
|
|
99
117
|
<p class="small text-muted mb-0">
|
|
100
|
-
|
|
118
|
+
The core insight: leveraging browser event bubbling. The 3 pillars: Parent Anchor, Bubbling Interception, and Selector Matching.
|
|
101
119
|
</p>
|
|
102
|
-
</
|
|
120
|
+
</a>
|
|
103
121
|
</div>
|
|
104
122
|
|
|
123
|
+
<!-- Chapter 3 -->
|
|
105
124
|
<div class="col-md-6">
|
|
106
|
-
<
|
|
107
|
-
<
|
|
108
|
-
<
|
|
109
|
-
|
|
125
|
+
<a href="./stories/03-the-staged-pipeline.html" class="story-card p-4">
|
|
126
|
+
<div class="d-flex align-items-center justify-content-between mb-2">
|
|
127
|
+
<span class="badge bg-purple text-white" style="background:#7c3aed;">Chapter 3</span>
|
|
128
|
+
<i class="bi bi-arrow-right text-muted"></i>
|
|
129
|
+
</div>
|
|
130
|
+
<h3 class="h5 fw-bold text-dark mb-2">The Staged Pipeline Architecture</h3>
|
|
110
131
|
<p class="small text-muted mb-0">
|
|
111
|
-
|
|
132
|
+
Deep dive into the 4 internal stages in <code>src/v3/</code>: Guards, Prepare, Listeners, and Dispatch.
|
|
112
133
|
</p>
|
|
113
|
-
</
|
|
134
|
+
</a>
|
|
114
135
|
</div>
|
|
115
136
|
|
|
137
|
+
<!-- Chapter 4 -->
|
|
116
138
|
<div class="col-md-6">
|
|
117
|
-
<
|
|
118
|
-
<
|
|
119
|
-
<
|
|
139
|
+
<a href="./stories/04-declarative-json-bindings.html" class="story-card p-4">
|
|
140
|
+
<div class="d-flex align-items-center justify-content-between mb-2">
|
|
141
|
+
<span class="badge bg-success">Chapter 4</span>
|
|
142
|
+
<i class="bi bi-arrow-right text-muted"></i>
|
|
143
|
+
</div>
|
|
144
|
+
<h3 class="h5 fw-bold text-dark mb-2">The Joy of Declarative JSON</h3>
|
|
145
|
+
<p class="small text-muted mb-0">
|
|
146
|
+
Interactive live demo running v3! How pure serializable <code>bindings.json</code> files decouple event configuration from execution.
|
|
147
|
+
</p>
|
|
148
|
+
</a>
|
|
149
|
+
</div>
|
|
150
|
+
</div>
|
|
151
|
+
</section>
|
|
152
|
+
|
|
153
|
+
<!-- Section 2: Technical Architecture & Specs -->
|
|
154
|
+
<section class="card shadow-sm border-0 p-4 mb-4">
|
|
155
|
+
<h2 class="h5 fw-bold text-dark mb-3">
|
|
156
|
+
<i class="bi bi-journal-text text-success me-2"></i> Technical Documentation & Specifications
|
|
157
|
+
</h2>
|
|
158
|
+
<div class="row g-3">
|
|
159
|
+
<div class="col-md-6">
|
|
160
|
+
<div class="p-3 bg-light rounded border h-100">
|
|
161
|
+
<h6 class="fw-bold text-dark mb-1">
|
|
162
|
+
<a href="architecture.md" class="text-decoration-none text-success">
|
|
163
|
+
<i class="bi bi-file-earmark-text me-1"></i> Architecture & Design Breakdown
|
|
164
|
+
</a>
|
|
120
165
|
</h6>
|
|
121
166
|
<p class="small text-muted mb-0">
|
|
122
|
-
|
|
167
|
+
Comprehensive reference of the 4 decoupled internal folders: <code>guards/</code>, <code>prepare/</code>, <code>listeners/</code>, and <code>dispatch/</code>.
|
|
123
168
|
</p>
|
|
124
169
|
</div>
|
|
125
170
|
</div>
|
|
126
171
|
|
|
127
172
|
<div class="col-md-6">
|
|
128
|
-
<div class="
|
|
129
|
-
<h6 class="fw-bold text-
|
|
130
|
-
<
|
|
173
|
+
<div class="p-3 bg-light rounded border h-100">
|
|
174
|
+
<h6 class="fw-bold text-dark mb-1">
|
|
175
|
+
<a href="binding-spec.md" class="text-decoration-none text-success">
|
|
176
|
+
<i class="bi bi-file-earmark-code me-1"></i> Binding Specification (JSON Schema)
|
|
177
|
+
</a>
|
|
131
178
|
</h6>
|
|
132
179
|
<p class="small text-muted mb-0">
|
|
133
|
-
|
|
180
|
+
Normative schema definition for <code>bindings.json</code> attributes: <code>event</code>, <code>selector</code>, <code>root</code>, and <code>clientData</code>.
|
|
134
181
|
</p>
|
|
135
182
|
</div>
|
|
136
183
|
</div>
|
|
137
184
|
</div>
|
|
138
185
|
</section>
|
|
139
186
|
|
|
140
|
-
<!--
|
|
141
|
-
<section class="card shadow-sm border-0 p-4
|
|
187
|
+
<!-- Quick Start -->
|
|
188
|
+
<section class="card shadow-sm border-0 p-4">
|
|
142
189
|
<h2 class="h5 fw-bold text-dark mb-2">
|
|
143
|
-
<i class="bi bi-
|
|
190
|
+
<i class="bi bi-lightning-charge-fill text-warning me-2"></i> Quick Start (v3)
|
|
144
191
|
</h2>
|
|
145
|
-
<p class="small text-muted mb-3">
|
|
146
|
-
Include the bundled distribution or ES module, then call <code>bindEvents</code>:
|
|
147
|
-
</p>
|
|
148
|
-
|
|
149
|
-
<pre class="mb-3"><code><script type="module" src="/docs/dist/v1/min.js"></script>
|
|
150
|
-
<script type="module">
|
|
151
|
-
const bindings = [
|
|
152
|
-
{
|
|
153
|
-
event: "click",
|
|
154
|
-
selector: ".btn-delete",
|
|
155
|
-
runner: (target) => ({ id: target.dataset.id }),
|
|
156
|
-
onExtract: (data) => console.log("Deleted:", data)
|
|
157
|
-
}
|
|
158
|
-
];
|
|
192
|
+
<p class="small text-muted mb-3">Load your declarative bindings JSON and delegate with one line of code:</p>
|
|
159
193
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
</section>
|
|
194
|
+
<pre class="mb-0"><code>import bindings from "./bindings.json" with { type: "json" };
|
|
195
|
+
import { bindEvents } from "dom-primitive-bindings";
|
|
163
196
|
|
|
164
|
-
|
|
165
|
-
<section class="card shadow-sm border-0 p-4">
|
|
166
|
-
<h2 class="h5 fw-bold text-dark mb-3">
|
|
167
|
-
<i class="bi bi-check2-circle text-success me-2"></i> Code Conventions
|
|
168
|
-
</h2>
|
|
169
|
-
<ul class="list-unstyled mb-0 small text-muted">
|
|
170
|
-
<li class="mb-2"><i class="bi bi-check-lg text-success me-2"></i><strong>Every folder has an <code>index.js</code></strong> file acting as its public gateway.</li>
|
|
171
|
-
<li class="mb-2"><i class="bi bi-check-lg text-success me-2"></i><strong>Single-function default export</strong> per file (no default export objects).</li>
|
|
172
|
-
<li class="mb-2"><i class="bi bi-check-lg text-success me-2"></i><strong>Zero comments in code:</strong> Folders and file names tell the story directly.</li>
|
|
173
|
-
<li><i class="bi bi-check-lg text-success me-2"></i><strong>Independent sibling package:</strong> Focused purely on event delegation without mixing extraction concerns.</li>
|
|
174
|
-
</ul>
|
|
197
|
+
bindEvents("table-container", bindings);</code></pre>
|
|
175
198
|
</section>
|
|
176
199
|
|
|
177
200
|
</main>
|
|
178
201
|
|
|
179
202
|
<footer class="text-center text-muted py-4 border-top small">
|
|
180
|
-
DOM Primitive Bindings · Single Source of Truth: <code>
|
|
203
|
+
DOM Primitive Bindings · Single Source of Truth: <code>v3</code> · MIT License
|
|
181
204
|
</footer>
|
|
182
205
|
|
|
183
206
|
</body>
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="UTF-8">
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
|
+
<title>Chapter 1: The Everyday Habit & The Listener Trap — DOM Primitive Bindings</title>
|
|
8
|
+
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
9
|
+
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css" rel="stylesheet">
|
|
10
|
+
<style>
|
|
11
|
+
body {
|
|
12
|
+
background-color: #f8fafc;
|
|
13
|
+
color: #334155;
|
|
14
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
15
|
+
line-height: 1.65;
|
|
16
|
+
}
|
|
17
|
+
.story-hero {
|
|
18
|
+
background: linear-gradient(135deg, #065f46 0%, #047857 100%);
|
|
19
|
+
color: #fff;
|
|
20
|
+
padding: 40px 0;
|
|
21
|
+
}
|
|
22
|
+
pre {
|
|
23
|
+
background-color: #0f172a;
|
|
24
|
+
color: #e2e8f0;
|
|
25
|
+
padding: 16px;
|
|
26
|
+
border-radius: 8px;
|
|
27
|
+
font-size: 0.9rem;
|
|
28
|
+
overflow-x: auto;
|
|
29
|
+
}
|
|
30
|
+
code:not(pre code) {
|
|
31
|
+
background-color: #e2e8f0;
|
|
32
|
+
color: #0f172a;
|
|
33
|
+
padding: 2px 6px;
|
|
34
|
+
border-radius: 4px;
|
|
35
|
+
font-size: 0.88em;
|
|
36
|
+
}
|
|
37
|
+
.trap-card {
|
|
38
|
+
border-left: 4px solid #ef4444;
|
|
39
|
+
background: #fff;
|
|
40
|
+
border-radius: 8px;
|
|
41
|
+
}
|
|
42
|
+
.demo-box {
|
|
43
|
+
background: #ffffff;
|
|
44
|
+
border: 1px solid #cbd5e1;
|
|
45
|
+
border-radius: 8px;
|
|
46
|
+
padding: 20px;
|
|
47
|
+
}
|
|
48
|
+
</style>
|
|
49
|
+
</head>
|
|
50
|
+
|
|
51
|
+
<body>
|
|
52
|
+
|
|
53
|
+
<header class="story-hero shadow-sm mb-4">
|
|
54
|
+
<div class="container">
|
|
55
|
+
<div class="d-flex justify-content-between align-items-center flex-wrap gap-2">
|
|
56
|
+
<div>
|
|
57
|
+
<span class="badge bg-danger mb-2">Chapter 1 of 4</span>
|
|
58
|
+
<h1 class="h3 fw-bold mb-1">The Everyday Habit & The Listener Trap</h1>
|
|
59
|
+
<p class="text-white small mb-0">Why attaching event listeners to individual DOM elements breaks in production.</p>
|
|
60
|
+
</div>
|
|
61
|
+
<a href="../index.html" class="btn btn-outline-light btn-sm">
|
|
62
|
+
<i class="bi bi-arrow-left me-1"></i> Story Index
|
|
63
|
+
</a>
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
</header>
|
|
67
|
+
|
|
68
|
+
<main class="container pb-5" style="max-width: 900px;">
|
|
69
|
+
|
|
70
|
+
<!-- Part 1: How We Usually Do It -->
|
|
71
|
+
<section class="card shadow-sm border-0 p-4 mb-4">
|
|
72
|
+
<h2 class="h5 fw-bold text-dark mb-3">
|
|
73
|
+
<i class="bi bi-code-slash text-success me-2"></i> 1. The Everyday Habit
|
|
74
|
+
</h2>
|
|
75
|
+
<p>
|
|
76
|
+
In every frontend codebase, developers routinely write loops to attach click listeners directly onto individual buttons:
|
|
77
|
+
</p>
|
|
78
|
+
|
|
79
|
+
<pre><code>// The standard habit: query all buttons and attach listeners individually
|
|
80
|
+
const buttons = document.querySelectorAll(".btn-action");
|
|
81
|
+
|
|
82
|
+
buttons.forEach((btn) => {
|
|
83
|
+
btn.addEventListener("click", (event) => {
|
|
84
|
+
console.log("Clicked:", btn);
|
|
85
|
+
});
|
|
86
|
+
});</code></pre>
|
|
87
|
+
|
|
88
|
+
<p class="mb-0 text-success fw-semibold">
|
|
89
|
+
<i class="bi bi-check2-circle me-1"></i> On a static page with 5 buttons, it works immediately. But what happens in a dynamic application?
|
|
90
|
+
</p>
|
|
91
|
+
</section>
|
|
92
|
+
|
|
93
|
+
<!-- Part 2: The Three Silent Traps -->
|
|
94
|
+
<section class="card shadow-sm border-0 p-4 mb-4">
|
|
95
|
+
<h2 class="h5 fw-bold text-danger mb-3">
|
|
96
|
+
<i class="bi bi-exclamation-triangle-fill text-danger me-2"></i> 2. The Three Production Traps
|
|
97
|
+
</h2>
|
|
98
|
+
|
|
99
|
+
<!-- Trap 1 -->
|
|
100
|
+
<div class="trap-card p-3 shadow-sm mb-3">
|
|
101
|
+
<h3 class="h6 fw-bold text-danger mb-1">
|
|
102
|
+
<i class="bi bi-1-circle-fill me-1"></i> Trap 1: The Dynamic DOM Dead-Zone
|
|
103
|
+
</h3>
|
|
104
|
+
<p class="small text-muted mb-0">
|
|
105
|
+
When the user paginates, filters a table, or adds a new card dynamically, new DOM elements enter the page. Since the initial <code>querySelectorAll</code> loop already ran, <strong>none of the new elements have event listeners attached</strong>. Clicks do nothing, and the UI appears dead!
|
|
106
|
+
</p>
|
|
107
|
+
</div>
|
|
108
|
+
|
|
109
|
+
<!-- Trap 2 -->
|
|
110
|
+
<div class="trap-card p-3 shadow-sm mb-3">
|
|
111
|
+
<h3 class="h6 fw-bold text-danger mb-1">
|
|
112
|
+
<i class="bi bi-2-circle-fill me-1"></i> Trap 2: The Double-Trigger Cascade
|
|
113
|
+
</h3>
|
|
114
|
+
<p class="small text-muted mb-0">
|
|
115
|
+
To fix Trap 1, developers often re-run their binding function after every render. But existing elements now get a <strong>second listener</strong>! Clicking a button now fires twice, then 4 times, then 8 times, triggering duplicate API calls or state corruption.
|
|
116
|
+
</p>
|
|
117
|
+
</div>
|
|
118
|
+
|
|
119
|
+
<!-- Trap 3 -->
|
|
120
|
+
<div class="trap-card p-3 shadow-sm mb-0">
|
|
121
|
+
<h3 class="h6 fw-bold text-danger mb-1">
|
|
122
|
+
<i class="bi bi-3-circle-fill me-1"></i> Trap 3: Memory Leaks
|
|
123
|
+
</h3>
|
|
124
|
+
<p class="small text-muted mb-0">
|
|
125
|
+
In large single-page applications, thousands of event listeners attached to discarded DOM elements prevent garbage collection, causing heap bloat and lagging browser tabs.
|
|
126
|
+
</p>
|
|
127
|
+
</div>
|
|
128
|
+
</section>
|
|
129
|
+
|
|
130
|
+
<!-- Part 3: Live Interactive Proof -->
|
|
131
|
+
<section class="card shadow-sm border-0 p-4 mb-4">
|
|
132
|
+
<h2 class="h5 fw-bold text-dark mb-2">
|
|
133
|
+
<i class="bi bi-play-circle text-success me-2"></i> 3. Try It Yourself: The Dynamic Dead-Zone
|
|
134
|
+
</h2>
|
|
135
|
+
<p class="small text-muted mb-3">
|
|
136
|
+
Notice how Button #1 (hooked on page load) works, but dynamically added Button #2 fails completely:
|
|
137
|
+
</p>
|
|
138
|
+
|
|
139
|
+
<div class="demo-box mb-3">
|
|
140
|
+
<div class="d-flex gap-2 mb-3" id="buttons-wrapper">
|
|
141
|
+
<button class="btn btn-outline-primary btn-sm naive-btn" id="initial-btn">Initial Button #1</button>
|
|
142
|
+
</div>
|
|
143
|
+
<button class="btn btn-secondary btn-sm" id="btn-add-dynamic">
|
|
144
|
+
<i class="bi bi-plus-circle me-1"></i> Add Dynamic Button #2
|
|
145
|
+
</button>
|
|
146
|
+
</div>
|
|
147
|
+
|
|
148
|
+
<div class="bg-light p-3 rounded border">
|
|
149
|
+
<div class="small fw-bold text-dark mb-1">Console Feedback:</div>
|
|
150
|
+
<div id="trap-feedback" class="font-monospace small text-muted">Click any button above to test...</div>
|
|
151
|
+
</div>
|
|
152
|
+
</section>
|
|
153
|
+
|
|
154
|
+
<!-- Navigation -->
|
|
155
|
+
<div class="d-flex justify-content-between align-items-center mt-4">
|
|
156
|
+
<a href="../index.html" class="btn btn-outline-secondary">
|
|
157
|
+
<i class="bi bi-arrow-left me-1"></i> Back to Hub
|
|
158
|
+
</a>
|
|
159
|
+
<a href="./02-the-delegation-epiphany.html" class="btn btn-success">
|
|
160
|
+
Next: Chapter 2 — The Delegation Epiphany <i class="bi bi-arrow-right ms-1"></i>
|
|
161
|
+
</a>
|
|
162
|
+
</div>
|
|
163
|
+
|
|
164
|
+
</main>
|
|
165
|
+
|
|
166
|
+
<script>
|
|
167
|
+
const feedback = document.getElementById("trap-feedback");
|
|
168
|
+
|
|
169
|
+
// Naive binding on page load:
|
|
170
|
+
document.querySelectorAll(".naive-btn").forEach((btn) => {
|
|
171
|
+
btn.addEventListener("click", () => {
|
|
172
|
+
feedback.innerHTML = `<span class="text-success fw-bold">Success:</span> Initial button clicked!`;
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
document.getElementById("btn-add-dynamic").addEventListener("click", () => {
|
|
177
|
+
const wrapper = document.getElementById("buttons-wrapper");
|
|
178
|
+
const newBtn = document.createElement("button");
|
|
179
|
+
newBtn.className = "btn btn-outline-danger btn-sm naive-btn";
|
|
180
|
+
newBtn.textContent = "Dynamic Button #2";
|
|
181
|
+
wrapper.appendChild(newBtn);
|
|
182
|
+
feedback.innerHTML = `<span class="text-danger">Dynamic button added! Click it now — notice NOTHING happens because it has no listener!</span>`;
|
|
183
|
+
});
|
|
184
|
+
</script>
|
|
185
|
+
|
|
186
|
+
</body>
|
|
187
|
+
|
|
188
|
+
</html>
|
|
189
|
+
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="UTF-8">
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
|
+
<title>Chapter 2: The Delegation Epiphany — DOM Primitive Bindings</title>
|
|
8
|
+
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
9
|
+
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css" rel="stylesheet">
|
|
10
|
+
<style>
|
|
11
|
+
body {
|
|
12
|
+
background-color: #f8fafc;
|
|
13
|
+
color: #334155;
|
|
14
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
15
|
+
line-height: 1.65;
|
|
16
|
+
}
|
|
17
|
+
.story-hero {
|
|
18
|
+
background: linear-gradient(135deg, #065f46 0%, #047857 100%);
|
|
19
|
+
color: #fff;
|
|
20
|
+
padding: 40px 0;
|
|
21
|
+
}
|
|
22
|
+
pre {
|
|
23
|
+
background-color: #0f172a;
|
|
24
|
+
color: #e2e8f0;
|
|
25
|
+
padding: 16px;
|
|
26
|
+
border-radius: 8px;
|
|
27
|
+
font-size: 0.9rem;
|
|
28
|
+
overflow-x: auto;
|
|
29
|
+
}
|
|
30
|
+
code:not(pre code) {
|
|
31
|
+
background-color: #e2e8f0;
|
|
32
|
+
color: #0f172a;
|
|
33
|
+
padding: 2px 6px;
|
|
34
|
+
border-radius: 4px;
|
|
35
|
+
font-size: 0.88em;
|
|
36
|
+
}
|
|
37
|
+
.pillar-card {
|
|
38
|
+
border-left: 4px solid #10b981;
|
|
39
|
+
background: #fff;
|
|
40
|
+
border-radius: 8px;
|
|
41
|
+
padding: 16px;
|
|
42
|
+
}
|
|
43
|
+
.demo-box {
|
|
44
|
+
background: #ffffff;
|
|
45
|
+
border: 1px solid #cbd5e1;
|
|
46
|
+
border-radius: 8px;
|
|
47
|
+
padding: 20px;
|
|
48
|
+
}
|
|
49
|
+
</style>
|
|
50
|
+
</head>
|
|
51
|
+
|
|
52
|
+
<body>
|
|
53
|
+
|
|
54
|
+
<header class="story-hero shadow-sm mb-4">
|
|
55
|
+
<div class="container">
|
|
56
|
+
<div class="d-flex justify-content-between align-items-center flex-wrap gap-2">
|
|
57
|
+
<div>
|
|
58
|
+
<span class="badge bg-primary mb-2">Chapter 2 of 4</span>
|
|
59
|
+
<h1 class="h3 fw-bold mb-1">The Delegation Epiphany</h1>
|
|
60
|
+
<p class="text-white small mb-0">Harnessing the browser's native event bubbling to attach once and handle forever.</p>
|
|
61
|
+
</div>
|
|
62
|
+
<a href="../index.html" class="btn btn-outline-light btn-sm">
|
|
63
|
+
<i class="bi bi-arrow-left me-1"></i> Story Index
|
|
64
|
+
</a>
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
</header>
|
|
68
|
+
|
|
69
|
+
<main class="container pb-5" style="max-width: 900px;">
|
|
70
|
+
|
|
71
|
+
<!-- Part 1: Event Bubbling -->
|
|
72
|
+
<section class="card shadow-sm border-0 p-4 mb-4">
|
|
73
|
+
<h2 class="h5 fw-bold text-dark mb-3">
|
|
74
|
+
<i class="bi bi-lightbulb-fill text-warning me-2"></i> 1. The Core Insight: Event Bubbling
|
|
75
|
+
</h2>
|
|
76
|
+
<p>
|
|
77
|
+
Every browser has a built-in superpower that developers often forget: <strong>Event Bubbling</strong>.
|
|
78
|
+
</p>
|
|
79
|
+
<p>
|
|
80
|
+
When a user clicks a button inside a card or table row, the click event does not stay on the button. It bubbles upward through every ancestor in the DOM tree until it reaches <code>document</code>:
|
|
81
|
+
</p>
|
|
82
|
+
|
|
83
|
+
<pre><code>[ <button class="btn-action"> ] ──(bubbles up)──> [ <div class="card"> ] ──> [ <div id="container"> ]
|
|
84
|
+
▲
|
|
85
|
+
│
|
|
86
|
+
Attach ONE listener here ────┘</code></pre>
|
|
87
|
+
|
|
88
|
+
<p class="mb-0">
|
|
89
|
+
Instead of attaching 500 listeners to 500 buttons, attach <strong>exactly 1 listener</strong> to the parent container!
|
|
90
|
+
</p>
|
|
91
|
+
</section>
|
|
92
|
+
|
|
93
|
+
<!-- Part 2: The Three Pillars -->
|
|
94
|
+
<section class="card shadow-sm border-0 p-4 mb-4">
|
|
95
|
+
<h2 class="h5 fw-bold text-dark mb-3">
|
|
96
|
+
<i class="bi bi-diagram-3-fill text-success me-2"></i> 2. The Three Pillars of Delegation
|
|
97
|
+
</h2>
|
|
98
|
+
|
|
99
|
+
<div class="row g-3">
|
|
100
|
+
<div class="col-md-4">
|
|
101
|
+
<div class="pillar-card h-100">
|
|
102
|
+
<h6 class="fw-bold text-success mb-2">1. The Anchor</h6>
|
|
103
|
+
<p class="small text-muted mb-0">The stable parent element (container ID or DOM element) that lives throughout the page lifecycle.</p>
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
<div class="col-md-4">
|
|
107
|
+
<div class="pillar-card h-100">
|
|
108
|
+
<h6 class="fw-bold text-info mb-2">2. The Interceptor</h6>
|
|
109
|
+
<p class="small text-muted mb-0">A single listener per event type (e.g. <code>click</code>) that catches events as they bubble up.</p>
|
|
110
|
+
</div>
|
|
111
|
+
</div>
|
|
112
|
+
<div class="col-md-4">
|
|
113
|
+
<div class="pillar-card h-100">
|
|
114
|
+
<h6 class="fw-bold text-primary mb-2">3. The Matcher</h6>
|
|
115
|
+
<p class="small text-muted mb-0"><code>event.target.closest(selector)</code> checks if the clicked element matches the target selector.</p>
|
|
116
|
+
</div>
|
|
117
|
+
</div>
|
|
118
|
+
</div>
|
|
119
|
+
</section>
|
|
120
|
+
|
|
121
|
+
<!-- Part 3: Live Interactive Proof -->
|
|
122
|
+
<section class="card shadow-sm border-0 p-4 mb-4">
|
|
123
|
+
<h2 class="h5 fw-bold text-dark mb-2">
|
|
124
|
+
<i class="bi bi-play-circle text-success me-2"></i> 3. Try It: Instant Dynamic Support
|
|
125
|
+
</h2>
|
|
126
|
+
<p class="small text-muted mb-3">
|
|
127
|
+
Notice how dynamic buttons work immediately with zero re-binding:
|
|
128
|
+
</p>
|
|
129
|
+
|
|
130
|
+
<div class="demo-box mb-3" id="delegation-container">
|
|
131
|
+
<div class="d-flex gap-2 flex-wrap mb-3" id="del-buttons-wrapper">
|
|
132
|
+
<button class="btn btn-outline-success btn-sm delegated-btn" data-name="Button #1">Button #1</button>
|
|
133
|
+
</div>
|
|
134
|
+
<button class="btn btn-primary btn-sm" id="btn-add-delegated">
|
|
135
|
+
<i class="bi bi-plus-circle me-1"></i> Add Dynamic Button
|
|
136
|
+
</button>
|
|
137
|
+
</div>
|
|
138
|
+
|
|
139
|
+
<div class="bg-light p-3 rounded border">
|
|
140
|
+
<div class="small fw-bold text-dark mb-1">Delegated Console:</div>
|
|
141
|
+
<div id="del-feedback" class="font-monospace small text-muted">Click any button inside the box...</div>
|
|
142
|
+
</div>
|
|
143
|
+
</section>
|
|
144
|
+
|
|
145
|
+
<!-- Navigation -->
|
|
146
|
+
<div class="d-flex justify-content-between align-items-center mt-4">
|
|
147
|
+
<a href="./01-the-listener-trap.html" class="btn btn-outline-secondary">
|
|
148
|
+
<i class="bi bi-arrow-left me-1"></i> Chapter 1: The Trap
|
|
149
|
+
</a>
|
|
150
|
+
<a href="./03-the-staged-pipeline.html" class="btn btn-success">
|
|
151
|
+
Next: Chapter 3 — The Staged Pipeline <i class="bi bi-arrow-right ms-1"></i>
|
|
152
|
+
</a>
|
|
153
|
+
</div>
|
|
154
|
+
|
|
155
|
+
</main>
|
|
156
|
+
|
|
157
|
+
<script>
|
|
158
|
+
const container = document.getElementById("delegation-container");
|
|
159
|
+
const feedback = document.getElementById("del-feedback");
|
|
160
|
+
let count = 1;
|
|
161
|
+
|
|
162
|
+
// ONE listener attached to the container:
|
|
163
|
+
container.addEventListener("click", (e) => {
|
|
164
|
+
const match = e.target.closest(".delegated-btn");
|
|
165
|
+
if (match && container.contains(match)) {
|
|
166
|
+
feedback.innerHTML = `<span class="text-success fw-bold">Delegated Click Captured:</span> ${match.dataset.name} (Tag: <${match.tagName}>)`;
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
document.getElementById("btn-add-delegated").addEventListener("click", () => {
|
|
171
|
+
count++;
|
|
172
|
+
const wrapper = document.getElementById("del-buttons-wrapper");
|
|
173
|
+
const btn = document.createElement("button");
|
|
174
|
+
btn.className = "btn btn-outline-success btn-sm delegated-btn";
|
|
175
|
+
btn.dataset.name = `Dynamic Button #${count}`;
|
|
176
|
+
btn.textContent = `Button #${count}`;
|
|
177
|
+
wrapper.appendChild(btn);
|
|
178
|
+
feedback.innerHTML = `<span class="text-info">Dynamic Button #${count} appended. Click it — it works immediately with ZERO re-hooking!</span>`;
|
|
179
|
+
});
|
|
180
|
+
</script>
|
|
181
|
+
|
|
182
|
+
</body>
|
|
183
|
+
|
|
184
|
+
</html>
|
|
185
|
+
|