Adding an attribute to primefaces by extending commandButton component - jsf

Primefaces 8
Java 11
I would like to add a controller attribute to a commandButton which takes in page bean instance as value . I can then use this page bean in command button renderer to disable the button by calling method on page bean.
Component Bean class
#FacesComponent(CustomCommandButton.COMPONENT_TYPE)
public class CustomCommandButton extends CommandButton
{
private static final Logger logger = Logger.getInstance(CustomCommandButton.class);
public static final String COMPONENT_FAMILY = "com.xxx.faces.component";
public static final String COMPONENT_TYPE = "com.xxx.faces.component.CustomCommandButton";
private enum PropertyKeys {
controller
}
private IpmsBean controller;
#Override
public String getFamily() {
return COMPONENT_FAMILY;
}
#Override
public String getRendererType() {
return CustomCommandButtonRenderer.RENDERER_TYPE;
}
public CustomCommandButton()
{
super();
}
public void setController(IpmsBean bean)
{
getStateHelper().put(PropertyKeys.controller, bean);
this.controller=bean;
}
public IpmsBean getController()
{
return (IpmsBean) getStateHelper().eval(PropertyKeys.controller, null);
}
}
Renderer class which doesn't have whole lot just yet:
#FacesRenderer(
componentFamily=CustomCommandButton.COMPONENT_FAMILY,
rendererType=CustomCommandButtonRenderer.RENDERER_TYPE
)
public class CustomCommandButtonRenderer extends CommandButtonRenderer {
public static final String RENDERER_TYPE = "com.xxx.faces.component.CustomCommandButtonRenderer";
#Override
public void encodeEnd(FacesContext context, UIComponent component)
throws java.io.IOException {
//plan is to disable button after asking pagebean
//if (component.getController().shouldDisable())
// component.setDisabled("true");
//
super.encodeEnd(context, component);
}
}
taglibs-faces.xml
<?xml version="1.0"?>
<facelet-taglib version="2.0"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facelettaglibrary_2_0.xsd"
id="lu">
<namespace>http://com.xxx.faces/ui</namespace>
<tag>
<tag-name>customCommandButton</tag-name>
<component>
<component-type>com.xxx.faces.component.CustomCommandButton</component-type>
<renderer-type>com.xxx.faces.component.CustomCommandButtonRenderer</renderer-type>
</component>
<attribute>
<description>
<![CDATA[Any instance of IpmsBean.]]>
</description>
<name>controller</name>
<required>false</required>
<type>com.xxx.xxxx.client.jsf.bean.IpmsBean</type>
</attribute>
...copied rest of commandbutton's attributes here...
</tag>
</facelet-taglib>
xhtml page:
<ui:composition 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"
xmlns:p="http://primefaces.org/ui"
template="/resources/layout/masterLayout.xhtml"
xmlns:c="http://xmlns.jcp.org/jsf/core"
xmlns:incl="http://java.sun.com/jsf/composite/includes"
xmlns:lu="http://com.xxx.faces/ui">
<lu:customCommandButton
value="Custom Button"
controller="#{regionPolicyBean}"
oncomplete="hello()"/>
....
</ui:composition>
"Custom Button" shows up just fine. But setController(bean) method is not valled. setOnComplete() method is called. Not sure what am I missing here.
I did refer to this answer Adding Custom Attributes to Primefaces Autocomplete Component in JSF but still not sure why setter is not being called.

Not a direct answer, but I don't see why you do this the hard way. You can simply do:
<p:commandButton ...>
<f:attribute name="controller" value="#{...}"/>
</p:commandButton>
and get controller from the component's attributes map.

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.

Custom converter with attributes/injection not working with anymore after upgrade to JSF 2.3

