9 Replies Latest reply on Oct 27, 2007 6:50 AM by pmuir

    Wannabe-Example needs help growing up

    stephen.friedrich

      Every now and then someone asked in the forum how to best implement a common UI for editing master data with Seam:
      Multiple tabs/areas where each tab shows a list of items plus details for the item currently selected in the list.

      Last time we talked about it was here:
      http://www.jboss.com/index.html?module=bb&op=viewtopic&t=117825

      I tried and created a first draft of the most basic example of them all - see screenshot:
      http://www.eekboom.com/jboss-seam-work/work.png
      (Why can't I attach files here?)
      A single area, an Employee entitiy with only first name and last name properties (plus id), an EmployeeHome component, single query defined in components.xml, list and "detail" form in xhtml file. (I started with the seampay example and changed/stripped it to arrive at this.)

      Currently it is not (explicitly) using conversations in any way.
      To run the example simply download here
      http://www.eekboom.com/jboss-seam-work/seamwork.zip
      unzip into <seam-1.2.1-root-dir>/examples, build with ant and open http://localhost:8080/jboss-seam-work

      There is nothing really remarkable about the single xhtml file.
      The table has an edit button in each row that refreshes the page with the employee id in a request parameter:

       <s:link view="/employees.xhtml">
       <f:param name="employeeId" value="#{employee.id}"/>
       <img src="img/edit.gif" alt="#{employee.id}" border="0"/>
       </s:link>
      

      A delete button in each row directly calls into the EntityHome:
       <s:link action="#{employeeHome.remove}">
       <f:param name="employeeId" value="#{employee.id}"/>
       <img src="img/delete.gif" alt="#{employee.id}" border="0"/>
       </s:link>
      

      The f:param "outjects" the selected employee's id as a request parameter.
      The request parameter is forwarded to the EmployeeHome by using a page parameter
      (pages.xml):
       <page view-id="/employees.xhtml">
       <param name="employeeId" value="#{employeeHome.id}" converterId="javax.faces.Long" />
       </page>
      



      Open issues / questions in the example

      * Is it ok to "manually" add a newly created entity to the entity query's result list like this (in EmployeeHome):
       public String persist() {
       String outcome = super.persist();
       employees.getResultList().add(getInstance());
       return outcome;
       }
      


      * At first after deleting an employee the employee's details were still shown in the detail form (but editing/resubmitting the form caused an exception of course). I tried to get around this by overriding the remove method in EmployeeHome:
       public String remove() {
       String outcome = super.remove();
       setId(null);
       return outcome;
       }
      

      After that the detail form was indeed empty after deleting an employee.
      Still when you delete an employee, then fill in data in the detail form (without first selecting another employee or clicking on "Create New Employee) and click on "Create" it still crashes with an exception. Any idea?

      * Why do I need to add a newly created employee to the list myself, but a deleted employee is removed from the list automatically?

      * Currently it seems that the whole list of employees is reloaded from DB each time I select another employee in the list (by clicking the edit button).
      How can I achieve this behaviour:
      - The list should only be reloaded when selecting the "area", i.e. when clicking on the "Employees" link in the header.
      - However when a single employee is selected that single employee should be reloaded from DB prior to showing it in the details area. (To be sure to
      start the edit using the most up-to-date data.) When the employee has already been deleted, then a message should be shown instead and it should be removed from the list.
      - Ideally when opening another employee "area" in a new browser tab (by ctrl-clicking (with firefox) on an edit button or the "Employees" header) then the new tab should act independently from the first (i.e. loading the employee list once, when opened and refreshing a single employee on each selection).

      * In seampay the PaymentHome had a "@RequestParameter Long paymentId;" declaration. Is it really needed? Seems to be unused, but it's hard to tell because there's always so much going on behind the scene when using seam.

      If somebody could help me understanding / fixing these issues I can write some more explanatory text plus enhance the example with one or two more entities, relations and details.

      When it's done I can also update it to work with Seam 2.0.0 - for now I'd like to stick with 1.2.1, because that's the version our small app will go live with.


        • 1. Re: Wannabe-Example needs help growing up
          pmuir

           

          "stephen.friedrich" wrote:
          (Why can't I attach files here?)


          Soon now we shall be moving to a new forum (I can't wait :)

          * Is it ok to "manually" add a newly created entity to the entity query's result list like this (in EmployeeHome):
           public String persist() {
           String outcome = super.persist();
           employees.getResultList().add(getInstance());
           return outcome;
           }
          


          It's a bit strange ;) In Seam2 we have org.jboss.seam.afterTransactionSuccess.Employee (assmuming the entity you EmployeeHome is named Employee) - so you can observe this event and when it occurs you can call employees.refresh() - this is the nice way to do it.

          Does doing a flush and then a employees.refresh() not work in 1.2.1?

          * At first after deleting an employee the employee's details were still shown in the detail form (but editing/resubmitting the form caused an exception of course). I tried to get around this by overriding the remove method in EmployeeHome:
           public String remove() {
           String outcome = super.remove();
           setId(null);
           return outcome;
           }
          

          After that the detail form was indeed empty after deleting an employee.
          Still when you delete an employee, then fill in data in the detail form (without first selecting another employee or clicking on "Create New Employee) and click on "Create" it still crashes with an exception. Any idea?



          Current CVS gives you a clearInstance() method - it's

           public void clearInstance()
           {
           setInstance(null);
           setId(null);
           }


          try that and get rid of it when you upgrade.
          * Why do I need to add a newly created employee to the list myself, but a deleted employee is removed from the list automatically?


          Probably to do with when the list refreshes (the EntityQuery is request scoped).

          * Currently it seems that the whole list of employees is reloaded from DB each time I select another employee in the list (by clicking the edit button).


          Yup, the query is request scoped ;) You can make it Conversation scoped but then you have to manually manage refreshs (easier when you have the events discussed above).

          How can I achieve this behaviour:
          - The list should only be reloaded when selecting the "area", i.e. when clicking on the "Employees" link in the header.


          Put the query into conversation scope and use an action to refresh when you click the button.

          - However when a single employee is selected that single employee should be reloaded from DB prior to showing it in the details area. (To be sure to
          start the edit using the most up-to-date data.) When the employee has already been deleted, then a message should be shown instead and it should be removed from the list.


          The Home should always load a instance from the db, not the EntityQuery.

          - Ideally when opening another employee "area" in a new browser tab (by ctrl-clicking (with firefox) on an edit button or the "Employees" header) then the new tab should act independently from the first (i.e. loading the employee list once, when opened and refreshing a single employee on each selection).


          This should happen if you are using conversations

          * In seampay the PaymentHome had a "@RequestParameter Long paymentId;" declaration. Is it really needed? Seems to be unused, but it's hard to tell because there's always so much going on behind the scene when using seam.


          Is the paymentId used in the code? If not, its not needed (@RequestParameter is just an injector).

          HTH!

          • 2. Re: Wannabe-Example needs help growing up
            stephen.friedrich

            Thanks Pete for the quick answer! It helps somewhat, but still I don't really know how to make use of conversations.
            If you could stay with me for a while that would be great! I will really try to come up with a helpful example in the end.

            1) Deletion problem
            You suggested calling both setInstance(null); and setId(null); in the remove method of the employee home.
            That did not help. After some late night debugging I found out that page param should be read literally. The employee id is stored in Seam's page context whenever an employee is selected. It stays there if the employee is deleted, so the next creation of a new EmployeeHome will dutifully transfer it again.
            Adding this in remove() helps:

            Contexts.getPageContext().remove("groupId");

            I would prefer not to hard code parameter names in Java, though.
            Of course you do not have this particular problem when you use separate pages for listing of employees and and the employee details (and only have a delete button on the details page).
            It would help if the doc made it clear that page params are in fact stored in the page context. Currently all I read is "request parameter is transfered to the model according to the page param definition in pages.xml".

            2) Page params vs. request params vs. DataModelSelection
            Turns out in seampay the @RequestParameter field is really needed.
            seampay uses two different ways to transfer an id parameter to a home object:
            Account home uses the page param method. Payment home uses @RequestParameter and overrides the base class's getId() to return the id from the parameter.
            An additional option would be to use neither page params nor request parameters, but to use @DataModel/@DataModelSelection (like the "clickable list" messages example does).
            How should I decide which method to use?

            2) Conversation scoped query result
            You're right it really feels strange to update the query's result list directly.
            However I do not want to refresh all entities because a single, unrelated entity has been created.
            I could either wrap the query in a conversation scoped component of my own and outject a List from that component (again pretty much like the MessageManagerBean in the messages example).
            (That way it wouldn't be so strange to add an element to the list after a create operation.)
            Alternatively I could add that functionality to the EmployeeHome itself, but that would mix responsibility for the employee list with that for the selected employee.
            Or of course I could make the query itself conversation scoped and continue to update the result list in such a strange way. In the end however I'll probably have to wrap the query anayway because the "restrictions"/query-by-example feature will be too limiting to support more complex filters.
            What do you think?

            3) Atomic create
            Oh well, adding a newly created instance to the list myself isn't transaction-save, is it? When in the end the transaction fails I may already have added the instance. Can I do something like this (in my home object)?
             public String save() {
             if (isManaged()) {
             return update();
             }
             String result = persist();
            
             //noinspection unchecked
             List<T> resultList = getEntityQuery().getResultList();
             resultList.add(getInstance());
            
             return result;
             }
            

            save() is not marked @Transactional. Will the @Transactional annotation on persist() still be honored when I call persist() directly from within the save() method as above?

            4) Conversations
            What annotations/tag attributes do I need to add where to start a conversation when the "Employees" header is clicked or when an employee in the list is opened in a new browser tab?


            • 3. Re: Wannabe-Example needs help growing up
              pmuir

               

              "stephen.friedrich" wrote:
              1) Deletion problem
              You suggested calling both setInstance(null); and setId(null); in the remove method of the employee home.
              That did not help. After some late night debugging I found out that page param should be read literally. The employee id is stored in Seam's page context whenever an employee is selected. It stays there if the employee is deleted, so the next creation of a new EmployeeHome will dutifully transfer it again.
              Adding this in remove() helps:
              Contexts.getPageContext().remove("groupId");

              I would prefer not to hard code parameter names in Java, though.
              Of course you do not have this particular problem when you use separate pages for listing of employees and and the employee details (and only have a delete button on the details page).


              I think this is a case where @RequestParameter may be better.

              You can then just use it to initialize the id of the EntityHome in the first instance of the click on the list - it won't get propagated if you remove it from pages.xml.

              It would help if the doc made it clear that page params are in fact stored in the page context. Currently all I read is "request parameter is transfered to the model according to the page param definition in pages.xml".


              http://jira.jboss.com/jira/browse/JBSEAM-1896

              2) Page params vs. request params vs. DataModelSelection
              Turns out in seampay the @RequestParameter field is really needed.
              seampay uses two different ways to transfer an id parameter to a home object:
              Account home uses the page param method. Payment home uses @RequestParameter and overrides the base class's getId() to return the id from the parameter.
              An additional option would be to use neither page params nor request parameters, but to use @DataModel/@DataModelSelection (like the "clickable list" messages example does).
              How should I decide which method to use?


              I would use @RequestParameter. page params are very useful, but not quite as versatile (as they do the parameter management for you whilst using @RequestParameter you have to do it yourself)

              2) Conversation scoped query result
              You're right it really feels strange to update the query's result list directly.
              However I do not want to refresh all entities because a single, unrelated entity has been created.
              I could either wrap the query in a conversation scoped component of my own and outject a List<Employee> from that component (again pretty much like the MessageManagerBean in the messages example).
              (That way it wouldn't be so strange to add an element to the list after a create operation.)
              Alternatively I could add that functionality to the EmployeeHome itself, but that would mix responsibility for the employee list with that for the selected employee.
              Or of course I could make the query itself conversation scoped and continue to update the result list in such a strange way. In the end however I'll probably have to wrap the query anayway because the "restrictions"/query-by-example feature will be too limiting to support more complex filters.
              What do you think?


              I would do something like the event handling method in Seam2. Build a custom version of EntityHome

              public class EmployeeHome extends EntityHome<E> {
              
              @Transactional
              public String persist() {
               String result = super.persist();
               raiseEvent("mydomain.afterTransactionSuccess.Employee");
              }
              
              // Same for remove, update
              }


              <event type="myDomain.afterTransactionSuccess.Employee">
               <action execute="#{employees.refresh}" />
              </event>


              I would avoid futzing with the EntityQuery results yourself.

              save() is not marked @Transactional. Will the @Transactional annotation on persist() still be honored when I call persist() directly from within the save() method as above?


              The method called by Seam must be marked @Transactional, and calls to super. aren't intercepted by Seam. So yes, you must mark it @Transactional.

              4) Conversations
              What annotations/tag attributes do I need to add where to start a conversation when the "Employees" header is clicked or when an employee in the list is opened in a new browser tab?


              Probably best to do this in pages.xml using <begin-conversation /> - you need to decide where to do conversation demaraction - I wouldn't on clicking on employees, but on clicking on an employee to edit (and make sure to rejoin any existing conversation).

              Actually you really should use Seam2 (CR1 out over the next couple of days) as you get the events described above, and natural conversation id support which is ideal for this application.

              • 4. Re: Wannabe-Example needs help growing up
                amitev

                BTW off topic question - i'm using seam with pojos and SMPC (no ejb3). I'm not using @Transactional at all. Is this a problem?

                • 5. Re: Wannabe-Example needs help growing up
                  stephen.friedrich

                  Thanks again, Pete. Some details are clearer now. Overall we're still turning around in cycles.

                  An initial requirement was:
                  Employee list should only be refreshed when clicking on the "Employees" header.
                  If I don't immediately start a conversation then each single employee selection will reload the list, right?
                  I I do immediately start a conversation I don't know where to end it.

                  Also I still don't see how selecting an employee from the list can continue in the same conversation when opened in the same browser tab and can start a new conversation when opened in a new browser tab.

                  How does the "natural conversation id" deal with multiple tabs? I might specify a specific conversation id for editing employees. Does it get some kind of suffix when I open several employee areas in multiple tabs?

                  • 6. Re: Wannabe-Example needs help growing up
                    pmuir

                    Then yes, make the entire Employees conversational - you want to make the link not propagate the conversation, then add a <begin-conversation /> element to the page definition in pages.xml. You might consider a nested conversation for editing an employee along with manual flush mode to make that atomic. You don't need to end the conversation - it can either timeout OR you can specify *which* conversation to join when you click the link and make it so when Employees are clicked you go back to the same conversation (workspace). To do this you need to specify the conversation id (unique across the app, perhaps Employees-). Or you can use natural conversations in Seam2.

                    <conversation name="EditEmployee"
                     parameter-name="employeeId"
                     parameter-value="#{employee.id}"/>
                    
                    <page conversation="EditEmployee" />


                    is a natural conversation - so less wiring than manually specifying ids (but still the same thing under the bonnet).

                    • 7. Re: Wannabe-Example needs help growing up
                      matt.drees

                      Pete, correct me if I'm wrong with this.

                      "stephen.friedrich" wrote:

                      Also I still don't see how selecting an employee from the list can continue in the same conversation when opened in the same browser tab and can start a new conversation when opened in a new browser tab.


                      I don't think you can do that. It's the same link. So if ctrl-clicking creates a new conversation in a new tab, then normal-clicking will create a new conversation in the current tab. Which, from what I understand of your requirements, is fine.


                      "pete.muir@jboss.org" wrote:

                      You might consider a nested conversation for editing an employee along with manual flush mode to make that atomic.


                      Pete, would you mind elaborating on why a nested conversation would be beneficial?




                      • 8. Re: Wannabe-Example needs help growing up
                        matt.drees

                         

                        "amitev" wrote:
                        BTW off topic question - i'm using seam with pojos and SMPC (no ejb3). I'm not using @Transactional at all. Is this a problem?


                        That's what I do.

                        As long as you're living in the JSF lifecycle, (and using Seam-managed transactions, of course), you should be fine. If you have to do asynchronous things, you'll probably need @Transactional.

                        • 9. Re: Wannabe-Example needs help growing up
                          pmuir

                           

                          "matt.drees" wrote:
                          Pete, would you mind elaborating on why a nested conversation would be beneficial?


                          Read Jacob Orshalick's soon to be published article on nested conversations - it does a really good job of explaining why they are useful - much better than I could do in a forum post. Including what I was referring to here - taking a snapshot of state for sub edit conversations.