Html-помощник

Каждое веб-приложение формирует большое количество HTML-разметки. Если разметка статическая, её можно эффективно сформировать смешиванием PHP и HTML в одном файле, но когда разметка динамическая, становится сложно формировать её без дополнительной помощи. Yii предоставляет такую помощь в виде Html-помощника, который обеспечивает набор статических методов для обработки часто-используемых HTML тэгов, их атрибутов и содержимого.

Примечание: Если ваша разметка близка к статической, лучше использовать непосредственно HTML. Нет никакой необходимости в том, чтобы всё подряд оборачивать вызовами Html-помощника.

Основы

Так как формирование динамической HTML-разметки при помощи конкатенации строк очень быстро вносит хаос в проект, Yii предоставляет набор методов для управления атрибутами тэгов и формирования тэгов на основе этих атрибутов.

Формирование тэгов

Код формирования тэга выглядит следующим образом:

<?= Html::tag('p', Html::encode($user->name), ['class' => 'username']) ?>

Здесь первый аргумент - это название тэга. Второй - содержимое, которое будет заключено между открывающим и закрывающим тэгами. Заметьте, что мы используем Html::encode. Это связано с тем, что содержимое не экранируется автоматически, чтобы можно было по-необходимости использовать чистый HTML. Третий аргумент - это массив настроек для HTML-кода, а другими словами - массив атрибутов для тэга. В этом массиве ключи являются названиями атрибутов, например class, href или target, а значения в массиве являются значениями этих атрибутов.

Вышеприведённый код сформирует следующую HTML-разметку:

<p class="username">samdark</p>

В тех случаях, когда вам необходимо только закрыть или открыть тэг, можно использовать методы Html::beginTag() и Html::endTag().

