Adding a block type
A block type exists in four places. Miss one and the failure is silent, in a different way each time — which is what makes this expensive to discover by trial and error.
| # | Place | What it decides | Failure if missing |
|---|---|---|---|
| 1 | Resources/bweb/schema/blocks/<type>.json | Properties, sections, events, validation | resolve() returns {error: "Schema introuvable pour le type: <type>"}, so the panel has no field and validate() refuses every write |
| 2 | Resources/bweb/bspk/<type>.html | The HTML actually rendered | The block renders as an empty <bspk> |
| 3 | BSPK_SET_STORAGE_WEBPARAM (vc_DragableObject) | Presence in the palette, icon, category, tree icon | Nobody can drop it — and see the security note below |
| 4 | Misc/lang/<lang>/BSPK.json | Palette label, section and field labels, help texts | Raw keys (BSPK_myBlock) on screen |
Then run BSPK_REFRESH_STORAGE. Nothing above is read from disk at render time.
1. The schema
SchemaEngine.resolve(<type>) merges, in this order: the fragments listed in includes (from schema/shared.json), the css fragment (schema/css.json, which also injects stylePresets), then the type's own properties — the last one wins. The declaration format of a single property is covered in The properties contract.
meta carries category, label, icon, draggable, internal, childrenAllowed and render. All 28 current types use render: "template"; no other value is implemented.
meta.category is not what the palette shows. The schemas use three values (container, form, others); the palette uses six, from a different list (point 3).
2. The template
BSPK_LoadTpl reads every file of Resources/bweb/bspk/ into Storage.vo_Tpl.bspk, keyed by Lowercase(File.name) — extension dropped. The renderer looks the template up by Lowercase($WebContent.type) (in GG_PROCESS_COMPONENTS).
So the file name is the type, case-insensitively. qrCodeScanner.html is found as qrcodescanner; slider-part1.html matches no type and is dead weight.
The template is then run through PROCESS 4D TAGS with three parameters:
| In the template | Is |
|---|---|
$1 | $WebContent.uuidKey — the block's uuid, for unique ids |
$2 | The BSPK_WEB_CONTENT entity (a copy unless updateFromObject is set) |
$3 | The renderer's ad-hoc object — where a type can hand itself extra values (the captcha puts vt_CaptchaText there) |
Every template opens with the same preamble: take $2.properties when $2 is an object, otherwise BASE64-decode and parse $2. That second branch is how one template calls another directly — captcha.html renders a fieldText that way.
A render error is caught and turned into a toast naming the block, the type and the uuid, so a broken template does not take the page down. See 4D in BWEB.
3. The palette, and why it is not cosmetic
BSPK_SET_STORAGE_WEBPARAM pushes one object per type into Storage.vo_WebParam.vc_DragableObject:
{ "Xliff": "BSPK_myBlock", "dataItem": "myBlock", "icon": "bi-...",
"category": "form", "children": 0 }
Six categories: container, content, form, data, navigation, and model (the slot zone, filtered out unless GG_IS_MODEL_EDITOR, in dev-pannel.html).
Three constraints apply to that list:
- Entries of one category must stay contiguous:
dev-pannel.htmlopens a new grid when the category changes and closes the last one by index. - The string
containermust not change:legacy-port.jsuses it to know which blocks accept children. - The icon is reused by the block tree (
GG_MODEL_TREE_NODE,navItem.html,navGroup.html), so a type absent from the palette has no icon there either.
The security consequence. GG_WEB_SOCKET_SECURITY_CHECK derives the list of input block types from this very collection:
vc_DragableObject.query("(category = form AND dataItem # button) OR (dataItem = listbox)")
Trap: a new input block filed under any other category is therefore not treated as a field by the POST check — its value is stripped from
vt_FieldValuewith no message. That is whyfieldCaptureandqrCodeScannersit informalthough the schemas file them underothers.
4. Translations
The palette label is the Xliff key; the rest come from the schema (meta.label, section labels, each property's label and explanatoryText). All of them are keys resolved by BSPK_Translate, which returns the key itself when it is missing — so a forgotten key shows as BSPK_myBlock rather than raising an error. Add it to every language file; the editing rules are in Translations (never use JSON.stringify on those files).
5. Refresh, in this order
Schemas and templates live in Storage, populated by BSPK_LoadTpl and refreshed by BSPK_REFRESH_STORAGE. They are no longer read from disk on every resolve().
- Changed a schema or a template:
BSPK_REFRESH_STORAGEis enough. - Changed a host
.4dm:RELOAD PROJECT, thenBSPK_REFRESH_STORAGE. - Changed the component (
BSPK_SET_STORAGE_WEBPARAMincluded): restart 4D.
The order matters when both changed: refreshing first rescans the class still in memory. See Reload or restart.
Trap: a malformed schema JSON is swallowed twice — once by the
TryinBSPK_LoadTpl, once by theTryinSchemaEngine._loadJson. The type then resolves toNulland the panel is simply empty. Validate the JSON before blaming the panel.
What creating a block does at runtime
Dropping from the palette posts addItem: "<type>" to WebFormController.moduleSave. It names the block with getUniqueBlockName, calls GG_INIT_CSS_PROPERTIES, then applies per-type CSS defaults for accordion, slider and wrapper. Nothing else is initialised: a new type starts with empty properties, so every template must tolerate an absent key (see The 4D language).
See also
- The properties contract — declaring one property, and what
validate()refuses - The dev panel — how the panel builds its fields from the schema
- Events and the POST — declaring
eventsand receiving the POST - Styling: the CSS cube and Tailwind — the
cssPropertiescube and Tailwind extraction

