Skip to content

PlayerUi

import { PlayerUi, UiDocument, type UiEvent } from "@sagl/server";

UI documents the server opens on a player's screen (player.ui): an RML client file that the server fills in by element id, and hears clicks, changes and form submits from.

A server-run shop: the server set every text, value and class

Layouts are RML and RCSS, styled like any other: see Styling and layouts.

Server documents (player.ui) Client scripts (sagl.ui)
Logic runs On the server: every click is a round trip In the game, instantly
Good for Shops, dialogs, forms, menus that change server state HUDs, animation, live updates, anything reacting per frame
Needs a client script No Yes
Survives client script reloads Yes No

Both work with the same layouts and the same element methods, so a document can start on the server and move to a client script when it outgrows it.


PlayerUi

open(file, options?)

open(file: string, options?: UiOpenOptions): UiDocument

Opens an RML client file on their screen. Its relative links (images, stylesheets) resolve beside it; /sagl/ui/theme/base.rcss is SAGL's theme.

Parameter Type Description
file string The client file, e.g. "ui/shop.rml".
options UiOpenOptions Optional.

Returns the UiDocument, straight away: changes made before it reaches the player are applied in order.

Throws ReferenceError if there's no such client file; RangeError past 16 open documents.

const shop = player.ui.open("ui/shop.rml");

documents

readonly documents: UiDocument[]

The documents open on their screen now.

closeAll()

Closes every document the server has open on their screen.

UiOpenOptions

Field Type Default Description
input boolean true Takes the player's input while shown. false for display-only panels.
cursor boolean input Shows the mouse cursor.
keyboard boolean input Keys go to the document.
gamepad boolean input The gamepad moves focus (A activates, B backs out).
movement boolean false Leaves the player's movement to the game.
escapeCloses boolean true Esc (or gamepad B) closes it, and its close handlers run.

UiDocument

What open returns. The setters work on the document's elements by id and return the document, so calls chain. Changes to a closed document are ignored.

Member Description
id Its number, unique per player.
file The client file it was opened from.
player Whose screen it's on.
open boolean: false once it's closed (by close(), by the player, or by them leaving).

setText(id, text)

setText(id: string, text: unknown): this

Sets an element's text. It's shown as text: < and & are not markup.

shop.setText("greeting", `Welcome, ${player.name}`);

setHtml(id, rml)

setHtml(id: string, rml: string): this

Replaces an element's contents with RML, up to 5 KB per call.

shop.setHtml("items", items.map((i) => `<div class="item" id="${i.id}">${i.name} - $${i.price}</div>`).join(""));

setValue(id, value)

setValue(id: string, value: unknown): this

A form control's value: <input>, <select>, <textarea>.

setChecked(id, checked?)

setChecked(id: string, checked?: boolean): this

Checks (the default) or unchecks a checkbox or radio button.

setClass(id, name, on?)

setClass(id: string, name: string, on?: boolean): this

Adds (on, the default) or removes a class.

shop.setClass(item.id, "sold", stock[item.id] === 0);

setAttribute(id, name, value)

setAttribute(id: string, name: string, value: string | null): this

Sets an attribute, or removes it with null. Not id.

shop.setAttribute("bar", "style", `width: ${pct}%`);

focus(id)

Gives an element keyboard and gamepad focus.

show(), hide()

Hides it (and gives the player's input back) without closing it, and shows it again.

close()

Closes it for good. Its close handlers don't run: you closed it.

on(type, id?, handler)

on(type: UiEventType, handler: (e: UiEvent) => void): this
on(type: UiEventType, id: string, handler: (e: UiEvent) => void): this

Handles events from the whole document, or from one element (and what's inside it). The player's game only sends the types something listens for.

Parameter Type Description
type string click, change, submit, keydown, focus, blur, mouseover, mouseout, or close (the player closed it).
id string Optional: only events from this element.
handler function Gets a UiEvent.
shop.on("click", "cancel", () => shop.close());
shop.on("change", "note", (e) => { if (e.enter) save(e.value); });
shop.on("submit", (e) => buy(player, e.fields.item, e.fields.gift === "yes"));

Warning

Events come from the player's game, so treat them like any other client input: check that they can afford it, that they're still in the shop, and so on. Players send them at a limited rate.

off(type, handler?)

Removes a handler, or every handler of that type.

onClose(fn)

The same as on("close", fn): the player closed it with Esc.

UiEvent

Field Type Description
type string The event type.
document, player Where it came from.
id string The nearest element with an id ("" for none).
value string A form control's value (change, submit, clicks on controls).
checked boolean Checkboxes and radio buttons.
enter boolean change from a text input: Enter was pressed in it.
key number keydown: RmlUi's key identifier (KI_A 12 ... KI_Z 37, KI_0 2 ... KI_9 11).
fields Record<string, string> submit: the form's controls by name (unchecked boxes left out), plus submit: the submit button's value.

Forms

A <form> submits when the player presses an <input type="submit"> in it. (In RmlUi, <button> is only a styled element: it clicks, but doesn't submit.) The submit input shows its value as its label.

ui/shop.rml
<rml>
<head>
  <link type="text/rcss" href="/sagl/ui/theme/base.rcss"/>
  <style>body { position: absolute; left: 50%; top: 18%; width: 440dp; margin-left: -220dp; }</style>
</head>
<body class="panel">
  <h1 id="title">Shop</h1>
  <div class="list" id="items"></div>
  <form>
    <input class="text" type="text" name="note" id="note"/>
    <div><input type="checkbox" name="gift" id="gift" value="yes"/> Gift wrap</div>
    <input type="submit" class="button" value="Buy"/>
    <button id="cancel">Cancel</button>
  </form>
  <span class="status" id="status"></span>
</body>
</rml>

See the shop menu example for the server side.