How use primefaces autocomplete into composite with preselected values? - jsf

I have a problem using autocomplete primefaces component inside of an UIInput composite. My goal is to init the application with preselected value in autocomplete field, showing a label accordingly. Below I show a test code
Page testPage.xhtml
<f:view id="view" locale="#{webSession.currentLanguage.locale}">
<h:head>
<title>...</title>
</h:head>
<h:body>
<h:form>
<utils:element/>
<p:autoComplete
value="#{testPage.attr}"
completeMethod="#{testPage.completeMethod}"
var="item"
itemLabel="#{item}"
itemValue="#{item}" />
</h:form>
</h:body>
</f:view>
Managed Bean TestPage.xhtml
#ManagedBean(name = "testPage")
#ViewScoped
public class TestPage {
private String attr;
#PostConstruct
public void init(){
attr = "value 1";
}
public String getAttr() {
return attr;
}
public void setAttr(String attr) {
this.attr = attr;
}
public List<String> completeMethod(String query) {
return Arrays.asList(new String[]{"1111", "2222", "3333"});
}
}
This approach works fine using the autocomplete directly on testPage.xhtml. However, I want to wrap this autocomplete in a element composite, as showed in following code
element.xhtml composite page
<ui:component xmlns="http://www.w3.org/1999/xhtml"
xmlns:p="http://primefaces.org/ui"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:cc="http://java.sun.com/jsf/composite">
<cc:interface componentType="elementComponent">
</cc:interface>
<cc:implementation>
<p:autoComplete
value="#{cc.attr}"
completeMethod="#{cc.completeMethod}"
var="item"
itemLabel="#{item}"
itemValue="#{item}" />
</cc:implementation>
</ui:component>
ElementComponent composite backing
#FacesComponent("elementComponent")
#ViewScoped
public class ElementComponent extends UIInput implements NamingContainer{
private String attr;
#Override
public String getFamily() {
return UINamingContainer.COMPONENT_FAMILY;
}
public List<String> completeMethod(String query) {
return Arrays.asList(new String[]{"value 1", "value 2", "value 3"});
}
#Override
public void encodeBegin(FacesContext context) throws IOException {
attr = "value 1";
}
public String getAttr() {
return attr;
}
public void setAttr(String attr) {
this.attr = attr;
}
}
But when I include the element composite in testPage.xhtml, the autocomplete does not show the preselected value (unlike the direct implementation). Is there any way to solve this? Maybe any method or attribute is missing in the implementation of FacesComponent? I tend to think this is a bug between the implementation of primefaces and the implementation of composite, but I am not sure.

The problem was the method encodeBegin(). This implementation require the encode of the component class, and the encode of the parent (UIInput).
Incorrect
#Override
public void encodeBegin(FacesContext context) throws IOException {
attr = "value 1";
}
Correct
#Override
public void encodeBegin(FacesContext context) throws IOException {
attr = "value 1";
super.encodeBegin();
}

Related

How to use jsf in "namespaced mode"

