Статьи

Первые шаги с Джакартой Struts, часть 2

В прошлый раз, когда мы смотрели на Jakarta Struts , я объяснил уровни персистентности и бизнес-объектов и написал код для извлечения списка тем из базы данных и представления их в виде объектов. Мы использовали Struts ActionMapping и ActionForward, чтобы добраться до JSP, который фактически отображает темы на экране. Давайте посмотрим на эту задачу немного подробнее сейчас.

Если вы этого еще не сделали, вы можете сначала загрузить код, который мы будем использовать:

Отображение тем

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&amp;r=1">Some Topic</a>  <a href="/webforum/ViewTopic.do?pid=2&amp;r=18">Another Topic</a> 
В следующий раз

В следующей части мы рассмотрим добавление к перечисленным темам гиперссылок, при нажатии на которые пользователи могут просматривать текст темы и любые связанные с ней ответы.