JSF2:为什么在复合中的panelGroup的呈现中进行空测试会阻止调用动作? [英] JSF2: why does empty test in rendered of panelGroup in composite prevent action from being called?

查看:158
本文介绍了JSF2:为什么在复合中的panelGroup的呈现中进行空测试会阻止调用动作?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

当来自定制复合组件的某些其他JSF元素与提交h:form的h:commandButton位于同一页面时,我无法调用托管bean的操作.我可以将导致问题的页面元素的范围缩小到执行以下对我而言无害的外观渲染测试的页面元素:

        <h:panelGroup rendered="#{not empty cc.attrs.element}">

或:

        <h:panelGroup rendered="#{empty cc.attrs.element}">

其中'element'是必需属性,通过ID从JPA实体加载.关于空cc.attrs.element"或非空cc.attrs.element"测试的某些内容会阻止正确提交表单,并阻止在出现的任何h:form中调用操作.

表单提交页面debug.xhtml是:

<f:view>
    <f:metadata>
        <f:viewParam name="id" value="#{elementController.id}"/>
    </f:metadata>
</f:view>

<h:head>
    <title>DEBUG</title>
</h:head>
<h:body>
<h:form>        
<util:view_link element="#{elementController.selected}"/>
<h:commandButton value="Apply" action="#{elementController.reflect}" /> 
        </h:form>
    </h:body>

请注意,通过ElementController.setId的视图参数"id"将通过JPA中的id强制加载选定"或当前" Element实体,此处未显示.然后,应通过动作Reflection()将其移至test.xhtml:

@JSFaction
public Object reflect() {
    String $i = "reflect";
    log_debug($i);
    if (current==null) {
        log_warn($i, "can't generate reflect outcome for null current element");
        return null;
    }
    //return "reflect?faces-redirect=true&includeViewParams=true&id="+current.getId();
    //return "test?faces-redirect=true&includeViewParams=true&id="+current.getId();
    //return "reflect?id="+current.getId();
     return "/test?faces-redirect=true&id="+current.getId();
}

