Crear un nuevo elemento DOM a partir de una cadena HTML utilizando métodos DOM integrados o Prototipo


622

Tengo una cadena HTML que representa un elemento: '<li>text</li>'. Me gustaría agregarlo a un elemento en el DOM (a ulen mi caso). ¿Cómo puedo hacer esto con Prototype o con métodos DOM?

(Sé que podría hacer esto fácilmente en jQuery, pero desafortunadamente no estamos usando jQuery).


Lo siento, ¿qué estás tratando de lograr exactamente? Cadena HTML -> elemento dom?
Crescent Fresh

36
Es lamentable que estas soluciones sean tan indirectas. Deseo que el comité de normas especifique algo similar como:var nodes = document.fromString("<b>Hello</b> <br>");
Sridhar Sarnobat


1
Tuve un problema con lo anterior, porque tenía atributos que debían pasarse y no tenía ganas de analizarlos: "<table id = '5ccf9305-4b10-aec6-3c55-a6d218bfb107' class = 'data-table display de borde de fila 'cellspacing =' 0 'width =' 100% '> </table> "así que simplemente usé: $ (" <table id =' 5ccf9305-4b10-aec6-3c55-a6d218bfb107 'class =' ​​data -table row-border display 'cellspacing =' 0 'width =' 100% '> </table> ")
stevenlacerda

Respuestas:


818

Nota: la mayoría de los navegadores actuales admiten <template>elementos HTML , que proporcionan una forma más confiable de convertir elementos de creación a partir de cadenas. Vea la respuesta de Mark Amery a continuación para más detalles .

Para navegadores más antiguos y node / jsdom : (que aún no admite <template>elementos en el momento de la escritura), use el siguiente método. Es lo mismo que hacen las bibliotecas para obtener elementos DOM de una cadena HTML ( con algo de trabajo adicional para que IE evite errores con su implementación 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; 
}

Tenga en cuenta que, a diferencia de las plantillas HTML, esto no funcionará para algunos elementos que legalmente no pueden ser hijos de a <div>, como <td>s.

Si ya está utilizando una biblioteca, le recomendaría que se adhiera al método aprobado por la biblioteca para crear elementos a partir de cadenas HTML:


1
Cómo establecer innerHTML en el div creado usando jQuery sin utilizar esta asignación innerHTML insegura (div.innerHTML = "algún valor")
Shivanshu Goyal

12
El nombre de la función createElementFromHTML es engañoso ya que div.firstChilddevuelve un Nodeque no es, HTMLElementpor ejemplo, no puede node.setAttribute. Para crear un Elementretorno div.firstElementChildde la función en su lugar.
Semmel

Gracias, la <div>envoltura del HTML que agregué me .innerHTMLestaba molestando. Nunca pensé en usar .firstChild.
Ivan

Estoy tratando de analizar un SVG dentro de lo creado divy la salida es [object SVGSVGElement]mientras el registro de la consola me da el elemento DOM correcto. ¿Qué estoy haciendo mal?
ed1nh0

1
Usaría firstElementChild en lugar de firstChild (consulte w3schools.com/jsref/prop_element_firstelementchild.asp ), porque si hay espacio al frente o al final de la plantilla, firstChild devolverá el texto vacío Node
Chris Panayotoff

344

HTML 5 introdujo el <template>elemento que puede usarse para este propósito (como se describe ahora en la especificación WhatWG y los documentos MDN ).

Un <template>elemento se utiliza para declarar fragmentos de HTML que se pueden utilizar en scripts. El elemento se representa en el DOM como un HTMLTemplateElementque tiene una .contentpropiedad de DocumentFragmenttipo, para proporcionar acceso a los contenidos de la plantilla. Esto significa que puede convertir una cadena HTML en elementos DOM configurando innerHTMLun <template>elemento y luego buscando en la propiedad template's .content.

Ejemplos:

/**
 * @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>');

Tenga en cuenta que los enfoques similares que utilizan un elemento contenedor diferente, como undiv no funcionan del todo. HTML tiene restricciones sobre qué tipos de elementos pueden existir dentro de qué otros tipos de elementos; por ejemplo, no puedes poner a tdcomo hijo directo de a div. Esto hace que estos elementos se desvanezcan si intenta establecer el valor innerHTMLde a divpara contenerlos. Ya que<template> s no tienen tales restricciones en su contenido, esta deficiencia no se aplica cuando se usa una plantilla.

Sin embargo, templateno es compatible con algunos navegadores antiguos. A partir de enero de 2018, ¿puedo usar ... estima que el 90% de los usuarios de todo el mundo están usando un navegador que admite templates . En particular, ninguna versión de Internet Explorer los admite; Microsoft no implementó el templatesoporte hasta el lanzamiento de Edge.

Si tiene la suerte de escribir código que solo está dirigido a usuarios en navegadores modernos, continúe y utilícelos ahora mismo. De lo contrario, es posible que tenga que esperar un tiempo para que los usuarios se pongan al día.


99
agradable, enfoque moderno. Probado en Opera, olvidemos IE
Adi Prasetyo

2
Este es un enfoque efectivo y muy limpio; sin embargo, (al menos en Chrome 50) esto interrumpe el manejo de la etiqueta del script. En otras palabras, el uso de este método para crear una etiqueta de script y luego agregarlo al documento (cuerpo o encabezado) no da como resultado la evaluación de la etiqueta y, por lo tanto, evita que se ejecute el script. (Esto puede ser por diseño si la evaluación ocurre en el
archivo

1
¡ENCANTADOR! incluso puede consultar elementos haciendo algo como: template.content.querySelector ("img");
Roger Gajraj

1
@Crescent Fresh: Bastante justo, tenía la impresión de que querías que tu respuesta desapareciera, ya que dijiste que ahora es irrelevante, mis disculpas. Un buen compromiso sería agregar una declaración a su respuesta señalando a los lectores a esta, como se ha discutido ahora en meta , solo esperemos que la guerra de edición no se recupere a partir de ahí.
BoltClock

2
Hay un problema con eso. Si tiene una etiqueta allí con espacios dentro (!) Al inicio o al final, ¡se eliminarán! No me malinterpreten, no se trata de los espacios eliminados html.trimsino del análisis interno de la configuración innerHTML. En mi caso, elimina espacios importantes como parte de un TextNode. :-(
e-motiv

110

Utilice insertAdarestHTML () . Funciona con todos los navegadores actuales, incluso con IE11.

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


99
Primero, insertAdjacentHTMLfunciona con todos los navegadores desde IE 4.0.
Jonas Äppelgran

66
En segundo lugar, es genial! Una gran ventaja en comparación innerHTML += ...es que las referencias a elementos anteriores todavía están intactas utilizando este método.
Jonas Äppelgran

77
En tercer lugar, los valores posibles para el primer argumento es: beforebegin, afterbegin, beforeend, afterend. Ver el artículo de MDN.
Jonas Äppelgran

mejor respuesta para mí
Jordania

44
Lástima que no devuelva el HTML insertado como un elemento
Mojimi

30

Las implementaciones de DOM más nuevas tienen range.createContextualFragment, lo que hace lo que quiere de una manera independiente del marco.

Es ampliamente compatible. Sin embargo, para estar seguro, verifique su compatibilidad en el mismo enlace MDN, ya que cambiará. A partir de mayo de 2017, esto es todo:

Feature         Chrome   Edge   Firefox(Gecko)  Internet Explorer   Opera   Safari
Basic support   (Yes)    (Yes)  (Yes)           11                  15.0    9.1.2

3
Tenga en cuenta que esto tiene inconvenientes similares a la configuración innerHTMLde un div; ciertos elementos, como tds, serán ignorados y no aparecerán en el fragmento resultante.
Mark Amery

"Hay informes de que Safari de escritorio admitió en un momento Range.createContextualFragment (), pero no es compatible al menos en Safari 9.0 y 9.1". (Enlace MDN en la respuesta)
akauppi

27

No necesita ningún ajuste, tiene una API nativa:

const toNodes = html =>
    new DOMParser().parseFromString(html, 'text/html').body.childNodes[0]

99
Esto sufre el mismo inconveniente principal que la respuesta aceptada: destrozará HTML como <td>text</td>. Esto se debe a que DOMParserestá intentando analizar un documento HTML completo y no todos los elementos son válidos como elementos raíz de un documento.
Mark Amery

44
-1 porque este es un duplicado de una respuesta anterior que señalaba explícitamente el inconveniente mencionado en mi comentario anterior.
Mark Amery

20

Para ciertos fragmentos html como <td>test</td>, div.innerHTML, DOMParser.parseFromString y range.createContextualFragment (sin el contexto correcto) las soluciones mencionadas en otras respuestas aquí, no crearán el <td>elemento.

jQuery.parseHTML () los maneja correctamente ( extraje la función parseHTML de jQuery 2 en una función independiente que se puede usar en bases de código que no son jquery).

Si solo admite Edge 13+, es más simple usar la etiqueta de plantilla HTML5:

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

var documentFragment = parseHTML('<td>Test</td>');

16

Aquí hay una manera simple de hacerlo:

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);

44
@MarkAmery la diferencia aquí es que usa un fragmento para permitir que se agreguen múltiples elementos raíz en el DOM, lo cual es un beneficio adicional. Si solo William mencionara esa es su respuesta ...
Koen.

10

Puede crear nodos DOM válidos a partir de una cadena usando:

document.createRange().createContextualFragment()

El siguiente ejemplo agrega un elemento de botón en la página tomando el marcado de una cadena:

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);


55
A pesar de que esto crea un DocumentFragmentobjeto, todavía , para mi gran sorpresa, sufre el mismo defecto que la respuesta aceptada: si lo hace document.createRange().createContextualFragment('<td>bla</td>'), obtendrá un fragmento que solo contiene el texto 'bla' sin el <td>elemento. O al menos, eso es lo que observo en Chrome 63; No he profundizado en las especificaciones para averiguar si es el comportamiento correcto o no.
Mark Amery

9

Con Prototype, también puedes hacer:

HTML:

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

JS:

$('mylist').insert('<li>text</li>');

¿Es esto jQuery? o JS?
Jeya Suriya Muthumari

1
Esto está utilizando métodos jQuery no integrados DOM como op mencionado.
Juan Hurtado

1
@JuanHurtado esto está usando PrototypeJs como OP solicitó hace casi 11 años;)
Pablo Borowicz

8

Estoy usando este método (funciona en IE9 +), aunque no analizará <td>ni a otros hijos directos no válidos del cuerpo:

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>

5

Para mejorar aún más el útil fragmento .toDOM () que podemos encontrar en diferentes lugares, ahora podemos usar backticks ( literales de plantilla ) de forma segura .

Entonces podemos tener comillas simples y dobles en la declaración foo html.

Esto se comporta como heredocs para aquellos familiarizados con el término.

Esto se puede mejorar además con variables, para hacer plantillas complejas:

Los literales de plantilla están encerrados por el carácter back-tick ( ) (acento grave) en lugar de comillas dobles o simples. Los literales de plantilla pueden contener marcadores de posición. Estos se indican con el signo de dólar y las llaves ($ {expresión}). Las expresiones en los marcadores de posición y el texto entre ellos se pasan a una función. La función predeterminada simplemente concatena las partes en una sola cadena. Si hay una expresión que precede al literal de la plantilla (etiqueta aquí), se llama "plantilla etiquetada". En ese caso, la expresión de etiqueta (generalmente una función) se llama con el literal de plantilla procesada, que luego puede manipular antes de generar. Para escapar de una marca de retroceso en una plantilla literal, coloque una barra invertida \ antes de la marca de retroceso.

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}

Entonces, ¿por qué no usar directamente .innerHTML += ? Al hacerlo, todo el DOM está siendo recalculado por el navegador, es mucho más lento.

https://caniuse.com/template-literals


4

Agregué un Documentprototipo que crea un elemento a partir de una cadena:

Document.prototype.createElementFromString = function (str) {
    const element = new DOMParser().parseFromString(str, 'text/html');
    const child = element.documentElement.querySelector('body').firstChild;
    return child;
};

Tenga en cuenta que, como se señaló en otra parte de esta página , esto no funcionará para tds.
Mark Amery

3

HTML5 y ES6

<template>

Manifestación

"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>


2

Tarde pero solo como una nota;

Es posible agregar un elemento trivial al elemento de destino como contenedor y eliminarlo después de usarlo.

// Probado en Chrome 23.0, Firefox 18.0, es decir, 7-8-9 y Opera 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>

2

La solución más rápida para representar DOM desde una cadena:

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};
};

Y aqui puede verificar el rendimiento para la manipulación DOM React vs JS nativo

Ahora puedes simplemente usar:

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

Y, por supuesto, en un futuro próximo utilizará referencias de memoria porque el "elemento" es su nuevo DOM creado en su documento.

Y recuerda que "innerHTML =" es muy lento: /


1

Aquí está mi código, y funciona:

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

    var tr = div.getElementsByTagName('tr');
    // ...
}

1
Falla si el elemento a crear es en sí mismo una tabla.
Mark Amery

0

Por lo demás, pensé en compartir esto sobre un enfoque complicado pero simple que se me ocurrió ... Tal vez alguien encuentre algo útil.

/*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')"
}));

0

¿Por qué no hacer con nativos js?

    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;

-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);

-2

Puede usar la siguiente función para convertir el texto "HTML" al elemento

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


-1; esta es la misma técnica propuesta en la respuesta aceptada y tiene los mismos inconvenientes, en particular, no funciona para tds.
Mark Amery

-3
var jtag = $j.li({ child:'text' }); // Represents: <li>text</li>
var htmlContent = $('mylist').html();
$('mylist').html(htmlContent + jtag.html());

Use jnerator


-3

Aquí está el código de trabajo para mí

Quería convertir la cadena ' Texto ' a elemento HTML

var diva = UWA.createElement('div');
diva.innerHTML = '<a href="http://wwww.example.com">Text</a>';
var aelement = diva.firstChild;

¿Qué es wwww?
Tintin81

-3

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


Gracias por este fragmento de código, que puede proporcionar una ayuda limitada e inmediata. Una explicación adecuada mejoraría enormemente su valor a largo plazo al mostrar por qué esta es una buena solución al problema y la haría más útil para futuros lectores con otras preguntas similares. Por favor, editar su respuesta a añadir un poco de explicación, incluyendo los supuestos realizados.
Dwhitz

-7

Esto también funcionará:

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

Se siente más como una forma jquery con las llamadas de función encadenadas.


26
¿Se siente como jQuery porque es jQuery?
Tim Ferrell

55
¡Esa última línea es simplemente graciosa!
kumarharsh
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.