Yesod: виджеты
Приветствую!
В прошлой заметке мы поближе познакомились с Yesod-шаблонами. Теперь мы готовы рассмотреть одну из фундаментальных идей в Yesod, а именно идею виджета (widget).
Не просто XML
Задумаемся: что такое по сути своей HTML-страница? Это не более чем XML-строка, а если ещё проще, совокупность вложенных тегов с неким текстовым содержимым. Всё. Но с другой стороны, заходя на сайт, неужели мы смотрим на его главную страницу как на XML-строку? Едва ли. Страница предстаёт нашему взору не как совокупность каких-то тегов, а как совокупность понятных нам частей: вот вверху слева расположен логотип, а вверху справа - главное меню, вот боковая колонка, вот форма подписки на новости, а вот подвал с меткой об авторских правах и контактными ссылками.
Авторы Yesod не захотели иметь дело с XML-строкой, а потому ввели простое и важное понятие “виджет”. Виджет - это некая (более-менее завершённая) часть веб-страницы. Таким образом, страница - это уже не множество XML-тегов, это совокупность виджетов. Главное меню - виджет, форма регистрации - виджет, блок социальных кнопок - виджет, подвал - виджет, и так далее. Главное преимущество такого подхода - разделение. А мы с вами знаем, что разделение большой монолитной сущности на систему малых сущностей - это мощное оружие в борьбе со сложностью. Оперируя виджетами, мы легко можем компоновать из них страницы любой сложности.
Шаблоны как части виджетов
Пришла пора открыть правду: уже известные нам шаблоны .hamlet
, .lucius
и .julius
- это составные кирпичики виджетов. Подчёркиваю: виджетов, а не страниц! Из шаблонов строятся виджеты, а из виджетов - страницы. Впрочем, простая страница может состоять и из одного-единственного виджета.
Вот почему в одной из предыдущих заметок я привёл следующий пример структуры из каталога templates
:
templates/
home/
login/
login.hamlet
login.julius
login.lucius
register/
register.hamlet
register.julius
register.lucius
socialLogin/
socialLogin.hamlet
socialLogin.julius
socialLogin.lucius
Теперь всё встало на свои места: домашняя страница (home
) представлена тремя виджетами (login
, register
, socialLogin
), каждый из которых построен из трёх шаблонов.
Таким образом, мы уже не думаем об HTML-коде домашней страницы или об общем JS-скрипте для домашней страницы. Вместо этого мы думаем об HTML-коде для формы входа, о CSS-стилях для группы кнопок социального логина и о JS-валидаторе для формы регистрации. Уже нет монолита (страницы), а есть понятные нашему разуму блоки (виджеты). Разумеется, в процессе сборки нашего проекта всё это превращается в некие монолитные HTML/CSS/JS, но нам с вами беспокоиться об этом уже не нужно, об этом позаботится Yesod.
Использование виджетов
Итак, как же использовать эти самые виджеты? Чтобы это понять, откроем уже известный нам файл Handler/Home.hs
и перепишем обработчик GET
-запросов к главной странице следующим образом:
getHomeR :: Handler Html
= defaultLayout $ do
getHomeR "Welcome to Yesod!"
setTitle $(widgetFile "homepage")
Обратите внимание на странного вида строку:
$(widgetFile "homepage")
Это и есть ответ на наш вопрос. Самым распространённым и простым способом использования виджетов является функция widgetFile
. Но вы спросите, почему она заключена в скобки со знаком доллара впереди? Причина в том, что эта строка, строго говоря, не является Haskell-кодом. Это шаблонный код, превращающийся в реальный Haskell в процессе сборки проекта. Сейчас нам необязательно знать, как всё это работает, поэтому двигаемся дальше.
Функция widgetFile
принимает единственный аргумент, являющийся именем виджета. По договорённости в Yesod имя виджета совпадает с именами шаблонов, составляющих данный виджет. Именно поэтому мы указываем имя “homepage”, поскольку данный виджет представлен тремя одноимёнными файлами, расположенными в каталоге templates
. Таким образом, главная страница состоит из одного-единственного виджета. Если же мы хотим составить эту страницу из трёх упомянутых выше виджетов, мы просто указываем полные файловые пути до них (начиная от корня каталога templates
):
getHomeR :: Handler Html
= defaultLayout $ do
getHomeR "Welcome to Yesod!"
setTitle $(widgetFile "home/login/login")
$(widgetFile "home/socialLogin/socialLogin")
$(widgetFile "home/register/register")
Как вы уже догадались, функция widgetFile
, получая название виджета, сама находит соответствующие шаблонные файлы и вытаскивает из них содержимое. И как вы видите, скомпоновать страницу из нескольких виджетов проще простого, нужно лишь перечислить их друг за другом.
Умолчальная обёртка
Взглянем ещё раз на функцию getHomeR
:
getHomeR :: Handler Html
= defaultLayout $ do
getHomeR "Welcome to Yesod!"
setTitle $(widgetFile "homepage")
Назначение функции setTitle
, полагаю, понятно с первого взгляда: она устанавливает заголовок страницы, отображающийся на текущей вкладке нашего браузера. А что же такое defaultLayout
?
Заглянем в каталог templates
и вспомним файл default-layout-wrapper.hamlet
. Как вы уже знаете, этот файл представляет собой общий HTML-скелет для всех (или, по крайней мере, многих) страниц. Так вот функция defaultLayout
и является обёрткой для нашей страницы: она берёт виджет “homepage” и заворачивает его в умолчальный HTML-скелет, возвращая готовый HTML-код страницы. Именно поэтому, создавая виджет “homepage”, мы думаем исключительно об этом виджете, абстрагируясь от общих, умолчальных HTML-аспектов.
Разумеется, мы можем создать несколько HTML-скелетов для разных групп страниц нашего веб-приложения, но это тема для отдельного разговора.
Вперёд!
Настало время потрогать наш единственный виджет, и начнём мы с файла homepage.hamlet
. Откроем его и напишем в нём следующее:
<div .centered>
<h1>Я - единственный виджет этой страницы!
Теперь откроем homepage.lucius
и напишем в нём:
.centered {
text-align: center;
}
А вот файл homepage.julius
нам пока не нужен, поэтому просто очистим его содержимое, сохраним и закроем.
Теперь выполняем знакомую нам команду:
$ yesod devel -p 3001
Проект будет пересобран и запущен на localhost:3001
. Заходим - и перед нами наша отцентрированная фраза, как мы и ожидали. В данном случае вся страница представлена одним виджетом, но мы могли бы сделать и так:
getHomeR :: Handler Html
= defaultLayout $ do
getHomeR "Welcome to Yesod!"
setTitle $(widgetFile "homepage")
$(widgetFile "homepage")
$(widgetFile "homepage")
$(widgetFile "homepage")
Если пересобрать проект и взглянуть на страницу, мы увидим четыре фразы, выровненные по центру, потому что главная страница теперь составлена из четырёх виджетов.
Разделяй и властвуй
Разумеется, мы могли бы вынести класс .centered
в некий глобальный стилевой файл (и позже я покажу как это сделать), но зачем? Если виджет воспринимается как нечто целостное и некие стили предназначены лишь для этого виджета - пусть эти стили и хранятся только в его .lucius
-шаблоне. Ведь наша цель - разбить монолитность, а не наращивать её. Впрочем, если класс .centered
планируется использовать и в других виджетах - тогда другое дело, мы должны вынести его в некое общее место (и кстати, сделать это можно разными способами).
То же относится и к JS. Мы можем хранить JS-функции, специфичные для виджета, в его .julius
-шаблоне (и это правильный путь), а можем вынести в некий глобальный JS-файл. Позже мы увидим, как это сделать.
Собственно, на этом можно закончить. Вышеизложенного уже достаточно для того, чтобы начать что-то творить, хотя в будущих заметках мы увидим, что с виджетами не всё так просто.