Skip to content

Commit 507f97f

Browse files
authored
Merge pull request #1111 from Patternslib/basepattern-notify-not-instantiated
Basepattern notify not instantiated
2 parents bd95995 + e8640a9 commit 507f97f

File tree

4 files changed

+204
-11
lines changed

4 files changed

+204
-11
lines changed

src/core/basepattern.js

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
*
77
* For usage, see basepattern.md
88
*/
9+
import events from "./events";
910
import logging from "./logging";
1011

1112
const log = logging.getLogger("basepattern");
@@ -46,6 +47,14 @@ class BasePattern {
4647
if (typeof this.el[`pattern-${this.name}`] !== "undefined") {
4748
// Do not reinstantiate
4849
log.debug(`Not reinstatiating the pattern ${this.name}.`, this.el);
50+
51+
// Notify that not instantiated
52+
this.el.dispatchEvent(
53+
new Event(`not-init.${this.name}.patterns`, {
54+
bubbles: true,
55+
cancelable: false,
56+
})
57+
);
4958
return;
5059
}
5160

@@ -80,9 +89,15 @@ class BasePattern {
8089
* @param {function} callback - Callback to call when the event is thrown.
8190
*/
8291
one(event_name, event_callback) {
83-
this.el.addEventListener(`${event_name}.${this.name}.patterns`, event_callback, {
84-
once: true,
85-
});
92+
events.add_event_listener(
93+
this.el,
94+
`${event_name}.${this.name}.patterns`,
95+
`basepattern-one--${event_name}.${this.name}.patterns`,
96+
event_callback,
97+
{
98+
once: true,
99+
}
100+
);
86101
}
87102

88103
/**

src/core/basepattern.test.js

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,48 @@ describe("Basepattern class tests", function () {
185185
expect(el["pattern-example"]).toBeInstanceOf(Pat);
186186
});
187187

188-
it("6.1 - Registers a one-time event listener on the element.", async function () {
188+
it("6.1 - Can register a special namespaced one-time event listener on the element.", async function () {
189+
class Pat extends BasePattern {
190+
static name = "example";
191+
static trigger = ".example";
192+
}
193+
194+
const el = document.createElement("div");
195+
196+
const pat = new Pat(el);
197+
198+
let cnt = 0;
199+
pat.one("test", () => {
200+
cnt++;
201+
});
202+
203+
el.dispatchEvent(new Event("test.example.patterns")); // Note this special event name.
204+
205+
expect(cnt).toBe(1);
206+
207+
// The handler is now unregistered.
208+
el.dispatchEvent(new Event("test.example.patterns"));
209+
210+
expect(cnt).toBe(1);
211+
});
212+
213+
it("6.2 - Throws a init event after asynchronous initialization has finished.", async function () {
214+
const events = (await import("./events")).default;
215+
class Pat extends BasePattern {
216+
static name = "example";
217+
static trigger = ".example";
218+
}
219+
220+
const el = document.createElement("div");
221+
222+
const pat = new Pat(el);
223+
await events.await_pattern_init(pat);
224+
225+
// If test reaches this expect statement, the init event catched.
226+
expect(true).toBe(true);
227+
});
228+
229+
it("6.3 - Throws a not-init event in case of an double initialization event which is handled by await_pattern_init.", async function () {
189230
const events = (await import("./events")).default;
190231
class Pat extends BasePattern {
191232
static name = "example";
@@ -200,5 +241,55 @@ describe("Basepattern class tests", function () {
200241

201242
// If test reaches this expect statement, the init event catched.
202243
expect(true).toBe(true);
244+
245+
// The same pattern cannot be registered twice without destroying the
246+
// first one. If the same pattern is initialized twice on the same
247+
// element, a event is thrown.
248+
const pat2 = new Pat(el);
249+
try {
250+
await events.await_pattern_init(pat2);
251+
} catch (e) {
252+
expect(e instanceof Error).toBe(true);
253+
expect(e.message).toBe(`Pattern "example" not initialized.`);
254+
}
255+
256+
// We can also use the catch method of the promise to handle that case.
257+
const pat3 = new Pat(el);
258+
await events.await_pattern_init(pat3).catch((e) => {
259+
expect(e instanceof Error).toBe(true);
260+
expect(e.message).toBe(`Pattern "example" not initialized.`);
261+
return; // We need to return here.
262+
});
263+
264+
// If test reaches this expect statement, the not-init event was catched.
265+
expect(true).toBe(true);
266+
});
267+
268+
it("6.4 - Destroying a pattern before re-initializing again also works together with await_pattern_init.", async function () {
269+
const events = (await import("./events")).default;
270+
class Pat extends BasePattern {
271+
static name = "example";
272+
static trigger = ".example";
273+
}
274+
275+
const el = document.createElement("div");
276+
el.classList.add("example");
277+
278+
const pat = new Pat(el);
279+
await events.await_pattern_init(pat);
280+
281+
// If test reaches this expect statement, the init event catched.
282+
expect(true).toBe(true);
283+
284+
pat.destroy();
285+
286+
const pat2 = new Pat(el);
287+
await events.await_pattern_init(pat2).catch(() => {
288+
// this is not reached.
289+
expect(false).toBe(true);
290+
});
291+
292+
// If test reaches this expect statement, the init event catched.
293+
expect(true).toBe(true);
203294
});
204295
});

src/core/events.js

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// Event listener registration for easy-to-remove event listeners.
44
// once Safari supports the ``signal`` option for addEventListener we can abort
55
// event handlers by calling AbortController.abort().
6-
const event_listener_map = {};
6+
export const event_listener_map = {};
77

88
/**
99
* Add an event listener to a DOM element under a unique id.
@@ -22,11 +22,21 @@ const add_event_listener = (el, event_type, id, cb, opts = {}) => {
2222
}
2323
remove_event_listener(el, id); // do not register one listener twice.
2424

25+
// Create event_listener_map entry if not existent.
2526
if (!event_listener_map[el]) {
2627
event_listener_map[el] = {};
2728
}
28-
event_listener_map[el][id] = [event_type, cb, opts.capture ? opts : undefined]; // prettier-ignore
29-
el.addEventListener(event_type, cb, opts);
29+
let _cb = cb;
30+
if (opts?.once === true) {
31+
// For `once` events, also remove the entry from the event_listener_map.
32+
_cb = (e) => {
33+
delete event_listener_map[el][id];
34+
cb(e);
35+
};
36+
}
37+
// Only `capture` option is necessary for `removeEventListener`.
38+
event_listener_map[el][id] = [event_type, _cb, opts.capture ? opts : undefined];
39+
el.addEventListener(event_type, _cb, opts);
3040
};
3141

3242
/**
@@ -90,7 +100,29 @@ const await_event = (el, event_name) => {
90100
*/
91101
const await_pattern_init = (pattern) => {
92102
// See: https://stackoverflow.com/a/44746691/1337474
93-
return new Promise((resolve) => pattern.one("init", resolve));
103+
return new Promise((resolve, reject) => {
104+
// Case initialized
105+
pattern.one("init", () => {
106+
// Resolve promise and unregister the not-init event handler.
107+
remove_event_listener(
108+
pattern.el,
109+
`basepattern-one--not-init.${pattern.name}.patterns`
110+
);
111+
resolve();
112+
});
113+
114+
// Case not initialized
115+
pattern.one("not-init", () => {
116+
// Reject promise and unregister the init event handler.
117+
remove_event_listener(
118+
pattern.el,
119+
`basepattern-one--init.${pattern.name}.patterns`
120+
);
121+
reject();
122+
});
123+
}).catch(() => {
124+
throw new Error(`Pattern "${pattern.name}" not initialized.`);
125+
});
94126
};
95127

96128
/**

src/core/events.test.js

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,22 @@ describe("core.events tests", () => {
5050
done();
5151
});
5252

53-
it("Awaits an event to happen", async () => {
53+
it("Supports once-events and unregisters them from the event_listener_map", async () => {
54+
const event_listener_map = (await import("./events")).event_listener_map;
55+
const el = document.createElement("div");
56+
57+
// register the once-event handler
58+
events.add_event_listener(el, "test", "test_once_event", () => {}, {
59+
once: true,
60+
});
61+
62+
expect(event_listener_map[el].test_once_event).toBeDefined();
63+
el.dispatchEvent(new Event("test"));
64+
65+
expect(event_listener_map[el].test_once_event).not.toBeDefined();
66+
});
67+
68+
it("Awaits an event to happen.", async () => {
5469
const el = document.createElement("div");
5570

5671
window.setTimeout(() => {
@@ -63,7 +78,7 @@ describe("core.events tests", () => {
6378
expect(true).toBe(true);
6479
});
6580

66-
it("Awaits a pattern to be initialized", async () => {
81+
it("Awaits a pattern to be initialized.", async () => {
6782
const pat = Base.extend({
6883
name: "tmp",
6984
trigger: ".pat-tmp",
@@ -79,7 +94,47 @@ describe("core.events tests", () => {
7994
expect(true).toBe(true);
8095
});
8196

82-
it("Awaits a class based pattern to be initialized", async () => {
97+
it("Awaits a class based pattern to be initialized.", async () => {
98+
class Pat extends BasePattern {
99+
static name = "tmp";
100+
static trigger = ".pat-tmp";
101+
init() {}
102+
}
103+
104+
const el = document.createElement("div");
105+
const instance = new Pat(el);
106+
107+
await events.await_pattern_init(instance);
108+
109+
// If test reaches this expect statement, all is fine.
110+
expect(true).toBe(true);
111+
112+
// The same pattern cannot be registered twice without destroying
113+
// the first one. If the same pattern is initialized twice on the
114+
// same element, a event is thrown.
115+
const pat2 = new Pat(el);
116+
try {
117+
await events.await_pattern_init(pat2);
118+
} catch (e) {
119+
expect(e instanceof Error).toBe(true);
120+
expect(e.message).toBe(`Pattern "tmp" not initialized.`);
121+
}
122+
123+
// We can also use the catch method of the promise to handle that
124+
// case.
125+
const pat3 = new Pat(el);
126+
await events.await_pattern_init(pat3).catch((e) => {
127+
expect(e instanceof Error).toBe(true);
128+
expect(e.message).toBe(`Pattern "tmp" not initialized.`);
129+
return; // We need to return here.
130+
});
131+
132+
// If test reaches this expect statement, the not-init event was
133+
// catched.
134+
expect(true).toBe(true);
135+
});
136+
137+
it("Handles double-registration attempts by rejecting the await_pattern_init promise.", async () => {
83138
class Pat extends BasePattern {
84139
static name = "tmp";
85140
static trigger = ".pat-tmp";

0 commit comments

Comments
 (0)