Windows and menus
Input, confirmation and choice in MrPoulpi UI: blocking windows, OpenUI, menus split into sections, client actions and secured server actions.
Three window types: input (input), confirmation (confirm) and choice (choice). Only one window is open at a time; opening another one replaces the first.
While a window is open, the player has the cursor and no longer controls their character. Escape closes the window, Enter submits the input, Tab moves from one button to the next.
Blocking windows
The simplest way: the function waits for the player’s answer and returns it. Call it inside a thread.
CreateThread(function()
local plate = exports.mrpoulpi_ui:Input({ title = 'Plate', placeholder = '12ABC345', maxLength = 8 })
if not plate then return end
if not exports.mrpoulpi_ui:Confirm(('Register plate %s?'):format(plate)) then return end
local method = exports.mrpoulpi_ui:Choice({
title = 'Payment',
options = {
{ id = 'bank', label = 'Bank', variant = 'success' },
{ id = 'cash', label = 'Cash', variant = 'info' }
}
})
if method then
TriggerServerEvent('garage:buyPlate', plate, method)
end
end)
| Export | Returns | If the player cancels |
|---|---|---|
Input(data) |
the typed text, or a number with inputType = 'number' |
nil |
Confirm(data) |
true if confirmed, false if refused |
false |
Choice(data) |
the option id, then the full option |
nil |
If the window is closed by another script or replaced by another one, the function returns the cancel value.
Window fields
These fields work for the blocking functions as well as for OpenUI.
Common
| Field | Default | Description |
|---|---|---|
type |
'choice' |
input, confirm or choice. Not needed with Input, Confirm and Choice. |
title |
— | Window title. |
cursor |
true |
false gives focus without showing the cursor. |
action |
— | Action called on submit (input and confirmation). See Actions. |
server |
false |
With action, sends the action to the server. |
Every text accepts a shared/locales.lua key instead of text.
Input (input)
| Field | Default | Description |
|---|---|---|
placeholder |
“Enter a value” | Grey text in the empty field. |
value |
empty | Prefilled value. |
maxLength |
Config.Input.maxLength |
Number of characters, never more than Config.Input.maxLength. |
inputType |
'text' |
'number' only accepts numbers and returns a number. |
required |
true |
false accepts an empty input. |
Confirmation (confirm)
| Field | Default | Description |
|---|---|---|
question |
— | Question shown. |
confirmLabel |
“Confirm” | Confirm button. |
cancelLabel |
“Cancel” | Refuse button. |
Choice (choice)
| Field | Default | Description |
|---|---|---|
options |
— | List of buttons (below). |
cancel |
true |
Adds a button to close. |
cancelLabel |
“Cancel” or “Close” | Text of that button. |
columns |
3 |
Number of columns (1 to 4) of a menu split into sections. |
Each option:
| Field | Description |
|---|---|
id |
Identifier returned. By default, the position in the list. |
label |
Button text. |
variant |
Colour: success, info, warning, danger or neutral (default). |
group |
Section title. See Menus in sections. |
disabled |
true shows the button greyed out, not clickable. |
keepOpen |
true keeps the window open after the click. |
action, server |
Action triggered by this button. |
onSelect |
Function called on click: onSelect(payload, context). Client only. |
ace |
Server menu only: the option is only shown to players with this ACE. |
Menus in sections
As soon as an option has a group, the window becomes a menu: options are sorted under one title per group, in the order the groups appear, and the close button moves to the bottom.
exports.mrpoulpi_ui:OpenUI({
type = 'choice',
title = 'Garage',
columns = 2,
options = {
{ id = 'out', group = 'Vehicles', label = 'Take out', variant = 'success', action = 'garage:out' },
{ id = 'store', group = 'Vehicles', label = 'Store', action = 'garage:store' },
{ id = 'repair', group = 'Services', label = 'Repair', variant = 'warning', action = 'garage:repair' },
{ id = 'wash', group = 'Services', label = 'Wash', disabled = true }
}
})
With keepOpen = true, the player can click several buttons in a row. Choice only returns on a click on an option without keepOpen, or on closing.
Actions
Instead of waiting for the answer, you can tie an action to a button. It is useful for a menu opened from the server, or reused in several places.
On click, in this order:
- the option
onSelect, if any; - the client hook
Hooks.OnAction(name, payload, context); - the client action registered under that name, if any;
- otherwise, if the option has
server = true, the action is sent to the server.
The payload is { id = '…' } for a choice and { value = … } for an input or a confirmation. context is the whole window.
Client action
exports.mrpoulpi_ui:RegisterUIAction('garage:out', function(payload, context)
print('Chosen option:', payload.id)
end)
The action is removed automatically when the resource that registered it stops.
Server action
exports.mrpoulpi_ui:RegisterServerAction('shop:payBank', function(source, payload)
-- payload.id or payload.value, already checked
end)
RegisterNetEvent('shop:open', function()
exports.mrpoulpi_ui:OpenUI(source, {
type = 'choice',
title = 'Payment',
options = {
{ id = 'bank', label = 'Bank', variant = 'success', action = 'shop:payBank', server = true },
{ id = 'cash', label = 'Cash', variant = 'info', action = 'shop:payCash', server = true }
}
})
end)
The server accepts an action in two cases only:
| Case | Condition |
|---|---|
Action registered with RegisterServerAction |
Always accepted. With { requirePermission = true } as 3rd argument, restricted to players with the menu permission. |
server = true option of the last window opened by the server for that player, without RegisterServerAction |
Restricted to players with the menu permission, then handled by Hooks.OnServerAction. Valid once, unless the option has keepOpen. |
For a window meant for every player, use RegisterServerAction.
Before reaching your callback, each action is also:
- limited to one every
Config.Security.actionCooldownmilliseconds per player; - refused if its name is longer than 64 characters;
- cleaned: only
id(64 characters at most) andvalue(text of at mostConfig.Input.maxLengthcharacters, number or boolean) are passed on; - submitted to
Hooks.CanUseAction(source, name, payload).
Refusals go through Hooks.Log (unknown_action, action_denied, invalid_payload).
Updating an open window
exports.mrpoulpi_ui:SetUIData({ title = 'Garage (3 vehicles)' })
Given fields replace the old ones; the others are kept. To follow opening and closing from another script, listen to the local events mrpoulpi_ui:opened and mrpoulpi_ui:closed.
Blocking the opening
In client/hooks.lua, Hooks.CanOpen can refuse a window depending on the player’s state:
function Hooks.CanOpen(data)
if IsEntityDead(PlayerPedId()) then
return false
end
return true
end
Blocking functions then return the cancel value straight away.