Création d'un nouvel élément DOM à partir d'une chaîne HTML à l'aide de méthodes DOM intégrées ou d'un prototype

622

J'ai une chaîne HTML représentant un élément: '<li>text</li>'. Je voudrais l'ajouter à un élément du DOM (un uldans mon cas). Comment puis-je faire cela avec Prototype ou avec les méthodes DOM?

(Je sais que je pourrais le faire facilement dans jQuery, mais malheureusement nous n'utilisons pas jQuery.)

Omer Bokhari
la source
Désolé, qu'essayez-vous d'accomplir exactement? Chaîne HTML -> élément dom?
Crescent Fresh
36
Il est regrettable que ces solutions soient si indirectes. Je souhaite que le comité des normes précise quelque chose de similaire comme:var nodes = document.fromString("<b>Hello</b> <br>");
Sridhar Sarnobat
1
J'ai rencontré un problème avec ce qui précède, car j'avais des attributs à transmettre et je n'ai pas eu envie de les analyser: "<table id = '5ccf9305-4b10-aec6-3c55-a6d218bfb107' class = 'data-table affichage de la bordure de ligne 'cellspacing =' 0 'width =' 100% '> </table> "donc, j'ai simplement utilisé: $ (" <table id =' 5ccf9305-4b10-aec6-3c55-a6d218bfb107 'class =' ​​data -table affichage de bordure de ligne 'cellspacing =' 0 'width =' 100% '> </table> ")
stevenlacerda

Réponses:

818

Remarque: la plupart des navigateurs actuels prennent en charge les <template>éléments HTML , qui offrent un moyen plus fiable de transformer la création d'éléments à partir de chaînes. Voir la réponse de Mark Amery ci-dessous pour plus de détails .

Pour les navigateurs plus anciens et node / jsdom : (qui ne prend pas encore en charge les <template>éléments au moment de la rédaction), utilisez la méthode suivante. C'est la même chose que les bibliothèques utilisent pour obtenir des éléments DOM à partir d'une chaîne HTML ( avec un travail supplémentaire pour IE pour contourner les bogues avec son implémentation de innerHTML):

function createElementFromHTML(htmlString) {
  var div = document.createElement('div');
  div.innerHTML = htmlString.trim();

  // Change this to div.childNodes to support multiple top-level nodes
  return div.firstChild; 
}

Notez que contrairement aux modèles HTML, cela ne fonctionnera pas pour certains éléments qui ne peuvent pas légalement être des enfants de a <div>, tels que <td>s.

Si vous utilisez déjà une bibliothèque, je vous recommande de vous en tenir à la méthode approuvée par la bibliothèque pour créer des éléments à partir de chaînes HTML:

Croissant frais
la source
1
Comment définir innerHTML sur le div créé en utilisant jQuery sans utiliser cette affectation innerHTML non sécurisée (div.innerHTML = "some value")
Shivanshu Goyal
12
Le nom de la fonction createElementFromHTML est trompeur car div.firstChildretourne un Nodequi n'est pas un HTMLElementexemple ne peut pas node.setAttribute. Pour créer un Elementretour div.firstElementChildde la fonction à la place.
Semmel
Merci, l' <div>habillage du HTML que j'ai ajouté .innerHTMLme gênait. Je n'ai jamais pensé à utiliser .firstChild.
Ivan
J'essaie d'analyser un SVG à l'intérieur du créé divet et la sortie est [object SVGSVGElement]alors que le journal de la console me donne l'élément DOM correct. Qu'est-ce que je fais mal?
ed1nh0
1
J'utiliserais firstElementChild au lieu de firstChild (voir w3schools.com/jsref/prop_element_firstelementchild.asp ), car s'il y a de l'espace devant ou à la fin du modèle, le firstChild retournerait un textNode vide
Chris Panayotoff
344

HTML 5 a introduit l' <template>élément qui peut être utilisé à cet effet (comme décrit maintenant dans les spécifications WhatWG et les documents MDN ).

Un <template>élément est utilisé pour déclarer des fragments de HTML qui peuvent être utilisés dans des scripts. L'élément est représenté dans le DOM comme un HTMLTemplateElementqui a une .contentpropriété de DocumentFragmenttype, pour donner accès au contenu du modèle. Cela signifie que vous pouvez convertir une chaîne HTML à des éléments DOM en définissant le innerHTMLd'un <template>élément, pour atteindre ensuite dans la templatede » .contentla propriété.

Exemples:

/**
 * @param {String} HTML representing a single element
 * @return {Element}
 */
function htmlToElement(html) {
    var template = document.createElement('template');
    html = html.trim(); // Never return a text node of whitespace as the result
    template.innerHTML = html;
    return template.content.firstChild;
}

var td = htmlToElement('<td>foo</td>'),
    div = htmlToElement('<div><span>nested</span> <span>stuff</span></div>');

/**
 * @param {String} HTML representing any number of sibling elements
 * @return {NodeList} 
 */
function htmlToElements(html) {
    var template = document.createElement('template');
    template.innerHTML = html;
    return template.content.childNodes;
}

var rows = htmlToElements('<tr><td>foo</td></tr><tr><td>bar</td></tr>');

Notez que des approches similaires qui utilisent un élément de conteneur différent tel qu'un adiv ne fonctionnent pas tout à fait. HTML a des restrictions sur les types d'éléments autorisés à exister à l'intérieur de quels autres types d'éléments; par exemple, vous ne pouvez pas mettre un en tdtant qu'enfant direct d'un div. Cela provoque la disparition de ces éléments si vous essayez de définir le innerHTMLde a divpour les contenir. Puisque les <template>s n'ont pas de telles restrictions sur leur contenu, cette lacune ne s'applique pas lors de l'utilisation d'un modèle.

Cependant, templaten'est pas pris en charge dans certains anciens navigateurs. En janvier 2018, Puis-je utiliser ... estime que 90% des utilisateurs dans le monde utilisent un navigateur qui prend en charge l' templateart . En particulier, aucune version d'Internet Explorer ne les prend en charge; Microsoft n'a pas implémenté le templatesupport avant la sortie d'Edge.

Si vous avez la chance d'écrire du code uniquement destiné aux utilisateurs de navigateurs modernes, allez-y et utilisez-les dès maintenant. Sinon, vous devrez peut-être attendre un certain temps pour que les utilisateurs se rattrapent.

Mark Amery
la source
9
belle approche moderne. Testé sur Opera, oublions IE
Adi Prasetyo
2
Il s'agit d'une approche efficace et très propre; cependant (au moins dans Chrome 50), cela interrompt la gestion des balises de script. En d'autres termes, l'utilisation de cette méthode pour créer une balise de script, puis l'ajouter au document (corps ou tête) n'entraîne pas l'évaluation de la balise et empêche donc l'exécution du script. (Cela peut être
voulu
1
CHARMANT! vous pouvez même rechercher des éléments en faisant quelque chose comme: template.content.querySelector ("img");
Roger Gajraj
1
@Crescent Fresh: Très bien, j'avais l'impression que vous vouliez que votre réponse disparaisse puisque vous avez dit qu'elle n'était plus pertinente, mes excuses. Un bon compromis serait d'ajouter une déclaration à votre réponse pointant les lecteurs vers celle-ci, comme cela a maintenant été discuté sur les méta - espérons simplement que la guerre d'édition ne reprendra pas à partir de là.
BoltClock
2
Il y a un problème avec ça. Si vous avez une étiquette avec des espaces à l'intérieur (!) Au début ou à la fin, ils seront supprimés! Ne vous méprenez pas, il ne s'agit pas des espaces supprimés par html.trimmais par l'analyse interne du paramètre innerHTML. Dans mon cas, il supprime les espaces importants faisant partie d'un textNode. :-(
e-motiv
110

Utilisez insertAdjacentHTML () . Il fonctionne avec tous les navigateurs actuels, même avec IE11.

var mylist = document.getElementById('mylist');
mylist.insertAdjacentHTML('beforeend', '<li>third</li>');
<ul id="mylist">
 <li>first</li>
 <li>second</li>
</ul>

Christian d'Heureuse
la source
9
Tout d'abord, insertAdjacentHTMLfonctionne avec tous les navigateurs depuis IE 4.0.
Jonas Äppelgran
6
Deuxièmement, c'est génial! Un gros avantage par rapport à innerHTML += ...est que les références aux éléments précédents sont toujours intactes en utilisant cette méthode.
Jonas Äppelgran
7
Troisièmement, les valeurs possibles pour le premier argument est: beforebegin, afterbegin, beforeend, afterend. Voir l'article MDN.
Jonas Äppelgran
meilleure réponse pour moi
Jordan
4
Dommage qu'il ne retourne pas le HTML inséré en tant qu'élément
Mojimi
30

Les implémentations DOM plus récentes ont range.createContextualFragment, ce qui fait ce que vous voulez d'une manière indépendante du framework.

Il est largement pris en charge. Pour être sûr cependant, vérifiez sa compatibilité dans le même lien MDN, car il va changer. En mai 2017, c'est tout:

Feature         Chrome   Edge   Firefox(Gecko)  Internet Explorer   Opera   Safari
Basic support   (Yes)    (Yes)  (Yes)           11                  15.0    9.1.2
kojiro
la source
3
Notez que cela présente des inconvénients similaires à la définition innerHTMLd'un div; certains éléments, comme tds, seront ignorés et n'apparaîtront pas dans le fragment résultant.
Mark Amery
"Il existe des rapports selon lesquels Safari pour ordinateur de bureau a pris en charge à un moment donné Range.createContextualFragment (), mais il n'est pas pris en charge au moins dans Safari 9.0 et 9.1." (Lien MDN dans la réponse)
akauppi
27

Pas besoin de réglage, vous avez une API native:

const toNodes = html =>
    new DOMParser().parseFromString(html, 'text/html').body.childNodes[0]
math2001
la source
9
Cela souffre du même inconvénient majeur que la réponse acceptée - cela gênera HTML comme <td>text</td>. En effet, DOMParsertente d'analyser un document HTML complet et tous les éléments ne sont pas valides en tant qu'éléments racine d'un document.
Mark Amery
4
-1 car il s'agit d'un doublon d' une réponse antérieure qui a explicitement souligné l'inconvénient mentionné dans mon commentaire ci-dessus.
Mark Amery
20

Pour certains fragments html comme <td>test</td>, div.innerHTML, DOMParser.parseFromString et range.createContextualFragment (sans le bon contexte) les solutions mentionnées dans d'autres réponses ici, ne créeront pas l' <td>élément.

jQuery.parseHTML () les gère correctement (j'ai extrait la fonction parseHTML de jQuery 2 dans une fonction indépendante qui peut être utilisée dans des bases de code non jquery).

Si vous ne prenez en charge que Edge 13+, il est plus simple d'utiliser simplement la balise de modèle HTML5:

function parseHTML(html) {
    var t = document.createElement('template');
    t.innerHTML = html;
    return t.content.cloneNode(true);
}

var documentFragment = parseHTML('<td>Test</td>');
Munawwar
la source
16

Voici une façon simple de le faire:

String.prototype.toDOM=function(){
  var d=document
     ,i
     ,a=d.createElement("div")
     ,b=d.createDocumentFragment();
  a.innerHTML=this;
  while(i=a.firstChild)b.appendChild(i);
  return b;
};

var foo="<img src='//placekitten.com/100/100'>foo<i>bar</i>".toDOM();
document.body.appendChild(foo);
William malo
la source
4
@MarkAmery la différence ici est qu'il utilise un fragment pour permettre à plusieurs éléments racine d'être ajoutés dans le DOM, ce qui est un avantage supplémentaire. Si seulement William avait mentionné que c'était sa réponse ...
Koen.
10

Vous pouvez créer des nœuds DOM valides à partir d'une chaîne en utilisant:

document.createRange().createContextualFragment()

L'exemple suivant ajoute un élément de bouton dans la page en prenant le balisage d'une chaîne:

let html = '<button type="button">Click Me!</button>';
let fragmentFromString = function (strHTML) {
  return document.createRange().createContextualFragment(strHTML);
}
let fragment = fragmentFromString(html);
document.body.appendChild(fragment);

GibboK
la source
5
Même si cela crée un DocumentFragmentobjet, il souffre toujours - à ma grande surprise - du même défaut que la réponse acceptée: si vous le faites document.createRange().createContextualFragment('<td>bla</td>'), vous obtenez un fragment qui contient juste le texte 'bla' sans l' <td>élément. Ou du moins, c'est ce que j'observe dans Chrome 63; Je n'ai pas fouillé dans la spécification pour déterminer si c'est le bon comportement ou non.
Mark Amery
9

Avec Prototype, vous pouvez également faire:

HTML:

<ul id="mylist"></ul>

JS:

$('mylist').insert('<li>text</li>');
Pablo Borowicz
la source
Est-ce jQuery? ou JS?
Jeya Suriya Muthumari
1
Ceci utilise jQuery et non des méthodes DOM intégrées comme op mentionné.
Juan Hurtado
1
@JuanHurtado ceci utilise PrototypeJs comme OP l'a demandé il y a presque 11 ans;)
Pablo Borowicz
8

J'utilise cette méthode (fonctionne dans IE9 +), même si elle n'analysera pas <td>ou d'autres enfants directs du corps invalides:

function stringToEl(string) {
    var parser = new DOMParser(),
        content = 'text/html',
        DOM = parser.parseFromString(string, content);

    // return element
    return DOM.body.childNodes[0];
}

stringToEl('<li>text</li>'); //OUTPUT: <li>text</li>
usrbowe
la source
5

Pour améliorer en outre l' extrait .toDOM () utile que nous pouvons trouver à différents endroits, nous pouvons désormais utiliser en toute sécurité les backticks ( littéraux de modèle ).

Nous pouvons donc avoir des guillemets simples et doubles dans le foo déclaration html.

Cela se comporte comme des heredocs pour ceux qui connaissent le terme.

Cela peut être amélioré en outre avec des variables, pour rendre le modèle complexe:

Les littéraux de modèle sont entourés par le caractère back-tick ( ) (accent grave) au lieu de guillemets doubles ou simples. Les littéraux de modèle peuvent contenir des espaces réservés. Ceux-ci sont indiqués par le signe dollar et les accolades ($ {expression}). Les expressions dans les espaces réservés et le texte entre eux sont passés à une fonction. La fonction par défaut concatène simplement les parties en une seule chaîne. S'il y a une expression précédant le littéral du modèle (tag ici), cela s'appelle un "template taggé". Dans ce cas, l'expression de balise (généralement une fonction) est appelée avec le littéral de modèle traité, que vous pouvez ensuite manipuler avant de sortir. Pour échapper à un back-tick dans un modèle littéral, mettez une barre oblique inverse \ avant le back-tick.

String.prototype.toDOM=function(){
  var d=document,i
     ,a=d.createElement("div")
     ,b=d.createDocumentFragment()
  a.innerHTML = this
  while(i=a.firstChild)b.appendChild(i)
  return b
}

// Using template litterals
var a = 10, b = 5
var foo=`
<img 
  onclick="alert('The future start today!')"   
  src='//placekitten.com/100/100'>
foo${a + b}
  <i>bar</i>
    <hr>`.toDOM();
document.body.appendChild(foo);
img {cursor: crosshair}

Alors, pourquoi ne pas utiliser directement .innerHTML +=? Ce faisant, l'ensemble du DOM est recalculé par le navigateur, c'est beaucoup plus lent.

https://caniuse.com/template-literals

NVRM
la source
4

J'ai ajouté un Documentprototype qui crée un élément à partir d'une chaîne:

Document.prototype.createElementFromString = function (str) {
    const element = new DOMParser().parseFromString(str, 'text/html');
    const child = element.documentElement.querySelector('body').firstChild;
    return child;
};
Raza
la source
Notez que - comme indiqué ailleurs sur cette page - cela ne fonctionnera pas pour l' tdart.
Mark Amery
3

HTML5 et ES6

<template>

Démo

"use strict";

/**
 *
 * @author xgqfrms
 * @license MIT
 * @copyright xgqfrms
 * @description HTML5 Template
 * @augments
 * @example
 *
 */

/*

<template>
    <h2>Flower</h2>
    <img src="https://www.w3schools.com/tags/img_white_flower.jpg">
</template>


<template>
    <div class="myClass">I like: </div>
</template>

*/

const showContent = () => {
    // let temp = document.getElementsByTagName("template")[0],
    let temp = document.querySelector(`[data-tempalte="tempalte-img"]`),
        clone = temp.content.cloneNode(true);
    document.body.appendChild(clone);
};

const templateGenerator = (datas = [], debug = false) => {
    let result = ``;
    // let temp = document.getElementsByTagName("template")[1],
    let temp = document.querySelector(`[data-tempalte="tempalte-links"]`),
        item = temp.content.querySelector("div");
    for (let i = 0; i < datas.length; i++) {
        let a = document.importNode(item, true);
        a.textContent += datas[i];
        document.body.appendChild(a);
    }
    return result;
};

const arr = ["Audi", "BMW", "Ford", "Honda", "Jaguar", "Nissan"];

if (document.createElement("template").content) {
    console.log("YES! The browser supports the template element");
    templateGenerator(arr);
    setTimeout(() => {
        showContent();
    }, 0);
} else {
    console.error("No! The browser does not support the template element");
}
@charset "UTf-8";

/* test.css */

:root {
    --cololr: #000;
    --default-cololr: #fff;
    --new-cololr: #0f0;
}

[data-class="links"] {
    color: white;
    background-color: DodgerBlue;
    padding: 20px;
    text-align: center;
    margin: 10px;
}
<!DOCTYPE html>
<html lang="zh-Hans">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Template Test</title>
    <!--[if lt IE 9]>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv.js"></script>
    <![endif]-->
</head>

<body>
    <section>
        <h1>Template Test</h1>
    </section>
    <template data-tempalte="tempalte-img">
        <h3>Flower Image</h3>
        <img src="https://www.w3schools.com/tags/img_white_flower.jpg">
    </template>
    <template data-tempalte="tempalte-links">
        <h3>links</h3>
        <div data-class="links">I like: </div>
    </template>
    <!-- js -->
</body>

</html>

xgqfrms-gildata
la source
2

Tard mais juste comme une note;

Il est possible d'ajouter un élément trivial à l'élément cible en tant que conteneur et de le supprimer après utilisation.

// Testé sur chrome 23.0, firefox 18.0, c'est-à-dire 7-8-9 et opéra 12.11.

<div id="div"></div>

<script>
window.onload = function() {
    var foo, targetElement = document.getElementById('div')
    foo = document.createElement('foo')
    foo.innerHTML = '<a href="#" target="_self">Text of A 1.</a> '+
                    '<a href="#" onclick="return !!alert(this.innerHTML)">Text of <b>A 2</b>.</a> '+
                    '<hr size="1" />'
    // Append 'foo' element to target element
    targetElement.appendChild(foo)

    // Add event
    foo.firstChild.onclick = function() { return !!alert(this.target) }

    while (foo.firstChild) {
        // Also removes child nodes from 'foo'
        targetElement.insertBefore(foo.firstChild, foo)
    }
    // Remove 'foo' element from target element
    targetElement.removeChild(foo)
}
</script>
K-Gun
la source
2

Solution la plus rapide pour rendre le DOM à partir d'une chaîne:

let render = (relEl, tpl, parse = true) => {
  if (!relEl) return;
  const range = document.createRange();
  range.selectNode(relEl);
  const child = range.createContextualFragment(tpl);
  return parse ? relEl.appendChild(child) : {relEl, el};
};

Et ici vous pouvez vérifier les performances de la manipulation DOM React vs JS natif

Maintenant, vous pouvez simplement utiliser:

let element = render(document.body, `
<div style="font-size:120%;line-height:140%">
  <p class="bold">New DOM</p>
</div>
`);

Et bien sûr, dans un futur proche, vous utiliserez des références de la mémoire car var "element" est votre nouveau DOM créé dans votre document.

Et rappelez-vous que "innerHTML =" est très lent: /

Paweł Kołodziejczak
la source
1

Voici mon code, et cela fonctionne:

function parseTableHtml(s) { // s is string
    var div = document.createElement('table');
    div.innerHTML = s;

    var tr = div.getElementsByTagName('tr');
    // ...
}
Wen Qi
la source
1
Échoue si l'élément à créer est lui-même une table.
Mark Amery
0

Pour le diable, je pensais que je partagerais cela sur une approche compliquée mais simple que j'ai trouvée ... Peut-être que quelqu'un trouvera quelque chose d'utile.

/*Creates a new element - By Jamin Szczesny*/
function _new(args){
    ele = document.createElement(args.node);
    delete args.node;
    for(x in args){ 
        if(typeof ele[x]==='string'){
            ele[x] = args[x];
        }else{
            ele.setAttribute(x, args[x]);
        }
    }
    return ele;
}

/*You would 'simply' use it like this*/

$('body')[0].appendChild(_new({
    node:'div',
    id:'my-div',
    style:'position:absolute; left:100px; top:100px;'+
          'width:100px; height:100px; border:2px solid red;'+
          'cursor:pointer; background-color:HoneyDew',
    innerHTML:'My newly created div element!',
    value:'for example only',
    onclick:"alert('yay')"
}));
JxAxMxIxN
la source
0

Pourquoi ne pas faire avec des js natifs?

    var s="<span class='text-muted' style='font-size:.75em; position:absolute; bottom:3px; left:30px'>From <strong>Dan's Tools</strong></span>"
    var e=document.createElement('div')
    var r=document.createRange();
    r.selectNodeContents(e)
    var f=range.createContextualFragment(s);
    e.appendChild(f);
    e = e.firstElementChild;
Sam Saarian
la source
-1
function domify (str) {
  var el = document.createElement('div');
  el.innerHTML = str;

  var frag = document.createDocumentFragment();
  return frag.appendChild(el.removeChild(el.firstChild));
}

var str = "<div class='foo'>foo</div>";
domify(str);
Denim Demon
la source
-2

Vous pouvez utiliser la fonction suivante pour convertir le texte "HTML" en élément

function htmlToElement(html)
{
  var element = document.createElement('div');
  element.innerHTML = html;
  return(element);
}
var html="<li>text and html</li>";
var e=htmlToElement(html);

Saeed saeeyd
la source
-1; il s'agit de la même technique que celle proposée dans la réponse acceptée et présente les mêmes inconvénients - notamment, ne fonctionne pas pour l' tdal.
Mark Amery,
-3
var jtag = $j.li({ child:'text' }); // Represents: <li>text</li>
var htmlContent = $('mylist').html();
$('mylist').html(htmlContent + jtag.html());

Utiliser jnerator

Berezh
la source
-3

Voici le code de travail pour moi

Je voulais convertir la chaîne ' Text ' en élément HTML

var diva = UWA.createElement('div');
diva.innerHTML = '<a href="http://wwww.example.com">Text</a>';
var aelement = diva.firstChild;
Sandeep Badawe
la source
Qu'est-ce que c'est wwww?
Tintin81
-3

var msg = "test" jQuery.parseHTML (msg)

nitish kumar
la source
Merci pour cet extrait de code, qui pourrait fournir une aide immédiate limitée. Une explication appropriée améliorerait considérablement sa valeur à long terme en montrant pourquoi il s'agit d'une bonne solution au problème et le rendrait plus utile aux futurs lecteurs ayant d'autres questions similaires. Veuillez modifier votre réponse pour ajouter des explications, y compris les hypothèses que vous avez faites.
Dwhitz
-7

Cela fonctionnera aussi:

$('<li>').text('hello').appendTo('#mylist');

Cela ressemble plus à une manière jquery avec les appels de fonction chaînés.

jack
la source
26
Est-ce que ça ressemble à jQuery parce que c'est jQuery?
Tim Ferrell
5
Cette dernière ligne est tout simplement hilarante!
kumarharsh