I have a database table with some oneToMany relations.
this is fragment of the entity:
#OneToMany(mappedBy="auction")
private List<Biding> bidings;
now i want to print higher bid on my jsf website:
<ui:repeat var="singleAuction" value="#{auctionListBean.auctionList}" varStatus="status">
<h:outputLabel value="#{singleAuction.getHigherBid()}"/>
</ui:repeat>
this my aucListBean
#ManagedBean
public class AuctionListBean
{
#PersistenceContext()
EntityManager entityManager;
public List<AuctionBean> getAuctionList() {
Query query = entityManager.createQuery("SELECT e FROM Auction e");
#SuppressWarnings("unchecked")
List<AuctionBean> resultList = (List<AuctionBean>) query.getResultList();
return resultList;
}
}
I also have AuctionBean class which I used before to add new auction. Now I want to create a bean which has one property: list of auctionBean. I populate it using my entity and cast it to AuctionBean. In AuctionBean class I implemented mentioned method:
public double getHigherBid()
{
double higherBid = 0;
for(Biding a : bidings)
{
if(a.getCurrentPrice() > higherBid)
higherBid = a.getCurrentPrice();
}
return higherBid;
}
The problem is "Method not found". It seems like it still dont even use AuctionBean class for some reason. It may be a problem why it cannot see the method. I am doing it right and where is the problem exactly? Could you help?
I have two suggestions:
1. Use singleAuction.higherBid, not singleAuction.getHigherBid()
2. Specify the target class of your query. Here's an example:
return entityManager.createQuery("SELECT e FROM Auction e", AuctionBean.class)
.getResultList();
Related
I would like to pass an value to a managed bean under the hood. So I have this managed bean:
#ManagedBean(name = "mbWorkOrderController")
#SessionScoped
public class WorkOrderController {
// more attributes...
private WorkOrder workOrderCurrent;
// more code here...
public WorkOrder getWorkOrderCurrent() {
return workOrderCurrent;
}
public void setWorkOrderCurrent(WorkOrder workOrderCurrent) {
this.workOrderCurrent = workOrderCurrent;
}
}
It holds a parameter workOrderCurrent of the custom type WorkOrder. The class WorkOrder has an attribute applicant of type String.
At the moment I am using a placeholder inside my inputtext to show the user, what he needs to type inside an inputText.
<p:inputText id="applicant"
value="#{mbWorkOrderController.workOrderCurrent.applicant}"
required="true" maxlength="6"
placeholder="#{mbUserController.userLoggedIn.username}" />
What I want to do, is to automatically pass the value of mbUserController.userLoggedIn.username to mbWorkOrderController.workOrderCurrent.applicant and remove the inputText for applicant completely from my form.
I tried to use c:set:
<c:set value="#{mbUserController.userLoggedIn.username}" target="#{mbWorkOrderController}" property="workOrderCurrent.applicant" />
But unfortunatelly I get a javax.servlet.ServletException with the message:
The class 'WorkOrderController' does not have the property 'workOrderCurrent.applicant'.
Does anybody have an advice?
The class 'WorkOrderController' does not have the property 'workOrderCurrent.applicant'.
Your <c:set> syntax is incorrect.
<c:set value="#{mbUserController.userLoggedIn.username}"
target="#{mbWorkOrderController}"
property="workOrderCurrent.applicant" />
You seem to be thinking that the part..
value="#{mbWorkOrderController.workOrderCurrent.applicant}"
..works under the covers as below:
WorkOrderCurrent workOrderCurrent = mbWorkOrderController.getWorkOrderCurrent();
workOrderCurrent.setApplicant(applicant);
mbWorkOrderController.setWorkOrderCurrent(workOrderCurrent);
This isn't true. It works under the covers as below:
mbWorkOrderController.getWorkOrderCurrent().setApplicant(applicant);
The correct <c:set> syntax is therefore as below:
<c:set value="#{mbUserController.userLoggedIn.username}"
target="#{mbWorkOrderController.workOrderCurrent}"
property="applicant" />
That said, all of this isn't the correct solution to the concrete problem you actually tried to solve. You should perform model prepopulating in the model itself. This can be achieved by using #ManagedProperty to reference another bean property and by using #PostConstruct to perform initialization based on it.
#ManagedBean(name = "mbWorkOrderController")
#SessionScoped
public class WorkOrderController {
#ManagedProperty("#{mbUserController.userLoggedIn}")
private User userLoggedIn;
#PostConstruct
public void init() {
workOrderCurrent.setApplicant(userLoggedIn.getUsername());
}
// ...
}
Perhaps you could explain the context a bit more, but here's another solution. If you're navigating from another page, you can pass some identifier of work WorkOrder in the URL, like this http://host:port/context/page.xhtml?workOrderId=1.
Then, you can set the identifier in the managed bean like this:
<h:html>
<f:viewParam name="workOrderId" value="#{mbWorkOrderController.id}"/>
</h:html>
You'll have to add a new property to your bean:
public class WorkOrderController {
private long id;
public long getId() { return id; }
public void setId(long id) { this.id = id; }
// ...
}
And then, after the property has been set by JSF, you can find the work order in a lifecycle event:
<h:html>
<f:viewParam name="workOrderId" value="#{mbWorkOrderController.id}"/>
<f:event type="preRenderView" listener="#{mbWorkOrderController.findWorkOrder()}"/>
</h:html>
public class WorkOrderController {
private long id;
public long getId() { return id; }
public void setId(long id) { this.id = id; }
public void findWorkOrder() {
this.workOrderCurrent = null /* some way of finding the work order */
}
// ...
}
This strategy has the advantage of letting you have bookmarkable URLs.
I have used the HashMap method for binding a list of checkboxes to a Map<String, Boolean> with success. This is nice since it allows you to have a dynamic number of checkboxes.
I'm trying to extend that to a variable length list of selectManyMenu. Being that they are selectMany, I'd like to be able to bind to a Map<String, List<MyObject>>. I have a single example working where I can bind a single selectManyMenu to a List<MyObject> and everything works fine, but whey I put a dynamic number of selectManyMenus inside a ui:repeat and attempt to bind to the map, I end up with weird results. The values are stored correctly in the map, as verified by the debugger, and calling toString(), but the runtime thinks the map's values are of type Object and not List<MyObject> and throws ClassCastExceptions when I try to access the map's keys.
I'm guessing it has something to do with how JSF determines the runtime type of the target of your binding, and since I am binding to a value in a Map, it doesn't know to get the type from the value type parameter of the map. Is there any workaround to this, other than probably patching Mojarra?
In general, how can I have a page with a dynamic number of selectManyMenus? Without, of course using Primefaces' <p:solveThisProblemForMe> component. (In all seriousness, Primefaces is not an option here, due to factors outside of my control.)
The question UISelectMany on a List<T> causes java.lang.ClassCastException: java.lang.String cannot be cast to T had some good information that I wasn't aware of, but I'm still having issues with this SSCE:
JSF:
<ui:define name="content">
<h:form>
<ui:repeat value="#{testBean.itemCategories}" var="category">
<h:selectManyMenu value="#{testBean.selectedItemMap[category]}">
<f:selectItems value="#{testBean.availableItems}" var="item" itemValue="#{item}" itemLabel="#{item.name}"></f:selectItems>
<f:converter binding="#{itemConverter}"></f:converter>
<f:validator validatorId="test.itemValidator"></f:validator>
</h:selectManyMenu>
</ui:repeat>
<h:commandButton value="Submit">
<f:ajax listener="#{testBean.submitSelections}" execute="#form"></f:ajax>
</h:commandButton>
</h:form>
</ui:define>
Converter:
#Named
public class ItemConverter implements Converter {
#Inject
ItemStore itemStore;
#Override
public Object getAsObject(FacesContext context, UIComponent component, String value) {
return itemStore.getById(value);
}
#Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
return Optional.of(value)
.filter(v -> Item.class.isInstance(v))
.map(v -> ((Item) v).getId())
.orElse(null);
}
}
Backing Bean:
#Data
#Slf4j
#Named
#ViewScoped
public class TestBean implements Serializable {
private static final long serialVersionUID = 1L;
#Inject
ItemStore itemStore;
List<Item> availableItems;
List<String> itemCategories;
Map<String, List<Item>> selectedItemMap = new HashMap<>();
public void initialize() {
log.debug("Initialized TestBean");
availableItems = itemStore.getAllItems();
itemCategories = new ArrayList<>();
itemCategories.add("First Category");
itemCategories.add("Second Category");
itemCategories.add("Third Category");
}
public void submitSelections(AjaxBehaviorEvent event) {
log.debug("Submitted Selections");
selectedItemMap.entrySet().forEach(entry -> {
String key = entry.getKey();
List<Item> items = entry.getValue();
log.debug("Key: {}", key);
items.forEach(item -> {
log.debug(" Value: {}", item);
});
});
}
}
ItemStore just contains a HashMap and delegate methods to access Items by their ID field.
Item:
#Data
#Builder
public class Item {
private String id;
private String name;
private String value;
}
ItemListValidator:
#FacesValidator("test.itemValidator")
public class ItemListValidator implements Validator {
#Override
public void validate(FacesContext context, UIComponent component, Object value) throws ValidatorException {
if (List.class.isInstance(value)) {
if (((List) value).size() < 1) {
throw new ValidatorException(new FacesMessage(FacesMessage.SEVERITY_FATAL, "You must select at least 1 Admin Area", "You must select at least 1 Admin Area"));
}
}
}
}
Error:
java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to java.util.List
Stacktrace snipped but occurs on this line:
List<Item> items = entry.getValue();
What am I missing here?
As hinted in the related question UISelectMany on a List<T> causes java.lang.ClassCastException: java.lang.String cannot be cast to T, generic type arguments are unavailable during runtime. In other words, EL doesn't know you have a Map<String, List<Item>>. All EL knows is that you have a Map, so unless you explicitly specify a converter for the selected values, and a collection type for the collection, JSF will default to String for selected values and an object array Object[] for the collection. Do note that the [ in [Ljava.lang.Object indicates an array.
Given that you want the collection type to be an instance of java.util.List, you need to specify the collectionType attribute with the FQN of the desired concrete implementation.
<h:selectManyMenu ... collectionType="java.util.ArrayList">
JSF will then make sure that the right collection type is being instantiated in order to fill the selected items and put in the model. Here's a related question where such a solution is being used but then for a different reason: org.hibernate.LazyInitializationException at com.sun.faces.renderkit.html_basic.MenuRenderer.convertSelectManyValuesForModel.
Update: I should have tested the above theory. This doesn't work in Mojarra when the collection behind collectionType is in turn wrapped in another generic collection/map. Mojarra only checks the collectionType if the UISelectMany value itself already represents an instance of java.util.Collection. However, due to it being wrapped in a Map, its (raw) type becomes java.lang.Object and then Mojarra will skip the check for any collectionType.
MyFaces did a better job in this in its UISelectMany renderer, it works over there.
As far as I inspected Mojarra's source code, there's no way to work around this other way than replacing Map<String, List<Long>> by a List<Category> where Category is a custom object having String name and List<MyObject> selectedItems properties. True, this really kills the advantage of Map of having dynamic keys in EL, but it is what it is.
Here's a MCVE using Long as item type (just substitute it with your MyObject):
private List<Category> categories;
private List<Long> availableItems;
#PostConstruct
public void init() {
categories = Arrays.asList(new Category("one"), new Category("two"), new Category("three"));
availableItems = Arrays.asList(1L, 2L, 3L, 4L, 5L);
}
public void submit() {
categories.forEach(c -> {
System.out.println("Name: " + c.getName());
for (Long selectedItem : c.getSelectedItems()) {
System.out.println("Selected item: " + selectedItem);
}
});
// ...
}
public class Category {
private String name;
private List<Long> selectedItems;
public Category(String name) {
this.name = name;
}
// ...
}
<h:form>
<ui:repeat value="#{bean.categories}" var="category">
<h:selectManyMenu value="#{category.selectedItems}" converter="javax.faces.Long">
<f:selectItems value="#{bean.availableItems}" />
</h:selectManyMenu>
</ui:repeat>
<h:commandButton value="submit" action="#{bean.submit}">
<f:ajax execute="#form" />
</h:commandButton>
</h:form>
Do note that collectionType is unnecessary here. Only the converter is still necessary.
Unrelated to the concrete problem, I'd like to point out that selectedItemMap.entrySet().forEach(entry -> { String key ...; List<Item> items ...;}) can be simplified to selectedItemMap.forEach((key, items) -> {}) and that ItemListValidator is unnecessary if you just use required="true" on the input component.
hi guys sorry to post again but this is really confusing me
i have a query that i can get to run, the user enters a string and then the application will search for this in the database connected to the application, this bit i have working, however now i have successfully run the queury i am unsure of how to retrieve the data from it and be able to use it in the view in a h:datatable
currently to do the search, this is what is in the view, the faclet page the user enters
<h:inputText id="search" value="#{userdetailsController.search}" />
<p:commandButton value="Search" action="#{userdetailsController.submit}" ajax="true" />
this then goes to
#Named("userdetailsController")
#SessionScoped
public class UserdetailsController implements Serializable {
#EJB
private String search;
private List<Userdetails> item; // No need for DataModel here.
public String submit() {
item = ejbFacade.searchByString(search);
return ""; //change this !, testing only
}
public String getSearch() {
return search;
}
public void setSearch(String search) {
this.search = search;
}
then
public abstract class AbstractFacade<T> {
private Class<T> entityClass;
private List userList;
/* trying out a search function */
public List<T> searchByString(String string) {
System.out.println("in SearchByString");
return getEntityManager().createNamedQuery("Userdetails.findByUsername").setParameter("username", "%" + string + "%").getResultList();
}
which does the search in
#Entity
#Table(name = "USERDETAILS")
#XmlRootElement
#NamedQueries({
#NamedQuery(name = "Userdetails.findAll", query = "SELECT u FROM Userdetails u"),
#NamedQuery(name = "Userdetails.findById", query = "SELECT u FROM Userdetails u WHERE u.id = :id"),
#NamedQuery(name = "Userdetails.findByUsername", query = "SELECT u FROM Userdetails u WHERE u.username = :username")})
How can i once this process has been completed retrieve the data to print out in the console for example, what do i need to call to do this ?
i am unsure of how to retrieve the data from it
Your code already does that in the submit() method.
and be able to use it in the view in a h:datatable
Just add the following to your view, maybe below the command button in the same form, in order to present it:
<h:dataTable value="#{userdetailsController.item}" var="userdetails">
<h:column>#{userdetails.username}</h:column>
</h:dataTable>
And make sure that this is covered in the ajax update of the command button:
<p:commandButton ... update="#form" />
Unrelated to the concrete problem, I'd only rename the confusing property name item to items as it represents a collection of items, not a single item. Also, your submit() method doesn't need to return an empty string. It can also just be declared void. Also, ajax="true" is the default already for <p:commandButton>, you can just omit it.
JSF 2.0's ui:repeat tag gets the value of java bean(arraylist) as it's value property but size property doesn't. I am using the ui repeat inside of a datatable which shows statuses iteratively and ui repeat shows comments for each status. I am giving the size property of ui repeat from a java class because each status has a different number of comments. Therefore size should be decided dynamically. Here is the summary of what i've done. Model classes:
#ManagedBean
#RequestScoped
public class Comment {
private String commentAuthorName;
//getter and setter
}
This represents the Status class which has a list of comments:
#ManagedBean
#RequestScoped
public class Status {
private ArrayList<Comment> commentList;
private int numOfComments;
//getter and setter
}
This is giving an idea about StatusBean class:
#ManagedBean
#SessionScoped
public class StatusBean {
List<Status> panelList = new ArrayList<Status>();
List<Comment> commentList = new ArrayList<Comment>();
public static void process() {
panelList = StatusService.getPersonalStatus(log.getLoggeduser());//means fill list
commentList = StatusService.getPersonalComments(panelList);//gets comments via related statuses
for (int i=0; i<panelList.size(); i++) { //for each status
Status status = panelList.get(i);
for(Comment comment : commentList) { //for each comment of each status
status.setNumOfCommentsShown(1);
}
}
}
}
And view layer is sth like below. Ui repeat included in PrimeFaces DataTable to be able to show each comment for each status. I am using datatable because it has live scroll and it has to show all statuses iteratively and ui repeat looks best to show each comment for each status.
<p:dataTable liveScroll="true" value="#{StatusBean.panelList}"
var="Status" scrollable="true">
<ui:repeat var="Comment" value="#{Status.commentList}"
size="#{Status.numOfComments}"></ui:repeat>
</p:dataTable>
Debug result shows the #{Status.numOfComments} is correctly filled with expected integer but still it's not working. But if i write size=3 manually, it gives the expected result.
As far as I see there are no answer up till today, so I'll answer your question and give you some ideas on what I would have definitely changed in your stack.
Analysis of your problem
I have written a code similar to yours regarding the usage of <ui:repeat> with size attribute specified by a managed bean property and it didn't work for me either. No matter how hard I tried to set attribute's value by EL it didn't work. Moreover, it didn't work for the simplest EL like #{5} as well. I don't know where the problem stems from, but I think that the experiences ones here will enlighten us of why it is happening, will you?
Probably it is a glitch of the JSF <ui:repeat> component, then we shall give rise to an issue regarding it. If it is a feature it would be nice to understand it fully.
A working example of your code, as I understand it
Regarding the above code, there are many simple workarounds. In case you are so insistent on using the <ui:repeat> component I'll provide you with the basic working example. Your view layer is backed by a JSF managed bean and two model classes. My solution uses <ui:param> and <ui:fragment>. Here we go.
The view:
<p:dataTable value="#{statusBean.statusesList}" var="status">
<p:column headerText="Status name">
<h:outputText value="#{status.statusName}"/>
</p:column>
<p:column headerText="Status comments">
<ul>
<ui:param name="max" value="#{status.numOfComments}"/>
<ui:repeat var="comment" value="#{status.commentList}" varStatus="statusVar">
<ui:fragment rendered="#{statusVar.index lt max}">
<li>
<h:outputText value="Author: #{comment.authorName}; comment: #{comment.description}"/>
</li>
</ui:fragment>
</ui:repeat>
</ul>
</p:column>
</p:dataTable>
The model:
public class Comment {
private String authorName;
private String description;
}
with
public class Status {
private List<Comment> commentList;
private int numOfComments;
private String statusName;
}
The managed bean:
#ManagedBean
#RequestScoped
public class StatusBean {
private List<Status> statusesList;
public StatusBean() {
Status status;
List<Status> statusesList = new ArrayList<Status>();
Comment comment;
List<Comment> commentList;
for(int s = 0; s < 10; s++) {
commentList = new ArrayList<Comment>();
for(int c = 0; c < 20; c++) {
commentList.add(new Comment("User " + (s + 1) + "-" + (c + 1), "Description for comment " + (s + 1) + "-" + (c + 1)));
}
statusesList.add(new Status(commentList, (s + 1), "Status " + (s + 1)));
}
this.statusesList = statusesList;
}
}
Things I would definitely change in your code
After the code is working I would like to state some improvements I'd make.
Make your model a model: get rid of #ManagedBean and #...Scoped annotations and make them (detached) #Entity classes instead, delvered by your service bean;
Try to make the model as simple as possible, retaining the functionality of your application. My example uses only a list of statuses as data holders in your bean: you do not need a duplicate list of comments as it is already there for you in a Status object;
Do NOT use static methods inside beans, rather (pre)load nesessary data in your page action method (Servlet 3.0), or on preRenderView listener (Servlet 2.5), or in a #PostConstruct method. Make a good choice of bean scopes. Consulting an excellent overview by BalusC would be a great starting place;
Preload only the data you need from your data source with a service method call: if you are willing to show a limited amount of comments why fetch them from your datasource? Limit the result sets depending on what you need (remember, if you would like to implement 'view all' feature you may update your elements with ajax and load the rest). Making it work this way eliminates the need for usage of size="...";
Use standard UI components or the components of the component library of your choice, which is Primefaces, so try to follow Luiggi Mendoza's suggestions.
The sample kick-off example of a managed bean follows:
#ManagedBean
#RequestScoped
public class StatusBean {
private List<Status> statusesList;
#EJB
private StatusService statusService;
#ManagedProperty(value="#{user}")
private User user;
#PostConstruct
public void init() {
statusesList = statusService.getStatuses(user);
}
public List<Status> getStatusesList() {
return statusesList;
}
}
I don't seem to get multiple selection in PrimeFaces dataTables working.
I'm trying to implement a list of clients (dataList) and show their respective bookings in nested dataTables with a possibility to select multiple bookings for billing:
<p:dataList value="#{clientController.allClients}" var="client">
<p:column>
<p:dataTable value='#{client.bookingsDataModel}' var='item' selection="#{client.bookingsToBill}">
<p:column selectionMode="multiple" />
</p:dataTable>
</p:column>
</p:dataList>
My controller and backing bean classes:
public class ClientController {
public List<Client> getAllClients() {
return clients;
}
}
public class Client {
private List<Booking> bookings;
private Booking[] bookingsToBill;
public LeistungDataModel getBookingsDataModel() {
return new BookingsDataModel(bookings);
}
public Booking[] getBookingsToBill() {
return bookingsToBill;
}
public void setBookingsToBill(Booking[] bookingsToBill) {
this.bookingsToBill = bookingsToBill;
}
}
The data model class:
public class BookingsDataModel extends ListDataModel<Booking> implements SelectableDataModel<Booking> {
public BookingsDataModel(List<Booking> data) {
super(data);
}
#Override
public Booking getRowData(String rowKey) {
List<Booking> bookings = (List<Booking>) getWrappedData();
for(Booking booking : bookings) {
if(("booking_"+booking.getId().toString()).equals(rowKey)) {
return booking;
}
}
return null;
}
#Override
public Object getRowKey(Booking booking) {
return "booking_"+booking.getId().toString();
}
}
The browser posts the following data to the server, when I submit the form with my selections:
j_idt9%3Aj_idt13%3A0%3Aj_idt15_selection:booking_300,booking_301,booking_302
j_idt9%3Aj_idt13%3A1%3Aj_idt15_selection:booking_566,booking_567
j_idt9%3Aj_idt13%3A2%3Aj_idt15_selection:
Also, I found during debugging that the getRowData method of the BookingsDataModel returns the correct Booking objects (the selected ones).
However, always empty arrays are passed to the setBookingsToBill of my Client objects. What could be going wrong here?
Update:
An empty array is only passed the first Client objects - it doesn't matter if a booking has been selected or not. All other Client objects' setBookingsToBill methods are called with a parameter value of null.
Not really, if you want multiple selection with check box you have to do as jfs did:
In the showcase there is one example showing just that. It will create a column containing the boxes for the user to select. You can also do as you've said, using an attribute of p:dataTable, however this will not create the boxes and the user will have to control+click to do multiple select.
The selectionMode should be a part of the <p:dataTable> tag.
Here is a link to the showcase which has an example.
http://www.primefaces.org/showcase/ui/datatableRowSelectionMultiple.jsf