Criando um novo elemento DOM a partir de uma string HTML usando métodos DOM internos ou Prototype

621

Eu tenho uma string HTML que representa um elemento: '<li>text</li>'. Eu gostaria de anexá-lo a um elemento no DOM (a ulno meu caso). Como posso fazer isso com o Prototype ou com os métodos DOM?

(Eu sei que eu poderia fazer isso facilmente no jQuery, mas infelizmente não estamos usando o jQuery.)

Omer Bokhari
fonte
Desculpe, o que você está tentando realizar exatamente? String HTML -> elemento dom?
Crescent Fresh
36
É uma pena que essas soluções sejam tão indiretas. Desejo que o comitê de padrões especifique algo semelhante como:var nodes = document.fromString("<b>Hello</b> <br>");
Sridhar Sarnobat 27/02/2015
1
Eu tive um problema com o exposto acima, porque eu tinha atributos que precisavam ser repassados ​​e não sentia vontade de analisá-los: "<table id = '5ccf9305-4b10-aec6-3c55-a6d218bfb107' class = 'data-table exibição da borda da linha 'cellpacing =' 0 'width =' 100% '> </table> "então, eu simplesmente usei: $ (" <table id =' 5ccf9305-4b10-aec6-3c55-a6d218bfb107 'class =' ​​data -table display de borda de linha 'cellpacing =' 0 'width =' 100% '> </table> ")
stevenlacerda

Respostas:

818

Nota: a maioria dos navegadores atuais suporta <template>elementos HTML , que fornecem uma maneira mais confiável de ativar a criação de elementos a partir de strings. Veja a resposta de Mark Amery abaixo para obter detalhes .

Para navegadores mais antigos e node / jsdom : (que ainda não suporta <template>elementos no momento da escrita), use o seguinte método. É a mesma coisa que as bibliotecas costumam fazer para obter elementos DOM de uma string HTML ( com algum trabalho extra para o IE solucionar problemas com a implementação 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; 
}

Observe que, diferentemente dos modelos HTML, isso não funcionará para alguns elementos que não podem ser legalmente filhos de a <div>, como <td>s.

Se você já estiver usando uma biblioteca, recomendo que você siga o método aprovado pela biblioteca de criar elementos a partir de strings HTML:

Crescent Fresh
fonte
1
Como definir innerHTML ao div criado usando jQuery sem usar essa atribuição innerHTML inseguro (div.innerHTML = "algum valor")
Shivanshu Goyal
12
O nome da função createElementFromHTML é enganoso, pois div.firstChildretorna a Nodeque não é por HTMLElementexemplo, não pode node.setAttribute. Para criar um Elementretorno div.firstElementChildda função.
Semmel
Obrigado, o <div>empacotamento do HTML que eu adicionei .innerHTMLme incomodou. Eu nunca pensei em usar .firstChild.
Ivan
Eu estou tentando analisar um SVG dentro do criado dive a saída é [object SVGSVGElement]enquanto o log do console me fornece o elemento DOM correto. O que estou fazendo errado?
ed1nh0
1
Gostaria de usar firstElementChild vez de firstChild (veja w3schools.com/jsref/prop_element_firstelementchild.asp ), porque se houver espaço na frente ou no final do modelo, o firstChild voltaria textNode vazio
Chris Panayotoff
342

O HTML 5 introduziu o <template>elemento que pode ser usado para essa finalidade (como agora descrito nas especificações do WhatWG e nos documentos MDN ).

Um <template>elemento é usado para declarar fragmentos de HTML que podem ser utilizados em scripts. O elemento é representado no DOM como um HTMLTemplateElementque possui uma .contentpropriedade do DocumentFragmenttipo, para fornecer acesso ao conteúdo do modelo. Isso significa que você pode converter uma string HTML em elementos DOM, definindo o innerHTMLde um <template>elemento e acessando a propriedade template's .content.

Exemplos:

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

Observe que abordagens semelhantes que usam um elemento de contêiner diferente, como umdiv , não funcionam bem. O HTML tem restrições sobre quais tipos de elementos podem existir dentro de quais outros tipos de elementos; por exemplo, você não pode colocar a tdcomo filho direto de a div. Isso faz com que esses elementos desapareçam se você tentar definir o innerHTMLde a divpara contê-los. Como <template>s não têm essas restrições em seu conteúdo, essa falha não se aplica ao usar um modelo.

No entanto, templatenão é suportado em alguns navegadores antigos. Em janeiro de 2018, posso usar ... estima que 90% dos usuários globalmente estão usando um navegador que suporta templates . Em particular, nenhuma versão do Internet Explorer os suporta; A Microsoft não implementou o templatesuporte até o lançamento do Edge.

Se você tiver sorte o suficiente para escrever código direcionado apenas a usuários em navegadores modernos, vá em frente e use-o agora. Caso contrário, pode ser necessário esperar um pouco para que os usuários os atualizem.

