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

How to Move Checked Items to the Top of a Repeating Fieldset?

March 06 (3 weeks ago)

Edited March 06 (3 weeks ago)
Joel
Pro

I’m trying to reorder a repeating section (repeating_weapons) whenever a checkbox (attr_weapon_checkbox) is checked or unchecked. The goal is simple:

  • Checked items should move to the top.
  • Unchecked items should stay below.

Since I don't know javascript I've enlisted some AI help but we're constantly running into a problem. I've incorporated the script into the sheet's HTML in the <script> section. However, the console logs confirm that:

  • The script detects checkboxes correctly.
  • The items are sorted in the correct order.
  • But setSectionOrder("repeating_weapons", sortedArray); does not visually update the list—items remain in the same order as before.

Here is the most basic version of the script, before we tried about a dozen ways to trick/force it to work:

on("change:repeating_weapons:weapon_checkbox remove:repeating_weapons", function() {
    getSectionIDs("repeating_weapons", function(idArray) {
        let attributes = idArray.map(id => `repeating_weapons_${id}_weapon_checkbox`);
        getAttrs(attributes, function(values) {
            let checkboxState = {};
            idArray.forEach(id => {
                checkboxState[id] = values[`repeating_weapons_${id}_weapon_checkbox`] === "on" ? 1 : 0;
            });
            console.log("Checkbox states:", checkboxState);
            let sortedArray = idArray.sort((a, b) => checkboxState[b] - checkboxState[a]);
            console.log("Weapon IDs after sorting:", sortedArray);
            setSectionOrder("repeating_weapons", sortedArray);
        });
    });
});

I also see the console error:
"Character Sheet Error: Trying to do setSectionOrder when no character is active in sandbox."

My questions:

  1. Does setSectionOrder() require something specific to work?
  2. Is Roll20 blocking setSectionOrder() due to a "pre-defined key order" error?
  3. Is there another way to move checked items to the top of the list?

Any guidance is appreciated! Thanks in advance. 

And yes I understand AI is prone to errors or hallucinations, but it's what I have to work with without bothering nice humans like you all the time. :) I'm just trying to do the best I can without constantly posting in the forum for help, so if there are glaring errors in the code, I apologize. Again, thank you very much for your help and patience. 

March 06 (3 weeks ago)
vÍnce
Pro
Sheet Author

Hi Joel,
Are you wanting to actually re-order (ie visually) the repeating rows of a section or...?  Probably not what you want, but it would be easier to hide/show or otherwise style the repeating rows based on the checkbox using CSS.  Sorting would probably require re-creating all rows each time a row was added/removed or whenever the checkbox was toggled.  Maybe this could be combined with using duplicate repeating sections and only showing one version based on the checkbox?  Sorry, just thinking out loud.
I've never used setSectionOrder so I can't really suggest anything directly,
but GiGs has written up some useful info about it here: https://cybersphere.me/ordering-repeating-sections/


March 06 (3 weeks ago)

Edited March 09 (3 weeks ago)
GiGs
Pro
Sheet Author
API Scripter

Using AI is a bad idea for roll20 - AI depends on vast numbers of examples of existing code, which is fine for general HTML and JavaScript, but roll20 uses a different version of each and there will never be enough code examples for AI to construct code. It will sometimes  look correct, but will have subtle errors that take more tie fixing than just writing the code from scratch.