In a website we want to integrate some snippets provided by jsf-applications, think of a dashboard-app or a "portal-light". While analyzing the requirements we came across a blog-post by Arjan Tjims on jsf 2.3 new features, where he mentioned a new "namespaced mode":
In namespaced mode, which is specifically intended for Portlets but can be used in other environments as well, the partial response is given an id that's taken to be the "naming container id". All predefined postback parameter names (such as "javax.faces.ViewState", "javax.faces.ClientWindow", "javax.faces.RenderKitId", etc) are prefixed with this and the naming separator (default ":"). e.g. javax.faces.ViewState" becomes "myname:javax.faces.ViewState". Namespaced mode is activated when the UIViewRoot instance implements the NamingContainer interface.
Our application might be a usecase for that "namespaced mode", so we want to give it a try.
We built a MyUIViewRoot where we implemented NamingContainer and wrapped the original UIViewRoot-instance. We registered a MyViewHandler in faces-config.xml which handles the wrapping of the ViewRoot. For testing we used a simple counter-app with two <h:form>-elements (seems to be important).
We find that "namespace mode" seems to be activated, eg the javax.faces.ViewState indeed is prepended by some namespace and becomes j_id1:javax.faces.ViewState:0. But both actions do not work any more - the postback request does not restore the View any more but creates a new one. So with our simple approach we are missing something (btw, removing only the implements NamingContainer from MyUIViewRoot the counter-app works fine again).
Is there some documentation, a howto or a working example out there that we have overlooked?
How to activate "namespace mode" correctly? What have we missed to make the postback work again?
How can we make MyUIViewRoot to prepend the ViewState with myNamespace?
The application is running in a payara-5 application server.
Our index.xhtml:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:f="http://xmlns.jcp.org/jsf/core">
<h:head/>
<h:body>
<h:form id="counterForm">
<h:panelGrid columns="2">
<h:outputLabel value="Counter" />
<h:outputText value="#{counterUiController.counter}" />
</h:panelGrid>
<h:commandButton value="inc" action="#{counterUiController.incAction}">
<f:ajax execute="#form" render="#form" />
</h:commandButton>
</h:form>
<h:form id="resetForm">
<h:commandButton value="reset" action="#{counterUiController.resetAction}">
<f:ajax execute="#form" render=":counterForm" />
</h:commandButton>
</h:form>
</h:body>
</html>
The CounterUiController:
#Named
#ViewScoped
public class CounterUiController implements Serializable {
private int counter;
public int getCounter() {
return counter;
}
public void incAction() {
counter++;
}
public void resetAction() {
counter=0;
}
}
Our UIViewRoot-Implementation:
public class MyUIViewRoot extends UIViewRoot implements NamingContainer, FacesWrapper<UIViewRoot> {
private static final Logger LOG = Logger.getLogger(MyUIViewRoot.class.getName());
private UIViewRoot wrapped;
public MyUIViewRoot(UIViewRoot wrapped) {
this.wrapped = wrapped;
LOG.log(Level.INFO, "new instance created: {0}", this);
}
#Override
public UIViewRoot getWrapped() {
return wrapped;
}
#Override
public String createUniqueId() {
return wrapped==null ? null : wrapped.createUniqueId();
}
#Override
public void setId(String id) {
if( wrapped!=null ) {
wrapped.setId(id);
}
}
// all other methodes delegated to `wrapped` directly
}
Our ViewHandler:
public class MyViewHandler extends ViewHandlerWrapper {
private static final Logger LOG = Logger.getLogger(MyViewHandler.class.getName());
public MyViewHandler(ViewHandler wrapped) {
super(wrapped);
}
#Override
public UIViewRoot createView(FacesContext context, String viewId) {
UIViewRoot retval = super.createView(context, viewId);
retval = wrapIfNeeded(retval);
LOG.log(Level.INFO, "view created: {0}", retval);
return retval;
}
#Override
public UIViewRoot restoreView(FacesContext context, String viewId) {
UIViewRoot retval = super.restoreView(context, viewId);
retval = wrapIfNeeded(retval);
LOG.log(Level.INFO, "view restored: {0}", retval);
return retval;
}
private UIViewRoot wrapIfNeeded(UIViewRoot root) {
if (root != null && !(root instanceof MyUIViewRoot)) {
LOG.log(Level.INFO, "view wrapped: {0}, {1}", new Object[] { root, root.getId() });
return new MyUIViewRoot(root);
} else {
return root;
}
}
}
You need to replace the UIViewRoot not to wrap it.
public class NamespacedView extends UIViewRoot implements NamingContainer {
//
}
And then in faces-config.xml.
<component>
<component-type>javax.faces.ViewRoot</component-type>
<component-class>com.example.NamespacedView</component-class>
</component>
That's basically all. See also the Mojarra IT on this.

JSF lifecycle phase order

