Ordering Repeating Sections

It is sometimes the case that you need to know the current order of a repeating section. You can manually order the sections any way you desire – take the two pics below, for example:

These are the same simple repeating section, but the user was manually reordered them. Notice the row ID is being displayed to make it a butr more obvious (just look at the first 3 characters of each row ID).

There are times the order might matter to you. To be clear, this usually doesn’t matter. When running code a repeating section, the standard getSectionIDs function will give you one instance of each row id, and any code you run using that array will run once for each row, no matter their order.

But if, say, you are outputting the section to chat, you want display to match the order in the repeating section. Roll20 makes this harder than it needs to be, to be honest. getSectionIDs always orders the ids in order of creation, not display.

There are several ways to get around this.

Counter

If all you want is the repeating section to display a number on each row, you can do this with a pure CSS solution. This is a great way to show a display of the number of each row.

First create a place in the repeating section to hold the number. That’s the first span below.

<div class="section">
    <fieldset class="repeating_example">
        <span name="attr_counter" class="counter"></span>
        <span>Name</span>
        <input type="text" name="attr_name" placeholder="Enter name">
    </fieldset>
</div>Code language: HTML, XML (xml)

The real work happens in the CSS. First you need to initialise the conter by giving it a name (here, counting):

.section {
    counter-reset: counting;
}Code language: CSS (css)

Then add the conter to that span:

.counter::before {
    counter-increment: counting;
    content: counter(counting) '. ';
}Code language: CSS (css)

Notice this uses the ::before pseudo class. When you want to modify existing HTML, you’ll usually se ::before or ::after.

counter-increment says to bump the named counter up by 1.

content says add the listed content to the chosen element – here, that’s anything with the counter class.

That will look like this:

You can learn more about using counters here, but the important thing to realise: you can’t use the nmber in any way. It’s just for display piurposes. The counter will update naturally if you change the order of the rows, which is handy. You can learn more about counters here.

The Official method: setSectionorder

You should never use this method. There are bugs that have gone unfixed as long as the function has existed, and the offficial documentation is inaccurate, but it is here for completeness.

The official documentation can be found on the wiki, but I don’t include help for this function because of the issues with using it (which you can see described in that link). There are better ways of solving this issue, described below.

The Chris D Method: getSectionIDsOrdered

This is a really simple approach and will suit most people. The biggest issue is that there aren’t instructions on how to use it on the wiki, but it is really simple.

  1. Drop the getSectionIDsOrdered code in your script block.
  2. Type getSectionIDsOrdered instead of getSectionIDs, and that’s it.

You’ll find this method here on the wiki, but for ease of use, here is the code.

var getSectionIDsOrdered = function (sectionName, callback) {
  'use strict';
  getAttrs([`_reporder_${sectionName}`], function (v) {
    getSectionIDs(sectionName, function (idArray) {
      let reporder_array = v[`_reporder_${sectionName}`] ? v[`_reporder_${sectionName}`].toLowerCase().split(',') : [],
        ids = [...new Set(reporder_array.filter(x => idArray.includes(x)).concat(idArray))];
      callback(ids);
    });
  });
};Code language: JavaScript (javascript)

Just put that near the top of your script block, and you’re ready to go. Here’s an example of how to use it, contrasted with the same code where you don’t use it.

on('clicked:example', () => {
    getSectionIDs('repeating_example', ids => {
        console.info({ids});
    });
});Code language: JavaScript (javascript)
on('clicked:example', () => {
    getSectionIDsOrdered ('repeating_example', ids => {
        console.info({ids});
    });
});Code language: JavaScript (javascript)

Both of these sheet workers do the same thing. They print out a list of the row ids to the console. The first function shows the ids in the order they were created, and the second shows them in the order they are displayed on the sheet.

It’s that simple. Just type getSectionIDsOrdered iinstead of getSectionIds and proceed as normal.

