JSF lazy loading component value - jsf

Consider a simple h:outputText component:
<h:outputText value="#{myBean.myValue}"/>
How can I lazy load that value after the page has been rendered, and display custom 'ajax loading' icon instead of the value while this is being done?
I am using PrimeFaces 3.5 in my project so any PF-specific implementation will be welcome.

A suggest to do this by calling remoteCommand after on page load (it is done by setting autoRun attribute to true) and update your outputText.
private String myValue;
// getter and setter
public void initMyValue() {
// init myValue
}
On page you should have ajaxStatus component for viewing loading image, and your outputText. Also there should be p:remoteCommand component:
<p:ajaxStatus style="width:16px;height:16px;" id="ajaxStatusPanel">
<f:facet name="start">
<h:graphicImage value="ajaxloading.gif" />
</f:facet>
<f:facet name="complete">
<h:outputText value="" />
</f:facet>
</p:ajaxStatus>
<h:outputText id="myText" value="#{myBean.myValue}"/>
<p:remoteCommand autoRun="true" actionListener="#{myBean.initMyValue}" update="myText"/>
EDIT: I supposed that you want to lazy load value of outputText because it contains some long running calculations, but if you want to completely deffer rendering of outputText first add boolean property in your backing bean, and set this property to true at the end of initMyValue method:
private boolean loaded;
// getter and setter
public void initMyValue() {
// init myValue
loaded = true;
}
on the page reorganize it as follows:
<h:panelGroup id="myPanel" layout="block">
<h:graphicImage value="ajaxloading.gif" rendered="#{!myBean.loaded}"/>
<h:outputText value="#{myBean.myValue}" rendered="#{myBean.loaded}"/>
</h:panelGroup>
<p:remoteCommand autoRun="true" actionListener="#{myBean.initMyValue}" update="myPanel"/>

You can use a BlockUI to conditionally block the component while it loads.
Define a preRenderComponent event on the <h:outputText/>
<h:outputText id="myText">
<f:event name="preRenderComponent" id="started"/>
</h:outputText>
Define a <p:blockUI/> with the id of the event as the trigger
<p:blockUI block="myText" trigger="started" />
You can customize the blockui to display an image or whatever.
A word of caution: I presume you require this because you're doing some heavy lifting in the getter of that component. Know that the getter will be called several times in the lifecycle of that page. So hiding the fact that the operation is taking a long time will not change the fact. A better design would be to preload and cache the value for that component in a durable scope, rather than the theatrics of a "loading" throbber.

This is how I ended up implementing it:
<h:panelGroup id="loginLocation">
<p:graphicImage library="assets" name="small-kit-loader.gif" width="16" height="16" rendered="#{empty mybean.lastLoginLocation}"></p:graphicImage>
<h:outputText value="#{myBean.lastLoginLocation}" rendered="#{!empty myBean.lastLoginLocation}"/>
</h:panelGroup>
<p:remoteCommand global="false" actionListener="#{actionBean.getUserLoginLocation(myBean.selectedUser)}" name="refreshLoginLocation" id="rc1" autoRun="true" update="loginLocation" process="#this"></p:remoteCommand>
Personally I am not entirely happy with this implementation:
lazy loading state is stored server-side, not client-side where it should be
I have to implement separate method on my backing bean (getUserLoginLocation) to retrieve the value, and explicitly store it in another property (lastLoginLocation). It would have been much cleaner just to have a single getter that is lazy-called after rendering the page in browser
Not easily reusable - depends on backing bean 'loaded' flag (#{empty myBean.lastLoginLocation} in this case), and requires action listener to actually set the value. Any composite component based on this approach would also depend on specific code in backing bean.
Any recommendations on how to improve this code are welcome! :)

Related

How update attribute of p:remoteCommand works

