How to get a UIComponent by its component id in icefaces - jsf

I'm trying to access an icefaces component, exactly an Accordion so i can set its activeIndex from my bean. the problem is that the returned value is always null. this is my code.
public static UIComponent findComponentInRoot(String id) {
UIComponent component = null;
FacesContext facesContext = FacesContext.getCurrentInstance();
if (facesContext != null) {
UIComponent root = facesContext.getViewRoot();
component = findComponent(root, id);
}
return component;
}
public static UIComponent findComponent(UIComponent base, String id) {
if (id.equals(base.getId()))
return base;
UIComponent kid = null;
UIComponent result = null;
Iterator kids = base.getFacetsAndChildren();
while (kids.hasNext() && (result == null)) {
kid = (UIComponent) kids.next();
if (id.equals(kid.getId())) {
result = kid;
break;
}
result = findComponent(kid, id);
if (result != null) {
break;
}
}
return result;
}
and i call this method like this:
Accordion acco = (Accordion)findComponentInRoot("menuFormId:menu");
my page look like this or to say a part of it:
<h:form id="menuFormId">
<icecore:singleSubmit />
<ace:accordion id="menu" collapsible="true" autoHeight="false" >
<ace:accordionPane id="system" title="#{msgs.LABEL_ADMINISTRATION}"
rendered="#{navigationCtrl.functionList['GESUTAD'] or navigationCtrl.functionList['GESPROF'] or navigationCtrl.functionList['GESUTTOM'] or navigationCtrl.functionList['SYNCPRC']}">
<div class="divLinkStyle">
<ice:commandLink rendered="#{navigationCtrl.functionList['GESPROF']}" styleClass="linkMenu" action="#{navigationCtrl.redirectConsulterProfil}"
onmouseover="this.style.backgroundColor='#DEEDF8'" onmouseout="this.style.backgroundColor='#FFFFFF'">
<h:graphicImage value="../resources/images/util.png" />
<h:outputLabel value="#{msgs.LABEL_GESTION_PROFIL}" style="cursor: pointer;" />
</ice:commandLink>
</div>
...
Any ideas ?
my bean is session scoped.
i'm using icefaces 3.3.0 and jsf 2.2

You're confusing component ID with client ID. You're passing a client ID "menuFormId:menu" instead of component ID "menu" to your utility method, while the utility method actually finds the component by component ID instead of client ID.
Just use UIViewRoot#findComponent().
public static UIComponent findComponentInRoot(String id) {
return FacesContext.getCurrentInstance().getViewRoot().findComponent(id);
}
Unrelated to the concrete problem. You're making here a design mistake. The model should not be interested in the view. It should be the other way round. Set the activeIndex as a bean property and let the view hook on it the usual way.
<ace:accordion ... activeIndex="#{bean.activeIndex}">
In any case you're trying to grab/create/bind/manipulate/whatever a physical UIComponent instance in a backing bean class, you should absolutely stop coding and think twice if you're really doing things the right way. Ask if necessary at Stack Overflow if you can't figure out the right way.

Related

JSF Primefaces outputLabel get value is null

I have button on my view. When I click it there is an action handler where I'm getting view root from faces context and then I get form. Then I loop through facets and children of the form and I look for OutputLabel instances. When I get it I'm trying to getValue from it. But it's always null, but on rendered page there is value. OutputLabel (p:outputLabel) is placed in composite component...
What do I wrong? Some ideas?
// EDIT
public void buttonHandler(){
FacesContext facesContext = FacesContext.getCurrentInstance();
UIForm form = facesContext.getViewRoot().findComponent("formId");
List<String> list = new ArrayList<>();
collectLabels(form, list);
}
public void collectLabels(UIComponent component, List<String> list){
Iterator<UIComponent> iterator = component.getFacetsAndChildren();
while(iterator.hasNext()){
UIComponent child = iterator.next();
collectLabels(child, list);
}
if(component instanceof OutputLabel){
list.add(((OutputLabel)component).getValue());
}
}
I'm not at work now. So I don't remember my composite component code now. But it contains <p:outputLabel for="#{cc.attr.id}" value="#{cc.attr.labelValue}" />

ValueChangeEvent although value has not changed

