1 Reply Latest reply on Sep 23, 2010 10:39 AM by Juan Francisco Ara Monzón

    Conversation switcher for RichDropDownMenu (working component for u)

    Juan Francisco Ara Monzón Newbie

      Hi all, first poster, no question but solution ;)

      I've been struggling a lot trying to implement the posibility to display the conversation list in a RichDropDownMenu to allow users view their Current Windows like on many multi window desktop aplications.

      No much information was out there, the main problems where painting the <rich:menuItem/> elements. Inside <ui:repeat/> it was impossible (wrong phase!) and using c:forEach gave unexpeted results. At last, I made my own menu component in order to bind (JSF binding) it in a menu. It's not much customizable yet, but since I've no time to improve it, I'm donating it here.

      Keep in mind that if this component is in hot / main seamgen) sources won't work with JBoss 5 (diferent classloaders... plenty threads elsewhere). In order to make it work, place it under a JAR (or in your own custom components JAR) and add it to your_project/lib folder.

      Then edit the file deployed-jars-war.list under the root of your project folder and add a line for your newly created jar.

      Much of the code is inherited copied from org.jboss.seam.faces.Switcher class but due to the different scopes I was unable to use it in JSF binding (custom seam scopes like Scope.PAGE and Scope.Conversation cannot be used in bindings.

      You'll need the following libraries to compile:

      jsf-api.jar         (JSF)
      el-api.jar          (EL for Method Bindings)
      richfaces-ui.jar    (HtmlDropDownMenu)
      richfaces-impl.jar  (Dependencies from richfaces-ui.jar components)
      jboss-seam-ui       (renders links with HtmlLink from seam in order to rip the current conversation when 
                           switching to another conversation)

      I've been out of JSF development like 5 years (omg I started with Shale!) so be easy with me.

      Enjoy and distribute it if you want:

      package org.cbr600f.jsf.seam.components.richfaces;
      import java.io.Serializable;
      import java.util.ArrayList;
      import java.util.Collections;
      import java.util.List;
      import java.util.Set;
      import java.util.TreeSet;
      import javax.el.ExpressionFactory;
      import javax.el.MethodExpression;
      import javax.faces.FacesException;
      import javax.faces.application.Application;
      import javax.faces.component.UIParameter;
      import javax.faces.context.FacesContext;
      import org.jboss.seam.annotations.Create;
      import org.jboss.seam.annotations.In;
      import org.jboss.seam.annotations.Name;
      import org.jboss.seam.annotations.web.RequestParameter;
      import org.jboss.seam.core.ConversationEntries;
      import org.jboss.seam.core.ConversationEntry;
      import org.jboss.seam.core.Manager;
      import org.jboss.seam.ui.component.html.HtmlLink;
      import org.jboss.seam.web.Session;
      import org.richfaces.component.html.HtmlDropDownMenu;
      import org.richfaces.component.html.HtmlMenuItem;
       *  Scope.EVENT bean under the name "conversationMenu" that has the following
       *  properties and methods:
       *      - menu: #{conversationMenu.menu}
       *              HtmlDropDownMenu with nested menu items for active
       *              Long running conversations.
       *              In the menu, the Current Conversation is displayed using the
       *              styleClass "currentConversation" and the entry is displayed but
       *              disabled
       *              The MenuItems have the following generated ID:
       *              convMenuItem+#{conversation.id} and thus could be rerendered
       *              on Ajax Requests.
       *      - conversationIdOrOutcome : #{conversationMenu.conversationIdOrOutcome}
       *              The conversation to switch to if invoking #{conversationMenu.select}
       *              String containing the ID of the selected conversation.
       *              Can be passed as GET parameter under the name "conversationIdOrOutcome"
       *              so it can be used in form POST or GET requests.
       *      - select: #{conversationMenu.select}
       *              Method that switches to the selected conversation in
       *              #{conversationMenu.conversationIdOrOutcome} property.
       *              According to Seam documentation, switches to the last
       *              valid view-id in the selected conversation by performing a
       *              redirect.
       *              Beans on the conversation will be restored and the switch will
       *              be transparent for the user.
       *  Notes: Seam don't permit Scope.PAGE or Scope.CONVERSATION (custom seam scopes) for
       *  JSF binding components.
       *  Most of the code is inherited from the switcher class by Seam 2.1.0 GA
       *  The Switcher actions and components were not reused due to the Scope.PAGE
       *  being invalid in bindings.
       *  report bugs to ##########
       * @author ##########
      //@Scope(ScopeType.PAGE) <<-- DON'T WORK WITH JSF bindings
      public class ConversationMenu implements Serializable {
          //menu component for binding.
          private HtmlDropDownMenu menu;
          //exposes list for rendering (or not) menu
          private List<ConversationEntry> conversationList;
          @RequestParameter //works with form/GET and standard GET also
          //selected conversation
          private String conversationIdOrOutcome;
          private String resultingConversationIdOrOutcome;
          FacesContext ctx;
          public void buildMenu() {
              //Retrieve factory objects
              Application app = ctx.getApplication(); //creates components
              menu = (HtmlDropDownMenu) app.createComponent(HtmlDropDownMenu.COMPONENT_TYPE);
              for (final ConversationEntry conversation : conversationList) {
                  HtmlMenuItem menuItem = (HtmlMenuItem) app.createComponent(HtmlMenuItem.COMPONENT_TYPE);
                  menuItem.setId("convMenuItem" + conversation.getId());
                  HtmlLink link = createLinkForConversation(app, conversation);
                  if (conversation.isCurrent()) {
                      //menuItem.setStyle("text-decoration: overline underline");
          private HtmlLink createLinkForConversation(Application app, final ConversationEntry conversation) throws FacesException {
              ExpressionFactory ef = app.getExpressionFactory(); //creates MB expresions
              HtmlLink link = (HtmlLink) app.createComponent(HtmlLink.COMPONENT_TYPE);
              link.setId("convLink" + conversation.getId());
              link.setPropagation("none"); //needs to retrieve old conv, don't prop. actual
              UIParameter fParam = new UIParameter();
              fParam.setName("conversationIdOrOutcome"); //works with GET request
              MethodExpression m = ef.createMethodExpression(
                      "#{conversationMenu.select}", //method
                      Void.TYPE,  //retValue
                      new Class<?>[0]); //arguments
              return link;
          private void createListItems() {
              ConversationEntries conversationEntries = ConversationEntries.getInstance();
              if (conversationEntries == null) {
                  conversationList = Collections.EMPTY_LIST;
              } else {
                  Set<ConversationEntry> orderedEntries = new TreeSet<ConversationEntry>();
                  conversationList = new ArrayList<ConversationEntry>(conversationEntries.size());
                  for (ConversationEntry entry : orderedEntries) {
                      if (entry.isDisplayable() && !Session.instance().isInvalid()) {
          private String getLongRunningConversationId() {
              Manager manager = Manager.instance();
              if (manager.isLongRunningConversation()) {
                  return manager.getCurrentConversationId();
              } else if (manager.isNestedConversation()) {
                  return manager.getParentConversationId();
              } else {
                  //TODO: is there any way to set it to the current outcome, instead of null?
                  return null;
          public String getConversationIdOrOutcome() {
              return resultingConversationIdOrOutcome == null
                      ? getLongRunningConversationId()
                      : resultingConversationIdOrOutcome;
          public void setConversationIdOrOutcome(String selectedId) {
              this.conversationIdOrOutcome = selectedId;
          public HtmlDropDownMenu getMenu() {
              if (menu == null) {
              return menu;
          public void setMenu(HtmlDropDownMenu menu) {
              this.menu = menu;
          //Selects and redirects to selected conversation.
          public String select() {
              boolean isOutcome = conversationIdOrOutcome == null
                      || (!Character.isDigit(conversationIdOrOutcome.charAt(0)) && conversationIdOrOutcome.indexOf(':') < 0);
              String actualOutcome;
              if (isOutcome) {
                  resultingConversationIdOrOutcome = conversationIdOrOutcome;
                  actualOutcome = conversationIdOrOutcome;
              } else {
                  ConversationEntry ce = ConversationEntries.instance().getConversationEntry(conversationIdOrOutcome);
                  if (ce != null) {
                      resultingConversationIdOrOutcome = ce.getId();
                  actualOutcome = null;
              return actualOutcome;