I've made small jsf app and a bit confused about lifecycle order, i'm getting unexpected NPE on postback even though i'm creating that object on every request. Can someone explain what's happening under the covers. Here is the code:
Entity.java
public class Entity {
private Long id;
private String property;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getProperty() {
return property;
}
public void setProperty(String property) {
this.property = property;
}
}
Bean.java
import javax.enterprise.inject.Model;
#Model
public class Bean {
private Long id;
private Entity entity;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Entity getEntity() {
return entity;
}
public void loadEntity() {
this.entity = new Entity();
}
}
edit.xhtml
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:f="http://xmlns.jcp.org/jsf/core"
xmlns:o="http://omnifaces.org/ui">
<f:view transient="true">
<f:metadata>
<f:viewParam name="id" value="#{bean.id}"/>
<f:viewAction onPostback="true" action="#{bean.loadEntity()}"/>
</f:metadata>
<h:body>
<o:form useRequestURI="true">
<h:inputText value="#{bean.entity.property}"/>
<h:commandButton value="Save"/>
</o:form>
</h:body>
</f:view>
</html>
Action methods like <f:viewAction action> are invoked during invoke application phase. The model values are updated during update model values phase. So, the entity is created one phase too late and still null when property needs to be set.
Get rid of the <f:viewAction> and make it a #PostConstruct method instead.
#PostConstruct
public void init() {
this.entity = new Entity();
}

Using SelectManyCheckbox with list of objects

Im trying to create a JSF page that lets the user select X amount of ingredients, and then saves those selected ingredients to a list.
Ingredient is an object with two values, String IngredientName and int ingredientPrice.
What I want to do is to create 1 selectItem per Ingredient in an IngredientList (dynamically sized), and then save the selected items to another list of ingredients.
I've tried doing this multiple different ways but either I get classcast exceptions or the checkboxes don't appear at all.
My Bean:
#ManagedBean
#SessionScoped
public class ManagedIngredientsBean {
#EJB
IngredientBean iBean;
private List<Ingredient> ingredientList;
private List<Ingredient> checkedOptions;
private List<SelectItem> selectList;
public ManagedIngredientsBean() {
}
public String createNew(){
ingredientList = iBean.getAllIngredients();
selectList = new ArrayList<SelectItem>(ingredientList.size());
for(Ingredient i : ingredientList){
selectList.add(new SelectItem(i.getIngredientName()));
}
return "createnew.xhtml";
}
public List<SelectItem> getSelectList() {
return selectList;
}
public void setSelectList(List<SelectItem> selectList) {
this.selectList = selectList;
}
public List<Ingredient> getCheckedOptions() {
return checkedOptions;
}
public void setCheckedOptions(List<Ingredient> checkedOptions) {
this.checkedOptions = checkedOptions;
}
public List<Ingredient> getIngredientList() {
return ingredientList;
}
public void setIngredientList(List<Ingredient> ingredientList) {
this.ingredientList = ingredientList;
}
#FacesConverter(value="userConverter")
public static class UserConverter implements Converter {
public Object getAsObject(FacesContext facesContext,
UIComponent component, String value) {
return value;
}
public String getAsString(FacesContext facesContext,
UIComponent component, Object o) {
Ingredient i = (Ingredient) o;
return i.getIngredientName();
}
}
}
IngredientBean used to get the Ingredient items from the persistence database and returning them as a list:
#Stateless(name = "IngredientEJB")
public class IngredientBean {
EntityManagerFactory entFactory;
EntityManager em;
public IngredientBean() {
entFactory = Persistence.createEntityManagerFactory("NewPersistenceUnit");
em = entFactory.createEntityManager();
}
public List<Ingredient> getAllIngredients(){
TypedQuery<Ingredient> ingQuery = em.createQuery("SELECT i FROM Ingredient i", Ingredient.class);
List<Ingredient> iList = ingQuery.getResultList();
return iList;
}
}
My JSF Page:
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://xmlns.jcp.org/jsf/core">
<h:head>
<title>Create New Order</title>
</h:head>
<h:body>
<h:form>
<h:selectManyCheckbox value = "#{managedIngredientsBean.checkedOptions}">
<f:converter converterId="userConverter"/>
<f:selectItem value = "#{managedIngredientsBean.selectList}" var = "item" itemLabel = "#{item.getIngredientName()}" itemValue = "#{item}"/>
</h:selectManyCheckbox>
</h:form>
</h:body>
</html>
I'm probably missing something obvious or simply misunderstanding how to use the selectManyCheckbox element but I'm completely stuck on how to fix this. Appreciate any answers on how I should be implementing this. :)
Edit: Forgot to mention, the createNew() method in the managed bean is called in the previous JSF page and redirects to this one.
your converter is broken.
first, it have to be a bean so must not be a static class.
second, it "should" be symmetric:
x.equals(c.getAsObject(ctx, comp, c.getAsString(ctx, component, x))); "should" be true.
#FacesConverter(value="userConverter")
public class UserConverter implements Converter
{
public Object getAsObject(FacesContext facesContext, UIComponent component, String value)
{
return database.loadIngredientByUniqueValue(value);
}
public String getAsString(FacesContext facesContext,UIComponent component, Object o)
{
Ingredient i = (Ingredient) o;
return i.getSomeUniqueValue();
}
}

