Пользователь
0,0
рейтинг
17 января 2013 в 18:40

Разработка → Что нам стоит 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, сделать это более лучше, пишите код.
Vitali Borovik @WAYS
карма
25,5
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (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 и иные.
    В куче такого кода через два месяца не разберешься.
    • 0
      Добавлю ещё doT.js тоже неплохой doT.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) ну это вы преувеличиваете.

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