如何在Spring Roo / Dojo中执行非强制下拉框?

时间:2011-08-12 13:03:56

标签: java spring dojo spring-roo

在Spring Roo(1.1.5)中,我有一个实体“书”,可以引用实体“发布者”。

class Book {
   @ManyToOne(optional=true)
   Publisher publisher
}

现在我有了Roo生成的Controller和JSPX文件。 在GUI中,用于创建和更新Book的是Roo生成的下拉框(由dijit.form.FilteringSelect修饰)以选择发布者。 但是用户必须选择一个发布者;没有“空”字段!

我的第一次尝试只是在列表中添加null值,表示下拉框的选项。 但那失败了。 (java.lang.IllegalArgumentException: Bean object must not be null) - 所以这可能是错误的方式。

所以在我尝试自己扩展select.tagx文件之前,我想问如果有人已经解决了这个问题(有一个带Spring Roo / Dojo的可选下拉框),或者我一些完全错误的东西它应该在正常的情况下工作而没有新的东西?

2 个答案:

答案 0 :(得分:3)

我找到了一个解决方案,但这只是因为我的应用程序不再是标准的Roo应用程序。 无论如何,我将解释我的解决方案,也许有人找到一种方法来适应标准的Roo应用程序。

想法是在required属性为false时在下拉框中添加空选择。 主要问题是如果下拉框中有一个没有value的选项,则dijti / dojo扩展名将无法正常工作。 所以我的解决方案是给他们例如value "null"<option value="null></option>)。 在服务器端,必须更改将数据库ID(即正常值)转换为实体(通过从数据库加载)的转换器, 这样它就可以将字符串"null"转换为null而不是实体。

但这就是春天Roo的问题。 Roo使用自动注册的org.springframework.core.convert.support.IdToEntityConverter (未记录https://jira.springsource.org/browse/SPR-7461)并且如果实体类作为静态查找器方法,将尝试将每个对象转换为实体。 我发现无法修改它的行为。

但我个人有很多运气,因为前段时间我改变了我的应用程序,它没有那个静态查找器,所以我有自己的通用Id到实体转换器,很容易改变。 转换器将String转换为Entity。如果String为“null”,则返回null,否则它将String转换为数字并按此数字/ id加载实体。

对于视图,它必须扩展select.tagx文件。

select.tagx文件包含12种填充选择框的方法。

  • 其中6个是多选的,所以他们可以保持原样。
  • 2如果不是多个用于禁用表单绑定,则必须在select标记之后添加此块

第75,130行,

<c:if test="${not required}">
    <option value="null"></option>
</c:if>
  • 其他4个有点复杂

...

<form:select id="_${sec_field}_id" items="${items}" path="${sec_field}" disabled="${disabled}" />
 ...
 <form:select id="_${sec_field}_id" items="${items}" path="${sec_field}" disabled="${disabled}" itemLabel="${sec_itemLabel}"/>
 ...
<form:select id="_${sec_field}_id" items="${items}" path="${sec_field}" disabled="${disabled}" itemValue="${fn:escapeXml(itemValue)}" />
 ...
<form:select id="_${sec_field}_id" items="${items}" path="${sec_field}" disabled="${disabled}" itemValue="${fn:escapeXml(itemValue)}" itemLabel="${sec_itemLabel}"/>

他们需要更换完整的标签(我只会在4的最后一个中展示它,但另一个是相似的,除了必须删除itemVlaue和/或itemLabel参数

<form:select id="_${sec_field}_id" path="${sec_field}" disabled="${disabled}">                          
    <c:if test="${not required}">                               
       <option value="null"></option>
    </c:if>
    <form:options items="${items}" itemValue="${fn:escapeXml(itemValue)}" itemLabel="${sec_itemLabel}"/>
</form:select>

现在应该可以了。


但它有一个小缺陷。如果存在没有发布者的Book,则空下拉选项将不具有select属性。 这不是很糟糕,因为它是最顶级的选项,如果没有选择其他选项,将会显示。

如果有人不能接受这个漏洞,那么处理这个问题的一种方法是编写一个扩展org.springframework.web.servlet.tags.form.Option的自己的jsp标签(执行spring选项标签的类)。 真正需要改变的只有两件事:

1)如果绑定状态为null,方法isSelected(Object resolvedValue)必须返回true(所以这个方法变得非常简单)

private boolean isSelected(Object resolvedValue) {
  BindStatus bindStatus = getBindStatus();  
  return bindStatus == null || bindStatus.getValue() == null || bindStatus.getActualValue() == null;
}

2)如果标签是在没有身体或空身体的情况下呈现的(方法renderDefaultContent),则呈现的html option的内容应为空,但不应为value。     因此,必须将renderOption(SpecialWay)方法的第二个参数设置为空字符串。

