Forms tutorial: Difference between revisions

From RPTools Wiki
Jump to navigation Jump to search
No edit summary
m (Text replacement - "<source" to "<syntaxhighlight")
 
(32 intermediate revisions by 9 users not shown)
Line 6: Line 6:


''A tutorial to creating html forms for maptool.''
''A tutorial to creating html forms for maptool.''
{{stub|difference between frames and dialogs / closing frames}}


== What is this about? ==
== What is this about? ==


The [[input|input()]] function is a great way to get data from the user. Its simple to use as well. But it limits you in the ways you can design the resulting dialog. You might even miss features like multi-line textboxes. Maybe you want keep an dialog open to send it when you are ready, but still want the other maptool features to work - and not freeze.
The [[input|input()]] function is a great way to get data from the user. It is simple to use as well. But it limits you in the ways you can design the resulting dialog. You might even miss features like multi-line textboxes. Maybe you want keep a dialog open to send it when you are ready, but still want the other maptool features to work - and not freeze.


If you can't create the user interaction with [[input|input()]] you have to create a html form. And here I explain to you how to do that.
If you can't create the user interaction with [[input|input()]] you have to create an html form. And here I explain to you how to do that.


But be aware! A input pauses your macro, creates a pop up dialog and creates variables containing the entered data all by itself. With html forms you have to split the process in (at least) two macros and make all that by yourself.
But be aware! An input pauses your macro, creates a pop up dialog and creates variables containing the entered data all by itself. With html forms you have to split the process into (at least) two macros and make all that by yourself.


I assume you know how to write simple macros and create/use lib:tokens. All my code examples will be located on a [[Library Token|lib:token]] named "Lib:token".
I assume you know how to write simple macros and create/use lib:tokens. All my code examples will be located on a [[Library Token|lib:token]] named "Lib:token".
Line 24: Line 21:
== Where can you use forms? ==
== Where can you use forms? ==


Maptool accepts html in uncountable places and theoretically where ever html is interpreted you could create a form. But really useful is it to place your html form either in a [[dialog (roll option)|dialog]] or a [[frame (roll option)|frame]]. A frame is a dockable window while a dialog is floating above the rest of the UI. A dialog has a close button as default while a frame has no buttons other than those you create there.
Maptool accepts html in uncountable places and theoretically wherever html is interpreted you could create a form. But it is really useful to place your html form either in a [[dialog (roll option)|dialog]] or a [[frame (roll option)|frame]]. A frame is a dockable window while a dialog is floating above the rest of the UI. A dialog has a close button as default while a frame has no buttons other than those you create there.


Both commands (or to be more specific: roll options) open some kind of window. Both frames and dialogs are named so when you use code that would open a window, it will update the content of an open window with the same name if that exists.
Both commands (or to be more specific: roll options) open some kind of window. Both frames and dialogs are named so when you use code that would open a window, it will update the content of an open window with the same name if that exists.


You can close dialogs in macro with [[closeDialog|closeDialog()]] and in later versions you can close frames as well([[closeFrame|closeFrame()]]).
You can close dialogs in macro with [[closeDialog|closeDialog()]] and in later versions you can close frames as well ([[closeFrame|closeFrame()]]).


Lets create a macro "openFrame" so that we can display a form.
Lets create a macro "openFrame" so that we can display a form.
<source line lang="mtmacro">
<syntaxhighlight line lang="mtmacro">
[frame("myForm"): {
[frame("myForm"): {
     here will be a fishy form
     here will be a fishy form
}]
}]
</source>
</syntaxhighlight>


For my following examples we will use this slightly changed openFrame-macro:
For my following examples we will use this slightly changed openFrame-macro:
<source line lang="mtmacro">
<syntaxhighlight line lang="mtmacro">
[frame("myForm"): {
[frame("myForm"): {
     <h3>my form:</h3>
     <h3>my form:</h3>
     [r, macro("displayForm@Lib:token"): ""]
     [r, macro("displayForm@Lib:token"): ""]
}]
}]
</source>
</syntaxhighlight>


And probably you can guess: all the form related code will be placed in  a displayForm-macro. So we can forget about opening the frame and concentrate all on forms. Yay.
And probably you can guess: all the form related code will be placed in  a displayForm-macro. So we can forget about opening the frame and concentrate all on forms. Yay.
Line 49: Line 46:
== About forms ==
== About forms ==


Now lets begin with that form. HTML supports user editable forms and a godo variations of input fields that can be placed in such a form. A html page (your frame for example) can even contain multiple forms (but you'll only receive the content of one of them).
Now let's begin with that form. HTML supports user editable forms and a good variety of input fields that can be placed in such a form. An HTML page (your frame for example) can even contain multiple forms (but you'll only receive the content of one of them).


