Code Patterns

Copy-paste examples for common plugin tasks. Commands, events, ECS, GUI, and more.

← Back to Patterns
ui

ui-form-inputs

Form pattern with TextField, NumberField, and CheckBox inputs. Shows how to read input values using the @-prefix in EventData codec and binding input values to events.

Example Code

java
// FormPage.ui (simplified)
Group {
    Anchor: (Width: 480, Height: 380);
    Background: #141c26(0.98);
    LayoutMode: Top;
    Padding: (Full: 24);

    Label { Text: "Settings"; Style: (FontSize: 24, TextColor: #ffffff); Anchor: (Height: 40); }

    // Text input field with ID for binding
    TextField #NameInput {
        Text: "";
        PlaceholderText: "Enter name...";
        Anchor: (Height: 40);
    }

    // Checkbox with nested structure
    Group #NotifyOption {
        LayoutMode: Left;
        Anchor: (Height: 30);
        CheckBox #CheckBox { Value: true; }
        Label { Text: "Enable notifications"; Style: (TextColor: #ffffff); }
    }

    TextButton #SaveButton { Text: "Save"; Anchor: (Width: 100, Height: 40); }
    TextButton #CancelButton { Text: "Cancel"; Anchor: (Width: 100, Height: 40); }
}

// FormPage.java
public class FormPage extends InteractiveCustomUIPage<FormPage.FormEventData> {

    public static class FormEventData {
        public String action;           // Which button clicked
        public String playerName;       // Text input value
        public boolean notifications;   // Checkbox state

        // CODEC: Fields with @ prefix read from UI inputs
        public static final BuilderCodec<FormEventData> CODEC = BuilderCodec.builder(FormEventData.class, FormEventData::new)
            .append(new KeyedCodec<>("Action", Codec.STRING),
                (o, v) -> o.action = v, o -> o.action).add()
            // @ prefix = bound to input element
            .append(new KeyedCodec<>("@PlayerName", Codec.STRING),
                (o, v) -> o.playerName = v, o -> o.playerName).add()
            .append(new KeyedCodec<>("@Notifications", Codec.BOOLEAN),
                (o, v) -> o.notifications = v, o -> o.notifications).add()
            .build();
    }

    public FormPage(@Nonnull PlayerRef playerRef) {
        super(playerRef, CustomPageLifetime.CanDismissOrCloseThroughInteraction, FormEventData.CODEC);
    }

    @Override
    public void build(
        @Nonnull Ref<EntityStore> ref,
        @Nonnull UICommandBuilder cmd,
        @Nonnull UIEventBuilder evt,
        @Nonnull Store<EntityStore> store
    ) {
        cmd.append("Pages/FormPage.ui");

        // Bind Save button with input values
        // EventData.append binds: "@FieldName" to "#ElementId.Value"
        evt.addEventBinding(
            CustomUIEventBindingType.Activating,
            "#SaveButton",
            new EventData()
                .append("Action", "Save")
                .append("@PlayerName", "#NameInput.Value")               // Read TextField
                .append("@Notifications", "#NotifyOption #CheckBox.Value") // Read CheckBox
        );

        evt.addEventBinding(
            CustomUIEventBindingType.Activating,
            "#CancelButton",
            new EventData().append("Action", "Cancel")
        );
    }

    @Override
    public void handleDataEvent(
        @Nonnull Ref<EntityStore> ref,
        @Nonnull Store<EntityStore> store,
        @Nonnull FormEventData data
    ) {
        if ("Save".equals(data.action)) {
            // data.playerName contains what user typed
            // data.notifications contains checkbox state
            playerRef.sendMessage(Message.raw("Saved: " + data.playerName));
        }

        Player player = store.getComponent(ref, Player.getComponentType());
        player.getPageManager().setPage(ref, store, Page.None);
    }
}

Thread Safety

Form data is validated on server - never trust client input

Common Mistakes

Forgetting the @ prefix in codec field names when binding to inputs - without @ the value wont be read from UI