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

Advanced interaction with sheetworker / type=action buttons

1675610482
Jiboux
Pro
Sheet Author
Compendium Curator
Hello, I have a custom sheet, with a companion API. I am constantly looking what is done with the API that could be transferred to the sheetworkers because sheetworkers are both more stable and not behind the Pro tier paywall. Among the legacy reasons that some functions were in the API, for some functions it was in order to have more flexibility as far as user interaction, with buttons appearing in the chat and queries. But this has developped over several years and roll20 has been offering new functions, and it may be possible that new functions are available that I am not familiar with, that would allow to do it all in sheetworker. Below is an example of something I'd like to change and see if there are suggestions /reference sheets to get inspiration Setup link between 2 repitems (i.e. value of one repitem being calculated with a value of another repitem. Each of the 2 linked repitems have to store the rowID of the other repitem in order to make the calculation (once the rowID are stored, API is not needed anymore, but the sequence to set it up is in the API: Character Sheet: I n each repitems there is a button that triggers the API "LinkAdd1" Function. API: When the "LinkAdd1" command is received, a chat message is sent with 2 buttons Add Link Delete Link  API: The Delete Link Button will trigger the API to sent to chat another series of button "Which link do you want to delete", and one button for each of the existing links. Pressing the button updates the attribute hosting the list of linked rowids to remove the selected one API: The Add Link button is for a "LinkAdd2" function and includes a query that will first give a dropdown to select which repeating_section we want to link to, and a second cascading query to type the name or first letters of the repitem name we want to link to. (For example User would choose "Talents" and then Type "Tiger" ) API : The LinkAdd2 function, will search in the target repeating_section for all repitems with a name matching the target. For each of those, the API would proceed to post in the chat a button to the function "LinkAdd3" (So if for example the user has a "Tiger Spring", and a "Tiger Dance" talent, 1 button would be posted to the chat for each with "2 matched talents, which one do you want to link to" API: FINALLY, when the linkadd3 button is pressed, the attribute hosting the list of links is updated with the appropriate rowID I am currently making thoughts of transferring that to sheetworker in order to be more robust and not depending a sheetworker/API communication that has constraints, but I can't think on how with the sheetworker I can make a user dialog like the one above... Any hint of functionalities that allow such levels of dialog with the user ? Example of sheets ?
1675626104
GiGs
Pro
Sheet Author
API Scripter
I havent tried to study the sequence (I just woke up), but I recommend looking at Custom Roll Parsing for sheet workers. Many things were put in screpts because they were just not possible to do in a sheet worker. CRP makes some things possible that weren't possible before. There are still some things you can only do with scripts, but I'd start with CRPs for everything else.
1675626568
Jiboux
Pro
Sheet Author
Compendium Curator
Thanks, I'll look into that !
1675627114
Jiboux
Pro
Sheet Author
Compendium Curator
GiGs said: I havent tried to study the sequence (I just woke up), but I recommend looking at Custom Roll Parsing for sheet workers. Many things were put in screpts because they were just not possible to do in a sheet worker. CRP makes some things possible that weren't possible before. There are still some things you can only do with scripts, but I'd start with CRPs for everything else. OK, I looked a bit into it, and I guess what you are referring to is the fact that action buttons can now be included in a roll result Also, you can now include a clickable action button directly in a roll (or in a macro) in the same way that roll buttons can be included:  [Click Here](~[character id]|[action button name]) .  If including this in a roll made from the character sheet, the character id can be omitted (same as roll buttons):  [Click Here](~[action button name]). Note that when starting a roll via the  startRoll  sheet worker, the system will not be able to automatically add the correct repeating section id, and you will need to fully resolve the attribute name  [Click Here](~repeating_test_-MckIineUhDw8UwVpy-z_test1). This is very cool indeed because it gives the possibility of interaction with the user. Looking at my use case, few questions: 1-Can you have a variable number of buttons created this way ? In my example, at some point the user is filling in a string, and the system looks at all repitems that have the name that includes this string, and sends in the chat one button for each for the user to choose from 2-Can the button trigger popup queries for the user to fill in ?{Type Talent Name first letters}, that will then be passed to the sheetworker triggered by the action button as a parameter ? Thanks for your support, always very appreciated
1675628667

