Tabs in dialog() or frame()

Discuss macro implementations, ask for macro help (to share your creations, see User Creations, probably either Campaign Frameworks or Drop-in Resources).

Moderators: dorpond, trevor, Azhrei, giliath, jay, Mr.Ice

Post Reply
User avatar
Oriet
Cave Troll
Posts: 30
Joined: Mon Dec 05, 2011 10:58 am
Location: Somewhere that's not there, but not really here.
Contact:

Tabs in dialog() or frame()

Post by Oriet »

I was wondering if there was a way to create tabs in dialog() or frame(), like there is for input(). I'm trying to make my character sheet macro a bit nicer looking and easier for my players to use, but so far I have been unsuccessful in trying to get it to work.

Since I don't feel I explained myself clearly enough, I'll post some examples and such. First off I'm trying to do this for a game system I'm working on creating, and am doing so in conjunction with making macros for it so it can have seamless integration in MapTool.

[spoiler=Campaign Properties]

Code: Select all

*display.health (Health):{if(wounds.mortal > 0, "Greviously Injured", if(wounds.heavy > 0, "Seriously Injured", if(wounds.medium > 0, "Injured", if(wounds.light > 0 , "Roughed Up", "Healthy"))))}
*@
display.stats (Stats):AP {condition.action.points}, Move {derived.movement}
*
display.armour (Armour):{equip.armour.name}
*@
display.resistance (Resist):Ki {attrib.brawn + attrib.size.mod + equip.armour.kinetic}, El {attrib.brawn + attrib.size.mod + equip.armour.elemental}, En {attrib.brawn + attrib.size.mod + equip.armour.energy}
*
display.weapon.one (Weapon):{if(equip.weapon.one != "", getStrProp(equip.weapon.one, "sor.weapon.name"), "")}
*@
display.ammo.one (Ammo):{if(getStrProp(equip.weapon.one, "sor.weapon.clip.max") != "", " (" + getStrProp(equip.weapon.one, "sor.weapon.clip.current") + "/" + getStrProp(equip.weapon.one, "sor.weapon.clip.max") + ")", "")}
attrib.brawn (Br):3
attrib
.finesse (Fi):3
attrib
.reasoning (Re):3
attrib
.Spirit (Sp):3
attrib
.size.mod:0
derived
.fatigue.max:{attrib.brawn + attrib.spirit}
derived.fatigue.current:0
derived
.movement (Mv):{attrib.brawn + attrib.finesse}
defence.dodge
defence
.parry
defence
.block
wounds
.light:0
wounds
.medium:0
wounds
.heavy:0
wounds
.mortal:0
condition
.action.points:2
skill
.melee.combat:0
skill
.ranged.combat:0
skill
.awareness:0
skill
.dodge:0
equip
.armour.name:Clothing
equip
.armour.val:0
equip
.armour.kinetic:0
equip
.armour.elemental:0
equip
.armour.energy:0
equip
.weapon.one
[/spoiler]

I used input() to make a rudimentary character sheet, but don't like the limitations on formatting. I did use the tab feature in it, but that still only helps so much.

Image

[spoiler=Rudimentary Character Sheet]

Code: Select all

[h: character.name = token.name]
[
h: error = input(
    "tab0|Attributes||TAB", 
    
"Name|"+getName()+"",
    "Label|"+getLabel()+"",
    "Brawn|"+attrib.brawn+" ## Finesse|"+attrib.finesse+" ## Reasoning|"+attrib.reasoning+" ## Spirit|"+attrib.spirit+" ## SizeMod|"+attrib.size.mod+" ## label|SecondaryAttributes||label|span=true",
    "tab1|Skills||TAB",
    "MeleeCombat|"+skill.melee.combat+" ## RangedCombat|"+skill.ranged.combat+" ## -Projectiles ## -Rayguns ## Awareness|"+skill.awareness+" ## Dodge|"+skill.dodge+"",
    "tab2|Equipment||TAB",
    "ArmourType|"+equip.armour.name+" ## Protection|"+equip.armour.val+" ## WeaponOne|"+getStrProp(equip.weapon.one, "sor.weapon.name")+""
)]
[
h: abort(error)]

[
h,if(Name == ""): Name = "Unknown"]
[
h,if(Label == 0): Label = ""]

