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
This post has been closed. You can still view previous posts, but you can't post any new replies.

Can't change two attribute at once

1547658201

Edited 1547658370
Loki
Sheet Author
Hi, I've stumbled over a really annoying issue that I, for now, devoted way too much time to. Especially because I'm suspecting that another user could see the solution on the first glance. There are two Sheet Worker Scripts: One that listenes to three specific input boxes (because there are three types of mana) where users can note their spent mana and subtracts the noted mana from the pool. This works perfectly fine. The second script is part of an interface to automatically subtract a specific amount of any of the three kinds of mana (in fact changing the content of the input fields). The mana boxes look like this: In HTML: <td align="center"><input type="number" name="attr_fokus_t" readonly="readonly" value=0 style="text-align: center; background-color:#81BEF7;" /></td> <td align="center">K: <input type="number" name="attr_fokus_k" value=0 style="text-align: center; background-color:#81BEF7;" /></td> <td align="center">E: <input type="number" name="attr_fokus_e" value=0 style="text-align: center; background-color:#81BEF7;" /></td> <td align="center">V: <input type="number" name="attr_fokus_v" value=0 style="text-align: center; background-color:#81BEF7;" /></td> ( I know, neither table centered design nor inline CSS are good ... ) The script that listens to changes looks like this: on("change:fokus_k change:fokus_e change:fokus_v", function(f) { getAttrs(["fokus_k", "fokus_e", "fokus_v", "fokus_t", "fokus"], function(v) { let update = {}; let newValue = f.newValue || 0; let previousValue = f.previousValue || 0; if (f.sourceAttribute == "fokus_k") { update["fokus_t"] = +v.fokus_t - (+newValue - +previousValue); } else if (f.sourceAttribute == "fokus_e") { update["fokus_t"] = +v.fokus_t - (+newValue - +previousValue); } else if (f.sourceAttribute == "fokus_v") { update["fokus_t"] = +v.fokus_t - (+newValue - +previousValue); } setAttrs(update); }); }); It basically only listens and changes the attribute "fokus_t" (which stands for the mana pool that is depleted / refilled). The second script looks like this: on("change:fokusv change:fokuse change:fokusk change:fokuskzue", function(f) { getAttrs(["fokusv", "fokuse", "fokusk", "fokuskzue", "fokusnum", "fokus_k", "fokus_e", "fokus_v"], function(v) { let fok = +v.fokusnum; let update = {}; update["fokusnum"] = 0; if (v.fokusv == true) { update["fokus_v"] = +v.fokus_v + +fok; update["fokusv"] = false; } else if (v.fokusk == true) { update["fokus_k"] = +v.fokus_k + +fok; update["fokusk"] = false; } else if (v.fokuse == true) { update["fokus_e"] = +v.fokus_e + +fok; update["fokuse"] = false; } else if (v.fokuskzue == true) { update["fokus_k"] = +v.fokus_k - +fok; update["fokus_e"] = +v.fokus_e + +fok; update["fokuskzue"] = false; } setAttrs(update); }); }); It checks which of the four buttons (option fields) is pressed, does the math and sets the option field to false. Everything works fine up to the last "else if" block. This button just transfers mana from field to another. Whenever I try to change two attributes ("fokus_k" and "fokus_e" in this case) only the latter change ("fokus_e") is made. When I remove one of the two commands the other one gets executed, no matter which one. By subtracting from "fokus_k" it should trigger the first script and add the same amount to "fokus_t", to then add the amount to "fokus_e" and subtract it from "fokus_t". So I came to the conclusion that somewhat seems to prevent the script from changing to attributes that are listended to in another script. But to be honest I don't know why. So I think I have another problem here that I just can't see. Maybe you can? Greetings, Loki [Edit]attribute s of course[/Edit]
1547663989
GiGs
Pro
Sheet Author
API Scripter
Why do you use double signs everywhere? For example, this:             update["fokus_k"] = +v.fokus_k - +fok; update["fokus_e"] = +v.fokus_e + +fok; should be             update["fokus_k"] = v.fokus_k - fok; update["fokus_e"] = v.fokus_e + fok; I'll have a better look at the script later once I've woken up and see if I can spot an issue, but in the meantime try this and see if it changes behaviour.
1547672652
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
Hmm, well, GiGs has already pointed out one issue with your code. I think I've got a few more to add too. Some of this probably has nothing to do with your issue, and some of it probably is contributing to it. I'll annotate your code: on("change:fokus_k change:fokus_e change:fokus_v", function(f) {//this change event runs when any change to these is made, whether it's from a user input or a sheetworker. getAttrs(["fokus_k", "fokus_e", "fokus_v", "fokus_t", "fokus"], function(v) { let update = {}; let newValue = f.newValue || 0; let previousValue = f.previousValue || 0; if (f.sourceAttribute == "fokus_k") {//You should really use === instead of ==. === looks for same type as well as value, while == will attempt to type convert either side of the argument to get a match. This means that 'a String' == true will return TRUE, while 'a String' === true will not (and is much faster). update["fokus_t"] = v.fokus_t - (newValue - previousValue); } else if (f.sourceAttribute == "fokus_e") { update["fokus_t"] = v.fokus_t - (newValue - previousValue); } else if (f.sourceAttribute == "fokus_v") { update["fokus_t"] = v.fokus_t - (newValue - previousValue); } setAttrs(update); }); }); on("change:fokusv change:fokuse change:fokusk change:fokuskzue", function(f) {//as with your previous worker, this one will be triggered by any change event (user or sheetworker initiated) getAttrs(["fokusv", "fokuse", "fokusk", "fokuskzue", "fokusnum", "fokus_k", "fokus_e", "fokus_v"], function(v) {//I'd look at changing your name scheme. Keeping fokusk vs fokus_k straight is going to be a potential source of errors let fok = v.fokusnum; let update = {}; update["fokusnum"] = 0; if (v.fokusv == true) {//See the discussion above about === v ==. Here the better option is to simply do if(v.fokusv){... . Note that with this method, or as you have it now, if v.fokusv ever equals the number 0 (as in is a number rather than text), then this if won't be triggered even though the fokusv key does exist in the v object. I'll discuss a better way to do this down below update["fokus_v"] = v.fokus_v + fok; update["fokusv"] = false;//Attributes on the sheet don't handle true booleans well. I'd recommend setting this as a string instead. } else if (v.fokusk == true) { update["fokus_k"] = v.fokus_k + fok; update["fokusk"] = false;//Attributes on the sheet don't handle true booleans well. I'd recommend setting this as a string instead. } else if (v.fokuse == true) { update["fokus_e"] = v.fokus_e + fok; update["fokuse"] = false;//Attributes on the sheet don't handle true booleans well. I'd recommend setting this as a string instead. } else if (v.fokuskzue == true) { update["fokus_k"] = v.fokus_k - fok; update["fokus_e"] = v.fokus_e + fok; update["fokuskzue"] = false;//Attributes on the sheet don't handle true booleans well. I'd recommend setting this as a string instead. } setAttrs(update);//This setAttrs will trigger any sheetworkers that listen for the attributes contained as keys in update. This will cause a feedback loop since this is setting attributes that this worker listens to. }); }); So, none of this really answers your question about what's going on, but as I was rereading your post, something struck me: By subtracting from "fokus_k" it should trigger the first script and add the same amount to "fokus_t", to then add the amount to "fokus_e" and subtract it from "fokus_t".  It is rarely a good idea to design sheetworkers so that they cascade change events (by which I mean, the setAttrs in one is meant to trigger the change listener of a second). Doing cascades like this makes the sheet run slower, and can cause unexpected behavior as you then have multiple asynchronous events being fired which may or may not pick up changes done by something else. Why not just do the part that you are trying to trigger when you do the setting initially? Maybe something like this (assuming I've understood the need properly): on("change:fokusv change:fokuse change:fokusk change:fokuskzue", function(f) { getAttrs(["fokusv", "fokuse", "fokusk", "fokuskzue", "fokusnum", "fokus_k", "fokus_e", "fokus_v"], function(v) { let fok = v.fokusnum; let update = {}; update["fokusnum"] = 0; if (v.fokusv == true) { update["fokus_v"] = v.fokus_v + fok; update["fokusv"] = false; } else if (v.fokusk == true) { update["fokus_k"] = v.fokus_k + fok; update["fokusk"] = false; } else if (v.fokuse == true) { update["fokus_e"] = v.fokus_e + fok; update["fokuse"] = false; } else if (v.fokuskzue == true) { update["fokus_k"] = v.fokus_k - fok; update["fokus_e"] = v.fokus_e + fok + v.fokus_k ; update["fokuskzue"] = false; } setAttrs(update); }); }); Doing this, you then don't need to adjust fokus_t, especially since that was just going to immediately be undone and added to fokus_e anyways. Additionally, I'm not sure why you have fokusv/e/k. They don't appear in your provided html, so I'm not sure what type of input they are, but why not just listen directly to fokus_v/e/k instead of listening to these intermediates?
1547716973

Edited 1547717393
Loki
Sheet Author
@GiGs Thank you for your quick reply! As far as I know the "+" is the unary operator for a forced number conversion. Unfortunately sometimes I have to use those, because otherwise the code treats the values as strings although they both came directly from <input type=number>-fields. This results in something like "03" in a number field instead of 3. So at least if there is any math to do I always add the unary operator, because it doesn't seem to have any disadvantages. @Scott C. Thanks to you for you very detailed answer! Here is the HTML definition of the attributes fokusv, fokusk, fokuse and fokuskzue: <table style="font-weight: bold; padding: 0px; border-collapse: collapse" width="100%" align="center">     <tr bgcolor="#0489B1" style="color:white" align="center">     <td colspan="4">Fokus</td>     </tr>     <tr>         <td width="15%" style="background-color:#81BEF7;"><input type="number" name="attr_fokusnum" value=0 min=0 style="font-weight: bold; background-color:#81BEF7; font-size:14px; text-align:center; width:100%" /></td>         <td width="28%" style="background-color:#81BEF7;"><input type="checkbox" name="attr_fokusv" value="fokusv" class="sheet-reg" style="font-weight: bold; background-color:#81BEF7; font-size:10px;" /><span class="sheet-reg">Verzehren</span></td>         <td width="28%" style="background-color:#81BEF7;"><input type="checkbox" name="attr_fokuse" value="fokuse" class="sheet-reg" style="font-weight: bold; background-color:#81BEF7; font-size:10px;" /><span class="sheet-reg">Erschöpfen</span></td>         <td width="28%" style="background-color:#81BEF7;"><input type="checkbox" name="attr_fokusk" value="fokusk" class="sheet-reg" style="font-weight: bold; background-color:#81BEF7; font-size:10px;" /><span class="sheet-reg">Kanalisieren</span></td>     </tr>     <tr>         <td colspan="4" style="background-color:#81BEF7;" align="center"><input type="checkbox" name="attr_fokuskzue" value="fokuskzue" class="sheet-regbig" style="font-weight: bold; background-color:#81BEF7; font-size:10px;" /><span class="sheet-regbig">Kanalisierten zu Erschöpftem Fokus umwandeln</span></td>     </tr> </table> So ... they're just checkboxes somehow disguised as buttons for the user interface (which is also the answer to your question, why I am cascading those Sheet Worker scripts). I really don't wanted to do this in the first place and in the first place it was as you suggested - every attribute change was made at once and there was no problem. But the interface that allows the user to automatically calculate mana loss (instead of forcing them to change it in the boxes from the first screenshot manually) is not perfect. If the user makes a mistake then there were no way to change it back, because the fields fokus_k, fokus_e and fokus_v were "readonly=true", so that the user can't mess with them. Upon user request I decided to make those fields accessible so i threw out the "readonly=true" part. But now I had no listeners to those boxes, because they were not meant to be changed by the user in the first place (and the mana pool was depleted / refilled directly). And when I added them I stumbled over the problem described above. I'd be glad to get rid of the cascading effect and chane it back to one single function to handle both interface (fokusv, fokusk, fokuse) using and direct editing in the fokus input-boxes (fokus_v, fokus_k, fokus_e). But how? If I listen to both, the buttons (interface) and the boxes (direct input), how can i distinguish which method is used, since the interface also has to change the boxes and therefore create another loop: fokusv (button) gets clicked function to listen to buttons and boxes is triggered Interface changes number in fokus_v Interface changes mana pool function to listen to buttons and boxes is triggered Box changes mana pool Solving this would get rid of the problem so that would be my top priority here. Sidenote: I'm very sorry if my explanations are overly elaborate or are lacking precision. Since English ist not my mother language I sometimes have problems to express what I want to express. I also have taken into account your suggestions (and changed the values of the checkboxes a little bit). Thank you for that! But as you already assumed the changes didn't solve the problem. For the record I note the changes. on("change:fokus_k change:fokus_e change:fokus_v", function(f) { getAttrs(["fokus_k", "fokus_e", "fokus_v", "fokus_t", "fokus"], function(v) { let update = {}; let newValue = f.newValue || 0; let previousValue = f.previousValue || 0; if (f.sourceAttribute === "fokus_k") { update["fokus_t"] = v.fokus_t - (newValue - previousValue); } else if (f.sourceAttribute === "fokus_e") { update["fokus_t"] = v.fokus_t - (newValue - previousValue); } else if (f.sourceAttribute === "fokus_v") { update["fokus_t"] = v.fokus_t - (newValue - previousValue); } setAttrs(update); }); }); on("change:fokusv change:fokuse change:fokusk change:fokuskzue", function(f) { getAttrs(["fokusv", "fokuse", "fokusk", "fokuskzue", "fokusnum", "fokus_k", "fokus_e", "fokus_v"], function(v) { let update = {}; update["fokusnum"] = 0; if (v.fokusv === "fokusv") { update["fokus_v"] = +v.fokus_v + +v.fokusnum; update["fokusv"] = "off"; setAttrs(update); } else if (v.fokusk === "fokusk") { update["fokus_k"] = +v.fokus_k + +v.fokusnum; update["fokusk"] = "off"; setAttrs(update); } else if (v.fokuse === "fokuse") { update["fokus_e"] = +v.fokus_e + +v.fokusnum; update["fokuse"] = "off"; setAttrs(update); } else if (v.fokuskzue === "fokuskzue") { update["fokus_k"] = +v.fokus_k - +v.fokusnum; update["fokus_e"] = +v.fokus_e + +v.fokusnum; update["fokuskzue"] = "off"; setAttrs(update); } }); }); Greetings, Loki
1547722055
GiGs
Pro
Sheet Author
API Scripter
Loki said: @GiGs Thank you for your quick reply! As far as I know the "+" is the unary operator for a forced number conversion. Unfortunately sometimes I have to use those, because otherwise the code treats the values as strings although they both came directly from <input type=number>-fields. This results in something like "03" in a number field instead of 3. So at least if there is any math to do I always add the unary operator, because it doesn't seem to have any disadvantages. Jut a quick reply to this part because I don't have time to check out the rest just yet. I wasn't aware of that, thanks for bringing it to my attention! I always use parseInt or parseFloat when I want to be sure I'm dealing with a number, something like this: parseInt(v.fokus_t, 10)||0 The unary + is a lot shorter, and does seem to be better in some case. Very interesting.
1547741141
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
As GiGs, I'll have to parse the rest when I haven't just gotten up, but I also wanted to say thanks for the unary operator. I've been doing *1 to force the conversion.
1547744316

Edited 1547744346
Loki
Sheet Author
I'm glad that I can give you a piece of advice in return. To be fair, as you can see in my code, using the unary operator can be quite messy if you're making many calculations / additions. It happens quite often that I mistake an "+" meant for addition as an unary operator or vice versa (especially if the one follows the other) and get the compiler to stop working.
1547748843
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
so, Loki, I think you can actually use your original checkbox listener as you had it, you just need to slightly change your setAttrs call at the very end to set the attributes silently so that the worker listening to those attributes doesn't react to the change: on("change:fokusv change:fokuse change:fokusk change:fokuskzue", function(f) { getAttrs(["fokusv", "fokuse", "fokusk", "fokuskzue", "fokusnum", "fokus_k", "fokus_e", "fokus_v"], function(v) { let fok = v.fokusnum; let update = {}; update["fokusnum"] = 0; if (v.fokusv == true) { update["fokus_v"] = v.fokus_v + fok; update["fokusv"] = false; } else if (v.fokusk == true) { update["fokus_k"] = v.fokus_k + fok; update["fokusk"] = false; } else if (v.fokuse == true) { update["fokus_e"] = v.fokus_e + fok; update["fokuse"] = false; } else if (v.fokuskzue == true) { update["fokus_k"] = v.fokus_k - fok; update["fokus_e"] = v.fokus_e + fok + v.fokus_k; update["fokuskzue"] = false; } setAttrs(update ,{silent:true} ); }); }); You could also achieve the same end result by instead changing your fokus_e/v/k listener to be this: on("change:fokus_k change:fokus_e change:fokus_v", function(f) {     if(f.sourceType === 'sheetworker'){         return;     } getAttrs(["fokus_k", "fokus_e", "fokus_v", "fokus_t", "fokus"], function(v) { let update = {}; let newValue = f.newValue || 0; let previousValue = f.previousValue || 0; if (f.sourceAttribute == "fokus_k") { update["fokus_t"] = +v.fokus_t - (+newValue - +previousValue); } else if (f.sourceAttribute == "fokus_e") { update["fokus_t"] = +v.fokus_t - (+newValue - +previousValue); } else if (f.sourceAttribute == "fokus_v") { update["fokus_t"] = +v.fokus_t - (+newValue - +previousValue); } setAttrs(update); }); }); My personal preference is to use the silent setting so that I can still do cascading events in those rare cases I need to. It also just seems like cleaner code to me.
1547754303
Loki
Sheet Author
Thank you! I was not aware of the possibility to change attributes silently. I will try it! :) Greetings, Loki
1547804494

Edited 1547804530
Loki
Sheet Author
Now that I had time to test your suggestions I want to finally say Thank You again. Though both methods work, I'll stick with the "return-method" instead of the "silent" one. Because it is more likely that I (and possible future developers) know what's going on here when I look at the code again in half a year than in case of the "silent" solution that I even didn't know it existed. Greetings, Loki