For general syntax information about the {{code|<form>}}-tag and the input fields I find  [[http://www.w3schools.com/html/html/forms.asp w3schools.com]] quite helpful.
For general syntax information about the {{code|<form>}}-tag and the input fields I find  [[http://www.w3schools.com/html/html/forms.asp w3schools.com]] quite helpful.


We begin at the start and create a form with two text input fields.
We begin at the start and create a form with two text input fields.
<source line lang="html4strict">
<syntaxhighlight line lang="html4strict">
<!-- this example displays correctly but does nothing -->
<!-- this example displays correctly but does nothing -->
<form>
<form>
Line 60: Line 57:
Strength: <input type="text" name="str">
Strength: <input type="text" name="str">
</form>
</form>
</source>
</syntaxhighlight>


Note that you can place all possible html in such a form so you can easily design it any way you want. Create tables, use CSS, fonts, colors, ... '''Maptool only supports HTML3.2 and [[Supported CSS Styles|CSS1]]'''. This is because the java controls being used in maptool don't support more recent versions of HTML/CSS. Dont blame maptool ;)
Note that you can place all possible HTML in such a form so you can easily design it any way you want. Create tables, use CSS, fonts, colors, ... '''MapTool only supports HTML3.2 and [[Supported CSS Styles|CSS1]]'''. This is because the java controls being used in MapTool don't support more recent versions of HTML/CSS. Don't blame MapTool ;)


While this is pretty handy you don't get the data your user enters yet. First we dont have a submit button and second maptool doesnt know where to send that data.
While this is pretty handy you don't get the data your user enters yet. First we don't have a submit button and second MapTool doesn't know where to send that data.


If we do it right a form - if submitted - calls another macro, lets call that 'processForm', and passes the entered data as macro.args.
If we do it right a form - if submitted - calls another macro, let's call that 'processForm', and passes the entered data as macro.args.
You can receive this data as string property list or as json which I prefer. If you prefer string property lists you have to omit the method field of the form tag (and change the processForm-macros).
You can receive this data as a string property list or as json which I prefer. If you prefer string property lists you have to omit the method field of the form tag (and change the processForm macro).


We specify the called macro using [[macroLinkText|macroLinkText()]]. You should not specify the macro.args here as it will interfere with the form data.
We specify the called macro using [[macroLinkText|macroLinkText()]]. You should not specify the macro.args here as it will interfere with the form data.


Now lets make my little form work:
Now let's make my little form work:
<source line lang="mtmacro">
<syntaxhighlight line lang="mtmacro">
[h: processorLink = macroLinkText("processForm@Lib:token", "all")]
[h: processorLink = macroLinkText("processForm@Lib:token", "all")]
<form action="[r:processorLink]" method="json">
<form action="[r:processorLink]" method="json">
Line 79: Line 76:
<input type="submit" name="myForm_btn" value="Okay">
<input type="submit" name="myForm_btn" value="Okay">
</form>
</form>
</source>
</syntaxhighlight>


And create the processForm-macro.
And create the processForm-macro.
<source line lang="mtmacro">
<syntaxhighlight line lang="mtmacro">
<pre>
<pre>
[r: json.indent(macro.args,2)]
[r: json.indent(macro.args,2)]
</pre>
</pre>
</source>
</syntaxhighlight>
With this setup we can very easily find out how a specific form packs the data entered and how we could work with that. For this tutorial this processForm-macro will do.
With this setup we can very easily find out how a specific form packs the data entered and how we could work with that. For this tutorial this processForm-macro will do.


The output we receive from this example is
The output we receive from this example is
<source line lang="mtmacro">
<syntaxhighlight line lang="mtmacro">
{
{
   "charName": "the fishy dude",
   "charName": "the fishy dude",
Line 96: Line 93:
   "myForm_btn": "Okay"
   "myForm_btn": "Okay"
}
}
</source>
</syntaxhighlight>
Now its pretty easy to access the name and strength using [[json.get|json.get()]].
Now it's pretty easy to access the name and strength using [[json.get|json.get()]].


== The input fields ==
== The input fields ==
Line 104: Line 101:
Now let me introduce you to the input fields in detail. Some are a little tricky in how they send their data - so there will be advice about that as well.
Now let me introduce you to the input fields in detail. Some are a little tricky in how they send their data - so there will be advice about that as well.


In general all input fields should be given a name. This name will be used in the resulting json data as key.
In general all input fields should be given a name. This name will be used in the resulting json data as a key.


[[image:Cif_forms_tutorial_example_input_fields.png]]
[[image:Cif_forms_tutorial_example_input_fields.png]]
Line 110: Line 107:
=== Text fields ===
=== Text fields ===


<source line lang="html4strict">
<syntaxhighlight line lang="html4strict">
<input type="text" name="" size="" maxlength="" value="">
<input type="text" name="" size="" maxlength="" value="">
</source>
</syntaxhighlight>
This is your standard one line text input field. The width of the field can be set with ''size'' and the maximum length of the input with ''maxlength''. If you set a ''value'' your field will appear filled with that.
This is your standard one line text input field. The width of the field can be set with {{code|size}} and the maximum length of the input with {{code|maxlength}}. If you set a {{code|value}} your field will appear filled with that.


You can have a password type text field as well if you set
You can have a password type text field as well if you set


<source line lang="html4strict">
<syntaxhighlight line lang="html4strict">
<input type="password" name="" size="" maxlength="" value="">
<input type="password" name="" size="" maxlength="" value="">


</source>
</syntaxhighlight>


=== Multi line text fields ===
=== Multi line text fields ===


If you need multiple lines you use
If you need multiple lines you use
<source line lang="html4strict">
<syntaxhighlight line lang="html4strict">
<textarea name="" cols="" rows="">
<textarea name="" cols="" rows="">
Enter your text here...
Enter your text here...
</textarea>
</textarea>
</source>
</syntaxhighlight>
You can specifiy the size of that text box with {{code|cols}} and {{code|rows}}. A preset text would be written between the open and closing tags.
You can specify the size of that text box with {{code|cols}} and {{code|rows}}. A preset text would be written between the open and closing tags.


'''TRICK:''' You can process the content of a textarea line by line if you use the following trick. By using [[encode|encode()]] on the complete content you change line breaks into {{code|%0A}}. Then you can use string list functions using {{code|%0A}} as separator.
'''TRICK:''' You can process the content of a textarea line by line if you use the following trick. By using [[encode|encode()]] on the complete content you change line breaks into {{code|%0A}}. Then you can use string list functions using {{code|%0A}} as separator.


As example let me show you a processForm macro that adds all numbers you enter in the textarea - one number per line. Dice expressionsare evaluated.
As example let me show you a processForm macro that adds all numbers you enter in the textarea - one number per line. Dice expressions are evaluated.
<source line lang="mtmacro">
<syntaxhighlight line lang="mtmacro">
<!-- processForm -->
[h:'<!-- processForm -->']
[h: formData = macro.args]
[h: formData = macro.args]
<!-- get the content of a textarea named "textarea" -->
[h:'<!-- get the content of a textarea named "textarea" -->']
[h: text = json.get(formData, "textarea")]
[h: text = json.get(formData, "textarea")]
<!-- encode it -->
[h:'<!-- encode it -->']
[h: text = encode(text)]
[h: text = encode(text)]
<!-- loop through the content -->
[h:'<!-- loop through the content -->']
[h: sum=0]
[h: sum=0]
[h, foreach(line, text, "", "%0A"), code: {
[h, foreach(line, text, "", "%0A"), code: {
     <!-- decode line again -->
     [h:'<!-- decode line again -->']
     [h: decodedLine = decode(line)]
     [h: decodedLine = decode(line)]
     [h, if(isNumber(decodedLine):
     [h, if(isNumber(decodedLine):
Line 152: Line 149:
     ]
     ]
}]
}]
<!-- and output. done. -->
[h:'<!-- and output. done. -->']
[r: sum]
[r: sum]
</source>
</syntaxhighlight>


=== Drop down lists ===
=== Drop down lists ===


<source line lang="html4strict">
<syntaxhighlight line lang="html4strict">
<select name="" size="">
<select name="" size="">
     <option>A</option>
     <option>A</option>
Line 164: Line 161:
     <option selected="selected">C</option>
     <option selected="selected">C</option>
</select>
</select>
</source>
</syntaxhighlight>
'''NOTE''' {{code|multiple}} doesnt work, only one entry appears in the resulting json. Known bug.
'''NOTE''' {{code|multiple}} doesn't work, only one entry appears in the resulting json. Known bug.


=== Radio buttons ===
=== Radio buttons ===


<source line lang="html4strict">
<syntaxhighlight line lang="html4strict">
A<input type="radio" name="group1" value="A" checked="checked">
A<input type="radio" name="group1" value="A" checked="checked">
B<input type="radio" name="group1" value="B">
B<input type="radio" name="group1" value="B">
Line 177: Line 174:
B<input type="radio" name="group2" value="B">
B<input type="radio" name="group2" value="B">
C<input type="radio" name="group2" value="C">
C<input type="radio" name="group2" value="C">
</source>
</syntaxhighlight>


=== Checkboxes ===
=== Checkboxes ===


<source line lang="html4strict">
<syntaxhighlight line lang="html4strict">
<input type="checkbox" name="group1" value="A"> A
<input type="checkbox" name="group1" value="A"> A
<input type="checkbox" name="group1" value="B"> B
<input type="checkbox" name="group1" value="B"> B
<input type="checkbox" name="group1" value="C"> C
<input type="checkbox" name="group1" value="C"> C
<input type="checkbox" name="group1" value="D" checked="checked"> D
<input type="checkbox" name="group1" value="D" checked="checked"> D
</source>
</syntaxhighlight>
'''NOTE''' unchecked boxes don't appear in the json; only checked ones will.
'''NOTE''' unchecked boxes don't appear in the json; only checked ones will.
So test if a box is checked by using {{code|json.contains}} on the field name.
So test if a box is checked by using {{code|json.contains}} on the field name.
Line 192: Line 189:
See my [[Forms tutorial#Predefine checkboxes|"Good advice" tip #4]] for another way to treat this (you can predefine the value with a 0-value).
See my [[Forms tutorial#Predefine checkboxes|"Good advice" tip #4]] for another way to treat this (you can predefine the value with a 0-value).


'''NOTE''' multiple selection doesnt work as well. So do not name the checkboxes alike.
'''NOTE''' multiple selection doesn't work as well. So do not name the checkboxes alike.


=== Hidden data ===
=== Hidden data ===


<source line lang="html4strict">
<syntaxhighlight line lang="html4strict">
<input type="hidden" name="" value="">
<input type="hidden" name="" value="">
</source>
</syntaxhighlight>
Since you cannot send additional information to your form processor using the args parameter of {{code|macroLinkText}} you have to send it piggyback with the form data. This can be done with invisible fields.
Since you cannot send additional information to your form processor using the args parameter of {{code|macroLinkText}} you have to send it piggyback with the form data. This can be done with invisible fields.
Unfortunately, assigning a [[JSON Array]] or a [[JSON Object]] as a value can cause the JSON to get mangled. To have it parse properly, a work around is to replace the quote marks {{code|"}} around the value by {{code|'}}; another work around is to encode the json first, and decode it in the {{code|macroLinkText}} macro.


=== Buttons ===
=== Buttons ===


<source line lang="html4strict">
<syntaxhighlight line lang="html4strict">
<input type="submit" name="" value="">
<input type="submit" name="" value="">
</source>
</syntaxhighlight>
The button caption is set via the {{code|value}} parameter.
The button caption is set via the {{code|value}} parameter.


'''NOTE''' only the pressed button appears in the json. If you use multiple buttons use [[json.contains|json.contains()]] to  identify it. Predefining the key/name could probably help (see checkboxes).
'''NOTE''' only the pressed button appears in the json. If you use multiple buttons use [[json.contains|json.contains()]] to  identify it. Predefining the key/name could probably help (see checkboxes).


'''NOTE''' You can use html formating inside of the button caption (value parameter). You have to enable this by beginning with {{code|<html>}}.
'''NOTE''' You can use '''HTML formatting''' inside of the button caption (value parameter). You have to enable this by beginning with {{code|<html>}} like this:
 
<syntaxhighlight lang="mtmacro">
 
<input type="submit" value="<html><b>Button</b></html>">
 
</syntaxhighlight>
 
You can't apply any kind of CSS to HTML inputs.
To remove the HTML tags from the submitted value you can use code like this
 
<syntaxhighlight line lang="mtmacro">
 
[H: submit = json.get(macro.args,"submit")]
[H: submit = replace(submit,"<[^>]*?>","")]
 
</syntaxhighlight>


=== Image buttons ===
=== Image buttons ===


First you have to get the asset of the image you want to place on a button. Good ways to do so is by using an image table or image tokens. I wont explain that here in more detail.
First you have to get the asset of the image you want to place on a button. Good ways to do so are by using an image table or image tokens. I won't explain that here in more detail.
<source line lang="html4strict">
<syntaxhighlight line lang="html4strict">
<input type="image" src="" name="" value="">
<input type="image" src="" name="" value="">
</source>
</syntaxhighlight>


This image button submits the form exactly as a submit button does. As {{code|src}} you have to set an image asset. Note that you cannot used resized assets (using the ASSETxSIZE notation or specifying the size on asset generating function calls).
This image button submits the form exactly as a submit button does. As {{code|src}} you have to set an image asset. Note that you cannot used resized assets (using the ASSETxSIZE notation or specifying the size on asset generating function calls).


It does not only send the button name and value but also the coordinates where you clicked in the image. This could be used for some pretty UI.
It not only sends the button name and value but also the coordinates where you clicked in the image. This could be used for some pretty UI.


<source line lang="mtmacro">
<syntaxhighlight line lang="mtmacro">
<!-- this image button pushed .. -->
<!-- this image button pushed .. -->


Line 234: Line 249:
     "img_btn.y": "13"
     "img_btn.y": "13"
}
}
</source>
</syntaxhighlight>


== Events ==
== Events ==


Since I'll use this in one of my examples (see below) let me very shortly introduce you some kind of event maptool supports and how to set it up. A discussion about this can be found in the [http://forums.rptools.net/viewtopic.php?p=143242#p143242 maptool forums].
Since I'll use this in one of my examples (see below) let me very shortly introduce you to some kinds of events that MapTool supports and how to set it up. A discussion about this can be found in the [http://forums.rptools.net/viewtopic.php?p=143242#p143242 MapTool forums] and a list of events on the [[:Category:Event]] page.


Maptool can react on three events: if a token is changed, if selection is changed and if impersonation is changed. You can specifiy a macro that is called if one event happens.
Maptool macros can react on three events: if a token is changed, if token selection is changed and if impersonation is changed. You can specifiy a macro that is called if one event happens.


This will work if a frame is open at that moment. The onChangeToken-event is a little bit tricky. First it is fired numerous times and not only if you'd expect it. Second is your macro can change tokens and so fire the event and call itself ... what could cause problems.
This will work if a frame is open at that moment. The onChangeToken-event is a little bit tricky. First it is fired numerous times and not only if you'd expect it. Second is your macro can change tokens and so fire the event and call itself… what could cause problems.


The other two events are pretty easy to use and quite handy for dumping informations about selected tokens and such.
The other two events are pretty easy to use and quite handy for dumping information about selected tokens and such.


To set it up you have to define a html header and specify a specific link element. So your frame content should begin like this
To set it up you have to define an HTML header and specify a specific link element. So your frame content should begin like this:
<source line lang="html4strict">
<syntaxhighlight line lang="html4strict">
<html>
<html>
<head>
<head>
Line 253: Line 268:
</head>
</head>
<body>
<body>
</source>
</syntaxhighlight>
Replace macroLink by an actual macroLinkText-call to a macro of your choice. A common practice is to call the frame opening macro itself to actualize the content. the {{code|rel}} parameter is set either {{code|onChangeSelection}},{{code|onChangeImpersonated}} or
Replace {{code|macroLink}} by an actual macroLinkText-call to a macro of your choice. A common practice is to call the frame opening macro itself to actualize the content. the {{code|rel}} parameter is set either {{code|onChangeSelection}}, {{code|onChangeImpersonated}} or {{code|onChangeToken}}.
 
{{code|onChangeToken}}.


== Good advice ==
== Good advice ==
Line 262: Line 275:
=== Always encode user input ===
=== Always encode user input ===


It is always wise to trust in the dumbness of users. If they can break things they will. And i dont say they do it intentionally.
It is always wise to trust in the dumbness of users. If they can break things they will. And I don't say they do it intentionally.
 
So you should protect your macros against trouble-making user inputs. For example a comma inside of an item of a comma separated list breaks the list.
 
So it's always good to use [[encode|encode()]] on user input (and [[decode|decode()]] to .. well .. decode it again).
 
=== Building complex HTML forms can take time ===
 
Just be aware of this. Building and rendering HTML and collecting and displaying lots of data and images and fields can take serious time. If your frame is updated frequently it could cause speed issues.
 
Think about storing calculated frame content, only updating the necessary parts. Reduce the number of updates to the needed minimum. (See Caching)
 
Don't make things complicated if you do not want to have speed issues but be prepared to fight them if you do.
 
=== Caching HTML forms ===
 
Since building HTML forms can take serious amounts of time it is a good practice to store built form HTML in a token property and reuse it as long it doesn't have to be rebuilt. It's especially effective if you build complex stuff by accessing lots of properties - usually the case if you build character sheets. When creating complex HTML structures and storing them into a token property you're asking for trouble so it's common practice to encode them first before you store them.
It's also best to store character sheets (token specific) onto the (n)pc token and general forms like weapon list, skill list, etc. onto a lib:token.
 
Here an example of 'caching' a charactersheet.
 
<syntaxhighlight line lang="mtmacro">
[h: rebuild = macro.args]
[h: id = currentToken()]
[h: output = getProperty("charSheetCache", id)]


So you should protect your macros against trouble making user inputs. For example can a comma inside of an item of a comma separated list break the list.
[h, if(rebuild || output == ""), code: {
    [h: output = "here you build"]
    [h: output = output + "your mega complex character sheet"]
    ...
    [h: output = encode(output)]


So its always always good to use [[encode|encode()]] on user input (and [[decode|decode()]] to .. well .. decode it again).
    [h:'<!-- though it might be better to define a UDF for that -->']
    [h:'<!-- e.g: output = encode(createSheetContent()) -->']


=== Building complex html forms can take time ===
    [h: setProperty("charSheetCache", output, id)]
};{}]


Just be aware of this. Building and rendering html and collecting and displaying lots of data and images and fields can take serious time. If your frame is updated frequently it could cause speed issues.
[frame("Character Sheet"):{
    [r: decode(output)]
}]
</syntaxhighlight>


Think about storing calculated frame content, only updating the necessary parts. Reduce the number of updates to the needed minimum.


Dont make things complicated if you do not have speed issues but be prepared to fight them if you do.
A nice technique to individualize cached forms/html is described here: [http://forums.rptools.net/viewtopic.php?f=20&t=16324&start=0  Making cached structures dynamic (Load BIG forms FAST)]


=== Don't forget the token context ===
=== Don't forget the token context ===
 
When you work with macrolinks you can easily lose the token context. If you happen to work with explicit ids and get/setProperty() a lot that may be no problem for you.
When you work with macrolinks you can easily use the token context. If you happen to work with explicit ids and get/setProperty() a lot that may be no problem for you.


However it does change the chat output. If a macrolink is called with unknown token context instead of token image and name the chat line begins with user name.
However it does change the chat output. If a macrolink is called with unknown token context instead of token image and name the chat line begins with user name.


If you dont like this always specify the token context in your {{func|macroLink}} and {{func|macroLinkText}} calls.
If you don't like this always specify the token context in your {{func|macroLink}} and {{func|macroLinkText}} calls.


An example of this can be found in the [http://forums.rptools.net/viewtopic.php?p=170425#p170425 forum].
An example of this can be found in the [http://forums.rptools.net/viewtopic.php?p=170425#p170425 forum].
Line 289: Line 333:


Checkboxes only create data in macro.args if they are checked. There is a neat trick to always create the relevant data even if it is unchecked.
Checkboxes only create data in macro.args if they are checked. There is a neat trick to always create the relevant data even if it is unchecked.
Predefine the key/value-pair using a hidden input with a 0 (of course you have to use the same name as your checkbox has). A checked checkbox will overwrite a predefined 0 while a unchecked checkbox (as it does not generate anything) won't overwrite a predefined 1.
Predefine the key/value-pair using a hidden input with a 0 (of course you have to use the same name as your checkbox has). A checked checkbox will overwrite a predefined 0 while an unchecked checkbox (as it does not generate anything) won't overwrite a predefined 1.


If you want to have a initially checked checkbox you can set it as checked like this (regardless of beeing predefined or not)
If you want to have an initially checked checkbox you can set it as checked like this (regardless of being predefined or not)
<source line lang="html4strict">
<syntaxhighlight line lang="html4strict">
<input type="checkbox" name="surprised"  value="1" checked="checked" />
<input type="checkbox" name="surprised"  value="1" checked="checked" />
</source>
</syntaxhighlight>
Big thanks to wolph42 for learning me this.
Big thanks to wolph42 for teaching me this.


=== Don't shy away from layout tables ===
=== Don't shy away from layout tables ===


In webdesign layout tables might be a no-go. Don't be afraid of them in maptool. They are a great way to precisly align your form elements. Let me demonstrate how different a simple layout table looks compared to a very simplistic inline approach.
In webdesign layout tables might be a no-go. Don't be afraid of them in MapTool. They are a great way to precisely align your form elements. Let me demonstrate how different a simple layout table looks compared to a very simplistic inline approach.


[[Image:Cif forms tutorial example layout table.png]]
[[Image:Cif forms tutorial example layout table.png]]


<source line lang="mtmacro">
<syntaxhighlight line lang="mtmacro">
[frame("test"): {
[frame("test"): {


Line 332: Line 376:


}]
}]
</source>
</syntaxhighlight>


== The big examples ==
== The big examples ==
Line 343: Line 387:
[[image:Cif_forms_tutorial_screenshot_example1.png]]
[[image:Cif_forms_tutorial_screenshot_example1.png]]


First we need a frame. We want it to auto-update with the selected content. We pass the selected tokens to the character sheet generating macro so we know what do display.
First we need a frame. We want it to auto-update with the selected content. We pass the selected tokens to the character sheet generating macro so we know what to display.


We want more eyecandy, so we will use css. As we like separating css rules from the content we will place it in its own macro.
We want more eye candy, so we will use CSS. As we like separating CSS rules from the content we will place it in its own macro.


'''openCharacterSheet'''
'''openCharacterSheet'''
<source line lang="mtmacro">
<syntaxhighlight line lang="mtmacro">
[h: link = macroLinkText("openCharacterSheet@Lib:token", "none")]
[h: link = macroLinkText("openCharacterSheet@Lib:token", "none")]
[frame("csheet"): {
[frame("csheet"): {
Line 361: Line 405:
</html>
</html>
}]
}]
</source>
</syntaxhighlight>


'''css'''
'''css'''
<source line lang="css">
<syntaxhighlight line lang="css">
.odd { background-color: #FFFFFF }
.odd { background-color: #FFFFFF }
.even { background-color: #EEEEAA }
.even { background-color: #EEEEAA }
th { background-color: #113311; color: #FFFFFF }
th { background-color: #113311; color: #FFFFFF }
</source>
</syntaxhighlight>


Then we have to actually build the character sheet. Since selection will cause this to be called we have to deal with empty and multiple selections. We'll just don't create any output then.
Then we have to actually build the character sheet. Since selection will cause this to be called we have to deal with empty and multiple selections. We just don't create any output then.


'''characterSheet'''
'''characterSheet'''
<source line lang="mtmacro">
<syntaxhighlight line lang="mtmacro">
[h: id = macro.args]
[h: id = macro.args]
[r, if(listCount(id)!=1), code: {};{
[r, if(listCount(id)!=1), code: {};{
Line 410: Line 454:
}]
}]
[h: classes = "Warrior, Rogue, Wizard, Priest"]
[h: classes = "Warrior, Rogue, Wizard, Priest"]
[h: class = getProperty("Class", id)]
[h: CharClass = getProperty("CharClass", id)]
<tr class="[r:row]">
<tr class="[r:row]">
     <td><b>Class:</b></td>
     <td><b>Class:</b></td>
     <td>
     <td>
         <select name="Class" size="1">
         <select name="CharClass" size="1">
         [r, foreach(c, classes, ""), code: {
         [r, foreach(c, classes, ""), code: {
             <option [r, if(c==class): "selected"]>[r:c]</option>
             <option [r, if(c==CharClass): "selected"]>[r:c]</option>
         }]
         }]
     </select>
     </select>
Line 425: Line 469:
</form>
</form>
}]
}]
</source>
</syntaxhighlight>


If the submit button is pressed we want to save the changes back to the token.
If the submit button is pressed we want to save the changes back to the token.


'''editCharacterSheet'''
'''editCharacterSheet'''
<source line lang="mtmacro">
<syntaxhighlight line lang="mtmacro">
[h: arguments = macro.args]  
[h: arguments = macro.args]  
[h: id = json.get(arguments, "id")]
[h: id = json.get(arguments, "id")]


<!-- set primary attributes -->
[h:'<!-- set primary attributes -->']
[h: attributes = "Strength, Dexterity, Intelligence, Endurance"]
[h: attributes = "Strength, Dexterity, Intelligence, Endurance"]
[h, foreach(attrib, attributes), code: {
[h, foreach(attrib, attributes), code: {
     [h: val = json.get(macro.args, attrib)]
     [h: val = json.get(macro.args, attrib)]
     [h, if(! isNumber(val)): val=eval(val)]
     [h, if(! isNumber(val)): val=eval(val)]
     <!-- allowed values are 1..6 -->
     [h:'<!-- allowed values are 1..6 -->']
     [h: val = min(max(val,1), 6)]
     [h: val = min(max(val,1), 6)]
     [r: setProperty(attrib, val, id)]
     [r: setProperty(attrib, val, id)]
Line 445: Line 489:


[h: setProperty("Hit Points", 6*getProperty("Endurance",id), id)]
[h: setProperty("Hit Points", 6*getProperty("Endurance",id), id)]
[h: setProperty("Class", json.get(macro.args, "Class"), id)]
[h: setProperty("Class", json.get(macro.args, "CharClass"), id)]
[h: setProperty("Movement", getProperty("Dexterity",id), id)]
[h: setProperty("Movement", getProperty("Dexterity",id), id)]


[h: class = getProperty("Class", id)]
[h: CharClass = getProperty("CharClass", id)]
[h, switch(class):
 
[h, switch(CharClass):
     case "Warrior": val=6;
     case "Warrior": val=6;
     case "Rogue": val=2;
     case "Rogue": val=2;
Line 462: Line 507:
Changes saved to [r: getName(id)].
Changes saved to [r: getName(id)].
[h, macro("openCharacterSheet@Lib:token"): id]
[h, macro("openCharacterSheet@Lib:token"): id]
</source>
</syntaxhighlight>


If you'd want to play with this you'd surely come up with lots of improvements .. great! I would as well. But this should be enough to demonstrate building a character sheet or editor with html forms.
If you'd want to play with this you'd surely come up with lots of improvements .. great! I would as well. But this should be enough to demonstrate building a character sheet or editor with html forms.
Line 470: Line 515:
=== Click-based Target selection ===
=== Click-based Target selection ===


There are very differen ways how to select targets of an action. The clickbased targeting (first done by Rumble) works best in maptool version b70 or later with the "unowned selection" feature.
There are very different ways to select targets of an action. The clickbased targeting (first done by Rumble) works best in MapTool version b70 or later with the "unowned selection" feature.




[[image:Cif_forms_tutorial_screenshot_example2.png]]
[[image:Cif_forms_tutorial_screenshot_example2.png]]


You impersonate or select the active token. Then you execute a macro, eg "Attack" that opens a frame. In that frame you can enter additional infos like modifier. While that frame is open you select((with the mouse on the map)) the target(s) of the attack. The frame has a button that actually performs the attack.
You impersonate or select the active token. Then you execute a macro, e.g. "Attack" that opens a frame. In that frame you can enter additional information like modifiers. While that frame is open you select (with the mouse on the map) the target(s) of the attack. The frame has a button that actually performs the attack.


Lets start with the macro that opens that frame.
Let's start with the macro that opens that frame.


'''openActionFrame'''
'''openActionFrame'''


This macro first determines what token should be considered the ''active'' token. A token impersonated would be preferred. Otherwise the selection is taken. If no or multiple tokens are selected the macro us aborted.
This macro first determines what token should be considered the ''active'' token. A token impersonated would be preferred. Otherwise the selection is taken. If no or multiple tokens are selected the macro is aborted.


Then the macro checks if the user has the right to perform actions with   that token - he has if he is GM or own the token.
Then the macro checks if the user has the right to perform actions with that token - he does if he is the GM or owns the token.


After that the current selection is cleared and the frame displaying code is called.
After that the current selection is cleared and the frame displaying code is called.
Line 489: Line 534:
If you have different actions to perform here would the place to branch into different actionFrames according to the chosen action.
If you have different actions to perform here would the place to branch into different actionFrames according to the chosen action.


<source line lang="mtmacro">
<syntaxhighlight line lang="mtmacro">
[h: chosenAction = macro.args]
[h: chosenAction = macro.args]
[h, if(hasImpersonated()): activeId = getImpersonated(); activeId = getSelected()]
[h, if(hasImpersonated()): activeId = getImpersonated(); activeId = getSelected()]
Line 498: Line 543:
[h: deselectTokens()]
[h: deselectTokens()]


<!-- call right actionFrame for chosenAction -->
[h:'<!-- call right actionFrame for chosenAction -->']
[r, macro("actionFrame@Lib:tkn"): activeId]
[r, macro("actionFrame@Lib:tkn"): activeId]
</source>
</syntaxhighlight>




Line 507: Line 552:
'''Attack'''
'''Attack'''


<source line lang="mtmacro">
<syntaxhighlight line lang="mtmacro">
[r, macro("openActionFrame@Lib:token"):"Attack"]  
[r, macro("openActionFrame@Lib:token"):"Attack"]  
</source>
</syntaxhighlight>


''Note'' that I send "Attack" to the frame opening macro and that this is placed in a variable named "chosenAction". It is never used. If you want to support different actions (like ranged and melee attacks) you could, right after the comment, branch - depending on "chosenAction" - into different actionFrame.
''Note'' that I send {{code|"Attack"}} to the frame opening macro and that this is placed in a variable named {{code|chosenAction}}. It is never used. If you want to support different actions (like ranged and melee attacks) you could, right after the comment, branch - depending on {{code|chosenAction}} - into a different actionFrame.


'''actionFrame'''
'''actionFrame'''
This is pretty simple. It shows a frame and uses the{{code|onChangeSelection}}-event to display the targets.
This is pretty simple. It shows a frame and uses the {{code|onChangeSelection}}-event to display the targets.


<source line lang="mtmacro">
<syntaxhighlight line lang="mtmacro">
[h: activeId = macro.args]
[h: activeId = macro.args]
[h: selection = getSelected()]
[h: selection = getSelected()]
Line 540: Line 585:
<input type="hidden" name="targets" value="[r:selection]">
<input type="hidden" name="targets" value="[r:selection]">
}]
}]
</source>
</syntaxhighlight>


'''performAction'''
'''performAction'''
<source line lang="mtmacro">
<syntaxhighlight line lang="mtmacro">
[h: arguments = macro.args]
[h: arguments = macro.args]
[h: id = json.get(arguments, "id")]
[h: id = json.get(arguments, "id")]
[h: targets = json.get(arguments, "targets")]
[h: targets = json.get(arguments, "targets")]
[h, if(listCount(targets)<1): abort(0)]
[h, if(listCount(targets)<1): abort(0)]
<!-- target and performer could be the same -->
[h:'<!-- target and performer could be the same -->']
<!-- target could be one or many -->
[h:'<!-- target could be one or many -->']


<!-- roll the attack -->
[h:'<!-- roll the attack -->']
<b>Melee attack:</b><br>
<b>Melee attack:</b><br>
[r, foreach(target, targets, "<br>"), code: {
[r, foreach(target, targets, "<br>"), code: {
Line 563: Line 608:
     };{and misses [r:getName(target)]}]
     };{and misses [r:getName(target)]}]
}]
}]
</source>
</syntaxhighlight>
This macro does the actual attack. The attacker and the targets are submitted via ''macro.args''. The rest is the usual dice rolling and comparing to target numbers and stuff...
This macro does the actual attack. The attacker and the targets are submitted via {{code|macro.args}}. The rest is the usual dice rolling and comparing to target numbers and stuff...


Again this could be done better. It doesnt modify the roll by the entered mods. It does not even model the sample ruleset right. You'd want to support attack powers and maybe set states for being wounded or dead.
Again this could be done better. It doesn't modify the roll by the entered mods. It does not even model the sample ruleset right. You'd want to support attack powers and maybe set states for being wounded or dead.


But it demonstrates how to target .. the rest is up to you.
But it demonstrates how to target .. the rest is up to you.


'''Download''' a lib token for this example [http://www.bastian-dornauf.de/example2.rptok example2.rptok] (token is saved with b73)
'''Download''' a lib token for this example [http://www.bastian-dornauf.de/example2.rptok example2.rptok] (token is saved with b73)

Latest revision as of 23:59, 14 March 2023


ADVANCED
THIS IS AN ADVANCED ARTICLE

HTML forms covered in fish

A tutorial to creating html forms for maptool.

What is this about?

The input() function is a great way to get data from the user. It is simple to use as well. But it limits you in the ways you can design the resulting dialog. You might even miss features like multi-line textboxes. Maybe you want keep a dialog open to send it when you are ready, but still want the other maptool features to work - and not freeze.

If you can't create the user interaction with input() you have to create an html form. And here I explain to you how to do that.

But be aware! An input pauses your macro, creates a pop up dialog and creates variables containing the entered data all by itself. With html forms you have to split the process into (at least) two macros and make all that by yourself.

I assume you know how to write simple macros and create/use lib:tokens. All my code examples will be located on a lib:token named "Lib:token".

NOTE I'm not the first one who tried to explain this. There is a nice tutorial on using html frames for creating a character sheet that covers even css embedding and tab page creation: Introduction to Dialogs and Frames.

Where can you use forms?

Maptool accepts html in uncountable places and theoretically wherever html is interpreted you could create a form. But it is really useful to place your html form either in a dialog or a frame. A frame is a dockable window while a dialog is floating above the rest of the UI. A dialog has a close button as default while a frame has no buttons other than those you create there.

Both commands (or to be more specific: roll options) open some kind of window. Both frames and dialogs are named so when you use code that would open a window, it will update the content of an open window with the same name if that exists.

You can close dialogs in macro with closeDialog() and in later versions you can close frames as well (closeFrame()).

Lets create a macro "openFrame" so that we can display a form.

[frame("myForm"): {
    here will be a fishy form
}]

For my following examples we will use this slightly changed openFrame-macro:

[frame("myForm"): {
    <h3>my form:</h3>
    [r, macro("displayForm@Lib:token"): ""]
}]

And probably you can guess: all the form related code will be placed in a displayForm-macro. So we can forget about opening the frame and concentrate all on forms. Yay.

About forms

Now let's begin with that form. HTML supports user editable forms and a good variety of input fields that can be placed in such a form. An HTML page (your frame for example) can even contain multiple forms (but you'll only receive the content of one of them).

For general syntax information about the <form>-tag and the input fields I find [w3schools.com] quite helpful.

We begin at the start and create a form with two text input fields.

<!-- this example displays correctly but does nothing -->
<form>
Character name: <input type="text" name="charName"><br>
Strength: <input type="text" name="str">
</form>

Note that you can place all possible HTML in such a form so you can easily design it any way you want. Create tables, use CSS, fonts, colors, ... MapTool only supports HTML3.2 and CSS1. This is because the java controls being used in MapTool don't support more recent versions of HTML/CSS. Don't blame MapTool ;)

While this is pretty handy you don't get the data your user enters yet. First we don't have a submit button and second MapTool doesn't know where to send that data.

If we do it right a form - if submitted - calls another macro, let's call that 'processForm', and passes the entered data as macro.args. You can receive this data as a string property list or as json which I prefer. If you prefer string property lists you have to omit the method field of the form tag (and change the processForm macro).

We specify the called macro using macroLinkText(). You should not specify the macro.args here as it will interfere with the form data.

Now let's make my little form work:

[h: processorLink = macroLinkText("processForm@Lib:token", "all")]
<form action="[r:processorLink]" method="json">
Character name: <input type="text" name="charName"><br>
Strength: <input type="text" name="str"><br>
<input type="submit" name="myForm_btn" value="Okay">
</form>

And create the processForm-macro.

<pre>
[r: json.indent(macro.args,2)]
</pre>

With this setup we can very easily find out how a specific form packs the data entered and how we could work with that. For this tutorial this processForm-macro will do.

The output we receive from this example is

{
  "charName": "the fishy dude",
  "str": "7",
  "myForm_btn": "Okay"
}

Now it's pretty easy to access the name and strength using json.get().

The input fields

Now let me introduce you to the input fields in detail. Some are a little tricky in how they send their data - so there will be advice about that as well.

In general all input fields should be given a name. This name will be used in the resulting json data as a key.

Text fields

<input type="text" name="" size="" maxlength="" value="">

This is your standard one line text input field. The width of the field can be set with size and the maximum length of the input with maxlength. If you set a value your field will appear filled with that.

You can have a password type text field as well if you set

<input type="password" name="" size="" maxlength="" value="">

Multi line text fields

If you need multiple lines you use

<textarea name="" cols="" rows="">
Enter your text here...
</textarea>

You can specify the size of that text box with cols and rows. A preset text would be written between the open and closing tags.

TRICK: You can process the content of a textarea line by line if you use the following trick. By using encode() on the complete content you change line breaks into %0A. Then you can use string list functions using %0A as separator.

As example let me show you a processForm macro that adds all numbers you enter in the textarea - one number per line. Dice expressions are evaluated.

[h:'<!-- processForm -->']
[h: formData = macro.args]
[h:'<!-- get the content of a textarea named "textarea" -->']
[h: text = json.get(formData, "textarea")]
[h:'<!-- encode it -->']
[h: text = encode(text)]
[h:'<!-- loop through the content -->']
[h: sum=0]
[h, foreach(line, text, "", "%0A"), code: {
    [h:'<!-- decode line again -->']
    [h: decodedLine = decode(line)]
    [h, if(isNumber(decodedLine):
        sum = sum + decodedLine;
        sum = sum + eval(decodedLine)
    ]
}]
[h:'<!-- and output. done. -->']
[r: sum]

Drop down lists

<select name="" size="">
    <option>A</option>
    <option>B</option>
    <option selected="selected">C</option>
</select>

NOTE multiple doesn't work, only one entry appears in the resulting json. Known bug.

Radio buttons

A<input type="radio" name="group1" value="A" checked="checked">
B<input type="radio" name="group1" value="B">
C<input type="radio" name="group1" value="C">

A<input type="radio" name="group2" value="A" checked="checked">
B<input type="radio" name="group2" value="B">
C<input type="radio" name="group2" value="C">

Checkboxes

<input type="checkbox" name="group1" value="A"> A
<input type="checkbox" name="group1" value="B"> B
<input type="checkbox" name="group1" value="C"> C
<input type="checkbox" name="group1" value="D" checked="checked"> D

NOTE unchecked boxes don't appear in the json; only checked ones will. So test if a box is checked by using json.contains on the field name.

See my "Good advice" tip #4 for another way to treat this (you can predefine the value with a 0-value).

NOTE multiple selection doesn't work as well. So do not name the checkboxes alike.

Hidden data

<input type="hidden" name="" value="">

Since you cannot send additional information to your form processor using the args parameter of macroLinkText you have to send it piggyback with the form data. This can be done with invisible fields.

Unfortunately, assigning a JSON Array or a JSON Object as a value can cause the JSON to get mangled. To have it parse properly, a work around is to replace the quote marks " around the value by '; another work around is to encode the json first, and decode it in the macroLinkText macro.

Buttons

<input type="submit" name="" value="">

The button caption is set via the value parameter.

NOTE only the pressed button appears in the json. If you use multiple buttons use json.contains() to identify it. Predefining the key/name could probably help (see checkboxes).

NOTE You can use HTML formatting inside of the button caption (value parameter). You have to enable this by beginning with <html> like this:

<input type="submit" value="<html><b>Button</b></html>">

You can't apply any kind of CSS to HTML inputs. To remove the HTML tags from the submitted value you can use code like this

[H: submit = json.get(macro.args,"submit")]
[H: submit = replace(submit,"<[^>]*?>","")]

Image buttons

First you have to get the asset of the image you want to place on a button. Good ways to do so are by using an image table or image tokens. I won't explain that here in more detail.

<input type="image" src="" name="" value="">

This image button submits the form exactly as a submit button does. As src you have to set an image asset. Note that you cannot used resized assets (using the ASSETxSIZE notation or specifying the size on asset generating function calls).

It not only sends the button name and value but also the coordinates where you clicked in the image. This could be used for some pretty UI.

<!-- this image button pushed .. -->

<input type="image" src="[r:getImage("Image:Attack")]" name="img_btn" value="image button clicked">

<!-- .. would send this args -->
{
    "img_btn.value": "image button clicked",
    "img_btn.x": "21",
    "img_btn.y": "13"
}

Events

Since I'll use this in one of my examples (see below) let me very shortly introduce you to some kinds of events that MapTool supports and how to set it up. A discussion about this can be found in the MapTool forums and a list of events on the Category:Event page.

Maptool macros can react on three events: if a token is changed, if token selection is changed and if impersonation is changed. You can specifiy a macro that is called if one event happens.

This will work if a frame is open at that moment. The onChangeToken-event is a little bit tricky. First it is fired numerous times and not only if you'd expect it. Second is your macro can change tokens and so fire the event and call itself… what could cause problems.

The other two events are pretty easy to use and quite handy for dumping information about selected tokens and such.

To set it up you have to define an HTML header and specify a specific link element. So your frame content should begin like this:

<html>
<head>
  <link rel='onChangeSelection' type='macro' href='macroLink'>
</head>
<body>

Replace macroLink by an actual macroLinkText-call to a macro of your choice. A common practice is to call the frame opening macro itself to actualize the content. the rel parameter is set either onChangeSelection, onChangeImpersonated or onChangeToken.

Good advice

Always encode user input

It is always wise to trust in the dumbness of users. If they can break things they will. And I don't say they do it intentionally.

So you should protect your macros against trouble-making user inputs. For example a comma inside of an item of a comma separated list breaks the list.

So it's always good to use encode() on user input (and decode() to .. well .. decode it again).

Building complex HTML forms can take time

Just be aware of this. Building and rendering HTML and collecting and displaying lots of data and images and fields can take serious time. If your frame is updated frequently it could cause speed issues.

Think about storing calculated frame content, only updating the necessary parts. Reduce the number of updates to the needed minimum. (See Caching)

Don't make things complicated if you do not want to have speed issues but be prepared to fight them if you do.

Caching HTML forms

Since building HTML forms can take serious amounts of time it is a good practice to store built form HTML in a token property and reuse it as long it doesn't have to be rebuilt. It's especially effective if you build complex stuff by accessing lots of properties - usually the case if you build character sheets. When creating complex HTML structures and storing them into a token property you're asking for trouble so it's common practice to encode them first before you store them. It's also best to store character sheets (token specific) onto the (n)pc token and general forms like weapon list, skill list, etc. onto a lib:token.

Here an example of 'caching' a charactersheet.

[h: rebuild = macro.args]
[h: id = currentToken()]
[h: output = getProperty("charSheetCache", id)]

[h, if(rebuild || output == ""), code: {
    [h: output = "here you build"]
    [h: output = output + "your mega complex character sheet"]
    ...
    [h: output = encode(output)]

    [h:'<!-- though it might be better to define a UDF for that -->']
    [h:'<!-- e.g: output = encode(createSheetContent()) -->']

    [h: setProperty("charSheetCache", output, id)]
};{}]

[frame("Character Sheet"):{
    [r: decode(output)]
}]


A nice technique to individualize cached forms/html is described here: Making cached structures dynamic (Load BIG forms FAST)

Don't forget the token context

When you work with macrolinks you can easily lose the token context. If you happen to work with explicit ids and get/setProperty() a lot that may be no problem for you.

However it does change the chat output. If a macrolink is called with unknown token context instead of token image and name the chat line begins with user name.

If you don't like this always specify the token context in your macroLink() and macroLinkText() calls.

An example of this can be found in the forum.

Predefine checkboxes

Checkboxes only create data in macro.args if they are checked. There is a neat trick to always create the relevant data even if it is unchecked. Predefine the key/value-pair using a hidden input with a 0 (of course you have to use the same name as your checkbox has). A checked checkbox will overwrite a predefined 0 while an unchecked checkbox (as it does not generate anything) won't overwrite a predefined 1.

If you want to have an initially checked checkbox you can set it as checked like this (regardless of being predefined or not)

<input type="checkbox" name="surprised"  value="1" checked="checked" />

Big thanks to wolph42 for teaching me this.

Don't shy away from layout tables

In webdesign layout tables might be a no-go. Don't be afraid of them in MapTool. They are a great way to precisely align your form elements. Let me demonstrate how different a simple layout table looks compared to a very simplistic inline approach.

[frame("test"): {

<h3>this is ugly</h3>
Value <input type="text" size="5" /><br>
Option1<input type="checkbox" /><br>
Option2<input type="checkbox" /><br>
<input type="submit"><br>

<h3>this is pretty</h3>
<table>
<tr>
   <td>Value</td>
   <td><input type="text" size="5" /></td>
</tr>
<tr>
   <td>Option1</td>
   <td><input type="checkbox" /></td>
</tr>
<tr>
   <td>Option2</td>
   </td><input type="checkbox" />
</td>
<tr>
   <td colspan="2"><input type="submit" /></td>
</tr>
</table>

}]

The big examples

Character sheet/editor

Lets create an character sheet and editor for the maptool sample ruleset using what we learned so far.


First we need a frame. We want it to auto-update with the selected content. We pass the selected tokens to the character sheet generating macro so we know what to display.

We want more eye candy, so we will use CSS. As we like separating CSS rules from the content we will place it in its own macro.

openCharacterSheet

[h: link = macroLinkText("openCharacterSheet@Lib:token", "none")]
[frame("csheet"): {
<html>
<head>
<link rel="onChangeSelection" type="macro" href="[r:link]">
<link rel="stylesheet" type="text/css" href="css@Lib:token"></link>
</head>
<body>
[r, macro("characterSheet@Lib:token"): getSelected()]
</body>
</html>
}]

css

.odd { background-color: #FFFFFF }
.even { background-color: #EEEEAA }
th { background-color: #113311; color: #FFFFFF }

Then we have to actually build the character sheet. Since selection will cause this to be called we have to deal with empty and multiple selections. We just don't create any output then.

characterSheet

[h: id = macro.args]
[r, if(listCount(id)!=1), code: {};{

[h: link = macroLinkText("editCharacterSheet@Lib:token", "all")]
<form action="[r:link]" method="json">
 <input type="hidden" name="id" value="[r:id]">
<h1>[r:getName(id)]</h1>

<table width="*">
<tr>
  <th colspan="2">Primary Attributes</th>
</tr>

[h: attributes = "Strength, Dexterity, Intelligence, Endurance"]
[h: row = "odd"]
[r, foreach(attrib, attributes, ""), code: {
    <tr class="[r:row]">
    <td><b>[r:attrib]:</b></td>
    <td><input type="text" name="[r:attrib]" value="[r:getProperty(attrib, id)]" size="3" align="right"></td>
    </tr>
    [h: row = if(row=="odd", "even", "odd")]
}]

<tr>
  <th colspan="2">Secondary Attributes</th>
</tr>
[h: attributes = "Hit Points, Armor, Movement"]
[h: row = "odd"]
[r, foreach(attrib, attributes, ""), code: {
    <tr class="[r:row]">
    <td><b>[r:attrib]:</b></td>
    <td>[r:getProperty(attrib, id)]</td>
    </tr>
    [h: row = if(row=="odd", "even", "odd")]
}]
[h: classes = "Warrior, Rogue, Wizard, Priest"]
[h: CharClass = getProperty("CharClass", id)]
<tr class="[r:row]">
    <td><b>Class:</b></td>
    <td>
        <select name="CharClass" size="1">
        [r, foreach(c, classes, ""), code: {
            <option [r, if(c==CharClass): "selected"]>[r:c]</option>
        }]
    </select>
    </td>
</tr>

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

If the submit button is pressed we want to save the changes back to the token.

editCharacterSheet

[h: arguments = macro.args] 
[h: id = json.get(arguments, "id")]

[h:'<!-- set primary attributes -->']
[h: attributes = "Strength, Dexterity, Intelligence, Endurance"]
[h, foreach(attrib, attributes), code: {
    [h: val = json.get(macro.args, attrib)]
    [h, if(! isNumber(val)): val=eval(val)]
    [h:'<!-- allowed values are 1..6 -->']
    [h: val = min(max(val,1), 6)]
    [r: setProperty(attrib, val, id)]
}]

[h: setProperty("Hit Points", 6*getProperty("Endurance",id), id)]
[h: setProperty("Class", json.get(macro.args, "CharClass"), id)]
[h: setProperty("Movement", getProperty("Dexterity",id), id)]

[h: CharClass = getProperty("CharClass", id)]

[h, switch(CharClass):
    case "Warrior": val=6;
    case "Rogue": val=2;
    case "Wizard": val=1;
    case "Priest": val=4;
   default: val=0
]
[h: setProperty("Armor", val, id)]

[h: setProperty("Hit Points", 6*getProperty("Endurance",id), id)]

Changes saved to [r: getName(id)].
[h, macro("openCharacterSheet@Lib:token"): id]

If you'd want to play with this you'd surely come up with lots of improvements .. great! I would as well. But this should be enough to demonstrate building a character sheet or editor with html forms.

Download this example:example1.rptok(token is saved with b73) Drop this libtoken into an empty map and toy around with it.

Click-based Target selection

There are very different ways to select targets of an action. The clickbased targeting (first done by Rumble) works best in MapTool version b70 or later with the "unowned selection" feature.


You impersonate or select the active token. Then you execute a macro, e.g. "Attack" that opens a frame. In that frame you can enter additional information like modifiers. While that frame is open you select (with the mouse on the map) the target(s) of the attack. The frame has a button that actually performs the attack.

Let's start with the macro that opens that frame.

openActionFrame

This macro first determines what token should be considered the active token. A token impersonated would be preferred. Otherwise the selection is taken. If no or multiple tokens are selected the macro is aborted.

Then the macro checks if the user has the right to perform actions with that token - he does if he is the GM or owns the token.

After that the current selection is cleared and the frame displaying code is called.

If you have different actions to perform here would the place to branch into different actionFrames according to the chosen action.

[h: chosenAction = macro.args]
[h, if(hasImpersonated()): activeId = getImpersonated(); activeId = getSelected()]
[h, if(listCount(activeId)!=1): assert(0, "You have to select only one token")]
[h: gm = isGM()]
[h: owned = isOwner(getPlayerName(), activeId)]
[h, if(gm ||  owned): ""; assert(0, "You have no right to act with this token.")]
[h: deselectTokens()]

[h:'<!-- call right actionFrame for chosenAction -->']
[r, macro("actionFrame@Lib:tkn"): activeId]


Now we need a way for the user to call this macro. This can be either a campaign or a token macro - depending on your taste.

Attack

[r, macro("openActionFrame@Lib:token"):"Attack"]

Note that I send "Attack" to the frame opening macro and that this is placed in a variable named chosenAction. It is never used. If you want to support different actions (like ranged and melee attacks) you could, right after the comment, branch - depending on chosenAction - into a different actionFrame.

actionFrame This is pretty simple. It shows a frame and uses the onChangeSelection-event to display the targets.

[h: activeId = macro.args]
[h: selection = getSelected()]
[h: link = macroLinkText("actionFrame@Lib:tkn", "none", activeId)]
[h: perform= macroLinkText("performAction@Lib:tkn", "all")]
[frame("Action"): {
<html>
<head>
<link rel='onChangeSelection' type='macro' href='[r:link]'>
</head>
<body>
<b>Attacker:</b><br>
[r, token(activeId): strformat("<img src='%s' alt='%s'>", getTokenImage(50), getName())]
<br>
<b>Targets:</b> <br>
[r, foreach(id, selection, " "), code: {
[r, token(id): strformat("<img src='%s' alt='%s'>", getTokenImage(50), getName())]
}]
<form action="[r:perform]" method="json">
<input type="text" name="mods" value="0"><br>
<input type="submit" name="btn_submit" value="Perform action">
<input type="hidden" name="id" value="[r:activeId]">
<input type="hidden" name="targets" value="[r:selection]">
}]

performAction

[h: arguments = macro.args]
[h: id = json.get(arguments, "id")]
[h: targets = json.get(arguments, "targets")]
[h, if(listCount(targets)<1): abort(0)]
[h:'<!-- target and performer could be the same -->']
[h:'<!-- target could be one or many -->']

[h:'<!-- roll the attack -->']
<b>Melee attack:</b><br>
[r, foreach(target, targets, "<br>"), code: {
    <b>[r: getName(id)]</b> rolls
    [r, token(id): rollResult = 1d20 + Strength]
    [r, if(rollResult>=15), code: {
        and hits <b>[r:getName(target)]</b> for
        [r: dmg = 1d6 + getProperty("Strength", id) - getProperty("Armor", target)]
        points of damage.
        [h: setProperty("Hit Points", getProperty("Hit Points", target) - max(0,dmg), target)]
    };{and misses [r:getName(target)]}]
}]

This macro does the actual attack. The attacker and the targets are submitted via macro.args. The rest is the usual dice rolling and comparing to target numbers and stuff...

Again this could be done better. It doesn't modify the roll by the entered mods. It does not even model the sample ruleset right. You'd want to support attack powers and maybe set states for being wounded or dead.

But it demonstrates how to target .. the rest is up to you.

Download a lib token for this example example2.rptok (token is saved with b73)