自定义组件,即UIInput和UICommand



在处理JSF自定义组件时遇到了一些问题。如果有人能让我领先就好了。

这个想法是有一个组件,可以让我在LocalDate中分页。想想一个更简单的日期选择器。呈现的HTML有两个按钮,一个按钮将日期增加一天,另一个按钮则将日期减少一天。值本身存储在隐藏的输入中。附件是该组件的一个简单版本,它不起作用,但希望能描述我试图实现的目标。

其想法是在页面上显示<my:datePager value="#{myBackingBean.someDate}" />,并在单击任一按钮后执行一些逻辑/操作。

例如<my:datePager value="#{myBackingBean.someDate} action="..." /><my:datePager value="#{myBackingBean.someDate} previous="..." next="..."/>不知道什么更好。

以下是我遇到的问题:如何在组件上调用previousnext方法

到目前为止,这是我的JSF 2.3自定义组件:

@FacesComponent(LocalDatePager.COMPONENT_TYPE)
public class LocalDatePager extends UIInput {
public static final String COMPONENT_TYPE = "LocalDatePager";
public LocalDatePager() {
setConverter(LocalDateConverter.INSTANCE);
}
public void previous() {
LocalDate localDate = (LocalDate) getValue();
localDate = localDate.minusDays(1);
setValue(localDate);
}
public void next() {
LocalDate localDate = (LocalDate) getValue();
localDate = localDate.plusDays(1);
setValue(localDate);
}
@Override
public void decode(FacesContext context) {
super.decode(context);
}
@Override
public void encodeEnd(FacesContext context) throws IOException {
ResponseWriter writer = context.getResponseWriter();
writer.startElement("div", this);
writer.writeAttribute("class", "btn-group", null);
writer.writeAttribute("role", "group", null);
writer.startElement("button", this);
writer.writeAttribute("type", "button", null);
writer.writeAttribute("class", "btn btn-outline-primary", null);
writer.writeText("Previous", null);
writer.endElement("button");
writer.startElement("button", this);
writer.writeAttribute("type", "button", null);
writer.writeAttribute("class", "btn btn-outline-primary", null);
writer.writeText("Next", null);
writer.endElement("button");
writer.endElement("div");
writer.startElement("input", this);
writer.writeAttribute("name", getClientId(context), "clientId");
writer.writeAttribute("type", "hidden", null);
writer.writeAttribute("value", getValue(), "value");
writer.endElement("input");
}
}

根据我的理解,我的头脑是UIInputUICommand。我需要实现ActionEvent吗?火灾数值变化事件?

目前,我在backingbean中而不是在组件中执行上一个和下一个方法。这导致了很多重复的代码,我认为自定义组件最适合。我试着使用复合组件,但这也没有让我走多远。

到目前为止,另一种方法是客户端行为:执行JavaScript,更改隐藏输入的值并提交表单。这感觉更糟。

如果有人能给我一个正确的方向就好了。

作为UIInput

首先,您需要在encodeXxx()方法中将按钮渲染为普通提交按钮。此外,您更希望使用encodeAll()而不是encodeEnd(),因为这看起来是一个";叶子;组件,因此您不想与儿童打交道。则实现CCD_ 14更加有效。将现有的encodeEnd()方法重命名为encodeAll(),并调整按钮的编码如下:

writer.startElement("button", this);
writer.writeAttribute("type", "submit", null); // instead of "button"
writer.writeAttribute("name", getClientId(context) + "_previous", "clientId");
writer.writeText("Previous", null);
// ...
writer.startElement("button", this);
writer.writeAttribute("type", "submit", null); // instead of "button"
writer.writeAttribute("name", getClientId(context) + "_next", "clientId");
writer.writeText("Next", null);
// ...
// The hidden input field in your existing code is fine as-is.

然后您可以在请求参数映射中检查它们。如果你想让你的组件保持为UIInput,那么我建议用getConvertedValue()方法执行工作:

@Override
protected Object getConvertedValue(FacesContext context, Object submittedValue) throws ConverterException {
LocalDate localDate = (LocalDate) super.getConvertedValue(context, submittedValue);
if (localDate != null) {
Map<String, String> params = context.getExternalContext().getRequestParameterMap();
if (params.get(getClientId(context) + "_previous") != null) {
localDate = localDate.minusDays(1);
}
else if (params.get(getClientId(context) + "_next") != null) {
localDate = localDate.plusDays(1);
}
}
return localDate;
}

然后,您可以简单地在值更改侦听器中完成这项工作:

<my:datePager value="#{bean.localDate}" valueChangeListener="#{bean.onpage}" />
public void onpage(ValueChangeEvent event) {
// ...
}

基本上就是这些。无需手动触发值更改事件。剩下的逻辑已经由JSF处理了。

然而,这种方法的缺点是,它可能在JSF生命周期中的错误时刻被调用。值更改侦听器在验证阶段被调用,而您确实希望它在调用应用程序阶段被调用。假设这个按钮被放置在一个状态取决于与#{bean.localDate}相关联的数据的表单中,那么它们的更新模型值阶段可能会出错,因为localDate更改得太快了。

作为UICommand

如果上述缺点是一个真正的障碍,尽管有解决办法,那么您最好将UIInput转换为UICommand。不可能两者都有。你选一个或另一个。我个人的建议是选择CCD_;更自然的";wrt在JSF生命周期期间的行为,同时考虑到组件的唯一目的("分页到下一个/上一个日期"(。以下是步骤:

  1. extends UIInput换成extends UICommand

  2. 删除构造函数中的setConverter()调用。

  3. 保留encodeAll()方法。一切都很好。

  4. 删除getConvertedValue()方法并实现decode(),如下所示:

    @Override
    public void decode(FacesContext context) {
    Map<String, String> params = context.getExternalContext().getRequestParameterMap();
    if (params.get(getClientId(context) + "_previous") != null) {
    queueEvent(new ActionEvent(context, this));
    }
    else if (params.get(getClientId(context) + "_next") != null) {
    queueEvent(new ActionEvent(context, this));
    }
    }
    

    这些queueEvent()调用将导致调用同一组件的broadcast()。因此按如下方式覆盖它:

    @Override
    public void broadcast(FacesEvent event) throws AbortProcessingException {
    FacesContext context = event.getFacesContext();
    Map<String, String> params = context.getExternalContext().getRequestParameterMap();
    LocalDate localDate = LocalDate.parse(params.get(getClientId())); // TODO: gracefully handle any conversion error.
    if (params.get(getClientId(context) + "_previous") != null) {
    localDate = localDate.minusDays(1);
    }
    else if (params.get(getClientId(context) + "_next") != null) {
    localDate = localDate.plusDays(1);
    }
    getValueExpression("value").setValue(context.getELContext(), localDate);
    super.broadcast(event); // Invokes bean method.
    }
    

    请注意value属性是如何在模型中手动解码、转换和更新的。尽管这是作为隐藏的输入值传递的,但您可能需要添加一些逻辑来优雅地处理TODO中所示的任何转换错误。

现在您可以按如下方式使用它:

<my:datePager value="#{bean.localDate}" action="#{bean.onpage}" />
public void onpage() {
// ...
}

相关内容

  • 没有找到相关文章

最新更新