3 Replies Latest reply on Nov 16, 2008 9:02 AM by Nick Belaevski

    Problems with dynamically created fields, value expressions

    Andrew Hart Newbie

      I have a multiple page modal wizard. On the third modal screen of this wizard, the user is allowed to enter a URL for a WMS (Web Mapping Service) service and click a button. This button invokes a backing bean action that "discovers" the capabilities of the service and then dynamically creates a tab in a tab panel that displays the name of the service with a delete button and a list of map layers preceded by check boxes that allow the user to select layers for inclusion in a planning scenario.

      My problem is that the dynamically created components including the delete button and the check boxes don't seem to work properly the FIRST time the tab is dynamically rendered. If the user backs up in the wizard and then goes back in, they then "work", i.e., the delete button invokes the delete method it's supposed to, and the check boxes pass their values to the appropriate boolean instance variables in my server side objects.

      The weird thing is that I render the dynamic tab the same way when they user enters the tab, or when they enter a value and click the discovery button: I whack all of the tabs in the tab panel and build them all from scratch.

      I've used a phase listener to dump the component tree and it looks right. I've also used watched the post coming back and I see the appropriate post request parameters heading back to the server. I *suspect* some sort of lifecycle problem, like the first time the user gets the dynamic tab rendered we're not invoking the update phase or something.

      So, does this ring a bell with anyone? We're using Richfaces with SEAM.

      Here's some code snippets:

      The page with the bound panelGrid component:

       <a4j:form id="scenarioSelectServersForm">
       <a4j:outputPanel layout="block" id="scenarioSelectServers" style="height: auto; width: 620px;">
      
       <ui:include src="/views/messages.xhtml"/>
      
       <s:validateAll>
      
       <h:panelGrid cellpadding="0" cellspacing="0" style="width:100%;">
       <div class="label"><h:outputText value="Please select Scenario GIS Servers:"/></div>
       <rich:spacer height="2" />
       <rich:separator height="2" lineType="solid" />
       <rich:spacer height="2" />
      
       <h:panelGrid id="panelServerSelection" columns="3">
       <h:panelGroup>
       <div class="entry">
       <div class="label"><h:outputLabel for="serverUrl" value="WMS Service URL:"/></div>
       <div class="input">
       <h:inputText id="serverUrl" size="100" value="#{scenarioManager.newServerURL}" required="false">
       <f:attribute name="errorLabel" value="ServerUrl" />
       </h:inputText>
       </div>
       </div>
       </h:panelGroup>
       <h:selectOneMenu id="serviceType" value="#{scenarioManager.newServiceType}">
       <f:selectItem itemValue="WMS"/>
       <f:selectItem itemValue="GeoRSS" itemDisabled="true" />
       </h:selectOneMenu>
       <a4j:commandButton id="check"
       value="Check Service"
       action="#{scenarioManager.discoverService()}"
       reRender="modalView"
       onclick="this.disabled=true;showProgressBar();"
       oncomplete="this.disabled=false;closeProgressBar();" >
       </a4j:commandButton>
       </h:panelGrid>
      
       <rich:separator height="2" lineType="solid" style="padding: 8px;"></rich:separator>
      
       <h:panelGrid id="tabs" binding="#{uiState.panelGrid}" style="border:1;"/>
      
       <rich:separator height="2" lineType="solid" style="padding: 8px;"></rich:separator>
      
       <h:panelGroup>
       <div class="actionButtons" style="text-align: right;">
      
       <ui:include src="/views/modalWizardNavigation.xhtml" />
      
       <a4j:commandButton id="finishEdit" value="Finish"
       action="#{scenarioManager.persistScenario}"
       rendered="#{scenarioManager.editing}"
       onclick="this.disabled=true;document.run_button=this;"
       oncomplete="document.run_button.disabled=false;modalPanelClose('modalPanel');setWorkspace(#{workspaceTree.workspaceId});refreshMapScenario(false);"
       reRender="toolbarMenu, classif_top, workspaceTree, classif_bottom, statusbar, scenarioPrefsList, scenarioPrefsRevertJS, modalView">
       <a4j:ajaxListener type="jmat.utils.RerenderOnNoErrors" />
       <a4j:actionparam name="initial" assignTo="#{modalManager.modalName}"
       value="initPage" />
       </a4j:commandButton>
       <a4j:commandButton id="finishAdd" value="Finish"
       action="#{scenarioManager.persistScenario}"
       rendered="#{!scenarioManager.editing}"
       onclick="this.disabled=true;document.run_button=this;"
       oncomplete="document.run_button.disabled=false;modalPanelClose('modalPanel');setWorkspace(#{workspaceTree.workspaceId});refreshMapScenario(true);"
       reRender="toolbarMenu, classif_top, workspaceTree, classif_bottom, statusbar, scenarioPrefsList, scenarioPrefsRevertJS, modalView">
       <a4j:ajaxListener type="jmat.utils.RerenderOnNoErrors" />
       <a4j:actionparam name="initial" assignTo="#{modalManager.modalName}"
       value="initPage" />
       </a4j:commandButton>
       <a4j:commandButton id="cancel" value="Cancel"
       action="#{scenarioManager.cancel}" immediate="true"
       onclick="resetForm('scenarioAddEdit');"
       oncomplete="Richfaces.hideModalPanel('modalPanel');"
       style="margin-left: 10px;" reRender="modalView">
       <a4j:actionparam name="initial" assignTo="#{modalManager.modalName}"
       value="initPage" />
       </a4j:commandButton>
       </div>
       </h:panelGroup>
       </h:panelGrid>
      
       </s:validateAll>
      
       </a4j:outputPanel>
       </a4j:form>
      



      These are the relevant methods from the scenarioManager:
      
       /**
       * We call this when we start an add or edit, or when we delete an
       * existing WMSServer in order to rebuild the WMSServer service tab.
       */
       public void initServices() {
       // Clear the services ui component panel
       uiState.initServerTabPanel();
       if (this.editingScenario == null) {
       System.out.println("The editing scenario is null, returning...");
       }
       // We need to set up the panel for all the existing
       // services in the scenario here...
       List<WMSServer> serverList = this.editingScenario.getServerList();
       for (int i=0; i< serverList.size(); i++) {
       WMSServer server = serverList.get(i);
       uiState.addServerTabPanel(i, server);
       }
       } // initServices
      
       /*
       * This List is used to keep track of WMSServer objects that have been
       * deleted from the Scenario.
       */
       private List<WMSServer> deleteServices = new ArrayList<WMSServer>();
      
       /**
       * The user has clicked the Delete button for a particular WMS service,
       * so we need to delete the tab and add the WMS server to a list of
       * deleted services to be finalized when the user persists the scenario.
       *
       * Deletes the WMSServer at the position in editingScenario.serviceList
       * at position serviceIndex in the List. We move them from the editingScenario
       * serviceList to the deleteServices list. We also immediately rebuild the
       * services tab so that the user sees their deletion take immediate effect.
       * Then, when the user finalizes the deletion by persisting the scenario,
       * we iterate through the deleteServices list and actually remove them from
       * the database.
       */
       @Begin (join=true)
       public void deleteService(Integer serviceIndex) {
       int iServiceIndex = serviceIndex.intValue();
       if (this.editingScenario == null) {
       System.out.println("editingScenario is null, returning...");
       return;
       }
       WMSServer deletedService = this.editingScenario.getServerList().remove(iServiceIndex);
       if (deletedService != null)
       if (deletedService.getId() != null)
       this.deleteServices.add(deletedService);
      
       // Now we would need to rebuild the tab panel
       initServices();
       }
      
       /**
       * The user has clicked the discovery button.
       */
       public void discoverService() {
       try {
       WMSServer newServer = WMSUtils.discoverService(newServerURL);
       // If no exception is thrown, then discovery succeeded, so we
       // need to add this new server to the scenario's list of services,
       // add it to the tab panel, and then clear the url field
       // in preparation for adding another...
       newServer.setScenario(this.editingScenario);
       this.editingScenario.getServerList().add(newServer);
      
       // Clear all the existing children of the panelGrid and tabPanel...
       // originally, I thought I could just add each tab as it was discovered,
       // but this never worked. I had to always clear it all out and add
       // them all .
       uiState.initServerTabPanel();
      
       // We need to set up the panel for all the existing
       // services in the scenario here...
       List<WMSServer> serverList = this.editingScenario.getServerList();
       for (int i=0; i< serverList.size(); i++) {
       WMSServer server =serverList.get(i);
       uiState.addServerTabPanel(i, server);
       }
      
       // The discovery succeeded and we have created a tab for the service,
       // so we clear the input field they used to enter the URL.
       this.setNewServerURL("");
      
       } catch (DiscoveryException de) {
       // There was a problem with discovery: they either entered an invalid
       // URL, or there was some other problem with discovering the capabilities
       // of the WMS service. We have not cleared the newServerURL, have not
       // created a new tab, so we display an appropriate message and allow them
       // to correct...
       de.printStackTrace();
       jmatMessageManager.addApplicationMessage(JmatMessage.JmatMessageStyle.JMAT_WARNING,de.getMessage());
       return;
       }
       } // discoverService
      



      And, finally, here is the code that actually creates the dynamic components. Originally, I had this in the ScenarioManager EJB, but that doesn't work with SEAM; To get the page component to bind I had to put the server side object in a plain old session bean, UIState:

       /**
       * Requests that the server tab panel be initialized to an empty state.
       * Creates a brand new tab panel component and adds it to the serverPanelGrid.
       */
       public void initServerTabPanel() {
       System.out.println("initServerTabPanel");
      
       FacesContext context = FacesContext.getCurrentInstance();
       Application application = context.getApplication();
      
       if (this.serverPanelGrid == null) {
       this.serverPanelGrid = (HtmlPanelGrid)application.createComponent(HtmlPanelGrid.COMPONENT_TYPE);
       this.serverPanelGrid.setStyle("margin:0px; padding:0px; width:100%; height:100%;");
       this.serverPanelGrid.setId("tabs");
       } else
       this.serverPanelGrid.getChildren().clear();
      
       if (this.serverTabPanel == null) {
       this.serverTabPanel = (HtmlTabPanel)application.createComponent(HtmlTabPanel.COMPONENT_TYPE);
       this.serverTabPanel.setTitle("WMS Servers");
       this.serverTabPanel.setSwitchType("ajax");
       this.serverTabPanel.setStyle("layout:block; margin:0px; padding:0px; width:600px; height:350px;");
       } else
       this.serverTabPanel.getChildren().clear();
       this.serverPanelGrid.getChildren().add(this.serverTabPanel);
      
       } // initServerTabPanel
      
      
       /**
       * Add a new tab to the server tab panel.
       *
       * @param i
       * @param server
       */
       public void addServerTabPanel(int i, WMSServer server) {
       HtmlTab tab = buildServerTab(i, server);
       this.serverTabPanel.getChildren().add(tab);
       }
      
       /**
       * This function is used to dynamically build the tab panels that appear in the
       * richtab component that is placed in the UiState serverPanelGrid.
       *
       * @param serviceNumber - The number of the service, as it should be rendered in
       * the service tab title. This is also equal to the index of the
       * service in the scenario.
       * @param server - The WMSServer object describing the service.
       * @return - An HtmlTab UI component.
       */
       public HtmlTab buildServerTab(int serviceNumber, WMSServer server) {
       System.out.println("UiState.buildServerTab");
      
       FacesContext context = FacesContext.getCurrentInstance();
       Application application = context.getApplication();
      
       String tabTitle = "Server # " + (serviceNumber + 1);
       // create the new tab component
       HtmlTab tab = (HtmlTab)application.createComponent(HtmlTab.COMPONENT_TYPE);
       tab.setLabel(tabTitle);
       tab.setStyle("margin:0px; padding:0px; width:100%; height:100%;");
      
       // put an outermost panelGrid in it to establish a scrolling pane
       HtmlPanelGrid titlePanelGrid = (HtmlPanelGrid)application.createComponent(HtmlPanelGrid.COMPONENT_TYPE); //new HtmlPanelGrid();
       titlePanelGrid.setStyle("layout:block; margin:0px; padding:0px; width:100%; height:50px;");
       titlePanelGrid.setColumns(2);
      
       // Create the server name and add it to panel
       HtmlOutputText serverNameText = new HtmlOutputText();
       serverNameText.setValue(server.getTitle());
       titlePanelGrid.getChildren().add(serverNameText);
      
       // Create a button to allow the user to delete a server
       HtmlAjaxCommandButton deleteButton = new HtmlAjaxCommandButton();
       deleteButton.setTitle("Delete");
       deleteButton.setValue("Delete");
       String methodExpression = "#{scenarioManager.deleteService(" + (serviceNumber) + ")}";
       Class[] argTypes = {Integer.class};
       MethodExpression deleteAction = context.getApplication().getExpressionFactory().createMethodExpression(context.getELContext(),methodExpression,null,argTypes);
       deleteButton.setOnclick("this.disabled=true;");
       deleteButton.setOncomplete("this.disabled=false;");
       deleteButton.setActionExpression(deleteAction);
       deleteButton.setReRender("modalView");
       //deleteButton.setImmediate(true);
       titlePanelGrid.getChildren().add(deleteButton);
      
       // Put a separator in between the server name and the list of layers...
       HtmlSeparator separator = new HtmlSeparator();
       titlePanelGrid.getChildren().add(separator);
      
       // put an outermost panelGrid in it to establish a scrolling pane...
       HtmlPanel layerPanel = (HtmlPanel)application.createComponent(HtmlPanel.COMPONENT_TYPE);
       layerPanel.setStyle("layout:block; margin:0px; padding:0px; width:620px; height:300px;");
       layerPanel.setStyleClass("scrollme");
      
       HtmlPanelGrid layerPanelGrid = (HtmlPanelGrid)application.createComponent(HtmlPanelGrid.COMPONENT_TYPE); // new HtmlPanelGrid();
       layerPanelGrid.setBorder(0);
       layerPanelGrid.setColumns(3);
       layerPanelGrid.setColumnClasses("col50,col50,col500");
       layerPanelGrid.setStyle("width:98%;height:100%;");
      
       HtmlOutputText selectLabel = new HtmlOutputText();
       selectLabel.setValue("Select");
       HtmlOutputText showLabel = new HtmlOutputText();
       showLabel.setValue("Show");
       HtmlOutputText layerNameLabel = new HtmlOutputText();
       layerNameLabel.setValue("Layer Name");
      
       layerPanelGrid.getChildren().add(selectLabel);
       layerPanelGrid.getChildren().add(showLabel);
       layerPanelGrid.getChildren().add(layerNameLabel);
      
       // Now, iterate thru the service layers and emit a line for each...
       for (int i=0; i < server.getLayers().size(); i++) {
       // the current layer is indexed by the loop counter...
       WMSLayer layer = server.getLayers().get(i);
      
       // Build the boolean check box that allows the user to select the layer...
       HtmlSelectBooleanCheckbox selectLayerBox = (HtmlSelectBooleanCheckbox)application.createComponent(HtmlSelectBooleanCheckbox.COMPONENT_TYPE);
       String selectLayerBoxId = "s" + (serviceNumber) + "l" + i + "Selected";
       selectLayerBox.setId(selectLayerBoxId);
       // build the value EL expression that ties the checkbox value back to the layer selected Boolean attribute...
       String valueExpression = "#{scenarioManager.editingScenario.serverList[" + serviceNumber + "].layers[" + i + "].selected}";
       ValueExpression ve = context.getApplication().getExpressionFactory().createValueExpression(context.getELContext(),valueExpression,Boolean.class);
       selectLayerBox.setValueExpression("value", ve);
       //selectLayerBox.setImmediate(true);
      
       // Now, build the boolean check box that allow the user to mark a selected layer as visible...
       HtmlSelectBooleanCheckbox showLayerBox = (HtmlSelectBooleanCheckbox)application.createComponent(HtmlSelectBooleanCheckbox.COMPONENT_TYPE);
       String showLayerBoxId = "s" + (serviceNumber) + "l" + i + "Show";
       showLayerBox.setId(showLayerBoxId);
       valueExpression = "#{scenarioManager.editingScenario.serverList[" + serviceNumber + "].layers[" + i + "].showLayer}";
       ValueExpression ve2 = context.getApplication().getExpressionFactory().createValueExpression(context.getELContext(),valueExpression,Boolean.class);
       // build the value EL expression that ties the checkbox value back to the layer selected Boolean attribute...
       showLayerBox.setValueExpression("value", ve2);
       showLayerBox.setDisabled(!layer.getSelected()); // the initial disabled status depends on the initial setting of the layer selection
       //showLayerBox.setValue(layer.getShowLayer());
      
       // Set onChange script for selected checkbox to enable/disable the Enabled checkbox when clicked
       selectLayerBox.setOnclick("document.getElementById('" + SERVER_FORM_NAME + showLayerBoxId + "').disabled = (!this.checked);");
      
       // create an output text field showing the layer name...
       HtmlOutputText layerNameText = new HtmlOutputText();
       layerNameText.setValue(layer.getName());
      
       // Finally, put them all in the layer panel grid...
       layerPanelGrid.getChildren().add(selectLayerBox);
       layerPanelGrid.getChildren().add(showLayerBox);
       layerPanelGrid.getChildren().add(layerNameText);
       // ...and, then the layer panel grid into the layer panel.
       layerPanel.getChildren().add(layerPanelGrid);
       }
      
       // Lastly, add the panelGrid to the tab and return...
       tab.getChildren().add(titlePanelGrid);
       tab.getChildren().add(layerPanel);
      
       return tab;
       } // buildServerTab
      


      Any help is greatly appreciated; I've been stuck on this for a while.