[
h: setName(Name)]
[
h: setLabel(Label)]

[
h: attrib.brawn = Brawn]
[
h: attrib.finesse = Finesse]
[
h: attrib.reasoning = Reasoning]
[
h: attrib.spirit = Spirit]
[
h: attrib.size.mod = SizeMod]

[
h: skill.melee.combat = MeleeCombat]
[
h: skill.ranged.combat = RangedCombat]
[
h: skill.awareness = Awareness]

[
h: equip.armour.name = ArmourType]
[
h: equip.armour.val = Protection]

[
r: "Character sheet has been updated."] 
[/spoiler]

I'd really like to use dialog() or frame() for the character sheet, as it has a lot more functionality and can be made to look much nicer, but I also want tabs similar to those available in the input() function. I had found a couple sites talking about how to use CSS to make tabs, but when I tried to integrate them they never worked. Unfortunately I forgot to save those attempts, though I could recreate them if desired.

I even tried encapsulating the links to other pages in the character sheet in borders, but I seem to be messing it up too much. There is a gap between the faux tabs and the page contents, and the current faux tab isn't using the background colour it should (or that I'm trying to get it too, at least).

Image

[spoiler=open.char.sheet Macro]

Code: Select all

[h: link = macroLinkText("[email protected]:tokens", "none")]    
[frame("CharacterSheet"): {
    [h: page = getStrProp(macro.args, "Page")]
    [h,if(page==""): page="Overview"]
    <html>
    <head>
        <link rel="onChangeSelection" type="macro" href="[r:link]">
        <link rel="stylesheet" type="text/css" href="[email protected]:tokens"></link>
    </head>
    <body>

        [h: has.permission = 0]
        [h: acting.token = getSelected()]
        [h: acting.player = getPlayerName()]
        [h,if(isOwner(acting.player, acting.token)): has.permission = 1]
        [h,if(isGM()): has.permission = 1]

        [if(has.permission == 0),code:
        {
            [r: "You do not have permission to view this token's character sheet."]
        };{
            [h: "<!-- Stuff -->"]
        [h: id = getSelected()]
        [h: header.args = strPropFromVars("page, id", "unsuffixed")]
        [r,macro("[email protected]:tokens"): header.args]
        <br>
        [r,macro("char.sheet."+page+"@Lib:tokens"): id]
        }]
    </body>
    </html>
}]
 
[/spoiler]
[spoiler=char.sheet.css Macro on Lib:tokens]

Code: Select all

