0

I'm having problem with h:form, which contains composite, which in turn contains p:dialog appendTo="@(body) with h:form. When p:selectOneRadio is selected, an ajax is fired which somehow destroyes backing bean (@PostConstruct method is invoked).

Someone had similar problem here (without solution provided).

Bean

@ManagedBean
@javax.faces.bean.ViewScoped
public class TestBean implements Serializable {
    private String radioValue;

    @PostConstruct
    private void onPostConstruct() { /* ... */ }
    public void init() { /* ... */ }
    public void onCommandButtonAction() { /* ... */ }
    public void onRadioChange() { /* ... */ }

    public String getRadioValue() { return radioValue; }
    public void setRadioValue(String radioValue) { this.radioValue = radioValue; }
}

View

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:custom="http://java.sun.com/jsf/composite/components">

    <h:head>
    </h:head>

    <h:body>
        <f:event type="preRenderComponent" listener="#{testBean.init}"/>

        <h:form id="form">
            <custom:radioComponent id="radioComponent"/>
        </h:form>
    </h:body>
</html>

Composite (radioComponent)

<ui:composition 
        xmlns="http://www.w3.org/1999/xhtml"
        xmlns:ui="http://java.sun.com/jsf/facelets"
        xmlns:p="http://primefaces.org/ui"
        xmlns:f="http://xmlns.jcp.org/jsf/core"
        xmlns:composite="http://java.sun.com/jsf/composite"
        xmlns:h="http://xmlns.jcp.org/jsf/html"
        xmlns:dialog="http://java.sun.com/jsf/composite/components/dialogs">

    <composite:interface>
    </composite:interface>

    <composite:implementation>
        <p:selectOneRadio
                id="selectOneRadio"
                value="#{testBean.radioValue}">

            <f:selectItem itemLabel="a" itemValue="a" />
            <f:selectItem itemLabel="b" itemValue="b" />

            <p:ajax event="change"
                    listener="#{testBean.onRadioChange}"
                    update="text"/>
        </p:selectOneRadio>

        <h:outputText id="text" value="#{testBean.radioValue}"/>

        <!-- if not present, @PostConstruct is NOT invoked -->
        <dialog:formDlg id="formDlg"/>
    </composite:implementation>
</ui:composition>

Dialog

<ui:composition 
        xmlns="http://www.w3.org/1999/xhtml"
        xmlns:ui="http://java.sun.com/jsf/facelets"
        xmlns:h="http://java.sun.com/jsf/html"
        xmlns:p="http://primefaces.org/ui"
        xmlns:composite="http://java.sun.com/jsf/composite">

    <composite:interface>
    </composite:interface>

    <composite:implementation>
        <!--
        if appendTo="@(body)" is ommited,
        radio button does not destroy bean
        i.e. @PostConstruct is NOT invoked
        -->
        <p:dialog
                id="formDlg"
                widgetVar="formDlgWidget"
                modal="true" 
                appendTo="@(body)">

            <h:form id="dialogForm">
                <!--
                does not work if appendTo="@(body)" is present
                if appendTo="@(body)" is ommited,
                action is getting invoked...
                -->
                <p:commandButton
                        value="click"
                        action="#{testBean.onCommandButtonAction}"/>
            </h:form>
        </p:dialog>
    </composite:implementation>
</ui:composition>

Note, dialog's form is rendered as input (I was expecting form tag):

form rendered as input

EDIT

As Kukeltje point me out, this is a result of poor design. Forms nesting, even if nested form is moved out by appendTo="@(body)" it's initially illegal construct as BalusC stated here. So the lesson is composites should never be wrapped by outer form as they may contain own forms.

WORKAROUND

If you want to avoid rewritting of whole page composition, you can use OmniFaces utility tag o:moveComponent to move "formDialog" out of the parent form.

<o:moveComponent
        id="moveDialog"
        for=":#{facesContext.viewRoot.clientId}" 
        destination="ADD_LAST">
    <dialog:formDlg id="formDlg"/>
</o:moveComponent>

Note, for="{cc.attrs.parentFormClientId}" resolves to an emtpy string, even if this attribute is provided.

