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

Omer Bokhari
fuente
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:

Creciente Fresco
fuente
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.

Mark Amery
fuente
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>

Christian d'Heureuse
fuente
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
kojiro
fuente
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]
matemáticas2001
fuente
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>');
Munawwar
fuente
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);
william malo
fuente
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);

GibboK
fuente
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>');
Pablo Borowicz
fuente
¿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>
usrbowe
fuente
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

NVRM
fuente
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;
};
Raza
fuente
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>

xgqfrms-gildata
fuente
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>
K-Gun
fuente
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: /

Paweł Kołodziejczak
fuente
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');
    // ...
}
Wen Qi
fuente
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')"
}));
JxAxMxIxN
fuente
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;
Sam Saarian
fuente
-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
fuente
-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);

Saeed Saeeyd
fuente
-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

Berezh
fuente
-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;
Sandeep Badawe
fuente
¿Qué es wwww?
Tintin81
-3

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

nitish kumar
fuente
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.

Jack
fuente
26
¿Se siente como jQuery porque es jQuery?
Tim Ferrell
55
¡Esa última línea es simplemente graciosa!
kumarharsh