On to your specific problem: this is something that can be done on rol20, but the method is clunky. Here's the sequence you need to follow (all in javascritpt):

  1. Start with on(change: for the checkboxes.
  2. Copy the contents of the existing repeating section
  3. Delete the contents of the repeating section
  4. Recreate the repeating section, creating those you want to appear at the top first (and making sure any that should be checked are, and others aren't).

Roll20 does not use HTML where fieldsets are concerned - it uses its own code. setSectionOrder is a frustrating function, and does not do what you are trying to do.

March 07 (3 weeks ago)
GiGs
Pro
Sheet Author
API Scripter

Thank you, Vince. I forgot I'd written that post.

March 07 (3 weeks ago)
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator

Setsectionorder was fixed last year, so just reordering the section should work fine. Are you getting the no active character sheet error with the code you posted?

March 07 (3 weeks ago)
GiGs
Pro
Sheet Author
API Scripter


Scott C. said:

Setsectionorder was fixed last year

That's interesting, I may have to play around with it.


March 11 (3 weeks ago)

Edited March 11 (3 weeks ago)
Joel
Pro

Sorry for the delay, I was away for a bit. Thank you very much for your replies. I am aware that the AI makes people-who-can-do-this-themselves cringe, but as I said, it's all I have to help me parse through this without writing dozens of forum posts a day. It is extremely helpful probably 90% of the time, and can be coaxed by me to figure out more complicated stuff at least half the time, so hopefully it's preventing me from writing 95% of my potential posts, lol. I don't say this to ignite a debate about using AI, but rather to explain that when I answer your questions, I'm still relying on the AI to help me with the answers. Not because I am unaware of your concerns, but because I have few other resources to try to explain what seems to be going on.

To address another comment, hiding or diminishing the unchecked rows with css is not really the goal. The goal was simply to include a little script in the HTML that would notice if the user clicked a checkbox next to a weapon in the repeating_weapons section, it would automatically move that row to the top of the list, and move everything else down by one. Unchecking a box probably doesn't need to do anything at all. After each positive/yes/true check of a checkbox, the corresponding row would be moved to the top of the order, and that's it. If a new weapon is subsequently checked, then it would move to the top, pushing all the others down. (For people wanting to know why, it is because the players have tons and tons of weapons from many different technology levels, law levels, cultures, and so on, but they're only allowed to bring a few on any particular adventure, subject to the adventure's conditions. At the start of each adventure, they would be able to go through their huge list of weapons, click the checkmark for any weapon they intend to bring, which would move the weapon to the top of the list for this adventure without them having to manually drag them to the top past sometimes 20+ or 30+ other weapons. With just a click it could appear at the top, the player could do that three times for the three weapons they want to bring on this adventure, all the other weapons would be moved down. Bonus, over time the weapons they use the most are going to naturally congregate at the top. Easy peasy, if it actually worked.)

Key Findings According to My Robot Overlord

1️⃣ setSectionOrder() executes, but the order is overridden by Roll20’s "pre-defined key order".

  • The script detects and correctly rearranges the order.
  • However, the list remains in the original order after execution.
  • Even when we introduce a delayed setSectionOrder(), the change still does not persist.

2️⃣ We sometimes see: "Character Sheet Error: Trying to do setSectionOrder when no character is active in sandbox."

  • Not every test triggers this error, but it only appears sometimes.
  • It happens when Roll20 does not detect an active character (which may explain why setSectionOrder() is ignored).
  • However, even when this error doesn’t appear, setSectionOrder() still fails to reorder items.

3️⃣ Adding a delay does NOT fix the problem.

  • Even when we run setSectionOrder() a second time after a 3-second delay, the pre-defined key order still persists.
  • This means Roll20 isn't just applying its own order once—it actively enforces it.

4️⃣ When we remove items, Roll20 correctly detects the change, but it still does not allow reordering.

  • Testing to determine if deleting an item would force a reorder.
  • Deleting an item does not reset the enforced key order.
  • Even after an item is removed and the script runs, the "pre-defined key order" log still appears.

Possible Explanations for the Issue

  1. Roll20 has hardcoded restrictions on setSectionOrder() for certain repeating sections.

    • It might only work for newly created sections but not for sections that already have a saved order.
    • Since we see "Found a pre-defined key order!", it suggests Roll20 locks the order and does not allow changes via sheet workers.
  2. setSectionOrder() might require a "character context" to work.

    • The "Character Sheet Error" appears sometimes, meaning Roll20 might be ignoring setSectionOrder() unless a character is active.
    • However, even when there’s no error, reordering still fails, so this is not the only problem.
  3. Roll20 may not allow setSectionOrder() outside of the API mod scripts.

    • setSectionOrder() is supposed to work in sheet workers, but perhaps it was never fully implemented or stopped working in a recent update.
    • We should confirm with Roll20 devs if this function still works in sheet workers or if it’s restricted to API scripts only.
March 11 (3 weeks ago)

Edited March 11 (3 weeks ago)
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator

The issue is what you are passing to setSectionOrder. It needs the section name without the repeating_ prefix. e.g.:

setSectionOrder("weapons", sortedArray);

This is noted in the wiki article about setSectionOrder. I've also edited the wiki entry to remove the warning about it not working.

The "Character Sheet Error: Trying to do setSectionOrder when no character is active in sandbox." error is from your tests with the deferment. Sheetworkers do not work with any sort of asynchronicity (e.g. setTimeout, async/await) as it breaks the connection between the callstack and the active character sheet; the sole exception is startRoll, which allows you to await it's result without breaking the connection.

March 11 (3 weeks ago)
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator

I also want to note, that when GiGs and I say we recommend not using AI, we are not just being luddites. As an example of why not to use AI, the entirety of the troubleshooting steps it had you do for the setSectionOrder issue are things that cannot have caused the issue and/or are things you actively cannot do in Roll20.

March 11 (3 weeks ago)
GiGs
Pro
Sheet Author
API Scripter


Joel said:

I am aware that the AI makes people-who-can-do-this-themselves cringe, but as I said, it's all I have to help me parse through this without writing dozens of forum posts a day.

Scott has already responded to this but I also want to throw my oar in. My comments above were not about AI - they were aboutt Roll20. You will not get a solution for your problem on Roll20 using AI. If you read my comment again, you should see that I said that AI can help with HTML and JavaScript - but on Roll20, it will steer you wrong. I explained why you should not not use it here.
March 12 (2 weeks ago)
Joel
Pro

Thank you. To clarify (I often have difficulty communicating effectively) I do not at all think you are being luddites or that you are wrong. I was only trying to say that it is the only tool I have to help me do this, other than to pester you all with all my myriad ideas, and I didn't want to pass myself off as if I personally am writing all this script by hand, or as if I can answer all of your questions using my own knowledge, when in fact I have been reliant on the AI to help me. That's all, and I do not doubt that you know more about how roll20 works. I appreciate your help and insights more than you can know. I feel bad asking someone to write code for me from whole cloth.

So based on the excellent advice you have given, I now have it correctly detecting that a checkbox has been ticked to "on" (true/1), it is able to extract the selected row ID from the sourceAttribute, and it moves the newly ticked row to the top (more or less). Already-ticked rows remain ticked and at/near the top (excellent), while unticked rows remain below all the ticked rows (excellent). That is nearly working as intended, the problem I have been trying to solve for several hours now is why the reordering within groups of ticked and unticked seems unexpected. Ticked rows might go to the very top of the list or might go to the bottom of the other ticked rows. Unticked rows do stay below all the ticked ones as intended, but they get reordered oddly instead of merely being pushed down one row. It is difficult to find a pattern. I wonder if it is applying something like alphabetical order based on row ID or something, clumping them into the two groups but unnecessarily reordering them within their own groups.

For example, I will start with the weapons ordered Pistol 1, Pistol 2, Pistol 3, Pistol 4, and Pistol 5. I will tick "on" Pistol 3, and the expected order should be 3 (ticked), 1, 2, 4, 5 (so #3 rises to the top, all the others get pushed down but otherwise in the same order they had previously been in). But what I actually get is 3 (ticked), 4, 5, 2, 1. This weirdness is repeatable whenever no rows are already checked. If I start with 1, 2, 3, 4, 5 and tick row #4, I would expect the new order to be 4 (ticked), 1, 2, 3, 5 but instead I get 4 (ticked), 3, 5, 2, 1. If I untick and reorder all the weapons, close the sheet and refresh the roll20 game, when I click #4 again I get the exact same results (so they're not entirely random). If I leave a row ticked and at the top, when I tick a new row it will move up to the top, but whether it gets moved to the very top of the list or merely above the unticked rows is a gamble. I'm sure there is a pattern but I can't find it. 

The code below is what I am using, I am sure it is not up to snuff but it's where I am at. If you're able to see where it has gone awry, I'd be very grateful, as always. 

on("change:repeating_weapons:weapon_selected", function(eventInfo) {
    getAttrs([eventInfo.sourceAttribute], function(values) {
        console.log("Checkbox Changed - Raw Value:", values[eventInfo.sourceAttribute]);

        if (values[eventInfo.sourceAttribute] !== "on") {
            console.log("Checkbox was unchecked; no reordering needed.");
            return;
        }

        // Extract the row ID manually
        let sourceSectionID = eventInfo.sourceAttribute.split("_")[2];

        console.log("Checkbox checked for:", sourceSectionID); // Debugging

        getSectionIDs("weapons", function(idArray) {
            console.log("Original Weapon Order:", idArray);

            // Get checked state for all rows
            let attrNames = idArray.map(id => `repeating_weapons_${id}_weapon_selected`);

            getAttrs(attrNames, function(allValues) {
                let weaponData = idArray.map(id => ({
                    id: id,
                    checked: allValues[`repeating_weapons_${id}_weapon_selected`] === "on"
                }));

                // Sort: Checked items first, but maintain relative order
                let sortedArray = weaponData
                    .sort((a, b) => b.checked - a.checked) // Move checked rows up
                    .map(w => w.id); // Extract only the IDs

                console.log("New Weapon Order:", sortedArray);
                setSectionOrder("weapons", sortedArray);
            });
        });
    });
});

I did try dozens of fixes, some of them were improvements but none of them quite worked so I won't bore you with them all. I did find that changing sorting of b.checked - a.checked to a more complex method, variations on something like this:

let sortedArray = [
    sourceSectionID,  // Always put the newly checked row at the top
    ...checkedRows.filter(id => id !== sourceSectionID), // Keep previously checked rows below
    ...idArray.filter(id => !checkedRows.includes(id)) // Keep unchecked rows in original order
];

made it more predictable but still not quite right, it was still reordering rows that should have essentially just been pushed down.

Having everything reorder instead of merely being pushed down the order seems like unwanted behavior that hopefully we can stop. My goal now is to move a newly ticked row to the bottom of any already-ticked rows (i.e. the top of the unticked rows). Each newly ticked row would then appear under any already ticked rows, and all unticked rows would maintain their current relative order below those, merely being pushed down, rather than further reordering themselves as they currently are doing. 

Thank you in advance for any insights. 


March 12 (2 weeks ago)

Edited March 12 (2 weeks ago)
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator

So, what is happening is that the idArray returned by getSectionIDs is always in lexographic order (aka alphanumeric), which is essentially the same as order of creation because of how the row IDs are generated. Your sorting code is only checking the checked state, and is returning 1, 0, or -1 depending on the check states of the two rows at each pass of the sort. This means that when it reorders things, it will essentially order by check state, and then by creation time. If you want it to still respect any custom ordering that the user has done, you'll need to get the _reporder_repeating_weapons and turn the lexographically ordered idArray into an array that is sorted according to the existing sort. Here's a function to do that, adapted from my K-scaffold sheet framework:

/**
 * Orders a single ID array.
 * @param {string[]} repOrder - Array of IDs in the order they are in on the sheet, typically from the corresponding _repOrder attribute.
 * @param {string[]} IDs - Array of IDs to be ordered. Aka the default ID Array passed to the getSectionIDs callback
 * @param {object} [attributes] - the object containing your results from getAttrs
 * @returns {string[]} - The ordered id array
 */
const orderSection = function(repOrder,IDs=[], attributes){
  const attributeKeys = Object.keys(attributes);
  // create an array with the custom ordered IDs at the front followed by the unordered IDs in the back
  const idArr = [...repOrder.filter(v => v),...IDs.filter(id => !repOrder.includes(id.toLowerCase()))]
    // filter this array of IDs to remove old IDs from deleted rows that stay in the _reporder attribute
    .filter(id => {
      const exists = attributeKeys.some(k => k.includes(id));
      return exists;
    });
  return idArr;
};

To use the function above, you'll need to slightly modify your script:

on("change:repeating_weapons:weapon_selected", function(eventInfo) {
    getAttrs([eventInfo.sourceAttribute], function(values) {
        console.log("Checkbox Changed - Raw Value:", values[eventInfo.sourceAttribute]);

        if (values[eventInfo.sourceAttribute] !== "on") {
            console.log("Checkbox was unchecked; no reordering needed.");
            return;
        }

        // Extract the row ID manually
        let sourceSectionID = eventInfo.sourceAttribute.split("_")[2];

        console.log("Checkbox checked for:", sourceSectionID); // Debugging

        getSectionIDs("weapons", function(idArray) {
            console.log("Original Weapon Order:", idArray);

            // Get checked state for all rows
            let attrNames = idArray.map(id => `repeating_weapons_${id}_weapon_selected`);
            attrNames.push('_reporder_repeating_weapons'); //add the reporder for the section to your getArray
            getAttrs(attrNames, function(allValues) {
                const repOrder = allValues._reporder_repeating_weapons.split(/\s*,\s*/);//make an array of the reordered IDs
                const orderedIDs = orderSection(repOrder,idArray,allValues);
                let weaponData = orderedIDs.map(id => ({
id: id, checked: allValues[`repeating_weapons_${id}_weapon_selected`] === "on" })); // Sort: Checked items first, but maintain relative order let sortedArray = weaponData .sort((a, b) => b.checked - a.checked) // Move checked rows up .map(w => w.id); // Extract only the IDs console.log("New Weapon Order:", sortedArray); setSectionOrder("weapons", sortedArray); }); }); }); });

That should make it so that it respects the existing order of items for things that are not moved. It should also put any newly checked items at the bottom of the list of checked items.

March 13 (2 weeks ago)

Edited March 13 (2 weeks ago)
Joel
Pro

Thank you for all that. Unfortunately it behaves pretty much like the original, the items are still reordered strangely, where it does group the ticked and unticked rows in separate groups, with the ticked ones on top, but it still re-sorts both the ticked and unticked rows within their respective groups. I presume it's still following its lexographic order.

This is exactly how I have it in the HTML, I am running no API scripts from the roll20 sandbox. 

    <script type="text/worker">

/**
 * Orders a single ID array.
 * @param {string[]} repOrder - Array of IDs in the order they are in on the sheet, typically from the corresponding _repOrder attribute.
 * @param {string[]} IDs - Array of IDs to be ordered. Aka the default ID Array passed to the getSectionIDs callback
 * @param {object} [attributes] - the object containing your results from getAttrs
 * @returns {string[]} - The ordered id array
 */
const orderSection = function(repOrder,IDs=[], attributes){
  const attributeKeys = Object.keys(attributes);
  // create an array with the custom ordered IDs at the front followed by the unordered IDs in the back
  const idArr = [...repOrder.filter(v => v),...IDs.filter(id => !repOrder.includes(id.toLowerCase()))]
    // filter this array of IDs to remove old IDs from deleted rows that stay in the _reporder attribute
    .filter(id => {
      const exists = attributeKeys.some(k => k.includes(id));
      return exists;
    });
  return idArr;
};

on("change:repeating_weapons:weapon_selected", function(eventInfo) {
    getAttrs([eventInfo.sourceAttribute], function(values) {
        console.log("Checkbox Changed - Raw Value:", values[eventInfo.sourceAttribute]);

        if (values[eventInfo.sourceAttribute] !== "on") {
            console.log("Checkbox was unchecked; no reordering needed.");
            return;
        }

        // Extract the row ID manually
        let sourceSectionID = eventInfo.sourceAttribute.split("_")[2];

        console.log("Checkbox checked for:", sourceSectionID); // Debugging

        getSectionIDs("weapons", function(idArray) {
            console.log("Original Weapon Order:", idArray);

            // Get checked state for all rows
            let attrNames = idArray.map(id => `repeating_weapons_${id}_weapon_selected`);
            attrNames.push('_reporder_repeating_weapons'); //add the reporder for the section to your getArray
            getAttrs(attrNames, function(allValues) {
                const repOrder = allValues._reporder_repeating_weapons.split(/\s*,\s*/);//make an array of the reordered IDs
                const orderedIDs = orderSection(repOrder,idArray,allValues);
                let weaponData = orderedIDs.map(id => ({
                    id: id,
                    checked: allValues[`repeating_weapons_${id}_weapon_selected`] === "on"
                }));
                // Sort: Checked items first, but maintain relative order
                let sortedArray = weaponData
                    .sort((a, b) => b.checked - a.checked) // Move checked rows up
                    .map(w => w.id); // Extract only the IDs

                console.log("New Weapon Order:", sortedArray);
                setSectionOrder("weapons", sortedArray);
            });
        });
    });
});

and then the rest of the scripts for the sheet follow from there.

March 13 (2 weeks ago)
Joel
Pro

I also realized something that I should correct, maybe it will make this easier. I used to think that unticking a row would not really need to do anything, but while testing this I've realized that was shortsighted. If I have a few weapons ticked and they're all at the top (even though the order may appear strange, at least they're all at the top), when I untick a weapon it is not just going to drop back below the ticked ones, it still sits at the top wherever it already was in the order until another weapon is ticked "on" and that triggers a reordering. It's not that big of a deal, someone could just tick a weapon off and on again to re-sort the list, but if we're tweaking this anyway, maybe it's worth exploring whether a newly unticked row can also trigger a reordering, dropping the newly unticked row below the ones that are still ticked. Question mark?

March 13 (2 weeks ago)
Joel
Pro

Removing just the bit that was checking to see if a box was unchecked and then ignoring it has fixed the unticking issue. I know that originally I thought that was the behavior I wanted, but as things evolved I now realize it would be fine to simply re-order the list any time a box is ticked or unticked. So I removed this bit....

       if (values[eventInfo.sourceAttribute] !== "on") {
            console.log("Checkbox was unchecked; no reordering needed.");
            return;
        }

Now whenever a row is ticked or unticked, it re-sorts the list to put only ticked rows at the top, all the unticked rows go below. I'm not sure if my deletion is the proper way, there might be a much better way, but I wanted to try. If only we could preserve the relative order so everything is just pushed down below the ticked rows instead of re-sorting them lexographically. 

March 13 (2 weeks ago)
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator

yep, removing that if was the proper call, it was stopping the function from running if the value wasn't "on"

March 13 (2 weeks ago)

Edited March 13 (2 weeks ago)
Joel
Pro

Cool, thanks. Any thoughts on why it is still re-ordering weirdly? If you missed it, I described it a couple of posts up... 

https://app.roll20.net/forum/permalink/12266834/

March 13 (2 weeks ago)
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator

Ah! I forgot that repOrder stores the IDs with casing, but idArray has them all in lowerCase. This meant the orderSections wasn't able to properly compare the repOrder array and the idArray. Need to add .toLowerCase() before the split on repOrder. Additionally, I'd recommend not doing your map to weaponData as it is adding an extra recursion that isn't needed, instead just compare the values directly in the sort:

/**
 * Orders a single ID array.
 * @param {string[]} repOrder - Array of IDs in the order they are in on the sheet, typically from the corresponding _repOrder attribute.
 * @param {string[]} IDs - Array of IDs to be ordered. Aka the default ID Array passed to the getSectionIDs callback
 * @param {object} [attributes] - the object containing your results from getAttrs
 * @returns {string[]} - The ordered id array
 */
const orderSection = function(repOrder,IDs=[], attributes){
  const attributeKeys = Object.keys(attributes);
  // create an array with the custom ordered IDs at the front followed by the unordered IDs in the back
  const idArr = [...repOrder.filter(v => v),...IDs.filter(id => !repOrder.includes(id.toLowerCase()))]
    // filter this array of IDs to remove old IDs from deleted rows that stay in the _reporder attribute
    .filter(id => {
      const exists = attributeKeys.some(k => k.includes(id));
      return exists;
    });
  return idArr;
};

on("change:repeating_weapons:weapon_selected", function(eventInfo) {
    getAttrs([eventInfo.sourceAttribute], function(values) {
        console.log("Checkbox Changed - Raw Value:", values[eventInfo.sourceAttribute]);

        // Extract the row ID manually
        let sourceSectionID = eventInfo.sourceAttribute.split("_")[2];

        console.log("Checkbox checked for:", sourceSectionID); // Debugging

        getSectionIDs("weapons", function(idArray) {
            console.log("Original Weapon Order:", idArray);

            // Get checked state for all rows
            let attrNames = idArray.map(id => `repeating_weapons_${id}_weapon_selected`);
            attrNames.push('_reporder_repeating_weapons'); //add the reporder for the section to your getArray
            getAttrs(attrNames, function(allValues) {
                const repOrder = allValues._reporder_repeating_weapons.toLowerCase().split(/\s*,\s*/);//make an array of the reordered IDs
                const orderedIDs = orderSection(repOrder,idArray,allValues);
                // Sort: Checked items first, but maintain relative order
                let sortedArray = orderedIDs.toSorted((a,b) => {
                    const aName = `repeating_weapons_${a}_weapon_selected`;
                    const bName = `repeating_weapons_${b}_weapon_selected`;
                    const aValue = allValues[aName] === 'on';
                    const bValue = allValues[aName] === 'on';
                    return bValue - aValue;
                });

                console.log("New Weapon Order:", sortedArray);
                setSectionOrder("weapons", sortedArray);
            });
        });
    });
});
March 13 (2 weeks ago)
Joel
Pro

I regret to inform you that it did not work. :(  The checkbox does of course tick on or off, and the sheet blinks like it is going to re-order the list, but they do not actually get re-ordered at all, not even moving ticked ones to the top any more. Here is the log while I ticked row #3 on/off, then #4 on/off, then #5 on. One odd thing I noticed, not sure if relevant, it appears that no matter which row I tick on or off, it suggests the same Original Weapon order each time, and the same New Weapon Order each time. Thanks for your attempts, I hate being the bearer of bad news. Hopefully it is a simple fix?

Found a pre-defined key order!
12vtt.bundle.3d9f714f902c13ed2239.js:47204 Found a pre-defined key order!
VM4:24 Checkbox Changed - Raw Value: on
VM4:29 Checkbox checked for: -ok84xz9o3ush9eemkcc
VM4:32 Original Weapon Order: (5) ['-ok84xz9o3ush9eemkcc', '-ok850qvw8zzovzjkg2o', '-ok853d2ajmmeyasvw3y', '-olabjzxywhoza5b505u', '-olabnnluxqureqx7dk2']
VM4:49 New Weapon Order: (5) ['-olabnnluxqureqx7dk2', '-olabjzxywhoza5b505u', '-ok84xz9o3ush9eemkcc', '-ok850qvw8zzovzjkg2o', '-ok853d2ajmmeyasvw3y']
7vtt.bundle.3d9f714f902c13ed2239.js:47204 Found a pre-defined key order!
7vtt.bundle.3d9f714f902c13ed2239.js:47204 Found a pre-defined key order!
12vtt.bundle.3d9f714f902c13ed2239.js:47204 Found a pre-defined key order!
VM4:24 Checkbox Changed - Raw Value: 0
VM4:29 Checkbox checked for: -ok84xz9o3ush9eemkcc
VM4:32 Original Weapon Order: (5) ['-ok84xz9o3ush9eemkcc', '-ok850qvw8zzovzjkg2o', '-ok853d2ajmmeyasvw3y', '-olabjzxywhoza5b505u', '-olabnnluxqureqx7dk2']
VM4:49 New Weapon Order: (5) ['-olabnnluxqureqx7dk2', '-olabjzxywhoza5b505u', '-ok84xz9o3ush9eemkcc', '-ok850qvw8zzovzjkg2o', '-ok853d2ajmmeyasvw3y']
7vtt.bundle.3d9f714f902c13ed2239.js:47204 Found a pre-defined key order!
7vtt.bundle.3d9f714f902c13ed2239.js:47204 Found a pre-defined key order!
12vtt.bundle.3d9f714f902c13ed2239.js:47204 Found a pre-defined key order!
VM4:24 Checkbox Changed - Raw Value: on
VM4:29 Checkbox checked for: -ok850qvw8zzovzjkg2o
VM4:32 Original Weapon Order: (5) ['-ok84xz9o3ush9eemkcc', '-ok850qvw8zzovzjkg2o', '-ok853d2ajmmeyasvw3y', '-olabjzxywhoza5b505u', '-olabnnluxqureqx7dk2']
VM4:49 New Weapon Order: (5) ['-olabnnluxqureqx7dk2', '-olabjzxywhoza5b505u', '-ok84xz9o3ush9eemkcc', '-ok850qvw8zzovzjkg2o', '-ok853d2ajmmeyasvw3y']
7vtt.bundle.3d9f714f902c13ed2239.js:47204 Found a pre-defined key order!
7vtt.bundle.3d9f714f902c13ed2239.js:47204 Found a pre-defined key order!
12vtt.bundle.3d9f714f902c13ed2239.js:47204 Found a pre-defined key order!
VM4:24 Checkbox Changed - Raw Value: 0
VM4:29 Checkbox checked for: -ok850qvw8zzovzjkg2o
VM4:32 Original Weapon Order: (5) ['-ok84xz9o3ush9eemkcc', '-ok850qvw8zzovzjkg2o', '-ok853d2ajmmeyasvw3y', '-olabjzxywhoza5b505u', '-olabnnluxqureqx7dk2']
VM4:49 New Weapon Order: (5) ['-olabnnluxqureqx7dk2', '-olabjzxywhoza5b505u', '-ok84xz9o3ush9eemkcc', '-ok850qvw8zzovzjkg2o', '-ok853d2ajmmeyasvw3y']
7vtt.bundle.3d9f714f902c13ed2239.js:47204 Found a pre-defined key order!
7vtt.bundle.3d9f714f902c13ed2239.js:47204 Found a pre-defined key order!
12vtt.bundle.3d9f714f902c13ed2239.js:47204 Found a pre-defined key order!
VM4:24 Checkbox Changed - Raw Value: on
VM4:29 Checkbox checked for: -ok853d2ajmmeyasvw3y
VM4:32 Original Weapon Order: (5) ['-ok84xz9o3ush9eemkcc', '-ok850qvw8zzovzjkg2o', '-ok853d2ajmmeyasvw3y', '-olabjzxywhoza5b505u', '-olabnnluxqureqx7dk2']
VM4:49 New Weapon Order: (5) ['-olabnnluxqureqx7dk2', '-olabjzxywhoza5b505u', '-ok84xz9o3ush9eemkcc', '-ok850qvw8zzovzjkg2o', '-ok853d2ajmmeyasvw3y']
7vtt.bundle.3d9f714f902c13ed2239.js:47204 Found a pre-defined key order!
March 13 (2 weeks ago)

Edited March 13 (2 weeks ago)
Joel
Pro

oooo I think I found it... there's a new typo in that script referencing bValue to AllValues[aName] instead of [bName]!!!

Fingers crossed...

March 13 (2 weeks ago)
Joel
Pro

Holy Guacamole !!!!  I think that was it!!!!

Extremely Excited GIFs | Tenor

March 13 (2 weeks ago)
Joel
Pro

Scott, thanks so much for your help with this. This is a huge QoL improvement for us. I very much appreciate it. I apologize in advance for the things in the near future I'm probably going to also need help with, lol.

March 13 (2 weeks ago)
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator

Heh, sorry about that typo. Not sure how that got corrupted in the copy and paste of the code. Glad it's working for you now though!

March 13 (2 weeks ago)

Edited March 13 (2 weeks ago)
vÍnce
Pro
Sheet Author

Sorting repeating fieldsets alphanumerically by a given field (attr_name) would a nice "option" for many character sheets.

March 16 (2 weeks ago)
Joel
Pro
Hey Scott, I noticed a little hiccup, wonder if it is easy to fix. The script breaks if the character has not already manually re-ordered the list. Not a big deal, but if anyone else uses this, they might think it is simply broken. If I manually re-order someone's list, then it seems to work fine afterwards, so far.

The error I was getting said this:
sheetsandboxworker.js:796 TypeError: Cannot read properties of undefined (reading 'toLowerCase')
    at Object.eval [as -NpR5Dla_VoThzwbRrOI//repeating_weapons_-oltzm-fftpqcdrmadw5//0.8205140544152247] (eval at messageHandler (sheetsandboxworker.js:765:1), <anonymous>:38:72)
    at fullfillAttrReq (sheetsandboxworker.js:55:1)
    at messageHandler (sheetsandboxworker.js:774:1)
sheetsandboxworker.js:797 TypeError: Cannot read properties of undefined (reading 'toLowerCase')
    at Object.eval [as -NpR5Dla_VoThzwbRrOI//repeating_weapons_-oltzm-fftpqcdrmadw5//0.8205140544152247] (eval at messageHandler (sheetsandboxworker.js:765:1), <anonymous>:38:72)
    at fullfillAttrReq (sheetsandboxworker.js:55:1)
    at messageHandler (sheetsandboxworker.js:774:1)
March 16 (2 weeks ago)
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator

Ah, yeah, that's easy to fix:

const repOrder = allValues._reporder_repeating_weapons?.toLowerCase().split(/\s*,\s*/) || [];
March 16 (2 weeks ago)
Joel
Pro

Cool, thanks, seems to be working.
So for anyone coming along after the fact and trying to do this, here is the total

/* **** START: REORDER THE WEAPONS BASED ON CHECKBOXES **** */
/**
 * Orders a single ID array.
 * @param {string[]} repOrder - Array of IDs in the order they are in on the sheet, typically from the corresponding _repOrder attribute.
 * @param {string[]} IDs - Array of IDs to be ordered. Aka the default ID Array passed to the getSectionIDs callback
 * @param {object} [attributes] - the object containing your results from getAttrs
 * @returns {string[]} - The ordered id array
 */
const orderSection = function(repOrder,IDs=[], attributes){
  const attributeKeys = Object.keys(attributes);
  // create an array with the custom ordered IDs at the front followed by the unordered IDs in the back
  const idArr = [...repOrder.filter(v => v),...IDs.filter(id => !repOrder.includes(id.toLowerCase()))]
    // filter this array of IDs to remove old IDs from deleted rows that stay in the _reporder attribute
    .filter(id => {
      const exists = attributeKeys.some(k => k.includes(id));
      return exists;
    });
  return idArr;
};

on("change:repeating_weapons:weapon_selected", function(eventInfo) {
    getAttrs([eventInfo.sourceAttribute], function(values) {
        console.log("Checkbox Changed - Raw Value:", values[eventInfo.sourceAttribute]);

        // Extract the row ID manually
        let sourceSectionID = eventInfo.sourceAttribute.split("_")[2];

        console.log("Checkbox checked for:", sourceSectionID); // Debugging

        getSectionIDs("weapons", function(idArray) {
            console.log("Original Weapon Order:", idArray);

            // Get checked state for all rows
            let attrNames = idArray.map(id => `repeating_weapons_${id}_weapon_selected`);
            attrNames.push('_reporder_repeating_weapons'); //add the reporder for the section to your getArray
            getAttrs(attrNames, function(allValues) {
                const repOrder = allValues._reporder_repeating_weapons?.toLowerCase().split(/\s*,\s*/) || []; //make an array of the reordered IDs
                const orderedIDs = orderSection(repOrder,idArray,allValues);
                // Sort: Checked items first, but maintain relative order
                let sortedArray = orderedIDs.toSorted((a,b) => {
                    const aName = `repeating_weapons_${a}_weapon_selected`;
                    const bName = `repeating_weapons_${b}_weapon_selected`;
                    const aValue = allValues[aName] === 'on';
                    const bValue = allValues[bName] === 'on';
                    return bValue - aValue;
                });

                console.log("New Weapon Order:", sortedArray);
                setSectionOrder("weapons", sortedArray);
            });
        });
    });
});
/* **** END: REORDER THE WEAPONS BASED ON CHECKBOXES **** */