13 Replies Latest reply on Oct 26, 2006 12:59 PM by japplicoon

    Seam application with tree on left and editor for selected t

    lawrieg

      I've just started looking at Seam and am investigating whether it might be well-suited to a new application I'll be developing. I envisage the app having a tree on the left and an editor for the currently selected tree nore object on the right. And if a user selects an object and starts editing it but then clicks on another tree node before saving I'd like to display a "Do you want to save your changes" dialog with Yes, No and Cancel options. Is this sort of functionality easy (/possible) to implement with Seam? (I'm coming from a Swing background so perhaps there is a better way to design this as a Seam web app???)

      Any help would be really appreciated!

      Thanks in advance,
      Lawrie

        • 1. Re: Seam application with tree on left and editor for select
          christian.bauer

          It's actually pretty easy in Seam, I did this just last week. The bigger problem is getting all the right libraries and taglibs and stuff working, if you have never created an EJB3/JSF app before. So you should definitely start with an easier example, like the ones bundled in Seam.

          For the tree widget, don't even think about using the Tomahawk/MyFaces components, they are a waste of time. Use Oracle ADF or the open source port MyFaces Trinidad.

          If you can wait another 3-4 weeks, you will see my example available on http://caveatemptor.hibernate.org

          • 2. Re: Seam application with tree on left and editor for select
            christian.bauer

            Some code:

            package auction.beans;
            
            import org.jboss.seam.annotations.*;
            import org.jboss.seam.annotations.datamodel.DataModel;
            import org.jboss.seam.annotations.datamodel.DataModelSelection;
            import org.jboss.seam.ScopeType;
            import org.hibernate.ejb.HibernateEntityManager;
            
            import javax.ejb.Stateful;
            import javax.ejb.Remove;
            import javax.persistence.EntityManager;
            
            import auction.model.Category;
            import auction.model.Item;
            import auction.dao.CategoryDAO;
            import auction.interceptors.LoggedIn;
            
            import java.util.*;
            
            /**
             * A conversational component for browsing the catalog.
             *
             * When the first variable for "catalog" has to be resolved, this component is
             * instantiated and a conversation begins. All root categories (with no parent)
             * of the catalog are loaded immediately and held during the conversation.
             *
             * When a category is selected, the selected value is passed into the method
             * setSelectedCategory(category) manually, this is the easiest way to get the
             * value of a click from a Trinidad tree JSF component.
             *
             * The item list is loaded when a category is selected and outjected into a
             * data model for display. When a user triggers an action in the data model's
             * table, the selectedItem of this conversation is set.
             *
             * The conversation ends usually by timeout or when it is destroyed
             * with one of Seams UI conversation management switchers/lists.
             *
             * @author Christian Bauer
             */
            @Name("catalog")
            @Scope(ScopeType.CONVERSATION) // Redundant, a SFSB is default in conversation context
            
            @LoggedIn // Wrap LoginInterceptor around every method call
            
            @Stateful
            public class CatalogBean implements Catalog {
            
             // We don't call the EntityManager directly in this bean (except to flush), only the DAOs.
             // But the DAOs are not always called in every method that does data access. For example,
             // when setSelectedCategory() is called, the categoryDAO is injected into this bean,
             // but the persistence context is not set on the categoryDAO (because no method is
             // called on the DAO, we just load a collection on-demand). So the persistence context
             // doesn't join the JTA transaction, which means it doesn't get flushed when
             // setSelectedCategory() returns (no synchronization registered). Short story: The
             // persistence context needs to be injected managed here, so that lazy loading works
             // properly in all methods of this stateful bean.
             @In(value = "caveatEmptorEM", create = true)
             private EntityManager em;
            
             // A stateless (yes, even for them create="true" is required) DAO component injected by Seam
             @In(create = true)
             private CategoryDAO categoryDAO;
            
             // This bean holds the root categories during a conversation
             private List<Category> rootCategories;
            
             // If present, Seam outjects this into the 'currentCategory' CONVERSATION variable
             @In(required = false)
             @Out(required = false, scope = ScopeType.CONVERSATION)
             private Category currentCategory;
            
             // Seam outjects this always to the 'itemList" CONVERSATION variable as a JSF DataModel
             @DataModel
             private List<Item> itemList;
            
             /**
             * Seam calls this when the Catalog bean is instantiated for a particular CONVERSATION.
             * Seam will start a long-running conversation when this method is called.
             */
             @Create
             @Begin(flushMode = FlushModeType.MANUAL)
             public void refreshRootCategories() {
             rootCategories = categoryDAO.findAll(true);
             }
            
             public List<Category> getRootCategories() {
             return rootCategories;
             }
            
             /**
             * Loads the selected category's items into a data model that is then outjected.
             * @param selectedCategory
             */
             public void setSelectedCategory(Category selectedCategory) {
             // Prepare the member variable for outjection
             currentCategory = selectedCategory;
             // Refresh the itemList (wrap the Set in a List, no pre-defined or database sorting)
             itemList = new ArrayList<Item>(currentCategory.getItems());
             }
            
             @Remove @Destroy @End
             public void closeCatalog() {
             // Nothing to do here, Seam will close the managed persistence context when
             // this method returns.
             }
            
            }
            


            <!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:h="http://java.sun.com/jsf/html"
             xmlns:f="http://java.sun.com/jsf/core"
             xmlns:tr="http://myfaces.apache.org/trinidad"
             xmlns:s="http://jboss.com/products/seam/taglib"
             template="template.xhtml">
            
             <ui:define name="screen">Catalog</ui:define>
            
             <ui:define name="sidebar">
            
             <h1>#{messages['AuctionCategories']}:</h1>
            
             <tr:form>
             <!-- Let Trinidad inject the selected category into our conversation component -->
             <!-- Trinidad tree icons are really really ugly (HTML entities), there is no way to change them... -->
             <tr:tree var="cat" value="#{catalogCategoryAdapter.treeModel}">
             <f:facet name="nodeStamp">
             <tr:commandLink text="#{cat.name}" styleClass="nodeText">
             <tr:setActionListener from="#{cat}" to="#{catalog.selectedCategory}"/>
             </tr:commandLink>
             </f:facet>
             </tr:tree>
            
             </tr:form>
            
             </ui:define>
            
             <ui:define name="content">
            
             <!-- Headline -->
             <div class="section">
             <h1>
             <!-- Read the event context variable "currentCategory" that was outjected by our conversation component -->
             <h:outputText value="#{messages['PleaseSelectCategory']}" rendered="#{currentCategory == null}"/>
             <h:outputText value="#{messages['SelectedCategory']}: #{currentCategory.name}" rendered="#{currentCategory != null}"/>
             </h1>
             </div>
            
             <!-- Item table -->
             <div class="section">
             <!-- Read the conversation context variable "itemList", outjected by the catalog conversation component -->
             <h:outputText value="#{messages['NoItemsInCategory']}"
             rendered="#{itemList != null and itemList.rowCount==0}"/>
            
             <h:form>
             <h:dataTable value="#{itemList}" var="itm" rendered="#{itemList.rowCount>0}">
             <h:column>
             <f:facet name="header">#{messages['ItemName']}</f:facet>
             #{itm.name}
             </h:column>
             <h:column>
             <f:facet name="header">#{messages['ItemDescription']}</f:facet>
             #{itm.description}
             </h:column>
             <h:column>
             <f:facet name="header">#{messages['ItemSeller']}</f:facet>
             #{itm.seller.username}
             </h:column>
             <h:column>
             <f:facet name="header">#{messages['Action']}</f:facet>
             <s:link value="#{messages['PlaceBid']}" styleClass="button" action="#{placeBid.selectItem(itm)}"/>
             </h:column>
             </h:dataTable>
             </h:form>
            
             </div>
            
             </ui:define>
            
            </ui:composition>
            



            • 3. Re: Seam application with tree on left and editor for select
              lawrieg

              Thanks ever so much, Christian - that's really helpful. You've certainly encouraged me to investigate further now that I know it's all do-able. I'm looking forward to seeing the CaveatEmptor sample app - although, unfortunately, I've got to get out a prototype before then...

              Thanks again,
              Lawrie.

              • 4. Re: Seam application with tree on left and editor for select
                japplicoon

                Great, Christian! I plan a similar application. I'm also awaiting your example! But may I ask you three short questions about your code now?

                a) I don't see any @DataModelSelection tag on your "currentCategory" variable, you seem to use another Bean for selection. Is there a reason you chose to not use it or just a taste question? I had some problems with DataModelSelection immediately triggering the next list of categories, but that might have been a scope issue.

                b) Will editing the selected item (or doing whatever with it) start a nested conversation or will some code call closeCatalog() when starting the editing process?

                Thanks a lot!!

                • 5. Re: Seam application with tree on left and editor for select
                  christian.bauer

                  a) Well, the data model in for the tree is a Trinidad TreeModel, right now Seam doesn't have a @DataModel/@DataModelSelection binding for that. If you look at the xhtml, I use an action listener in the tree node facet: catalog.setSelectedCategory(category). I didn't want to dig deeper into the underdocumented mess of tree components, so there might be a better way to get a direct binding, instead of copying a value into a setter method.

                  b) Actually, the only client that I expect to call closeCatalog() is Seam when it times out the conversation. This would be the case when the user starts a new catalog conversation by clicking on the top-level navigation menu "Catalog". The previous conversation is then inactive and timed out. Not sure this is the best strategy, I'll need to decide when the application is a little more complex.

                  I guess your question is really if the right-hand side edit panel runs inside the same conversation and if it's a nested conversation. Yes, and Maybe. It's definitely the same identity scope for database objects, so I'd use one overall conversation and one conversation-scope persistence context.

                  • 6. Re: Seam application with tree on left and editor for select
                    christian.bauer

                    This is how I get stuff into the tree:

                    package adapters;
                    
                    import org.apache.myfaces.trinidad.model.ChildPropertyTreeModel;
                    import org.apache.myfaces.trinidad.model.TreeModel;
                    
                    import java.util.Collection;
                    
                    /**
                     * Wraps a nested tree of nodes into a Trinidad TreeModel.
                     *
                     * The primary purpose of this adapter is to check if a node has children,
                     * or not. The wrapped TreeModel then results in correct rendering of the
                     * tree, either as a container or a node icon.
                     *
                     * See faces-config.xml for a usage example.
                     *
                     * @author Christian Bauer
                     */
                    public class TreeModelAdapter {
                    
                     private Collection rootCollection;
                     private String childCollectionPropertyName;
                    
                     public TreeModelAdapter() {}
                    
                     public TreeModel getTreeModel() {
                     return new ContainerAwareChildPropertyTreeModel(rootCollection, childCollectionPropertyName);
                     }
                    
                     public Collection getRootCollection() {
                     return rootCollection;
                     }
                    
                     public void setRootCollection(Collection rootCollection) {
                     this.rootCollection = rootCollection;
                     }
                    
                     public String getChildCollectionPropertyName() {
                     return childCollectionPropertyName;
                     }
                    
                     public void setChildCollectionPropertyName(String childCollectionPropertyName) {
                     this.childCollectionPropertyName = childCollectionPropertyName;
                     }
                    
                     /**
                     * This inner class extends the built-in Trinidad TreeModel, which by default
                     * treats every node as a container. Users have to click on a node to see
                     * if there is something in the container, and the rendering is also wrong.
                     * This might trigger loading of the children (the collection of children is
                     * touched) if you have lazy loading. However, you now get correct rendering
                     * of nodes and a regular node does no longer appear as a container.
                     */
                     class ContainerAwareChildPropertyTreeModel extends ChildPropertyTreeModel {
                    
                     public ContainerAwareChildPropertyTreeModel(Object object, String string) {
                     super(object, string);
                     }
                    
                     public ContainerAwareChildPropertyTreeModel() {
                     super();
                     }
                    
                     public boolean isContainer() {
                     boolean hasChildren = false;
                     // Hit the children collection of this container to
                     // figure out if its really a container
                     enterContainer();
                     if (getRowCount() > 0) hasChildren = true;
                     exitContainer();
                    
                     return hasChildren;
                     }
                     }
                    }
                    


                    <faces-config>
                    
                     <!-- JSF backing beans that call Seam components, these
                     backing beans provide an additional layer between
                     business components managed by Seam and presentation-only
                     beans. Common backing beans in a Seam application are
                     model adapters (that convert from a java.util.Collection
                     to a Trinidad TreeModel, for example).
                     -->
                     <managed-bean>
                     <managed-bean-name>catalogCategoryAdapter</managed-bean-name>
                     <managed-bean-class>adapters.TreeModelAdapter</managed-bean-class>
                     <managed-bean-scope>request</managed-bean-scope>
                     <managed-property>
                     <property-name>rootCollection</property-name>
                     <value>#{catalog.rootCategories}</value>
                     </managed-property>
                     <managed-property>
                     <property-name>childCollectionPropertyName</property-name>
                     <value>childCategories</value>
                     </managed-property>
                     </managed-bean>
                    ...
                    


                    I guess I should use a Seam component for this last bit, instead of a dumb JSF backing bean.


                    • 7. Re: Seam application with tree on left and editor for select
                      gavin.king

                      Christian, have you noticed the @DataBinder / @DataSelector meta-annotations?

                      You can probably implement @DataModel-like functionality for the Trinidad stuff using those annotations. Check org.jboss.seam.databinding package for examples.

                      • 8. Re: Seam application with tree on left and editor for select
                        christian.bauer

                        Yes, I remember you implemented something more generic for databinding. Didn't really have time to look into that so far. I will try to use it for TreeModel and SelectItem, which are probably the most common data models besides tabular stuff.

                        • 9. Re: Seam application with tree on left and editor for select
                          gavin.king

                          Yup. Trees are harder to handle than lists though, because they can have different object types at each level of the tree.

                          Its easy for, eg. Category trees ... but that is not enough for a generic solution, unfortunately.

                          • 10. Re: Seam application with tree on left and editor for select
                            japplicoon

                            Thank you, Christian, for the very detailed instructions!
                            Now I'll have a look at DataBinder.

                            • 11. Re: Seam application with tree on left and editor for select
                              japplicoon

                              Christian, sorry, but I have another question about your example, now it is a persistence/memory issue:

                              A category has a List of child categories and a list of items. You lazily init these when a category is selected ... inside a Seam conversation - that is your design, isn't it?
                              Then if the user successively views the itemslists of each category, do you step-by-step save the whole catalog data in the conversation?

                              So if there are many items and much data in each of them, would it be necessary to dismiss the children and items Lists on the category, fetching child categories and items manually?

                              I hope you are not too bored by my beginner questions ...



                              • 12. Re: Seam application with tree on left and editor for select
                                christian.bauer

                                You have to understand how a/the persistence context works. The persistence context is a cache, basically an identity map that has as keys the primary keys of database rows, and as values some Java instances. During one conversation, no matter how it it gets there (find by identifier, query, lazy initialization), I have one persistence context and one identity map.

                                You can manage a persistence context. The EntityManager API has clear() and the Hibernate Session API also evict(o) for single instances.

                                You can load stuff into the persistence context any way you want, the important thing is to keep the identity scope so that at most you have one in-memory representation of a particular database row. Without this, you would work with detached objects, a totally different concept that implies much more manual work.

                                Read this: http://www.manning.com/bauer2

                                • 13. Re: Seam application with tree on left and editor for select
                                  japplicoon

                                  Ah, perfect clarification! My mistaking was to think things somehow get "copied" from the persistence context onto the objects and then staying there, but I see ....


                                  Read this: http://www.manning.com/bauer2

                                  I watch my (german) book seller all the time for your book to be listed as available ;-)

                                  One more big, big "THANK YOU!"