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

Action Buttons in the Macro Bar

1638635263
Isurandil
Pro
Sheet Author
Currently, I am trying to convert the very complex rolls for Das Schwarze Auge 4.1 to use custom roll parsing reducing the roll to 3d20 (give or take some cs/cf). While I have a working proof of concept, I have not yet figured out one issue: Roll Buttons can be dragged into the macro bar and used from there. Custom Roll Parsing requires action buttons. Action buttons cannot be dragged into the macro bar. The question is: How can I replace the complex roll with its difficult to see roll results with clearer and easier to understand action buttons, but still have these rolls available in the macro bar? (Pretty sure this has been asked and solved already, but I couldn't find an answer in the Wiki and here.)
1638639143

Edited 1638655543
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
Unfortunately, this is still an outstanding bug with custom roll parsing. There is a workaround though. The workaround requires the following: action buttons as hidden elements with a name different from the roll button they are replacing (maybe something like the original button's name + "action", e.g. athletics-action ) A hidden attribute for each button, for simplicity sake name this the same as your action button, but replacing the dash with an underscore (e.g. athletics_action ) Roll buttons remain the visible button on the sheet, but change their value property to be an attribute reference to the hidden attribute (e.g.  @{athletics_action} ) So, your action buttons refer to what is currently an empty attribute. We're going to fix that with sheetworkers which will populate these attributes with an ability call to the action button. The reason we need to do it this way is that the call has to have the character's name in it (e.g. %{John Smith|athletics_action}), and there's no way to do that directly in the html. So, we'll make a sheetworker that triggers when the sheet is opened and when the character's name is changed. This is going to look rather large because it requires iterating through repeating sections and over a ton of attributes. I've included some infrastructure to do that. //A function to combine getting repeating and non repeating attributes. const getAllAttrs = function(props,sectionDetails,callback){ getSections(sectionDetails,(repeats,sections)=>{ getAttrs([...props,...repeats],(attributes)=>{ callback(attributes,sections); }) }); }; //Iterates over the provided repeating sections and assembles an array of attributes to get from those sections for each row. const getSections = function(section_details,callback){ let queueClone = _.clone(section_details); const worker = (queue,repeatAttrs=[],sections={})=>{ let detail = queue.shift(); getSectionIDs(detail.section,(IDs)=>{ sections[detail.section] = IDs; IDs.forEach((id)=>{ detail.fields.forEach((f)=>{ repeatAttrs.push(`${detail.section}_${id}_${f}`); }); }); repeatAttrs.push(`_reporder_${detail.section}`); if(queue.length){ worker(queue,repeatAttrs,sections); }else{ callback(repeatAttrs,sections); } }); }; if(!queueClone[0]){ callback([],{}); } worker(queueClone); }; const repeatingSections = [{section:'repeating_attack',fields:['attack_1_action','attack_2_action']},{section:'repeating_ability',fields:['action']}];//Replace these with your sheet's repeating sections const actionRefs = ['athletics_action','strength_action','charisma_action'];//Replace these with the non repeating attributes for your sheet. const actionCallUpdate = function(){ getAllAttrs(['character_name',actionRefs],repeatingSections,(attributes,sections)=>{ let actions = Object.keys(attributes).filter((key)=>key!=='character_name'); const setObj = actions.reduce((accumulator,actionName)=>{ accumulator[actionName] = `%{${attributes.character_name}|${actionName.replace(/_/g,'-')}}`;//Update the action call return accumulator;//The memo (accumulator in this case) must always be returned when using a reduce function },{}); setAttrs(setObj,{silent:true});//Apply the changes }); }; on('sheet:opened change:character_name',actionCallUpdate); Hope that helps!
1638651932
Isurandil
Pro
Sheet Author
Whoa, thanks, that's much. Would you mind giving me an example with the bare minimum of HTML and CSS? If the hidden attribute was hardcoded, that would surely help me figure out what exactly is going on and how things are interacting.
1638655409

Edited 1638655658
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
Sure, so, let's take athletics and the repeating_ability button since they're both example buttons in the script. That html would look like: <input type='hidden' name='attr_athletics_action' value=''> <button type='action' name='act_athletics-action' style='display:none;'></button><!--Note that I'd probably give this a class of .hidden or just the hidden property, but those both require a little css backup--> <button type='roll' name='roll_athletics' value='@{athletics_action}'>Roll</button> <fieldset class='repeating_ability'> <input type='hidden' name='attr_action' value=''> <button type='action' name='act_action' style='display:none;'></button><!--Note that I'd probably give this a class of .hidden or just the hidden property, but those both require a little css backup--> <button type='roll' name='roll_roll' value='@{action}'>Roll</button> </fieldset> EDIT: Oh, and I had a screw up in the script of the previous post. The created button call was calling the right button name. That's now fixed. EDIT 2: In case you're wondering why I use dashes as a separator in the name of action buttons, it's because action buttons with underscores in their names don't work when they are placed in repeating sections and I prefer all things of a given type to have a set naming scheme.
1638722663
Isurandil
Pro
Sheet Author
Thanks, I think now I understand what's going on: Roll buttons can call abilities. Action buttons provide abilities. Ability calls require the character name (or target or selected ). All/Most of the Sheetworker Script is for getting the character name and building the ability call. So, a basic example for a character named 'Roller' looks like this: <!-- The visible and draggable button for the macro bar --> <button type="roll" name="roll_athletics" value="%{Roller|athletics}">Athletics</button> <!-- The invisible button allowing the use of custom roll parsing <button type="action" class="hidden" name="act_athletics"></button> The sheetworker script for the custom roll parsing remains untouched. In your second post, you replaced the direct ability call with an attribute that, ultimately, also contains the character name and the ability call. (Hope this also helps others.) Thank you again, Scott C. 😎
1638729983
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
yes, although it's important to note a few things. You need the hidden attribute as that's the only way to have the character name update to match a given character. If you hard code it into the html, it will only work for that single character. The action button MUST have a different name from the roll button. Otherwise the ability call that is sent to chat will not call the action button reliably. It will randomly choose between them, perhaps based on their order in the html, but I'm not 100% sure on that, and that isn't something you should rely on.
1638732329
Andreas J.
Forum Champion
Sheet Author
Translator
Scott C. said: It will randomly choose between them, perhaps based on their order in the html, but I'm not 100% sure on that, and that isn't something you should rely on. I remember if you had two identically named roll buttons with different rolls, and dragged either one to the macrobar, it would always roll the last one defined in the html. But yeah, probably not a smart idea to do even if this case also ends up giving consistent results. Isurandil, if you test it with both having the same name, try switch around the order too, and see which one works better. <!-- The invisible button allowing the use of custom roll parsing <button type="action" class="hidden" name="act_athletics"></button> <!-- The visible and draggable button for the macro bar --> <button type="roll" name="roll_athletics" value="%{Roller|athletics}">Athletics</button>
1641946015
I was wondering, why is a sheetworker necessary to set the character name for the call? Could you not do something like %{@character_name}|athletics} for the roll button value? If that wouldn't work, why wouldn't it?
1641949173

Edited 1641949207
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
Unfortunately the chat parser's order of operations prevents us from doing something that simple. Tl;dr: Abilities are expanded first, so given a character named Test , the chat parser tries to look for %{@{Test|character_name}|athletics}  first, which, because of how the chat parser detects the end of calls, means it actually looks for %{@{Test|character_name} .
1643490938
Bodo
Pro
Sheet Author
Everybody who has no repeating sections with buttons to modify could simply the whole code like this:     on('sheet:opened change:character_name', function() {         getAttrs(["character_name"], function(values){             const actions = ['athletics_action','strength_action','charisma_action'];             const setObj = actions.reduce((accumulator,actionName)=>{                 accumulator[actionName] = `%{${values.character_name}|${actionName.replace(/_/g,'-')}}`;//Update the action call                 return accumulator; //The memo (accumulator in this case) must always be returned when using a reduce function             },{});             setAttrs(setObj,{silent:true});//Apply the changes         });         });