Search code examples
jsfjsf-2.2myfaces

PostAddToView event not fired for Head in myfaces


I'm dynamically injecting some JS into all my pages, and this works fine in Mojarra, but I've found out it fails in myfaces.

My event listener is configured as:

<application>
    <system-event-listener>
        <system-event-listener-class>a.b.HeadResourceListener</system-event-listener-class>
        <system-event-class>javax.faces.event.PostAddToViewEvent</system-event-class>
        <source-class>javax.faces.component.UIOutput</source-class>
    </system-event-listener>
</application>

With code looking something like:

public class HeadResourceListener implements SystemEventListener {

  @Override
  public boolean isListenerForSource(Object source) {
    return "javax.faces.Head".equals(((UIComponent) source).getRendererType());
  }

  @Override
  public void processEvent(SystemEvent event) {
    UIComponent outputScript = new UIOutput();
    outputScript.setRendererType("javax.faces.resource.Script");
    UIOutput content = new UIOutput();
    content.setValue("var abc='';");
    outputScript.getChildren().add(content);
    context.getViewRoot().addComponentResource(context, outputScript, "head");
  }
}

Unfortunately, with myfaces, the rendererType of the source is never javax.faces.Head (I only found occurrences of javax.faces.resources.Script and javax.faces.resources.Stylesheet)

Is there any specific reason why the behaviour differs here? Any suggestions for another solution maybe?

EDIT

As suggested, when linking this listener to source-class , it is triggered in myfaces. However, on postback, I get duplicate id errors...

Caused by: org.apache.myfaces.view.facelets.compiler.DuplicateIdException:    Component with duplicate id "j_id__v_7" found. The first component is {Component-  Path : [Class: javax.faces.component.UIViewRoot,ViewId: /user/login.xhtml][Class:  org.apache.myfaces.component.ComponentResourceContainer,Id:  javax_faces_location_head][Class: javax.faces.component.UIOutput,Id: j_id__v_7]}
at  org.apache.myfaces.view.facelets.compiler.CheckDuplicateIdFaceletUtils.createAndQueueException(CheckDuplicateIdFaceletUtils.java:148)
at [internal classes]
at javax.faces.application.ViewHandlerWrapper.renderView(ViewHandlerWrapper.java:73)
at org.apache.myfaces.tomahawk.application.ResourceViewHandlerWrapper.renderView(ResourceViewHandlerWrapper.java:169)
at javax.faces.application.ViewHandlerWrapper.renderView(ViewHandlerWrapper.java:73)

Solution

  • It's a bug in MyFaces.

    JSF 2.3 specification says the following in table 9.2:

    TABLE 9-2 Standard HTML RenderKit Tag Library

    getComponentType()     getRendererType()
    javax.faces.Output     javax.faces.Head
    

    As per chapter 4.1.10.1 of the same specification, javax.faces.Output maps to javax.faces.component.UIOutput.

    4.1.10.1 Component Type

    The standard component type for UIOutput components is “javax.faces.Output”.

    So, the <h:head> must be an instance of UIOutput.

    If we look back at table 9.2, the javax.faces.Output can have multiple renderers, so you can indeed only listen on <source-class> of javax.faces.component.UIOutput and you'd have to manually inspect its renderer type to be javax.faces.Head. Your HeadResourceListener is correct.

    See also: