【发布时间】: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属于哪个组件?它应该是您希望将动态创建的子对象挂钩的那个,因此<xf:input>而不是例如UIViewRoot或其他家长。用 XHTML 术语来说,就是<xf:input><f:event type="postAddToView" ...></xf:input>。另见 a.o.以stackoverflow.com/questions/3510614/… 的选项#2 为例。 -
在自定义组件术语中,这将添加
@ListenerFor(systemEventClass=PostAddToViewEvent.class)并在processEvent(ComponentSystemEvent)中完成event instanceof PostAddToView的工作。如果确实如此,请告诉我,然后我会添加一个答案。