<metaname="description"content="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."/>
<metaproperty="og:description"content="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."/>
{"mainEntityOfPage":{"@type":"WebPage","@id":"/enigma-bbs/configuration/menu-hjson.html"},"description":"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.","url":"/enigma-bbs/configuration/menu-hjson.html","@type":"BlogPosting","publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"/enigma-bbs/assets/images/enigma-logo.png"}},"headline":"Menu HSJON","dateModified":"2022-08-01T21:06:07+00:00","datePublished":"2022-08-01T21:06:07+00:00","@context":"https://schema.org"}</script>
<p>The core of a ENiGMA½ based BBS is it’s menus driven by what will be referred to as <codeclass="language-plaintext highlighter-rouge">menu.hjson</code>. Throughout ENiGMA½ documentation, when <codeclass="language-plaintext highlighter-rouge">menu.hjson</code> is referenced, we’re actually talking about <codeclass="language-plaintext highlighter-rouge">config/menus/yourboardname-*.hjson</code>. 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 <ahref="https://hjson.org/">HJSON</a> format.</p>
<p><imgclass="emoji"title=":information_source:"alt=":information_source:"src="https://github.githubassets.com/images/icons/emoji/unicode/2139.png"height="20"width="20"> See also <ahref="/enigma-bbs/configuration/hjson.html">HJSON General Information</a> for more information on the HJSON file format.</p>
<p><imgclass="emoji"title=":bulb:"alt=":bulb:"src="https://github.githubassets.com/images/icons/emoji/unicode/1f4a1.png"height="20"width="20"> Entries in <codeclass="language-plaintext highlighter-rouge">menu.hjson</code> are often referred to as <em>blocks</em> or <em>sections</em>. Each entry defines a menu. A menu in this sense is something the user can see or visit. Examples include but are not limited to:</p>
<ul>
<li>Classical navigation and menus such as Main, Messages, and Files.</li>
<li>Art file display.</li>
<li>Module driven menus such as <ahref="/enigma-bbs/modding/local-doors.html">door launchers</a>, <ahref="../modding/onelinzerz.md">Onelinerz</a>, and other custom mods.</li>
</ul>
<p>Menu entries live under the <codeclass="language-plaintext highlighter-rouge">menus</code> section of <codeclass="language-plaintext highlighter-rouge">menu.hjson</code>. The <em>key</em> for a menu is it’s name that can be referenced by other menus and areas of the system.</p>
<p>Below is a very basic menu entry called <codeclass="language-plaintext highlighter-rouge">showSomeArt</code> that displays some art then returns to the previous menu after the user hits a key:</p>
<pre><codeclass="language-hjson">showSomeArt: {
art: someart.ans
config: { pause: true }
}
</code></pre>
<p>As you can see a menu can be very simple.</p>
<p><imgclass="emoji"title=":information_source:"alt=":information_source:"src="https://github.githubassets.com/images/icons/emoji/unicode/2139.png"height="20"width="20"> Remember that the top level menu may include additional files using the <codeclass="language-plaintext highlighter-rouge">includes</code> directive. See <ahref="/enigma-bbs/configuration/config-files.html">Configuration Files</a> for more information on this.</p>
<h2id="common-menu-entry-members">Common Menu Entry Members</h2>
<p>Below is a table of <strong>common</strong> menu entry members. These members apply to most entries, though entries that are backed by a specialized module (ie: <codeclass="language-plaintext highlighter-rouge">module: bbs_list</code>) may differ. Menus that use their own module contain a <codeclass="language-plaintext highlighter-rouge">module</code> declaration:</p>
<td>A friendly description that can be found in places such as “Who’s Online” or wherever the <codeclass="language-plaintext highlighter-rouge">%MD</code> MCI code is used.</td>
<td>Specifies the menu to go to next. Can be explicit or an array of possibilities dependent on ACS. See <strong>Flow Control</strong> in the <strong>ACS Checks</strong> section below. If <codeclass="language-plaintext highlighter-rouge">next</code> is not supplied, the next menu is this menus parent. Note that special built in methods such as <codeclass="language-plaintext highlighter-rouge">@systemMethod:logoff</code> can also be utilized here.</td>
<td>Specifies a prompt, by name, to use along with this menu. Prompts are configured in the <codeclass="language-plaintext highlighter-rouge">prompts</code> section. See <strong>Prompts</strong> for more information.</td>
<td>An object containing additional configuration. See <strong>Config Block</strong> below.</td>
</tr>
</tbody>
</table>
<h3id="config-block">Config Block</h3>
<p>The <codeclass="language-plaintext highlighter-rouge">config</code> block for a menu entry can contain common members as well as a per-module (when <codeclass="language-plaintext highlighter-rouge">module</code> is used) settings.</p>
<td>If <codeclass="language-plaintext highlighter-rouge">true</code> a pause will occur after showing this menu. Useful for simple menus such as displaying art or status screens.</td>
<td>Sets the number of <strong>milliseconds</strong> before the system will automatically advanced to the <codeclass="language-plaintext highlighter-rouge">next</code> menu.</td>
<td>Sets a SyncTERM style font to use when displaying this menus <codeclass="language-plaintext highlighter-rouge">art</code>. See font listing in <ahref="/enigma-bbs/art/general.html">General Art Information</a>.</td>
<td>An array of menu flag(s) controlling menu behavior. See <strong>Menu Flags</strong> below.</td>
</tr>
</tbody>
</table>
<h4id="menu-flags">Menu Flags</h4>
<p>The <codeclass="language-plaintext highlighter-rouge">menuFlags</code> field of a <codeclass="language-plaintext highlighter-rouge">config</code> block can change default behavior of a particular menu.</p>
<td>Prevents the menu from remaining in the menu stack / history. When this flag is set, when the <strong>next</strong> menu falls back, this menu will be skipped and the previous menu again displayed instead. Example: menuA -> menuB(noHistory) -> menuC: Exiting menuC returns the user to menuA.</td>
<td>When <em>this</em> menu is exited, fall back beyond the parent as well. Often used in combination with <codeclass="language-plaintext highlighter-rouge">noHistory</code>.</td>
<td>If set, when the next menu is entered, forward any <codeclass="language-plaintext highlighter-rouge">extraArgs</code> arguments to <em>this</em> menu on to it.</td>
</tr>
</tbody>
</table>
<h2id="forms">Forms</h2>
<p>ENiGMA½ uses a concept of <em>forms</em> in menus. A form is a collection of associated <em>views</em>. Consider a New User Application using the <codeclass="language-plaintext highlighter-rouge">nua</code> module: The default implementation utilizes a single form with multiple EditTextView views, a submit button, etc. Forms are identified by number starting with <codeclass="language-plaintext highlighter-rouge">0</code>. A given menu may have mutiple forms (often associated with different states or screens within the menu).</p>
<p>Menus may also support more than one layout type by using a <em>MCI key</em>. 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 (<codeclass="language-plaintext highlighter-rouge">VM</code> key) while another uses a horizontal (<codeclass="language-plaintext highlighter-rouge">HM</code> 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 <codeclass="language-plaintext highlighter-rouge">menu.hjson</code>.</p>
<p>For more information on views and associated MCI codes, see <ahref="/enigma-bbs/art/mci.html">MCI Codes</a>.</p>
<p>When a form is submitted, it’s data is matched against a <em>submit handler</em>. When a match is found, it’s <em>action</em> is performed. Note: Setting the value explicitly to null matches against any value.</p>
<p>Submit actions are declared using the <codeclass="language-plaintext highlighter-rouge">action</code> member of a submit handler block. Actions can be kick off system/global or local-to-module methods, launch other menus, etc.</p>
<td>Executes <em>methodName</em> local to the calling module. That is, the module set by the <codeclass="language-plaintext highlighter-rouge">module</code> member of a menu entry.</td>
<p>In addition to simple simple actions, <codeclass="language-plaintext highlighter-rouge">action</code> may also be:</p>
<ul>
<li>An array of objects containing ACS checks and a sub <codeclass="language-plaintext highlighter-rouge">action</code> if that ACS is matched. See <strong>Action Matches</strong> in the ACS documentation below for details.</li>
<li>An array of actions. In this case a random selection will be made. Example:
<pre><codeclass="language-hjson">submit: [
{
value: { command: "FOO" }
action: [
// one of the following actions will be matched:
"@menu:menuStyle1"
"@menu:menuStyle2"
]
}
]
</code></pre>
</li>
</ul>
<h4id="method-signature">Method Signature</h4>
<p>Methods executed using <codeclass="language-plaintext highlighter-rouge">@method</code>, or <codeclass="language-plaintext highlighter-rouge">@systemMethod</code> have the following signature:</p>
<p>Many built in global/system methods exist. Below are a few. See <ahref="/core/system_menu_method.js">system_menu_method</a> for more information.</p>
<p>The above entry <codeclass="language-plaintext highlighter-rouge">telnetConnected</code> is set as the Telnet server’s first menu entry (set by <codeclass="language-plaintext highlighter-rouge">firstMenu</code> in the Telnet server’s config). The entry sets up a few things:</p>
<ul>
<li>A <codeclass="language-plaintext highlighter-rouge">art</code> spec of <codeclass="language-plaintext highlighter-rouge">CONNECT</code>. (See <ahref="/enigma-bbs/art/general.html">General Art Information</a>).</li>
<li>A <codeclass="language-plaintext highlighter-rouge">next</code> entry up the next menu, by name, in the stack (<codeclass="language-plaintext highlighter-rouge">matrix</code>) that we’ll go to after <codeclass="language-plaintext highlighter-rouge">telnetConnected</code>.</li>
<li>An <codeclass="language-plaintext highlighter-rouge">config</code> block containing a single <codeclass="language-plaintext highlighter-rouge">nextTimeout</code> field telling the system to proceed to the <codeclass="language-plaintext highlighter-rouge">next</code> (<codeclass="language-plaintext highlighter-rouge">matrix</code>) entry automatically after 1500ms.</li>
</ul>
<p>Now let’s look at <codeclass="language-plaintext highlighter-rouge">matrix</code>, the <codeclass="language-plaintext highlighter-rouge">next</code> entry from <codeclass="language-plaintext highlighter-rouge">telnetConnected</code>:</p>
<pre><codeclass="language-hjson">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.
//
}
}
}
</code></pre>
<p>In the above entry, you’ll notice <codeclass="language-plaintext highlighter-rouge">form</code>. This defines a form(s) object. In this case, a single form by ID of <codeclass="language-plaintext highlighter-rouge">0</code>. The system is then told to use a block only when the resulting art provides a <codeclass="language-plaintext highlighter-rouge">VM</code> (<em>VerticalMenuView</em>) MCI entry. Some other bits about the form:</p>
<ul>
<li>
<codeclass="language-plaintext highlighter-rouge">VM1</code> is then setup to <codeclass="language-plaintext highlighter-rouge">submit</code> and start focused via <codeclass="language-plaintext highlighter-rouge">focus: true</code> as well as have some menu entries (“login”, “apply”, …) defined. We provide an <codeclass="language-plaintext highlighter-rouge">argName</code> of <codeclass="language-plaintext highlighter-rouge">matrixSubmit</code> for this element view.</li>
<li>The <codeclass="language-plaintext highlighter-rouge">submit</code> object tells the system to attempt to apply provided match entries from any view ID (<codeclass="language-plaintext highlighter-rouge">*</code>).</li>
<li>Upon submit, the first match will be executed. For example, if the user selects “login”, the first entry with a value of <codeclass="language-plaintext highlighter-rouge">{ matrixSubmit: 0 }</code> will match (due to 0 being the first index in the list and <codeclass="language-plaintext highlighter-rouge">matrixSubmit</code> being the arg name in question) causing <codeclass="language-plaintext highlighter-rouge">action</code> of <codeclass="language-plaintext highlighter-rouge">@menu:login</code> to be executed (go to <codeclass="language-plaintext highlighter-rouge">login</code> menu).</li>
</ul>
<h2id="prompts">Prompts</h2>
<p>Prompts are found in the <codeclass="language-plaintext highlighter-rouge">prompts</code> section of menu files. Prompts allow for quick user input and shorthand form requirements for menus. Additionally, prompts are often used for for multiple menus. Consider a pause prompt or menu command input for example.</p>
<p>TODO: additional prompt docs</p>
<h2id="acs-checks">ACS Checks</h2>
<p>Menu modules can check user ACS in order to restrict areas and perform flow control. See <ahref="/enigma-bbs/configuration/acs.html">ACS</a> for available ACS syntax.</p>
<h3id="menu-access">Menu Access</h3>
<p>To restrict menu access add an <codeclass="language-plaintext highlighter-rouge">acs</code> key to <codeclass="language-plaintext highlighter-rouge">config</code>. Example:</p>
<p>The <codeclass="language-plaintext highlighter-rouge">next</code> member of a menu may be an array of objects containing an <codeclass="language-plaintext highlighter-rouge">acs</code> 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 <codeclass="language-plaintext highlighter-rouge">acs</code> can be used as a catch all. Example:</p>
<p>Another area in which you can apply ACS in a menu is art asset specs.</p>
<pre><codeclass="language-hjson">someMenu: {
desc: Neato Dorito
art: [
{
acs: GM[couriers]
art: COURIERINFO
}
{
// show ie: EVERYONEELSE.ANS to everyone else
art: EVERYONEELSE
}
]
}
</code></pre>
<h2id="case-study-adding-a-sub-menu-to-main">Case Study: Adding a Sub Menu to Main</h2>
<p>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 <codeclass="language-plaintext highlighter-rouge">mainMenu</code> entry somewhere, create a new menu:</p>
<pre><codeclass="language-hjson">snazzyTown: {
desc: Snazzy Town
art: snazzy
config: {
cls: true
pause: true
}
}
</code></pre>
<p>Now let’s make it accessible by “S” from the main menu. By default the main menu entry is named <codeclass="language-plaintext highlighter-rouge">mainMenu</code>. Within the <codeclass="language-plaintext highlighter-rouge">mainMenu</code>’s <codeclass="language-plaintext highlighter-rouge">submit</code> block you will see some existing action matches to “command”. Simply add a new one pointing to <codeclass="language-plaintext highlighter-rouge">snazzyTown</code>:</p>
<pre><codeclass="language-hjson">{
value: { command: "S" }
action: @menu:snazzyTown
}
</code></pre>
<p>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.</p>
<h2id="case-study-adding-a-new-user-password-nup">Case Study: Adding a New User Password (NUP)</h2>
<p>You’ve got a super 31337 board and want to prevent lamerz! Let’s run through adding a NUP to your application flow.</p>
<p>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 <codeclass="language-plaintext highlighter-rouge">newUserApplicationPre</code>. 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 <codeclass="language-plaintext highlighter-rouge">LAMER.ANS</code> and boot ‘em.</p>
<p>First, let’s create a new menu for the NUP:</p>
// anything else will result in going to the badNewUserPassword menu
value: { nup: null }
action: @menu:badNewUserPassword
}
]
}
}
}
}
</code></pre>
<p>Looks like we’ll need a <codeclass="language-plaintext highlighter-rouge">badNewUserPassword</code> menu as well! Let’s create a very basic menu to show art then disconnect the user.</p>
// 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
}
}
</code></pre>
<p>Great, we have a couple new menus. Now let’s just point to them. Remember the existing <codeclass="language-plaintext highlighter-rouge">newUserApplicationPre</code> menu? All that is left to do is point it’s <codeclass="language-plaintext highlighter-rouge">next</code> to our <codeclass="language-plaintext highlighter-rouge">newUserPassword</code> menu:</p>
<p>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.</p>
<p>This can be accomplished with the system method <codeclass="language-plaintext highlighter-rouge">@systemMethod:setClientEncoding</code>.</p>
<h3id="simple-example">Simple example</h3>
<p>A basic config to use this could look something like the following:</p>
<p>The above example can be further extended to default to the automatically detected encoding by using a slightly more complicated menu system:</p>
<p>```hjson</p>
<p>telnetConnected: {
art: CONNECT
next: [
{
acs: EC0
next: clientSelectCP437
}
{
next: clientSelectUTF8
}
]
config: { nextTimeout: 1500 }
}</p>
<p>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
}
]
}
}
}
}</p>
<p>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
}
]
}
}
}
}</p>
<p>Use the same artfile as the previous example.</p>
<p><em>Note</em>: This example can be shortened by using @reference sections if desired.</p>
<p>The <codeclass="language-plaintext highlighter-rouge">acs:</code> sections above will send the user to a different menu depending on whether they have encoding CP437 using <codeclass="language-plaintext highlighter-rouge">EC0</code> or anything else sent to the UTF8 menu. From there <codeclass="language-plaintext highlighter-rouge">focusItemIndex</code> chooses the default item.</p>