I'm working on an existing JSF component where the encodeEnd method ends with:
// popComponentFromEL(context);
The Javadoc for UIComponent#popComponentFromEL(FacesContext context) tells me:
Pop the current UIComponent from the FacesContext attributes map so that the previous UIComponent, if any, becomes the current component.
When and why would you need or want that?
I've found that none of the other components in the same library are using it.
That's the counterpart of pushComponentToEL() whose Javadoc explains this more elaborately.
pushComponentToEL
public final void pushComponentToEL(FacesContext context, UIComponent component)
Push the current UIComponent this to the FacesContext attribute map using the key CURRENT_COMPONENT saving the previous UIComponent associated with CURRENT_COMPONENT for a subsequent call to popComponentFromEL(javax.faces.context.FacesContext).
This method and popComponentFromEL() form the basis for the contract that enables the EL Expression "#{component}" to resolve to the "current" component that is being processed in the lifecycle. The requirements for when pushComponentToEL() and popComponentFromEL() must be called are specified as needed in the javadoc for this class.
After pushComponentToEL() returns, a call to getCurrentComponent(javax.faces.context.FacesContext) must return this UIComponent instance until popComponentFromEL() is called, after which point the previous UIComponent instance will be returned from getCurrentComponent()
Basically, this approach
public void encodeXxx(FacesContext context) {
try {
pushComponentToEL(context, this);
// ...
}
finally {
popComponentFromEL(context);
}
}
allows you during the // ... process to grab this component using #{component} in EL, or UIComponent#getCurrentComponent() in a managed bean.
One of well known examples is this construct:
<h:inputText ... styleClass="#{component.valid ? 'valid' : 'error'}" />
where #{component.valid} basically refers UIInput#isValid().
Related
I'm trying to understand the working principles of UIInput component and EL-expressions in JSF. Here is the code fragment of UIInput component source code:
public void updateModel(FacesContext context) {
if (context == null) {
throw new NullPointerException();
}
if (!isValid() || !isLocalValueSet()) {
return;
}
ValueExpression ve = getValueExpression("value");
if (ve != null) {
Throwable caught = null;
FacesMessage message = null;
try {
ve.setValue(context.getELContext(), getLocalValue()); //1
setValue(null);
setLocalValueSet(false);
}
//catch clause
Now, I have a simple bean and the input component binded to its property:
<h:inputText value="#{myBean.hello}" />
#ManagedBean
#SessionScoped
public class MyBean{
private String hello;
//getter,setter
}
As far as I understand, the updateModel method is called by the inherited from the UIComponentBase class processUpdates(FacesContext) method which is a standard callback for the Update Model Values phase. So, after setting breakpoint at //1 and performing step next the flow's stopped at the corresponding bean property setter method.
My question is about of purpose of javax.el.ValueExpression. Is it resposible for all interatctions (incapsulates) between the component class and the bean's property in order for getting/setting values to bean's properties?
EL is like a "path" and is needed to navigate the bean. The UIInput is the MVC "view" to the MVC "model" represented by your bean. EL links the two together. It only encapsulates the traversal itself. It identifies the subject of the interaction, but the interactions are defined by the component class. Compare h:outputText to h:inputText: both basically take a value="#{some.el}" attribute; the fact that one allows the user to modify it is a fact about the component, not the path to the bean property itself.
Some components define attributes that should be populated by references to methods rather than bean properties, but again, the EL is just the traversal—invoking those methods is something the component does. And in that case you would be dealing with a MethodExpression instead of a ValueExpression.
The JSF lifecycle dictates that things happen in a certain overall order, but the component author still has ample opportunity to be creative (or wreak havoc) by doing things their own way.
I need to add a component (UIParameter) to a HtmlCommandLink component dinamically through a Phase Listener.
What I want to achieve is that every element <h:link outcome="out"> renders as <a href="out_url_parsed + ?param=paramvalue">.Where "param" is my component.
I've tried using this
private void addElement(final PhaseEvent event, final Class clazz, final UIComponent component) {
final FacesContext fcontext = event.getFacesContext();
UIViewRoot root = fcontext.getViewRoot();
if (root == null) {
return;
}
root.visitTree(new FullVisitContext(fcontext), new VisitCallback() {
#Override
public VisitResult visit(VisitContext context, UIComponent target) {
if (clazz.isInstance(target)) {
LOGGER.info("Element Found");
UIParameter parameter = new UIParameter();
parameter.setValue("willberonadom");
parameter.setId("sessiontoken");
target.getChildren().add(parameter);
}
return VisitResult.ACCEPT;
}
});
}
But it's not working. The element is actually found on the tree but the UIParameter does not render.
I've found that the UIViewRoot only has child elements after RENDER_RESPONSE phase. So i think this is why my added element is not rendered at the end of the process.
I'm sure I can add this param editing the views but I don't want to do that since it must be present on all h:link in the application and must be present on any other new added too. So I consider this as a better approach to avoid missing tags
On a similar case I've managed to add input hidden elements to every form on view with this code...
HtmlInputHidden hiddenToken = new HtmlInputHidden();
hiddenToken.setId("sessiontoken");
hiddenToken.setValue("willberandom");
hiddenToken.setRendered(true);
root.addComponentResource(event.getFacesContext(), hiddenToken,"form");
But it doesn't work on anchor tags
There are several mistakes:
You want to add a parameter to a HtmlCommandLink component which represents <h:commandLink>, but you're giving an example with <h:link>, which is represented by HtmlOutcomeTargetLink. What exactly do you want?
A PhaseListener on beforePhase() of RENDER_RESPONSE may be too late on GET requests which would only build the view for the first time during render response. At the moment your PhaseListener runs, the UIViewRoot would have no children at all. You'd better hook on view build time instead. For that, a SystemEventListener on PostAddToViewEvent is the best suitable.
You're setting the parameter name as an id instead of name. Use UIParameter#setName() instead of UIParameter#setId().
Provided that you actually meant to add them to <h:link> components, then here's a kickoff example how you can achieve that with a SystemEventListener.
public class YourSystemEventListener implements SystemEventListener {
#Override
public boolean isListenerForSource(Object source) {
return source instanceof HtmlOutcomeTargetLink;
}
#Override
public void processEvent(SystemEvent event) throws AbortProcessingException {
UIParameter parameter = new UIParameter();
parameter.setName("sessiontoken");
parameter.setValue("willberonadom");
((UIComponent) event.getSource()).getChildren().add(parameter);
}
}
(if you actually want to apply them on <h:commandLink> as well, just extend the isListenerForSource() check with a || source instanceof HtmlCommandLink)
In order to get it to run, register it as follows in faces-config.xml:
<application>
<system-event-listener>
<system-event-listener-class>com.example.YourSystemEventListener</system-event-listener-class>
<system-event-class>javax.faces.event.PostAddToViewEvent</system-event-class>
</system-event-listener>
</application>
I have a custom component that look as follow
<custom:container>
<custom:checkbox index="0"/>
<custom:checkbox index="1"/>
</custom:container>
so when encodeBegin first call, it will show hit the tag <custom:container>, and it will try to save this component cliend id,
private String containerClientId;
public void encodeBegin(FacesContext context, UIComponent component){
if (component instanceof ManyCheckboxContainer) {
containerClientId = component.getClientId(context);
return;
}
}
so encodeEnd get called when I hit <custom:checkbox index="0"/>, like this
public void encodeEnd(FacesContext context, UIComponent component) throws IOException {
...
if (component instanceof Checkbox) {
renderCheckbox(context, (Checkbox) component);
}
...
}
protected void renderCheckbox(FacesContext facesContext, InforRadio radio) throws IOException {
...
UIComponent uiComponent = radio.findComponent(containerClientId);
if(uiComponent == null){
//throw error
}
...
}
If I DO NOT have this custom component inside composite component then everything work great, but once I put it in a composite component, radio.findComponent(containerClientId); return null. Is this a bug in mojarra? I test this under 2.1.10 and 2.1.11, same behavior.
EDIT
So I take that back, this behavior happen when my custom component is inside two nested NamingContainer, so something like this
<h:form id="myForm">
<f:subView id="myView">
<custom:container id="myCustom">
<custom:checkbox index="0"/>
<custom:checkbox index="1"/>
</custom:container>
</f:subView>
</h:form>
so in this case the client id (that return by component.getClientId(context)) for <custom:container> is myForm:myView:myCustom, but inside Mojarra, the findComponent method has this
public UIComponent findComponent(String expr) {
...
else if (!(base instanceof NamingContainer)) {
// Relative expressions start at the closest NamingContainer or root
while (base.getParent() != null) {
if (base instanceof NamingContainer) {
break;
}
base = base.getParent();
}
...
}
so it looks for the next ancestor NamingContainer, which in my case is the f:subView not the h:form. It then parse the client id, loop through it, each time passing piece of the id to the UIComponent findComponent(UIComponent base, String id, boolean checkId). So the first time in, this method take form3 as id and current UIComponent is f:subView, it search for all its facets and children to see if any component match form3, of course, none will match since form3 is the parent of f:subView in my structure. So null is return. Is this Mojarra bugs or am I doing some wrong. I thought that the client id is relative from the next NamingContainer ancestor instead of all the way to the root of the NamingContainer? Am I wrong on that?
After reading the docs it seems to me that Mojarra got it right.
According to the docs you should place a seperator (a colon) in front of your search expression if you want to do an "absolute" search from the root of your tree.
Otherwise it will do a relative search from the component itself if it is a NamingContainer or the first parent that is a NamingContainer.
BTW: The docs I linked to appear to be the same as the official docs distributed with the specs. Official specs are here.
I have a jsf composite component implemented from two p:calendar components.
The idea is when the first calendar is selected, the value of the second calendar need to be reset. There is a problem when the validation takes place, and the reset of the second calendar is not performed.
After reading posts I decided to use EditableValueHolder in my validator.
I have custom validator: in which I added the following code:
#Override
public void validate(FacesContext fc, UIComponent uic, Object o) throws ValidatorException {
//....
resetValues(fc);
}
public void resetValues(FacesContext fc) {
PartialViewContext partialViewContext = fc.getPartialViewContext();
Collection<String> renderIds = partialViewContext.getRenderIds();
UIComponent input;
UIViewRoot viewRoot = fc.getViewRoot();
for (String renderId : renderIds) {
input = viewRoot.findComponent(renderId);
if (input.isRendered() && input instanceof EditableValueHolder) {
EditableValueHolder editableValueHolder = (EditableValueHolder) input;
editableValueHolder.setSubmittedValue(null);
editableValueHolder.setValue(null);
editableValueHolder.setValid(true);
editableValueHolder.setLocalValueSet(false);
}
}
}
After debug I can see that each code line is passed, but nothing is happening on jsf side.
This is not the right moment to reset the values. They will be overridden anyway for the current component after the validate() method leaves and also for the second calendar once it get validated. You need to perform the reset somewhere after the update model values phase, preferably before the invoke action phase, so that you've chance to change the model value in an action(listener) method. You could use an ActionListener or a PhaseListener for this.
By the way, the JSF utility library OmniFaces has a reuseable solution for this in flavor of ResetInputAjaxActionListener.
We have numerical values in our database, representing a two-value-state. Of course this would perfectly match a boolean, but oracle has no such datatype. The NUMBER(1,0) type from the database is matched to a java.lang.Short type in Java (sometimes they used a NUMBER(*,0) to represent booleans, which are matched to java.math.BigDecimal).
Since it is somehow obvious, I want to offer ice:selectBooleanCheckbox in the view as a value representation and UIComponent to the user. (I use IceFaces as JSF implementation)
Since some people who specified JSF think it is obvious to always match the value of a ice:selectBooleanCheckbox or in JSF h:selectBooleanCheckbox to a boolean in the model, so the renderer of the component never calls any converter, even if you specify one:
Issue disscused at java.net
Therefore I tried the following:
I created a converter to specify it in the UIComponent:
public class BooleanBigDecimalConverter implements Converter {
public Object getAsObject(FacesContext context, UIComponent component, String str) {
if (StringUtils.isEmptyString(str)) {
return new BigDecimal(0);
}
if (str.equals("true")) {
return new BigDecimal(1);
} else {
return new BigDecimal(0);
}
}
public String getAsString(FacesContext context, UIComponent component, Object obj) {
if (obj != null) {
String str = obj.toString();
if (str.equalsIgnoreCase("1")
|| str.equalsIgnoreCase("yes")
|| str.equalsIgnoreCase("true")
|| str.equalsIgnoreCase("on")) {
return "true";
} else {
return "false";
}
}
return "false";
}
}
The converter works fine for the render phase (the getAsString-method is called correctly), but the getAsObject-method (Ignore that it's not correct at the moment, because it's not called anyway, so it will be fixed if it's called!) is never called, because in the renderer of the UIComponent a converter is not foreseen, like you can see here (snip from com.icesoft.faces.renderkit.dom_html_basic.CheckboxRenderer):
public Object getConvertedValue(FacesContext facesContext, UIComponent uiComponent, Object submittedValue) throws ConverterException
{
if(!(submittedValue instanceof String))
throw new ConverterException("Expecting submittedValue to be String");
else
return Boolean.valueOf((String)submittedValue);
}
So this results in an IllegalArgumentException, since in the UpdateModelValues phase it is tried to apply a Boolean to a numerical value (please ignore the BigDecimal/Short confusion... it is just a numerical type in any case!).
So I tried to overwrite the renderer with a new one like this:
import com.icesoft.faces.component.ext.renderkit.CheckboxRenderer;
public class CustomHtmlSelectBooleanCheckbox extends CheckboxRenderer {
public Object getConvertedValue(FacesContext context, UIComponent component, Object submittedValue) throws ConverterException {
Converter converter = ((ValueHolder) component).getConverter();
return converter.getAsObject(context, component, (String) submittedValue);
}
}
and registered it like this in faces-config.xml:
<render-kit>
<renderer>
<component-family>com.icesoft.faces.HtmlSelectBooleanCheckbox</component-family>
<renderer-type>com.icesoft.faces.Checkbox</renderer-type>
<renderer-class>com.myapp.web.util.CustomHtmlSelectBooleanCheckbox</renderer-class>
</renderer>
</render-kit>
I guess this should be correct, but the overridden method "getConvertedValue" is never called, nor is the getAsObject()-method, so I guess I made a mistake in registering the custom renderer, but I can't find any more documentation or hints how to do this properly and especially how to find the correct component-family (I looked up the one I use in icefaces.taglib.xml) and the correct renderer-type.
I don't want to edit the complete model because of this. Any hints, how this can be resolved?
We could fix the problem and correctly register our custom renderer.
The problem was to find the correct properties for the intended renderer. Our tries were wrong, since I found out how to get the appropriate information. It's a bit of work and searching, but it finally did the trick.
Just start your container in debug mode and add a breakpoint on class level into the derived class the custom renderer is based on (in my case com.icesoft.faces.renderkit.dom_html_basic.CheckboxRenderer).
During container start-up this breakpoint will be reached and in the stacktrace you'll find a call of the method FacesConfigurator.configureRenderKits().
This object contains an ArrayList of registered renderers. I searched the list for the renderer I'd have liked to overwrite and could find the informations I need to register my custom renderer. In my case this is the correct entry in faces-config.xml:
<render-kit>
<description>The ICEsoft Renderers.</description>
<render-kit-id>ICEfacesRenderKit</render-kit-id>
<render-kit-class>com.icesoft.faces.renderkit.D2DRenderKit</render-kit-class>
<renderer>
<component-family>javax.faces.SelectBoolean</component-family>
<renderer-type>com.icesoft.faces.Checkbox</renderer-type>
<renderer-class>com.myapp.web.util.CustomHtmlSelectBooleanCheckbox</renderer-class>
</renderer>
</render-kit>
Now the getAsObject()-method in the converter is called by the custom renderer. Be sure to override the method correctly, in case you don't want a converter on every SelectBooleanCheckbox object:
public Object getConvertedValue(FacesContext context,
UIComponent component, Object submittedValue)
throws ConverterException {
Converter converter = ((ValueHolder) component).getConverter();
if (converter == null) {
if(!(submittedValue instanceof String))
throw new ConverterException("Expecting submittedValue to be String");
else
return Boolean.valueOf((String)submittedValue);
}
return converter.getAsObject(context, component,
(String) submittedValue);
}
Otherwise you'll get a NullPointerException.
PS: There surely is a smarter way to achieve this information, but I am not smart enough. ;-)
You don't say whether you're using Hibernate but I assume that you must be for this to be an issue. Have you tried treating the numeric as a boolean in your mapping?
See this thread from the Hibernate forums