Pergunta

I've been asked to refactor the following code, which works:

<a4j:commandButton type="image"
    image="/someImage.gif"
    action="#{SomeViewController.someDeleteAction}"
    onclick="return confirm('#{msg['a.message']}');"
    render="someDataTableWithItems">
    <f:setPropertyActionListener
        target="#{SomeViewModel.selectedItem}" value="#{item}" />
</a4j:commandButton>

..to an alternative which allowed to use a custom popup to confirm the action. This snippet is embedded into a column inside a rich:dataTable component. #{item} is a reference to the object assigned to the row where this button is (defined in the dataTable as var="item").

I decided to do a JSF composite component (first one I do) to have something I could reuse. It's based on this answer by elias

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml"
    xmlns:ui="http://java.sun.com/jsf/facelets"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:f="http://java.sun.com/jsf/core"
    xmlns:a4j="http://richfaces.org/a4j"
    xmlns:rich="http://richfaces.org/rich"
    xmlns:cc="http://java.sun.com/jsf/composite">
<cc:interface>
    <cc:attribute name="message" default="#{msg['a.default.message']}" />
    <cc:attribute name="header"
        default="#{msg['a.default.header']}" />
    <cc:attribute name="action" required="true"
        method-signature="java.lang.String action()" />
    <cc:attribute name="actionListener" required="false"
        method-signature="java.lang.String action()" />
    <cc:attribute name="value" default="Send" />
    <cc:attribute name="cancelBtn" default="#{msg['a.default.cancel']}" />
    <cc:attribute name="confirmBtn" default="#{msg['a.default.ok']}" />
    <cc:attribute name="render" default="@form" />
    <cc:attribute name="type" default="submit" />
    <cc:attribute name="image" required="false"/>
    <cc:attribute name="tooltip" required="false" />
</cc:interface>
<cc:implementation>
    <a4j:commandButton type="#{cc.attrs.type}" rendered="#{empty cc.attrs.actionListener and not empty cc.attrs.image}"
        image="#{cc.attrs.image}" value="#{cc.attrs.value}"
        oncomplete="#{rich:component('somePopup')}.show()">
        <rich:tooltip followMouse="false" showDelay="1000" rendered="#{not empty cc.attrs.tooltip}">
            #{cc.attrs.tooltip}
        </rich:tooltip>
    </a4j:commandButton>
    <a4j:commandButton type="#{cc.attrs.type}" rendered="#{not empty cc.attrs.actionListener and not empty cc.attrs.image}"
        actionListener="#{cc.attrs.actionListener}"
        image="#{cc.attrs.image}" value="#{cc.attrs.value}"
        oncomplete="#{rich:component('somePopup')}.show()">
        <rich:tooltip followMouse="false" showDelay="1000" rendered="#{not empty cc.attrs.tooltip}">
            #{cc.attrs.tooltip}
        </rich:tooltip>
    </a4j:commandButton>
    <a4j:commandButton type="#{cc.attrs.type}" rendered="#{empty cc.attrs.actionListener and empty cc.attrs.image}"
        value="#{cc.attrs.value}"
        oncomplete="#{rich:component('somePopup')}.show()">
        <rich:tooltip followMouse="false" showDelay="1000" rendered="#{not empty cc.attrs.tooltip}">
            #{cc.attrs.tooltip}
        </rich:tooltip>
    </a4j:commandButton>
    <a4j:commandButton type="#{cc.attrs.type}" rendered="#{not empty cc.attrs.actionListener and empty cc.attrs.image}"
        actionListener="#{cc.attrs.actionListener}" value="#{cc.attrs.value}"
        oncomplete="#{rich:component('somePopup')}.show()">
        <rich:tooltip followMouse="false" showDelay="1000" rendered="#{not empty cc.attrs.tooltip}">
            #{cc.attrs.tooltip}
        </rich:tooltip>
    </a4j:commandButton>
    <rich:popupPanel id="somePopup"
        header="#{cc.attrs.header}" autosize="true" resizeable="false">
        <p>#{cc.attrs.message}</p>
        <a4j:commandButton action="#{SomeViewController.someDeleteAction}" <!-- It should be #{cc.attrs.action} but I just put this for debug -->
            value="#{cc.attrs.confirmBtn}" render="#{cc.attrs.render}"
            oncomplete="#{rich:component('somePopup')}.hide()">
            <cc:insertChildren />
        </a4j:commandButton>
        <h:commandButton value="#{cc.attrs.cancelBtn}"
            onclick="#{rich:component('somePopup')}.hide(); return false;" />
    </rich:popupPanel>
</cc:implementation>
</html>

..and replace the previous a4j:commandButton with this:

