Tworzenie nowego elementu DOM z ciągu HTML przy użyciu wbudowanych metod DOM lub Prototypu

621

Mam ciąg reprezentujący element HTML: '<li>text</li>'. Chciałbym dołączyć go do elementu w DOM ( ulw moim przypadku a). Jak mogę to zrobić za pomocą Prototypu lub metod DOM?

(Wiem, że mógłbym to zrobić w jQuery, ale niestety nie używamy jQuery.)

Omer Bokhari
źródło
Przepraszam, co dokładnie próbujesz osiągnąć? Ciąg HTML -> element dom?
Crescent Fresh
36
Szkoda, że ​​te rozwiązania są tak pośrednie. Chciałbym, aby komitet normalizacyjny sprecyzował coś podobnego:var nodes = document.fromString("<b>Hello</b> <br>");
Sridhar Sarnobat
1
Miałem problem z powyższym, ponieważ miałem atrybuty, które należało przekazać i nie miałem ochoty ich analizować: "<table id = '5ccf9305-4b10-aec6-3c55-a6d218bfb107' class = 'data-table wyświetlanie krawędzi wiersza 'cellspacing =' 0 'width =' 100% '> </table> "więc po prostu użyłem: $ (" <table id =' 5ccf9305-4b10-aec6-3c55-a6d218bfb107 'class =' ​​data -tabela wiersza obramowania wyświetlacza 'cellspacing =' 0 'width =' 100% '> </table> ")
stevenlacerda

Odpowiedzi:

817

Uwaga: większość obecnych przeglądarek obsługuje <template>elementy HTML , które zapewniają bardziej niezawodny sposób obracania tworzenia elementów z ciągów. Aby uzyskać szczegółowe informacje, zobacz odpowiedź Marka Amery poniżej .

W przypadku starszych przeglądarek i węzła / jsdom : (który nie obsługuje jeszcze <template>elementów w momencie pisania), użyj następującej metody. To samo robią biblioteki, aby uzyskać elementy DOM z łańcucha HTML ( z dodatkową pracą dla IE do obejścia błędów związanych z implementacją 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; 
}

Zauważ, że w przeciwieństwie do szablonów HTML , nie będzie to działać w przypadku niektórych elementów, które nie mogą legalnie być dziećmi elementów <div>takich jak <td>s.

Jeśli już korzystasz z biblioteki, radzę trzymać się zatwierdzonej przez bibliotekę metody tworzenia elementów z ciągów HTML:

Półksiężyc Świeży
źródło
1
Jak ustawić innerHTML do utworzonego div za pomocą jQuery bez korzystania z tego niebezpiecznego przypisania innerHTML (div.innerHTML = „jakaś wartość”)
Shivanshu Goyal
11
Nazwa funkcji createElementFromHTML wprowadza w błąd, ponieważ div.firstChildzwraca wartość, Nodektóra nie jest HTMLElementnp node.setAttribute. Nie może . Aby zamiast tego utworzyć Elementzwrot div.firstElementChildz funkcji.
Semmel,
Dziękuję, <div>pakowanie kodu HTML, który dodałem, .innerHTMLmnie denerwowało. Nigdy nie myślałem o użyciu .firstChild.
Ivan
Próbuję parsować plik SVG w utworzonym pliku, diva wynik jest wyświetlany, [object SVGSVGElement]gdy dziennik konsoli podaje poprawny element DOM. Co ja robię źle?
ed1nh0
1
Użyłbym firstElementChild zamiast firstChild (patrz w3schools.com/jsref/prop_element_firstelementchild.asp ), ponieważ jeśli jest miejsce przed lub na końcu szablonu, firstChild zwróci pusty textNode
Chris Panayotoff
342

W HTML 5 wprowadzono <template>element, który można wykorzystać do tego celu (jak teraz opisano w specyfikacji WhatWG i dokumentach MDN ).

<template>Element służy do deklarowania fragmenty HTML, który może być wykorzystywany w skryptów. Element jest reprezentowany w DOM jako element, HTMLTemplateElementktóry ma .contentwłaściwość DocumentFragmenttypu, aby zapewnić dostęp do zawartości szablonu. Oznacza to, że można przekonwertować ciąg HTML do elementów DOM ustawiając innerHTMLsię z <template>elementu, a następnie sięgając do template„s .contentnieruchomości.

