Get row number in p:dataTable of dynamic element - jsf

I'm curious about how to get the row number of an element inside the <p:dataTable>.
<p:dataTable id="userDataTable" value="#{bean.rows}" rowIndexVar="rowIndex">
<p:column headerText="RowCounter">
<p:commandLink id="row#{rowIndex+1}" actionListener="#{bean.getRows}">
<h:outputText value="Show Row #{rowIndex+1}" />
</p:commandLink>
</p:column>
</p:dataTable>
Bean:
public void getRows(ActionEvent ae) {
System.out.println(ae.getComponent().getId().toString());
}
Always prints row1, no matter which <p:commandLink> is clicked. What am I missing?

As to your concrete problem, the id attribtue of a JSF component is evaluated during view build time. However, the #{rowIndex} is only set during view render time. Thus, at the moment the id attribute is evaluated, the #{rowIndex} is nowhere been set and defaults to 0. This problem has essentially exactly the same grounds as already answered and explained here: JSTL in JSF2 Facelets... makes sense? Note thus that there's only one <p:commandLink> component, not multiple. It's just that it's been reused multiple times during generating HTML (everytime with the same component ID!).
To fix it, just use id="row" instead. The dynamic ID makes no sense in this particular case. JSF would already automaticlaly prepend the row index (check generated HTML output to see it). I'm not sure why exactly you incorrectly thought that you need to manually specify the row index here, so it's hard to propose the right solution as there are chances that you actually don't need it at all. See also How can I pass selected row to commandLink inside dataTable?
For the case you really need the row index, here's how you could obtain it:
Integer rowIndex = (Integer) ae.getComponent().getNamingContainer().getAttributes().get("rowIndex");
The UIComponent#getNamingContainer() returns the closest naming container parent which is in your particular case the data table itself, in flavor or UIData which in turn has thus the rowIndex property. You can alternatively also do so, which is a bit more self documenting:
UICommand commandLink = (UICommand) ae.getComponent();
UIData dataTable = (UIData) commandLink.getNamingContainer();
Integer rowIndex = dataTable.getRowIndex();

Related

Why can <c:forEach> or <ui:repeat> not access <p:dataTable var>?

I am working with jsf Mojarra 2.2.7, Java 8, Primefaces 5.1 and netbeans 8.0.2
I have a class Event with a property List<GameRecord> gameRecordList. GameRecord includes List<Boolean> gamesEntered and other properties. The idea is I have a list of people in an event and am configuring if they are entered into bets or competitions.
In my .xhtml file I have
<p:dataTable value="#{events.gameRecordList}" var="item" rowIndexVar="rowIndex">
<p:column>#{item.field1}</p:column>
<p:column>#{item.field2}</p:column>
<c:forEach items="#{events.gameRecordList.get(rowIndex).gamesEntered}" var="game">
<p:column>
<p:selectBooleanCheckbox value="#{game}"/>
</p:column>
</c:forEach>
</p:dataTable>
The <c:forEach> should work with value="#{item.gamesEntered}" rather than the full string but it does not. I have tried <ui:repeat> but either way the page comes up blank where this data should have appeared.
Does this make sense or is there a reason the full addressing is required to make it work?
The <c:forEach> should work with value="#{item.gamesEntered}" rather than the full string but it does not.
JSTL tags run during view build time, building JSF component tree. JSF components run during view render time, producing HTML output. So at the moment <c:forEach> runs, <p:dataTable> hasn't run and its var is nowhere available and will evaluate as null. Note that the same applies to rowIndexVar, which will evaluate as 0 (the default value of an int).
See also
JSTL in JSF2 Facelets... makes sense?
I have tried <ui:repeat> but either way the page comes up blank where this data should have appeared.
UIData components can only accept UIColumn children. The <ui:repeat> isn't such one. The <c:forEach> works because it basically produces a bunch of physical <p:column> components for the datatable. You're lucky that each item has apparently the same amount of gamesEntered as the first item, this would otherwise have failed hard as well.
See also:
How to use ui:repeat in datatable to append columns?
By the way, you need <p:columns> which is basically an <ui:repeat> which extends from UIColumn class. But also here, its value cannot be set on a per-row basis, only on a per-table basis. The rowIndexVar isn't available in <p:columns value> and would evaluate as 0 anyway.
<p:dataTable value="#{events.gameRecordList}" var="item" rowIndexVar="rowIndex">
<p:column>#{item.field1}</p:column>
<p:column>#{item.field2}</p:column>
<p:columns value="#{events.gameRecordList[0].gamesEntered}" columnIndexVar="columnIndex">
<p:selectBooleanCheckbox value="#{events.gameRecordList[rowIndex].gamesEntered[columnIndex]}"/>
</p:columns>
</p:dataTable>
See also:
Primefaces static and dynamic columns in datatable
Dynamic columns with List<List> in <p:dataTable><p:columns>

