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;
}
}