Skip to content

Commit 5933a35

Browse files
committed
feat: Add class based base pattern.
This is a new way to define Patterns by extending the BasePattern class and instantiating it on an element. This is additionally to the old Base pattern approach or even the very old approach of just defining a Pattern following the specs. The BasePattern class in core/basepattern uses JavaScript classes with all the object oriented benefits. Yes, that might be syntactic sugar, but then again not. For usage see: src/core/basepattern.md
1 parent 27fc87b commit 5933a35

File tree

3 files changed

+269
-0
lines changed

3 files changed

+269
-0
lines changed

src/core/basepattern.js

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/**
2+
* A Base pattern for creating scoped patterns.
3+
*
4+
* Each instance of a pattern has its own local scope.
5+
* A new instance is created for each DOM element on which a pattern applies.
6+
*
7+
* For usage, see basepattern.md
8+
*/
9+
import logging from "./logging";
10+
11+
const log = logging.getLogger("Patternslib Base");
12+
13+
class BasePattern {
14+
static name; // name of pattern used in Registry.
15+
static trigger; // A CSS selector to match elements that should trigger the pattern instantiation.
16+
parser; // Options parser.
17+
18+
constructor(el, options = {}) {
19+
// Make static ``name`` and ``trigger`` available on instance.
20+
this.name = this.constructor.name;
21+
this.trigger = this.constructor.trigger;
22+
23+
if (!el) {
24+
log.warn(`No element given to pattern ${this.name}.`);
25+
return;
26+
}
27+
if (el.jquery) {
28+
el = el[0];
29+
}
30+
this.el = el;
31+
32+
// Initialize asynchronously.
33+
//
34+
// 1) We need to call the concrete implementation of ``init``, but the
35+
// inheritance chain is not yet set up and ``init`` not available.
36+
//
37+
// 2) We want to wait for the init() to successfuly finish and fire an
38+
// event then.
39+
// But the constructer cannot not return a Promise, thus not be
40+
// asynchronous but only return itself.
41+
//
42+
// Both limitations are gone in next tick.
43+
//
44+
window.setTimeout(async () => {
45+
if (typeof this.el[`pattern-${this.name}`] !== "undefined") {
46+
// Do not reinstantiate
47+
log.debug(`Not reinstatiating the pattern ${this.name}.`, this.el);
48+
return;
49+
}
50+
51+
// Create the options object by parsing the element and using the
52+
// optional optios as default.
53+
this.options = this.parser?.parse(this.el, options) ?? options;
54+
55+
// Store pattern instance on element
56+
this.el[`pattern-${this.name}`] = this;
57+
58+
// Initialize the pattern
59+
await this.init();
60+
61+
// Notify that now ready
62+
this.el.dispatchEvent(
63+
new Event(`init.${this.name}.patterns`, {
64+
bubbles: true,
65+
cancelable: true,
66+
})
67+
);
68+
}, 0);
69+
}
70+
71+
init() {
72+
// Extend this method in your pattern.
73+
}
74+
}
75+
76+
export default BasePattern;
77+
export { BasePattern };

src/core/basepattern.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# BasePattern base pattern class.
2+
3+
A Base pattern for creating scoped patterns.
4+
5+
Each instance of a pattern has its own local scope.
6+
A new instance is created for each DOM element on which a pattern applies.
7+
8+
9+
## Usage:
10+
11+
Also see: https://github.com/Patternslib/pat-PATTERN_TEMPLATE
12+
13+
14+
import { BasePattern } from "@patternslib/patternslib/src/core/basepattern";
15+
import Parser from "@patternslib/patternslib/src/core/parser";
16+
import registry from "@patternslib/patternslib/src/core/registry";
17+
18+
export const parser = new Parser("test-pattern");
19+
parser.addArgument("example-option", "Stranger");
20+
21+
class Pattern extends BasePattern {
22+
static name = "test-pattern";
23+
static trigger = ".pat-test-pattern";
24+
parser = parser;
25+
26+
async init() {
27+
import("./test-pattern.scss");
28+
29+
// Try to avoid jQuery, but here is how to import it.
30+
// eslint-disable-next-line no-unused-vars
31+
const $ = (await import("jquery")).default;
32+
33+
// The options are automatically created, if parser is defined.
34+
const example_option = this.options.exampleOption;
35+
this.el.innerHTML = `
36+
<p>hello, ${example_option}, this is pattern ${this.name} speaking.</p>
37+
`;
38+
}
39+
}
40+
41+
// Register Pattern class in the global pattern registry
42+
registry.register(Pattern);
43+
44+
// Make it available
45+
export default Pattern;
46+

