How to call a managed bean method from an outputLink? - jsf

This may have been covered somewhere but I'm having trouble forming the question for search engine and no goods leads thus far.
I'm working on a page that acts as entity view. Lots of results come from database and only a handful are displayed at a time. So you can imagine that I want to build a list of links that take user to another page of entities. This is all my code - no PrimeFaces or any other front-end nifty pagination solutions. At least for now.
To the code:
#Named
#SessionScoped
public class ArticleIndexBean {
List<Article> articleList=new ArrayList<>();
List<Article> articleSubList=new ArrayList<>();
#PostConstruct
public void loadScreenSupport() {
search();
toEntityPage(1);
}
protected void search() {
// this method sets articleList which is the full list fetched from the database
}
public void toEntityPage(int pageNumber) {
// this method sets articleSubList which is a subset of articleList
}
Each page link needs to call toEntiyPage(n). I am aware of commandLink but I want to avoid a POST request. Also, the bean is currently session scoped and I will try to make it conversation scoped later. It will certainly NOT be request scoped, as I don't want to do a full db search each time a user wants to jump to another page. So #PostConstruct won't help, either.
So with a menu like this: 1 * 2 * 3 * 4 * 5 how do I code an outputLink or any other type of link that will call my ArticleIndexBean.toEntityPage(int) via a GET request?
Solution
Based on input from Laurent, I added a currentEntityPageNumber property and a toCurrentEntityPage() method to my bean. The toCurrentEntityPage() simply calls toEntityPage(getCurrentEntityPageNumber()).
<html lang="en"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:ui="http://java.sun.com/jsf/facelets"
>
<f:metadata>
<f:viewParam name="pn" value="#{articleIndexBean.currentEntityPageNumber}" />
<f:event type="preRenderView" listener="#{articleIndexBean.toCurrentEntityPage()}" />
</f:metadata>
<c:forEach var="pageNumber" begin="1" end="${articleIndexBean.getEntityPageCount()}">
<h:outputLink value="ar_index.xhtml">
<h:outputText value="${pageNumber}" />
<f:param name="pn" value="${pageNumber}" />
</h:outputLink>
</c:forEach>
It would certainly be better if we could call toEntityPage(pageNumber) directly but this works fine.

Assuming you are using JSF 2.2, you could use the viewParam to retrieve the page in the GET parameters and viewAction to call a method before the rendering (actually called in the INVOKE_APPLICATION phase by default).
Facelets:
<f:metadata>
<f:viewParam name="page" value="#{articleIndexBean.entityPage}" />
<f:viewAction action="#{articleIndexBean.loadScreenSupport}" />
</f:metadata>
If you are using JSF 2.0 or JSF 2.1, then you have to replace viewAction by:
<f:event type="preRenderView" listener="#{articleIndexBean.loadScreenSupport}" />
Java:
#Named
#SessionScoped
public class ArticleIndexBean {
List<Article> articleList=new ArrayList<>();
List<Article> articleSubList=new ArrayList<>();
int pageNumber = 1; // by default first page
public void loadScreenSupport() {
search();
toEntityPage(pageNumber);
}
public int getPageNumber() {
return pageNumber;
}
public void setPageNumber(int pageNumber) {
this.pageNumber = pageNumber;
}
protected void search() {
// this method sets articleList which is the full list fetched from the database
}
public void toEntityPage(int pageNumber) {
// this method sets articleSubList which is a subset of articleList
}
}
The link to the page is then easy:
<h:outputLink value="resultPage.xhtml">
<h:outputText value="2" />
<f:param name="page" value="2" />
</h:outputLink>
Reference:
What can <f:metadata>, <f:viewParam> and <f:viewAction> be used for?

Related

How to reuse Primefaces datatable with different data in different browser tabs?

My application has a query param in the url which is used to render different data using the same xhtml (all dynamic content) in a datatable.
Scope of the bean is session scope, the datatable renders all data, and has lots of input elements which open different dialogs.
If I open another browser tab sending a different query param, the datatable renders perfectly, however if I go back to the first tab, all commandlinks won't invoke the actions and the whole application will start to act very erratically.
According to my research this is because I changed the data in the datatable, I tried naming it with dynamic ids, dynamic widget names, but nothing seems to work.
<f:metadata>
<f:viewParam name="param" value="#{moduleBean.param}"/>
<f:viewAction action="#{moduleBean.setup}" />
</f:metadata>
<c:set var="module" value="#{moduleBean.param}" />
<p:dataTable id="#{module}-dataTable" value="#{moduleBean.model[module]}" var="data">
<p:commandLink action="#{moduleBean.openModuleDetails}" update=":#{module}-searchDialog" oncomplete="PF('#{module}-searchWidget').show();">
<f:param name="module" value="#{module}" />
<f:param name="dataRow" value="#{data.dbKey}" />
</p:commandLink>
</p:dataTable>
#Named
#javax.faces.view.ViewScoped
public class ModuleBean implements Serializable {
private String param;
public void setup() throws IOException {
this.model.put(this.param, new LazyDataModel(this.param));
}
public Map<String, LazyDataModel> getModel() {
return model;
}
}
This builds all the expected html with all correct ids in each tab, however JSF is still not processing the action inside the commandlink. Needless to say, if I stick to only one browser tab everything works perfectly.
Sometimes it starts working after clicking twice in the link, but going back and forth between the browser tabs will eventually always crash it.
Adding an action listener to the commandlink didnt fix it either.
Any suggestions on how to make JSF treat the same datatable as different entities on the same page but with different parameters ?
Without knowing more about the underlying bean - if you place your moduleBean in #SessionScoped this would be the expected behavior. The session (and session scoped beans) are shared between browser tabs. So you cannot rely on the underlying values from two different tabs.
Try changing to #RequestScoped/#ViewScoped for the backing values of the table data.
Here is a complete solution that works, note that this uses PrimeFaces 6.2, Apache Commons and Lombok;
#Data
#Named
#ViewScoped
public class TableTestBackingBean implements Serializable {
private int param;
#Inject
private PersonsBean personsBean;
public void onClicked() {
System.out.println("Clicked fine!");
}
public List<Person> getPersons() {
return personsBean.getPersons()[param];
}
}
#Data
#ApplicationScoped
public class PersonsBean {
#Data
#AllArgsConstructor
public class Person {
private String firstName;
private String lastName;
private int age;
}
private List<Person> persons[];
#PostConstruct void init() {
persons = new List[4];
for (int j = 0; j < 4; j++) {
persons[j] = new ArrayList<>();
for (int i = 0; i < 30; i++) {
final String firstName =
RandomStringUtils.randomAlphanumeric(10);
final String lastName =
RandomStringUtils.randomAlphanumeric(10);
final int age = RandomUtils.nextInt(0, 120);
persons[j].add(new Person(firstName, lastName, age));
}
}
}
}
<?xml version="1.0" encoding="UTF-8"?>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html"
xmlns:p="http://primefaces.org/ui" xmlns:f="http://java.sun.com/jsf/core">
<f:metadata>
<f:viewParam name="param" value="#{tableTestBackingBean.param}"/>
</f:metadata>
<h:head>
<title>Test</title>
</h:head>
<h:body>
<p:dataTable value="#{tableTestBackingBean.persons}" var="t">
<p:column headerText="First Name"><h:outputText value="#{t.firstName}" /></p:column>
<p:column headerText="Last Name"><h:outputText value="#{t.lastName}" /></p:column>
<p:column headerText="Age"><h:outputText value="#{t.age}" /></p:column>
</p:dataTable>
<h:form>
<p:commandButton action="#{tableTestBackingBean.onClicked}" value="Click Me!" />
</h:form>
</h:body>
</html>
This uses an application scoped bean for the table data, keeping it completely static. This works without a hitch and the table renders the data differently based on the parameter passed in param.

