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:
Related
I have problem to display StreamedContent PDF in DocumentViewer from Primefaces Extensions (6.2.9) with PrimeFaces 6.2 and MyFaces 2.2.12. I read the same question, but it's an other situation.
Message: Missing PDF in PrimeFaces Extensions DocumentViewer
This is my xhtml code
<p:commandButton icon="fa fa-print" actionListener="#{bean.onPrerender}" />
Dialog code
<p:dialog id="dvDialog" widgetVar="dv_dialog" dynamic="true" header="Document" width="1200px" height="700px" modal="true">
<pe:documentViewer cache="true" height="500" value="#{bean.content}" download="report.pdf" />
</p:dialog>
This is my java code
private StreamedContent content;
public void onPrerender(ActionEvent actionEvent) {
try {
ByteArrayOutputStream out = new ByteArrayOutputStream();
Document document = new Document();
PdfWriter.getInstance(document, out);
document.open();
for (int i = 0; i < 50; i++) {
document.add(
new Paragraph("All work and no play makes Jack a dull boy"));
}
document.close();
// content = new DefaultStreamedContent(
// new ByteArrayInputStream(out.toByteArray()), "application/pdf");
content = new ByteArrayContent(out.toByteArray(), "application/pdf");
} catch (Exception e) {
e.printStackTrace();
}
PrimeFaces.current().executeScript("PF('dv_dialog').show()");
}
public StreamedContent getContent() {
return content;
}
public void setContent(StreamedContent content) {
this.content = content;
}
The error message
PDF.js v1.10.88 (build: c62a1938)
Message: Missing PDF "http://localhost:8080/hoft/javax.faces.resource/dynamiccontent.properties.xhtml?ln=primefaces&v=6.2&pfdrid=1a55ef4c9448951fae5f493579cf80e1&pfdrt=sc&pfdrid_c=true&download=report.pdf".
have anyone clue, what is wrong with my code? it is actually the code in demo showcase Primeface-Extensions with modification.
My project use iframe and the documentviewer will display in a popup dialog. I also tried with #SessionScoped and #ViewScoped, but have no luck.
If I try it in stand alone project, it works (without iframe). May be someone can give clues, how to debug to find the problem.
Please help.... Thank you.
I get error message
pdf.viewer.js.xhtml?ln=primefaces-extensions&v=6.2.9:17581 GET http://localhost:8081/hoft/javax.faces.resource/dynamiccontent.properties.xhtml?ln=primefaces&v=6.2&pfdrid=3c954d24c76c30714a581092c23e1489&pfdrt=sc&pfdrid_c=true&download=report.pdf 404
PDFFetchStreamReader # pdf.viewer.js.xhtml?ln=primefaces-extensions&v=6.2.9:17581
getFullReader # pdf.viewer.js.xhtml?ln=primefaces-extensions&v=6.2.9:17527
(anonymous) # pdf.viewer.js.xhtml?ln=primefaces-extensions&v=6.2.9:4388
(anonymous) # pdf.viewer.js.xhtml?ln=primefaces-extensions&v=6.2.9:1002
resolveCall # pdf.viewer.js.xhtml?ln=primefaces-extensions&v=6.2.9:1001
_createStreamSink # pdf.viewer.js.xhtml?ln=primefaces-extensions&v=6.2.9:1266
MessageHandler._onComObjOnMessage # pdf.viewer.js.xhtml?ln=primefaces-extensions&v=6.2.9:1094
pdf.viewer.js.xhtml?ln=primefaces-extensions&v=6.2.9:19633 Uncaught (in promise) Error: Missing PDF file.
at pdf.viewer.js.xhtml?ln=primefaces-extensions&v=6.2.9:19633
I tried this using:
Java EE 7
GlassFish 4.1.2
PrimeFaces 6.2
PrimeFaces-Extensions 6.2.9
At the bean (class) code:
#ManagedBean
#ApplicationScoped
public class DocumentViewerController {
The scope is #ApplicationScoped. I have a private StreamedContent attribute. And two main public methods:
First method: It's called from actionListener attribute of a p:commandButton. The method receive a parameter (in my case).
public void onPrerender(Tramite tramite) {
tramiteSelected = tramite;
numeroTramite = tramite.getNumero();
contrato = tramite.getContrato();
}
Second method: It's used from a pe:documentViewer inside a dialog component, like this:
<pe:documentViewer id="certificadoViewer"
height="500px"
width="750px"
cache="false"
value="#{documentViewerController.certificado}"
download="certificado_#{documentViewerController.numero}_#{documentViewerController.contrato}.pdf" />
Note: The 2nd method works like a property (getter and setter). THAT'S THE TRICK.
The final code from my project is:
# Bean (DocumentViewerController.java):
package com.epmrpsd.certificado.consulta.controladores;
import com.epmrpsd.certificado.consulta.controladores.util.JsfUtil;
import com.epmrpsd.certificado.consulta.entidades.Tramite;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import javax.faces.bean.ApplicationScoped;
import javax.faces.bean.ManagedBean;
import org.primefaces.model.DefaultStreamedContent;
import org.primefaces.model.StreamedContent;
/**
*
* #author pbonilla
*/
#ManagedBean
#ApplicationScoped
public class DocumentViewerController {
private StreamedContent content;
private Integer numeroTramite;
private Integer contrato;
private Tramite tramiteSelected;
// Path where the file exists
private String pdfPathDirectory = "C:\\Users\\<user>\\certificados\\";
public void onPrerender(Tramite tramite) {
tramiteSelected = tramite;
numeroTramite = tramite.getNumero();
contrato = tramite.getContrato();
}
public StreamedContent getCertificado() {
InputStream stream = null;
try {
File file = new File(pdfPathDirectory + numeroTramite + "_" + contrato + ".pdf");
if (file.exists()) {
stream = new FileInputStream(file);
} else {
JsfUtil.addErrorMessage("Error", "No se ha encontrado el archivo");
}
this.content = new DefaultStreamedContent(stream, "application/pdf");
} catch (FileNotFoundException fnfex) {
JsfUtil.addErrorMessage("Error", "No se ha encontrado el archivo. Error: " + fnfex.getMessage());
fnfex.printStackTrace();
} catch (Exception e) {
JsfUtil.addErrorMessage("Error", "Se ha generado un error al cargar el certificado. Error: " + e.getMessage());
e.printStackTrace();
}
return content;
}
public void setCertificado(StreamedContent contenido) {
content = contenido;
}
public Tramite getTramiteSelected() {
return tramiteSelected;
}
public void setTramiteSelected(Tramite tramiteSelected) {
this.tramiteSelected = tramiteSelected;
}
public Integer getNumero() {
return numeroTramite;
}
public void setNumero(Integer numeroTramite) {
this.numeroTramite = numeroTramite;
}
public Integer getContrato() {
return contrato;
}
public void setContrato(Integer contrato) {
this.contrato = contrato;
}
}
# View (index.xhtml):
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:f="http://xmlns.jcp.org/jsf/core"
xmlns:p="http://primefaces.org/ui"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:pe="http://primefaces.org/ui/extensions">
<h:head>
<title>Consulta de Certificados Digitales</title>
<h:outputStylesheet library="css" name="epmrpsd.css" />
<h:outputStylesheet library="webjars" name="font-awesome/5.5.0/css/all-jsf.css" />
<h:outputStylesheet library="css" name="jsfcrud.css"/>
<h:outputScript library="js" name="jsfcrud.js"/>
<link rel="shortcut icon" type="image/png" href="#{resource['images/logo.png']}"/>
</h:head>
<h:body>
<div id="background" style="position: fixed;">
<h:form id="formCertificados">
<div class="ui-g" style="margin-top: 25px;">
<div class="ui-g-1"></div>
<div class="ui-g-10">
<p:growl id="mensajes" />
<Extra code> ...
<p:outputPanel id="pnlCertificado">
<p:dataTable id="tramitesTable"
value="#{tramiteController.items}"
var="tramite"
rowKey="#{tramite.id}"
selectionMode="single"
selection="#{tramiteController.selected}"
emptyMessage="No se encontraron trámites con los criterios dados"
rows="10"
rowsPerPageTemplate="10,20,30,40,50">
<p:column headerText="Número Trámite" >
<h:outputText value="#{tramite.numero}" />
</p:column>
<p:column headerText="Descripción" >
<h:outputText value="#{tramite.tipo.descripcion}" />
</p:column>
<p:column headerText="Número Contrato" >
<h:outputText value="#{tramite.contrato}" />
</p:column>
<p:column style="text-align: center" headerText="Acción" >
<center>
<p:commandButton id="viewCertificado"
styleClass="ui-priority-primary"
value="Ver certificado"
actionListener="#{documentViewerController.onPrerender(tramite)}"
update=":ViewCertificadoForm"
oncomplete="PF('ViewCertificadoDialog').show()" />
</center>
</p:column>
</p:dataTable>
</p:outputPanel>
</div>
<div class="ui-g-1"></div>
</div>
</h:form>
<ui:include src="ViewCertificado.xhtml"/>
</div>
</h:body>
</html>
And the final component for the view is (ViewCertificado.xhtml):
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:f="http://xmlns.jcp.org/jsf/core"
xmlns:p="http://primefaces.org/ui"
xmlns:pe="http://primefaces.org/ui/extensions">
<ui:composition>
<p:dialog id="ViewCertificadoDlg"
widgetVar="ViewCertificadoDialog"
modal="true"
resizable="false"
appendTo="#(body)"
header="Certificado #{documentViewerController.contrato}">
<h:form id="ViewCertificadoForm">
<h:panelGroup id="display">
<p:panelGrid columns="1" rendered="#{documentViewerController.tramiteSelected != null}">
<pe:documentViewer id="certificadoViewer"
height="500px"
width="750px"
cache="false"
value="#{documentViewerController.certificado}"
download="certificado_#{documentViewerController.numero}_#{documentViewerController.contrato}.pdf" />
</p:panelGrid>
<p:commandButton value="Cerrar" onclick="ViewCertificadoDialog.hide()"/>
</h:panelGroup>
</h:form>
</p:dialog>
</ui:composition>
</html>
I'm using p:tabView to split the editable data into the sections. All edit elements are places inside p:inplace components. The problem is specifically with p:selectOneMenu component.
When the whole tab view is refreshed, the value of the p:selectOneMenu that are on the other tabs as active, are set to null. I don't know why. Is this a bug, or is this a false usage of PrimeFaces componenents?
The environment:
PrimeFaces 3.4
MyFaces 2.0.7
IBM WebSphere 7.0
The way to reproduce the error:
Make a tabView with more that one tab
place in one tab the selectOneMenu inside inplace
make button that will update the tabView
choose the value for selectOneMenu, change the tab, click refresh and return to the tab with selectOneMenu
The code of the sample page and bean:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:p="http://primefaces.org/ui">
<h:head>
<f:facet name="first">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</f:facet>
<title>QMWPSUI</title>
<h:outputScript library="qm" name="qmutils.js" />
<h:outputScript library="qmwpsui" name="process.js" />
</h:head>
<h:body>
<h:form id="main">
<p:blockUI block="main" trigger="refreshButton" widgetVar="block">
<p:graphicImage
value="#{resource['primefaces-qmui:images/waitprogress.gif']}" />
</p:blockUI>
<h3>Test</h3>
<p:commandButton id="refreshButton" widgetVar="refreshButton"
action="#{test.refresh}"
icon="ui-icon-refresh" title="#{i18n['action.reload']}"
onclick="block.show()"
update="tabView"/>
<p:tabView id="tabView" orientation="top" dynamic="TRUE">
<p:tab id="tab1" title="Tab 1" process="#this">
<h:outputLabel value="Type*:" for="type"/>
<p:inplace emptyLabel="Click here to change">
<p:selectOneMenu id="typ" value="#{test.type}" effect="fade"
style="width:300px">
<f:selectItem itemLabel="" itemValue="" />
<f:selectItem itemLabel="Type a" itemValue="a" />
<f:selectItem itemLabel="Type b" itemValue="b" />
<f:selectItem itemLabel="Type c" itemValue="c" />
</p:selectOneMenu>
</p:inplace>
</p:tab>
<p:tab id="tab2" title="Tab 2" process="#this">
</p:tab>
<p:tab id="tab3" title="Tab 3" process="#this">
</p:tab>
</p:tabView>
</h:form>
</h:body>
The bean class:
import java.io.Serializable;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;
#ManagedBean(name = "test")
#ViewScoped
public class TestBean implements Serializable {
private static final long serialVersionUID = 1L;
private static final Logger log = LoggerFactory.getLogger(TestBean.class);
private String type;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
#Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("TestBean [");
if (type != null) {
builder.append("type=");
builder.append(type);
}
builder.append("]");
return builder.toString();
}
public void refresh() {
log.info("Refresh");
log.info("Test = <{}>", this);
}
}
you need a converter for this case
public class EmptyToNullConverter implements Converter {
#Override
public Object getAsObject(FacesContext facesContext, UIComponent component,
String value) {
Object retorno = value;
if (value == null || value.isEmpty()) {
if (component instanceof EditableValueHolder) {
((EditableValueHolder) component).setSubmittedValue(null);
}
retorno = null;
}
return retorno;
}
#Override
public String getAsString(FacesContext facesContext, UIComponent component,
Object value) {
return (value == null) ? null : value.toString();
}
}
and define in your faces-config.xml
<converter>
<converter-for-class>java.lang.String</converter-for-class>
<converter-class>org.converter.EmptyToNullConverter</converter-class>
</converter>
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.
I have facelets page and managed bean that is associated with it.
i have used primefaces components and my problem is i want to get the values selected by the user of each components when a commandbutton is clicked.
when i try to write a JoptionPane or system.out.print it does not work. i have set the commandbutton action property to btnsearchFlight method whcih is found in the managedbean. so any one what is the problem what i am missing. Or an example will be very much appreciated.
Here is the Facelets page
<?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:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core" xmlns:p="http://primefaces.org/ui">
<h:head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<link href="./resources/css/default.css" rel="stylesheet" type="text/css" />
<link href="./resources/css/cssLayout.css" rel="stylesheet" type="text/css" />
<title>Airline Travel Planner</title>
</h:head>
<h:body>
<h:form id="form">
<div id="top">
<ui:insert name="top">AirLine Travel Planner</ui:insert>
</div>
<div>
<div id="left">
<ui:insert name="left"></ui:insert>
</div>
<div>
<div id="right">
<ui:insert name="right"></ui:insert>
</div>
<div id="content" class="right_content" style="height: 500px">
<ui:insert name="content">
<p:selectOneRadio binding="#{calendarBean1.rdbTripType}" id="rdbTripType" value="#{calendarBean1.rdbTripType}">
<f:selectItem itemLabel="One Way" itemValue="1" />
<f:selectItem itemLabel="Round Trip" itemValue="2" />
</p:selectOneRadio>
<br/>
<h:outputLabel>From:</h:outputLabel>
<p:selectOneMenu value="#{calendarBean1.cityInfo}" style="" effect="fold" editable="true">
<f:selectItems value="#{calendarBean1.cityInfo}" />
</p:selectOneMenu>
<h:outputLabel style="position: relative">To:</h:outputLabel>
<p:selectOneMenu value="#{calendarBean1.cityInfo}" effect="fold" editable="true">
<f:selectItems value="#{calendarBean1.cityInfo}" />
</p:selectOneMenu>
<br/><br/>
<h:outputLabel>Depart On:</h:outputLabel>
<p:calendar value="#{calendarBean1.date3}" id="popupButtonDepartOn" showOn="button" />
<h:outputLabel>Arrive On:</h:outputLabel>
<p:calendar value="#{calendarBean1.date2}" id="popupButtonArriveOn" showOn="button" />
<br/> <br/>
<h:outputText value="Passenger Type" />
<p:selectOneMenu id="selectOneMenuPassengerType" binding="#{calendarBean1.selectOneMenuPassengerType}" value="#{calendarBean1.selectOneMenuPassengerType}" >
<f:selectItem itemLabel="Select One" itemValue="" />
<f:selectItem itemLabel="Adult" itemValue="1" />
<f:selectItem itemLabel="Child" itemValue="2" />
<f:selectItem itemLabel="Infant" itemValue="3" />
</p:selectOneMenu>
<br/> <br/>
<p:selectBooleanCheckbox value="#{calendarBean1.lowestFareChecked}" />
<h:outputText value="Lowest Fare" />
<br/>
<p:commandButton id="btnSearchFlight" value="Search" action="#{calendarBean1.searchFlight}" >
</p:commandButton>
Here is the managed bean class
package test.sample;
import java.io.Serializable;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.Date;
import javax.faces.bean.ManagedBean;
import javax.faces.model.SelectItem;
import javax.swing.JOptionPane;
import org.primefaces.component.commandbutton.CommandButton;
import org.primefaces.component.selectonemenu.SelectOneMenu;
import org.primefaces.component.selectoneradio.SelectOneRadio;
import storageMethods.FlightMethod;
/**
*
* #author Nati
*/
#ManagedBean(name = "calendarBean1")
public class CalendarBean1 implements Serializable{
/**
* Creates a new instance of CalendarBean1
*/
public CalendarBean1() {
}
private Date date1;
private Date date2;
private Date date3;
private boolean lowestFareChecked;
public boolean isLowestFareChecked() {
return lowestFareChecked;
}
public void setLowestFareChecked(boolean lowestFareChecked) {
this.lowestFareChecked = lowestFareChecked;
}
public Date getDate1() {
return date1;
}
public void setDate1(Date date1) {
this.date1 = date1;
}
public Date getDate2() {
return date2;
}
public void setDate2(Date date2) {
this.date2 = date2;
}
public Date getDate3() {
return date3;
}
public void setDate3(Date date3) {
this.date3 = date3;
}
private SelectOneRadio rdbTripType = new SelectOneRadio();
public SelectOneRadio getRdbTripType() {
return rdbTripType;
}
public void setRdbTripType(SelectOneRadio rdbTripType) {
this.rdbTripType = rdbTripType;
}
public CommandButton btnSearchFlight = new CommandButton();
public CommandButton getBtnSearchFlight() {
return btnSearchFlight;
}
public void setBtnSearchFlight(CommandButton btnSearchFlight) {
this.btnSearchFlight = btnSearchFlight;
}
private SelectOneMenu selectOneMenuPassengerType = new SelectOneMenu();
public SelectOneMenu getSelectOneMenuPassengerType() {
return selectOneMenuPassengerType;
}
public void setSelectOneMenuPassengerType(SelectOneMenu selectOneMenuPassengerType) {
this.selectOneMenuPassengerType = selectOneMenuPassengerType;
}
public ArrayList<SelectItem> CityInfo;
public ArrayList<SelectItem> getCityInfo() {
CityInfo = CityInfo();
return CityInfo;
}
public void setCityInfo(ArrayList<SelectItem> CityInfo) {
this.CityInfo = CityInfo;
}
public String SearchFlight() {
// JOptionPane.showMessageDialog(null, rdbTripType.getValue().toString());
// JOptionPane.showMessageDialog(null, selectOneMenuPassengerType.getValue().toString());
JOptionPane.showMessageDialog(null,date3);
// System.out.print("hi");
// System.out.print(isLowestFareChecked());
return null;
}
}
Maybe just a typo when posting on stackoverflow, but your method is Capitalized:
SearchFlight
And your action in .xhtml button is lowercase.
action="#{calendarBean1.searchFlight}
Shouldn't it be
public String searchFlight() {
I never used Swing components in a JSf franework. But the system.out should work when commented out. If you method is called. Don't you get any errors in your logs?
I think you mixed webapp development (JSF) with desktop development (Swing). With JSF you don't need to create a backing bean component for every single facelet component. You don't need
org.primefaces.component.commandbutton.CommandButton
org.primefaces.component.selectonemenu.SelectOneMenu
org.primefaces.component.selectoneradio.SelectOneRadio
in your bean if you only want to bind the input values of these components.
Of course in some situations you will get benefits from component binding but it is not necessary in your example.
In the facelet you use the value attribute and the binding attribute, but they have the same content.
If you are only interested in input values, the value attribute is all you need. Let this attribute point to a backing bean field that only will hold the value, e.g. an int or String.
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.