How can the dependencies between drop downs be managed? - jsf

I am using JSF2.0. I have three drop downs :
masterDropDown.
childDropDownA
childDropDownB
The business rule that needs to be enforced is :
Whenever masterDropDown changes , I need to populate childDropDownA and childDropDownB.
The contents of childDropDownA and childDropDownB are same except that when the user selects something from childDropDownA, childDropDownB should have its old contents except the one selected by user in childDropDownA. The same rule applies when the user selects childDropDownB first.
What is the cleanest way to achieve this in JSF?

Plain JSF 2:
<h:form>
<h:selectOneMenu id="ddlMaster" value="#{bean.ddlMasterSelected}">
<f:listItems value="#{bean.ddlMasterData}" />
<f:ajax listener="#{bean.fillChildren}" render="ddlChildA ddlChildB" />
</h:selectOneMenu>
<h:selectOneMenu id="ddlChildA" value="#{bean.ddlChildASelected}">
<f:listItems value="#{bean.ddlChildAData}" />
<f:ajax listener="#{bean.refillChildB}" render="ddlChildB" />
</h:selectOneMenu>
<h:selectOneMenu id="ddlChildB" value="#{bean.ddlChildBSelected}">
<f:listItems value="#{bean.ddlChildBData}" />
<f:ajax listener="#{bean.refillChildA}" render="ddlChildA" />
</h:selectOneMenu>
</h:form>
Managed bean
#ManagedBean
#ViewScoped
public class Bean {
private Map<String, String> ddlMasterData;
private Map<String, String> ddlChildAData;
private Map<String, String> ddlChildBData;
private String ddlMasterSelected;
private String ddlChildASelected;
private String ddlChildBSelected;
#PostConstruct
public void init() {
//fill initial values for the drop down lists...
//this is a raw idea, you must refine it
DDLService ddlService = new DDLService();
ddlMasterData = ddlService.getDDLMasterData();
//empty data for children ddls
ddlChildAData = new LinkedHashMap<String, String>();
ddlChildBData = new LinkedHashMap<String, String>();
}
public void refillChildB(AjaxBehaviorEvent event) {
//filling the children ddls
DDLService ddlService = new DDLService();
ddlChildASelected = ddlService.getDDLChildData();
//same method to fill child data on ddlChildBSelected because is same data
ddlChildBSelected = ddlService.getDDLChildData();
}
public void fillChildren(AjaxBehaviorEvent event) {
//when the user selects something from childDropDownA
//childDropDownB should have its old contents
//I assume it must be cleared, you can change this behavior though
ddlChildBData = new LinkedHashMap<String, String>();
}
public void refillChildA(AjaxBehaviorEvent event) {
//The same rule applies when the user selects childDropDownB first
//I assume it must be cleared, you can change this behavior though
ddlChildAData = new LinkedHashMap<String, String>();
}
}

Related

How to Compare the values JSF selectonemenu

I am developing a JSF application
I have 2 selectOnemenu controls and submit button.
I need to disable the button if the values of 2 fields are equal
<h:selectOneMenu id="from" value="#{currencyBean.history.fromCurrency}" >
<f:selectItems value="#{currencyBean.currency}" var="c" itemValue="#{c}" itemLabel="#{c.name}"/>
</h:selectOneMenu>
<p:outputLabel for="to" value="to:" />
<h:selectOneMenu id="to" value="#{currencyBean.history.toCurrency}" >
<f:selectItems value="#{currencyBean.currency}" var="c" itemValue="#{c}" itemLabel="#{c.name}"/>
</h:selectOneMenu>
<p:commandButton id="calculateButton" value="Convert" update=":convert :historyPanel" onstart="PF('statusDialog').show()" onsuccess="PF('statusDialog').hide()" validateClient="true" ajax="true" action="#{currencyBean.Calculate()}" />
I tried to use onchange with ajax but everytime I change one dropdown the value of the second drowpdown became null in the backbean so I cannot read it.
Here is my backbean
#Named(value = "currencyBean")
#RequestScoped
public class CurrencyBean implements Serializable {
/**
* Creates a new instance of CurrencyBean
*/
private History history;
private Currency currency;
private Date currentDate;
#Inject
private Loginbean loginBean;
private List<History> historyList;
public List<History> getHistoryList() {
int userId = loginBean.getUserId();
if (userId != -1) {
return new HistoryHelper().GetHistoryListByUser(userId);
}
return null;
}
public CurrencyBean() {
history = new History();
}
public History getHistory() {
return history;
}
public void setHistory(History history) {
this.history = history;
}
public Currency[] getCurrency() {
return Currency.values();
}
public Date getCurrentDate() {
return new Date();
}
public void Calculate() {
int userId = loginBean.getUserId();
if (userId != -1) {
new CurrencyClient().Convert(userId, this.history);
}
}
}
any clue ?
My assumption is that all of your problems come from your managed bean scope. You have #Request scope so every request your managed bean will be removed from container, thus when you define onchange="submit()" (this is only my assumption because you haven't define how you implement onchange attribute) and you select value from one selectBox component values for this component is updated but the first one is still null. When you select second selectBox value updated from first selectBox doesn't exists anymore as managed bean has been removed after first request. You should try with wider scope for instance #ViewScope. If it doesn't help then further informations like implementation onchange attribute will be needed

