Menu HJSON
The core of a ENiGMA½ based BBS is it’s menus driven by what will be referred to as menu.hjson. Throughout ENiGMA½ documentation, when menu.hjson is referenced, we’re actually talking about config/menus/yourboardname-*.hjson. These files determine the menus (or screens) a user can see, the order they come in, how they interact with each other, ACS configuration, and so on. Like all configuration within ENiGMA½, menu configuration is done in HJSON format.
- Classical navigation and menus such as Main, Messages, and Files.
- Art file display.
- Module driven menus such as door launchers, Onelinerz, and other custom mods.
Menu entries live under the menus section of menu.hjson. The key for a menu is it’s name that can be referenced by other menus and areas of the system.
Below is a very basic menu entry called showSomeArt that displays some art then returns to the previous menu after the user hits a key:
showSomeArt: { art: someart.ans config: { pause: true }}As you can see a menu can be very simple.
Common Menu Entry Members
Section titled “Common Menu Entry Members”Below is a table of common menu entry members. These members apply to most entries, though entries that are backed by a specialized module (ie: module: bbs_list) may differ. Menus that use their own module contain a module declaration:
module: some_fancy_moduleSee documentation for the module in question for particulars.
| Item | Description |
|---|---|
desc | A friendly description that can be found in places such as “Who’s Online” or wherever the %MD MCI code is used. |
art | An art file spec. See General Art Information. |
next | Specifies the menu to go to next. Can be explicit or an array of possibilities dependent on ACS. See Flow Control in the ACS Checks section below. If next is not supplied, the next menu is this menus parent. Note that special built in methods such as @systemMethod:logoff can also be utilized here. |
prompt | Specifies a prompt, by name, to use along with this menu. Prompts are configured in the prompts section. See Prompts for more information. |
submit | Defines a submit handler when using prompt. |
form | An object defining one or more forms available on this menu. |
module | Sets the module name to use for this menu. The system ships with many build in modules or you can build your own! |
config | An object containing additional configuration. See Config Block below. |
Config Block
Section titled “Config Block”The config block for a menu entry can contain common members as well as a per-module (when module is used) settings.
| Item | Description |
|---|---|
cls | If true the screen will be cleared before showing this menu. |
pause | Controls post-display pause. true or 'end': pause once at the end; 'pageBreak': paginate art screen-by-screen with a prompt between pages; '<promptId>': end pause using the named prompt (shorthand for pause: true + pausePrompt: <promptId>). See Pause Prompts. |
pausePrompt | Override the prompt name(s) used for pauses. A string uses that prompt for both end-of-art and page-break pauses; an object { end, page } controls each independently. Takes precedence over the pause: '<promptId>' shorthand. See Pause Prompts. |
pausePosition | Force the pause prompt to a specific screen position: { row, col } (1-based). |
nextTimeout | Sets the number of milliseconds before the system will automatically advanced to the next menu. |
baudRate | Throttle art display to simulate a modem at the given speed. Works with all terminal clients. See Baud Rates. |
font | Sets a SyncTERM style font to use when displaying this menus art. See font listing in General Art Information. |
menuFlags | An array of menu flag(s) controlling menu behavior. See Menu Flags below. |
Menu Flags
Section titled “Menu Flags”The menuFlags field of a config block can change default behavior of a particular menu:
| Flag | Description |
|---|---|
noHistory | When leaving the current menu to load/chain to another, remove this menu from history. In other words, the fallback from the next menu would not be this one, but the previous. |
mergeFlags | Generally used in code only: Request that any flags from menu.hjson |
forwardArgs | Forward this menu’s extraArgs to the next. |
💡 In JavaScript code,
MenuFlagsfrommenu_module.jscontains constants for these flags.
ENiGMA½ uses a concept of forms in menus. A form is a collection of associated views. Consider a New User Application using the nua module: The default implementation utilizes a single form with multiple EditTextView views, a submit button, etc. Forms are identified by number starting with 0. A given menu may have mutiple forms (often associated with different states or screens within the menu).
Menus may also support more than one layout type by using a MCI key. A MCI key is a alpha-numerically sorted key made from 1:n MCI codes. This lets the system choose the appropriate set of form(s) based on theme or random art. An example of this may be a matrix menu: Perhaps one style of your matrix uses a vertical light bar (VM key) while another uses a horizontal (HM key). The system can discover the correct form to use by matching MCI codes found in the art to that of the available forms defined in menu.hjson.
For more information on views and associated MCI codes, see MCI Codes.
Submit Handlers
Section titled “Submit Handlers”When a form is submitted, it’s data is matched against a submit handler. When a match is found, it’s action is performed. Note: Setting the value explicitly to null matches against any value.
Submit Actions
Section titled “Submit Actions”Submit actions are declared using the action member of a submit handler block. Actions can be kick off system/global or local-to-module methods, launch other menus, etc.
| Action | Description |
|---|---|
@menu:menuName | Takes the user to the menuName menu |
@systemMethod:methodName | Executes the system/global method methodName. See System Methods below. |
@method:methodName | Executes methodName local to the calling module. That is, the module set by the module member of a menu entry. |
@method:/path/to/some_module.js:methodName | Executes methodName exported by the module at /path/to/some_module.js. |
Advanced Action Handling
Section titled “Advanced Action Handling”In addition to simple simple actions, action may also be:
- An array of objects containing ACS checks and a sub
actionif that ACS is matched. See Action Matches in the ACS documentation below for details. - An array of actions. In this case a random selection will be made. Example:
submit: [ { value: { command: "FOO" } action: [ // one of the following actions will be matched: "@menu:menuStyle1" "@menu:menuStyle2" ] }]Method Signature
Section titled “Method Signature”Methods executed using @method, or @systemMethod have the following signature:
(callingMenu, formData, extraArgs, callback)System Methods
Section titled “System Methods”Many built in global/system methods exist. Below are a few. See system_menu_method for more information.
| Method | Description |
|---|---|
login | Performs a standard login. |
login2FA_OTP | Performs a 2-Factor Authentication (2FA) One-Time Password (OTP) check, if configured for the user. |
logoff | Performs a standard system logoff. |
prevMenu | Goes to the previous menu. |
nextMenu | Goes to the next menu (as set by next) |
prevConf | Sets the users message conference to the previous available. |
nextConf | Sets the users message conference to the next available. |
prevArea | Sets the users message area to the previous available. |
nextArea | Sets the users message area to the next available. |
setClientEncoding | Sets the client encoding (such as cp437 and utf-8.) |
Example
Section titled “Example”Let’s look a couple basic menu entries:
telnetConnected: { art: CONNECT next: matrix config: { nextTimeout: 1500 }}The above entry telnetConnected is set as the Telnet server’s first menu entry (set by firstMenu in the Telnet server’s config). The entry sets up a few things:
- A
artspec ofCONNECT. (See General Art Information). - A
nextentry up the next menu, by name, in the stack (matrix) that we’ll go to aftertelnetConnected. - An
configblock containing a singlenextTimeoutfield telling the system to proceed to thenext(matrix) entry automatically after 1500ms.
Now let’s look at matrix, the next entry from telnetConnected:
matrix: { art: MATRIX desc: Login Matrix form: { 0: { // // Here we have a MCI key of "VM". In this case we could // omit this level since no other keys are present. // VM: { mci: { VM1: { submit: true focus: true items: [ "login", "apply", "log off" ] argName: matrixSubmit } } submit: { *: [ { value: { matrixSubmit: 0 } action: @menu:login } { value: { matrixSubmit: 1 }, action: @menu:newUserApplication } { value: { matrixSubmit: 2 }, action: @menu:logoff } ] } }
// // If we wanted, we could declare a "HM" MCI key block here. // This would allow a horizontal matrix style when the matrix art // loaded contained a %HM code. // } }}In the above entry, you’ll notice form. This defines a form(s) object. In this case, a single form by ID of 0. The system is then told to use a block only when the resulting art provides a VM (VerticalMenuView) MCI entry. Some other bits about the form:
VM1is then setup tosubmitand start focused viafocus: trueas well as have some menu entries (“login”, “apply”, …) defined. We provide anargNameofmatrixSubmitfor this element view.- The
submitobject tells the system to attempt to apply provided match entries from any view ID (*). - Upon submit, the first match will be executed. For example, if the user selects “login”, the first entry with a value of
{ matrixSubmit: 0 }will match (due to 0 being the first index in the list andmatrixSubmitbeing the arg name in question) causingactionof@menu:loginto be executed (go tologinmenu).
Prompts
Section titled “Prompts”Prompts are found in the prompts section of menu files. Prompts allow for quick user input and shorthand form requirements for menus. Additionally, prompts are often used for multiple menus. Consider a pause prompt or menu command input for example.
Structure
Section titled “Structure”The prompts: block is a top-level sibling of menus: in a menu file — it is not nested inside any individual menu entry:
{ menus: { // ... }
prompts: { myPrompt: { art: MYPRMPT // art file to display — required mci: { // MCI config — note: no "form:" wrapper here ET1: { argName: input maxLength: 20 focus: true submit: true } } // optional: actionKeys for key bindings (e.g. Escape to cancel) } }}A menu entry opts into a prompt by name via the prompt key:
myMenu: { desc: My Menu art: MYMENU prompt: myPrompt submit: [ // ... ]}The prompt’s art is displayed as a persistent overlay while the user interacts with it. After each submit the prompt redraws — this is what enables the classic “command bar” pattern used throughout ENiGMA½.
Real-World Example
Section titled “Real-World Example”The menuCommand prompt (defined in main.in.hjson) is reused across many menus — doors, file base, mail, and more. It displays a label (TL1) alongside a text entry field (ET2) that captures a command string:
prompts: { menuCommand: { art: MNUPRMT mci: { TL1: {} ET2: { argName: command width: 20 maxLength: 20 submit: true textStyle: upper focus: true } } }}Any menu that needs a command bar simply sets prompt: menuCommand — no duplication required.
For a yes/no confirmation style prompt, use a ToggleMenuView (TM):
logoffConfirmation: { art: LOGPMPT mci: { TM1: { argName: promptValue items: [ "yes", "no" ] focus: true hotKeys: { Y: 0, N: 1 } hotKeySubmit: true } }}actionKeys
Section titled “actionKeys”Prompts support an optional actionKeys array for binding keys to system actions (most commonly Escape to go back):
myPrompt: { art: MYPRMPT mci: { /* ... */ } actionKeys: [ { keys: [ "escape" ] action: @systemMethod:prevMenu } ]}ACS Checks
Section titled “ACS Checks”Menu modules can check user ACS in order to restrict areas and perform flow control. See ACS for available ACS syntax.
Menu Access
Section titled “Menu Access”To restrict menu access add an acs key to config. Example:
opOnlyMenu: { desc: Ops Only! config: { acs: ID1 }}Action Matches
Section titled “Action Matches”Action blocks (action) can perform ACS checks:
// ...{ action: [ { acs: SC1 action: @menu:secureMenu } { action: @menu:nonSecureMenu } ]}Flow Control
Section titled “Flow Control”The next member of a menu may be an array of objects containing an acs check as well as the destination. Depending on the current user’s ACS, the system will pick the appropriate target. The last element in an array without an acs can be used as a catch all. Example:
login: { desc: Logging In next: [ { // >= 2 calls else you get the full login acs: NC2 next: loginSequenceLoginFlavorSelect } { next: fullLoginSequenceLoginArt } ]}Art Asset Selection
Section titled “Art Asset Selection”Another area in which you can apply ACS in a menu is art asset specs.
someMenu: { desc: Neato Dorito art: [ { acs: GM[couriers] art: COURIERINFO } { // show ie: EVERYONEELSE.ANS to everyone else art: EVERYONEELSE } ]}Case Study: Adding a Sub Menu to Main
Section titled “Case Study: Adding a Sub Menu to Main”A very common task: You want to add a new menu accessible from “Main”. First, let’s create a new menu called “Snazzy Town”! Perhaps under the mainMenu entry somewhere, create a new menu:
snazzyTown: { desc: Snazzy Town art: snazzy config: { cls: true pause: true }}Now let’s make it accessible by “S” from the main menu. By default the main menu entry is named mainMenu. Within the mainMenu’s submit block you will see some existing action matches to “command”. Simply add a new one pointing to snazzyTown:
{ value: { command: "S" } action: @menu:snazzyTown}That’s it! When users type “S” at the main menu, they’ll be sent to the Snazzy Town menu. Since we did not supply additional flow logic when they exit, they will fall back to main.
Case Study: Adding a New User Password (NUP)
Section titled “Case Study: Adding a New User Password (NUP)”You’ve got a super 31337 board and want to prevent lamerz! Let’s run through adding a NUP to your application flow.
Given the default menu system, two “pre” new user application menus exist due to the way Telnet vs SSH logins occur. We’ll focus only on Telnet here. This menu is newUserApplicationPre. Let’s say you want to display this preamble, but then ask for the NUP. If the user gets the password wrong, show them a LAMER.ANS and boot ‘em.
First, let’s create a new menu for the NUP:
newUserPassword: { art: NUP.ANS next: newUserApplication desc: NUP!
form: { 0: { mci: { ET1: { // here we create an argument/variable of "nup" argName: nup focus: true submit: true } } submit: { *: [ { // if the user submits "nup" with the correct // value of "nolamerz" action will send // them to the next menu defined above -- // in our case: newUserApplication value: { nup: "nolamerz" } action: @systemMethod:nextMenu } { // anything else will result in going to the badNewUserPassword menu value: { nup: null } action: @menu:badNewUserPassword } ] } } }}Looks like we’ll need a badNewUserPassword menu as well! Let’s create a very basic menu to show art then disconnect the user.
badNewUserPassword: { art: LAMER.ANS // here we use a built in system method to boot them. next: @systemMethod:logoff config: { // wait 2s after showing the art before kicking them nextTimeout: 2000 }}Great, we have a couple new menus. Now let’s just point to them. Remember the existing newUserApplicationPre menu? All that is left to do is point it’s next to our newUserPassword menu:
newUserApplicationPre: { // easy! Just tell the system where to go next next: newUserPassword // note that the rest of this menu is omitted for clarity}Case Study: Manual Encoding Selection
Section titled “Case Study: Manual Encoding Selection”Enigma½ tries to automatically determine the proper encoding for a client when it connects. Unfortunately, there are cases where the wrong encoding can be selected, resulting in terminal programs that are not supported. If your user base contains users that would like to connect with unsupported clients, one solution is to offer manual encoding selection.
This can be accomplished with the system method @systemMethod:setClientEncoding.
Simple example
Section titled “Simple example”A basic config to use this could look something like the following:
telnetConnected: { art: CONNECT next: clientSelectEncoding config: { nextTimeout: 1500 }}
clientSelectEncoding: { art: CLTSEL.ASC next: matrix form: { 0: { mci: { HM1: { submit: true hotKeys: { U: 0, C: 1 } hotKeysSubmit: true focus: true argName: encoding items: [ { text: U) UTF-8 data: utf-8 } { text: C) CP437 data: cp437 } ] } } submit: { *: [ { value: { encoding: null } action: @systemMethod:setClientEncoding } ] } } }}The artfile for this should not contain extended characters, a simple file list the following should work:
Choose your encoding:
%HM1Auto selection example
Section titled “Auto selection example”The above example can be further extended to default to the automatically detected encoding by using a slightly more complicated menu system:
telnetConnected: { art: CONNECT next: [ { acs: EC0 next: clientSelectCP437 } { next: clientSelectUTF8 } ] config: { nextTimeout: 1500 }}
clientSelectUTF8: { art: CLTSEL.ASC next: matrix config: { font: utf-8 } form: { 0: { mci: { HM1: { submit: true hotKeys: { U: 0, C: 1 } hotKeysSubmit: true focus: true argName: encoding focusItemIndex: 0 items: [ { text: U) UTF-8 data: utf-8 } { text: C) CP437 data: cp437 } ] } } submit: { *: [ { value: { encoding: null } action: @systemMethod:setClientEncoding } ] } } }}
clientSelectCP437: { art: CLTSEL.ASC next: matrix config: { font: cp437 } form: { 0: { mci: { HM1: { submit: true hotKeys: { U: 0, C: 1 } hotKeysSubmit: true focus: true argName: encoding focusItemIndex: 1 items: [ { text: U) UTF-8 data: utf-8 } { text: C) CP437 data: cp437 } ] } } submit: { *: [ { value: { encoding: null } action: @systemMethod:setClientEncoding } ] } } }}Use the same artfile as the previous example.
Note: This example can be shortened by using @reference sections if desired.
The acs: sections above will send the user to a different menu depending on whether they have encoding CP437 using EC0 or anything else sent to the UTF8 menu. From there focusItemIndex chooses the default item.