Skip to content

Rendering Mode Selection & Anti-Patterns

Reference for how middag-io/framework picks between a server-rendered host page, an Inertia SPA response, and a pure JSON API response — and the anti-patterns to avoid on any of the three. Backs FW-005.

Mode selection

ModeBase classOutputTypical use
Host pagesAbstractControllerHTML via the host's own output layer (e.g. Moodle $OUTPUT + Mustache)Admin, settings, reports
SPA (Inertia)AbstractController + InertiaAdapter::render()JSON to ReactDashboards, dynamic forms
API (JSON)AbstractApiControllerPure JSONREST / AJAX / mobile

AbstractController and AbstractApiController (Http/Controller/) are the only two OSS base classes. Host pages and Inertia SPA screens share the same base — the only difference is whether the controller calls InertiaAdapter::render(); see FW-006 for the Inertia wire protocol itself (shared props, redirects, partial reloads, versioning).

A single extension can mix all three modes freely across its own controllers — nothing forces every screen in an extension onto the same mode, and using Inertia is never mandatory. A simple extension can stay entirely on host pages.

Anti-patterns

Anti-patternWhy it's a problemDo instead
Calling the host's native output renderer directly from an extension (e.g. $OUTPUT->render() in a Moodle host)Bypasses the controller boundary the framework is meant to ownReturn output through the controller/renderable, never a bare host call
Setting page metadata straight on a host global (e.g. $PAGE->set_title())Couples extension code to a host global that a different host target (standalone, WordPress) does not haveGo through the page-identity contract the framework or UI package defines
Reaching for Inertia on a simple admin-settings screenSPA overhead with no payoff when a plain host page does the jobDefault to a host-page controller; opt into Inertia only when the screen needs it
Putting non-trivial logic inside a server-side template (e.g. Mustache)Template-logic complexity is a smell regardless of which templating mechanism a host adapter usesPrepare data in the controller/renderable; keep the template a dumb view

Page-identity concerns (title, breadcrumbs) belong behind a contract the framework or UI package defines, not a bare host global — the same host-portability reasoning applies generally across the framework.

Table and widget composition

The framework itself has no Widget/ or Table/ concern — no abstract_widget or Moodle-flexible_table-wrapping table_builder exists in src/. That responsibility belongs entirely to middag-io/ui's own Table/ builder (its dense_table-style block), since a Moodle-specific table abstraction has no place in a host-agnostic framework. The rule survives one layer up: extensions never touch a host's native table class directly — enforced by the UI package's block contract rather than by this framework.

MIDDAG © 2015-2026