Actions let the page ask native code to do something when a button is clicked or a key is pressed. A native handler runs on the thread that owns the data.

## Example Data

The examples on this page give the player a potion counter and two actions (one takes an amount)— see [Describing Types with Schemas](/docs/2.0/describing-types-with-schemas):

```cpp
struct Player {
  int health = 80;
  int potions = 3;

  void UsePotion() {
    if (potions > 0) {
      potions--;
      health = 100;
    }
  }

  static constexpr auto schema = dd::Schema(
      dd::Field("health", &Player::health),
      dd::Field("potions", &Player::potions),
      dd::Action("usePotion"),
      dd::Action<int>("heal"));  // carries an amount
};
```

Native code binds the player instance:

```cpp
Player player;

// This instance will be exposed to markup as "player.<field>..."
dd::Binding binding = ctx.Bind("player", player);
```

## Firing an Action

Set `ul-on:<event>` on an element with the path to the action— the action fires each time the event occurs:

```html
<p>Health: {{player.health}}</p>
<button type="button" ul-on:click="player.usePotion">
  Drink ({{player.potions}} left)
</button>
```

Any DOM event name works (eg, `ul-on:dblclick` or `ul-on:keydown`).

### Canceling the Default Action

Add `.prevent` after the event name to cancel the event's default action (such as a link navigating)— `.prevent` is the only modifier:

```html
<a href="#" ul-on:click.prevent="player.usePotion">Drink</a>
```

A `ul-on:submit` action always cancels form submission, with or without `.prevent`— a bound form never reloads the page.

> 🚧 Buttons Inside Forms
>
> A button inside a `<form>` submits the form by default, which reloads the page unless the form has a `ul-on:submit` action. Always set `type="button"` on buttons.

## Handling an Action

Register a handler on the binding with the action's name:

```cpp
binding.OnAction<"usePotion">([&] { player.UsePotion(); });
```

### When Handlers Run

When a user triggers an action, it passes through three stages:

1. The user clicks an element, and the action waits in a queue.
2. The next `Sync()` runs the handler on the home thread (the thread that owns the data).
3. The same `Sync()` sends the changed values to the page.

Call `Sync()` in native code to process the queue:

```cpp
ctx.Sync();  // runs UsePotion(), then the page shows "Drink (2 left)"
```

> 🚧 Keep Calling Sync
>
> Actions from the page reach handlers only inside `Sync()`. Keep calling it while the page is visible, even when the game is paused.

### Calling a Member Function

Pass a member function of the bound type to run it on the bound instance:

```cpp
binding.OnAction<"usePotion">(&Player::UsePotion);  // runs player.UsePotion()
```

To call a member function on another object, pass the object before the member function.

## Posting Actions from Native Code

Post an action from native code on any thread to place it in the same queue as page actions, running the handler at the next `Sync()`:

```cpp
// From any thread (eg the input thread's potion hotkey):
binding.PostAction<"usePotion">();
```

`PostAction()` is safe to call from any thread, letting other threads hand work to the thread that owns the data. The call returns `false` if the action wasn't queued, which happens if the action queue is full or the `Binding` no longer works (such as when its name is rebound or its `Context` is destroyed)— see [Binding Threads and Lifetime](/docs/2.0/binding-threads-and-lifetime).

### Sending Data with an Action

To pass data with an action, declare the payload type in the schema and accept it in the handler:

```cpp
binding.OnAction<"heal">([&](int amount) {
  player.health = std::min(player.health + amount, 100);
});

// A healing zone on another thread sends an amount:
binding.PostAction<"heal">(25);
```

Only native code sends data with an action (events from the page have no payload). The payload can be a scalar or a struct.

### Without a Binding

If you don't have a `Binding` at hand (such as inside a DOM listener), call `Context::PostAction()` with the binding and action names joined by a dot:

```cpp
ctx.PostAction("player.heal", 25);
```

### Collapsing Repeated Actions

For actions that fire frequently (such as scroll positions or zoom levels), add `dd::OnlyLatest` to deliver only the newest data at each `Sync()`:

```cpp
struct Map {
  double zoom = 1.0;

  static constexpr auto schema = dd::Schema(
      dd::Field("zoom", &Map::zoom),
      dd::Action<double>("setZoom", dd::OnlyLatest));  // newest value only
};
```

This collapses repeated actions from both native code and the page into a single delivery per `Sync()`.

> 📘 Requests Can Arrive Late
>
> A handler can run after the state that prompted the action has changed (eg, if an item was already dropped). Treat each action as a request that native code can decline.