src/core/basepattern.test.js

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
import { BasePattern } from "./basepattern";
2+
import registry from "./registry";
3+
import utils from "./utils";
4+
import { jest } from "@jest/globals";
5+
6+
describe("Basepattern class tests", function () {
7+
const patterns = registry.patterns;
8+
9+
beforeEach(function () {
10+
registry.clear();
11+
});
12+
13+
afterEach(function () {
14+
registry.patterns = patterns;
15+
jest.restoreAllMocks();
16+
});
17+
18+
it("1 - Trigger and name are statically available on the class.", async function () {
19+
class Pat extends BasePattern {
20+
static name = "example";
21+
static trigger = ".example";
22+
}
23+
24+
// trigger and name are static and available on the class itself
25+
expect(Pat.trigger).toBe(".example");
26+
expect(Pat.name).toBe("example");
27+
28+
const el = document.createElement("div");
29+
30+
const pat = new Pat(el);
31+
await utils.timeout(1);
32+
33+
expect(pat.name).toEqual("example");
34+
expect(pat.trigger).toEqual(".example");
35+
});
36+
37+
it("2 - Base pattern is class based and does inheritance, polymorphism, encapsulation, ... pt1", async function () {
38+
class Pat1 extends BasePattern {
39+
some = "thing";
40+
}
41+
class Pat2 extends Pat1 {
42+
init() {
43+
this.extra();
44+
}
45+
extra() {
46+
this.some = "other";
47+
}
48+
}
49+
50+
const el1 = document.createElement("div");
51+
const el2 = document.createElement("div");
52+
53+
const pat1 = new Pat1(el1);
54+
await utils.timeout(1);
55+
56+
const pat2 = new Pat2(el2);
57+
await utils.timeout(1);
58+
59+
expect(pat1 instanceof Pat1).toBe(true);
60+
expect(pat1 instanceof Pat2).toBe(false);
61+
expect(pat1.el).toEqual(el1);
62+
expect(pat1.some).toEqual("thing");
63+
64+
expect(pat2 instanceof Pat1).toBe(true);
65+
expect(pat2 instanceof Pat2).toBe(true);
66+
expect(pat2.el).toEqual(el2);
67+
expect(pat2.some).toEqual("other");
68+
});
69+
70+
it("3 - can be extended multiple times", async function () {
71+
class Pat1 extends BasePattern {
72+
static name = "example1";
73+
something = "else";
74+
init() {}
75+
}
76+
class Pat2 extends Pat1 {
77+
static name = "example2";
78+
some = "thing2";
79+
init() {}
80+
}
81+
class Pat3 extends Pat2 {
82+
static name = "example3";
83+
some = "thing3";
84+
init() {}
85+
}
86+
87+
const el = document.createElement("div");
88+
const pat1 = new Pat1(el);
89+
await utils.timeout(1);
90+
const pat2 = new Pat2(el);
91+
await utils.timeout(1);
92+
const pat3 = new Pat3(el);
93+
await utils.timeout(1);
94+
95+
expect(pat1.name).toEqual("example1");
96+
expect(pat1.something).toEqual("else");
97+
expect(pat1.some).toEqual(undefined);
98+
expect(pat1 instanceof BasePattern).toBeTruthy();
99+
expect(pat1 instanceof Pat2).toBeFalsy();
100+
expect(pat1 instanceof Pat3).toBeFalsy();
101+
102+
expect(pat2.name).toEqual("example2");
103+
expect(pat2.something).toEqual("else");
104+
expect(pat2.some).toEqual("thing2");
105+
expect(pat2 instanceof BasePattern).toBeTruthy();
106+
expect(pat2 instanceof Pat1).toBeTruthy();
107+
expect(pat2 instanceof Pat3).toBeFalsy();
108+
109+
expect(pat3.name).toEqual("example3");
110+
expect(pat3.something).toEqual("else");
111+
expect(pat3.some).toEqual("thing3");
112+
expect(pat3 instanceof BasePattern).toBeTruthy();
113+
expect(pat3 instanceof Pat1).toBeTruthy();
114+
expect(pat3 instanceof Pat2).toBeTruthy();
115+
});
116+
117+
it("4 - The pattern instance is stored on the element itself.", async function () {
118+
class Pat extends BasePattern {
119+
static name = "example";
120+
}
121+
122+
const el = document.createElement("div");
123+
const pat = new Pat(el);
124+
await utils.timeout(1);
125+
126+
expect(el["pattern-example"]).toEqual(pat);
127+
});
128+
129+
it("5 - Registers with the registry.", async function () {
130+
class Pat extends BasePattern {
131+
static name = "example";
132+
static trigger = ".example";
133+
}
134+
135+
registry.register(Pat);
136+
137+
const el = document.createElement("div");
138+
el.classList.add("example");
139+
140+
registry.scan(el);
141+
await utils.timeout(1);
142+
143+
// gh-copilot wrote this line.
144+
expect(el["pattern-example"]).toBeInstanceOf(Pat);
145+
});
146+
});

0 commit comments

Comments
 (0)