This question already has answers here:
How to ajax-refresh dynamic include content by navigation menu? (JSF SPA)
(3 answers)
Closed 1 year ago.
I'm relatively new to JSF and trying to learn how current JSF 2 applications are designed. I've seen reference to single page applications that use ajax. Can someone fill me in on some of the techniques used and / or point me to a model or book? The books I've seen (JSF Complete Reference etc.) are good for basic tech issues but I can't find a source for current design techniques.
Thanks
Dave
In order to implement your Single Page Application, you should state which piece of your page should be rendered. This can be accomplished making use of a boolean flag such as create, edit, list, and so on. For instance, see the following (Just relevant code)
<h:body>
<h:form rendered="#{userController.stateManager.create}">
<h:panelGroup rendered="#{not empty facesContext.messageList or userController.stateManager.failure}">
<!--render error message right here-->
</h:panelGroup>
<div>
<label>#{messages['br.com.spa.domain.model.User.name']}</label>
<h:inputText value="#{user.name}"/>
</div>
<h:commandButton action="#{userController.create}">
<f:ajax execute="#form" render="#all"/>
<f:actionListener type="br.com.spa.web.faces.listener.StateManagerActionListener" />
<f:setPropertyActionListener target="#{userController.stateManager.create}" value="true"/>
<f:setPropertyActionListener target="#{userController.user}" value="#{user}" />
</h:commandButton>
</form>
</h:body>
Notice that our form will be rendered when a flag create is true - See second line above. To wrap our flags, we create a classe named StateManager as follows
/**
* I am using lombok, which takes care of generating our getters and setters. For more info, please refer http://projectlombok.org/features/index.html
*/
#Setter #Getter
public class StateManager {
private boolean create;
private boolean edit;
private boolean list;
}
Now, because we are using only a single page, we should use a ViewScoped managed bean, which keep our managed bean scoped active as long as you are on the same view - Is it a single page application, right ? So, no navigation. With this in mind, let's create our managed bean.
#ManagedBean
#ViewScoped
public class UserController implements StateManagerAwareManagedBean {
private #Inject UserService service;
private #Getter #Setter stateManager = new StateManager();
private #Getter #Setter List<User> userList = new ArrayList<User>();
private #Getter #Setter User user;
#PostConstruct
public void initialize() {
list();
}
public void create() {
service.persist(user);
stateManager.setCreate(false);
stateManager.setList(true);
stateManager.setSuccess(true);
}
public void edit() {
service.merge(user);
stateManager.setEdit(false);
stateManager.setList(true);
stateManager.setSuccess(true);
}
public void list() {
userList = service.list();
stateManager.setList(true);
}
}
For each action method, we define which piece of our page should be rendered. For instance, consider that our form was processed, covering all of JSF lyfecycle, which implies that their values was successfully converted and validated, and our action method invoked. By using as example our create action method - see above -, we set its create flag as false because our form was converted and validated, so we do not need to show it again (Unless you want). Furthermore, we set both list and success flag as true, which indicates that the list of our page should be rendered and our form was successfully processed - You could use this flag to show something like "User created" such as bellow
<h:panelGroup rendered="#{userController.stateManager.success}">
#{messages['default.created.message']}
</h:panelGroup>
Now, let's discuss which piece of our page should be rendered when it is called for the first time. Maybe you do not know but a void method annotated with #PostConstruct will be called first. So we define which piece of our page should be rendered. In our example, we call list method, which sets its list flag as true and populate a backing list.
#PostConstruct
public void initialize() {
list();
}
Finally, let's review the following order nested within h:commandButton
<h:commandButton action="#{userController.create}">
<f:ajax execute="#form" render="#all"/>
<f:actionListener type="br.com.spa.web.faces.listener.StateManagerActionListener" />
<f:setPropertyActionListener target="#{userController.stateManager.create}" value="true"/>
<f:setPropertyActionListener target="#{userController.user}" value="#{user}" />
</h:commandButton>
First of all, you should call an ActionListener - here called StateManagerActionListener - which takes care of resetting any StateManager - code bellow. It must be called first before any other setPropertyActionListener designed to control any flag because the order defined within h:commandButton is the order in which they will be called. keep this in mind.
public class StateManagerActionListener implements ActionListener {
public void processAction(ActionEvent e) throws AbortProcessingException {
Map<String,Object> viewMap = FacesContext.getCurrentInstance().getViewRoot().getViewMap();
for(Map.Entry<String,Object> entry: viewMap.entrySet()) {
if(entry.getValue() instanceof StateManagerAwareManagedBean) {
((StateManagerAwareManagedBean) entry.getValue()).setStateManager(new StateManager());
}
}
}
}
StateManagerAwareManagedBean - used in our ViewScoped Managed bean -, which allows that we reset any StateManager of any ManagedBean instead of resetting one by one in our ActionListener, is defined as follows
public interface StateManagerAwareManagedBean {
StateManager getStateManager();
void setStateManager(StateManager stateManager);
}
Second, after defining our ActionListener, we use a setPropertyActionListener which set the flag which controls the enclosing piece of the view as true. It is needed because our form is supposed to be not converted and validated. So, in our action method, we set this flag as false as discussed before.
A couple of notes
User is marked as a RequestScoped ManagedBean so that it can not be injected into a ViewScoped one using a ManagedProperty because its scope is shother. To overcome this issue, i set its value by using a <f:setPropertyActionListener target="#{userController.user}" value="#{user}"> - See our form
Our example use JEE features which need a proper Application Server. For more info, refer http://docs.oracle.com/javaee/6/tutorial/doc/
ManagedBean can play different roles such as a Controller, DTO and so on. When it play a role of a Controller, i prefer suffix its name with Controller. For more info, refer http://java.dzone.com/articles/making-distinctions-between
Related
I'm using JSF and Primefaces. I have an edit.xhtml page with a f:viewParam receiving an entity id:
<f:viewParam name="id" value="#{backingBean.entity}" converter="entityConverter" />
I have two commandButton, one to submit and save the entity:
<p:commandButton ajax="false" value="#{bundle.save}"
action="#{backingBean.save()}"/>
Another to add an item to a collection of the entity:
<p:commandButton ajax="true" process="#this" value="#{bundle.add}"
actionListener="#{backingBean.addItem()}" />
This is my BackingBean:
#ViewScoped
#Named("backingBean")
public class BackingBean {
#EJB
MyDAO myDAO;
private Entity entity; //with getters and setters
public void addItem() {
entity.getData().add(new Item()); //another entity object
}
public void save(){
myDAO.save(entity);
}
...
}
Also I have an EntityConverter class that invoques the DAO and load the object:
#Override
public Object getAsObject(FacesContext context, UIComponent component, String value) {
try {
return myDAO.findById(Entity.class, Long.valueOf(value));
} catch (Exception e) {
return null;
}
}
If I try to add more than one items or if I click on save button, the Entity in the BackingBean class is reloaded by calling the getAsObject method of the converter class.
What I'm doing wrong?
Thanks!
For clarity the normal f:param will always behave that way. I use in all my projects OmniFaces ViewParam which fixes these issues.
Stateless mode to avoid unnecessary conversion, validation and model updating on postbacks
The standard UIViewParameter implementation calls the model setter
again after postback. This is not always desired when being bound to a
view scoped bean and can lead to performance problems when combined
with an expensive converter. To solve this, this component by default
stores the submitted value as a component property instead of in the
model (and thus in the view state in case the binding is to a view
scoped bean).
The standard UIViewParameter implementation calls the converter and
validators again on postbacks. This is not always desired when you
have e.g. a required="true", but the parameter is not retained on form
submit. You would need to retain it on every single command
link/button by . To solve this, this component doesn't call
the converter and validators again on postbacks.
I try to add a object to a LinkedList in a #ConversationScoped backing bean.
#Named
#ConversationScoped
public class CategoryController implements Serializable{
...
private List<Category> selectedCategories = new LinkedList<Category>();
...
#PostConstruct
public void initNewMember() {
conversation.begin();
newCategory = new Category();
loadExistingCategories();
}
...
}
I want to send an ajax request with new objects (of type category). They should simply be added to the linked list.
this.selectedCategories.add(newParentCategory);
With the ajax render attribute <f:ajax render="allSelectedCategories"/> I immediately render the output text to render the object list.
<h:outputText id="allSelectedCategories" value="#{categoryController.selectedCategories}" />
And yes, the object I clicked is displayed, but the previously clicked objects are gone.
The values do not serialize/persist in memory during my "conversation". What do I need to do to make that conversion scope temporarily persist the values of the ajax calls?
I really want to get used to CDI and abandon the ManagedBean path for this project (e.g. #ViewScoped), despite the fact that it works like a charm.
Also, I cannot reproduce the following tutorial on CDI Conversation Scope. I simply cannot debug into the initConversation by adding
<f:event listener="#{categoryController.initConversation}"
type="preRenderView"></f:event>
I have read a lot of posts at Stackoverflow but I didn't succeed in implementing the belowmentioned problem from my side.
the problem is: I need to type some text in <p:inputTextarea> and when clicking on button I need to get this value in the bean method.
I.e.:
<p:inputTextarea binding="#{input}"/>
<p:commandButton action="#{pessoaMB.adicionarContato(input.value)}" immediate="true"/>
with the bean method:
public void adicionarContato(String value) {
System.out.println(value);
}
The code I'm using gives me a null value.
I'm using #ViewScoped and cannot change this.
First of all, a side note: it is a bad practice to work with JSF components, you should work with model instead. I.e. don't use binding="#{input}", but stick to value="#{bean.text}".
Second, I doubt that immediate="true" is used appropriately in your setup. When used in a UICommand component like <h:commandButton> it will cause to skip JSF lifecycle for components with immediate="false" (or omitted, as it's the default), thus their value won't be set at all. Still, JSF will still preset submittedValue behind the scenes before the action method is executed.
Also, I strongly recommend to read BalusC's blog post Debug JSF lifecycle, as it is more than enlightening on the topic.
As to the solution, I'd suggest to deal with value binding with the bean, as presented in the first comment. With this approach you won't need action method parameter at all. Moreover, rethink your use of immediate attribute. If you think it's correct then you've got two choices: (1) use immediate="true" on <p:inputTextarea> or (2) switch to action="#{bean.action(input.submittedValue)}".
I would've done this :
<h:form>
<p:inputText value="#{pessoaMB.input}"/>
<p:commandButton value="add" action="#{pessoaMB.adicionarContato}" />
</h:form>
input would be here a pessoaMB property with a getter and setter (an IDE can autogenerate it).
private String input;
public String getInput() {
return input;
}
public void setInput(String input) {
this.input = input;
}
As for the adicionarContato method, it would be like this :
public void adicionarContato() {
System.out.println(input);
}
You should create a new class, i.e:
public class MyFields(){
String input1;
String input2; //and so on...
//getters and setters
}
Then, in pessoaMB create a property:
private MyFields inputFields; //getter and setter
Finally, in your xhtml file:
<h:form>
<p:inputText value="#{pessoaMB.inputFields.input1}"/>
<p:inputText value="#{pessoaMB.inputFields.input2}"/>
<!-- add more inputText components... -->
<p:commandButton value="add" action="#{pessoaMB.adicionarContato}" />
</h:form>
I have a ViewScoped ManagedBean. This bean has a boolean attribute which controls whether a datatable should be displayed. See below:
<p:dataTable value="#{loc.locationRows}" var="obj" ... rendered="#{loc.renderLocationTable}">
<p:column>
...
</p:column>
...
</p:dataTable>
My ManagedBean looks like this:
#ManagedBean(name = "loc")
#ViewScoped
public class LocationController implements Serializable {
private boolean renderLocationTable = false;
// JSF ActionListener.
public void methodA() {
if(someCondition) {
renderLocationTable = true; // this is the only time we should render location table
}
}
}
As soon as methodA() gets called and some condition is met, then the table should be rendered; and this works fine. But, the problem is this, for each and every other JSF ActionListener method which gets called, I have to explicitly set the rendered boolean back to false. See below:
#ManagedBean(name = "loc")
#ViewScoped
public class LocationController implements Serializable {
private boolean renderLocationTable = false;
// JSF ActionListener.
public void methodA() {
if(someCondition) {
renderLocationTable = true; // this is the only time we should render location table
}
}
// JSF ActionListener.
public void methodB() {
renderLocationTable = false;
}
// JSF ActionListener.
public void methodC() {
renderLocationTable = false;
}
}
I've given a very small snippet of the actual ManagedBean and XHTML file. In-reality, these files are huge and lot's of stuff is happening with several other boolean "rendered" flags. It is becoming increasingly difficult to keep these flags accurate. Plus, each ActionListener method now has to know about all boolean flags even if they are not related to the business at-hand.
This is what I'd love to be able to do:
<f:event type="postRenderView" listener="#{loc.resetRenderLocationTable}" />
<p:dataTable value="#{loc.locationRows}" var="obj" ... rendered="#{loc.renderLocationTable}">
<p:column>
...
</p:column>
...
</p:dataTable>
Then, in the ManagedBean have a method:
public void resetRenderLocationTable(ComponentSystemEvent event) {
renderLocationTable = false;
}
Wouldn't this be nice? No more playing games with resetting boolean variables. No more test cases where we need to make sure the table doesn't get displayed when it shouldn't be. The rendered flag can be set to true when the appropriate JSF ActionListener method sets it to true and then the "post-back" call will reset the flag back to false...Perfect. BUT, apparently there's no way of doing this out-of-the-box with JSF.
So, does anyone have a solution to this issue?
Thanks!
By the way, this situation happens probably a lot more than you think. Anytime you have a form with several commandButtons using ActionListeners, then this situation could happen to you. If you've ever had a JSF ManagedBean and you find yourself setting boolean flags to true or false scattered through-out the class, then this situation applies to you.
You didn't added primefaces tag, but according to your code I see that you are using Primefaces. A suppose your methodA() is called from, for example p:commandButton. I suggest first to create primefaces remote command:
<p:remoteCommand name="resetRenderLocationTable">
<f:setPropertyActionListener value="#{false}" target="#{loc.renderLocationTable}"/>
</p:remoteCommand>
this will create JavaScript function named resetRenderLocationTable whose call will generate AJAX request which will set renderLocationTable property to false. Now just add call to that function in oncomplete of you commandButton (or any other AJAX source):
<p:commandButton action="#{loc.methodA()}" update="myDatatable" oncomplete="resetRenderLocationTable()"/>
In next request you don't have to worry about resetting this property, just update your datatable.
I'm looking for some piece of code for setting a property in a JSF managed bean. My first idea was something like that:
<c:set var="#{loginBean.device}" value="mobil"></c:set>
That means I want to set the attribute device to the value "mobil" without a button have to been clicked.
Yes, you can use c:set for this purpose.
<c:set value="mobil" target="#{loginBean}" property="device" />
Doc: http://docs.oracle.com/cd/E17802_01/j2ee/javaee/javaserverfaces/2.0/docs/pdldocs/facelets/c/set.html
However, setting a static value rarely makes sense. You might consider to set a default value directly in your managed bean class. Also in terms of maintainability since you can handle constants better in the Java code than in the view layer.
I think you want the JSF tag child tag setPropertyActionListener. You can set this as a child tag in any ActionComponent.
<h:anyActionComponent id="component1">
<f:setPropertyActionListener target="#{loginBean.device}" value="mobil" />
</h:anyActionComponent>
UPDATE:
I originally misunderstood the users problem. They have a page, and they want a property to be set when the page loads. There is a couple ways to do this, but both are a little different. If you want to set a property to a value after every postback then you can use the #PostConstruct annotation on a ManagedBean method.
#PostConstruct
public void initializeStuff() {
this.device = "mobil";
}
Now if I have a ViewScoped or SessionScope bean that needs to be initialized with a default value just once when the page loads then you can set a phase lifecycle event that will run after every postback and check to see if the page should be initialized or not.
mah.xhmtl:
<f:event listener="#{loginBean.initialize()}" type="preRenderView" />
LoginBean:
public void initialize() {
if (this.device == null)
this.device = "mobil";
}
I am not able to Comment: If you need the value to be ready on page on load, you could use Managed Bean to directly initialize value or use its constructor or #PostConstruct to do the same.
#ManagedBean
#ResquestScoped
public class LoginBean {
private String device = "some value";
//Using Constructor
public LoginBean() {
device = getvalueFromSomewhere();
}
//Using PostConstruct
#PostConstruct
public void init() {
device = getvalueFromSomewhere();
}
}
Instead of setting the value in the xhtml file you can set via another ManagedBean. For instance if you have managedBean1 which manages page1.xhtml and managedBean2 which manages page2.xhtml. If page1.xhtml includes page2.xhtml like:
<ui:include src="page2.xhtml"/>
in managedBean1 you can have at the top
#ManagedProperty(value = "#{managedBean2}")
private ManagedBean2 managedBean2;
and in the PostConstruct
#PostConstruct
public void construct() {
managedBean2.setProperty(...);
}
worked for me anyway...