# JavaScript on the sheet

> rpgdesk.js from code: connecting, reading and writing the document, following changes, computed fields and lists.

Page: https://docs.rpgdesk.com/en/fichas/javascript/

When HTML attributes are not enough (some math, a special button, a rule of your system), JavaScript comes in. Everything goes through the `RPGDesk` object, which rpgdesk.js creates. The complete list of functions is in [The rpgdesk.js API](https://docs.rpgdesk.com/en/referencia/api/).

## Connect

```js
RPGDesk.connect().then(({ doc, me, item, room, kind, language, test }) => {
  // everything that uses the document starts here
});
```

`RPGDesk.connect()` waits for the connection with RPG Desk (or with test mode) and returns the **context**:

| Field      | What it is                                                                                   |
| ---------- | -------------------------------------------------------------------------------------------- |
| `doc`      | the sheet’s document (see below)                                                             |
| `me`       | who is looking: `{ id, nick, role, isOwner }`. `role` is `'gm'`, `'player'` or `'spectator'` |
| `item`     | the sheet: `{ id, name, type, ownerId }`                                                     |
| `room`     | the room: `{ id, name, system }`                                                             |
| `kind`     | `'sheet'` or `'table'` (room table)                                                          |
| `language` | `'pt'` or `'en'`, the RPG Desk language of whoever is looking                                |
| `test`     | `true` in test mode, outside RPG Desk                                                        |
| `plugin`   | your plugin’s `{ id, version }`                                                              |

You can call `RPGDesk.connect()` as many times as you want: it is always the same connection. Once connected, `RPGDesk.context` and `RPGDesk.doc` also give you the context and the document.

## Read and write

```js
doc.get('name');                 // 'Aria'
doc.get('hp');                   // { current: 18, max: 22 } (a group becomes an object)
doc.get('does_not_exist');       // undefined

await doc.set('level', 4);                         // writes a field
await doc.set('hp', { current: 10, max: 12 });     // writes field by field (what is missing stays)
await doc.set('notes', null);                      // deletes
await doc.update({ 'hp.current': 3, 'hp.max': 10 }); // several at once
```

- Values are texts, numbers, true/false or objects made of them. Lists are written through the list API (see below).
- `set` and `update` return a promise: the write goes to RPG Desk, which checks the permission and sends the change back in milliseconds.
- `doc.batch(() => { … })` joins several writes into a single batch, applied whole or refused whole.
- `doc.canWrite('hp.current')` says whether the person can write there.

A write without permission (a player writing on someone else’s sheet) is refused. To find out:

```js
doc.on('rejected', (message) => RPGDesk.ui.toast(message, 'error'));
```

## Follow the changes

```js
const stop = doc.watch('hp', (hp, { local }) => {
  bar.style.width = `${(hp.current / hp.max) * 100}%`;
}, { now: true });
```

- `watch` tells you about every change on the path, or on any field inside it, whoever made it.
- `{ now: true }` calls the function right away, with the current value.
- `local` says whether the change came from this browser.
- `watch` returns the function that stops it.

## Computed fields

A value that comes from others (an attribute’s modifier, the total load) is computed with `doc.compute`:

```js
const mod = (value) => Math.floor(((Number(value) || 10) - 10) / 2);
doc.compute('str_mod', ['strength'], mod);
doc.compute('defense', ['dexterity', 'armor'], (dex, armor) => 10 + mod(dex) + (Number(armor) || 0));
```

`doc.compute(target, [sources], function)` writes the target when a source changes. Show the result with `data-text` or use it in rolls (`data-roll="1d20 + {str_mod}"`).

> **Do not write inside a watch**
>
> Everyone with the sheet open runs the same code. A `watch` that writes would make each person write again. `compute` writes only in the browser the change came from, and only if the person can write to the target.

## Lists

```js
const inv = doc.list('inventory');

inv.items();            // [{ id: 'i1…', name: 'Rope', qty: 1 }, …]
inv.length;             // 1
const id = inv.add({ name: 'Torch', qty: 3 });   // at the end; inv.add(value, 0) puts it first
await inv.item(id).set('qty', 2);
await inv.move(id, 0);  // changes its position
await inv.remove(id);
inv.watch((items) => totalEl.textContent = items.length, { now: true });
```

- Each item has its own `id`, which `add` returns right away.
- `inv.item(id)` has `get`, `set`, `watch` and `remove`, with paths relative to the item.
- Paths can also be arrays: `['inventory', '#' + id, 'qty']` is the `qty` field of item `id`.

## Room events

```js
RPGDesk.on('players', (players) => { /* someone joined or left */ });
RPGDesk.on('me', (me) => { /* my role changed */ });
RPGDesk.on('language', (lang) => { /* 'pt' or 'en' */ });
RPGDesk.on('theme', (colors) => { /* the theme colors changed */ });
RPGDesk.on('library', (items) => { /* the library changed */ });
```

`RPGDesk.on` returns the function that stops listening.

## A sheet in two languages

Use `language` to switch the texts:

```js
const TEXTS = { pt: { roll: 'Rolar' }, en: { roll: 'Roll' } };
RPGDesk.connect().then(({ language }) => {
  for (const b of document.querySelectorAll('[data-roll]')) b.textContent = TEXTS[language].roll;
});
```

The plugin’s name and the sheet’s title, in the manifest, also accept both languages: `"title": { "pt": "Personagem", "en": "Character" }`. See [The manifest](https://docs.rpgdesk.com/en/referencia/manifesto/).

## When something fails

An error in your code does not bring the room down: the sheet shows a badge with the number of **Page errors**, and the panel has **Copy errors**. In test mode, errors show up in the **Log**. See [Test and develop](https://docs.rpgdesk.com/en/fichas/desenvolvimento/#page-errors).