JSF PrimeFaces Extensions Timeline: How to update a Timeline via AJAX?

I'm having a hard time doing basic AJAX updates of a timeline.
Let me start with a basic example where I want to update the start and end times of a timeline based on the selection of a dropdown list:
<h:form id="form">
<h:outputLabel for="period" value="#{str.schedule_period}"/>
<h:selectOneMenu id="period" value="#{timelineController.period}" label="#{str.schedule_period}">
<f:selectItems value="#{timelineController.periodWeeks}" />
<p:ajax event="change" update="timeline" />
</h:selectOneMenu>
<pe:timeline id="timeline" value="#{timelineController.model}"
editable="true"
eventMargin="0"
minHeight="120"
stackEvents="false"
start="#{timelineController.timelineStart}"
min="#{timelineControllertimelineStart}"
end="#{timelineController.timelineEnd}"
max="#{timelineController.timelineEnd}"
showNavigation="false" showButtonNew="false"
showCurrentTime="false"
axisOnTop="true"
timeZone="#{timelineController.timezone}"
zoomMin="28800000"
dropActiveStyleClass="ui-state-highlight" dropHoverStyleClass="ui-state-hover">
<p:ajax event="drop" listener="#{timelineController.onDrop}"
global="false" process="timeline"/>
</pe:timeline>
</h:form>
When I select an item in the dropdown list, an AJAX event fires and sets the period property in the backing bean, but the new value is not reflected in the timeline component. As a workaround, I wrapped the timeline in a p:outputPanel and updated the wrapper instead and it works:
...
<h:selectOneMenu id="period" value="#{timelineController.period}" label="#{str.schedule_period}">
<f:selectItems value="#{timelineController.periodWeeks}" />
<p:ajax event="change" update="wrapper" />
</h:selectOneMenu>
...
<p:outputPanel id="wrapper">
<pe:timeline id="timeline" value="#{timelineController.model}"
editable="true"
eventMargin="0"
minHeight="120"
stackEvents="false"
start="#{timelineController.timelineStart}"
min="#{timelineControllertimelineStart}"
end="#{timelineController.timelineEnd}"
max="#{timelineController.timelineEnd}"
showNavigation="false" showButtonNew="false"
showCurrentTime="false"
axisOnTop="true"
timeZone="#{timelineController.timezone}"
zoomMin="28800000"
dropActiveStyleClass="ui-state-highlight" dropHoverStyleClass="ui-state-hover">
<p:ajax event="drop" listener="#{timelineController.onDrop}"
global="false" process="wrapper"/>
</pe:timeline>
</p:outputPanel>
Note that I also had to change the process attribute of p:ajax to wrapper.
So my first question is: why doesn't the update work without wrapping the timeline component?
My second question is about drag and drop. As you can you see from my code above, I have attached a drop listener to the timeline. And I'm also able to drag and drop events from a p:dataList BEFORE I make a selection in the dropdown list. Once I select a new period in the dropdown list, the timeline gets updated appropriately, but I'm not able to drag and drop events to the timeline any more (the onDrop listener doesn't get fired). Here's my p:dataList:
<p:dataList id="eventsList" value="#{timelineController.users}"
var="user" itemType="none">
<h:panelGroup id="eventBox" layout="box" style="z-index:9999; cursor:move;">
#{user.toString()}
</h:panelGroup>
<p:draggable for="eventBox" revert="true" helper="clone" cursor="move"/>
</p:dataList>
Any ideas what's wrong here?
I'm also including the TimelineController class for reference:
#ManagedBean
#ViewScoped
public class TimelineController {
#EJB UserService userDao;
private TimelineModel model;
private String name;
private ZoneId timezone;
private Period period;
private Duration defaultShiftDuration;
private LocalDateTime timelineStart;
private LocalDateTime timelineEnd;
#PostConstruct
protected void initialize() {
timezone = ZoneId.of("Europe/Berlin);
period = Period.ofWeeks(2);
defaultShiftDuration = Duration.ofHours(8);
timelineStart = LocalDateTime.now().with(DayOfWeek.MONDAY).withHour(0).withMinute(0).truncatedTo(ChronoUnit.MINUTES);
// create timeline model
model = new TimelineModel();
}
public TimelineModel getModel() {
return model;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getTimezone() {
return timezone.getId();
}
public void setTimezone(String timezone) {
this.timezone = ZoneId.of(timezone);
}
public List<SelectItem> getPeriodWeeks() {
List<SelectItem> weeks = Lists.newArrayList();
weeks.add(new SelectItem(1, "1 " + JsfUtil.getStringResource("schedule_week")));
weeks.add(new SelectItem(2, "2 " + JsfUtil.getStringResource("schedule_weeks")));
weeks.add(new SelectItem(3, "3 " + JsfUtil.getStringResource("schedule_weeks")));
return weeks;
}
public int getPeriod() {
return period.getDays() / 7;
}
public void setPeriod(int nWeeks) {
this.period = Period.ofWeeks(nWeeks);
timelineEnd = null;
}
public Date getTimelineStart() {
return Date.from(timelineStart.atZone(timezone).toInstant());
}
public Date getTimelineEnd() {
if (timelineEnd == null) {
timelineEnd = timelineStart.plus(period);
}
return Date.from(timelineEnd.atZone(timezone).toInstant());
}
public void setStartsOn(String startsOn) {
timelineStart = LocalDateTime.parse(startsOn + "T00:00");
timelineEnd = null;
}
public List<User> getUsers(){
return userDao.findAll();
}
public void onDrop(TimelineDragDropEvent e) {
// get dragged model object (event class) if draggable item is within a data iteration component,
// update event's start and end dates.
User user = (User) e.getData();
Date endDate = Date.from(e.getStartDate().toInstant().plus(defaultShiftDuration));
// create a timeline event (not editable)
TimelineEvent event = new TimelineEvent(user, e.getStartDate(), endDate, true, e.getGroup());
// add a new event
TimelineUpdater timelineUpdater = TimelineUpdater.getCurrentInstance(":form:timeline");
model.add(event, timelineUpdater);
}
}
The problem was a missing widgetVar attribute in the timeline component. This looks like a bug to me, since I'm not using the client side API of the component. I will file a bug in PF Extensions project.

Using h:selectBooleanCheckbox with c:if to obtain row value of a <rich:datatable> - JSF

I'm using a <rich:datatable> to show the content of a List<Map<String, String>
In the code below, spotlightPtBindings is the List<Map<String, String> and spotlightbinding represents each Map<String, String>. In the first column, I'm showing one selectBooleanCheckBox for eah row. When a selectBooleanCheckBox is checked, I'd like to send the value of the Map<String, String> corresponding to the key "URI" as a parameter to the method: inserirBean.onSelectedCheckBox(uri), and that's why I put this value in a ui:param of name: uri. The problem here is that when I try to print the value uri received in inserirBean.onSelectedCheckBox(uri), I don't get the any output, as if it is empty. Below there's the rest of the code:
InsereDocumento.xhtml
<rich:dataTable value="#{inserirBean.spotlightPtBindings}" var="spotlightbinding">
<rich:column>
<f:facet name="header">*</f:facet>
<ui:param name="uri" value="#{spotlightbinding['URI']}"/>
<h:selectBooleanCheckbox value="#{selectionBean.selected}" />
<c:if test="#{selectionBean.selected}">
#{inserirBean.onSelectedCheckBox(uri)}"
</c:if>
</rich:column>
<c:forEach items="#{inserirBean.variableNamesPt}" var="vname">
<rich:column>
<f:facet name="header">#{vname}</f:facet>
#{spotlightbinding[vname]}
</rich:column>
</c:forEach>
</rich:dataTable> <br />
SelectionBean
package managedBeans;
import java.io.Serializable;
import javax.faces.bean.ManagedBean;
#ManagedBean
public class CheckBoxSelectionBean implements Serializable {
private transient boolean selected = false;
private static final long serialVersionUID = 1L;
public CheckBoxSelectionBean() {
// TODO Auto-generated constructor stub
}
public boolean isSelected() {
return selected;
}
public void setSelected(boolean selected) {
this.selected = selected;
}
}
InserirBean - I'm not showing here how the List<Map<String, String>> named spotlightPtBinding and how the List<String> variableNamesPt were populated, because it was a complex process, but I can tell you they are certainly populated, cause I can see their content on the screen.
#ManagedBean
public class InsereDocumentoBean implements Serializable {
private static final long serialVersionUID = 1L;
private List<String> variableNamesPt = new ArrayList<String>();
private List<Map<String, String>> spotlightPtBindings = new ArrayList<Map<String, String>>();
public List<String> getVariableNamesPt() {
return variableNamesPt;
}
public List<Map<String, String>> getSpotlightPtBindings() {
return this.spotlightPtBindings;
}
public void onSelectedCheckBox(String uri) {
System.out.println("URI: " + uri);
}
}
What may the problem be? Thank you! I'm new to JSF and need your help!
In JSF rendering is a two-step process: there's view build time and view render time.
Although they're in the same file, some tags take effect at view build time, some at render time.
JSTL tags like <c:forEach>, <c:if> and all tag handlers (including <ui:param>, see here) are evaluated at view build time, they add content to the "final" xml tree that is then rendered by JSF.
JSF HTML tags and derivates like <rich:dataTable> are evaluated at view render time, so the datatable's var is evaluated later then the <ui:param> which causes spotlightbinding not to be bound when it's assigned to uri.
Instead, I suggest you assign an ajax listener to call the function:
<h:selectBooleanCheckbox value="#{selectionBean.selected}">
<f:ajax listener="#{inserirBean.onSelectedCheckBox(spotlightbinding['URI'])}" />
</h:selectBooleanCheckbox>
Note that the listener is called whenever the value changes.

JSF links in table not updating, sorting not swapping

I have a table displayed. When the user clicks on the headers i want the table to reorganise itself, sorted according to the selected header. Clicking on the same header again, swaps the order of sorting
Within the table, there are several commandLinks.
Now, the problems are as follows
the first update to the sorting order happens, but clicking on the header again will not swap the order.
When the table is refreshed, the links have their value changed, but the link still maps to the object that was previously there
Code:
Table:
<h:form title="table">
<h:dataTable value="#{employee.employeeList}" var="a"
styleClass="order-table" headerClass="order-table-header"
rowClasses="order-table-odd-row,order-table-even-row"
style="width:100%">
<h:column>
<f:facet name="header"><h:commandLink value="Number" action="#{employee.sortByNumber()}" /></f:facet>
<h:commandLink value="#{a.number}" action="#{employee.setEmp(a)}" />
</h:column>
...
...
...
</h:dataTable>
</h:form>
Employee Bean:
public String filter = ""; //has getters and setters
public String order = ""; //has getters and setters
public static final String ORDER_ASCENDING = "asc";
public static final String ORDER_DESCENDING = "desc";
public static final String FILTER_NUMBER = "number";
public void sortByNumber(){
if(filter.equals(FILTER_NUMBER)){
swapOrder();
}else{
filter = FILTER_NUMBER;
order = ORDER_ASCENDING;
}
refreshPage();
}
public void swapOrder(){
if(order.equals(ORDER_ASCENDING)){
order = ORDER_DESCENDING;
}else{
order = ORDER_ASCENDING;
}
}
public void refreshPage(){
FacesContext context = FacesContext.getCurrentInstance();
String viewId = context.getViewRoot().getViewId();
ViewHandler handler = context.getApplication().getViewHandler();
UIViewRoot root = handler.createView(context, viewId);
root.setViewId(viewId);
context.setViewRoot(root);
}
public String setEmp(Employee employee) {
this.employee = employee;
return "details"; //redirects to the details page where the employee set in the previous line is used
}

jsf datatable row selection problem

I have a problem with selecting rows in the Primefaces Datatable. I use dynamic columns, so the standard row selection mechanism is not usable here, I implement checkbox selection myself.
To help, here's s simplified version of what I have in my xhtml:
<h:form>
<p:dataTable id="table"
var="result"
value="#{tableBean.results}">
<p:columns value="#{tableBean.columnNames}" var="column" columnIndexVar="colIndex">
<f:facet name="header">
#{column}
</f:facet>
<h:panelGroup rendered="#{colIndex==0}">
<h:outputLabel>#{rowIndex}</h:outputLabel>
<h:selectBooleanCheckbox value="#{tableBean.selectedRows[result[0]]}"/>
</h:panelGroup>
</p:columns>
</p:dataTable>
<h:commandButton value="Submit"></h:commandButton>
</h:form>
And here's what I have in the managed bean to select the checkboxes:
package testpackage;
import java.util.*;
import javax.faces.bean.*;
#ManagedBean
#SessionScoped
public class TableBean
{
private Map<String, Boolean> selectedRows = new HashMap<String, Boolean>();
List<List<String>> results = new LinkedList<List<String>>();
public TableBean()
{
List<String> row1 = new LinkedList<String>();
List<String> row2 = new LinkedList<String>();
row1.add("row1.ref");
row1.add("row1.id");
row1.add("row1.status");
row2.add("row2.ref");
row2.add("row2.id");
row2.add("row2.status");
results.add(row1);
results.add(row2);
//selectedRows.put("row2.ref", true);
}
public Map<String, Boolean> getSelectedRows()
{
return selectedRows;
}
public String submit()
{
List<List<String>> selectedResults = new ArrayList<List<String>>();
for (List<String> result : results)
{
if (selectedRows.get(result.get(0)) != null)
{
selectedResults.add(result);
selectedRows.remove(result.get(0));
}
}
return null;
}
public List<List<String>> getResults()
{
return results;
}
public List<String> getColumnNames()
{
List<String> columnNames = new LinkedList<String>();
columnNames.add("");
columnNames.add("REF");
columnNames.add("ID");
columnNames.add("STATUS");
return columnNames;
}
}
The getSelectedRows method works great, but the problem is that the setSelectedRows method is never called, so I don't know which checkboxes the user has selected. Maybe I overlook something very trivial, but cannot find the solution.
Any ideas on this? I would be very glad if you helped, or give any other row selection solution for the dynamic columns.
Thx in advance,
Levi
To me it looks you are rendering the wrong field in selectBooleanCheckBox.
You should be using variable or field from the result variable.
My solution:
In your situation you are rendering an object from List as a form of table row so if you want to make some changes and retrieve the status of that row then you should be using the variable from that object only.
I understand you are submitting the whole form and want to pickup all updated rows, in that case you will have to loop through the whole List and find all the rows which have been updated by checking the status in Request Handler(Action) bean.
Hope that helps.
The setter is never called for nested objects. You're the one who's responsible for creating them, not JSF. JSF just gets the nested object and then calls the setter on it (which is the put() method in case of a Map). You just need to determine the selected rows in the action method. Add an action method to the commandbutton:
<h:commandButton value="Submit" action="#{bean.submit}"></h:commandButton>
which is definied like follows (guessing/assuming that var="result" is in essence an Object[]):
public String submit() {
List<Object[]> selectedResults = new ArrayList<Object[]>();
for (Object[] result : results) {
if (selectedRows.get((String) result[0])) {
selectedResults.add(result);
selectedRows.remove(result[0]); // Reset.
}
}
// Now selectedResults contains all selected results.
}

Resources