Przykłady:

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

Zauważ, że podobne podejścia, które wykorzystują inny element kontenera, np.div Nie całkiem działają. HTML ma ograniczenia co do tego, jakie typy elementów mogą istnieć, w których innych typach elementów; na przykład nie możesz umieścić tdjako bezpośredniego dziecka div. Powoduje to zniknięcie tych elementów, jeśli spróbujesz ustawić innerHTMLa, divaby je zawierał. Ponieważ <template>nie mają takich ograniczeń dotyczących ich zawartości, to niedociągnięcie nie dotyczy korzystania z szablonu.

Jednak templatenie jest obsługiwany w niektórych starych przeglądarkach. Od stycznia 2018 r. Mogę używać ... szacuje, że 90% użytkowników na całym świecie korzysta z przeglądarki obsługującej templates . W szczególności żadna wersja programu Internet Explorer nie obsługuje ich; Microsoft nie wdrożył templatewsparcia do czasu wydania Edge.

Jeśli masz szczęście pisać kod skierowany tylko do użytkowników w nowoczesnych przeglądarkach, skorzystaj z nich już teraz. W przeciwnym razie użytkownik może chwilę poczekać, aż nadrobią zaległości.

Mark Amery
źródło
9
fajne, nowoczesne podejście. Testowany na Operze, zapomnijmy o IE
Adi Prasetyo
2
Jest to skuteczne i bardzo czyste podejście; jednak (przynajmniej w Chrome 50) psuje to obsługę tagów skryptowych. Innymi słowy, użycie tej metody do utworzenia znacznika skryptu, a następnie dołączenie go do dokumentu (treści lub głowy) nie powoduje oceny znacznika, a tym samym uniemożliwia wykonanie skryptu. (Może tak być zgodnie z projektem, jeśli ocena odbywa się przy dołączaniu; nie mogę tego powiedzieć z całą pewnością.)
shanef22
1
ŚLICZNY! możesz nawet zapytać o elementy, wykonując coś takiego: template.content.querySelector ("img");
Roger Gajraj,
1
@Crescent Fresh: W porządku, miałem wrażenie, że chciałeś odejść od odpowiedzi, ponieważ powiedziałeś, że teraz nie ma to znaczenia, przepraszam. Dobrym kompromisem byłoby dodanie do odpowiedzi oświadczenia wskazującego czytelników na ten, jak to zostało już omówione na stronie meta - miejmy tylko nadzieję, że wojna edycji nie zacznie się odtąd .
BoltClock
2
Jest z tym jeden problem. Jeśli masz tam tag ze spacjami w środku (!) Na początku lub na końcu, zostaną one usunięte! Nie zrozumcie mnie źle, nie chodzi tu o spacje usuwane przez, html.trimale o wewnętrzne analizowanie ustawienia innerHTML. W moim przypadku usuwa ważne spacje będące częścią textNode. :-(
e-motiv
109

Użyj insertAdjacentHTML () . Działa ze wszystkimi aktualnymi przeglądarkami, nawet z 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
źródło
9
Po pierwsze, insertAdjacentHTMLdziała ze wszystkimi przeglądarkami od IE 4.0.
Jonas Ęppelgran
6
Po drugie, jest świetnie! Dużym plusem w porównaniu do innerHTML += ...tego jest to, że odniesienia do poprzednich elementów są nadal nienaruszone przy użyciu tej metody.
Jonas Ęppelgran
7
Po trzecie, możliwe wartości pierwszego argumentu jest: beforebegin, afterbegin, beforeend, afterend. Zobacz artykuł MDN.
Jonas Ęppelgran
najlepsza odpowiedź dla mnie
Jordan
4
Szkoda, że ​​nie zwraca wstawionego HTML jako elementu
Mojimi
30

Nowsze implementacje DOM mają to range.createContextualFragment, co chcesz w sposób niezależny od frameworka.

Jest szeroko obsługiwany. Dla pewności sprawdź zgodność w tym samym łączu MDN, ponieważ będzie się on zmieniał. Od maja 2017 r. Jest to:

Feature         Chrome   Edge   Firefox(Gecko)  Internet Explorer   Opera   Safari
Basic support   (Yes)    (Yes)  (Yes)           11                  15.0    9.1.2
kojiro
źródło
3
Zauważ, że ma to podobne wady jak ustawienie innerHTMLdiv; niektóre elementy, takie jak tds, zostaną zignorowane i nie pojawią się w powstałym fragmencie.
Mark Amery
„Istnieją raporty, że Safari na komputerze stacjonarnym w pewnym momencie obsługuje Range.createContextualFragment (), ale nie jest obsługiwane co najmniej w Safari 9.0 i 9.1.” (Link MDN w odpowiedzi)
akauppi
27

Nie potrzebujesz żadnych poprawek, masz natywny interfejs API:

const toNodes = html =>
    new DOMParser().parseFromString(html, 'text/html').body.childNodes[0]
math2001
źródło
9
Ma to tę samą poważną wadę co zaakceptowana odpowiedź - będzie przypominać HTML <td>text</td>. Wynika to z tego, że DOMParserpróbuje parsować pełny dokument HTML i nie wszystkie elementy są prawidłowe jako elementy główne dokumentu.
Mark Amery
4
-1, ponieważ jest to kopia wcześniejszej odpowiedzi, która wyraźnie wskazywała na wadę wspomnianą w moim komentarzu powyżej.
Mark Amery
20

W przypadku niektórych fragmentów HTML, takich jak <td>test</td>div.innerHTML, DOMParser.parseFromString i range.createContextualFragment (bez odpowiedniego kontekstu) rozwiązania wymienione w innych odpowiedziach tutaj nie utworzą <td>elementu.

jQuery.parseHTML () obsługuje je poprawnie (wyodrębniłem funkcję parseHTML jQuery 2 do niezależnej funkcji, która może być używana w bazach kodu nie-jquery).

Jeśli obsługujesz tylko Edge 13+, łatwiej jest po prostu użyć tagu szablonu HTML5:

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

var documentFragment = parseHTML('<td>Test</td>');
Munawwar
źródło
16

Oto prosty sposób:

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
źródło
4
@Mark Różnica polega na tym, że używa fragmentu, aby umożliwić dołączenie wielu elementów głównych w DOM, co stanowi dodatkową korzyść. Gdyby tylko William wspomniał, to jest jego odpowiedź ...
Koen.
10

Możesz utworzyć prawidłowe węzły DOM z ciągu znaków, używając:

document.createRange().createContextualFragment()

Poniższy przykład dodaje element przycisku na stronie pobierając znacznik z ciągu:

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
źródło
5
Mimo że tworzy to DocumentFragmentobiekt, nadal - ku memu wielkiemu zdziwieniu - cierpi z powodu tej samej wady co przyjęta odpowiedź: jeśli to zrobisz document.createRange().createContextualFragment('<td>bla</td>'), otrzymasz fragment zawierający po prostu tekst „bla” bez <td>elementu. A przynajmniej tak obserwuję w Chrome 63; Nie zagłębiłem się w specyfikację, aby dowiedzieć się, czy jest to prawidłowe zachowanie, czy nie.
Mark Amery
9

Dzięki Prototype możesz także:

HTML:

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

JS:

$('mylist').insert('<li>text</li>');
Pablo Borowicz
źródło
Czy to jQuery? lub JS?
Jeya Suriya Muthumari,
1
Wykorzystuje to wbudowane metody DOM jQuery, jak wspomniano powyżej.
Juan Hurtado
1
@JuanHurtado używa PrototypeJs zgodnie z żądaniem OP prawie 11 lat temu;)
Pablo Borowicz
8

