Skip to content

Decorator Panel Pages

ng-druid edited this page Jan 27, 2026 · 2 revisions

Overview

Panel Page Decorators are special panel pages that act as layout wrappers around other panel pages. They allow you to apply shared structure, navigation, styling, and reusable layout logic across multiple pages without duplicating layout code. A decorator does not represent a standalone page. Instead, it becomes the “outer shell” that wraps a normal panel page when selected by routing and selection rules.

In the editor UI, decorators appear to the user as pages that can contain “yield panes.” A yield pane indicates where the inner (content) page will appear inside the decorator. In the backend and routing system, decorators behave like specialized router rules.

Decorators allow clean separation between layout and content while keeping full flexibility in how layouts are composed.


What a Decorator Is

A decorator is a panel page whose sole purpose is to wrap other panel pages. Decorators:

• Are panel pages authored the same as any normal panel page
• Contain one or more yield panes that define where child content should render
• Are activated when the routing layer determines they match the current route
• Become the “outermost” layer for the page request
• Do not represent content by themselves
• Are not rendered unless another page is selected to fill the yield pane

A decorator may contain multiple nested panels, complex layout structures, shared navigation bars, headers, footers, or sidebars.


How Decorators Are Identified

The routing system identifies decorators in two steps:

  1. A panel page whose path begins with * is considered eligible to be a decorator.
  2. If multiple decorators match a URL, Selection Rules determine which decorator should be applied.

Decorators do not produce their own routes. They are not directly navigated to.


When Decorators Are Used

Decorators are used when:

• You want a layout to apply across many unrelated panel pages
• A route should always appear inside a particular layout wrapper
• A layout varies based on user attributes, context, parameters, or custom rules
• You want DRY layout logic that is defined once and used everywhere
• You want nested layouts (decorator wrapped by another decorator)

Because decorators operate at the routing layer, they activate before any content is rendered.


When Decorators Should Not Be Used

Decorators are not appropriate when:

• The layout variation is purely visual and does not need independent page identity
• You just need reusable blocks or widgets inside a page (use a panel or component instead)
• You need small conditional regions inside a page rather than a full layout
• You want dynamic rendering inside a normal page (use a content plugin)

Decorators are large‑scale layout tools. They should not be used for micro‑interactions.


How Decorators Work

1. Routing determines the correct decorator

When a URL is requested:

• The route matcher identifies the target panel page
• All decorators (* path pages) are evaluated
• Any decorators whose wildcard path matches the route become candidates
• Selection Rules (context, args, user ID, etc.) determine the best match
• The chosen decorator becomes the “active” page in the router

2. The real page is then resolved and passed to the decorator

The PanelPageRouterComponent receives:

• The decorator page ID
• The target content page ID

The router sets the decorator as the current page and updates the store via PageInfo.

3. The decorator yields the real content page

Inside the decorator, any pane whose plugin is “yield” becomes a child router:

• The yield plugin resolves which page should be embedded
• The decorator’s layout surrounds that child page
• The nested page renders normally inside the decorator’s yield position

Because panel pages already support nested rendering, no changes to PanelPageComponent are required.


Yielding vs. Routing

To end users, the feature is shown as “yielding content.”
To the system, yield panes function like embedded routers.

Internally the concept is treated as routing because:

• The router selects the outer decorator
• Routing state must reflect the decorator
• The final rendered page identity is updated after yield resolution
• The app must know which page is truly active (for metadata, breadcrumbs, state, SSR)

UI uses the friendly term “yield,” but internally it is implemented as a routing abstraction.


Selection Rules

Selection Rules determine which decorator (if any) should wrap a given page. Rules may use:

• Route parameters
• URL arguments
• User identity
• Global context data
• Arbitrary logic provided by rule plugins

Selection rules ensure predictable behavior even if multiple decorator pages match the same wildcard.


Nested Decorators

Decorators can wrap other decorators. Example:

• Decorator A yields Decorator B
• Decorator B yields Page X

Nesting is fully supported because each decorator simply embeds its child using the yield plugin.


Summary

Decorators provide a robust, flexible layout system by:

• Sitting above normal pages in routing
• Allowing dynamic, rule‑based selection
• Using yield panes to insert real page content
• Supporting arbitrarily deep nesting
• Keeping layout concerns separate from content concerns
• Avoiding duplication of layout structures

They are ideal for global layouts, dynamic layouts, or any situation where a page should appear inside a specialized wrapper.



DECORATOR ROUTING PIPELINE (Visual Overview)

 ┌─────────────────────────┐
 │ User navigates to URL   │
 │ e.g. /articles/123      │
 └─────────────┬───────────┘
               │
               ▼
 ┌─────────────────────────┐
 │ 1. PanelPageRouter      │
 │    collects candidates  │
 │    (normal + decorator) │
 └─────────────┬───────────┘
               │
               ▼
 ┌──────────────────────────────────────┐
 │ 2. Filter Decorator Pages            │
 │    (path starts with '*')            │
 │                                      │
 │ Example:                             │
 │   * /articles/* → Article Layout     │
 │   * /* → Global Site Wrapper         │
 └─────────────┬────────────────────────┘
               │
               ▼
 ┌──────────────────────────────────────┐
 │ 3. Apply Selection Rules             │
 │    (user, args, context, params,     │
 │     hostname, feature flags, etc.)   │
 │                                      │
 │ Result: choose ONE decorator         │
 └─────────────┬────────────────────────┘
               │
               ▼
 ┌──────────────────────────────────────┐
 │ 4. Router switches to Decorator Page │
 │    (PanelPageRouter loads decorator) │
 │                                      │
 │ PageInfo = decorator                 │
 └─────────────┬────────────────────────┘
               │
               ▼
 ┌──────────────────────────────────────┐
 │ 5. Decorator Page Layout Renders     │
 │                                      │
 │ Panels contain yield panes (slots)   │
 └─────────────┬────────────────────────┘
               │
               ▼
 ┌──────────────────────────────────────┐
 │ 6. Yield Plugin resolves inner page  │
 │    (via YieldRuleResolver)           │
 │                                      │
 │ Example: /articles/123               │
 │ → yields “ArticleContentPage”        │
 └─────────────┬────────────────────────┘
               │
               ▼
 ┌─────────────────────────────────────────┐
 │ 7. Decorator embeds inner page via:     │
 │   <classifieds-ui-panel-page            │
 │        [id]="innerPageId" nested="true" │
 │                                        │
 │ PageInfo is updated to inner page       │
 └─────────────────────────────────────────┘

Clone this wiki locally