0

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.

Community
  • 1
  • 1
jplatasv
  • 155
  • 3
  • 18

2 Answers2

1

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

Community
  • 1
  • 1
Makhiel
  • 3,874
  • 1
  • 15
  • 21
  • I'm not sure how to implement your suggestion. – jplatasv Apr 02 '14 at 11:40
  • Thanks for your help, @Makhiel. I had already tried the same way you explained, but neither the setters are being fired nor the function defined as action is being called. The popup is not even closing after completion. I think that something is getting lost in the view as I see this message on the javascript console: `Uncaught TypeError: Cannot call method 'hide' of undefined`. That hide function is probably the `hide` popup function that it's called on the `oncompletion` attribute of the `a4j:commandButton`. Any other idea about what could be happening? Thanks in advance. – jplatasv Apr 02 '14 at 13:51
  • Hi @Makhiel. I've already tried your suggestion, with a `a4j:commandButton` outside the `rich:popupPanel` I have inside the composite component, and it works as expected. However, when using the same button inside the popup, a non-postback HTTP POST is sent to the server (all other popups in the view send a postback only) causing the view initialization method to be executed. Although it's execution seems to go fine and no bean states are lost, the `a4j:commandButton` action method is not fired. – jplatasv Apr 03 '14 at 11:04
  • Well all the other buttons use `actionListener`, the one inside uses `action`, try it with `actionListener` instead. – Makhiel Apr 03 '14 at 12:01
  • Hi @Makhiel, it doesn't work either. I did a workaround which I'll now post. Thanks again for your help. – jplatasv Apr 04 '14 at 09:39
0

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>
jplatasv
  • 155
  • 3
  • 18