> ## Documentation Index
> Fetch the complete documentation index at: https://docs.elementlabs.net/llms.txt
> Use this file to discover all available pages before exploring further.

# Views

> Define an alternative map with its own markers, camera, look, interactions and interface.

A view is a named map: its own markers, camera limits, look, interactions and interface. A resource
registers one, then opens the map with its id.

Use a view for a map that is not the world map, such as a property viewer, a job board or a delivery
selector. For adding markers to the standard map, use `AddBlip` instead. See
[Exports](/products/map/reference/exports).

Nothing is resolved when a view is registered. Everything — the configuration, the views it extends,
the parameters it was opened with — is worked out when the map opens, so a reloaded config or an
edited parent view reaches every view built on it without anyone registering anything again.

## Contract

```lua theme={null}
exports.element_map:RegisterView(view)   --> boolean
exports.element_map:Open('property', { house = 12 })
```

`RegisterView` answers whether the view was accepted. A view whose fields are of the wrong kind is
refused, with the reason in the server console, rather than being carried into a map that then fails
to open.

The standard map is registered under the id `world`. Registering a view with that id replaces it.

## Inputs

| Field          | Type             | Default  | Meaning                                                                    |
| -------------- | ---------------- | -------- | -------------------------------------------------------------------------- |
| `id`           | `string`         | required | The id passed to `Open`. Registering the same id twice replaces the first. |
| `extends`      | `string` or list | none     | Views this one is built on, applied in order before it.                    |
| `label`        | `string`         | none     | The view's name, passed to the interface.                                  |
| `params`       | function         | none     | Checks what `Open` was called with. See [Parameters](#parameters).         |
| `camera`       | table            | none     | Camera overrides for this view.                                            |
| `visual`       | table            | none     | Look overrides for this view.                                              |
| `focus`        | see below        | player   | Where the map opens.                                                       |
| `blips`        | list of layers   | none     | Where the markers come from. See [Marker layers](#marker-layers).          |
| `capabilities` | table            | all on   | What the player may do. See [Capabilities](#capabilities).                 |
| `surface`      | table            | config   | What the interface draws. See [Surface](#surface).                         |
| `on`           | table            | none     | The handlers. See [Handlers](#handlers).                                   |

### `extends`

A view listed in `extends` is applied first, and this view's own fields are applied over it. Camera
and look groups merge key by key, capabilities and interface fields are replaced one by one, marker
layers are matched by their id, and the handlers of both run.

```lua theme={null}
exports.element_map:RegisterView({
  id = 'job-base',
  camera = { openHeight = 600.0 },
  capabilities = { poi = false },
  surface = { legend = false },
})

exports.element_map:RegisterView({
  id = 'delivery',
  extends = 'job-base',
  blips = { { id = 'drops', kind = 'static', blips = drops } },
})
```

A chain that loops back on itself, or that names a view which is not registered, is reported and the
missing level is skipped; the rest of the chain still applies.

### `focus`

Where the map opens, instead of over the player.

| Value                                   | Meaning                                                              |
| --------------------------------------- | -------------------------------------------------------------------- |
| `vector3`                               | Opens over that point.                                               |
| `{ coords = vector3, height = number }` | The same, at your own altitude instead of the camera's `openHeight`. |
| `{ entity = number, height = number }`  | Opens over an entity, following it while the camera flies in.        |
| A function returning any of the above   | Worked out each time the view opens.                                 |

### Marker layers

`blips` is a list of layers drawn together, each with an `id` of its own. A view that mirrors the
world map and adds markers of its own is two layers, not a choice between them.

| `kind`     | Fields               | Meaning                                                     |
| ---------- | -------------------- | ----------------------------------------------------------- |
| `world`    | `filter`, `decorate` | The markers your other resources create.                    |
| `static`   | `blips`              | A fixed list, created when the view opens.                  |
| `dynamic`  | `blips`, `refresh`   | A function returning a list, called on every rebuild.       |
| `provider` | `load`, `refresh`    | A list that has to be fetched, answered through a callback. |

`filter(ctx, blip)` returns whether a world marker reaches the map at all. `decorate(ctx, blip)`
returns marker information — the same fields as `SetBlipMeta` — layered over what the marker's own
resource set. The `blip` it is handed has `handle`, `coords`, `type`, `sprite`, `color`, `alpha`,
`display`, `shortRange`, `route`, `entity`, `name`, `icon` and `category`.

`refresh` is a number of milliseconds. Left out, a layer is built once per open. A layer is also
rebuilt on demand with `RefreshView`, or from a handler with `ctx.invalidate(layerId)`.

```lua theme={null}
blips = {
  { id = 'world', kind = 'world', filter = function(ctx, blip) return blip.category == 3 end },
  { id = 'shops', kind = 'static', blips = shops },
  { id = 'players', kind = 'dynamic', refresh = 2000, blips = function(ctx) return nearbyPlayers() end },
  {
    id = 'houses',
    kind = 'provider',
    load = function(ctx, done)
      lib.callback('myresource:houses', false, done)
    end,
  },
}
```

A view mirrors the world map once: a second `world` layer is refused.

### Capabilities

Every capability is on unless the view turns it off. A capability is either a switch or a function
asked again on every frame, which is how one depends on where the camera is or on what your resource
knows at that moment.

| Capability   | Off removes                                                                  |
| ------------ | ---------------------------------------------------------------------------- |
| `pan`        | Dragging the camera.                                                         |
| `zoom`       | Zooming.                                                                     |
| `blipHover`  | The growth of a marker under the cursor.                                     |
| `blipClick`  | Selecting markers.                                                           |
| `blipFocus`  | The camera's move onto a selected marker, while the click is still reported. |
| `waypoint`   | Setting a destination, by any means.                                         |
| `poi`        | Placing points of interest.                                                  |
| `route`      | Route drawing, and the wide map view routes need.                            |
| `highDetail` | The high/low marker-detail toggle.                                           |
| `pages`      | The page tabs.                                                               |
| `settings`   | The Settings tab.                                                            |
| `exit`       | The Exit tab, and Escape as a way out of the map.                            |

```lua theme={null}
capabilities = {
  poi = false,
  waypoint = function(ctx) return ctx.coords.z < 500.0 end,
}
```

The on-screen prompts follow the answers, so the player is never told about a control the view does
not have. A view that turns `exit` off must close itself — from a handler with `ctx.close()`, or
from your resource with `Close`.

### Surface

What the interface draws around the map. Every field defaults to the [hud
configuration](/products/map/configuration/hud), so a view that says nothing looks like the standard
map.

| Field           | Type                                            | Meaning                                                    |
| --------------- | ----------------------------------------------- | ---------------------------------------------------------- |
| `title`         | `string` or function                            | A line drawn under the tabs.                               |
| `subtitle`      | `string` or function                            | A second, quieter line.                                    |
| `theme`         | `string`                                        | Passed to the interface untouched, for a look of your own. |
| `profile`       | `boolean`                                       | The player card.                                           |
| `legend`        | `boolean`                                       | The marker list.                                           |
| `scale`         | `boolean`                                       | The scale bar.                                             |
| `location`      | `boolean`                                       | The street and area readout.                               |
| `crosshair`     | `boolean`                                       | The centre marker.                                         |
| `tabs`          | `boolean`                                       | The tab bar as a whole.                                    |
| `pages`         | `"all"`, `"none"`, a list of ids, or a function | Which registered pages this view hosts.                    |
| `instructional` | `boolean`, a list, or a function                | The control prompts.                                       |

A page the view does not list is neither drawn as a tab nor opened by `OpenPage`. `instructional` as
a function is handed the prompts the capabilities produced, so a view adds to them rather than
restating them.

### Parameters

`Open` takes a second argument, passed through to the view. A view that declares `params` checks it
and turns it into what its handlers receive; returning nothing refuses the open, which is how a view
states that it cannot be shown without being told what to show.

```lua theme={null}
exports.element_map:RegisterView({
  id = 'property',
  params = function(raw)
    if type(raw) ~= 'table' or type(raw.house) ~= 'number' then return nil end
    return { house = raw.house }
  end,
  surface = { title = function(ctx) return 'House ' .. ctx.params.house end },
  blips = {
    { id = 'house', kind = 'dynamic', blips = function(ctx) return houseMarkers(ctx.params.house) end },
  },
})

exports.element_map:Open('property', { house = 12 })
```

`SetViewParams` changes them while the map stays open: the markers, the rules and the interface are
all worked out again.

### Handlers

Handlers live under `on` and are all optional. Each is given the [context](#the-context) first.

| Handler         | Arguments                      | Returning `true`                                                |
| --------------- | ------------------------------ | --------------------------------------------------------------- |
| `canOpen`       | `ctx`                          | Nothing. Returning `false` or a reason string refuses the open. |
| `onOpen`        | `ctx`                          | None                                                            |
| `onReady`       | `ctx`                          | None                                                            |
| `onClose`       | `ctx`                          | None                                                            |
| `onTick`        | `ctx`, `dt`                    | None. Runs every frame: keep it short.                          |
| `onBlipHover`   | `ctx`, `id`, `coords`          | None                                                            |
| `onBlipClick`   | `ctx`, `id`, `coords`          | Suppresses the camera's move to the marker.                     |
| `onBlipContext` | `ctx`, `id` or `nil`, `coords` | Suppresses the destination the right click would set.           |
| `onWaypoint`    | `ctx`, `coords`                | Suppresses setting a destination and drawing the route.         |
| `onPageEnter`   | `ctx`, `id`                    | None                                                            |
| `onPageLeave`   | `ctx`, `id`                    | None                                                            |
| `onParams`      | `ctx`, `params`                | None                                                            |

Returning anything other than `true`, including nothing, lets the map's own behaviour run as well.
That is how a view adds to a behaviour rather than replacing it. A handler that raises an error is
reported and takes that one handler out of that one interaction; the map stays open.

Handlers of a view and of the views it extends all run, this view's own first, and the first one to
return `true` ends the interaction.

### The context

| Field                      | Meaning                                                             |
| -------------------------- | ------------------------------------------------------------------- |
| `ctx.id`                   | The view's id.                                                      |
| `ctx.params`               | What the view was opened with, after its own `params` check.        |
| `ctx.camera`, `ctx.visual` | The configuration this view is actually running.                    |
| `ctx.coords`               | Where the camera is.                                                |
| `ctx.open`                 | Whether the map is up.                                              |
| `ctx.setParams(params)`    | Replaces the parameters.                                            |
| `ctx.patch(patch)`         | Changes `camera`, `visual`, `capabilities` or `surface` while open. |
| `ctx.invalidate(layerId)`  | Rebuilds one marker layer, or every one.                            |
| `ctx.close()`              | Closes the map.                                                     |

## Following a view from another resource

A view's `on` handlers belong to whoever registered it. Everybody else takes part through the SDK's
hooks, which give priorities, one-shot handlers and registration that goes away with the resource.

| Hook                 | Argument | When                                                                   |
| -------------------- | -------- | ---------------------------------------------------------------------- |
| `el:map:view:open`   | `ctx`    | Before the camera moves. Returning `false` stops the map from opening. |
| `el:map:view:ready`  | `ctx`    | The camera has arrived.                                                |
| `el:map:view:close`  | `ctx`    | The view has left the screen.                                          |
| `el:map:view:params` | `ctx`    | Its parameters changed.                                                |

```lua theme={null}
local Hooks = ElementLabs.Shared.Hooks

Hooks:register('el:map:view:open', function(ctx)
  if ctx.id == 'property' and not playerMayBrowse() then
    return false
  end
end)
```

The argument is the same [context](#the-context) the view's own handlers are given, minus any
knowledge of what its parameters mean. Only `el:map:view:open` is waited for, and only it can refuse
anything: the other three are told what happened.

## Outputs

`RegisterView` returns `true` when the view was accepted. It is available to `Open` from that moment
and stays registered until the resource that registered it stops, or until `RemoveView` is called.

Opening a view calls `canOpen`, then the `el:map:view:open` hook, then raises `el:map:open` and
`el:map:view:enter` with the view's id and calls `onOpen`. `onReady` follows once the camera has
arrived. Closing raises `el:map:view:leave` and `el:map:close`. See
[Events](/products/map/reference/events).

Registering a view that is already on screen applies the change to it: the limits, look, rules,
interface and markers are taken from the new definition without the map closing. Removing it closes
the map, because there is nothing left to show.

## Errors

Opening an id that was never registered opens nothing, and says so in the console. This is a change
from earlier releases, which fell back to the standard map.

A view whose parameters are refused, whose `canOpen` says no, or whose `el:map:view:open` hook is
refused by another resource, also opens nothing. The player is left where they were. A hook that
raises, or that does not answer within its deadline, counts as agreement: the SDK's hook dispatch
reports it and carries on, so the map is not another resource's to break by failing.

## Example

A property viewer: its own markers, no destinations, opening over one house, and a title that
follows what it was opened with.

```lua theme={null}
AddEventHandler('el:map:ready', function()
  exports.element_map:RegisterView({
    id = 'property',
    label = 'Property',
    params = function(raw)
      if type(raw) ~= 'table' or type(raw.coords) ~= 'vector3' then return nil end
      return raw
    end,
    focus = function(ctx) return { coords = ctx.params.coords, height = 300.0 } end,
    camera = { openHeight = 400.0 },
    blips = {
      {
        id = 'house',
        kind = 'static',
        blips = { { coords = vector3(-802.0, 175.0, 72.0), sprite = 40, color = 2, name = 'For sale' } },
      },
    },
    capabilities = { waypoint = false, poi = false, route = false },
    surface = { title = 'For sale', legend = false, pages = 'none' },
    on = {
      onBlipClick = function(ctx, id, coords)
        TriggerEvent('myresource:showProperty', coords)
        return true
      end,
    },
  })
end)

RegisterCommand('properties', function()
  exports.element_map:Open('property', { coords = vector3(-802.0, 175.0, 72.0) })
end, false)
```

Adding to the standard map instead of replacing it. The destination is still set, and the resource
is told about it:

```lua theme={null}
exports.element_map:RegisterView({
  id = 'delivery',
  extends = 'world',
  on = {
    onWaypoint = function(ctx, coords)
      TriggerServerEvent('myresource:deliveryTarget', coords)
    end,
  },
})
```

## Related

* [Exports](/products/map/reference/exports)
* [Events](/products/map/reference/events)
* [Pages](/products/map/reference/pages)
* [Camera](/products/map/configuration/camera)
