11521 sujets

JavaScript, DOM et API Web HTML5

Bonsoir et joyeux Noël à tous!!

Je suis tombé aujourd'hui sur un problème que je n'avias encore jamais eu l'occasion de rencontrer et sur lequel j'aimerais vos avis.

Je crée en JavaScript des éléments <input id="xxx" class="yyyy" value="zzzz"> (il s'agit d'un formulaire de saisie dont la longueur est indéterminée, d'où la nécessité de générer les balises <input> en fonction du contexte)

J'ai l'impression que la propriété element.className ne fonctionne pas pour ces éléments et que l'on obtient une valeur "null" pour element.className, par contre on obtient la bonne valeur pour element.getAttribute('class').

Questions:
1) est-ce le résultat du réveillon d'hier soir ou est-ce que c'est bien ce qui se passe?
2) si c'est bien ce qui se passe, y a-t-il d'autres joyeusetés dans ce genre à prendre en compte?
3) y a-t-il un document qui fasse le point sur ce problème?

Note: j'utilise FireFox, c'est peut être dû à ce navigateur, mais de toute façon il faut que ça marche quel que soit le navigateur.
Modifié par PapyJP (25 Dec 2016 - 18:39)
Bonsoir.

J'utilise Mozilla Firefox 48 et je ne rencontre pas ce genre de problèmes... La classe de l'input, créé dynamiquement, est accessible via className...

D'après un vague souvenir que j'ai, les seules fois où il y avait des problèmes de ce type étaient dans le cas où la valeur de l'attribut était numérique...

Smiley smile
Merci de ta réponse.
Il me semble que la raison est que la classe n'est pas initialisée à la création de la balise <input>.
Dans une balise statique, même si on n'a pas mis de classe dans la balise, element.className retourne une chaîne vide. Dans une balise créée dynamiquement ça semble retourner "null".
Je teste demain et je rends compte du résultat.
Bonne soirée
Modifié par PapyJP (26 Dec 2016 - 21:24)
Hmm!! mon hypothèse d'hier soir n'est pas correcte.
Ce que j'ai écrit:
var numberInput = document.createElement('input');
numberInput.setAttribute('type', 'text');
numberInput.id = 'numbers_' + curIndex;
numberInput.className = '';
numberInput.setAttribute('onchange', 'saveTopics()');
console.log(numberInput);

SI je regarde le résultat dans la console, j'ai
<input id="numbers_00" class="" onchange="saveTopics()" type="text">

C'est à dire que
numberInput.className = '';

n'a pas été pris en compte

Pour contourner le problème, j'ai écrit une fonction
function setClassName(element, clName) {
    if(!element) return false;
    var className = getClassName(element);
    if(element.className) element.className = clName;
    else element.setAttribute('class', clName);
    return true;
}

En mettant une trace dans cette fonction, je constate effectivement que dans le cas des balises créées dynamiquement .className rend "null".

La version de FireFox que j'utilise est 50.10.0, c'est la plus récente semble-t-il.
Bonjour, PapyJP.

Ce que je ne comprends pas, c'est pourquoi vous voulez absolument faire des inputs avec des classes vides...

Si vous avez besoin d'un input avec classe, vous pouvez la rajouter après, non ?

Smiley confus
Zelena a écrit :
Bonjour, PapyJP.
Ce que je ne comprends pas, c'est pourquoi vous voulez absolument faire des inputs avec des classes vides...
Si vous avez besoin d'un input avec classe, vous pouvez la rajouter après, non ?
Smiley confus

La valeur de la class de <input> est vide initialement, ensuite elle est modifiée par des fonctions JS selon le contexte. La fonction saveTopics(this) effectue des contrôles sur la valeur entrée par l'utilisateur et en déduit la classe à attribuer à cet <input>.
C'est cette fonction qui a besoin de retrouver la valeur courante de .className pour la modifier.
Je constate que si la classe est vide, en fait il la considère comme "null" et pas comme "".
Cela doit vouloir dire que className est un objet et non une chaîne de caractères.
J'ai du mal à y voir clair dans les documentations.
En ce cas, pourquoi ne pas faire un test du genre :

if (!element_input.className)
  {
  element_input.className = 'la valeur que vous voulez';
  }
  else 'un autre traitement...'


Smiley ohwell

(PS : Je précise que je ne suis pas une spécialiste du Javascript...)
Modifié par Zelena (27 Dec 2016 - 12:29)
Bonjour,

JE n'ai jamais eu ce genre de problème avec des formulaires dynamiques, ni avec firefox, ni avec un autre navigateur.

Par contre j'ai deux commentaires à faire.

Le premier, ceci:
numberInput.setAttribute('onchange', 'saveTopics()');

Il vaudrait mieux ne pas utiliser setAttribute pour définir les gestionnaires d'évènement DOM0. Je ne suis pas sûr que ça marche correctement sur tous les navigateurs. JE me souviens m'être déjà fait avoir par quelque chose comme ça par le passé.
Pourquoi ne pas écrire directement numberInput.onchange = function() { ... }; ?

Deuxième commentaire: si ton formulaire est dynamique dans le genre bouton add qui ajoute une série de champs...
1 - La méthode DOM cloneNode est relativement sous-utilisée. C'est pourtant le moyen le plus facile de rajouter des éléments dynamiquement dès lors qu'on en a une première série. On la clone, puis on modifie seulement les attributs qui le nécéssitent. C'est plus simple que de tout créer de zéro à chaque ajout.
2 - Plutôt que de nommer ses champs valeur1, valeur2, valeur3, ... valeurN, le nommer valeur[] et adapter l'application serveur pour recevoir un tableau est aussi beaucoup plus simple. Par contre les id doivent toujours être uniques malheureusement, donc pour être accessible avec des labels il est toujours nécessaire de bidouiller.
QuentinC a écrit :

Il vaudrait mieux ne pas utiliser setAttribute pour définir les gestionnaires d'évènement DOM0. Je ne suis pas sûr que ça marche correctement sur tous les navigateurs. JE me souviens m'être déjà fait avoir par quelque chose comme ça par le passé.
Pourquoi ne pas écrire directement numberInput.onchange = function() { ... }; ?

Sur ce point, je pense qu'utiliser addEventListener est plus intéressant. L'"écouteur" est placé sur l'élément qui va contenir tous les inputs... il suffit après de vérifier, à chaque fois, si l'élément qui a déclenché l'événement est bien celui qui est désiré. Cela évite de multiplier les gestionnaires d'événements...

Smiley smile
QuentinC a écrit :
Bonjour,
Je n'ai jamais eu ce genre de problème avec des formulaires dynamiques, ni avec firefox, ni avec un autre navigateur.
Par contre j'ai deux commentaires à faire.
Le premier, ceci:
numberInput.setAttribute('onchange', 'saveTopics()');

Il vaudrait mieux ne pas utiliser setAttribute pour définir les gestionnaires d'évènement DOM0. Je ne suis pas sûr que ça marche correctement sur tous les navigateurs. JE me souviens m'être déjà fait avoir par quelque chose comme ça par le passé.
Pourquoi ne pas écrire directement numberInput.onchange = function() { ... }; ?

Pour la même raison: ces fichues balises dynamiques ne semblent pas accepter davantage .onchange que .className
QuentinC a écrit :

Deuxième commentaire: si ton formulaire est dynamique dans le genre bouton add qui ajoute une série de champs...
1 - La méthode DOM cloneNode est relativement sous-utilisée. C'est pourtant le moyen le plus facile de rajouter des éléments dynamiquement dès lors qu'on en a une première série. On la clone, puis on modifie seulement les attributs qui le nécessitent. C'est plus simple que de tout créer de zéro à chaque ajout.

Non, ce n'est pas plus simple dans mon cas.
On part d'un objet JavaScript qui contient n entrées.
Le programme fait une boucle sur ces entrées et génère autant de balises input que d'entrées dans la liste, ce qui permet à l'utilisateur de modifier éventuellement leur contenu, plus une si l'utilisateur veut en ajouter une. Le code en question est une simple boucle, sinon il faudrait faire du code pour générer la première, puis une boucle pour générer les autres par cloneNode, puis changer tous les attributs. cloneNode(false) est très bine pour générer un conteneur, mais un <input> n'est pas un conteneur.
QuentinC a écrit :

2 - Plutôt que de nommer ses champs valeur1, valeur2, valeur3, ... valeurN, le nommer valeur[] et adapter l'application serveur pour recevoir un tableau est aussi beaucoup plus simple. Par contre les id doivent toujours être uniques malheureusement, donc pour être accessible avec des labels il est toujours nécessaire de bidouiller.

Les valeurs entrées par l'utilisateur sont uniquement utilisées localement, c'est pourquoi ces balises n'ont pas d'attribut name. Le script récupère les entrées, les traite et met le résultat dans un <input type="hidden"> qui, lui, est effectivement envoyé au serveur.
En JavaScript, il est plus simple d'utiliser des id et d'adresser les éléments par document.getElementById.
Modifié par PapyJP (27 Dec 2016 - 19:38)
Zelena a écrit :

