Conversation switcher for RichDropDownMenu (working component for u)
cbr600f.juan.juanylaura.es Sep 23, 2010 10:34 AMHi 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 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 @Name("conversationMenu") 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; //transient private String resultingConversationIdOrOutcome; @In FacesContext ctx; @Create public void buildMenu() { //Retrieve factory objects Application app = ctx.getApplication(); //creates components menu = (HtmlDropDownMenu) app.createComponent(HtmlDropDownMenu.COMPONENT_TYPE); createListItems(); for (final ConversationEntry conversation : conversationList) { HtmlMenuItem menuItem = (HtmlMenuItem) app.createComponent(HtmlMenuItem.COMPONENT_TYPE); menuItem.setId("convMenuItem" + conversation.getId()); HtmlLink link = createLinkForConversation(app, conversation); menuItem.getChildren().add(link); if (conversation.isCurrent()) { //menuItem.setStyle("text-decoration: overline underline"); menuItem.setStyleClass("currentConversation"); menuItem.setDisabled(true); } menu.getChildren().add(menuItem); } menu.setId("conversationMenu"); } 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.setValue(conversation.getDescription()); link.setPropagation("none"); //needs to retrieve old conv, don't prop. actual UIParameter fParam = new UIParameter(); fParam.setName("conversationIdOrOutcome"); //works with GET request fParam.setValue(conversation.getId()); link.getChildren().add(fParam); MethodExpression m = ef.createMethodExpression( ctx.getELContext(), "#{conversationMenu.select}", //method Void.TYPE, //retValue new Class<?>[0]); //arguments link.setActionExpression(m); return link; } private void createListItems() { //Scope.SESSION ConversationEntries conversationEntries = ConversationEntries.getInstance(); if (conversationEntries == null) { conversationList = Collections.EMPTY_LIST; } else { Set<ConversationEntry> orderedEntries = new TreeSet<ConversationEntry>(); orderedEntries.addAll(conversationEntries.getConversationEntries()); conversationList = new ArrayList<ConversationEntry>(conversationEntries.size()); for (ConversationEntry entry : orderedEntries) { if (entry.isDisplayable() && !Session.instance().isInvalid()) { conversationList.add(entry); } } } } 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) { buildMenu(); } 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(); ce.redirect(); } actualOutcome = null; } return actualOutcome; } }