Pass a parameter to f:event listener

I have the following event of preRenderView type to execute a method before loading the page:
<f:event listener="#{aprobacionFlujoController.preRender}" type="preRenderView" />
I need to send a parameter to that listener, which is the best way to do it, probe as follows:
<f:metadata>
<f:event listener="#{aprobacionFlujoController.preRender}" type="preRenderView" />
<f:attribute name="myid" value="true" />
</f:metadata>
Controller
public void retrieveData(ComponentSystemEvent event) {
String id = (String) event.getComponent().getAttributes().get("myid");
}
But getAttributes() method returns empty.
Well, if you are using JSF 2.2 then you can use the <f:viewAction /> to perform tasks before page is rendered since what you are trying to achieve from what I can tell is to retrieve data from DB to display, in other words to have a dynamic web page.
In your facelets page:
<h:head>
<f:metadata>
<f:viewParam name="parameter_id" value="#{yourbean.searchId}" />
<f:viewAction action="#{yourbean.getInfo}" />
</f:metadata>
</h:head>
What the viewParam does is to set the property passed in the value attribute; this property comes as an HTTP GET REQUEST parameter, e.g:
yourwebsite.com/yourpage.xhtml?parameter_id=parameter_value
In your Managed Baen you just have a method that performs the data extraction using the property:
#NAmed
#your_scope
public class YourBean implements Serializable{
private String searchId;
public void getInfo(){
yourService.getData(searchId);
// ... the rest ....
}
// setter & getter for searchId
}
If you want to do it your way, asuming its because you're using JSF 2.1 | 2.0 then maybe try this to get the request parameters:
String parameter = ((Map<String,String>)Facescontext.getExternalContext().getRequestParameterMap()).get("your_parameter_name");