Korzystam z tej metody (działa w IE9 +), chociaż nie będzie ona analizować <td>ani innych nieprawidłowych bezpośrednich elementów potomnych:

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
źródło
5

Aby dodatkowo ulepszyć przydatny fragment kodu .toDOM (), który możemy znaleźć w różnych miejscach, możemy teraz bezpiecznie korzystać odwrócone, pojedyncze apostrofy ( literałów szablonów ).

Więc możemy mieć pojedyncze i podwójne cudzysłowy w deklaracji html foo .

Tak się zachowuje Zachowują heredoki dla osób zaznajomionych z tym terminem.

Można to dodatkowo ulepszyć o zmienne, aby tworzyć złożone szablony:

Literały szablonów są otoczone znakiem back-tick ( ) (grave accent) zamiast podwójnych lub pojedynczych cudzysłowów. Literały szablonów mogą zawierać symbole zastępcze. Wskazują na to znak dolara i nawiasy klamrowe ($ {wyrażenie}). Wyrażenia w symbolach zastępczych i tekst między nimi są przekazywane do funkcji. Domyślna funkcja po prostu łączy części w pojedynczy ciąg. Jeśli przed literałem szablonu znajduje się wyrażenie (tutaj tag), nazywa się to „szablonem tagowanym”. W takim przypadku wyrażenie znacznika (zwykle funkcja) jest wywoływane z przetworzonym dosłownym szablonem, którym można następnie manipulować przed wyprowadzeniem. Aby uniknąć tyknięcia w literale szablonu, umieść ukośnik \ przed tyknięciem wstecz.

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}

