JSF 2.0:使用请求范围的 bean 渲染带有初始数据的表单字段(以更新特定于当前组件的服务器数据)
-
26-09-2019 - |
题
一个很正常的案例:
我们有一个“portlet”复合组件,它有两种状态:膨胀和折叠。Portlet 开始时处于展开状态,但用户可以通过单击它们来折叠它们。应将此状态保存到会话中,以便一旦用户刷新页面或导航到新页面,Portlet 就会记住它们是否已展开/折叠。
这就是问题的核心: :您实际上如何将这种状态保存实现到可以多次插入到页面上的组件中?
一些背景/想法:
通过使用会话范围的 bean 可以轻松地实现一个 portlet 的状态保存。通过创建固定数量的会话范围 bean 或将不同的属性声明到一个 bean 中,还可以支持固定数量的 portlet。然而,我什至不想提及为什么这些方法不好。
我最初的想法是在单个会话 bean 下创建一个 Map(或某些对象结构)来保存所有 portlet 的状态。但是,我无法直接从 JSF 引用这些数据(以便 JSF 也会更新它们)。我考虑编写一个特定的 getter/setter 来获取/更新映射中的正确值,但这是不可能的,因为在执行 getter 和 setter 期间没有识别数据。
然而,很明显我需要将状态保存到会话 bean 中。我可以通过发布表单来轻松地节省状态 f:ajax
并使用执行特殊方法 listener
以及提交的数据。为了支持组件的多个实例,我可以使用请求范围的 bean(多个实例)来处理每个展开/折叠。然而,为了实际发布标识 portlet 及其状态的数据,我需要首先在渲染时将其插入到表单字段中。
那么,如何在渲染时实际为每个 portlet 提供正确的数据(在本例中实际上只是状态布尔值/枚举,但考虑应该处理更多数据的情况)?
看起来:
h:inputHidden
和h:inputText
不支持设置除值属性之外的初始值(这将指向#{portletBean.portletState}
).- 我无法在创建时自动加载具有正确初始值的请求 bean,因为没有可用的标识信息。
再次感觉好像失去了一些东西......指点?
我想至少一个答案是使用 UIComponent
而不是复合组件,但我想将其保留为复合组件,因为它包含大量原始 HTML 元素。
更新:
- 有一个 相当相似的问题, ,建议使用视图范围的 bean。然而,我认为这在这里不可行。
解决方案 3
我用了一个 之前解决了从 JS 调用 bean 方法的 hack:
创建了一个空的、看不见的形式, h:commandButton
和 f:ajax
在里面呼唤 listener
.
创建了一个单独的按钮来启动展开/折叠事件。从该按钮启动 javascript 方法,该方法对展开/折叠进行动画处理,同时单击表单内的隐藏按钮。所有需要的数据都使用 listener
属性。侦听器以会话 bean 为目标,该会话 bean 检查/更新由组件的客户端 ID 标识的状态图。
这很简单,奇怪为什么我没有早点意识到这一点......
然而,它仍然远非完美,并且它没有回答如何使用默认值启动请求范围的 bean 以将它们用作存储/传输形式。
其他提示
我为此找到了一些可能的技巧。我将把它们发布在这里,但我不认为这些真的是正确的方法:
使用javascript设置表单字段 渲染后立即使用正确的数据:
window.addEvent('domready', function() {
var portletState = '#{portletBean.getPortletState(cc.attrs.clientId)}';
// find the field and set the data...
});
感觉很hacky。我不想走这条路。
使用组件的属性映射来保存数据:
<cc:interface>
<cc:attribute name="portletState" default="#{portletBean.getPortletState(cc.attrs.clientId)}" />
</cc:interface>
从代码中访问它:
public void stateChangedCallback(String clientId) {
UIComponent component = FacesContext.getCurrentInstance().getViewRoot().findComponent(clientId);
String state = (String) component.getAttributes().get("portletState");
}
也感觉不太对劲。我也不确定它是否有效(你实际上可以在默认属性中使用 EL 吗)。
使用组件的属性映射 通过在调用组件时提供数据来保存数据:
<portlet:portlet id="specificPortlet">
<f:attribute name="portletState" value="#{portletBean.getPortletState(component.clientId)}" />
</portlet:portlet>
同样,非常笨拙并且重复您的代码。
您可以使用 taglibs 来做到这一点,您可以将参数传递到标签中:
将以下内容添加到您的标签库中:
<tag>
<tag-name>date</tag-name>
<source>components/date.xhtml</source>
</tag>
然后这是components/date.xhtml的内容
<ui:composition name="date_template">
<h:panelGrid id="#{id}Panel" columns="1" border="0" cellpadding="0" cellspacing="0">
<rich:calendar immediate="true" id="#{id}" popup="true" datePattern="dd.MM.yyyy"
enableManualInput="true" showApplyButton="true" cellWidth="12px" cellHeight="11px" style="width:100px"
value="#{dateForm.date}" required="#{required}" readonly="#{readonly}" validator="#{dateForm.validateDate}">
<a4j:support event="onchanged" reRender="#{id}Panel,#{reRenderDate}"/>
<ui:insert />
</rich:calendar>
<rich:message for="#{id}" errorClass="error" />
</h:panelGrid>
</ui:composition>
然后你可以这样使用:
<adse:date id="dateTransfert" required="true"
dateForm="#{dossierBean.dateTransfert}"
readonly="false" reRenderDate="montantAncien,montantNouveau,montantEmolument">
<a4j:support event="oninputblur" reRender="dateTransfertPanel,montantAncien,montantNouveau,montantEmolument"/>
</adse:date>
在本例中,传入了 dateForm,这是一个对象,在 taglib 中我们使用该对象的属性 (dateForm.date)。另请注意 <ui:insert />,它可用于在 XHTML 中包含其他元素。
因此,您可以传递每个 portlet 的上下文。您甚至可以通过将 portletBean 作为定义 getPortletState() 方法的超类来制定此标准。