Создание нового элемента DOM из строки HTML с использованием встроенных методов DOM или Prototype

622

У меня есть HTML - строка , представляющая элемент: '<li>text</li>'. Я хотел бы добавить его к элементу в DOM ( ulв моем случае). Как я могу сделать это с помощью Prototype или DOM?

(Я знаю, что мог бы легко это сделать в jQuery, но, к сожалению, мы не используем jQuery.)

Омер Бохари
источник
Извините, что вы пытаетесь достичь? HTML строка -> элемент dom?
Crescent Fresh
36
К сожалению, эти решения являются настолько косвенными. Я хотел бы, чтобы комитет по стандартам уточнил что-то подобное:var nodes = document.fromString("<b>Hello</b> <br>");
Шридхар Сарнобат,
1
У меня была проблема с вышеперечисленным, потому что у меня были атрибуты, которые нужно было передать, и я не чувствовал необходимости разбирать их: "<table id = '5ccf9305-4b10-aec6-3c55-a6d218bfb107' class = 'data-table отображение границы строки 'cellspacing =' 0 'width =' 100% '> </ table> "поэтому я просто использовал: $ (" <идентификатор таблицы =' 5ccf9305-4b10-aec6-3c55-a6d218bfb107 'class =' ​​data отображение строки строки 'cellspacing =' 0 'width =' 100% '> </ table> ")
stevenlacerda

Ответы:

818

Примечание: большинство современных браузеров поддерживают <template>элементы HTML , которые обеспечивают более надежный способ превращения создания элементов из строк. См . Ответ Марка Эмери ниже для деталей .

Для более старых браузеров и для node / jsdom : (который еще не поддерживает <template>элементы на момент написания) используйте следующий метод. Это то же самое, что библиотеки используют для получения элементов DOM из строки HTML ( с некоторой дополнительной работой для IE, чтобы обойти ошибки с его реализацией 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; 
}

Обратите внимание, что в отличие от шаблонов HTML это не будет работать для некоторых элементов, которые по закону не могут быть потомками a <div>, таких как <td>s.

Если вы уже используете библиотеку, я бы порекомендовал вам придерживаться одобренного библиотекой метода создания элементов из строк HTML:

Свежий полумесяц
источник
1
Как установить innerHTML для созданного div, используя jQuery, без использования этого небезопасного присваивания innerHTML (div.innerHTML = "some value")
Shivanshu Goyal
12
Имя функции createElementFromHTML вводит в заблуждение, поскольку div.firstChildвозвращает a, Nodeкоторое, HTMLElementнапример, не может node.setAttribute. Вместо этого создать Elementвозврат div.firstElementChildиз функции.
Semmel
Спасибо, <div>упаковка HTML, которую я добавил, .innerHTMLменя раздражала. Я никогда не думал об использовании .firstChild.
Иван
Я пытаюсь проанализировать SVG внутри созданного, divи вывод [object SVGSVGElement]пока консольный журнал дает мне правильный элемент DOM. Что я делаю неправильно?
ed1nh0
1
Я бы использовал firstElementChild вместо firstChild (см. W3schools.com/jsref/prop_element_firstelementchild.asp ), потому что если в начале или конце шаблона есть пробел, firstChild вернет пустой текстNode
Chris Panayotoff
344

В HTML 5 появился <template>элемент, который можно использовать для этой цели (как сейчас описано в спецификации WhatWG и документах MDN ).

<template>Элемент используется для объявления фрагменты HTML , которые могут быть использованы в сценариях. Элемент представлен в DOM как объект HTMLTemplateElementсо .contentсвойством DocumentFragmentтипа, чтобы обеспечить доступ к содержимому шаблона. Это означает , что вы можете преобразовать строку HTML для элементов DOM, установив innerHTMLиз <template>элемента, затем достигая в template«S .contentсобственности.

Примеры:

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

Обратите внимание, что похожие подходы, в которых используется другой элемент контейнера, напримерdiv , не совсем работают. HTML имеет ограничения на то, какие типы элементов могут существовать внутри каких других типов элементов; Например, вы не можете поставить tdв качестве прямого потомка div. Это приводит к тому, что эти элементы исчезают, если вы пытаетесь установить innerHTMLa divдля их содержания. Поскольку у <template>s нет таких ограничений на их содержимое, этот недостаток не применяется при использовании шаблона.

Тем templateне менее, не поддерживается в некоторых старых браузерах. По состоянию на январь 2018 г. Можно ли использовать ... по оценкам, 90% пользователей во всем мире используют браузер, который поддерживает templates . В частности, ни одна версия Internet Explorer не поддерживает их; Microsoft не реализовала templateподдержку до выхода Edge.

Если вам повезло, что вы пишете код, предназначенный только для пользователей в современных браузерах, используйте его прямо сейчас. В противном случае вам, возможно, придется подождать некоторое время, чтобы пользователи наверстали упущенное.