Primefaces 6.0. I understand that update attribute of p:remoteCommand should be used to specify clientIds of the components that should be updated by AJAX. I am trying to understand how PF works. In combination with DataTable it doesn't seem to work as expected. When I try to directly set update="form:dataTable:2:bColumn", it has no efect. However, doing this (commented out in the below code) RequestContext.getCurrentInstance().update("form:dataTable:2:bColumn"); will force PF to update the specified outputText.
Why is this happening? I will be happy for technical explanation - I am trying to find the answer by debugging PF Java/Javascript sources.
<h:form id="form">
<p:remoteCommand name="remoteCall"
action="#{grid4.onEdit}"
update="form:dataTable:2:bColumn"
/>
<p:dataTable id="dataTable"
var="gridItem"
value="#{grid4.gridItems}"
editable="true" editMode="cell"
>
<p:ajax event="cellEdit"
oncomplete="remoteCall()">
</p:ajax>
<p:column headerText="A">
<p:cellEditor>
<f:facet name="output"><h:outputText value="#{gridItem.a}" /></f:facet>
<f:facet name="input"><p:inputText value="#{gridItem.a}"/></f:facet>
</p:cellEditor>
</p:column>
<p:column headerText="B">
<h:outputText id="bColumn" value="#{gridItem.b}" />
</p:column>
</p:dataTable>
</h:form>
Bean
#ManagedBean
#ViewScoped
public class Grid4 {
private List<GridItem> gridItems = new ArrayList<>();
public Grid4() {
gridItems.add(new GridItem("1", "a","b"));
gridItems.add(new GridItem("2", "a","b"));
gridItems.add(new GridItem("3", "a","b"));
}
public void onEdit() {
System.out.println("onEdit()");
gridItems.get(2).setB("CHANGED VALUE");
// RequestContext.getCurrentInstance().update("form:dataTable:2:bColumn");
}
public List<GridItem> getGridItems() {
return gridItems;
}
public void setGridItems(List<GridItem> gridItems) {
this.gridItems = gridItems;
}
}
basically jsf ids an client side ids are two different things (check this answer and this post for a better understanding).
When you use RequestContext.getCurrentInstance().update("form:dataTable:2:bColumn"); that method use the client id to find the components that have to be updated, but in the case of the update property of p:remoteCommand it is expecting a jsf id, not the generated client id, so that´s why your update doesn't work. However, primefaces support jquery selectors to update components, so you could use a client side id on an update property like this update="#(#yourElementId)"
Let me start by mentioning that this is not specific to the p:remoteCommand. The reason for the behaviour you notice is rather simple although not directly obvious maybe since it is unfortunately not in the PrimeFaces documentation.
The update attribute in:
<p:remoteCommand name="remoteCall"
action="#{grid4.onEdit}"
update="form:dataTable:2:bColumn"
/>
uses a relative path if it does not start with a : and since the p:remoteCommand is already in the naming container with id='form', the form in the update attribute is superfluous and even makes it not work (run your app in dev mode, add a messages tag and see the errors).
So
<p:remoteCommand name="remoteCall"
action="#{grid4.onEdit}"
update="dataTable:2:bColumn"
/>
Should work, as should
<p:remoteCommand name="remoteCall"
action="#{grid4.onEdit}"
update=":form:dataTable:2:bColumn"
/>
The
RequestContext.getCurrentInstance().update("form:dataTable:2:bColumn");
is always absolute, so the colon is not needed here and it will find the element starting from the root (form 'prefix' is needed then)

Auto calculated fields

I've the following form:
<h:form>
<h:inputText size="2" value="#{orderMB.quantity}" />
<h:outputLabel value=" #{orderMB.totalPriceOneItem} €" />
<h:commandButton value="submit" />
</h:form>
And I've the following method in a session scoped managed bean:
public void setQuantity(int quantity) {
this.quantity = quantity;
setTotalPriceOneItem(quantity* item.getItem().getPrice().doubleValue());
}
I would like to auto-update the total price result on every key press of the input field. How can I achieve this without pressing the submit button?
Your code isn't doing that anywhere. It's missing a <f:ajax>.
<h:inputText size="2" value="#{orderMB.quantity}">
<f:ajax event="keyup" render="total" />
</h:inputText>
<h:outputText id="total" value="#{orderMB.totalPriceOneItem} €" />
The event attribute can be set to any HTML DOM event on which JSF must submit the form by ajax, such as click, keyup, blur, focus, etc. The render attribute can be set to any JSF client ID which needs to be updated when the ajax submit finishes. In this case it's referring the ID of the component showing total price.
Note that I replaced the wrong <h:outputLabel> by <h:outputText>. Also noted should be that a setter isn't exactly the right place to perform business logic (a getter also not!). Better revert that setter method to a true setter and add an ajax listener method:
<f:ajax event="keyup" listener="#{orderMB.updateTotalPriceOneItem}" render="total" />
public void updateTotalPriceOneItem() {
totalPriceOneItem = quantity * item.getItem().getPrice().doubleValue();
}
In case when it still doesn't work, then verify if you have a <h:head> in the template instead of a <head>. If still in vain, work through commandButton/commandLink/ajax action/listener method not invoked or input value not updated.
That said, I strongly recommend to take a pause and work through a sane JSF 2.x book. The above is usually already covered in the 1st chapter.

How to send the currently iterated item to h:selectBooleanCheckbox with f:ajax event=change

