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
Все примеры предполагают созданный псевдоним (alias):
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-атрибуты мы можем изменять и читать тремя способами, зависит от вас, какой вам больше понравится. Первый из них – через свойства (property):
$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()
умеет упрощать
составление query-параметров в URL:
echo Html::el('a')->href('index.php', [
'id' => 10,
'lang' => 'en',
]);
// '<a href="index.php?id=10&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 < 20</span>'
И наоборот, внутреннее содержимое получаем методами getHtml()
или
getText()
. Вторая из них удаляет из вывода HTML-теги и преобразует
HTML-сущности в символы.
echo $el->getHtml(); // '10 < 20'
echo $el->getText(); // '10 < 20'
Дочерние узлы
Внутренность элемента может быть также массивом дочерних (children)
узлов. Каждый из них может быть либо строкой, либо другим Html
элементом. Вставляем их с помощью addHtml()
или addText()
:
$el = Html::el('span')
->addHtml('hello<br>')
->addText('10 < 20')
->addHtml( Html::el('br') );
// <span>hello<br>10 < 20<br></span>
Другой способ создания и вставки нового Html
узла:
$ul = Html::el('ul');
$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
, добавляет элемент в конец.
// вставляет элемент на первую позицию и сдвигает остальные
$el->insert(0, Html::el('span'));
Все узлы получаем методом getChildren()
и удаляем их методом
removeChildren()
.
Создание document fragment
Если мы хотим работать с массивом узлов и нас не интересует
обрамляющий элемент, мы можем создать так называемый document fragment,
передав null
вместо имени элемента:
$el = Html::el(null)
->addHtml('hello<br>')
->addText('10 < 20')
->addHtml( Html::el('br') );
// hello<br>10 < 20<br>
Более быстрый способ создания фрагмента предлагают методы
fromHtml()
и fromText()
:
$el = Html::fromHtml('hello<br>');
echo $el; // 'hello<br>'
$el = Html::fromText('10 < 20');
echo $el; // '10 < 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"'
Важной особенностью является автоматическая защита от Cross Site Scripting (XSS). Все значения
атрибутов или содержимое, вставленное через setText()
или
addText()
, надежно экранируются:
echo Html::el('div')
->title('" onmouseover="bad()')
->setText('<script>bad()</script>');
// <div title='" onmouseover="bad()'><script>bad()</script></div>
Преобразование HTML ↔ текст
Для преобразования HTML в текст вы можете использовать статический
метод htmlToText()
:
echo Html::htmlToText('<span>One & Two</span>'); // 'One & Two'
HtmlStringable
Объект Nette\Utils\Html
реализует интерфейс Nette\HtmlStringable
, с
помощью которого, например, Latte или формы различают объекты, которые
имеют метод __toString()
, возвращающий HTML-код. Так что не произойдет
двойного экранирования, если, например, мы выведем объект в шаблоне с
помощью {$el}
.