Group - guiled/LRE GitHub Wiki

Groups

Introduction

Groups are a powerful feature introduced in LRE7 that allow you to handle multiple character sheet elements (components) as a single entity. Instead of writing repetitive code for each component, you can group them together and apply the same operations to all of them at once.

Groups are also Data Providers, which means you can use all the powerful data manipulation methods like where(), select(), transform(), etc. on your groups.

Quick example

Imagine you have a character sheet with the six main D&D ability scores: Strength, Dexterity, Constitution, Intelligence, Wisdom, and Charisma. You want to highlight all of them when the character levels up.

Without groups, you would write:

sheet.get("strength").addClass("highlight");
sheet.get("dexterity").addClass("highlight");
sheet.get("constitution").addClass("highlight");
sheet.get("intelligence").addClass("highlight");
sheet.get("wisdom").addClass("highlight");
sheet.get("charisma").addClass("highlight");

With groups, this becomes:

const abilityScores = sheet.group("abilities", ["strength", "dexterity", "constitution", "intelligence", "wisdom", "charisma"]);
abilityScores.addClass("highlight");

Creating a Group

To create a group, use the sheet.group() method:

// Create a group with component IDs
const savingThrows = sheet.group("saves", ["strSave", "dexSave", "conSave", "intSave", "wisSave", "chaSave"]);

// Create an empty group and add components later
const skills = sheet.group("allSkills");
skills.add("acrobatics");
skills.add("athletics");
// ...

What can be grouped?

You can group most character sheet components:

  • Input fields (text inputs, number inputs)
  • Labels (display text)
  • Checkboxes
  • Choice (dropdowns/selects)
  • MultiChoice (multiple selection)
  • Toggle (clickable elements with states)
  • Icons
  • Containers (divs, rows, columns)

⚠️ Important: You cannot add a group to another group. Attempting to do so will throw an error.


Component Management Methods

add

group.add(component: string | Component): Group

Adds a component to the group. You can pass either the component ID (as a string) or a component object.

const weapons = sheet.group("weaponGroup");

// Add by component ID
weapons.add("weapon1_name");
weapons.add("weapon1_damage");

// Add by component object
const attackBonus = sheet.get("weapon1_attack");
weapons.add(attackBonus);

When a component is added, the group triggers the add event, followed by an update event.

remove

group.remove(component: string | Component): Group

Removes a component from the group. Like add(), you can pass either the component ID or the component object.

// Remove the attack bonus from our weapon group
weapons.remove("weapon1_attack");

When a component is removed, the group triggers the remove event, followed by an update event.

count

group.count(): number

Returns the number of components currently in the group.

const abilityScores = sheet.group("abilities", ["strength", "dexterity", "constitution", "intelligence", "wisdom", "charisma"]);
log(abilityScores.count()); // Output: 6

knownChildren

group.knownChildren(): Array<Component>

Returns an array containing all the component objects in the group.

const abilities = sheet.group("abilities", ["strength", "dexterity", "constitution"]);

abilities.knownChildren().forEach(function(component) {
    log("Component: " + component.realId() + " = " + component.value());
});
// Output:
// Component: strength = 16
// Component: dexterity = 14
// Component: constitution = 12

includes, contains, has

group.includes(component: string | Component): boolean

group.contains(component: string | Component): boolean

group.has(component: string | Component): boolean

All three methods are equivalent. They check whether a component is part of the group.

const abilities = sheet.group("abilities", ["strength", "dexterity"]);

log(abilities.includes("strength")); // Output: true
log(abilities.has("intelligence"));  // Output: false

get or find

group.get(realId: string): Component | null

group.find(realId: string): Component | null

Both methods are equivalent. They return the component matching the given realId, or null if not found.

const abilities = sheet.group("abilities", ["strength", "dexterity"]);

const strComponent = abilities.find("strength");
log(strComponent.value()); // Output: 16

const unknown = abilities.get("unknown_id");
log(unknown); // Output: null

Value Methods

Groups allow you to get or set the values of all components at once. The values are returned as an object where keys are the component realId and values are the component values.

value

group.value(): Object — Get all component values

group.value(newValues: Object): void — Set multiple component values

Getting values:

const abilities = sheet.group("abilities", ["strength", "dexterity", "constitution"]);

log(abilities.value());
// Output: {
//   "strength": 16,
//   "dexterity": 14,
//   "constitution": 12
// }

Setting values:

// Set all ability scores to specific values
abilities.value({
    "strength": 18,
    "dexterity": 16,
    "constitution": 14
});

This is extremely useful when loading data from a table. For example, loading a monster's stats:

// Assuming you have a "monsters" table with strength, dexterity, constitution columns
const monsterData = Tables.get("monsters").where("id", "goblin").singleValue();

abilities.value({
    "strength": monsterData.strength,
    "dexterity": monsterData.dexterity,
    "constitution": monsterData.constitution
});

virtualValue

group.virtualValue(): Object

