简体   繁体   English

JSF 2.0:使用请求范围的Bean呈现具有初始数据的表单字段(以更新特定于当前组件的服务器数据)

[英]JSF 2.0: Render form fields with initial data using request-scoped beans (to update server data specific to current component)

A pretty normal case: 一个很正常的情况:

We have a 'portlet' composite component that has two states: expanded and collapsed. 我们有一个“ portlet”复合组件,具有两个状态:展开和折叠。 Portlets start as expanded, but user can collapse them by clicking on them. Portlet从扩展开始,但是用户可以通过单击将它们折叠。 This state should be saved to the session, so that once the user refreshes the page or navigates to a new, the portlets remember if they are expanded/collapsed. 此状态应该保存到会话中,以便一旦用户刷新页面或导航到新的portlet,Portlet就会记住它们是否已展开/折叠。

This is the core of the problem : How would you actually implement such state-saving into a component, that can be inserted on page multiple times? 这是问题的核心 :您如何实际将这种状态保存方式实现到可以多次插入页面的组件中?

Some background / ideas: 一些背景/想法:

Implementing state-saving for one portlet would be easily achieved by using a session-scoped bean. 通过使用会话范围的bean,可以很容易地实现为一个portlet实现状态保存。 Also fixed number of portlets could be supported by creating a fixed number of session-scoped beans or declaring different properties into one bean. 通过创建固定数量的会话范围的bean或在一个bean中声明不同的属性,也可以支持固定数量的portlet。 However, I don't want to even mention why those approaches is bad. 但是,我什至不想提及为什么这些方法不好。

My initial idea for this was to create a Map (or some object structure) under single session bean to hold the states for all of the portlets. 我最初的想法是在单个会话Bean下创建一个Map(或某种对象结构)来保存所有portlet的状态。 However, I cannot directly refer to this data from JSF (so that JSF would also update them). 但是,我无法直接从JSF引用此数据(以便JSF也会更新它们)。 I considered writing a specific getter/setter to fetch/update the right value in the map, but that's not possible as there's no identifying data during the execution of getters and setters. 我考虑编写一个特定的getter / setter来获取/更新映射中的正确值,但这是不可能的,因为在执行getter和setter的过程中没有识别数据。

However, it is probably clear that I need to save the states into a session bean. 但是,很显然,我需要将状态保存到会话bean中。 I can do the state-saving pretty easily by posting a form with f:ajax and executing a special method using listener and the submitted data. 通过发布带有f:ajax的表单并使用listener和提交的数据执行特殊方法,我可以非常轻松地进行状态保存。 In order to support multiple instances of component, I could use (multiple instances of) a request-scoped bean to handle each expand/collapse. 为了支持组件的多个实例,我可以使用一个请求范围的bean(的多个实例)来处理每个扩展/折叠。 However, in order to actually post the data that identifies the portlet and its' state, I need to first insert it on the form fields in render-time. 但是,为了实际发布标识portlet及其状态的数据,我需要在渲染时首先将其插入到表单字段中。

So, how to actually provide each portlet with right data on render-time (actually just state boolean/enum in this case, but consider a case where more data should be handled)? 那么,如何在渲染时实际为每个portlet提供正确的数据(在这种情况下,实际上只是声明布尔值/枚举,但考虑应该处理更多数据的情况)?

