jQuery / Changer une balise HTML

Pour changer une balise HTML avec jQuery, créez un élément du type voulu puis remplacez l’ancien. Affecter tagName ne renomme pas l’élément. Pour garder les valeurs saisies et événements enfants, déplacez les véritables nœuds avant de supprimer leur conteneur, plutôt que de recopier une chaîne HTML.

Nom de balise et remplacement d’élément

Le DOM, Document Object Model, représente le document par des objets nœuds. Element.tagName est en lecture seule. .attr("tagName", "section") ne change pas le type ; .prop() ne permet pas non plus de réécrire cette propriété.

Créez un section avec document.createElement() puis remplacez div avec .replaceWith(). L’objet extérieur est nouveau, même si seul son nom semble différent. Les variables qui pointent sur div ne désignent pas automatiquement section.

L’exercice compare les états pendant cette substitution ; il ne constitue pas une conception sémantique complète d’une section autonome. Sur un site, choisissez section selon le sens du contenu et ses titres. Pour un simple changement visuel, class et CSS peuvent suffire.

Exemple complet conservant les enfants

Enregistrez tout en UTF-8 sous tag-demo.html et ouvrez le fichier dans le navigateur. Internet charge le fichier jQuery officiel 4.0.0. Aucun serveur ni paquet n’est nécessaire. Les comportements principaux ont été exécutés dans Edge sous Windows avec 3.7.1 et 4.0.0.

div contient texte, commentaire, champ et bouton. Le bouton a un gestionnaire direct, main une délégation, et div ses propres données jQuery et gestionnaire. Des compteurs séparés permettent de distinguer leur conservation.

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Replace a container tag, keep its children</title>
  <script src="https://code.jquery.com/jquery-4.0.0.min.js"></script>
  <style>
    body { font: 16px/1.6 sans-serif; margin: 20px; max-width: 760px; }
    button, input { font: inherit; max-width: 100%; box-sizing: border-box; }
    button { margin: 4px; }
    .card { border: 3px solid teal; padding: 16px; margin: 16px 0; }
    #result { white-space: pre-wrap; overflow-wrap: anywhere; }
  </style>
</head>
<body>
  <button type="button" id="change">Change div to section</button>
  <button type="button" id="reset">Reset</button>
  <main id="board"></main>
  <pre id="result" aria-live="polite"></pre>
  <script>
    $(function () {
      let original, originalInput, originalNodes;
      let childClicks = 0, wrapperClicks = 0, delegatedClicks = 0;
      function report() {
        const card = document.getElementById("card");
        const sameChildren = originalNodes.every((node, i) => card.childNodes[i] === node);
        $("#result").text(`tag=${card.localName}\nsame wrapper=${card === original}\nsame children=${sameChildren}\nsame input=${card.querySelector("input") === originalInput}\ninput=${originalInput.value}\nold data=${$(original).data("lesson")}\nchild=${childClicks}; wrapper=${wrapperClicks}; delegated=${delegatedClicks}`);
      }
      function reset() {
        $("#board").empty();
        const card = document.createElement("div");
        card.id = "card"; card.className = "card";
        card.setAttribute("aria-label", "Editable note");
        card.append(document.createTextNode("Keep this text. "), document.createComment("keep this comment"));
        const input = document.createElement("input");
        input.type = "text"; input.value = "First note"; input.setAttribute("aria-label", "Note");
        const button = document.createElement("button");
        button.type = "button"; button.className = "inspect"; button.textContent = "Inspect";
        card.append(input, button); document.getElementById("board").append(card);
        original = card; originalInput = input; originalNodes = Array.from(card.childNodes);
        childClicks = wrapperClicks = delegatedClicks = 0;
        $(card).data("lesson", "old wrapper").on("click", function () { wrapperClicks++; });
        $(button).on("click", function () { childClicks++; });
        $(input).on("input", report);
        $("#change").prop("disabled", false); report();
      }
      $("#board").on("click.tagLesson", ".inspect", function () { delegatedClicks++; report(); });
      $("#change").on("click", function () {
        const old = document.getElementById("card");
        const replacement = document.createElement("section");
        for (const name of ["id", "class", "aria-label"]) {
          if (old.hasAttribute(name)) replacement.setAttribute(name, old.getAttribute(name));
        }
        // Move existing children BEFORE jQuery removes the old wrapper.
        $(replacement).append($(old).contents());
        $(old).replaceWith(replacement);
        $(this).prop("disabled", true); report();
      });
      $("#reset").on("click", reset);
      reset();
    });
  </script>
</body>
</html>

Sortie initiale. same children vérifie identité et ordre du texte, du commentaire et des éléments.

tag=div
same wrapper=true
same children=true
same input=true
input=First note
old data=old wrapper
child=0; wrapper=0; delegated=0
  1. Modifiez First note. Même <em>Edited note</em> reste une valeur saisie, sans interprétation HTML.
  2. Un clic sur Inspect donne child=1, wrapper=1, delegated=1 : bouton, ancien div et main répondent chacun.
  3. Change div to section donne tag=section, same wrapper=false, same children=true et same input=true. La saisie reste, old data devient undefined.
  4. Un autre Inspect donne child=2, wrapper=1, delegated=2. Le gestionnaire enfant et la délégation restent actifs ; celui de div ne passe pas à section.
  5. Reset recrée div, valeur initiale et compteurs nuls. Il efface volontairement les modifications pour recommencer la comparaison.

Conservation et configuration explicite

Exécutez $(replacement).append($(old).contents()) avant le remplacement. .contents() comprend éléments, texte et commentaires enfants immédiats. append déplace ces objets existants et préserve ainsi la valeur actuelle du champ et ses gestionnaires enfants. .children() seul omettrait texte et commentaires hors éléments.

Ensuite, .replaceWith() retire le div vide et nettoie ses données et gestionnaires jQuery. Remplacer d’abord puis récupérer les enfants peut perdre leur état pendant ce nettoyage. Ce procédé ne conserve pas tout : focus, sélection de texte et références internes des plugins demandent une vérification séparée.

L’exemple copie explicitement id, class et aria-label. Les autres attributs ne sont pas transférés automatiquement. Tout copier peut conserver des attributs invalides, rôles obsolètes ou gestionnaires en ligne. Choisissez les attributs nécessaires et revoyez leur sens pour l’accessibilité. jQuery .data() et les attributs HTML data-* sont des stockages distincts.

.replaceWith() renvoie la collection jQuery d’origine supprimée, pas le nouvel élément. Utilisez replacement ou sélectionnez à nouveau l’élément courant. Si le comportement du conteneur est nécessaire, configurez-le explicitement sur le nouveau ou déléguez depuis un ancêtre stable.

Limites et diagnostic

Lire $(old).html() et appliquer .html() au nouveau réanalyse une chaîne. Une apparence identique ne conserve pas les objets enfants, leurs références, événements directs ou état de saisie actuel. Évitez également de construire du HTML avec des chaînes saisies par l’utilisateur.

Il s’agit de conteneurs HTML ordinaires, pas d’un convertisseur universel pour input ou img sans enfants, structures spéciales de tableaux, contrôles de formulaire, SVG ou éléments personnalisés. Vérifiez enfants autorisés et sens. Remplacer button par a change notamment rôle et fonctionnement clavier.

Si l’aspect change, inspectez CSS dépendant du type comme div.card et sélecteurs cherchant encore div. Gardez id unique et préservez étiquettes et ancres. Contrôlez événements directs, délégation, focus, sens accessible et rétablissement sur la page réelle.