Re-execute f:viewAction when ViewScoped bean is recreated following a POST request

Environment: JSF 2.2 with Mojarra 2.2.12 & CDI ViewScoped beans & javax.faces.STATE_SAVING_METHOD set to client.
In order to properly initialize my bean thanks to <f:viewParam ... />, I would like to (re-)execute <f:viewAction action="#{bean.onLoad}" /> when my ViewScoped bean is recreated (view was pushed out from the LRU, cf. com.sun.faces.numberOfLogicalViews) following a POST request.
<f:metadata>
<f:viewParam maxlength="100" name="name" value="#{bean.file}" />
<f:viewAction action="#{bean.onLoad}" />
</f:metadata>
<o:form includeRequestParams="true">
<!-- action can only work if onLoad has been called -->
<p:commandButton action="#{bean.action}" />
</o:form>
Any ideas?
Notes:
I'm aware of postBack="true" but it's not suitable as bean.onLoad() would be called on every POST request.
I cannot call onLoad() in #PostConstruct method because values have not been set by viewParam yet (cf. When to use f:viewAction versus PostConstruct?).
I'm aware of postBack="true" but it's not suitable as bean.onLoad() would be called on every POST request.
You can just use EL in onPostback attribute wherein you check if the model value and/or request parameter is present.
If the model value is required, then just check if it's present or not:
<f:metadata>
<f:viewParam maxlength="100" name="name" value="#{bean.file}" required="true" />
<f:viewAction action="#{bean.onLoad}" onPostback="#{empty bean.file}" />
</f:metadata>
If the model value is not required, then check the request parameter too:
<f:metadata>
<f:viewParam maxlength="100" name="name" value="#{bean.file}" />
<f:viewAction action="#{bean.onLoad}" onPostback="#{empty bean.file and not empty param.name}" />
</f:metadata>
I cannot call onLoad() in #PostConstruct method because values have not been set by viewParam yet.
Given the presence of <o:form> in your snippet, I see that you're using OmniFaces. The very same utility library offers a CDI #Param annotation for the very purpose of injecting, converting and validating HTTP request parameters before the #PostConstruct runs.
The entire <f:viewParam><f:viewAction> can therefore be replaced as below:
#Inject #Param(name="name", validators="javax.faces.Length", validatorAttributes=#Attribute(name="maximum", value="100"))
private String file;
#PostConstruct
public void onLoad() {
if (!Faces.isValidationFailed()) {
// ...
}
}
Or if you've Bean Validation (aka JSR303) at hands:
#Inject #Param(name="name") #Size(max=100)
private String file;
#PostConstruct
public void onLoad() {
if (!Faces.isValidationFailed()) {
// ...
}
}
Though I havent been struggeling with the numberOfLogicalView I often need to reinitialize beans with a long living scope. Eg a usecase is to (re-) load some detail data from database when the user clicks on some entity in a table-view. Then I simply use some initialized-flag, eg
#SomeLongLivingScope
public class MyBean {
private boolean initialized = false;
public void preRenderView() {
if ( initialized ) return;
try {
do_init();
} finally {
initialized = true;
}
}
public void someDataChangedObserver( #Observes someData ) {
...
initialized = false;
}
}
Using this pattern you can use your viewAction with postBack="true".

