5. Replacing a core pattern#
Sometimes tweaking options is not enough, and you want your own implementation of a core pattern.
In this chapter, we replace markspeciallinks, and external links get a different icon.
5.1. Why load order is not enough#
The registry rule from The Blicca JS stack in a nutshell applies. First registration wins. Mockup registers its patterns in an asynchronously loaded chunk, so the exact timing between the host and your remote is not guaranteed. Racing the host is not a strategy.
Patternslib provides an official switch instead: the pattern blacklist.
5.2. The blacklist preload#
The file static/pattern-blacklist.js is a tiny, unbuilt JavaScript file:
window.__patternslib_patterns_blacklist = (
window.__patternslib_patterns_blacklist || []
).concat(["markspeciallinks"]);
The profile registers it as its own bundle, without a depends value:
<records
interface="plone.base.interfaces.IBundleRegistry"
prefix="plone.bundles/blicca-preload"
>
<value key="enabled">True</value>
<value key="jscompilation">++plone++blicca.staticresourceoverride/pattern-blacklist.js</value>
</records>
It renders before the Plone bundle and runs synchronously, while Mockup registers its patterns in an async chunk. The original pattern therefore never gets registered.
Two things make this ordering reliable.
First, the depends field only knows “after”: an empty value means “as early as possible”, and * means “after all other bundles”, so depends="*" would be exactly the wrong choice here.
Bundles without dependencies render in the alphabetical order of their registry record names, and plone.bundles/blicca-preload sorts before plone.bundles/plone.
Second, even a synchronous script that renders after the Plone bundle still runs before Mockup’s async pattern chunk.
The blacklist takes effect at registration time, so the position among the synchronous scripts does not matter.
Note
Bundles are just files. This one needs no build at all.
5.3. The replacement#
The blacklist blocks any registration under the blocked name, including yours.
Therefore, the replacement in resources/markspeciallinks/markspeciallinks.js registers under its own name, but with the original trigger:
import $ from "jquery";
import mockupParser from "@patternslib/patternslib/src/core/mockup-parser";
import MarkSpecialLinks from "@plone/mockup/src/pat/markspeciallinks/markspeciallinks";
export default MarkSpecialLinks.extend({
name: "blicca-markspeciallinks",
trigger: ".pat-markspeciallinks",
parser: null,
async init() {
this.options = $.extend(
true,
{},
this.defaults,
mockupParser.getOptions(this.el, "markspeciallinks"),
);
this.protocol_icon_map = {
...this.protocol_icon_map,
https: "box-arrow-up-right",
http: "box-arrow-up-right",
};
return this.constructor.__super__.init.call(this);
},
});
Note the three tricks:
We extend the original class, and reuse its whole implementation.
We register under the name
blicca-markspeciallinks, with the original trigger.pat-markspeciallinks.The Mockup parser reads options based on the pattern name, and our name differs. Therefore,
init()fetches the original’s options itself withmockupParser.getOptions(), including the inheritance from the<body>.
5.4. Exercise#
Change the icon that external links get.
Pick any name from Bootstrap Icons.
As a bonus, additionally set rel="noopener noreferrer" on external links.
5.5. Checkpoint#
External links show your icon. The console confirms that the original was skipped:
registry: Pattern name markspeciallinks is blacklisted.