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

Help to make changes in repetitive sections

1676469186

Edited 1676469260
Maïlare
Pro
Sheet Author
API Scripter
Hello, I want to update values in repetitive sections in terms of an external value. In repetitive sections I have the stats of my monsters and when the level of group is changed (the external value), I want to update 3 variables for all my monsters. Here is the code I have wrote. The external value and the repetitive sections (the 6 others have the same structure) : < div class = "char" style = " color: black; font-weight: bold; font-size: large;" > Niveau du groupe : < input class = "textbox-stat" type = "number" name = "attr_NvGroupe" value = "1" /></ div >< br >< br >     < div class = "dark_row" >         < div class = "title" style = " font-effect:engrave; margin-top:35px;" > Enahora </ div >         < fieldset class = "repeating_En" >             < div class = "dark_row" style = " font-size:20px;line-height:25px;" >                 < div   class = "arme-title" style = " margin-left:75px; margin-top: 10px;" > Nom </ div >                 < div   class = "arme-title" style = " margin-left:60px;" > FO </ div >                 < div   class = "arme-title" style = " margin-left:30px;" > DEX </ div >                 < div   class = "arme-title" style = " margin-left:25px;" > PO </ div >                 < div   class = "arme-title" style = " margin-left:58px;" > Dégâts </ div >                 < div   class = "arme-title" style = " margin-left:50px;" > PdV </ div >                 < div   class = "arme-title" style = " margin-left:28px;" > XP </ div >                 < div   class = "arme-title" style = " margin-left:80px;" > Description </ div >             </ div >             < div class = "dark_row" >                 < button   type = "roll" title = "bestiaireEnDesc" name = "roll_En_desc" value = "&{template:bestiaire} {{name=@{En_name } }} {{image=[Image](@{En_image})}} {{description=@{En_Description } }} {{carac1=@{En_FO } }} {{carac2=@{En_DEX } }} {{carac3=@{En_PO } }} {{pdv=@{En_PdV } }}" ></ button >                 < input class = "textbox" type = "text" name = "attr_En_name" style = " width:120px;" />                 < input class = "textbox-stat" type = "number" name = "attr_En_FO" style = " width:50px;text-align:center;" />                 < input class = "textbox-stat" type = "number" name = "attr_En_DEX" style = " width:50px;text-align:center;" />                 < input class = "textbox-stat" type = "number" name = "attr_En_PO" style = " width:50px;" />                 < input class = "textbox-stat" type = "number" name = "attr_En_Deg" style = " width:40px;text-align:center;" > d10+ </ input >                 < input class = "textbox-stat" type = "number" name = "attr_En_DegModif" style = " width:40px;text-align:center;" />                 < input class = "textbox-stat" type = "number" name = "attr_En_PdV" style = " width:50px;" />                 < input class = "textbox-stat" type = "number" name = "attr_En_Xp" style = " width:50px;" />                 < textarea class = "textareaboxStat" type = "text" name = "attr_En_Description" style = " vertical-align: top; width:200px;" ></ textarea >                 < br >                 < button type = "roll" style = " margin-left:180px;" title = "bestiaireEnForce" name = "roll_En_FO" value = "&{template:enyllion} {{perso=@{En_name } }} {{name=Force}} {{Carac=[[@{En_FO}+?{Modificateur|0}]]}}  {{Roll=[[1D100]]}}" ></ button >                 < button type = "roll" style = " margin-left:30px;" title = "bestiaireEnDexterité" name = "roll_En_DEX" value = "&{template:enyllion} {{perso=@{En_name } }} {{name=Dexterité}} {{Carac=[[@{En_DEX}+?{Modificateur|0}]]}}  {{Roll=[[1D100]]}}" ></ button >                 < button type = "roll" style = " margin-left:30px;" title = "bestiaireEnPouvoir" name = "roll_En_PO" value = "&{template:enyllion} {{perso=@{En_name } }} {{name=Pouvoir}} {{Carac=[[@{En_PO}+?{Modificateur|0}]]}}  {{Roll=[[1D100]]}}" ></ button >                 < button type = "roll" style = " margin-left:75px;" title = "bestiaireEnDegats" name = "roll_En_Degats" value = "&{template:degats} {{perso=@{En_name } }} {{name=Dégats}} {{Roll=[[@{En_Deg}d10+@{En_DegModif}]]}}" ></ button >                 < div class = "arme-title" style = " margin-left:114px;" > Image </ div >                 < textarea class = "textareaboxStat" type = "text" name = "attr_En_Image" style = " vertical-align: top; width:200px; margin-left: 10px;" ></ textarea >                 </ div >             < br >             < br >         </ fieldset > And here the sheet worker on ( "sheet:opened change:NvGroupe" , function (){         getSectionIDs ( "repeating_En" , "repeating_Ky" , "repeating_No" , "repeating_Am" , "repeating_Te" , "repeating_dem" , "repeating_drag" function ( ids ) {             const fieldnames = [];             ids . forEach ( id => fieldsnames . push ( 'repeating_En' + id + '_En_Deg' , 'repeating_En' + id + '_En_DegMod' , 'repeating_En' + id + '_En_PdV' , 'repeating_Ky' + id + '_Ky_Deg' , 'repeating_Ky' + id + '_Ky_DegMod' , 'repeating_Ky' + id + '_Ky_PdV' , 'repeating_No' + id + '_No_Deg' , 'repeating_No' + id + '_No_DegMod' , 'repeating_No' + id + '_No_PdV' , 'repeating_Am' + id + '_Am_Deg' , 'repeating_Am' + id + '_Am_DegMod' , 'repeating_Am' + id + '_Am_PdV' , 'repeating_Te' + id + '_Te_Deg' , 'repeating_Te' + id + '_Te_DegMod' , 'repeating_Te' + id + '_Te_PdV' , 'repeating_dem' + id + '_dem_Deg' , 'repeating_dem' + id + '_dem_DegMod' , 'repeating_dem' + id + '_dem_PdV' , 'repeating_drag' + id + '_drag_Deg' , 'repeating_drag' + id + '_drag_DegMod' , 'repeating_drag' + id + '_drag_PdV' ,));                     getAttrs ([ "NvGroupe" , fieldnames ], function ( values ) {                 var enDeg = parseInt ( values . repeating_En_En_Deg );                 var enDegMod = parseInt ( values . repeating_En_En_DegMod );                 var enPdv = parseInt ( values . repeating_En_En_PdV );                 var amDeg = parseInt ( values . repeating_Am_Am_Deg );                 var amDegMod = parseInt ( values . repeating_Am_Am_DegMod );                 var amPdv = parseInt ( values . repeating_Am_Am_PdV );                 var kyDeg = parseInt ( values . repeating_Ky_Ky_Deg );                 var kyDegMod = parseInt ( values . repeating_Ky_Ky_DegMod );                 var kyPdv = parseInt ( values . repeating_Ky_Ky_PdV );                 var noDeg = parseInt ( values . repeating_No_No_Deg );                 var noDegMod = parseInt ( values . repeating_No_No_DegMod );                 var noPdv = parseInt ( values . repeating_No_No_PdV );                 var teDeg = parseInt ( values . repeating_Te_Te_Deg );                 var teDegMod = parseInt ( values . repeating_Te_Te_DegMod );                 var tePdv = parseInt ( values . repeating_Te_Te_PdV );                 var demDeg = parseInt ( values . repeating_dem_dem_Deg );                 var demDegMod = parseInt ( values . repeating_dem_dem_DegMod );                 var demPdv = parseInt ( values . repeating_dem_dem_PdV );                 var dragDeg = parseInt ( values . repeating_drag_drag_Deg );                 var dragDegMod = parseInt ( values . repeating_drag_drag_DegMod );                 var dragPdv = parseInt ( values . repeating_drag_drag_PdV );                 var niveau = parseInt ( values . NvGroupe );                 var coeff = 1.15 ;                 var enDegTot = 0 ;                 var kyDegTot = 0 ;                 var noDegTot = 0 ;                 var amDegTot = 0 ;                 var teDegTot = 0 ;                 var demDegTot = 0 ;                 var dragDegTot = 0 ;                 var enDegNv = 0 ;                 var kyDegNv = 0 ;                 var noDegNv = 0 ;                 var amDegNv = 0 ;                 var teDegNv = 0 ;                 var demDegNv = 0 ;                 var dragDegNv = 0 ;                 var enDegModNv = 0 ;                 var kyDegModNv = 0 ;                 var noDegModNv = 0 ;                 var amDegModNv = 0 ;                 var teDegModNv = 0 ;                 var demDegModNv = 0 ;                 var dragDegModNv = 0 ;                 var enPdvNv = 0 ;                 var kyPdvNv = 0 ;                 var noPdvNv = 0 ;                 var amPdvNv = 0 ;                 var tePdvNv = 0 ;                 var demPdvNv = 0 ;                 var dragPdvNv = 0 ;                                 if ( niveau > 1 ) {                     enDegTot = (( enDeg * 10 ) + enDegMod ) * ( coeff ^( niveau - 1 ));                     kyDegTot = (( kyDeg * 10 ) + kyDegMod ) * ( coeff ^( niveau - 1 ));                     noDegTot = (( noDeg * 10 ) + noDegMod ) * ( coeff ^( niveau - 1 ));                     amDegTot = (( amDeg * 10 ) + amDegMod ) * ( coeff ^( niveau - 1 ));                     teDegTot = (( teDeg * 10 ) + teDegMod ) * ( coeff ^( niveau - 1 ));                     demDegTot = (( demDeg * 10 ) + demDegMod ) * ( coeff ^( niveau - 1 ));                     dragDegTot = (( dragDeg * 10 ) + dragDegMod ) * ( coeff ^( niveau - 1 ));                     enDegNv = Math . floor ( enDegTot / 10 );                     enDegModNv = enDegTot % 10 ;                     noDegNv = Math . floor ( noDegTot / 10 );                     noDegModNv = noDegTot % 10 ;                     kyDegNv = Math . floor ( kyDegTot / 10 );                     kyDegModNv = kyDegTot % 10 ;                     amDegNv = Math . floor ( amDegTot / 10 );                     amDegModNv = amDegTot % 10 ;                     teDegNv = Math . floor ( teDegTot / 10 );                     teDegModNv = teDegTot % 10 ;                     demDegNv = Math . floor ( demDegTot / 10 );                     demDegModNv = demDegTot % 10 ;                     dragDegNv = Math . floor ( dragDegTot / 10 );                     dragDegModNv = dragDegTot % 10 ;                     enPdvNv = enPdv * ( coeff ^( niveau - 1 ));                     noPdvNv = noPdv * ( coeff ^( niveau - 1 ));                     kyPdvNv = kyPdv * ( coeff ^( niveau - 1 ));                     amPdvNv = amPdv * ( coeff ^( niveau - 1 ));                     tePdvNv = tePdv * ( coeff ^( niveau - 1 ));                     demPdvNv = demPdv * ( coeff ^( niveau - 1 ));                     dragPdvNv = dragPdv * ( coeff ^( niveau - 1 ));                     setAttrs ({                         repeating_En_En_Deg: enDegNv ,                         repeating_En_En_DegMod: enDegModNv ,                         repeating_En_En_PdV: enPdvNv ,                         repeating_No_No_Deg: noDegNv ,                         repeating_No_No_DegMod: noDegModNv ,                         repeating_No_No_PdV: noPdvNv ,                         repeating_Ky_Ky_Deg: kyDegNv ,                         repeating_Ky_Ky_DegMod: kyDegModNv ,                         repeating_Ky_Ky_PdV: kyPdvNv ,                         repeating_Am_Am_Deg: amDegNv ,                         repeating_Am_Am_DegMod: amDegModNv ,                         repeating_Am_Am_PdV: amPdvNv ,                         repeating_Te_Te_Deg: teDegNv ,                         repeating_Te_Te_DegMod: teDegModNv ,                         repeating_Te_Te_PdV: tePdvNv ,                         repeating_dem_dem_Deg: demDegNv ,                         repeating_dem_dem_DegMod: demDegModNv ,                         repeating_dem_dem_PdV: demPdvNv ,                         repeating_drag_drag_Deg: dragDegNv ,                         repeating_drag_drag_DegMod: dragDegModNv ,                         repeating_drag_drag_PdV: dragPdvNv                     });                 }             });         });     }); Thanks for your help.
1676482542

