5

I have page where I render some h:panelGroup panels. Those panels are realized as plugins registered in a plugin registry on startup. Part of the plugins api is a custom jsf component where I get the registered plugins for extension point and include their facelet templates by path:

<c:forEach items="#{pluginRegistry.getPlugins(point)}" var="extension">
    <ui:include src="#{extension.path}" />
</c:forEach>

The page where I include the panels looks like:

<h:panelGrid id="dashboard" columns="3">
    <cmf:insertPageFragments point="dashboardExtensionPoint" />
</h:panelGrid>

For every panel there are facelet templates like the one below:

<rich:panel id="caseDetailsPanel" header="panel label">
    <!-- panel content -->
</rich:panel>

Now, the problem is that the very first panel in the list returned by the pluginsRegistry is rendered in the page with the provided id like formId:caseDetailsPanel for example. The rest of them have generated ids like formId:j_idt223 !!! Obviously if I want to rerender some of the panels, I can't do that.

That happens when environment is jboss AS 7.1 with JSF 2.1, richfaces 4.2.3.Final. When deployed on jboss-eap-6.1 everything looks fine but for now I can't use this jboss version.

Any suggestions on how to workaround this issue?

factor5
  • 321
  • 2
  • 12

1 Answers1

6

There can not be multiple JSF components with the same ID. Each JSF component must have an unique ID. When dynamically creating JSF components using JSTL, you need to manually assign and ensure an unique ID, otherwise JSF will discard the provided ID and autogenerate an unique ID.

There are several ways to achieve this, depending on the concrete functional requirement and the existing code.

  1. Use use the iteration index of <c:forEach>.

    <c:forEach ... varStatus="loop">
        ...
        <rich:panel id="caseDetailsPanel_#{loop.index}" ...>
    

    This will generate caseDetailsPanel_0, caseDetailsPanel_1, etc depending on the current iteration index.

  2. Use the unique identifier of the currently iterated item. It isn't clear based on the information provided so far if you have any, so here's just a fictive example assuming that the class behind #{extension} has an id property representing the technical DB identifier.

    <c:forEach ... var="extension">
        ...
        <rich:panel id="caseDetailsPanel_#{extension.id}" ...>
    
  3. Wrap #1 or #2 if necessary in a <f:subview> with an unique identifier, so that you don't need to modify the includes.

    <c:forEach ... varStatus="loop">
        <f:subview id="panel_#{loop.index}">
            <ui:include ... />
    

    The <f:subview> creates a new NamingContainer around it, so you end up getting formId:panel_0:caseDetailsPanel, formId:panel_1:caseDetailsPanel and so on.

A completely different alternative would be to use <ui:repeat> instead of <c:forEach>. The <ui:repeat> does not run during view build time, but during view render time. This way there's physically only one <rich:panel id="caseDetailsPanel"> component in the component tree which is reused multiple times during generating HTML whereby JSF will take care of generating the right IDs with the <ui:repeat> index like so formId:repeatId:0:caseDetailsPanel. However, this in turn may cause trouble with <ui:include> as it also runs during view build time and thus can't get the #{extension} at hands.

BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • Every registered panel has its own unique id. The one I post as code example is only for example. – factor5 May 23 '13 at 12:57
  • I believe you completely missed the point of how JSTL works in JSF. I recommend to take a step back and carefully read http://stackoverflow.com/a/3343681/157882 and then read my answer once again. – BalusC May 23 '13 at 12:58
  • @Adrian: this is indeed related, but I disagree that this is a bug on JSTL/JSF. See also Manfred's comment *"The c:forEach tag is not a NamingContainer so it should not do anything to make sure that is the case."* You're basically dynamically constructing the JSF component tree yourself, so you should also be dealing with unique IDs yourself. When statically constructing JSF component tree you also don't specify multiple components with the same ID, right? The difference with JSTL way is that JSF don't crash with "Duplicate component ID" error, but instead discards it and autogenerates one. – BalusC May 23 '13 at 13:18
  • So, it looks like the actual problem that I have wasn't so obvious. "Obviously if I want to rerender some of the panels, I can't do that." Even if I wrap the panels in namingcontainer I can't refresh the a particular panel as result of some action. – factor5 May 23 '13 at 13:56
  • Most JSF starters indeed fail when it comes to "view build time" versus "view render time" and which tags participate when. No worries, you're not the only one, you'll get it and the answer is already given in detail. Edit: as per your edited comment: You can certainly refresh them. You can just use EL in `render` attribute. E.g. `render="caseDetailsPanel_#{loop.index}"`. This is further completely unrelated to naming containers. – BalusC May 23 '13 at 13:57
  • Actually I really can't do that or I am missing something. You are right the the naming container probably is not exactly related to the problem but anyway if wrap the panel inside one I get ids like formId:j_idt223:caseLinksPanel. Now, on the page I have a that as result should refresh just the caseLinksPanel panel, right? But it doesn't. If I use the solution from here https://java.net/projects/glassfish/lists/commits/archive/2011-07/message/544 it works as it completely removes the c:forEach. – factor5 May 23 '13 at 14:18
  • Anyway, after I've patched the jboss7.1 with the jboss-jsf-api_2.1_spec-2.1.19.Final-redhat-1.jar and jsf-impl-2.1.19-redhat-1.jar taken from jboss-eap-6.1 its seems everything is working fine now doing it in the way explained in the first post. – factor5 May 23 '13 at 20:41