I've the below form:
<h:form>
<h:dataTable value="#{bean.items}" var="item">
<h:column>
<h:selectBooleanCheckbox value="#{item.enabled}" valueChangeListener="#{bean.onchangeEnabled}">
<f:ajax event="change" />
</h:selectBooleanCheckbox>
</h:column>
<h:column>#{item.name}</h:column>
</h:dataTable>
</h:form>
I would like to get #{item} or at least #{item.name} in the value change listener method:
public void onchangeEnabled(ValueChangeEvent e) {
// I would like to get #{item.name} here too.
}
How can I achieve this?
First of all, the valueChangeListener is the wrong tool for the job. Use <f:ajax listener>. Second, event="change" is the wrong choice in case of checkboxes/radiobuttons because their physical value actually never changes. You should use event="click", but this is the default already, so you can just omit it.
All in all, the proper initial code should look like this:
<h:selectBooleanCheckbox value="#{item.enabled}">
<f:ajax listener="#{bean.onchangeEnabled}" />
</h:selectBooleanCheckbox>
with
public void onchangeEnabled(AjaxBehaviorEvent event) { // Note: event argument is optional.
// ...
}
Once fixed it like that, then you can easily make use of EL 2.2 capability to pass method arguments:
<h:selectBooleanCheckbox value="#{item.enabled}">
<f:ajax listener="#{bean.onchangeEnabled(item)}" />
</h:selectBooleanCheckbox>
with
public void onchangeEnabled(Item item) {
// ...
}
See also:
When to use valueChangeListener or f:ajax listener?
What values can I pass to the event attribute of the f:ajax tag?
How can I pass selected row to commandLink inside dataTable?
Understanding PrimeFaces process/update and JSF f:ajax execute/render attributes
For selectBooleanCheckbox it only reacts on the event click and the form should be posted.
so add this to the checkbox
valueChangeListener="#{mybean.myfunction}" onchange="submit();"
it should get fired !

jsf datatable why commandLink clientid resolved before update model values phase

I have a datatable that iterates over a list, lets call it myList. I populate this myList based on some request parameters. Inside the datatable there are commandLinks. If i put a dummy entry into myList during apply request values phase, i can click on the first commandLink, and it works as it should (it is executed during invoke application phase, and by then the correct entries are in myList). If i dont do it, or i click on the second or later commandLink, nothing happens. So im guessing the clientId of the command button is resolved during apply request phase, even thought it is only used during the invoke application phase, which results in the broken commandLinks.
something like this:
<h:selectManyCheckbox styleClass="hidden"
value="#{cc.attrs.selectionList.selected}"
converter="#{cc.attrs.converter}" >
<f:selectItems value="#{cc.attrs.selectionList.all}"
var="item" itemValue="#{item}" itemLabel="" />
</h:selectManyCheckbox>
<h:dataTable value="#{cc.attrs.selectionList.selectedTest}" var="item">
<h:column>
<h:commandLink value="deselect" action="#{cc.attrs.selectionList.deSelect(item)}">
<f:ajax execute=":#{component.parent.parent.parent.clientId}"
render=":#{component.parent.parent.parent.clientId}" />
</h:commandLink>
</h:column>
</h:dataTable>
and the model:
public List<E> getSelected()
{
return myList;
}
public List<E> getSelectedTest()
{
if(FacesContext.getCurrentInstance().getCurrentPhaseId().equals(PhaseId.RESTORE_VIEW) && getSelectedList().isEmpty())
{
return Collections.singletonList(myList.get(0));
}
else if(FacesContext.getCurrentInstance().getCurrentPhaseId().equals(PhaseId.APPLY_REQUEST_VALUES) && getSelectedList().isEmpty())
{
return Collections.nCopies(2, myList.get(0));
}
else if(FacesContext.getCurrentInstance().getCurrentPhaseId().equals(PhaseId.PROCESS_VALIDATIONS) && getSelectedList().isEmpty())
{
return Collections.nCopies(3, myList.get(0));
}
else if(FacesContext.getCurrentInstance().getCurrentPhaseId().equals(PhaseId.UPDATE_MODEL_VALUES) && getSelectedList().isEmpty())
{
return Collections.nCopies(4, myList.get(0));
}
return myList;
}
public void deSelect(E item)
{
myList.remove(item);
}
with this example, the top two commandLinks of the datatable works.
My question is why is this behaviour, and is there any way around without filling myList with dummy entries? I do not want to use any (viewscoped) backing bean to store the data.
During apply request values phase, JSF needs to iterate over the model in order to find the clicked command link. If the model changes incompatibly during the HTTP request wherein the form submit is processed (the postback) as compared to the initial HTTP request wherein the table with the command links is shown, then JSF may not be able to find the clicked command link and thus never queue the desired action, or the object representing the "current row" is not the same as the enduser intented.
If your bean is request scoped, then it should be written in such way that it initializes selectedTest in the constructor or #PostConstruct method based on some request parameter. At least, you should absolutely not perform business logic in getters.
You can pass the parameters necessary for reconstructing the selectedTest as <f:param> in the command link.
<h:commandLink ...>
<f:param name="some" value="#{bean.some}" />
</h:commandLink>
And prepare the model as follows:
#ManagedProperty
private String some;
#PostConstruct
public void init(){
selectedTest = populateItBasedOn(some);
}
// Don't change standard getters/setters!
See also:
commandButton/commandLink/ajax action/listener method not invoked or input value not updated - point 4
I managed to get my way around by binding the selectManyCheckbox itself to my componentBindings HashMap, and using that for the dataTable (with immediate="true" on the selectManyCheckbox):
<h:selectManyCheckbox immediate="true" styleClass="hidden"
binding="#{componentBindings[cc.attrs.selectionList]}"
value="#{cc.attrs.selectionList.selected}"
converter="#{cc.attrs.converter}" >
<f:selectItems value="#{cc.attrs.selectionList.all}" var="item"
itemValue="#{item}" itemLabel="" />
</h:selectManyCheckbox>
<h:dataTable value="#{componentBindings[cc.attrs.selectionList].value}" var="item">
<h:column>
<h:commandLink value="deselect" action="#{cc.attrs.selectionList.deSelect(item)}">
<f:ajax execute=":#{component.parent.parent.parent.clientId}"
render=":#{component.parent.parent.parent.clientId}" />
</h:commandLink>
</h:column>
</h:dataTable>
in faces-config.xml:
<managed-bean>
<description>Holder of all component bindings.</description>
<managed-bean-name>componentBindings</managed-bean-name>
<managed-bean-class>java.util.HashMap</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
</managed-bean>

