Объединить строки в JSP EL?

У меня есть список beans, каждый из которых имеет свойство, которое само является списком адресов электронной почты.

<c:forEach items="${upcomingSchedule}" var="conf">
    <div class='scheduled' title="${conf.subject}" id="scheduled<c:out value="${conf.id}"/>">
    ...
    </div>
</c:forEach>

Это отображает один <div> за bean в списке.

Для подписок, что я хотел бы сделать, это объединить каждую из записей в списке, чтобы сформировать один String, который будет отображаться как часть атрибута <div> title. Зачем? Поскольку мы используем javascript-библиотеку (mootools), чтобы превратить этот <div> в плавающий подсказку, а библиотека превращает title в текст всплывающей подсказки.

Итак, если ${conf.subject} был "Subject", в конечном итоге мне хотелось бы, чтобы title <div> был "Subject: [email protected], [email protected] и т.д.", содержащий всех адресов электронной почты под-списка.

Как я могу это сделать с помощью JSP EL? Я стараюсь держаться подальше от размещения блоков сценария в файле jsp.

Ответ 1

Выяснил несколько грязный способ сделать это:

<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> несколько раз, ссылаясь на его собственное значение, чтобы добавить/объединить строки.

Ответ 2

"Чистым" способом сделать это будет использование функции. Поскольку функция JSTL join не будет работать на Collection, вы можете написать свой собственный без особых проблем и повторно использовать его повсюду вместо вырезания и вставки большого фрагмента кода цикла.

Вам нужна реализация функции и TLD, чтобы ваше веб-приложение узнало, где его найти. Поместите их вместе в JAR и поместите в свой каталог WEB-INF/lib.

Здесь схема:

ком/х/TagLib/ядро ​​/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();
  }

}

META-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, что тривиально; инструмент может легко сгенерировать его.

Ответ 4

Если ваш подсписчик является ArrayList, и вы делаете это:

<div class='scheduled' title="${conf.subject}: ${conf.invitees}" id="scheduled${conf.id}">

вы получаете почти то, что вам нужно.

Единственное различие заключается в том, что заголовок будет: "Тема: [[email protected], [email protected] и т.д.]".

Может быть, может быть достаточно для вас.

Ответ 5

Я думаю, что это то, что вы хотите:

<c:forEach var="tab" items="${tabs}">
 <c:set var="tabAttrs" value='${tabAttrs} ${tab.key}="${tab.value}"'/>
</c:forEach>

В этом случае у меня был hashmap с идентификатором табуляции (ключ) и URL (значение). Переменная tabAttrs не устанавливается до этого. Поэтому он просто устанавливает значение в текущее значение tabAttrs ('' для запуска) плюс выражение key/value.

Ответ 6

Просто поставьте строку рядом с var с сервера, например:

<c:forEach items="${upcomingSchedule}" var="conf">
    <div class='scheduled' title="${conf.subject}" 

         id="scheduled${conf.id}">

    ...
    </div>
</c:forEach>

Слишком поздно!!!

Ответ 7

Похоже, что библиотеки тегов-меток значительно продвинулись, поскольку этот ответ был изначально опубликован, поэтому я в конечном итоге внес некоторые радикальные изменения, чтобы заставить работу работать. Мой конечный результат:

Файл библиотеки тегов:

<?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="," />],

Ответ 8

Вы можете использовать API потока EL 3.0. Например, если у вас есть список строк,

<div>${stringList.stream().reduce(",", (n,p)->p.concat(n))}</div>

Если у вас есть список объектов для ex. Person (firstName, lastName), и вы хотите выполнить только одно свойство из них (ex firstName), вы можете использовать карту,

<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>

Будьте осторожны! EL 3.0 Stream API был доработан до API Java 8 Stream, и он отличается от этого. Они не могут загорать оба apis, потому что они нарушают обратную совместимость.