Марк Эмери
источник
9
хороший, современный подход. Протестировано на Opera, давайте забудем IE
Adi Prasetyo
2
Это эффективный подход и очень чистый; однако (по крайней мере, в Chrome 50) это нарушает обработку тегов скрипта. Другими словами, использование этого метода для создания тега сценария и последующего добавления его в документ (тело или заголовок) не приводит к оценке тега и, следовательно, предотвращает выполнение сценария. (Это может быть
умышленно,
1
LOVELY! Вы даже можете запросить элементы, выполнив что-то вроде: template.content.querySelector ("img");
Роджер Гайрай
1
@Crescent Fresh: Достаточно справедливо, у меня сложилось впечатление, что вы хотели, чтобы ваш ответ исчез, поскольку вы сказали, что теперь он не имеет значения, мои извинения. Хорошим компромиссом было бы добавление в ваш ответ утверждения, указывающего читателям на этот вопрос, как это уже обсуждалось на мета - давайте просто надеяться, что война редактирования не возобновится.
BoltClock
2
Есть одна проблема с этим. Если у вас есть тег с пробелами внутри (!) В начале или конце, они будут удалены! Не поймите меня неправильно, дело не в удаленных пробелах, html.trimа во внутреннем разборе установки innerHTML. В моем случае это удаляет важные пробелы, являющиеся частью textNode. :-(
e-мотив
110

Используйте insertAdjacentHTML () . Он работает со всеми современными браузерами, даже с IE11.

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

Кристиан д'Эрёз
источник
9
Во-первых, insertAdjacentHTMLработает со всеми браузерами начиная с IE 4.0.
Йонас Эппелгран
6
Во-вторых, это здорово! Большим плюсом по сравнению с этим innerHTML += ...является то, что при использовании этого метода ссылки на предыдущие элементы остаются неизменными.
Йонас Эппелгран
7
В- третьих, возможные значения первого аргумента: beforebegin, afterbegin, beforeend, afterend. Смотрите статью MDN.
Йонас Эппелгран
лучший ответ для меня
Иордания
4
Жаль, что он не возвращает вставленный HTML как элемент
Модзими
30

В более новых реализациях DOM есть то range.createContextualFragment, что вы хотите, независимо от фреймворка.

Это широко поддерживается. Чтобы быть уверенным, проверьте его совместимость в той же ссылке MDN, поскольку она будет меняться. По состоянию на май 2017 года это так:

Feature         Chrome   Edge   Firefox(Gecko)  Internet Explorer   Opera   Safari
Basic support   (Yes)    (Yes)  (Yes)           11                  15.0    9.1.2
Кодзиро
источник
3
Обратите внимание, что у этого есть подобные недостатки к установке innerHTMLэлемента div; некоторые элементы, такие как tds, будут игнорироваться и не появятся в результирующем фрагменте.
Марк Амери
«Есть сообщения, что настольный Safari в какой-то момент поддерживал Range.createContextualFragment (), но он не поддерживается по крайней мере в Safari 9.0 и 9.1». (Ссылка MDN в ответе)
akauppi
27

Нет необходимости в настройке, у вас есть собственный API:

const toNodes = html =>
    new DOMParser().parseFromString(html, 'text/html').body.childNodes[0]
math2001
источник
9
Это имеет тот же серьезный недостаток, что и принятый ответ - он будет портить HTML как <td>text</td>. Это потому, что DOMParserпытается проанализировать полный HTML-документ , и не все элементы допустимы в качестве корневых элементов документа.
Марк Амери
4
-1, потому что это дубликат более раннего ответа, который явно указал на недостаток, упомянутый в моем комментарии выше.
Марк Амери
20

Для определенных фрагментов HTML, таких как <td>test</td>div.innerHTML, DOMParser.parseFromString и range.createContextualFragment (без правильного контекста) решения, упомянутые в других ответах, не будут создавать <td>элемент.

jQuery.parseHTML () обрабатывает их должным образом (я извлек функцию parseHTML в jQuery 2 в независимую функцию, которая может использоваться в кодовых базах не-jquery).

Если вы поддерживаете только Edge 13+, проще использовать тег шаблона HTML5:

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

var documentFragment = parseHTML('<td>Test</td>');
Munawwar
источник
16

Вот простой способ сделать это:

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);
Уильям Мало
источник
4
@MarkAmery разница здесь в том, что он использует фрагмент, чтобы разрешить добавление нескольких корневых элементов в DOM, что является дополнительным преимуществом. Если бы только Уильям упомянул, что это его ответ ...
Коэн.
10

Вы можете создать действительные DOM-узлы из строки, используя:

document.createRange().createContextualFragment()

В следующем примере добавляется элемент кнопки на странице, извлекающий разметку из строки:

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
источник
5
Несмотря на то, что создает DocumentFragmentобъект, он все еще - к моему большому удивлению - страдает от того же дефекта в общепринятом ответ: если вы делаете document.createRange().createContextualFragment('<td>bla</td>'), вы получите фрагмент , который содержит только текст «бла» без <td>элемента. Или, по крайней мере, это то, что я наблюдаю в Chrome 63; Я не вникал в спецификации , чтобы выяснить , является ли это правильное поведение или нет.
Марк Амери
9

С Prototype вы также можете сделать:

HTML:

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

JS:

$('mylist').insert('<li>text</li>');
Пабло Борович
источник
Это jQuery? или JS?
Джейя Сурия Мутхумари
1
Это использует jQuery, а не встроенные методы DOM, как упоминалось выше.
Хуан Уртадо
1
@JuanHurtado это использует PrototypeJs как OP, запрошенный почти 11 лет назад;)
Пабло Борович
8

