# Views

> Buttons and menus whose clicks are handled inside the same command. The best choice for games, confirmations and menus.

A **View** is a set of buttons and menus. When someone clicks, a function in the same command runs. That function can see every variable of the command.

Use a View when the clicks happen while the command is running: a "Yes / No" question, a game, a page of choices. Use a [component handler](https://ccbot.app/docs/reference/components) only for buttons that must keep working for days, like a role panel.

Read [Buttons and menus](https://ccbot.app/docs/concepts/buttons-and-menus) to choose between the two.

## A full example

```python
# pre-hook
class Confirm(View):
    def __init__(self, user_id):
        self.user_id = user_id      # only this member can click
        self.choice = None

    @button(label="Yes", style="success", custom_id="yes")
    async def yes(self, interaction, component):
        await defer(interaction, action="update")
        self.choice = "yes"
        self.disable_all_components()
        await self.update_view()
        self.stop()

    @button(label="No", style="danger", custom_id="no")
    async def no(self, interaction, component):
        await defer(interaction, action="update")
        self.choice = "no"
        self.disable_all_components()
        await self.update_view()
        self.stop()

view = Confirm(user_id=user.id)
view.interaction = interaction      # lets update_view() change the reply
set_view(view)
set_content("Delete all your saved notes?")
```

```python
# post-hook
if view.choice == "yes":
    send_message(channel, "Deleted.")
elif view.choice is None:
    send_message(channel, "No answer. Nothing was deleted.")
```

What happens:

1. The pre-hook makes the View and calls `set_view`.
2. The bot sends the reply with the buttons and waits for clicks.
3. A click runs `yes` or `no`. `self.stop()` ends the View.
4. The post-hook runs. `view.choice` holds the answer.

> **Rules for Views:** - Never call `super().__init__()` in your View.
> - After `set_view(view)`, do not call `view.wait()`. The bot does it.
> - Callbacks always look like `async def name(self, interaction, component)`.

## View attributes

Set these on your View.

| Attribute | What it does |
|---|---|
| `user_id` | A member ID, or a list of IDs, allowed to click. `None` lets anyone click. |
| `interaction` | Set to `interaction` so `update_view()` changes the command's reply. |
| `message` | Set when the View is on a message you sent. `send_message` sets it for you. |
| `guild_id` | Set for you by `set_view` and `send_message`. |

## View methods

### self.stop

Ends the View. `wait()` then returns `"stopped"`.

```python
self.stop()
```

`stop()` also ends the callback. Lines after it do not run, so call it last.

### self.update_view

Shows your changes to the buttons on the message. Needs `await`.

```python
await self.update_view()
```

### self.update_component

Changes one button or menu. Use the `custom_id` you wrote in the decorator.

```python
self.update_component(custom_id, **changes)
```

```python
self.count += 1
self.update_component("add", label=f"Clicked {self.count} times")
await self.update_view()
```

### self.disable_all_components

Greys out every button and menu. Pass `ignore=[...]` to keep some active.

```python
self.disable_all_components(ignore=None)
```

### view.wait

Waits for clicks until the View stops or nobody clicks for `timeout` seconds. Needs `await`.

```python
await view.wait(timeout=180, disable_on_timeout=False, ignore_errors=False)
```

| Argument | What it does |
|---|---|
| `timeout` | Seconds with no clicks before the View ends. Each click starts the clock again. |
| `disable_on_timeout` | Grey out the buttons when time runs out. |
| `ignore_errors` | Keep the View going when a callback has an error. |

Returns `"stopped"` or `"timeout"`.

Only call `wait` yourself when you did **not** use `set_view`, for example when the View is on a message from `send_message`:

```python
view = Poll()
send_message(channel, "Vote now!", components=view)
result = await view.wait(timeout=60, disable_on_timeout=True)
```

## button

Adds a button. Put it above an `async def` inside your View.

```python
@button(
    label,
    custom_id=None,
    style="primary",
    is_disabled=False,
    emoji=...,
    row=1,
    url=None,
    hidden=False,
)
```

| Argument | What it does |
|---|---|
| `label` | The button text. |
| `custom_id` | A name for the button. Needed for `update_component`. |
| `style` | `primary` (blue), `secondary` (grey), `success` (green), `danger` (red), `link`. |
| `emoji` | An emoji shown on the button. |
| `row` | Which row, from 1 to 5. |
| `url` | Makes it a link button. Link buttons do not run a callback. |
| `hidden` | `True` hides the button until you show it with `update_component`. |

A wrong `style` silently becomes `primary`.

In signatures on this page, `...` means the argument is optional and has no default. Leave it out when you do not need it.

Extra keyword arguments are kept in `component.metadata`, so a callback can read them.

## text_menu

Adds a menu with your own options.

```python
@text_menu(
    options,
    custom_id=None,
    placeholder=...,
    min_values=0,
    max_values=1,
    is_disabled=False,
    row=None,
    hidden=False,
)
```

`options` is a list of `TextOption`:

```python
TextOption(label, value, description=..., emoji=..., default=False)
```

**What the member picked is in `interaction.values`.** It is always a list, even with `max_values=1`.

```python
class Colors(View):
    @text_menu(
        options=[
            TextOption(label="Red", value="red", emoji="🔴"),
            TextOption(label="Blue", value="blue", emoji="🔵"),
        ],
        custom_id="color",
        placeholder="Pick a color",
    )
    async def pick(self, interaction, component):
        await defer(interaction, action="update")
        self.color = interaction.values[0]
        self.stop()
```

> **Watch out:** Use `interaction.values`, not `component.values`. `component` is what you declared, not what the member picked.

## role_menu

Adds a menu that lists the server's roles. `interaction.values` holds the picked role IDs.

```python
@role_menu(custom_id=None, placeholder=..., min_values=0, max_values=1,
           is_disabled=False, row=None, hidden=False)
```

## user_menu

Adds a menu that lists members. `interaction.values` holds the picked member IDs. Same arguments as `role_menu`.

```python
@user_menu(custom_id=None, placeholder=..., min_values=0, max_values=1)
```

## channel_menu

Adds a menu that lists channels. `interaction.values` holds the picked channel IDs.

```python
@channel_menu(channel_types, custom_id=None, placeholder=...,
              min_values=0, max_values=1)
```

`channel_types` is a list from: `text`, `voice`, `category`, `news`, `news-thread`, `public-thread`, `private-thread`, `forum`, `stage`.

## Layout rules

Discord limits how buttons and menus fit on a message:

- At most **5 rows**.
- At most **5 buttons** in a row.
- A menu takes a whole row. It cannot share a row with buttons or another menu.

The bot stops with a clear message when a View breaks a rule.

## Answering a click

Discord wants an answer to every click within **3 seconds**. Start each callback with one of these:

| Call | Use when |
|---|---|
| `await defer(interaction, action="update")` | You change the message the buttons are on. |
| `await defer(interaction, ephemeral=True)` | You send a new private message later. |
| `await send_modal(form, interaction)` | You open a form. Opening a form is the answer. Do not defer too. |

See [Interactions](https://ccbot.app/docs/reference/interactions) and [Modals](https://ccbot.app/docs/reference/modals).
