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

Ordering a Repeating section

1684195669

Edited 1684301289
GiGs
Pro
Sheet Author
API Scripter
I'm having an issue. How would you get the order of a repeating section in sheet worker code from what is displayed on the character sheet. Let's say you have 4 rows: if you use getSectionIDs it will give the ids in order they were created: 1, 2, 3, 4. Now, lets say the user modifiers the list, moving the last one to be the second, so the order is now 1, 4, 2, 3. How do I get that to have the indexes 1, 2, 3, 4 ? Also the wiki has an entry for a function sectionOrder, which is deleted !&gt; <a href="https://wiki.roll20.net/Character_Sheet_Development/Repeating_Section#setSectionOrder.28_.29" rel="nofollow">https://wiki.roll20.net/Character_Sheet_Development/Repeating_Section#setSectionOrder.28_.29</a> I'm sure there used to be a function here, created by Aaron (Chris D, with assistance from Jakob). Where has it gone?
1684195786

Edited 1685490424
GiGs
Pro
Sheet Author
API Scripter
Hmm. The setSectionOrder function is mentioned here: <a href="https://wiki.roll20.net/Sheet_Worker_Scripts#setSectionOrder.28.3CRepeating_Section_Name.3E.2C_.3CSection_Array.3E.2C_.3CCallback.3E.29" rel="nofollow">https://wiki.roll20.net/Sheet_Worker_Scripts#setSectionOrder.28.3CRepeating_Section_Name.3E.2C_.3CSection_Array.3E.2C_.3CCallback.3E.29</a> but it mentions problems, and also the syntax isnt very helpful. I can't see how it is supposed to be used.
1684199871
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
To order the section array based on the actual order of the section, you need to get the pseudo attribute for the reporder for the section. The wiki's description is complete, but also confusing on first read: Note that you may use GetAttrs() (described above) to request the _reporder_repeating_&lt;sectionname&gt; attribute to get a list of all the IDs in the section that have been ordered. However note that this may not include the full listing of all IDs in a section. Any IDs not in the list that are in the section are assumed to come after the ordered IDs in lexographic order. What that means in practice is that the _reporder_repeating_&lt;sectionname&gt; will be all IDs from the start of the section to the last ID that has been reordered by the user, and then any ID not in the _reporder attribute will be after that in the same order it is in the default IDArray. In the K-scaffold , I setup the aliases for getAttrs so that they always return the IDs in the actual order rather than the lexographic order. Here's what that function looks like (adjusted to not require the K-scaffold): /** * Orders the section id arrays for all sections in the `sections` object to match the repOrder attribute. * @memberof Utilities * @param {object} attributes - The attributes object as passed to the callback for getAttrs. * @param {object[]} sections - Object containing the IDs for the repeating sections, indexed by repeating section name. */ const orderSections = function(attributes,sections){ Object.keys(sections).forEach((section)=&gt;{ attributes[`_reporder_${section}`] = commaArray(attributes[`_reporder_${section}`]); orderSection(attributes[`_reporder_${section}`],sections[section]); }); }; /** * Orders a single ID array. * @memberof Utilities * @param {string[]} repOrder - Array of IDs in the order they are in on the sheet. * @param {string[]} IDs - Array of IDs to be ordered. Aka the default ID Array passed to the getSectionIDs callback */ const orderSection = function(repOrder,IDs=[]){ IDs.sort((a,b)=&gt;{ return repOrder.indexOf(a.toLowerCase()) - repOrder.indexOf(b.toLowerCase()); }); }; /** * Splits a comma delimited string into an array * @memberof Utilities * @param {string} string - The string to split. * @returns {array} - The string segments of the comma delimited list. */ const commaArray = function(string=''){ return string.toLowerCase().split(/\s*,\s*/); }; As for setSectionOrder, it exists but as is noted in the wiki the documentation provided is incorrect and the function has some serious bugs that I keep hoping Roll20 will fix. To use setSectionOrder, you just pass the name of the repeating section (minus the repeating_ &nbsp;prefix) and an array with the IDs for the section in the order you want them. However, this causes issues with the display of inputs in the moved rows such that they will appear to have NOT moved because the displayed values in inputs will be what was in that location previously. This is a visual bug only though, and the actual attribute values aren't changed unless the user then goes in and edits the visually wrong attribute values. This is a very bizarre bug, and I'm sure this explanation of it makes nearly no sense. There are hoops you can jump through to get it to work, but they require an incredible amount of code infrastructure to pull off and even then aren't 100% effective. My recommendation is to just not use the function until Roll20 fixes the bug. The bug is so bad, that the better solution is to just completely delete all rows in a section, and recreate them in the order you want them when you want to reorder things.
1684204640