@Override
protected void renderDefaultContent(TagWriter tagWriter) throws JspException {
  Object value = this.pageContext.getAttribute(VALUE_VARIABLE_NAME);    
  renderOptionSpecialWay(value, "", tagWriter);
}

但是因为isSelected方法是私有的并且无法覆盖,所以必须复制renderOption(可以重命名)并且必须更改它以便它调用“new”isSelected方法。必须对两个方法renderDefaultContentrenderFromBodyContent执行相同操作,因为renderOption也是私有的。

所以有人想出了这个课程:

public class NullOptionTag extends OptionTag {

  @Override
  protected void renderDefaultContent(TagWriter tagWriter) throws JspException {
    Object value = this.pageContext.getAttribute(VALUE_VARIABLE_NAME);
    renderOptionSpecialWay(value, "", tagWriter);
  }

  @Override
  protected void renderFromBodyContent(BodyContent bodyContent, TagWriter tagWriter) throws JspException {
   Object value = this.pageContext.getAttribute(VALUE_VARIABLE_NAME);
   String label = bodyContent.getString();
   renderOptionSpecialWay(value, label, tagWriter);
  }

  private void renderOptionSpecialWay(Object value, String label, TagWriter tagWriter) throws JspException {
    tagWriter.startTag("option");
    writeOptionalAttribute(tagWriter, "id", resolveId());
    writeOptionalAttributes(tagWriter);
    String renderedValue = getDisplayString(value, getBindStatus().getEditor());
    tagWriter.writeAttribute(OptionTag.VALUE_VARIABLE_NAME, renderedValue);
    if (isSelected(value)) {
        tagWriter.writeAttribute("selected", "selected");
    }
    if (isDisabled()) {
        tagWriter.writeAttribute("disabled", "disabled");
    }
    tagWriter.appendValue(label);
    tagWriter.endTag();
  }

  private boolean isSelected(Object resolvedValue) {
    BindStatus bindStatus = getBindStatus();      
    return bindStatus == null || bindStatus.getValue() == null || bindStatus.getActualValue() == null;
  }
}

接下来要做的是将此类添加到标记库定义中,以便可以在select.tagx

中使用它
 <form:select id="_${sec_field}_id" path="${sec_field}" disabled="${disabled}">                         
   <c:if test="${not required}">                                
     <formExtension:nulloption value="null"></formExtension:nulloption>
   </c:if>
   <form:options items="${items}" itemValue="${fn:escapeXml(itemValue)}" itemLabel="${sec_itemLabel}"/>
</form:select>

答案 1 :(得分:1)

还有另一种解决这个问题的方法。 我的解决方案不需要更改select.tagx文件。

我改变了两个地方。

首先,在BookController,我覆盖了此特定对象的填充方法,例如Publisher

@ModelAttribute("publisher")
public Collection<Publisher> populatePublisher() {
    Collection<Publisher> result = new ArrayList<Publisher>();

    // Add an empty item
    Publishertmp = new Publisher();
    tmp.setId(0L);
    result.add(tmp);
    result.addAll(Publisher.findAllPublisher());

    return result ;
}

将Id设置为0非常重要。如果Id不为0,则不会显示第一个空项目。

然后我覆盖了ApplicationConversionServiceFactoryBean

protected void installFormatters(FormatterRegistry registry) {

    registry.addConverter(new PublisherConverter());

    super.installFormatters(registry);
    // Register application converters and formatters 
}

static class PublisherConverter implements Converter<Publisher, String> {
    public String convert(Publisher publisher) {
        if (publisher.getId().intValue() == 0) {
            return "-- Not Selected --";
        }
        return new StringBuilder().append(publisher.toString());
    }
}

通过更改转换器,您可以在第一个项目上看到-- Not Selected --。 当表单提交给控制器时,您只需添加代码以识别是否选择了空发布者并进行调整以适合您的逻辑。我相信这是关于它的。

您可以将发布者下拉框保留为required = "true",它仍然可以使用。