Динамические сниппеты
Довольно часто при разработке приложений возникает необходимость выполнения операций AJAX, например, в отдельных строках таблицы или элементах списка. В качестве примера, мы можем выбрать список статей, позволяя вошедшему в систему пользователю выбрать «нравится/не нравится» для каждой из них. Код презентера и соответствующего шаблона без AJAX будет выглядеть примерно так (я перечисляю наиболее важные фрагменты, код предполагает наличие сервиса для разметки рейтингов и получения коллекции статей — конкретная реализация не важна для целей данного руководства):
Template:
Аяксизация
Теперь давайте привнесем AJAX в это простое приложение. Изменение
рейтинга статьи не настолько важно, чтобы требовать HTTP-запрос с
перенаправлением, поэтому в идеале это должно быть сделано с помощью
AJAX в фоновом режиме. Мы будем использовать скрипт обработчика из дополнений с
обычным соглашением, что AJAX ссылки имеют CSS класс ajax
.
Однако как это сделать конкретно? Nette предлагает 2 способа: способ динамических фрагментов и способ компонентов. У обоих есть свои плюсы и минусы, поэтому мы покажем их по очереди.
Путь динамических сниппетов
В терминологии Latte динамический сниппет — это особый случай
использования тега {snippet}
, когда в имени сниппета используется
переменная. Такой сниппет не может быть найден просто в любом месте
шаблона — он должен быть обернут статическим сниппетом, т. е. обычный,
или внутри {snippetArea}
. Мы можем изменить наш шаблон следующим
образом.
Каждая статья теперь определяет один сниппет, который имеет ID статьи
в заголовке. Все эти фрагменты затем объединяются в один фрагмент под
названием articlesContainer
. Если мы опустим этот фрагмент обертки, Latte
предупредит нас об исключении.
Всё, что осталось сделать, это добавить перерисовку в презентер — просто перерисовать статическую обертку.
Измените родственный метод handleUnlike()
таким же образом, и AJAX
будет работать!
Однако у этого решения есть и обратная сторона. Если мы подробнее рассмотрим, как работает AJAX-запрос, то обнаружим, что хотя приложение выглядит эффективным внешне (оно возвращает только один сниппет для данной статьи), на самом деле оно отображает все сниппеты на сервере. Он поместил нужный фрагмент в нашу полезную нагрузку, а остальные отбросил (таким образом, совершенно без необходимости, он также извлек их из базы данных).
Чтобы оптимизировать этот процесс, нам понадобится действие, при
котором мы передаем коллекцию $articles
шаблону (скажем, в методе
renderDefault()
). Мы воспользуемся тем, что обработка сигнала
происходит до методов render<Something>
:
Теперь, когда сигнал обрабатывается, вместо коллекции со всеми
статьями в шаблон передается только массив с одной статьей — той,
которую мы хотим отобразить и отправить в полезной нагрузке браузеру.
Таким образом, {foreach}
будет выполнен только один раз, и никаких
дополнительных сниппетов не будет выведено.
Компонентный способ
Совершенно другое решение использует другой подход, чтобы избежать
динамических сниппетов. Хитрость заключается в том, чтобы перенести
всю логику в отдельный компонент — отныне у нас не презентер, который
будет заботиться о вводе рейтинга, а специальный LikeControl
. Класс
будет выглядеть следующим образом (кроме того, он также будет
содержать render
, handleUnlike
и т. д. методы):
Шаблон компонента:
Конечно, мы изменим шаблон представления, и нам придется добавить фабрику к презентеру. Поскольку мы будем создавать компонент столько раз, сколько статей мы получим из базы данных, мы будем использовать класс Multiplier для этого:
Вид шаблона сокращен до необходимого минимума (и полностью свободен от сниппетов!):
Мы почти закончили: приложение теперь будет работать в AJAX. Здесь также необходимо оптимизировать приложение, так как из-за использования базы данных Nette, обработка сигнала будет излишне загружать все статьи из базы данных вместо одной. Однако преимущество в том, что рендеринга не будет, потому что на самом деле рендерится только наш компонент.