4 Replies Latest reply on Oct 8, 2008 9:47 AM by newlukai

    xhtml files in subdirectories

      Hi there,


      I'm implementing an administration interface to manage several entities. For every database table I created two pages: the first on, say languages.xhtml displays all available languages in a datatable. The user can add, edit or delete a language. To add or edit a language another page should be displayed which shows the details of this language in a form. As soon as the user wants to edit a language, a conversation is started which ends if the user cancels or saves this language.


      Nothing new here ;)


      Now I tried to put those two files in a subdirectory since I want to separate the administration interface from the user interface. So I created a subdirectory admin and saved those two files there. Every JSF tag which references a file (like h:graphicImage and so on) now use such an URL: /path/file; it works. Then I read about the basePath and used it for my CSS.


      Here are those two pages:


      languages.xhtml


      <ui:composition
       xmlns="http://www.w3.org/1999/xhtml"
       xmlns:ui="http://java.sun.com/jsf/facelets"
       xmlns:h="http://java.sun.com/jsf/html"
       xmlns:f="http://java.sun.com/jsf/core"
       xmlns:s="http://jboss.com/products/seam/taglib"
       xmlns:a4j="http://richfaces.org/a4j"
       xmlns:rich="http://richfaces.org/rich"
       template="/template.xhtml">
      
      <ui:define name="content">
       <h:form id="languages">
        <s:div styleClass="buttonBar">
         <h:commandLink action="#{adminLanguage.add}" styleClass="button">
          <h:graphicImage url="/gfx/icon_add.png" />
          <h:outputText value="#{msgs.button_add}" />
         </h:commandLink>
        </s:div>
        
        <rich:dataTable
         value="#{languages}"
         var="iterLanguage"
         id="table_languages"
         rows="20"
         styleClass="fullWidth"
         rowClasses="even, odd">
      
         <rich:column sortBy="#{iterLanguage.token}">     
          <f:facet name="header">
           <h:outputText value="#{msgs.label_language_token}" />
          </f:facet>
      
          <h:outputText value="#{iterLanguage.token}" />
         </rich:column>
         
         <rich:column sortBy="#{iterLanguage.isoToken}">    
          <f:facet name="header">
           <h:outputText value="#{msgs.label_language_isoToken}" />
          </f:facet>
      
          <h:outputText value="#{iterLanguage.iso_token}" />
         </rich:column>
      
         <rich:column sortBy="#{iterLanguage.type}">
          <f:facet name="header">
           <h:outputText value="#{msgs.label_language_type}" />
          </f:facet>
      
          <h:outputText value="#{iterLanguage.type}" />
         </rich:column>
         
         <rich:column>     
          <f:facet name="header">
           <h:outputText value="#{msgs.header_actions}"/>
          </f:facet>
      
          <h:panelGroup>
           <h:commandLink action="#{adminLanguage.edit}" styleClass="button">
            <h:graphicImage url="/gfx/icon_edit.png" />
            <h:outputText value="#{msgs.button_edit}" />
           </h:commandLink>
           
           <h:commandLink action="#{adminLanguage.delete}" styleClass="button">
            <h:graphicImage url="/gfx/icon_delete.png" />
            <h:outputText value="#{msgs.button_delete}" />
           </h:commandLink>
          </h:panelGroup>
         </rich:column>
        </rich:dataTable>
      
        <rich:datascroller for="table_languages" />
       </h:form>
      </ui:define>
      </ui:composition>



      language.xhtml


      <ui:composition
       xmlns="http://www.w3.org/1999/xhtml"
       xmlns:ui="http://java.sun.com/jsf/facelets"
       xmlns:h="http://java.sun.com/jsf/html"
       xmlns:f="http://java.sun.com/jsf/core"
       xmlns:s="http://jboss.com/products/seam/taglib"
       xmlns:a4j="http://richfaces.org/a4j"
       xmlns:rich="http://richfaces.org/rich"
       template="/template.xhtml">
      
      <ui:define name="content">
       <h:form id="language">
        <ui:include src="/includes/messages.xhtml" />
        
        <h:panelGrid cellpadding="5" cellspacing="0" columns="2">
         <h:outputText value="#{msgs.label_language_token}:" id="label_language_token" />
         <s:decorate id="decorate_language_token" template="/includes/decorateField.xhtml">
          <h:inputText value="#{language.token}" id="language_token">
           <s:validate />
          </h:inputText>
         </s:decorate>
         
         <h:outputText value="#{msgs.label_language_isoToken}:" id="label_language_isoToken" />
         <s:decorate id="decorate_language_isoToken" template="/includes/decorateField.xhtml">
          <h:inputText value="#{language.token}" id="language_isoToken">
           <s:validate />
          </h:inputText>
         </s:decorate>
         
         <h:outputText value="#{msgs.label_language_type}:" id="label_language_type" />
         <s:decorate id="decorate_language_type" template="/includes/decorateField.xhtml">
          <h:inputText value="#{language.type}" id="language_type">
           <s:validate />
          </h:inputText>
         </s:decorate>
         
         <h:outputText value="#{msgs.label_language_descr}:" id="label_language_descr" />
         <h:inputTextarea value="#{language.descr}" rows="5" cols="50" id="language_descr" />
      
         <h:commandLink action="#{adminLanguage.save}" styleClass="button">
          <h:graphicImage url="/gfx/icon_save.png" />
          <h:outputText value="#{msgs.button_save}" />
         </h:commandLink>
         <h:commandLink action="cancel" immediate="true" styleClass="button">
          <h:graphicImage url="/gfx/icon_cancel.png" />
          <h:outputText value="#{msgs.button_cancel}" />
         </h:commandLink>
        </h:panelGrid>
       </h:form>
      </ui:define>
      </ui:composition>



      And the backing bean:


      @Stateful
      @Name("adminLanguage")
      @Scope(ScopeType.SESSION)
      public class AdminLanguage implements IAdminAttribute, Serializable {
       private static final long serialVersionUID = 5816783546495530542L;
       
      
       @In
       private transient IAttributeService<Language> languageService;
       
       @In(required = false, scope = ScopeType.CONVERSATION) @Out(required = false, scope = ScopeType.CONVERSATION)
       private Language language;
       
       @DataModel
       private List<Language> languages;
       
       @DataModelSelection
       private Language selectedLanguage;
       
      
       @Factory("languages")
       public void getItems() {
        languages = languageService.select();
       }
       
      
       @Begin(join = true)
       public String add() {
        language = new Language();
        
        return "add";
       }
       
       @Begin(join = true)
       public String edit() {
        language = selectedLanguage;
        
        return "edit";
       }
      
       @End
       public String save() {
        try {
         if(languageService.select(language.getId()) == null) {
          languageService.insert(language);
         } else {
          languageService.update(language);
         }
        } catch (MissingPropertiesException e) {
         for(MissingProperty property : e.getMissingProperties()) {
          FacesUtil.addInfo(property.getControlID(), e.getResourceKey(), e.getResourceKey() + "_" + property.getControlID());
         }
         
         return null;
        } catch (EntityAlreadyExistsException e) {
         return null;
        } catch (EntityDoesntExistException e) {
         return null;
        }
      
        getItems();
        
        language = new Language();
        
        return "saved";
       }
       
       public String delete() {
        languageService.delete(selectedLanguage);
        languages.remove(selectedLanguage);
           selectedLanguage = null;
        
        return "deleted";
       }
       
       @Remove @Destroy
       public void destroy() {
       }
      }



      pages.xml



      <page view-id="/admin/languages.xhtml">
        <navigation>
         <rule if-outcome="add">
          <redirect view-id="/admin/language.xhtml" />
         </rule>
         <rule if-outcome="edit">
          <redirect view-id="/admin/language.xhtml" />
         </rule>
        </navigation>
       </page>
       
       <page view-id="/admin/language.xhtml">
        <navigation>
         <rule if-outcome="cancel">
          <end-conversation />
          <redirect view-id="/admin/languages.xhtml" />
         </rule>
         <rule if-outcome="saved">
          <redirect view-id="/admin/languages.xhtml" />
         </rule>
        </navigation>
       </page>




      The problem now is that the conversation ends unexpectedly. The use case is as follows: The user clicks Add to add a new language. The language.xhtml shows with empty fields. The user enters a token (nothing else) and then clicks Save. Now the page should be redisplayed with some error messages and the already entered value.
      The error messages are displayed correctly. But the fields are empty again, since the conversation ended.
      And I don't know why it ended. As I told you before I have a several pairs of pages that are almost the same. And they work. The only difference is that they aren't stored in a subdirectory and that they refer to other database tables. But the structure is the same.


      Thanks in advance
      Newlukai

        • 1. Re: xhtml files in subdirectories
          gjeudy

          In your usecase what code executes in the save method ? I assume it goes in the MissingPropertiesException catch block and returns null ?


          @End
           public String save() {
            try {
             if(languageService.select(language.getId()) == null) {
              languageService.insert(language);
             } else {
              languageService.update(language);
             }
            } catch (MissingPropertiesException e) {
             for(MissingProperty property : e.getMissingProperties()) {
              FacesUtil.addInfo(property.getControlID(), e.getResourceKey(), e.getResourceKey() + "_" + property.getControlID());
             }
             
             return null;
            } catch (EntityAlreadyExistsException e) {
             return null;
            } catch (EntityDoesntExistException e) {
             return null;
            }
          
            getItems();
            
            language = new Language();
            
            return "saved";
           }
          



          Ok hmm returning null should not end the conversation per @End annotation javadoc.. Maybe you are facing a bug ? Try running this in your debug and see if @End annotation is still getting processed even if you returned null...


          Why not leverage hibernate validation to achieve the MissingProperties check ? See more info on this. That's how I generally do basic validation and that works well I don't have to deal with the problem you are having.

          • 2. Re: xhtml files in subdirectories

            Guillaume Jeudy wrote on Oct 07, 2008 17:51:


            In your usecase what code executes in the save method ? I assume it goes in the MissingPropertiesException catch block and returns null ?


            Right. That's the code that's executed.



            Guillaume Jeudy wrote on Oct 07, 2008 17:51:


            Ok hmm returning null should not end the conversation per @End annotation javadoc.. Maybe you are facing a bug ? Try running this in your debug and see if @End annotation is still getting processed even if you returned null...


            That's an idea. I'll try to debug the @End annotation.



            Guillaume Jeudy wrote on Oct 07, 2008 17:51:


            Why not leverage hibernate validation to achieve the MissingProperties check ? See more info on this. That's how I generally do basic validation and that works well I don't have to deal with the problem you are having.


            I'm not very satisfied neither with my validation nor with hibernate validation. The problem with hibernate validation is that I can't have a single h:messages tag on my page with messages like Please enter the name. or Please enter a desription.. Hibernate validation only produces messages like Value required. or length must be between 2 and 10. Since I can't change this message in a way to display the label of this input I have to use multiple h:message tags which are placed near the input fields. The advantage of hibernate validation is that it takes place in the correct phase of JSF lifecycle and easy to use.

            • 3. Re: xhtml files in subdirectories

              Guillaume Jeudy wrote on Oct 07, 2008 17:51:


              Ok hmm returning null should not end the conversation per @End annotation javadoc.. Maybe you are facing a bug ? Try running this in your debug and see if @End annotation is still getting processed even if you returned null...


              OK. I debugged ConversationInterceptor and saw that the conversation is not ended.
              So I wondered why all the values are reset and debugged into the setter. It's called twice during Updated Model Values: the first invocation sets the property to the entered value, the second invocation resets it. Normally this setter should only be called once. Any hint why the setter is called twice?

              • 4. Re: xhtml files in subdirectories

                Jens Weintraut wrote on Oct 07, 2008 16:07:


                language.xhtml

                  <h:panelGrid cellpadding="5" cellspacing="0" columns="2">
                   <h:outputText value="#{msgs.label_language_token}:" id="label_language_token" />
                   <s:decorate id="decorate_language_token" template="/includes/decorateField.xhtml">
                    <h:inputText value="#{language.token}" id="language_token">
                     <s:validate />
                    </h:inputText>
                   </s:decorate>
                   
                   <h:outputText value="#{msgs.label_language_isoToken}:" id="label_language_isoToken" />
                   <s:decorate id="decorate_language_isoToken" template="/includes/decorateField.xhtml">
                    <h:inputText value="#{language.token}" id="language_isoToken">
                     <s:validate />
                    </h:inputText>
                   </s:decorate> 
                




                Copy'n'Paste ist just great. I referenced #{language.token} in two input fields.