Dlaczego więc nie użyć bezpośrednio .innerHTML += ? W ten sposób przeglądarka oblicza cały DOM, co jest znacznie wolniejsze.

https://caniuse.com/template-literals

NVRM
źródło
4

Dodałem Documentprototyp, który tworzy element z łańcucha:

Document.prototype.createElementFromString = function (str) {
    const element = new DOMParser().parseFromString(str, 'text/html');
    const child = element.documentElement.querySelector('body').firstChild;
    return child;
};
Raza
źródło
Zauważ, że - jak wskazano w innym miejscu na tej stronie - nie zadziała dla tds.
Mark Amery
3

HTML5 i ES6

<template>

Próbny

"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
źródło
2

Późno, ale dla przypomnienia;

Możliwe jest dodanie trywialnego elementu do elementu docelowego jako kontenera i usunięcie go po użyciu.

// Testowane na chrome 23.0, firefox 18.0, tj. 7-8-9 i 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
źródło
2

Najszybsze rozwiązanie do renderowania DOM z ciągu:

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

I tutaj możesz sprawdzić wydajność manipulacji DOM React vs. natywny JS

Teraz możesz po prostu użyć:

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

I oczywiście w niedalekiej przyszłości będziesz używać referencji z pamięci, ponieważ var „element” to twoja nowo utworzona DOM w dokumencie.

I pamiętaj, że „innerHTML =” działa bardzo wolno: /

Paweł Kołodziejczak
źródło
1

Oto mój kod i działa:

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

    var tr = div.getElementsByTagName('tr');
    // ...
}
Wen Qi
źródło
1
Nie powiedzie się, jeśli sam element, który ma zostać utworzony, jest tabelą.
Mark Amery
0

Cholera, myślałem, że podzielę się tym ze skomplikowanym, ale jednak prostym podejściem, które wymyśliłem ... Może ktoś znajdzie coś przydatnego.

/*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
źródło
0

Dlaczego nie zrobić z natywnym 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
źródło
-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
źródło
-2

Możesz użyć następującej funkcji do konwersji tekstu „HTML” na element

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
źródło
-1; jest to ta sama technika, co zaproponowana w zaakceptowanej odpowiedzi i ma te same wady - zwłaszcza nie działa dla tds.
Mark Amery
-3
var jtag = $j.li({ child:'text' }); // Represents: <li>text</li>
var htmlContent = $('mylist').html();
$('mylist').html(htmlContent + jtag.html());

Użyj jneratora

Bereż
źródło
-3

Oto działający kod dla mnie

Chciałem przekonwertować ciąg „ Tekst ” na element HTML

var diva = UWA.createElement('div');
diva.innerHTML = '<a href="http://wwww.example.com">Text</a>';
var aelement = diva.firstChild;
Sandeep Badawe
źródło
Co to jest wwww?
Tintin81,
-3

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

nitish kumar
źródło
Dziękujemy za ten fragment kodu, który może zapewnić pewną ograniczoną, natychmiastową pomoc. Właściwe wyjaśnienie byłoby znacznie poprawić swoją długoterminową wartość pokazując dlaczego jest to dobre rozwiązanie problemu i byłoby bardziej użyteczne dla czytelników przyszłości z innymi, podobnymi pytaniami. Proszę edytować swoją odpowiedź dodać kilka wyjaśnień, w tym założeń już wykonanych.
Dwhitz
-7

To też zadziała:

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

To bardziej przypomina jquery z połączonymi wywołaniami funkcji.

Jacek
źródło
26
Czy to wygląda jak jQuery, ponieważ jest to jQuery?
Tim Ferrell
5
Ta ostatnia linia jest po prostu przezabawna!
kumarharsh