group.virtualValue(newValues: Object): void

Same behavior as value(), but uses the component's virtualValue() method. Virtual values are temporary values that don't persist when the sheet is saved.

// Show what the stats would be with a buff, without actually changing them
const buffedStats = {
    "strength": abilities.find("strength").value() + 4,
    "dexterity": abilities.find("dexterity").value(),
    "constitution": abilities.find("constitution").value() + 2
};
abilities.virtualValue(buffedStats);

rawValue

group.rawValue(): Object

Returns the raw values of all components without any transformation. This is the unprocessed value as stored by Let's Role.

log(abilities.rawValue());
// Output: { "strength": "16", "dexterity": "14", "constitution": "12" }

text

group.text(): Object — Get all component texts

group.text(newTexts: Object): void — Set multiple component texts

Gets or sets the display text of all components.

const abilityLabels = sheet.group("abilityLabels", ["strLabel", "dexLabel", "conLabel"]);

// Get all label texts
log(abilityLabels.text());
// Output: { "strLabel": "Strength", "dexLabel": "Dexterity", "conLabel": "Constitution" }

// Set all labels (useful for localization)
abilityLabels.text({
    "strLabel": "Force",
    "dexLabel": "Dextérité",
    "conLabel": "Constitution"
});

Visibility Methods

visible

group.visible(): boolean — Returns true only if ALL components are visible

group.visible(newValue: boolean | Object): void — Set visibility for all or specific components

const secrets = sheet.group("hiddenInfo", ["secretName", "secretLocation"]);

// Check if all components are visible
if (secrets.visible()) {
    log("All secrets are revealed!");
}

// Hide all components
secrets.visible(false);

// Show only specific components
secrets.visible({
    "secretName": true,
    "secretLocation": false
});

// Dynamic visibility based on a checkbox
secrets.visible(sheet.get("revealSecrets"));

show

group.show(): void

Makes all components in the group visible.

// When the player reaches level 5, reveal advanced abilities
sheet.get("characterLevel").on("update", function(levelComponent) {
    if (levelComponent.value() >= 5) {
        advancedAbilities.show();
    }
});

hide

group.hide(): void

Hides all components in the group.

// Hide all spell slots when character is not a spellcaster
const spellSlots = sheet.group("spells", ["slot1", "slot2", "slot3", "slot4", "slot5"]);
if (!isSpellcaster) {
    spellSlots.hide();
}

toggle

group.toggle(): void

Toggles the visibility of all components. If visible, they become hidden; if hidden, they become visible.

// Create a collapsible inventory section
sheet.get("toggleInventoryBtn").on("click", function() {
    inventoryGroup.toggle();
});

CSS Class Methods

addClass

group.addClass(className: string): Group

Adds a CSS class to all components in the group.

// Highlight all damage-related fields when in combat
const damageFields = sheet.group("damage", ["weapon1_damage", "weapon2_damage", "spell_damage"]);
damageFields.addClass("combat-highlight");

removeClass

group.removeClass(className: string): Group

Removes a CSS class from all components in the group.

// Remove highlight when combat ends
damageFields.removeClass("combat-highlight");

toggleClass

group.toggleClass(className: string): Group

Toggles a CSS class on all components. If the class is present, it's removed; if absent, it's added.

// Toggle a "selected" style on all skill components
skills.toggleClass("selected");

hasClass

group.hasClass(className: string): boolean

Returns true only if ALL components in the group have the specified class.

const abilities = sheet.group("abilities", ["strength", "dexterity"]);
abilities.find("strength").addClass("proficient");

log(abilities.hasClass("proficient")); // Output: false (dexterity doesn't have it)

abilities.find("dexterity").addClass("proficient");
log(abilities.hasClass("proficient")); // Output: true (both have it now)

getClasses

group.getClasses(): Array<string>

Returns an array of CSS class names that are present on ALL components in the group.

const abilities = sheet.group("abilities", ["strength", "dexterity", "constitution"]);

// Add "stat-field" to all components, and "highlighted" to only some
abilities.addClass("stat-field");
abilities.find("strength").addClass("highlighted");

log(abilities.getClasses());
// Output: ["stat-field"]  (only classes shared by ALL components)

Other Methods

autoLoadSaveClasses

group.autoLoadSaveClasses(): Group

Enables automatic saving and loading of CSS class changes for all components in the group. This persists class modifications (like added/removed classes) between sessions.

// Remember which ability scores have proficiency markers
abilityScores.autoLoadSaveClasses();

setToolTip

group.setToolTip(text: string, placement?: string): void

Sets a tooltip on all components in the group.

// Add tooltips to all saving throw fields
savingThrows.setToolTip("Roll this saving throw to resist effects");

// With specific placement
savingThrows.setToolTip("Saving Throw", "top");

setChoices

group.setChoices(choices): void

This method exists for API compatibility but does nothing on groups. Use it on individual Choice or MultiChoice components instead.


Identification Methods

