Элементы 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">'

Установка:

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> является пустым элементом

Атрибуты 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 se ignoruje
$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():

$el = Html::el('input');
$el->{'data-max-size'} = '500x300'; // není tolik elegantní
$el->data('max-size', '500x300'); // je elegatní
echo $el; // '<input data-max-size="500x300">'

Если значением атрибута данных является массив, он автоматически сериализуется в 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>

Другой способ создания и вставки нового узла Html:

$el = Html::el('ul')
	->create('li', ['class' => 'first'])
		->setText('první');
// <ul><li class="first">první</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, то элемент добавляется последним.

// vloží prvek na první pozici a ostatní posune
$el->insert(0, Html::el('span'));

Все узлы извлекаются с помощью метода getChildren() и удаляются с помощью метода removeChildren().

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

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

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

Методы fromHtml() и fromText() предлагают более быстрый способ создания фрагмента:

$el = Html::fromHtml('hello<br>');
echo $el; // 'hello<br>'

$el = Html::fromText('10 < 20');
echo $el; // '10 &lt; 20'

Генерирование вывода 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"'

Важной особенностью является автоматическая защита от межсайтового скриптинга (XSS). Любые значения атрибутов или содержимое, вставленное через setText() или addText(), надежно экранируется:

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