1

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)
Steven De Groote
  • 2,187
  • 5
  • 32
  • 52
  • Can you replicate it via a forked primefaces-test? I added 2 unittests in MF but it seems to work. If yes, please create a MyFaces JIRA ticket. – tandraschko Apr 12 '20 at 21:32
  • I indeed forgot about that, but yes, I have a repro now. Check out https://github.com/stevendegroote/primefaces-test . You'll see the method isListenerForSource executed in Mojarra23, and not with Myfaces23 – Steven De Groote Apr 14 '20 at 07:29
  • I think it should work when you use javax.faces.component.html.HtmlHead instead of UIOutput in the source-class. I'm not sure wheter it should be supported by the specs or MyFaces is "to hard". – tandraschko Apr 14 '20 at 09:02
  • Added as enhancement for 2.3-next-M3: https://issues.apache.org/jira/browse/MYFACES-4328 Its not clear in the specs how it should work, therefore not a bug. – tandraschko Apr 14 '20 at 13:15
  • @tandraschko Your suggestion works in myfaces, only to end up with another, duplicate id issue. Unfortunately, changing the source-class also breaks my listener in Mojarra (where it's no longer being called). – Steven De Groote Apr 16 '20 at 11:40

1 Answers1

2

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:

Community
  • 1
  • 1
BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • Do you have any suggestions for a workaround maybe? – Steven De Groote Apr 17 '20 at 17:30
  • You could listen on `PostAddToViewEvent` of `UIViewRoot` instead. – BalusC Apr 17 '20 at 22:38
  • That works in Mojarra indeed. Sadly, in MyFaces, my component gets added twice on each page. Another bug it seems. Pfffft :( – Steven De Groote Apr 20 '20 at 08:11
  • Awkward. You should probably check for `ResourceHandler#isResourceRendered()` or check if `UIViewRoot#getComponentResources()` already contains it. By the way, does renderer type of `javax.faces.Body` come through in MyFaces or not? – BalusC Apr 22 '20 at 08:54