Parallel: pages-system.ko.md
This document explains how to register page handlers and wire them to the EstreUI page system through the PagesProvider pattern and EstreUiCustomPageManager.
For the page handler lifecycle itself (onBring, onShow, etc.), see page-handlers.en.md.
EstreUI's page system has three layers of abstraction:
EstreUiPageManager ← framework-level, manages all PIDs, handles bringPage()
↑
EstreUiCustomPageManager ← subclass point for apps, bridges alias → PID
↑
Your app page manager ← adds auth gating, post-login queuing, etc.
The PagesProvider is a class that bundles three things:
- An alias → PID map (
static get pages) - An auth policy object (
static get operator) - Per-page handler classes (instance fields)
Maps human-friendly page names to PID strings:
class MyPagesProvider {
static get pages() { return {
"home": "&m=home",
"home_root": "&m=home#root",
"profile": "&m=profile",
"profile_edit": "&m=profile#edit@main",
"settings": "&m=menu#settings",
"login": "&b=login",
"signup": "&b=login#sign_up",
"signup_step0": "&b=login#sign_up@sign_up%0",
"signup_step1": "&b=login#sign_up@sign_up%1",
"signup_step2": "&b=login#sign_up@sign_up%2",
"image_viewer": "&b=imageViewer^#root@main",
"search": "&o=customToast#search^",
"": "",
}; }Key points:
- Each alias is a short, memorable name; each value is a full PID.
- The map is passed to
EstreUiCustomPageManager.init()which stores it aspageManager.extPidMap. - When
bringPage("*alias")is called, the*prefix triggers a lookup inextPidMap. EstreUiCustomPageManager.bringPage(id)automatically prepends*— so your app callsmyPageManager.bringPage("home"), not"*home".
Defines which pages are public (accessible without authentication):
static get operator() { return {
get publicPages() { return [
"login",
"signup",
"signup_step0",
"signup_step1",
"signup_step2",
]; },
get nestedOnUnauthed() { return {
// "restricted_page": "fallback_page",
}; },
requiredAuth(pn) {
if (this.publicPages.indexOf(pn) > -1) return false;
else if (pn in this.nestedOnUnauthed) return this.nestedOnUnauthed[pn];
else return true;
}
}; }| Field | Purpose |
|---|---|
publicPages |
Array of aliases that don't require auth. |
nestedOnUnauthed |
Map of alias → fallback alias. If the user isn't authed, redirect to the fallback instead of blocking. |
requiredAuth(pn) |
Returns false (public), true (requires auth), or a string (redirect alias). |
Each instance field is named after a page alias and assigned an EstrePageHandler subclass:
// ... constructor, init(), etc.
"home" = class extends EstrePageHandler {
onBring(handle) {
// first-time setup
}
onShow(handle) {
// runs every time the page becomes visible
}
}
"profile" = class extends EstrePageHandler {
$nameField;
onBring(handle) {
this.$nameField = handle.$host.find(".name");
}
onOpen(handle, data) {
if (data?.userName) this.$nameField.text(data.userName);
}
}
"settings" = class extends EstrePageHandler {
onShow(handle) {
// refresh settings UI
}
}Handler classes are defined inline as instance fields of the provider. The field name must match an alias key from static get pages. When EstreUiCustomPageManager.init() is called, each handler class is registered to the PID that corresponds to its alias.
The provider takes references to the app's managers:
#pageManager = null;
#sessionManager = null;
constructor(pageManager, sessionManager) {
this.#pageManager = pageManager;
this.#sessionManager = sessionManager;
}
async init() {
// load any async settings before handlers fire
return this;
}The init() method is awaited before handlers are registered, so you can load configuration or stored preferences that handlers will need.
EstreUiCustomPageManager is the subclass point provided by EstreUI:
class EstreUiCustomPageManager {
init(extPidMap, pageHandlers) {
pageManager.extPidMap = extPidMap;
EstreUiPage.registerProvider(pageHandlers);
for (var id in pageHandlers)
EstreUiPage.registerHandler(extPidMap[id], pageHandlers[id]);
return this;
}
bringPage(id, intent, instanceOrigin) {
return pageManager.bringPage("*" + id, intent, instanceOrigin);
}
closePage(id, closeHost = false, instanceOrigin = null) {
return pageManager.closePage("*" + id, closeHost, instanceOrigin);
}
// also: showPage, showOrBringPage, hidePage
}init() does three things:
- Sets the alias map on the framework's
pageManager. - Registers the provider instance (so handlers can access
this.provider). - Iterates over all handler entries and registers each one by PID.
The primary reason to subclass is to intercept bringPage() and enforce authentication:
class MyPageManager extends EstreUiCustomPageManager {
#pageOperator = null;
init(extPidMap, pageHandlers, pageOperator) {
this.#pageOperator = pageOperator;
return super.init(extPidMap, pageHandlers);
}
async bringPage(pid, intent) {
if (!sessionManager.isAuthed) {
const isRA = this.#pageOperator.requiredAuth(pid);
if (isRA === true) {
// queue the request for after login
sessionManager.requestBringOnAuthed({
caller: this, pid, intent
});
await this.bringPage("login");
return true;
} else if (isRA !== false) {
// redirect to fallback page
pid = isRA;
}
}
return await super.bringPage(pid, intent);
}
onAuthed(data) {
// called after successful login; replay queued navigation
if (data.pid != null) {
setTimeout(() => this.bringPage(data.pid, data.intent), 500);
}
}
}The auth gate flow:
- User tries to open a protected page.
bringPage()checksrequiredAuth().- If auth is required, the navigation request is queued and login is shown.
- After login succeeds,
onAuthed()replays the queued request.
In the boot sequence (inside sessionManager.init() callback):
myPageManager.init(
MyPagesProvider.pages,
await new MyPagesProvider(myPageManager, sessionManager).init(),
MyPagesProvider.operator
);This must happen before estreUi.init(), because the framework needs the alias map to resolve section exports.
When myPageManager.bringPage("home") is called:
myPageManager.bringPage("home")
→ super.bringPage("home")
→ pageManager.bringPage("*home")
→ lookup extPidMap["home"] → "&m=home"
→ pageManager.bringPage("&m=home")
→ open/show section, fire handler lifecycle
The * prefix is the signal for the framework to look up the alias in extPidMap. The ! prefix resolves against the framework's built-in managed PID map (for dialogs, alerts, etc.).
EstreUiPage.registerProvider(provider) stores the provider instance. When a handler class is later instantiated, it receives this.provider — which is the provider instance — giving handlers access to shared services:
"home" = class extends EstrePageHandler {
onShow(handle) {
// this.provider is the MyPagesProvider instance
const session = this.provider.sessionManager;
if (session.isAuthed) {
// load data
}
}
}EstreUiPage.registerHandler(pid, handlerClass) maps a PID to its handler class. When the page handle for that PID is created, the handler is instantiated and attached to the page handle's .handler property.
After all registrations, EstreUiPage.commit() finalizes the mapping, preventing further modifications.