SoG : Les applets

SoG (Stash of Games) est un projet personnel de plate-forme Web de jeux multijoueurs en cours de développement, composée d'un serveur HTTP général pour exposer un portail et une administration, ainsi que de serveurs WebSocket spécifiques aux jeux.
Stash of Games (SoG)
La particularité de ce projet à base de HTML, CSS, JavasScript et diverses API du navigateur (WebGPU, Fetch, etc.) est d'être écrit entièrement à la main. Quand on dit "à la main", c'est vraiment chaque caractère tapé l'un après l'autre : les seuls outils utilisés sont Notepad++, sans autre assistance que la coloration syntaxique (autrement dit, même pas de complétion automatique), et le débogueur de Firefox. Il va sans dire qu'aucun framework n'est utilisé. En fait, il n'est même pas question d'utiliser le sugar coating de JavaScript que constituent les class (quelle horreur, réservée à ceux qui n'ont vraiment rien compris au langage !) et autres async. On est vraiment brut de fonderie.
Dans l'attente de la distribution d'une première version, le code de différentes applets sera publié sur ce site.
Les applets s'appuient sur une architecture qui sera documentée ultérieurement.
On commence par une liste 1D ou 2D d'éléments HTML quelconques, qui reprend toutes les fonctionnalités d'une liste d'Explorer, à savoir la sélection et la multisélection d'options à la souris ou au clavier, à l'appui de l'utilisation des touches Space, Ctrl et Shift, auxquelles on ajoute Alt pour permettre la sélection des options qui occupent une région rectangulaire, et aussi... le drag'n'drop des options sélectionnées.
Mise à jour du 03/01/2026 : List (v10).

Une liste 1D ou 2D d'éléments HTML quelconques (v10)

Une des limitations les plus lamentables du HTML, c'est l'impossibilité de pouvoir afficher une option d'un <select> sous la forme de HTML quelconque, notamment une image. Pour dépasser cette limitation, pas d'autre choix que de coder sa propre liste.
Le problème est que les fonctionnalités de ce que l'on croyait une bête de liste à force de l'utiliser se révèlent alors, et qu'elles sont sacrément riches : sélection, multisélection, gestion du point de départ d'une sélection, gestion de l'attribution du focus à une option, scrolling, recentrage automatique sur une option, etc.
List permet ce qu'un <select> permet, et plus encore, puisqu'il permet d'afficher une liste sous forme 1D classique, ou sous forme 2D :
  • en 1D, la liste peut se présenter sous la forme d'une ligne (scrolling horizontal) ou d'une colonne (scrolling vertical) ;
  • en 2D, la liste peut comporter un nombre de lignes fixes et un nombre de colonnes variable (scrolling horizontal), ou un nombre de colonnes fixe et un nombre de lignes variable (scrolling vertical).
Aussi, il est possible de glisser-poser les options sélectionnées de la liste.
La liste est une applet qui prend les styles suivants :
Style Description
.layout
Objet qui décrit le layout :
{
	list: {
		mode: <"horizontal" ou "vertical">,
		cols: <nombre de colonnes>,
		rows: <nombre de lignes>,
		width: <largeur de la liste : valeur en pixels "Xpx" ou en pourcentage "X%", "content" ou "max-content">
		height: <hauteur de la liste : valeur en pixels "Xpx" ou en pourcentage "X%", "content" ou "max-content">
		gap: <espace entre les options : valeur entière en pixels>
	},
	option: {
		width: <largeur d'une option, exclusion faite de sa bordure : valeur entière en pixels, "content", "track" ou "list">,
		height: <hauteur d'une option, exclusion faite de sa bordure : valeur entière en pixels, "content", "track" ou "list">
	},
	scrollbar: {
		width: <largeur d'une scrollbar verticale : valeur entière en pixels, par défaut DEFAULT_SCROLLBAR_WIDTH>
		height: <hauteur d'une scrollbar horizontale : valeur entière en pixels, par défaut DEFAULT_SCROLLBAR_HEIGHT>
	}
}
Dans .list, si .mode est "vertical", le scrolling est vertical : .cols correspond au nombre de colonnes visibles et figé, et .rows correspond au nombre de lignes visibles et variable, et les options sont disposées par colonne puis ligne. Et inversement, si .mode est "horizontal", le scrolling est horizontal : .rows correspond au nombre de lignes visibles et figé, et .cols correspond au nombre de colonnes visibles et variable, et les options sont disposées par ligne puis colonne.
La possibilité de mentionner les dimensions d'une scrollbar est fournie parce qu'elles pourraient varier selon le contexte d'affichage.
L'utilisateur est responsable du dimensionnement via les propriétés .width et .height des styles .layout.list et .layout.option :
  • Largeur d'une option :
    (valeur) Largeur (valeur) en pixels
    content Largeur de son contenu
    track Largeur de la plus large des options de sa colonne
    list
    ***** Uniquement si la largeur de la liste est (valeur), sinon cette dernière est forcée à DEFAULT_LIST_WIDTH *****
    ***** Dans une liste à scrolling vertical, ne fait sens que si la liste ne contient qu'une colonne *****
    Largeur de la liste
  • Hauteur d'une option :
    (valeur) Hauteur (valeur) en pixels
    content Hauteur de son contenu
    track Hauteur de la plus haute des options de sa ligne
    list
    ***** Uniquement si la hauteur de la liste est (valeur), sinon cette dernière est forcée à DEFAULT_LIST_WIDTH *****
    ***** Dans une liste à scrolling horizontal, ne fait sens que si la liste ne contient qu'une ligne *****
    Largeur de la liste
  • Largeur de la liste :
    (valeur) Largeur (valeur) en pixels
    content
    ***** Uniquement si la largeur d'option est (valeur), sinon la largeur de la liste est forcée à DEFAULT_LIST_WIDTH *****
    Nombre de colonnes * Largeur (valeur) d'une option
    max-content (Nombre de colonnes * Largeur de la plus large de toutes les options) + Largeur de la scrollbar
  • Hauteur de la liste :
    (valeur) Hauteur (valeur) en pixels
    content
    ***** Uniquement si la hauteur d'option est (valeur) sinon la hauteur de la liste est forcée à DEFAULT_LIST_HEIGHT *****
    Nombre de lignes * Hauteur (valeur) d'une option
    max-content (Nombre de lignes * Hauteur de la plus haute de toutes les options) + Hauteur de la scrollbar
Par conséquent, pour reproduire une liste de base où les contenus des options ont une même hauteur mais des largeurs variables, si bien que la largeur commune des options doit être ajustée à celle dont le contenu est le plus large :
.layout {
	.list {
		.mode: "vertical",
		.cols: 1,
		.rows: <nombre de lignes d'options>,
		.width: "100%",	// ou une valeur "px"
		.height: "max-content"
	}
	.option {
		.width: "list",
		.height: "content"
	}
}
Si on considère une telle liste de base, il semblerait plus simple de caler la largeur d'une option sur la largeur de la liste, plutôt que l'inverse. Dès lors, pourquoi ne pas avoir retenu cette solution ? Pour le comprendre, il faut relever que ce qui serait effectivement plus simple pour la largeur de la liste ne serait pas vrai pour sa hauteur : concernant cette dernière, il serait plus simple de la caler sur la hauteur d'une option, plutôt que l'inverse. Dès lors, comme l'applet doit permettre d'afficher une liste qui comprend N colonnes et N lignes, la simplicité imposait de caler les dimensions de la liste sur celles des options, et non l'inverse.
Néanmoins, conscient que cette limitation peut irriter, il est désormais possible de spécifier que .layout.option.width vaut "list". Cette nouvelle modalité permet de caler la largeur d'une option sur celle de la liste. Dans une liste à scrolling vertical telle que la liste de base qui vient d'être évoquée, il ne fait donc sens de l'utiliser que si la liste ne comporte qu'une colonne d'options. La modalité est aussi valable pour .layout.option.height - et similairement, dans une liste à scrolling horizontal, il ne fait sens de l'utiliser que si cette liste ne comporte qu'une ligne d'options.
Pour terminer sur la responsabilité de l'utilisateur en matière de dimensionnement de la liste, il faut préciser que l'interface de l'applet, telle que décrite par /applets/tools/List.html, est contenue dans un <div> :
<div data-id="$applet">
	...
</div>
Par conséquent, si l'utilisateur souhaite pouvoir dimensionner la liste à l'aide de (valeur) affectées aux styles .layout.list.width et .layout.list.height, il doit être conscient que ce dimensionnement ne sera possible que dans les limites imposées par la hiérarchie des éléments à laquelle l'utilisateur rattache ce <div>. Cela est tout particulièrement vrai quand (valeur) est un pourcentage.
Dans l'exemple /tests/List/index.html, la liste est logée dans la colonne de gauche d'une grille d'une ligne et deux colonnes, la colonne de gauche contenant la liste tandis que celle de droite contient une zone de drop. On souhaite que chaque colonne fasse 200 pixels de large et 400 pixels de haut. Pour que les (valeur) de .layout.list.width et .layout.list.height en pourcentage soient relatives à ces dimensions, et que quoi qu'il arrive toute partie de la liste qui déborde soit cachée, l'utilisateur doit ainsi décrire la grille :
<!-- D'abord, il faut contraindre les dimensions des lignes et des colonnes de la grille non seulement par grid-template-columns mais aussi par grid-template-rows, sinon la hauteur de la grille correspondra à laquelle des hauteurs de l'applet et de la zone de drop est la plus grande, ce qui laissera à l'applet la liberté de s'étendre en hauteur, rendant impossible l'utilisation d'une (valeur) en pourcentage pour .layout.list.height -->

<div style="display:grid;grid-template-columns:repeat(2,200px);gap:5px;grid-template-rows:400px">

	<!-- Ensuite, il faut introduire un élément auquel $applet va être rattaché. Pour indiquer que $applet doit recouvrir toute la surface de la cellule de la grille qui contient cet élément, il faut que ce dernier recouvre toute cette surface. De plus, tout comme les dimensions de cet élément sont contraintes par celui qui le contient, il faut contraindre celles du contenu de cet élément aux dimensions de cet élément. Cet élément étant un <div> comme celui qui le contient, le même problème appelle la même solution : il faut que cet élément soit une grille avec grid-template-columns:100% et grid-template-rows:100% -->

	<div data-id="$list" style="display:grid;grid-template-columns:100%;grid-template-rows:100%"></div>
		<div id="$applet">

			<!-- Le template de la liste se trouvera dans le shadow DOM de $applet -->

		</div>
	</div>
	<div data-id="$drop" style="background-color:green;height:100%"></div>
</div>
L'utilisateur est responsable du positionnement de $tag dans une option. Pour lui faciliter le travail, une option est une grille qui ne comporte qu'une ligne et qu'une colonne, si bien que l'utilisateur peut utiliser toutes les valeurs possible des styles "justify-self" et "align-self" pour cela.
.list
Objet qui décrit l'apparence de la liste :
{
	.background: <valeur CSS>
}
.option
Objet qui décrit l'apparence d'une option de la liste :
{
	border: {
		radius: <valeur CSS>,
		thickness: <valeur CSS>,
		style: <valeur CSS>,
		color: <valeur CSS>
	},
	background: <valeur CSS>,
	focused: {
		border: {
			style: <valeur CSS>,
			color: <valeur CSS>
		}
	},
	hovered: {
		background: <valeur CSS>
	},
	selected: {
		background: <valeur CSS>
	}
}
...et les paramètres suivants :
Paramètre Description
.template URL du template (optionnel, <www_root>/applets/tools/List.html par défaut
.dragging Permettre de glisser-poser une option (optionnel, ce doit être un UUID qui sera transmis dans les données du drag'n'drop)
.doMultiselect Permettre de sélectionner plusieurs options (optionnel, false par défaut)
Vu de l'utilisateur, les fonctionnalités de cette liste sont les suivantes :
  • Pour réduire la sélection à l'option qui détient le focus, et faire de cette option le point de départ d'une sélection : cliquer.
  • Pour sélectionner toutes les options consécutives dans le flot entre l'option qui est le point de départ d'une sélection et celle qui détient le focus : cliquer en maintenant Shit appuyée, ou presser Space en maintenant Shift appuyée. Pour rappel, le flot est en ligne puis en colonne si le nombre de colonnes est fixe (scrolling vertical), ou en colonne puis en ligne si le nombre de lignes est fixe (scrolling horizontal).
  • Pour sélectionner tous les options dans une zone rectangulaire entre l'option qui est le point de départ d'une sélection et celle qui détient le focus : cliquer en maintenant Alt appuyée, ou presser Space en maintenant Alt appuyée.
  • Pour sélectionner / déselectionner une option dans la sélection : cliquer en maintenant Ctrl appuyée, ou presser Space en maintenant Ctrl appuyée.
  • Pour déplacer le focus sans modifier la sélection ni le point de départ de la sélection : presser une des touches de déplacement (ArrowLeft, ArrowRight, ArrowUp, ArrowDown, PageUp, PageDown, Home, End) en maintenant Ctrl appuyée.
  • Pour faire défiler le contenu de la liste ligne par ligne (si scrolling vertical) ou colonne par colonne (si scrollling horizontal) sans rien modifier à l'état de la liste (focus, point de départ de la sélection, sélection) : utiliser la molette de la souris.
  • Quand l'utilisateur donne le focus à une option ou qu'il fait défiler le contenu de la liste, ce contenu de la liste scrolle automatiquement pour afficher autant que possible l'option en question. Autant que possible, car si l'option est plus large et/ou plus haute que la partie visible de la liste, elle ne peut être affichée que partiellement en prenant le parti de la caler à gauche / en haut de cette zone.
  • Pour glisser-poser la sélection... glisser-poser une des options sélectionnées, tout simplement !
Pour utiliser l'applet une fois qu'il l'a instanciée, l'utilisateur doit fournir une liste d'items - qui seront enveloppés par autant d'options - sous forme d'objets quelconques qui implémentent cette interface :
{
	$tag: <HTML element>
}
Difficile de faire moins contraignant...
Bien évidemment, il est possible de modifier le contenu de la liste à tout instant :
  • pour retirer des items à partir de références sur ces derniers, ou d'indices des options qui leur correspondent ;
  • pour ajouter des items en fin de liste ou aux indices d'options données.
Cliquez ici pour tester la liste, et ici pour récupérer le code.
SoG : Les applets