Suppose I have a composite component
<!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:composite="http://java.sun.com/jsf/composite"
`enter code here` xmlns:h="http://java.sun.com/jsf/html"
xmlns:p="http://primefaces.org/ui">
<h:head></h:head>
<h:body>
<composite:interface componentType="editableLabel">
<composite:attribute name="value" required="true" type="java.lang.String"/>
<composite:attribute name="oldValue" type="java.lang.String"/>
<composite:attribute name="editMode" required="false" default="#{false}" type="java.lang.Boolean"/>
<composite:attribute name="updateListener" required="false" method-signature="void actionListener()"/>
<composite:attribute name="cancelListener" required="false" method-signature="void actionListener()"/>
</composite:interface>
<composite:implementation>
<h:panelGroup id="editableLabelComponent">
<h:panelGroup rendered="#{cc.attrs.editMode}">
<p:inputText value="#{cc.attrs.value}"/>
<p:commandButton value="Update" actionListener="#{cc.update}" update="editableLabelComponent"/>
<p:commandButton value="Cancel" actionListener="#{cc.cancel}" update="editableLabelComponent"/>
</h:panelGroup>
<p:commandLink>
<p:outputLabel id="display" value="#{cc.attrs.value}" rendered="#{!cc.attrs.editMode}"/>
<p:ajax event="click" listener="#{cc.toggleEditMode}" update="editableLabelComponent"/>
</p:commandLink>
</h:panelGroup>
</composite:implementation>
</h:body>
</html>
with a FacesComponent
import org.apache.commons.lang3.StringUtils;
import javax.el.ELContext;
import javax.el.ValueExpression;
import javax.faces.component.FacesComponent;
import javax.faces.component.NamingContainer;
import javax.faces.component.UIComponentBase;
import javax.faces.component.UINamingContainer;
import javax.faces.context.FacesContext;
import java.io.Serializable;
/**
* Created by labraham on 1/14/16.
*/
@FacesComponent(value = "editableLabel")
public class EditableLabel extends UIComponentBase implements Serializable, NamingContainer {
private static final long serialVersionUID = 108467781935083432L;
public static final String VALUE_ATTRIBUTE = "#{cc.attrs.value}";
public static final String OLD_VALUE_ATTRIBUTE = "#{cc.attrs.oldValue}";
public static final String EDIT_MODE_ATTRIBUTE = "#{cc.attrs.editMode}";
private FacesContext context;
private ELContext elContext;
@Override
public String getFamily() {
return UINamingContainer.COMPONENT_FAMILY;
}
/**
*
*/
public void update() {
ValueExpression oldValue = getAttr(OLD_VALUE_ATTRIBUTE, String.class);
String value = (String) getAttr(VALUE_ATTRIBUTE, String.class).getValue(getElContext());
oldValue.setValue(getElContext(), value);
toggleEditMode();
}
/**
*
*/
public void cancel() {
ValueExpression value = getAttr(VALUE_ATTRIBUTE, String.class);
String oldValue = (String) getAttr(OLD_VALUE_ATTRIBUTE, String.class).getValue(getElContext());
value.setValue(getElContext(), oldValue);
toggleEditMode();
}
/**
*
*/
public void toggleEditMode() {
if (StringUtils.isEmpty((String) getAttr(OLD_VALUE_ATTRIBUTE, String.class).getValue(getElContext()))) {
getAttr(OLD_VALUE_ATTRIBUTE, String.class).setValue(getElContext(),
getAttr(VALUE_ATTRIBUTE, String.class).getValue(getElContext())
);
}
ValueExpression editmode = getAttr(EDIT_MODE_ATTRIBUTE, Boolean.class);
editmode.setValue(getElContext(), !((Boolean) editmode.getValue(getElContext())));
}
/**Get value expression for a given attr.*/
private ValueExpression getAttr(final String attribute, final Class<?> attributeType) {
return getContext().getApplication().getExpressionFactory()
.createValueExpression(getElContext(), attribute, attributeType);
}
/**Get ElContext.*/
public ELContext getElContext() {
if (this.elContext == null) {
elContext = getContext().getELContext();
}
return elContext;
}
public void setElContext(final ELContext elContext) {
this.elContext = elContext;
}
/**Get faces context*/
public FacesContext getContext() {
if (this.context == null) {
context = FacesContext.getCurrentInstance();
}
return context;
}
public void setContext(final FacesContext context) {
this.context = context;
}
}
now all instances of this widget will have the baseline functionality for cancel and update as defined by the cancel() and update() functions. However in addition this basic functionality the user may want some additional functionality (for instance calling a webservice on update) which they will add by some sort of backing bean method in the updateListener and/or cancelListener attributes.
My question is Is it possible to call the method that the user attached to the listener attributes programmatically from inside the FacesComponent or am I going about this the wrong way?
Yes, it's available as a MethodExpression
typed attribute. All composite attributes are available the usual way by the inherited UIComponent#getAttributes()
map. See also How to access Composite Component attribute values in the backing UIComponent?
E.g., to get #{cc.attrs.updateListener}
and invoke with no params:
MethodExpression updateListener = (MethodExpression) getAttributes().get("updateListener");
updateListener.invoke(getFacesContext().getELContext(), null);
Unrelated to the concrete problem, the way how you deal with ValueExpression
attributes is clumsy. To get #{cc.attrs.oldvalue}
, just do:
String oldvalue = (String) getAttributes().get("oldvalue");
To set #{cc.attrs.value}
, just do:
getAttributes().put("value", oldvalue);
Having FacesContext
and ELContext
as instance variables does technically fortunately not harm in this specific construct, but this is fishy, you'd better get them in method local scope. The FacesContext
is just available by inherited UIComponent#getFacesContext()
.