Edited 1675628744
GiGs
Pro
Sheet Author
API Scripter
The answer to both questions is yes. For the first question, it's not as straightforward as it should be, but it is possible. The easiest way is to create an object with the button name and its macro text, then in the CRP, you can detect which button triggered the worker and use the relevent macro string. For the second, you can have any macro string run, and that will include popup menus. You then use the results of that in the CRP worker following. (I'd like to include examples for both, but still waking up! and your question is a bit vague so I don't have much to work with.)
1675630435
Jiboux
Pro
Sheet Author
Compendium Curator
Thanks ! already very cool to answer when still waking up. Trying to elaborate my example: Step 1 : the user presses a "link" button on the charactersheet. The system answers with a CRP with 2 action buttons LinkAdd and LinkDel Step 2 : The user has pressed LinkAdd. A first query pops up with a dropdown ?{Create Link, To which category do you want to Link|Talents|Discipline|Item|Weapon}. Once the first query is answered a second pop up with Free text opens ?{Type the first letters of the <Response to Query1>} (note that the <Response to Query1> is not absolutely necessary ) - This is my question 2 Step 3 : A sheetworker function LinkAdd() has been trigger by the first button. It searches all the entries in the repeating section corresponding to the query1 that have a name that includes query2. For each match it sends through a second CRP a button LinkAdd2(Name) Step 4: When the user presses one of the LinkAdd2(Name) Button, the link is created between the original repitem where the "link" button was pressed in step 1, and the repitem that was matched in step 3/4
1675656039
GiGs
Pro
Sheet Author
API Scripter
It will probably be easier to include the section of the script code you are trying to make into a worker. Seeing exactly what the script is doing will make it easier to say whether its possible in workers.
1675738784
Jiboux
Pro
Sheet Author
Compendium Curator
I am always reluctant to share code, because there is so much to unpack, but let's try. First the user presses a button in the sheet that reads like this (T, NAC, SK, SPM, and WPN are codes that we use that are unique for each repeating section and are shorter way to refer to it that the whole repeating_xxx (so for example, an Attribute name could be repeating_talents_rOwId_T_Name)) value="!Earthdawn~ charID: @{character_id}~ Chatmenu: Link: T : @{T_RowID}" This sends a command to the API Paser (Chatmenu: Link: ) that is processed here           case "link": {      // chatmenu: Link: (code - T, NAC, SK, SPM, WPN): rowID                       // List all existing links for this rowID and ask if to be removed. And a button to add a link.             let pre = Earthdawn.buildPre( ssa[ 2 ], ssa[ 3 ]);             s = "Managing links for " + Earthdawn.codeToName( ssa[ 2 ] ) + " " + Earthdawn.getAttrBN( this.charID, pre + "Name", "") + ".  "                   + Earthdawn.texttip( "(hover)", "Links are used to automatically integrate a value from another ability in the calculation of this ability. "                   + "For example, a Free Talent or Free Matrix should be linked to a Discipline so that its Rank automatically updates when the Circle is updated. "                   + "A Knack would be linked to the referenced Talent, so that the Knack rank automatically uses the Talent Rank. "                   + "Links can also be used to create combos between elements, such as Surprise Short Sword, "                   + "that could be linked with the Talent Surprise Strike and a Weapon Short Sword to get the total damage roll, "                   + "or Tiger Dance that could be linked to Talents Tiger Spring and Air Dance to roll the combination of the 2 Talents)");             addButtonWithCharID( "Add a Link", "ChatMenu: Linkadd1: " + ssa[ 2 ] + ": " + ssa[ 3 ] +                   ": ?{What are we linking this to|Talent,T|Discipline,DSP|Skill,SK|Weapon,WPN|Thread Item,TI|Attribute}: "                   + "?{Name to link to (Make sure substring is an exact match, including case. Example - Melee)}",                   "Link this to an Talent, Discipline Circle, Skill, Weapon, Thread Item or Attribute, such that this item will use that items Rank." , Earthdawn.Colors.param, Earthdawn.Colors.paramfg);             lst = Earthdawn.getAttrBN( this.charID, pre + "LinksGetValue", "" );                   // repeating_talents_xxx_T_name+repeating_talents_xxx_T_name2,another             let lst2 = Earthdawn.getAttrBN( this.charID, pre + "LinksGetValue_max", "" );             if( lst2.length < 5 )               lst2 = "";    // strip out any (0) that was put here.             if( lst )               lst = lst.split( "," );             if( lst2 )               lst2 = lst2.split( "," );             if( !lst || !lst2 || lst.length == 0 )               s += "Existing links: None.";             else for( let i = 0; i < lst.length; ++i ) {               let t = lst2[ i ],                 att = !t.startsWith( "repeating_" ),                 code = Earthdawn.repeatSection( 3, t );               s += "Remove ";               if( att )                 addButtonWithCharID( lst[ i ].trim(), "ChatMenu: LinkRemove: " + ssa[ 2 ] + ": " + ssa[ 3 ] + ": Attribute: "  + lst2[ i ], "Make this item no longer linked to this Attribute.", Earthdawn.Colors.param2, Earthdawn.Colors.param2fg);               else {                 let tmp = "item.";                 tmp = Earthdawn.codeToName( code ) + ".";                 addButtonWithCharID( lst[ i ].trim(),                     "ChatMenu: LinkRemove: " + ssa[ 2 ] + ": " + ssa[ 3 ] + ": " + code + ": "  + Earthdawn.repeatSection( 2, t ),                     "Make this item no longer linked to this " + tmp, Earthdawn.Colors.param2, Earthdawn.Colors.param2fg);             } }                       this.chat( s.trim(), Earthdawn.whoTo.player | Earthdawn.whoFrom.api | Earthdawn.whoFrom.noArchive, "API" );           } break;  // end link Result is : The button in "Add a Link" is a new chat command to the API linkadd1: case "linkadd1": { // ChatMenu: linkadd1: (code: T, NAC, SK, SPM, WPN): (rowID): (Attribute, T, SK, DSP, TI, or WPN) : (name string to search for) // User has given us a string to attempt to link to. Search all entries to see if there is a match. Confirm the match. if( ssa.length < 6 || ssa[ 5 ].length < 2 ) // Make sure got a valid command line. return; let attrib = (ssa[ 4 ] === "Attribute"), skipmost = false; if( attrib ) { // Check for the "real" attributes first, and if you find one, don't search for any more. let tar = [ "dex", "str", "tou", "per", "wil", "cha" ], t = Earthdawn.safeString( ssa[ 5 ] ).toLowerCase(), effect = ( t.indexOf( "-effect" ) !== -1 ), step = ( t.indexOf( "-step" ) !== -1 ); for( let i = 0; i < tar.length; ++i ) if( t.indexOf( tar[ i ] ) !== -1 ) { ssa[ 5 ] = Earthdawn.safeString( tar[ i ].slice(0, 1)).toUpperCase() + tar[ i ].slice( 1 ) + (effect ? "-Effect" : "") + (step ? "-Step" : ""); skipmost = true; } } if( !skipmost ) { let attributes = findObjs({ _type: "attribute", _characterid: this.charID }), found = []; // if( ssa[ 4 ] === "T" ) { // let tar = [ "SP-Spellcasting-Effective-Rank", "SP-Patterncraft-Effective-Rank", "SP-Elementalism-Effective-Rank", "SP-Illusionism-Effective-Rank", // "SP-Nethermancy-Effective-Rank", "SP-Shamanism-Effective-Rank", "SP-Wizardry-Effective-Rank", "SP-Power-Effective-Rank", "SP-Willforce-Effective-Rank" ]; // for( let i = 0; i < tar.length; ++i ) // if( tar[ i ].indexOf( ssa[ 5 ] ) !== -1 ) { // attrib = true; // ssa[ 4 ] = "Attribute"; // ssa[ 5 ] = tar[ i ]; // } // } if( attrib ) { _.each( attributes, function (att) { if ( att.get( "name" ).indexOf( ssa[ 5 ] ) != -1) found.push( att ); }); // End for each attribute. } else { // It is a repeating section, check each name. let lookfor = "_" + ssa[ 4 ] + "_Name", strMatch = Earthdawn.safeString( ssa[ 5 ] ).toLowerCase(); _.each( attributes, function (att) { if( Earthdawn.safeString( att.get("name")).endsWith( lookfor ) && Earthdawn.safeString( att.get( "current")).toLowerCase().indexOf( strMatch ) != -1) found.push( att ); }); } // End for each attribute. if( found.length == 0 ) { this.chat( "No matches found.", Earthdawn.whoFrom.apiWarning); break; } else if ( found.length == 1 ) ssa[ 5 ] = attrib ? found[ 0 ].get( "name" ) : Earthdawn.repeatSection( 2, found[ 0 ].get( "name" ) ); // This option immediately falls into lindadd2. else { s = found.length + " matches found. Select which to Link: "; for( let i = 0; i < found.length; ++i ) addButtonWithCharID( ssa[ 4 ] + " " + ( attrib ? found[ i ].get( "name" ) + " " : "") + found[ i ].get( "current" ) , "ChatMenu: Linkadd2: " + ssa[ 2 ] + ":" + ssa[ 3 ] + ": " + ssa[ 4 ] + ": " + (attrib ? found[ i ].get( "name" ) : Earthdawn.repeatSection( 2, found[ i ].get( "name" ) )), "Add this Link." , Earthdawn.Colors.param, Earthdawn.Colors.paramfg); this.chat( s.trim(), Earthdawn.whoFrom.apiWarning | Earthdawn.whoFrom.noArchive, "API" ); break; } } } // end linkAdd1 This button will via query ask for name and code of another item to link, and will look how many it finds. If it finds just one, it will create the link, If it finds >1 it will post a message with yet another button linkadd2 to chose from. The 2 possibilities are in the screenshot below case "linkadd2": { // User has pressed a button telling us which exact one to link to OR we fell through from above due to only finding one candidate. // Here we do the actual linking. // ChatMenu: linkadd2: (code: T, NAC, SK, SPM, WPN): (rowID): (Attribute, T, SK, DSP, TI, or WPN): link rowID or attribute name // First code/rowID combo is row that is making link (getting a value). // Second code/rowID combo is row that is being linked to (providing a value). // Can make a link T, NAC, SK, SPM, WPN // Can link to T, SK, WPN, DSP, TI, attributes???. Maybe static talents. // note removed NAC if( ssa.length < 6 || ssa[ 5 ].length < 2 ) // Make sure got a valid command line. return; // walk links. Check to make sure that nothing links back to this (or anything else in the tree). function walkLinks( nextCode, nextRow, badLink, nameList ) { // Check to make sure row being linked does not reference row adding link. let links = Earthdawn.getAttrBN( edParse.charID, Earthdawn.buildPre( nextCode, nextRow) + "LinksGetValue_max" ); if( links ) { let alinks = links.split(); for( let i = 0; i < alinks.length; ++i ) { if( alinks[ i ].indexOf( badLink ) !== -1 ) { edParse.chat( "Error! attempted circular reference: " + nameList + " to " + Earthdawn.getAttrBN( edParse.charID, Earthdawn.buildPre( nextCode, nextRow) + "Name", Earthdawn.whoFrom.apiWarning )); return false; } else if( alinks[ i ].startsWith( "repeating_" )) if ( !walkLinks( Earthdawn.repeatSection( 3, alinks[ i ]), Earthdawn.repeatSection( 2, alinks[ i ]), badLink, nameList + " to " + Earthdawn.getAttrBN( edParse.charID, Earthdawn.buildPre( nextCode, nextRow) + "Name", "" ))) return false; } } return true; }; // end walkLinks() if( !walkLinks( ssa[ 4 ], ssa[ 5 ], ssa[ 3 ], Earthdawn.getAttrBN( this.charID, Earthdawn.buildPre( ssa[ 2 ], ssa[ 3 ]) + "Name", "" ))) return; let att = (ssa[ 4 ] === "Attribute"); // Given a fully qualified argument name, it will Add the Display and Links. function linkAdd( linkName, dispAdd, linkToAdd) { let obj = Earthdawn.findOrMakeObj({ _type: "attribute", _characterid: edParse.charID, name: linkName }); let dlst = obj.get( "current" ), llst = obj.get( "max" ); if( !llst || llst.length < 5 ) { // there are no existing links, so we are adding the first one. llst = []; dlst = []; } else { // There are existing links. llst = llst.split( "," ); dlst = dlst.split( "," ); if( llst.length != dlst.length ) { edParse.chat( "Warning! internal data mismatch in linkadd2.", Earthdawn.whoFrom.apiError); Earthdawn.errorLog( "Warning! internal data mismatch in linkadd2.", edParse); log( dlst ); log( llst); llst = []; dlst = []; } } dlst.push( dispAdd ); llst.push( linkToAdd ); Earthdawn.set( obj, "max", llst.join(), linkToAdd ); //log( "setww " + obj.get( "name" ) + " from: " + obj.get( "current" ) + " to: " + dlst.join()); Earthdawn.setWithWorker( obj, "current", dlst.join(), dispAdd ); let sflag = false; if( linkName.startsWith( "repeating_" ) ) { let code = Earthdawn.repeatSection( 3, linkName ) if( code === "T" || code === "NAC" || code === "SK" || code === "WPN" || code === "SPM" ) sflag - true; } else sflag = true; if( sflag ) edParse.addSWflag( "Trigger2", linkName ); } // end linkAdd let t = att ? ssa[ 5 ] : Earthdawn.getAttrBN( this.charID, Earthdawn.buildPre( ssa[ 4 ], ssa[ 5 ] ) + "Name", "" ), l; function lnk() { l = ""; for( let i = 0; i < arguments.length; ++i ) { let tmp = (att ? arguments[ i ] : Earthdawn.buildPre( ssa[ 4 ], ssa[ 5 ] )) + arguments[ i ]; l += ((i === 0) ? "" : "+" ) + tmp; } }; // repeating_talents_xxx_T_name+repeating_talents_xxx_T_name2,another switch( ssa[ 4 ] ) { case "Attribute": lnk( ssa[ 5 ] ); break; case "DSP": lnk( "Circle" ); break; case "NAC": lnk( "Step" ); break; case "TI": lnk( "Rank" ); break; default: lnk( "Effective-Rank" ); break; // T, SK, WPN } linkAdd( Earthdawn.buildPre( ssa[ 2 ], ssa[ 3 ]) + "LinksGetValue", t.replace( /[\,|\+]/g, ""), // absolutely can't have any commas or plus signs in the name. l); // LinksGetValue are of form comma delimited list of fully qualified attributes, maybe more than one seperated by plus signs. if( !att ) // LinksProviceValue are comma delimited lists of form (code);(rowID). linkAdd( Earthdawn.buildPre( ssa[ 4 ], ssa[ 5 ]) + "LinksProvideValue", Earthdawn.getAttrBN( this.charID, Earthdawn.buildPre( ssa[ 2 ], ssa[ 3 ] ) + "Name", "" ).replace( /[\,|\+]/g, ""), ssa[ 2 ] + ";" + ssa[ 3 ]); this.chat( "Linked " + ssa[ 4 ] + "-" + t, Earthdawn.whoFrom.apiWarning | Earthdawn.whoFrom.noArchive, "API" ); this.sendSWflag(); } break; // end linkAdd2
1675747765

Edited 1675747894
GiGs
Pro
Sheet Author
API Scripter
I can see why you were reluctant to share the script, but it was essential to get an idea of the scope and approach of the poroject. That's a task that's too big to help much with, but I can see this: I think you can do this with a CRP - the structures you use are things that should work in the more limited version of JS available in character sheets. My main concern is the use of obj.get - you will need another way to get whatever is called there. obj.get is mostly replaced with getAttrs in sheet workers (and you usually do all your getAttrs calls at the start, though that's not essential - it's just efficient). Also Things like Earthdawn.getAttrBN suggest there is a big object variable there that would need to be recreated in the sheet worker - which won't be possibile if this sheet is for public consumption). The CRP solution will probably require at least as much code as the script. It won't be a small job.
1675812593
Jiboux
Pro
Sheet Author
Compendium Curator
Thanks a lot Gig... I put this development in my todo/roadmap, because I think it will be interesting