11521 sujets

JavaScript, DOM et API Web HTML5

Bonjour à tous.

Je viens avec une question pour expert.
J'ai un formulaire HTML simple avec un champ texte équipé d'une datalist alimentée par une table de données de plus de 13000 lignes.
Sur mon ordi tout va bien, mais quand je teste ce formulaire sur mon iPhone les 13000 lignes sont un problème : il faut plusieurs secondes pour faire apparaitre la liste lorsque je la déroule.
Si je saisis quelques lettres dans le champ texte avant de dérouler la liste, ça la diminue, donc ça va, mais je ne peux pas empêcher un utilisateur de dérouler la liste sans saisir des lettres. Donc, je cherche un moyen de résoudre ce problème (et je suis ouvert à toutes les suggestions).

Pour tenter de le résoudre, j'ai essayé cette idée :
- Je double mon champ texte principal d'un deuxième qui n'est pas lié à la liste.
- En css, je masque le champ texte principal (display none, width 0 ou autre) pour forcer l'utilisateur à saisir d'abord des lettres dans le champ texte secondaire.
- Avec un onchange et une fonction JS, je transfert la valeur du champ secondaire au champ principal, je masque le champ secondaire et je fais apparaitre le champ principal (avec la datalist). Comme ça, l'utilisateur se retrouve avec le champ principal et la liste portée par la datalist, mais restreinte par le texte qu'il a été forcé de saisir d'abord.

Précision importante : comme il s'agit de forcer la diminution du volume de la liste, je conditionne l'essentiel du code de ma fonction JS à un minimum de 3 caractères saisis. Donc, si l'utilisateur efface des caractères dans le champ principal, ce dernier est de nouveau masqué et le champ secondaire réapparait.

Et ainsi de suite...

Ca fonctionne, mais croyez le croyez le pas, quand le champ principal apparait pour la deuxième fois, le contenu de la datalist disparait littéralement !

Exemple :
- L'utilisateur tape "bif" dans le champ secondaire => le champ principal apparait
- Il efface le f => "bi" => le champ secondaire réapparait
- Il tape un d => "bid" => le champ principal réapparait... et la liste a disparu

Comme la liste est très longue, il faut du temps à mon navigateur pour la dissoudre. Donc, si j'affiche l'outil de développement pour voir le code HTML de ma page, je vois disparaitre la datalist ligne par ligne sous mes yeux ! Smiley eek

Une idée pour résoudre mon problème ? D'avance merci

Le PHP qui génère mes champs texte et la datalist :
echo "<label>Produit</label><input type=\"text\" name=\"produit2\" id=\"produit2\" maxlenght=300 value=\"\" autocomplete=\"off\" onchange=\"active_liste('#produit2', '#produit');\">";
echo "<input type=\"text\" name=\"produit\" id=\"produit\" maxlenght=300 value=\"\" autocomplete=\"off\" onchange=\"active_liste('#produit2', '#produit'); verif_produit('#produit');\" list=\"produits\">";	// style=\"display: none;\"
echo "<datalist id=\"produits\">";
	$req = "SELECT Nom FROM Produit ORDER BY Nom;";
	$resp = $connexion->query($req);
	while($lp = $resp->fetch_object())
		echo "<option>".$lp->Nom."</option>";
echo "</datalist>";


La fonction JS :

function active_liste(champ1, champ2)
{
	if($(champ1).css('width') == '690px')
		$(champ2).val($(champ1).val());
	else
		$(champ1).val($(champ2).val());

	if($(champ1).val().length >= 3)
	{
 		$(champ1).css('width', '0');
 		$(champ2).css('width', '690px');
	}
	else
	{
 		$(champ1).css('width', '690px');
 		$(champ2).css('width', '0');
	}
}

Modifié par louisss (07 Mar 2023 - 13:40)
PS : dans votre code php vous mettez des antislashs de partout pour échapper les doubles quotes.
echo "<datalist id=\"produits\">";

Vous devriez plutôt utiliser des simples quotes :
echo '<datalist id="produits">';

Tout de suite c'est beaucoup plus clair !

Et aussi, du javascript qui repose sur du style de ce type est un code fragile :
if($(champ1).css('width') == '690px')

Au pire ce serait mieux que le script se repose sur une classe qui servirait d'état. Le style devrait être à part.

Enfin, les éléments de style ne devraient pas être codés en dur (690px). Mais bon, là, je commence à m'éloigner beaucoup de la demande initiale.
Modifié par Olivier C (08 Mar 2023 - 09:07)
Bonjour Olivier.

Merci pour ces premiers éléments.

Pour les doubles quotes, c'est noté, facile.

Mon code JS est fragile, c'est clair. En fait, je cherche d'abord une solution qui fonctionne pour résoudre mon problème de lenteur sur iOS. Ensuite, j'améliorerai mon code.

Je n'avais pas fait de préchargement. J'ai donc essayé en espérant que ça résoudrait le problème, mais sans être complètement convaincu car j'ignore la réponse à cette question : quand je veux faire apparaitre la datalist dans le champ texte, elle apparait lentement parce qu'elle n'est pas encore complètement chargée ou parce qu'elle est simplement trop lourde pour le navigateur ?

J'ai essayé de mettre en place le préchargement avec ce code :

Dans le <head> :

<link rel="preload" href="produits.php" as="script">


Dans la page :