id, realId, name

All three methods return the identifier used when creating the group with sheet.group("identifier").

const abilityScores = sheet.group("myAbilities", ["strength", "dexterity"]);

log(abilityScores.id());     // Output: "myAbilities"
log(abilityScores.realId()); // Output: "myAbilities"
log(abilityScores.name());   // Output: "myAbilities"

Events

Groups can trigger and respond to events. They automatically propagate update and click events from their components.

add

Triggered when a component is added to the group.

abilities.on("add", function(group, addedComponent) {
    log("Added: " + addedComponent.realId());
});

remove

Triggered when a component is removed from the group.

abilities.on("remove", function(group, removedComponent) {
    log("Removed: " + removedComponent.realId());
});

update

Triggered when any component in the group is updated (value change), or when a component is added/removed.

// Recalculate ability modifiers whenever any score changes
abilities.on("update", function(group) {
    group.knownChildren().forEach(function(cmp) {
        const score = cmp.value();
        const modifier = Math.floor((score - 10) / 2);
        sheet.get(cmp.realId() + "_mod").value(modifier);
    });
});

click

Triggered when any component in the group is clicked.

// Log which ability was clicked
abilities.on("click", function(group, clickedComponent) {
    log("Player clicked on " + clickedComponent.realId());
});

Using Groups as Data Providers

Since groups are Data Providers, you can use all the powerful data manipulation methods. The data keys are the component realId and values are the component values.

const abilities = sheet.group("abilities", ["strength", "dexterity", "constitution", "intelligence", "wisdom", "charisma"]);

// Get the highest ability score
const best = abilities.max();
log("Best ability: " + best.singleId() + " = " + best.singleValue());

// Get the sum of all ability scores (useful for point-buy validation)
const total = abilities.sum();
log("Total points: " + total);

// Filter abilities with score >= 14
const highScores = abilities.filter(function(value, key) {
    return value >= 14;
});
highScores.each(function(value, key) {
    log(key + " is high: " + value);
});

Complete Example: D&D Character Sheet

Here's a complete example showing how groups can simplify a D&D character sheet:

init = function(sheet) {
    // Group all ability scores
    const abilities = sheet.group("abilities", [
        "strength", "dexterity", "constitution", 
        "intelligence", "wisdom", "charisma"
    ]);
    
    // Group ability modifiers
    const modifiers = sheet.group("modifiers", [
        "str_mod", "dex_mod", "con_mod",
        "int_mod", "wis_mod", "cha_mod"
    ]);
    
    // Group saving throws
    const saves = sheet.group("saves", [
        "str_save", "dex_save", "con_save",
        "int_save", "wis_save", "cha_save"
    ]);
    
    // Calculate all modifiers when any ability changes
    abilities.on("update", function() {
        const values = abilities.value();
        const mods = {};
        
        mods["str_mod"] = Math.floor((values["strength"] - 10) / 2);
        mods["dex_mod"] = Math.floor((values["dexterity"] - 10) / 2);
        mods["con_mod"] = Math.floor((values["constitution"] - 10) / 2);
        mods["int_mod"] = Math.floor((values["intelligence"] - 10) / 2);
        mods["wis_mod"] = Math.floor((values["wisdom"] - 10) / 2);
        mods["cha_mod"] = Math.floor((values["charisma"] - 10) / 2);
        
        modifiers.value(mods);
    });
    
    // Highlight proficient saves
    sheet.get("proficientSaves").on("update", function(cmp) {
        const proficient = cmp.value(); // Array of proficient save IDs
        
        saves.knownChildren().forEach(function(save) {
            if (proficient.includes(save.realId())) {
                save.addClass("proficient");
            } else {
                save.removeClass("proficient");
            }
        });
    });
    
    // Quick preset: apply a monster template
    sheet.get("applyGoblinStats").on("click", function() {
        abilities.value({
            "strength": 8,
            "dexterity": 14,
            "constitution": 10,
            "intelligence": 10,
            "wisdom": 8,
            "charisma": 8
        });
    });
    
    // Toggle visibility of spellcasting section
    const spellSection = sheet.group("spellcasting", [
        "spellcastingAbility", "spellSaveDC", "spellAttackBonus",
        "spellSlots1", "spellSlots2", "spellSlots3"
    ]);
    
    sheet.get("isSpellcaster").on("update", function(cmp) {
        if (cmp.value()) {
            spellSection.show();
        } else {
            spellSection.hide();
        }
    });
};

Non-consistent Methods

The following methods are available for API compatibility but behave differently for groups than for regular components:

Method Behavior for Groups
parent() Always returns the Sheet that created the group
sheet() Always returns the Sheet that created the group
repeater() Always returns undefined
entry() Always returns undefined
exists() Always returns true
setChoices() Does nothing
valueProvider() Returns undefined
dataProvider() Returns undefined
index() Always returns null
valueData() Always returns null
⚠️ **GitHub.com Fallback** ⚠️