I do have a page, where I have a list of rich:collapsiblePanel that hold input elements. These collapsiblePanels themselves store their expanded/collapsed state within a backing bean.
Now I have the use case to Open/Close all of these collapsiblePanels at once, with one mouse click. So I have tried to achieve this with the two commandButtons over the list. These use the attached actionListener to iterate over all backing beans of the collapsiblePanels and set the expanded flag to true/false.
This seems to work, unless you Open or Close one of the collapsiblePanels on their own. As soon as that happens clicking the buttons does not do anything anymore.
<h:form prependId="false">
<a4j:commandButton value="Open All" actionListener="#{viewBean.doOpenAll}" render="c" />
<a4j:commandButton value="Close All" actionListener="#{viewBean.doCloseAll}" render="c" style="margin-left: 10px;" />
<a4j:outputPanel id="c">
<a4j:repeat id="repeat" value="#{viewBean.items}" var="item">
<rich:collapsiblePanel id="panel" expanded="#{item.expanded}">
<h:outputLabel id="text_lbl" value="text" />
<h:inputText id="text" value="#{item.text}" />
</rich:collapsiblePanel>
</a4j:repeat>
</a4j:outputPanel>
</h:form>
I have published a project on github so that you can try around with the code.
For completeness here are the two backing beans
#ViewScoped
#ManagedBean
public class ViewBean implements Serializable {
static final Logger LOG = LoggerFactory.getLogger(ViewBean.class);
private static final long serialVersionUID = -6239437588285327644L;
private List<ListItem> items;
public ViewBean() {
items = new ArrayList<ListItem>(10);
for (int i = 0; i < 10; i++) {
items.add(new ListItem("item " + i));
}
}
public void doOpenAll() {
LOG.debug("open all");
for (ListItem item : items) {
item.setExpanded(true);
}
}
public void doCloseAll() {
LOG.debug("close all");
for (ListItem item : items) {
item.setExpanded(false);
}
}
public List<ListItem> getItems() {
return items;
}
}
public class ListItem {
private boolean expanded;
private String text;
public ListItem(String text) {
super();
this.text = text;
}
public boolean isExpanded() {
return expanded;
}
public void setExpanded(boolean expanded) {
this.expanded = expanded;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
}
This may be related to this RichFaces bug?!: https://issues.jboss.org/browse/RF-11546
Related
I've a dataTable within a dataTable. The initial dataTable has a list of components that appear on the page. One of these components will be a list of strings which can have elements added or deleted. When I change a string in this list, I expect the value to show up in the bean and it is not.
Below I have an example of my problem. The page renders a text input field for the first component then three text input fields to represent the second component which is a list of three input fields.
I have valueChange listener on all the input fields. The listener, is in the InnerBean class, prints out the source and the value that changed.
For the standalone input field, the listener correctly prints out the changed value and shows that the bean has been updated with this value. For any of the input fields from the list, the listener prints out the previous value of the input field and the bean has not been updated. On the ajax update of the inner datatable, the changed value is replace with the original value.
Since the valueChange listener is called, it appears that Primefaces knows that the value has changed. The code just doesn't seem to record the changed value.
Any help is appreciated.
I'm using Primefaces 8.0 and JSF 2.2.20.
Here is the xhtml:
<p:panel id="testPanel" header="#{myController.outerBean.name}" toggleable="true" collapsed="false" >
<p:dataTable id="testTable" value="#{myController.outerBean.innerBeanList}" var="bean">
<p:column >
<!-- TEXT COMPONENT-->
<h:panelGroup rendered="#{bean.type eq 'text'}" >
<p:inputText id="textfield" value="#{bean.value}" style="width:100%;" >
<p:ajax event="valueChange" listener="#{bean.textListListener}" update="testTable" />
</p:inputText>
</h:panelGroup>
<!-- LIST COMPONENT -->
<h:panelGroup rendered="#{bean.type eq 'textlist'}" >
<p:dataTable id="testListTable" styleClass="datatableWithoutBorder" style="width:320px"
var="textAddition" value="#{bean.list}" rowIndexVar="rowIndex" >
<p:column >
<p:inputText id="textAdd" value="#{textAddition}" style="width: 100%;">
<p:ajax event="valueChange" listener="#{bean.textListListener}" update="testListTable"/>
</p:inputText>
</p:column>
</p:dataTable>
</h:panelGroup>
</p:column>
</p:dataTable>
<h:panelGrid columns="1" style="width:100%;">
<h:panelGroup style="float:right">
<p:commandButton id="submitBtn" value="Submit"
action="#{dummyController.submit}"
update="messages #this"
icon="fa fa-save"/>
</h:panelGroup>
</h:panelGrid>
</p:panel>
My controller code:
public class MyController {
private OuterBean outerBean;
public MyController() {
System.out.println("MyController instantiated");
setOuterBean(new OuterBean());
}
public void submit() {
for (InnerBean ab: outerBean.getInnerBeanList()) {
System.out.println(ab.getLabel() + ": " + ab.getValue() + ":" + ab.getList() );
}
}
public void clear() {
// TODO
}
// Getter/Setter methods
public OuterBean getOuterBean() {
return outerBean;
}
public void setOuterBean(OuterBean outerBean) {
this.outerBean = outerBean;
}
}
My OuterBean with the list of components:
public class OuterBean implements Serializable {
private String name;
private String value;
private List<InnerBean> innerBeanList;
public OuterBean() {
name = "Entry Panel #1";
value = "";
innerBeanList = new ArrayList<InnerBean>();
InnerBean ab1 = new InnerBean();
ab1.setLabel("First Component");
ab1.setType("text");
ab1.setValue("Input text");
innerBeanList.add(ab1);
InnerBean ab2 = new InnerBean();
ab2.setLabel("Second Component");
ab2.setType("textlist");
ArrayList<String> list = new ArrayList<String>();
list.add("Item 1");
list.add("Item 2");
list.add("Item 3");
ab2.setList(list);
innerBeanList.add(ab2);
}
//
// Getter/Setters
//
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public List<InnerBean> getInnerBeanList() {
return innerBeanList;
}
public void setInnerBeanList(List<InnerBean> innerBeanList) {
this.innerBeanList = innerBeanList;
}
}
My InnerBean which represents a component to be render. One of which can be a list of strings:
public class InnerBean implements Serializable {
// Type of component
public static final String TEXT = "text";
public static final String TEXTLIST = "textlist";
private String label;
private String type; // If TEXT, use value; if TEXTLIST, use list.
private String value;
private List<String> list = new ArrayList<String>();
public InnerBean() {
}
public void textListListener(AjaxBehaviorEvent event) {
System.out.println("Listener called");
System.out.println(" Source: " + event.getSource().toString());
System.out.println(" Value: " + ((UIInput)event.getSource()).getValue());
System.out.println(" List: " + list.toString());
System.out.println(" Event: " + event.toString());
}
//
// Setters and getters
//
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public List<String> getList() {
return list;
}
public void setList(List<String> list) {
this.list = list;
}
}
I'm attempting to implement lazy loading into my application with sorting, but am having little issues with sorting and paginator. SortBy does work so like Paginator single but when i click on sort on first column with page > 0, the LazyDataModel#load is called with "first" parameter = 0!
Then instead of sorting data of page 1 return on page 0
Here is my managed bean class code:
#ViewScoped
#ManagedBean
public class PlayerMB implements Serializable {
private static final long serialVersionUID = 1L;
private LazyDataModel<Player> players = null;
private int sizePlayer;
private Player player;
public LazyDataModel<Player> getAllPlayers() {
if (players == null) {
players = new PlayerLazyList();
}
setSizePlayer(players.getRowCount());
return players;
}
public Player getPlayer() {
if(player == null){
player = new Player();
}
return player;
}
public void setPlayer(Player player) {
this.player = player;
}
public int getSizePlayer()
{
return sizePlayer;
}
public void setSizePlayer(int sizePlayer) {
this.sizePlayer = sizePlayer;
}
}
Player class code:
public class Player implements Serializable{
private static final long serialVersionUID = 1L;
private int id;
private String name;
private int age;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
#Override
public int hashCode() {
return getId();
}
#Override
public boolean equals(Object obj) {
if(obj instanceof Player){
Player player = (Player) obj;
return player.getId() == getId();
}
return false;
}
/*public int sortByModel(Object player1, Object player2)
{
return ((Player) player1).getName().compareTo(((Player) player2).getName());
}*/
}
dataTable.xhtml:
<f:view>
<h:form>
<p:dataTable id="lazyDataTable"
value="#{playerMB.allPlayers}"
var="player" paginator="true"
rows="10"
selection="#{playerMB.player}"
selectionMode="single"
paginatorTemplate="{CurrentPageReport} {FirstPageLink} {PreviousPageLink} {PageLinks} {NextPageLink} {LastPageLink} {RowsPerPageDropdown}"
rowsPerPageTemplate="5,10,15"
style="width: 80%;margin-left: 10%;
margin-right: 10%;"
lazy="true">
<f:facet name="header">List of players</f:facet>
<p:ajax event="rowSelect" update=":playerDialogForm" oncomplete="PF('playerDetails').show();" />
<p:ajax event="page"
listener="#{playerMB.casePage}"/>
<p:column sortBy="#{player.name}" headerText="Name" > <!-- sortBy does not work with lazyDataTable but more work occurs-->
<h:outputText value="#{player.name}" />
</p:column>
<p:column sortBy="#{player.age}" headerText="Age">
<h:outputText value="#{player.age}" />
</p:column>
<f:facet name="footer">tot: #{playerMB.sizePlayer}</f:facet>
</p:dataTable>
</h:form>
PlayerLazyList class code who extends LazyDataModel
public class PlayerLazyList extends LazyDataModel<Player> {
//private int pagina;
#Override
public List<Player> load(int first, int pageSize, String sortField,
SortOrder sortOrder, Map<String, Object> filters) {
List<Player> allplayers = CreatePlayers.players; //here load all Player
System.out.println("first =" + first + "; pagesize =" + pageSize + "; sortField = " + sortField);
players = CreatePlayers.getPlayers(first, pageSize,allplayers.size()); //here load player from "first" for "max" element
if (sortField != null && sortField.equals("name") && sortOrder.name().equals("ASCENDING"))
Collections.sort(players, new NamePlayerComparatorAsc());
if (sortField != null && sortField.equals("name") && sortOrder.name().equals("DESCENDING"))
Collections.sort(players, new NamePlayerComparatorDesc());
System.out.println("size dopo" + players.size());
// set the total of players
if(getRowCount() <= 0){
//setRowCount(playerDAO.countPlayersTotal());
setRowCount(allplayers.size());
}
// set the page dize
setPageSize(pageSize);
return players;
}
private static final long serialVersionUID = 1L;
private List<Player> players;
//private MyTransaction transaction;
#Override
public Object getRowKey(Player player) {
return player.getId();
}
#Override
public Player getRowData(String playerId) {
Integer id = Integer.valueOf(playerId);
for (Player player : players) {
if(id.equals(player.getId())){
return player;
}
}
return null;
}
}
Primefaces 5.2, Wildfly 8.2.0, Mojarra 2.2.8
You seem to expect it sorts just the page you are on. That is just not how it works. It works perfectly and as designed and as expected by (almost) everybody (that is everybody but you). It sorts the full possible resultset. All items that were on page 0 will now most likely be spread over several other pages. So staying on the newly populalted page 1 totally does not make any sense.
The behaviour you describe can be seen in the showcase to iirc
There is a <p:selectOneListbox> with about 20 items. The first 5 items can be viewed in the list and then the remaining can be scrolled to and selected. The list is ajaxified. When the item numbers greater than 5 is selected (after scrolling to it) the scrollbar does not remain at its place; it moves to the top position. This makes the selected item (for example item 9) invisible. The program uses a ajax listener. The app uses PrimeFaces 5.0.
But, when the ajax listener is removed the selected (item 9) is visible and the scroller does not move to top (remove the p:ajax tag's listener attribute in the below JSF page).
I would like to know how to make the scrollbar not move when any item is selected while using the ajax listener.
The JSF page:
<p:selectOneListbox id="list"
scrollHeight="100"
value="#{bean.todo}">
<f:selectItems value="#{bean.data}"
var="t"
itemLabel="#{t.name}"
itemValue="#{t.name}"/>
<p:ajax process="#this"
update="#this msg"
listener="#{bean.valueChanged}" />
</p:selectOneListbox>
<br /><h:outputText id="msg" value="#{bean.message}" />
The bean's code:
import javax.faces.bean.*;
import java.io.Serializable;
import java.util.*;
import javax.faces.event.AjaxBehaviorEvent;
import javax.faces.component.UIOutput;
#ManagedBean(name="bean")
#SessionScoped
public class TodosBean implements Serializable {
private List<Todo> data;
private String todo; // selected item value
private String msg;
public TodosBean() {
loadData();
if (data.size() == 0) {
return;
}
Todo t = data.get(0);
String name = t.getName();
setTodo(name); // select the first item in the list
setMessage(name);
}
private void loadData() {
data = new ArrayList<>();
data.add(new Todo("1first", "1"));
data.add(new Todo("2second", "2"));
data.add(new Todo("3third", "3"));
data.add(new Todo("4fourth", "4"));
data.add(new Todo("5fifth", "5"));
data.add(new Todo("6sixth", "6"));
data.add(new Todo("7seventh", "7"));
data.add(new Todo("8eighth", "8"));
}
public List<Todo> getData() {
return data;
}
public void setMessage(String msg) {
this.msg = msg;
}
public String getMessage() {
return msg;
}
public String getTodo() {
return todo;
}
public void setTodo(String t) {
todo = t;
}
public void valueChanged(AjaxBehaviorEvent e) {
String name = (String) ((UIOutput) e.getSource()).getValue();
setMessage(name + " selected.");
}
}
public class Todo {
private String name;
private String desc;
public Todo() {}
public Todo(String name, String desc) {
this.name = name;
this.desc = desc;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
#Override
public String toString() {
return this.name;
}
}
Remove the update="#this will solve it. Updating a control will move it to a default state. It might also be that in a newer version of PF this default state is improved and it still is at the correct position
There is a list that is dynamically generated at runtime. I am using a dataTable to represent it. Inside each row is a dropdown list. As soon as the user selects a value from the drop down list in a row, then all the other rows must be disabled.?
<h:dataTable value="#{user.orderList}" var="item">
<h:column>
<h:selectOneMenu value="#{user.sometuff}" >
<f:selectItems value="#{user.someItems}" />
</h:selectOneMenu>
</h:column>
</h:dataTable>
How can I achieve this with <f:ajax>?
here is a working example.
imho, it's way easier to implement with ajax4jsf (richfaces), using a4j:repeat and a4j:ajax tags.
xhtml code:
<h:form id="form">
<h:dataTable id="tableId" value="#{user.orderList}" var="item">
<h:column>
<h:selectOneMenu value="#{item.selectedItem}" disabled="#{user.oneItemSelected and (item.selectedItem == null || item.selectedItem == '')}">
<f:selectItems value="#{user.selectItemList}" />
<f:ajax execute="#this" render="#form" listener="#{user.updateSelectionFlag}"></f:ajax>
</h:selectOneMenu>
</h:column>
</h:dataTable>
</h:form>
order item code:
public class Order implements Serializable
{
private static final long serialVersionUID = 1L;
private String selectedItem;
public String getSelectedItem() {
return selectedItem;
}
public void setSelectedItem(String selectedItem) {
this.selectedItem = selectedItem;
}
}
managed bean code:
#Named
#ViewScoped
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private List<Order> orderList;
private List<SelectItem> selectItemList;
private boolean oneItemSelected;
public List<SelectItem> getSelectItemList() {
if (selectItemList == null)
{
selectItemList = new ArrayList<SelectItem>();
selectItemList.add(new SelectItem("", "---Please Choose---"));
selectItemList.add(new SelectItem("1", "Item 1"));
selectItemList.add(new SelectItem("2", "Item 2"));
selectItemList.add(new SelectItem("3", "Item 3"));
selectItemList.add(new SelectItem("4", "Item 4"));
}
return selectItemList;
}
public List<Order> getOrderList() {
if (orderList == null)
{
orderList = new ArrayList<Order>();
for (int i=0 ; i<4 ; i++)
{
orderList.add(new Order());
}
}
return orderList;
}
public boolean isOneItemSelected() {
return oneItemSelected;
}
public void updateSelectionFlag()
{
oneItemSelected = false;
for (int i=0 ; i<getOrderList().size() ; i++)
{
Order order = getOrderList().get(i);
if (order.getSelectedItem() != null && !order.getSelectedItem().equals(""))
{
oneItemSelected = true;
break;
}
}
}
}
by the way, instead of checking the whole array in updateSelectionFlag, it's better to check only the submitted item's value. but i couldn't figure out how to get the clicked row index of h:dataTable. doing a binding prevents combo values from being submitted, and datatable does not provide a varStatus attribute like ui:repeat does.
Suppose the code of this page:
<h:form prependId="false" id="form">
<h:selectManyCheckbox id="checkBoxList" value="#{backedBean.lstIdSelectedItems}" layout="pageDirection">
<f:selectItems value="#{backedBean.lstAvailableItems}" var="item" itemLabel="#{item.label}" itemValue="#{item.value}" />
<f:ajax listener="#{backedBean.itemClicked}" />
</h:selectManyCheckbox>
</h:form>
And the code of a session managed bean:
public class BackedBean implements Serializable {
private List<SelectItem> lstAvailableItems;
private List<Long> lstIdSelectedItems;
public BackedBean() {
lstAvailableItems = new ArrayList<SelectItem>();
lstIdSelectedItems = new ArrayList<Long>();
}
#PostConstruct
private void postConstruct(){
for (int i = 0; i < 10; i++) {
SelectItem item = new SelectItem(new Long(i), "CHKID " + i);
lstAvailableItems.add(item);
}
}
public void itemClicked(AjaxBehaviorEvent ae){
HtmlSelectManyCheckbox uiCmp = (HtmlSelectManyCheckbox)ae.getSource();
// (1) Here I would like to get the ID of the item that has been clicked.
}
In (1) I would like to get the ID of the element that has been clicked by the user. I can see in the lstIdSelectedItems array list the IDs of all elements selected by the user, but how can I get the ID of the element that the user has clicked?
I have tried to use the f:attribute tag inside of the selectManyCheckbox, but the attribute is not in the component map when the ajax listener method is called in the backed bean. I have used this, but doesn't work:
<h:selectManyCheckbox id="checkBoxList" value="#{backedBean.lstIdSelectedItems}" layout="pageDirection">
<f:selectItems value="#{backedBean.lstAvailableItems}" var="item" itemLabel="#{item.label}" itemValue="#{item.value}">
<f:attribute name="clicked" value="#{item.value}" />
</f:selectItems>
<f:ajax listener="#{backedBean.itemClicked}" />
</h:selectManyCheckbox>
Any ideas?
Regards.
You're thus interested in the actual value change and not only in the new value. Bring in a valueChangeListener which compares the old value with the new value and prepares some properties which the ajax listener method could intercept on.
E.g.
<h:selectManyCheckbox value="#{bean.selectedItems}" valueChangeListener="#{bean.selectedItemsChanged}" converter="javax.faces.Long">
<f:selectItems value="#{bean.availableItems}" />
<f:ajax listener="#{bean.itemSelected}" />
</h:selectManyCheckbox>
with
private Map<String, Long> availableItems; // +getter
private List<Long> selectedItems; // +getter+setter
private Long selectedItem;
private boolean selectedItemRemoved;
#PostConstruct
public void init() {
availableItems = new LinkedHashMap<String, Long>();
for (long i = 0; i < 10; i++) {
availableItems.put("CHKID " + i, i);
}
}
public void selectedItemsChanged(ValueChangeEvent event) {
List<Long> oldValue = (List<Long>) event.getOldValue();
List<Long> newValue = (List<Long>) event.getNewValue();
if (oldValue == null) {
oldValue = Collections.emptyList();
}
if (oldValue.size() > newValue.size()) {
oldValue = new ArrayList<Long>(oldValue);
oldValue.removeAll(newValue);
selectedItem = oldValue.iterator().next();
selectedItemRemoved = true;
}
else {
newValue = new ArrayList<Long>(newValue);
newValue.removeAll(oldValue);
selectedItem = newValue.iterator().next();
selectedItemRemoved = false;
}
}
public void itemSelected(AjaxBehaviorEvent event) {
System.out.println("Selected item: " + selectedItem);
System.out.println("Selected item removed? " + selectedItemRemoved);
}
When in the list "selectedItems" you unchecked the end element, the code no call the method itemSelected.