12 Replies Latest reply on Sep 24, 2007 3:49 PM by Alexander Smirnov

    reRendering a bound h:panelGroup

    davidintx Newbie

      Using Richfaces-3.1.1-SNAPSHOT (specifically, the version included in Seam-2.0.0.CR1 released a few days ago)

      I have three items on a page
      A. A h:selectOneMenu with a id="pickColumn" and, in the a:support tag, reRender="pickOperator"
      B. Another h:selectOneMenu with a id="pickOperator" and, in the a:support tag, reRender="pickValue"
      C. A h:panelGroup with an id of "pickValue" which can contain text fields, dropdowns, or some combination of both. This h:panelGroup is bound to a backing bean via binding = "#{userAccountDisplay.searchValuePanel}". The getSearchValuePanel method returns an HtmlPanelGroup

      Currently, when the "pickColumn" dropdown is changed (event="onchange"), the choices in the "pickOperator" dropdown are updated via a4j with new values, which is what I expect. However, when the "pickOperator" dropdown column is changed, the contents of the "pickValue" panelGroup are not updated. Below is the xhtml page I am using, followed by a description of what I've already tried via code changes, debugging, and logging.

       <h:selectOneMenu id="pickColumn"
       value="#{userAccountSearch.searchColumn}">
       <a:support event="onchange" reRender="pickOperator"
       ajaxSingle="true" />
       <s:selectItems value="#{userAttributeDefinitionList}"
       var="userAttributeDefinition"
       label="#{userAttributeDefinition.label}" />
       <s:convertEntity />
       </h:selectOneMenu>
       <h:selectOneMenu id="pickOperator"
       value="#{userAccountSearch.operator}" converter="#{classConverter}">
       <a:support event="onchange" reRender="pickValue"
       ajaxSingle="true" />
       <s:selectItems value="#{userAccountSearch.operators}"
       var="operator"
       label="#{messages[operator.getName()]}" />
       </h:selectOneMenu>
       <h:panelGroup id="pickValue" binding = "#{userAccountDisplay.searchValuePanel}"/>
      


      I have tried removing reRender="pickValue" from the pickOperator element, and instead wrapping the h:panel with <a:outputPanel ajaxRendered="true"> but that didn't help.

      I attached a debugger, and it turns out that the getSearchValuePanel method isn't actually even being called when the pickOperator dropdown is changed.

      When I submitted the form via non ajax methods, everything in the view updated properly--that is, the h:panelGroup displayed the appropriate items for what was contained in the pickOperator dropdown.

      Here is the contents of the log, when the "pickOperator" dropdown is changed: (I've manually broken up some of the long lines, to avoid excess horizontal scrolling )
      debug[16:26:47,603]: Have Event [object Object] with properties: target: [object HTMLSelectElement], srcElement: undefined, type: change
      debug[16:26:47,603]: NEW AJAX REQUEST !!! with form :main
      debug[16:26:47,603]: Append hidden control main with value [main] and value attribute [main]
      debug[16:26:47,603]: Append select-one control main:pickColumn with value [2] and value attribute [null]
      debug[16:26:47,603]: Append select-one control main:pickOperator with value [com.hrworx.formworx.model.query.operator.date.DateIsInTheLast] and value attribute [null]
      debug[16:26:47,603]: Append text control main:j_id32 with value [] and value attribute [null]
      debug[16:26:47,603]: Append hidden control javax.faces.ViewState with value [_id4] and value attribute [_id4]
      debug[16:26:47,603]: Append select-one control main:pickOperator with value [com.hrworx.formworx.model.query.operator.date.DateIsInTheLast] and value attribute [null]
      debug[16:26:47,603]: parameter main:j_id10 with value main:j_id10
      debug[16:26:47,603]: Start XmlHttpRequest
      debug[16:26:47,603]: Reqest state : 1
      debug[16:26:47,603]: QueryString: AJAXREQUEST=_viewRoot&main=main&main%3ApickColumn=2&main%3ApickOperator=com.hrworx.formworx.model.query.operator.date.DateIsInTheLast
      &main%3ApickOperator=com.hrworx.formworx.model.query.operator.date.DateIsInTheLast&main%3Aj_id32=&javax.faces.ViewState=_id4
      &main%3Aj_id10=main%3Aj_id10&
      debug[16:26:47,603]: Reqest state : 1
      debug[16:26:47,783]: Reqest state : 2
      debug[16:26:47,783]: Reqest state : 3
      debug[16:26:47,783]: Reqest state : 4
      debug[16:26:47,783]: Reqest end with state 4
      debug[16:26:47,783]: Response with content-type: text/xml;charset=UTF-8
      debug[16:26:47,783]: Full response content: <?xml version="1.0"?> <html xmlns="http://www.w3.org/1999/xhtml"><head><title></title>
      <script type="text/javascript" src="/formworx-war/a4j_3_1_0-SNAPSHOTorg.ajax4jsf.javascript.AjaxScript"> </script></head>
      <body><span id="main:pickValue"><input type="text" name="main:j_id32" value="" /></span><meta name="Ajax-Update-Ids" content="main:pickValue" />
      <span id="ajax-view-state"><input type="hidden" name="javax.faces.ViewState" id="javax.faces.ViewState" value="_id4" /></span>
      <meta id="Ajax-Response" name="Ajax-Response" content="true" /></body></html>
      debug[16:26:47,783]: Header Ajax-Expired not found, search in <meta>
      debug[16:26:47,783]: search for elements by name 'meta' in element #document
      debug[16:26:47,783]: getElementsByTagName found 2
      debug[16:26:47,783]: Find <meta name='Ajax-Update-Ids' content='main:pickValue'>
      debug[16:26:47,783]: Find <meta name='Ajax-Response' content='true'>
      debug[16:26:47,783]: Header Ajax-Update-Ids not found, search in <meta>
      debug[16:26:47,783]: search for elements by name 'meta' in element #document
      debug[16:26:47,783]: getElementsByTagName found 2
      debug[16:26:47,783]: Find <meta name='Ajax-Update-Ids' content='main:pickValue'>
      debug[16:26:47,783]: Update page by list of rendered areas from response main:pickValue
      debug[16:26:47,783]: search for elements by name 'script' in element #document
      debug[16:26:47,783]: getElementsByTagName found 1
      debug[16:26:47,793]: <script> in response with src=/formworx-war/a4j_3_1_0-SNAPSHOTorg.ajax4jsf.javascript.AjaxScript
      debug[16:26:47,793]: Such element exist in document
      debug[16:26:47,793]: search for elements by name 'link' in element #document
      debug[16:26:47,793]: getElementsByTagName found 0
      debug[16:26:47,793]: Attempt to update part of page for Id: main:pickValue
      debug[16:26:47,793]: call getElementById for id= main:pickValue
      debug[16:26:47,793]: Replace content of node by replaceChild()
      debug[16:26:47,793]: search for elements by name 'script' in element span
      debug[16:26:47,793]: getElementsByTagName found 0
      debug[16:26:47,793]: Scripts in updated part count : 0
      debug[16:26:47,793]: Update part of page for Id: main:pickValue successful
      debug[16:26:47,793]: call getElementById for id= ajax-view-state
      debug[16:26:47,793]: Hidden JSF state fields: [object HTMLSpanElement]
      debug[16:26:47,793]: Namespace for hidden view-state input fields is undefined
      debug[16:26:47,793]: search for elements by name 'input' in element span
      debug[16:26:47,793]: getElementsByTagName found 1
      debug[16:26:47,793]: Replace value for inputs: 3 by new values: 1
      debug[16:26:47,793]: Input in response: javax.faces.ViewState
      debug[16:26:47,793]: Found same input on page with type: hidden
      debug[16:26:47,803]: search for elements by name 'INPUT' in element span
      debug[16:26:47,803]: getElementsByTagName found 0
      debug[16:26:47,803]: Replace value for inputs: 3 by new values: 0
      debug[16:26:47,803]: call getElementById for id= _A4J.AJAX.focus
      debug[16:26:47,803]: No focus information in response
      debug[16:26:47,803]: call getElementById for id= org.ajax4jsf.oncomplete
      


      Any suggestions regarding how I can update the panelGroup via ajax would be appreciated.

        • 1. Re: reRendering a bound h:panelGroup
          Sergey Smirnov Master

          wrap each selectOneMenu with own a4j:region

          • 4. Re: reRendering a bound h:panelGroup
            davidintx Newbie

            Thanks for the suggestion. Unfortunately, I am still seeing the same behavior. I compared the a4j log from before and after adding the region tag around each selectOneMenu, and it was the same. Attaching a debugger showed that the getSearchValuePanel method was still not getting called after the second selectOneMenu changed.

            Oddly enough, one thing did change for the better after adding the region tags. Previously, when I updated the first selectOneMenu, the choices in the second selectOneMenu updated, as I wanted them to. However, in the backing bean, when the first selectOneMenu value changes, I also set the value of the second selectOneMenu to a specific default value from among the new options. This value is now being properly reflected as the initial choice of the second selectOneMenu, whereas it wasn't before.

            However, the original problem, of the panelGroup not updating, remains.

            • 5. Re: reRendering a bound h:panelGroup
              Sergey Smirnov Master

              Hmm, it looks like you use request scope bean and want the properties set in one request to be available at the next request. However, it is impossible by definition.

              Try to analyze the situation by yourself. You have an ajaxSingle="true". So, did not presume that the other fields will come with the request parameters map. How are you going to relay on the property set in the previous request if that bean is already gone with the end of the request cycle?

              • 6. Re: reRendering a bound h:panelGroup
                davidintx Newbie

                Thanks for the suggestion. These are seam conversation scoped components, so they should still be present. To verify, I ran under a debugger and looked at the jvm id of the userAccountSearch and userAccountDisplay instances, and they remained the same between invocations. I removed ajaxSingle="true" anyway, but I still got the same behavior.

                I tried something that seems to me to indicate that the problem is specifically with the reRendering of the panelGroup. I added a new method, getStyleClass, to the UserAccountDisplay class, and then I reference that method in both an outputText and as the styleClass of the panelGroup. Specifically:

                 <h:outputText id="valueText" value="#{userAccountDisplay.styleClass}"/>
                 <h:panelGroup id="pickValue" binding = "#{userAccountDisplay.searchValuePanel}" styleClass="#{userAccountDisplay.styleClass}" />
                

                I have reRender="valueText,pickValue" in the second selectOneMenu. When this second selectOneMenu changes, the valueText component is reRendered properly, but the panelGroup is still not reRendered. Specifically, the value of the outputText changes, but the styleClass of the panelGroup does not change, even though both are reading from the same backing bean.

                Any additional advice you might have or suggestions for things I might try would be appreciated.

                • 7. Re: reRendering a bound h:panelGroup
                  davidintx Newbie

                  I did some more experimenting. The issue seems to be with binding, not with h:panelGroup. I changed the h:outputText so that it was bound to an HtmlOutputText in an underlying bean, and changed the h:panelGroup so that it was not. Calling reRender on both items caused only the panel group to be updated, not the outputText. Specifically, I changed the xhtml fragment to

                  <h:outputText binding="#{userAccountDisplay.outputTextTest}" id="valueText" />
                  <h:panelGroup id="pickValue" styleClass="#{userAccountDisplay.styleClass}" >
                   <h:outputText value="#{userAccountDisplay.styleClass}"/>
                  </h:panelGroup>
                  


                  Is there something special I need to do to cause bound components to be reRendered properly?




                  • 8. Re: reRendering a bound h:panelGroup
                    Sergey Smirnov Master

                    What you have in the userAccountDisplay?

                    • 9. Re: reRendering a bound h:panelGroup
                      davidintx Newbie

                      I discovered a workaround, which I put in place in UserAccountDisplay. Basically, while the getter corresponding to a binding was not being called (getSearchValuePanel or getOutputTestText), the setter was being called each time. I modified the object passed in via the setter, and that modification was visible in the page. You can see this in place in the code below.

                      I don't really like doing it this way, as it differs from what I need to do when the form elements are submitted in a non-ajax way, and the whole idea of modifying the method parameters passed in a setter seems bad. Nonetheless, I started to implement it, since it works. Then, I ran straight into http://jira.jboss.com/jira/browse/JBSEAM-1753. I started to work around that bug as well, by using things like

                      UserAccountSearch userAccountSearch = (UserAccountSearch)Component.getInstance("userAccountSearch");
                      

                      instead of
                      @In
                      UserAccountSearch userAccountSearch
                      


                      but it turns out that I would need to do that not only in UserAccountDisplay for all my dependencies, but also in UserAccountSearch, and everything called by those classes, all the way back to the database, negating the use of @In pretty much everywhere.

                      So, for now, I'm just going to watch that seam bug, and when it gets fixed, I'll pick up the ajax stuff again.

                      Here is the UserAccountDisplay class. Package and imports removed for brevity.
                      
                      @Name("userAccountDisplay")
                      public class UserAccountDisplay
                      {
                      
                       @In(create = true)
                       private InputsConverter inputsConverter;
                      
                       @Logger
                       Log log;
                      
                      
                       public HtmlOutputText getOutputTextTest()
                       {
                       HtmlOutputText htmlOutputText = new HtmlOutputText();
                       htmlOutputText.setValue(getStyleClass());
                       return htmlOutputText;
                       }
                      
                       public void setOutputTextTest(final HtmlOutputText output)
                       {
                       log.debug("setOutputTextTest called");
                       output.setValue("changed in the setter");
                       }
                      
                      
                      
                       public HtmlPanelGroup getSearchValuePanel()
                       {
                       UserAccountSearch userAccountSearch = (UserAccountSearch)Component.getInstance("userAccountSearch");
                       Class<? extends Operator> clazz = userAccountSearch.getOperator();
                       Operator operator = null;
                      
                       try
                       {
                       Constructor<? extends Operator> constructor = clazz.getConstructor();
                       operator = constructor.newInstance();
                       }
                       catch (RuntimeException re)
                       {
                       log.error("RuntimeException while creating a new instance of operator", re);
                       }
                       catch (Exception e)
                       {
                       log.error("non runtime exception while creating a new instance of operator", e);
                       }
                       // not catching null exception--we want a null exception to be thrown if
                       // for some reason the operator instance is not instantiated.
                       List<Input> inputs = operator.getInputs();
                       HtmlPanelGroup panelGroup = inputsConverter.convertInputsToHtml(inputs);
                       panelGroup.setId("pickValue");
                       return panelGroup;
                       }
                      
                       /**
                       * @param panelGroup
                       */
                       public void setSearchValuePanel(final HtmlPanelGroup panelGroup)
                       {
                       panelGroup.getChildren().clear();
                       HtmlPanelGroup newPanelGroup = getSearchValuePanel();
                       panelGroup.getChildren().addAll(newPanelGroup.getChildren());
                       log.debug("set search value panel called");
                      
                       }
                      
                       public String getStyleClass()
                       {
                       UserAccountSearch userAccountSearch = (UserAccountSearch)Component.getInstance("userAccountSearch");
                       if (userAccountSearch.getSearchColumn().getType().equals(UserAttributeType.DATE))
                       {
                       return "dateValue";
                       }
                       else
                       {
                       return "stringValue";
                       }
                       }
                      
                       public void setStyleClass(final String style)
                       {
                       log.debug("set style called");
                       }
                      
                      }
                      


                      • 10. Re: reRendering a bound h:panelGroup
                        Alexander Smirnov Master

                        Really, JSF 'binding' feature can't work with beans scope other then "request".
                        I'm not fully understand you code, but you can have conflict by restore/create components instances in code.
                        Can you test this code with ordinary form submit instead of ajax ?

                        • 11. Re: reRendering a bound h:panelGroup
                          davidintx Newbie

                          Yes, it works fine with a regular html submit. With a regular html submit, the getter methods associated with the binding (getSearchValuePanel, getOutputTestText) do get called each time.

                          • 12. Re: reRendering a bound h:panelGroup
                            Alexander Smirnov Master

                            This is implementation-specific behavior.
                            In common case, JSF must restore component on RESTORE_VIEW phase, and call setXXX bean method ( if you bean is shared between requests, you can get wrong reference to a component instance ). No getXXX method is called, JSF use only view tree to a component processing.
                            On render View phase, JSF must prevent re-creating of the components, already placed in the wiev tree. So, getXXX method call is not nessesary.