PrimeFaces paginator selection

I have a PrimeFaces (5) DataTable and like to store the page size selection in the view, so that the user sees the same number of rows when he returns to the view.
But all I see from documentation is that there is a page event which can be captured, but which won't give me the currently selected page size. The closest I got was getting the event's source (the DataTable) and get the rows attribute, but it only contains the number of rows before applying the new value.
Here on SO, I found a solution here, but it seems to me not the best way to implement it having to use some inline JQuery stuff.
It seems to me a not-so-exotic feature to persist the page size selection within the session, so there must be a good solution for it, I assume?
As you already noticed, the page event is being fired before the selected number of rows has been applied to the table. At the moment, there is no event being fired after changing the number of rows.
However, there is a possible workaround:
bind your datatable to the backing bean
Use AJAX page-event without any listener. Instead, call a <p:remoteCommand> after ajax request is complete.
Add your actionListener method to the <p:remoteCommand>.
Within this method, check the datatable's number of rows. It's the new value.
Keep in mind, page event is not only fired when changing the number of rows, but also on page change.
Example:
XHTML/JSF
<h:form>
<p:remoteCommand name="pageChanged" actionListener="#{tableBean.onPageChanged}"/>
<p:dataTable binding="#{tableBean.table}" paginator="true" rowsPerPageTemplate="5,10,20" rows="5">
<p:ajax event="page" process="#none" oncomplete="pageChanged()" />
<!-- columns here -->
</p:dataTable>
</h:form>
TableBean.java
private DataTable table;
public void onPageChanged(){
int numRows = table.getRows();
// ...
}
//getter/setter for table

input component inside ui:repeat, how to save submitted values

I'm displaying a list of questions from database and for each question I have to display a list of options, in this case radio buttons.
<ui:repeat value="#{formData.questions}" var="question">
<div>
<p:outputLabel value="#{question.name}" />
<p:selectOneRadio value="#{formData.selectedOption}">
<f:selectItems value="#{formData.options}" />
</p:selectOneRadio>
</div>
</ui:repeat>
I need to save the checked option for each question.
How can I do this?
You need to associate the input value with the repeated variable var in some way. Right now you're not doing that anywhere and basically binding all input values to one and same bean property. So, when the form gets submitted, every iteration will override the bean property everytime with the value of the current iteration round until you end up getting the value of the last iteration round. This is definitely not right.
The simplest way would be to directly associate it with the object represented by var:
<p:selectOneRadio value="#{question.selectedOption}">
In your specific case, this only tight-couples the "question" model with the "answer" model. It's reasonable to keep them separated. A more proper solution in your specific case is to map it with currently iterated #{question} as key (provided that it has a proper equals() and hashCode() implementation, obviously):
<p:selectOneRadio value="#{formData.selectedOptions[question]}">
With:
private Map<Question, String> selectedOptions = new HashMap<>();
Regardless of the approach, in the action method, just iterate over it to collect them all.

<h:selectOneMenu> value change listener invoked for all dropdowns instead of only the current