Edited 1684205246
GiGs
Pro
Sheet Author
API Scripter
Thank you for that, Scott. It looks comprehensive. I'm still not sure exactly how to use it, though. Questions coming! "The wiki's description is complete, but also confusing on first read:" - and not just on the first read! It looks like orderSections loops through calling orderSection once for each named section. Looking at orderSections: attributes: this an object, but what does it contain exactly, and what does that object look like? sections: if I'm reading this correctly, its a group of sections and their ids, like { &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; repeating_weapons: [-saghsuagisi, -ahgjhgbjgfs, -haygiu67t485ta], &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; repeating_gear: [-w48yyhwity548, -esa74w67ytwagh] } (Not real IDs, just some keysmashing there!) Is that correct? Also with the link you provided, I see the function getSectionIDsOrdered : what would code using that look like? For that matter, lets say I have an attribute inside a repeating section: the attribute is attr_number , the section is repeating_gear . What code would I need to update that number value to show the currently displayed row? (Maybe a redundant value, since you can see the repeating section, but bear with me :))
1684208080

Edited 1684208299
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
"The wiki's description is complete, but also confusing on first read:" - and not just on the first read! Haha, yeah, I should have put something along the lines of, "confusing until you grok it, and then you still aren't sure of the best way to describe it". It looks like orderSections loops through calling orderSection once for each named section. Looking at orderSections: attributes: &nbsp;this an object, but what does it contain exactly, and what does that object look like? sections: &nbsp;if I'm reading this correctly, its a group of sections and their ids, like { &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; repeating_weapons: [-saghsuagisi, -ahgjhgbjgfs, -haygiu67t485ta], &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; repeating_gear: [-w48yyhwity548, -esa74w67ytwagh] } (Not real IDs, just some keysmashing there!) Is that correct? Yep! you've pretty much got it. The attributes &nbsp;argument is just the object that you would get in the callback to getAttrs. E.g. getSectionIDs('repeating_section',(idArray)=&gt;{ const sections = {repeating_section:[]}; const getArr = idArray.reduce((accumulator,id) =&gt; { accumulator.push(`repeating_section_${id}_name`); sections.repeating_section.push(id); return accumulator; },['_reporder_repeating_section']); getAttrs(getArr,(attributes) =&gt; { console.log(sections.repeating_section); // =&gt; the idArray in creation order orderSections(attributes,sections); console.log(sections.repeating_section); // =&gt; the idArray in true order }) }); Also with the link you provided, I see the function&nbsp; getSectionIDsOrdered : what would code using that look like? The getSectionIDsOrdered function that is shown on the wiki isn't my favorite implementation of this because it nests your actual work in another getAttrs which as we both know causes problems, especially for something like this where you might need to do this action on 2, 3, 4, or even more repeating sections at once. Essentially, the orderSections code from the K-scaffold on the other hand goes the other direction and probably over obfuscates the functionality, but it does that because it's built to support the scaffold's automation features. If someone is building this infrastructure from scratch, using just the orderSection function as needed is probably plenty fine. For that matter, lets say I have an attribute inside a repeating section: the attribute is&nbsp; attr_number , the section is&nbsp; repeating_gear . What code would I need to update that number value to show the currently displayed row? (Maybe a redundant value, since you can&nbsp; see&nbsp; the repeating section, but bear with me :)) Assuming we're using the full orderSections code above, then we could just do this: getSectionIDs('repeating_section',(idArray)=&gt;{ const sections = {repeating_section:[]}; const getArr = idArray.reduce((accumulator,id) =&gt; { accumulator.push(`repeating_section_${id}_name`); sections.repeating_section.push(id); return accumulator; },['_reporder_repeating_section']); getAttrs(getArr,(attributes) =&gt; { orderSections(attributes,sections); const setObj = {}; sections.repeating_section.forEach((id,index) =&gt; { setObj[`repeating_section_${id}_number`] = index; }) setAttrs(setObj,{silent:true}); }) }); And, just because I can't resist plugging the scaffold, here's what a fully functional sheet would look like doing that using full K-scaffold: PUG include k-scaffold +fieldset({name:'section'}) +number({name:'number',readonly:'',trigger:{ calculation:'getRowIndex' }}) +text({name:'name',trigger:{ affects:['repeating_section_$X_number'] }}) +kscript include ./javascript/getRowIndex.js ./javascript/getRowIndex.js /** * gets the row index for a given row and returns it. * @param {object} trigger - The trigger that caused the function to be called * @param {object[]} sections - All the repeating section IDs * @returns {number} - The row index */ const getRowIndex = function({trigger,sections}){ const [section,rowID] = k.parseTriggerName(trigger.name); return sections[section].indexOf(rowID); }; k.registerFuncs({getRowIndex}); Fixed some typos from me being tired. -_-
1684214114
GiGs
Pro
Sheet Author
API Scripter
Thank you! I'm tired too and will have a good look through this tomorrow.
1684277135

Edited 1684287375
GiGs
Pro
Sheet Author
API Scripter
Scott, I think I follow your code (not so much the underlying HTML) but I'm having an issue with it. I tried creating the numbers list for testing, and its updating but only at specific times. I cant figure out to trigger the recalulation. I can nest it inside a on('change: event, but changing the rows doesnt trigger that. I have tio change rows, then change something in one of the rows. Also, I figured out how to use the other script, and had misattributed the source (Chris D with some help from Jakob). Amusingly, I found a post from myself explaining how to use it. It has the same problem, though. Stats arent updated on change. How do I get these changes to update automatically on changing a row, without needing some manual way to trigger the update?
1684278268

Edited 1684278344
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
you need to watch the pseudo attribute for the reporder for the repeating section. It has a change event just like any other attribute. And, that reminds me I need to actually add a reorder listener capability to the K-scaffold
1684287240
GiGs
Pro
Sheet Author
API Scripter
Scott C. said: you need to watch the pseudo attribute for the reporder for the repeating section. It has a change event just like any other attribute. What would that look like? Here's what Ive tried that fails: on ( 'change:_reporder_repeating_section
1684289027
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
Hmm, that's what I thought it was. I'll have to check once I'm home for the night&nbsp;
1684289480
GiGs
Pro
Sheet Author
API Scripter
Thats what I thought it was too, I remember mention of it, but I cant get it to respond to a change event.
1684290256

Edited 1684295273
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
ah! it's a nested listener . on('change:_reporder:section',()=&gt;{}); EDIT: Fixed a typo found thanks to GiGs' post below and described in my post below that.
1684294901

Edited 1684295005
GiGs
Pro
Sheet Author
API Scripter
That's not working for me either. I have this code (with your functions from above) on ( 'change:reporder:section' ,() =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; console . info ( 'scott: reporder' ) &nbsp; &nbsp; &nbsp; &nbsp; getSectionIDs ( 'repeating_section' ,( idArray ) =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; const sections = { repeating_section: []}; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; const getArr = idArray . reduce (( accumulator , id ) =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; accumulator . push ( `repeating_section_ ${ id } _name` ); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sections . repeating_section . push ( id ); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return accumulator ; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; },[ '_reporder_repeating_section' ]); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; getAttrs ( getArr ,( attributes ) =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; orderSections ( attributes , sections ); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; const setObj = {}; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sections . repeating_section . forEach (( id , index ) =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; setObj [ `repeating_section_ ${ id } _number` ] = index + 1 ; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; setAttrs ( setObj ,{ silent: true }); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }); &nbsp; &nbsp; &nbsp; &nbsp; }); &nbsp; &nbsp; }); It isn't doing anything. What should I be doing?
1684295210
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
Bah! I missed an underscore when I wrote out the listener syntax. It should be: on('change: _ reporder:section',()=&gt;{});
1684300773
GiGs
Pro
Sheet Author
API Scripter
I feel bad about not spotting that, too. That works, yay! New problem: it now works when I move something around in a section. But if I add new row, it gets numbered 1, and sticks at that number. If I move any row, it reorders the numbers appropriately, but if I dont move a row, just rely on change;repeating_section it won't change from 1. Interestingly, if I use Chris D's function, the new row is added at the proper number. So at the moment, despirte the potential lag, it is working better.
1684431992

Edited 1684432034
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
It bothered me that it was having issues, so I went and tracked down what was going on. I had screwed up the sort logic. Replacing orderSections and orderSection with these versions will fix that issue (and allow us to get away from the potential lag of the wiki function): /** * Orders the section id arrays for all sections in the `sections` object to match the repOrder attribute. * @memberof Utilities * @param {object} attributes - The attributes object as passed to the callback for getAttrs. * @param {object[]} sections - Object containing the IDs for the repeating sections, indexed by repeating section name. */ const orderSections = function(attributes,sections){ Object.keys(sections).forEach((section)=&gt;{ attributes[`_reporder_${section}`] = commaArray(attributes[`_reporder_${section}`]); sections[section] = orderSection(attributes[`_reporder_${section}`],sections[section]); }); }; /** * Orders a single ID array. * @memberof Utilities * @param {string[]} repOrder - Array of IDs in the order they are in on the sheet. * @param {string[]} IDs - Array of IDs to be ordered. Aka the default ID Array passed to the getSectionIDs callback * @returns {string[]} - The properly ordered array of IDs for a given section. */ const orderSection = function(repOrder,IDs=[]){ const idArr = [...repOrder,...IDs.filter(id =&gt; !repOrder.includes(id.toLowerCase()))]; return idArr; };
1684466296
GiGs
Pro
Sheet Author
API Scripter
I was worried you weren't going to come back to this one. I'll check it out tomorrow :)
1684467171
vÍnce
Pro
Sheet Author
and maybe the wiki can be updated with this version...
1684473627

Edited 1684473658
GiGs
Pro
Sheet Author
API Scripter
I don't know - a link to Scott's site where these functions are, mentioning the pitfalls, and saying there is an alternative, might be better. The existing function is short and fits the wiki well.