Edited 1676483330
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
Hi Mailare and welcome to sheet building! I'm assuming that NvGroupe  is your external attribute somewhere else on the sheet. There are a few problems with this sheetworker setup to do what you want and there are some best practices that I would recommend following in order to avoid some of these problems. Problems Your listener is using a camel cased attribute name, NvGroupe . The on  listener requires that the string passed to it be entirely lowercase. This means that your listener will not fire when the NvGroupe  attribute changes. I just realized this isn't actually specified in the wiki. I'll get that updated in a bit. When you set repeating attributes , you must specify the id of the row that you are going to set. If you look in your dev console when this runs on sheet load you should see an error that says something along the lines of "You attempted to set an attribute that starts with repeating, but did not specify a row id". I'll show how to fix this in some code at the end. Your getSectionIDs isn't using the proper format. Best Practices Because the sheetworker listener requires lowercase strings, it is best practice to use a casing system like snake_case  or kebab-case  for attribute and button names. My personal practice is to use snake_case  for attribute and roll button names and kebab-case  for action button names. This helps differentiate these in the code and snake_case   breaks action buttons that are in repeating sections . Here's some of the other benefits of using these casing systems: They are more accessible as assistive software has an easier time reading attribute_name  or attribute-name  than it does reading attributeName . They are easier to read when in a long macro than camelCase , PascalCase , or nocase . Because the rest of Roll20's backend is case insensitive for attribute and button names, you can't use case to differentiate attributes or buttons from other instances of themselves. Using snake or kebab case helps ensure we don't accidentally make a duplicate of an attribute because we changed the casing. Since lowercase is required in one piece of the code, using it throughout makes our code consistent and us less likely to introduce bugs I'd recommend using whole words for attribute names instead of abbreviations. It will make your life easier when you come back to edit the sheet six months or a year later, it makes it easier for the community to help you with problems, and it will make your user's lives easier when they try to build custom macros for your sheet. EDIT: I would also recommend that you go with generic attribute names in your repeating sections, so DegMod  instead of En_DegMod  and Ky_DegMod . The full name of these attributes already includes the repeating section, so there's no need to further differentiate them ( repeating_En_-09asdlkj-lkj_DegMod  instead of repeating_En_ -09asdlkj-lkj_En_DegMod ) . Using the prefix in the actual attribute name also makes writing a sheetworker that handles all of these sections harder. Here's some code that should work (untested): // /** * function to get section ids of multiple repeating sections * @param {object[]} queue - Array of section details on how to assemble the names of a section * @param {function} callback - the function to call once all section info is assembled * @param {string[]} [getArr = []] - Array that will hold all the attribute names that eventually need to be gotten * @param {object} [sections = {}] - object that lists the ids found for each section, indexed by section name. */ const getSections = (queue,callback,getArr = [],sections = {}) => { const sectionDetail = queue.shift(); getSectionIDs(sectionDetail.section,(idArr) => { // Store the idArr in the sections object so that we can use later to iterate over ids for a given section. sections[sectionDetail.section] = idArr; // iterate through the ids in the section idArr.forEach(id => { // Iteratively add the full field names to the getArr sectionDetail.fields.forEach(field => getArr.push(`${sectionDetail.section}_${id}_${field}`)) }); // If there are sections left to work, call the function again to continue working through the queue. Otherwise, call the callback function. if(queue.length){ getSections(queue,callback,getArr,sections); }else{ callback(getArr,sections); } }) } // Make an array to hold all of the information about our repeating sections. const repeatingSections = [ {section:"repeating_En",fields:['En_Deg','En_DegMod','En_PdV',]}, {section:"repeating_Ky",fields:['Ky_Deg','Ky_DegMod','Ky_PdV',]}, {section:"repeating_No",fields:['No_Deg','No_DegMod','No_PdV',]}, {section:"repeating_Am",fields:['Am_Deg','Am_DegMod','Am_PdV',]}, {section:"repeating_Te",fields:['Te_Deg','Te_DegMod','Te_PdV',]}, {section:"repeating_dem",fields:['dem_Deg','dem_DegMod','dem_PdV',]}, {section:"repeating_drag",fields:['drag_Deg','drag_DegMod','drag_PdV',]} ]; on("sheet:opened change:nvgroupe", function () { // clone the repeatingSections array so that we don't mutate the base array each time this runs const sectionQueue = [...repeatingSections]; // Convert the getSectionIDs to a burn down or queue pattern // I've moved this out to it's own function that you can use elsewhere getSections( sectionQueue, (getArr,sections)=>{ getAttrs(getArr,(values) => { // Create an empty object that we'll use to accumulate the changes we want to make to the sheet const setObj = {}; // iterate over each section name and the ids for that section Object.entries(sections) .forEach(([section,idArr]) => { idArr.forEach(id => { const niveau = parseInt(values.NvGroupe); // Put all our logic inside the if so it only runs if we need to use it. if(niveau > 1){ // use the section name, minus the repeating_ bit as the prefix for the attribute name. const sectionPrefix = section.replace(/repeating_/,''); // Create the full row string so we don't have to remake it for each calculation const row = `${section}_${id}_${sectionPrefix}`; // Convert to numbers const deg = parseInt(values[`${row}_Deg`]); const degMod = parseInt(values[`${row}_DegMod`]); const pdv = parseInt(values[`${row}_Pdv`]); // create our deg total attribute const coeff = 1.15; let degTotal = ((deg * 10) + degMod) * (coeff ^ (niveau - 1)); setObj[`${row}_Deg`] = Math.floor(degTotal / 10); setObj[`${row}_DegMod`] = setObj[`${row}_Deg`] % 10; setObj[`${row}_Pdv`] = pdv * (coeff ^ (niveau - 1)); } }) }) // Apply all of our changes at once // I almost always use the silent option on setAttrs // to avoid cascading event listeners which can really // bog your sheet down setAttrs(setObj,{silent:true}); }) }, ['NvGroupe'] ) }); Note that there should really be handling in your parseInts for what to do if the value is not parsable to an integer. Also, I would wonder why this needs to run on sheet startup. Running it on startup means that your sheet's values are likely going to change any time someone opens the sheet, which is likely not what you want.
1676552561

Edited 1676638652
Maïlare
Pro
Sheet Author
API Scripter
Hello Scott, Thanks for your detailed answer, your time and all the tips. I have changed NvGroupe by nvgroupe in my code but your code don't work :'( My code is on this Github if you want to see and test. The bestiary html code is on line 1029 and your code in line 2264. <a href="https://github.com/mailare49/Chroniques-D-Enyllion-Character-Sheet-For-Roll20" rel="nofollow">https://github.com/mailare49/Chroniques-D-Enyllion-Character-Sheet-For-Roll20</a>