[highlight]SCOPE[/highlight]
The purpose of htmlform.ash is to allow you to write relay scripts without necessarily having a deep knowledge of HTML forms (although you will need to know at least a bit about HTML formatting if you want your scripts to look nice). It is designed to make the process of gathering input from the user look like a simple sequence of prompts, when in fact the script is being reloaded multiple times in order to present and validate the user's choices.
[highlight]GLOBALS[/highlight]
There are two global variables defined by the library that you'll need to know about:
string[string] fields is a map holding the result of a call to form_fields(). The current value of any input element can be retrieved as
fields[name], although you probably won't have to do that very often as the functions that actually write the elements to the page will return the current value of that element. You can also modify values in the map, prior to writing the corresponding element, to change their value - for example, to change the default values of certain fields based on the user's input in other fields.
boolean success is true when a valid page submission has occurred - the fields map will be non-empty (unlike the initial run of the script in which the choices are initially shown in the browser), and none of the fields have a validation failure. Button actions should generally be conditional on this variable being true (and will automatically be so, if you use the test_button() function).
All other internal global variables, and functions for internal use, have names starting with an underscore, to minimize the chance of a name collision with your script.
[highlight]NAMES[/highlight]
Every HTML form element requires a distinguishing name, which for maximum compatibility should be chosen with the same basic rules as ASH variables - start with a letter, use only numbers and letters. Take care if you're generating names automatically; for example, if you were generating a field for each item in a list, it would be a bad idea to use the actual item names as (part of) the fields' names, since items may have arbitrarily weird characters in their names. The items' ID numbers (retrievable with to_int()) would be a much safer basis for making field names.
For the most part, element names must be distinct (and strange things will happen if you violate that). There are two exceptions:
* Radio buttons normally come in mutually-exclusive groups, all of which must share the same name - in fact, that's the only thing that relates them as a group (there's no requirement for them to be physically grouped, although of course that would usually be good style).
* Submit buttons can share a name if they do the same thing - for example, you might want to have a "Save changes" button at both the top and bottom of a tall form. If a button has no action at all (it just updates the form), its name can be left blank.
[highlight]ATTRIBUTES[/highlight]
There is a large and ever-growing set of optional attributes that can be specified to customize the appearance and behavior of HTML form elements. It would be utterly impractical for htmlform to have optional parameters for every possible attribute, or even for every particularly useful attribute. Instead, you can supply arbitrary attributes by preceding the call that writes an element with one or more calls to:
void attr( string attribute )
The string will be inserted at the appropriate place in the HTML tag being written. If called multiple times, the strings will be separated by spaces.
USEFUL ATTRIBUTES
attr("title='TEXT'") will add a tooltip to any element.
Advanced users may want to use style & class attributes for fine control over element appearance.
A complete list of attributes is entirely beyond the scope of this documentation (see any HTML reference, instead), but the most useful ones will be pointed out in blocks like this.
[highlight]VALIDATORS[/highlight]
The functions that produce a field for the user to type in (write_field() and write_textarea()) can be passed the name of a validator function that can check (and possibly modify) the field value. Any validation failure is shown in red to the right of the field, and blocks submission of the form until the user corrects the problem. Validator functions are provide for all the datatypes for which typed-in values make sense:
intvalidator, floatvalidator, itemvalidator, locationvalidator, skillvalidator, effectvalidator, familiarvalidator, monstervalidator
(They AREN'T provided for types like 'class' and 'slot' that have only a few possible values; other input elements are more appropriate in those cases).
None of these accept the 'none' value; there is a separate
itemnonevalidator that will accept items or 'none' (similar validators for other types didn't seem likely to be as useful). There is also a
nonemptyvalidator that simply requires the entered string to not be blank.
intvalidator and floatvalidator additionally support bounds checking via the function:
void range( float min, float max ) - place this just before the call to write_field().
The provided validators only check that the entered text is something understandable to KoLmafia - not that you (for example) actually have the named familiar, or that the item goes in some specific slot. If you want to perform such checks, you'll need to write your own validator, which is just a function with one string parameter (the element name), that returns a string. Any non-empty string is treated as a validation error. Returning an empty string indicates success, in which case the function is allowed to modify the value in the fields[] map, which will update the value shown in the browser. (That's why the function is passed the field name, rather than the field value. All of the built-in validators that allow fuzzy matching use this feature, in order to replace an abbreviated user entry with the full, proper name of the object.) Here's a simple example that verifies that the entered text is a valid item (duplicating the function of itemvalidator), which furthermore is a hat that you own:
PHP:
string hatvalidator(string name)
{
item it = to_item(fields[name]);
if (it == $item[none]) {
return "A valid hat is required.";
}
fields[name] = to_string(it); // normalize
if (item_type(it) != "hat") {
return "That's not a hat.";
}
if (available_amount(it) == 0) {
return "You don't have that hat.";
}
return ""; // success!
}
You'd then pass "hatvalidator" as the 4th parameter to write_field().
[highlight]TOP LEVEL FUNCTIONS[/highlight]
void write_header()
void finish_header()
void write_page() - equivalent to write_header() followed by finish_header()
void finish_page()
All scripts using htmlform must use these functions to generate the overall page structure. You can use write_page() at the top of your main() function and finish_page() at the bottom; you can also split the write_page() into separate write_header() and finish_header() calls if you need to write something into the page header yourself (such as a script or stylesheet). All of the other htmlform function calls MUST be located between write_page/finish_header and finish_page (or in functions that you call from within that range, of course).
void write_box( string label )
void finish_box()
These can be used (in matched pairs only, please!) anywhere within the body of the page, to produce a box around a group of related elements. The label will typically appear at the top left of the box; it can contain HTML markup, or can be blank. (Internal details: this produces a <fieldset> tag, possibly containing a <legend>.)
USEFUL ATTRIBUTES
attr("align=center") or
attr("align=right") will change the position of the label.
The following two functions are actualy part of ASH, but I'll mention them here because you'll be using them a lot:
void write(string)
void writeln(string)
These are only valid in a relay script, and produce raw output to the relay browser. The string will generally be interpreted as HTML; if it may contain characters that have special meaning in HTML (such as ampersands and angle brackets), you can use
entity_encode() on the string to prevent any such interpretation.
The only difference between the two functions is that writeln() appends a line break to the string; line breaks are normally not significant in HTML, so they will generally have the same effect. In particular, writeln() will not normally produce a visible line break in the browser; you'll need to use a HTML tag like <br> for that. The main reason for using writeln() is to make the HTML more human-readable, which may be important if you aren't getting the results you expect, and need to 'View Source' in the browser.
[highlight]GENERAL USAGE[/highlight]
The functions that actually produce form elements in the browser will usually have a first parameter that specifies an initial value (of some specific datatype), and will return an updated value (of the same type). On the initial run of the script (when the user hasn't had a chance to enter anything yet), and whenever the field value fails validation, the initial value will be returned, otherwise the user's entry. The general idiom for writing elements depends on what you'll be doing with these values:
Stored in a local variable (no persistence):
float favoriteNumber = write_field( 42.0, ...
Note that the use of a floating-point number for the initial value is significant; using the integer 42 instead would result in a field that only accepted integers.
Saved in a KoLmafia preference:
set_property( "favNum", write_field( to_float(get_property("favNum")), ...
Note that type conversion will generally be required, as properties are always treated as strings. You could also use a string field, and explicitly choose the appropriate validator:
set_property( "favNum", write_field( get_property("favNum"), ..., "floatvalidator"));
Saved in a Zlib script setting:
vars["favNum"] = write_field( to_float(vars["favNum"]), ...
You'll need to explicitly write the vars back to disk on success; sorry, I don't know the usual idiom for that.
[highlight]BASIC ELEMENTS[/highlight]
Single-line text entry fields (HTML <input type=text>):
<type> write_field( <type> initialValue, string name, string label )
<type> write_field( <type> initialValue, string name, string label, string validator )
Supported <type>s are string, int, float, item, location, skill, effect, familiar, monster. The 3-parameter version uses the default validator for <type> (except for strings, which default to no validation).
'name' is the element name as described previously, and must be unique.
'label' is text (possibly containing HTML markup) to be placed to the left of the field. It can be left blank if you want no label, or will be providing a label separately via write_label().
USEFUL ATTRIBUTES
attr("type=password") - obscures the text entered in the field.
attr("type=search") - may give the field OS-dependent features suitable for a search field, such as a distinct appearance and a button to clear the field. This is not widely supported yet, but is supposedly backwards compatible.
attr("size=N") - resizes the field so that about N characters will fit without scrolling.
attr("maxlength=N") - prevents entry of more than N characters in total.
attr("placeholder='TEXT'") - shows grayed-out text when the field is empty and unselected, as a hint to what you should type there. Only applicable to string fields, since no other type ever has an empty value. Ignored in older browsers.
Multiple-line text entry (HTML <textarea>):
string write_textarea( string initialValue, string name, string label, int cols, int rows)
string write_textarea( string initialValue, string name, string label, int cols, int rows, string validator )
Like the above, but supports multiple lines of text, with scrolling. The number of columns and rows are required; there is no validator by default. This is only provided in a string version, since no other datatype can meaningfully have a multi-line value.
USEFUL ATTRIBUTES
attr("readonly") - the text can still be scrolled and selected, but can't be edited. Useful for text that is intended to be copied & pasted elsewhere.
Either of the above (as well as some other functions described laer) can have a separate label (HTML <label for>) that isn't directly attached to the field; this is useful when using tables to lay out the page nicely, and the labels need to go in a different column than the fields.
void write_label( string name, string label )
The label can contain HTML markup.
The name must match the name used in a write_field(), write_textarea(), write_select(), or write_choice(), that itself does not specify a label. The label can appear before or after the field on the page.
This isn't supported for checkboxes, which generally wouldn't benefit from having a detached label.
It's also unsupported for radio buttons, which have the additional problem of needing more than just the name to identify a particular button (since all the buttons in a group must share the same name).
string write_hidden( string initialValue, string name )
This is effectively a field that isn't shown to, or editable by, the user - it's just passed on from page load to page load. This might be useful if your script relies on some value that requires lengthy calculation; you can avoid recalculating it each time. Only a string version is provided, use the ASH type conversion functions if you want to remember a value of a different type.
boolean write_check( boolean initialValue, string name, string label )
Produces a checkbox element; not much to say about this one.
<type> write_radio( <type> initialValue, string name, string label, <type> value )
void write_radio( string label, <type> value )
Produces a radio button; <type>s supported are all the basic ASH datatypes except boolean. The 2-parameter form reuses the initialValue and name parameters from the previous 4-parameter call; those two are required to be the same for every radio button in a group (although you could use the 4-parameter version every time, if that's more convenient).
Each individual radio button will appear marked when the current value (from initialValue or user selection) matches its 'value' parameter. The results aren't well-defined if two buttons in a group have the same value, or if none of the values match the initialValue - don't do that.
It is quite likely that you'll provide the same value for both 'label' and 'value'.
[highlight]SELECT & CHOICE ELEMENTS[/highlight]
These functions produce a popup menu with an arbitrary set of choices - basically a more compact version of a radio button group. The required structure is that every call to write_select() must be balanced by a call to finish_select(); in between, the only valid calls are write_option()s, and maybe some balanced (but non-overlapping) calls to write_group() and finish_group().
<type> write_select( <type> initialValue, string name, string label )
Supported <type>s are all basic ASH types except boolean.
USEFUL ATTRIBUTES
attr("onchange='document.relayform.submit()'") - selecting a value from this popup will automatically submit and validate the form, just as if a submit button were clicked.
attr("size=N") - presents the choices as a scrolling list that's N lines tall, instead of a popup.
attr("multiple") - allows selection of more than one choice at a time. htmlform doesn't fully support this yet, and the return value from write_select() is no longer useful. Ask if you want details on how to use this, but please keep in mind that multiple selection lists aren't the most intuitive UI out there - many users won't known how to select multiple items from them.
void finish_select()
Don't forget to finish what you started!
void write_option( string label, <type> value )
void write_option( <type> labelAndValue )
Adds a choice to the list. The 1-parameter version uses the string version of the value as the label, which is quite often what you want.
void write_group( string label )
void finish_group()
Identifies the contained group of write_option() calls as being related, with the given label describing the group. The exact appearance depends on the browser; some will use indentation to indicate the grouping, others will create a submenu for each group. Old browsers will ignore groups completely.
For your convenience, a function is provided that wraps calls to write_select() and finish_select(), with a loop of write_option() calls in between. The downside is that you have no control over the choice labels, they're always. the same as the values.
<type> write_choice( <type> initialValue, string name, string label, boolean[<type>] values )
A choice is added for each key in the values map that has a true value; note that this is exactly the datatype returned by plural typed constants, so you can easily specify a constant list of values, or use a form like $items[] to get a list of every known item in the game.
Possible attributes are the same as for write_select().
item write_choice( item initialValue, string name, string label, int[item] values )
This variant only works with one fairly common data structure, as returned by get_inventory() and several other ASH functions. The choice labels will include the integer value from the map, in parentheses after the item name.
[highlight]BUTTONS[/highlight]
In order to actually receive any input from the user, the form in the relay browser has to be submitted back to KoLmafia. It's possible to attach JavaScript to the form elements to automatically submit the page when their value changes, but a more common approach would be to provide explicit buttons for this purpose.
boolean write_button( string name, string label )
'name' can be left blank if you never need to know that this particular button was clicked, in which case the function always returns false. Otherwise, it returns the same value as test_button(name):
boolean test_button( string name )
Returns true if the named button was responsible for submitting the form, AND the global 'success' variable is true (indicating that no field validation errors were found). This is provided as a separate function as the point at which the test is done is significant. If the button's action depends on all the other fields (as is normally the case), the test has to be done at the very end of the page processing - after all the fields have been processed and validated - even if the button is physically located at the top of the form. On the other hand, a button that resets fields to some default value (by changing their values in fields[]) would need to be tested near the start, before the fields themselves are processed - even if the button itself is written at the end of the form.
Note that fields are only validated on a button click, normally. If your script performs any immediate action (rather than just saving values for later use), and there are any validated fields involved, it might be best to provide two buttons - one to check the form (it would have no action attached, and would need no name), and another to actually perform the task, once the user has seen that all their typed-in values were interpreted as desired.
Attached is a test script that uses just about every type-specific version of every htmlform function. The current value of each element is simply displayed to the element's right, so this isn't a very good example of how you'd actually use the functions, but is more of a showcase of what you can do with them.