HTML-элементы

Класс Nette\Utils\Html – помощник для генерации HTML-кода, который помогает предотвратить уязвимость Cross-Site Scripting (XSS).

Он работает так: его объекты представляют HTML-элементы, вы задаёте им параметры и затем выводите их:

$el = Html::el('img');  // создаёт элемент <img>
$el->src = 'image.jpg'; // задаёт атрибут src
echo $el;               // выводит '<img src="image.jpg">'

Тело элемента можно наполнить текстом и другими элементами методом add(). Текст экранируется автоматически, элементы вставляются как есть:

echo Html::el('div')->add(
	'Hello ',
	Html::el('b')->setText('world'),
);
// '<div>Hello <b>world</b></div>'

Установка:

composer require nette/utils

Во всех примерах предполагается, что определён такой псевдоним класса:

use Nette\Utils\Html;

Создание HTML-элемента

Элемент создаётся методом Html::el():

$el = Html::el('img'); // создаёт элемент <img>

Кроме имени можно указать и другие атрибуты в синтаксисе HTML:

$el = Html::el('input type=text class="red important"');

Или передать их ассоциативным массивом во втором параметре:

$el = Html::el('input', [
	'type' => 'text',
	'class' => 'important',
]);

Изменение и получение имени элемента:

$el->setName('img');
$el->getName(); // 'img'
$el->isEmpty(); // true, потому что <img> - пустой (void) элемент

HTML-атрибуты

Отдельные HTML-атрибуты можно задавать и получать тремя способами; какой предпочесть, решать вам. Первый – через свойства:

$el->src = 'image.jpg'; // задаёт атрибут src

echo $el->src; // 'image.jpg'

unset($el->src);  // удаляет атрибут
// или $el->src = null;

Второй способ – вызов методов, которые, в отличие от установки свойств, можно объединять в цепочку:

$el = Html::el('img')->src('image.jpg')->alt('photo');
// <img src="image.jpg" alt="photo">

$el->alt(null); // удаляет атрибут

А третий способ самый многословный:

$el = Html::el('img')
	->setAttribute('src', 'image.jpg')
	->setAttribute('alt', 'photo');

echo $el->getAttribute('src'); // 'image.jpg'

$el->removeAttribute('alt');

Атрибуты можно задавать пакетно через addAttributes(array $attrs) и удалять через removeAttributes(array $attrNames).

Значением атрибута может быть не только строка: для логических атрибутов можно использовать логические значения:

$checkbox = Html::el('input')->type('checkbox');
$checkbox->checked = true;  // <input type="checkbox" checked>
$checkbox->checked = false; // <input type="checkbox">

Атрибутом может быть и массив значений, которые выводятся через пробел. Это удобно, например, для классов CSS:

$el = Html::el('input');
$el->class[] = 'active';
$el->class[] = null; // null игнорируется
$el->class[] = 'top';
echo $el; // '<input class="active top">'

Альтернатива – ассоциативный массив, где значения указывают, должен ли ключ попасть в вывод:

$el = Html::el('input');
$el->class['active'] = true;
$el->class['top'] = false;
echo $el; // '<input class="active">'

Стили CSS можно записывать ассоциативными массивами:

$el = Html::el('input');
$el->style['color'] = 'green';
$el->style['display'] = 'block';
echo $el; // '<input style="color:green;display:block">'

До сих пор мы использовали свойства, но того же можно добиться методами:

$el = Html::el('input');
$el->style('color', 'green');
$el->style('display', 'block');
echo $el; // '<input style="color:green;display:block">'

Или даже самым многословным способом:

$el = Html::el('input');
$el->appendAttribute('style', 'color', 'green');
$el->appendAttribute('style', 'display', 'block');
echo $el; // '<input style="color:green;display:block">'

И последняя деталь: метод href() может упростить составление параметров запроса в URL:

echo Html::el('a')->href('index.php', [
	'id' => 10,
	'lang' => 'en',
]);
// '<a href="index.php?id=10&amp;lang=en"></a>'

Атрибуты data

У атрибутов data особая поддержка. Поскольку в их именах есть дефисы, обращение к ним через свойства и методы не так изящно, поэтому есть отдельный метод data():

$el = Html::el('input');
$el->{'data-max-size'} = '500x300'; // не так изящно
$el->data('max-size', '500x300'); // изящно
echo $el; // '<input data-max-size="500x300">'

Если значение атрибута data – массив, он автоматически сериализуется в JSON:

$el = Html::el('input');
$el->data('items', [1,2,3]);
echo $el; // '<input data-items="[1,2,3]">'

Содержимое элемента

Внутреннее содержимое элемента задаётся методами setHtml() или setText(). Первый используйте только тогда, когда уверены, что параметр содержит заведомо безопасную строку HTML.

echo Html::el('span')->setHtml('hello<br>');
// '<span>hello<br></span>'

echo Html::el('span')->setText('10 < 20');
// '<span>10 &lt; 20</span>'