how to add a component to the page from a managed bean in jsf / primefaces [duplicate]

This question already has answers here:
How to dynamically add JSF components
(3 answers)
Closed 6 years ago.
A click on a commandButton should trigger an action in a ManagedBean: to add a new "outputText" component to the current page.
The overall idea is to have the page changed dynamically with user action, with server side action because new elements added to the page need data from a db to be laid out.
-> How do I add a component to the page from a managed bean in jsf / primefaces? Let's say that the elements should be added in an existing div like:
<div id="placeHolder">
</div>
(this div could be changed to a jsf panel if needs be)
Note: if alternative methods are better to achieve the same effect I'd be glad to learn about them.
I'll provide you another solution apart from the one you posted. Basically it has a List of given outputs, which is increased everytime the button is pushed. That should render exactly the same DOM tree as the solution you stated:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:ui="http://java.sun.com/jsf/facelets">
<h:head>
<title>Tiles</title>
<h:outputStylesheet name="css/320andup_cle.css" />
</h:head>
<h:body>
<h:form>
<h:commandButton actionListener="#{bean.createNewTile}" title="new"
value="new" />
</h:form>
<h:panelGroup layout="block" id="tiles">
<ui:repeat var="str" value="#{bean.strings}">
<h:panelGroup>
<h:outputText styleClass="tile" value="#{str}" />
</h:panelGroup>
</ui:repeat>
</h:panelGroup>
</h:body>
</html>
#ManagedBean
#SessionScoped
public class Bean {
List<String> strings = new ArrayList<String>();
public List<String> getStrings() {
return strings;
}
public void createNewTile() {
strings.add("output");
}
}
Apart from being much simpler IMHO, it has a main advantage: it doesn't couple your server side code to JSF implicit API. You can change the #ManagedBean annotation for #Named if you want it to be a CDI managed bean.
The solution:
This is a jsf page with a button creating a new div each time it is clicked:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html">
<h:head>
<title>Tiles</title>
<h:outputStylesheet name="css/320andup_cle.css" />
</h:head>
<h:body>
<h:form>
<h:commandButton actionListener="#{bean.createNewTile()}" title="new" value="new"/>
</h:form>
<h:panelGroup layout="block" id="tiles">
</h:panelGroup>
</h:body>
</html>
The Managed Bean:
#Named
#SessionScoped
public class Bean implements Serializable {
private UIComponent found;
public void createNewTile() {
HtmlPanelGroup div = new HtmlPanelGroup();
div.setLayout("block");
HtmlOutputText tile = new HtmlOutputText();
tile.setValue("heeeeeRRRRRRRRRRRRRR ");
tile.setStyleClass("tile");
div.getChildren().add(tile);
doFind(FacesContext.getCurrentInstance(), "tiles");
found.getChildren().add(div);
}
private void doFind(FacesContext context, String clientId) {
FacesContext.getCurrentInstance().getViewRoot().invokeOnComponent(context, clientId, new ContextCallback() {
#Override
public void invokeContextCallback(FacesContext context,
UIComponent component) {
found = component;
}
});
}
}
See this app built with this logic of dynamically generated components: https://github.com/seinecle/Tiles

jsf viewparam lost after validation error [duplicate]

