Что нам стоит DOM построить

Скажу сразу, всю DOM-модель мы строить не будем, а лишь рассмотрим ее элементы и как с ними работать при помощи jQuery. Статья рассчитана на начинающих или тех кто хочет вспомнить как можно строить элементы «на лету», надеюсь кому-то это будет полезно.
Большинство веб-разработчиков сталкивается с необходимостью вставить какое-либо содержимое из js, возможно это ajax или событие. Но никто не задумывается о том что с вашим кодом возможно кому-то придется работать. И часто даже в очень известных плагинах можно встретить код такого типа:

var content = "<table>"
for(i=0; i<3; i++){
    content += '<tr><td>' + 'result ' +  i + '</td></tr>';
}
content += "</table>"

$('#table').append(content);


Сразу видно, что этот кусочек кода сделан на быструю руку, я и сам так когда-то делал, но ведь можно сделать и лучше.
В статье я вывел самые основные элементы которые используются при разработке, возможно они помогут вам сэкономить время.
Под катом много кода с примерами.

Вот еще интересный вариант:

function createTable() {
            $("#dynamicTable").append("<table>");
            $("#dynamicTable").append("<caption>My table</caption>");
            $("#dynamicTable").append("<thead>");
            $("#dynamicTable").append("<tr>");
            $("#dynamicTable").append("<th>A</th>");
            $("#dynamicTable").append("<th>B</th>");
            $("#dynamicTable").append("<th>C</th>");
            $("#dynamicTable").append("</tr>");
            $("#dynamicTable").append("</thead>");
            
            $("#dynamicTable").append("<tbody>");
....
}

(так делать точно не нужно)

Сложно сказать хороший это код или нет, но он явно недружелюбный. Все элементы можно создавать с чистого Javascript, что и делает сам jQuery. Поэтому спорить о быстродействии нет смысла, я не замерял разницу, но очевидно, что она незначительная, перейдем к коду.

Div


var mydiv = $('<div/>', {
    id:     'mydiv',
    class:  'mydiv',
    text: 	'Содержимое блока' 
});
$('.content').append(mydiv);

Пример вывода:
<div class="mydiv" id="mydiv">Содержимое блока</div>
Демо

Form


var myform = $("<form/>", {
  	action: "/", 
}).appendTo('.content');

Пример вывода:
<form action="/"></form>
Демо

Input


$('<input/>', {
    id:     'myinput-1',
    class:  'myinput',
    type: 	'text',
    name: 	'myinput-1',
    val: 	'Habr',
    css: {
            'border': '1px solid red'
    } 
}).appendTo(myform);

Можно и так:
$('<input/>').attr({
    id:     'myinput-2',
    class:  'myinput',
    type: 	'text',
    name: 	'myinput-2',
    placeholder: 	'Поиск...'
}).appendTo(myform);


$('<input/>', {
    type: 	'submit',
    name: 	'send',
    val: 	'Отправить' 
}).appendTo(myform);

Пример вывода:
<form action="/">
	<input style="border: 1px solid red;" name="myinput-1" class="myinput" id="myinput-1" type="text">
	<input placeholder="Поиск..." name="myinput-2" class="myinput" id="myinput-2" type="text">
	<input value="Отправить" name="send" type="submit">
</form>
Демо

Select


var myselect = $('<select/>', { id: 'myselect'});
var items = ["Google","Yandex","Bing"];
//Наполняем список
$.each(items,function() {
	$('<option/>', {
		val:  this,
		text: this
	}).appendTo(myselect);
});
myselect.val('Yandex');
$('.content').append(myselect);

Пример вывода:
<select id="myselect">
	<option value="Google">Google</option>
	<option value="Yandex">Yandex</option>
	<option value="Bing">Bing</option>
</select>
Демо

Как видим все работает хорошо, но где же наш любимый selected?
Заменим:
myselect.val('Yandex');
на:
$('option[value="Yandex"]', myselect).attr('selected', 'selected');
И все заработает как мы привыкли, хотя иногда первого варианта достаточно

