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

Updating items in repeating area

I need to locate and update items within a repeating area when an attribute not in the repeating area changes.  Here is the script: on("change:STR-DM sheet:opened", function() { console.log("==============================="); console.log("Entering STR-DM change listener"); getAttrs(['STR-DM'],function(values) { console.log("Updating Bludgeon Weapons..."); getSectionIDs("repeating_bludgeon", function(idarray1) { console.log(" blud count: ".concat(idarray1.length)); for(var i=0; i < idarray1.length; i++) { console.log(" blud idlen:".concat(idarray1[i].length)); console.log(" blud id:".concat("|",idarray1[i],"|")); console.log(" looking for:".concat("|repeating_bludgeon_",idarray1[i],"_bludgeon-bulk-penalty|")); getAttrs(['repeating_bludgeon_' + idarray1[i] + '_bludgeon-bulk-penalty'], function(vals) { console.log("return count: ".concat(vals.length)); for(var j=0 ; j<vals.length; j++) console.log(" vals[".concat(j,"]: ",vals[j])); }) } }); }) }) And here is the console output produces when this triggers: Updating Bludgeon Weapons... blud count: 2 blud idlen:20 blud id:|-lwwxjzt9rxhsjch8r1l| looking for:|repeating_bludgeon_-lwwxjzt9rxhsjch8r1l_bludgeon-bulk-penalty| eval" class="frame-link-source" draggable="false"> blud idlen:20 blud id:|-lwwjrw4uoowar7-dv8v| eval" class="frame-link-source" draggable="false"> looking for:|repeating_bludgeon_-lwwjrw4uoowar7-dv8v_bludgeon-bulk-penalty| eval" class="frame-link-source" draggable="false"> return count: undefined as you can see, I get the IDs of the repeating items, but attempts to retrieve an attribute using that ID fail.   By looking at "eventInfo.sourceAttribute" in an "on:change" listener, I have confirmed that the string that appears in "looking for" above is a correct full name. There are also no "case" issues... the html fieldset in question is "repeating_bludgeon", and the attribute in question is "attr_bludgeon-bulk-penalty". What am I doing wrong?
1547834655
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
Hi Bob, There's a few things I notice with your code. The first one is that getAttrs should never be in a recursion like a for or each loop. I'd recommend the following changes: on("change:STR-DM sheet:opened", function() { console.log("==============================="); console.log("Entering STR-DM change listener"); getAttrs(['STR-DM'],function(values) { console.log("Updating Bludgeon Weapons..."); getSectionIDs("repeating_bludgeon", function(idarray1) {             const idCollection = [];//We'll collect all the attribute names in here. console.log(` blud count: ${idarray1.length}`);//using object literal notation makes your log declarations easier to read for(var i=0; i < idarray1.length; i++) { console.log(` blud idlen:${idarray1[i].length}`); console.log(` blud id:|${idarray1[i]}|`); console.log(` looking for:|repeating_bludgeon_${idarray1[i]}_bludgeon-bulk-penalty|`); idCollection.push(`repeating_bludgeon_${idarray1[i]}_bludgeon-bulk-penalty`);//push the attribute name into the collection array }             getAttrs(idCollection, function(vals) {//get all the attributes contained in the collection array console.log(`return count: ${vals.length}`);                 console.log(`vals:`);                 console.log(vals);//Let's make sure of the contents of the returned object.     for(var j=0 ; j<vals.length; j++) console.log(` vals[${j}]: ${vals[j]}`);//Note that for this for loop and for your one above, you could instead use an _.each or a .each }) }); }) }) These may not immediately fix your issue, but will provide some better troubleshooting info. One question I have is, have you set bludgeon-bulk-penalty in at least one repeating item yet?
ok. I can make the recursion change you suggest. (Eventually, it's gonna be even larger than that, as I have to retrieve items from four different repeating areas ultimately - but I can still put them all in one collection and do one really big getAttrs - the work to be done is the same regardless of the source repeating area. (The setAttrs may get interesting though.) bludgeon-bulk-penalty is set for each repeating item as the item is created - and I know that is working. I've tried other fields as well (including the bludgeon-name attribute (the assignment of which is what triggers the creation of all of the other attributes in the repeating item). Same result there too. I'll switch the recursion around and try again - will post result here when done.
OK... the object literals don't work at all: Updating Bludgeon Weapons... blud count: ${idarray1.length} blud idlen: ${idarray1[i].length} blud id: |${idarray1[i]}| looking for: |repeating_bludgeon_${idarray1[i]}_bludgeon-bulk-penalty| blud idlen: ${idarray1[i].length} blud id: |${idarray1[i]}| looking for: |repeating_bludgeon_${idarray1[i]}_bludgeon-bulk-penalty| return count: ${vals.length} I'll put the concat's back
1547840345
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
Yep, it's the only thing that I see that is odd in your code. Also, just as an explanation of some of the efficiency behind not putting getAttrs (and to a lesser extent setAttrs) in a recursion; the time it takes for getAttrs to return when looking for 1 attribute and 1000 attributes is practically identical. On the other hand, doing just 10 nested getAttrs will create a noticeable lag on the sheet as the asynchronous function will have to return for each one before moving to the next. Additionally, doing a recursion and causing multiple getAttrs to be fired at once can cause issues with your sheet as everything is asynchronous and so you can't be sure of which one will return first creating situations where the result returned can be apparently random.
1547840420
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
Hmm, what did you enter for the literal lines?
I copied your code.  But back to the real problem... here's the output with the concats back in place (no change from original problem!): Updating Bludgeon Weapons... blud count: 2 blud idlen: 20 blud id: |-lwwxjzt9rxhsjch8r1l| looking for: |repeating_bludgeon_-lwwxjzt9rxhsjch8r1l_bludgeon-bulk-penalty| blud idlen: 20 eval" class="frame-link-source" draggable="false"> blud id: |-lwwjrw4uoowar7-dv8v| looking for: |repeating_bludgeon_-lwwjrw4uoowar7-dv8v_bludgeon-bulk-penalty| return count: undefined eval" class="frame-link-source" draggable="false"> ​
here's the current source: on("change:STR-DM sheet:opened", function() { console.log("==============================="); console.log("Entering STR-DM change listener"); getAttrs(['STR-DM', 'STR-curr'],function(values) { console.log("Updating Bludgeon Weapons..."); getSectionIDs("repeating_bludgeon", function(idarray1) { console.log(' blud count: '.concat(idarray1.length)); const idCollection = []; for(var i=0; i < idarray1.length; i++) { console.log(' blud idlen: '.concat(idarray1[i].length)); console.log(' blud id: |'.concat(idarray1[i],'|')); console.log(' looking for: |repeating_bludgeon_'.concat(idarray1[i],'_bludgeon-bulk-penalty|')); idCollection.push('repeating_bludgeon_'+idarray1[i]+'_bludgeon-bulk-penalty'); } getAttrs(idCollection, function(vals) { console.log('return count: '.concat(vals.length)); for(var j=0 ; j<vals.length; j++) console.log(' vals['.concat(j,']: ',vals[j])); }) }); }) })
1547841513

Edited 1547841567
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
Can you put a log of vals itself in the getAttrs? after your return count log?
just in case I wasn't clear and it matters..... this is sheet worker code, not API
1547841676
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
Yep, I realize that, and other than getAttrs, setAttrs, and the other functions listed in the sheetworker wiki, there's not much of a difference between api and sheetworker.
1547842316
GiGs
Pro
Sheet Author
API Scripter
One thing I;m confused about in this code: why does this first getAttrs exist? getAttrs(['STR-DM', 'STR-curr'],function(values) { I cant see anywhere those attributes are actually used. But if they are going to be used later, they could be added to the second getAttrs statement (by adding them to the idCollection array).
Here's console.log(vals): Object { "repeating_bludgeon_-lwwxjzt9rxhsjch8r1l_bludgeon-bulk-penalty" : 0 , "repeating_bludgeon_-lwwjrw4uoowar7-dv8v_bludgeon-bulk-penalty" : 1 } And... it appears that vals contains the correct information!  The two penalties are correct.  So... it's the .length property that isn't defined?! I'm guessing that's because it's an Object, not an Array!!! So I'll have to rely on the length of the original collection. Aaand...  if I do this: getAttrs(idCollection, function(vals) { console.log('return count: '.concat(vals.length)); console.log(vals); for(var j=0 ; j<idarray1.length; j++) console.log("penalty ".concat(j,": ",vals[idCollection[j]])); }) i get this: Object { "repeating_bludgeon_-lwwxjzt9rxhsjch8r1l_bludgeon-bulk-penalty" : 0 , "repeating_bludgeon_-lwwjrw4uoowar7-dv8v_bludgeon-bulk-penalty" : 1 } penalty 0: 0 penalty 1: 1 I think I can get this to work now.  kludgy... but it'll do.
GiGs said: One thing I;m confused about in this code: why does this first getAttrs exist? getAttrs(['STR-DM', 'STR-curr'],function(values) { I cant see anywhere those attributes are actually used. But if they are going to be used later, they could be added to the second getAttrs statement (by adding them to the idCollection array). The code isn't finished yet.  the STR-DM will be used to affect the values of the bulk-penalty attributes, and the bulk-penalty attributes then get reset. Before I built the whole mess, I wanted to make sure I was properly retrieving values.  This is one of a number of things that happens in an on:change STR-DM event listener. Other functionality was already coded and working, and (at least to get started), I was tacking onto the end of it. My remaining frustration is that I can't figure out how to reference the results of the 'second' getAttrs by name! The vals object has the property:value pairs correctly... the property names are the full (with rowID) names of the repeating attributes I want... but I can't figure out how to specify the value I want by 'constructing' the attribute name!  There are going to be other attributes in there before I'm done, and I really don't want to get into something where I have to say (take the first and second attribute values... check against the third... update if necessary.  Now take the fourth and fifth... check against the sixth... update if necessary... etc.    I can code this, of course... but it's about as far from 'self-documenting' as one could possibly get.
1547844036
GiGs
Pro
Sheet Author
API Scripter
GiGs said: One thing I;m confused about in this code: why does this first getAttrs exist? getAttrs(['STR-DM', 'STR-curr'],function(values) { I cant see anywhere those attributes are actually used. But if they are going to be used later, they could be added to the second getAttrs statement (by adding them to the idCollection array). This is what I mean on("change:STR-DM sheet:opened", function () { console.log("==============================="); console.log("Entering STR-DM change listener"); getSectionIDs("repeating_bludgeon", function (idarray1) { console.log(' blud count: '.concat(idarray1.length)); const idCollection = []; for (var i = 0; i < idarray1.length; i++) { console.log(' blud idlen: '.concat(idarray1[i].length)); console.log(' blud id: |'.concat(idarray1[i], '|')); console.log(' looking for: |repeating_bludgeon_'.concat(idarray1[i], '_bludgeon-bulk-penalty|')); idCollection.push('repeating_bludgeon_' + idarray1[i] + '_bludgeon-bulk-penalty'); } idCollection.push('STR-DM'); idCollection.push('STR-curr'); getAttrs(idCollection, function (vals) { console.log('return count: '.concat(vals.length)); for (var j = 0; j < vals.length; j++) console.log(' vals['.concat(j, ']: ', vals[j])); }); }); });
1547844461

Edited 1547844693
GiGs
Pro
Sheet Author
API Scripter
Bob O. said: My remaining frustration is that I can't figure out how to reference the results of the 'second' getAttrs by name! The vals object has the property:value pairs correctly... the property names are the full (with rowID) names of the repeating attributes I want... but I can't figure out how to specify the value I want by 'constructing' the attribute name!  There are going to be other attributes in there before I'm done, and I really don't want to get into something where I have to say (take the first and second attribute values... check against the third... update if necessary.  Now take the fourth and fifth... check against the sixth... update if necessary... etc.    I can code this, of course... but it's about as far from 'self-documenting' as one could possibly get. The trick here is that you need to use the idArray as well. You dont loop through the idCollection, you loop through idArray. Something like for (var i = 0; i < idarray1.length; i++) {     let bulkpenalty = vals['repeating_bludgeon_' + idarray1[i] + '_bludgeon-bulk-penalty']; let anotherstat = vals['repeating_bludgeon_' + idarray1[i] + '_anotherstat']; } This will give you the value of those stats, and you can work with them. The order within vals or idcollection doesn't matter. the getAttrs line creates the vals object with the key:value  pairs, and you construct the key using idArray as you need it. Looping through idArray means you have one loop for each row of the repeating section, which is very efficient and easy to follow. This is also why you can combine both getAttrs operations into a single one as in my previous post, since you aren't looping through vals or idCollection, you dont have to worry about the non-repeating stats being in the loop.
1547845256
GiGs
Pro
Sheet Author
API Scripter
By the way, i recommend avoiding "-" in attribute names. better to use underscores instead. So STR_DM instead of STR-DM (or no symble at all like STRDM) This is because you can stumble into syntax errrors in your your sheetworkers if you aren't careful. For example, let strdm = vals.STR_DM is valid, but let strdm = vals.STR-DM will produce an error. You can avoid this error by using the alternative syntax: let strdm = vals['STR-DM']  (as you have to do when constructing attributes like the idarray ones in my previous post), but there are plenty of other places where this will trip you up so it's just better IMO to use underscores (or no symbol at all) over hyphens for attribute names.
I could do that, but the problem now seems to be finding a syntax that will let me take things out of the vals object by name.  Nothing I've tried so far is working!  I get the desire to minimize the number of asych things going on. But first, I have a more fundamental problem to solve: vals['repeating_bludgeon_'+<the id>+'_bludgeon-bulk-penalty'] doesn't work, nor does building the attribute name with .concat.  I really don't want to have to rely on knowing the order in which the getAttrs was constructed.
1547845667
GiGs
Pro
Sheet Author
API Scripter
This absolutely should work: let bulkpenalty = vals['repeating_bludgeon_' + idarray1[i] + '_bludgeon-bulk-penalty']; Can you share your full code?
GiGs said: ... Something like for (var i = 0; i < idarray1.length; i++) {     let bulkpenalty = vals['repeating_bludgeon_' + idarray1[i] + '_bludgeon-bulk-penalty']; let anotherstat = vals['repeating_bludgeon_' + idarray1[i] + '_anotherstat']; } This will give you the value of those stats, and you can work with them. ... This is the what's not working now!  I know the results are in vals... I know the values are assigned to properties that look like <string>+<id>+<string>, but when I try to reference them as above, the result is an 'undefined' value.
1547846107
GiGs
Pro
Sheet Author
API Scripter
I'll need to see your full code to be able to figure out why its not working.
Here's the full function code: on("change:STR-DM sheet:opened", function() { console.log("==============================="); console.log("Entering STR-DM change listener"); getAttrs(['STR-DM', 'STR-curr'],function(values) { var newSigned = (values['STR-DM']<0?"":"+") + values['STR-DM']; setAttrs({'STR-DM-signed': newSigned}); setAttrs({'STR-label': "STR (DM".concat(newSigned,")")}); setAttrs({'STR-full-label': "STR ".concat(values['STR-curr']," (DM",newSigned,")")}); console.log("Updating Bludgeon Weapons..."); getSectionIDs("repeating_bludgeon", function(idarray1) { console.log(' blud count: '.concat(idarray1.length)); const idCollection = []; for(var i=0; i < idarray1.length; i++) { console.log(' blud idlen: '.concat(idarray1[i].length)); console.log(' blud id: |'.concat(idarray1[i],'|')); console.log(' looking for: |repeating_bludgeon_'.concat(idarray1[i],'_bludgeon-bulk-penalty|')); idCollection.push('repeating_bludgeon_'+idarray1[i]+'_bludgeon-bulk-penalty'); } getAttrs(idCollection, function(vals) { console.log('return count: '.concat(vals.length)); console.log(vals); for(var j=0 ; j<idarray1.length; j++) { let bulkPenalty = vals['repeating_bludgeon_'+idarray1[i]+'_bludgeon-bulk-penalty']; console.log("bulkPenalty: ".concat(bulkPenalty)); } }) }); }) }) The code in bold italics was already in place... already working... and already slated to be modified to reduce to a single setAttrs call.  Please don't get off-topic by pointing that out here. The code beginning with the "updating Bludgeon..." log is the code in question. You'll note that I have EXACTLY your code in the last for loop to attempt to log the values of bulkPenalty.  Here's the complete console log for the event that triggers the code (including the stuff generated by things other than my console.log calls): =============================== Entering STR-DM change listener Really updating character sheet values Setting up repeating sections took until 19ms Finding list of dirty attributes took until 19ms Querytest took until 22ms Attribute cache compliation took until 23ms Set values (including auto-calcuating variables) took until 30ms Took 32ms Updating Bludgeon Weapons... blud count: 2 blud idlen: 20 blud id: |-lwwxjzt9rxhsjch8r1l| looking for: |repeating_bludgeon_-lwwxjzt9rxhsjch8r1l_bludgeon-bulk-penalty| blud idlen: 20 blud id: |-lwwjrw4uoowar7-dv8v| eval" class="frame-link-source" draggable="false"> looking for: |repeating_bludgeon_-lwwjrw4uoowar7-dv8v_bludgeon-bulk-penalty| return count: undefined Object { "repeating_bludgeon_-lwwxjzt9rxhsjch8r1l_bludgeon-bulk-penalty" : 0 , "repeating_bludgeon_-lwwjrw4uoowar7-dv8v_bludgeon-bulk-penalty" : 1 } bulkPenalty: undefined
1547848380

Edited 1547848627
GiGs
Pro
Sheet Author
API Scripter
Here's the problem: for(var j=0 ; j<idarray1.length; j++) { let bulkPenalty = vals['repeating_bludgeon_'+ idarray1[i] +'_bludgeon-bulk-penalty']; You changed i to j on the for line, but didnt change in idarray. That second line should be let bulkPenalty = vals['repeating_bludgeon_'+ idarray1[j] +'_bludgeon-bulk-penalty']; PS:  The code in bold italics was already in place... already working... and already slated to be modified to reduce to a single setAttrs call.  Please don't get off-topic by pointing that out here. Perfectly anticipated, I was going to comment on that, haha.
1547848675

Edited 1547849229
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
To iterate through the object, I'd recommend doing this: on("change:STR-DM sheet:opened", function() { console.log("==============================="); console.log("Entering STR-DM change listener"); getAttrs(['STR-DM', 'STR-curr'],function(values) { var newSigned = (values['STR-DM']<0?"":"+") + values['STR-DM']; setAttrs({'STR-DM-signed': newSigned}); setAttrs({'STR-label': "STR (DM".concat(newSigned,")")}); setAttrs({'STR-full-label': "STR ".concat(values['STR-curr']," (DM",newSigned,")")}); console.log("Updating Bludgeon Weapons..."); getSectionIDs("repeating_bludgeon", function(idarray1) { console.log(' blud count: '.concat(idarray1.length)); const idCollection = []; for(var i=0; i < idarray1.length; i++) { console.log(' blud idlen: '.concat(idarray1[i].length)); console.log(' blud id: |'.concat(idarray1[i],'|')); console.log(' looking for: |repeating_bludgeon_'.concat(idarray1[i],'_bludgeon-bulk-penalty|')); idCollection.push('repeating_bludgeon_'+idarray1[i]+'_bludgeon-bulk-penalty'); } getAttrs(idCollection, function(vals) { console.log('return count: '.concat(vals.length)); console.log(vals);                  /*for(var j=0 ; j<idarray1.length; j++) { let bulkPenalty = vals['repeating_bludgeon_'+idarray1[i]+'_bludgeon-bulk-penalty']; console.log("bulkPenalty: ".concat(bulkPenalty)); }                 I've commented out this code. We're going to convert this to an _.each loop (can also be done using .each; _.each is just my preference)*/                 const valKeys = _.keys(vals);//We extract the keys of the object for use as we need them. I extract them here so that I don't have to continuously do _.keys(vals) throughout the code. We can then filter and manipulate this starting array as needed                 let bulkPenKeys = _.filter(valKeys,(key)=>{return /bulk-penalty$/.test(key)});//This step isn't really necessary with your code as it is now, but when your getAttrs is returning many attributes you'll want to be able to easily and quickly look for only specific keys                  _.each(bulkPenKeys,(key)=>{                     let bulkPenalty = vals[key];                     console.log('bulkPenalty: '+bulkPenalty);                 }); }) }); }) }) That should  take care of your problem.
1547848840
GiGs
Pro
Sheet Author
API Scripter
By the way if you change for(var i=0; i < idarray1.length; i++) To  for(let i=0; i < idarray1.length; i++) You can keep using i in following for statements without issue. I'm guessing you changed to j because of an error something like "i is already defined" Let and var are both ways of definign a variable, but variables created with let only exist within their scope. So in the above for loop, when you create the variable i, it is then (simple explanation) destroyed when the loop ends, and you can reuse the letter i later without issue. It helps avoids problems like this.
OK... Problem solved! Thanks to both Scott C and GiG for their input. I've now had drilled into me the importance of remembering the nature of asynchronous callbacks, the importance of remembering to keep scope in mind with variables, arrays, objects, and the importance of remembering the differences between arrays and objects.  (One incidental questions arose that I'd like opinions on: Would it be worth my time and effort to sweep through my whole sheet to (a) get rid of uppercase characters in attribute names and/or (b) get rid of hyphens in favor of underscores? Why wasn't the "repeating_blade_${bladeIds[i]}_blade_bulk" syntax working? (Or should I go back one more time and test this now that everything else is working and tested?) To satisfy the curious, here's what the finished code looks like (accessing all six repeating arrays... a single getAttrs... a single setAttrs...: everything nested so that nothing gets done prematurely because of asynchronicity. on("change:STR-DM sheet:opened", function() { const idCollection = []; idCollection.push('STR-DM'); getSectionIDs("repeating_bludgeon", function(bludgeonIds) { for (let i = 0 ; i < bludgeonIds.length ; i++) idCollection.push('repeating_bludgeon_'+bludgeonIds[i]+'_bludgeon-bulk'); getSectionIDs("repeating_blade", function(bladeIds) { for(let i=0; i < bladeIds.length; i++) idCollection.push('repeating_blade_'+bladeIds[i]+'_blade-bulk'); getSectionIDs("repeating_archaic", function(archaicIds) { for(let i=0; i < archaicIds.length; i++) idCollection.push('repeating_archaic_'+archaicIds[i]+'_archaic-bulk'); getSectionIDs("repeating_energy", function(energyIds) { for(let i=0; i < energyIds.length; i++) idCollection.push('repeating_energy_'+energyIds[i]+'_energy-bulk'); getSectionIDs("repeating_slug", function(slugIds) { for(let i=0; i < slugIds.length; i++) idCollection.push('repeating_slug_'+slugIds[i]+'_slug-bulk'); getSectionIDs("repeating_hwport", function(hwportIds) { for(let i=0; i < hwportIds.length; i++) idCollection.push('repeating_hwport_'+hwportIds[i]+'_hwport-bulk'); getAttrs(idCollection,function(values) { let strDM = values['STR-DM']; let newSigned = (strDM < 0 ? "" : "+") + strDM; var idUpdates = {'STR-DM-signed' : newSigned, 'STR-label' : "STR (DM".concat(newSigned,")"), 'STR-full-label' : "STR ".concat(values['STR-curr']," (DM",newSigned,")")}; for (let i = 0 ; i < bludgeonIds.length ; i++) { let bulk = values['repeating_bludgeon_'+bludgeonIds[i]+'_bludgeon-bulk']; let bulkPenalty = Math.max(0, (bulk - strDM)); idUpdates['repeating_bludgeon_'+bludgeonIds[i]+'_bludgeon-bulk-penalty'] = bulkPenalty; } for (let i = 0 ; i < bladeIds.length ; i++) { let bulk = values['repeating_blade_'+bladeIds[i]+'_blade-bulk']; let bulkPenalty = Math.max(0, (bulk - strDM)); idUpdates['repeating_blade_'+bladeIds[i]+'_blade-bulk-penalty'] = bulkPenalty; } for (let i = 0 ; i < archaicIds.length ; i++) { let bulk = values['repeating_archaic_'+archaicIds[i]+'_archaic-bulk']; let bulkPenalty = Math.max(0, (bulk - strDM)); idUpdates['repeating_archaic_'+archaicIds[i]+'_archaic-bulk-penalty'] = bulkPenalty; } for (let i = 0 ; i < energyIds.length ; i++) { let bulk = values['repeating_energy_'+energyIds[i]+'_energy-bulk']; let bulkPenalty = Math.max(0, (bulk - strDM)); idUpdates['repeating_energy_'+energyIds[i]+'_energy-bulk-penalty'] = bulkPenalty; } for (let i = 0 ; i < slugIds.length ; i++) { let bulk = values['repeating_slug_'+slugIds[i]+'_slug-bulk']; let bulkPenalty = Math.max(0, (bulk - strDM)); idUpdates['repeating_slug_'+slugIds[i]+'_slug-bulk-penalty'] = bulkPenalty; } for (let i = 0 ; i < hwportIds.length ; i++) { let bulk = values['repeating_hwport_'+hwportIds[i]+'_hwport-bulk']; let bulkPenalty = Math.max(0, (bulk - strDM)); idUpdates['repeating_hwport_'+hwportIds[i]+'_hwport-bulk-penalty'] = bulkPenalty; } setAttrs(idUpdates); }) }) }) }) }) }) }) })
1547928389
GiGs
Pro
Sheet Author
API Scripter
Would it be worth my time and effort to sweep through my whole sheet to (a) get rid of uppercase characters in attribute names and/or (b) get rid of hyphens in favor of underscores? It's not absolutely necessary to do these, but if you still have a lot of coding to do, it could be worth it.  Why wasn't the  "repeating_blade_${bladeIds[i]}_blade_bulk"  syntax working? (Or should I go back one more time and test this now that everything else is working and tested?) I came into this thread late so can't say definitively. but I'd expect it was other issues causing it and it would be fine now. In principle, it should work. I have two comments on your finished code. Firstly, your worker is incomplete. At the moment, when str-DM changes, your bulk penalty is recalculated. But what about when values change within your repeating fieldset? When you add or remove entries, or change the value of them? That doesnt trigger any change, so your bulk penalty will be wrong until you change STR-DM again. Secondly,  as far as I can see, you never use parts of one repeating set to affect another. So nesting all 6 repeating sets in one worker like this is inefficient. A better approach would be to have 6 different workers, one for each set, which would also let you add the relevant parts of repeating set to the change line, without it getting tooo clunky. It might not seem efficient to have 6 workers instead of one, but the code is cleaner, and you can use a foreach loop to write it as a single function to cope with all 6 workers. I'll post a version later once I've woken up.  Regardless, even if you stick with one big mega worker, you probably need to think about handling changes in the repeating sets.
The change directly from the repeating area was already coded - and working fine. (And that's more efficient because it only has to worry about one entry on one repeating list. They really only trigger when a new repeating item is created (a "change" on the repeating item name causes a whole raft of attributes to be loaded from static info, and once loaded for a given item name, the base data (in this case, the "...-bulk" attribute.  bulk-penalty gets calculated at this time as well (based on the current STR-DM value). But... When STR-DM changes, I have to look through every weapon category (bludgeon, blade, unarmed, archaic, energy, slug, and heavy (hwport)) to find those that have an attribute which causes aiming to be dependent on strength.  Hence one STR-DM listener to check all lists.  They're nested because the getSectionIDs is asynchronouse - the only way to ensure that idCollection is complete before I start actually processing it is to nest. Alternatively, I could spawn the seven GetSectionIDs 'simultaneously', but then each one would have to process its own result... and I end up with 7 GetAttrs and 7 SetAttr calls. The only efficiency I was still considering was to actually see if a recalc of bulk-penalty is even necessary.  If the retrieved attr_...-bulk value is -3, then the penalty will always be zero - and will have already been calculated to be zero when the item was created.  So I could check for -3, and if found, don't bother recalculating and stuffing into the array that feeds the setAttrs. In practice, that would probably cut the setAttrs down by something like 50 to 75% for the average player.  (I'm an old coding dinosaur though... from the days when you were taught that the logical operations needed to see if work was necessary could actually be more time consuming than the recalc and save process.)
1547935842

Edited 1547936426
GiGs
Pro
Sheet Author
API Scripter
This is how I'd do your worker, with a bit of explanation afterwards: on("change:STR-DM sheet:opened", function () { getAttrs('STR-DM', function (values) { let strDM = values['STR-DM']; let newSigned = (strDM < 0 ? "" : "+") + strDM; var idUpdates = { 'STR-DM-signed': newSigned, 'STR-label': "STR (DM".concat(newSigned, ")"), 'STR-full-label': "STR ".concat(values['STR-curr'], " (DM", newSigned, ")") }; setAttrs(idUpdates); }); }); ['bludgeon', 'blade', 'archaic', 'energy', 'slug', 'hwport'].forEach(function (item) { on("change:STR-DM sheet:opened", function () { const idCollection = []; idCollection.push('STR-DM'); getSectionIDs(`repeating_${item}`, function (ids) { for (let i = 0; i < ids.length; i++) idCollection.push(`repeating_${item}_${ids[i]}_${item}-bulk`); getAttrs(idCollection, function (values) { let strDM = values['STR-DM'];                 let idUpdates = {}; for (let i = 0; i < ids.length; i++) { let bulk = values[`repeating_${item}_${ids[i]}_${item}-bulk`]; let bulkPenalty = Math.max(0, (bulk - strDM)); idUpdates[`repeating_${item}_${ids[i]}_${item}-bulk-penalty`] = bulkPenalty; } setAttrs(idUpdates); }); }); }); }); I use the string literal syntax in a few places, but I see you've already used that elsewhere so don't need to explain it. But just in case `repeating_${item}_${ids[i]}_${item}-bulk` is equivalent to 'repeating_' + item + '_' + ids[i] + '_' + item + '-bulk' It's just the string literal version is a bit easier to type and manage when you have a lot of variables mixed with strings. The forEach lets you create a single function that builds the separate sheetworkers needed for each repeating section. You said: The change directly from the repeating area was already coded - and working fine. (And that's more efficient because it only has to worry about one entry on one repeating list.  That's why this approach is better, and you can copy whatever routine you have setting up those workers into this one too, for increased efficiency. The way you named your repeating sections made it extremely easy to set this up. The format: repeating_ item _id_ item _bulk meant it was very easy to use a variable to replace those sections of the attribute name. PS your big macro is missing a bunch of semicolons at the ends of each }) . They aren't always necessary, but when an error crops up, missing semi-colons can make errors harder to track down so its worth trying to keep them consistent.
1547936292
GiGs
Pro
Sheet Author
API Scripter
If you wanted to expand the previous function to handle repeating section changes, you'd just change the on(change line from this: on("change:STR-DM sheet:opened", function () { To something like on(`change:STR-DM change:repeating_${item} :${item}_bulk sheet:opened`, function () { or on(`change:STR-DM change:repeating_${item} remove:repeating_${item} sheet:opened`, function () { And then add whatever extra setup stuff you need in the function
are there limits as to where the object literal syntax works or doesn't work?  When I try to use it in the assignment portion of a setAttrs call, it doesn't translate. setAttrs({'repeating_blade_blade-ap-label': 'AP: ${bladeWeapons[i].ap}' }); ...ends up giving a label on my character sheet that says AP: ${bladeWeapons[i].ap} instead of AP:2 The concatenate syntax: setAttrs({'repeating_blade_blade-ap-label': 'AP: '.concat(bladeWeapons[i].ap) }); provides the correct result.
1547948647

Edited 1547948655
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
Your problem is that you aren't using the proper syntax, you are using string syntax. You need to use the literal syntax. This is ` vs '. So that should be: setAttrs({'repeating_blade_blade-ap-label': `AP: ${bladeWeapons[i].ap}` });
1547948753

Edited 1547948823
GiGs
Pro
Sheet Author
API Scripter
I see the issue. You are using the wrong kind of quotes. String literals require the backtick (found on my keyboard on the key to the left of the number 1). it looks like this: `, not ' or ". So the above example setAttrs({'repeating_blade_blade-ap-label': 'AP: ${bladeWeapons[i].ap}' }); should be etAttrs({'repeating_blade_blade-ap-label': `AP: ${bladeWeapons[i].ap}` });
1547948795
GiGs
Pro
Sheet Author
API Scripter
Ninja'd!
ah!!! not the easiest thing to notice!  thanks.
1548452111

Edited 1548452338
Hoping somebody can help here... I'm now trying to use the suggestion made by GiGs above to reduce the number of copies of similar code in my sheet worker script by using the foreach... approach, but I'm having syntax problems... Here's what I've got: var cutlass = {name:"Cutlass", tl:"2", damage:"3d6", weight:"4", cost:"200", ap:"0", bulky: "0", veryBulky:"0", singleUse:"0", psi:"0" }; var dagger = {name:"Dagger", tl:"1", damage:"1d6+2", weight:"1", cost:"10", ap:"0", bulky: "0", veryBulky:"0", singleUse:"0", psi:"0" }; var greatAxe = {name:"Great Axe", tl:"2", damage:"4d6+2", weight:"10", cost:"750", ap:"0", bulky: "0", veryBulky:"1", singleUse:"0", psi:"0" }; var bladeWeapons = [ cutlass, dagger, greatAxe ]; var club = {name:"Club", tl:"1", damage:"2d6", weight:"3", cost:"0", ap:"0", bulky: "0", veryBulky: "0", smasher:"0", singleUse:"0", stun:"0" }; var mace = {name:"Mace", tl:"1", damage:"2d6+2", weight:"3", cost:"20", ap:"0", bulky: "1", veryBulky: "0", smasher:"1", singleUse:"0", stun:"0" }; var staff = {name:"Staff", tl:"1", damage:"2d6", weight:"3", cost:"0", ap:"0", bulky: "0", veryBulky: "0", smasher:"0", singleUse:"0", stun:"0" }; var bludgeonWeapons = [ club, mace, staff ]; ['blade','bludgeon'].forEach(function(wpntype) { on (`change:repeating_${wpntype}:${wpntype}-name sheet:opened`, function() { console.log("======================================="); console.log(`Entering new ${wpntype} listener`); getAttrs([`repeating_${wpntype}_${wpntype}-name`], function(values) { for (i in values) console.log(`...retrieved ${i}: ${values[i]}`); // returns expected value(s) console.log ("...checking ",`${wpntype}Weapons`.length, " weapons..."); // returns expected value console.log ("...looking for ",values[`repeating_${wpntype}_${wpntype}-name`]); // returns expected value for (var i = 0 ; i < `${wpntype}Weapons`.length ; i++) { console.log("...trying ", `${wpntype}Weapons`[i].name); // returning 'undefined' if (`${wpntype}Weapons`[i].name == values[`repeating_${wpntype}_${wpntype}-name`]) { console.log("...found it at position: ",i); // // more stuff will go here when this problem is solved. // break; } } console.log("...done looking"); }); }); }); and here's the result when I try to define a blade on the character sheet... ======================================= Entering new blade listener ...retrieved repeating_blade_blade-name: Cutlass ...checking 3 weapons... ...looking for Cutlass ...trying undefined // 3 repeats ...done looking So... the correct version of the listener fires (the 'blade' version)... and the syntax `${wpntype}Weapons`.length works fine to get the number of items in the bladeWeapons array.  And `repeating_${wpntype}_${wpntype}-name` retrieves the proper weapon name from the character sheet. But when I try to do a name check to select the correct one of the three, all I get is 'undefined'.  So... `${wpntype}Weapons`[i].name apparently isn't the correct syntax to find the entry in the bladeWeapons array that matches the weapon name brought in by the getAttrs function.  But what is the correct syntax?!!!  I've tried about 30 variants involving (both dot notation and bracket notation for the name property... moving the reverse quotes and/or braces around...but I haven't hit on the syntax that works. Once I have that syntax, the intention is to retrieve selected property values and setAttr them to the character sheet. (NOTE: data arrays at top have been simplified for showing here. There are more than 3 entries, and there are currently 7 full arrays... but 2 arrays of 3 each is enough to explain my problem.) Any help is greatly appreciated.
1548453451

Edited 1548455778
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
You've got some issues with your use of the literal syntax: var cutlass = {name:"Cutlass", tl:"2", damage:"3d6", weight:"4", cost:"200", ap:"0", bulky: "0", veryBulky:"0", singleUse:"0", psi:"0" }; var dagger = {name:"Dagger", tl:"1", damage:"1d6+2", weight:"1", cost:"10", ap:"0", bulky: "0", veryBulky:"0", singleUse:"0", psi:"0" }; var greatAxe = {name:"Great Axe", tl:"2", damage:"4d6+2", weight:"10", cost:"750", ap:"0", bulky: "0", veryBulky:"1", singleUse:"0", psi:"0" }; var bladeWeapons = [ cutlass, dagger, greatAxe ]; var club = {name:"Club", tl:"1", damage:"2d6", weight:"3", cost:"0", ap:"0", bulky: "0", veryBulky: "0", smasher:"0", singleUse:"0", stun:"0" }; var mace = {name:"Mace", tl:"1", damage:"2d6+2", weight:"3", cost:"20", ap:"0", bulky: "1", veryBulky: "0", smasher:"1", singleUse:"0", stun:"0" }; var staff = {name:"Staff", tl:"1", damage:"2d6", weight:"3", cost:"0", ap:"0", bulky: "0", veryBulky: "0", smasher:"0", singleUse:"0", stun:"0" }; var bludgeonWeapons = [ club, mace, staff ]; ['blade','bludgeon'].forEach(function(wpntype) { on (`change:repeating_${wpntype}:${wpntype}-name sheet:opened`, function() { console.log("======================================="); console.log(`Entering new ${wpntype} listener`); getAttrs([`repeating_${wpntype}_${wpntype}-name`], function(values) { for (i in values) console.log(`...retrieved ${i}: ${values[i]}`); // returns expected value(s) //console.log ("...checking ",`${wpntype}Weapons`.length, " weapons...");You're using what I'm reasonably sure is incorrect console.log syntax, I'm actually surprised this works at all see my replacement on the next line|| returns expected value                            console.log(`...checking ${`${wpntype}Weapons`.length} weapons...`);//What you were using was the syntax for concatenating objects together, It works, but is probably not ideal. console.log ("...looking for ",values[`repeating_${wpntype}_${wpntype}-name`]); // returns expected value for (var i = 0 ; i < `${wpntype}Weapons`.length ; i++) {//I'd really recommend using .each, .foreach, or _.each instead of a for loop here; or _.find to have it short circuit when it finds the right one //console.log("...trying ", `${wpntype}Weapons`[i].name);Here's your problem, you are looking for the name key of the Ith bit of blade/bludgeonWeapons, but blade/bludgeonWeapons is just an array of strings, this log would work easiest as my substitution on the next line || returning 'undefined'                    console.log(`...trying ${`${wpntype}Weapons`}); if (`${wpntype}Weapons`[i] === values[`repeating_${wpntype}_${wpntype}-name`]) {//Had the same problem as in your console.log one line above. I've corrected it here. I've also changed this to use === instead of == so that there are no possibilities of type switching giving an erroneous positive; === is also more performant. console.log("...found it at position: ",i); // // more stuff will go here when this problem is solved. // break; } } console.log("...done looking"); }); }); }); Hope that helps, Scott EDIT: Note that I was wrong about your console.log syntax being incorrect. It actually is correct, just not how I typically log things.
I know I have a syntax problem! That's why I'm asking for help :) Not sure what you meant about blade/bludgeon being arrays of strings.  bladeWeapons and bludgeonWeapons are arrays of objects, each object contains a series of key/value pairs (where the values all happen to be strings).  My problem is identifying the correct syntax for finding the value of a property of an object that is part of an array of objects, and where that array is one of several which I select based on the value of a parameter (wpntype) passed into the function. (If wpntype = blade, then I want to ultimately be accessing the contents of the bladeWeapons[i] object, where i is the index for whatever object in the array has the same 'name' property as the name value that triggered the listener in the first place (e.g. "Cutlass"). The correct listener is firing:  when I'm looking for Cutlass data, the on("change: repeating_blade_blade-name" ... listener is firing. After the getAttrs call, I can see "blade" as wpntype and "Cutlass" as `repeating_${wpntype}_${wpntype}-name` . What I can't correctly access is bladeWeapon[0].name (which would also be "Cutlass", and would tell me that I want the 0th element of the bladeWeapons array for all my other properties. ${wpntype}Weapon[i].name (with or without the reverse quotes tried in a number of different places) apparently isn't it. I've tried `${wpntype}`Weapon[i].name , `${wpntype}Weapon`[i].name . (I've even tried replacing the .name with ['name'] , to no avail. (The console.log stuff seemed to be working, as you later noted.  It is also currently the product of experimentation with the object literals to see if I could work out a syntax that worked.  My usual console.logs are a lot cleaner.)
Okay... I'm liking the _.find instead of the for loop, but I still have the same problem... If I do:             var useWeapon = _.find(bladeWeapons, {name: values[`repeating_${wpntype}_${wpntype}-name`]}); (Note that I hardcoded bladeWeapons in here just for testing purposes) ... useWeapon gets a copy of the object I want, and accessing its properties is simple.  But this now brings me back to the original question:  what syntax will allow me to use the wpntype parameter to "construct" the first argument in the find function?   As before... `${wpntype}`Weapons and `${wpntype}Weapons` don't work.
1548471749

Edited 1548471820
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
if you're using _.find you need to actually test the values in bladeWeapons, i'd recommend this: var useWeapon = _.find(bladeWeapons, (weapon)=>{     return weapon.name === values[`repeating_${wpntype}_${wpntype}-name`]; }); console.log(useWeapon);
1548478687

Edited 1548481710
GiGs
Pro
Sheet Author
API Scripter
I think I know what you're doing here, and I have an idea for a better approach. I think your underlying approach is a bit more convoluted than it needs to be. There's no need to loop through your objects to find which one is selected: you already know which is selected, because it's the weapon that has just changed. Just having this in the code will reveal it: const weapon = values[`repeating_${wpntype}_${wpntype}-name`]; The data object at the start is your main problem. If you structure the data batter, your code will be simpler. Something like this (I'm guessing that you want to fill attributes within the same repeating set with the values in your initial data): const weapons = { 'Cutlass': {tl:"2", damage:"3d6", weight:"4", cost:"200", ap:"0", bulky: "0", veryBulky:"0", singleUse:"0", psi:"0" }, 'Dagger': {tl:"1", damage:"1d6+2", weight:"1", cost:"10", ap:"0", bulky: "0", veryBulky:"0", singleUse:"0", psi:"0" }, 'Great Axe': {tl:"2", damage:"4d6+2", weight:"10", cost:"750", ap:"0", bulky: "0", veryBulky:"1", singleUse:"0", psi:"0" }, 'Club': {tl:"1", damage:"2d6", weight:"3", cost:"0", ap:"0", bulky: "0", veryBulky: "0", smasher:"0", singleUse:"0", stun:"0" }, 'Mace': {tl:"1", damage:"2d6+2", weight:"3", cost:"20", ap:"0", bulky: "1", veryBulky: "0", smasher:"1", singleUse:"0", stun:"0" }, 'Staff': {tl:"1", damage:"2d6", weight:"3", cost:"0", ap:"0", bulky: "0", veryBulky: "0", smasher:"0", singleUse:"0", stun:"0" }, }; ['blade','bludgeon'].forEach(function(wpntype) { on (`change:repeating_${wpntype}:${wpntype}-name sheet:opened`, function() { console.log("======================================="); console.log(`Entering new ${wpntype} listener`); getAttrs([`repeating_${wpntype}_${wpntype}-name`], function(values) { const weapon = values[`repeating_${wpntype}_${wpntype}-name`]; console.log(`Weapon: ${weapon}`); const weaponStats = weapons[weapon]; console.log(`Weapon Stats: ${JSON.stringify(weaponStats)}`); const settings = {}; Object.keys(weaponStats).forEach(stat => { settings[`repeating_${wpntype}_${wpntype}-${stat}`] = weaponsStats[stat]; // this is the tricky line }); console.log(`Output Settings: ${JSON.stringify(settings)}`); setAttrs(settings); console.log("...done looking"); }); }); }); A couple of things to notice here:  Putting all the weapon stats within a single object means its easier to extract data using the weapon name. This assumes that your weapon names are chosen from a dropdown, so you know they'll always be valid.  If that's not the case, you'd need to test weaponStats is valid before proceeding with the rest of the worker. I've put all the blade and bludgeon weapons in the same object. The way this is structured, it should work fine. Blade and bludgeon fieldsets have different attributes, but you dont need to check for that because you have built the variables to have the correct data for their fieldset. Object.keys returns a list of all the keys in the variable, and this loops through them, and for each key builds an attribute name, sets that as a key in the settings object, and gives it he appropriate value. That leads to the tricky line: settings[`repeating_${wpntype}_${wpntype}-${stat}`] = weaponsStats[stat]; // this is the tricky line I've had to guess at what your attributes are, within the fieldset. If they have the same names as the variables you've used in your weapon vars, it will be fine. You should change them to match exactly. (And should probably make them all lower case in both the variables and the fieldset html for simplicity). 
GiGs said: I think I know what you're doing here, and I have an idea for a better approach. I think your underlying approach is a bit more convoluted than it needs to be. There's no need to loop through your objects to find which one is selected: you already know which is selected, because it's the weapon that has just changed. ... An excellent suggestion!!  And one that works perfectly for me. Sometimes it's really good to have a second, third, or fourth perspective on a problem! This round of problems is finished... now on to find the next one!