10 Replies Latest reply on Nov 17, 2006 11:52 AM by anti spart

    How to impliment a Paged + Sorted DataTable (session) withou

    anti spart Newbie

      I have what must be a fairly typical usecase.

      1. User starts at search / list page.
      2. Selects an object from list and modifies it
      3. Returns to search / list page.

      Now, I can get this to work with something similar to the Booking example.

      SFSB

      @DataModel
      private List sessions;

      @Factory("playerSessions")
      @Observer("dataChanged")
      public String find() {
      query();
      return null;
      }

      Another SFSB:
      public String create() {
      ...
      events.raiseEvent("dataChanged");
      ...
      }

      The problem lies when the backing data is modified outside the application or by another user and hence the "dataChanged" event is never fired, the @Observer on the find() method is never triggered and the data is stale upon subsequent requests.

      Now, this problem goes away if I stick the SFSB that outjects the @DataModel into Event Scope, but then things like sort order and number of rows gets lost between requests.

      So, I thought maybe using PAGE scope for the @DataModel and Session Scope for the SFSB that outjects the @DataModel, but the factory method on the @DataModel still doesnt get invoked when the page is loaded the 2nd time. And, obviously, a paged scoped Action bean cannot be stateful.

      So, I'm not quite sure what to do. I know I can , for example, put an object in session scope that tracks sort order, etc for each list but that feels pretty ugly.

      Is there an excepted pattern for this?

        • 1. Re: How to impliment a Paged + Sorted DataTable (session) wi
          anti spart Newbie

          Well, my temporary solution is that instead of outjecting a @DataModel I just set the value of the DataTable to a method that always refreshs the list.

          view:
          <t:dataTable value="#{bean.sessions}" var="session">
          ...

          SFSB:

          private List sessions;

          public String find() {
          query();
          return null;
          }

          public List getSessions() {
          query();
          return sessions;
          }

          ----

          This seems to be a reasonably non-ugly solution, but it's not really the "Seam" way of doing things, so I'd really love to hear a better way to keep sort order, search, rowcount, values (session or conversation scope) while having the outjected @DataModel always be the latest (refreshed).

          I think, ideally, Seam would provide an annotation to acheive this. Thoughts?

          • 2. Re: How to impliment a Paged + Sorted DataTable (session) wi
            Pete Muir Master

            How are you dealing with pagination and sorting?

            A couple of suggestions, (2) is much better IMO:

            1) Create a new DataBinder for this, with isDirty() returning true always - you would only need to extend the DataModelBinder in Seam and override that method

            2) Outject the DataModel into Event scope but keep the pagination and sorting controls in Conversation scope. (N.B. Make sure the instance (java) variable and context variable are both refreshed)

            • 3. Re: How to impliment a Paged + Sorted DataTable (session) wi
              anti spart Newbie

              petemuir, thanks for the suggestion. You're right that #2 is the way to go, but I've tried that and got this exception:

              java.lang.IllegalArgumentException: @DataModel scope must be ScopeType.UNSPECIFIED or ScopeType.PAGE:

              If I could @DataModel(scope=ScopeType.EVENT)
              and If that means that this list would be refreshed (the factory method called) on every new event then that'd be perfect.

              I actually tried outjecting a List (instead of DataModel) to get around this issue but I found that while the list would be refreshed everytime I hit "search" (as it always did) it would still not be refreshed on subsequent page loads.

              • 4. Re: How to impliment a Paged + Sorted DataTable (session) wi
                Norman Richards Master

                Maybe you could try a page action to refresh the data?

                • 5. Re: How to impliment a Paged + Sorted DataTable (session) wi
                  Pete Muir Master

                   

                  "antispart" wrote:
                  I actually tried outjecting a List (instead of DataModel) to get around this issue but I found that while the list would be refreshed everytime I hit "search" (as it always did) it would still not be refreshed on subsequent page loads.


                  I suspect this was because although the context variable was being refereshed, the java variable in your SFSB wasn't null, so the @Factory wasn't being called. Did you have an @Out, or did you use @Factory("list", scope=ScopeType.EVENT)?



                  • 6. Re: How to impliment a Paged + Sorted DataTable (session) wi
                    anti spart Newbie

                     

                    "petemuir" wrote:
                    I suspect this was because although the context variable was being refereshed, the java variable in your SFSB wasn't null, so the @Factory wasn't being called. Did you have an @Out, or did you use @Factory("list", scope=ScopeType.EVENT)?


                    You're right. I switched to using @Factory(value="list", scope=ScopeType.EVENT) and that worked as expected. Now I have a new issue though:

                    The search list page also has an area that displays the current selection. It uses an a4j:commandButton to reRender the "currentSelection" outputPanel. This worked fine with the list in session scope, now that it's in Event scope everytime I select a new item from the list to be displayed in the "currentSelection" area the @Factory for the list gets called and it makes a new call to the DB.

                    I'm not sure why this should happen, b/c it's not reRendering the list outputPanel just the "currentSelection" one... so why would the list @Factory be called?

                    • 7. Re: How to impliment a Paged + Sorted DataTable (session) wi
                      anti spart Newbie

                       

                      "norman.richards@jboss.com" wrote:
                      Maybe you could try a page action to refresh the data?


                      Well, this would actually work great.

                      Is there a way to refresh a Page scoped list in an action method?

                      private List list;
                      
                      @Factory(value="list", scope=ScopeType.PAGE)
                      public List<Registration> getlist() {
                       query();
                       return list;
                      }
                      
                      public String sort()
                       query();
                       return null; // reRender table only
                      }
                      


                      Now, everything works great, it sorts properly, Page scope is working as expected, etc, except:

                      I can't outject a new value to Page scope in an Action method (or at least I dont know how to). When sort() is called, it sorts the list properly and sets the list property to the correctly sorted list but this isnt pushed out to page scope. Maybe this is because I'm returning null (the page should not be redisplayed, only a specific outputPanel should be reRendered). If I return a string then it's fine, the new data outjected to Page scope.

                      So, I guess my question is is there a way to refresh the Page scoped value from an action method?

                      • 8. Re: How to impliment a Paged + Sorted DataTable (session) wi
                        Norman Richards Master

                        In the above example, the factory only gets called once per page scope. That's it. There is nothing in that code that would ever cause the updated list to be pushed to the page scope. For that, you need to take the scope off of the factory and outject the list into page scope


                        If you took the scope off of the factory itself and outjected the datamodel into page scope, then when the list changed in sort() the results would show up. If you wanted it outjected on every access to the page (not just ones that call sort) then use a page action.

                        • 9. Re: How to impliment a Paged + Sorted DataTable (session) wi
                          anti spart Newbie

                           

                          "norman.richards@jboss.com" wrote:
                          If you took the scope off of the factory itself and outjected the datamodel into page scope, then when the list changed in sort() the results would show up. If you wanted it outjected on every access to the page (not just ones that call sort) then use a page action.


                          Hm, I guess the problem is that I need it both ways:

                          1) Refreshed data on every page load, so a browser refresh would bring up new data.

                          2) No refresh of data (no db call) when making using an ajax4jsf commandButton where the table is not to be reRendered.

                          Ideally, there would be a data refresh (db call) only in the case that i browse away from the page. so browsing to any other page would reset the DataModel.

                          I guess this would be a "page exit action"? Or is there another way of doing this?

                          • 10. Re: How to impliment a Paged + Sorted DataTable (session) wi
                            anti spart Newbie

                            I found something that works for me. I'm including it here in case it might help someone else (or perhaps someone could suggest a better solution):

                            @Stateful
                            @Name("testAction")
                            @Scope(ScopeType.CONVERSATION)
                            public class TestActionBean implements TestActionLocal {
                             @Logger private Log log;
                            
                             @In(create=true)
                             private EntityManager entityManagerSeam;
                            
                             private String searchString;
                             private int pageSize = 10;
                             private int page = 0;
                             private int pageCount = 0;
                             private String column = "testcolumn";
                             private boolean ascending = true;
                             private boolean refresh = false;
                            
                             public String find() {
                             setRefresh(true);
                             return null;
                             }
                            
                             public String sort(String newcolumn) {
                             if (column.equals(newcolumn))
                             ascending = !ascending;
                             column = newcolumn;
                             return find();
                             }
                            
                             public boolean isRefresh() {
                             return refresh;
                             }
                            
                             public void setRefresh(boolean refresh) {
                             this.refresh = refresh;
                             }
                            
                            
                             public String firstPage() {
                             page = 0;
                             return find();
                             }
                            
                             public String previousPage() {
                             page--;
                             return find();
                             }
                            
                             public String nextPage() {
                             page++;
                             return find();
                             }
                            
                             public String lastPage() {
                             page = pageCount - 1;
                             return find();
                             }
                            
                             public int getPage() {
                             return page;
                             }
                            
                             public void setPage(int page) {
                             this.page = page;
                             }
                            
                             public int getPageCount() {
                             return pageCount;
                             }
                            
                             public void setPageCount(int pageCount) {
                             this.pageCount = pageCount;
                             }
                            
                             public int getPageSize() {
                             return pageSize;
                             }
                            
                             public void setPageSize(int pageSize) {
                             this.pageSize = pageSize;
                             }
                            
                             public boolean isAscending() {
                             return ascending;
                             }
                            
                             public String getColumn() {
                             return column;
                             }
                            
                             public String getSearchString() {
                             return searchString;
                             }
                            
                             public void setSearchString(String searchString) {
                             this.searchString = searchString;
                             }
                            
                             public List get() {
                             ...
                            
                             return entityManagerSeam.createQuery("...")
                             .setParameter("search", searchPattern)
                             .setMaxResults(pageSize)
                             .setFirstResult(page * pageSize)
                             .getResultList();
                             }
                            


                            @Name("testList")
                            @Scope(ScopeType.PAGE)
                            public class TestList implements java.io.Serializable {
                             @Logger private Log log;
                            
                             @In(create=true)
                             private FacesMessages facesMessages;
                            
                             @In(create=true)
                             private TestActionLocal testAction;
                            
                             private List testList;
                            
                             private void refresh() {
                             try {
                             testList = (List) testAction.get();
                             } catch (Exception e) {
                             log.error("Exception", e);
                             facesMessages.addFromResourceBundle("errors_List");
                             }
                             }
                            
                             @Unwrap
                             public List create() {
                             if (testList==null || testAction.isRefresh()) {
                             testAction.setRefresh(false);
                             refresh();
                             }
                             return testList;
                             }
                            }
                            


                            With this setup ajax method invocations do not cause a new DB call (do not refresh the list) unless they're specifically meant to. The list is refreshed every time the page is accessed (but only once - by the testList==null test before hitting the db).

                            This works well for me and was the cleanest pattern I could find. Improvements very welcome though.