5 Replies Latest reply on Aug 20, 2007 11:47 AM by Matt Drees

    how to conditionally render a row of dataTable

    Ning Zhao Apprentice

      A column of a h:dataTable can be conditionally rendered. But is there any way to conditionally render a row of a dataTable? This can be useful, for example, for reducing communication between database and app server.

      In my application, each entity has a flag column with values like "creating", "created", "updating", "updated",...etc in the database. There is an extremely heavy weight stateless session bean called "dataStore" which has static fields for holding List allFoo, List allBar ....etc, this has proven working very well as data cache. Although my entities seldom get updated or deleted, I am thinking how can I still use this caching approach if I need to implement update or delete. It's of course easy to change the flags in the CRUD flag columns. But then, there is a need for conditionally rendering a row. It would be nice to only render rows with "created" or "updated" flag in the dataTable to the end user. If only there's something like:

      <h:dataTable value="#{fooManager.fooList}" var="foo" rowRendered="#{foo.crudStatus eq 'created' or foo.crudStatus eq 'updated'}">
      </h:dataTable>
      


      Is there any exisiting lib can do this? If not, how about implement it as a Seam feature?


      Regards,
      Ellen

        • 1. Re: how to conditionally render a row of dataTable
          Matt Drees Master

          I imagine you could create an el function to do your filtering for you. Something like

          <h:dataTable value="#{my:filter(fooManager.fooList)}" var="foo" >
          </h:dataTable>
          


          • 2. Re: how to conditionally render a row of dataTable
            Ning Zhao Apprentice

            Yes that's a good idea! Could you please point me to documentation on how to create an EL function? In my view code (.xhtml files), there is heck lot of rendering rules (occurence of the word "rendered = " > 100), some are repeated again and again and again for different fields/buttons.

            It would be really nice if I could say something like:

            rendering rules: for view-id = "foo.xhtml"
            
            rule: "rendering rule for the use case "create a foo"
            when
             fooManager.useCases.peek() == "createAFoo",
             fooManager.foo.crudStatus.toString == "new", // "new" means the id has not been fetched from the DB yet.
             identity.hasRole('admin'),
             .....
            then
             render fields: setter for (fooManager.foo.name, fooManager.foo.description, fooManager.foo......)
             render action button: button1, button2....
             not render: // some fields and/or buttons you do not want to render
            
            end
            
            rule: "rendering rule for the use case "update a foo"
            when
             ....
            then
            ...
            end
            
            



            This way composition will be really easy....Even better, the view can be mapped/annotated in the entity class to reduce the view code, for example:

            @Column(name = "name"....)
            @View(outputText)
            public String getName(){...}
            
            @View(inputText)
            public void setName(String name){...}
            
            .....
            


            And in the fooManager class:
            
            ...
            
            @View(actionButton)
            public void tryUpdateAFoo(){
            }
            
            ...
            




            Regards,
            Ellen

            • 3. Re: how to conditionally render a row of dataTable
              Matt Drees Master

              Rick Hightower wrote a couple good articles on Facelets, and the second gives a good example on using functions. I found it helpful.

              He goes for the programmatic approach of defining a facelets tag library; some people prefer defining their library in xml. I like the programmatic approach better; it seems create less duplication.

              http://www.ibm.com/developerworks/java/library/j-facelets2.html

              As far as creating some kind of annotation-driven framework for conditional rendering, I'm sure you could do it. Instead of using el functions, I'd guess you would be better off creating a component that marks its children rendered or not rendered, depending on their value-bindings. You could get some direction on how to do this by looking at Seam's components for model validation (ModelValidator and UIValidateAll, as well as Hibernate Validator code). But who knows; maybe that'd be harder to do.

              • 4. Re: how to conditionally render a row of dataTable
                Ning Zhao Apprentice

                I read that article. It seems there's a lot work to do in the infrastructure for adding a simple filtering feature....

                Here are small view files in my own project demonstrating how I compose views. Hopefully they are readable enough....the foodCont is the name of a manager backing bean. This file is never directly accessed, but always included in other view files. All my directly-accessed views are started with a verb, shortly describing a use case.

                Here the view-a-food.xhtml:

                <!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
                 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
                
                <!-- version: $Id: view-a-food.xhtml 211 2007-08-16 16:42:02Z ningning $ -->
                
                <ui:composition xmlns="http://www.w3.org/1999/xhtml"
                 xmlns:s="http://jboss.com/products/seam/taglib"
                 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:rich="http://richfaces.ajax4jsf.org/rich"
                 xmlns:a="https://ajax4jsf.dev.java.net/ajax"
                 template="layout/template.xhtml">
                
                 <ui:define name="body">
                
                 <h3>Food Item Detail</h3>
                 <h:messages globalOnly="true" styleClass="message" />
                
                 <ui:include src="/includes/food.xhtml" />
                 <rich:spacer width="100%" height="7" />
                
                 <div class="section"><s:button value="Ok"
                 action="#{foodCont.quitAFoodView()}" /></div>
                 </ui:define>
                </ui:composition>
                



                Here the ultimately reusable food.xhtml:

                <!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
                 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
                
                <!-- version: $Id: food.xhtml 210 2007-08-16 06:34:57Z ningning $ -->
                
                <ui:composition xmlns="http://www.w3.org/1999/xhtml"
                 xmlns:s="http://jboss.com/products/seam/taglib"
                 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:rich="http://richfaces.ajax4jsf.org/rich"
                 xmlns:a="https://ajax4jsf.dev.java.net/ajax">
                
                 <div class="section">
                 <!-- #################### begin error messages ##################### -->
                 <s:fragment rendered="#{not empty foodCont.getErrors()}">
                 <rich:dataList value="#{foodCont.getErrors()}" var="error">
                 <h:outputText value="#{error.getMessage()}" class="error" />
                 </rich:dataList>
                 <rich:spacer width="100%" height="7" />
                 </s:fragment>
                 <!-- ####################### end error messages ############## -->
                
                 <!-- ################# permission guard ######################### -->
                 <s:fragment
                 rendered="#{foodCont.getNeedsLogin() and not identity.loggedIn}">
                 <div class="section"><h:form>
                 <rich:panel>
                 <f:facet name="header">Login</f:facet>
                 <p>You need to login to perform this action</p>
                 <div class="dialog"><h:panelGrid columns="2"
                 rowClasses="prop" columnClasses="name,value">
                 <h:outputLabel for="username">Username</h:outputLabel>
                 <h:inputText id="username" value="#{foodCont.actor.name}" />
                 <h:outputLabel for="password">Password</h:outputLabel>
                 <h:inputSecret id="password"
                 value="#{foodCont.actor.password}" />
                 <h:outputLabel for="rememberMe">Remember me</h:outputLabel>
                 <h:selectBooleanCheckbox id="rememberMe"
                 value="#{identity.rememberMe}" />
                 </h:panelGrid></div>
                 </rich:panel>
                 <div class="actionButtons"><h:commandButton value="Login"
                 action="#{foodCont.login}" /></div>
                 </h:form></div>
                 <rich:spacer width="100%" height="7" />
                 </s:fragment>
                 <!-- ################## end of permission guard ############### -->
                
                 <!-- ############### begin atomic fields of a food ################# -->
                 <s:fragment>
                 <s:decorate template="/layout/display.xhtml">
                 <ui:define name="label">Food Description</ui:define>
                 <h:outputText value="#{foodCont.food.longDesc}" />
                 </s:decorate>
                
                 <s:decorate template="/layout/display.xhtml">
                 <ui:define name="label">Food Group: </ui:define>
                 <h:outputText value="#{foodCont.food.fdGrp.desc_en}" />
                 </s:decorate>
                
                 <s:fragment rendered="#{foodCont.food.comName != null}">
                 <s:decorate template="/layout/display.xhtml">
                 <ui:define name="label">Common Name:</ui:define>
                 <h:outputText value="#{foodCont.food.comName}" />
                 </s:decorate>
                 </s:fragment>
                
                 <s:fragment rendered="#{foodCont.food.facName != null}">
                 <s:decorate template="/layout/display.xhtml">
                 <ui:define name="label">Manufacturer Name:</ui:define>
                 <h:outputText value="#{foodCont.food.facName}" />
                 </s:decorate>
                 </s:fragment>
                
                 <s:fragment rendered="#{foodCont.food.sciName != null}">
                 <s:decorate template="/layout/display.xhtml">
                 <ui:define name="label">Scientific Name:</ui:define>
                 <h:outputText value="#{foodCont.food.sciName}" />
                 </s:decorate>
                 </s:fragment>
                
                 <s:fragment rendered="#{foodCont.food.refuDesc != null}">
                 <s:decorate template="/layout/display.xhtml">
                 <ui:define name="label">Refuse Description:</ui:define>
                 <h:outputText value="#{foodCont.food.refuDesc}" />
                 </s:decorate>
                
                 <s:decorate template="/layout/display.xhtml">
                 <ui:define name="label">Refuse Percentage:</ui:define>
                 <h:outputText value="#{foodCont.food.refuPercentage}%" />
                 </s:decorate>
                 </s:fragment>
                
                 <s:fragment rendered="#{not empty foodCont.food.userOpinions}">
                 <s:decorate template="/layout/display.xhtml">
                 <ui:define name="label">Average User Rate:</ui:define>
                 <h:outputText value="#{foodCont.food.getRateMean()}">
                 <f:convertNumber type="number" maxFractionDigits="3" />
                 </h:outputText>
                 <h:outputText
                 value=" from #{foodCont.food.getTotalRates()} rates" />
                 </s:decorate>
                 </s:fragment>
                
                 <s:decorate template="/layout/display.xhtml">
                 <ui:define name="label">Current Price per 100g:</ui:define>
                 <h:outputText value="#{foodCont.food.pricePer100g}">
                 <f:convertNumber pattern="? ###0.00" />
                 </h:outputText>
                 </s:decorate>
                
                 <rich:spacer width="100%" height="7" />
                 </s:fragment>
                 <!-- ############### end of atomic fields of a food ############### -->
                
                 <!-- ################# begin weight convertor #################### -->
                 <s:fragment rendered="#{not empty foodCont.food.getWeights()}">
                 <div class="section"><h:form>
                
                 <s:decorate template="/layout/edit.xhtml">
                 <ui:define name="label">Amount: </ui:define>
                 <h:inputText value="#{foodCont.food.givenAmount}"
                 required="true" />
                 </s:decorate>
                
                 <h:selectOneMenu id="selectUnit" class="value"
                 value="#{foodCont.food.givenWeight}">
                 <s:selectItems value="#{foodCont.food.weights}" var="unit"
                 label="#{unit.msreDesc}"
                 noSelectionLabel="Select a weight unit..." />
                 <s:convertEntity />
                 </h:selectOneMenu>&#160;&#160;
                
                 <h:commandButton type="submit" value="Convert to gram"
                 action="#{foodCont.food.convertWeight()}" />
                 </h:form> &#160;&#160; <h:outputText
                 value="#{foodCont.food.getWeightInGram()}">
                 <f:convertNumber type="number" maxFractionDigits="3" />
                 </h:outputText> gram</div>
                 <rich:spacer width="100%" height="7" />
                 </s:fragment>
                 <!-- ################# end weight convertor #################### -->
                
                 <!-- ############### begin control options ################# -->
                 <div class="section"><rich:toolBar height="26"
                 itemSeparator="disc">
                 <rich:toolBarGroup
                 rendered="#{(identity.hasRole('admin') and (foodCont.getUseCases().peek() ne 'updateAFood')) or (not identity.loggedIn)}">
                 <s:link action="#{foodCont.tryUpdateAFood()}" value="Update Price" />
                 </rich:toolBarGroup>
                
                 <rich:toolBarGroup>
                 <s:link
                 action="#{userOpinionCont.startAddOpinion(foodCont.food)}"
                 value="Rate and/or Comment it"
                 rendered="#{userOpinionCont.getUseCases().peek() ne 'addOrUpdateUserOpinion'}" />
                 </rich:toolBarGroup>
                
                 <rich:toolBarGroup>
                 <s:link action="#{foodCont.food.setRenderNutrients(true)}"
                 value="Show Nutrient Facts"
                 rendered="#{not foodCont.food.getRenderNutrients()}" />
                 <s:link action="#{foodCont.food.setRenderNutrients(false)}"
                 value="Fold Nutrient Facts"
                 rendered="#{foodCont.food.getRenderNutrients()}" />
                 <s:link action="#{foodCont.food.setRenderComments(true)}"
                 value="Show User Comments"
                 rendered="#{foodCont.food.getComments().size > 0 and not foodCont.food.getRenderComments()}" />
                 <s:link action="#{foodCont.food.setRenderComments(false)}"
                 value="Fold User Comments"
                 rendered="#{foodCont.food.getComments().size > 0 and foodCont.food.getRenderComments()}" />
                 <s:link action="#{foodCont.food.setRenderRecipes(true)}"
                 value="Show Containing Recipes"
                 rendered="#{foodCont.food.getIngredients().size > 0 and not foodCont.food.getRenderRecipes()}" />
                 <s:link action="#{foodCont.food.setRenderRecipes(false)}"
                 value="Fold Containing Recipes"
                 rendered="#{foodCont.food.getIngredients().size > 0 and foodCont.food.getRenderRecipes()}" />
                 </rich:toolBarGroup>
                 </rich:toolBar>
                 <rich:spacer width="100%" height="7" />
                 </div>
                 <!-- ################ end of control options ################## -->
                
                 <!-- ############### begin of price update form ################## -->
                 <s:fragment
                 rendered="#{foodCont.getUseCases().peek() eq 'updateAFood'}">
                 <div class="section"><h:form>
                 <s:decorate template="/layout/edit.xhtml">
                 <ui:define name="label">Current Price per 100g:</ui:define>
                 <h:inputText value="#{foodCont.food.pricePer100g}"
                 required="true" />
                 <h:outputText value=" euro" />
                 </s:decorate>
                 <div class="section"><h:commandButton value="update"
                 action="#{foodCont.doneUpdateAFood()}" /><s:button
                 value="Cancel" action="#{foodCont.cancelUpdatePrice()}" /></div>
                 </h:form></div>
                 <div class="section"><rich:spacer width="100%" height="7" /></div>
                 </s:fragment>
                 <!-- ############### end of price update form ################## -->
                
                 <!-- ############### begin of user opinion form ############### -->
                 <s:fragment
                 rendered="#{userOpinionCont.getUseCases().peek() eq 'addOrUpdateUserOpinion'}">
                 <div class="section"><ui:include src="user-opinion.xhtml" />
                 <rich:spacer width="100%" height="7" /></div>
                 </s:fragment>
                 <!-- ################ end of user opinion form ################## -->
                
                 <!-- ############## begin of nutrient facts ################### -->
                 <s:fragment
                 id="nutrients" rendered="#{foodCont.food.getRenderNutrients()}">
                 <div class="section">
                 <h4>Nutrient Facts</h4>
                 <h:form>
                 <rich:dataTable id="nutrientFacts" rows="25"
                 value="#{foodCont.food.getNutrients()}" var="n">
                 <rich:column>
                 <f:facet name="header">
                 <h:outputText value="Nutrient Desc" />
                 </f:facet>
                 <h:outputText value="#{n.nutr.nutrDesc}" />
                 </rich:column>
                
                 <rich:column>
                 <f:facet name="header">
                 <h:outputText value="Amount per 100g" />
                 </f:facet>
                 <h:outputText value="#{n.nutrVal} #{n.nutr.units}">
                 <f:convertNumber type="number" maxFractionDigits="3" />
                 </h:outputText>
                 </rich:column>
                
                 <rich:column rendered="#{foodCont.food.getWeightInGram()>0}">
                 <f:facet name="header">
                 <h:outputText
                 value="Amount in #{foodCont.food.getGivenAmount()} #{foodCont.food.getGivenWeight().msreDesc}" />
                 </f:facet>
                 <h:outputText
                 value="#{foodCont.food.getWeightInGram() / 100 * n.nutrVal}">
                 <f:convertNumber type="number" maxFractionDigits="3" />
                 </h:outputText>
                 #{n.nutr.units}
                 </rich:column>
                 </rich:dataTable>
                 <rich:datascroller for="nutrientFacts" />
                 </h:form></div>
                 <rich:spacer width="100%" height="7" />
                 </s:fragment>
                 <!-- ################# end of nutrient facts #################### -->
                
                 <!-- ################# begin of user comments table ############# -->
                 <s:fragment id="userCommments"
                 rendered="#{foodCont.food.getComments().size > 0 and foodCont.food.getRenderComments()}">
                 <div class="section"><h:form>
                 <rich:dataTable id="commentsTable" rows="25"
                 value="#{foodCont.food.getComments()}" var="o">
                 <rich:column>
                 <f:facet name="header">
                 <h:outputText value="User" />
                 </f:facet>
                 <h:outputText value="#{o.user.name}" />
                 </rich:column>
                
                 <rich:column>
                 <f:facet name="header">
                 <h:outputText value="Rate" />
                 </f:facet>
                 <h:outputText value="#{o.rate}" />
                 </rich:column>
                
                 <rich:column>
                 <f:facet name="header">
                 <h:outputText value="Comment" />
                 </f:facet>
                 <h:outputText value="#{o.comment}" />
                 </rich:column>
                
                 <rich:column>
                 <f:facet name="header">
                 <h:outputText value="Date" />
                 </f:facet>
                 <h:outputText value="#{o.version}" />
                 </rich:column>
                 </rich:dataTable>
                 <rich:datascroller for="commentsTable" />
                 </h:form></div>
                 <rich:spacer width="100%" height="7" />
                 </s:fragment>
                 <!-- ################# end of user comments table ############# -->
                
                 <!-- ################# begin of containing recipes table ########### -->
                 <s:fragment
                 rendered="#{foodCont.food.getIngredients().size > 0 and foodCont.food.getRenderRecipes()}">
                 <div class="section"><h:form>
                 <rich:dataTable id="recipesTable" rows="25"
                 value="#{foodCont.food.getIngredients()}" var="i">
                 <rich:column>
                 <f:facet name="header">
                 <h:outputText value="Recipe Id" />
                 </f:facet>
                 <h:outputText value="#{i.recipe.id}" />
                 </rich:column>
                
                 <rich:column>
                 <f:facet name="header">
                 <h:outputText value="Recipe Name" />
                 </f:facet>
                 <h:outputText value="#{i.recipe.name}" />
                 </rich:column>
                
                 <rich:column>
                 <f:facet name="header">
                 <h:outputText value="Weight Percentage" />
                 </f:facet>
                 <h:outputText value="#{i.getWeightPercentage()}">
                 <f:convertNumber pattern="###0.00%" />
                 </h:outputText>
                 </rich:column>
                
                 <rich:column>
                 <f:facet name="header">
                 <h:outputText value="Option" />
                 </f:facet>
                 <h:commandButton
                 action="#{foodCont.selectContainingRecipe(i)}"
                 value="view the recipe" />
                 </rich:column>
                 </rich:dataTable>
                 <rich:datascroller for="recipesTable" />
                 </h:form></div>
                 <rich:spacer width="100%" height="7" />
                 </s:fragment>
                 <!-- ################# end of containing recipes table ########### --></div>
                </ui:composition>
                


                yes my entity object is very rich of behaviours and transient member fields...

                Here a bit navigation rules....
                <page view-id="/view-a-food.xhtml">
                 <navigation from-action="#{foodCont.quitAFoodView()}">
                 <rule
                 if="#{foodCont.getUseCases().peek() eq 'readFoods'}">
                 <redirect view-id="/view-foods.xhtml" />
                 </rule>
                 </navigation>
                 <navigation from-action="#{recipeManager.setRecipe(r)}">
                 <redirect view-id="/view-a-recipe.xhtml" />
                 </navigation>
                 <navigation>....</navigation>
                 </page>
                


                the code in the conversation beans is very lean, just does stuffs like begin/end conversations, push/pop/remove use cases, etc.

                • 5. Re: how to conditionally render a row of dataTable
                  Matt Drees Master

                  Yeah, there is some infrastructure for setting up a tag library. But I think it's totally worth it.