И наоборот, внутреннее содержимое можно получить методами getHtml() или getText(). Второй убирает из содержимого HTML-теги и превращает HTML-сущности обратно в символы.

echo $el->getHtml(); // '10 &lt; 20'
echo $el->getText(); // '10 < 20'

Дочерние узлы

Внутренним содержимым элемента может быть и массив дочерних узлов. Каждый потомок может быть строкой или другим объектом Html. Они добавляются через addHtml() или addText():

$el = Html::el('span')
	->addHtml('hello<br>')
	->addText('10 < 20')
	->addHtml( Html::el('br') );
// <span>hello<br>10 &lt; 20<br></span>

Метод add() вставляет сразу несколько потомков. Строки экранируются так же, как в addText(), объекты Html вставляются как есть, а значения null пропускаются, что удобно для условного содержимого. Строку, которая заведомо является безопасным HTML, оберните в Html::html():

$el = Html::el('span')->add(
	'10 < 20',
	Html::el('br'),
	Html::html('hello<br>'),
	$showNote ? Html::el('small')->setText('note') : null,
);
// <span>10 &lt; 20<br>hello<br><small>note</small></span>

Ещё один способ создать и вставить новый узел Html:

$ul = Html::el('ul');
$ul->create('li', ['class' => 'first'])
	->setText('first');
// <ul><li class="first">first</li></ul>

С узлами можно работать как с элементами массива. То есть обращаться к отдельным узлам через квадратные скобки, считать их через count() и обходить их:

$el = Html::el('div');
$el[] = '<b>hello</b>';
$el[] = Html::el('span');
echo $el[1]; // '<span></span>'

foreach ($el as $child) { /* ... */ }

echo count($el); // 2

Новый узел можно вставить в определённую позицию методом insert(?int $index, $child, bool $replace = false). Если $replace = false, элемент вставляется на позицию $index, а остальные сдвигаются. Если $index = null, элемент добавляется в конец.

// вставляет элемент на первую позицию и сдвигает остальные
$el->insert(0, Html::el('span'));

Все узлы можно получить методом getChildren() и удалить методом removeChildren().

Создание фрагмента документа

Если вы хотите работать с набором узлов и вам не нужен обёртывающий элемент, вы можете создать фрагмент документа. Он выводит только своих потомков, без собственного тега. Метод fragment() создаёт его и наполняет потомками одним вызовом по тем же правилам, что и add():

echo Html::fragment(
	Html::el('strong')->setText('hello'),
	'10 < 20',
	Html::el('br'),
);
// <strong>hello</strong>10 &lt; 20<br>

Фрагмент только с текстовым или только с HTML-содержимым создают методы text() и html():

echo Html::text('10 < 20');   // '10 &lt; 20'
echo Html::html('hello<br>'); // 'hello<br>'

Если вам нужна поддержка версий до 4.1.5, создайте фрагмент, передав null вместо имени элемента, и наполните его через addHtml() и addText(). Вместо text() и html() в этих версиях есть методы fromText() и fromHtml(), которые ещё работают, но объявлены устаревшими:

$el = Html::el(null)
	->addHtml('hello<br>')
	->addText('10 < 20');
// hello<br>10 &lt; 20

echo Html::fromText('10 < 20');   // '10 &lt; 20'
echo Html::fromHtml('hello<br>'); // 'hello<br>'

Генерация HTML-вывода

Проще всего вывести HTML-элемент через echo или приведением объекта к (string). Можно выводить отдельно открывающий тег, закрывающий тег и атрибуты:

$el = Html::el('div class=header')->setText('hello');

echo $el;               // '<div class="header">hello</div>'
$s = (string) $el;      // '<div class="header">hello</div>'
$s = $el->toHtml();     // '<div class="header">hello</div>'
$s = $el->toText();     // 'hello'
echo $el->startTag();   // '<div class="header">'
echo $el->endTag();     // '</div>'
echo $el->attributes(); // 'class="header"'

Метод render(?int $indent = null) предлагает красивый вывод. Если вы передадите уровень отступа, вывод будет аккуратно разбит на строки с отступами:

echo $el->render(0); // возвращает HTML с отступами

Важная особенность – автоматическая защита от Cross-Site Scripting (XSS). Все значения атрибутов и содержимое, вставленное через setText(), addText(), add() или fragment(), надёжно экранируются:

echo Html::el('div')
	->title('" onmouseover="bad()')
	->setText('<script>bad()</script>');

// <div title='" onmouseover="bad()'>&lt;script&gt;bad()&lt;/script&gt;</div>

Преобразование HTML ↔ текст

Для преобразования HTML в текст можно использовать статический метод htmlToText():

echo Html::htmlToText('<span>One &amp; Two</span>'); // 'One & Two'

HtmlStringable

Объект Nette\Utils\Html реализует интерфейс Nette\HtmlStringable. Latte и Forms используют этот интерфейс, например, чтобы отличать объекты, у которых метод __toString() возвращает HTML-код. Это предотвращает двойное экранирование, если вы, например, выводите объект в шаблоне через {$el}.

версия: 4.x