4

I have a page using dynamic forms where I am creating the component tree programatically (which is not up for debate in this question) Some of the input controls I need to render require an ajax handler.

The xhtml fragment (included by a <ui:include> from another fragment) is :

<ui:composition xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:f="http://java.sun.com/jsf/core"
    xmlns:h="http://java.sun.com/jsf/html" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:p="http://xmlns.jcp.org/jsf/passthrough">

    <h:panelGroup id="id_Group1" binding="#{questionaire.group1}" layout="block"/>

</ui:composition>

Based on other SO anwsers, I have the following bean code:

   public HtmlPanelGroup getGroup1() {

        // irrelevant code omitted

        HtmlSelectOneRadio selectUI = new HtmlSelectOneRadio();
        AjaxBehavior valueChangeAction = (AjaxBehavior)FacesUtils.getApplication().createBehavior(AjaxBehavior.BEHAVIOR_ID);

        valueChangeAction.addAjaxBehaviorListener(new ProbeQuestionListener(currentQuestion, "probeDiv" + questionNumber));


        selectUI.addClientBehavior("change", valueChangeAction);
        valueChangeAction.setRender(Collections.singletonList("probeDiv" + questionNumber));

       // further code to customise the control, create the panel group and probe div and wire everything together omitted
    }

This renders correctly and I see:

<input type="radio" onchange="mojarra.ab(this,event,'change',0,'probeDiv2')" value="0" id="answer_1:0" name="answer_1">

However, clicking the radio button gives me a javascript console error: reference error: mojarra is not defined

Now, if I modify the xhtml to include a "normal" ajax control, e.g.

<ui:composition xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:f="http://java.sun.com/jsf/core"
    xmlns:h="http://java.sun.com/jsf/html" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:p="http://xmlns.jcp.org/jsf/passthrough">

    <h:panelGroup id="id_Group1" binding="#{questionaire.group1}" layout="block"/>

    <!-- include a hacky hidden ajax field to force inclusion of the ajax javascript -->
    <h:panelGroup layout="block" id="hiddenAjaxDiv" style="display:none">
        <h:inputText id="hiddenAjax">
            <f:ajax execute="hiddenAjax" render="hiddenAjaxDiv" />
        </h:inputText>
    </h:panelGroup>    

</ui:composition>

This works and firebug network monitor shows my ajax event from the radio button is posted to the app.

So, finally, my question:

How do I programatically force the inclusion of the ajax javascript library and dispense with the horrible hack I am currently using?

Note: I am not interested in any answer that starts "don't use dynamically generated components" - for several reasons, this is not an option.

Steve Atkinson
  • 1,219
  • 2
  • 12
  • 30

1 Answers1

10

Basically, you need to include the Faces script file. You can declare it in one of the following ways, depending on the Faces/JSF version:

<!-- Faces 4.0 -->
<h:outputScript library="jakarta.faces" name="faces.js" target="head" />

<!-- JSF 3.0 -->
<h:outputScript library="jakarta.faces" name="jsf.js" target="head" />

<!-- JSF 2.x -->
<h:outputScript library="javax.faces" name="jsf.js" target="head" />

That script contains in case of Mojarra the mojarra definition among the standard faces/jsf namespace containing the Faces ajax scripts. This is normally already auto-included when using <f:ajax>.

You can explicitly declare it in the <h:head> of your master template, if necessary via <ui:define>/<ui:include>. It won't load duplicate copies of the script file if already implicitly required by the view.

You can even programmatically create it:

UIComponent facesjs = new UIOutput();
facesjs.getAttributes().put("library", "jakarta.faces");
facesjs.getAttributes().put("name", "faces.js");
facesjs.setRendererType("jakarta.faces.resource.Script");
FacesContext context = FacesContext.getCurrentInstance();
context.getViewRoot().addComponentResource(context, facesjs, "head");

Also here, it won't load duplicate copies of the Faces script file if already implicitly required by the view.


Unrelated to the concrete problem, you should really prefer <f:event type="postAddToView"> over binding when you need to programmatically populate the component tree:

<h:panelGroup id="id_Group1" layout="block">
    <f:event type="postAddToView" listener="#{questionaire.populateGroup1}" />
</h:panelGroup>

with

public void populateGroup1(ComponentSystemEvent event) {
    HtmlPanelGorup group1 = (HtmlPanelGroup) event.getComponent();
    // ...
}

This guarantees that the tree is populated at exactly the right moment, and keeps getters free of business logic, and avoids potential "duplicate component ID" trouble when #{questionaire} is in a broader scope than the request scope, and keeps the bean free of UIComponent properties which in turn avoids potential serialization trouble and memory leaking when the component is held as a property of a serializable bean. See also How does the 'binding' attribute work in JSF? When and how should it be used?

BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • Marvellous! Once again, you've solved our problem. I will also look into the postAddToView as you suggest. – Steve Atkinson Nov 13 '13 at 17:55
  • balus, can you please expand on duplicate Id issues if my bean is in view scope? I vaguely remember reading this somewhere a while ago but can't find the question in which you discussed the detail of it – Steve Atkinson Nov 13 '13 at 20:35
  • 1
    @SteveAtkinson I guess it's too late to reply you, but here is the detailled explanation if someone else ended here: http://stackoverflow.com/a/14917453/4170582 – Tarik Mar 11 '15 at 10:47
  • Thanks for the prompt - I'd actually forgotten to look into it - and the reminder is timely as I shortly need to amend that bean for some new features. The article explains some behaviour I saw when first writing the class that I didn't fully understand and to get around it, I shamefully was programatically recreating the view on each request - now I know why that was happening and can change t the correct method that Balus suggests, – Steve Atkinson Mar 14 '15 at 12:29