0 Replies Latest reply on Mar 10, 2009 6:46 AM by alpha88

    Seam's improvement

    alpha88
      Hi,

      I have some improvements about Seam. See the following:

      // import statements

      @Stateless
      @Name("messageService")
      public class MessageServiceImpl implements MessageService {

          @PersistenceContext
          private EntityManager em;

          // equals signal omitted
          @DataModel(scope=ScopeType.PAGE)
          private List<Message> messageList;

          @DataModelSelection
          @Out(required=false)
          private Message selectedMessage;

          public void getMessage(Integer messageId) {
              selectedMessage = em.find(Message.class, messageId);
          }

          public void getMessage() {
              // selectedMessage has been retrieved through @DataModelSelection, so no action is needed
              return;
          }

          @Factory("messageList")
          public void getAllMessage() {
              messageList = em.createQuery("from Message m order by m.info asc").getResultList();
          }
         
      }

      If i have requested a getAllMessage.seam as follow:

      <h:form>
          // throws @Factory("messageList") and fills out @DataModel messageList in MessageServiceImpl
          <h:dataTable value="#{messageList}" var="_message">
              <h:column>
                  #{_message.info}
              </h:column>
              <h:column>
                  <s:link value="#{_message.info}" action="#{messageService.getMessage}"/>
              </h:column>
              <h:column>
                  <h:commandLink value="#{_message.info}" action="#{messageService.getMessage(_message.id)}"/>
              </h:column>
          </h:dataTable>
      </h:form>

      See the code above: why do i use "s:link" when the action is getMessage and "h:commandLink" when the action is getMessage(_message.id) ?

      Answer is found in MessageServiceImpl:

      @DataModelSelection(scope=ScopeType.PAGE)
      private List<Message> messageList;

      ScopeType.PAGE says: remember my state at next postback OR save my state in the JSF UI tree. Ok. It happens "s:link" does not work if state is stored at JST UI tree. "h:commandLink" does. By the way, "h:commandLink" needs to be inserted inside a "h:form"; "s:link" does not; So, the postback needs remember which _message has been selected when you call "action=#{messageService.getMessage(_message.id)}". "h:commandLink" fulfill this requirement. If you use a "s:link" then you must call "action=#{messageService.getMessage}" with no arguments and use a @DataModelSelection to gain access to message selected in @DataModel.

      Ok, let's go. So, what's the problem ?

      first: if you use a getMessage method, what comes in mind ? here the answer: which message ? So we need a parameter, often a id in order to retrieve the requested message. Because "s:link" does not allows to pass parameters whether we use a @DataModel in PAGE scope, we have to adapt our getMessage with no arguments in order to use "s:link" because seam does not provide this funcionality.

      What i suggest ? Something like this:

      A "action" tag nested a "s:link" that support simple Java types - primitives and Wrappers

      <s:link>
          <action method="getMessage">
              <param>#{_message.id}</param>
          </action>
      </s:link>

      It will throws getMessage(Integer id)

      It could be use a JSF converter like this one

      <s:link>
          <action method="#{messageService.getMessage}">
              <param converterType="javax.faces.convert.IntegerConverter">#{_message.id}</param>
          </action>
      </s:link>

      Or allows multiple simple Java Types parameters like this one whether we use a composite id

      <s:link>
          <action method="#{messageService.getMessage}">
              <param converterType="javax.faces.convert.IntegerConverter">#{_message.id1}</param>
              <param converterType="javax.faces.convert.IntegerConverter">#{_message.id2}</param>
              <param converterType="javax.faces.convert.IntegerConverter">#{_message.id3}</param>
          </action>
      </s:link>

      It will throw getMessage(Integer id1, Integer id2, Integer id3)

      Cool, don't. This way we do not have to put @DataModel in PAGE scope. We could simply put it in a EVENT scope, which is lighter.

      Now, let's consider the following scenario: we have removed the @DataModel and @DataModelSelection. Again, there is another approach to @Factory method as follows

      // ATT: @DataModel can not be marked in method
      @Factory("messageList")
      public List<Message> getMessageList() {
          return em.createQuery("from Message m order by m.info asc").getResultList();
      }

      Notice that getMessageList returns a List<Message> (opposed to void in previous example). This way, we cannot use <h:dataTable because @Factory method has been not encapsulated in a @DataModel as follows

      // It does not work. Factory method needs to be encapsuled in a @DataModel.
      <h:dataTable value="#{messageList}" var="_message">

      What's the solution ?

      // ui facelets
      <ui:repeat var="_message" value="#{messageList}">
          #{message.info}
      </ui:repeat>

      With param tag, as propposed, we could be use as follows, even whether our MessageServiceImpl does not contain a @DataModel and @DataModelSelection:

      <ui:repeat var="_message" value="#{messageList}">
          <p>
              <s:link value="#{message.info}">
                 <action method="#{messageService.getMessage}">
                     <param>#{_message.id}</param>
                 </action>
              </s:link>
          </p>
      </ui:repeat>

      ATT: as we have left @DataModel behind, we can not use something like this

      <s:link action="#{messageService.getMessage}" because neither @DataTable nor @DataModelSelection lives in MessageServiceImpl anymore.

      Regards,
      Arthur Ronald F D Garcia - Java programmer
      (Natal - Brazil)