Skip to content

JavaScript on the sheet

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.

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.

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:

doc.on('rejected', (message) => RPGDesk.ui.toast(message, 'error'));
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.

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

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}").

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.
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.

Use language to switch the texts:

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.

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.