Минул год с момента выхода первой статьи серии "Изучение Grails". Как и было обещано в последней статье 2008 г., в новом году будут продемонстрированы новые приложения. Итак, скажите "до свидания" программе планирования путешествий и встречайте систему для работы с блогами.
Мы будем называть это приложение Blogito. Можно считать, что это название произошло либо от испанского "маленький блог", либо от знаменитой цитаты Декарта: "Я мыслю – значит, я существую" (Cogito ergo sum). Полную версию исходного кода можно загрузить с сайта blogito.org. Эта и следующая статьи посвящены разработке функциональности данного приложения.
В настоящей статье основное внимание уделяется тому, как можно радикально изменить внешний вид приложения Grails. Наше прошлогоднее приложение для путешественников выглядело пугающе, вероятно, понравиться оно могло только программистам (правда, если быть честным, то на тот момент функциональность была важнее интерфейса). Теперь же, немного подправив страницы CSS и несколько шаблонов, мы создадим Web-приложение, которое будет разительно отличаться от стандартного интерфейса Grails. Читая статью, вы также освежите в памяти такие аспекты Grails, как генерация каркасов (scaffolding), автоматическое присвоение временных меток, редактирование шаблонов по умолчанию, создание собственных библиотек тегов, а также настройка ключевых конфигурационных файлов, например Bootstrap.groovy и URLMapper.groovy.
Однако вначале вам необходимо установить Grails 1.1, которая на момент написания этой статьи еще была в статусе бета-версии.
Grails работает быстрее, если у вас установлена Java 1.5 или 1.6. Чтобы проверить версию Java, выполните команду java -version.
Убедившись в наличии Java 1.5 или 1.6, для установки Grails достаточно просто выполнить следующее.
- Download файл grails.zip с сайта Grails.
- Распаковать grails.zip.
- Создать переменную окружения
GRAILS_HOME. - Добавить каталог GRAILS_HOME/bin в переменную
PATH.
Если у вас есть приложение, созданное на основе предыдущей версии Grail, то вы можете перевести его на последнюю версию, выполнив команду grails upgrade. Однако что делать, если требуется работать одновременно с несколькими версиями Grails?
При работе с UNIX®-подобной операционной системой (т.е. ОС семейства UNIX, Linux® или OS X) можно легко переключаться между различными версиями, используя символическую ссылку в качестве значения переменной окружения $GRAILS_HOME. Например, на моем компьютере GRAILS_HOME указывает на каталог /opt/grails, и я могу легко менять версию Grails при помощи команды ln -s, как показано в листинге 1.
Листинг 1. Создание символической ссылки для переменной
$GRAILS_HOME в UNIX, Linux и Mac OS X
$ ln -s /opt/grails-1.1-beta1 grails
$ ls -l | grep "grails"
lrwxr-xr-x 1 sdavis admin 17 Dec 5 11:12 grails -> grails-1.1-beta1/
drwxr-xr-x 14 sdavis admin 476 Nov 10 2006 grails-0.3.1
drwxr-xr-x 16 sdavis admin 544 Feb 9 2007 grails-0.4.1
drwxr-xr-x 17 sdavis admin 578 Apr 6 2007 grails-0.4.2
drwxr-xr-x 17 sdavis admin 578 Jun 15 2007 grails-0.5
drwxr-xr-x 19 sdavis admin 646 Jul 30 2007 grails-0.5.6
drwxr-xr-x 18 sdavis admin 612 Sep 18 2007 grails-0.6
drwxr-xr-x 19 sdavis admin 646 Feb 19 2008 grails-1.0
drwxr-xr-x 18 sdavis admin 612 Apr 5 2008 grails-1.0.2
drwxr-xr-x 18 sdavis admin 612 Oct 9 21:46 grails-1.0.3
drwxr-xr-x 18 sdavis admin 612 Nov 24 20:43 grails-1.0.4
drwxr-xr-x 18 sdavis admin 612 Dec 5 11:13 grails-1.1-beta1
|
Если вы работаете под Windows®, то проще всего непосредственно изменять значение переменной %GRAILS_HOME%. При этом не забывайте каждый раз перезапускать все выполняющиеся командные интерпретаторы.
Выполните команду grails -version, чтобы убедиться в корректности значения переменной GRAILS_HOME и в том, что вы используете последнюю версию Grails. Результат команды должен выглядеть как в листинге 2.
Листинг 2. Результат выполнения команды
grails -version
$ grails -version
Welcome to Grails 1.1-beta2 - http://grails.org/
Licensed under Apache Standard License 2.0
Grails home is set to: /opt/grails
|
После того как установлен Grails 1.1, можно переходить непосредственно к разработке нового приложения.
Выполните команду grails create-app blogito для создания структуры каталогов будущего приложения. Затем перейдите в каталог blogito и запустите команду grails create-domain-class Entry, которая создаст класс для представления записей в блоге. Далее откройте файл Entry.groovy в каталоге grails-app/domain и добавьте в него строки из листинга 3.
Листинг 3. Создание класса
Entry
class Entry {
static constraints = {
title()
summary(maxSize:1000)
dateCreated()
lastUpdated()
}
String title
String summary
Date dateCreated
Date lastUpdated
}
|
Каждый экземпляр класса Entry содержит поля title и summary. Если задать ограничение maxSize равным 1000 символам, то простое текстовое поле в автоматически генерируемых формах HTML будет заменено на текстовую область summary.
Не забывайте, что поля dateCreated и lastUpdated выполняют специальную функцию в Grails. Они хранят временные метки, которые отлично подходят для приложений, работающих с блогами. В частности, они позволяют отображать наиболее свежие записи в начале списка.
Создав класс для представления объектов из предметной области, можно переходить к контроллеру. Выполните команду grails create-controller Entry и добавьте содержимое листинга 4 в файл grails-app/controllers/EntryController.groovy.
Листинг 4. Создание класса
EntryController
class EntryController {
def scaffold = Entry
}
|
Строка def scaffold = Entryвыглядит, на первый взгляд, просто, однако на самом деле представляет собой инструкцию, получив которую Grails сгенерирует остальную часть каркаса для поддержки класса Entry. Как вы скоро увидите, будет создана таблица entry, содержащая колонки, соответствующие каждому полю класса Entry, а также специальные колонки ID (первичный ключ) и version (для наложения оптимистических блокировок). Кроме того, будет сгенерирован полный набор серверных страниц Groovy (GSP), реализующих рутинные, но необходимые функции CRUD (для добавления, выборки, обновления и удаления записей).
Далее выполните команду grails run-app и откройте страницу с адресом http://localhost:8080/blogito в браузере. Кликните на ссылке EntryController, а затем на New Entry. Как видите, в форме ввода новой записи содержатся все поля класса Entry (рисунок 1). К сожалению, поля, соответствующие временным меткам, не должны быть доступны пользователям, поэтому придется подкорректировать шаблоны по умолчанию, чтобы устранить эту проблему.
Рисунок 1. Редактируемые временные метки в форме для создания экземпляров Entry
Редактирование шаблонов по умолчанию
После выполнения команды grails generate-views Entry поля dateCreated и lastUpdated можно вручную удалить из файлов GSP, однако это скорее лечение симптомов, чем болезни. Скорее всего, данные поля никогда не должны фигурировать в формах для создания и редактирования записей, поэтому лучше изменить шаблон, применяющийся при выполнении def scaffold.
Для этого сначала выполните команду grails install-templates, а затем загляните в каталог src/templates/scaffolding, в котором находятся файлы create.gsp и edit.gsp. Добавьте dateCreated и lastUpdated в список excludedProps в каждом из файлов (листинг 5).
Листинг 5. Исключение полей с временными метками из шаблонов страниц list.gsp и show.gsp
excludedProps = ['version',
'id',
'dateCreated',
'lastUpdated',
Events.ONLOAD_EVENT,
Events.BEFORE_DELETE_EVENT,
Events.BEFORE_INSERT_EVENT,
Events.BEFORE_UPDATE_EVENT]
|
Перезапустите Grails и убедитесь, что эти поля больше не представлены в формах HTML (рисунок 2).
Рисунок 2. Форма без временных меток
Изменение принципа сортировки записей
Начав добавлять новые записи, вы обнаружите, что по умолчанию они сортируются в порядке убывания ID. При этом сообщения в блогах, как правило, отображаются в обратном хронологическом порядке, т.е. наиболее свежие показываются вверху списка. В предыдущих версиях Grails для изменения порядка следования записей приходилось вручную редактировать код замыкания list в файле EntryController.groovy, добавляя две строки, показанные в конце листинга 6. С одной стороны, это несложно, но с другой – этот код уже не будет частью автоматически генерируемого каркаса приложения. (Если вас интересует реализация контроллера по умолчанию, то вы можете заглянуть в файл src/templates/scaffolding/Controller.groovy или выполнить команду grails generate-controller Entry.)
Листинг 6. Сортировка в Grails 1.0.x
def list = {
if(!params.max) params.max = 10
if(!params.sort) params.sort = "lastUpdated"
if(!params.order) params.order = "desc"
[ entryList: Entry.list( params ) ]
}
|
В Grails 1.1 появилась простая, но исключительно полезная конструкция в статическом блоке mapping – sort. Добавьте блок mapping, приведенный в листинге 7, в файл Entry.groovy. Теперь сортировка задается в классе модели, а следовательно можно по-прежнему использовать стандартный def scaffold в контроллере.
Листинг 7. Добавление конструкции
sort в статический блок mapping
class Entry {
static constraints = {
title()
summary(maxSize:1000)
dateCreated()
lastUpdated()
}
static mapping = {
sort "lastUpdated":"desc"
}
String title
String summary
Date dateCreated
Date lastUpdated
}
|
Перезапустите Grails и проверьте, что вновь добавляемые записи действительно отображаются вверху, как показано на рисунке 3.
Рисунок 3. Новый порядок сортировки записей
Создание тестовых записей в процессе разработки
Вы заметили, что созданные вами записи исчезают при каждом новом запуске Grails. Это не ошибка, так и было задумано. Таблица entry уничтожается и создается заново при каждом запуске Grails. В этом можно убедиться, заглянув в файл grails-app/conf/DataSource.groovy. Как видите, в режиме разработки значением db-create является create-drop.
Это значение можно заменить на update, но это также не лучший вариант. На начальных этапах разработки схема базы данных, как правило, нестабильна, поскольку постоянно добавляются и удаляются новые поля, модифицируются ограничения и т.д. Поэтому имеет смысл сохранить режим create-drop, пока все не стабилизируется.
Можно изменить файл grails-app/conf/BootStrap.groovy таким образом, чтобы избавиться от необходимости каждый раз вводить тестовые данные в процессе разработки. В листинге 8 приведен пример кода, который добавляет новые записи при каждом запуске Grails.
Листинг 8. Добавление тестовых записей в процессе разработки
import grails.util.GrailsUtil
class BootStrap {
def init = { servletContext ->
switch(GrailsUtil.environment){
case "development":
new Entry(
title:"Grails 1.1 beta is out", summary:"Check out the new features").save()
new Entry(
title:"Just Released - Groovy 1.6 beta 2", summary:"It is looking good.").save()
break
case "production":
break
}
}
def destroy = {
}
}
|
Перезапустив Grails, вы должны увидеть в таблице entry записи, показанные на рисунке 4.
Рисунок 4. Тестовые записи, добавляемые при старте приложения
Автоматически сгенерированная HTML-таблица выглядит неплохо для начала, однако очевидно, что она не может претендовать на роль постоянного решения для Blogito. Как правило, страницы блогов показывают дату, заголовок и краткое содержание записей в вертикальном, а не горизонтальном порядке, причем записи расположены одна под другой.
Чтобы сделать соответствующие изменения, сначала выполните команду grails generate-views Entry. Страницы GSP, которые ранее являлись частью динамического каркаса, теперь должны появиться в каталоге grails-app/views/entry. Откройте файл list.gsp и измените заголовок с Entry List на Blogito. Далее удалите блоки <h1> и <g:if> и замените элемент <div class="list"> на фрагмент, показанный в листинге 9.
Листинг 9. Изменение внешнего вида страницы list.gsp
<div class="list">
<g:each in="${entryInstanceList}" status="i" var="entryInstance">
<div class="entry">
<span class="entry-date">${entryInstance.lastUpdated}</span>
<h2><g:link action="show" id="${entryInstance.id}"
>${entryInstance.title}</g:link></h2>
<p>${entryInstance.summary}</p>
</div>
</g:each>
</div>
|
Обратите внимание, насколько упростился код страницы. Теги <fieldValue> можно удалить, поскольку они привязывают поля классов модели к полям формы HTML, а на данной странице этого не требуется. Каждый экземпляр Entry помещается в именованный элемент <div>, а поле lastUpdated – в именованный элемент <span>. При помощи атрибутов class можно использовать стили CSS, о которых будет рассказано чуть ниже. Поля title и summary находятся внутри стандартных HTML-тегов для разметки заголовков и абзацев.
Обновите страницу со списком в браузере (см. рисунок 5). Пока сложно утверждать, что страница сильно улучшилась, однако для соответствующего оформления достаточно добавить лишь несколько простых инструкций CSS.
Рисунок 5. Новый список записей без CSS
Добавьте CSS из листинга 10 в конец файла web-app/css/main.css.
Листинг 10. Изменение внешнего вида list.gsp при помощи CSS
/* Изменение внешнего вида Blogito */
.entry {
padding-bottom: 2em;
}
.entry-date {
color: #999;
}
|
Теперь снова обновите страницу в браузере. Как видите, страница стала выглядеть несколько более стильно (рисунок 6). Мы еще не закончили, но для начала она смотрится неплохо.
Рисунок 6. Новый список записей с применением CSS
Создание библиотеки тегов Date
Теперь пришло время попробовать улучшить визуальное представление даты последнего редактирования (lastUpdated). Если вы планируете повторно использовать этот элемент, то лучше всего его поместить в библиотеку тегов (TagLib). Выполните команду grails create-tag-lib Date и добавьте код, показанный в листинге 11, в файл grails-app/taglib/DateTagLib.groovy.
Листинг 11. Класс
DateTagLib
import java.text.SimpleDateFormat
class DateTagLib {
def longDate = {attrs, body ->
//разбор полученной даты
def b = attrs.body ?: body()
def d = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").parse(b)
//если атрибут format не задан, то использовать следующий
def pattern = attrs["format"] ?: "EEEE, MMM d, yyyy"
out << new SimpleDateFormat(pattern).format(d)
}
}
|
Далее поместите поле lastUpdated в файле grails-app/views/entry/list.gsp в новый тег <g:longDate>, как показано в листинге 12.
Листинг 12. Использование тега
<g:longDate> на странице list.gsp
<div class="entry">
<span class="entry-date"><g:longDate>$
{entryInstance.lastUpdated}</g:longDate></span>
<h2>${entryInstance.title}</h2>
<p>${entryInstance.summary}</p>
</div>
|
Перезапустите Grails и обновите страницу в браузере. Вы должны увидеть даты в новом формате (рисунок 7).
Рисунок 7. Отображение дат в новом формате при помощи тега
<g:longDate>
Размещение элементов на странице выглядит неплохо, и было бы целесообразно использовать такую же схему для show.gsp. Для этого создайте страницу _entry.gsp в каталоге grails-app/views/entry и добавьте в нее код из листинга 13 (разумеется, вы с тем же успехом можете скопировать его из list.gsp).
Листинг 13. Код _entry.gsp
<div class="entry">
<span class="entry-date"><g:longDate>
${entryInstance.lastUpdated}</g:longDate></span>
<h2><g:link action="show" id="${
entryInstance.id}">${entryInstance.title}</g:link></h2>
<p>${entryInstance.summary}</p>
</div>
|
Подредактируйте страницу list.gsp как показано в листинге 14, чтобы начать использовать новый частичный шаблон.
Листинг 14. Использование частичного шаблона _entry.gsp в list.gsp
<div class="list">
<g:each in="${entryInstanceList}" status="i" var="entryInstance">
<g:render template="entry" bean="${entryInstance}" var="entryInstance" />
</g:each>
</div>
|
Теперь этот частичный шаблон можно также применить на странице show.gsp (листинг 15).
Листинг 15. Использование частичного шаблона _entry.gsp в show.gsp
<div class="body">
<g:render template="entry" bean="${entryInstance}" var="entryInstance" />
<div class="buttons">
<!-- и так далее -->
</div>
</div>
|
Обновите список в браузере. Он должен выглядеть в точности так же, как и ранее. Теперь нажмите на заголовок записи и убедитесь, что для ее показа применяется тот же шаблон.
Как видите, приложение начинает принимать очертания, и пришло время заменить стандартный заголовок Grails на ваш собственный.
Вы не увидите ссылки на логотип Grails ни в list.gsp, ни в show.gsp. Как вы помните, Grails использует SiteMesh для компоновки страниц из различных частей. Если вы посмотрите в код страницы grails-app/views/layouts/main.gsp, то увидите, в каком месте включается файл grails_logo.jpg.
Создайте еще одни частичный шаблон под именем named _header.gsp в каталоге grails-app/views/layouts и добавьте в него фрагмент кода из листинга 16. Обратите внимание на то, что Blogito – это обратная ссылка на главную страницу.
Листинг 16. Код частичного шаблона _header.gsp
<div id="header">
<p><g:link class="header-main" controller="entry">
Blogito</g:link></p>
<p class="header-sub">A tiny little blog</p>
</div>
|
Теперь измените main.gsp, включив в него файл _header.gsp, как показано в листинге 17.
Листинг 17. Использование нового шаблона _header.gsp на страницу main.gsp
<body>
<div id="spinner" class="spinner" style="display:none;">
<img src="${createLinkTo(dir:'images',file:'spinner.gif')}"
alt="Spinner" />
</div>
<g:render template="/layouts/header"/>
<g:layoutBody />
</body>
|
Наконец, добавьте в to web-app/css/main.css стили, показанные в листинге 18.
Листинг 18. Стили CSS для частичного шаблона _header.gsp
#header {
background: #67c;
padding: 2em 1em 2em 1em;
margin-bottom: 1em;
}
a.header-main:link, a.header-main:visited {
color: #fff;
font-size: 3em;
font-weight: bold;
}
.header-sub {
color: #fff;
font-size: 1.25em;
font-style: italic;
}
|
После обновления страница должна выглядеть как на рисунке 8. Нажмите на заголовке какой-либо записи, а затем вновь на ссылку Blogito вверху экрана, чтобы вернуться на главную страницу.
Рисунок 8. Новое оформление верхней части страницы
Скрытие панели навигации до момента аутентификации
Еще одним характерным признаком приложения Grails является навигационная панель. Несмотря на то что аутентификация пользователя будет реализована только в следующей статье, уже сейчас можно не показывать панель навигации пользователям, не выполнившим вход в систему. Для этого достаточно заключить элемент <div> панели в простую проверку <g:if>. Этот элемент проверяет наличие переменной user в области видимости сессии.
Измените страницы list.gsp и show.gsp как показано в листинге 19.
Листинг 19. Скрытие навигационной панели, если пользователь не аутентифицирован
<g:if test="${session.user}">
<div class="nav">
<span class="menuButton">
<a class="home" href="${createLinkTo(dir:'')}">Home</a>
</span>
<span class="menuButton">
<g:link class="create" action="create">New Entry</g:link>
</span>
</div>
</g:if>
|
Добавьте такую же проверку для кнопок на странице show.gsp (вы же не хотите, чтобы неаутентифицированные пользователи могли редактировать и удалять записи, правда?).
Наконец, добавьте еще один маленький штрих к странице list.gsp из эстетических соображений. Вынесите блок paginateButtons <div> из body <div> как показано в листинге 20. Таким образом, панель растянется на всю ширину экрана, улучшив восприятие нижней части страницы.
Листинг 20. Вынос блока
paginateButtons <div> за пределы body <div> по эстетическим соображениям
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<meta name="layout" content="main" />
<title>Blogito</title>
</head>
<body>
<g:if test="${session.user}">
<div class="nav">
<span class="menuButton">
<a class="home" href="${createLinkTo(dir:'')}">Home</a>
</span>
<span class="menuButton">
<g:link class="create" action="create">New Entry</g:link>
</span>
</div>
</g:if>
<div class="body">
<div class="list">
<g:each in="${entryInstanceList}" status="i" var="entryInstance">
<g:render template="entry" bean="${entryInstance}" var="entryInstance" />
</g:each>
</div>
</div>
<div class="paginateButtons">
<g:paginate total="${Entry.count()}" />
</div>
</body>
</html>
|
Также добавьте фрагмент CSS, показанный в листинге 21, чтобы блок paginateButtons <div> отображался под body <div>, а не рядом с ним.
Листинг 21. CSS, гарантирующая, что блок
paginateButtons <div> будет отображаться внизу экрана
.paginateButtons{
clear: left;
}
|
Последний раз обновите страницу в браузере. Она должна выглядеть как на рисунке 9.
Рисунок 9. Скрытие навигационной панели
Теперь, когда все готово, осталось сделать EntryController домашней страницей по умолчанию. Для этого необходимо добавить связывание относительного пути / (последнего символа в URL http://localhost:9090/blogito/) с EntryController. Измените файл grails-app/conf/UrlMappings.groovy в соответствии с листингом 22.
Листинг 22. Задание
EntryController в качестве домашней страницы по умолчанию
class UrlMappings {
static mappings = {
"/$controller/$action?/$id?"{
constraints {
// добавьте ограничения здесь
}
}
"/"(controller:"entry")
"500"(view:'/error')
}
}
|
Целью этой статьи было показать пример того, как можно придать новый вид вашему приложению Grails. Вы можете изменить цвета, шрифты и отступы блочных элементов при помощи всего нескольких строк в CSS. Вы также можете повторно использовать некоторые фрагменты кода, оформляя их в виде частичных шаблонов и библиотек тегов. В итоге получится приложение, использующее все преимущества Grails и обладающее оригинальным интерфейсом.
В следующей статье мы продолжим работу над приложением Blogito. Будет добавлен класс User, с помощью которого разные пользователи смогут добавлять записи. Вы также познакомитесь с кодеками Grails и поэкспериментируете с отображениями URL. Не забывайте, что полную версию приложения можно загрузить с сайта http://blogito.org. Пока же – получайте удовольствие от изучения Grails.
Научиться
- Оригинал статьи: Mastering Grails: Give your Grails applications a facelift (Скотт Дэвис, developerWorks, январь 2009 г.). (EN)
- Посетите Web-сайт Grails. (EN)
- Ознакомьтесь с документацией по инфраструктуре Grails – своего рода "библией" данной технологии. (EN)
- В статье Использование классов при проектирование Web-страниц (Молли Хольцшлаг, Molly Holzschlag, developerWorks, сентябрь 1999 г.) описываются два способа применения стилевых классов для облегчения проектирования и дальнейших изменений документов HTML. (EN)
- Ознакомьтесь с советом Будьте аккуратны при выборе размеров шрифтов, приведенным в серии W3C, посвященной обеспечению качества (Quality Assurance). (EN)
-
Модель блоков: узнайте больше об атрибутах
paddingиmarginв CSS. (EN) - Узнайте больше о Groovy и Grails, прочитав последнюю книгу Скотта Дэвиса под названием Рецепты Groovy (Скотт Дэвис, Pragmatic Programmers, 2007 г.). (EN)
- Узнайте больше о Groovy, посетив официальный сайт проекта. (EN)
- На сайте AboutGroovy.com вы найдете последние новости из мира Groovy и ссылки на статьи. (EN)
Получить продукты и технологии
-
Grails: загрузите последнюю версию Grails. (EN)
Обсудить
- Примите участие в обсуждении материала на форуме.
- Читайте блоги developerWorks и присоединяйтесь к нашему сообществу. (EN)