This, and all the following methods, relies on the fact that Roll20 does create an attribute that contains information on the reordered rows: _reporder_repeating_[secton_name]. This attribute can be accessed like any other, and you can see in the code above where it has been used – if you really want to. But remember, the beauty of this method is that you don’t need to understand how it works. Just replace getSectionIds with getSectionIDsOrdered and you are good (as long as you have copied the above thread – just once – into your script block.

Scott C Method: orderSection

If there’s anyone I’d call the ‘character sheet expert’, I’d say it’s Scott C. He really is that good, and he has a solution for this issue. By the way, you can find a lot of coding solutions at Scott’s site: K-scaffold. (That’s an excelent site to browse if you like coding, by the way.)

Scott correctly points out a problem with the Chris D method – it has some inherent lag. It calls getAttrs in the function, and you also will call it again later. getAttrs is an asynchronous function which creates the chance that the code will run slowly.

For my sheets, I consider this to be an acceptable issue. One extra getAttrs call per getSectionIDs call probably won’t have much impact. But there are sheets where this will matter.

When efficiency is important, use Scott’s method, as descrbed in this linked post (and the one two posts down in that same thread), and this post (and Scott’s comments earlier in the same thread).

GiGs Method: getSectionsOrdered

This is a simple drop-in function based on getSectionIDs that is used exactly the same as the custom function, getSectionObject I created earlier. I think this is the best solution, but that depends on what you mean by ‘best’. Scott’s method is more efficent, but this method is easier to use. It doesn’t look very pretty, but trust me it is simple.

The code combines Chris D’s function with my own custom function for handling multiple repeating sections from an earlier post. I won’t go into detail on how it’s built – see the earlier post on the custom function for multiple repeating sections for that. I just describe here how to use it.

First, copy the code below, and paste it in your script block, before any code working with repeating sections.

const getSectionsOrdered = (sections, callback) => {
    'use_strict';
    const fields_object = {};
    let keys = Object.keys(sections);
    const reporders = keys.map(key => `_reporder_repeating_${key}`);
    getAttrs(reporders, values => {
        const burndown = () => {
            let section = keys.shift();
            getSectionIDs(`repeating_${section}`, ids => {
                let reporder_array = values[`_reporder_repeating_${section}`] ? 
                     values[`_reporder_repeating_${section}`].toLowerCase().split(',') : [];
                // sort the rows appropriately, and populate data[section] ids.
                sorted_ids = [...new Set(reporder_array.filter(x => ids.includes(x)).concat(ids))];
                fields_object[section] = sorted_ids;
                // add to the fields_object
                fields_object.fields = sorted_ids.reduce((m, id) => 
                    [...m, ...sections[section].map(field => 
                        `repeating_${section}_${id}_${field}`)],
                            fields_object.fields ? fields_object.fields : []);
                if (keys.length) burndown();
                else callback(fields_object);
            });
        };
        burndown();
    });
};Code language: JavaScript (javascript)

Now you’re ready to use it. It is used exactly like the earlier custom function for gathering multiple repeating sections. In most getSectionIds workers, you’ll need to create a fields array after getSectionIds. In this function, you create a sections object just before the getSectionsOrdered call. other than that it is identical.

Here is that earlier function rewritten to show how it is done:

on('clicked:example', () => {
  getSectionIDs('repeating_example', ids => {
     const fields = [];
     ids.forEach(id => 
      fields.push(`repeating_example_${id}_name`));
     console.info({field});
   });
});Code language: JavaScript (javascript)
on('clicked:example', () => {
    const sections = {
        example: ['name']
    };
    getSectionsOrdered (sections, data => {
        console.info({data});
    });
});Code language: JavaScript (javascript)

This method can be used with any number of repeating sections, including just one. It gives you a data object that contains the names ofall the attributes you asked for, across all repeating sections, and gives you the ids for all sections. Crucially, they are all ordered how they appear on the sheet.

Overview

If all you want is a way to list the number of a row, visually on the sheet, use the Counter method described at the top of this page. Otherwise, use one of these three functions:

  • Chris D’s getSectionIDsOrdered: Use this when you don’t care about efficiency, and want an extremely simple method for dealing with one section at a time.
  • My getSectionsOrdered function: Use this when you need to work with more than one section at a time, and don’t mind the extra getAttrs call. You can use this for one repeating section, so I tend to use this instead of the Chris D method: I just need to use one function. But if you never work with more than one repeating section at a time, Chris D’s method works fine.
  • Scott’s orderSection function: Use this method when you want to cut out all sources of inefficiency and don’t mind doing a bit of extra coding. It is objectively the best solution on this page, but it takes a bit more work to use it.

There you have it. Use the method that you prefer. Just remember thatin most cases, you don’t need any of these. The ordering of a repeating section doesn’t usually matter. When it does matter, you’ll know. The rest of the time, you can ignore this post.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.