matoni
  • 2,171
  • 17
  • 33
  • @Kukeltje Thanks for the link. Do you know about some workaround? I have complex page where base form is wrapping almost everything (sad) - I would like to avoid rewriting of this page for know If possible. – matoni May 17 '19 at 11:05
  • Composites should not contain their own form is a different (imo) better approach. In the case of a dialog that needs to be modal, you can also 'require' the use of a `ui:insert/ui:define` construct especially for the modal dialogs. https://stackoverflow.com/questions/4792862/how-to-include-another-xhtml-in-xhtml-using-jsf-2-0-facelets – Kukeltje May 17 '19 at 11:06
  • @Kukeltje _"Composites should not contain their own form ..."_ is there any guide how to deal with composites containing inputs? Relying on outer form may look a bit weird (maybe I'm wrong). – matoni May 17 '19 at 11:10
  • One generic 'rule' I read somehwere is that developers should not have to know the internal structure of the composite component (and not have experience any side effects when using one. Additional 'rule' information can be read here: https://stackoverflow.com/questions/6822000/when-to-use-uiinclude-tag-files-composite-components-and-or-custom-componen and the _"is supposed to be bound to a single bean property."_ is most likely 'violated' here since a dialog with a form most likely contains additional input. – Kukeltje May 17 '19 at 11:30
  • Altough I can imagine some additional search functionality for the real input feels like a valid usecase for being in a composite component (at least it feels like that for me). The requirement of the dialog being modal and due to other page design related things the (technical) requriement of the `appendTo="@(body)` is what creates the conflict here. I never ran into this, most likely due to other page design (template) so I do not have a direct solution to this (primarily) html problem (not specifically JSF/PrimeFaces related although it manifests itself here – Kukeltje May 17 '19 at 11:33
  • But it is an interesting usecase which I'll try to think about over the weekend (if I do not forget) But on second thought, I do not see you using 'modal' anywhere, so why the need for an `appendTo="@(body)"` – Kukeltje May 17 '19 at 11:35
  • @Kukeltje (forget to include "modal" in sample, I will update cod in my question) Yeah, I've implemented custom dialog which handles specific file uploads i.e. there is a composite + backing bean for this composite which has to obey contract - dialog handler interface (this type is used as composite attribute). But I'm having trouble to integrate it in existing page due of poor page design - If there wasn't outer form which wraps all composites, I would not get trapped in this situation :). Again, thank you for your time. – matoni May 17 '19 at 11:42
  • The outer form is not the 'problem' imo. An explicit form around (composite) inputs is a normal design. The modal dialog inside the composite that requires `appendTo="(@body")`" and its own form is what is causing the problem. You could see if you can put an ` in the template before `

    ` and in the composite of the selectOneRadio, surround the `` with a `...` Not sure if it works though from composites. I use this construct all the time and it makes the `appendTo` superfluous btw.

    – Kukeltje May 17 '19 at 11:58
  • if you have new problems, create a new question. The latest edit is not related to this one so I reverted it. – Kukeltje May 17 '19 at 12:50
  • @Kukeltje Ok, I will leave it as is, I just wanted to provide side effect (which might be googled as well by someone having similar trouble). – matoni May 17 '19 at 12:56
  • A side effect of what? It just seemed to be a statement all on its own. Not related to anything before it – Kukeltje May 17 '19 at 12:59
  • @Kukeltje `@PostConstruct` is standardly invoked on page visit for `@ViewScoped` bean, however `@PostConstruct` is also fired by ajax when nesting forms as I experienced, hence `@PostConstruct` operating with GET parameters will throw NPE in case of ajax which will pop-up in browser console as _'Uncaught TypeError: Cannot read property 'getElementsByTagName' of null'_, but never mind, I think question already contains enough source of information with links you provided. – matoni May 17 '19 at 13:14
  • Workaround is nice (thougt I mentioned it but see I did not). But 'workarounds' are answers and as such should be answers, not in edits of questions. Please create an answer with a code example and I'll upvote! – Kukeltje May 19 '19 at 18:13
  • @Kukeltje Currently it is not possible to add an answer as question is closed. – matoni May 19 '19 at 20:06

0 Answers0