Skip to content

Commit 5ef9434

Browse files
authored
Merge pull request #1225 from Patternslib/registry-sort-early
feat(core registry, basepattern): Introduce ability to order patterns.
2 parents 0b485d6 + a9deff7 commit 5ef9434

6 files changed

Lines changed: 248 additions & 38 deletions

File tree

src/core/basepattern.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ class BasePattern {
1616
static name; // name of pattern used in Registry.
1717
static trigger; // A CSS selector to match elements that should trigger the pattern instantiation.
1818
static parser; // Options parser.
19+
static order = 1000; // Registry pattern sorting.
1920

2021
// Parser options
2122
parser_group_options = true;

src/core/basepattern.md

Lines changed: 37 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,42 +5,53 @@ A Base pattern for creating scoped patterns.
55
Each instance of a pattern has its own local scope.
66
A new instance is created for each DOM element on which a pattern applies.
77

8+
## Pattern initialization order
9+
10+
The order of patterns in the patterns registry can be modified using the order
11+
property. Lower values will sort and initialize those patterns earlier than
12+
others with higher numbers. The default is 1000 - if you don't have any special
13+
needs, just leave out the `order` property from your Pattern class.
14+
**pat-validation** needs to be initialized early before other patterns can handle
15+
`submit` events - it has a sort order of 100.
16+
**pat-clone-code** needs to copy the Pattern markup before it is eventually
17+
modified - it has a sort order of 200.
818

919
## Usage:
1020

1121
Also see: https://github.com/Patternslib/pat-PATTERN_TEMPLATE
1222

23+
```javascript
24+
import { BasePattern } from "@patternslib/patternslib/src/core/basepattern";
25+
import Parser from "@patternslib/patternslib/src/core/parser";
26+
import registry from "@patternslib/patternslib/src/core/registry";
1327

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");
28+
export const parser = new Parser("test-pattern");
29+
parser.addArgument("example-option", "Stranger");
2030

21-
class Pattern extends BasePattern {
22-
static name = "test-pattern";
23-
static trigger = ".pat-test-pattern";
24-
static parser = parser;
31+
class Pattern extends BasePattern {
32+
static name = "test-pattern";
33+
static trigger = ".pat-test-pattern";
34+
static parser = parser;
35+
static order = 1000; // Optional. Leave out for the default value of 1000.
2536

26-
async init() {
27-
import("./test-pattern.scss");
37+
async init() {
38+
import("./test-pattern.scss");
2839

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;
40+
// Try to avoid jQuery, but here is how to import it.
41+
// eslint-disable-next-line no-unused-vars
42+
const $ = (await import("jquery")).default;
3243

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-
}
44+
// The options are automatically created, if parser is defined.
45+
const example_option = this.options.exampleOption;
46+
this.el.innerHTML = `
47+
<p>hello, ${example_option}, this is pattern ${this.name} speaking.</p>
48+
`;
3949
}
50+
}
4051

41-
// Register Pattern class in the global pattern registry
42-
registry.register(Pattern);
43-
44-
// Make it available
45-
export default Pattern;
52+
// Register Pattern class in the global pattern registry
53+
registry.register(Pattern);
4654

55+
// Make it available
56+
export default Pattern;
57+
```

src/core/registry.js

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -133,19 +133,26 @@ const registry = {
133133
},
134134

135135
orderPatterns(patterns) {
136-
// Always add pat-validation as first pattern, so that it can prevent
137-
// other patterns from reacting to submit events if form validation
138-
// fails.
139-
if (patterns.includes("validation")) {
140-
patterns.splice(patterns.indexOf("validation"), 1);
141-
patterns.unshift("validation");
142-
}
143-
// Add clone-code to the very beginning - we want to copy the markup
144-
// before any other patterns changed the markup.
145-
if (patterns.includes("clone-code")) {
146-
patterns.splice(patterns.indexOf("clone-code"), 1);
147-
patterns.unshift("clone-code");
136+
patterns = [...patterns];
137+
const sorted_patterns = [];
138+
139+
// Sort patterns
140+
for (const name of patterns) {
141+
const pattern = registry.patterns[name];
142+
if (!pattern) {
143+
// No registered pattern. Ignore that.
144+
continue;
145+
}
146+
sorted_patterns.push([name, pattern?.order || 1000]);
148147
}
148+
// Sorting. Sort for the value in the sorted_patterns map.
149+
patterns = sorted_patterns
150+
.toSorted((a, b) => {
151+
return a[1] - b[1];
152+
})
153+
.map((item) => {
154+
return item[0];
155+
});
149156

150157
return patterns;
151158
},

src/core/registry.test.js

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import Base from "./base";
2+
import BasePattern from "./basepattern";
23
import registry from "./registry";
34

45
describe("pat-registry: The registry for patterns", function () {
@@ -150,4 +151,185 @@ describe("pat-registry: The registry for patterns", function () {
150151
expect(tree.textContent).toBe("initialized");
151152
});
152153

154+
describe("orderPatterns", function () {
155+
it("Orders patterns by their order property with lower values first", function () {
156+
// Create test patterns with different order values
157+
class Pattern1 extends BasePattern {
158+
static name = "pattern1";
159+
static order = 500;
160+
}
161+
162+
class Pattern2 extends BasePattern {
163+
static name = "pattern2";
164+
static order = 100;
165+
}
166+
167+
class Pattern3 extends BasePattern {
168+
static name = "pattern3";
169+
static order = 300;
170+
}
171+
172+
// Register patterns
173+
registry.register(Pattern1);
174+
registry.register(Pattern2);
175+
registry.register(Pattern3);
176+
177+
const pattern_names = ["pattern1", "pattern2", "pattern3"];
178+
const ordered_patterns = registry.orderPatterns(pattern_names);
179+
180+
// Should be ordered by order property: pattern2 (100), pattern3 (300), pattern1 (500)
181+
expect(ordered_patterns).toEqual(["pattern2", "pattern3", "pattern1"]);
182+
});
183+
184+
it("Uses default order of 1000 for patterns without explicit order", function () {
185+
class PatternWithOrder extends BasePattern {
186+
static name = "pattern-with-order";
187+
static order = 200;
188+
}
189+
190+
class PatternWithoutOrder extends BasePattern {
191+
static name = "pattern-without-order";
192+
// No order property, should use default 1000
193+
}
194+
195+
registry.register(PatternWithOrder);
196+
registry.register(PatternWithoutOrder);
197+
198+
const pattern_names = ["pattern-without-order", "pattern-with-order"];
199+
const ordered_patterns = registry.orderPatterns(pattern_names);
200+
201+
// pattern-with-order (200) should come before pattern-without-order (1000)
202+
expect(ordered_patterns).toEqual(["pattern-with-order", "pattern-without-order"]);
203+
});
204+
205+
it("Handles patterns with same order value consistently", function () {
206+
class Pattern1 extends BasePattern {
207+
static name = "pattern1";
208+
static order = 500;
209+
}
210+
211+
class Pattern2 extends BasePattern {
212+
static name = "pattern2";
213+
static order = 500;
214+
}
215+
216+
registry.register(Pattern1);
217+
registry.register(Pattern2);
218+
219+
const pattern_names = ["pattern2", "pattern1"];
220+
const ordered_patterns = registry.orderPatterns(pattern_names);
221+
222+
// Both have same order, should maintain stable sort
223+
expect(ordered_patterns).toEqual(["pattern2", "pattern1"]);
224+
});
225+
226+
it("Ignores non-existent patterns during ordering", function () {
227+
class ExistingPattern extends BasePattern {
228+
static name = "existing";
229+
static order = 300;
230+
}
231+
232+
registry.register(ExistingPattern);
233+
234+
const pattern_names = ["non-existent", "existing", "another-non-existent"];
235+
const ordered_patterns = registry.orderPatterns(pattern_names);
236+
237+
// Only existing pattern should be returned
238+
expect(ordered_patterns).toEqual(["existing"]);
239+
});
240+
241+
it("Returns empty array when no valid patterns are provided", function () {
242+
const pattern_names = ["non-existent1", "non-existent2"];
243+
const ordered_patterns = registry.orderPatterns(pattern_names);
244+
245+
expect(ordered_patterns).toEqual([]);
246+
});
247+
248+
it("Does not modify the original patterns array", function () {
249+
class Pattern1 extends BasePattern {
250+
static name = "pattern1";
251+
static order = 500;
252+
}
253+
254+
class Pattern2 extends BasePattern {
255+
static name = "pattern2";
256+
static order = 100;
257+
}
258+
259+
registry.register(Pattern1);
260+
registry.register(Pattern2);
261+
262+
const pattern_names = ["pattern1", "pattern2"];
263+
const original_order = [...pattern_names];
264+
265+
const ordered_patterns = registry.orderPatterns(pattern_names);
266+
267+
// Original array should be unchanged
268+
expect(pattern_names).toEqual(original_order);
269+
// But result should be sorted
270+
expect(ordered_patterns).toEqual(["pattern2", "pattern1"]);
271+
});
272+
273+
it("Validates expected order values for special patterns", function () {
274+
// Test the specific order values mentioned in the commit
275+
class ValidationPattern extends BasePattern {
276+
static name = "validation";
277+
static order = 100;
278+
}
279+
280+
class CloneCodePattern extends BasePattern {
281+
static name = "clone-code";
282+
static order = 200;
283+
}
284+
285+
class RegularPattern extends BasePattern {
286+
static name = "regular";
287+
static order = 1000;
288+
}
289+
290+
registry.register(ValidationPattern);
291+
registry.register(CloneCodePattern);
292+
registry.register(RegularPattern);
293+
294+
const pattern_names = ["regular", "clone-code", "validation"];
295+
const ordered_patterns = registry.orderPatterns(pattern_names);
296+
297+
// Should be ordered: validation (100), clone-code (200), regular (1000)
298+
expect(ordered_patterns).toEqual(["validation", "clone-code", "regular"]);
299+
});
300+
301+
it("Works with mixed order values including edge cases", function () {
302+
class EarliestPattern extends BasePattern {
303+
static name = "earliest";
304+
static order = 1;
305+
}
306+
307+
class LatestPattern extends BasePattern {
308+
static name = "latest";
309+
static order = 9999;
310+
}
311+
312+
class NegativeOrderPattern extends BasePattern {
313+
static name = "negative";
314+
static order = -50;
315+
}
316+
317+
class DefaultPattern extends BasePattern {
318+
static name = "default";
319+
// Uses default order 1000
320+
}
321+
322+
registry.register(EarliestPattern);
323+
registry.register(LatestPattern);
324+
registry.register(NegativeOrderPattern);
325+
registry.register(DefaultPattern);
326+
327+
const pattern_names = ["latest", "default", "earliest", "negative"];
328+
const ordered_patterns = registry.orderPatterns(pattern_names);
329+
330+
// Should be ordered by order value: negative (-50), earliest (1), default (1000), latest (9999)
331+
expect(ordered_patterns).toEqual(["negative", "earliest", "default", "latest"]);
332+
});
333+
});
334+
153335
});

src/pat/clone-code/clone-code.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ class Pattern extends BasePattern {
1212
static trigger = ".pat-clone-code";
1313
static parser = parser;
1414

15+
// Initialize clone-code early.
16+
// We want to copy the markup before any other patterns have changed it.
17+
static order = 200;
18+
1519
async init() {
1620
// Source
1721
if (this.options.source.lastIndexOf(":", 0) === 0) {

src/pat/validation/validation.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@ class Pattern extends BasePattern {
3737
static trigger = "form.pat-validation";
3838
static parser = parser;
3939

40+
// Initialize pat-validation early.
41+
// We need to prevent other patterns from reacting to submit events if form
42+
// validation fails (e.g. pat-inject).
43+
static order = 100;
44+
4045
init() {
4146
events.add_event_listener(
4247
this.el,

0 commit comments

Comments
 (0)