Sur ce point, je pense qu'utiliser addEventListener est plus intéressant. L'"écouteur" est placé sur l'élément qui va contenir tous les inputs... il suffit après de vérifier, à chaque fois, si l'élément qui a déclenché l'événement est bien celui qui est désiré. Cela évite de multiplier les gestionnaires d'événements...

Smiley smile

Pas dans mon cas: je tiens à vérifier l'élément qui vient d'être modifié, donc il est aussi simple de faire un onchange="checkItem(this)"
a écrit :
Sur ce point, je pense qu'utiliser addEventListener est plus intéressant. L'"écouteur" est placé sur l'élément qui va contenir tous les inputs... il suffit après de vérifier, à chaque fois, si l'élément qui a déclenché l'événement est bien celui qui est désiré. Cela évite de multiplier les gestionnaires d'événements...


Pas faux. Mais je voulais éviter d'introduire autre chose et en rester aux évènements DOM0 pour ne pas embrouiller inutilement.

a écrit :
Pour la même raison: ces fichues balises dynamiques ne semblent pas accepter davantage .onchange que .className


Tu n'aurais pas une page d'exemple ? Parce que ça me paraît quand même assez improbable.
A mon avis il y a forcément un truc qui rentre en conflit avec ton code: un plugin JQuery à la con qui fait des modif moisies pour corriger un prétendu bug de CSS encore plus moisi par exemple ?

Pas plus tard que l'autre jour, je suis tombé sur un machin merdique qui supprime des <tr> tout en conservant les <td>, invalidant le code HTML et le rendant inaccessible... tout ça pour une connerie de scroll sous chrome.
Donc dès qu'on utilise un code qu'on n'a pas écrit soi-même, méfiance.

Le seul vrai bug spécifique à firefox depuis que je fais du web que j'ai eu une fois, c'était un truc bien bizarre avec des labels.
Sinon en général, pour ce genre de manip, quand ça marche sur un navigateur, il n'y a pas de raison que ça ne marche pas sur un autre. Sauf si tu as un impératif de compatibilité IE6-8.


a écrit :
Non, ce n'est pas plus simple dans mon cas.
On part d'un objet JavaScript qui contient n entrées.
Le programme fait une boucle sur ces entrées et génère autant de balises input que d'entrées dans la liste, ce qui permet à l'utilisateur de modifier éventuellement leur contenu, plus une si l'utilisateur veut en ajouter une. Le code en question est une simple boucle, sinon il faudrait faire du code pour générer la première, puis une boucle pour générer les autres par cloneNode, puis changer tous les attributs. cloneNode(false) est très bine pour générer un conteneur, mais un <input> n'est pas un conteneur.


Je pensais à cloneNode(true), justement.

Mais en fait, d'après ce que tu décris là, ton formulaire n'est pas réellement dynamique. Une fois la page chargée, le nombre d'éléments est fixe, il n'évolue plus par la suite.
Dans ce cas tu te trompes de problème, c'est côté serveur, ou du moins avec un moteur de template, qu'il faudrait sans doute générer ce code HTML.

Pour moi, le formulaire deviendrait dynamique s'il y avait un bouton add qui ajoutait une ligne; ou mieux, qui ajoute une ligne automatiquement quand on appuie sur tab dans le dernier champ s'il n'est pas vide.

a écrit :
Les valeurs entrées par l'utilisateur sont uniquement utilisées localement, c'est pourquoi ces balises n'ont pas d'attribut name. Le script récupère les entrées, les traite et met le résultat dans un <input type="hidden"> qui, lui, est effectivement envoyé au serveur.


Dans ce cas je suis d'avis que tu te compliques inutilement la vie. Sauf bien sûr si tu n'as pas de choix sur ce que doit recevoir le serveur.

a écrit :
En JavaScript, il est plus simple d'utiliser des id et d'adresser les éléments par document.getElementById.


Oui et non. Apprenez à utiliser querySelector/querySelectorAll. Vous pourrez même vous passer de cette pieuvre de JQuery.
Ces deux fonctions marchent sur IE9+ donc il n'y a plus vraiment de raison de ne pas les utiliser...
Merci à tous pour vos réponses.
Je ne peux pas vous donner la page d'exemple, parce que je l'ai tellement chanstiquée que plus rien ne marche.
Il s'agit d'un truc très particulier, un formulaire qui permettra au propriétaire du site de mettre à jour une table de paramètres sans avoir à entrer dans du codage de données un peu complexe pour un non technicien.
Donc ça se fait de la façon suivante:
1) la page se charge et retrouve la table des paramètres qui se trouve dans un fichier .js
2) cette table de paramètres est alors présentée comme un formulaire, avec un paramètre par balise <input>
3) chaque fois qu'un paramètre est modifié, le script vérifie sa validité, change la classe de la balise <input> si nécessaire
4) quand on appuie sur le bouton OK, le script vérifie s'il reste un paramètre invalide, et si c'est OK il appelle un script PHP qui met à jour le fichier .js sur le site.

