2

I know what causes the following problem. I am looking for an elegant way to address the problem rather than a brute-force (and non-DRY) workaround I've found.

I have a highly reusable h:form in a template (and I am aware of the dangers of "god forms", this is not quite a god form), and I insert editable content into that form via the template, with an identical action bar of command buttons both at the top and the bottom of the page. (There hundreds of clients to this template, and some insert different actions bars into the template.)

The only reason I am doing this (bar top and bottom) is user convenience; I have found when using many content management systems that it is annoying to have to scroll down or scroll up to find a Save button (or other buttons in an action bar) on long forms.

The template (please don't tell me it's a "god form") has:

<h:form prependId=".." id="form">

 <div id="actions-upper" class="actions">
   <ui:insert name="actions"/>
 </div> 

... other reusable stuff

 <div id="actions-lower" class="actions">
   <ui:insert name="actions"/>
 </div>

</h:form>

Every edit.xhtml page (there are lots of them) that is a client to a template inserts the action bar, along with a compatible #{manager} backing bean parameter:

<ui:composition template="/template.xhtml">
...
    <ui:define name="actions">
        <util:edit_actions id="edit_actions" manager="#{manager}"/>
    </ui:define>

 ... other insertions with editable content omitted

Note how above I have given that CC `util:edit_actions' an id (which I did not do on this CC until recently for reasons I'll explain below).

So you can see that the exact same actions toolbar is inserted into the top and bottom just inside form section of a page. If, however, you do this as shown above with an id passed for edit_actions you get:

javax.servlet.ServletException: Component ID edit_actions has already been found in the view.  

I have been using this template with success for years until I introduced the explicit id, for reasons now illustrated below.

The edit_actions CC has some command buttons such as:

 <composite:implementation>
  <p:toolbar>
   <p:toolbarGroup>
    <p:commandButton 
      ajax ="true"
      action="#{cc.attrs.manager.crud.update}"
      value="Save" 
      update="@form"
      id="save"
     />
         ...

Now that general Save button is not always the only button in the form; there are sometimes other buttons that perform interim AJAX actions with conditionally required input fields, such as this from an embedded links table editor:

 <p:inputText 
   id="newLinkUrl"
   value="#{cc.attrs.manager.newLinkUrl}"
   required="#{param['edit_actions:save']==null}"
   validator="urlValidator"
 />

<p:commandButton                        
   value="Add new link !"
   action="#{cc.attrs.manager.addNewLink(cc.attrs.element)}"
   update="... newLinkUrl ..."
  />

(Where BTW that urlValidator does NOT throw on null, the system relies on the conditional required for that so that the general @form Save always works.)

But to get the conditional required to work:

   required="#{param['edit_actions:save']==null}"

I have to give the inserted edit_actions CC an explicit id whenever performing the insert in any of the hundreds of edit.xhtml client pages that use it:

<ui:composition template="/template.xhtml">
...
    <ui:define name="actions">
        <util:edit_actions id="edit_actions" manager="#{manager}"/>
    </ui:define>

But as shown above, if I do include the id there, it now causes an error (but without it I can't use the conditional required trick).

There are two workarounds I've found so far:

  1. Simply don't have the action bar in the template twice. This is unacceptable, it simply breaks the feature by avoiding it.

  2. Having 2 different insertion points in the template does work, but you have to be careful with the IDs.

And are problems with the 2nd one:

<ui:composition template="/template.xhtml">
 <ui:define name="actions_upper">
  <util:edit_actions id="edit_actions_upper" manager="#{manager}"/>
 </ui:define>
 <ui:define name="actions_lower">
  <util:edit_actions id="edit_actions_lower" manager="#{manager}"/>
 </ui:define>

Note that this code above is not Don't Repeat Yourself (DRY) code, which I consider one of the most important coding practices, and something JSF is usually particularly good at addressing. Ensuring that the above template insertion pattern and id pattern is addressed in hundreds of edit.xhtml page is just plain error prone, unless I can somehow encapsulate that ui:define pair while still being able to inject any compatible #{manager}.

And the conditional required test then has to test on both upper and lower Save buttons:

<p:inputText 
  id="newLinkUrl"
  value="#{cc.attrs.manager.newLinkUrl}"
  required="#{param['edit_actions_upper:save']==null and param['edit_actions_lower:save']==null}"
  validator="urlValidator"
 />

All in all, a rather ugly non-DRY workaround.

Q1: Is there any way I can somehow dynamically change the id of an inserted edit_action.xhtml automatically so that it can appear in the template in 2 different places without a clashing component id error ?

Q2: Alternatively, is there some way I can encapsulate the two ui:define in the workaround for the upper vs lower bar insertion (as show in workaround 2.), while still having the ability to inject the #{manager} (so that I can include it and reuse it as encapsulated policy in hundreds of edit.xhtml clients to the template) ?


EDIT: this attempt to encapsulate my "double action bar" pattern does not seem to work. From /include/edit_actions_defines.xhtml:

<ui:composition 
    xmlns="http://www.w3.org/1999/xhtml"
    xmlns:f="http://xmlns.jcp.org/jsf/core"
    xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
    xmlns:util="http://xmlns.jcp.org/jsf/composite/util">

    <ui:define name="actions_upper">
        <util:edit_actions id="edit_actions_upper" manager="#{manager}"/>
    </ui:define>

    <ui:define name="actions_lower">
        <util:edit_actions id="edit_actions_lower" manager="#{manager}"/>
    </ui:define>

</ui:composition>

With attempted use by edit.xhtml:

<ui:composition template="/template.xhtml">

    <ui:include src="/include/edit_actions_defines.xhtml">
        <ui:param name="manager" value="#{specificManager}"/>            
    </ui:include>

Seems to be ignored silently.

Community
  • 1
  • 1
  • Have you considered using `composite:insertChildren` and nesting all your editing components inside the composite containing the upper & lower buttons? – axemoi Apr 17 '17 at 13:13
  • All suggestions welcome, but I don't see how `composite:insertChildren` helps further. I already use it to inject aspects specific to editing a particular entity type (along with a matching backing bean `manager`) into editing CCs that are included in the system described in the question (although not shown there), and already between the upper and lower buttons. The upper and lower buttons are always identical (for a given page) but I put different button sets (along with their matching `manager`) into the same template for different entity types (via variations on `edit.xhtml`). Will think. – Webel IT Australia - upvoter Apr 18 '17 at 06:20
  • I was referring to refactoring your pages so that your form components can be nested in a CC that contains your upper & lower buttons, but if you're inserting children into those it may not work for you. I imagine you may have already thought of this, but if you don't need to support small devices (phones), I've used a fixed div to hold the interim buttons (save, etc) to achieve what you're after with long multi-page forms. – axemoi Apr 18 '17 at 12:52

1 Answers1

0

I've found an solution (workaround really) to my own problem meeting my requirements. Not pretty, but might be of use to others.

Instead of testing for a specific component id, I use a regexp:

/**
 * Searches for a component id ending in ":save".
 * 
 * @return 
 */
public boolean isSaveButtonPressed() {        
    Map<String, String> parameterMap = FacesContext.getCurrentInstance()
            .getExternalContext().getRequestParameterMap();
    for (String key : parameterMap.keySet()) {
        boolean matches = Pattern.matches("^j.*:save",key);
        if (matches) return true;
    }
    return false;
}

And the required test is simply:

<p:inputText 
  id="newLinkUrl"
  value="#{cc.attrs.manager.newLinkUrl}"
  required="#{not cc.attrs.manager.saveButtonPressed}"
  validator="urlValidator"
/>

Then when inserting my util:edit_actions component (which is to appear both top and bottom of the form via the template for user convenience) I don't pass it an explicit id, I just let JSF generate them, and they are different (no longer clash) for both Save buttons.

So I can have my twice-injected edit_actions cake and eat it. The required test on the URL string field fails correctly as desired for both Save buttons.