JSF dataTable with selectOneListbox

I have a dataTable that lists some objects and I want to set a property for those objects using a selectOneListbox. This is my dataTable
<h:dataTable value="#{someHandler.entities}"
binding="#{someHandler.dataTable}" var="entitiy">
<h:column>
<f:facet name="header">
<t:outputText value="Level" />
</f:facet>
<h:selectOneListbox id="level" value="#{entitiy.level}" size="1"
valueChangeListener="#{someHandler.changeLevel}"
onchange="submit()">
<f:selectItem itemValue="-" itemLabel="-" />
<f:selectItem itemValue="ALL" itemLabel="ALL" />
(and so on)
</h:selectOneListbox>
</h:column>
<h:column>
<f:facet name="header">
<t:outputText value="Name" />
</f:facet>
<h:outputText value="#{entitiy.name}" />
</h:column>
</h:dataTable>
The valueChangeListener looks like this:
public void changeLevel(ValueChangeEvent event) {
String newLevel = (String) event.getNewValue();
Logger logger = (Logger) dataTable.getRowData();
logger.setLevel(Level.toLevel(newLevel));
}
(dataTable is an HtmlDataTable object.)
However, the event object is always the same - no matter which row the selectOneListbox was in. (It seems always the logger in the first row). The Logger object I get is also not the one I want.
Any ideas? Thank you!
And anothers questions? Is the entitiy.setLevel() method called even though I have a valueChangeListener? I use entitiy.level because I want to show the chosen level as a default for those entity.
Thank you!
There are two potential problems here:
First, the onchange="submit()" submits the entire form. The valueChangeListener will be invoked on all input elements of which the submitted value differs from the initial value.
You need to preset the value behind #{entitiy.level} with the same value as the default menu option. E.g. in the constructor.
public Entity() {
level = "-";
}
Or, better, make the default value null.
<f:selectItem itemValue="#{null}" itemLabel="-" />
so that the valueChangeListener won't be invoked when the particular menu is not changed.
Or, when you are already on JSF 2.x (please always mention exact JSF impl/version in your JSF questions), you can use <f:ajax> tag for this without the need for a valueChangeListener with a hacky onchange="submit()".
Second, you need to ensure that the datatable value #{someHandler.entities} returns exactly the same list during the submit as it was during the initial request. So, do the data loading in the bean (post)constructor. In JSF 2.x you'd like to put the bean in the view scope as well.
Unrelated to the concrete problem, you can also just use <h:selectOneMenu> instead of a <h:selectOneListbox size="1">.

Resources