Mark Amery
fonte
9
agradável, abordagem moderna. Testado em Opera, vamos esquecer IE
Adi Prasetyo
2
Esta é uma abordagem eficaz e muito limpa; no entanto, (pelo menos no Chrome 50), isso interrompe o tratamento da tag de script. Em outras palavras, o uso desse método para criar uma marca de script e depois anexá-la ao documento (corpo ou cabeça) não resulta na avaliação da marca e, portanto, impede a execução do script. (Isto pode ser por design se a avaliação acontece em prender, eu não poderia dizer com certeza.)
shanef22
1
ENCANTADOR! você pode até consultar elementos fazendo algo como: template.content.querySelector ("img");
Roger Gajraj
1
@Crescent Fresh: Justo, fiquei com a impressão de que você queria sua resposta, já que disse que agora é irrelevante, minhas desculpas. Um bom compromisso seria adicionar uma declaração à sua resposta, apontando os leitores para esta, como já foi discutido na meta - vamos apenas esperar que a guerra de edição não retorne a partir daí.
BoltClock
2
Há um problema com isso. Se você tiver uma tag com espaços dentro (!) No início ou no final, eles serão removidos! Não me interpretem mal, não se trata dos espaços removidos, html.trimmas pela análise interna da configuração innerHTML. No meu caso, remove espaços importantes fazendo parte de um textNode. :-(
e-motiv 24/04
110

Use insertAdjacentHTML () . Ele funciona com todos os navegadores atuais, mesmo com o 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
fonte
9
Primeiro, insertAdjacentHTMLfunciona com todos os navegadores desde o IE 4.0.
Jonas Äppelgran # 10/18
6
Segundo, é ótimo! Uma grande vantagem em comparação innerHTML += ...é que as referências aos elementos anteriores ainda estão intactas usando esse método.
Jonas Äppelgran # 10/18
7
Terceiro, os valores possíveis para o primeiro argumento é: beforebegin, afterbegin, beforeend, afterend. Veja o artigo MDN.
Jonas Äppelgran # 10/18
melhor resposta para mim
Jordan
4
Pena que não devolver o HTML inserido como um elemento
Mojimi
30

Implementações DOM mais recentes têm range.createContextualFragment, o que faz o que você deseja de uma maneira independente da estrutura.

É amplamente suportado. Para ter certeza, verifique sua compatibilidade no mesmo link MDN, pois ele será alterado. Em maio de 2017, é isso:

Feature         Chrome   Edge   Firefox(Gecko)  Internet Explorer   Opera   Safari
Basic support   (Yes)    (Yes)  (Yes)           11                  15.0    9.1.2
kojiro
fonte
3
Observe que isso tem desvantagens semelhantes à configuração innerHTMLde uma div; certos elementos, como tds, serão ignorados e não aparecerão no fragmento resultante.
Mark Amery
"Há relatos de que o Safari para desktop em um momento oferece suporte a Range.createContextualFragment (), mas ele não é suportado pelo menos no Safari 9.0 e 9.1." (Link MDN na resposta)
akauppi
27

Não há necessidade de nenhum ajuste, você tem uma API nativa:

const toNodes = html =>
    new DOMParser().parseFromString(html, 'text/html').body.childNodes[0]
math2001
fonte
9
Isso sofre da mesma grande desvantagem da resposta aceita - ele irá alterar o HTML <td>text</td>. Isso ocorre porque DOMParserestá tentando analisar um documento HTML completo e nem todos os elementos são válidos como elementos raiz de um documento.
Mark Amery
4
-1 porque esta é uma duplicata de uma resposta anterior que apontou explicitamente a desvantagem mencionada no meu comentário acima.
Mark Amery
20

Para certos fragmentos de html <td>test</td>, como as soluções div.innerHTML, DOMParser.parseFromString e range.createContextualFragment (sem o contexto correto) mencionados em outras respostas aqui, não criarão o <td>elemento.

O jQuery.parseHTML () lida com eles adequadamente (extraí a função parseHTML do jQuery 2 em uma função independente que pode ser usada em bases de código que não sejam de jquery).

Se você suporta apenas o Edge 13+, é mais simples usar a tag do modelo HTML5:

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

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

Aqui está uma maneira simples de fazer isso:

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
fonte
4
@MarkAmery, a diferença aqui é que ele usa um fragmento para permitir que vários elementos raiz sejam anexados no DOM, o que é um benefício adicional. Se ao menos o William mencionou que é a resposta dele ...
Koen.
10

Você pode criar nós DOM válidos a partir de uma sequência usando:

document.createRange().createContextualFragment()

O exemplo a seguir adiciona um elemento de botão na página que recebe a marcação de uma sequência:

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
fonte
5
Mesmo que isso crie um DocumentFragmentobjeto, ele ainda - para minha grande surpresa - sofre do mesmo defeito que a resposta aceita: se o fizer document.createRange().createContextualFragment('<td>bla</td>'), obtém um fragmento que contém apenas o texto 'bla' sem o <td>elemento. Ou pelo menos, é o que observo no Chrome 63; Eu não me aprofundei nas especificações para descobrir se é o comportamento correto ou não.
Mark Amery
9

Com o Prototype, você também pode:

HTML:

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

JS:

$('mylist').insert('<li>text</li>');
Pablo Borowicz
fonte
Isso é jQuery? ou JS?
Jeya Suriya Muthumari
1
Isso está usando métodos DOM internos do jQuery, não como op mencionado.
Juan Hurtado
1
@JuanHurtado, este está usando PrototypeJs como OP solicitado há quase 11 anos;) #
Pablo Borowicz
8

