Difference between pages "Htmlform" and "JavaScript Support"

From Kolmafia
(Difference between pages)
Jump to navigation Jump to search
imported>StDoodle
m
 
 
Line 1: Line 1:
{{TOCright}}{{DISPLAYTITLE:HTMLform (htmlform.ash)}}
+
As of revision 20509, KoLmafia supports scripting in JavaScript! You can run JS code from the CLI using <code>js <nowiki><code></nowiki></code>, and you can call scripts through any of the normal methods. Consult and "lifecycle" scripts (e.g. <code>betweenBattleScript</code>) are supported as well as of revision 20519. '''This support is still experimental - you have been warned.'''
{{Attention|
 
This page details the use of functions in a script library. The information here is only useful to those who have followed the included steps to make use of these functions; they are not built-in to KoLmafia.
 
}}
 
== About jasonharper's Form Library ==
 
[http://kolmafia.us/showthread.php?3842 HTMLform] is a code library for building relay browser scripts that accept user input.
 
  
== Scope ==
+
== Basics ==
 +
* All the methods in the ASH runtime library are available, with names of methods converted to camelCase. So, for example, <code>print_html</code> in ASH becomes <code>printHtml</code> in JS.
 +
* In scripts called from a file, you can access the runtime library by calling <code>require("kolmafia")</code>, so <code>const { printHtml } = require("kolmafia")</code>. If running from the command line via <code>js</code>, the runtime library functions are all available in the global scope (so you can do <code>js print("Hello world!");</code>).
 +
* ASH maps are converted to plain JS objects, and ASH arrays are converted to JS arrays.
 +
* You can look at the type reference for the JS version of the ASH runtime library with <code>jsref</code>, which works just like <code>ashref</code>.
 +
* Objects like monsters and items can be constructed via the <code>Monster</code> and <code>Item</code> global objects, along with the rest of ASH's enumerated types (stat, phylum, location, etc.). <code>Monster.get</code> takes a number or a string, just like <code>$monster</code> in ASH, or an array of numbers and strings to construct an array of monsters. <code>Monster.all</code> works like <code>$monsters[]</code> in ASH; it takes no arguments and returns an array of all monsters.
 +
* Mafia supports <code>require</code> for both ASH and JS scripts. For ASH scripts, it will execute top-level code but only export functions, not variables, in the top scope.
 +
* If you want Mafia to run your <code>main</code> function, you '''<u>must</u>''' export it by adding it to module.exports, just as you would for a Node module. For the uninitiated, this just means adding <code>module.exports.main = main</code> to the end of your script. You will want to do the same with any function or value you want to be available to other scripts via require.
  
The purpose of HTMLform 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.
+
== JavaScript Version and Features ==
 +
KoLmafia uses the [[wikipedia:Rhino (JavaScript engine)|Rhino]] engine to execute JavaScript code. Rhino supports an older version of JavaScript called "ES5", plus some features from newer versions. This means that many JavaScript features that work in web browsers might not work in KoLmafia.
  
== Globals ==
+
Here is an incomplete list of features supported by Rhino ([https://kolmafia.us/threads/javascript-bugs.25638/post-160384 source]):
  
There are two global variables defined by the library that you'll need to know about:
+
=== Supported ===
'''string &#x005b;string&#x005d;''' ''fields'' is a map holding the result of a call to {{f|form_fields}}. The current value of any input element can be retrieved as '''''fields&#x005b;name&#x005d;''''', 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.
+
* Syntax
The global '''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|test_button()]] function).
+
** <code>let</code> and (partially) <code>const</code>
 +
*** Does not support block-level scoping or temporal dead zones, meaning that you cannot use <code>const</code> for loop variables. <code>for (const a in obj) { ... }</code> is a syntax error.
 +
** Array/object destructuring (but spread/rest syntax (<code>...</code>) is ''not'' supported)
 +
** <code>for...of</code> loop
 +
** Arrow functions: <code>() => {}</code>
 +
** Octal and binary literals
 +
* Features
 +
** Symbol
 +
** Set, Map, WeakSet, WeakMap
 +
** ES2015 methods in Array, Math, Number, Object, String
 +
** <code>Array.prototype.includes()</code>
 +
** <code>String.prototype.padStart()/padEnd()/trimStart()/trimEnd()</code>
 +
** TypedArray: Can be constructed, but most TypedArray-specific methods are unavailable.
  
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.
+
=== Unsupported ===
 +
* Syntax
 +
** Spread/rest syntax (<code>...</code>)
 +
** Template string literals: Backtick string literals (<code>``</code>) are not a syntax error, but are treated as plain string literals.
 +
** Classes
 +
** Default function parameters
 +
** Computed property names
 +
** Async/Await
 +
** Trailing commas in function definitions (oddly, trailing commas are supported in function ''calls'')
 +
* Features
 +
** Promise
 +
** Proxy
 +
** Reflect
  
== Names ==
+
=== Other ===
 +
Most JavaScript globals available in browsers and/or server-side environments like Node.js are ''not'' available. This includes <code>alert()</code>, <code>console.log()</code>, and <code>setTimeout()</code>.
  
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 {{f|to_int}}) would be a much safer basis for making field names.
+
== Transpiling ==
  
For the most part, element names must be distinct (and strange things will happen if you violate that). There are two exceptions:
+
Folks with experience doing JavaScript web development are likely well-acquainted with tools such as Babel, Webpack, and TypeScript. These tools allow you to write modern JavaScript code. and can transpile the code down to an older version of JS supported by a particular engine. That approach works well with Rhino.
* 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.
 
  
== Attributes ==
+
* Babel: As of r20558, you will still need to apply several patches to Babel in order to get babel-preset-env to work. See [https://github.com/phulin/bean-casual/tree/ts] for an example of a working Babel/Webpack/Typescript configuration; you'll need the configuration files and also the patches, which can be applied with patch-package.
 
+
* TypeScript: If you use Babel without TypeScript, setting the [https://www.typescriptlang.org/tsconfig#target target] to <samp>"ES5"</samp> will usually work.
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.
 
 
 
{{att_box|<p>{{att|title&#61;'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.</p>}}
 
 
 
== Validators ==
 
 
 
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:
 
 
 
== Generating the Page Structure ==
 
{{HideLink|write_header}}{{Function|
 
name=write_header|
 
return_type=void|
 
}}
 
{{Function|
 
name=finish_header|
 
return_type=void|
 
}}
 
{{Function|
 
name=write_page|
 
return_type=void|
 
}}
 
write_page() is equivalent to write_header() followed by finish_header(), but the separate functions may be preferred so that you can add information into the page header such as a script or stylesheet.
 
 
 
{{HideLink|finish_page}}{{Function|
 
name=finish_page|
 
return_type=void|
 
}}
 
This must be used at the end of the form.
 
 
 
{{HideLink|write_box}}{{Function|
 
name=write_box|
 
parameter1={{Param|string|label}}|
 
return_type=void|
 
}}
 
{{Function|
 
name=finish_box|
 
return_type=void|
 
}}
 
These can be used in matched pairs around a group of elements to enclose them in a box. The label will be at the top of the box.<br />
 
'''attr("align=center")''' or '''attr("align=right")''' will change the position of the label.
 
 
 
== Making use of HTMLform ==
 
 
 
To include this library in your script, first you must download it from the location provided below and make sure it is in your /scripts directory (or a sub-directory thereof). Then, simply add the following towards the top any script in which you wish to have access to these functions:
 
{{CodeSample|
 
code=
 
<syntaxhighlight>
 
import "htmlform.ash";
 
</syntaxhighlight>}}
 
Then all these functions will be available in your script. Have fun! Note that most of these functions are intended for use in relay info scripts, which must be placed in the /relay directory (top-level only, no sub-directories) and must begin with "relay_" to show in the user's relay browser.
 
 
 
== More Information ==
 
<p>See the thread for HTMLform on the KoLmafia forum [http://kolmafia.us/showthread.php?3842 here].</p>
 
 
 
[[Category:Scripting]][[Category:ASH Function Libraries]]
 

Revision as of 11:13, 20 December 2020

As of revision 20509, KoLmafia supports scripting in JavaScript! You can run JS code from the CLI using js <code>, and you can call scripts through any of the normal methods. Consult and "lifecycle" scripts (e.g. betweenBattleScript) are supported as well as of revision 20519. This support is still experimental - you have been warned.

Basics

  • All the methods in the ASH runtime library are available, with names of methods converted to camelCase. So, for example, print_html in ASH becomes printHtml in JS.
  • In scripts called from a file, you can access the runtime library by calling require("kolmafia"), so const { printHtml } = require("kolmafia"). If running from the command line via js, the runtime library functions are all available in the global scope (so you can do js print("Hello world!");).
  • ASH maps are converted to plain JS objects, and ASH arrays are converted to JS arrays.
  • You can look at the type reference for the JS version of the ASH runtime library with jsref, which works just like ashref.
  • Objects like monsters and items can be constructed via the Monster and Item global objects, along with the rest of ASH's enumerated types (stat, phylum, location, etc.). Monster.get takes a number or a string, just like $monster in ASH, or an array of numbers and strings to construct an array of monsters. Monster.all works like $monsters[] in ASH; it takes no arguments and returns an array of all monsters.
  • Mafia supports require for both ASH and JS scripts. For ASH scripts, it will execute top-level code but only export functions, not variables, in the top scope.
  • If you want Mafia to run your main function, you must export it by adding it to module.exports, just as you would for a Node module. For the uninitiated, this just means adding module.exports.main = main to the end of your script. You will want to do the same with any function or value you want to be available to other scripts via require.

JavaScript Version and Features

KoLmafia uses the Rhino engine to execute JavaScript code. Rhino supports an older version of JavaScript called "ES5", plus some features from newer versions. This means that many JavaScript features that work in web browsers might not work in KoLmafia.

Here is an incomplete list of features supported by Rhino (source):

Supported

  • Syntax
    • let and (partially) const
      • Does not support block-level scoping or temporal dead zones, meaning that you cannot use const for loop variables. for (const a in obj) { ... } is a syntax error.
    • Array/object destructuring (but spread/rest syntax (...) is not supported)
    • for...of loop
    • Arrow functions: () => {}
    • Octal and binary literals
  • Features
    • Symbol
    • Set, Map, WeakSet, WeakMap
    • ES2015 methods in Array, Math, Number, Object, String
    • Array.prototype.includes()
    • String.prototype.padStart()/padEnd()/trimStart()/trimEnd()
    • TypedArray: Can be constructed, but most TypedArray-specific methods are unavailable.

Unsupported

  • Syntax
    • Spread/rest syntax (...)
    • Template string literals: Backtick string literals (``) are not a syntax error, but are treated as plain string literals.
    • Classes
    • Default function parameters
    • Computed property names
    • Async/Await
    • Trailing commas in function definitions (oddly, trailing commas are supported in function calls)
  • Features
    • Promise
    • Proxy
    • Reflect

Other

Most JavaScript globals available in browsers and/or server-side environments like Node.js are not available. This includes alert(), console.log(), and setTimeout().

Transpiling

Folks with experience doing JavaScript web development are likely well-acquainted with tools such as Babel, Webpack, and TypeScript. These tools allow you to write modern JavaScript code. and can transpile the code down to an older version of JS supported by a particular engine. That approach works well with Rhino.

  • Babel: As of r20558, you will still need to apply several patches to Babel in order to get babel-preset-env to work. See [1] for an example of a working Babel/Webpack/Typescript configuration; you'll need the configuration files and also the patches, which can be applied with patch-package.
  • TypeScript: If you use Babel without TypeScript, setting the target to "ES5" will usually work.