I cannot see why the question is duplicate. If I debug the code then - when the button is clicked - no new value of projectSelected is being detected. Even the hashCode is the same. The equals method of the ProjectEntity only contains the id which is the same since it comes from the database and is not changed anywhere. Null values don't exist in the selection.
There was, however, too much code to reproduce the problem. I removed unnecessary code and the problem still persists.
Original question: In the following form with 3 <p:selectOneMenu> -fields if the submit button is clicked a valueChangeEvent is fired for the projectSelector field although it hasn't changed. Why is that? Like that the actual action behind the button is never called. I would expect a valueChangeEvent to be fired only in case the project changes.
Update: Trying to find the cause I replaced the ProjectEntity with String and then it worked. So I thought it must be the equals method of ProjectEntity but that only compares the id. I debugged further and found out that the selected value is being compared with a ProjectEntity with all fields set to null which gives a false and hence a valueChangeEvent. So the question is why is there a ProjectEntity with all fields set to null? I debugged into UIInput.compareValues which has that "null"-ProjectEntity being the previous value. That is being returned by UIOuput.getLocalValue. Where does it come from?
Update2: Even when using the equals and hashCode from selectOneMenu shows after submit always the last item in the list as selected item the behaviour does not change. I created an ear file readily to be deployed to e.g. a wildfly and would appreciate any help since I am stuck on this question.
<h:form>
<p:outputLabel value="#{msgs.timeProject}"/>
<p:selectOneMenu value="#{timeBean.model.projectSelected}"
converter="projectConverter"
onchange="submit()"
valueChangeListener="#{timeBean.projectChanged}"
immediate="true"
required="true">
<f:selectItems value="#{timeBean.model.allProjects}"
var="singleProject"
itemValue="#{singleProject}"
itemLabel="#{singleProject.name}"/>
</p:selectOneMenu>
<p:commandButton value="#{msgs.send}"
action="#{timeBean.myAction}"
ajax="false"/>
<p:outputLabel value="#{timeBean.model.resultValue}"
rendered="#{not empty timeBean.model.resultValue}"/>
</h:form>
The converter
#FacesConverter(value = "projectConverter")
public class ProjectConverter implements Converter {
#Inject
private ProjectService projectService;
#Override
public Object getAsObject(final FacesContext facesContext, final UIComponent uiComponent, final String projectName) {
if (StringUtils.isEmpty(projectName)) {
return null;
}
final List<ProjectEntity> projects = projectService.findAll();
for (ProjectEntity project : projects) {
if (StringUtils.equals(projectName, project.getName())) {
return project;
}
}
return null;
}
#Override
public String getAsString(final FacesContext facesContext, final UIComponent uiComponent, final Object value) {
if (value == null) {
return null;
}
if (value instanceof ProjectEntity) {
return ((ProjectEntity) value).getName();
}
return "???projectName???";
}
}
The equals-method of the ProjectEntity
#Override
public boolean equals(final Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
final ProjectEntity that = (ProjectEntity) o;
return id != null ? id.equals(that.id) : that.id == null;
}
And the change listener inside the timeBean
public void projectChanged(final ValueChangeEvent event) {
final ProjectEntity projectSelected = (ProjectEntity) event.getNewValue();
model.setProjectSelected(projectSelected);
final FacesContext context = FacesContext.getCurrentInstance();
context.renderResponse();
}
The TimeModel
public class TimeModel {
private ProjectEntity projectSelected;
private List<ProjectEntity> allProjects;
private String resultValue;
... getters and setters ...
I'll guess, that the problem resides inside the ProjectConverter class, cause it may run into troubles to assign a valid projectService instance. Maybe you remove the injection and try to compute the value programatically in the getAsObject, getAsString methods by explicit cdi-finders.
I remember to run in a similar situation, when i was injecting in a ServletFilter.

How to override h:selectOneRadio renderer? Where is the renderer class in jsf-impl?

Is it possible to override renderer used by <h:selectOneRadio>? I tried to find the class from jsf-impl package of JSF 2.2 but didn't find it. The reason I want to do this is to get rid of the table it generates.
Is it possible to override renderer used by h:selectOneRadio?
Yes, surely it is. Otherwise, UI component libraries like PrimeFaces couldn't exist.
I tried to find the class from jsf-impl package but didn't find it.
The exact class depends on the JSF implementation you're using. If it's Mojarra, then it's the com.sun.faces.renderkit.html_basic.RadioRenderer class. If it's MyFaces, then it's the org.apache.myfaces.renderkit.html.HtmlRadioRenderer class.
In order to properly override it, just extend the class and override methods where necessary and register it as follows in your faces-config.xml:
<render-kit>
<renderer>
<component-family>javax.faces.SelectOne</component-family>
<renderer-type>javax.faces.Radio</renderer-type>
<renderer-class>com.example.MyRadioRenderer</renderer-class>
</renderer>
</render-kit>
Keep in mind that you're this way tight-coupling the renderer to the specific JSF impl/version. Such an extended renderer is not compatible with a different JSF implementation (i.e. your app wouldn't deploy when you ever replace Mojarra by MyFaces) and may possibly break when the current JSF implementation has been updated to a newer version. If you worry about this, consider writing the renderer entirely from scratch, like PrimeFaces et.al. do.
The reason I want to do this is to get rid of the table it generates.
Consider looking at Tomahawk or PrimeFaces instead of reinventing the wheel. They have respectively a <t:selectOneRadio layout="spread"><t:radio> and <p:selectOneRadio layout="custom"><p:radioButton> which allows you positioning those things everywhere you want.
See also:
<h:selectOneRadio> renders table element, how to avoid this?
I added
<render-kit>
<renderer>
<component-family>javax.faces.SelectOne</component-family>
<renderer-type>javax.faces.Radio</renderer-type>
<renderer-class>com.sial.ecommerce.configurator.ui.model.RadioRendererWithoutDataTable</renderer-class>
</renderer>
</render-kit>
to faces-config.xml.
And created a class which extends com.sun.faces.renderkit.html_basic.RadioRenderer And I did override the method encodeEnd then commented out the code which adding table elements.
public class RadioRendererWithoutDataTable extends com.sun.faces.renderkit.html_basic.RadioRenderer {
#Override
public void encodeEnd(FacesContext context, UIComponent component) throws IOException {
rendererParamsNotNull(context, component);
if (!shouldEncode(component)) {
return;
}
ResponseWriter writer = context.getResponseWriter();
assert (writer != null);
String alignStr;
Object borderObj;
boolean alignVertical = false;
int border = 0;
if (null != (alignStr = (String) component.getAttributes().get("layout"))) {
alignVertical = alignStr.equalsIgnoreCase("pageDirection");
}
if (null != (borderObj = component.getAttributes().get("border"))) {
border = (Integer) borderObj;
}
Converter converter = null;
if (component instanceof ValueHolder) {
converter = ((ValueHolder) component).getConverter();
}
// renderBeginText(component, border, alignVertical, context, true);
Iterator<SelectItem> items = RenderKitUtils.getSelectItems(context, component);
Object currentSelections = getCurrentSelectedValues(component);
Object[] submittedValues = getSubmittedSelectedValues(component);
Map<String, Object> attributes = component.getAttributes();
OptionComponentInfo optionInfo = new OptionComponentInfo((String) attributes.get("disabledClass"),
(String) attributes.get("enabledClass"), (String) attributes.get("unselectedClass"),
(String) attributes.get("selectedClass"), Util.componentIsDisabled(component), isHideNoSelection(component));
int idx = -1;
while (items.hasNext()) {
SelectItem curItem = items.next();
idx++;
// If we come across a group of options, render them as a nested
// table.
if (curItem instanceof SelectItemGroup) {
// write out the label for the group.
if (curItem.getLabel() != null) {
// if (alignVertical) {
// writer.startElement("tr", component);
// }
//writer.startElement("td", component);
writer.writeText(curItem.getLabel(), component, "label");
// writer.endElement("td");
// if (alignVertical) {
// writer.endElement("tr");
// }
}
// if (alignVertical) {
// writer.startElement("tr", component);
// }
// writer.startElement("td", component);
// writer.writeText("\n", component, null);
// renderBeginText(component, 0, alignVertical, context, false);
// render options of this group.
SelectItem[] itemsArray = ((SelectItemGroup) curItem).getSelectItems();
for (int i = 0; i < itemsArray.length; ++i) {
renderOption(context, component, converter, itemsArray[i], currentSelections, submittedValues, alignVertical, i,
optionInfo);
}
// renderEndText(component, alignVertical, context);
// writer.endElement("td");
// if (alignVertical) {
// writer.endElement("tr");
// writer.writeText("\n", component, null);
// }
} else {
renderOption(context, component, converter, curItem, currentSelections, submittedValues, alignVertical, idx, optionInfo);
}
}
//renderEndText(component, alignVertical, context);
}
Then it worked for me.
When I given
<h:selectOneRadio >
<f:selectItem itemValue="1" itemLabel="Item 1" />
<f:selectItem itemValue="2" itemLabel="Item 2" />
</h:selectOneRadio>
in my jsf page.
It converted to
<input type="radio" name="bulkForm:j_idt224" id="bulkForm:j_idt224:0" value="1"><label for="bulkForm:j_idt224:0"> Item 1</label>
<input type="radio" name="bulkForm:j_idt224" id="bulkForm:j_idt224:1" value="2"><label for="bulkForm:j_idt224:1"> Item 2</label>
which was what I need.

Mojarra findComponent return null for valid id when inside composite component

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.

Using kaptcha with JSF

I'm trying to use http://code.google.com/p/kaptcha/ which looks like a very easy way to include CAPTCHA. My demo app is JSF and although the instructions are simple for JSP, I don't know how to use them in JSF. How do I translate this in JSF?
In your code that manages the submit action:
String kaptchaExpected = (String)request.getSession()
.getAttribute(com.google.code.kaptcha.Constants.KAPTCHA_SESSION_KEY);
String kaptchaReceived = request.getParameter("kaptcha");
if (kaptchaReceived == null || !kaptchaReceived.equalsIgnoreCase(kaptchaExpected))
{
setError("kaptcha", "Invalid validation code.");
}
I tried putting it in my:
public String button1_action() {
// TODO: Process the action.
return "success";
}
but it doesn't understand the request object :(
This equivalent JSF action should do it:
// bind to <h:inputText value="#{thisbean.kaptchaReceived}" />
private String kaptchaReceived;
public String getKaptchaReceived() {
return kaptchaReceived;
}
public void setKaptchaReceived(String kaptcha) {
kaptchaReceived = kaptcha;
}
public String button1_action() {
if (kaptchaReceived != null) {
FacesContext context = FacesContext
.getCurrentInstance();
ExternalContext ext = context.getExternalContext();
Map<String, Object> session = ext.getSessionMap();
String kaptchaExpected = session
.get(com.google.code.kaptcha.Constants.KAPTCHA_SESSION_KEY);
if (kaptchaReceived.equalsIgnoreCase(kaptchaExpected)) {
return "success";
}
}
return "problem";
}
This assumes that you want to use h:inputText and h:graphicImage in your JSF view instead of HTML elements.
Implementing validator is another easy way to validate the kaptcha.
<h:inputText id="kaptcha" autocomplete="off" required="true">
<f:validator validatorId="kaptchaValidator" />
</h:inputText>
<h:message for="kaptcha" styleClass="errorMessage"/>
--- Validator ---
public class KaptchaValidator implements Validator {
#Override
public void validate(FacesContext facesContext, UIComponent uiComponent, Object value) throws ValidatorException {
HttpSession session = (HttpSession) facesContext.getExternalContext().getSession(true);
String kaptchaExpected = (String) session.getAttribute(com.google.code.kaptcha.Constants.KAPTCHA_SESSION_KEY);
String kaptchaReceived = (String) value;
if (kaptchaReceived == null || !kaptchaReceived.equalsIgnoreCase(kaptchaExpected)) {
FacesMessage message = new FacesMessage();
message.setDetail("Invalid Security Code.");
message.setSummary("Invalid security code.");
message.setSeverity(FacesMessage.SEVERITY_INFO);
throw new ValidatorException(message);
}
}
You can retrieve the request object from the JSF External Context, which is accessible from the FacesContext, using the following code:
HttpServletRequest request = (HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest();
Edit (thanks to McDowell) :
Another way is to use the FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap() method to access the request parameters...

Resources