echo '<label for="produit">Produit</label><input type="text" name="produit" id="produit" maxlenght=300 value="" autocomplete="off"  list="produits">';
echo '<datalist id="produits">';
	include("produits.php");
echo '</datalist>';


Et dans le nouveau fichier produits.php :

$req = "SELECT Nom FROM Produit ORDER BY Nom;";
$resp = $connexion->query($req);
while($lp = $resp->fetch_object())
	echo '<option>'.$lp->Nom.'</option>';


Ca ne résout pas le problème.
J'ai vérifié, produits.php génère bien les bonnes data.
Le datalist retrouve bien le contenu attendu.

Précision : l'inspecteur web me donne un warning : "The resource produits.php was preloaded using link preload but not used within a few seconds from the window's load event. Please make sure it wasn't preloaded for nothing."

Visiblement, je ne m'y prend pas bien...
Qu'est-ce qui ne va pas dans mon preload ?
Dans le preload vous ne pouvez pas mettre un fichier tel que PHP, il faut forcément que ce soit un fichier statique.
Bonjour Olivier.

a écrit :
Dans le preload vous ne pouvez pas mettre un fichier tel que PHP, il faut forcément que ce soit un fichier statique.


Ok, mais alors du coup je ne comprends plus en quoi un preload pourrait m'aider à résoudre mon problème.
L'idée, c'était pas de préloader la datalist des 13000 produits ?
Oui bien sûr, mais là vous êtes côté frontend, pas côté backend, il vous faut donc des données lisibles par le navigateur.

Un fichier JSON ce serait pas mal, vous pouvez le créer via PHP au moment de l'appel de la page par exemple.
Modifié par Olivier C (13 Mar 2023 - 10:12)
Modérateur
Bonjour,

Pourquoi ne pas avoir juste un seul input et simplement supprimer la valeur de son attribut list si sa valeur contient moins de n caractères ?

Exemple minimal :
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<?php
echo '<label>Produit : ';
echo '<input name="produit" id="produit" maxlenght=300 autocomplete="off" oninput="active_liste()">';
echo '</label>';
echo '<datalist id="produits">';
	// les lignes qui suivent sont à remplacer par une requête à la base de données
	$k=0;
	while($k<13000)
		echo "<option>".md5($k++)."</option>";
echo "</datalist>";
?>
<script>
function active_liste()
{
	let n=3,e=document.getElementById("produit");
	if(e.value.length<n) e.removeAttribute("list");
	else e.setAttribute("list","produits");
}
</script>
</body>
</html>

Amicalement,
Modifié par parsimonhi (13 Mar 2023 - 13:43)
Bonjour.

Olivier C a écrit :
Oui bien sûr, mais là vous êtes côté frontend, pas côté backend, il vous faut donc des données lisibles par le navigateur.

Un fichier JSON ce serait pas mal, vous pouvez le créer via PHP au moment de l'appel de la page par exemple.


J'ai essayé mais ça aggrave le problème plutôt que de le résoudre, car la génération d'un fichier JSON de 13000 lignes à l'appel de la page est terriblement long...
Et de fait, je ne fais que rajouter un intermédiaire là : au lieu d'envoyer les données de ma bdd MySQL dans la datalist, je les fais passer par un fichier externe. Rien qu'en théorie je ne vois pas comment ça peut améliorer les choses...

Qu'est-ce qui m'a échappé ?
parsimonhi a écrit :
Pourquoi ne pas avoir juste un seul input et simplement supprimer la valeur de son attribut list si sa valeur contient moins de n caractères ?


Effectivement parsimonhi, c'est nettement plus simple comme ça. Mais le problème reste exactement le même : lorsque mon champ texte "reçoit" la datalist dans son attribut list : le contenu de la datalist disparait !

C'est dingue, non ???
Ooooooooookaaaaaay.

A force de chercher, j'ai fini par trouver l'origine de cette surprenante disparition de ma datalist : visiblement, c'est lié au fait que mon champ text et ma datalist se suivent...

Si je fais ça...

echo '<label for="produit">Produit</label><input type="text" name="produit" id="produit" maxlenght=300 value="" autocomplete="off" list="produits">';
$req = "SELECT Nom FROM Produit ORDER BY Nom;";
$resp = $connexion->query($req);
echo '<datalist id="produits">';
	while($lp = $resp->fetch_object())
		echo '<option value='".$lp->Nom."'>';
echo '</datalist>';

... me fait de saisir quelques caractères, puis de sortir de champ, puis d'y revenir fait disparaitre le contenu de la datalist.

Par contre, si je fais ça :

echo '<label for="produit">Produit</label><input type="text" name="produit" id="produit" maxlenght=300 value="" autocomplete="off" list="produits">';
echo '<br>';
$req = "SELECT Nom FROM Produit ORDER BY Nom;";
$resp = $connexion->query($req);
echo '<datalist id="produits">';
	while($lp = $resp->fetch_object())
		echo '<option value='".$lp->Nom."'>';
echo '</datalist>';

Le problème est résolu !

La seule différence entre ces deux codes, c'est le <br> placé entre le input et la datalist.
Faut le savoir !
Je précise que je code sous MacOS et que je constate le problème avec les dernières versions de Safari, Chrome, Firefox et Edge.

Maintenant reste la problématique de départ : sous iOS, la liste s'ouvre lentement si l'utilisateur ne commence pas par saisir quelques lettres pour la réduire. Et la proposition de parsimonhi a l'air de bien fonctionner. Merci !