Skip to content
Open
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
104 changes: 104 additions & 0 deletions docs/blicca-custiomization-training/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
---
myst:
html_meta:
"description": "Half-day training on customizing the Blicca (formerly Plone Classic UI) JavaScript stack: pattern options, custom patterns, replacing core patterns, and overriding Svelte components."
"property=og:description": "Half-day training on customizing the Blicca (formerly Plone Classic UI) JavaScript stack: pattern options, custom patterns, replacing core patterns, and overriding Svelte components."
"property=og:title": "Blicca JS stack insights — how to customize Mockup"
"keywords": "Plone, Blicca, Classic UI, Mockup, Patternslib, Svelte, JavaScript, training, customization"
---

(blicca-js-stack-insights-label)=

# Blicca JS stack insights — how to customize Mockup

Level
: Beginner to intermediate

Blicca — Plone's server-rendered frontend, formerly known as Classic UI — ships a modern JavaScript stack.
It consists of Patternslib-based patterns, webpack module federation, and Svelte apps such as the content browser.
The stack is very customizable, if you know where the hooks are.

In this half-day training, we build a small add-on that overrides the stack at every level, without forking `plone.staticresources` or Mockup.
The add-on [`blicca.staticresourceoverride`](https://github.com/collective/blicca.staticresourceoverride) is the reference implementation for this training and your safety net during the exercises.

## What you will learn

After this training, you can:

1. Configure patterns site-wide via the `plone.patternoptions` registry record, without writing any JavaScript.
2. Build and register your own Patternslib pattern, and ship it as a module federation remote bundle.
3. Replace a core pattern using the pattern blacklist, while reusing the original implementation and its options.
4. Override a Svelte component of the content browser via the shared `@plone/registry`.
5. Explain how the Blicca JS stack loads, registers, and shares code, and debug it when it doesn't.

## Prerequisites

You need basic Plone knowledge, such as installing add-ons and working with GenericSetup profiles.
JavaScript basics with ES6 syntax and module imports are enough.
No Svelte experience is required.

Bring a laptop with the following software installed:

- The prerequisites from the Plone documentation, {doc}`Create a project with Cookieplone <plone:install/create-project-cookieplone>`: uv, Make, and Git
- Node.js 22 or later
- pnpm, where `corepack enable` is all it takes, as the version is pinned in the project's `package.json`
- A code editor

```{tip}
Run the setup from {ref}`blicca-setup-label` before the training.
This warms your package caches and saves conference wifi.
```

## How to use the step tags

The git history of the training repository mirrors the four training chapters.
Each tag is a working state of the add-on after the corresponding chapter:

| Tag | State after |
| -------- | ----------------------------------------------------------------- |
| `step-1` | Pattern options via the registry only, no JavaScript build yet |
| `step-2` | Own pattern `pat-blicca` plus webpack and module federation setup |
| `step-3` | Core pattern `markspeciallinks` replaced via the blacklist |
| `step-4` | Svelte `SelectedItem` override, identical to `main` |

If you fall behind, jump to the current step and continue from there:

```shell
git checkout step-2
pnpm install && pnpm run build
```

Reinstall the add-on, or reimport its profile, after switching steps, so that new registry records are applied.

## How to update your checkout

We keep improving the material until the training starts.
The `main` branch only moves forward, but the step tags are re-pointed whenever the material changes, and a plain `git fetch` does not update tags that already exist locally.
Update the source checkout in your project like this:

```shell
cd backend/sources/blicca.staticresourceoverride
git stash
git fetch --force --tags origin
git checkout main
git reset --hard origin/main
```

`git stash` saves your own changes from the exercises, so that you can restore them later with `git stash pop`.
`git reset --hard` discards everything that is not on `origin/main`.
Afterwards, run `pnpm install && pnpm run build` again, and reinstall the add-on.

```{toctree}
:caption: Chapters
:maxdepth: 1
:numbered:

intro
setup
pattern-options
own-pattern
replace-pattern
svelte-override
stretch-folder-contents
production
```
61 changes: 61 additions & 0 deletions docs/blicca-custiomization-training/intro.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
---
myst:
html_meta:
"description": "How the Blicca JavaScript stack works: Mockup, the Patternslib registry, module federation, and Svelte components."
"property=og:description": "How the Blicca JavaScript stack works: Mockup, the Patternslib registry, module federation, and Svelte components."
"property=og:title": "The Blicca JS stack in a nutshell"
"keywords": "Plone, Blicca, Mockup, Patternslib, module federation, Svelte, registry"
---

(blicca-intro-label)=

# The Blicca JS stack in a nutshell

Before we override anything, let's look at the moving parts.
This chapter is a guided tour with the browser's developer tools, not a slide deck.

## Mockup and the Plone bundle

[Mockup](https://github.com/plone/mockup) is the JavaScript package behind the Plone bundle.
The bundle is shipped by `plone.staticresources` as `++plone++static/bundle-plone/bundle.min.js`, and registered in the resource registry under the name `plone`.

## Patterns and the registry

Patterns are registered in the Patternslib registry.
During the DOM scan, each pattern initializes on the elements that match its trigger, which is a plain CSS selector such as `.pat-tinymce`.

The registry follows one important rule.
**First registration wins.**
A pattern name can only be registered once, and later attempts are ignored.
Remember this rule for {ref}`blicca-replace-pattern-label`.

## Module federation

The Plone bundle is a module federation host.
Add-on bundles are remotes.
The host initializes each remote automatically on document-ready and shares core modules with it, including the Patternslib registry, `@plone/registry`, jQuery, and Bootstrap.
This way, the add-on and the core talk to the same registry instances.

## Svelte components

Newer patterns, such as `pat-contentbrowser`, are Svelte apps.
They pull some of their sub-components from the `@plone/registry` component registry.
That is exactly where add-ons can hook in their own components, as we do in {ref}`blicca-svelte-override-label`.

Why Svelte, and not React?
Svelte compiles components to small, plain JavaScript without a virtual DOM, so the runtime footprint stays small.
That is a good fit for a server-rendered UI that is progressively enhanced with JavaScript.
Components read like HTML, CSS, and JavaScript, which keeps the learning curve flat for integrators.
And Blicca deliberately stays free of React dependencies: it does not need Volto's stack to render a widget.

## See it live

Open your browser's developer tools on any Plone page, and observe the stack at work:

- The `<body>` element carries `data-pat-*` attributes with global pattern options.
- The network tab shows the bundle and its lazily loaded chunks.
- The console logs a message for every initialized module federation bundle:

```console
Patternslib Module Federation: Loaded and initialized bundle "__patternslib_mf__bliccastaticresourceoverride".
```
105 changes: 105 additions & 0 deletions docs/blicca-custiomization-training/own-pattern.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
---
myst:
html_meta:
"description": "Build and register your own Patternslib pattern for Blicca, and ship it as a module federation remote bundle."
"property=og:description": "Build and register your own Patternslib pattern for Blicca, and ship it as a module federation remote bundle."
"property=og:title": "Your own pattern"
"keywords": "Plone, Blicca, Patternslib, BasePattern, webpack, module federation, bundle"
---

(blicca-own-pattern-label)=

# Your own pattern

In this chapter, we write a pattern from scratch, and ship it in our own bundle.
At the end, a badge appears on every element that carries the class `pat-blicca`.

## A class-based pattern

The file {file}`resources/pat-blicca/blicca.js` contains a pattern in the current Patternslib style:

```js
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("blicca");
parser.addArgument("color", "#0083be");
parser.addArgument("label", "Blicca override active");

class Pattern extends BasePattern {
static name = "blicca";
static trigger = ".pat-blicca";
static parser = parser;

init() {
const badge = document.createElement("span");
badge.textContent = `★ ${this.options.label}`;
this.el.style.outline = `2px dashed ${this.options.color}`;
this.el.append(badge);
}
}

registry.register(Pattern);
```

Three things matter here:

- The `trigger` is a plain CSS selector.
- The `Parser` declares the options, and fills them from `data-pat-blicca` attributes, including inheritance from parent elements.
- `registry.register(Pattern)` makes the registry scan the document, including content that arrives later through modals, `pat-inject`, or the folder contents view.

## The bundle around it

The webpack setup comes from `@patternslib/dev`, and the module federation plugin turns the bundle into a remote.
The entry point {file}`resources/index.js` only contains a dynamic import, exported as default:

```js
export default import("./overrides");
```

The dynamic import creates a split point.
Webpack needs it to negotiate the shared dependencies with the Plone bundle at runtime, before our code runs.
The default export matters, too.
Since Mockup 5.6.13, the module federation helper of the Plone bundle awaits the exported promise of every remote, and the Patternslib registry, since 9.11, waits for that before its initial scan of the page.
Your patterns and components are therefore registered before the first scan, no matter how fast the remote loads.

The profile registers the built bundle in {file}`profiles/default/registry/bundles.xml`:

```xml
<records
interface="plone.base.interfaces.IBundleRegistry"
prefix="plone.bundles/blicca-staticresourceoverride"
>
<value key="enabled">True</value>
<value key="jscompilation">++plone++blicca.staticresourceoverride/bundles/blicca.staticresourceoverride-remote.min.js</value>
<value key="depends">plone</value>
</records>
```

The `depends` value makes sure that the bundle loads after the host.

## Exercise

Start the watcher:

```shell
pnpm run watch
```

Edit a page, and give a paragraph the class `pat-blicca` in the TinyMCE source view.
Save and observe the badge and the dashed outline.

Now pass options through the markup:

```html
<p class="pat-blicca" data-pat-blicca="color: #d63384">With option.</p>
```

Then make it your own.
Add a new option to the parser, and use it in `init()`.

## Checkpoint

The badge and the outline appear on your paragraph.
Changing `data-pat-blicca` changes the result.
86 changes: 86 additions & 0 deletions docs/blicca-custiomization-training/pattern-options.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
---
myst:
html_meta:
"description": "Configure Blicca patterns site-wide with the plone.patternoptions registry record, without writing any JavaScript."
"property=og:description": "Configure Blicca patterns site-wide with the plone.patternoptions registry record, without writing any JavaScript."
"property=og:title": "Overrides without JavaScript"
"keywords": "Plone, Blicca, patternoptions, registry, pattern options, markspeciallinks"
---

(blicca-pattern-options-label)=

# Overrides without JavaScript

Many customizations don't need a single line of JavaScript.
In this chapter, we make external links open in a new window, site-wide, with pure XML.

## How pattern options travel

The registry record `plone.patternoptions` is a dictionary that maps a pattern name to a JSON string of options.
Plone renders each entry as a `data-pat-<name>` attribute on the `<body>` element.
The pattern's options parser walks up the DOM tree, so every pattern element inherits these options.

Plone itself uses this mechanism, and configures `pickadate` and `plone-modal` this way.

## Exercise: change options through the web

Open {menuselection}`Site Setup --> Management --> Configuration Registry` and search for `plone.patternoptions`.
Add an entry with the key `markspeciallinks` and the value:

```json
{ "external_links_open_new_window": "true" }
```

Reload the page, and inspect the `<body>` element in the developer tools.
Notice the new `data-pat-markspeciallinks` attribute.
External links in the content area now open in a new window.

## Persist it in a GenericSetup profile

A through-the-web change lives in the database only.
We persist it in the add-on's profile instead, in {file}`profiles/default/registry/patternoptions.xml`:

```xml
<?xml version="1.0" encoding="utf-8"?>
<registry>
<record name="plone.patternoptions">
<value purge="false">
<element key="markspeciallinks">{"external_links_open_new_window": "true"}</element>
</value>
</record>
</registry>
```

```{important}
Keep `purge="false"`.
Without it, the import wipes the entries of the Plone core and of other add-ons.
```

## The trigger class catch

Options alone don't run a pattern.
The trigger class must be present in the DOM.

The `pat-markspeciallinks` class on the `<body>` is only rendered when one of Plone's link settings is enabled.
That's why the profile also enables `plone.mark_special_links` in {file}`profiles/default/registry/linksettings.xml`:

```xml
<?xml version="1.0" encoding="utf-8"?>
<registry>
<record name="plone.mark_special_links">
<value>True</value>
</record>
</registry>
```

When a pattern "does nothing", first check the trigger, then the options.

## Precedence

An entry in `plone.patternoptions` overrides the `<body>` attribute that Plone's `IPatternsSettings` adapters render for the same pattern.
This includes the link settings from the control panel.
This is powerful, so be deliberate.

## Checkpoint

External links open in a new tab, and you did not run any build.
Loading
Loading