Объединить строки в JSP EL?
Вопрос
У меня есть список bean-компонентов, каждый из которых имеет свойство, которое само по себе является списком адресов электронной почты.
<c:forEach items="${upcomingSchedule}" var="conf">
<div class='scheduled' title="${conf.subject}" id="scheduled<c:out value="${conf.id}"/>">
...
</div>
</c:forEach>
Это делает один <div>
на каждый компонент в списке.
Для подсписка я хотел бы объединить каждую запись в списке, чтобы сформировать одну строку, которая будет отображаться как часть <div>
's title
атрибут.Почему?Поскольку мы используем библиотеку javascript (mootools), чтобы превратить это <div>
в плавающую подсказку, а библиотека превращает title
в текст подсказки.
Так что если ${conf.subject}
был «Тема», в конечном итоге мне бы хотелось title
принадлежащий <div>
быть «Тема:blah@blah.com, blah2@blah2.com и т. д.», содержащий все адреса электронной почты подсписка.
Как я могу это сделать с помощью JSP EL?Я стараюсь избегать помещения блоков скриптлетов в файл jsp.
Решение 2
Придумал несколько грязный способ сделать это:
<c:forEach items="${upcomingSchedule}" var="conf">
<c:set var="title" value="${conf.subject}: "/>
<c:forEach items="${conf.invitees}" var="invitee">
<c:set var="title" value="${title} ${invitee}, "/>
</c:forEach>
<div class='scheduled' title="${title}" id="scheduled<c:out value="${conf.id}"/>">
...
</div>
</c:forEach>
Я просто использую <c:set>
несколько раз, ссылаясь на собственное значение, чтобы добавить / объединить строки.
Другие советы
«Чистый» способ сделать это — использовать функцию.Как JSTL join
функция не будет работать на Collection
, вы можете без особых проблем написать свой собственный и повторно использовать его повсюду вместо того, чтобы вырезать и вставлять большой кусок кода цикла.
Вам нужна реализация функции и TLD, чтобы ваше веб-приложение знало, где ее найти.Соберите их в JAR и поместите в каталог WEB-INF/lib.
Вот схема:
com/x/taglib/core/StringUtil.java
package com.x.taglib.core;
public class StringUtil {
public static String join(Iterable<?> elements, CharSequence separator) {
StringBuilder buf = new StringBuilder();
if (elements != null) {
if (separator == null)
separator = " ";
for (Object o : elements) {
if (buf.length() > 0)
buf.append(separator);
buf.append(o);
}
}
return buf.toString();
}
}
МЕТА-INF/x-c.tld:
<taglib xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd" version="2.0">
<tlib-version>1.0</tlib-version>
<short-name>x-c</short-name>
<uri>http://dev.x.com/taglib/core/1.0</uri>
<function>
<description>Join elements of an Iterable into a string.</description>
<display-name>Join</display-name>
<name>join</name>
<function-class>com.x.taglib.core.StringUtil</function-class>
<function-signature>java.lang.String join(java.lang.Iterable, java.lang.CharSequence)</function-signature>
</function>
</taglib>
Хотя TLD немного многословен, знание одного из них — хороший навык для любого разработчика, работающего с JSP.И поскольку вы выбрали для представления такой стандарт, как JSP, велика вероятность, что у вас есть инструменты, которые вам помогут.
Этот подход имеет много преимуществ по сравнению с альтернативой добавления дополнительных методов в базовую модель.Эту функцию можно написать один раз и повторно использовать в любом проекте.Он работает со сторонней библиотекой с закрытым исходным кодом.Различные разделители могут поддерживаться в разных контекстах, не засоряя API модели новым методом для каждого.
Самое главное, он поддерживает разделение ролей разработки представления и модели-контроллера. Задачи в этих двух областях часто выполняются разными людьми в разное время.Поддержание слабой связи между этими уровнями сводит к минимуму сложность и затраты на обслуживание.Когда даже такое тривиальное изменение, как использование другого разделителя в презентации, требует от программиста модификации библиотеки, вы получаете очень дорогую и громоздкую систему.
А StringUtil
класс одинаков независимо от того, представлен ли он как функция EL или нет.Единственным «дополнительным» необходимым является TLD, который тривиален;инструмент может легко сгенерировать его.
Не могли бы вы использовать это? Похоже, он хочет массив вместо списка ..
${fn:join(array, ";")}
http: // java .sun.com / продукты / JSP / JSTL / 1.1 / документы / tlddocs / п / join.fn.html р>
Если ваш подсписок является ArrayList и вы делаете это:
<div class='scheduled' title="${conf.subject}: ${conf.invitees}" id="scheduled${conf.id}">
вы получаете почти то, что вам нужно. Р>
Разница лишь в том, что заголовок будет: " Тема: [blah@blah.com, blah2@blah2.com и т. д.] ".
Может быть, может быть достаточно хорошо для вас. Р>
Я думаю, это то, что вы хотите:
<c:forEach var="tab" items="${tabs}">
<c:set var="tabAttrs" value='${tabAttrs} ${tab.key}="${tab.value}"'/>
</c:forEach>
В этом случае у меня была хеш-карта с идентификатором вкладки (ключ) и URL-адресом (значение).Переменная tabAttrs до этого не установлена.Таким образом, он просто устанавливает текущее значение tabAttrs ('' для начала) плюс выражение ключ/значение.
Просто поместите строку рядом с var с сервера, например так:
<c:forEach items="${upcomingSchedule}" var="conf">
<div class='scheduled' title="${conf.subject}"
id="scheduled${conf.id}">
...
</div>
</c:forEach>
Слишком поздно !!!
Способ реализации библиотек тегов, похоже, значительно изменился с тех пор, как этот ответ был первоначально опубликован, поэтому в итоге я внес некоторые радикальные изменения, чтобы все заработало.Мой окончательный результат был:
Файл библиотеки тегов:
<?xml version="1.0" encoding="UTF-8"?>
<taglib version="2.1" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-jsptaglibrary_2_1.xsd">
<tlib-version>1.0</tlib-version>
<short-name>string_util</short-name>
<uri>/WEB-INF/tlds/string_util</uri>
<info>String Utilities</info>
<tag>
<name>join</name>
<info>Join the contents of any iterable using a separator</info>
<tag-class>XXX.taglib.JoinObjects</tag-class>
<body-content>tagdependent</body-content>
<attribute>
<name>iterable</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
<type>java.lang.Iterable</type>
</attribute>
<attribute>
<name>separator</name>
<required>false</required>
<rtexprvalue>false</rtexprvalue>
<type>java.lang.String</type>
</attribute>
</tag>
<tag>
<name>joinints</name>
<info>Join the contents of an integer array using a separator</info>
<tag-class>XXX.taglib.JoinInts</tag-class>
<body-content>tagdependent</body-content>
<attribute>
<name>integers</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
<type>java.lang.Integer[]</type>
</attribute>
<attribute>
<name>separator</name>
<required>false</required>
<rtexprvalue>false</rtexprvalue>
<type>java.lang.String</type>
</attribute>
</tag>
</taglib>
JoinInts.java
public class JoinInts extends TagSupport {
int[] integers;
String separator = ",";
@Override
public int doStartTag() throws JspException {
if (integers != null) {
StringBuilder buf = new StringBuilder();
if (separator == null) {
separator = " ";
}
for (int i: integers) {
if (buf.length() > 0) {
buf.append(separator);
}
buf.append(i);
}
try {
pageContext.getOut().print(buf);
} catch (IOException ex) {
Logger.getLogger(JoinInts.class.getName()).log(Level.SEVERE, null, ex);
}
}
return SKIP_BODY;
}
@Override
public int doEndTag() throws JspException {
return EVAL_PAGE;
}
public int[] getIntegers() {
return integers;
}
public void setIntegers(int[] integers) {
this.integers = integers;
}
public String getSeparator() {
return separator;
}
public void setSeparator(String separator) {
this.separator = separator;
}
}
Чтобы использовать его:
<%@ taglib prefix="su" uri="/WEB-INF/tlds/string_util.tld" %>
[new Date(${row.key}), <su:joinints integers="${row.value}" separator="," />],
Вы можете использовать EL 3.0 Stream API. Например, если у вас есть список строк,
<div>${stringList.stream().reduce(",", (n,p)->p.concat(n))}</div>
Если у вас есть список объектов, например, Person (firstName, lastName) и вы хотите объединить только одно свойство из них (ex firstName), которое вы можете использовать map,
<div>${personList.stream().map(p->p.getFirstName()).reduce(",", (n,p)->p.concat(n))}</div>
В вашем случае вы могли бы использовать что-то подобное (уберите также последний ','),
<c:forEach items="${upcomingSchedule}" var="conf">
<c:set var="separator" value=","/>
<c:set var="titleFront" value="${conf.subject}: "/>
<c:set var="titleEnd" value="${conf.invitees.stream().reduce(separator, (n,p)->p.concat(n))}"/>
<div class='scheduled' title="${titleFront} ${titleEnd.isEmpty() ? "" : titleEnd.substring(0, titleEnd.length()-1)}" id="scheduled<c:out value="${conf.id}"/>">
...
</div>
</c:forEach>
Будьте осторожны! API потока EL 3.0 был завершен до того, как API Java 8 Stream , и он отличается от этого. Они не могут использовать оба API, потому что это нарушит обратную совместимость.