<my:buttonConfirm type="image" id="someID"
    image="/someImage.gif"
    action="#{SomeViewController.someDeleteAction}"
    message="#{msg['a.confirmation.message']}"
    render="someDataTableWithItems"
    tooltip="#{msg['a.tooltip.message']}">
    <f:setPropertyActionListener for="someID"
        target="#{SomeViewModel.selectedItem}" value="#{item}" />
</my:buttonConfirm>

The popup appears and you can cancel the action, but when confirming it, SomeViewModel is reinstantiated again, losing the previous bean in scope.

The scope is a custom view scope got from here

I then changed the scope of the model to this one:

@Component("SomeViewModel")
@ViewScoped

Although I tried to use @ManagedBean instead of @Component, the app gave me autowiring errors so I left @Component. The scope was now kept. I don't know whether the mix of JSF and Spring annotations on this way may have any other consequences.

However the scope of SomeViewModel was now fine, the f:setPropertyActionListener target was never set and the action #{SomeViewController.someDeleteAction} was never called. I have been unable to debug this (not sure where to put breakpoints to see what happens in the middle).

Thanks in advance for your help.

Foi útil?

Solução 2

After trying some suggestions and researching a little on my own I concluded that there is some kind of problem when using a4j:commandButton inside a rich:popupPanel. Action methods are not being called and attributes defined on f:setPropertyActionListener are not being setted. I have been unable to find out what is exactly getting lost in there.

I've seen examples on the Internet with a4j:commandButton inside a popup so I am not sure if this is caused by any of my dependencies. I use jsf-api 2.1.19, jsf-impl 2.1.19-jbossorg-1 and richfaces 4.3.5.Final.

This is the workaround I finally did. I hope it can be helpful to anyone with the same problem I had:

confirmButton.xhtml

<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<html xmlns="http://www.w3.org/1999/xhtml"
    xmlns:ui="http://java.sun.com/jsf/facelets"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:f="http://java.sun.com/jsf/core"
    xmlns:a4j="http://richfaces.org/a4j"
    xmlns:rich="http://richfaces.org/rich"
    xmlns:cc="http://java.sun.com/jsf/composite">
<cc:interface>
    <cc:attribute name="message"
        default="Some message" />
    <cc:attribute name="header"
        default="Some header" />
    <cc:attribute name="cancelBtn" default="No" />
    <cc:attribute name="confirmBtn" default="Yes" />
    <cc:attribute name="type" default="submit" />
    <cc:attribute name="icon" required="false" />
    <cc:attribute name="image" required="false" />
    <cc:attribute name="action"
        targets="popupConfirmButton" />
    <cc:actionSource name="confirmListeners"
        targets="popupConfirmButton" />
</cc:interface>
<cc:implementation>
    <a4j:commandButton type="#{cc.attrs.type}"
        image="#{cc.attrs.image}"
        oncomplete="#{rich:component('popupConfirm')}.show()">
    </a4j:commandButton>
    <a4j:commandButton id="popupConfirmButton"
        style="visibility: hidden;" render="#{cc.attrs.render}">
    </a4j:commandButton>
    <rich:popupPanel id="popupConfirm" header="#{cc.attrs.header}"
        autosized="true" width="475" resizeable="false">
        <f:facet name="controls">
            <h:outputLink value="#"
                onclick="#{rich:component('popupConfirm')}.hide(); return false;" />
        </f:facet>
        <h:panelGrid columns="2">
            <h:graphicImage value="#{cc.attrs.icon}" height="64" width="64" />
            <p>#{cc.attrs.message}</p>
        </h:panelGrid>
        <br />
        <div align="right">
            <a4j:commandButton value="#{cc.attrs.confirmBtn}"
                onclick="#{rich:element('popupConfirmButton')}.click();
                #{rich:component('popupConfirm')}.hide();" />
            &#160;
            <h:commandButton value="#{cc.attrs.cancelBtn}"
                onclick="#{rich:component('popupConfirm')}.hide(); return false;" />
        </div>
    </rich:popupPanel>
</cc:implementation>
</html>

Component usage

<my:confirmButton type="image" image="someButtonImage.gif"
    icon="someWarningImage.gif"
    action="#{SomeViewController.doStuff}"
    message="Some message"
    render="someComponentID">
    <f:setPropertyActionListener for="confirmListeners"
        target="#{SomeViewModel.someProperty}" value="foo" />
</my:confirmButton>

Outras dicas

The <f:setPropertyActionListener> does not attach to a component id, but to an ActionSource. See this answer for how to do it.

Edit:

Basically instead of <f:setPropertyActionListener for="buttonId"> you'll have

<cc:interface>
    <cc:actionSource name="source" targets="buttonId" />
    …
</cc:interface>
<cc:implementation>
    <a4jcommandButton id="buttonId" … />
</cc:implementation>

And point to it by <f:setPropertyActionListener for="source">.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top