Я использую этот метод (работает в IE9 +), хотя он не будет анализировать <td>или некоторые другие недопустимые прямые потомки тела:

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
источник
5

Кроме того, для улучшения полезного фрагмента .toDOM (), который мы можем найти в разных местах, теперь мы можем безопасно использовать обратные ссылки ( литералы шаблона ).

Таким образом, мы можем иметь одинарные и двойные кавычки в объявлении foo html.

Это ведет себя как heredocs для тех, кто знаком с этим термином.

Кроме того, это может быть улучшено с помощью переменных, чтобы сделать сложные шаблоны:

Шаблонные литералы заключены в символ 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}

Итак, почему бы не использовать напрямую .innerHTML +=? Таким образом, весь DOM пересчитывается браузером, это намного медленнее.

https://caniuse.com/template-literals

NVRM
источник
4

Я добавил Documentпрототип, который создает элемент из строки:

Document.prototype.createElementFromString = function (str) {
    const element = new DOMParser().parseFromString(str, 'text/html');
    const child = element.documentElement.querySelector('body').firstChild;
    return child;
};
Раза
источник
Обратите внимание, что - как указано в другом месте на этой странице - это не будет работать для tds.
Марк Амери
3

HTML5 & ES6

<template>

демонстрация

"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
источник
2

Поздно, но просто как записка;

Можно добавить тривиальный элемент в целевой элемент в качестве контейнера и удалить его после использования.

// Проверено на Chrome 23.0, Firefox 18.0, т.е. 7-8-9 и 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
источник
2

Самое быстрое решение для визуализации DOM из строки:

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

И здесь вы можете проверить производительность для манипулирования DOM React против родного JS

Теперь вы можете просто использовать:

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

И, конечно, в ближайшем будущем вы используете ссылки из памяти, потому что var "element" - это ваш новый созданный DOM в вашем документе.

И помните, что "innerHTML =" очень медленный: /

Павел Колодзейчак
источник
1

Вот мой код, и он работает:

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

    var tr = div.getElementsByTagName('tr');
    // ...
}
Вэнь Ци
источник
1
Сбой, если создаваемый элемент сам является таблицей.
Марк Эмери
0

Черт возьми, я думал, что поделюсь этим со сложным, но все же простым подходом, который я придумал ... Может, кто-то найдет что-то полезное.

/*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
источник
0

Почему бы не сделать с родным 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

Вы можете использовать следующую функцию для преобразования текста «HTML» в элемент

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; это та же самая техника, которая предложена в принятом ответе, и имеет те же недостатки - особенно, не работает для tds.
Марк Амери
-3
var jtag = $j.li({ child:'text' }); // Represents: <li>text</li>
var htmlContent = $('mylist').html();
$('mylist').html(htmlContent + jtag.html());

Используйте jnerator

Berezh
источник
-3

Вот рабочий код для меня

Я хотел преобразовать текстовую строку в элемент HTML

var diva = UWA.createElement('div');
diva.innerHTML = '<a href="http://wwww.example.com">Text</a>';
var aelement = diva.firstChild;
Сандип Бадаве
источник
Что такое wwww?
Tintin81,
-3

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

нитиш кумар
источник
Спасибо за этот фрагмент кода, который может оказать некоторую ограниченную, немедленную помощь. Надлежащее объяснение будет значительно улучшить свою долгосрочную ценность, показывая , почему это является хорошим решением проблемы и сделает его более полезным для читателей будущих с другими подобными вопросами. Пожалуйста, измените свой ответ, чтобы добавить некоторые объяснения, в том числе предположения, которые вы сделали.
Dwhitz
-7

Это тоже будет работать:

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

Это больше похоже на способ jquery с цепными вызовами функций.

Джек
источник
26
Это похоже на jQuery, потому что это jQuery?
Тим Феррелл
5
Эта последняя строчка просто смешная!
kumarharsh