Invoke ActionListener of Backing Component in Composite Component

try to write a composite component that allows mutltiple text inputs. I read that it is possible to define a backing component for a composite component, so I don't have to write a renderer nor a handler. What I couldn't figure out is how to delegate actions declared in composite's xhtml to the backing component. I guess i did not yet quite understand the concept of this. Does anybody has an Idea?
I am using Tomcat 7, EL 2.2, Spring 3, Mojarra 2.1.7
This is the way i'd like to use the component:
<custom:multiInput value="#{backingBean.inputList}"/>
Where the BackingBean.java holds a list of objects:
#Component
#Scope(value="view")
public class BackingBean {
...
private List<Foo> inputList;
....
}
The composite component multiInput.xhtml looks like this:
<cc:interface componentType="MultiInput">
<cc:attribute name="value" required="true" type="java.util.List" />
</cc:interface>
<cc:implementation>
<div id="#{cc.clientId}">
<h:dataTable value="#{cc.attrs.rows}" var="row">
<h:column>
<!-- here will be a selector component in order to select a foo object -->
</h:column>
<h:column>
<h:commandButton value="Remove Row">
<f:ajax execute=":#{cc.clientId}" render=":#{cc.clientId}" listener="#{cc.removeRow(row)}" />
</h:commandButton>
</h:column>
<h:column>
<h:commandButton value="Add Row" rendered="#{cc.lastRow}">
<f:ajax execute=":#{cc.clientId}" render=":#{cc.clientId}" listener="#{cc.addEmptyRow()}" />
</h:commandButton>
</h:column>
</h:dataTable>
</div>
</cc:implementation>
And here the backing component MultiInput.java:
#FacesComponent(value="MultiInput")
public class MultiInput extends UIInput implements NamingContainer, Serializable{
...
#Override
public String getFamily() {
return "javax.faces.NamingContainer";
}
#Override
public void encodeBegin(FacesContext context) throws IOException {
initRowsFromValueAttribute();
super.encodeBegin(context);
}
public void removeRow(MultiInputRow row) {
// why is this method is never reached when clicking remove button?
}
public void addEmptyRow() {
// why is this method is never reached when clicking add button?
}
public ListDataModel<MultiSelectRow> getRows() {
return (ListDataModel<MultiSelectRow>) getStateHelper().eval(PropertyKeys.rows, null);
}
private void setRows(ListDataModel<MultiSelectRow> rows) {
getStateHelper().put(PropertyKeys.rows, rows);
}
...
}
Now - removeRow and addEmptyRow is never called on MultiInput. An ajax request is triggered but it gets lost somewhere. Why?
I think the method signature for ajax listener methods should include the AjaxBehaviorEvent (unverified):
public void addEmptyRow(AjaxBehaviorEvent event) { ... }
and the f:ajax tag should just look like (without parentheses):
<f:ajax execute=":#{cc.clientId}" render=":#{cc.clientId}" listener="#{cc.addEmptyRow}" />
I'm struggling with the same problem here: using <f:ajax>, action listener methods in the composite component backing component are not executed.
It works partially when using Primefaces <p:commandButton>: the action listener method is correctly called in this case. However, the value of the 'process' attribute seems to be ignored in this case: All form fields are submitted, which causes validation failure in my case. If this is not a problem for you, you could try this.
I have created some test classes that reproduce the problem:
The composite component file testComponent.xhtml:
<html xmlns="http://www.w3c.org/1999/xhtml" xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:p="http://primefaces.org/xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:composite="http://java.sun.com/jsf/composite">
<composite:interface componentType="testComponent">
</composite:interface>
<composite:implementation>
<div id="#{cc.clientId}">
<h:panelGroup id="addPanel">
<h:inputText id="operand1" value="#{cc.operand1}"/>
<h:outputText value=" + " />
<h:inputText id="operand2" value="#{cc.operand2}"/>
<h:outputText value=" = " />
<h:outputText id="result" value="#{cc.result}" />
<br />
<p:commandButton id="testButton1" value="Primefaces CommandButton"
actionListener="#{cc.add()}" process="addPanel" update="addPanel"/>
<h:commandButton id="testButton2" value="f:ajax CommandButton">
<f:ajax execute="addPanel" render="addPanel" listener="#{cc.add()}" />
</h:commandButton>
</h:panelGroup>
</div>
</composite:implementation>
</html>
The backing component class:
package be.solidfrog.pngwin;
import javax.faces.component.FacesComponent;
import javax.faces.component.UINamingContainer;
import javax.faces.event.ActionEvent;
#FacesComponent("testComponent")
public class TestComponent extends UINamingContainer {
private Integer operand1, operand2, result;
public void add() {
System.err.println("Adding " + operand1 + " and " + operand2);
result = operand1 + operand2;
}
public Integer getOperand1() { return operand1; }
public void setOperand1(Integer operand1) { this.operand1 = operand1; }
public Integer getOperand2() { return operand2; }
public void setOperand2(Integer operand2) { this.operand2 = operand2; }
public Integer getResult() { return result; }
public void setResult(Integer result) { this.result = result; }
}
And the using page test.xhtml:
<!DOCTYPE html>
<html xmlns="http://www.w3c.org/1999/xhtml" xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html" xmlns:p="http://primefaces.org/ui"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:sf="http://java.sun.com/jsf/composite/solidfrog">
<h:body>
<h:messages />
<h:form id="testForm">
<h:outputLabel for="field1" value="Integer field: "/>
<h:inputText id="field1" value="#{testBean.field1}" />
<hr/>
<sf:testComponent id="testComponent" />
</h:form>
</h:body>
</html>
When clicking the first button and filling in the two operand fields, the result is correctly calculated. However, when a non-numeric value is entered in field1, there is a failed verification.
When using the second button, the action listener method is never calculated. However, the complete form is always submitted, so entering a non-numeric value in field1 triggers the error too.
I also tried p:ajax, which behaved the same as f:ajax.
I really have no idea what is happening here. Hopefully someone with more JSF wisdom can help out.
Although I don't understand everything in detail, I found a way to make it work. Since on each request a new instance of the backing component MultiInput is created, I had to save the state by overwriting saveState and restoreState. This way I could keep the property rows as a simple property. I also removed the encodeBegin method and overwrote getSubmittedValue.
At least this way it is working in Mojarra. When using MyFaces with default settings, I got some serialization exceptions, but I did not get deepter into that since we will stick on Mojarra. Also MyFaces seemed to be more stricked with ajax event listeners. It required "AjaxBehaviorEvent" parameters in listener methods.
Here the complete backing component MultInput:
#FacesComponent(value = "MultiInput")
public class MultiInput extends UIInput implements NamingContainer, Serializable {
ListDataModel<MultiInputRow> rows;
#Override
public String getFamily() {
return "javax.faces.NamingContainer";
}
#Override
public Object getSubmittedValue() {
List<Object> values = new ArrayList<Object>();
List<MultiInputRow> wrappedData = (List<MultiInputRow>) getRows().getWrappedData();
for (MultiInputRow row : wrappedData) {
if (row.getValue() != null) { // only if a valid value was selected
values.add(row.getValue());
}
}
return values;
}
public boolean isLastRow() {
int row = getRows().getRowIndex();
int count = getRows().getRowCount();
return (row + 1) == count;
}
public boolean isFirstRow() {
int row = getRows().getRowIndex();
return 0 == row;
}
public void removeRow(AjaxBehaviorEvent e) {
List<MultiInputRow> wrappedData = (List<MultiInputRow>) getRows().getWrappedData();
wrappedData.remove(rows.getRowIndex());
addRowIfEmptyList();
}
public void addEmptyRow(AjaxBehaviorEvent e) {
List<MultiInputRow> wrappedData = (List<MultiInputRow>) getRows().getWrappedData();
wrappedData.add(new MultiInputRow(null));
}
public ListDataModel<MultiInputRow> getRows() {
if (rows == null) {
rows = createRows();
addRowIfEmptyList();
}
return rows;
}
public List<Object> getValues() {
return (List<Object>) super.getValue();
}
private ListDataModel<MultiInputRow> createRows() {
List<MultiInputRow> wrappedData = new ArrayList<MultiInputRow>();
List<Object> values = getValues();
if (values != null) {
for (Object value : values) {
wrappedData.add(new MultiInputRow(value));
}
}
return new ListDataModel<MultiInputRow>(wrappedData);
}
private void addRowIfEmptyList() {
List<MultiInputRow> wrappedData = (List<MultiInputRow>) rows.getWrappedData();
if (wrappedData.size() == 0) {
wrappedData.add(new MultiInputRow(null));
}
}
#Override
public Object saveState(FacesContext context) {
if (context == null) {
throw new NullPointerException();
}
Object[] values = new Object[2];
values[0] = super.saveState(context);
values[1] = rows != null ? rows.getWrappedData() : null;
return (values);
}
#Override
public void restoreState(FacesContext context, Object state) {
if (context == null) {
throw new NullPointerException();
}
if (state == null) {
return;
}
Object[] values = (Object[]) state;
super.restoreState(context, values[0]);
rows = values[1] != null ? new ListDataModel<MultiInputRow>((List<MultiInputRow>) values[1]) : null;
}
/**
* Represents an editable row that holds a value that can be edited.
*/
public class MultiInputRow {
private Object value;
MultiInputRow(Object value) {
this.value = value;
}
public Object getValue() {
return value;
}
public void setValue(Object value) {
this.value = value;
}
}
}