I'm using MyFaces 1.1. I have two <h:selectOneMenu>s dropdowns which each point to same valueChangeListener method.
<h:selectOneMenu id="d1" value="#{mybean.selectedChannel1}"
onchange="submit()" valueChangeListener="#{myform.channelValuechange}">
<f:selectItems value="#{mybean.channelList}"/>
</h:selectOneMenu>
<h:selectOneMenu id="d2" value="#{mybean.selectedChannel2}"
onchange="submit()" valueChangeListener="#{myform.channelValuechange}">
<f:selectItems value="#{mybean.channelList}"/>
</h:selectOneMenu>
When I change the first dropdown, then the value change listener method get fired correctly. In the method, I'm obtaining the ID of the current component as sourceId via ValueChangeEvent argument and then comparing it as follows:
if (sourceId.equals("d1")) {
// ...
} else if (sourceId.equals("d2")) {
// ...
}
However, my concrete problem is that d2 block is also called when d1 is changed.
I tried the one and other and figured that the following helped to solve the problem:
if (!event.getPhaseId().equals(PhaseId.INVOKE_APPLICATION)) {
event.setPhaseId(PhaseId.INVOKE_APPLICATION);
event.queue();
}
However, I don't feel like that it's the best solution. How is this caused and how can I solve it without using the above code?
With onchange="submit()" you're basically submitting the entire form when the current input element is changed, not only the currently changed input! In contrary to what many starters incorrectly think, there is no means of any input-specific JavaScript/Ajax magic here. As you're submitting the entire form, it would trigger the processing of all input components.
The valueChangeListener is always invoked when the submitted value of the input component does not equals() the initial model value as in the backing bean. Given that in your case both menus hit the value change listener when you change only the first one, that can only mean that the default select item value of the second menu does not equals() the initial model value in the backing bean.
You need to make sure that #{mybean.selectedChannel2} of the second menu has by default exactly the same value as the first item of #{mybean.channelList} of the second menu's list. This way the value change listener won't be invoked for the second menu when you change the first menu.
See also:
When to use valueChangeListener or f:ajax listener? (just to learn what's different in JSF 2, for the case you're interested)

JSF binding with setValueExpression read-only?

I try to create an InputField in the backing bean and add it to the view, but the databinding seems to work just read-only.
I create a UIInput in the Backing-Bean like this:
UIComponent textInput = new UIInput();
textInput.setId("operandInputText");
textInput.setValueExpression("value", ef.createValueExpression(elCtx, "#{row.operandValues[0]}", String.class));
textInput.setValueExpression("rendered", ef.createValueExpression(elCtx, "#{row.inputType == 'text'}", Boolean.class));
mInputPanelGroup.getChildren().add(textInput);
The panelGroup is inside a column of a dataTable and bound to the bean:
<p:column id="operandColumn">
<h:panelGroup id="inputPanelGroup" binding="#{locateEmployeeBean.inputPanelGroup}" >
<h:inputText id="testInput" value="#{row.operandValues[0]}" />
</h:panelGroup>
</p:column>
The <h:inputText/> inside the PanelGroup is just for testing and this is where I found out that the binding I did with setValueExpression(...) works at least read-only.
In the browser I now have 2 inputFields, first the 'testInput' and then 'operandInputText'.
When I enter a value in 'operandInputText' and submit, the value does not get saved, but when I enter a value in the 'testInput'-Field, it get's submitted and in addition the value gets displayed in BOTH inputFields.
The operandValues is a simpe object array:
private Object[] mOperandValues = new Object[2];
Could this have anything to do with the dataType I pass to setValueExpression(...)?
I tried Object, but that didn't change anything.
Any idea why this happens?
Thanks in advance!
I found the solution to my problem. Honestly it was an article by #BalusC Using Datatables: Populate datatable what took me on the right path.
Previously I added the components during PreRenderView-Phase, then I saw in your example that you populate the bound component ONCE in the getter (which is then obviously way earlier during RestoreView-Phase). That is how I've done it now and it works flawlessly, the Inputfields now work both ways (read+write).
Thanks alot for your work #BalusC!

Resources