- customer wish (via Olli, 2026-10-27): a more visual main menu in the style of rossvideo.com - image cards instead of plain link lists; for Insights a left-rail submenu whose hover swaps a visual area showing the latest news and the next events - new entry layout "spotlight" in evo_megamenu_json, built strictly on the catalog pattern so the extension stays project-neutral: `nav` is a flat rail from the linked page's live children (abstract as teaser, first page media as 640px-webp image from stage 2 - the hover swap material), `spotlight` is emitted verbatim from the new BuildSpotlightEvent (null without a listener); curated columns are ignored like on pagetree - new vitec listener ProvideInsightsSpotlight supplies the cards: the three latest news (storage pid 11, type 0 - the Solr convention; link /news/article/<path_segment> per the NewsDetail route enhancer; fal_media as processed 640px webp) and the next two events (hideonwebsite = 0, upcoming by eventstart/eventend; link = eventurl or the events page) - pagetree columns can now carry an image (stage 2+, first page media of the column's page): attach images in the page properties and the markets/solutions panels turn into image cards without further code - TCA select and label extended; README brought up to date (the pagetree layout had never been documented) with the spotlight JSON contract, reference mirror synced; frontend briefing updated - activation is editorial: switch the Insights menu entry to the spotlight layout and link it to the Insights page Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
168 lines
6.3 KiB
Markdown
168 lines
6.3 KiB
Markdown
# EVO Megamenu JSON
|
|
|
|
An editable megamenu for headless TYPO3. One content element holds the whole
|
|
menu; the extension publishes it as JSON, the front end renders it.
|
|
|
|
Project-neutral by design — nothing in here knows which site it runs on.
|
|
|
|
## What the editor gets
|
|
|
|
A plugin called **Megamenu**. Inside it:
|
|
|
|
| Level | Holds |
|
|
|---|---|
|
|
| **Entry** | one item in the main bar: label, target page, panel layout |
|
|
| **Column** | a heading (optionally a link) and its items |
|
|
| **Item** | label, link, teaser line, badge, thumbnail, “page not ready” flag |
|
|
| **Teaser card** | kicker, headline, text, link, image — for editorial panels |
|
|
| **Featured** | one highlighted block per entry, edited on the entry itself |
|
|
|
|
Five panel layouts per entry: `columns` for a curated structure menu,
|
|
`teasers` for a small link column next to cards (news, stories),
|
|
`pagetree` for a structure menu built live from the page tree,
|
|
`catalog` for a data-driven panel whose content comes from the site
|
|
package, and `spotlight` for a visual panel with a left-rail nav and live
|
|
content cards (see below).
|
|
|
|
## Installation
|
|
|
|
```bash
|
|
composer require evomedien/evo-megamenu-json
|
|
vendor/bin/typo3 extension:setup
|
|
vendor/bin/typo3 database:updateschema "*.add,*.change"
|
|
```
|
|
|
|
In the site configuration, add the set **EVO Megamenu (JSON)** and set:
|
|
|
|
| Setting | Meaning |
|
|
|---|---|
|
|
|
|
| `megamenu.contentUid` | uid of the Megamenu element to publish on every page |
|
|
| `megamenu.storagePid` | alternative: folder whose first Megamenu element is used |
|
|
|
|
Leave both uid and pid at 0 and the menu is only rendered where the element
|
|
sits — useful while building it.
|
|
|
|
## Payload
|
|
|
|
```json
|
|
"megaMenu": {
|
|
"settings": {
|
|
"stage": 3, "openOn": "hover", "closeDelay": 140, "columnsPerRow": 4,
|
|
"panelWidth": "container", "pendingBehaviour": "fallback",
|
|
"showCounts": true, "showViewAll": true, "viewAllLabel": "View all %s"
|
|
},
|
|
"entries": [
|
|
{
|
|
"title": "Products",
|
|
"link": "/products",
|
|
"layout": "columns",
|
|
"columns": [
|
|
{
|
|
"title": "IP Video Streaming",
|
|
"link": "/products/ip-video-streaming",
|
|
"items": [
|
|
{ "title": "Appliances", "link": "/products/ip-video-streaming/appliances",
|
|
"pending": true, "teaser": "MGW Diamond, Ace, Pico",
|
|
"image": { "url": "/fileadmin/...", "alternative": "", "title": "", "width": 600, "height": 400 } }
|
|
]
|
|
}
|
|
],
|
|
"featured": { "kicker": "Datasheet", "title": "MGW Diamond-H", "text": "…", "link": "/product/…", "image": { } }
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
`settings` travels with the menu on purpose: the front end reads hover
|
|
behaviour, close delay, column count and labels from the payload instead of
|
|
hard-coding them, so an editor can change them without a deploy.
|
|
|
|
### Content stages
|
|
|
|
`stage` decides how much detail is emitted — 1 structure only, 2 adds images,
|
|
teaser lines and cards, 3 adds the featured block. Later stages only **add**
|
|
keys, so a front end written for stage 1 keeps working when the stage is
|
|
raised.
|
|
|
|
### `pending`
|
|
|
|
An item whose target page does not exist yet carries `"pending": true`.
|
|
`settings.pendingBehaviour` says what to do with it: `fallback` (link to the
|
|
column heading instead), `mute` (show, not clickable) or `hide`. This keeps
|
|
a menu shippable while its deeper pages are still being built.
|
|
|
|
### Layout `catalog`
|
|
|
|
For panels built from data rather than by hand — a product catalog with
|
|
categories, groups and items, a search bar, whatever the site needs. The
|
|
extension stays project-neutral: it dispatches
|
|
`Evomedien\EvoMegamenuJson\Event\BuildCatalogEvent` for every catalog entry
|
|
and emits whatever a listener sets via `setCatalog()` verbatim as
|
|
`catalog` on the entry (`null` without a listener). The entry's teaser cards
|
|
are emitted as `teasers` as usual and serve as the panel's featured tiles;
|
|
its columns are ignored.
|
|
|
|
```php
|
|
#[AsEventListener(identifier: 'site/megamenu-catalog')]
|
|
final class ProvideCatalog
|
|
{
|
|
public function __invoke(BuildCatalogEvent $event): void
|
|
{
|
|
$event->setCatalog(['categories' => [/* … */]]);
|
|
}
|
|
}
|
|
```
|
|
|
|
`getEntry()` hands the listener the raw entry row (to tell several catalog
|
|
entries apart), `getStage()` the content stage.
|
|
|
|
### Layout `pagetree`
|
|
|
|
A structure panel that can never age against the site: the columns mirror
|
|
the live subtree of the page the entry links to - one column per child
|
|
page, its children as the items. Hidden or nav-hidden pages leave the menu
|
|
with the next cache flush. Curated columns are ignored; teasers and the
|
|
featured slot stay editorial. From stage 2 a child page with media gets its
|
|
first image as `image` on the column - attach images in the page
|
|
properties and the panel turns visual by itself.
|
|
|
|
### Layout `spotlight`
|
|
|
|
A visual panel ("rossvideo style"): `nav` is a flat left rail built live
|
|
from the linked page's children - each item with `title`, `link` and, from
|
|
stage 2, the page `abstract` as `teaser` plus the first page media as
|
|
`image`, so the front end can swap the visual area on hover. Next to it the
|
|
extension emits `spotlight`, filled by the site package: it dispatches
|
|
`Evomedien\EvoMegamenuJson\Event\BuildSpotlightEvent` per spotlight entry
|
|
and emits whatever a listener sets via `setSpotlight()` verbatim
|
|
(`null` without a listener). Curated columns are ignored.
|
|
|
|
The VITEC site package ships `ProvideInsightsSpotlight`, which supplies
|
|
|
|
```json
|
|
"spotlight": {
|
|
"news": [ {"title", "date", "link", "teaser?", "image?"} ],
|
|
"events": [ {"title", "start", "end", "venue", "booth", "city",
|
|
"country", "attendanceMode", "link", "teaser?", "image?"} ]
|
|
}
|
|
```
|
|
|
|
- `news`: the three latest news records (storage pid 11, type 0 - the Solr
|
|
convention), `link` = `/news/article/<path_segment>` per the NewsDetail
|
|
route enhancer, image from `fal_media` as processed 640px webp.
|
|
- `events`: the next two events (`hideonwebsite` = 0, upcoming by
|
|
eventstart/eventend), `link` = the record's `eventurl` or the events
|
|
page, image processed the same way.
|
|
|
|
## Notes
|
|
|
|
* Every link is a resolved URL — page, record, external or file, run through
|
|
typolink at render time.
|
|
* Translations are overlaid per record, so a menu can be localised entry by
|
|
entry.
|
|
* The renderer never throws: a broken menu returns an empty value rather than
|
|
taking the page payload down.
|
|
* Without `friendsoftypo3/headless` the element has no JSON envelope to render
|
|
into — the extension is built for headless installations.
|