I am trying to upgrade/use a custom converter that worked with JSF 2.2 (on Wildfly 13) to work on JSF 2.3 (with Mojarra 2.3.9.SP02 running on Wildfly 17.0.1)
The converter is used via its own tag defined in a tag library.
Everything's fine as long as no tag attributes are used. The attributes are just not set in the converter. The setters never get called.
But if I remove the managed = true from the converter the attributes are set but then the injection no longer works.
The converter is used like this:
<h:inputText id="text" value="#{welcome.text}">
<cdijsf:converterWithAttr id="myConverter" attr="myAttrValue" />
</h:inputText>
Tag library:
<facelet-taglib version="2.3"
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-facelettaglibrary_2_3.xsd">
<namespace>http://cdijsf.transdata.net/jsf</namespace>
<tag>
<tag-name>converterWithAttr</tag-name>
<converter>
<converter-id>cdijsf.ConverterWithAttr</converter-id>
</converter>
<attribute>
<name>attr</name>
<type>java.lang.String</type>
</attribute>
</tag>
</facelet-taglib>
And this is the converter code:
#Dependent
#FacesConverter(value = "cdijsf.ConverterWithAttr", managed = true)
public class ConverterWithAttr implements Converter<String> {
#Inject
private BeanManager beanManager;
private String attr;
public ConverterWithAttr() {
}
#PostConstruct
private void init() {
// If 'managed = true' beanManager is injected at this point.
// If 'managed = false' beanManager is null at this point
}
#Override
public String getAsObject(FacesContext context, UIComponent component, String value) {
return value;
}
#Override
public String getAsString(FacesContext context, UIComponent component, String value) {
return value;
}
public String getAttr() {
return attr;
}
public void setAttr(String attr) {
// If 'managed = true' setAttr is never called
// If 'managed = false' setAttr is called
this.attr = attr;
}
}
faces-config.xml:
<faces-config version="2.3"
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-facesconfig_2_3.xsd">
</faces-config>
And I also explicitly declare JSF 2.3 like this:
#FacesConfig(version = Version.JSF_2_3)
#ApplicationScoped
public class JsfConfiguration {
}
beans.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_2_0.xsd"
version="2.0"
bean-discovery-mode="annotated">
</beans>

How use primefaces autocomplete into composite with preselected values?

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

JSF 2.0 encodeAll() invoked prior to sessionCreated()

I have created a custom component in JSF 2.0. Its purpose is to display a session attribute value in a input text box. I want the session attribute to be created in HttpSessionListener sessionCreated method. The problem is encodeAll method is getting invoked prior to sessionCreated method. What should I do to make sessionCreated invoked prior to encodeAll?
web.xml
<context-param>
<param-name>javax.faces.FACELETS_LIBRARIES</param-name>
<param-value>/WEB-INF/components.taglib.xml</param-value>
</context-param>
<listener>
<listener-class>mycomp.jsf.listener.MyListener</listener-class>
</listener>
components.taglib.xml
<facelet-taglib xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-facelettaglibrary_2_0.xsd"
version="2.0">
<namespace>http://mycomp/jsf/components</namespace>
<tag>
<tag-name>mycomp</tag-name>
<component>
<component-type>MyComp</component-type>
</component>
</tag>
</facelet-taglib>
MyListener.java
public class MyListener implements HttpSessionListener {
#Override
public void sessionCreated(HttpSessionEvent event) {
HttpSession session = event.getSession();
session.setAttribute("sessionid", session.getId());
}
#Override
public void sessionDestroyed(HttpSessionEvent se) {
}
}
MyComp.java
#FacesComponent(value = "MyComp")
public class MyComp extends UIComponentBase {
public MyComp() {
setRendererType(null);
}
protected Class getComponentClass() {
return this.getClass();
}
#Override
public void decode(FacesContext context) {
//some logic
}
#Override
public void encodeAll(FacesContext context) throws IOException {
String clientId = getClientId(context) + ":token";
HttpSession session = (HttpSession) context.getExternalContext().getSession(false);
String sessionId = (String) session.getAttribute("sessionid");
if (sessionId == null || "".equals(sessionId)) {
throw new RuntimeException("sessionid is missing!");
}
ResponseWriter writer = context.getResponseWriter();
writer.startElement("input", this);
writer.writeAttribute("type", "text", "type");
writer.writeAttribute("name", clientId, "name");
writer.writeAttribute("value", sessionId, "value");
writer.endElement("input");
}
#Override
public String getFamily() {
return null;
}
}
index.xhtml
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:t="http://mycomp/jsf/components">
<h:head>
</h:head>
<h:body>
<h:form>
<t:mycomp />
</h:form>
</h:body>
</html>
Replace getSession(false) by getSession(true).
HttpSession session = (HttpSession) context.getExternalContext().getSession(true);
The boolean tells whether the session should be created or not when it isn't created yet. With getSession(false) you're thus risking getting a null when it isn't been created at that point yet and hence the NullPointerException whenever you call any methods on it. See also the javadoc.
Unrelated to the concrete problem, cleaner is to use ExternalContext#getSessionMap() instead. Having "raw" javax.servlet imports in your JSF code namely usually indicates smells in the JSF code. I.e. something is been done unnecessarily overcomplicated. You should then look for simpler ways with pure JSF API.
Replace
HttpSession session = (HttpSession) context.getExternalContext().getSession(true);
String sessionId = (String) session.getAttribute("sessionid");
if (sessionId == null || "".equals(sessionId)) {
throw new RuntimeException("sessionid is missing!");
}
by
String sessionId = (String) context.getExternalContext().getSessionMap().get("sessionid");
if (sessionId == null || "".equals(sessionId)) {
throw new RuntimeException("sessionid is missing!");
}

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