【问题标题】:Subscribing to PostValidationEvent of dynamicaly created child component订阅动态创建的子组件的验证后事件
【发布时间】:2015-05-23 06:23:00
【问题描述】:

对于我喜欢使用 JSF 作为真正的 UI 前端的表单框架,我正在寻找一种方法,如果子组件中的值发生更改,则通知父组件。基本“控件”的外观如下所示(省略了主体/头部,因为没有十几个类就没有人可以运行它):

<xf:input ref="/my/xpath/value">
    <xf:label>Label</xf:label>
</xf:input>

我开发的xf:input 组件,根据ref="/my/xpath/value" 指向的值的类型,动态创建了一个真实的ui 组件(PrimeFaces 的)。这个真正的 ui 组件是在 preRenderView 事件中创建的,就像在这个例子中所做的那样。它在'父'控件中通过以下方法处理

@Override
public void processEvent(SystemEvent event) throws AbortProcessingException {

    FacesContext context = FacesContext.getCurrentInstance();

    if (!context.isPostback()) {
        control = createControl(context);
//context.getApplication().unsubscribeFromEvent(PostValidateEvent.class, getControl().getClass(), this);
        control.subscribeToEvent(PostValidateEvent.class, this);
    }
}

实际的控件都添加了一个以编程方式添加的 ajax 处理程序,这使得只处理特定输入(“隐式 ajax”)成为可能。通常会应用默认的 JSF 组件验证,这一切都很好。

问题/挑战在于,在这个“包装器”组件中,我希望在验证后获知值的变化。我的第一个想法是给我们动态添加的控件上的 subscribeEvent,如下所示:

control.subscribeToEvent(PostValidateEvent.class, this);

订阅有效,但在回发时,UIComponent (Mojarra 2.2.9) 中会抛出 NPE,因为在以下方法中 wrapped 为空

public boolean isListenerForSource(Object component) {

    if (wrapped instanceof SystemEventListener) {
        return ((SystemEventListener) wrapped).isListenerForSource(component);
    } else {
        return instanceClass.isAssignableFrom(component.getClass());
    }

}

这可能是因为在提交数据时实际组件似乎是新创建的,因此“订阅”丢失了。

ViewRoot 上注册不起作用,因为事件源始终是ViewRoot,而在Application 上注册是完全错误的。

可能是我正在寻找错误方向的解决方案,但现在我一无所知。请记住,我无法直接控制创建的 ui 控件,如果可以阻止,我也不想覆盖它们的渲染器。因此,从子控件向父级发送信号不是一种选择。

我尝试过的其他事情:

  • 使用 valueChangeListeners 但这也无法解决许多其他问题(包括使其可扩展的方法)
  • 使用具有绑定但未能动态包含它们的复合组件,要求命名容器与框架其余部分所需的 id、xhtml 和/或生成的 dom 中的标签、提示和警报的位置冲突
  • 标签处理程序在创建树时对其进行操作

这一切都与 Mojarra 2.2.9 一起使用(尚未检查更新或 MyFaces)

【问题讨论】:

  • 组件最好在视图构建期间创建。 preRenderView 应该是 postAddToView。你试过了吗?
  • 是的,我可以理解这一点,但是我发现的所有严肃的例子(包括来自 Kennard 咨询关于 MetaWidget 的帖子)都是这样做的。虽然现在我搜索它,但我看到了one that uses the postAddToView。让我试试……
  • @BalusC:我尝试将其更改为PostAddToView 事件,然后PostValidateEvent 上的UIComponent 中没有NPE,但根本没有任何事件:-(.. . 还有一个更大的区别, 动态添加的孩子完全丢失了, 至少在将孩子转储到PostRestoreStateEvent 时. 它不再存在. 然后我想到在 PostRestoreStateEvent 的控件中添加postValidateEvent因为那是我真正需要它的时刻。值得注意的是,它可以工作,但只有一次。在对同一组件的第二次请求时,NPE 再次出现。
  • postAddToView 属于哪个组件?它应该是您希望将动态创建的子对象挂钩的那个,因此 &lt;xf:input&gt; 而不是例如UIViewRoot 或其他家长。用 XHTML 术语来说,就是&lt;xf:input&gt;&lt;f:event type="postAddToView" ...&gt;&lt;/xf:input&gt;。另见 a.o.以stackoverflow.com/questions/3510614/… 的选项#2 为例。
  • 在自定义组件术语中,这将添加@ListenerFor(systemEventClass=PostAddToViewEvent.class) 并在processEvent(ComponentSystemEvent) 中完成event instanceof PostAddToView 的工作。如果确实如此,请告诉我,然后我会添加一个答案。

标签: jsf-2 jsf-2.2


【解决方案1】:

在 PreRenderViewEvent 中添加组件效果很好。问题是您似乎无法在请求中保留对事件的订阅。实际组件被重新创建(在我假设的 RestoreViewPhase 中,没有检查),然后对事件的订阅仍然存在,只是应该调用它的“包装”上下文是空的。

在这个特定组件的 PostRestoreStateEvent 中添加 PostValidationEvent 事件(它是 FacesContext.getCurrentInstance().getPartialViewContext().getExecuteIds() 中唯一的一个)使其触发,如 cmets 中所述。在下一个请求中摆脱 NPE 的技巧(hack/workaround/...)实际上是再次删除该事件。

((UIComponent) event.getSource()).unsubscribeFromEvent(PostValidateEvent.class, this);

我将尝试创建一个没有任何 PrimeFaces 或 OmniFaces 的示例,然后看看会发生什么,因为它们似乎都是上下文的包装器,我想确保它们不是导致行为的原因。

【讨论】:

    猜你喜欢
    • 2019-01-24
    • 1970-01-01
    • 2010-09-07
    • 1970-01-01
    • 2019-09-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多