I would like to be able to change the text in the url bar to reflect the current state of my app (like GMail does).
Mainly i'm referring to changing the url parameters to reflect the values in my form. (Once a user enters a parameter I would like to change the url parameter that corresponds)
Any idea on how to do that?
Thanks!
To the point, you want fire a HTTP GET request instead of a HTTP POST request. Changing the view side is trivial, use
<form action="targetpage.jsf">
instead of
<h:form>
In the managed bean which is associated with targetpage.jsf you however need to do a bit more changes. JSF 1.2 doesn't offer facilities to set GET request parameters for you by the view declaration, nor does it convert/validate the parameters (JSF 2.0 has <f:viewParam> for this).
You need to gather/convert/validate all request parameters yourself in the constructor and/or #PostConstruct of the backing bean and invoke the action over there as well. There are basically two ways to gather the parameters:
Define the parameter as <managed-property> of the <managed-bean> in faces.config.xml so that JSF will set it for you.
E.g.
<h:inputText id="input" />
(which will generate <input type="text" id="input" name="input" /> in HTML, it's the name attribute which is been used as request parameter name; rightclick page in browser and view source if you're unsure)
with
<managed-property>
<property-name>input</property-name>
<value>#{param.input}</value>
</managed-property>
and
private String input; // +setter
EL supports automatic conversion to primitive types and their wrappers as well, so you could for numbers also use private Long input; instead. The caveat is however that this would throw an ugly and unhandleable exception when the value is not parseable as a number.
Or, gather them yourself by ExternalContext#getRequestParameterMap().
public class Bean {
private String input;
public Bean() {
Map<String, String> params = FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap();
input = params.get("input");
// ...
}
}
This allows for more fine-grained conversion/validation/errorhandling.
Related
I am working on a JSF app with a form:
<h:inputText value="#{model.firstname}" />
<h:inputText value="#{model.employeeNumber}" converter="javax.faces.Integer"/>
...
Here's the model with bean validation:
#NotNull(message="Please enter a surname")
private String firstname;
#Min(value=1)
#Max(value=2000)
private Integer employeeNumber;
...
//setters and getters
Everything is working nicely apart from the explicitly coded 'back' button within the page which goes to the previous page / form.
I want the values the user entered to restore when they return to the above page again regardless of whether the data is valid.for example, if the user enters abc into the employeeNumber field this String cannot be stored to the Integer on the model.
I understand that JSF stores user entered values into "Request Values" for each UIComponent. It is these I would like to restore rather than that of my model because the above form had not had it's data validated yet.
How can I do this?
(data validation will happen when the user clicks submit).
My colleague has just mentioned there may be a way to accomplish this using omnifaces
As of now, there is. Lot of things were already available in OmniFaces except of only one small missing key part. I just committed a Hacks#getStateHelper() to 2.3 SNAPSHOT which should expose the protected UIComponent#getStateHelper() method into public. Then, it's doable together with help of EditableValueHolderStateHelper already in OmniFaces since 1.0 and with <o:form> and <o:ignoreValidationFailed> in order to invoke action anyway irrespective of conversion/validation errors (as the "back" button should do).
So, if you make sure you use at least OmniFaces 2.3 (currently only available as SNAPSHOT), then you can achieve the requirement with below session scoped helper bean, utilizing several OmniFaces utility classes Faces, Hacks and EditableValueHolderStateHelper:
#Named
#SessionScoped
public class Forms implements Serializable {
private transient Map<String, StateHelper> states = new ConcurrentHashMap<>();
public void saveState(ComponentSystemEvent event) {
UIComponent form = event.getComponent();
FacesContext context = Faces.getContext();
StateHelper state = Hacks.getStateHelper(form);
EditableValueHolderStateHelper.save(context, state, form.getFacetsAndChildren());
states.put(Faces.getViewId(), state);
}
public void restoreState(ComponentSystemEvent event) {
StateHelper state = states.get(Faces.getViewId());
if (state != null) {
UIComponent form = event.getComponent();
FacesContext context = Faces.getContext();
EditableValueHolderStateHelper.restore(context, state, form.getFacetsAndChildren());
}
}
public void removeState() {
states.remove(Faces.getViewId());
}
}
The saveState needs to be invoked during postValidate event of the form component. The restoreState() needs to be invoked during postAddToView event of the form component. The removeState() needs to be invoked during succesful action. Below is an example form:
<o:form>
<f:event type="postAddToView" listener="#{forms.restoreState}" />
<f:event type="postValidate" listener="#{forms.saveState}" />
<h:inputText value="#{bean.string}" required="true" />
<h:inputText value="#{bean.integer}" required="true" />
<h:commandButton value="save" actionListener="#{forms.removeState()}" action="#{bean.save}" />
<h:commandButton value="back" action="#{bean.back}">
<o:ignoreValidationFailed />
</h:commandButton>
<h:messages />
</o:form>
Major advantage of this approach is that no modifications needs to be made to existing validation rules and backing beans, hereby thus keeping all advantages of JSF and BV.
Make sure you clear server session state and/or increase serialVersionUID of Forms class whenever you make changes in the component tree structure of the associated forms, else you'll have to make prechecks and/or properly handle exceptions. Giving the forms and input components a fixed ID is also strongly recommended.
I have dealt with just this problem in the past - by not using the validation annotations such as #NotNull, #Min, and #Max. When using those annotations, invalid data cannot be applied to the model, and so the state cannot be saved on the server.
Instead, I had to code the validation logic in the method behind the Submit button. The downside is that JSF isn't doing the work for you; you have to do it yourself. The upside is that you have more control over exactly when and how the validation is applied.
I do not really understand how getter and setter work althougth it is a basic concept. I have the following code, how is the attribute id sent to Managed Bean? Is it captured by getter method?
My facelet
<p:inputText id="id" value="#{bean.id}">
My managed bean
private String id;
public void setId(String id) {
this.id = id;
}
public String getId() {
return id;
}
The call of getter and setter methods by #{} expressions is not part of JSF but Expression Language (most known as EL). JSF takes advantage of EL to bind the data of the HTML components to the fields of a bean through proper getters and setters. This is:
If the bean exists, Expression Language will execute the proper getter of the registered bean in the proper scope.
If client performs a form submission or an ajax request, then the components that are sent to the server (usually all the components in the <h:form>, in case of ajax requests you can state which components to send to the server) will contain a new value, and this value will be set to the field with the proper setter method.
For example, you have a SayHelloBean which belongs to request scope:
#RequestScoped
#ManagedBean
public class LoginBean {
private String name;
//proper getter
public String getName() {
return this.name;
}
//proper setter
public void setName(String name) {
this.name = name;
}
}
And these 2 facelets pages (since it's an example I avoid declaring <html>, <h:head>, <h:body> and other elements, just focusing on the relevant code)
Page1.xhtml:
<h:form>
Please tell me your name
<h:inputText value="#{loginBean.name}" />
<h:commandButton action="page2" />
</h:form>
Page2.xhtml:
Hello #{loginBean.name}
This is what happens behind the scenes:
When Page1.xhtml is loaded, a new instance of LoginBean, which we may call loginBean, will be created by JSF and registered into JSP request scope. Since the value of <h:inputText /> is bound to LoginBean#name (which is read as the field name of LoginBean class), then EL will display the value of loginBean#name (which is read as the field name of instance loginBean), and since that is not initialized, EL will display null, as an empty string.
When you submit the form of Page1.xhtml, since LoginBean is #RequestScoped then JSF will create a new instance of LoginBean, which we may call it loginBean2 (adding 2 in the end because this instance is totally different from the loginBean previously created) and will register it in JSP request scope. Since the value of <h:inputText /> is bound to LoginBean#name, JSF will validate and set the data by calling the proper setter. This will make loginBean2#name have the value of the <input type="text"> that was rendered by <h:inputText/>.
At last, JSF will make sure to navigate to Page2.xhtml through forward, where when processing it, it will find #{loginBean.name} and EL will check for the value of loginBean2#name and replace it.
The steps explained here are a very small explanation (and with lot of elements not explained) of the JSF lifecycle and how JSF uses getters and setters.
More info:
How to pass parameter to jsp:include via c:set? What are the scopes of the variables in JSP?
How to choose the right bean scope?
The Lifecycle of a JavaServer Faces Application
Differences between Forward and Redirect
Additional note: since you're learning JSF, avoid putting any business logic code in getters/setters. This is greatly explained here: Why JSF calls getters multiple times
Whenever you use something like
#{someBean.someField}
the EL looks for a someBean.getSomeField() or someBean.setSomeField(...) method, depending on whether you're reading that field or writing in it (which can easily be inferred from the context). JSF never accesses a field directly (i.e without making use of its getter or setter). Try deleting the getter and setter of a given field and you'll see it won't work.
I have a facelet template with:
<f:metadata>
<o:viewParam name="id" value="#{homeBean.id}" />
</f:metadata>
<h:form>
<h:inputHidden value="#{homeBean.id}" />
<h:inputText value="#{homeBean.user.firstName}" />
<h:commandButton value="Submit" action="#{homeBean.onSave()}" />
</h:form>
and a request scoped bean with:
#Named
#RequestScoped
public class HomeBean {
private Integer id;
private User user;
public void setId(Integer id) {
System.out.println("setId called");
user = // code for loading User entity bean with supplied id
}
// other accessors for id and user
}
Initial page load works well, entity is loaded and displayed in a form, inputHidden is set to entity id. Problem is that submit throws:
javax.el.PropertyNotFoundException - Target unreachable, base expression '. user' resolved to null
probably because getUser is called before setId. How can I solve this? I really would like to have a request scoped bean, I know that this can be easily solved with at least viewaccess scoped bean.
EDIT: Now i noticed that exception is thrown in Process Validations phase, I initially thought that exception is thrown in Update Model Values phase. I changed "private User" to "private User user = new User()" and now it's OK, but it feels little weird.
Regards,
Pavel
The OmniFaces <o:viewParam> sets the request parameter only in the initial request and not in postbacks. This is intented to be used with #ViewScoped beans so that the request parameter isn't unnecessarily been validated, converted and updated on every single postback (because it's already still present in a view scoped bean). The API documentation and the showcase example also explicitly mentions that it should be used with view scoped beans.
You've there however a request scoped bean which get trashed and recreated on every single request, also on postbacks to the same view. So the user property disappears and falls back to default null on every subsequent postback request.
There are basically 2 ways to fix it:
Replace <o:viewParam> by <f:viewParam>. It will call the setter on every request, also on postbacks.
Replace #Named #RequestScoped by #ManagedBean #ViewScoped, this way the bean will live as long as you're interacting with the same view. Or if you insist in using CDI, use #Named #ConversationScoped instead, but you have to manage the begin and end of the conversation yourself.
In a previous question BalusC gave me good advice on how a button, in place of a commandButton is useful for non ajax navigation. In particular it updates the destination address in the http: position which is useful for the user to bookmark a page.
I tried to use this information to my advantage until I came upon a problem. In a button I tried to use outcome="#{backing.something}" to find out that it gives me a null result. This looks like a timing problem in that action="#{}" is evaluated only when the button is pressed whereas outcome apparently wants a fixed string which gets checked when the page is loaded.
So I went back to commandButton with ajax="false". This has a problem that my navigation address is the page I came from, not the one I am navigating to. This is the wrong bookmark for the user.
I appreciate all the help I have received in stackoverflow on my learning exercise.
Ilan
The <h/p:button outcome> is not intented to invoke a bean action method, but to contain the outcome string directly. Any EL in there is evaluated immediately as a value expression. So the method behind it would immediately be invoked when you just open the page containing the <h/p:button>.
There are in your particular case basically two ways to invoke a bean action method on navigation. If you need to invoke it before the navigation takes place and the action isn't intented to be re-invoked everytime when the enduser reopens/reloads the GET request, then make it a POST-Redirect-GET request. It's a matter of adding faces-redirect=true to the outcome value in query string syntax.
E.g.
<p:commandButton action="#{bean.submit}" ... />
with
public String submit() {
// ...
return "nextpage?faces-redirect=true";
}
This way the browser will be redirected to the target page after POST, hence the enduser will see the target URL being reflected in the address bar.
Or if you need to invoke the action everytime when the enduser reopens/reloads the GET request, do the job in the (post)constructor or preRenderView listener method of the request/view scoped backing bean instead.
E.g.
<p:button outcome="nextpage" ... />
with
#ManagedBean
#RequestScoped
public class NextpageBacking {
public NextpageBacking() {
// In constructor.
}
#PostConstruct
public void onPostConstruct() {
// Or in postconstructor (will be invoked after construction AND injection).
}
public void onPreRenderView() {
// Or before rendering the view (will be invoked after all view params are set).
}
// ...
}
The pre render view listener method needs to be definied as follows in the nextpage
<f:event type="preRenderView" listener="#{nextpageBacking.onPreRenderView}" />
See also:
What can <f:metadata>, <f:viewParam> and <f:viewAction> be used for?
Communication in JSF 2.0 - Processing GET request parameters
I am using JSF 1.1. I have a JSF page with a request scoped bean and a readonly input field.
<h:inputText id="dt" value="#{bean.sdate}" readonly="#{bean.disable}" />
<a onclick="cal('dt');"><img src="fr.gif" border="0"></a>
When I set the input value using JavaScript and click on command button, then the data in input field disappears.
How is this caused and how can I solve it.
That's because the property is set to readonly. If this evaluates true, then JSF won't process the submitted value and hence the model won't be updated. If you want to set it to readonly on rendering the view and have JSF to process the submitted value, then you'd need to make it to evaluate true on render response phase only. You can use FacesContext#getRenderResponse() for this. You'd need to do this in your isDisable() method.
public boolean isDisable() { // TODO: rename to isReadonly().
return FacesContext.getCurrentInstance().getRenderResponse();
}
Note: in JSF2 you could access FacesContext#getCurrentInstance() by #{facesContext} in the view as well, this saves some boilerplate in the model:
<h:inputText ... readonly="#{facesContext.renderResponse}" />
Also note that when you're using JSF2 <f:viewParam>, then this approach won't work on GET requests anymore. See also Make a p:calendar readonly for the explanation and workaround.