В прошлый раз, когда мы смотрели на Jakarta Struts , я объяснил уровни персистентности и бизнес-объектов и написал код для извлечения списка тем из базы данных и представления их в виде объектов. Мы использовали Struts ActionMapping и ActionForward, чтобы добраться до JSP, который фактически отображает темы на экране. Давайте посмотрим на эту задачу немного подробнее сейчас.
Если вы этого еще не сделали, вы можете сначала загрузить код, который мы будем использовать:
- Загрузить struts_webforum_jars.zip (855 КБ)
- Скачать struts_webforum_src.zip (52,2 КБ)
Отображение тем
Struts поставляется с beli-тегом taglib, который предоставляет функции, связанные с JavaBeans, и html
taglib, который отображает общие атрибуты HTML и обработчики событий JavaScript. Оба они используются в topics.jsp
, как и та logic
библиотека тегов, которая упоминалась в прошлый раз
Следующий фрагмент кода (из topics.jsp
) иллюстрирует использование тега bean:message
, который используется для визуализации интернационализированного сообщения на основе ключа сообщения и локали. Это позволяет нам отделять строковые константы пользовательского интерфейса, которые используются в приложении, вместо того, чтобы жестко кодировать их в JSP. Эти строки ищутся в файле свойств Java, который указан с помощью тега message-resources
в файле конфигурации Struts.
<bean:message key="app.doctype" /> <html:html locale="true" xhtml="true"> <head> <bean:message key="app.content.language" /> <bean:message key="app.content.type" /> <link rel="stylesheet" type="text/css" href="<html:rewrite page="/assets/css/webforum.css" />" /> <title> <bean:message key="topics.title" /> - <bean:message key="app.title" /> </title> </head>
Этот код использует теги bean:message
для визуализации объявления DOCTYPE, а также мета-объявлений HTML Content-Type и Content-Language. Тег html:html
используется для визуализации тега HTML в начале документа с использованием декларации формата XHTML и соответствующей локали.
В файле struts-config.xml
объявление для файла свойств, содержащего строковые константы, выглядит следующим образом:
<message-resources parameter="com.johntopley.webforum.view.Resources" null="false"/> <message-resources parameter="com.johntopley.webforum.view.GlobalErrors" null="false" key="GlobalErrors"/>
Этот раздел должен следовать за разделом < action-mappings
>. Он сообщает Struts, что нужно найти все строковые константы пользовательского интерфейса в файлах Resources.properties
и GlobalErrors.properties
, и что оба этих файла находятся в пакете com.johntopley.webforum.view
.
Атрибут null
не очень интуитивен; установите значение false, чтобы в JSP отображались все ошибки, возникающие из-за невозможности найти ресурс сообщения в файле свойств. Это особенно полезно при отладке. Я также объявил, что буду использовать отдельный файл свойств для хранения строковых констант ошибок. Атрибут key
сообщает Struts, что я хочу, чтобы сообщения из второго файла свойств сохранялись с отдельным именованным ключом пакета — GlobalErrors
. Тег bean:message
должен иметь атрибут bean="GlobalErrors"
для отображения сообщения из этого файла. Обратите внимание, что хранить строки ошибок в файле свойств, который отделен от остального текста пользовательского интерфейса, совершенно необязательно. Я делаю это, потому что мне нравится дополнительное разделение.
Тег html:rewrite
используется в операторе связи таблицы стилей для создания правильного URL-адреса для пути к таблице стилей. Затем он передается в тег HTML-ссылки как обычно.
Давайте посмотрим на файл Resources.properties
. Соглашение, которое я люблю использовать, состоит в том, чтобы ставить перед приложением строковые константы. Я префикс всех других строковых констант, с именем JSP, в котором они используются. Например, это константы для страницы Темы:
#-- topics.jsp -- topics.guest.welcome.1=Welcome Guest. topics.guest.welcome.2=Register topics.guest.welcome.3= or topics.guest.welcome.4=Log in topics.guest.welcome.5=to create a new topic. topics.heading.column1=Subject topics.heading.column2=Replies topics.heading.column3=Author topics.heading.column4=Posted topics.newtopic=New Topic topics.notopics=There are no topics to display. topics.table.summary=A list of topics and summary information about them topics.title=Topics topics.viewtopics=View Topics
Напомним, что в прошлый раз в HTTP-запросе хранился объект Posts
, содержащий упорядоченную коллекцию объектов Post
. Нам как-то нужно циклически проходить через эту коллекцию и извлекать соответствующую информацию из каждого объекта Post
. Struts приходит нам на помощь с тремя тегами из logic
taglib.
Мы используем тег итерации для перебора коллекции. Но, прежде всего, нам необходимо учитывать тот факт, что мы можем иметь дело с пустой коллекцией: может не отображаться никаких тем. Тег notEmpty
и его противоположный номер, empty
тег, допускают условную обработку на основе того, является ли конкретная переменная нулевой или пустым объектом String, Collection или Map. Вот набросок кода из topics.jsp
:
<logic:notEmpty name="com.johntopley.webforum.postlist" property="posts"> <logic:iterate id="topic" name="com.johntopley.webforum.postlist" type="com.johntopley.webforum.model.Post" property="posts" length="16"> . <%-- There are posts, so display the details. --%> . </logic:iterate> </logic:notEmpty> <logic:empty name="com.johntopley.webforum.postlist" property="posts"> . <%-- No posts, display a message. --%> . </logic:empty>
Здесь много чего происходит. Атрибут name
используемый в notEmpty
, iterate
и empty
, сообщает Struts имя JavaBean, который содержит коллекцию, для которой необходимо notEmpty
iterate
. В данном случае это com.johntopley.webforum.postlist
, который, возможно, вы помните, является ключом HTTP-запроса, под которым мы сохранили объекты Posts
, в ViewTopicsAction
Action ViewTopicsAction
. Мы использовали немного косвенности, потому что мы не обращались к ключу напрямую. Вместо этого мы ссылались на него через POST_LIST_KEY
статическую переменную KeyConstants
классе KeyConstants
. Кстати, обратите внимание, как я префикс ключа к имени пакета, чтобы избежать коллизий пространства имен в запросе HTTP.
Атрибут property
также используется во всех трех тегах. Он сообщает Struts, какое из свойств объекта, на которое ссылается name
, содержит коллекцию. Значением этого атрибута должно быть имя метода доступа (getter) в классе коллекции, но без «get» (или для логических свойств, без «is»). Наш класс Posts
имеет метод getPosts
, поэтому мы просто getPosts
property
значение posts
.
Атрибут id
в теге iterate
создает локальную переменную в теле тега; мы можем использовать это для ссылки на текущую строку в цикле. Я назвал это topic
. Представьте, что это похоже на переменную «i» в цикле «for». Я также использовал атрибут length
, чтобы указать, что мы хотим отображать только шестнадцать лучших тем.
Наконец, атрибут type
— это полностью определенное имя класса, до которого мы хотим уменьшить значение объекта, представляющего каждую строку. Это важно; если мы не выполним это приведение, мы не сможем получить доступ к свойствам каждого отдельного объекта Post
в коллекции, так как мы все равно будем иметь дело с java.lang.Object
(s).
Обратите внимание, что комбинация notEmpty
и empty
тегов фактически позволяет нам создавать структуру if / else, не прибегая к коду скрипта JSP. Рекомендуется избегать скриплетов в JSP — они должны содержать только разметку, а не голый код Java. Этот условный шаблон обработки if / else повторяется с другими тегами в logic
теговой библиотеке.
Добавление тела в цикл, наконец, дает нам наш список тем:
<logic:notEmpty name="com.johntopley.webforum.postlist" property="posts"> <logic:iterate id="topic" name="com.johntopley.webforum.postlist" type="com.johntopley.webforum.model.Post" property="posts" length="16"> <tr> <td class="topics"> <bean:write name="topic" property="subject" filter="true" /> </td> <td class="replies"> <bean:write name="topic" property="replyCount" /> </td> <td class="author"> <bean:write name="topic" property="author" /> </td> <td class="posted"> <bean:write name="topic" property="timestamp" formatKey="app.date.format" /> </td> </tr> </logic:iterate> </logic:notEmpty> <logic:empty name="com.johntopley.webforum.postlist" property="posts"> <tr> <td class="topics"> <bean:message key="topics.notopics" /> </td> <td class="replies"></td> <td class="author"></td> <td class="posted"></td> </tr> </logic:empty>
Наиболее важным моментом, который следует здесь отметить, является использование тегов bean:write
для визуализации содержимого JavaBean, на которое ссылается атрибут name
этого тега. Мы соединяем все это вместе, устанавливая значение этого атрибута в bean-объект topic
, предоставленный тегом iterate
для каждой строки в цикле. Поскольку мы приводим объекты в коллекции к правильному типу, тег write
может получить доступ к свойствам каждого объекта Post
, используя соглашение JavaBeans, объясненное ранее.
Теперь мы написали большую часть JSP, которая требуется для отображения списка тем. Без дальнейших проволочек давайте напишем код, который превращает каждую тему в гиперссылку
Создание тематических гиперссылок
Тег Struts html:link
используется для визуализации гиперссылки, как следует из названия. В приведенном ниже коде используется атрибут forward
тега, чтобы указать имя глобального ActionForward, содержащего URI гиперссылки (в данном случае ViewTopic
):
<tr> <td class="topics"> <html:link forward="ViewTopic" name="params"> <bean:write name="topic" property="subject" filter="true" /> </html:link> </td>
Я объясню использование атрибута name
через минуту. Прежде всего, я хочу объяснить, что делает атрибут filter
в теге bean:write
. Проще говоря, при значении true он заменяет зарезервированные символы HTML их эквивалентными объектами. Хотя значение по умолчанию — true, я включил его в код, чтобы сделать процесс понятным.
Теперь вернемся к поставленной задаче. Вы также можете вспомнить из первой части этой серии, что мы хотим, чтобы гиперссылки по темам отображались в цвете непосещенных ссылок, когда на тему делаются новые ответы. Это означает, что гиперссылка должна быть слегка изменена при получении нового ответа. Так или иначе, нам нужно закодировать в гиперссылке не только уникальный идентификатор темы, которую мы хотим просмотреть, но также и другой параметр запроса, который указывает количество ответов на эту тему.
Тег link
позволяет нам ссылаться на JavaBean, который содержит карту параметров запроса — или имеет свойство, которое само содержит такую карту. В этом случае мы можем создать такой bean-компонент и ссылаться на него, используя атрибут name
в теге link
. Тег jsp:useBean
используется вне цикла logic:iterate
для создания bean- params
под названием params
, который является HashMap.
В цикле у нас есть двухстрочный скриптлет, который:
- хранит количество ответов на тему в HashMap под ключом с именем
r
- хранит идентификатор сообщения темы в HashMap под ключом с именем
pid
К сожалению, я не смог найти способ устранить код скриптлета. Но, по крайней мере, это только две строки. Я уверен, что это можно устранить с помощью JSTL, но это тема для другого урока! Полный код показан ниже:
<jsp:useBean id="params" class="java.util.HashMap" scope="page" /> <logic:notEmpty name="com.johntopley.webforum.postlist" property="posts"> <logic:iterate id="topic" name="com.johntopley.webforum.postlist" type="com.johntopley.webforum.model.Post" property="posts" length="16"> <% params.put("r", topic.getReplyCount()); params.put("pid", topic.getPostID()); %> <tr> <td class="topics"> <html:link forward="ViewTopic" name="params"> <bean:write name="topic" property="subject" filter="true" /> </html:link> </td> . . . </tr> </logic:iterate> </logic:notEmpty>
Фактические гиперссылки выглядят так:
<a href="/webforum/ViewTopic.do?pid=1&r=1">Some Topic</a> <a href="/webforum/ViewTopic.do?pid=2&r=18">Another Topic</a>
В следующий раз
В следующей части мы рассмотрим добавление к перечисленным темам гиперссылок, при нажатии на которые пользователи могут просматривать текст темы и любые связанные с ней ответы.