Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/core/basepattern.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ class BasePattern {
static name; // name of pattern used in Registry.
static trigger; // A CSS selector to match elements that should trigger the pattern instantiation.
static parser; // Options parser.
static order = 1000; // Registry pattern sorting.

// Parser options
parser_group_options = true;
Expand Down
63 changes: 37 additions & 26 deletions src/core/basepattern.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,42 +5,53 @@ A Base pattern for creating scoped patterns.
Each instance of a pattern has its own local scope.
A new instance is created for each DOM element on which a pattern applies.

## Pattern initialization order

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

## Usage:

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

```javascript
import { BasePattern } from "@patternslib/patternslib/src/core/basepattern";
import Parser from "@patternslib/patternslib/src/core/parser";
import registry from "@patternslib/patternslib/src/core/registry";

import { BasePattern } from "@patternslib/patternslib/src/core/basepattern";
import Parser from "@patternslib/patternslib/src/core/parser";
import registry from "@patternslib/patternslib/src/core/registry";

export const parser = new Parser("test-pattern");
parser.addArgument("example-option", "Stranger");
export const parser = new Parser("test-pattern");
parser.addArgument("example-option", "Stranger");

class Pattern extends BasePattern {
static name = "test-pattern";
static trigger = ".pat-test-pattern";
static parser = parser;
class Pattern extends BasePattern {
static name = "test-pattern";
static trigger = ".pat-test-pattern";
static parser = parser;
static order = 1000; // Optional. Leave out for the default value of 1000.

async init() {
import("./test-pattern.scss");
async init() {
import("./test-pattern.scss");

// Try to avoid jQuery, but here is how to import it.
// eslint-disable-next-line no-unused-vars
const $ = (await import("jquery")).default;
// Try to avoid jQuery, but here is how to import it.
// eslint-disable-next-line no-unused-vars
const $ = (await import("jquery")).default;

// The options are automatically created, if parser is defined.
const example_option = this.options.exampleOption;
this.el.innerHTML = `
<p>hello, ${example_option}, this is pattern ${this.name} speaking.</p>
`;
}
// The options are automatically created, if parser is defined.
const example_option = this.options.exampleOption;
this.el.innerHTML = `
<p>hello, ${example_option}, this is pattern ${this.name} speaking.</p>
`;
}
}

// Register Pattern class in the global pattern registry
registry.register(Pattern);

// Make it available
export default Pattern;
// Register Pattern class in the global pattern registry
registry.register(Pattern);

// Make it available
export default Pattern;
```
31 changes: 19 additions & 12 deletions src/core/registry.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,19 +133,26 @@ const registry = {
},

orderPatterns(patterns) {
// Always add pat-validation as first pattern, so that it can prevent
// other patterns from reacting to submit events if form validation
// fails.
if (patterns.includes("validation")) {
patterns.splice(patterns.indexOf("validation"), 1);
patterns.unshift("validation");
}
// Add clone-code to the very beginning - we want to copy the markup
// before any other patterns changed the markup.
if (patterns.includes("clone-code")) {
patterns.splice(patterns.indexOf("clone-code"), 1);
patterns.unshift("clone-code");
patterns = [...patterns];
const sorted_patterns = [];

// Sort patterns
for (const name of patterns) {
const pattern = registry.patterns[name];
if (!pattern) {
// No registered pattern. Ignore that.
continue;
}
sorted_patterns.push([name, pattern?.order || 1000]);
}
// Sorting. Sort for the value in the sorted_patterns map.
patterns = sorted_patterns
.toSorted((a, b) => {
return a[1] - b[1];
})
.map((item) => {
return item[0];
});

return patterns;
},
Expand Down
182 changes: 182 additions & 0 deletions src/core/registry.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Base from "./base";
import BasePattern from "./basepattern";
import registry from "./registry";

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

describe("orderPatterns", function () {
it("Orders patterns by their order property with lower values first", function () {
// Create test patterns with different order values
class Pattern1 extends BasePattern {
static name = "pattern1";
static order = 500;
}

class Pattern2 extends BasePattern {
static name = "pattern2";
static order = 100;
}

class Pattern3 extends BasePattern {
static name = "pattern3";
static order = 300;
}

// Register patterns
registry.register(Pattern1);
registry.register(Pattern2);
registry.register(Pattern3);

const pattern_names = ["pattern1", "pattern2", "pattern3"];
const ordered_patterns = registry.orderPatterns(pattern_names);

// Should be ordered by order property: pattern2 (100), pattern3 (300), pattern1 (500)
expect(ordered_patterns).toEqual(["pattern2", "pattern3", "pattern1"]);
});

it("Uses default order of 1000 for patterns without explicit order", function () {
class PatternWithOrder extends BasePattern {
static name = "pattern-with-order";
static order = 200;
}

class PatternWithoutOrder extends BasePattern {
static name = "pattern-without-order";
// No order property, should use default 1000
}

registry.register(PatternWithOrder);
registry.register(PatternWithoutOrder);

const pattern_names = ["pattern-without-order", "pattern-with-order"];
const ordered_patterns = registry.orderPatterns(pattern_names);

// pattern-with-order (200) should come before pattern-without-order (1000)
expect(ordered_patterns).toEqual(["pattern-with-order", "pattern-without-order"]);
});

it("Handles patterns with same order value consistently", function () {
class Pattern1 extends BasePattern {
static name = "pattern1";
static order = 500;
}

class Pattern2 extends BasePattern {
static name = "pattern2";
static order = 500;
}

registry.register(Pattern1);
registry.register(Pattern2);

const pattern_names = ["pattern2", "pattern1"];
const ordered_patterns = registry.orderPatterns(pattern_names);

// Both have same order, should maintain stable sort
expect(ordered_patterns).toEqual(["pattern2", "pattern1"]);
});

it("Ignores non-existent patterns during ordering", function () {
class ExistingPattern extends BasePattern {
static name = "existing";
static order = 300;
}

registry.register(ExistingPattern);

const pattern_names = ["non-existent", "existing", "another-non-existent"];
const ordered_patterns = registry.orderPatterns(pattern_names);

// Only existing pattern should be returned
expect(ordered_patterns).toEqual(["existing"]);
});

it("Returns empty array when no valid patterns are provided", function () {
const pattern_names = ["non-existent1", "non-existent2"];
const ordered_patterns = registry.orderPatterns(pattern_names);

expect(ordered_patterns).toEqual([]);
});

it("Does not modify the original patterns array", function () {
class Pattern1 extends BasePattern {
static name = "pattern1";
static order = 500;
}

class Pattern2 extends BasePattern {
static name = "pattern2";
static order = 100;
}

registry.register(Pattern1);
registry.register(Pattern2);

const pattern_names = ["pattern1", "pattern2"];
const original_order = [...pattern_names];

const ordered_patterns = registry.orderPatterns(pattern_names);

// Original array should be unchanged
expect(pattern_names).toEqual(original_order);
// But result should be sorted
expect(ordered_patterns).toEqual(["pattern2", "pattern1"]);
});

it("Validates expected order values for special patterns", function () {
// Test the specific order values mentioned in the commit
class ValidationPattern extends BasePattern {
static name = "validation";
static order = 100;
}

class CloneCodePattern extends BasePattern {
static name = "clone-code";
static order = 200;
}

class RegularPattern extends BasePattern {
static name = "regular";
static order = 1000;
}

registry.register(ValidationPattern);
registry.register(CloneCodePattern);
registry.register(RegularPattern);

const pattern_names = ["regular", "clone-code", "validation"];
const ordered_patterns = registry.orderPatterns(pattern_names);

// Should be ordered: validation (100), clone-code (200), regular (1000)
expect(ordered_patterns).toEqual(["validation", "clone-code", "regular"]);
});

it("Works with mixed order values including edge cases", function () {
class EarliestPattern extends BasePattern {
static name = "earliest";
static order = 1;
}

class LatestPattern extends BasePattern {
static name = "latest";
static order = 9999;
}

class NegativeOrderPattern extends BasePattern {
static name = "negative";
static order = -50;
}

class DefaultPattern extends BasePattern {
static name = "default";
// Uses default order 1000
}

registry.register(EarliestPattern);
registry.register(LatestPattern);
registry.register(NegativeOrderPattern);
registry.register(DefaultPattern);

const pattern_names = ["latest", "default", "earliest", "negative"];
const ordered_patterns = registry.orderPatterns(pattern_names);

// Should be ordered by order value: negative (-50), earliest (1), default (1000), latest (9999)
expect(ordered_patterns).toEqual(["negative", "earliest", "default", "latest"]);
});
});

});
4 changes: 4 additions & 0 deletions src/pat/clone-code/clone-code.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ class Pattern extends BasePattern {
static trigger = ".pat-clone-code";
static parser = parser;

// Initialize clone-code early.
// We want to copy the markup before any other patterns have changed it.
static order = 200;

async init() {
// Source
if (this.options.source.lastIndexOf(":", 0) === 0) {
Expand Down
5 changes: 5 additions & 0 deletions src/pat/validation/validation.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ class Pattern extends BasePattern {
static trigger = "form.pat-validation";
static parser = parser;

// Initialize pat-validation early.
// We need to prevent other patterns from reacting to submit events if form
// validation fails (e.g. pat-inject).
static order = 100;

init() {
events.add_event_listener(
this.el,
Expand Down
Loading