mirror of
git://git.tartarus.org/simon/puzzles.git
synced 2025-04-21 16:05:44 -07:00
js: Convert menus to use semantically appropriate HTML elements
Presets are now radio buttons with labels, and menu items that take actions are now buttons. The <li> representing each menu item is now a thin wrapper around another element: a <label> for radio buttons, a <button> for other buttons, and a <div> for submenu headings. All of the things that previously applied to the <li> now apply to that inner element instead. This means that presets can now use the standard "checked" attribute to indicate which one is selected, and buttons can be disabled using the standard "disabled" attribute. It also means that we can query and set the state of all the presets at once through their RadioNodeList. I think this should also make the menus more accessible, and make it easier to make them keyboard-controllable.
This commit is contained in:
39
emcclib.js
39
emcclib.js
@ -70,18 +70,19 @@ mergeInto(LibraryManager.library, {
|
|||||||
js_add_preset: function(menuid, ptr, value) {
|
js_add_preset: function(menuid, ptr, value) {
|
||||||
var name = UTF8ToString(ptr);
|
var name = UTF8ToString(ptr);
|
||||||
var item = document.createElement("li");
|
var item = document.createElement("li");
|
||||||
item.setAttribute("data-index", value);
|
var label = document.createElement("label");
|
||||||
var tick = document.createElement("span");
|
var tick = document.createElement("input");
|
||||||
|
tick.type = "radio";
|
||||||
tick.className = "tick";
|
tick.className = "tick";
|
||||||
tick.appendChild(document.createTextNode("\u2713"));
|
tick.name = "preset";
|
||||||
item.appendChild(tick);
|
tick.value = value;
|
||||||
item.appendChild(document.createTextNode(name));
|
label.appendChild(tick);
|
||||||
|
label.appendChild(document.createTextNode(name));
|
||||||
|
item.appendChild(label);
|
||||||
gametypesubmenus[menuid].appendChild(item);
|
gametypesubmenus[menuid].appendChild(item);
|
||||||
gametypeitems.push(item);
|
|
||||||
|
|
||||||
item.onclick = function(event) {
|
tick.onclick = function(event) {
|
||||||
if (dlg_dimmer === null) {
|
if (dlg_dimmer === null) {
|
||||||
gametypeselectedindex = value;
|
|
||||||
command(2);
|
command(2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -100,13 +101,14 @@ mergeInto(LibraryManager.library, {
|
|||||||
// We still create a transparent tick element, even though it
|
// We still create a transparent tick element, even though it
|
||||||
// won't ever be selected, to make submenu titles line up
|
// won't ever be selected, to make submenu titles line up
|
||||||
// nicely with their neighbours.
|
// nicely with their neighbours.
|
||||||
|
var label = document.createElement("div");
|
||||||
var tick = document.createElement("span");
|
var tick = document.createElement("span");
|
||||||
tick.appendChild(document.createTextNode("\u2713"));
|
|
||||||
tick.className = "tick";
|
tick.className = "tick";
|
||||||
item.appendChild(tick);
|
label.appendChild(tick);
|
||||||
item.appendChild(document.createTextNode(name));
|
label.appendChild(document.createTextNode(name));
|
||||||
|
item.appendChild(label);
|
||||||
var submenu = document.createElement("ul");
|
var submenu = document.createElement("ul");
|
||||||
item.appendChild(submenu);
|
label.appendChild(submenu);
|
||||||
gametypesubmenus[menuid].appendChild(item);
|
gametypesubmenus[menuid].appendChild(item);
|
||||||
var toret = gametypesubmenus.length;
|
var toret = gametypesubmenus.length;
|
||||||
gametypesubmenus.push(submenu);
|
gametypesubmenus.push(submenu);
|
||||||
@ -120,7 +122,7 @@ mergeInto(LibraryManager.library, {
|
|||||||
* dropdown.
|
* dropdown.
|
||||||
*/
|
*/
|
||||||
js_get_selected_preset: function() {
|
js_get_selected_preset: function() {
|
||||||
return gametypeselectedindex;
|
return menuform.elements["preset"].value;
|
||||||
},
|
},
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -131,16 +133,7 @@ mergeInto(LibraryManager.library, {
|
|||||||
* which turn out to exactly match a preset).
|
* which turn out to exactly match a preset).
|
||||||
*/
|
*/
|
||||||
js_select_preset: function(n) {
|
js_select_preset: function(n) {
|
||||||
gametypeselectedindex = n;
|
menuform.elements["preset"].value = n;
|
||||||
for (var i in gametypeitems) {
|
|
||||||
var item = gametypeitems[i];
|
|
||||||
var tick = item.firstChild;
|
|
||||||
if (item.getAttribute("data-index") == n) {
|
|
||||||
tick.classList.add("selected");
|
|
||||||
} else {
|
|
||||||
tick.classList.remove("selected");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
16
emccpre.js
16
emccpre.js
@ -126,15 +126,17 @@ var dlg_return_funcs = null;
|
|||||||
var dlg_return_sval, dlg_return_ival;
|
var dlg_return_sval, dlg_return_ival;
|
||||||
|
|
||||||
// The <ul> object implementing the game-type drop-down, and a list of
|
// The <ul> object implementing the game-type drop-down, and a list of
|
||||||
// the <li> objects inside it. Used by js_add_preset(),
|
// the sub-lists inside it. Used by js_add_preset().
|
||||||
// js_get_selected_preset() and js_select_preset().
|
var gametypelist = null;
|
||||||
var gametypelist = null, gametypeitems = [];
|
|
||||||
var gametypeselectedindex = null;
|
|
||||||
var gametypesubmenus = [];
|
var gametypesubmenus = [];
|
||||||
|
|
||||||
// C entry point for miscellaneous events.
|
// C entry point for miscellaneous events.
|
||||||
var command;
|
var command;
|
||||||
|
|
||||||
|
// The <form> encapsulating the menus. Used by
|
||||||
|
// js_get_selected_preset() and js_select_preset().
|
||||||
|
var menuform = null;
|
||||||
|
|
||||||
// The two anchors used to give permalinks to the current puzzle. Used
|
// The two anchors used to give permalinks to the current puzzle. Used
|
||||||
// by js_update_permalinks().
|
// by js_update_permalinks().
|
||||||
var permalink_seed, permalink_desc;
|
var permalink_seed, permalink_desc;
|
||||||
@ -187,10 +189,7 @@ function canvas_mouse_coords(event, element) {
|
|||||||
|
|
||||||
// Enable and disable items in the CSS menus.
|
// Enable and disable items in the CSS menus.
|
||||||
function disable_menu_item(item, disabledFlag) {
|
function disable_menu_item(item, disabledFlag) {
|
||||||
if (disabledFlag)
|
item.disabled = disabledFlag;
|
||||||
item.className = "disabled";
|
|
||||||
else
|
|
||||||
item.className = "";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dialog-box functions called from both C and JS.
|
// Dialog-box functions called from both C and JS.
|
||||||
@ -420,6 +419,7 @@ function initPuzzle() {
|
|||||||
|
|
||||||
gametypelist = document.getElementById("gametype");
|
gametypelist = document.getElementById("gametype");
|
||||||
gametypesubmenus.push(gametypelist);
|
gametypesubmenus.push(gametypelist);
|
||||||
|
menuform = document.getElementById("gamemenu");
|
||||||
|
|
||||||
// In IE, the canvas doesn't automatically gain focus on a mouse
|
// In IE, the canvas doesn't automatically gain focus on a mouse
|
||||||
// click, so make sure it does
|
// click, so make sure it does
|
||||||
|
100
html/jspage.pl
100
html/jspage.pl
@ -94,37 +94,43 @@ EOF
|
|||||||
#gamemenu ul li {
|
#gamemenu ul li {
|
||||||
/* Add a little mild text formatting */
|
/* Add a little mild text formatting */
|
||||||
font-weight: bold; font-size: 0.8em;
|
font-weight: bold; font-size: 0.8em;
|
||||||
/* Line height and padding appropriate to top-level menu items */
|
|
||||||
padding-left: 0.75em; padding-right: 0.75em;
|
|
||||||
padding-top: 0.2em; padding-bottom: 0.2em;
|
|
||||||
margin: 0;
|
|
||||||
/* Suppress the text-selection I-beam pointer */
|
/* Suppress the text-selection I-beam pointer */
|
||||||
cursor: default;
|
cursor: default;
|
||||||
/* Surround each menu item with a border. */
|
/* Surround each menu item with a border. */
|
||||||
border: 1px solid rgb(180,180,180);
|
border: 1px solid rgb(180,180,180);
|
||||||
/* Arrange that the borders of each item overlap the ones next to it. */
|
/* Arrange that the borders of each item overlap the ones next to it. */
|
||||||
margin: -0.5px;
|
margin: -0.5px;
|
||||||
}
|
|
||||||
|
|
||||||
#gamemenu ul li.disabled {
|
|
||||||
/* Grey out menu items with the "disabled" class */
|
|
||||||
color: rgba(0,0,0,0.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
#gamemenu ul li.separator {
|
|
||||||
color: transparent;
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#gamemenu ul li:hover {
|
|
||||||
/* When the mouse is over a menu item, highlight it */
|
|
||||||
background: rgba(0,0,0,0.3);
|
|
||||||
/* Set position:relative, so that if this item has a submenu it can
|
/* Set position:relative, so that if this item has a submenu it can
|
||||||
* position itself relative to the parent item. */
|
* position itself relative to the parent item. */
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
#gamemenu ul li.disabled:hover {
|
#gamemenu ul li.separator {
|
||||||
|
width: 1.5em;
|
||||||
|
color: transparent;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The interactive contents of menu items are their child elements. */
|
||||||
|
#gamemenu ul li > * {
|
||||||
|
/* Line height and padding appropriate to top-level menu items */
|
||||||
|
padding-left: 0.75em; padding-right: 0.75em;
|
||||||
|
padding-top: 0.2em; padding-bottom: 0.2em;
|
||||||
|
margin: 0;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
#gamemenu ul :disabled {
|
||||||
|
/* Grey out disabled buttons */
|
||||||
|
color: rgba(0,0,0,0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
#gamemenu ul li > :hover {
|
||||||
|
/* When the mouse is over a menu item, highlight it */
|
||||||
|
background: rgba(0,0,0,0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
#gamemenu ul li > :disabled:hover {
|
||||||
/* Disabled menu items don't get a highlight on mouse hover */
|
/* Disabled menu items don't get a highlight on mouse hover */
|
||||||
background: inherit;
|
background: inherit;
|
||||||
}
|
}
|
||||||
@ -183,7 +189,7 @@ EOF
|
|||||||
left: inherit; right: 100%;
|
left: inherit; right: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
#gamemenu ul li:hover > ul {
|
#gamemenu ul li:hover > * > ul {
|
||||||
/* Last but by no means least, the all-important line that makes
|
/* Last but by no means least, the all-important line that makes
|
||||||
* submenus be displayed! Any <ul> whose parent <li> is being
|
* submenus be displayed! Any <ul> whose parent <li> is being
|
||||||
* hovered over gets display:flex overriding the display:none
|
* hovered over gets display:flex overriding the display:none
|
||||||
@ -191,13 +197,35 @@ EOF
|
|||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#gamemenu button {
|
||||||
|
/* Menu items that trigger an action. We put some effort into
|
||||||
|
* removing the default button styling. */
|
||||||
|
appearance: none;
|
||||||
|
font: inherit;
|
||||||
|
padding: initial;
|
||||||
|
color: inherit;
|
||||||
|
background: initial;
|
||||||
|
border: initial;
|
||||||
|
text-align: inherit;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
#gamemenu .tick {
|
#gamemenu .tick {
|
||||||
/* The tick next to a menu item, or its unselected equivalent. */
|
/* The tick at the start of a menu item, or its unselected equivalent.
|
||||||
|
* This is represented by an <input type="radio">, so we put some
|
||||||
|
* effort into overriding the default style. */
|
||||||
|
appearance: none;
|
||||||
|
margin: initial;
|
||||||
|
font: inherit;
|
||||||
padding-right: 0.5em;
|
padding-right: 0.5em;
|
||||||
color: transparent;
|
color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
#gamemenu .tick.selected {
|
#gamemenu .tick::before {
|
||||||
|
content: "\\2713";
|
||||||
|
}
|
||||||
|
|
||||||
|
#gamemenu .tick:checked {
|
||||||
/* Tick for a selected menu entry. */
|
/* Tick for a selected menu entry. */
|
||||||
color: inherit;
|
color: inherit;
|
||||||
}
|
}
|
||||||
@ -284,20 +312,20 @@ ${unfinishedpara}
|
|||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
<div id="puzzle" style="display: none">
|
<div id="puzzle" style="display: none">
|
||||||
<div id="gamemenu"><ul><li>Game...<ul
|
<form id="gamemenu"><ul><li><div>Game...<ul
|
||||||
><li id="specific">Enter game ID</li
|
><li><button type="button" id="specific">Enter game ID</button></li
|
||||||
><li id="random">Enter random seed</li
|
><li><button type="button" id="random">Enter random seed</button></li
|
||||||
><li id="save">Download save file</li
|
><li><button type="button" id="save">Download save file</button></li
|
||||||
><li id="load">Upload save file</li
|
><li><button type="button" id="load">Upload save file</button></li
|
||||||
></ul></li
|
></ul></div></li
|
||||||
><li>Type...<ul id="gametype"></ul></li
|
><li><div>Type...<ul id="gametype"></ul></div></li
|
||||||
><li class="separator"></li
|
><li class="separator"></li
|
||||||
><li id="new" class="afterseparator">New<span class="verbiage"> game</span></li
|
><li class="afterseparator"><button type="button" id="new">New<span class="verbiage"> game</span></button></li
|
||||||
><li id="restart">Restart<span class="verbiage"> game</span></li
|
><li><button type="button" id="restart">Restart<span class="verbiage"> game</span></button></li
|
||||||
><li id="undo">Undo<span class="verbiage"> move</span></li
|
><li><button type="button" id="undo">Undo<span class="verbiage"> move</span></button></li
|
||||||
><li id="redo">Redo<span class="verbiage"> move</span></li
|
><li><button type="button" id="redo">Redo<span class="verbiage"> move</span></button></li
|
||||||
><li id="solve">Solve<span class="verbiage"> game</span></li
|
><li><button type="button" id="solve">Solve<span class="verbiage"> game</span></button></li
|
||||||
></ul></div>
|
></ul></form>
|
||||||
<div align=center>
|
<div align=center>
|
||||||
<div id="resizable">
|
<div id="resizable">
|
||||||
<canvas id="puzzlecanvas" width="1px" height="1px" tabindex="1">
|
<canvas id="puzzlecanvas" width="1px" height="1px" tabindex="1">
|
||||||
|
Reference in New Issue
Block a user