-
Notifications
You must be signed in to change notification settings - Fork 1
feat: JS server extension points, client-callable actions, and the JSServerExtensionInvoker SDK #687
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
romain-pm
wants to merge
39
commits into
main
Choose a base branch
from
feature/js-server-extensions
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
feat: JS server extension points, client-callable actions, and the JSServerExtensionInvoker SDK #687
Changes from all commits
Commits
Show all changes
39 commits
Select commit
Hold shift + click to select a range
d255110
docs: add ADRs for JavaScript server extension points
romain-pm 7b3ffd9
refactor: introduce AbstractServiceRegistrar shared base for JS exten…
romain-pm 28c9782
feat: JS modules can declare choicelist initializers
romain-pm 8c5eeff
feat: JS modules can declare actions
romain-pm 3c93a15
feat: JS modules can declare server-side node validators
romain-pm ece86fb
feat: typed registerRenderFilter helper + hydrogen sample for JS exte…
romain-pm 32cec5c
docs: changelog note and CND reference cross-link for JS extension po…
romain-pm 3b6c507
fix: use JahiaUser.getUsername() in the action test fixture
romain-pm 777bc31
fix: findings from live smoke testing on Jahia 8.2
romain-pm 6469897
feat: JS migrations — live-testing fixes, e2e fixtures + spec, docs +…
romain-pm e1f6ce7
refactor: rename "migrations" feature to "content patches"
romain-pm b7930af
feat: expose JSServerExtensionInvoker SDK for third-party server exte…
romain-pm f3508e5
refactor: rename registerAction to registerNodeLegacyAction
romain-pm 075a37e
feat: actions — server functions callable from client components (#588)
romain-pm 5008239
style: apply prettier across the branch's files
romain-pm 517fd5d
refactor: readability pass ahead of code review
romain-pm 9778c83
refactor: narrow the public API surface (architecture review shortlist)
romain-pm 76631bb
docs: record the servlet transport swap as ADR-0008 intended evolutio…
romain-pm 7c5be88
feat: async callbacks for all extension points + engine package hygiene
romain-pm 9685263
refactor: extract content patches to their own PR (#697)
romain-pm c0cdca7
fix: update tests/yarn.lock for the devalue dependency
romain-pm 3b02d04
fix(engine): keep the debugger working when only the SDK is exported
romain-pm 3fb73a7
test: fix the three specs the branch's new features shipped red
romain-pm f4ec857
test: give the last two specs the environment they assume
romain-pm f7115f5
test: pin the choicelist localization, not the platform's locale routing
romain-pm 2eb1a58
fix(engine): settle async results in the JSServerExtensionInvoker SDK
romain-pm 8f5d7e6
docs(engine): document the nested-invocation limit of async callbacks
romain-pm 0da4649
fix(vite-plugin): honor actions.inputGlob in the action transforms
romain-pm b9ef448
docs: clean stale references left by the content-patches split and re…
romain-pm 0dac3ee
fix(library): stop forwarding unexpected action error messages to cal…
romain-pm 179984a
fix(vite-plugin): harden the generated action stubs
romain-pm 8be4374
fix(engine): reserve 'jsAction', preserve chained choicelist values
romain-pm 39d5200
fix(samples): redirect the contact form back to the page, fix doc nits
romain-pm f7487fb
Merge branch 'feature/js-server-extensions' into fix/js-e2e-suite
romain-pm 3e7c81c
docs(library): stop promising the content locale to choicelist initia…
romain-pm af758cb
Merge branch 'feature/js-server-extensions' into fix/js-e2e-suite
romain-pm 7cffccb
docs: carry the action lessons the e2e repairs uncovered into the guide
romain-pm 20c2435
Merge branch 'fix/js-e2e-suite': fold the integration-suite repairs i…
romain-pm b4020d1
test(e2e): align genericActionTest with the masked error messages
romain-pm File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| --- | ||
| # Allowed version bumps: patch, minor, major | ||
| javascript-modules: minor | ||
| --- | ||
|
|
||
| New: actions — server functions callable from client components as plain async calls. Export a function from a `.action.ts` file, import it in an island, and call it: arguments and results are serialized automatically (devalue), with optional input validation via any Standard Schema compatible library (`action(schema, fn)`). | ||
|
|
||
| New: `registerNodeLegacyAction` registers Jahia's node-bound `.do` action endpoints from JavaScript. | ||
|
|
||
| The vite plugin's server bundle input now also includes action files (`actions.inputGlob` option, default `**/*.action.{js,ts}`); the same files are replaced by fetch stubs in the client bundle. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| --- | ||
| # Allowed version bumps: patch, minor, major | ||
| javascript-modules: minor | ||
| --- | ||
|
|
||
| JavaScript modules can now declare choicelist initializers, server-side node validators and actions — extension points that previously required a Java module. Use the new `registerChoiceListInitializer`, `registerNodeValidator`, `registerNodeLegacyAction` and `registerRenderFilter` functions from `@jahia/javascript-modules-library`. | ||
|
|
||
| Note for existing modules using `server.registry.add("render-filter", …)`: a declared `priority` is now honored (it was previously ignored and forced to 0), which may reorder such filters in the render chain. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| --- | ||
| # Allowed version bumps: patch, minor, major | ||
| javascript-modules: minor | ||
| --- | ||
|
|
||
| Java modules can now consume JavaScript-declared server extensions through the new `JSServerExtensionInvoker` OSGi service. A module can define its own extension type, let JavaScript modules contribute entries via `server.registry.add`, and invoke their callbacks from Java without depending on GraalVM APIs. This enables, for example, form-field validators written in JavaScript to run during server-side form submission processing. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,119 @@ | ||
| # Actions framework improvements plan | ||
|
|
||
| Improvements to the `feature/js-server-extensions` branch (PR #687) driven by the first external | ||
| consumer of the actions framework and the `JSServerExtensionInvoker` SDK: **formidable** (PR | ||
| Jahia/formidable#164 + the `fmdb:callServerAction` bridge built on top of it). No code here yet — | ||
| this file is the agreed plan. Items 1–3 target the current branch or an immediate follow-up; item | ||
| 4 is explicitly a separate, dedicated PR. | ||
|
|
||
| Context: formidable evaluated replacing its `formidable-form-action` registry type with the | ||
| generic actions framework and concluded (correctly) that the two solve different problems — form | ||
| actions need host-object context (action node, session, request, files) and HTTP-status failure | ||
| semantics that the devalue RPC transport doesn't carry. Formidable keeps its own registry type | ||
| consumed through the SDK, and instead **bridges** generic actions as one selectable form action | ||
| (`fmdb:callServerAction`). That bridge is what surfaces the gaps below. | ||
|
|
||
| ## 1. Action metadata (`label`, `description`, `tags`) | ||
|
|
||
| **Problem.** Registry entries of type `action` carry only `key` (`<moduleName>/<exportName>`), | ||
| `type`, `bundleKey`, and the opaque `execute`. Any consumer that wants to *enumerate* actions — | ||
| formidable's "Call Server Action" picker, a future admin UI, docs tooling — has no human label and, | ||
| worse, no way to distinguish actions meant for such reuse from internal client RPCs. Formidable | ||
| defined the consuming convention already (entries tagged `form-action` are listed/invoked; see | ||
| `formidable-elements/src/server/actions/callServerAction.server.ts`), but nothing can produce the | ||
| tag yet. | ||
|
|
||
| **Design.** | ||
|
|
||
| - Metadata is attached to the exported function itself, so it survives the vite plugin's lexical | ||
| export discovery without new syntax: | ||
| - `action(schema, fn, meta?)` — third optional argument on the safe wrapper. | ||
| - `withActionMeta(fn, meta)` — helper for raw (schema-less) exports. | ||
| - Both store the object under a well-known symbol/property (e.g. `fn[ACTION_META]`). | ||
| - `registerActionsModule` reads the property and spreads it as **flat fields** on the registry | ||
| entry: `label: string`, `description?: string`, `tags?: string[]`. Flat because `Registry.find` | ||
| filters on top-level fields only, and because consumers read entries as plain maps through the | ||
| SDK (`forEach`) where nested structures add noise. `execute` semantics are unchanged. | ||
| - Reserved/known tag values are documented, starting with `form-action` (formidable's contract: | ||
| the action accepts a single `{formId, locale, parameters}` argument; return value ignored). | ||
| Tags are otherwise free-form. | ||
| - Guard: metadata keys are whitelisted (`label`, `description`, `tags`) so authors can't shadow | ||
| `execute`/`key`/`type`/`bundleKey`. | ||
|
|
||
| **Touch points.** `javascript-modules-library/src/framework/actions/action.ts`, | ||
| `registerActionsModule.ts`, types, the "Actions" guide, one Cypress assertion enumerating the | ||
| test-module action with metadata. No engine-java change. | ||
|
|
||
| ## 2. SDK `Invoker.call` settles thenables | ||
|
|
||
| **Problem.** `JSServerExtensionInvoker.Invoker.call` converts the JS return value to plain Java | ||
| immediately, so a JS extension returning a promise is unusable through the SDK. The engine solved | ||
| this for its own endpoint with `JSPromise.settle` (microtask drain on host return), but SDK | ||
| consumers can't reach it. Formidable had to build a two-phase workaround (`execute` returns | ||
| `{pending: true}`; a second `collect` call, made after the first host return drained the microtask | ||
| queue, reads the captured outcome — see `JsFormActionDispatcher` + `registerFormAction`). | ||
|
|
||
| **Design.** | ||
|
|
||
| - In `JSServerExtensionInvokerImpl`, before `convert(...)`: if the result is thenable, settle it | ||
| with `JSPromise.settle` (move/share the class — it currently lives in `actions/`); convert the | ||
| fulfilled value, or throw a `RuntimeException` carrying the rejection reason. Never-settling | ||
| promises (timer/I-O-dependent) fail with the same explicit message as the endpoint. | ||
| - Javadoc the contract on `Invoker.call`; add GraalJS-backed unit tests mirroring the existing | ||
| `JSPromise` tests, but through the SDK surface. | ||
| - Backward compatible: sync results behave exactly as before. | ||
|
|
||
| **Follow-up in formidable once released:** delete the `pending`/`collect` protocol on both sides | ||
| (TS adapter + dispatcher) — the wrapper just returns the handler's promise. | ||
|
|
||
| ## 3. Export the registrar SPI | ||
|
|
||
| **Problem.** `Registrar` is already a whiteboard (`JavascriptModuleListener` binds | ||
| `Registrar` services with dynamic/multiple cardinality), so third-party bundles *could* plug into | ||
| JS-module lifecycle — but the `...engine.registrars` package is not exported; only `...engine.sdk` | ||
| is. Consumers that want to publish JS registry entries as their own OSGi services (the | ||
| `AbstractServiceRegistrar` pattern used by choicelists/render-filters/legacy actions) must instead | ||
| re-resolve entries per call, and re-implement matching/fallback logic. | ||
|
|
||
| **Design.** | ||
|
|
||
| - Promote a consumer-facing SPI into the `sdk` package (keeping the internal registrars where they | ||
| are): `JSExtensionRegistrar` (the `register(Bundle)`/`unregister(Bundle)` pair) and an exported | ||
| abstract base equivalent to `AbstractServiceRegistrar` (service class + registry type + | ||
| `createBridge`), documented with the same invariants (bridges re-resolve entries inside | ||
| `doWithContext` per invocation; per-entry failure isolation). | ||
| - Internal registrars migrate to the exported base at leisure; no behavior change. | ||
|
|
||
| **Payoff for formidable:** `JsFormActionDispatcher`, the optional-reference plumbing in | ||
| `FormSubmitServlet`, and the Java-vs-JS precedence special-casing in `FormSubmissionPipeline` | ||
| collapse into one registrar that publishes each `formidable-form-action` entry as a regular | ||
| `FormAction` OSGi service. | ||
|
|
||
| ## 4. Java-native `invokeAction` API — separate, dedicated PR (after 1–3) | ||
|
|
||
| **Problem.** Generic actions can only be invoked first-class over HTTP. Java code (or SDK | ||
| consumers like formidable's bridge, currently doing this from TS instead) must hand-build a | ||
| devalue-serialized args string and parse the devalue result — the wire format leaks into every | ||
| caller. | ||
|
|
||
| **Design sketch (to refine in its own PR).** | ||
|
|
||
| - New SDK method, e.g. `ActionInvoker.invoke(String name, Object... args)`: | ||
| - resolves the `action` entry, serializes args and deserializes the result **by delegating to a | ||
| JS-side adapter** (the library owns devalue, per ADR-0008's "the JS adapter owns all | ||
| serialization" — Java must not re-implement devalue), | ||
| - settles promises (depends on item 2), | ||
| - maps `{message, issues?}` rejections to a typed `ActionInvocationException`. | ||
| - Open questions for that PR: which Java types are devalue-encodable (align with island props), | ||
| whether host objects should be allowed as args (probably not — keeps the "actions are | ||
| context-free" model honest), and whether invocation should honor `tags` (e.g. refuse untagged | ||
| internal actions when called by third parties). | ||
|
|
||
| ## Suggested sequencing | ||
|
|
||
| 1. Item 1 (metadata) — small, unblocks formidable's picker end-to-end; can land in PR #687 or as | ||
| the first follow-up. | ||
| 2. Item 2 (promise settling) — engine-java only, well-testable; unblocks async simplification for | ||
| every SDK consumer. | ||
| 3. Item 3 (registrar SPI) — API-design-heavy; needs maintainer alignment on the exported surface. | ||
| 4. Item 4 (invokeAction) — dedicated PR after the above, since it builds on items 1–2. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,89 @@ | ||
| --- | ||
| page: | ||
| $path: /sites/academy/home/documentation/jahia/8_2/developer/javascript-module-development/legacy-node-actions | ||
| jcr:title: Declaring Legacy Node Actions | ||
| j:templateName: documentation | ||
| content: | ||
| $subpath: document-area/content | ||
| --- | ||
|
|
||
| Legacy node actions are HTTP endpoints bound to content nodes: appending `.<actionName>.do` to a node URL invokes the action against that node. They expose the classic Jahia `org.jahia.bin.Action` mechanism to JavaScript modules, for parity with Java modules — useful for plain HTML form submissions and for interoperating with existing `.do`-based integrations. | ||
|
|
||
| > To call server code from client components (islands), prefer [actions](../7-actions/README.md): typed, client-callable functions with automatic serialization. | ||
|
|
||
| ## Declaring a legacy node action | ||
|
|
||
| Call `registerNodeLegacyAction` at the top level of a server file (it registers the action as a side effect at module startup, like `jahiaComponent`): | ||
|
|
||
| ```ts | ||
| import { registerNodeLegacyAction } from "@jahia/javascript-modules-library"; | ||
|
|
||
| registerNodeLegacyAction( | ||
| { name: "myModuleGreet", requiredMethods: ["GET"], requireAuthenticatedUser: false }, | ||
| ({ parameters, resource }) => ({ | ||
| json: { | ||
| greeting: `Hello ${parameters.who?.[0] ?? "world"}`, | ||
| path: resource.getNode().getPath(), | ||
| }, | ||
| }), | ||
| ); | ||
| ``` | ||
|
|
||
| The action is then reachable on any node URL: | ||
|
|
||
| ``` | ||
| GET /cms/render/live/en/sites/mysite/home.myModuleGreet.do?who=Jahia | ||
| Accept: application/json | ||
| → 200 {"greeting": "Hello Jahia", "path": "/sites/mysite/home"} | ||
| ``` | ||
|
|
||
| Note that Jahia's render servlet only writes the JSON body when the request declares it accepts JSON — send an `Accept: application/json` header (browsers submitting forms get the redirect/status behavior instead). | ||
|
|
||
| ## Declaration options | ||
|
|
||
| | Option | Description | | ||
| | -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | | ||
| | `name` | The URL-visible action name. Names are platform-wide (shared with Java modules, last registration wins) — prefix them with your module name. | | ||
| | `requiredMethods` | Allowed HTTP methods, e.g. `["POST"]`. Defaults to Jahia's default (GET and POST). | | ||
| | `requireAuthenticatedUser` | Defaults to **`true`** (Jahia's default): guests get a 401. Set to `false` explicitly for public actions. | | ||
| | `requiredPermission` | Permission required on the target node, e.g. `"jcr:write"`. | | ||
| | `requiredWorkspace` | Restrict to `"default"` or `"live"`. | | ||
|
|
||
| ## The handler | ||
|
|
||
| The handler receives a context object: | ||
|
|
||
| - `parameters` — merged query-string and form parameters, as `Record<string, string[]>`, | ||
| - `resource` / `renderContext` / `session` — the target resource, render context and user JCR session, | ||
| - `request` — escape hatch: the raw `HttpServletRequest` (headers, cookies, body), | ||
| - `urlResolver` — escape hatch: the Jahia URL resolver. | ||
|
|
||
| And returns (possibly asynchronously — `async` handlers are supported, limited to microtask-based work: the server runtime has no timers or async I/O): | ||
|
|
||
| - `json` — an object serialized as the JSON response body, | ||
| - `statusCode` — HTTP status, default 200, | ||
| - `redirect` (+ `absoluteRedirect`) — redirect the client instead of returning a body. | ||
|
|
||
| Returning nothing sends an empty 200. | ||
|
|
||
| Do not combine `redirect` with a `statusCode`: the platform picks the redirect status itself, and a | ||
| 3xx `statusCode` makes Jahia answer `sendError()` instead of redirecting. For actions answering a | ||
| plain browser form POST, always return a `redirect` — a bare `json` result leaves the visitor on a | ||
| blank page (the JSON body is only written for requests that ask for JSON). | ||
|
|
||
| ## CSRF protection for POST actions | ||
|
|
||
| POST, PUT and DELETE requests to `.do` URLs — and GET requests made with an authenticated session — are blocked by Jahia's CSRF guard unless the URL is whitelisted. **This is your module's responsibility**: ship an OSGi configuration file in your module's `settings/configurations/` folder: | ||
|
|
||
| ```properties | ||
| # settings/configurations/org.jahia.modules.jahiacsrfguard-mymodule.cfg | ||
| whitelist = *.myModuleSubmit.do,*.myModuleOther.do | ||
| ``` | ||
|
|
||
| Whitelisting disables CSRF protection for those URLs, so only do it for actions designed to be called without a CSRF token (e.g. public form submissions), and keep the patterns as narrow as possible. Without this file, POST calls to your action fail with a 403. | ||
|
|
||
| ## Good to know | ||
|
|
||
| - **Keep handlers fast and non-blocking** — they run on a request thread. | ||
| - **Content modifications**: use the provided `session` to read/write JCR content as the calling user; standard permissions apply, plus `requiredPermission` if you set it. | ||
| - **Errors**: an exception thrown by the handler results in an error response; validate input and return explicit `statusCode` values (e.g. 400) for expected failures. |
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This implementation seems a bit underwhelming, there's not much we can do in there:
What goals do we have with choicelist initializers? |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| --- | ||
| page: | ||
| $path: /sites/academy/home/documentation/jahia/8_2/developer/javascript-module-development/choicelist-initializers | ||
| jcr:title: Declaring Choicelist Initializers | ||
| j:templateName: documentation | ||
| content: | ||
| $subpath: document-area/content | ||
| --- | ||
|
|
||
| Choicelist initializers populate the dropdown lists offered to editors in Content Editor. Out of the box, Jahia provides initializers such as `resourceBundle` or `nodes`; with JavaScript modules you can declare your own initializers in JavaScript, without writing a Java module. | ||
|
|
||
| ## Declaring an initializer | ||
|
|
||
| Call `registerChoiceListInitializer` at the top level of a server file (it registers the initializer as a side effect at module startup, like `jahiaComponent`): | ||
|
|
||
| ```ts | ||
| import { registerChoiceListInitializer } from "@jahia/javascript-modules-library"; | ||
|
|
||
| registerChoiceListInitializer({ key: "myModuleColors" }, ({ locale }) => [ | ||
| { label: locale.startsWith("fr") ? "Rouge" : "Red", value: "red" }, | ||
| { label: locale.startsWith("fr") ? "Vert" : "Green", value: "green" }, | ||
| ]); | ||
| ``` | ||
|
|
||
| Then reference the initializer's key from a property definition in your CND file: | ||
|
|
||
| ```cnd | ||
| [mymodule:myComponent] > jnt:content, mix:title | ||
| - color (string, choicelist[myModuleColors]) | ||
| ``` | ||
|
|
||
| The callback returns the list of choices as `{ label, value, properties? }` objects: | ||
|
|
||
| - `label` is the text shown to the editor, | ||
| - `value` is the string persisted in the JCR, | ||
| - `properties` is optional metadata interpreted by the editing UI, e.g. `{ defaultProperty: true }` to preselect a choice, or `{ image: "/path.png" }` to display a thumbnail. | ||
|
|
||
| ## The initializer context | ||
|
|
||
| The callback receives a context object: | ||
|
|
||
| | Property | Description | | ||
| | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | ||
| | `param` | The parameter from the CND declaration: `choicelist[myModuleColors='myParam']` passes `"myParam"`. Empty string when absent. | | ||
| | `locale` | BCP-47 language tag to localize labels for (e.g. `"en"`, `"fr"`). Whether the platform forwards the content language or the editor's UI language varies across Jahia versions. | | ||
| | `values` | Choices accumulated by previous initializers when several are chained in the CND declaration (e.g. `choicelist[resourceBundle,myModuleColors]`). Include them in your result to keep them. | | ||
| | `node` | The node being edited, when it exists (it does not on creation forms). | | ||
| | `java` | Escape hatch: the raw Java objects received by the underlying `ModuleChoiceListInitializer` — `propertyDefinition` (`ExtendedPropertyDefinition`), `locale` (`java.util.Locale`), `values`, `context`. | | ||
|
|
||
| For example, an initializer that lists values differently per property and honors a parameter: | ||
|
|
||
| ```ts | ||
| registerChoiceListInitializer({ key: "myModuleSizes" }, ({ param, values, java }) => { | ||
| const sizes = [...values, { label: "Small", value: "s" }, { label: "Medium", value: "m" }]; | ||
| if (param === "extended") { | ||
| sizes.push({ label: `Large (${java.propertyDefinition.getName()})`, value: "l" }); | ||
| } | ||
| return sizes; | ||
| }); | ||
| ``` | ||
|
|
||
| ## Good to know | ||
|
|
||
| - **Keys are platform-wide.** Initializer keys live in a single namespace shared with Java modules; the last registration wins. Prefix your keys with your module name (`myModuleColors`, not `colors`). | ||
| - **Keep callbacks fast.** The callback runs synchronously every time an editor form displays the choicelist. | ||
| - **Labels are your responsibility.** Unlike `choicelist[resourceBundle]`, labels are not resolved from resource bundles automatically — return localized labels using the `locale` from the context (you can use your module's i18n setup or any custom logic). |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not a big fan of releasing something already legacy on day one
Maybe we should start by designing "Jahia as a framework" and offer entrypoints for both Java and JS