I'm using JSF 2.2 / Mojarra 2.2.8
In my model there are java.util.Set and I want to edit those Set
public class MyModel {
private Set<Foo> fooSet;
private Set<Bar> barSet;
// getters and setters
}
public class Foo {
private String label;
//getter and setter
}
public class Bar {
private String name;
// getter and setter
}
I'm using composite component for that
<h:form>
<ez:editFooSet myModel="#{someBean.myModel}"/>
<ez:editBarSet myModel="#{someBean.myModel}"/>
<!-- ... -->
</h:form>
My idea is to store a List needed by the ui:repeat in a JSF ManagedBean and use a #FacesComponent to convert the Set to List in encodeBegin() and the List to Set in updateModel()
editFooSet.xhtml :
<cc:interface componentType="my.app.component.FooSetComponent">
<cc:attribute name="myModel" type="my.app.model.MyModel" required="true"/>
</cc:interface>
<cc:implementation>
<ui:repeat value="#{fooSetBean.value}" var="item">
<h:outputLabel value="Foo label: "/>
<h:inputText value="#{item.label}"/>
<h:commandButton value="remove" action="#{fooSetBean.remove(item)}"/>
</ui:repeat>
<h:commandButton value="add" action="#{fooSetBean.add()}"/>
</cc:implementation>
FooSetBean.java
#Named
#ViewScoped
public class FooSetBean {
private List<Foo> value;
// getter and setter
puvlic void remove(Foo foo) {
fooList.remove(foo);
}
public void add() {
fooList.add(new Foo());
}
}
and the FooSetComponent.java :
#FacesComponent("my.app.component.FooSetComponent")
public class FooSetComponent extends UIInput implements NamingContainer {
#Override
public String getFamily() {
return UINamingContainer.COMPONENT_FAMILY;
}
#Override
public Object getSubmittedValue() {
return null;
}
#Override
public void encodeBegin(FacesContext context) throws IOException {
MyModel model = (MyModel) super.getAttributes().get("myModel");
Collection<Foo> foos = model.getFooSet();
List<Foo> fooList = new ArrayList<>(foos);
FooSetBean bean = context.getApplication().evaluateExpressionGet(context, "#{fooSetBean}", FooSetBean.class) ;
bean.setValue(fooList);
super.encodeBegin(context);
}
#Override
public void updateModel(FacesContext context) {
MyModel model = (MyModel) super.getAttributes().get("myModel");
FooSetBean bean = context.getApplication().evaluateExpressionGet(context, "#{fooSetBean}", FooSetBean.class) ;
Collection<Foo> newValue = bean.getValue();
model.setFooSet(new HashSet<>(newValue));
}
}
and the same for editBarSet.xhtml, BarSetBean.java and BarSetComponent.java
And that solution is working
My problem is that I have a lot of those Set and I want to factorize this code
I want to have something like that :
<h:form>
<ez:editRepeat value="#{someBean.myModel.fooSet}" itemClass="#{Foo.class}">
<h:outputLabel value="Foo label: "/>
<h:inputText value="#{item.label"/>
</ez:editRepeat>
<ez:editRepeat value="#{someBean.myModel.barSet}" itemClass="#{Bar.class}">
<h:outputLabel value="Bar name: "/>
<h:inputText value="#{item.name}"/>
</ez:editRepeat>
<!-- ... -->
</h:form>
with the editRepeat.xhtml :
<cc:interface componentType="my.app.component.EditRepeatComponent">
<cc:attribute name="value" type="java.util.collection" required="true"/>
<cc:attribute name="itemClass" type="java.lang.Class" required="true"/>
</cc:interface>
<cc:implementation>
<ui:repeat value="#{fooSetBean.value}" var="item" id="repeat">
<cc:insertChildren/>
<h:commandButton value="remove" action="#{cc.remove(item)}"/>
</ui:repeat>
<h:commandButton value="add" action="#{cc.add()}"/>
</cc:implementation>
with a EditRepeatComponent.java
#FacesComponent("my.app.component.EditRepeatComponent")
public class EditRepeatComponent extends UIInput implements NamingContainer {
#Override
public String getFamily() {
return UINamingContainer.COMPONENT_FAMILY;
}
#Override
public void encodeBegin(FacesContext context) throws IOException {
Collection value = (Collection) super.getAttributes().get("value");
List<Foo> list = new ArrayList<>(value);
setList(list);
super.encodeBegin(context);
}
public List getList() {
return (List) getStateHelper().get("list");
}
public void setList(List list) {
getStateHelper().put("list", list);
}
public void add() {
try {
Class itemClass = (Class) super.getAttributes().get("itemClass");
Object newItem = itemClass.newInstance();
getList().add(newItem);
} catch (InstantiationException | IllegalAccessException ex) {
throw new RuntimeException(ex);
}
}
public void remove(Object item) {
getList().remove(item);
}
#Override
public void updateModel(FacesContext context) {
// ???
}
#Override
public Object getSubmittedValue() {
// ???
}
}
But that doesn't work
After a few seconds (the system works during 1 second) I have an exeption :
java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
at java.util.ArrayList.rangeCheck(ArrayList.java:653)
at java.util.ArrayList.get(ArrayList.java:429)
at javax.faces.component.AttachedObjectListHolder.restoreState(AttachedObjectListHolder.java:166)
at javax.faces.component.UIComponentBase.restoreState(UIComponentBase.java:1611)
at com.sun.faces.application.view.FaceletPartialStateManagementStrategy$2.visit(FaceletPartialStateManagementStrategy.java:380)
at com.sun.faces.component.visit.FullVisitContext.invokeVisitCallback(FullVisitContext.java:151)
at javax.faces.component.UIComponent.visitTree(UIComponent.java:1689)
at javax.faces.component.UIComponent.visitTree(UIComponent.java:1700)
at javax.faces.component.UIComponent.visitTree(UIComponent.java:1700)
at com.sun.faces.application.view.FaceletPartialStateManagementStrategy.restoreView(FaceletPartialStateManagementStrategy.java:367)
at com.sun.faces.application.StateManagerImpl.restoreView(StateManagerImpl.java:138)
at com.sun.faces.application.view.ViewHandlingStrategy.restoreView(ViewHandlingStrategy.java:123)
at com.sun.faces.application.view.FaceletViewHandlingStrategy.restoreView(FaceletViewHandlingStrategy.java:585)
at com.sun.faces.application.view.MultiViewHandler.restoreView(MultiViewHandler.java:150)
at javax.faces.application.ViewHandlerWrapper.restoreView(ViewHandlerWrapper.java:353)
at javax.faces.application.ViewHandlerWrapper.restoreView(ViewHandlerWrapper.java:353)
at javax.faces.application.ViewHandlerWrapper.restoreView(ViewHandlerWrapper.java:353)
at org.omnifaces.viewhandler.RestorableViewHandler.restoreView(RestorableViewHandler.java:86)
at com.sun.faces.lifecycle.RestoreViewPhase.execute(RestoreViewPhase.java:197)
at com.sun.faces.lifecycle.Phase.doPhase(Phase.java:101)
at com.sun.faces.lifecycle.RestoreViewPhase.doPhase(RestoreViewPhase.java:121)
at com.sun.faces.lifecycle.LifecycleImpl.execute(LifecycleImpl.java:198)
at javax.faces.webapp.FacesServlet.service(FacesServlet.java:646)
at io.undertow.servlet.handlers.ServletHandler.handleRequest(ServletHandler.java:85)
at io.undertow.servlet.handlers.security.ServletSecurityRoleHandler.handleRequest(ServletSecurityRoleHandler.java:61)
at io.undertow.servlet.handlers.ServletDispatchingHandler.handleRequest(ServletDispatchingHandler.java:36)
at org.wildfly.extension.undertow.security.SecurityContextAssociationHandler.handleRequest(SecurityContextAssociationHandler.java:78)
at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
at io.undertow.servlet.handlers.security.SSLInformationAssociationHandler.handleRequest(SSLInformationAssociationHandler.java:131)
at io.undertow.servlet.handlers.security.ServletAuthenticationCallHandler.handleRequest(ServletAuthenticationCallHandler.java:56)
at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
at io.undertow.security.handlers.AbstractConfidentialityHandler.handleRequest(AbstractConfidentialityHandler.java:45)
at io.undertow.servlet.handlers.security.ServletConfidentialityConstraintHandler.handleRequest(ServletConfidentialityConstraintHandler.java:63)
at io.undertow.security.handlers.AuthenticationMechanismsHandler.handleRequest(AuthenticationMechanismsHandler.java:58)
at io.undertow.servlet.handlers.security.CachedAuthenticatedSessionHandler.handleRequest(CachedAuthenticatedSessionHandler.java:70)
at io.undertow.security.handlers.SecurityInitialHandler.handleRequest(SecurityInitialHandler.java:76)
at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
at org.wildfly.extension.undertow.security.jacc.JACCContextIdHandler.handleRequest(JACCContextIdHandler.java:61)
at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
at io.undertow.servlet.handlers.ServletInitialHandler.handleFirstRequest(ServletInitialHandler.java:261)
at io.undertow.servlet.handlers.ServletInitialHandler.dispatchRequest(ServletInitialHandler.java:247)
at io.undertow.servlet.handlers.ServletInitialHandler.access$000(ServletInitialHandler.java:76)
at io.undertow.servlet.handlers.ServletInitialHandler$1.handleRequest(ServletInitialHandler.java:166)
at io.undertow.server.Connectors.executeRootHandler(Connectors.java:197)
at io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:759)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
And I don't understand why
And I don't know yet how to implements updateModel() or getSubmittedValue() to make all the system working
After 2 days I finally succeed
Omnifaces doc http://showcase.omnifaces.org/functions/Converters gave me the solution to deal with ui:repeat : using toArray()
<h:form>
<ez:editRepeat value="#{someBean.myModel.fooSet}" itemClass="my.app.model.Foo">
<h:outputLabel value="Foo label: "/>
<h:inputText value="#{item.label}"/>
</ez:editRepeat>
<ez:editRepeat value="#{someBean.myModel.barSet}" itemClass="my.app.model.Bar">
<h:outputLabel value="Bar name: "/>
<h:inputText value="#{item.name}"/>
</ez:editRepeat>
<!-- ... -->
</h:form>
the editRepeat.xtml (I'm using primefaces p:commanButton to specify update and process attribute so that I don't lose unsubmited input and I don't submit all the form)
<cc:interface>
<cc:attribute name="value" type="java.util.Collection" required="true"/>
<cc:attribute name="itemClass" type="java.lang.String" required="true"/>
</cc:interface>
<cc:implementation>
<h:panelGroup style="display: block; background-color: rgba(200, 200, 200, 0.5); padding: 12px;">
<ui:repeat value="#{cc.attrs.value.toArray()}" var="item">
<h:panelGroup style="background-color: rgba(200, 200, 200, 0.5); margin-left: 12px; margin-bottom: 12px; display: block; padding: 12px;">
<cc:insertChildren/>
<p:commandButton value="remove" action="#{editRepeatBean.remove(cc.attrs.value, item)}"
update="#parent:#parent:#parent" process="#parent:#parent:#parent"
style="margin-left: 12px;"/>
</h:panelGroup>
</ui:repeat>
<p:commandButton value="add" action="#{editRepeatBean.add(cc.attrs.value, cc.attrs.itemClass)}" update="#parent" process="#parent"/>
</h:panelGroup>
</cc:implementation>
the EditRepeatBean.java
#Named
#RequestScoped
public class EditRepeatBean {
public void add(Collection collection, String itemClassName) {
try {
Class itemClass = Class.forName(itemClassName);
Object item = itemClass.newInstance();
collection.add(item);
} catch (InstantiationException | IllegalAccessException | ClassNotFoundException ex) {
throw new RuntimeException(ex);
}
}
public void remove(Collection collection, Object item) {
collection.remove(item);
}
}
and if you have :
public class MyModel {
private Set<Foo> fooSet;
// getter and setter
}
public class Foo {
private String label;
private Set<Bar> barSet;
// getters and setters
}
public class Bar {
private String name;
// getter and setter
}
you can do
<h:form>
<ez:editRepeat value="#{someBean.myModel.fooSet}" itemClass="my.app.model.Foo">
<h:outputLabel value="Foo label: "/>
<h:inputText value="#{item.label}"/>
<ez:editRepeat value="#{item.barSet}" itemClass="my.app.model.Bar">
<h:outputLabel value="Bar name: "/>
<h:inputText value="#{item.name}"/>
</ez:editRepeat>
</ez:editRepeat>
<!-- ... -->
</h:form>
and it's working too
There is still one problem remaining : the Set must not be null, I will edit if I found a solution
EDIT : solution for the null Collection
Just change the editRepeat.xhtml interface to add a componentType so that the collection will be initialize in the encodeBegin() method and add an cc:attribute to secify the implementation of a Collection with a default value to HashSet
<cc:interface componentType="my.app.component.EditRepeatComponent">
<cc:attribute name="value" type="java.util.Collection" required="true"/>
<cc:attribute name="itemClass" type="java.lang.String" required="true"/>
<cc:attribute name="collectionImpl" type="java.lang.String" default="java.util.HashSet"/>
</cc:interface>
and the EditRepeatComponent.java
#FacesComponent("my.app.component.EditRepeatComponent")
public class EditRepeatComponent extends UIInput implements NamingContainer {
#Override
public String getFamily() {
return UINamingContainer.COMPONENT_FAMILY;
}
#Override
public void encodeBegin(FacesContext context) throws IOException {
ELContext elContext = context.getELContext();
ValueExpression valueExpression = super.getValueExpression("value");
if (valueExpression.getValue(elContext) == null) {
try {
String collectionImpl = (String) super.getAttributes().get("collectionImpl");
Class<? extends Collection> collectionClass = (Class<? extends Collection>) Class.forName(collectionImpl);
Collection collection = collectionClass.newInstance();
valueExpression.setValue(elContext, collection);
} catch (InstantiationException | IllegalAccessException | ClassNotFoundException ex) {
throw new RuntimeException(ex);
}
}
super.encodeBegin(context);
}
}
well there is still a problem... when an ez:editRepeat is in an other ez:editRepeat, the remove of the inner ez:editRepeat doesn't work
Caused by: javax.el.PropertyNotFoundException: The class 'my.app.model.Bar' does not have the property 'barSet'
Edit : Final solution.
With the previous solution, there was a problem in nested <editRepeat>, during the processValidators() phase, the var of inner <repeat> components is null, causing a Exception and I don't know why, it's may be a bug...
The solution is to #Override processValidators() and re set the repeat.var.
Here is the complete solution with some improvement :
The component is warpped in an other so that the update of the parent just update the component
All the code of EditRepeatBean has been moved to EditRepeatComponent
Add var attribute
rename attributes for consistency
update/render and process/execute done programmatically
<h:form>
<ez:editRepeat value="#{someBean.myModel.fooSet}"
itemType="my.app.model.Foo"
var="foo">
<h:outputLabel value="Foo label: "/>
<h:inputText value="#{foo.label}"/>
<ez:editRepeat value="#{foo.barSet}"
itemType="my.app.model.Bar"
var="bar">
<h:outputLabel value="Bar name: "/>
<h:inputText value="#{bar.name}"/>
</ez:editRepeat>
</ez:editRepeat>
<!-- ... -->
</h:form>
editRepeat.xhtml (the wrapper) :
<cc:interface>
<cc:attribute name="value" type="java.util.Collection" required="true"/>
<cc:attribute name="itemType" type="java.lang.String" required="true"/>
<cc:attribute name="collectionType" type="java.lang.String" default="java.util.HashSet"/>
<cc:attribute name="var" type="java.lang.String" required="true"/>
</cc:interface>
<cc:implementation>
<h:panelGroup id="#{cc.id}Wrapper">
<ez:editRepeatWrapped value="#{cc.attrs.value}" var="#{cc.attrs.var}"
itemType="#{cc.attrs.itemType}"
collectionType="#{cc.attrs.collectionType}"
id="#{cc.id}Wrapped">
<cc:insertChildren/>
</ez:editRepeatWrapped>
</h:panelGroup>
</cc:implementation>
the editRepeatWrapped.xhtml :
<cc:interface componentType="my.app.component.EditRepeatComponent">
<cc:attribute name="value" type="java.util.Collection" required="true"/>
<cc:attribute name="itemType" type="java.lang.String" required="true"/>
<cc:attribute name="collectionType" type="java.lang.String" default="java.util.HashSet"/>
<cc:attribute name="var" type="java.lang.String" required="true"/>
</cc:interface>
<cc:implementation>
<h:panelGroup id="itemsGroup" style="display: block; background-color: rgba(0, 255, 0, 0.20); padding: 6px; margin: 6px;">
<ui:repeat value="#{cc.attrs.value.toArray()}" var="#{cc.attrs.var}"
id="#{cc.attrs.id}Repeat">
<h:panelGroup id="itemGroup" style="background-color: rgba(0, 255, 0, 0.2); margin-left: 12px; margin: 6px; display: block; padding: 6px;">
<cc:insertChildren/>
<p:commandButton value="remove" action="#{cc.remove()}"
style="margin-left: 12px;"/>
</h:panelGroup>
</ui:repeat>
<p:commandButton value="add" action="#{cc.add()}"/>
</h:panelGroup>
</cc:implementation>
the EditeRepeatComponent.java :
#FacesComponent("my.app.component.EditRepeatComponent")
public class EditRepeatComponent extends UIInput implements NamingContainer {
#Override
public String getFamily() {
return UINamingContainer.COMPONENT_FAMILY;
}
#Override
public void processValidators(FacesContext context) {
initVar(); // because repeat.var is null at this stage
super.processValidators(context);
}
#Override
public void encodeBegin(FacesContext context) throws IOException {
initValue(context);
initVar();
super.encodeBegin(context);
}
/**
* set var of the repeat component
*/
private void initVar() {
String idRepeatComponent = ((String) super.getAttributes().get("id")) + "Repeat";
String var = (String) getAttributes().get("var");
UIRepeat repeatConponent = (UIRepeat) super.findComponent(idRepeatComponent);
repeatConponent.setVar(var);
}
/**
* if the value is null then initialize the collection with the collection type attribute
*/
private void initValue(FacesContext context) {
ELContext elContext = context.getELContext();
ValueExpression valueExpression = super.getValueExpression("value");
Collection collection = (Collection) valueExpression.getValue(elContext);
if (collection == null) {
try {
String collectionType = (String) getAttributes().get("collectionType");
Class<? extends Collection> collectionClass = (Class<? extends Collection>) Class.forName(collectionType);
collection = collectionClass.newInstance();
valueExpression.setValue(elContext, collection);
} catch (InstantiationException | IllegalAccessException | ClassNotFoundException ex) {
throw new RuntimeException(ex);
}
}
}
public void remove() {
String var = (String) getAttributes().get("var");
Object item = evaluate(var);
Collection collection = (Collection) getAttributes().get("value");
collection.remove(item);
updateView();
}
private Object evaluate(String var) {
FacesContext facesContext = getFacesContext();
ELContext elContext = facesContext.getELContext();
Application application = facesContext.getApplication();
ExpressionFactory expressionFactory = application.getExpressionFactory();
ValueExpression expression = expressionFactory.createValueExpression(elContext, "#{" + var + "}", Object.class);
Object item = expression.getValue(elContext);
return item;
}
public void add() {
try {
Collection collection = (Collection) getAttributes().get("value");
String itemType = (String) getAttributes().get("itemType");
Class itemClass = Class.forName(itemType);
Object item = itemClass.newInstance();
collection.add(item);
updateView();
} catch (InstantiationException | IllegalAccessException | ClassNotFoundException ex) {
throw new RuntimeException(ex);
}
}
/**
* render/update and execute/process the wrapper of the component
*/
private void updateView() {
PartialViewContext context = getFacesContext().getPartialViewContext();
String parentId = this.getParent().getClientId();
context.getRenderIds().add(parentId);
context.getExecuteIds().add(parentId);
}
}
Not that <ui:repeat ... var="#{cc.attrs.var}" ...> is useless, the var isn't set that way (and I don't know why...), it is set in the EditRepeatComponent.initVar() during both encodeBegin() and processValidators() I just put var="#{cc.attrs.var}" for the understanding
Related
Every time I select some of the elements of my p:datatable it always returns the first one, always.
I have this following piece of code that is used on my screen, in which the detailEntity is referenced in the table as a row of the same.
Tab Lots
<sgv:detailCrud
scrollable="true"
scrollHeight="350"
updateClass="lotChangeListener, .crudButtons"
controller="#{productLotsController}"
btnDetailDeleteLabel="#{productByLotsController.inNewMode or productLotsController.inNewMode ? productsBundle.deleteLabel : productsBundle.inactivateLabel}"
withDelete="#{productLotsController.showEntity and productLotsController.deletableEntity}"
disabled="#{not (productByLotsController.byLots and not productsController.inViewMode)}">
<p:column headerText="#{productsBundle.productLotsBarcode}">
<h:outputText
value="#{detailEntity.barcode}"
converter="barcodeConverter" />
</p:column>
<p:column headerText="#{productsBundle.productLotsNumber}">
<h:outputText value="#{detailEntity.number}" />
</p:column>
<p:column
headerText="#{productsBundle.productLotsExpiration}"
rendered="#{productByLotsController.perishable}">
<h:outputText value="#{detailEntity.expirationData.expiration}">
<f:convertDateTime
type="both"
locale="#{currentUserConfigController.locale}" />
</h:outputText>
</p:column>
<p:column
headerText="#{productsBundle.fieldLabelStockAvailable}"
rendered="#{productsController.hasStockByLocation()}">
<h:outputText value="#{productLotsController.getStockByLocation(detailEntity).totalizedStockData.availableAmount}" />
</p:column>
<p:column
headerText="#{productsBundle.fieldLabelStockReserved}"
rendered="#{productsController.hasStockByLocation()}">
<h:outputText value="#{productLotsController.getStockByLocation(detailEntity).totalizedStockData.reservedAmount}" />
</p:column>
//THIS BUTTON ONLY SELECT THE FIRST ROW OF MY TABLE, IT'S BROKEN.
<p:column
width="35"
rendered="#{productByLotsController.byLots}">
<p:commandButton
id="btnPrintPaperLot"
title="#{productsBundle.btnPrintBarCode}"
process="#this"
actionListener="#{printerSelectorController.onPrint()}"
icon="fa fa-print">
<f:setPropertyActionListener
value="PAPER"
target="#{printerSelectorController.type}" />
<f:setPropertyActionListener
value="#{productLotsController.getTypePrintedProductLot(detailEntity)}"
target="#{printerSelectorController.value}" />
</p:commandButton>
</p:column>
As you can see, I have a custom component that I created called sgv:detailCrud. He is responsible for owning the CRUD architecture of my services, being able to delete, create or view details of a line. (VIDEO DETAILS)
Details CRUD Component
<!DOCTYPE html>
<html
xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:composite="http://xmlns.jcp.org/jsf/composite"
xmlns:f="http://xmlns.jcp.org/jsf/core"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
xmlns:c="http://java.sun.com/jsp/jstl/core"
xmlns:p="http://primefaces.org/ui"
xmlns:of="http://omnifaces.org/functions">
<head>
<title>Details CRUD Component</title>
</head>
<body>
<composite:interface>
<composite:attribute name="style" />
<composite:attribute name="styleClass" />
<composite:attribute name="listStyle" />
<composite:attribute name="listStyleClass" />
<composite:attribute name="datailStyle" />
<composite:attribute name="datailStyleClass" />
<composite:attribute
name="controller"
required="true"
type="eprecise.sgv.server.core.CrudController" />
<composite:attribute
name="disabled"
default="false" />
<composite:attribute
name="withInsert"
default="true" />
<composite:attribute
name="scrollable"
default="false" />
<composite:attribute
name="scrollHeight"
default="400" />
<composite:attribute
name="withDelete"
default="true" />
<composite:attribute
name="withUpdate"
default="true" />
<composite:attribute
name="emptyMessage"
default="#{detailCrudBundle.emptyMessage}" />
<composite:attribute
name="btnNewLabel"
default="#{detailCrudBundle.btnNewLabel}" />
<composite:attribute
name="btnDetailDeleteLabel"
default="#{detailCrudBundle.detailBtnDelete}" />
<composite:attribute
name="btnDetailSaveLabel"
default="#{detailCrudBundle.detailBtnSave}" />
<!-- Parametro de classe Css listener de update -->
<composite:attribute
name="updateClass"
default="#{cc.id}ChangeListener" />
<composite:facet name="list" />
<composite:facet name="details" />
</composite:interface>
<composite:implementation>
<p:outputPanel
id="#{cc.id}"
styleClass="#{cc.id}ChangeListener">
<p:panel
styleClass="Wrapper"
rendered="#{not cc.attrs.controller.showEntity}">
<f:facet name="header">
<composite:renderFacet name="title" />
<p:commandLink
id="btnNew"
styleClass="Fright"
update="#(.#{cc.attrs.updateClass},.#{cc.id}ChangeListener)"
process="#this"
icon="fa fa-plus-circle"
disabled="#{cc.attrs.disabled}"
rendered="#{cc.attrs.withInsert}"
action="#{cc.attrs.controller.onBtnNovoClick()}">
<i class="fa fa-plus-circle Fs23 White"></i>
</p:commandLink>
<p:tooltip
for="btnNew"
showDelay="500"
value="#{cc.attrs.btnNewLabel}" />
<div class="clearfix"></div>
</f:facet>
<p:dataTable
id="list"
selectionMode="single"
scrollable="#{cc.attrs.scrollable}"
scrollHeight="#{cc.attrs.scrollHeight}"
selection="#{cc.attrs.controller.entity}"
emptyMessage="#{cc.attrs.emptyMessage}"
value="#{cc.attrs.controller.dataModel}"
reflow="true"
var="detailEntity"
rowKey="#{detailEntity.id}">
<f:attribute
name="styleClass"
value="#{cc.attrs.listStyleClass} #{cc.attrs.styleClass}" />
<p:ajax
event="rowSelect"
listener="#{cc.attrs.controller.onEntityClick()}"
process="#this"
update="#(.#{cc.attrs.updateClass}, .#{cc.id}ChangeListener, .formConfirmDialog)"
onstart="crudController.blockTable();"
onsuccess="crudController.unblockTable();" />
<composite:insertChildren />
</p:dataTable>
</p:panel>
<p:panel
id="detailCrudPanel"
styleClass="disableFluidForButtons detailForm"
rendered="#{cc.attrs.controller.showEntity}">
<f:attribute
name="styleClass"
value="#{cc.attrs.datailsStyleClass} #{cc.attrs.styleClass} detailForm" />
<f:facet name="header">
<p:commandButton
id="btnSaveDetail_#{cc.id}"
styleClass="saveBtn RaisedButton Fright WidAuto"
update="#(.#{cc.attrs.updateClass}, .#{cc.id}ChangeListener)"
process="#(.detailForm)"
value="#{cc.attrs.btnDetailSaveLabel}"
icon="fa fa-save"
rendered="#{cc.attrs.withUpdate and not cc.attrs.disabled}"
action="#{cc.attrs.controller.onBtnSalvarClick()}" />
<p:commandButton
action="#{cc.attrs.controller.onBtnDeleteClick()}"
icon="fa fa-trash-o"
styleClass="deleteBtn Fright WidAuto"
value="#{cc.attrs.btnDetailDeleteLabel}"
disabled="#{cc.attrs.disabled}"
process="#this"
rendered="#{cc.attrs.withDelete and (cc.attrs.controller.inEditMode or cc.attrs.controller.inViewMode)}"
update="#(.#{cc.attrs.updateClass}, .#{cc.id}ChangeListener)">
</p:commandButton>
<p:commandButton
update="#(.#{cc.attrs.updateClass}, .#{cc.id}ChangeListener)"
process="#this"
styleClass="cancelBtn Fright WidAuto"
icon="fa fa-ban"
value="#{detailCrudBundle.detailBtnCancel}"
rendered="#{not cc.attrs.disabled}"
action="#{cc.attrs.controller.onBtnCancelarClick()}" />
<p:commandButton
update="#(.#{cc.attrs.updateClass}, .#{cc.id}ChangeListener)"
process="#this"
styleClass="RedButton RaisedButton"
icon="fa fa-arrow-left WidAuto"
value="#{detailCrudBundle.detailBtnBack}"
rendered="#{cc.attrs.disabled}"
action="#{cc.attrs.controller.onBtnCancelarClick()}" />
<p:defaultCommand
target="btnSaveDetail_#{cc.id}"
scope="detailCrudPanel" />
</f:facet>
<composite:renderFacet name="details" />
</p:panel>
</p:outputPanel>
</composite:implementation>
</body>
</html>
Another detail is that I can do these crud operations on the rows of the table without any problem, that is, hitting the index. However, I have this problem of always selecting the first row when I click my button to print a lot variation, it's like it doesn't obey the crud architecture for some bizarre reason...
I believe this problem is only for the view, but for the sake of conscience I will post the code of the two controllers of this button here too, follow the thread.
Method that should take the selected entity in the table and assemble it from the String Builder
public String getTypePrintedProductLot(ProductLot selectedLot) {
if (selectedLot != null && selectedLot instanceof ProductLot) {
final Optional<ProductLabelSetting> optional = this.productLabelSettingsRepository.listActives().stream().findFirst();
if (optional.isPresent()) {
final StringBuilder builder = new StringBuilder();
final SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy");
final HashMap<String, Object> params = new HashMap<String, Object>();
final Logger log = LoggerFactory.getLogger(ProductLotsController.class);
log.warn("********** SELECTED LOT NUMBER IN TABLE **********\n");
log.warn(selectedLot.getNumber());
log.warn("********** SELECTED LOT NUMBER IN TABLE **********\n");
params.put("product", selectedLot);
final ProductLot lot = selectedLot;
params.put("lot", lot);
if (lot.getExpirationData() instanceof PerishableExpirationData) {
params.put("lotExpiration", sdf.format(lot.getExpirationData().getExpiration()));
} else {
params.put("lotExpiration", "Não Perecível");
}
params.put("login", this.currentUser.getLogin().toUpperCase());
params.put("enterprise", this.currentEnterprise.getName().toUpperCase());
builder.append(optional.get().parser(params));
return builder.toString();
}
}
return null;
}
Controller that receives the values from the String Builder and prints the variation according to this information
package eprecise.sgv.server.core.printers;
import java.io.Serializable;
import java.util.Optional;
import javax.inject.Inject;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import eprecise.sgv.server.products.ProductLotsController;
import org.apache.commons.lang3.StringUtils;
import eprecise.sgv.server.core.ViewController;
import eprecise.sgv.server.core.auth.Current;
import eprecise.sgv.server.core.util.FacesMessageUtil;
import eprecise.sgv.server.devicesHub.remoteHw.RemoteHwChannel;
import eprecise.sgv.server.devicesHub.remoteHw.printers.RemoteHwPrinter;
import eprecise.sgv.server.devicesHub.remoteHw.printers.RemoteHwPrintersRepository;
import eprecise.sgv.server.users.User;
import org.primefaces.PrimeFaces;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
#ViewController
public class PrinterSelectorController implements Serializable {
private enum PrintType {
COMMON,
PAPER,
COUPON
}
private static final long serialVersionUID = 1L;
private final RemoteHwPrintersRepository repository;
private final User user;
private final RemoteHwChannel remoteHwChannel;
private String id;
private String value;
private String type;
private #NotNull #Min(1) int amount = 1;
private Boolean withAmount = false;
private RemoteHwPrinter printer;
public PrinterSelectorController() {
this.repository = null;
this.user = null;
this.remoteHwChannel = null;
}
#Inject
public PrinterSelectorController(RemoteHwPrintersRepository repository, #Current User user, RemoteHwChannel remoteHwChannel) {
this.repository = repository;
this.user = user;
this.remoteHwChannel = remoteHwChannel;
}
public void onPrint() {
if (!this.withAmount) {
switch (PrintType.valueOf(this.type.toUpperCase())) {
case COMMON:
break;
case PAPER:
if (this.user.hasDefaultPrinterPaper()) {
if (this.print(this.user.getConfigurations().getDefaultPrinters().getPaper())) {
FacesMessageUtil.addInfoMessage("Etiqueta impressa com sucesso.");
break;
}
} else {
this.showDialog();
}
break;
case COUPON:
break;
default:
this.showDialog();
break;
}
} else {
this.showDialog();
}
}
public void onPrint(RemoteHwPrinter printer) {
if (printer != null) {
switch (PrintType.valueOf(this.type.toUpperCase())) {
case COMMON:
break;
case PAPER:
if (this.print(printer)) {
FacesMessageUtil.addInfoMessage("Etiqueta impressa com sucesso.");
}
break;
case COUPON:
break;
}
} else {
FacesMessageUtil.addErrorMessage("Nenhuma impressora selecionada.");
}
}
public void onPrintSelect() {
this.onPrint(this.printer);
}
private boolean print(DefaultPrinter defaultPrinter) {
final Optional<RemoteHwPrinter> optional = this.repository.findById(defaultPrinter.getId());
if (optional.isPresent()) {
return this.print(optional.get());
}
this.showDialog();
return false;
}
private boolean print(RemoteHwPrinter printer) {
if (StringUtils.isNotBlank(this.value)) {
final StringBuilder builder = new StringBuilder();
for (int i = 1; i <= this.amount; i++) {
builder.append(this.value);
}
this.remoteHwChannel.sendPrint(printer, builder.toString());
this.clearFields();
return true;
}
FacesMessageUtil.addErrorMessage("Modelo a ser impresso está vazio.");
return false;
}
private void clearFields() {
this.amount = 1;
this.printer = null;
this.id = null;
this.value = null;
this.type = null;
}
private void showDialog() {
final Logger log = LoggerFactory.getLogger(PrinterSelectorController.class);
log.warn("********* VALUE OF SELECTED LOT *********\n");
log.warn(this.value);
log.warn("********* VALUE OF SELECTED LOT *********\n");
PrimeFaces.current().executeScript(
new StringBuilder("PF('").append(this.id).append("_").append("listPrintersDialogJs").append("').show();").toString());
}
public String getId() {
return this.id;
}
public void setId(String id) {
this.id = id;
}
public String getValue() {
return this.value;
}
public void setValue(String value) {
this.value = value;
}
public String getType() {
return this.type;
}
public void setType(String type) {
this.type = type;
}
public int getAmount() {
return this.amount;
}
public void setAmount(int amount) {
this.amount = amount;
}
public RemoteHwPrinter getPrinter() {
if (StringUtils.isEmpty(this.type)) {
return null;
}
switch (PrintType.valueOf(this.type.toUpperCase())) {
case COMMON:
break;
case PAPER:
if (this.user.hasDefaultPrinterPaper()) {
final Optional<RemoteHwPrinter> optional = this.repository
.findById(this.user.getConfigurations().getDefaultPrinters().getPaper().getId());
if (optional.isPresent()) {
this.printer = optional.get();
}
}
break;
case COUPON:
break;
}
return this.printer;
}
public void setPrinter(RemoteHwPrinter printer) {
this.printer = printer;
}
public Boolean getWithAmount() {
return this.withAmount;
}
public void setWithAmount(Boolean withAmount) {
this.withAmount = withAmount;
}
}
Details
I'm using Java 8 with primefaces in version 7
Some images
Video
https://youtu.be/kw_xhQxrV-M
I do not why although I verify everything which is right. Anyone know?
I tried to debug:
- getAsString() return ID (that's right)
- when I submitted - getAsObject() always throw exception because the label values was always passed instead of ID values.
My code as below:
my xhtml file:
<div class="ui-grid-row" style="margin-top: 5px; margin-bottom: 5px;">
<div class="ui-grid-col-2 pdt4">
<p:outputLabel for="txtApprovalScheduler"
value="#{lang['workforce.category.parttimeManagement.approved.scheduler']}"/>
</div>
<div class="ui-grid-col-2">
<p:selectOneMenu value="#{parttimeController.selectedAgentDTO}"
id="txtApprovalScheduler"
filterMatchMode="contains" editable="true"
style="width: 86%;"
required="true"
requiredMessage="#{lang['workforce.category.parttimeManagement.approved.scheduler.missing']}">
<f:converter converterId="agentConverter"/>
<f:selectItem itemValue="" itemLabel="#{lang['wf.common.choose']}"/>
<f:selectItems value="#{parttimeController.agentDTOs}" var="agentItem1"
itemLabel="#{agentItem1.userName}"
itemValue="#{agentItem1}"/>
</p:selectOneMenu>
</div>
<div class="ui-grid-col-2 pdt4">
<p:outputLabel for="txtApprovalRegister"
value="#{lang['workforce.category.parttimeManagement.approved.register']}"/>
</div>
<div class="ui-grid-col-2">
<p:selectOneMenu value="#{parttimeController.selectedAgentDTOForRegister}"
id="txtApprovalRegister" editable="true"
filterMatchMode="contains" style="width: 86%; font-size: 12px !important;"
required="true"
requiredMessage="#{lang['workforce.category.parttimeManagement.approved.register.missing']}">
<f:converter converterId="agentConverter"/>
<f:selectItem itemValue="" itemLabel="#{lang['wf.common.choose']}"/>
<f:selectItems value="#{parttimeController.agentDTOs}" var="item1"
itemLabel="#{item1.userName}"
itemValue="#{item1}"/>
</p:selectOneMenu>
</div>
</div>
My Converter:
#FacesConverter(forClass = AgentDTO.class,value = "agentConverter")
public class AgentConverter implements Converter {
public static List<AgentDTO> listAgentDTOs;
#Override
public Object getAsObject(FacesContext facesContext, UIComponent uiComponent, String agentId) {
if (agentId.trim().equals("")) {
return null;
} else {
try {
Long number = Long.parseLong(agentId);
for (AgentDTO a : listAgentDTOs) {
if (a.getAgentId() == number) {
return a;
}
}
} catch (NumberFormatException exception) {
throw new ConverterException(new FacesMessage(FacesMessage.SEVERITY_ERROR, "Conversion Error", "Not a valid data"));
}
}
return null;
}
#Override
public String getAsString(FacesContext facesContext, UIComponent uiComponent, Object value) {
if (value == null || value.equals("") || "-1".equals(value)) {
return "";
} else {
String result = String.valueOf(((AgentDTO) value).getAgentId());
return result;
}
}
}
My bean:
#Component
#Scope("view")
#ManagedBean(name = "parttimeController")
public class ParttimeController extends BaseController implements Serializable {
private AgentDTO selectedAgentDTO;
private AgentDTO selectedAgentDTOForRegister;
private List<AgentDTO> agentDTOs;
.... getter/setter and initiallize AgentCenvert.listAgentDTOs
My class:
public class AgentDTO{
private Long agentId;
private String userName;
....getter/setter
}
I found out the reason - I only remove editable attribute in selectOneMenu. So getAsObject() will pass a value instead of a label
i am pretty new to jsf and i'm having a bit trouble implementing a subTable in my dataTable. Der var atribute from my subtable doesn't seem to evaluate to the elements from the list. Also the #PostConstruct method from my backing bean isn't called either, when i execute my webapp and navigate to the xhtml site. No errors are shown in the console, so i have pretty much no idea what i did wrong.
Backing Bean
#Named(value = "selfEvalBean")
#ViewScoped
public class SelfEvaluationBean extends AbstractBean implements Serializable {
private static final long serialVersionUID = 310401011219411386L;
private static final Logger logger = Logger.getLogger(SelfEvaluationBean.class);
#Inject
private ISelfEvaluationManager manager;
private List<SelfEvaluation> selfEvaluations;
private SelfEvaluationTopic topic;
public List<SelfEvaluation> getSelfEvaluations() {
return selfEvaluations;
}
public void setSelfEvaluation(final List<SelfEvaluation> theSelfEvaluations) {
selfEvaluations = theSelfEvaluations;
}
#PostConstruct
public void init() {
if (!isLoggedIn()) {
return;
}
final User user = getSession().getUser();
List<SelfEvaluation> eval = user.getSelfEvaluations();
if (eval == null) {
eval = manager.createSelfEvaluation(user);
}
selfEvaluations = eval;
topic = new SelfEvaluationTopic();
}
//some methods
/**
* #return the topic
*/
public SelfEvaluationTopic getTopic() {
return topic;
}
/**
* #param theTopic
*/
public void setTopic(final SelfEvaluationTopic theTopic) {
topic = theTopic;
}
}
SelEvaluation Class
#Entity
public class SelfEvaluation extends JPAEntity implements Serializable {
private static final long serialVersionUID = 1L;
#ManyToOne
private User user;
#Column
private String title;
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
private List<SelfEvaluationTopic> topics = new ArrayList<>();
public User getUser() {
return user;
}
public void setUser(final User theUser) {
user = theUser;
public List<SelfEvaluationTopic> getTopics() {
return topics;
}
public void setTopics(final List<SelfEvaluationTopic> theTopics) {
topics = theTopics;
}
public void addSelfEvalTopic(final SelfEvaluationTopic theTopic) {
topics.add(theTopic);
}
public void removeSelfEvalTopic(final SelfEvaluationTopic theTopic) {
topics.remove(theTopic);
}
#Override
public boolean equals(final Object theObject) {
if (!(theObject instanceof SelfEvaluation)) {
return false;
}
final SelfEvaluation other = (SelfEvaluation) theObject;
return getId().equals(other.getId());
}
public String getTitle() {
return title;
}
public void setTitle(final String title) {
this.title = title;
}
#Override
public int hashCode() {
return getId().hashCode();
}
#Override
public String toString() {
return String.format("SelfEvaluation {id: %d, from: %s}", getId(),
user.getUsername());
}
}
SelfEvaluationTopic Class
#Entity
public class SelfEvaluationTopic extends JPAEntity implements Serializable {
private static final long serialVersionUID = 1L;
#Column(nullable = false)
private String topic;
#Column
private boolean sad;
#Column
private boolean normal;
#Column
private boolean happy;
public String getTopic() {
return topic;
}
public void setTopic(final String theTopic) {
topic = assertNotNull(theTopic);
}
public boolean getSad() {
return sad;
}
public void setSad(final boolean evaluation) {
sad = evaluation;
}
public boolean getNormal() {
return normal;
}
public void setNormal(final boolean evaluation) {
normal = evaluation;
}
public boolean getHappy() {
return happy;
}
public void setHappy(final boolean evaluation) {
happy = evaluation;
}
#Override
public boolean equals(final Object theObject) {
if (!(theObject instanceof SelfEvaluationTopic)) {
return false;
}
final SelfEvaluationTopic other = (SelfEvaluationTopic) theObject;
return getId().equals(other.getId());
}
#Override
public int hashCode() {
return getId().hashCode();
}
#Override
public String toString() {
return String
.format("SelfEvaluationTopic {id: %d, topic: %s, sad: %b, normal: %b, happy: %b}",
getId(), topic, sad, normal, happy);
}
}
XHTML Site
<?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"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
xmlns:p="http://primefaces.org/ui">
<ui:composition template="templates/template.xhtml">
<!--header and footer template-->
<ui:define name="content">
<f:loadBundle basename="internationalization.selfEval" var="msg" />
<h:form id="form">
<p:growl id="info" autoUpdate="true" />
<p:dataTable id="selfEval" var="eval" value="#{selfEvalBean.selfEvaluations}" >
<f:facet name="header">
#{msg['header']}
</f:facet>
<p:columnGroup type="header">
<p:row>
<p:column>
<f:facet name="header">#{msg['selfEval']}</f:facet>
</p:column>
<p:column width="2%">
<f:facet id="sad" name="header">
<p:graphicImage library="images" name="sad.png"/>
</f:facet>
</p:column>
<p:column width="2%">
<f:facet id="sad" name="header">
<p:graphicImage library="images" name="normal.png"/>
</f:facet>
</p:column>
<p:column width="2%">
<f:facet id="sad" name="header">
<p:graphicImage library="images" name="happy.png"/>
</f:facet>
</p:column>
</p:row>
</p:columnGroup>
<p:subTable var="t" value="#{eval.topics}">
<f:facet name="header">
<h:outputText value="#{eval.title}" />
</f:facet>
<p:column id="topic" >
<h:outputText value="#{t}" /> <!--t is of type List<SelfEvaluation> and not SelfEvaluationTopic-->
<p:commandButton style="float:right; width:22px; height: 22px; background-color: #cd001e;" title="Delete" update=":form" action="#{selfEvalBean.remove(t)}" icon="fa fa-trash-o" />
</p:column>
<p:column width="2%" >
<div style="text-align: center;" >
<p:selectBooleanCheckbox id="s" value="#{t}" />
</div>
</p:column>
<p:column width="2%" >
<div style="text-align: center;" >
<p:selectBooleanCheckbox id="n" value="#{t}" />
</div>
</p:column>
<p:column width="2%" >
<div style="text-align: center;" >
<p:selectBooleanCheckbox id="h" value="#{t}" />
</div>
</p:column>
</p:subTable>
</p:dataTable>
<center>
<p:commandButton id="addSelfEvalTopic" styleClass="button" value="#{msg['actionAdd']}" onclick="PF('evalDialog').show();" update=":form" />
<p:commandButton id="selection" styleClass="button" style="float:right;" value="#{msg['actionSelect']}" action="#{selfEvalBean.save}" />
</center>
</h:form>
<p:dialog widgetVar="evalDialog" header="#{msg['newTopic']}" showEffect="clip" hideEffect="clip" resizable="false">
<h:form id="dialog">
<h:panelGrid columns="2">
<p:outputLabel value="Description:" />
<p:inputText value="#{selfEvalBean.topic.topic}" required="true" maxlength="60" />
<p:commandButton value="#{msg['actionSave']}" styleClass="button" action="#{selfEvalBean.addTopic}" update=":form" oncomplete="PF('evalDialog').hide();" />
<p:commandButton value="#{msg['actionCancel']}" styleClass="button" immediate="true" oncomplete="PF('evalDialog').hide();" />
</h:panelGrid>
</h:form>
</p:dialog>
</ui:define>
</ui:composition>
Manager Class fills the Database with some initial data, so there is nothing really interesting going on.
JSF version is 2.2.12 and PrimeFaces version is 6.0.
I'm using Maven for build and the webapp is running on GlassFish 4.1.1.
I have a class Document for create a Tree with a list of document objects.
public class Document implements Serializable {
private String name;
private String size;
private List<Field> fields;
public Document(String name, String size, String type) {
this.name = name;
this.size = size;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSize() {
return size;
}
public void setSize(String size) {
this.size = size;
}
public List<Field> getFields() {
return fields;
}
public void setFields(List<Field> fields) {
this.fields = fields;
}
}
Also I have a Field class for store relevant information in documents
public class Field implements Serializable {
private int fieldIndex;
private String label;
private String value;
private List<Values> list;
public Field() {
}
public int getFieldIndex() {
return fieldIndex;
}
public void setFieldIndex(int fieldIndex) {
this.fieldIndex = fieldIndex;
}
public Field(String label) {
this.label = label;
}
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public List<Values> getList() {
return list;
}
public void setList(List<Values> list) {
this.list = list;
}
}
My ManagedBean create a tree with some documents and store some data for each document. When i select the tree node, it's show a dynamic form with every field and inputs for enter some value.
#ManagedBean(name="treeSelectionView")
#ViewScoped
public class SelectionView implements Serializable {
private TreeNode root1;
private TreeNode selectedNode;
private String email;
private List<Field> fields;
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
#PostConstruct
public void init() {
TreeNode root = new DefaultTreeNode(new Document("Files", "-", "Folder"), null);
TreeNode documents = new DefaultTreeNode(new Document("Documents", "-", "Folder"), root);
Field f1=new Field();
Field f2=new Field();
Field f3=new Field();
f1.setLabel("email");
f1.setValue("");
f2.setLabel("doc");
f2.setValue("");
f3.setLabel("otro");
f3.setValue("");
List<Field> fields=new ArrayList<Field>();
fields.add(f1);
fields.add(f2);
fields.add(f3);
List<Field> fields1=new ArrayList<Field>();
f1=new Field();
f2=new Field();
f3=new Field();
f1.setLabel("email");
f1.setValue("");
f2.setLabel("doc");
f2.setValue("");
f3.setLabel("otro");
f3.setValue("");
fields1.add(f1);
fields1.add(f2);
fields1.add(f3);
List<Field> fields2=new ArrayList<Field>();
f1=new Field();
f2=new Field();
f3=new Field();
f1.setLabel("email");
f1.setValue("");
f2.setLabel("doc");
f2.setValue("");
f3.setLabel("otro");
f3.setValue("");
fields2.add(f1);
fields2.add(f2);
fields2.add(f3);
//Documents
Document d1= new Document("Expenses.doc", "30 KB", "Word Document");
Document d2=new Document("Resume.doc", "10 KB", "Word Document");
Document d3=new Document("RefDoc.pages", "40 KB", "Pages Document");
d1.setFields(fields);
d2.setFields(fields1);
d3.setFields(fields2);
TreeNode expenses = new DefaultTreeNode("document",d1, documents);
TreeNode resume = new DefaultTreeNode("document", d2, documents);
TreeNode refdoc = new DefaultTreeNode("document",d3 , documents);
documents.setExpanded(true);
root1 = root;
root1.setExpanded(true);
}
public void onNodeDocumentSelect(NodeSelectEvent nodeSelected) {
// fields=((Document)nodeSelected.getTreeNode().getData()).getFields();
fields=((Document)selectedNode.getData()).getFields();
}
public TreeNode getRoot1() {
return root1;
}
public TreeNode getSelectedNode() {
return selectedNode;
}
public void setSelectedNode(TreeNode selectedNode) {
this.selectedNode = selectedNode;
}
public List<Field> getFields() {
return fields;
}
public void setFields(List<Field> fields) {
this.fields = fields;
}
}
My JSF looks like
<!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>
<title>Default title</title>
</h:head>
<h:body>
<h:panelGrid columns="2">
<h:form id="treeForm">
<p:panel id="panel22" header="Documents" style="height:400px">
<p:growl id="msgs" showDetail="true" />
<p:tree value="#{treeSelectionView.root1}" var="doc" selectionMode="single" selection="#{treeSelectionView.selectedNode}" >
<p:ajax process="#this" event="select" update=":myForm:dymanicForm" listener="#{treeSelectionView.onNodeDocumentSelect}" />
<p:treeNode expandedIcon="ui-icon-folder-open" collapsedIcon="ui-icon-folder-collapsed">
<h:outputText value="#{doc.name}"/>
</p:treeNode>
<p:treeNode type="document" icon="ui-icon-document" >
<h:outputText value="#{doc.name}" />
</p:treeNode>
</p:tree>
</p:panel>
</h:form>
<h:form id="myForm">
<p:panel id="panel222" header="Info Doccs" style="height:400px">
<p:panel id="dymanicForm" >
<ui:repeat value="#{treeSelectionView.fields}" var="componentMetadata">
<h:panelGrid columns="3">
<h:outputText value="#{componentMetadata.label}"/>:
<h:inputText id="field" value="#{componentMetadata.value}"
required="true" label="#{componentMetadata.label}"/>
<h:message for="field" style="color:red" /></h:panelGrid>
</ui:repeat>
</p:panel>
<h:commandButton value="Submit" action="result" />
</p:panel>
</h:form>
</h:panelGrid>
</h:body>
</html>
When I press the submit button the values are submitted and store in each field, also the validations are fired. But I really need remove the submit button and validate the form when i lost focus every node. By example if i am in the first node validate the form only when i lost focus , but if the form validation fail i need to stay in this node.
Really I appreciate any help you can give me.
Thanks a lot in advance.
I wanted to realize a similar view, and it took me quite some time to figure out.
The trick is not declaring the selection attribute on <p:tree, but just the selectionMode and manage the selection only with the listener, manually.
This causes the selection to be swapped in INVOKE_APPLICATION phase instead of UPDATE_MODEL_VALUES, preserving the right pane references in value expressions.
Furthermore, validation happens before selection swapping and, if fails, the selection swap is prevented.
I think this is exactly what you are looking for:
<h:form>
<p:layout fullPage="false" stateful="false" style="height:400px">
<p:layoutUnit position="center">
<p:tree id="tree" var="data" nodeVar="node" value="#{testTreeBean.root}"
selectionMode="single" dynamic="true" animate="true" highlight="true"
style="border: 0">
<p:ajax event="select" listener="#{testTreeBean.onSelect}" process="#form"
update="#this #form:details" />
<p:ajax event="expand" process="#this" />
<p:ajax event="collapse" process="#this" />
<p:treeNode expandedIcon="ui-icon-folder-open" collapsedIcon="ui-icon-folder-collapsed">
<p:outputPanel id="node">
<h:outputText value="#{data.someText1} - #{data.someText2}" />
</p:outputPanel>
</p:treeNode>
</p:tree>
</p:layoutUnit>
<p:layoutUnit position="east" size="65%" minSize="150">
<p:outputPanel id="details" style="padding: 1em">
<p:panelGrid columns="3" rendered="#{testTreeBean.data != null}">
<p:outputLabel value="someLabel1" for="#next" />
<p:inputText value="#{testTreeBean.data.someText1}" required="true" />
<p:message for="#previous" />
<p:outputLabel value="someLabel2" for="#next" />
<p:inputText value="#{testTreeBean.data.someText2}" required="true" />
<p:message for="#previous" />
</p:panelGrid>
<h:outputText value="please select a node in the left pane"
rendered="#{testTreeBean.data == null}" />
</p:outputPanel>
</p:layoutUnit>
</p:layout>
</h:form>
the bean:
#javax.faces.bean.ManagedBean
#javax.faces.bean.ViewScoped
public class TestTreeBean implements Serializable
{
private static final long serialVersionUID = 1L;
private TreeNode root;
private TreeNode selected;
// build a dummy tree...
#PostConstruct
public void init()
{
root = new DefaultTreeNode();
for(int i = 0; i < 5; i++)
{
SomeData data = new SomeData("node " + i, String.valueOf(System.currentTimeMillis()));
TreeNode node = new DefaultTreeNode(data, root);
for(int j = 0; j < 5; j++)
{
SomeData subData = new SomeData("subNode " + i + "." + j, String.valueOf(System.currentTimeMillis()));
#SuppressWarnings("unused")
TreeNode subNode = new DefaultTreeNode(subData, node);
}
}
}
// handle selection swap manually
public void onSelect(NodeSelectEvent event)
{
if(selected != null)
{
selected.setSelected(false);
}
selected = event.getTreeNode();
if(selected != null)
{
selected.setSelected(true);
}
}
// shortcut for getting the selected node data
public Object getData()
{
return selected == null ? null : selected.getData();
}
public TreeNode getSelected()
{
return selected;
}
public TreeNode getRoot()
{
return root;
}
}
finally, for completeness, the dummy data class:
public class SomeData implements Serializable
{
private String someText1;
private String someText2;
public SomeData()
{
super();
}
public SomeData(String someText1, String someText2)
{
super();
this.someText1 = someText1;
this.someText2 = someText2;
}
public String getSomeText1()
{
return someText1;
}
public void setSomeText1(String someText1)
{
this.someText1 = someText1;
}
public String getSomeText2()
{
return someText2;
}
public void setSomeText2(String someText2)
{
this.someText2 = someText2;
}
}
Happy gardening :)
I have the following code in my facelet page:
<hc:rangeChooser1 id="range_chooser"
from="#{testBean.from}"
to="#{testBean.to}"
listener="#{testBean.update}"
text="#{testBean.text}">
<f:ajax event="rangeSelected"
execute="#this"
listener="#{testBean.update}"
render=":form:growl range_chooser"/>
</hc:rangeChooser1>
This is my composite component:
<ui:component 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:cc="http://java.sun.com/jsf/composite"
xmlns:p="http://primefaces.org/ui">
<cc:interface componentType="rangeChooser">
<!-- Define component attributes here -->
<cc:clientBehavior name="rangeSelected" event="change" targets="hiddenValue"/>
<cc:attribute name="from" type="java.util.Calendar"/>
<cc:attribute name="to" type="java.util.Calendar"/>
<cc:attribute name="text" type="java.lang.String"/>
</cc:interface>
<cc:implementation>
<div id="#{cc.clientId}">
...
<p:inputText id="hiddenValue" value="#{cc.attrs.text}"/>
...
</div>
</cc:implementation>
</ui:component>
How do I pass attributes from, to and text from composite component to backing bean? I mean inject these values in backing component, and not through
<p:inputText id="hiddenValue" value="#{cc.attrs.text}"/>
Update: there's more correct definition what do I need: Be able to mutate objects which I pass from the backing bean to the composite component inside a backing component of the composite component. So when I perform process or execute my composite component I get the updated values.
This is my backing component:
#FacesComponent("rangeChooser")
public class RangeChooser extends UIInput implements NamingContainer {
private String text;
private Calendar from;
private Calendar to;
#Override
public void encodeBegin(FacesContext context) throws IOException{
super.encodeBegin(context);
}
public String getText() {
String text = (String)getStateHelper().get(PropertyKeys.text);
return text;
}
public void setText(String text) {
getStateHelper().put(PropertyKeys.text, text);
}
/*
same getters and setters for Calendar objects, from and to
*/
}
I just can't realize how do I move on? In general I need to take a value from <p:inputText id="hiddenValue" value="#{cc.attrs.text}"/> and convert it to two Calendars object from and to.
It will be great if somebody can point me toward right direction from here. I know that I need to use getAttributes().put(key,value) but don't know where to put this code. Thank you in advance.
Based on your comments this is what you expect.
Note that even if this implementation works, it is conceptually incorrect!
You are considering from and to as INPUTS (not input components, but input values) and text as OUTPUT. This is not the way JSF is intended to work!
However, here it is
<cc:interface componentType="rangeComponent">
<cc:attribute name="from" />
<cc:attribute name="to" />
<cc:clientBehavior name="rangeSelected" event="dateSelect" targets="from to"/>
</cc:interface>
<cc:implementation>
<div id="#{cc.clientId}">
<p:calendar id="from" value="#{cc.attrs.from}"/>
<p:calendar id="to" value="#{cc.attrs.to}"/>
</div>
</cc:implementation>
used in page:
<h:form>
<e:inputRange from="#{rangeBean.from}" to="#{rangeBean.to}" text="#{rangeBean.text}">
<p:ajax event="rangeSelected" process="#namingcontainer" update="#form:output" listener="#{rangeBean.onChange}" />
</e:inputRange>
<h:panelGrid id="output" columns="1">
<h:outputText value="#{rangeBean.from}"/>
<h:outputText value="#{rangeBean.to}"/>
<h:outputText value="#{rangeBean.text}"/>
</h:panelGrid>
</h:form>
with this backing component:
#FacesComponent("rangeComponent")
public class RangeComponent extends UINamingContainer
{
#Override
public void processUpdates(FacesContext context)
{
Objects.requireNonNull(context);
if(!isRendered())
{
return;
}
super.processUpdates(context);
try
{
Date from = (Date) getValueExpression("from").getValue(context.getELContext());
Date to = (Date) getValueExpression("to").getValue(context.getELContext());
ValueExpression ve = getValueExpression("text");
if(ve != null)
{
ve.setValue(context.getELContext(), from + " - " + to);
}
}
catch(RuntimeException e)
{
context.renderResponse();
throw e;
}
}
}
with this backing bean:
#ManagedBean
#ViewScoped
public class RangeBean implements Serializable
{
private static final long serialVersionUID = 1L;
private Date from = new Date(1000000000);
private Date to = new Date(2000000000);
private String text = "range not set";
public void onChange(SelectEvent event)
{
Messages.addGlobalInfo("[{0}] changed: [{1}]", event.getComponent().getId(), event.getObject());
}
// getters/setters
}
I rewrote the code using BalusC tecnique (and without PrimeFaces):
the form:
<h:form>
<e:inputRange value="#{rangeBean.range}">
<p:ajax event="change" process="#namingcontainer" update="#form:output"
listener="#{rangeBean.onChange}" />
</e:inputRange>
<h:panelGrid id="output" columns="1">
<h:outputText value="#{rangeBean.range}" />
</h:panelGrid>
</h:form>
the composite:
<cc:interface componentType="rangeComponent">
<cc:attribute name="value" />
<cc:clientBehavior name="change" event="change" targets="from to"/>
</cc:interface>
<cc:implementation>
<div id="#{cc.clientId}">
<h:inputText id="from" binding="#{cc.from}">
<f:convertDateTime type="date" pattern="dd/MM/yyyy" />
</h:inputText>
<h:inputText id="to" binding="#{cc.to}">
<f:convertDateTime type="date" pattern="dd/MM/yyyy" />
</h:inputText>
</div>
</cc:implementation>
the backing component:
#FacesComponent("rangeComponent")
public class RangeComponent extends UIInput implements NamingContainer
{
private UIInput from;
private UIInput to;
#Override
public String getFamily()
{
return UINamingContainer.COMPONENT_FAMILY;
}
#Override
public void encodeBegin(FacesContext context) throws IOException
{
String value = (String) getValue();
if(value != null)
{
String fromString = StringUtils.substringBefore(value, "-");
String toString = StringUtils.substringAfter(value, "-");
try
{
from.setValue(from.getConverter().getAsObject(context, from, fromString));
}
catch(Exception e)
{
from.setValue(new Date());
}
try
{
to.setValue(to.getConverter().getAsObject(context, to, toString));
}
catch(Exception e)
{
to.setValue(new Date());
}
}
super.encodeBegin(context);
}
#Override
public Object getSubmittedValue()
{
return (from.isLocalValueSet() ? from.getValue() : from.getSubmittedValue()) + "-" + (to.isLocalValueSet() ? to.getValue() : to.getSubmittedValue());
}
#Override
protected Object getConvertedValue(FacesContext context, Object submittedValue)
{
return from.getSubmittedValue() + "-" + to.getSubmittedValue();
}
public UIInput getFrom()
{
return from;
}
public void setFrom(UIInput from)
{
this.from = from;
}
public UIInput getTo()
{
return to;
}
public void setTo(UIInput to)
{
this.to = to;
}
}
and the managed bean:
#ManagedBean
#ViewScoped
public class RangeBean implements Serializable
{
private static final long serialVersionUID = 1L;
private String range = "01/01/2015-31/12/2015";
public void onChange(AjaxBehaviorEvent event)
{
Messages.addGlobalInfo("[{0}] changed: [{1}]", event.getComponent().getId(), event.getBehavior());
}
public String getRange()
{
return range;
}
public void setRange(String range)
{
this.range = range;
}
}
Note that managed bean only retains range property for get/set. From and to are gone, the backing component derives and rebuilds them itself.