I want to create JSF page with tabs. Something like this. But I wonder if I choose to do this with Jquery can I implement lazy loading - when I click a tab on the JSF page the content is generated when the tab is opened. Is it possible to implement lazy loading of tabs in pure JSF? And I suppose that I can easily implement AJAX in both cases.
Best wishes
The Primefaces Tabview component supports lazy loading.
Quote from the showcase:
Tab contents can be lazy loaded with ajax as well, when dynamic
attribute is set to "true" only the active tab's content will be
rendered and clicking on a lazy tab will fetch the tab contents with
ajax. This behavior is handy to save bandwith and reduce page size
when dealing with tabs having a lot of content.
Quick example from the showcase:
<h:form id="form">
<p:tabView id="tabView" dynamic="true" cache="true">
// tabs
</p:tabView>
</h:form>
The cache attribute is used to prevent ajax reloading of tab content if you toggle between tabs.
Note: If you want your tabs beans be Session Scope then read instructions in the buttom of the Answer...
Since you are don't want to use any third party Libarary here is a PureJSF + jQuery example
JSF + Jquery + Ajax Lazy Loading + View Scope Beans Example...
B.T.W here is how it looks like eventually :
You can look at the web server console for the print outs of #PostConstruct and the #PreDestroy when you click on each tab...
The content of the tab - the xhtml page and its bean will be loaded upon tab click (Lazy Loading) and will be destroyed upon click on other tab,
I suggest you to create a new project and slowly place all the files inside it and start playing and looking into it... its 100% working , but I placed some print outs just to see that it is really working...
The Example is very simple and straight forward....
First Of all go to jQueryUI and download it(1.8.18)
and place jquery-1.7.1_.min.js and jquery-ui-1.8.18.custom.min.js in WebContent\resources\js and jquery-ui-1.8.18.custom.css in WebContent\resources\css
Now to the other files...
myTabs.xhtml
<!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:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:c="http://java.sun.com/jsp/jstl/core">
<h:head>
<h:outputScript library="js" name="jquery-1.7.1_.min.js" target="head" />
<h:outputScript library="js" name="jquery-ui-1.8.18.custom.min.js" target="head" />
<h:outputStylesheet library="css" name="jquery-ui-1.8.18.custom.css" target="head" />
<h:outputScript library="js" name="mytabs.js" target="head" />
</h:head>
<h:body>
<f:view>
<h:form prependId="false">
<h:panelGroup id="tabs" layout="block">
<ul>
<c:forEach items="#{myTabs.tabs}" var="tab">
<li>#{tab.tabid}</li>
<h:commandButton id="button_#{tab.tabid}" value="TabClick" action="#{myTabs.switchPages(tab.tabid)}" style="display:none">
<f:ajax render="tabs"></f:ajax>
</h:commandButton>
</c:forEach>
</ul>
<c:forEach items="#{myTabs.tabs}" var="tab">
<h:panelGroup id="#{tab.tabid}" layout="block" rendered="#{tab.tabid eq myTabs.selectedTab}">
<ui:include src="#{tab.tabfilename}"></ui:include>
</h:panelGroup>
</c:forEach>
</h:panelGroup>
</h:form>
</f:view>
</h:body>
</html>
MyTabs.java
package pack;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.PostConstruct;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;
#ManagedBean
#SessionScoped
public class MyTabs{
#PostConstruct
public void init(){
tabs = new ArrayList<MyTabObject>();
tabs.add(new MyTabObject("tab1.xhtml", "tab1"));
tabs.add(new MyTabObject("tab2.xhtml", "tab2"));
tabs.add(new MyTabObject("tab3.xhtml", "tab3"));
}
String selectedTab="tab1";
public String getSelectedTab() {
return selectedTab;
}
public void setSelectedTab(String selectedTab) {
this.selectedTab = selectedTab;
}
public String switchPages(String selTab) {
selectedTab = selTab;
return "myTabs.xhtml";
}
List<MyTabObject> tabs;
public List<MyTabObject> getTabs() {
return tabs;
}
public void setTabs(List<MyTabObject> tabs) {
this.tabs = tabs;
}
}
MyTabObject
package pack;
public class MyTabObject{
String tabfilename;
String tabid;
public String getTabfilename() {
return tabfilename;
}
public void setTabfilename(String tabfilename) {
this.tabfilename = tabfilename;
}
public String getTabid() {
return tabid;
}
public void setTabid(String tabid) {
this.tabid = tabid;
}
public MyTabObject(String tabfilename, String tabid) {
super();
this.tabfilename = tabfilename;
this.tabid = tabid;
}
}
Tab1Page , (Tab2Page and Tab3Page are exactly the same , just change the number in all places)
package pack;
import java.io.Serializable;
import java.text.Format;
import java.text.SimpleDateFormat;
import java.util.Date;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;
#ManagedBean
#ViewScoped
public class Tab1Page implements Serializable{
/**
*
*/
private static final long serialVersionUID = 254415216070877770L;
// Constants
public final static String hashKey = "tab1PageTab";
public String actionString = "";
#PostConstruct
public void post(){
Format formatter;
Date date = new Date();
// Time formate 01:12:53 AM
formatter = new SimpleDateFormat("hh:mm:ss a");
tabName = formatter.format(date);
System.out.println("Tab1Page\t"+tabName+"\t#PostConstruct...");
}
#PreDestroy
public void destroy(){
Format formatter;
Date date = new Date();
// Time formate 01:12:53 AM
formatter = new SimpleDateFormat("hh:mm:ss a");
tabName = formatter.format(date);
System.out.println("Tab1Page\t"+tabName+"\t#PreDestroy...");
}
String tabName;
public String getTabName() {
return this.getClass().getName().substring(this.getClass().getName().lastIndexOf("."))+"\t"+tabName;
}
public void setTabName(String tabName) {
this.tabName = tabName;
}
public String getActionString() {
return actionString;
}
public void setActionString(String actionString) {
this.actionString = actionString;
}
}
tab1.xhtml (tab2.xhtml and tab3.xhtml are exactly the same - just replace the numbers)
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:c="http://java.sun.com/jsp/jstl/core">
<h:panelGroup>
<h:form>
<h:outputText value="#{tab1Page.tabName}" />
</h:form>
</h:panelGroup>
</ui:composition>
and to the last file
mytabs.js (place it in WebContent\resources\js)
$(document).ready(function () {
$("#tabs").tabs();
});
$(window).load(function() {
jsf.ajax.addOnEvent(function (data) {
if (data.status === "success") {
$("#tabs").tabs();
}
});
});
In order to use Session Scope Beans:
The method switchPages in MyTabs.java should be void and not to return anything, like this
public void switchPages(String selTab) {
selectedTab = selTab;
}
It's not a problem at all implement an ajax tab with jQuery UI.
See the documentation of jQuery Tabs with ajax here and click on "view source" to find the code you need.
You can look at the book Core Java Server Faces third edition page 339 to see how you can implement simple tabs with h:panelGrid.
The output is something like this:
This is the code example from the book:
...
<h:form>
<h:panelGrid styleClass="tabbedPane" columnClasses="displayPanel">
<!-- Tabs -->
<f:facet name="header">
<h:panelGrid columns="4" styleClass="tabbedPaneHeader">
<h:commandLink tabindex="1"
title="#{msgs.jeffersonTooltip}"
styleClass="#{tp.jeffersonStyle}"
actionListener="#{tp.jeffersonAction}">
#{msgs.jeffersonTab}
</h:commandLink>
...
</h:panelGrid>
</f:facet>
<!-- Tabbed pane content -->
<ui:include src="washington.xhtml" />
<ui:include src="roosevelt.xhtml" />
<ui:include src="lincoln.xhtml" />
<ui:include src="jefferson.xhtml" />
</h:panelGrid>
</h:form>
...
This is the description:
The tabbed pane is implemented with h:panelGrid. Because we do not specify
the columns attribute, the panel has one column. The panel’s header—defined
with an f:facet tag—contains the tabs, which are implemented with another
h:panelGrid that contains h:commandLink tags for each tab. The only row in the panel
contains the content associated with the selected tab.
When a user selects a tab, the associated action listener for the command link is
invoked and modifies the data stored in the backing bean. Because we use a
different CSS style for the selected tab, the styleClass attribute of each h:commandLink
tag is pulled from the backing bean with a value reference expression.
As you can see from the top picture in Figure 8–11, we have used the title
attribute to associate a tooltip with each tab. Another accessibility feature is the
ability to move from one tab to another with the keyboard instead of the
mouse. We implemented that feature by specifying the tabindex attribute for
each h:commandLink.
The content associated with each tab is statically included with the JSP include
directive. For our application, that content is a picture and some text, but
you could modify the included JSF pages to contain any set of appropriate
components. Notice that even though all the JSF pages representing content are
included, only the content associated with the current tab is rendered. That is
achieved with the rendered attribute—for example, jefferson.xhtml looks like this:
Putting It All Together
<h:panelGrid columns="2" columnClasses="presidentDiscussionColumn"
rendered="#{tp.jeffersonCurrent}">
<h:graphicImage value="/images/jefferson.jpg"/>
<span class="tabbedPaneContent">"#{msgs.jeffersonDiscussion}"</span>
</h:panelGrid>
Figure 8–12 shows the directory structure for the tabbed pane application and
Listings 8–14 through 8–17 show the most important files.
Unfortunately I don't know how to add lazy loading and AJAX support to this code.
Related
I have a this simplified form to show the challenge:
It is a form with multiple tabs (2 in this MVCE).
My goal is to highlight both fields in case of validation failure (but only for tab it is failing for).
TabView (backing bean)
package betlista.so.pf.findComponent;
import com.sun.faces.component.visit.FullVisitContext;
import org.primefaces.PrimeFaces;
import org.springframework.stereotype.Component;
import javax.faces.application.FacesMessage;
import javax.faces.component.UIComponent;
import javax.faces.component.UIInput;
import javax.faces.component.UIViewRoot;
import javax.faces.component.visit.VisitCallback;
import javax.faces.component.visit.VisitContext;
import javax.faces.component.visit.VisitResult;
import javax.faces.context.FacesContext;
import java.util.LinkedList;
import java.util.List;
#Component
public class TabView {
List<TabData> tabData = new LinkedList<>();
{
tabData.add(new TabData("name 1", "val1-a", null));
tabData.add(new TabData("name b", "val1-b", "val2-b"));
}
public List<TabData> getTabsData() {
return tabData;
}
public void save() {
boolean isValid = isValid();
if (isValid) {
// continue ...
}
}
private boolean isValid() {
boolean isOk = isOk();
if (isOk) {
return true;
}
FacesMessage message = new FacesMessage("Not saved!");
message.setSeverity(FacesMessage.SEVERITY_ERROR);
FacesContext context = FacesContext.getCurrentInstance();
context.addMessage(null, message);
final UIViewRoot viewRoot = context.getViewRoot();
List<UIInput> componentList = new LinkedList<>();
viewRoot.visitTree(new FullVisitContext(context), new VisitCallback() {
#Override
public VisitResult visit(VisitContext context, UIComponent target) {
if (target != null) {
final String id = target.getId();
if ("val1".equals(id) || "val2".equals(id)) {
if (target instanceof UIInput) {
componentList.add((UIInput) target);
}
}
}
return VisitResult.ACCEPT;
}
});
for (UIInput uiInput: componentList) {
uiInput.setValid(false);
}
context.validationFailed();
PrimeFaces.current().ajax().update("form");
final UIComponent val1 = context.getViewRoot().findComponent("val1");
return false;
}
private boolean isOk() {
return false;
}
}
in this simplified version isOk() returns false.
In a validation I know whether data for 1st or second tab is not ok and I'm trying to find a way how to highlight those two fields in tab.
I tried initially context.getViewRoot().findComponent(...), but I'm not able to "find it" (returns null). So to have access to the components I used this:
List<UIInput> componentList = new LinkedList<>();
viewRoot.visitTree(new FullVisitContext(context), new VisitCallback() {
#Override
public VisitResult visit(VisitContext context, UIComponent target) {
if (target != null) {
final String id = target.getId();
if ("val1".equals(id) || "val2".equals(id)) {
if (target instanceof UIInput) {
componentList.add((UIInput) target);
}
}
}
return VisitResult.ACCEPT;
}
});
so I have (in this case) all 4 components (but let say only those for first tab are invalid).
I'm trying to find a way how to identified which component belong to which tab (not sure whether to rely on a an order in list).
I was trying dynamic ID's, but it's not working, e.g. I added tab name (tabName)as attribute and used
<p:inputText id="#{cc.attr.tabName}-val2" ... />
I found no way how to add some custom flag/attribute whatever to be able to link component to a tab.
I was reading that ID can be dynamic once I'd use EL custom function (I mean I can use concatenation), but I was not able to find a resource describing it (I have no web.xml).
Code is available in GitHub.
myTab.xhtml (custom component)
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:f="http://java.sun.com/jsf/core" xmlns:p="http://primefaces.org/ui"
xmlns:composite="http://java.sun.com/jsf/composite">
<composite:interface>
<composite:attribute name="data" required="true" type="betlista.so.pf.findComponent.TabData" />
</composite:interface>
<composite:implementation>
<div class="ui-g">
<div class="ui-g-12">
<div class="ui-g-6">
<p:outputLabel value="Val 1:"/>
</div>
<div class="ui-g-6">
<p:inputText id="val1" value="#{cc.attrs.data.val1}" widgetVar="#{cc.attrs.data.tabName}-val1"/>
</div>
</div>
<div class="ui-g-12">
<div class="ui-g-6">
<p:outputLabel value="Val 2:"/>
</div>
<div class="ui-g-6">
<p:inputText id="val2" readonly="true" value="#{cc.attrs.data.val2}" widgetVar="#{cc.attrs.data.tabName}-val2"/>
</div>
</div>
</div>
</composite:implementation>
</html>
page.xhtml
<?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:p="http://primefaces.org/ui"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:comp="http://xmlns.jcp.org/jsf/composite/comp">
<h:head>
<h:outputScript library="webjars" name="font-awesome/5.5.0/js/all.js"/>
</h:head>
<h:body styleClass="mainBody">
<h:form id="form">
<p:tabView id="tabView" value="#{tabView.tabsData}" var="tabVar">
<p:tab id="tab" title="#{tabVar.tabName}">
<comp:myTab data="#{tabVar}" />
</p:tab>
</p:tabView>
<p:commandButton value="Save" action="#{tabView.save()}" process="#form" update="#form" />
<p:growl id="growl" life="3000"/>
</h:form>
</h:body>
</html>
I got very confused by debugger...
The simplest solution is to add label like this:
<p:inputText id="val1" value="#{cc.attrs.data.val1}" label="someLabel1" />
...what I missed earlier is, that label is not a field therefor it was difficult to find it, it is available under stateHelper:
Probably better solution (despite label I cannot see in generated HTML) is to use custom attribute like this (I'd bet I tried that as well, apparently not):
<p:inputText id="val2" readonly="true" value="#{cc.attrs.data.val2}">
<f:attribute name="someAttribute" value="value2" />
</p:inputText>
and it's easily accessible:
I have created a managed bean that searches a gene database currently it only returns the result to the glass fish console. I am wondering if anyone can offer any suggestions on how to get my search results to display into my gene/protein search tab.
My current set of code:
MainPage.xhtml
<?xml version="1.0" encoding="UTF-8"?>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<!-- this allows the page to see the welcomepage style sheet-->
<link rel="stylesheet" type="text/css" href="Group1/welcomepage.css" media="screen"/>
<title>Main Page</title>
<!-- allows the page to see the jquery function see "http://jquery.com/" for description -->
<script src="jquery-1.11.0.min.js" type="text/javascript"></script>
<!-- allows the page to see the js script tabs see file for detailed comments-->
<script type="text/javascript" src = "tabs.js"></script>
</head>
<body>
<!-- the wrapper places everyting in one frame so the objects do not move when browser size is altered-->
<div id ="wrapper">
<div id = "main">
<div id ="header">
<!-- again the logo this one will appear in the header in the top left and when clicked returns you to the home page -->
<a href="index.xhtml">
<img id ="logo1" alt="Group1 logo"/>
</a>
<!-- this is the single search bar and button the "this.select()" function selects all the content in the search box in one click-->
<form name="Search" onsubmit="#{SearchUniprot.query}">
<input type="text" id ="GeneralSearch" name="Search" value="#{SearchUniprot.userSearch}" onclick="this.select();"/>
<input type="submit" id ="subSearch" value="Search" name="GenSearchButton"/>
</form>
</div>
<!-- tab container this created the tabs according the to style set out in the CSS -->
<div id="tab-container">
<ul class="tab-menu">
<li id="Start" class="active">Start</li>
<li id="Genes">Genes/Proteins</li>
<li id="Litrature">Litrature</li>
<li id="Analysis">Analysis</li>
</ul>
<div class="clear"></div>
<!-- each div section below determines what is written in each tab -->
<div class="tab-top-border"></div>
<div id="Start-tab" class="tab-content active">
<h1>Recent Activiy</h1>
<p>Recent files will be shown here</p>
</div>
<div id="Genes-tab" class="tab-content">
<h1>Genes and Proteins</h1>
<p>Results for genes and proteins based on search </p>
</div>
<div id="Litrature-tab" class="tab-content">
<h1>Litrature</h1>
<p>Results of Litrature search will be shown here</p>
</div>
<div id="Analysis-tab" class="tab-content">
<h1>Analysis</h1>
<p>A type of analysis will be shown here</p>
</div>
</div>
</div>
</div>
</body>
</html>
SearchUniProt.java
package TestGeneSearch;
import javax.ejb.EJB;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ManagedProperty;
import javax.faces.bean.RequestScoped;
#ManagedBean (name ="SearchUniprot" )
#RequestScoped
public class SearchUniprot {
String userSearch;
public String getUserSearch() {
return userSearch;
}
public void setUserSearch(String userSearch) {
this.userSearch = userSearch;
}
public SearchUni getQuery() {
// make a new object of the SearchUniProt class and set the search term - obviously we need to read this in from the user in a demo
SearchUni query = new SearchUni("tuna");
//run the RunQuery method in SearchUniprot
query.RunQuery();
return query;
}
public SearchUniprot() {
}
}
SearchUni.java
package TestGeneSearch;
import javax.ejb.Stateful;
import uk.ac.ebi.webservices.axis1.stubs.ebeye.EBISearchService_PortType;
import uk.ac.ebi.webservices.axis1.stubs.ebeye.EBISearchService_Service;
import uk.ac.ebi.webservices.axis1.stubs.ebeye.EBISearchService_ServiceLocator;
#Stateful
public class SearchUni {
// Add business logic below. (Right-click in editor and choose
// "Insert Code > Add Business Method")
//searchterm = what you want to search for
private String searchterm;
public SearchUni() {
}
//constrcutor
public SearchUni(String s) {
searchterm = s;
}
public String[] RunQuery() {
try {
//set up to connect to the searchservice
EBISearchService_Service service = new EBISearchService_ServiceLocator();
EBISearchService_PortType srvProxy = service.getEBISearchServiceHttpPort();
// Get the number of results for the query - we don;t necessarily need this but it may be useful
int result = srvProxy.getNumberOfResults("uniprot", searchterm);
System.out.println(result);
//get all results IDs - can easily limit it to however many we want
String[] ids = srvProxy.getAllResultsIds("uniprot", searchterm);
for (int i = 0; i + 1 < ids.length; i++) {
System.out.println(ids[i]);
}
//get more fields - the fields we can get depend on the database to be searched.
//a note about protein names in Uniprot - Uniprot contains two sections, reviewed and unreviewd
//the reviewed entries will have a Reccomended Name (descRecName), the unreviewed entries will have
//a Submitted name (descSubName) - so each of our results will have either a descRecName or a descSubName
//but not both.
//gene name (gene_primary_name) may be null
//accession number (acc) is a stable identifier - the id field printed out above is not the same as an
//accession number and shouldn't be assumed to be stable
String fields[] = {"acc", "descRecName", "descSubName", "gene_primary_name", "organism_scientific_name"};
String[][] results = srvProxy.getResults("uniprot", searchterm, fields, 1, 100);
for (int i = 0; i < result; i++) {
for (int j = 0; j < fields.length; j++) {
System.out.println(results[i][j]);
}
}
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
}
You're only seeing the results in the console because you're just printing the results in this line
System.out.println(ids[i]);
What you need to do is to store this results somewhere, make them available to your managed bean (for example, storing in a managed bean attribute) and make this information available in your xhtml via this attribute getters and setters.
One thing that is not clear to me yet is why do you need a stateful EJB for this task. If the only thing you need is to query the database and show the results, you may prefer to use a stateless EJB instead, which is simpler and probably will perform better too.
I also suggest you to use some JSF frontend library such as primefaces, so you'll have a whole set of rich GUI elements ready to use. In your specific case, since you're dealing with genomic data, some results (I guess) can be quite big and you may want to use some lazy loading features that are trivial to do with primefaces (and not so trivial to do without it)
The logic is like this
xhtml
<?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:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:p="http://primefaces.org/ui">
<h:head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</h:head>
<h:body>
<h:form>
<p:inputText id="criteria" value="#{myMB.criteria}"/>
<p:inputText id="result" value="#{myMB.result}"/>
<p:commandButton value="query" action="#{myMB.query}" update="result"/>
</h:form>
</h:body>
</html>
when you hit the query button, myMB.query() method will be called, with the myMB.criteria populated in your managed bean, that may look like this
package mypackage;
import java.io.Serializable;
import javax.ejb.EJB;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;
#ManagedBean
#ViewScoped
public class MyMB implements Serializable{
private static final long serialVersionUID = 1L;
private String criteria;
private String result;
#EJB
private MyEJB myEJB;
public void query(){
this.result = myEJB.query(this.criteria);
}
public String getCriteria() {
return criteria;
}
public void setCriteria(String criteria) {
this.criteria = criteria;
}
public String getResult() {
return result;
}
public void setResult(String result) {
this.result = result;
}
}
notice that I use here a stateless EJB like this
package mypackage;
import javax.ejb.Stateless;
#Stateless
public class MyEJB {
public String query(String criteria) {
//do your query here
return "xyz";
}
}
after the query is done, the result will go to myMB.result, but it will only be shown after the xhtml process the
update="result"
notice that "result" in the line above is not myMB.result, but the xhtml tag with id="result"
I hope it's clearer now.
I have commandButton component on my JSF page:
<p:commandButton id="period"
value="#{myBean.isMonthly == false ? 'Yearly' : 'Monthly'}"
action="#{myBean.doSomeOtherStuff()}"
update="period myDataTable">
</p:commandButton>
I'm trying to update a dataTable upon a click on the button above.
When I click it, the dataTable is updated as desired whereas the commandButton behaves weirdly, resulting in a display like:
Can someone help me understand the causes of such a weird situation and also tell a solution if possible?
NOTES:
JSF implementation and version: Mojarra (javax.faces-2.1.11.jar)
View technology: Facelets (XHTML)
Copy'n'paste'n'runnable example! (SSCCE) listed below:
FilterBean.java:
package com.turkcell.mdealer.bean.impl;
import org.springframework.stereotype.Component;
#Component
public class FilterBean {
private boolean monthly = true;
public String applyPeriod(String caller) {
monthly = !monthly;
return caller;
}
public boolean isMonthly() {
return monthly;
}
public void setMonthly(boolean monthly) {
this.monthly = monthly;
}
}
sample.xhtml:
<f:view xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:c="http://java.sun.com/jsp/jstl/core"
xmlns:turkcell="http://turkcell.com/jsf/facelets"
xmlns:p="http://primefaces.org/ui" xmlns:o="http://omnifaces.org/ui"
xmlns:of="http://omnifaces.org/functions"
xmlns:pm="http://primefaces.org/mobile" contentType="text/html"
renderKitId="PRIMEFACES_MOBILE">
<pm:page title="Bayi Raporlari">
<pm:view id="FaturasizAktivasyon" swatch="c">
<p:outputPanel id="FaturasizPanel">
<h:form id="FaturasizForm">
<pm:content>
<p:commandButton id="period"
value="#{filterBean.monthly == false ? 'Yearly' : 'Monthly'}"
action="#{filterBean.applyPeriod('sample')}" update="period">
</p:commandButton>
</pm:content>
</h:form>
</p:outputPanel>
</pm:view>
</pm:page>
</f:view>
General view of libraries:
I don't know the reason of the weird behaviour
However, you can use two conditionally rendered button to achieve the same effect:
<p:panelGroup>
<p:commandButton value="Monthly"
action="#{myBean.doSomeOtherStuff()}"
update="myDataTable #parent" rendered="#{myBean.isMonthly}" />
<p:commandButton value="Yearly"
action="#{myBean.doSomeOtherStuff()}"
update="myDataTable #parent" rendered="#{not myBean.isMonthly}" />
</p:panelGroup>
I'm using the component rich:fileUpload for uploading files to my server the problem is that those files go along with a form that the user fills, so I wanna use one external button for doing this.
The user select the files to upload, fill the form and then click a "Submit" button at the bottom of the page. This upload the file with the form. I've tried it like this:
I'm able to hide the button inside the panel of fileUpload so the user don't click on it.
<rich:fileUpload id="fileUploadId"
style="width: 100%; height: 130px;"
fileUploadListener="#{documentsBean.listener}"
maxFilesQuantity="1"
uploadButtonClass="display-none"
uploadButtonClassDisabled="display-none">
</rich:fileUpload>
And what I've tried with the button is
<a4j: commandButton id="uploadFormButton"
value="Attach"
onclick="#{rich:component('fileUploadId')}.submitForm();"
oncomplete="#{rich:component('fileUploadId')}.clear(); return false;"/>
But it doesn't work.
I don't know if there is a way to do exactly what you want, but here is another solution you can use :
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:rich="http://richfaces.org/rich"
xmlns:a4j="http://richfaces.org/a4j"
xmlns:st="http://spectotechnologies.com/jsf"
xmlns:t="http://myfaces.apache.org/tomahawk">
...
<h:form enctype="multipart/form-data">
... your fields ...
<t:inputFileUpload value="#{bean.document}" />
<h:commandButton value="Submit" actionListener="#{bean.onButtonSubmitClick}" />
</h:form>
</html>
and the bean :
#ManagedBean
#RequestScoped
public class Bean
{
private UploadedFile m_oDocument;
public void setDocument(UploadedFile p_oDocument)
{
m_oDocument = p_oDocument;
}
#UploadedFileNotEmpty
#UploadedFileSize(max="10000000")
#UploadedFileExtension(accept="doc,docx,pdf,txt,rtf,xls,xlsx,zip,rar,jpg,jpeg,jpe,bmp,gif,png,csv,ppt,pptx,odp,pic,odt,ods")
public UploadedFile getDocument()
{
return m_oDocument;
}
public void onButtonSubmitClick(ActionEvent p_oEvent)
{
...
}
}
Hope this helps!
I am getting the following problem:
After my view is restored, validation of field causes JSF to skip to Render response phase (because required field is empty). But even though the current value (empty string) is rendered to show to user that he/she did not fill anything, the following statement is not executed:
<c:if test="#{empty cc.attrs.fieldValue}">
<f:attribute name="style" value="background-color: yellow;"/>
</c:if>
Is it a bug or a feature? Please help.
Complete test example (Netbeans 6.8 project) is here: http://www.221b.cz/so/JSFTester.zip
From the tutorial: "If the request is a postback and errors were encountered during the apply request values phase, process validations phase, or update model values phase, the original page is rendered during Render response phase" (http://java.sun.com/javaee/5/docs/tutorial/doc/bnaqq.html)
Does it mean that if view is restored in "Restore View" phase and then any apply request/validation/update model phase fails and skips to "Render response" that Render response only passes restored view without any changes to client?
Managed Bean (TesterBean.java):
package cz.test;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;
#ManagedBean
#RequestScoped
public class TesterBean {
// Simple DataStore (in real world EJB)
private static String storedSomeValue = null;
private String someValue;
public TesterBean() {
}
public String storeValue() {
storedSomeValue = someValue;
return "index";
}
public String eraseValue() {
storedSomeValue = null;
return "index";
}
public String getSomeValue() {
someValue = storedSomeValue;
return someValue;
}
public void setSomeValue(String someValue) {
this.someValue = someValue;
}
}
Composite component (field-component.xhtml):
<?xml version='1.0' encoding='ISO-8859-1' ?>
<!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://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:composite="http://java.sun.com/jsf/composite"
xmlns:c="http://java.sun.com/jsp/jstl/core">
<!-- INTERFACE -->
<composite:interface>
<composite:attribute name="currentBehaviour" type="java.lang.String" required="true"/>
<composite:attribute name="fieldValue" required="true"/>
</composite:interface>
<!-- IMPLEMENTATION -->
<composite:implementation>
<h:panelGrid columns="3">
<c:choose>
<c:when test="#{cc.attrs.currentBehaviour == 'READONLY'}" >
<h:outputText id="fieldValue" value="#{cc.attrs.fieldValue}">
</h:outputText>
</c:when>
<c:when test="#{cc.attrs.currentBehaviour == 'MANDATORY'}" >
<h:inputText id="fieldValue" value="#{cc.attrs.fieldValue}" required="true">
<f:attribute name="requiredMessage" value="Field is mandatory"/>
<c:if test="#{empty cc.attrs.fieldValue}">
<f:attribute name="style" value="background-color: yellow;"/>
</c:if>
</h:inputText> *
</c:when>
<c:when test="#{cc.attrs.currentBehaviour == 'OPTIONAL'}" >
<h:inputText id="fieldValue" value="#{cc.attrs.fieldValue}">
</h:inputText>
</c:when>
</c:choose>
<h:message for="fieldValue" style="color:red;" />
</h:panelGrid>
</composite:implementation>
Page (index.xhtml):
<?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://java.sun.com/jsf/html"
xmlns:ez="http://java.sun.com/jsf/composite/components">
<h:head>
<title>Testing page</title>
</h:head>
<h:body>
<h:form>
<h:outputText value="Some value:"/>
<ez:field-component currentBehaviour="MANDATORY" fieldValue="#{testerBean.someValue}"/>
<h:commandButton value="Store" action="#{testerBean.storeValue}"/>
<h:commandButton value="Erase" action="#{testerBean.eraseValue}" immediate="true"/>
</h:form>
<br/><br/>
<b>Why is field's background color not set to yellow?</b>
<ol>
<li>NOTICE: Field has yellow background color (mandatory field with no value)</li>
<li>Fill in any value (eg. "Hello") and press Store</li>
<li>NOTICE: Yellow background disappeared (as mandatory field has value)</li>
<li>Clear text in the field and press Store</li>
<li><b>QUESTION: Why is field's background color not set to yellow?</b></li>
<li>Press Erase</li>
<li>NOTICE: Field has yellow background color (mandatory field with no value)</li>
</ol>
</h:body>
EDIT, following Brian's suggestion (field-component.xhtml)
<?xml version='1.0' encoding='ISO-8859-1' ?>
<!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://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:composite="http://java.sun.com/jsf/composite">
<!-- INTERFACE -->
<composite:interface>
<composite:attribute name="currentBehaviour" type="java.lang.String" required="true"/>
<composite:attribute name="fieldValue" required="true"/>
</composite:interface>
<!-- IMPLEMENTATION -->
<composite:implementation>
<h:panelGrid columns="3">
<h:outputText rendered="#{cc.attrs.currentBehaviour == 'READONLY'}" id="fieldValue1" value="#{cc.attrs.fieldValue}" />
<h:inputText rendered="#{cc.attrs.currentBehaviour == 'MANDATORY'}" id="fieldValue2" title="#{cc.attrs.fieldValue}" value="#{cc.attrs.fieldValue}" required="true" style="#{empty cc.attrs.fieldValue ? 'background-color: yellow;' : ''}">
<f:attribute name="requiredMessage" value="Field is mandatory"/>
</h:inputText> *
<h:inputText rendered="#{cc.attrs.currentBehaviour == 'OPTIONAL'}" id="fieldValue3" value="#{cc.attrs.fieldValue}"/>
<h:message for="fieldValue" style="color:red;" />
</h:panelGrid>
</composite:implementation>
But still not working even if I got rid of JSTL:-( Seems that only value attribute is updated with new value from http request in h:inputText but the rest of attributes are not reevaluated in phase Render Response.
The <c:choose> and <c:when> tags you are using are JSTL tags, not JSF tags. This means they are evaluated at build time, rather than at render time. When you post back, the component tree does not get rebuilt, rather it get's re-rendered, and the <c: tags don't get re-evaluated.
Try your example again using <h:panelGroup rendered="#{}"> tags instead of the <c: tags.
See this write-up for more details:
http://drewdev.blogspot.com/2008/03/build-time-vs-render-time.html
It is very important to remember that you cannot have components "re-appear" on post back of a JSF form. This is because a JSF component tree should never be altered between having its state saved and when its state is restored. This is very important, so let me say again, a JSF component tree should never be altered between having its state saved and when its state is restored.
I did some investigation and debugging in the last two days and this is my result.
First of all, I simplified example to omit composite component and to have it as simple as possible.
Managed bean (TesterBean2.java)
package cz.test;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;
#ManagedBean
#RequestScoped
public class TesterBean2 {
// Simple DataStore (in real world EJB)
private static String storedSomeValue = null;
private String someValue;
public TesterBean2() {
}
public String storeValue() {
storedSomeValue = someValue;
return "index";
}
public String eraseValue() {
storedSomeValue = null;
return "index";
}
public String getSomeValue() {
someValue = storedSomeValue;
return someValue;
}
public void setSomeValue(String someValue) {
this.someValue = someValue;
}
}
Testing page (index.xhtml)
<?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://java.sun.com/jsf/html">
<h:head>
<title>Testing page</title>
</h:head>
<h:body>
<h:form>
<h:inputText id="fieldValue" requiredMessage="Field is mandatory" title="#{testerBean2.someValue}" value="#{testerBean2.someValue}" required="true" style="#{empty testerBean2.someValue ? 'background-color: yellow;' : ''}"/>
<h:commandButton value="Store" action="#{testerBean2.storeValue}"/>
<h:commandButton value="Erase" action="#{testerBean2.eraseValue}"/>
</h:form>
</h:body>
Where is the problem?
I think it is problem of com.sun.faces.renderkit_html_basic.TextRenderer and its method getEndTextToRender:
protected void getEndTextToRender(FacesContext context,
UIComponent component,
String currentValue)
throws IOException {
ResponseWriter writer = context.getResponseWriter();
assert(writer != null);
boolean shouldWriteIdAttribute = false;
boolean isOutput = false;
String style = (String) component.getAttributes().get("style");
String styleClass = (String) component.getAttributes().get("styleClass");
String dir = (String) component.getAttributes().get("dir");
String lang = (String) component.getAttributes().get("lang");
String title = (String) component.getAttributes().get("title");
if (component instanceof UIInput) {
writer.startElement("input", component);
writeIdAttributeIfNecessary(context, writer, component);
writer.writeAttribute("type", "text", null);
writer.writeAttribute("name", (component.getClientId(context)),
"clientId");
// only output the autocomplete attribute if the value
// is 'off' since its lack of presence will be interpreted
// as 'on' by the browser
if ("off".equals(component.getAttributes().get("autocomplete"))) {
writer.writeAttribute("autocomplete",
"off",
"autocomplete");
}
// render default text specified
if (currentValue != null) {
writer.writeAttribute("value", currentValue, "value");
}
// Rest of code omitted
}
A currentValue parameter is explicitly passed into this method called from com.sun.faces.renderkit.html_basic.HtmlBasicRenderer.encodeEnd
#Override
public void encodeEnd(FacesContext context, UIComponent component)
throws IOException {
rendererParamsNotNull(context, component);
if (!shouldEncode(component)) {
return;
}
ResponseWriter writer = context.getResponseWriter();
assert(writer != null);
// NOTICE currentValue getter
String currentValue = getCurrentValue(context, component);
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE,
"Value to be rendered {0}",
currentValue);
}
// NOTICE currentValue
getEndTextToRender(context, component, currentValue);
}
and if we look closer at a method getCurrentValue()
/**
* #param context the FacesContext for the current request
* #param component the UIComponent whose value we're interested in
*
* #return the value to be rendered and formats it if required. Sets to
* empty string if value is null.
*/
protected String getCurrentValue(FacesContext context,
UIComponent component) {
if (component instanceof UIInput) {
Object submittedValue = ((UIInput) component).getSubmittedValue();
if (submittedValue != null) {
// value may not be a String...
return submittedValue.toString();
}
}
String currentValue = null;
Object currentObj = getValue(component);
if (currentObj != null) {
currentValue = getFormattedValue(context, component, currentObj);
}
return currentValue;
}
the property returned by getSubmittedValue() is filled in Restore View phase (if Process validation phase skips to Render response phase). As a consequence we get "updated" value which was passed from user only for value attribute, the rest remain unaltered.
In case of successful Process Validation phase, which does not cause direct skip to Render Response phase (if user fills in any not null value), a new constructor of HtmlInputText is called and style, title etc. attributes are filled from the scratch. These attributes are filled from managed bean which was updated with proper data in phase Update Model Values.
OK, this is not a bug, but the feature. It only affirms my thesis that something smells in the sentence: "If the request is a postback and errors were encountered during the apply request values phase, process validations phase, or update model values phase, the original page is rendered during Render response phase".
Any clue how to solve this situation if I really long for yellow background for mandatory fields?
Updated project is here: http://www.221b.cz/so/JSFTester2.zip
I have finally managed to get the validation to work.
I used validator which has access to UIComponent. In case of failed validation special style is applied to the component. This style is also taken into consideration in Render response phase.
So how does it behave?
View is restored including style style="#{empty testerBean2.someValue ? 'background-color: yellow;' : ''}"
Validation does not pass. So testerBean2.someValue is not updated (as Update Model Values phase is skipped) but constant style is set into h:inputText using RequiredValidator - component.setValueExpression("style", new ValueExpressionLiteral("background-color: yellow;", String.class));
In Render Response yellow background is applied even if testerBean.someValue has not been updated as Required Validator has already set constant new ValueExpressionLiteral("background-color: yellow;", String.class)
I have implemented own required validator (inspired by Bashan's validator from http://www.codereye.com/2009/12/validating-empty-text-field-using-jsf.html).
RequiredValidator.java
package cz.test;
import javax.faces.application.FacesMessage;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.validator.Validator;
import javax.faces.validator.ValidatorException;
import org.apache.el.ValueExpressionLiteral;
public class RequiredValidator implements Validator {
public void validate(FacesContext context, UIComponent component, Object value) throws ValidatorException {
if (value == null || "".equals(value.toString().trim())) {
FacesMessage message = new FacesMessage();
String messageStr = (String) component.getAttributes().get("message");
if (messageStr == null) {
messageStr = "Please enter data";
}
message.setDetail(messageStr);
message.setSummary(messageStr);
message.setSeverity(FacesMessage.SEVERITY_ERROR);
component.setValueExpression("style", new ValueExpressionLiteral("background-color: yellow;", String.class));
throw new ValidatorException(message);
} else {
component.setValueExpression("style", new ValueExpressionLiteral("", String.class));
}
}
}
The line I added:
component.setValueExpression("style", new ValueExpressionLiteral("background-color: yellow;", String.class));
to force JSF trigger validation for empty fields (web.xml):
....
<context-param>
<param-name>javax.faces.VALIDATE_EMPTY_FIELDS</param-name>
<param-value>true</param-value>
</context-param>
....
to register validator with JSF (faces-config.xml):
<validator>
<validator-id>RequiredValidator</validator-id>
<validator-class>cz.test.RequiredValidator</validator-class>
</validator>
and web page using required validator and TesterBean2 (index.xhtml):
<?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://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core">
<h:head>
<title>Testing page</title>
</h:head>
<h:body>
<h:form>
<h:messages/>
<h:inputText id="fieldValue"
title="#{testerBean2.someValue}"
value="#{testerBean2.someValue}"
style="#{empty testerBean2.someValue ? 'background-color: yellow;' : ''}">
<f:validator validatorId="RequiredValidator"/>
<f:attribute name="message" value="Field is mandatory"/>
</h:inputText>
<h:commandButton value="Store" action="#{testerBean2.storeValue}"/>
<h:commandButton value="Erase" action="#{testerBean2.eraseValue}"/>
</h:form>
</h:body>
</html>
NOTICE: There cannot be used required attribute in h:inputText. It would outrun required validator.