<select id="myselect">
	<option value="Google">Google</option>
	<option selected="selected" value="Yandex">Yandex</option>
	<option value="Bing">Bing</option>
</select>


Radio


var myradiodiv = $('<div/>', {
    id:     'myradiodiv'
}).appendTo(".content");
var items = ["Google","Yandex","Bing"];
$.each(items, function(i,item) {
	$('<label/>').append(
		$('<input/>', {
			type: 'radio',
			name: 'myradio',
			val: 	item
		})
	).append(item).appendTo(myradiodiv);
});

Пример вывода:
<div id="myradiodiv">
	<label>Google
		<input value="Google" name="myradio" type="radio">
	</label>
	<label>Yandex
		<input value="Yandex" name="myradio" type="radio">
	</label>
	<label>Bing
		<input value="Bing" name="myradio" type="radio">
	</label>
</div>
Демо

Button


var mybutton = $('<button/>',
{
    text: 'Click Me',
    click: function () { alert('Hello Habr'); }
}).appendTo('.content');

Пример вывода:
<button>Click Me</button>


Iframe


$('<iframe/>', {
    name: 'myframe',
    id:   'myframe',
    src:  'about:blank'
}).appendTo('.content');

Пример вывода:
<iframe src="about:blank" id="myframe" name="myframe"></iframe>


Table


 заполняется аналогично  поэтому я его пропустил

//Данные var TableTitle = ["Site", "Google","Yandex","Bing"]; var TableValue = [ ["http://habr.ru/","4","6","26"], ["http://habrahabr.ru/","3","1","6"], ["http://google.ru/","1","1","1"] ]; //Создадим структуру var mytable = $('<table/>',{ class:'mytable' }).append( $('<thead/>'), $('<tfoot/>'), $('<tbody/>') ); //Наполняем табличку //Заголовок var TitleCell = $('<tr/>'); $.each(TableTitle,function( myIndex, myData ) { TitleCell.append( $('<th/>',{ text:myData }) ); }); $("thead",mytable).append(TitleCell); //Данные $.each(TableValue,function() { //Переопределяем строку var DataCell = $('<tr/>'); //Пробегаемся по ячейкам $.each(this,function() { DataCell.append( $('<td/>',{ text:this }) ); }); $("tbody",mytable).append(DataCell); }); //Без цикла (не обязательно) $.each(TableValue,function( i, myData ) { $("tbody",mytable).append( $('<tr/>').append( $('<td/>',{text:this[0]}), $('<td/>',{text:this[1]}), $('<td/>',{text:myData[2]}), //Или так $('<td/>',{text:myData[3]}) ) ); }); $('.content').append(mytable);

Пример вывода:
<table class="mytable">
	<thead>
		<tr>
			<th>Site</th>
			<th>Google</th>
			<th>Yandex</th>
			<th>Bing</th>
		</tr>
	</thead>
	<tfoot></tfoot>
	<tbody>
		<tr>
			<td>http://habr.ru/</td>
			<td>4</td><td>6</td>
			<td>26</td>
		</tr>
		<tr>
			<td>http://habrahabr.ru/</td>
			<td>3</td>
			<td>1</td>
			<td>6</td>
		</tr>
		<tr>
			<td>http://google.ru/</td>
			<td>1</td>
			<td>1</td>
			<td>1</td>
		</tr>
	</tbody>
</table>
Демо

Списки


var lang = ['Russian', 'English', 'Ukraine'];
var mylist = $('<ul/>');
//Наполняем
$.each(lang, function() {
    $('<li/>',{text:this}).appendTo(mylist);
    //Добавим ссылку
    $('<li/>').wrapInner(
    	$("<a/>",{
    		"href":"#",
    		text:this
    	}))
    .appendTo(mylist);
});
$('.content').append(mylist);

<ul>
	<li>Russian</li>
	<li><a href="#">Russian</a></li>
	<li>English</li>
	<li><a href="#">English</a></li>
	<li>Ukraine</li>
	<li><a href="#">Ukraine</a></li>
