How can I add attributes to components which don't have their own renderers using the f:attribute component? - jsf

I want to write a custom renderer for the h:selectOneMenu component and eventually make use of the description property of the UISelectItem class to add a title a.k.a. tooltip to f:selectItems following BalusC's profound guides in https://stackoverflow.com/a/25512124/3280015 and http://balusc.blogspot.de/2008/08/styling-options-in-hselectonemenu.html.
Now I did extend the com.sun.faces.renderkit.html_basic.MenuRenderer in my own CustomMenuRenderer, registered it with the faces-config.xml and overrode the renderOption method, adding the following code before option tag is terminated by the Responsewriter:
String titleAttributeValue = (String) component.getAttributes().get("title");
if (titleAttributeValue != null) {
String indexKey = component.getClientId(context)
+ "_currentOptionIndex";
Integer index = (Integer) component.getAttributes().get(indexKey);
if (index == null) {
index = 0;
}
component.getAttributes().put(indexKey, ++index);
}
I'm not quite sure I'm doing the indexKey thing right or whether I need it for the title attribute or should use a writer.writeAttribute("title", titleAttributeValue, null); instead because I don't have a list like in the optionClasses tutorial, but the code works so far!
In the actual view definition use case I did:
<h:selectOneMenu value="#{cc.data}">
<f:attribute name="title" value="outerTEST" />
<c:forEach items="#{cc.list}" var="val">
<f:selectItem value="#{val}" itemValue="#{val}" itemLabel="#{val.label}">
<f:attribute name="title" value="innerTEST #{val.description}" />
</f:selectItem>
</c:forEach>
</h:selectOneMenu>
(I just put the #{val.description} there in the title value to clarify my intention, it is currently still empty and I will have to think about how to populate it per element later, but for the sake of the question we can assume it is already filled.)
But now I'm getting the "outerTEST" properly showing up in the title attribute of the option in the resulting XHTML in the Browser, yet I'm not seeing any of the "innerTEST" which would and should be individual per selectItem and which is what this is eventually all about.
I understand the f:selectItem and f:selectItemscomponents do not have their own renderers but rendering of options is generally handled by the MenuRenderer via its renderOption method.
But how then would I add individual titles to the individual selectItems??
Thanks

Related

Dynamic form with selectManyCheckbox in hashmap structure

I have a bit complex structure. First, I start problems table. Each problem has a subcategory and maincategory and also each subcategory has a main category. Besides, a main category have sub categories and a subcategory have problems.
Then, I tried to list them like below.
I have created a dynamic web form with JSF SelectManyCheckbox. It is the following:
<ui:repeat var="mainCatvar" value="#{dprEdit.mainCategories}">
<h:outputText value="#{mainCatvar.mainCatName}" styleClass="mainTitle"/>
<ui:repeat var="subCategory" value="#{mainCatvar.subCategories}">
<h:outputText value="#{subCategory.subCatName}" styleClass="title" />
<p:selectManyCheckbox value="#{dprEdit.selectedProblems[subCategory]}">
<f:selectItems value="#{subCategory.problems}" var="problem"
itemValue="#{problem.problemId}"
itemLabel="#{problem.problemName}" />
</p:selectManyCheckbox>
</ui:repeat>
</ui:repeat>
In the java code,
problems = new ProblemDAO(ConnectionManager.getConnection())
.getProblems(formId);
for (int i = 0; i < problems.size(); i++) {
Problem problem = problems.get(i);
if (subcat == problem.getSubCat())
problemIdList = addElement(problemIdList,problem.getProblemId().toString());
else
Arrays.fill(problemIdList, null);
problemIdList = addElement(problemIdList,problem.getProblemId().toString());
subcat = problem.getSubCat();
selectedProblems.put(problem.getSubCat(), problemIdList);
}
PS: addElement is a function that is written to add an element to the array whose length is already defined.
I suppose, I send the data like below to the xhtml.
(subcat1, [1,2]),(subcat3,[5]) etc...
And the value of dprEdit.selectedProblems[subCategory] is a list.
And I send a list as a value.
So I expect that the problems whose ids are 1,2,5 must be checked but they don't.
Where am I wrong?
JSF 2.2, Mojarra, PrimeFaces and Tomcat 7.x.

JSF input processing order

Is there a way to specify the order in which the inputs should be set after a submit?
Here is my case:
<h:inputText id="fieldA" value=#{myBean.myObject.fieldA}" />
<h:inputText id="fieldB" value=#{myBean.myObject.fieldB}" />
<p:autoComplete id="myObject" value=#{myBean.myObject" converter="myObjectConverter" />
<h:inputText id="fieldC" value=#{myBean.myObject.fieldD}" />
<h:inputText id="fieldD" value=#{myBean.myObject.fieldC}" />
The issue I am encountering is that, as the inputs are processed in the ordered they are declared, fieldA and fieldB are set in the initial instance of myObject, then myObject is set (with a new instance thus filedA and fieldB values are lost), and finally fieldC and fieldD are set with no problem.
If I could manage to start by setting myObject first, that would solve my problem.
I will temporarily set the fields and myObject into two different attributes of my bean, and populate myObject after clicking a save button. But it looks more like a hack than a real solution.
Needless to say that declaring the autocomplete before the inputtexts is not an option.
Thanks in advance.
In shortcut:
You can use <p:inputText> tag from primefaces. Then, you can disable all inputs. Add ajax to your autoComplete, and update other inputs after processing autoComplete. Inputs disable attribute can be set to depend on whether the autoComplete is not null. This way you will make the user to enter the autoComplet first.
you can try to set immediate="true" to p:autocomplete, so that it will be processed in the APPLY_REQUEST_VALUES phase, before all other components.
The simple solution is to update h:inputTexts when p:autocomplete item is selected to reflect its values:
<p:autoComplete id="myObject" value="#{myBean.myObject}" ...>
<p:ajax event="itemSelect" process="#this" update="fieldA fieldB fieldC fieldD" />
</p:autoComplete>
but this reverts user inputs on h:inputTexts. And since you can't move p:autocomplete on top, probably this is not acceptable too.
In case you can't/don't want to use ajax, you can force an early model update:
<p:autoComplete id="myObject" value="#{myBean.myObject}" immediate="true"
valueChangeListener="#{component.updateModel(facesContext)}" ... />
but, in my opinion, this is not very user friendly...
P.S. this time it's tested :)
There's no pretty way to get around this; your situation is already less than ideal and is hacky (re: not being able to simply reorder the fields). One workaround is for you to set fieldA and fieldB as attributes of myObject. In the converter, you could then pull the values off the components. Observe
Set attributes thus
<h:inputText id="fieldA" binding=#{fieldA}" />
<h:inputText id="fieldB" binding=#{fieldB}" />
<p:autoComplete id="myObject" value=#{myBean.myObject" converter="myObjectConverter">
<f:attribute name="fieldA" value="#{fieldA}"/>
<f:attribute name="fieldB" value="#{fieldB}"/>
</p:autoComplete>
The binding attribute effectively turns those components into page-scoped variables, allowing you to then pass them as attributes on your p:autocomplete
Get the values of those variables in your converter
//Retrieve the fields and cast to UIInput, necessary
//to retrieve the submitted values
UIInput fieldA = (UIInput) component.getAttributes().get("fieldA");
UIInput fieldB = (UIInput) component.getAttributes().get("fieldB");
//Retrieve the submitted values and do whatever you need to do
String valueA = fieldA.getSubmittedValue().toString();
String valueB = fieldB.getSubmittedValue().toString();
More importantly, why can't you just reorder the fields/logical flow of your form? You can avoid all this nasty business if you did

How to get index of selected item of jsf f:selectItems?

I have seleconeradio, for example:
<h:selectOneRadio value="#{myBean.selectedValue}" layout="pageDirection">
<f:selectItems value="#{myBean.myList}" var="a" itemValue="#{a}" itemLabel="#{a}"/>
</h:selectOneRadio>
where myList is list of integers, e.g. 1,3,2,4.
If user selects second element (i.e. 3) I want in myBean selectedValue to be 2, so I want to get index of selectItems item.
What should I write in f:selectItems itemValue tag? Or it is impossible?
P.S. I can do it by creating a new class in which I have the index property and create a new list of that class, giving the right index. But it is very bad solution.
You can actually use c:forEach for this case. This is especially usefull when you have to deal with a collection containing duplicates and therefore can't use indexOf() for example.
<h:selectOneRadio value="#{myBean.selectedValue}" layout="pageDirection">
<c:forEach items="#{myBean.myList}" var="a" varStatus="idx">
<f:selectItem itemValue="#{idx.index}" itemLabel="#{a}"/>
</c:forEach>
</h:selectOneRadio>
Just be sure to include the JSP JSTL Core namespace if you haven't done yet.
xmlns:c="http://xmlns.jcp.org/jsp/jstl/core
you should use indexOf(Object o) .. it returns the index of the first occurrence of the specified element in this list, or -1 if this list does not contain the element...
your code should probably look like this..
int index = myList.indexof(selectedValue);

How do I set a selectiOneRadioButton-value inside an ui:repeat depending on the iterated entity?

in my usecase I have a (Primefaces) selectOneRadio. This is within an ui:repeat. Values of selectOneRadio are displayed fine. I want each selectOneRadio to have a useful default-value, depending on the iterated entity. This works fine, too, default is selected. But with my approach, I am not able to set the value of the selectOneRadio, as an exception rises:
javax.el.PropertyNotWritableException: [...] value="#{orderBean.getProductPriceId(product)}": Illegal Syntax for Set Operation
How do I set a selectiOneRadioButton-value inside an ui:repeat depending on the iterated entity?
OrderBean:
public class OrderBean {
private String productPriceId; // + getter and setter
public String getProductPriceId(final Product product) {
return product == null ? "" : product.getPricesAsList().get(0).getId().toString();
}
}
xhtml:
<ui:repeat var="product" value="...">
<p:selectOneRadio value="#{orderBean.getProductPriceId(product)}">
...
</p:selectOneRadio>
</ui:repeat>
This makes indeed no sense. You need to make sure that the model matches the view without any need for additional business logic.
Just use
<p:selectOneRadio value="#{product.priceId}">
and give the default item a value of null instead of "" so that it matches.

pagination in jsf

I would like your comments and suggestion on this. I am doing the pagination for a page in jsf. The datatable is bound to a Backing Bean property through the "binding" attribute. I have 2 boolean variables to determine whether to render 'Prev' and 'Next' Button - which is displayed below the datatable. When either the 'Prev' or 'Next' button is clicked, In the backing bean I get the bound dataTable property and through which i get the "first" and "rows" attribute of the datatable and change accordingly. I display 5 rows in the page. Please comment and suggest if there any better ways. btw, I am not interested in any JSF Component libraries but stick to only core html render kit.
public String goNext()
{
UIData htdbl = getBrowseResultsHTMLDataTable1();
setShowPrev(true);
//set Rows "0" or "5"
if(getDisplayResults().size() - (htdbl.getFirst() +5)>5 )
{
htdbl.setRows(5);//display 5 rows
}else if (getDisplayResults().size() - (htdbl.getFirst() +5)<=5) {
htdbl.setRows(0);//display all rows (which are less than 5)
setShowNext(false);
}
//set First
htdbl.setFirst(htdbl.getFirst()+5);
return "success";
}
public String goPrev()
{
setShowNext(true);
UIData htdbl = getBrowseResultsHTMLDataTable1();
//set First
htdbl.setFirst(htdbl.getFirst()-5);
if(htdbl.getFirst()==0)
{
setShowPrev(false);
}
//set Rows - always display 5
htdbl.setRows(5);//display 5 rows
return "success";
}
Please comment and suggest if there any better ways.
Well, that gives not much to answer on. It's at least not the way "I" would do, if you're asking for that. Long story short: Effective datatable paging and sorting. You only need Tomahawk (face it, it has its advantages). But if you're already on JSF2+Facelets instead of JSF1+JSP, then you can in fact also use ui:repeat and #ViewScoped instead of t:dataList and t:saveState.
We can use 'Repeat' component - this is similar to dataList or dataTable component in Primefaces
<p:repeat id="repeatComponent" var="education" value="#{backingBean.educationList}" emptyMessage="No records found">
<h:panelGroup>
<p:outputLabel for="center" value="Education Center:" />
<br />
<h:panelGroup>
<h:outputText id="center" value="#{education.centerName}">
</h:outputText>
</h:panelGroup>
</h:panelGroup>
</p:repeat>
This is similar to for loop in java
var - this act as loop iterator
value - takes list object
emptyMessage - takes String value, will get displayed when passed list object is empty

Resources