Jusque là, tout marchait très bien, mais il y avait un paramètre complexe que l'utilisateur ne savait pas modifier, et c'est là que les problèmes ont commencé.

Le paramètre en question est une chaine de caractères qui représente une liste de "choses" qui sont elles mêmes des listes. Il a la forme suivante:
[aaa,bbb,ccc],[ddd,ee],[ff],[gggg,hhhhhh,i,jjj]


Pour que l'utilisateur puisse s'y retrouver, je fais donc autant de <input> que de "sous-listes', dans le cas présent on aurait:

<input type="text" value="aaa,bbb,ccc">
<input type="text" value="ddd,ee">
<input type="text" value="ff">
<input type="text" value="gggg,hhhhh,i,jjj">
<input type="text" value="">

L'utilisateur peut modifier le contenu de chaque input. S'il veut ajouter un élément il le met dans le dernier <input> qui est vide, et s'il entre quelque chose dedans ça en génère un nouveau.

Lors de la validation finale, les informations sont regroupées pour donner à nouveau une chaine de caractères avec des [] et c'est cela qui est renvoyé au script PHP

Je sais bien qu'il y a plusieurs façons de faire ça (depuis 50 ans que je fais des programmes informatiques, je sais bien qu'il y a une grande façon de coder quelque chose) mais ce qui me préoccupe, ce n'est pas de trouver une façon de faire qui contournera le problème, c'est de comprendre quelque chose qui m'a échappé jusqu'à présent dans le fonctionnement de la manipulation du DOM avec JavaScript.
En gros, par essais et erreurs, je découvre que la manipulation des <input> dans le DOM ne semble pas être tout à fait la même selon qu'il s'agit d'une balise entrée en dur dans le HTML de la page, ou si ces <input> résultent d'une génération d'élément par JavaScript.

Par exemple si on génère quelque chose comme:
<input id="input1" type="text" value="">
et qu'on écrit

var element = document.getElementById('input1');
var param1 = element.value;

on devrait obtenir une chaine vide, mais en fait il semble que ce soit undefined.
J'écris alors

var element = document.getElementById('input1');
var param1 = element.value;
if(!param1) param1 = element.getAttribute('value');

mais je ne récupère toujours pas une chaîne vide, mais null.
J'ai fini par faire la fonction suivante:

function getValue(element) {
    if(!element) return '';
        var value = element.value;
        if(value) {
            if(value == null) return '';
            return value;
        }
        if(element.nodeName.toLowerCase() == 'textarea') return element.innerHTML;
        value = element.getAttribute('value');
        if(!value) return '';
        if(value == null) return '';
        return value;
}
.....
.....
var element = document.getElementById('input1');
//var param1 = element.value;
param1 = getValue(element);

Ce qui est une usine à gaz!!!

Apparemment vous n'avez pas rencontré ce genre de situation, qui me rend perplexe!!!

Note: en ce qui concerne className, qui était l'objet initial de ce fil, il semble qu'en utilisant classList on n'ait pas ce genre de problème: classList est bien initialisé à un tableau vide si la propriété class n'est pas définie ou mise à une chaine vide.
Mais j'ai fait tellement de changements et de tests que je ne suis pas sûr que ce soit tout à fait correct.
Javascript a tendance à faire des conversions et assez souvent...

Je crée un input avec :
var input = document.createElement('input');


Je mets la valeur de input.value dans une variable et j'obtiens la chaîne vide...

Donc c'est vrai... je ne vois pas où est votre souci...

Smiley sweatdrop
Un truc auquel je pense: est-ce que c'est bien un élément input qui est créé ?
Moyen simple pour le vérifier: alert(element) ==> [ Object InputHTMLElement ].

Ca peut paraître idiot, mais une faute de frappe ou de casse est si vite arrivée...

Parce que sinon non, vraiment, je ne vois pas non plus.
Merci à tous
Je n'ai pas trouvé le pourquoi, qui reste pour moi un mystèr.
J'ai fait des tests avec une page de test beaucoup plus simple qui ne donnent pas les mêmes résultats, je pense que ma page est trop compliquée et qu'il doit y avoir des bugs ailleurs qui créent des effets de bord.

J'abandonne pour le moment, je vais me tourner vers une autre façon d'entrer les données, ce qui évitera le problème.
De toute façon mon utilisateur n'était pas vraiment convaincu par cette approche.
Modifié par PapyJP (29 Dec 2016 - 23:21)