Roll20 uses cookies to improve your experience on our site. Cookies enable you to enjoy certain features, social sharing functionality, and tailor message and display ads to your interests on our site and others. They also help us understand how our site is being used. By continuing to use our site, you consent to our use of cookies. Update your cookie preferences .
×
Create a free account

Using setSectionOrder to reorder repeating field.

Good Afternoon, I am trying to make a few buttons that will reorder a repeating field used for storing spells using sheetworkers. The intended functionality is to be able to click a button to reorder alphabetically, by mana cost, or by school. Once I have it working, I will expand it outward to work for other spell tiers. While I try to write some of my own code, this was a little above my current skill level, so I have been going back and forth with chatGPT. I realize that AI has limitations, especially as related to roll20, but I find it tends to be useful for specific javascript functionality in a limited context. In this case, after correcting for several errors, the code almost seems to work, based on the final console.log output. But, it doesn't actually implement the change. What I eventually reached is that maybe setSectionOrder may not actually work with roll20 (which of course the AI did not realize initially, but eventually said). Is there an alternative method for this, or am I largely out of luck on this one? The code is below. As an aside, I can consider using an API based solution instead, if that is all that is available. However, due to unrelated issues, I am unable to use the API at present, so that is more of a long term solution. Relevant HTML Code: <!-- BASIC SPELLS --> <div class="sheet-section-spellsbasic"> <h2 style="text-align: center">Basic Spells</h2> <br/> <!-- BASIC SPELLS 2COLROW --> <div class="sheet-2colrow"> <!-- BASIC SPELLS LEFT COLUMN --> <div class="sheet-col"> <fieldset class="repeating_spellsbasic"> <table class="proficiency_table"> <thead> <tr> <th> Prep </th> <th> Bon </th> <th> Sig </th> <th> School </th> <th> Cost </th> <th> Roll </th> </tr> </thead> <tr> <td> <input type="checkbox" class="combat_text_input" name="attr_prepared" value="1" title="Check this if this spell is prepared. This must be checked for this spell to appear on your spell list in combat. @{prepared}" /> </td> <td> <input type="checkbox" class="combat_text_input" name="attr_bonus" value="1" title="Check this if this spell does not count against your total number of prepared spells. @{bonus}" /> </td> <td> <input type="checkbox" class="combat_text_input" name="attr_signature" value="1" title="Check this if this spell is a signature spell. @{signature}" /> </td> <td> <input type="text" class="proficiency_input" name="attr_school" value="0" title="What school this spell belongs to. @{school}" readonly /> </td> <td> <input type="text" class="proficiency_input" name="attr_mana_cost" value="0" title="The mana cost of this spell. @{mana_cost}" readonly /> </td> <td> <button type="roll" name="roll_castspellbutton" value="@{link}" title="Click this button to cast this spell. You will still need to select your character (in all cases) and the target (when necessary) for this to work. @{castspellbutton}"></button> </td> </tr> <tr> <td colspan=6> <input type="text" class="proficiency_input" name="attr_input" value="Spell Name" title="Place the spell name here." /> </td> </tr> <tr hidden > <td colspan=6> <input type="text" class="proficiency_input" name="attr_feedback" value="No feedback generated yet." title="The status of your last attempt to enter this feature. @{feedback}" readonly /> </td> </tr> <tr hidden > <td colspan=6> <input type="text" class="proficiency_input" name="attr_link" value="Spell Link" title="A link to the spell should be here." readonly /> </td> </tr> </table> <br/> </fieldset> </div> <!-- END OF BASIC SPELLS LEFT COLUMN style="display: none;" --> <!-- BASIC SPELLS RIGHT COLUMN --> <div class="sheet-col"> <table class="proficiency_table"> <thead> <tr> <th> Known </th> <th> Ready </th> <th> Prepared </th> <th> Roll </th> </tr> </thead> <tr> <td> <input class="skill_input" type="text" name="attr_known_basic_spells_count" value="0" title="How many basic spells you currently know. @{known_basic_spells_count}" readonly /> </td> <td> <input class="skill_input" type="text" name="attr_ready_basic_spells_count" value="0" title="How many basic spells you currently have prepared. @{ready_basic_spells_count}" readonly /> </td> <td> <input class="skill_input" type="text" name="attr_prepared_basic_spells_count" value="0" title="How many tier basic you currently have prepared. @{prepared_basic_spells_count}" readonly /> </td> <td> <button type="roll" name="roll_castspellbasicbutton" value="/w gm &{template:spell} {{name=@{selected|token_name}'s Basic Spells}} {{description=@{selected|basic_spells}}}" title="Click this button to roll all basic spells to chat. You will still need to select your character (in all cases) and the target (when necessary) for this to work. @{castspellbasicbutton}"></button> </td> </tr> </table> <textarea name="attr_basic_spells" value="Class Features" title="@{basic_spells}" style="width:98%; resize:vertical; height:34ex;" readonly ></textarea> <br/> <br/> <h3 style="text-align: center">Sort Basic Spells</h3> <br/> <table class="proficiency_table"> <thead> <tr> <th> Alphabetic </th> <th> Mana Cost </th> <th> School </th> </tr> </thead> <tr> <td> <button type="action" name="act_alphabeticsortspellbasicbutton" title="Click this button to alphabetize your basic spells. @{alphabeticsortspellbasicbutton}"></button> </td> <td> <button type="action" name="act_manasortspellbasicbutton" title="Click this button to sort your basic spells by mana cost. Variable spells will be listed together. Spells with the same cost will be alphabetized within that cost. @{manasortspellbasicbutton}"></button> </td> <td> <button type="action" name="act_schoolsortspellbasicbutton" title="Click this button to sort your basic spells by spell school. Spells with the same school will be alphabetized within that school. @{schoolsortspellbasicbutton}"></button> </td> </tr> </table> <br/> <br/> </div> <!-- END OF BASIC SPELLS RIGHT COLUMN --> </div> <!-- END OF BASIC SPELLS 2COLROW --> </div> <!-- END OF BASIC SPELLS --> Sorting Basic Spells Sheetworker: /* Sort Basic Spells Sheetworker */     on("clicked:alphabeticsortspellbasicbutton clicked:manasortspellbasicbutton clicked:schoolsortspellbasicbutton", function(eventInfo) { const buttonClicked = eventInfo.triggerName; const sectionName = "repeating_spellsbasic"; // <-- adjust if needed console.log(`Basic spell sort triggered: ${buttonClicked}`); getSectionIDs(sectionName, function(ids) { if (!ids.length) { console.log("No repeating entries found."); return; } // Build a list of all attributes to fetch const attrNames = []; ids.forEach(id => { attrNames.push(`${sectionName}_${id}_spellname`); attrNames.push(`${sectionName}_${id}_manacost`); attrNames.push(`${sectionName}_${id}_school`); }); // Fetch data getAttrs(attrNames, function(values) { const spells = ids.map(id => ({ id, name: (values[`${sectionName}_${id}_spellname`] || "").trim(), mana: (values[`${sectionName}_${id}_manacost`] || "").trim(), school: (values[`${sectionName}_${id}_school`] || "").trim() })); // Sorting logic if (buttonClicked === "clicked:alphabeticsortspellbasicbutton") { spells.sort((a, b) => a.name.localeCompare(b.name, undefined, { sensitivity: "base" })); } else if (buttonClicked === "clicked:manasortspellbasicbutton") { spells.sort((a, b) => { // Extract leading number if present const numA = parseInt(a.mana); const numB = parseInt(b.mana); // Handle "Variable" or non-numeric cases const isNumA = !isNaN(numA); const isNumB = !isNaN(numB); if (isNumA && isNumB) return numA - numB || a.name.localeCompare(b.name, undefined, { sensitivity: "base" }); if (isNumA && !isNumB) return -1; // numbers before non-numeric if (!isNumA && isNumB) return 1; // non-numeric after numbers return a.mana.localeCompare(b.mana, undefined, { sensitivity: "base" }) || a.name.localeCompare(b.name, undefined, { sensitivity: "base" }); }); } else if (buttonClicked === "clicked:schoolsortspellbasicbutton") { spells.sort((a, b) => a.school.localeCompare(b.school, undefined, { sensitivity: "base" }) || a.name.localeCompare(b.name, undefined, { sensitivity: "base" })); } // Apply the new order const order = spells.map(s => `${sectionName}_${s.id}`); setSectionOrder(sectionName, order); console.log(`Reordered ${sectionName}:`, order); }); }); }); /* End of Sort Basic Spells Sheetworker */
1761522836
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
the order that setSectionOrder takes is just the IDs, not the ids with the sectionName prefix. Also, the section name that is passed must not have the repeating_  prefix on it. See the wiki for details on how setSectionOrder works . So, you can just pass your spells variable in instead of doing the map to order and define a different version of sectionName to use with setSectionOrder. With regards to AI, the reason that those of us that know how to do stuff in R20 are constantly saying not to use it is that it consistently leads users down bad routes like this that are actively against the documentation. I have tried using AI to do basic R20 sheet tasks, and even with me knowing how everything should work, it is not able to work through the code correctly.
Thank you very much. I made the changes you said, and was able to find a couple more errors the AI made in the code. After correcting for those as well, I was able to get the code working. I greatly appreciate the assistance. In case it is of use to anyone down the line, here is the working code. The HTML is unchanged from the original post. /* Sort Basic Spells Sheetworker */ on("clicked:alphabeticsortspellbasicbutton clicked:manasortspellbasicbutton clicked:schoolsortspellbasicbutton", function(eventInfo) { const buttonClicked = eventInfo.triggerName; const sectionName = "repeating_spellsbasic"; // <-- adjust if needed console.log(`Basic spell sort triggered: ${buttonClicked}`); getSectionIDs(sectionName, function(ids) { if (!ids.length) { console.log("No repeating entries found."); return; } // Build a list of all attributes to fetch const attrNames = []; ids.forEach(id => { attrNames.push(`${sectionName}_${id}_input`); attrNames.push(`${sectionName}_${id}_mana_cost`); attrNames.push(`${sectionName}_${id}_school`); }); // Fetch data getAttrs(attrNames, function(values) { const spells = ids.map(id => ({ id, name: (values[`${sectionName}_${id}_input`] || "").trim(), mana: (values[`${sectionName}_${id}_mana_cost`] || "").trim(), school: (values[`${sectionName}_${id}_school`] || "").trim() })); // Sorting logic if (buttonClicked === "clicked:alphabeticsortspellbasicbutton") { spells.sort((a, b) => a.name.localeCompare(b.name, undefined, { sensitivity: "base" })); } else if (buttonClicked === "clicked:manasortspellbasicbutton") { spells.sort((a, b) => { // Extract leading number if present const numA = parseInt(a.mana); const numB = parseInt(b.mana); // Handle "Variable" or non-numeric cases const isNumA = !isNaN(numA); const isNumB = !isNaN(numB); if (isNumA && isNumB) return numA - numB || a.name.localeCompare(b.name, undefined, { sensitivity: "base" }); if (isNumA && !isNumB) return -1; // numbers before non-numeric if (!isNumA && isNumB) return 1; // non-numeric after numbers return a.mana.localeCompare(b.mana, undefined, { sensitivity: "base" }) || a.name.localeCompare(b.name, undefined, { sensitivity: "base" }); }); } else if (buttonClicked === "clicked:schoolsortspellbasicbutton") { spells.sort((a, b) => a.school.localeCompare(b.school, undefined, { sensitivity: "base" }) || a.name.localeCompare(b.name, undefined, { sensitivity: "base" })); } // Apply the new order const order = spells.map(s => `${s.id}`); setSectionOrder("spellsbasic", order); console.log(`Reordered ${sectionName}:`, spells); }); }); }); /* End of Sort Basic Spells Sheetworker */