Executing the ActionListener of a (Primefaces) menu item leads to an IllegalStateException

In JSF backed bean I got an IllegalStateException when the programmatically added action listener of a programmatically added Primefaces menu item is called. I tried both request and session scope but both are leading to the same error. Obviously there's need -- according to the stack trace -- to restore the view when an action listener is executed and I let my ToolbarBean implement Serializable with no different effect. What should I consider in order to get this to work?
User interface definition
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:p="http://primefaces.prime.com.tr/ui">
<h:head>
<title>TITLE</title>
</h:head>
<h:body>
<h:form>
<p:menu model="#{toolbarBean.model}" type="tiered" />
</h:form>
</h:body>
</html>
Backed bean providing the menu
#Named
#Scope("request")
public class ToolbarBean implements Serializable {
private static final long serialVersionUID = -8556751897482662530L;
public ToolbarBean() {
model = new DefaultMenuModel();
MenuItem item;
// Direct menu item
item = new MenuItem();
item.setValue("Menuitem 1");
item.addActionListener(new ActionListener() {
#Override
public void processAction(ActionEvent event)
throws AbortProcessingException {
System.out.println(event.toString());
}
});
model.addMenuItem(item);
item = new MenuItem();
item.setValue("Menuitem 2");
item.addActionListener(new ActionListener() {
#Override
public void processAction(ActionEvent event)
throws AbortProcessingException {
System.out.println(event.toString());
}
});
model.addMenuItem(item);
}
private MenuModel model;
public MenuModel getModel() {
return model;
}
}
Exception when clicking one of the menu buttons
javax.faces.FacesException: java.lang.IllegalStateException: java.lang.InstantiationException: id.co.sofcograha.baseui.ToolbarBean$1
at javax.faces.component.UIComponent.invokeOnComponent(UIComponent.java:1284)
at javax.faces.component.UIComponentBase.invokeOnComponent(UIComponentBase.java:673)
at javax.faces.component.UIComponent.invokeOnComponent(UIComponent.java:1290)
at javax.faces.component.UIComponentBase.invokeOnComponent(UIComponentBase.java:673)
at javax.faces.component.UIComponent.invokeOnComponent(UIComponent.java:1290)
at javax.faces.component.UIComponentBase.invokeOnComponent(UIComponentBase.java:673)
at javax.faces.component.UIComponent.invokeOnComponent(UIComponent.java:1290)
at javax.faces.component.UIComponentBase.invokeOnComponent(UIComponentBase.java:673)
at com.sun.faces.application.view.StateManagementStrategyImpl.restoreView(StateManagementStrategyImpl.java:297)
at com.sun.faces.application.StateManagerImpl.restoreView(StateManagerImpl.java:177)
at com.sun.faces.application.view.ViewHandlingStrategy.restoreView(ViewHandlingStrategy.java:119)
at com.sun.faces.application.view.FaceletViewHandlingStrategy.restoreView(FaceletViewHandlingStrategy.java:438)
at com.sun.faces.application.view.MultiViewHandler.restoreView(MultiViewHandler.java:144)
at javax.faces.application.ViewHandlerWrapper.restoreView(ViewHandlerWrapper.java:284)
at com.sun.faces.lifecycle.RestoreViewPhase.execute(RestoreViewPhase.java:182)
at com.sun.faces.lifecycle.Phase.doPhase(Phase.java:97)
at com.sun.faces.lifecycle.RestoreViewPhase.doPhase(RestoreViewPhase.java:107)
at com.sun.faces.lifecycle.LifecycleImpl.execute(LifecycleImpl.java:114)
at javax.faces.webapp.FacesServlet.service(FacesServlet.java:308)
EL (read: reflection) cannot access/construct anonymous classes. Refactor them into fullworthy classes.
So, replace
item.addActionListener(new ActionListener() {
#Override
public void processAction(ActionEvent event)
throws AbortProcessingException {
System.out.println(event.toString());
}
});
by
item.addActionListener(new FooActionListener());
and
public class FooActionListener implements ActionListener {
#Override
public void processAction(ActionEvent event)
throws AbortProcessingException {
System.out.println(event.toString());
}
}
See also:
How to invoke a JSF action on an anonymous class?
It looks like an additional restriction is that the ActionListener class have no contructor arguments, which kind of adds insult to injury here. As far as I can see the addActionListener probably just stores the class name of the object passed to it.
In fact if the intent was to make this listener unusable by preventing any data being passed to the listener from your backing bean they could hardly have done more.
You get another IllegalStateException if you try subclassing MenuItem.
You can't pass an object containing data to MenuItem as a value, it requires a String.
It doesn't seem to allow the listener as an inner class.
But I think I may have cracked it, by putting the needed data in the attributes map of the menu item.
Here's what I wound up with:
public class MenuSelectListener implements ActionListener {
public static final String MENU_ACTION_KEY = "menu.action.delegate";
private final static Log log = LogFactory.getLog(MenuSelectListener.class);
#Override
public void processAction(ActionEvent ae) throws AbortProcessingException {
System.out.println("listener invoked");
if (ae.getComponent() instanceof MenuItem) {
Runnable delegate = (Runnable) ae.getComponent().getAttributes().get(MENU_ACTION_KEY);
if(delegate != null)
delegate.run();
else
log.error("Menu action has no runnable");
} else {
log.error("Listener, wrong component class: " + ae.getComponent().getClass().getName());
}
}
}
To set up an item:-
item.setValue("Cancel");
sm.getChildren().add(item);
item.addActionListener(new MenuSelectListener());
item.getAttributes().put(MenuSelectListener.MENU_ACTION_KEY, new MiscActionDelegate(MiscActions.close));
With:
private class MiscActionDelegate implements Runnable, Serializable {
(works as an inner class, but cannot be anonymous).

Resources