.odd { background-color: #FFFFFF }
.even { background-color: #EEEEAA }
th { background-color: #113311; color: #FFFFFF }
.page { background-color:#BBBBBB; border-width:thin; border-color:#222222 }
.currentPage { background-color:#EEEEEE; border-width:thin; border-color:#222222 }   
[/spoiler]
[spoiler=char.sheet.header Macro on Lib:tokens]

Code: Select all

[h: varsFromStrProp(macro.args)]
[
h: currentPage = macro.args]
[
h: pages = "Overview,Basic_Skills,Broad_Skills"]
<
table>
    <td><img src='[r,token(id): getTokenImage(75)]'></img></td>
    <td><h1>[r,token(id): token.name]</td>
</
table>

<
table>
    [foreach(page, pages,""), code: {
        [h,if (page == currentPage): class="currentPage" ; class="page"]
        [h: callback = "[email protected]:tokens"]
        <td class="[r: class]">
            [r: macroLink(page, callback, "none", "Page="+page)]
        </td>
        }]
</
table> 
[/spoiler]
[spoiler=char.sheet.Overview Macro on Lib:tokens]

Code: Select all

[h: id = macro.args]
[
r, if(listCount(id)!=1), code: {};{
    [h: link = macroLinkText("[email protected]:tokens", "all")]
    <form action="[r:link]" method="json">
    <input type="hidden" name="id" value="[r:id]">

<
table style="background-color:#EEEEEE; border-width:thin; border-color:#222222">
    <tr>
    <table>
    <td valign="top">
        <table>
            <tr>
                <th colspan="*">Attribute</th>
                <th colspan="*">Score</th>
            </tr>

            [h: attributes = "Brawn, Finesse, Reasoning, Spirit"]
            [h: row = "odd"]
            [r,foreach(attrib, attributes, ""),code: {
                <tr class="[r:row]">
                    [h: property.name = 'attrib.' + attrib]
                    <td><b>[r:attrib]</b></td>
                    <td><input type="text" name="[r:attrib]" value="[r:getProperty(property.name, id)]" size="3" align="right"></td>
                </tr>
                [h: row = if(row=="odd", "even", "odd")]
            }]
        
            
        
</table>
    </td>

</
table>

    <input type="submit" name="edit_btn" value="Submit changes">
    </form>
}]
 
[/spoiler]

Currently I'm just trying to get the base functionality of the character sheet working, currently working on getting some form of decent looking tab feature functional. I can get the displayed properties and updating token properties working later.

Any and all advice, tips, examples, or explanations on how it cannot be done are welcome. :)
Kill them all, and let the Game Master grant the exp!

ImageImage

User avatar
Azhrei
Site Admin
Posts: 12061
Joined: Mon Jun 12, 2006 1:20 pm
Location: Tampa, FL

Re: Tabs in dialog() or frame()

Post by Azhrei »

Dialogs and frames are simply HTML 3.2 pages. This means that there is no inherent tab support, but it can be faked by using links. In other words, put a bunch of links across the top of an HTML page that cause the page to be reloaded with different content and you have "tabs". Most of the frameworks do this, I believe; I know for certain that lmarkus' D&D3.5 FW does this (it's what I use).

It would be really nice if some of these common types of things were implemented in a library token. Then a macro writer could cal a function and give it a list of tab titles and macros for each tab, and the function would generate the correct HTML and some graphics that simulate the typical look of tabs. Aliasmask may already have something like this (he tends to produce very modular stuff ;)).

User avatar
Oriet
Cave Troll
Posts: 30
Joined: Mon Dec 05, 2011 10:58 am
Location: Somewhere that's not there, but not really here.
Contact:

Re: Tabs in dialog() or frame()

Post by Oriet »

Thanks for the reply! Using what you said and looking back over things I was able to do a bit of trial and error and eventually arrive at a decent enough looking faux-tab system. I'll post the results for people to see, look over, and utilise what they want for their own things.

Image

[spoiler=Campaign Properties][/spoiler]

[spoiler=open.char.sheet Macro]

Code: Select all

[h: link = macroLinkText("[email protected]:tokens", "none")]    
[frame("CharacterSheet"): {
    [h: page = getStrProp(macro.args, "Page")]
    [h,if(page==""): page="Overview"]
    <html>
    <head>
        <link rel="onChangeSelection" type="macro" href="[r:link]">
        <link rel="stylesheet" type="text/css" href="[email protected]:tokens"></link>
    </head>
    <body>

        [h: num.selected.tokens = json.length(getSelected("json"))]
        [if(num.selected.tokens == 1),code:
        {
            [r,macro("[email protected]:tokens"): page]
        };{
            [r,if(num.selected.tokens == 0): "<b>No character selected.</b>"; "<b>Can only display one character sheet at a time. Please select only one token to view or edit their character sheet.</b>"]
        }]
    
    
</body>
    </html>
}]
 
[/spoiler]

[[email protected]:tokens]

Code: Select all

[h: id = getSelected()]
[
h: has.permission = 0]
        [h: acting.token = getSelected()]
        [h: acting.player = getPlayerName()]
        [h,if(isOwner(acting.player, acting.token)): has.permission = 1]
        [h,if(isGM()): has.permission = 1]

        [if(has.permission == 0),code:
        {
            [r: "<b>You do not have permission to view this token's character sheet.</b>"]
        };{
            [h: "<!-- Stuff -->"]
            [h: id = getSelected()]
            [h: page = macro.args]
            [h: header.args = strPropFromVars("page, id", "unsuffixed")]
            [r,macro("[email protected]:tokens"): header.args]
            [r,macro("char.sheet."+page+"@Lib:tokens"): id]
        }] 
[/spoiler]

[[email protected]:tokens]

Code: Select all

.odd { background-color: #7DC059 }
.even { background-color: #669D49 }
th { background-color: #283F1D; color: #FFFFFF }
.page { background-color:#669D49; border-width:1px; border-color:#324E24 }
.currentPage { background-color:#7DC059; border-width:1px; border-color:#1C2B14 }  
[/spoiler]

[[email protected]:tokens]

Code: Select all

[h: varsFromStrProp(macro.args)]
[
h: currentPage = page]
[
h: pages = "Overview,Skills"]
<
table>
    <td><img src='[r,token(id): getTokenImage(75)]'></img></td>
    <td><h1>[r,token(id): token.name]</td>
</
table>

<
table>
    [foreach(page, pages,""), code: {
        [h,if (page == currentPage): class="currentPage" ; class="page"]
        [h: callback = "[email protected]:tokens"]
        <td class="[r: class]">
            <b>[r: macroLink(page, callback, "none", "Page="+page)]</b>
        </td>
        }]
</
table> 
[/spoiler]

[[email protected]:tokens]

Code: Select all

[h: id = macro.args]

<
table style="background-color:#7DC059; border-width:thin; border-color:#1C2B14">
<
tr>
    <table>
    <td valign="top">
        <table>
            <tr>
                <th colspan="*">Attribute</th>
                <th colspan="*">Score</th>
            </tr>

            [h: attributes = "Brawn, Finesse, Reasoning, Spirit"]
            [h: row = "odd"]
            [token(id),r,foreach(attrib, attributes, ""),code: {
                <tr class="[r:row]">
                    [h: property.name = 'attrib.' + attrib]
                    <td><b>[r:attrib]</b></td>
                    <td><input type="text" name="[r:attrib]" value="[r:getProperty(property.name, id)]" size="3" align="right"></td>
                </tr>
                [h: row = if(row=="odd", "even", "odd")]
            }]
        
            
        
</table>
    </td>

    <td valign="top">
        <table>
            <tr>
                <th colspan="2">Health</th>
            </tr>
            <tr class="odd">
                <td><b>Fatigue:</b></td>
                <td>[token(id),r,if(hasProperty("wounds.fatigue", id)): wounds.fatigue; "0"]/5</td>
            </tr>
            <tr class="even">
                <td><b>Light Wounds:</b></td>
                <td>[token(id),r,if(hasProperty("wounds.light", id)): wounds.light; "0"]/10</td>
            </tr>
            <tr class="odd">
                <td><b>Medium Wounds:</b></td>
                <td>[token(id),r,if(hasProperty("wounds.medium", id)): wounds.medium; "0"]/5</td>
            </tr>
            <tr class="even">
                <td><b>Heavy Wounds:</b></td>
                <td>[token(id),r,if(hasProperty("wounds.heavy", id)): wounds.heavy; "0"]/2</td>
            </tr>
            <tr class="odd">
                <td><b>Mortal Wounds:</b></td>
                <td>[token(id),r,if(hasProperty("wounds.mortal", id)): wounds.mortal; "0"]/1</td>
            </tr>
        </table>
    </td>
    </table>
</
tr>
<
tr>
    <table>
        <tr>
            <th colspan="2">Derived</th>
        </tr>
        <tr class="odd">
            <td><b>Movement </b></td>
            <td>[r: getProperty("derived.movement", id)]</td>
        </tr>
        <tr class="even">
            <td><b>Action Points <b></td>
            <td>[r: getProperty("attrib.actions", id)]</td>
    </table>
</
tr>
</
table>


[
r, if(listCount(id)!=1), code: {};{
    [h: link = macroLinkText("[email protected]:tokens", "all")]
    <form action="[r:link]" method="json">
    <input type="hidden" name="id" value="[r:id]">
    <input type="submit" name="edit_btn" value="Submit changes">
    </form>
}]
 
[/spoiler]

[[email protected]:tokens]

Code: Select all

[h: id = macro.args]
[
r,if(listCount(id)!=1),code: {};{
    [h: link = macroLinkText("[email protected]:tokens", "all")]
    <form action="[r:link]" method="json">
    <input type="hidden" name="id" value="[r:id]">
    <table style="background-color:#7DC059; border-width:thin; border-color:#1C2B14">
        <tr>
            <th colspan="1">Skill</th>
            <th colspan="1">Bonus</th>
            <th colspan="4">Calculation</th>
        </tr>
        
        
[h: row = "odd"]

[
h: skill.list = "Acrobatics, Athletics, Awareness, Demolitions, Dodge, Endurance, Medicine, Programming, Search, Willpower, Melee_Combat, Unarmed, Blunt, Edged, Polearms, Flexible, Thrown, Ranged_Combat, Archery, Projectiles, Rayguns, Cannons, Missiles, Artillery, Academics, Accounting, Cryptography, History, Law, Tactics, Research, Art, Painting, Sculpture, Prose, Drive, Wheeled, Treaded, Water, Hover, Interaction, Disguise, Persuasion, Teaching, Wrangling, Culture, Popular_Culture, Psychology, Navigation, Surface, Space, FTL, Pilot, Air, Underwater, Space, FTL, Science, Astrogeology, Biology, Chemistry, Physics, Engineering, Survival, Forest, Desert, Tundra, Urban, Tracking, Thievery, Legerdemain, Stealth, Forgery, Security"]
[
h: attrib.values = "Br=attrib.brawn ; Fi=attrib.finesse ; Re=attrib.reasoning ; Sp=attrib.spirit"]

[
r,foreach(skill, skill.list, ""),code: {
    <tr class="[r: row]">
        <td><b>
            [h: skill.prop.name = "skill." + skill]
            [h: skill.prop = getProperty(skill.prop.name, id)]
            [h: skill.type = getStrProp(skill.prop, "type")]
            [r,if(skill.type == "tech"): "&#32;&#32;&#32;&#32;&#32;"; ""]
            [r: skill]
            [r,if(getStrProp(skill.prop, "trained") == 1): " ( T )"; ""]
        </b></td>
        <td style="text-align:center"><b>
            [h,if(skill.type == "tech"): total.modifier = getStrProp(skill.prop, "value") + getProperty(getStrProp(attrib.values, getStrProp(getProperty(getStrProp(skill.prop, "broad.skill"), id), "attrib")), id) + getStrProp(getStrProp(skill.prop, "broad.skill"), "value"); total.modifier = getStrProp(skill.prop, "value") + getProperty(getStrProp(attrib.values, getStrProp(skill.prop, "attrib")), id)]
            [r: total.modifier]
        </b></td>
        <td>[r: " = "]</td>
        <td>
            [r,if(skill.type != "tech"): getStrProp(skill.prop, "attrib") + " &#40;" + getProperty(getStrProp(attrib.values, getStrProp(skill.prop, "attrib")), id) + "&#41;"; "Broad"]
        </td>
        <td>[r: " + "]</td>
        <td><input type="text" name="[r:skill]" value="[r:getStrProp(skill.prop, "value")]" size="3" align="right"></td>
    </tr>
    [h: row = if(row == "odd", "even", "odd")]
}]

    </table>

    <input type="submit" name="edit_btn" value="Submit changes">
    </form>
}]
 
[/spoiler]

Currently the sheet just displays info, as I've not gotten around to coding in what's needed to update the properties on the token. Still, beyond the basic look I managed to get it to just pop up different messages based if no tokens are selected, multiple tokens are selected, or a token the player does not have ownership is selected, thus displaying the character sheet only for those tokens the player has ownership on and not popping up any errors in the chat log, so they and the GM can leave the sheet open while doing everything else (and can dock it as well).

I wish there was a way (without using images) to get the active faux-tab to not have the border separating it from the main sheet area. I was only able to get general borders to work, not side specifics, but I think it works well enough.

The only other issue is that it takes around a second to load the Skills faux-tab, though I'm not sure if there's an easy way to streamline that bit of code or not. Either way that's not an issue with the faux-tabs. so doesn't need to be addressed.
Kill them all, and let the Game Master grant the exp!

ImageImage

User avatar
Azhrei
Site Admin
Posts: 12061
Joined: Mon Jun 12, 2006 1:20 pm
Location: Tampa, FL

Re: Tabs in dialog() or frame()

Post by Azhrei »

Oriet wrote:I wish there was a way (without using images) to get the active faux-tab to not have the border separating it from the main sheet area. I was only able to get general borders to work, not side specifics, but I think it works well enough.

The Java library only supports CSS 2.1 (and only portions of it) and side-specific CSS requires CSS 3.0 minimum.

The only other issue is that it takes around a second to load the Skills faux-tab, though I'm not sure if there's an easy way to streamline that bit of code or not. Either way that's not an issue with the faux-tabs. so doesn't need to be addressed.

Aliasmask had the idea of generating the HTML once and caching it in a token property. Any time the skills change, create a new value for the property. Now that property can simply be displayed and that should be very fast compared to generating the HTML on the fly. Obviously, this approach can be generalized to any chunk of HTML which takes time to create.

Oh, and congrats on your success so far. :) It's easy for me to give pointers where people might want to look for information, but I've never actually done it myself!

User avatar
Bone White
Great Wyrm
Posts: 1124
Joined: Tue Aug 23, 2011 11:41 am
Location: Cornwall, UK

Re: Tabs in dialog() or frame()

Post by Bone White »

Azhrei wrote:
The only other issue is that it takes around a second to load the Skills faux-tab, though I'm not sure if there's an easy way to streamline that bit of code or not. Either way that's not an issue with the faux-tabs. so doesn't need to be addressed.

Aliasmask had the idea of generating the HTML once and caching it in a token property. Any time the skills change, create a new value for the property. Now that property can simply be displayed and that should be very fast compared to generating the HTML on the fly. Obviously, this approach can be generalized to any chunk of HTML which takes time to create.


Sorry to split from the main focus of the topic, but this sounds like a grand idea. For reference, could you direct me to the post with it in? I must've missed it.

User avatar
wolph42
Deity
Posts: 9852
Joined: Fri Mar 20, 2009 5:40 am
Location: Netherlands
Contact:

Re: Tabs in dialog() or frame()

Post by wolph42 »

its really really simple all the stuff of the form that is between the {} you put into a separate macro, say generateForm[some name] then you simply call [cacheForm = generateForm...] and [set(lib)Property("cacheForm", cacheForm)] done.

Now when you want to build the form you simply retrieve the value from the (lib)token and:
[h:cacheForm = get(lib)Property("cacheForm", "someToken")]
[dialog(...){[r:cacheForm]}]
c'est tout.

Now what's really interesting is that you can also create these forms to be dynamic :D thats my personal invention, by using strformat. I believe I wrote an wiki on it, but you can also search for 'dynamically cached forms'.

User avatar
Oriet
Cave Troll
Posts: 30
Joined: Mon Dec 05, 2011 10:58 am
Location: Somewhere that's not there, but not really here.
Contact:

Re: Tabs in dialog() or frame()

Post by Oriet »

Bone White wrote:Sorry to split from the main focus of the topic, but this sounds like a grand idea. For reference, could you direct me to the post with it in? I must've missed it.

I don't think it's really a split. Being able to store the HTML in a token property so it doesn't have to be generated every time would really help optimise the macro-set (or macro-chain, or whatever you want to call a group of interdependent macros), drastically decreasing the load on system resources. This becomes increasingly useful with the more faux-tabs there are and the more information stored on them, which if properly streamlined means the character sheet could easily contain [wiki]macroLinkText()[/wiki] for making skill checks, attacks, and other such rolls dependent on properties displayed therein.

[s]I want to say I saw the referenced thread at one point, but I'm not sure where or the details as at the time I was just dealing with far simpler functions and options.[/s] Scratch that, wolph42 made a post explaining how it's done while I was making mine.

[ETA]
Found on the wiki where it's talked about. Here is the part talking about caching html forms, which also has a link on making them dynamic. I'll definitely have to go over it a bit and implement it.
Kill them all, and let the Game Master grant the exp!

ImageImage

User avatar
wolph42
Deity
Posts: 9852
Joined: Fri Mar 20, 2009 5:40 am
Location: Netherlands
Contact:

Re: Tabs in dialog() or frame()

Post by wolph42 »

ah yes, encode/decode is a rather important step :mrgreen: the other option is the one from alias is to cram the lot in a json array
so cacheForm = json.append("", generateForm...()) instead of encoding. The advantage as AM mentioned is that you can add stuff at the end which would allow you to build the cache in step... although a simple concatenation of the strings would accomplish the same... choices choices...

User avatar
Bone White
Great Wyrm
Posts: 1124
Joined: Tue Aug 23, 2011 11:41 am
Location: Cornwall, UK

Re: Tabs in dialog() or frame()

Post by Bone White »

My char sheet takes about 20 seconds to update with my current database structure and such, which is rather painful. I limited it to only refreshing when a change has been made to reduce this, so I am storing the entire form as html, but re-loading the entire thing. I feel though that the slowness is caused more by my data structure than anything, however anything to speed this up is a good thing, thanks.

Post Reply

Return to “Macros”