Vous pourriez penser que créer une spinbox cohérente, propre et professionnelle serait une tâche simple en HTML... Cependant, à notre grand désespoir, il n'existe pas d'attribut standard pour indiquer à une entrée qu'elle ne doit accepter que des valeurs entières ou décimales. , tous les filtrages d'entrée doivent être JS. Outch !
Je vais implémenter cette fonctionnalité avec Go, a-h/Templ, Tailwind et mon bien-aimé Alpine.js pour me simplifier la vie.
Nous commençons par écrire un modèle de base pour notre spinbox entière :
templ IntSpinbox(name, label, value, tooltip string, saveinput bool, interval *IntInterval) { ... }
Nous définissons IntInterval comme suit :
type IntInterval struct { A, B int }
Avec l'intervalle, nous définirons le min et le max de l'entrée. Comme nous créons une spinbox entière, le pas sera toujours défini sur « 1 ».
templ IntSpinbox(name, label, value, tooltip string, saveinput bool, interval *IntInterval) { <input type="number" placeholder="Enter Int…" step="1" if interval != nil { min={ strconv.Itoa(interval.A) } max={ strconv.Itoa(interval.B) } } ...> }
Commençons maintenant à ajouter quelques classes, voici quelques propriétés spéciales et pseudo-éléments qui contrôlent le rendu de l'entrée.
select-none [-moz-user-select:none] [-ms-user-select:none] [-o-user-select:none] [-webkit-user-select:none]
Les classes supplémentaires suivantes sont utilisées pour supprimer les boutons spinner par défaut :
[&::-webkit-inner-spin-button]:[-webkit-apparence:aucun] [&::-webkit-outer-spin-button]:[-webkit-apparence:aucun] [-moz-apparence : champ de texte]
Enfin, ajoutons quelques rembourrages de base, anneaux, couleurs, etc...
bloc w-plein arrondi-l-md py-2 px-2.5 texte-gris-900 anneau-1 anneau-encart anneau-gris-300 espace réservé : texte-gris-400 focus : contour-aucun focus : anneau-2 focus : anneau-primaire-400 bg-gris-50 sm:text-sm sm:leading-6
En l'ajoutant à notre modèle, nous obtenons ce qui suit :
templ IntSpinbox(name, label, value, tooltip string, saveinput bool, interval *IntInterval) { <input type="number" placeholder="Enter Int…" step="1" if interval != nil { min={ strconv.Itoa(interval.A) } max={ strconv.Itoa(interval.B) } } class="block w-full rounded-l-md py-2 px-2.5 text-gray-900 ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:outline-none focus:ring-2 focus:ring-primary-400 bg-gray-50 sm:text-sm sm:leading-6 select-none [-moz-user-select:none] [-ms-user-select:none] [-o-user-select:none] [-webkit-user-select:none] [&::-webkit-inner-spin-button]:[-webkit-appearance:none] [&::-webkit-outer-spin-button]:[-webkit-appearance:none] [-moz-appearance:textfield]"> }
Vous devriez maintenant obtenir une entrée très semblable à du texte, avec une validation de base si vous passez votre souris dessus. Nous ajouterons la fonctionnalité permettant de vérifier les entrées entières valides dans la section suivante.
L'idée de base d'une spinbox entière est celle d'une entrée qui uniquement accepte les entiers. J'ai d'abord tenté d'implémenter cette fonction en utilisant l'attribut pattern de HTML comme suit :
<input type="number" pattern="[0-9]+" ... >
L'attribut pattern prend une chaîne regex et l'utilise pour valider la saisie de l'utilisateur. Cependant, il n'empêche pas la saisie d'une saisie non valide en premier lieu. En fait, il a été conçu pour une simple validation côté client.
Chaque fois que l'utilisateur appuie sur une touche à l'intérieur de la zone de saisie, un événement oninput est généré. capturez cet événement avec la syntaxe d'Alpine x-on:input et rectifiez la valeur en conséquence pour l'élément d'entrée. Créons un div parent avec un ensemble d'attributs x-data et ajoutons une fonction qui nous permettra de vérifier si l'entrée est un nombre... Après quoi nous pouvons arrondir la valeur en conséquence.
<div x-data="{isNumber(n) { return !isNaN(parseFloat(n)) && !isNaN(n - 0) }}"> <input ... x-on:input="$el.value = isNumber($el.value) ? Math.round($el.value) : null"> </div>
Pour ceux qui ne connaissent pas Alpine, $el ici est utilisé pour faire référence à l'élément DOM actuel.
Dans le div parent créé précédemment, nous ajoutons le class="flex" suivant et ajoutons un attrib x-ref="spinbox" à l'entrée afin que nos boutons puissent modifier son état via la propriété magique $refs.spinbox :
<div ... class="flex"> <input ... x-ref="spinbox"> </div>
Nous ajoutons ensuite un nouvel enfant après la saisie, qui contiendra nos boutons :
<div ...> <input ... x-ref="spinbox"> <div class="flex flex-col-reverse"> <!-- Decrement input's value --> <button type="button" class="flex-1 ...">-</button> <!-- Increment input's value --> <button type="button" class="flex-1 ...">+</button> </div> </div>
Ici, nous utilisons flex-col-reverse comme moyen simple de maintenir l'ordre de tabulation correct, il doit d'abord être tabulé sur « - », puis sur « + ».
On ajoute ensuite les gestionnaires d'évènements aux boutons en utilisant x-on:click, le code complet (hors CSS) est le suivant :
<div ... x-data="{ inc() { var e = $refs.spinbox; e.value = Math.min(Number(e.value) + Number(e.step), e.max); }, dec() { var e = $refs.spinbox; e.value = Math.max(Number(e.value) - Number(e.step), e.min); }, isNumber(n) { return !isNaN(parseFloat(n)) && !isNaN(n - 0) } }"> <input ... x-ref="spinbox" x-on:input="$el.value = isNumber($el.value) ? Math.round($el.value) : null"> <div ...> <!-- Decrement input's value --> <button type="button" ... x-on:click="dec">-</button> <!-- Increment input's value --> <button type="button" ... x-on:click="inc">+</button> </div> </div>
Nous devons convertir e.value et e.step avant de faire des calculs car ce sont des chaînes.
En ce qui concerne le CSS pour les boutons spinners, ils ont un style très similaire à celui de l'entrée, le code complet est ci-dessous.
templ IntSpinbox(name, label, value, tooltip string, saveinput bool, interval *IntInterval) { <!-- Disable inner & outer spinner buttons, use buttons to render increment and decrement input value... --> <div class="flex-1"> @InputLabel(name, label + " " + interval.toString(), tooltip) <input type="number" placeholder="Enter Int…" step="1" if interval != nil { min={ strconv.Itoa(interval.A) } max={ strconv.Itoa(interval.B) } } name={ name } value={ value } class="block w-full rounded-l-md py-2 px-2.5 text-gray-900 ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:outline-none focus:ring-2 focus:ring-primary-400 bg-gray-50 sm:text-sm sm:leading-6 select-none [-moz-user-select:none] [-ms-user-select:none] [-o-user-select:none] [-webkit-user-select:none] [&::-webkit-inner-spin-button]:[-webkit-appearance:none] [&::-webkit-outer-spin-button]:[-webkit-appearance:none] [-moz-appearance:textfield]" x-on:input="$el.value = !isNumber($el.value) ? null : Math.round($el.value)" x-ref="spinbox" autocomplete="off" > <div class="flex flex-col-reverse font-medium"> <!-- Decrement input's value --> <button type="button" class="flex-1 px-1 leading-none transition-colors ease-linear duration-100 rounded-br-md text-center text-sm bg-gray-100 hover:bg-gray-200 text-gray-500 hover:text-gray-900 ring-1 ring-inset ring-gray-300 focus:outline-none focus:ring-inset focus:ring-2 focus:ring-primary-400 select-none [-moz-user-select:none] [-ms-user-select:none] [-o-user-select:none] [-webkit-user-select:none]" x-on:click="dec">-</button> <!-- Increment input's value --> <button type="button" class="flex-1 px-1 leading-none transition-colors ease-linear duration-100 rounded-tr-md text-center text-sm bg-gray-100 hover:bg-gray-200 text-gray-500 hover:text-gray-900 ring-1 ring-inset ring-gray-300 focus:outline-none focus:ring-inset focus:ring-2 focus:ring-primary-400 select-none [-moz-user-select:none] [-ms-user-select:none] [-o-user-select:none] [-webkit-user-select:none]" x-on:click="inc">+</button> </div> </div> </div> }
Profitez :)
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!