Estou usando esse método (funciona no IE9 +), embora ele não analise <td>ou outros filhos diretos inválidos do corpo:

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
fonte
5

Para aprimorar ainda mais o trecho útil .toDOM () que podemos encontrar em lugares diferentes, agora podemos usar com segurança backticks ( literais de modelo ).

Assim, podemos ter aspas simples e duplas na declaração foo html.

Isso se comporta como heredocs para aqueles familiarizados com o termo.

Além disso, isso pode ser aprimorado com variáveis, para criar modelos complexos:

Os literais de modelo são delimitados pelo caractere de retorno ( ) (acento grave) em vez de aspas simples ou duplas. Literais de modelo podem conter espaços reservados. Eles são indicados pelo cifrão e chaves ($ {expression}). As expressões nos espaços reservados e o texto entre eles são passados ​​para uma função. A função padrão apenas concatena as partes em uma única sequência. Se houver uma expressão precedendo o literal do modelo (tag aqui), isso será chamado de "modelo marcado". Nesse caso, a expressão de tag (geralmente uma função) é chamada com o literal do modelo processado, que você pode manipular antes de enviar. Para escapar de um back-tick em um literal de modelo, coloque uma barra invertida \ antes do 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}

Então, por que não usar diretamente .innerHTML +=? Ao fazer isso, todo o DOM está sendo recalculado pelo navegador, é muito mais lento.

https://caniuse.com/template-literals

NVRM
fonte
4

Eu adicionei um Documentprotótipo que cria um elemento da string:

Document.prototype.createElementFromString = function (str) {
    const element = new DOMParser().parseFromString(str, 'text/html');
    const child = element.documentElement.querySelector('body').firstChild;
    return child;
};
Raza
fonte
Observe que, como indicado em outra parte desta página, isso não funcionará para tds.
Mark Amery
3

HTML5 e ES6

<template>

Demo

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

Tarde, mas apenas como uma nota;

É possível adicionar um elemento trivial ao elemento de destino como um contêiner e removê-lo após o uso.

// Testado no chrome 23.0, firefox 18.0, ie 7-8-9 e 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
fonte
2

Solução mais rápida para renderizar DOM a partir da string:

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

E aqui você pode verificar o desempenho da manipulação do DOM React vs JS nativo

Agora você pode simplesmente usar:

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

E, claro, em um futuro próximo, você usará referências da memória, porque var "elemento" é o seu novo DOM criado em seu documento.

E lembre-se "innerHTML =" é muito lento: /

Paweł Kołodziejczak
fonte
1

Aqui está o meu código e funciona:

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

    var tr = div.getElementsByTagName('tr');
    // ...
}
Wen Qi
fonte
1
Falha se o próprio elemento a ser criado for uma tabela.
22816 Mark Amery
0

No começo, pensei em compartilhar isso com uma abordagem complicada, mas simples, que eu criei ... Talvez alguém encontre 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
fonte
0

Por que não fazer com js nativos?

    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
fonte
-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
fonte
-2

Você pode usar a seguinte função para converter o texto "HTML" no 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
fonte
-1; essa é a mesma técnica proposta na resposta aceita e tem as mesmas desvantagens - principalmente, não 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
fonte
-3

Aqui está o código de trabalho para mim

Eu queria converter a string ' Text ' em elemento HTML

var diva = UWA.createElement('div');
diva.innerHTML = '<a href="http://wwww.example.com">Text</a>';
var aelement = diva.firstChild;
Sandeep Badawe
fonte
O que é wwww?
Tintin81 25/09/19
-3

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

nitish kumar
fonte
Obrigado por este trecho de código, que pode fornecer ajuda imediata e limitada. Uma explicação adequada melhoraria bastante seu valor a longo prazo, mostrando por que essa é uma boa solução para o problema e a tornaria mais útil para futuros leitores com outras perguntas semelhantes. Por favor edite sua resposta para adicionar alguma explicação, incluindo as suposições que você fez.
Dwhitz 22/05/19
-7

Isso também funcionará:

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

Parece mais uma maneira jquery com as chamadas de função encadeadas.

jack
fonte
26
Parece jQuery porque é jQuery?
Tim Ferrell
5
Essa última linha é apenas hilária!
precisa saber é o seguinte