您可以在上方看到我尝试了不同的结果URL,但事实证明,该问题与该URL没有任何关系,只是根本没有调用该操作(当{非空cc.attrs.element }测试是在另一个页面元素中进行的.

引起问题的复合组件是我的util:view_link ::

    <composite:interface>
    <composite:attribute name="element" required="true" type="com.greensoft.objectdb.test.entity.Element"/>
    <composite:attribute name="name" required="false"/>
    <composite:attribute name="showOwnerAsText" required="false" default="false"/>
    <composite:attribute name="showModelClass" required="false" default="false"/>
    <composite:attribute name="showEntityClass" required="false" default="false"/>
    <composite:attribute name="showId" default="false"/>
    <!--composite:attribute name="showHelp" required="false" default="false"/-->
    <!--composite:attribute name="title" required="false"/-->
</composite:interface>

<composite:implementation>

    <h:panelGroup rendered="#{empty cc.attrs.element}">
        <span class="warn">NULL</span>
    </h:panelGroup>

    <h:panelGroup rendered="#{not empty cc.attrs.element}">
        <h:panelGroup rendered="#{cc.attrs.showOwnerAsText}">
            <h:outputText style="font-weight:bold" value="#{cc.attrs.element.owner.name}."/>
        </h:panelGroup>

        <h:panelGroup rendered="#{cc.attrs.showId}">
            <h:link outcome="/view" value="&lt;#{cc.attrs.element.id}&gt;">
                <f:param name="id" value="#{cc.attrs.element.id}"/>
            </h:link>
        </h:panelGroup>

        <h:link outcome="/view" 
                value="#{empty cc.attrs.name ? cc.attrs.element.name: cc.attrs.name}"
                >
            <f:param name="id" value="#{cc.attrs.element.id}"/>
        </h:link>
    </h:panelGroup>
</composite:implementation>

您可以看到,这只是创建到每个Element的链接的可重用方法,并且数据库ID作为参数传递.如果没有元素,则显示"NULL".

通过一次注释掉一些部分(括号),我可以证明问题与render ="= {empty cc.attrs.element}和render ="#{not not empty cc.attrs.element}测试有关,这既可以防止表单提交,也可以防止操作由同一组成页面中的ah:commandButton调用(请参见上面的debug.xhtml).例如,如果我仅使用普通的h:panelGroup元素,但未进行"rendered =#{empty cc.attrs.element}"和"rendered ="#{not empty cc.attrs.element}"测试,则所有显示均正常,并且h:commandButton可以按预期工作,并且将动作称为ok,并且导航正常!

Q:为什么测试'rendered =#{empty cc.attrs.element}'或'rendered ="#{not empty cc.attrs.element}'会阻止正确的表单提交并阻止该操作被调用?

Webel非常感谢您的想法

仅当我在elementController.selected上进行测试时才会发生问题,其中.selected是@Entity元素.我使用带有String name属性的简单Duummy类和@RequestScoped DebugController进行了尝试,并且效果很好(调用了该操作).

<h:panelGroup rendered="#{not empty debugController.dummy}">
<h:outputText value="#{debugController.dummy.name}"/>
</h:panelGroup>
<h:commandButton value="Apply" action="#{elementController.reflect}" /> 

报告为错误: http://java.net/jira/browse/JAVASERVERFACES-2343

解决方案

我现在可以部分回答这个问题.现在,我可以看到正在发生的事情并对其进行描述,但是就JSF阶段和请求范围而言,我仍然还不完全了解为什么会这样发生.这是一个微妙而棘手的问题.

为了解释它,我重写了测试用例以使其更简单.带有id查询参数的view.xhtml测试页为:

<f:view>
    <f:metadata>
        <f:viewParam name="id" value="#{elementController.id}"/>
    </f:metadata>
</f:view>
<h:body>   
    <h:form>

       <h:panelGroup rendered="#{empty elementController.selected}">
           <div style="color:orange;">
               No Element found for id(#{elementController.id}) or page called without id query param.
           </div>
       </h:panelGroup>

       <h:panelGroup rendered="#{not empty elementController.selected}">
           [<h:outputText value="#{elementController.selected.id}"/>]&nbsp;
           <h:outputText value="#{elementController.selected.name}"/>
       </h:panelGroup>

        <br/><br/>

       <h:commandButton value="Apply" action="#{elementController.action}" />

    </h:form>
</h:body>

ElementController在其中扩展RequestController的位置.在加载view.xhtml时,使用setId将id查询参数设置为viewParam,然后通过id(从数据库)加载匹配的Element实体,并将其设置为当前"(也称为选定")元素,可以作为getSelected( ),它还对零电流进行了测试,这就是导致问题的原因:

public void setId(Long id) {
    log_debug("setId","id",id);
    if (id != null) {
        this.id = id;
        T found = (T) getAbstractFacade().find(id);
        if (found == null) {
            String $error = "No object with id(" + id + ") found for class " + getManagedClass().getSimpleName();
            log_error($error);
        }
        setCurrent(found);
    }
}

public T getSelected() {
    log_debug("getSelected","current",current);        
    if (current == null) {
        //current = newElement(); //THIS WAS CAUSING PROBLEMS
        log_warn("getSelected","null current Element");
    }
    return current;
}

问题在于,在提交表单时,对于测试'#{not empty elementController.selected}'或'#{empty elementController.selected}'的每个实例,两次调用getSelected()两次 字段名为当前"为空.

在@RequestScoped @ManagedBean ElementController中,getSelected()方法测试了当前(aka选定的)元素是否为null,并且正在执行导致错误的操作,这使该操作的调用短路在调用非空elementController.selected"时在commandButton上:

(我看到的问题与经受非空"或空"测试的选定"元素是否为@Entity无关,我误导了我,因为问题在于处理元素实体的ElementController,当然结论似乎还不正确.)

如果我摆脱了newElement()的分配,这会导致错误,并且在捕获没有id参数的视图页面时会捕获情况(触发通过id加载当前"实体) ,而不是记录警告,我可以看到,对于非空elementController.selected"或"empty elementController.selected"测试的每个实例,在表单提交时调用commandButton的操作之前,都会记录两次该警告. /p>

不知何故-这是我不完全理解的部分-在@RequestScoped下,分配给变量当前"(也称为选定")的Element丢失了,并且在测试中输入了"not not empty elementController.selected"视图页面(以及getSelected())被两次调用,其中current = null(具有副作用).

如果我将ElementController的范围更改为@SessionScoped,问题就消失了.当前的Element状态保持不变,并且永远不会使用'current'null调用getSelected().

对于在JSF阶段和请求范围方面进行解释的任何回复评论,我将不胜感激,为什么之前我的'current'变量突然突然为空,为什么'#{not empty elementController.selected}'被执行了两次强烈要求采取行动.我检查了之间是否根本没有调用setId(仅在初始视图页面加载时),是否有其他东西将current重置为null,或者在表单提交和操作调用之间重新创建了请求范围的Bean.

I am having problems getting an action of a managed bean to be called when certain other JSF elements from custom composite components are present in the same page as the h:commandButton that submits the h:form. I can narrow down the page elements that cause problems to those that perform the following harmless (to me) looking rendering tests:

        <h:panelGroup rendered="#{not empty cc.attrs.element}">

or:

        <h:panelGroup rendered="#{empty cc.attrs.element}">

where 'element' is a required attribute, loaded from a JPA entity by id. Something about that 'empty cc.attrs.element' or 'not empty cc.attrs.element' test blocks correct form submission and blocks action calling in any h:form in which it appears.

The form submission page debug.xhtml is:

<f:view>
    <f:metadata>
        <f:viewParam name="id" value="#{elementController.id}"/>
    </f:metadata>
</f:view>

<h:head>
    <title>DEBUG</title>
</h:head>
<h:body>
<h:form>        
<util:view_link element="#{elementController.selected}"/>
<h:commandButton value="Apply" action="#{elementController.reflect}" /> 
        </h:form>
    </h:body>

Note the view parameter 'id' that via ElementController.setId will force the loading of the 'selected' or 'current' Element entity by id from JPA, not shown here. This should then move to test.xhtml via the action reflect():

@JSFaction
public Object reflect() {
    String $i = "reflect";
    log_debug($i);
    if (current==null) {
        log_warn($i, "can't generate reflect outcome for null current element");
        return null;
    }
    //return "reflect?faces-redirect=true&includeViewParams=true&id="+current.getId();
    //return "test?faces-redirect=true&includeViewParams=true&id="+current.getId();
    //return "reflect?id="+current.getId();
     return "/test?faces-redirect=true&id="+current.getId();
}

You can see above I have experimented with different outcome URLs, but it turns out the problem has nothing to do with that, it is simply that the action is not called at all (when the {not empty cc.attrs.element} test is performed in another page element.

The composite component that causes the problem is my util:view_link::

    <composite:interface>
    <composite:attribute name="element" required="true" type="com.greensoft.objectdb.test.entity.Element"/>
    <composite:attribute name="name" required="false"/>
    <composite:attribute name="showOwnerAsText" required="false" default="false"/>
    <composite:attribute name="showModelClass" required="false" default="false"/>
    <composite:attribute name="showEntityClass" required="false" default="false"/>
    <composite:attribute name="showId" default="false"/>
    <!--composite:attribute name="showHelp" required="false" default="false"/-->
    <!--composite:attribute name="title" required="false"/-->
</composite:interface>

<composite:implementation>

    <h:panelGroup rendered="#{empty cc.attrs.element}">
        <span class="warn">NULL</span>
    </h:panelGroup>

    <h:panelGroup rendered="#{not empty cc.attrs.element}">
        <h:panelGroup rendered="#{cc.attrs.showOwnerAsText}">
            <h:outputText style="font-weight:bold" value="#{cc.attrs.element.owner.name}."/>
        </h:panelGroup>

        <h:panelGroup rendered="#{cc.attrs.showId}">
            <h:link outcome="/view" value="&lt;#{cc.attrs.element.id}&gt;">
                <f:param name="id" value="#{cc.attrs.element.id}"/>
            </h:link>
        </h:panelGroup>

        <h:link outcome="/view" 
                value="#{empty cc.attrs.name ? cc.attrs.element.name: cc.attrs.name}"
                >
            <f:param name="id" value="#{cc.attrs.element.id}"/>
        </h:link>
    </h:panelGroup>
</composite:implementation>

You can see this is just a reusable way of creating a link to each Element, with the database id passed as parameter. If there is no Element a "NULL" is shown.

By commenting out bits at a time (bracketing) I can prove the problem has something to do with rendered="#{empty cc.attrs.element} and rendered="#{not empty cc.attrs.element} tests, which both prevent form submission and the action from being invoked by a h:commandButton in the same composed page (see debug.xhtml above). If for example I just use plain h:panelGroup elements without the 'rendered="#{empty cc.attrs.element}' and 'rendered="#{not empty cc.attrs.element}' tests, everything displays fine and the h:commandButton works as expected and the action is called ok, and the navigation works fine !

Q: Why does test 'rendered="#{empty cc.attrs.element}' or 'rendered="#{not empty cc.attrs.element}' prevent correct form submission and stop the action from being called ?

Grateful for ideas, Webel

EDIT: The problem only happens when I test on elementController.selected, where .selected is an @Entity Element. I tried it with a simple Duummy class with String name property, and a @RequestScoped DebugController, and it worked fine (the action was called).

<h:panelGroup rendered="#{not empty debugController.dummy}">
<h:outputText value="#{debugController.dummy.name}"/>
</h:panelGroup>
<h:commandButton value="Apply" action="#{elementController.reflect}" /> 

Reported as bug: http://java.net/jira/browse/JAVASERVERFACES-2343

解决方案

I can now partially answer this question. I can now see what is happening and describe it, but I still do not understand fully - in terms of JSF phases and request scope - why it is happening as it is. It is a subtle and tricky problem.

To explain it I have rewritten the test case to make it simpler. The view.xhtml test page that takes an id query parameter is:

<f:view>
    <f:metadata>
        <f:viewParam name="id" value="#{elementController.id}"/>
    </f:metadata>
</f:view>
<h:body>   
    <h:form>

       <h:panelGroup rendered="#{empty elementController.selected}">
           <div style="color:orange;">
               No Element found for id(#{elementController.id}) or page called without id query param.
           </div>
       </h:panelGroup>

       <h:panelGroup rendered="#{not empty elementController.selected}">
           [<h:outputText value="#{elementController.selected.id}"/>]&nbsp;
           <h:outputText value="#{elementController.selected.name}"/>
       </h:panelGroup>

        <br/><br/>

       <h:commandButton value="Apply" action="#{elementController.action}" />

    </h:form>
</h:body>

Where ElementController extends RequestController. On loading a view.xhtml the id query parameter is set as a viewParam using setId, which loads a matching Element entity by id (from database) and sets it as the 'current' (a.k.a. "selected") Element, available as getSelected(), which also performed a test for null current, and that was what was causing the problem:

public void setId(Long id) {
    log_debug("setId","id",id);
    if (id != null) {
        this.id = id;
        T found = (T) getAbstractFacade().find(id);
        if (found == null) {
            String $error = "No object with id(" + id + ") found for class " + getManagedClass().getSimpleName();
            log_error($error);
        }
        setCurrent(found);
    }
}

public T getSelected() {
    log_debug("getSelected","current",current);        
    if (current == null) {
        //current = newElement(); //THIS WAS CAUSING PROBLEMS
        log_warn("getSelected","null current Element");
    }
    return current;
}

The problem was that on submission of the form, getSelected() was being called twice for each instance of the test '#{not empty elementController.selected}' or '#{empty elementController.selected}' before the action was being called and with the field 'current' null.

In the @RequestScoped @ManagedBean ElementController the getSelected() method had a test for whether the current (a.k.a. selected) Element is null, and was taking an action that lead to an error, and this was shortcircuiting the call of the action on the commandButton when a 'not empty elementController.selected' was called:

(The problem I am seeing has nothing to do with whether the "selected" Element subjected to the 'not empty' or 'empty' test is an @Entity or not, I was mislead to believe that because the problem is with the ElementController that handles Element entities, and of course that conclusion did not sound right anyway.)

If I get rid of the assignment of newElement(), which was causing an error, and was there to catch cases when the view page is invoked without an id parameter (that triggers loading of the 'current' entity by id), and instead log a warning, I can see that for each instance of a 'not empty elementController.selected' or 'empty elementController.selected' test, that warning is logged twice BEFORE the action of the commandButton is called on form submission.

Somehow - and this is the part I do not fully understand - under @RequestScoped, the Element that was assigned to variable 'current' (a.k.a. "selected") is lost, and the 'not empty elementController.selected' test in the view page (and thus getSelected()) is invoked twice with current = null (with side-effects).

If I change the scope of the ElementController to @SessionScoped the problem vanishes; the current Element state is maintained, and getSelected() is never called with 'current' null.

I would appreciate any reply comments that explain in terms of JSF phases and request scope why my 'current' variable is suddenly null and why the '#{not empty elementController.selected}' is performed twice before the action is called. I have checked that setId is not called at all in between (only on initial view page load), something else is resetting current to null, or the request scoped bean is recreated in between form submission and the calling of the action.

这篇关于JSF2:为什么在复合中的panelGroup的呈现中进行空测试会阻止调用动作?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