10 Replies Latest reply on Mar 7, 2007 3:29 AM by newlukai

    How to use ice:dataTable and @DataModel?

      Hi there,

      I've a problem that's hard to explain. But give me a chance ;)

      My application manages testactions. A testaction can be viewed by a tester (who executes the test), a developer (who fixes the software if a testaction failed) and a validator (who validates that the fix works). A user can now choose which page he wants to see since he is tester for this software and developer for another one and so on. Every page lists all the testactions to do for the user. The testaction objects are the same.

      On such a page the user has the choice to apply a filter on that list. E g. just list all the testactions with a high priority. Those filters are realised with selectOneMenus. When the user changes a filter, the list is updated according to the new filter.

      From that list the user selects a testaction by clicking its ID and is redirected to a page where he can edit the testaction.

      I hope you can follow.

      Because those lists almost do the same thing I introduced a superclass that manages all the list stuff like sorting (yes, the list is sortable) or the selection of a testaction.

      This superclass has three subclasses (I don't know how to phrase it, I'm German) which take care of the special things for tester, developer and validator. E. g. and especially the database query to retrieve all that testactions is different for each user role.

      OK. I think it's time for some code.

      Here's the superclass:

      public class TestactionHandling {
       protected List<Testaction> testactions;
      // @DataModelSelectionIndex
      // workaround: Seam can't handle @DataModelSelection AND
      // @DataModelSelectionIndex in one class
       protected int testactionIndex;
       @DataModelSelection
       protected Testaction testaction;
      
       @In(required=false)
       @Out(required=false, scope=ScopeType.SESSION)
       protected Testaction currentTestaction;
      
       protected List<Testaction> currentTestactions;
       protected int currentTestactionIndex;
      
       protected String column = "id";
       protected boolean ascending = true;
      
       private long lastRefreshTime;
       private final long REFRESH_PERIOD = 60000; //1 minute
      
      
       public String select() {
       currentTestactions = testactions;
      // currentTestaction.clone();
      // currentTestactionIndex = testactionIndex;
      // workaround
      
       for(Testaction ta : currentTestactions) {
       if(ta.getID() == currentTestaction.getID()) {
       currentTestactionIndex = currentTestactions.indexOf(ta);
       break;
       }
       }
       return "selected";
       }
      
       public String saveTestaction() {
       applyChanges();
       return currentTestactions.size() == 0 ? "backToList" : "browse";
       }
      
       public String saveTestactionAndNext() {
       applyChanges();
       return nextTestaction();
       }
      
       public String nextTestaction() {
       String outcome = shiftTestaction(1) ? "browse" : "backToList";
       return outcome;
       }
      
       public String prevTestaction() {
       String outcome = shiftTestaction(-1) ? "browse" : "backToList";
       return outcome;
       }
      
       public boolean isAscending() {
       return ascending;
       }
      
       public void setAscending(boolean ascending) {
       if(ascending != this.ascending) {
       testactions = null;
       }
       this.ascending = ascending;
       }
      
       public String getColumn() {
       return column;
       }
      
       public void setColumn(String column) {
       if(!column.equals(this.column)) {
       testactions = null;
       }
       this.column = column;
       }
      
       public int getTestactionIndexHR() {
       return currentTestactionIndex + 1;
       }
      
       public int getTestactionsSize() {
       int result = (currentTestactions == null) ? 0 : currentTestactions.size();
       return result;
       }
      
       protected void applyChanges() {
       }
      
       protected boolean shiftTestaction(int distance) {
       if(currentTestactions.size() == 0) {
       return false;
       }
       currentTestactionIndex += distance;
       while(currentTestactionIndex >= currentTestactions.size()) {
       currentTestactionIndex -= currentTestactions.size();
       }
       while(currentTestactionIndex < 0) {
       currentTestactionIndex += currentTestactions.size();
       }
       testaction = currentTestactions.get(currentTestactionIndex);
       updateTestactionForProtocol();
       return true;
       }
      
       protected boolean shouldRefresh() {
       if((System.currentTimeMillis() - REFRESH_PERIOD) > lastRefreshTime) {
       refresh();
       lastRefreshTime = System.currentTimeMillis();
       return true;
       }
       return false;
       }
      
       public String refresh() {
       testactions = null;
       return "";
       }
      
       private void updateTestactionsListAndSelectionModels() {
       currentTestactions.set(currentTestactionIndex, currentTestaction);
       testaction = currentTestaction;
       testactions = currentTestactions;
       }
      }


      And here is one of the subclasses:
      @Stateful
      @Scope(ScopeType.SESSION)
      @LoggedIn
      @Name("testactionDeveloper")
      public class TestactionDeveloperAction extends TestactionHandling implements
      TestactionDeveloper, Serializable {
       @In(required=false)
       private Release selectedRelease;
      
       @In(required=false)
       private Priorityclass selectedPriority;
      
       @In(required=false)
       private Severityclass selectedSeverity;
      
       @In(required=false)
       private User selectedUser;
      
       private Long lastSelectedReleaseID;
       private String lastSelectedPriorityID;
       private Integer lastSelectedSeverityID;
       private String lastSelectedUserID;
      
      
       @Factory("testactionsForDeveloper")
       public void initTestactions() {
       if(hasAFilterChanged() || shouldRefresh() || testactions == null) {
       testactions = EMHelper.execQuery(em, "from Testaction
       where (TACT_REV_ID=2 or TACT_REV_ID=9) and
       (TACT_BFV_ID=4 or TACT_BFV_ID=99)" + generateFilterStatements() +
       "order by TACT_ID asc"
       );
      
       updateFilterHelpers();
       }
       Collections.sort(testactions, new TestactionComparator(column, ascending));
       }
      
       @DataModel//(scope=ScopeType.PAGE)
       public List<Testaction> getTestactionsForDeveloper() {
       initTestactions();
       return testactions;
       }
      }


      Here is the page for that subclass:
      <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:ice="http://www.icesoft.com/icefaces/component"
       template="template.xhtml">
      
      <ui:define name="content">
       <h1>#{ares_messages.header_listForDeveloper}</h1>
       <ice:form>
       <div id="release">
       <table cellpadding="0" cellspacing="0" border="0" class="filter">
       <thead>
       <tr>
       <th colspan="4">
       <h:outputText value="#{ares_messages.filter_header}" />
       </th>
       </tr>
       <tr>
       <th><h:outputText value="#{ares_messages.filter_release}" /></th>
       <th><h:outputText value="#{ares_messages.filter_severity}" /></th>
       <th><h:outputText value="#{ares_messages.filter_priority}" /></th>
       <th><h:outputText value="#{ares_messages.filter_user}" /></th>
       </tr>
       </thead>
       <tr>
       <td>
       <ice:selectOneMenu value="#{releaseSelector.selectedReleaseNumber}"
       valueChangeListener="#{releaseSelector.valueChanged}"
       partialSubmit="true">
       <f:selectItems value="#{releaseSelector.releaseItems}" />
       </ice:selectOneMenu>
       </td>
       <td>
       <ice:selectOneMenu
       value="#{severitySelector.selectedSeverityNumber}"
       partialSubmit="true">
       <f:selectItems value="#{severitySelector.severityItems}" />
       </ice:selectOneMenu>
       </td>
       ...
       </tr>
       </table>
      
       <br />
       <ice:commandButton action="#{testactionDeveloper.refresh}"
       image="img/refresh.gif" styleClass="graphical"
       title="#{ares_messages.tooltip_refreshList}" />
       </div>
      
       <ice:dataTable var="testaction_var"
       value="#{testactionDeveloper.testactionsForDeveloper}"
       sortColumn="#{testactionDeveloper.column}"
       sortAscending="#{testactionDeveloper.ascending}">
       <f:facet name="header">
       <h:outputText value="#{ares_messages.label_ResultCount}:
       #{testactionsForDeveloper.rowCount}" />
       </f:facet>
       <h:column>
       <f:facet name="header">
       <ice:commandSortHeader columnName="id">
       <h:outputText value="#{ares_messages.label_testaction_ID}" />
       </ice:commandSortHeader>
       </f:facet>
       <ice:commandLink value="#{testaction_var.ID}"
       action="#{testactionDeveloper.select}" styleClass="rightAlignment" />
       </h:column>
      ...


      I think it's enough ;)
      Now my problem: I don't get it working properly with ICEfaces. It worked with Tomahawk, but doesn't with ICEfaces.

      There are three ways I tried:


      • The way described in the WIKI for Tomahawk's dataTable. The @DataModel and the @Factory are in the subclass and the dataTable refers to the @DataModel's name ("#{testactionForDevelopers}").
        The result is that when the user changes the filter, the list applies the filter values that were applied before. E. g. the user selects priority A, the list doesn't change. The user select priority B, then the list shows priority A and so on.
      • The way it's done in the Seam icefaces example. @Factory in the subclass, @DataModel on the testactions member in the superclass and the dataTable refers to the @Factory's name.
        The list is empty.
      • The way it's done in the ICEfaces TimeZone example. Remove @Factory and put the @DataModel on the testactions member in the superclass. The dataTable refers to "#{testactionDeveloper.testactionsForDeveloper}".
        The list can be filtered like it should. But when the user selects a testaction always the first one in the list is displayed. I think Seam can't determine the Index in this constellation.


        I've to excuse for this monster, but I don't know how to solve my problem and I don't want to give you too little information to help me.

        Thanks in advance
        Newlukai

        BTW: Did anyone get this ice:menuBar working with more than one level?


        • 1. Re: How to use ice:dataTable and @DataModel?

          I'm wondering if there's anybody using ice:dataTable, @DataModel and @DataModelSelectionIndex without any problems.

          Imagine you have a list rendered by an ice:dataTable. One column consists of h:commandLinks. The action method gathers information about the selected item and returns an outcome that tells JSF to show a page where the selected item is displayed more detailed.
          Just like a list of users. You click on a username and on the next page you see his address or something.

          The problem is that the @DataModelSelection(Index) is injected with the default value.

          • 2. Re: How to use ice:dataTable and @DataModel?
            ector7280

            Anyone,

            Is there a way to get a sortable icefaces dataTable to work with Seam or
            does this need to be added to the JIRA?

            thanks,

            JR

            • 3. Re: How to use ice:dataTable and @DataModel?

              Hi,

              I still don't get this working.

              Regards
              Newlukai

              • 4. Re: How to use ice:dataTable and @DataModel?
                danoakland

                I wouldn't say that it's without problems, but I did get the sortable table working with Seam 1.1.5 (and now upgraded to 1.1.6). I haven't gone to 1.2 yet.

                The trick that I had to use is to provide the ice:dataTable with the actual DataModel itself, not the outjected variable from Seam. For example, I have a bean that manages a table of companies that can be sorted by name or main office location:

                @Stateful
                @Name("companyList")
                @Scope(ScopeType.SESSION)
                public class CompanyListBean extends CompanyList {
                 ...
                
                 @DataModel
                 private List<CompanyListObject> companies;
                
                 ...
                
                 public boolean isAscending() {
                 // getter
                 }
                
                 public void setAscending(boolean ascending) {
                 // setter
                 }
                
                 public String getSortColumn() {
                 // getter
                 }
                
                 public void setSortColumn(String columnName) {
                 // setter
                 }
                
                 ...
                
                 public javax.faces.model.DataModel getResults() {
                 if (companies.size() > 1) {
                 // do sort logic if necessary...
                 java.util.Collections.sort(companies, someComparator);
                 }
                 return (javax.faces.model.DataModel) Contexts
                 .getSessionContext().get("companies");
                 }
                


                Then in the Faces view you use ice:dataTable like this:

                <ice:dataTable value="#{companyList.results}" var="result"
                 sortColumn="#{companyList.sortColumn}"
                 sortAscending="#{companyList.ascending}" ... >
                 <ice:column>
                 <f:facet name="header">
                 <ice:commandSortHeader columnName="name" arrow="true">
                 <ice:outputText value="Company Name" />
                 </ice:commandSortHeader>
                 </f:facet>
                 <h:outputLink value="#{result.url}">
                 #{result.name}
                 </h:outputLink>
                 </ice:column>
                 <ice:column>
                 <f:facet name="header">
                 <ice:commandSortHeader columnName="city" arrow="true">
                 <ice:outputText value="Main Office" />
                 </ice:commandSortHeader>
                 </f:facet>
                 <h:outputText>
                 #{result.city}
                 </h:outputText>
                 </ice:column>
                </ice:dataTable>
                


                and it works for me. I may have omitted some stuff from my example there, but hopefully you get the gist of it...

                It seems that Seam doesn't outject the updated DataModel on a partial submit, but the "getResults" method will.

                • 5. Re: How to use ice:dataTable and @DataModel?
                  tony.herstell1

                  I have set up and debugged a Sortable Paged Data Table using IceFaces.

                  It seems to work fine.

                  If you are still stuck I can post code and/or a link to the development box running it.

                  • 6. Re: How to use ice:dataTable and @DataModel?
                    tony.herstell1

                    aha. I didnt use @DataModelSelectionIndex...
                    I just added buttons on each line to go to actions:

                    <s:button value="#{messages.button_read}"
                     action="#{cRUDOrganisationController.startRead(the_organisation)}"/>
                    <s:button value="#{messages.button_update}"
                     action="#{cRUDOrganisationController.startUpdate(the_organisation)}"/>
                    <s:button value="#{messages.button_delete}"
                     action="#{cRUDOrganisationController.startDelete(the_organisation)}"/>
                    <s:button value="#{messages.button_add_image}"
                     action="#{uploadController.startUpload(the_organisation)}"/>
                    


                    As "the-organisation" is in scope then I pass the whole object to my action.

                    None of this fiddling with Indexes...

                    Just lasy I guess.

                    • 7. Re: How to use ice:dataTable and @DataModel?
                      danoakland

                      The example I posted above does make use of the @DataModelSelection annotation, although I left it out of the example (stupidly). I make use of the "companyList" via the @In annotation in another bean, and I can call a getter which returns the currently selected row of the DataModel. I'm pretty sure the trick is just to set the "value" attribute of the dataTable to a getter method that will supply an updated model even on a partial submit.

                      • 8. Re: How to use ice:dataTable and @DataModel?
                        tony.herstell1

                        I map it to the "model"...
                        @DataModel
                        private Organisation[] organisations;

                        (Easier to work with IceFaces with an Array!)

                        Thats all..

                        From Page...

                        <ice:dataTable id="organisationsTableComponent" value="#{organisations}" var="the_organisation" sortColumn="#{findOrganisationController.sortColumn}" sortAscending="#{findOrganisationController.ascending}" rows="10">
                        


                        From backing Bean....
                        /**
                         * @author Tony Herstell
                         * @version $Revision: 1.5 $ $Date: 2007-02-13 04:46:40 $
                         */
                        @CacheConfig(idleTimeoutSeconds=1800)
                        @Stateful
                        @Name("findOrganisationController")
                        @Scope(ScopeType.SESSION)
                        public class FindOrganisationControllerImpl implements Serializable, FindOrganisationController {
                        
                         @PersistenceContext(type = EXTENDED)
                         private EntityManager em;
                        
                         @DataModel
                         private Organisation[] organisations;
                        
                         @Logger
                         private Log log;
                        
                         @In(create=true)
                         @Out
                         private Organisation organisation;
                        
                         @In(value="organisationSearchCriteria", create=true)
                         private Organisation organisationSearchCriteria;
                        
                         @In
                         private Map<String, String> messages;
                        
                         /*
                         * GUI Support
                         */
                         public static final String ALL_OPTION = "all";
                         public static final String FILTERED_OPTION = "filtered";
                         private String selectedSearchCriteriaPanel = ALL_OPTION;
                        
                         private String sortColumn;
                         private boolean ascending = false;
                        
                         private boolean physicalDontCare = true; // Used for filtering only.
                         private static final String PHYSICAL_VALUE_REQUIRED = "physical_required";
                         private static final String PHYSICAL_VALUE_NOT_REQUIRED = "physical_not_required";
                         private String physicalValueRequiredPanel = PHYSICAL_VALUE_REQUIRED;
                        
                         /*
                         * Searh String
                         */
                         private static final String ALL_QUERY = "select o from Organisation o";
                        
                         private Query query;
                        
                         /*
                         * Methods
                         */
                         @Factory("organisations") // Is run whenever this bean is created (we know its going to be the "All" query.
                         public void initOrganisations() {
                         log.info("> initOrganisations");
                         applyAllQuery();
                         log.info("< initOrganisations");
                         }
                        
                        




                        • 9. Re: How to use ice:dataTable and @DataModel?

                          Thanks for your answers. But the thing is that - under certain circumstances - the sorting does work in my application.

                          My problem is a little bit more complex. Imagine that you just want to show up companies (danoakland) or organisations (Tony) that match certain criterias which can be varied by the user with selectOneMenus. Just think of one selectOneMenu which gives the user the option to filter for companies which have just 0-25, 25-50 or >50 employees.
                          Implementing this let me decide between two options how the application would act:

                          1 - The filtering works correctly, which means that the dataTable always show the entries matching the current filters. But: @DataModelSelection(Index) doesn't work.
                          2 - @DataModelSelection(Index) works, but then the dataTable shows the entries matching the filters the request before the current request.

                          It's just driving me crazy.

                          But I would thank you, Tony, you just give me some hope, that I would get it to work with your use of "the_organisation" in the buttons. Does this work in normal ice:commandLinks or does it only work with s:buttons?
                          Anyhow, it shows me I have to attend to EL again.

                          Thanks in advance
                          Newlukai

                          • 10. Re: How to use ice:dataTable and @DataModel?

                            Great. It works. Thank you.