Дополнительные атрибуты используются во многих методах Html-помощника и в различных виджетах. Во всех этих случаях в дело вступают механизмы дополнительной обработки данных, о которых следует знать:

  • Если значение равно null, соответствующий атрибут не будет выведен.
  • Атрибуты, значения которых имеют тип boolean, будут интерпретированы как логические атрибуты.
  • Значения атрибутов будут экранированы с использованием метода Html::encode().
  • Если в качестве значения атрибута передан массив, он будет обработан следующим образом:

    • Если атрибут является одним из атрибутов данных, указанных в yii\helpers\Html::$dataAttributes, например data или ng, то будет сформирован список атрибутов по одному для каждого элемента массива. Например, код 'data' => ['id' => 1, 'name' => 'yii'] сформирует data-id="1" data-name="yii"; а код 'data' => ['params' => ['id' => 1, 'name' => 'yii'], 'status' => 'ok'] сформирует data-params='{"id":1,"name":"yii"}' data-status="ok". Заметьте, что в последнем примере используется формат JSON для формирования вывода вложенного массива.

    • Если атрибут НЕ является атрибутом данных, значение будет сконвертировано в формат JSON. Например, код ['params' => ['id' => 1, 'name' => 'yii'] сформирует params='{"id":1,"name":"yii"}'.

Формирование CSS классов и стилей

При формировании атрибутов для HTML-тэгов часто приходится начинать с некоторых атрибутов по умолчанию, которые затем необходимо изменять. Для того, чтобы добавить или удалить CSS-класс, можно использовать следующий подход:

$options = ['class' => 'btn btn-default'];

if ($type === 'success') {
    Html::removeCssClass($options, 'btn-default');
    Html::addCssClass($options, 'btn-success');
}

echo Html::tag('div', 'Всё получилось!', $options);

// в случае, если $type содержит строку 'success', будет сформирован вывод
// <div class="btn btn-success">Всё получилось!</div>

Можно указать несколько CSS-классов, используя синтаксис массива:

$options = ['class' => ['btn', 'btn-default']];

echo Html::tag('div', 'Сохранить', $options);
// выведет '<div class="btn btn-default">Сохранить</div>'

При добавлении или удалении классов тоже можно использовать массивы:

$options = ['class' => 'btn'];

if ($type === 'success') {
    Html::addCssClass($options, ['btn-success', 'btn-lg']);
}

echo Html::tag('div', 'Сохранить', $options);
// выведет '<div class="btn btn-success btn-lg">Сохранить</div>'

Html::addCssClass() предотвращает дублирование классов, поэтому можно не беспокоиться о том, что какой-либо класс будет добавлен дважды:

$options = ['class' => 'btn btn-default'];

Html::addCssClass($options, 'btn-default'); // класс 'btn-default' уже добавлен

echo Html::tag('div', 'Сохранить', $options);
// выведет '<div class="btn btn-default">Сохранить</div>'

Если CSS-класс задаётся с помощью массива, можно использовать именованные ключи массива для обозначения логического предназначения класса. В этом случае класс с таким же ключом будет проигнорирован во время использования Html::addCssClass():

$options = [
    'class' => [
        'btn',
        'theme' => 'btn-default',
    ]
];

Html::addCssClass($options, ['theme' => 'btn-success']); // ключ 'theme' уже использован

echo Html::tag('div', 'Сохранить', $options);
// отобразит '<div class="btn btn-default">Сохранить</div>'

CSS-стили могут быть установлены схожим образом с помощью атрибута style:

$options = ['style' => ['width' => '100px', 'height' => '100px']];

// в результате будет style="width: 100px; height: 200px; position: absolute;"
Html::addCssStyle($options, 'height: 200px; position: absolute;');

// в результате будет style="position: absolute;"
Html::removeCssStyle($options, ['width', 'height']);

При использовании метода addCssStyle() можно указать массив, пары ключ-значение которого соответствуют названиям и значениям CSS-свойств, или строку, например width: 100px; height: 200px;. Эти два формата могут быть преобразованы друг в друга с помощью методов cssStyleFromArray() и cssStyleToArray(). Метод removeCssStyle() принимает на вход массив атрибутов, которые следует удалить. Если удаляется всего один атрибут, его можно передать строкой.

Экранирование контента

Для корректного и безопасного отображения контента специальные символы в HTML-коде должны быть экранированы. В чистом PHP это осуществляется с помощью функций htmlspecialchars и htmlspecialchars_decode. Проблема использования этих функций заключается в том, что приходится указывать кодировку и дополнительные флаги во время каждого вызова. Поскольку флаги всё время одинаковы, а кодировка остаётся одной и той же в пределах приложения, Yii в целях безопасности предоставляет два компактных и простых в использовании метода:

$userName = Html::encode($user->name);
echo $userName;

$decodedUserName = Html::decode($userName);

Формы

Разметка форм состоит из повторяющихся действий и часто приводит к ошибкам, поэтому есть целый набор методов, которые помогают справиться с этой задачей.

Примечание: Рассмотрите возможность использования ActiveForm, если работаете с моделями и нуждаетесь в валидации данных.

Создание форм

Открывающий тэг формы может быть выведен с помощью метода beginForm() как показано ниже:

<?= Html::beginForm(['order/update', 'id' => $id], 'post', ['enctype' => 'multipart/form-data']) ?>

Первый аргумент - это URL-адрес, на который будет отправлена форма. Он может быть задан в виде Yii-маршрута и параметров, подходящих для передачи в метод Url::to(). Второй аргумент - способ отправки данных: по умолчанию это post. Третий аргумент - массив атрибутов формы. В данном примере мы изменяем способ кодирования данных в POST-запросе на multipart/form-data. Это необходимо для загрузки файлов.

Закрыть тэг формы можно простым кодом:

<?= Html::endForm() ?>

Кнопки

Для формирования кнопок можно использовать следующий код:

<?= Html::button('Нажми меня!', ['class' => 'teaser']) ?>
<?= Html::submitButton('Отправить', ['class' => 'submit']) ?>
<?= Html::resetButton('Сбросить', ['class' => 'reset']) ?>

Первый аргумент во всех трёх методах - это название кнопки, а второй - её атрибуты. Название кнопки не экранируется, поэтому при получении данных от конечных пользователей экранируйте их с помощью метода Html::encode().

Поля ввода

Методы формирования полей ввода делятся на две группы. Первые начинаются со слова active и называются "active inputs", а вторые не содержат в своём названии слова active. "Active inputs" формируют поля ввода, которые получают данные из указанного атрибута модели, в то время как обычные методы формирования полей ввода непосредственно принимают данные.

Наиболее общие методы для формирования полей ввода:

// тип, название поля ввода, установленное в поле значение, атрибуты поля ввода
<?= Html::input('text', 'username', $user->name, ['class' => $username]) ?>

// тип, модель, атрибут модели, атрибуты поля ввода
<?= Html::activeInput('text', $user, 'name', ['class' => $username]) ?>

Если вам заранее известен тип поля ввода, удобнее будет пользоваться этими вспомогательными методами:

Сигнатура методов для формирования радио-переключателей и чекбоксов немного отличается:

<?= Html::radio('agree', true, ['label' => 'Я согласен']);
<?= Html::activeRadio($model, 'agree', ['class' => 'agreement'])

<?= Html::checkbox('agree', true, ['label' => 'Я согласен']);
<?= Html::activeCheckbox($model, 'agree', ['class' => 'agreement'])

Выпадающие и обычные списки могут быть сформированы следующим образом:

<?= Html::dropDownList('list', $currentUserId, ArrayHelper::map($userModels, 'id', 'name')) ?>
<?= Html::activeDropDownList($users, 'id', ArrayHelper::map($userModels, 'id', 'name')) ?>

<?= Html::listBox('list', $currentUserId, ArrayHelper::map($userModels, 'id', 'name')) ?>
<?= Html::activeListBox($users, 'id', ArrayHelper::map($userModels, 'id', 'name')) ?>

Первый аргумент - это значение атрибута name для поля ввода, второй - выбранное в нём значение и, наконец, третий аргумент - это массив, ключами которого является список значений для формирования списка, а значениями массива являются названия пунктов в нём.

Если вы хотите сделать в списке доступными для выбора несколько вариантов, хорошим выбором будет список чекбоксов:

<?= Html::checkboxList('roles', [16, 42], ArrayHelper::map($roleModels, 'id', 'name')) ?>
<?= Html::activeCheckboxList($user, 'role', ArrayHelper::map($roleModels, 'id', 'name')) ?>

Если же нет, используйте радио-переключатель:

<?= Html::radioList('roles', [16, 42], ArrayHelper::map($roleModels, 'id', 'name')) ?>
<?= Html::activeRadioList($user, 'role', ArrayHelper::map($roleModels, 'id', 'name')) ?>

Тэги label и отображение ошибок

Также как и для полей ввода, есть два метода формирования тэгов label для форм. Есть "active label", считывающий данные из модели и обычный тэг "label", принимающий на вход непосредственно сами данные:

<?= Html::label('Имя пользователя', 'username', ['class' => 'label username']) ?>
<?= Html::activeLabel($user, 'username', ['class' => 'label username'])

Для отображения общего списка ошибок формы на основе модели или моделей можно использовать следующий подход:

<?= Html::errorSummary($posts, ['class' => 'errors']) ?>

Для отображения одной отдельной ошибки:

<?= Html::error($post, 'title', ['class' => 'error']) ?>

Атрибуты name и value для полей ввода

Также имеются методы для получения значений атрибутов id, name и value для полей ввода, сформированных на основе моделей. Эти методы используются в основном внутренними механизмами, но иногда могут оказаться подходящими и для прямого использования:

// Post[title]
echo Html::getInputName($post, 'title');

// post-title
echo Html::getInputId($post, 'title');

// моё первое сообщение
echo Html::getAttributeValue($post, 'title');

// $post->authors[0]
echo Html::getAttributeValue($post, '[0]authors[0]');

В вышеприведённом коде первый аргумент - это модель, а второй аргумент - выражение для поиска атрибута модели. В самом простом случае оно представляет собой название атрибута модели, а вообще это может быть название, которому предшествует (и/или после которого следует) индекс массива. Такой формат используется в основном для табличного ввода данных:

  • [0]content используется для табличного ввода данных, чтобы указать на атрибут "content" первой модели табличного ввода;
  • dates[0] указывает на первый элемент массива, с помощью которого задан атрибут модели "dates";
  • [0]dates[0] указывает на первый элемент массива, с помощью которого задан атрибут "dates" первой модели табличного ввода.

Для того, чтобы получить название атрибута модели в чистом виде (без суффиксов и префиксов), можно использовать следующий подход:

// dates
echo Html::getAttributeName('dates[0]');

Подключение встроенных стилей и скриптов

Для формирования встроенных скриптов и стилей есть два метода:

<?= Html::style('.danger { color: #f00; }') ?>

Результатом будет:

<style>.danger { color: #f00; }</style>


<?= Html::script('alert("Привет!");', ['defer' => true]);

Результатом будет:

<script defer>alert("Привет!");</script>

Если вы хотите подключить внешний CSS-файл:

<?= Html::cssFile('@web/css/ie5.css', ['condition' => 'IE 5']) ?>

В результате получится:

<!--[if IE 5]>
    <link href="http://example.com/css/ie5.css" />
<![endif]-->

Первый аргумент - URL-адрес. Второй - массив настроек. Помимо обычных настроек можно указать следующие:

  • condition для оборачивания тэга <link с помощью условных комментариев с определённым условием. Надеемся, что вам никогда не понадобятся условные комментарии ;)
  • noscript может быть установлен в значение true, чтобы обернуть тэг <link с помощью тэга <noscript>, таким образом скрипт будет подключён только в том случае, если у пользователя в браузере нет поддержки JavaScript или же пользователь сам отключил его.

Для подключения JavaScript-файла используйте код:

<?= Html::jsFile('@web/js/main.js') ?>

Как и в случае с CSS, первый аргумент указывает ссылку на файл, который должен быть подключен. Настройки задаются во втором аргументе. Можно указать настройку condition таким же образом, каким она указывается для метода cssFile.

Ссылки

Существует удобный метод формирования ссылок:

<?= Html::a('Профиль', ['user/view', 'id' => $id], ['class' => 'profile-link']) ?>

Первый аргумент - это текст ссылки. Он не экранируется, поэтому при использовании данных, полученных от конечных пользователей, необходимо экранировать его с помощью Html::encode(). Второй аргумент - это содержимое атрибута href тэга <a. Смотрите Url::to() для получения подробностей относительно значений, которые могут быть переданы в качестве второго аргумента. Третий аргумент - массив атрибутов для тэга.

Если нужно сформировать ссылку mailto, можно использовать следующий подход:

<?= Html::mailto('Обратная связь', 'admin@example.com') ?>

Изображения

Для формирования тэга изображения используйте следующий код:

<?= Html::img('@web/images/logo.png', ['alt' => 'Наш логотип']) ?>

в результате получится:

<img src="http://example.com/images/logo.png" alt="Наш логотип" />

Помимо псевдонимов первый аргумент может принимать маршруты, параметры и URL-адреса таким же образом как и метод Url::to().

Списки

Неупорядоченные списки могут быть сформированы следующим образом:

<?= Html::ul($posts, ['item' => function($item, $index) {
    return Html::tag(
        'li',
        $this->render('post', ['item' => $item]),
        ['class' => 'post']
    );
}]) ?>

Для формирования упорядоченных списков используйте Html::ol().