It seems that: 看起来:

  • h:inputHidden and h:inputText do not support setting initial value other than than the value-attribute (which would point to #{portletBean.portletState} ). h:inputHiddenh:inputText除了值属性(指向#{portletBean.portletState} )以外,不支持设置其他初始值。
  • I cannot auto-load the request beans with right initial values on their creation time, as there's no identifying information available. 由于没有可用的标识信息,因此我无法在其创建时使用正确的初始值自动加载请求Bean。

Once again it feels like I'm missing something... pointers? 再次感觉到我缺少了一些东西……指针?

I suppose at least one of the answers would be to use UIComponent rather than composite components, but I would like to keep this as composite component as it contains lots of raw HTML elements. 我想至少有一个答案是使用UIComponent而不是复合组件,但是我想将其保留为复合组件,因为它包含许多原始HTML元素。

Update: 更新:

  • There is a rather similar question , with suggestion to use view-scoped bean. 有一个相当类似的问题 ,建议使用视图范围的bean。 I don't think this is viable here, however. 但是,我认为这不可行。

I got a few possible hacks for this. 我为此有一些可能的技巧。 I will post them here but I don't think these are really the way to go: 我将在这里发布它们,但我认为这些并不是真正的方法:

Use javascript to set form fields with the right data directly after rendering: 渲染后直接使用javascript设置具有正确数据的表单字段

window.addEvent('domready', function() {
    var portletState = '#{portletBean.getPortletState(cc.attrs.clientId)}';
    // find the field and set the data...
});

Feels veery hacky. 感觉很朴实。 I don't want to take this route. 我不想走这条路。

Use component's attribute map to hold the data: 使用组件的属性映射来保存数据:

<cc:interface>
    <cc:attribute name="portletState" default="#{portletBean.getPortletState(cc.attrs.clientId)}" />
</cc:interface>

Access that from code: 从代码访问:

public void stateChangedCallback(String clientId) {
    UIComponent component = FacesContext.getCurrentInstance().getViewRoot().findComponent(clientId);
    String state = (String) component.getAttributes().get("portletState");
}

Also doesn't feel right. 也感觉不对。 Also I am not sure if it works (can you actually use EL in default-attribute). 另外我不确定它是否有效(您是否可以在默认属性中实际使用EL)。

Use component's attribute map to hold the data by providing it when calling the component: 使用组件的属性映射通过在调用组件时提供数据来保存数据:

<portlet:portlet id="specificPortlet">
    <f:attribute name="portletState" value="#{portletBean.getPortletState(component.clientId)}" />
</portlet:portlet>

Again, very clumsy and duplicates your code. 再次,非常笨拙,并重复了您的代码。

You can do this using taglibs, you can pass parameters into tags: 您可以使用taglibs进行此操作,可以将参数传递给标签:

Add the following to your taglib: 将以下内容添加到您的taglib中:

<tag>
    <tag-name>date</tag-name>
    <source>components/date.xhtml</source>
</tag>

Then here is the contents of components/date.xhtml 这是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>

And then you use this like: 然后像这样使用:

<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>

In this case, dateForm is passed in, this is an object, and in the taglib we use properties of that object (dateForm.date). 在这种情况下,传入dateForm,这是一个对象,在taglib中,我们使用该对象的属性(dateForm.date)。 Also note the <ui:insert /> which can be used to include other elements in the XHTML. 还要注意<ui:insert />,它可以用来在XHTML中包括其他元素。

So, you could pass in context for each portlet. 因此,您可以为每个portlet传递上下文。 You could even make this standard by having portletBean as a superclass which defines the getPortletState() method. 您甚至可以通过将portletBean作为定义getPortletState()方法的超类来制定此标准。

I used a previously solved hack to call a bean method from JS : 我使用以前解决的技巧从JS调用bean方法

Created an empty, invisible form, with h:commandButton and f:ajax inside it calling listener . 创建了一个空的,不可见的表单,其中包含h:commandButtonf:ajax ,它调用了listener

Created a separate button for launching the expand/collapse event. 创建了一个单独的按钮来启动扩展/折叠事件。 From that button initiate javascript method, that animates the expansion/collapse and at the same time clicks the hidden button inside the form. 从该按钮启动javascript方法,该方法可以对展开/折叠进行动画处理,同时单击表单内的隐藏按钮。 All needed data is sent using the listener attribute. 所有需要的数据都使用listener属性发送。 Listener targets a session bean, which checks/updates a map of states, identified by component's client ids. 侦听器的目标是会话Bean,该会话Bean检查/更新由组件的客户端ID标识的状态图。

This is pretty simple, wonder why I didn't realize it eariler... 这很简单,想知道为什么我没有意识到这一点……

However, it's still far from perfect and it doesn't answer how the request-scoped beans could be initiated with default values to use them as storage/transport form. 但是,它还远非完美,它也无法回答如何使用默认值启动请求范围的Bean并将其用作存储/传输形式。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 请求范围的bean的Bean创建错误 - Bean creation errors for request-scoped beans 请求范围的bean和数据模型初始化? - Request-scoped beans and datamodel initialization? 应用服务器中的多个线程可以修改请求范围的组件吗? - Can multiple threads in application server modify request-scoped component? 为什么在没有请求的情况下注入请求范围的bean? - Why can I inject request-scoped beans in the absence of a request? 请求范围的 bean 在使用 Cucumber 的 Spring 测试中不起作用 - Request-scoped beans not working in Spring tests with Cucumber 使用请求范围的 bean function 作为 JSF 中临时呈现按钮的操作 - Using request-scoped bean function as action for a temporary rendered button in JSF 通过AJAX使用请求范围的Bean向h:dataTable添加一行,而不会丢失行数据 - Add a row to h:dataTable via AJAX with request-scoped bean without losing the row data Spring请求范围的Bean-所有字段为空/空 - Spring request-scoped bean - all fields null/empty 为什么不从表单值创建请求范围的bean? - Why request-scoped bean is not created from form values? 没有servlet的Guice请求作用域注入(在RPC服务器中) - Guice Request-scoped injection without a servlet (in an RPC server)
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM