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
getHomeR = defaultLayout $ do
    setTitle "Welcome to Yesod!"
    $(widgetFile "homepage")

Обратите внимание на странного вида строку:

$(widgetFile "homepage")

Это и есть ответ на наш вопрос. Самым распространённым и простым способом использования виджетов является функция widgetFile. Но вы спросите, почему она заключена в скобки со знаком доллара впереди? Причина в том, что эта строка, строго говоря, не является Haskell-кодом. Это шаблонный код, превращающийся в реальный Haskell в процессе сборки проекта. Сейчас нам необязательно знать, как всё это работает, поэтому двигаемся дальше.

Функция widgetFile принимает единственный аргумент, являющийся именем виджета. По договорённости в Yesod имя виджета совпадает с именами шаблонов, составляющих данный виджет. Именно поэтому мы указываем имя “homepage”, поскольку данный виджет представлен тремя одноимёнными файлами, расположенными в каталоге templates. Таким образом, главная страница состоит из одного-единственного виджета. Если же мы хотим составить эту страницу из трёх упомянутых выше виджетов, мы просто указываем полные файловые пути до них (начиная от корня каталога templates):

getHomeR :: Handler Html
getHomeR = defaultLayout $ do
    setTitle "Welcome to Yesod!"
    $(widgetFile "home/login/login")
    $(widgetFile "home/socialLogin/socialLogin")
    $(widgetFile "home/register/register")

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

Умолчальная обёртка

Взглянем ещё раз на функцию getHomeR:

getHomeR :: Handler Html
getHomeR = defaultLayout $ do
    setTitle "Welcome to Yesod!"
    $(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
getHomeR = defaultLayout $ do
    setTitle "Welcome to Yesod!"
    $(widgetFile "homepage")
    $(widgetFile "homepage")
    $(widgetFile "homepage")
    $(widgetFile "homepage")

Если пересобрать проект и взглянуть на страницу, мы увидим четыре фразы, выровненные по центру, потому что главная страница теперь составлена из четырёх виджетов.

Разделяй и властвуй

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

То же относится и к JS. Мы можем хранить JS-функции, специфичные для виджета, в его .julius-шаблоне (и это правильный путь), а можем вынести в некий глобальный JS-файл. Позже мы увидим, как это сделать.

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