all
I've been working on a composite component for a date range. Essentially, my composite component uses two Richfaces 4.3 calendar components underneath to capture the individual date values, generate a date range (a pair of LocalDate objects). I found this blog entry which was the basis for my custom component that combines the two submitted values on the calendar into one pair value.
Everything seems to work fine and the values are getting updated. However, I'm trying to figure out how to propagate the change event to the using xhtml page for a partial render of another component, and I've been unsuccessful. I've tried everything I could think of, but I think I'm missing something.
The page:
<rich:panel>
<f:facet name="header">Calendar Date Range Component</f:facet>
<h:outputText id="out1" binding="#{calendarDateRangeTestBean.component1}"
value="#{calendarDateRangeTestBean.dateRange}" converter="localDatePairConverter" /><br/>
<h:outputText id="out2" value="#{calendarDateRangeTestBean.dateRange}" converter="localDatePairConverter" /><b>NOT WORKING</b>
<yxp:calendarDateRange id="calendarDateRange" value="#{calendarDateRangeTestBean.dateRange}"
dataModel="#{calendarDateRangeTestBean}"
valueChangeListener="#{calendarDateRangeTestBean.processValueChange}">
<f:ajax execute="#all" listener="#{calendarDateRangeTestBean.processBehaviorEvent}"/>
<!-- This doesn't seem to work???? -->
<f:ajax execute="#all" render="out2" />
</yxp:calendarDateRange>
</rich:panel>
My test managed bean:
#ViewScoped
#ManagedBean
public class CalendarDateRangeTestBean extends AbstractCalendarDateRangeDataModel implements
ValueChangeListener, Serializable {
private static Logger logger = LoggerFactory.getLogger(CalendarDateRangeTestBean.class);
private Pair<LocalDate> dateRange = Pair.of(LocalDate.now(), LocalDate.now().plusDays(7));
private UIComponent component1;
public UIComponent getComponent1() {
return component1;
}
public LocalDateRange getDateRange() {
return dateRange;
}
public void processBehaviorEvent(final javax.faces.event.AjaxBehaviorEvent event) {
logger.info("processing event " + event + ": " + event.getBehavior());
final FacesContext context = FacesContext.getCurrentInstance();
logger.info("Setting render to " + component1.getClientId(context));
// This seems to cause a rerender of the first component
context.getPartialViewContext().getRenderIds().add(component1.getClientId(context));
}
#Override
public void processValueChange(final ValueChangeEvent event) throws AbortProcessingException {
logger.info(this.toString() + ": processing value change event " + event + ": ["
+ event.getOldValue() + ":" + event.getNewValue() + "]");
}
public void setComponent1(final UIComponent component1) {
this.component1 = component1;
}
public void setDateRange(final Pair<LocalDate> dateRange) {
logger.info("Setting date range to " + dateRange);
this.dateRange = dateRange;
}
}
My composite component:
<ui:composition 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://java.sun.com/jsf/facelets"
xmlns:a4j="http://richfaces.org/a4j"
xmlns:rich="http://richfaces.org/rich"
xmlns:composite="http://java.sun.com/jsf/composite">
<!-- Methods exposed on rich:component are available in the __proto__ object. -->
<composite:interface componentType="com.yieldex.platform.ui.CalendarDateRange">
<composite:attribute name="value" required="true" type="demo.Pair"/>
<composite:attribute name="dataModel" required="false" type="demo.Pair" />
<composite:clientBehavior name="change" event="change" targets="startCalendar endCalendar" default="true"/>
</composite:interface>
<composite:implementation>
<h:outputStylesheet library="yieldex/platform" name="css/yieldex-platform.css" target="head" />
<div id="#{cc.clientId}" class="yxp-calendar-date-range">
<rich:calendar id="startCalendar"
binding="#{cc.startCalendar}"
styleClass="yxp-start-date-range"
converter="localDateConverter" mode="ajax"
dataModel="#{not empty cc.attrs.dataModel ? cc.attrs.dataModel.startCalendarDataModel : standardCalendarDateRangeDataModel.startCalendarDataModel}"
monthLabels="#{dateRangeMessages.monthNames}"
weekDayLabelsShort="#{dateRangeMessages.weeksShort}"
monthLabelsShort="#{dateRangeMessages.monthNames}" popup="false"
showInput="false" showFooter="false" showWeeksBar="false"
showWeekDaysBar="true" showApplyButton="false"
buttonIcon="#{resource['yieldex/platform:img/1x1-transparent.png']}"
buttonDisabledIcon="#{resource['yieldex/platform:img/1x1-transparent.png']}">
<f:facet name="weekDays"></f:facet>
<f:ajax immediate="true" execute="#all" render="#this endCalendar"/>
</rich:calendar>
<rich:calendar id="endCalendar"
binding="#{cc.endCalendar}"
styleClass="yxp-end-date-range"
converter="localDateConverter" mode="ajax"
dataModel="#{not empty cc.attrs.dataModel ? cc.attrs.dataModel.endCalendarDataModel : standardCalendarDateRangeDataModel.endCalendarDataModel}"
monthLabels="#{dateRangeMessages.monthNames}"
weekDayLabelsShort="#{dateRangeMessages.weeksShort}"
monthLabelsShort="#{dateRangeMessages.monthNames}" popup="false"
showInput="false" showFooter="false" showWeeksBar="false"
showWeekDaysBar="true" showApplyButton="false"
buttonIcon="#{resource['yieldex/platform:img/1x1-transparent.png']}"
buttonDisabledIcon="#{resource['yieldex/platform:img/1x1-transparent.png']}">
<f:facet name="weekDays"></f:facet>
<f:ajax immediate="true" execute="#all" render="startCalendar #this"/>
</rich:calendar>
</div>
</composite:implementation>
</ui:composition>
My backing component:
#FacesComponent("com.yieldex.platform.ui.CalendarDateRange")
public class YXCalendarDateRange extends UIInput implements NamingContainer {
private UICalendar startCalendarComponent;
private UICalendar endCalendarComponent;
#Override
public void encodeBegin(final FacesContext context) throws IOException {
final Pair<LocalDate> value = (Pair<LocalDate>) this.getValue();
if (value == null) {
startCalendarComponent.setValue(null);
endCalendarComponent.setValue(null);
} else {
startCalendarComponent.setValue(value.getStart());
endCalendarComponent.setValue(value.getEnd());
}
super.encodeBegin(context);
}
#Override
protected Object getConvertedValue(final FacesContext context, final Object submittedValue) {
final LocalDate startDate = (LocalDate) startCalendarComponent.getConverter().getAsObject(context,
startCalendarComponent, (String) this.startCalendarComponent.getSubmittedValue());
final LocalDate endDate = (LocalDate) endCalendarComponent.getConverter().getAsObject(context,
endCalendarComponent, (String) this.endCalendarComponent.getSubmittedValue());
if (startDate == null || endDate == null) {
return null;
} else {
if (startDate.isAfter(endDate)) {
final FacesMessage message = new FacesMessage();
message.setSeverity(FacesMessage.SEVERITY_ERROR);
message.setSummary("start date cannot be after end date");
message.setDetail("start date cannot be after end date");
throw new ConverterException(message);
}
return Pair.of(startDate, endDate);
}
}
public UICalendar getEndCalendar() {
return this.endCalendarComponent;
}
#Override
public String getFamily() {
return UINamingContainer.COMPONENT_FAMILY;
}
public UICalendar getStartCalendar() {
return this.startCalendarComponent;
}
#Override
public Object getSubmittedValue() {
return this;
}
public void setEndCalendar(final UICalendar endCalendarComponent) {
this.endCalendarComponent = endCalendarComponent;
}
public void setStartCalendar(final UICalendar startCalendarComponent) {
this.startCalendarComponent = startCalendarComponent;
}
}
What I see is that the valueChangedEvent is coming though. I also see my processBehaviorEvent being called, and the first outputText being rerendered as I'm calling that programmatically. But the second one doesn't seem to get rerendered. I am trying to figure out if this is a bug in Mojarra 2.1.25 or is there something fundamentally wrong with my approach. Any suggestions would be greatly appreciated.
Any client ID in <f:ajax render> is evaluated relative to the parent naming container of the component it has been attached to. In this construct, the <f:ajax> ends up being attached inside the composite component, which is by itself a naming container. However, there's no component with ID out2 inside the composite, which is the problem.
To solve it, specify the absolute client ID. For example, when it's inside a <h:form id="formId"> element:
<f:ajax execute="#all" render=":formId:out2" />
If it's more complicated, binding the component to the view and refer to its client ID dynamically:
<h:outputText id="out2" binding="#{out2}" ... />
...
<f:ajax execute="#all" render=":#{out2.clientId}" />
See also:
How to find out client ID of component for ajax update/render? Cannot find component with expression "foo" referenced from "bar"
Related
I have this composite component:
inputMask.xhtml
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:composite="http://xmlns.jcp.org/jsf/composite"
xmlns:f="http://xmlns.jcp.org/jsf/core"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:c="http://xmlns.jcp.org/jsp/jstl/core">
<composite:interface>
<composite:attribute name="value" />
<composite:attribute name="mask" type="java.lang.String" required="true" />
<composite:attribute name="converterId" type="java.lang.String" default="br.edu.ufca.eventos.visao.inputmask.inputMask" />
</composite:interface>
<composite:implementation>
<h:outputScript library="script" name="inputmask.js" target="head" />
<h:inputText id="mascara">
<c:if test="#{cc.getValueExpression('value') != null}">
<f:attribute name="value" value="#{cc.attrs.value}" />
</c:if>
<f:converter converterId="#{cc.attrs.converterId}" />
<f:attribute name="mask" value="#{cc.attrs.mask}" />
</h:inputText>
<h:outputScript target="body">
defineMask("#{cc.clientId}", "#{cc.attrs.mask}");
</h:outputScript>
</composite:implementation>
</html>
In my last question:
Error trying to add composite component programmatically ("no tag was defined for name")
I was getting this error:
javax.faces.view.facelets.TagException: //C:/wildfly-10/standalone/tmp/eventos.ear.visao.war/mojarra7308315477323852505.tmp #2,127 <j:inputMask.xhtml> Tag Library supports namespace: http://xmlns.jcp.org/jsf/composite/componente, but no tag was defined for name: inputMask.xhtml
when trying to add the above composite component programmatically with this code:
Map<String, String> attributes = new HashMap<>();
attributes.put("mask", "999.999");
Components.includeCompositeComponent(Components.getCurrentForm(), "componente", "inputMask.xhtml", "a123", attributes);
but I managed to solve this problem this way:
The implementation of the method Components#includeCompositeComponent from OmniFaces 2.4 (the version I was using) is this:
public static UIComponent includeCompositeComponent(UIComponent parent, String libraryName, String tagName, String id, Map<String, String> attributes) {
String taglibURI = "http://xmlns.jcp.org/jsf/composite/" + libraryName;
Map<String, Object> attrs = (attributes == null) ? null : new HashMap<String, Object>(attributes);
FacesContext context = FacesContext.getCurrentInstance();
UIComponent composite = context.getApplication().getViewHandler()
.getViewDeclarationLanguage(context, context.getViewRoot().getViewId())
.createComponent(context, taglibURI, tagName, attrs);
composite.setId(id);
parent.getChildren().add(composite);
return composite;
}
So I decided to give a try to the code from an earlier version of OmniFaces (with some change adding the attributes parameter from me) of this method:
public static UIComponent includeCompositeComponent(UIComponent parent, String libraryName, String resourceName, String id, Map<String, String> attributes) {
// Prepare.
FacesContext context = FacesContext.getCurrentInstance();
Application application = context.getApplication();
FaceletContext faceletContext = (FaceletContext) context.getAttributes().get(FaceletContext.FACELET_CONTEXT_KEY);
// This basically creates <ui:component> based on <composite:interface>.
Resource resource = application.getResourceHandler().createResource(resourceName, libraryName);
UIComponent composite = application.createComponent(context, resource);
composite.setId(id); // Mandatory for the case composite is part of UIForm! Otherwise JSF can't find inputs.
// This basically creates <composite:implementation>.
UIComponent implementation = application.createComponent(UIPanel.COMPONENT_TYPE);
implementation.setRendererType("javax.faces.Group");
composite.getFacets().put(UIComponent.COMPOSITE_FACET_NAME, implementation);
if (!attributes.isEmpty()) {
ExpressionFactory factory = application.getExpressionFactory();
ELContext ctx = context.getELContext();
for (Map.Entry<String, String> entry : attributes.entrySet()) {
ValueExpression expr = factory.createValueExpression(ctx, entry.getValue(), Object.class);
composite.setValueExpression(entry.getKey(), expr);
}
}
// Now include the composite component file in the given parent.
parent.getChildren().add(composite);
parent.pushComponentToEL(context, composite); // This makes #{cc} available.
try {
faceletContext.includeFacelet(implementation, resource.getURL());
} catch (IOException e) {
throw new FacesException(e);
} finally {
parent.popComponentFromEL(context);
}
return composite;
}
And finally the error was gone. The composite component was dynamically added to the page.
But another problem appeared.
The action in a button to add the component is more or less like this:
if (Components.findComponent("form:a123") == null)
{
Map<String, String> attributes = new HashMap<>();
attributes.put("value", "#{bean.cpf}");
attributes.put("mask", "999.999.999-99");
includeCompositeComponent(Components.getCurrentForm(), "componente", "inputMask.xhtml", "a123", attributes);
}
As you can see, the composite component is only added once.
When the component is first added, the script code that is in the component:
<h:outputScript target="body">
defineMask("#{cc.clientId}", "#{cc.attrs.mask}");
</h:outputScript>
is added to the page. I can see it when I visualize the html source code in the browser. But on postbacks, this script code is not rendered anymore. It's not in the genereted html page. The <h:outputScript> with target="head" is rendered everytime, as expected, but not this one.
From my point of view, maybe there's still someting missing in the assembling of the composite component code in the method above to fix the script code even on postbacks on the page. I really don't know. It's just a guess.
Do you know what's going on or what's missing?
---- UPDATE 1 ----
I think that I really found the source of the problem. It seems that it's a bug in JSF related with scripts in composite components included programatically.
Here's what I found:
I noticed that the correct code from OmniFaces to include my composite component is this:
Components.includeCompositeComponent(Components.getCurrentForm(), "componente", "inputMask", "a123", attributes);
The correct is "inputMask", not "inputMask.xhtml". But as I told you before, when I use this code I get this error instead:
Caused by: javax.faces.FacesException: Cannot remove the same component twice: form:a123:j_idt2
So I suspected that the component with the id form:a123:j_idt2 was one of the h:outputScript present in the composite component. So I changed the composite component code to this:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:composite="http://xmlns.jcp.org/jsf/composite"
xmlns:f="http://xmlns.jcp.org/jsf/core"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:c="http://xmlns.jcp.org/jsp/jstl/core">
<composite:interface componentType="inputMask">
<composite:attribute name="value" />
<composite:attribute name="mask" type="java.lang.String" required="true" />
<composite:attribute name="converterId" type="java.lang.String" default="br.edu.ufca.eventos.visao.inputmask.inputMask" />
</composite:interface>
<composite:implementation>
<h:inputText id="mascara">
<c:if test="#{cc.getValueExpression('value') != null}">
<f:attribute name="value" value="#{cc.attrs.value}" />
</c:if>
<f:converter converterId="#{cc.attrs.converterId}" />
<f:attribute name="mask" value="#{cc.attrs.mask}" />
</h:inputText>
<script type="text/javascript">
defineMask("#{cc.clientId}", "#{cc.attrs.mask}");
</script>
</composite:implementation>
</html>
Removing all references to the h:outputScript tag. (Of course, I placed the inputmask.js script outside the composite component for the component to continue to work).
And now when I run the code, the component is finally added to the page without errors. But, as I said before with the code from an earlier version of OmniFaces, the script is still not rendered in postbacks. JSF only renders it when the component is added, loosing it on postbacks. I know this is not an expected behaviour.
So, I ask you: do you know how I can solve this script problem? Or at least any workaround I can use in this case?
Thank you in advance.
---- UPDATE 2 ----
I found a workaround for it. I did this in a backing component for the composite component and it worked, the script is always rendered:
#Override
public void encodeEnd(FacesContext context) throws IOException
{
super.encodeEnd(context);
ResponseWriter writer = context.getResponseWriter();
writer.startElement("script", this);
writer.writeText(String.format("defineMask('%s', '%s');",
getClientId(), getAttributes().get("mask")), null);
writer.endElement("script");
}
but it's kind of ugly and seems unnecessary. Again, if the component is not included programmatically, I don't need the backing component. It seems like a bug in JSF. Could some of you test and confirm this? I mean, test if a composite component with script in it added programmatically loses its script on postback.
P.S.: I'm using OmniFaces 2.4 and Mojarra 2.2.13.
The solution (workaround) is to remove all script from the composite component and create a backing component for it to do precisely what JSF was supposed to do:
package br.edu.company.project.view.inputmask;
import java.io.IOException;
import java.util.Map;
import javax.faces.component.FacesComponent;
import javax.faces.component.NamingContainer;
import javax.faces.component.UIInput;
import javax.faces.component.UINamingContainer;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import org.omnifaces.util.FacesLocal;
#FacesComponent("inputMask")
public class InputMask extends UIInput implements NamingContainer
{
private static final String SCRIPT_FILE_WRITTEN =
"br.edu.company.project.SCRIPT_FILE_WRITTEN";
#Override
public String getFamily()
{
return UINamingContainer.COMPONENT_FAMILY;
}
#Override
public void encodeBegin(FacesContext context) throws IOException
{
writeScriptFileIfNotWrittenYet(context);
super.encodeBegin(context);
}
#Override
public void encodeEnd(FacesContext context) throws IOException
{
super.encodeEnd(context);
writeMaskDefinition(context);
}
private void writeScriptFileIfNotWrittenYet(FacesContext context) throws IOException
{
if (FacesLocal.getRequestMap(context).putIfAbsent(
SCRIPT_FILE_WRITTEN, true) == null)
{
writeScript(context, w -> w.writeAttribute(
"src", "resources/script/inputmask.js", null));
}
}
private void writeMaskDefinition(FacesContext context) throws IOException
{
writeScript(context, w -> w.writeText(String.format(
"defineMask('%s', '%s');", getClientId(),
getAttributes().get("mask")), null));
}
private void writeScript(FacesContext context, WriteAction writeAction)
throws IOException
{
ResponseWriter writer = context.getResponseWriter();
writer.startElement("script", this);
writer.writeAttribute("type", "text/javascript", null);
writeAction.execute(writer);
writer.endElement("script");
}
#FunctionalInterface
private static interface WriteAction
{
void execute(ResponseWriter writer) throws IOException;
}
}
Again, you don't need this if your composite component won't be included programmatically. In this case, JSF works as expected and you don't need the backing component.
If someone have the time, I think it would be nice to file a bug report to the Mojarra team.
I want to implement f:validateWholeBean with JSF 2.3.
I tried to implement this example with Mojarra 2.3.0-m05 and Tomcat 8:
<h:form>
<h:panelGroup>
<h:inputSecret id="passwd" value="#{bean.dataList['passwd']}">
<f:ajax event="blur" render="passwdvalidator" />
</h:inputSecret>
<h:message id="passwdvalidator" for="passwd" />
</h:panelGroup>
<h:panelGroup>Confirm Password</h:panelGroup>
<h:panelGroup>
<h:inputSecret id="confurmpasswd" value="#{bean.dataList['passwd']}">
<f:ajax event="blur" render="confurmpasswdvalidator" />
</h:inputSecret>
<h:message id="confurmpasswdvalidator" for="confurmpasswd" />
</h:panelGroup>
<h:commandButton action="#{bean.submit}">
<f:ajax render="#form" execute="#form"></f:ajax>
</h:commandButton>
<f:validateWholeBean value="#{contactBean}" validationGroups="validateBean.ContactGroup" />
</h:form>
Custom Validator
#Named
#ViewScoped
public class NewAccountValidator implements Validator, Serializable
{
#Override
public void validate(FacesContext fc, UIComponent uic, Object o) throws ValidatorException
{
// not used
}
public void validatePasswords(FacesContext context, UIComponent component, Object value)
{
String l;
String s = value.toString().trim();
if (s != null)
{
// compare passwords
}
else
{
throw new ValidatorException(new FacesMessage(FacesMessage.SEVERITY_INFO,
s.isEmpty() ? " This field cannot be empty!" : " '" + s + "' is not a number!", null));
}
}
}
What is the proper way to implement solution with f:validateWholeBean and custom JSF validator?
You shouldn't implement a "standard" validator, but a ConstraintValidator.
You can find an example on Arjan Tijms Weblog:
<h:form>
<h:inputText value="#{indexBean.foo}">
<f:validateBean validationGroups="javax.validation.groups.Default,java.util.RandomAccess"/>
</h:inputText>
<h:inputText value="#{indexBean.bar}">
<f:validateBean validationGroups="javax.validation.groups.Default,java.util.RandomAccess"/>
</h:inputText>
<f:validateWholeBean value="#{indexBean}" validationGroups="java.util.RandomAccess"/>
<h:commandButton value="submit"/>
</h:form>
with backing bean:
#Named
#RequestScoped
#ValidIndexBean(groups = java.util.RandomAccess.class)
public class IndexBean implements ConstraintValidator<ValidIndexBean, IndexBean> {
#Constraint(validatedBy = IndexBean.class)
#Documented
#Target(TYPE)
#Retention(RUNTIME)
public #interface ValidIndexBean {
String message() default "Invalid Bean";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
#Inject // #EJB
private PersistenceService service;
#NotNull
private String foo;
#NotNull
private String bar;
#Override
public void initialize(ValidIndexBean constraintAnnotation) {
//
}
#Override
public boolean isValid(IndexBean other, ConstraintValidatorContext context) {
// return other.getFoo().equals(other.getBar());
return service.query("select count(p) from Person p where p.foo like ?1 and p.bar like ?2", other.getFoo(), other.getBar()) == 0;
}
...
}
answer for comments:
this is a regular bean, so yes, it can be #ViewScoped.
then you should create multiple validators: it's a bad practice to make a single validator perform multiple logics.
unrelated:
As I can see from the code you posted, you are misunderstanding the use of "classic" validator, making it a ManagedBean (CDI flavoured), but this is not the "plain" use of JSF Validators/Converters.
I suppose you are not using a validator, but a validation method instead.
A "classic" Validator should look like (see here):
#FacesValidator("usernameValidator")
public class UsernameValidator implements Validator, Serializable
{
#Override
public void validate(FacesContext context, UIComponent component, Object value) throws ValidatorException
{
// you should use THIS method to validate a single Component's Value
if(query("select count(*) from user where username = '?'", String.valueOf(value)) > 0)
{
throw new ValidatorException(new FacesMessage(FacesMessage.SEVERITY_ERROR, "invalid username"));
}
}
}
and should be used like:
<h:inputText value="#{someBean.username}" validator="usernameValidator" />
so:
"classic" Faces Validators are tought for validating one component's value
they shouldn't be #ManagedBean or #Named
they should be referenced by name (validator="usernameValidator" without using EL expressions validator="#{usernameValidator}")
However, it's a best practice for Validators/Converters to be "specialized": they should perform a single validation logic.
If you need to validate a component value, i.e. a Date, that must be non-null and greater than 01/01/1970, you'll need two specialized validators.
I would like to set up a Date field in my page like this
|hours| h |minutes|
where hours and minutes are in separated inputText.
The bean have this date
import java.util.Date;
...
private Date myDate;
...
and the page is
<h:form>
...
<h:inputText id="myDateHours" maxlength="2" value="#{myBean.myDate}"
<f:convertDateTime pattern="HH" />
</h:inputText>
<h:outputText value=" h " />
<h:inputText id="myDateMinutes" maxlength="2" value="#{myBean.myDate}"
<f:convertDateTime pattern="mm" />
</h:inputText>
...
</h:form>
But the problem is that when I submit the form only the last element is saved.
For instance if I type the hours and then the minutes, the hours are overwritten and the result is
| 00 | h | minutes |
I tried to set
<h:inputText id="myDateHours" value="#{myBean.myDate.hours}></h:inputText>
<h:inputText id="myDateMinutes" value="#{myBean.myDate.minutes}></h:inputText>
but I get a
Cannot convert 01/01/70 01:00 of type class java.util.Date to int
I don't want to replace my date field with two int field (hours and minutes...)
Do you have an idea?
Thanks
This particular case is not possible if you want to use a single model value.
This is however a perfect candidate for a composite component. It allows you to bind a single model value to a group of closely related existing components and perform the processing/conversion in the backing component, fully decoupled from the view and backing bean. One of the examples can be found in this article: composite component with multiple input fields. This example can in for your specific case be altered as follows:
/resources/components/inputTime.xhtml:
<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"
>
<cc:interface componentType="inputTime">
<cc:attribute name="value" type="java.util.Date" shortDescription="The selected time. Defaults to now." />
</cc:interface>
<cc:implementation>
<span id="#{cc.clientId}" style="white-space:nowrap">
<h:inputText id="hour" binding="#{cc.hour}" maxlength="2" converter="javax.faces.Integer" />h
<h:inputText id="minute" binding="#{cc.minute}" maxlength="2" converter="javax.faces.Integer" />
</span>
</cc:implementation>
</ui:component>
com.example.InputTime
#FacesComponent("inputTime")
public class InputTime extends UIInput implements NamingContainer {
private UIInput hour;
private UIInput minute;
/**
* As required by <cc:interface>.
*/
#Override
public String getFamily() {
return UINamingContainer.COMPONENT_FAMILY;
}
/**
* Set initial hour and minute based on model.
*/
#Override
public void encodeBegin(FacesContext context) throws IOException {
Calendar calendar = Calendar.getInstance();
Date date = (Date) getValue();
if (date != null) {
calendar.setTime(date);
}
hour.setValue(calendar.get(Calendar.HOUR_OF_DAY));
minute.setValue(calendar.get(Calendar.MINUTE));
super.encodeBegin(context);
}
/**
* Returns the submitted value in HH-mm format.
*/
#Override
public Object getSubmittedValue() {
return hour.getSubmittedValue() + "-" + minute.getSubmittedValue();
}
/**
* Converts the submitted value to concrete {#link Date} instance.
*/
#Override
protected Object getConvertedValue(FacesContext context, Object submittedValue) {
try {
return new SimpleDateFormat("HH-mm").parse((String) submittedValue);
}
catch (ParseException e) {
throw new ConverterException(e);
}
}
public UIInput getHour() {
return hour;
}
public void setHour(UIInput hour) {
this.hour = hour;
}
public UIInput getMinute() {
return minute;
}
public void setMinute(UIInput minute) {
this.minute = minute;
}
}
Usage:
<html ... xmlns:my="http://java.sun.com/jsf/composite/components">
...
<my:inputTime value="#{bean.date}" />
See also:
When to use <ui:include>, tag files, composite components and/or custom components?
You need two separate setter methods in the bean, and then do the merge in the server.
<h:inputText id="myDateHours" value="#{myBean.hours}></h:inputText>
<h:inputText id="myDateMinutes" value="#{myBean.minutes}></h:inputText>
Both must get date values, so then you can operate with a JAVA Calendar setting both fields, in the action invoked by your form.
Calendar calendar = new GregorianCalendar();
calendar.setTimeInMillis(0);
calendar.set(Calendar.HOUR, getHours().getHours());
calendar.set(Calendar.MINUTE, getMinutes().getMinutes());
Be aware of time zones if required.
I am having a problem with the SelectOneMenu control. I want the the selected item to be be displayed via the valueChange Ajax event listen. But this is not happening.
However, when I change the value in the SelectOneMenu and then click on the Submit button, then selected value is getting displayed via the 'save' bean function
Cannot figure out why this is not working. Would appreciate any help on this.
Thanks.
The relevant xhtml code is as follows:
<h:form>
<h:dataTable value="#{dynamicList.myData}" var="item" >
<h:column>
<h:outputText value="#{item.oracleType}"></h:outputText>
</h:column>
<h:column>
<h:selectOneMenu value="#{item.coffeeFlavour}" rendered="#{item.showLov}" >
<f:selectItems value="#{item.coffeeList}"></f:selectItems>
<f:ajax event="valueChange" listener="#{dynamicList.listen}" ></f:ajax>
</h:selectOneMenu>
<h:inputText value="#{item.coffeeFlavour}" rendered="#{item.showText}">
</h:inputText>
</h:column>
</h:dataTable>
<p:commandButton value="Submit" action="#{dynamicList.save}" ></p:commandButton>
</h:form>
The relevant bean code is as follows:
#ManagedBean
#ViewScoped
public class DynamicList implements Serializable{
private List<OraclePrfl> oracleList=new ArrayList<OraclePrfl>();
private String coffee;
private Map<String,String> coffeeList=new LinkedHashMap<String,String>();
public List<OraclePrfl> getOracleList() {
return oracleList;
}
public List<OraclePrfl> getMyData()
{
oracleList.clear();
oracleList.add(new OraclePrfl("Oracle Lot Number",new HashMap<String,String>(){
{
put("Coffee2 - Cream Latte", "Cream Latte");
put("Coffee2 - Extreme Mocha", "Extreme Mocha");
put("Coffee2 - Buena Vista", "Buena Vista");
}
},true,false));
oracleList.add(new OraclePrfl("Oracle Product Number",new HashMap<String,String>(){
{
put("ABC", "abc");put("PQR", "pqr");put("XYZ", "xyz");
}
},true,false));
oracleList.add(new OraclePrfl("Oracle Specification",new HashMap<String,String>(){
{
put("MNP", "mnp");put("WXY", "wxy");put("XYZ", "xyz");
}
},true,false));
oracleList.add(new OraclePrfl("Address",false,true));
return oracleList;
}
public void setOracleList(List<OraclePrfl> oracleList) {
this.oracleList = oracleList;
}
public String getCoffee() {
return coffee;
}
public void setCoffee(String coffee) {
this.coffee = coffee;
}
public Map<String,String> getCoffeeList() {
coffeeList.clear();
coffeeList.put("Coffee2 - Cream Latte", "Cream Latte"); //label, value
coffeeList.put("Coffee2 - Extreme Mocha", "Extreme Mocha");
coffeeList.put("Coffee2 - Buena Vista", "Buena Vista");
return coffeeList;
}
public void setCoffeeList(Map<String,String> coffeeList) {
this.coffeeList = coffeeList;
}
public void save(){
for(OraclePrfl oracle:oracleList){
System.out.println("oracle type------"+oracle.getOracleType()+"------coffee----
"+oracle.getCoffeeFlavour());
}
}
public void listen(AjaxBehaviorEvent event){
System.out.println("calling listener "+event.getSource().toString());
for(OraclePrfl oracle:oracleList){
System.out.println("type....."+oracle.getOracleType()+"----value-----
"+oracle.getCoffeeFlavour());
}
}
}
Try removing the event:
<f:ajax listener="#{dynamicList.listen}" ></f:ajax>
It should default to event="change".
In JSF & Primefaces web application, I want to pass a value for the complete method of primefaces input text area control. I have tried it as follows.
JSF file
<p:inputTextarea id="txtMicMemoVal"
value="#{patientReportController.memoEnterVal}"
style="min-width: 200px;"
completeMethod="#{investigationItemValueController.completeValues}" >
<f:attribute name="ii" value="#{pv.investigationItem}" />
<f:ajax event="blur" execute="#this"
listener="#{patientReportController.saveMemoVal(pv.id)}" ></f:ajax>
</p:inputTextarea>
Relevant Backing Bean
public List<String> completeValues(String qry) {
System.out.println("completing values");
FacesContext context = FacesContext.getCurrentInstance();
InvestigationItem ii;
try {
ii = (InvestigationItem) UIComponent.getCurrentComponent(context).getAttributes().get("ii");
System.out.println("ii = " + ii);
} catch (Exception e) {
ii = null;
System.out.println("error " + e.getMessage());
}
Map m = new HashMap();
String sql;
sql = "select v.name from InvestigationItemValue v "
+ "where v.investigationItem=:ii and v.retired=false and"
+ " (upper(v.code) like :s or upper(v.name) like :s) order by v.name";
m.put("s","'%"+ qry.toUpperCase()+"%'");
m.put("ii", ii);
List<String> sls = getFacade().findString(sql, m);
System.out.println("sls = " + sls);
return sls;
}
But the backing bean method is not fired when i enter text to input text area. But if I remove the f:attribute, backing bean is fired. But I want that parameter as well for functionality.
Thanks in advance to direct me to over come this issue.
Interesting question. Primefaces bounds you to receive only a String parameter in your completion method, so the only solution I see is evaluating your expression at server side, when completion function gets called.
I suppose you've got an iteration (either ui:repeat or p:dataTable) where each id differs from the previous one. If you don't, you can also use it.
That would be the way to go:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:p="http://primefaces.org/ui">
<h:head />
<h:body>
<h:form>
<ui:repeat var="str" value="#{bean.strings}">
<p:inputTextarea value="#{bean.value}" style="min-width: 200px;"
completeMethod="#{bean.complete}" />
</ui:repeat>
</h:form>
</h:body>
</html>
#ManagedBean
#RequestScoped
public class Bean {
public String value;
public List<String> strings = Arrays.asList("param1", "param2", "param3");
public List<String> complete(String query) {
List<String> results = new ArrayList<String>();
//Here we evaluate the current #{str} value and print it out
System.out.println(FacesContext
.getCurrentInstance()
.getApplication()
.evaluateExpressionGet(FacesContext.getCurrentInstance(),
"#{str}", String.class));
if (query.equals("PrimeFaces")) {
results.add("PrimeFaces Rocks!!!");
results.add("PrimeFaces has 100+ components.");
results.add("PrimeFaces is lightweight.");
results.add("PrimeFaces is easy to use.");
results.add("PrimeFaces is developed with passion!");
}
return results;
}
public List<String> getStrings() {
return strings;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
Note you're evaluating the current EL result for #{str} when method call performs. You'll get a different evaluation result depending on which p:inputTextArea you write to.
See also:
How to pass parameter to f:ajax in h:inputText? f:param does not work