</ul>
Демо

Скрипты


$("<script/>",{
	src:'js/inc.js'
}).appendTo("body");


Стили


$("<link/>",{
	href:'css/inc.css',
	rel:'stylesheet'
}).insertAfter("link:last");


Конструкцию $('<input/>') можно писать и без ключа $(''), тоже будет работать.
Если у кого-то есть замечания/предложения, буду рад выслушать в ПМ или комментариях.

Весь код на github

UPD: Если у вас много данных, могут быть проблемы с производительностью. Напомню, js код выполняется на стороне клиента и результат зависит от конфигурации его компьютера.
Генерация таблички размером 4 колонки на 100 строк у меня занимает 36мс.
Я не хочу спорить про производительность, о чем намекнул в начале статьи, если у Вас есть решения, как можно, используя jQuery, сделать это более лучше, пишите код.
Поделиться публикацией
Похожие публикации
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама
Комментарии 59
  • +1
    Век живи — век учись. Спасибо, не знал такого способа.
    • +10
      Сделайте, пожалуйста, наглядное сравнение производительности пары плохих вариантов из начала статьи и правильного. Очень удобно было бы посмотреть такой тест через сервис jsperf.com
      • +2
        и еще пример построения и вставкой dom цикле и второй вариант — с использованием documentFragment, чтобы на лицо было видно его большое преимущество. Перестроение DOM при изменении геометрических свойств — самая ресурсоемкая операция, поэтому никаких append в цикле.
        • –6
          Будет работать немного медленней, но не ощутимо для пользователя. Хотя опять же таки, смотря сколько у Вас в целом js кода и количество данных.
          • 0
            Сделал ниже
          • +4
            По поводу ваших созданий таблиц читаем "jQuery изнутри — парсинг html":

            Для случая с <td>Привет!</td>, код превратится в <table><tbody><tr><td>Привет!</td></tr></tbory></table>, а указатель на контейнер с нужным нам результатом будет смещен на глубину в 3 тега, то есть в <tr> вместо <div>, который был создан внутри safeFrag в самом начале


            Да и вообще лучше почитать всю серию «jQuery изнутри». Там много описано на эту тему и, в основном, более корректно и полно.
            • +5
              Да что ж такое, в третьем посте подряд создают DOM-дерево руками. Неужто времени не жалко?
              • +8
                Строить DOM создавая отдельно каждый элемент — это удар по производительности.
                Первый пример из статьи будет работать в 10-100 раз быстрее, особенно в цикле.
                Конечно так складывать строчки не очень красиво, зато работает быстро.

                Ну и конечно никто не запрещает использовать шаблоны для построения DOM, хотя бы тем же jQuery.tmpl
                • +2
                  А по моему все эти мудреные цепочки и вложенные методы jquery читаются хуже, чем старое доброе сложение строчек в цикле. +)
                  • –1
                    Быстрее будет не складывать строчки, а складывать их в массив и потом join-ить:
                    var html = [];
                    for (var i=0; i<100; i++) {
                      html.push('<div>');
                      html.push(i);
                      html.push('</div>');
                    }
                    $('#div').append(html.join(''));

                    Операция join быстрее срабатывает, чем множество +
                    • 0
                      Только если говорить об IE. Разница между склеиванием массива и конкатенацией во всех более мение новых браузерах не настолько велика. Хотя да, отрабатывать будет чуть быстрее. Жаль что в javascript нет своего string builder-а
                      • 0
                        Операция join быстрее срабатывает, чем множество +

                        пруф
                        • 0
                          jsperf.com/concat-vs-join444 — только если так. Вот только добавление элемента в массив операция даже более ресурсоемкая нежели конкатенация строк. Так что если делать все так как рекомендует lexxpavlov, то производительность в современных браузерах (в случае сборки шаблона) упадет в разы.
                          • +1
                            Давайте глянем полный пример:
                            jsperf.com/concat-vs-join444/2

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

                            В остальных браузерах однозначно имеет смысл конкатенировать, а не джоинить.
                            • 0
                              И правда, потестил сейчас оба варианта. Конкат быстрее оказывается.
                              Странно, потому что читал о таком способе и там убеждали в быстроте join, и тесты приводили. Я тогда не проверил сам, но запомнил. Извиняюсь, что привёл непроверенную информацию…
                              Кстати, интересно, что по тестам jsperf.com/concat-vs-join444/2 в некоторых браузерах join всё-таки быстрее. Но в остальных браузерах настолько медленнее, что небольшое превосходство в некоторых браузерах не окупится.

                              Вот мой тест: jsperf.com/concat-vs-join445
                              Интересно то, что при повышении объема данных join становится хуже и хуже…
                    • +13
                      Вам следует указать в статье, что такой способ создания элементов черезвычайно ресурсоемкий, и подходит только для создания небольшого количества элементов, и уж точно его нельзя использовать в цикле, раз уж ваша статья предназначена для новичков.
                      • 0
                        Дописал, спасибо.
                      • +8
                        Простите
                        Сложно сказать хороший это код или нет, но он явно недружелюбный.

                        Это на правах шутки?
                        • +4
                          Простите, а зачем, если есть вполне отличные шаблонизаторы? Тот же mustache, dust.js и иные.
                          В куче такого кода через два месяца не разберешься.
                        • +7
                          1) Да, jQuery позволяет так делать, но как минимум этого не стоит делать.
                          2) используйте хотя бы createDocumentFragment для сборки всего этого добра. как показывают бенчмарки — все это добро ускорится в разы.
                          3) Метод с конкатенацией строк отрабатывает в десятки раз быстрее.

                          Статья имеет смыл только в качестве пособия о возможностях jQuery, Использовать ЭТО в реальных проекта крайне не рекомендуется. Лучше уж использовать какие-нибудь шаблоны.
                          • +1
                            Еще стоит отметить, что использовать each для обхода массива (в примере с таблицей и списками) более ресурсоемкая операция чем:

                            for (var i = 0, l = arr.length(); i < l; i++) {
                                ...
                            }
                            


                            При этом кода не становится больше, а воспринимается он гораздо легче.
                            ИМХО пример с таблицей вообще ужасен.
                            • –4
                              Ну статья о jQuery. Если писать все на чистом js все будет быстрее и это очевидно. Если Вам не нравится, не пользуйтесь.
                              • +1
                                Да дело не в тормознутости jQuery, дело в неверном подходе.

                                На чистом js это будет что-то вроде:
                                var table = document.createElement('table')
                                var thead = document.createElement('thead')
                                var tr = document.createElement('tr')
                                var td = document.createElement('td')
                                var text = document.createTextNode('текст')
                                td.appendChild(text)
                                tr.appendChild(td)
                                thead.appendChild(tr)
                                table.appendChild(thead)
                                

                                за корректность не ручаюсь, но в таком духе. Прямо как в известном диалоге: открыла чемодан, достала сумочку, закрыла чемодан, открыла сумочку, достала кошелёк, закрыла сумочку, открыла чемодан, положиле сумочку, закрыла чемодан, открыла кошелёк…

                                Как вам тут уже правильно заметили, написать
                                <table><thead><tr><td>текст</td></tr></thead></table>
                                
                                и позволить браузеру распарсить это и построить дерево самому — значительно проще и понятнее, работает в десятки раз быстрее, чем если это делать на js, особенно для большого числа элементов.
                                • +1
                                  Суть в том что кто-нибудь напишет «а вот как можно делать» а потом впечатлительные люди и вправду станут так делать. А потом сиди и рефактори за них.
                                  • –3
                                    Вы правы, я даже с Вами согласен, но я не думаю что оно работает настолько медленно что бы так критиковать. Если брать даже очень большие проекты на чистом js — они все равно будут немного тормозить, так как в этом весь javascript, небольшие задачи он решает очень неплохо, даже вот такими методами.
                                    • +1
                                      Оно работает именно «настолько медленно». В сложном интерфейсе, когда динамически подгружается десяток блоков со сложной версткой вы сразу почувствуете лаги, если не использовать шаблоны, data-binding, и прочие прелести современных фреймворков (knockout, backbone, angular).
                                      Посмотрите как устроен любой сложный клиентский интерфейс, VK, Twitter и т.д

                                      Все сообщество веб-разработчиков бьется за каждую миллисекунду и мгновенную отзывчивость интерфейса, а вы предлагаете самый худший вариант создания dom-дерева, отсюда и критика.
                                      • +1
                                        jsperf.com/dom-gen — как-то так… Код не оптимален, буду рад если кто-то скажет как написать последние 2 теста правильно. Я увы давно на старом добром JS Этого не делал.
                                        • 0
                                          С увеличением объема (скажем не простенький списочек а здоровый кусок шаблона) разница будет только увеличиваться.
                                    • 0
                                      То есть вы считаете что фанаты jQuery даже массив обходят при помощи each? Даже если объем кода увеличивается?
                                    • +1
                                      У вас лишние скобки после length :).
                                      • 0
                                        Точно :) Но поправить уже не могу.
                                      • +1
                                        а еще быстрее будет
                                        for (var i = arr.length(); i--; )
                                        

                                        это конечно для перебора, если нам не важен порядок.
                                        • 0
                                          Это уже экономия на спичках. Тогда уже можно было бы использовать Array.map
                                      • +1
                                        var element = document.createElement(tagName);
                                        
                                        • 0
                                          Ексть еще интересный способ динамического создания элементов, основным преимуществом которого является удобная поддержка с точки зрения взаимодействия с верстальщиком.

                                          Идея: скрытый элемент в HTML-коде, затем через clone() получается его копия и уже в нее через селекторы вставляются нужные значения. Эдакий «шаблонизатор для бедных», когда по каким-то причинам настоящий шаблонизатор не используется.

                                          <div class="myclass tpl hidden">
                                                <h1></h1>
                                          </div>
                                          ...
                                          <div id="parent"></div>
                                          


                                          var tpl = $('.tpl').clone();
                                          tpl.removeClass('hidden tpl');
                                          tpl.find('h1').text('Hello!');
                                          tpl.appendTo('#parent');
                                          


                                          Есть и минусы, конечно. Например, наличие в коде лишнего элемента может дать погрешность, если в примере выше потребуется посчитать все элементы с классом myclass — нужно быть внимательным. Плюс — никаких тэгов в джавасрипте.
                                          • 0
                                            В общем-то clone даже не особо нужен, что мешает сразу нужную разметку вставить и скрыть ее до тех пор, пока она не понадобится?
                                            • 0
                                              В этом частном случае — да, но, как правило, подобным способом динамически создаются повторяющиеся элменты списка, строки таблицы либо целые однотипные блоки. Т.е. шаблон не должен исчезать.
                                          • 0
                                            * Удалено, повтор про .clone();
                                            • 0
                                              Попробуйте запустить свой «Вот еще интересный вариант:», осознайте, что он на самом деле вставит в DOM.
                                              • –2
                                                Если Вы не поняли это чужой код.
                                                • +1
                                                  Тогда к чему слова «Сложно сказать хороший это код или нет»? Этот код явно делает не то, что думал человек, написавший его.
                                                  «Статья рассчитана на начинающих», а вы оставляете такое без явного комментария о том, что такой код писать не надо.
                                                  • –1
                                                    Дописал, прошу прощение что ввел в заблуждение.
                                                  • 0
                                                    Как это вас извиняет? Топик-то ваш.
                                                • –7
                                                  Для меня самым не очевидным в jQuery оказался тот факт, что обработчики событий «наслаиваются» друг на друга. То есть на элемент #id событие $('#id).click() может повесится несколько раз, и автоматически не перезаписываются.
                                                  Конечно, unbind меня спас. Но сам факт хранения где-то в памяти богом забытого обработчика для элемента — смущает
                                                  • +3
                                                    Возможно я открою для вас секрет, но обработчики событий в jQuery навешиваются через стандартный eventListener браузера. И фишка событий в Dom в этом и состоит, что события наслаиваются и всплывают. Без этого нельзя было бы реализовать делегацию событий, без этого много чего пришлось бы реализовывать с костылями. Да и во всех других системах с Ui ивентов можно навесить столько, сколько угодно.
                                                    • 0
                                                      К слову, раз уж возникают вопросы…

                                                      jsfiddle.net/a6rsx/ — полное управление ивентами. Тобиш возможность контролировать порядок исполнения, временно отключать лишние ивенты и т.д.
                                                    • +2
                                                      OMG, вы сейчас серьезно?
                                                      Я так понял что вы изучали только jQuery а не JavaScript… Прочтите пару книг не о jQuery.
                                                      • 0
                                                        Что же столько негатива то, это был одним из моих первых опытов работы с DOM-моделью как таковой. Слышал о ней, читал, пробовал понемногу обращаться к элементам — но как оказалось раньше всё было довольно поверхностным. Я не говорю что наслаивание обработчиков это плохо, это всего лишь было для меня не очевидно, в отличии от перестроения модели при добавлении нового элемента, например.
                                                        • 0
                                                          По поводу наслаивания (я так понимаю вы о всплытии событий?) — есть дерево элементов. Как только произошел клик по одному из них, событие передается дальше, вниз по дереву, к основанию. так как (если не вдаваться в оформление) это логично — кликнул на блок, а он внутри другого блока, по нему выходит тоже кликнули, ну и т.д. до document.

                                                          То что можно навешивать много обработчиков одного события на один элемент — это уже мелочи.
                                                          • 0
                                                            Не совсем, я добавлял элемент из одной таблицы в другую с помощью .clone().after() и вешал на класс, который был у новой строки строки, событие $('.classname).onclick().
                                                            Проблема была в том, что для первого добавленного элемента, событие срабатывало 1 раз.
                                                            Для второго — два раза. Ну и так далее.

                                                            Поскольку при каждом новом добавлении элемента я вешал НОВЫЙ .onclick(), в добавок к старому.

                                                            Вы говорите немного о другом, вложенность по дереву меня не удивляет)
                                                            • +1
                                                              1) у метода clone есть параметр, который отвечает за сохранение навешанных событий у элемента.
                                                              2) логичнее в этом случае использовать делегирование событий.
                                                              • 0
                                                                Спасибо, буду знать. Но в моём случае объект, от которого наследовалась строка, обладал другим функционалом, и на наследуемый элемент в любом случае вешался новый обработчик.

                                                                Видимо, по-умолчанию наследование обработчиков выключено (не замечал лишних срабатываний).

                                                                А, ну да: withDataAndEvents (default: false)
                                                              • 0
                                                                Не совсем, я добавлял элемент из одной таблицы в другую с помощью .clone().after() и вешал на класс, который был у новой строки строки, событие $('.classname).onclick().

                                                                А зачем? Раз уж добавляли методом .clone().after(), то надо было сразу вешать .click()
                                                                • 0
                                                                  Вот об этом не подумал. Действительно, так было бы гораздо лучше.
                                                        • +1
                                                          При работе с DOM ещё полезно знать, что jquery-селекторы, это, по сути, массивы — таким образом можно быстро достучаться до чистой дом-ноды и применить к ней нативные(и быстрые) методы:
                                                          $('#element')[0] == document.getElementById('element')
                                                          • 0
                                                            в данном контексте это не существенно.
                                                          • +1
                                                            Хорошо описаны возможности JQuery, но я бы делал это на чистом JS, по нескольким причинам:
                                                            1) Это быстрее
                                                            2) Я знаю как это делается на JavaScript
                                                            3) Я не смогу запомнить как это делает JQuery =)
                                                            • 0
                                                              1) очевидно но плохо поддерживается. Идеальный компромис — конкатенация строк или шаблонизаторы.
                                                              3) ну это вы преувеличиваете.

                                                            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.