1 2 Previous Next 15 Replies Latest reply on Mar 23, 2009 9:53 AM by lrp

    Attaching parameters to be passed when getters and setters a

    lrp

      Hey there,

      I just started using richfaces for our most recent development project, and I've been very pleased with it so far. However, I've run into a tiny snag that I'm hoping someone might know a solution for. I'm using 3.3.0 GA, as well as the most recent version of facelets.

      In a given view I present out some data about the currently selected element. The thing about the elements I'm presenting is that they can have an arbitrary number of documentation elements attached to them. In other words, I have to generate a variable number of presentation elements for them. The problem arises when assigning value expressions to each of these presentation elements. In reality a documentation element is composed of two comboboxes and an inputtextarea, but I'll simplify it down to the inputtextarea. I have the following method which generates it:

       public void addInputTextarea(UIComponent container, String dokIndex, String valueExpression, String event, String text) {
       FacesContext ctx = FacesContext.getCurrentInstance();
       Application app = ctx.getApplication();
       HtmlInputTextarea textarea = (HtmlInputTextarea) app.createComponent(HtmlInputTextarea.COMPONENT_TYPE);
       textarea.setValueExpression("value", app.getExpressionFactory().createValueExpression(ctx.getELContext(), valueExpression, String.class));
      
       HtmlAjaxSupport ajaxSupport = (HtmlAjaxSupport) app.createComponent(HtmlAjaxSupport.COMPONENT_TYPE);
       ajaxSupport.setEvent(event);
       HtmlActionParameter dokIndexParameter = (HtmlActionParameter) app.createComponent(HtmlActionParameter.COMPONENT_TYPE);
       dokIndexParameter.setName("dokIndex");
       dokIndexParameter.setValue(dokIndex);
       ajaxSupport.getChildren().add(dokIndexParameter);
      
       textarea.getChildren().add(ajaxSupport);
       container.getChildren().add(textarea);
       }
      


      This method is called an arbitrary number of times as such:

      addInputTextarea(tekstGrid, "" + i, "#{skjemaBean.skjemaDocumentationText}", "onkeyup", tekst.getTekst());
      


      i here is the index of the documentation element in the underlying data model element. Since there is an arbitrary number of documentation elements, I was hoping to pass this index to the getSkjemaDocumentationText and setSkjemaDocumentationText methods through value binding, and let them handle the mapping to the data model. That's why I added the following part to the method that creates the text area:

      HtmlActionParameter dokIndexParameter = (HtmlActionParameter) app.createComponent(HtmlActionParameter.COMPONENT_TYPE);
       dokIndexParameter.setName("dokIndex");
       dokIndexParameter.setValue(dokIndex);
       ajaxSupport.getChildren().add(dokIndexParameter);
      


      In the getter and setter I then try to fetch this value using

      Map reqMap = FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap();
       String dokIndex = reqMap.get("dokIndex").toString();
      


      However, this ends up with a NullPointerException, indicating that the parameter wasn't passed. I've also tried attaching the parameter to the HtmlInputTextarea element directly, with the same result.

      So basically, my question would be: Is there any way to pass parameters to the getter and setter function called as a result of assigning a value expression binding to a UIComponent?

        • 1. Re: Attaching parameters to be passed when getters and sette
          nbelaevski

          Can you please check generated HTML code and post contents of "onkeyup" attribute of one of textarea components?

          • 2. Re: Attaching parameters to be passed when getters and sette
            lrp

            Thank you very much for the prompt response. Here is the onkeyup event of the first textarea in the generated html:

            onkeyup="A4J.AJAX.Submit('_viewRoot','innhold_tab_panel_dok_tab_dokumentasjonForm',event,{'parameters':{'dokIndex':'0','innhold_tab_panel_dok_tab_dokumentasjonForm:j_id14':'innhold_tab_panel_dok_tab_dokumentasjonForm:j_id14'} ,'actionUrl':'/seres/skjemautforsker/pages/main.jsf','similarityGroupingId':'innhold_tab_panel_dok_tab_dokumentasjonForm:j_id14'} )"

            It looks like dokIndex is set to 0, which is the correct index for the data model. The other onkeyup events also have the correct indexes. I checked a little further, and it seems that the setter method actually receives the parameter, but the getter method does not, thus resulting in the NullPointerException being thrown when calling it to get the initial value of the textarea.

            • 3. Re: Attaching parameters to be passed when getters and sette
              ilya_shaikovsky

              getters called many times through the lifecycle phases(while decoding, validation, rendering...) so seems in your case parameter just still not set before it happens first time. check in the debugger on which phase you get the NPE.

              • 4. Re: Attaching parameters to be passed when getters and sette
                lrp

                Hmm, I see. Unfortunately I get rerouted to my custom error page (where the error is displayed and logged) when the NullPointerException is thrown, so the a4j log (that's what you meant right?) is no longer visible.

                I tried returning a dummy value if the passed parameter wasn't there, just to see if this would be updated if I changed it. When changing this dummy value in the textarea, the underlying data model is also changed to reflect these changes, but on rerender the textarea content is changed to show the dummy value again, which indicates that the parameter isn't transferred neither on initial component creation nor when rerendering.

                Is there any way I can make sure the parameter is set immediately?

                • 5. Re: Attaching parameters to be passed when getters and sette
                  lrp

                  I've been experimenting a bit further with this and discovered something new.

                  After the page has rendered and the dummy values have been fetched (due to the parameter not being found during initial rendering), I change the values in the fields. When I do this, the setter method is called and the underlying data model is changed. For some reason, when calling the setter the getter is also called (I'm logging all calls for debugging purposes), and this time it finds the transmitted parameter.

                  So I tried navigating to another element and back (all rerendering is done via ajax, the whole page is never reloaded), and now it's back to displaying the dummy values again.

                  So it would seem that, as you said, the parameter simply doesn't exist when the component is being rendered / rerendered. I have to admit that this is a bit confusing to me. Is it impossible to transfer parameters to use during the initial value extraction / rendering, or am I just doing it wrong? I can't imagine that it should be impossible to have a variable number of components allowing data input on a page.

                  • 6. Re: Attaching parameters to be passed when getters and sette
                    ilya_shaikovsky

                    Please create a complete sample for me(page and bean) which I could just add to the application and check and send by email. Then I'll post the results after investigation there.

                    • 7. Re: Attaching parameters to be passed when getters and sette
                      lrp

                      Thank you, that's very kind of you.

                      I've made a minimalistic sample to illustrate the problem.

                      Page:

                      <?xml version="1.0" encoding="ISO-8859-1" ?>
                      <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
                       "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
                      <ui:composition xmlns="http://www.w3.org/1999/xhtml"
                       xmlns:ui="http://java.sun.com/jsf/facelets"
                       xmlns:f="http://java.sun.com/jsf/core"
                       xmlns:h="http://java.sun.com/jsf/html"
                       xmlns:a4j="http://richfaces.org/a4j"
                       xmlns:rich="http://richfaces.org/rich" >
                       <h:outputText id="headerTitle" value="Sample page" />
                       <rich:panel id="contentPanel" binding="#{sampleBean.contentPanel}" header="Sample content" />
                       <a4j:log popup="false" level="ALL" style="width: 800px; height: 300px;"></a4j:log>
                      </ui:composition>
                      


                      Backing bean:

                      package no.brreg.seres.skjemautforsker.beans;
                      
                      import java.util.ArrayList;
                      import java.util.Map;
                      
                      import javax.faces.application.Application;
                      import javax.faces.component.UIComponent;
                      import javax.faces.component.html.HtmlPanelGrid;
                      import javax.faces.context.FacesContext;
                      
                      import org.ajax4jsf.component.html.AjaxForm;
                      import org.ajax4jsf.component.html.HtmlActionParameter;
                      import org.ajax4jsf.component.html.HtmlAjaxSupport;
                      import org.richfaces.component.html.HtmlInputTextarea;
                      import org.richfaces.component.html.HtmlPanel;
                      
                      public class SampleBean {
                       private HtmlPanel contentPanel;
                       private ArrayList<String> sampleList; // In reality, documentation objects are data transfer objects with several fields, but I'm simplifying to string for the sake of the example.
                      
                       public SampleBean() {
                       sampleList = new ArrayList<String>();
                       sampleList.add("Value at index 0");
                       sampleList.add("Value at index 1");
                       sampleList.add("Value at index 2");
                       sampleList.add("Value at index 3");
                       sampleList.add("Value at index 4");
                       }
                      
                       public HtmlPanel getContentPanel() {
                       if (contentPanel == null) {
                       FacesContext ctx = FacesContext.getCurrentInstance();
                       Application app = ctx.getApplication();
                       contentPanel = (HtmlPanel) app.createComponent(HtmlPanel.COMPONENT_TYPE);
                       HtmlPanelGrid grid = (HtmlPanelGrid) app.createComponent(HtmlPanelGrid.COMPONENT_TYPE);
                       grid.setId("sampleGrid");
                       grid.setColumns(2);
                       for (int i = 0; i < sampleList.size(); i++) {
                       AjaxForm form = (AjaxForm) app.createComponent(AjaxForm.COMPONENT_TYPE);
                       form.setId(grid.getId() + "_form" + i);
                       form.setAjaxSubmit(true);
                       addInputTextarea(form, "" + i, "#{sampleBean.skjemaDocumentationText}", "onkeyup");
                       grid.getChildren().add(form);
                       }
                       contentPanel.getChildren().add(grid);
                       }
                       return contentPanel;
                       }
                      
                       public void setContentPanel(HtmlPanel contentPanel) {
                       this.contentPanel = contentPanel;
                       }
                      
                       public void addInputTextarea(UIComponent container, String dokIndex, String valueExpression, String event) {
                       FacesContext ctx = FacesContext.getCurrentInstance();
                       Application app = ctx.getApplication();
                       HtmlInputTextarea textarea = (HtmlInputTextarea) app.createComponent(HtmlInputTextarea.COMPONENT_TYPE);
                       textarea.setValueExpression("value", app.getExpressionFactory().createValueExpression(ctx.getELContext(), valueExpression, String.class));
                      
                       HtmlAjaxSupport ajaxSupport = (HtmlAjaxSupport) app.createComponent(HtmlAjaxSupport.COMPONENT_TYPE);
                       ajaxSupport.setEvent(event);
                       HtmlActionParameter dokIndexParameter = (HtmlActionParameter) app.createComponent(HtmlActionParameter.COMPONENT_TYPE);
                       dokIndexParameter.setName("dokIndex");
                       dokIndexParameter.setValue(dokIndex);
                       ajaxSupport.getChildren().add(dokIndexParameter);
                      
                       textarea.getChildren().add(ajaxSupport);
                       container.getChildren().add(textarea);
                       }
                      
                       public String getSkjemaDocumentationText() throws Exception {
                       Map reqMap = FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap();
                       Object o = reqMap.get("dokIndex");
                       if (o != null) {
                       int dokIndex = Integer.parseInt(o.toString());
                       return sampleList.get(dokIndex);
                       } else {
                       return "Error during data access";
                       }
                       }
                      
                       public void setSkjemaDocumentationText(String text) throws Exception {
                       Map reqMap = FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap();
                       int dokIndex = Integer.parseInt(reqMap.get("dokIndex").toString());
                       sampleList.set(dokIndex, text);
                       }
                      }
                      


                      I have logged calls to the setter and getter methods to be certain that the problem is reproduced (these calls have been removed before posting here). Upon rendering of the page, the dokIndex parameter is not found in the getter, and "Error during data access" is returned. When changing data, the parameter IS found in the setter method (confirmed with logging). During the updates, the getter method is also called for some reason, and now it finds the parameter (also confirmed with logging). However, when I reload the page again log calls in all five getter methods show that the parameter is not found.

                      If you know some way for me to accomplish this, I'd be very grateful. Been scratching my head over it all day. Thanks in advance.

                      • 8. Re: Attaching parameters to be passed when getters and sette
                        lrp

                        Bump. Did you get a chance to look at this?

                        • 9. Re: Attaching parameters to be passed when getters and sette
                          ilya_shaikovsky

                          The parameter not found after refresh because new view is built at render response and the requestMap is empty. And the new value not fetched on submit because of http://ishabalov.blogspot.com/2007/08/sad-story-about-uiinput.html.

                          In general in JSF world (and not only) we highly not recommend you to use getter and setter for some logic. New values and other business logic should be performed from your actions/listeners at fifth phase.

                          • 10. Re: Attaching parameters to be passed when getters and sette
                            lrp

                            Hmm, I see. It's unfortunate, as it seemed like the most elegant way of doing it.

                            If it won't take too much time, could you perhaps give a concrete example of how you would modify my given sample to make it work?

                            • 11. Re: Attaching parameters to be passed when getters and sette
                              lrp

                              Well, I experimented a bit with your suggestion to use an actionlistener. It's not quite so elegant as I would have liked it to be, but it does seem to be working now. The code is as follows:

                              Input text area creation:

                               public void addInputTextarea(UIComponent container, String dokIndex, String actionExpression, String event, String text) {
                               FacesContext ctx = FacesContext.getCurrentInstance();
                               Application app = ctx.getApplication();
                               HtmlInputTextarea textarea = (HtmlInputTextarea) app.createComponent(HtmlInputTextarea.COMPONENT_TYPE);
                               textarea.setValue(text);
                              
                               HtmlAjaxSupport ajaxSupport = (HtmlAjaxSupport) app.createComponent(HtmlAjaxSupport.COMPONENT_TYPE);
                               ajaxSupport.setEvent(event);
                               Class[] params = {ActionEvent.class};
                               MethodBinding binding = app.createMethodBinding(actionExpression, params);
                               ajaxSupport.setActionListener(binding);
                              
                               HtmlActionParameter dokIndexParameter = (HtmlActionParameter) app.createComponent(HtmlActionParameter.COMPONENT_TYPE);
                               dokIndexParameter.setName("dokIndex");
                               dokIndexParameter.setValue(dokIndex);
                               ajaxSupport.getChildren().add(dokIndexParameter);
                              
                               textarea.getChildren().add(ajaxSupport);
                               container.getChildren().add(textarea);
                               }
                              


                              Action listener:

                               public void skjemaDocumentationChangedAction(ActionEvent event) {
                               FacesContext ctx = FacesContext.getCurrentInstance();
                               Application app = ctx.getApplication();
                               Map reqMap = FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap();
                               HtmlInputTextarea textarea = (HtmlInputTextarea) event.getComponent().getParent();
                               String text = textarea.getValue().toString();
                               String dokIndex = reqMap.get("dokIndex").toString();
                               int index = Integer.parseInt(dokIndex);
                               DokumentasjonsVO dok = currentSkjema.getDokumentasjon().get(index);
                               if (dok instanceof TekstVO) {
                               TekstVO tekst = (TekstVO) dok;
                               tekst.setTekst(text);
                               }
                               }
                              


                              My only remaining concern is the fact that MethodBinding and setActionListener() seem to be deprecated. I tried doing something similar with MethodExpression and setActionExpression(), but got "java.lang.IllegalArgumentException: wrong number of arguments". Is there any way to gain access to the ActionEvent object when using MethodExpression / setActionExpression instead of MethodBinding / setActionListener?

                              If not, I guess I'll just have to live with using deprecated methods.

                              • 12. Re: Attaching parameters to be passed when getters and sette
                                ilya_shaikovsky

                                 

                                FacesContext facesCtx = FacesContext.getCurrentInstance();
                                 Application app = facesCtx.getApplication();
                                 ExpressionFactory elFactory = app.getExpressionFactory();
                                 ELContext elContext = facesCtx.getELContext();
                                elFactory.createMethodExpression(elContext, name, null, argtypes);
                                

                                should be used to create methodExpression.

                                • 13. Re: Attaching parameters to be passed when getters and sette
                                  lrp

                                  That's pretty much what I tried:

                                  Class[] params = {ActionEvent.class};
                                   MethodExpression actionExpression = app.getExpressionFactory().createMethodExpression(ctx.getELContext(), actionExpressionString, null, params);
                                   ajaxSupport.setActionExpression(actionExpression);
                                  


                                  That's when I get

                                  java.lang.IllegalArgumentException: wrong number of arguments
                                   at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
                                   at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
                                   at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
                                   at java.lang.reflect.Method.invoke(Method.java:585)
                                   at com.sun.el.parser.AstValue.invoke(AstValue.java:157)
                                   at com.sun.el.MethodExpressionImpl.invoke(MethodExpressionImpl.java:283)
                                   at javax.faces.component.MethodBindingMethodExpressionAdapter.invoke(MethodBindingMethodExpressionAdapter.java:88)
                                   at com.sun.faces.application.ActionListenerImpl.processAction(ActionListenerImpl.java:102)
                                   at javax.faces.component.UICommand.broadcast(UICommand.java:387)
                                   at org.ajax4jsf.component.AjaxActionComponent.broadcast(AjaxActionComponent.java:55)
                                   at org.ajax4jsf.component.AjaxViewRoot.processEvents(AjaxViewRoot.java:321)
                                   at org.ajax4jsf.component.AjaxViewRoot.broadcastEvents(AjaxViewRoot.java:296)
                                   at org.ajax4jsf.component.AjaxViewRoot.processPhase(AjaxViewRoot.java:253)
                                   at org.ajax4jsf.component.AjaxViewRoot.processApplication(AjaxViewRoot.java:466)
                                   at com.sun.faces.lifecycle.InvokeApplicationPhase.execute(InvokeApplicationPhase.java:82)
                                   at com.sun.faces.lifecycle.Phase.doPhase(Phase.java:100)
                                   at com.sun.faces.lifecycle.LifecycleImpl.execute(LifecycleImpl.java:118)
                                   at javax.faces.webapp.FacesServlet.service(FacesServlet.java:265)
                                   at
                                  


                                  When trying to change the values in the textarea that the action listener is assigned to.

                                  • 14. Re: Attaching parameters to be passed when getters and sette
                                    nbelaevski

                                    Action expression doesn't accept ActionEvent as argument.

                                    1 2 Previous Next