This question already has answers here:
Retaining GET request query string parameters on JSF form submit
(2 answers)
Closed 6 years ago.
I'm facing the following issue: in one page, I list all users of my application and have an "edit" button for each one, which is a "GET" link with ?id=<userid>.
The edit page has a <f:viewParam name="id" value="#{editUserBean.id}"/> in metadata.
If I made some input mistakes and submit (I use CDI Weld Bean validation), the page is displayed again, but I've lost the ?id=... in the URL and so lose the user id of the user I'm editing.
I've looked at a similar problem described in JSF validation error, lost value, but the solution with inputhidden (or worse, with tomahawk, which looks overkill) requires lot of uggly code.
I've tried adding a "Conversation" with CDI, and it is working, but it looks like too much overkill to me again.
Does there exists a simple solution in JSF to preserve view parameters in case of validation errors?
[My environment: Tomcat7 + MyFaces 2.1.0 + Hibernate Validator 4.2.0 + CDI(Weld) 1.1.2]
Interesting case. For everyone, the following minimal code reproduces this:
Facelet:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
>
<f:metadata>
<f:viewParam id="id" name="id" value="#{viewParamBean.id}"/>
</f:metadata>
<h:body>
<h:messages />
#{viewParamBean.id} <br/>
<h:form>
<h:inputText value="#{viewParamBean.text}" >
<f:validateLength minimum="2"/>
</h:inputText>
<h:commandButton value="test" action="#{viewParamBean.actionMethod}"/>
</h:form>
</h:body>
</html>
Bean:
#ManagedBean
#RequestScoped
public class ViewParamBean {
private long id;
private String text;
public void actionMethod() {
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
}
If you call the Facelet with viewparam.xhtml?id=12 it will display the 12 onscreen. If you then input something valid, e.g. aaaaa, the id will disappear from the URL, but keeps being displayed on screen (owning to the stateful nature of ui components).
However... as OP mentioned, as soon as any validator error occurs (e.g. entering a), the id will be permanently lost. Entering valid input afterwards will not bring it back. It almost seems like a bug, but I tried both Mojarra 2.1 and Myfaces 2.1 and both have the same behavior.
Update:
After some inspection, the problem seems to be in this method of `UIViewParameter' (Mojarra):
public void encodeAll(FacesContext context) throws IOException {
if (context == null) {
throw new NullPointerException();
}
// if there is a value expression, update view parameter w/ latest value after render
// QUESTION is it okay that a null string value may be suppressing the view parameter value?
// ANSWER: I'm not sure.
setSubmittedValue(getStringValue(context));
}
And then more specifically this method:
public String getStringValue(FacesContext context) {
String result = null;
if (hasValueExpression()) {
result = getStringValueFromModel(context);
} else {
result = (null != rawValue) ? rawValue : (String) getValue();
}
return result;
}
Because hasValueExpression() is true, it will try to get the value from the model (the backing bean). But since this bean was request scoped it will not have any value for this request, since validation has just failed and thus no value has ever been set. In effect, the stateful value of UIViewParameter is overwritten by whatever the backing bean returns as a default (typically null, but it depends on your bean of course).
One workaround is to make your bean #ViewScoped, which is often a better scope anyway (I assume you use the parameter to get a user from a Service, and it's perhaps unnecessary to do that over and over again at every postback).
Another alternative is to create your own version of UIViewParameter that doesn't try to get the value from the model if validation has failed (as basically all other UIInput components do).
You don't actually loose the view parameter. f:viewParam is stateful, so even if it's not in the URL, it's still there. Just put a break point or system.out in the setter bound to view param.
(if you google on viewParam stateless stateful you'll find some more info)
I've the same in my Application. I switched to #ViewAccessScoped which allows way more elegant implementations.
<f:metadata>
<f:viewParam id="id" name="id" value="#{baen.id}"/>
</f:metadata>
Or when you the first time get parameter from url, save it in session map and continue use from that map, and after save/or update the form clean map.
This is tricky, but you can try to restore view parameters with History API:
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:f="http://xmlns.jcp.org/jsf/core"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets">
<f:metadata >
<f:viewParam name="param1" value="#{backingBean.viewParam1}" />
<f:viewParam name="param2" value="#{backingBean.viewParam2}" />
<f:viewAction action="#{view.viewMap.put('queryString', request.queryString)}" />
</f:metadata>
<h:head>
<title>Facelet Title</title>
</h:head>
<h:body>
<ui:fragment rendered="#{facesContext.postback}" >
<script type="text/javascript">
var url = '?#{view.viewMap.get('queryString')}';
history.replaceState({}, document.title, url);
</script>
</ui:fragment>
<h:form>
<h:inputText id="name" value="#{backingBean.name}" />
<h:message for="name" style="color: red" />
<br />
<h:commandButton value="go" action="#{backingBean.go}" />
</h:form>
<h:messages globalOnly="true" />
</h:body>
</html>

Resources