Got a bit stuck while working on a JSF Custom Component. Would be nice if someone could give me head start.
The idea is to have a component that lets me page through a LocalDate. Think of a simpler date picker. The rendered HTML has two buttons, one to increment the date by a day and another button to decrement the date by a day. The value itself is stored in a input hidden. Attached is a simple version of the component which doesn't work, but hopefully describes what I am trying to achieve.
The idea is to have say <my:datePager value="#{myBackingBean.someDate}" /> on a page and execute some logic/action once one of either button has been clicked.
For example <my:datePager value="#{myBackingBean.someDate} action="..." /> or <my:datePager value="#{myBackingBean.someDate} previous="..." next="..."/> No idea what is better.
Here is what I am stuck with: How to call the previous and next methods on the component?
This is my JSF 2.3 custom component so far:
#FacesComponent(LocalDatePager.COMPONENT_TYPE)
public class LocalDatePager extends UIInput {
public static final String COMPONENT_TYPE = "LocalDatePager";
public LocalDatePager() {
setConverter(LocalDateConverter.INSTANCE);
}
public void previous() {
LocalDate localDate = (LocalDate) getValue();
localDate = localDate.minusDays(1);
setValue(localDate);
}
public void next() {
LocalDate localDate = (LocalDate) getValue();
localDate = localDate.plusDays(1);
setValue(localDate);
}
#Override
public void decode(FacesContext context) {
super.decode(context);
}
#Override
public void encodeEnd(FacesContext context) throws IOException {
ResponseWriter writer = context.getResponseWriter();
writer.startElement("div", this);
writer.writeAttribute("class", "btn-group", null);
writer.writeAttribute("role", "group", null);
writer.startElement("button", this);
writer.writeAttribute("type", "button", null);
writer.writeAttribute("class", "btn btn-outline-primary", null);
writer.writeText("Previous", null);
writer.endElement("button");
writer.startElement("button", this);
writer.writeAttribute("type", "button", null);
writer.writeAttribute("class", "btn btn-outline-primary", null);
writer.writeText("Next", null);
writer.endElement("button");
writer.endElement("div");
writer.startElement("input", this);
writer.writeAttribute("name", getClientId(context), "clientId");
writer.writeAttribute("type", "hidden", null);
writer.writeAttribute("value", getValue(), "value");
writer.endElement("input");
}
}
From my understanding the component I have is mind is both UIInput and UICommand. Do I need to implement ActionEvent? Fire value change events?
At the moment I do the whole previous and next methods in backing beans and not in a component. This leads to lots of duplicate code and I thought a custom component fits best. I tried to work with Composite Components, but this did not get me far either.
Another approach so far has been client behaviour: execute JavaScript, change the value of the hidden input and submit the form. This felt even worse.
Would be nice if someone could give me a pointer in the right direction.
As UIInput
First you need to render the buttons as normal submit buttons in encodeXxx() method. Also, you'd rather want to use encodeAll() instead of encodeEnd() for this because this appears to be a "leaf" component and you thus don't want to deal with children. Implementing encodeAll() is then more efficient. Rename your existing encodeEnd() method to encodeAll() and adjust the encoding of buttons as below:
writer.startElement("button", this);
writer.writeAttribute("type", "submit", null); // instead of "button"
writer.writeAttribute("name", getClientId(context) + "_previous", "clientId");
writer.writeText("Previous", null);
// ...
writer.startElement("button", this);
writer.writeAttribute("type", "submit", null); // instead of "button"
writer.writeAttribute("name", getClientId(context) + "_next", "clientId");
writer.writeText("Next", null);
// ...
// The hidden input field in your existing code is fine as-is.
Then you can just check for them in the request parameter map. If you want to keep your component to be an UIInput, then I suggest to perform the job in getConvertedValue() method:
#Override
protected Object getConvertedValue(FacesContext context, Object submittedValue) throws ConverterException {
LocalDate localDate = (LocalDate) super.getConvertedValue(context, submittedValue);
if (localDate != null) {
Map<String, String> params = context.getExternalContext().getRequestParameterMap();
if (params.get(getClientId(context) + "_previous") != null) {
localDate = localDate.minusDays(1);
}
else if (params.get(getClientId(context) + "_next") != null) {
localDate = localDate.plusDays(1);
}
}
return localDate;
}
Then you can simply do the job in a value change listener:
<my:datePager value="#{bean.localDate}" valueChangeListener="#{bean.onpage}" />
public void onpage(ValueChangeEvent event) {
// ...
}
That's basically all. No need to manually fire the value change event. The rest of the logic is already taken care of by JSF.
The disadvantage of this approach, however, is that this might be invoked at the wrong moment in the JSF lifecycle. A value change listener is invoked during validations phase while you really want it to be invoked during invoke application phase. Imagine that this button is placed in a form whose state depends on the data associated with #{bean.localDate}, then the update model values phase on them may go wrong because the localDate had been changed too soon.
As UICommand
If the abovementioned disadvantage is a real showstopper, although there is a work around, then you better convert the UIInput to an UICommand. It can't be both. You pick the one or the other. My personal advice would be to pick UICommand as that's "more natural" wrt the behavior during the JSF lifecycle while having the sole purpose of the component in mind ("paginating to next/previous date"). Here are the steps:
Swap out extends UIInput for extends UICommand.
Remove the setConverter() call in constructor.
Keep the encodeAll() method. It's totally fine.
Remove the getConvertedValue() method and implement decode() as below:
#Override
public void decode(FacesContext context) {
Map<String, String> params = context.getExternalContext().getRequestParameterMap();
if (params.get(getClientId(context) + "_previous") != null) {
queueEvent(new ActionEvent(context, this));
}
else if (params.get(getClientId(context) + "_next") != null) {
queueEvent(new ActionEvent(context, this));
}
}
These queueEvent() calls will cause the broadcast() of the very same component to be called. So override it as below:
#Override
public void broadcast(FacesEvent event) throws AbortProcessingException {
FacesContext context = event.getFacesContext();
Map<String, String> params = context.getExternalContext().getRequestParameterMap();
LocalDate localDate = LocalDate.parse(params.get(getClientId())); // TODO: gracefully handle any conversion error.
if (params.get(getClientId(context) + "_previous") != null) {
localDate = localDate.minusDays(1);
}
else if (params.get(getClientId(context) + "_next") != null) {
localDate = localDate.plusDays(1);
}
getValueExpression("value").setValue(context.getELContext(), localDate);
super.broadcast(event); // Invokes bean method.
}
Note how the value attribute is manually decoded, converted and updated in the model. Although this is passed around as a hidden input value, you might want to add some logic to gracefully handle any conversion error as indicated in the TODO.
Now you can use it as below:
<my:datePager value="#{bean.localDate}" action="#{bean.onpage}" />
public void onpage() {
// ...
}
Related
I cannot see why the question is duplicate. If I debug the code then - when the button is clicked - no new value of projectSelected is being detected. Even the hashCode is the same. The equals method of the ProjectEntity only contains the id which is the same since it comes from the database and is not changed anywhere. Null values don't exist in the selection.
There was, however, too much code to reproduce the problem. I removed unnecessary code and the problem still persists.
Original question: In the following form with 3 <p:selectOneMenu> -fields if the submit button is clicked a valueChangeEvent is fired for the projectSelector field although it hasn't changed. Why is that? Like that the actual action behind the button is never called. I would expect a valueChangeEvent to be fired only in case the project changes.
Update: Trying to find the cause I replaced the ProjectEntity with String and then it worked. So I thought it must be the equals method of ProjectEntity but that only compares the id. I debugged further and found out that the selected value is being compared with a ProjectEntity with all fields set to null which gives a false and hence a valueChangeEvent. So the question is why is there a ProjectEntity with all fields set to null? I debugged into UIInput.compareValues which has that "null"-ProjectEntity being the previous value. That is being returned by UIOuput.getLocalValue. Where does it come from?
Update2: Even when using the equals and hashCode from selectOneMenu shows after submit always the last item in the list as selected item the behaviour does not change. I created an ear file readily to be deployed to e.g. a wildfly and would appreciate any help since I am stuck on this question.
<h:form>
<p:outputLabel value="#{msgs.timeProject}"/>
<p:selectOneMenu value="#{timeBean.model.projectSelected}"
converter="projectConverter"
onchange="submit()"
valueChangeListener="#{timeBean.projectChanged}"
immediate="true"
required="true">
<f:selectItems value="#{timeBean.model.allProjects}"
var="singleProject"
itemValue="#{singleProject}"
itemLabel="#{singleProject.name}"/>
</p:selectOneMenu>
<p:commandButton value="#{msgs.send}"
action="#{timeBean.myAction}"
ajax="false"/>
<p:outputLabel value="#{timeBean.model.resultValue}"
rendered="#{not empty timeBean.model.resultValue}"/>
</h:form>
The converter
#FacesConverter(value = "projectConverter")
public class ProjectConverter implements Converter {
#Inject
private ProjectService projectService;
#Override
public Object getAsObject(final FacesContext facesContext, final UIComponent uiComponent, final String projectName) {
if (StringUtils.isEmpty(projectName)) {
return null;
}
final List<ProjectEntity> projects = projectService.findAll();
for (ProjectEntity project : projects) {
if (StringUtils.equals(projectName, project.getName())) {
return project;
}
}
return null;
}
#Override
public String getAsString(final FacesContext facesContext, final UIComponent uiComponent, final Object value) {
if (value == null) {
return null;
}
if (value instanceof ProjectEntity) {
return ((ProjectEntity) value).getName();
}
return "???projectName???";
}
}
The equals-method of the ProjectEntity
#Override
public boolean equals(final Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
final ProjectEntity that = (ProjectEntity) o;
return id != null ? id.equals(that.id) : that.id == null;
}
And the change listener inside the timeBean
public void projectChanged(final ValueChangeEvent event) {
final ProjectEntity projectSelected = (ProjectEntity) event.getNewValue();
model.setProjectSelected(projectSelected);
final FacesContext context = FacesContext.getCurrentInstance();
context.renderResponse();
}
The TimeModel
public class TimeModel {
private ProjectEntity projectSelected;
private List<ProjectEntity> allProjects;
private String resultValue;
... getters and setters ...
I'll guess, that the problem resides inside the ProjectConverter class, cause it may run into troubles to assign a valid projectService instance. Maybe you remove the injection and try to compute the value programatically in the getAsObject, getAsString methods by explicit cdi-finders.
I remember to run in a similar situation, when i was injecting in a ServletFilter.
Mojarra 2.2
I have two beans`.
public class MyBean1{
private String myProperty1;
//GET, SET, CTOR
public void doAction(){
//assign something to myProperty1
}
}
public class MyBean2{
private String myProperty2;
//GET, SET, CTOR
}
Now, I need to assign the value of the property MyBean1::myProperty1 to MyBean2::myProperty2 after the doAction() method invocation by clicking a button:
<h:commandButton action="#{myBean1.doAction}">
<f:setPropertyActionListener target="#{myBean2.myProperty2}"
value="#{myBean1.myProperty1}" />
</h:commandButton>
But it doesn't work. And I figured out that it doesn't work due to the following reason:
Clicking a button causes ActionEvent to be broadcasted. It performs by this method (javax.faces.component.UICommand):
public void broadcast(FacesEvent event) throws AbortProcessingException {
super.broadcast(event); //1 <-------------------- HERE
if (event instanceof ActionEvent) {
FacesContext context = getFacesContext();
MethodBinding mb = getActionListener();
if (mb != null) {
mb.invoke(context, new Object[] { event });
}
ActionListener listener =
context.getApplication().getActionListener();
if (listener != null) {
listener.processAction((ActionEvent) event);
}
}
}
I've noticed that the ActionEvent broadcasting by clickng the button is being handled by two listeners and one that comes from super.broadcast(event) invokation at //1 is com.sun.faces.facelets.tag.jsf.core.SetPropertyActionListenerHandler.SetPropertyListener.
The handling is performed before invokation of the action method.
Of course, I can embed the action method invokation into the MyBean1:getMyProperty1() getter, so the myProperty1 field will be properly intialized, but it seems quite wierd to me. What is the right way to achieve that?
I'm trying to develop a custom component that will need to call a method from the backingbean to get some data from the bb (this will be called in the decode phase after a certain Ajax call) with one parameter (it will come in the ajax call).
The problem I'm having is that I define the attribute as a MethodExpression (in the taglibrary and the component), I get the Ajax post, decode the parameter and when I try to get the Method binding from the component I get the following error:
javax.el.PropertyNotFoundException: /easyFaces.xhtml #19,151
dataSource="#{theBean.loadDataFromSource}": The class
'ar.com.easytech.faces.test.homeBean' does not have the property
'loadDataFromBean'.
Here is the relevant code.. (and please let me know if this is not the correct way to do this..)
taglib:
<attribute>
<display-name>Data Source</display-name>
<name>dataSource</name>
<required>true</required>
<type>javax.el.MethodExpression</type>
<method-signature>java.util.List theDataSource(java.lang.String)</method-signature>
</attribute>
Component definition:
public class Autocomplete extends HtmlInputText implements ClientBehaviorHolder
...
public MethodExpression getDataSource() {
return (MethodExpression) getStateHelper().eval(PropertyKeys.dataSource);
}
public void setDataSource(MethodExpression dataSource) {
getStateHelper().put(PropertyKeys.dataSource, dataSource);
}
and finally the rendered method that generates the error:
private List<Object> getData(FacesContext context, Autocomplete autocomplete, String data) {
Object dataObject = null;
MethodExpression dataSource = autocomplete.getDataSource();
if (dataSource != null) {
try {
dataObject = dataSource.invoke(context.getELContext(), new Object[] {data});
return convertToList(dataObject);
} catch (MethodNotFoundException e) {
logger.log(Level.INFO,"Method not found: {0}", dataSource.getExpressionString() );
}
}
return null;
}
Here is the method from the BB
public List<String> autcompleteFromSource(String param) {
List<String> tmpData = new ArrayList<String>();
tmpData.add("XXA_TABLE_A");
tmpData.add("XXA_TABLE_B");
tmpData.add("XXA_TABLE_C");
return tmpData;
}
And the .xhtml with the component
<et:autocomplete id="autoc" minLength="3" delay="500" value="#{easyfacesBean.selectedValue}" dataSource="#{easyfacesBean.autcompleteFromSource}" />
The thing is if I define a method getAutocompleteFromSource() it recognised the method and the error changes to can't convert list to MethodExpression, so evidently it is simply interpreting the autocompleteFromSource as a simple property and not a method definition, is this even the correct way to call method from BB? (giving that it's not an actual action nor validation )
I found the solution for this, as it turns out you also need to define a "Handler"to define the Method Signature, so I created the handler and added to the taglib and everything started to work fine..just for reference.. here is the handler..
Regards
public class AutocompleteHandler extends ComponentHandler {
public AutocompleteHandler(ComponentConfig config) {
super(config);
}
protected MetaRuleset createMetaRuleset(Class type) {
MetaRuleset metaRuleset = super.createMetaRuleset(type);
metaRuleset.addRule(new MethodRule("dataSource", List.class, new Class[] { String.class }));
return metaRuleset;
}
}
I have a "new item" form that requires a list of dates, with the following components:
A <rich:calendar> input;
A <a4j:commandButton> that adds the chosen date to a List<Date> chosenDates in the backing bean;
A <rich:dataTable> with it's value set to the List<Date> chosenDates attribute;
A <a4j:commandButton> per dataTable row that removes it's date from theList<Date> chosenDates;
How to validate (JSF's validation phase) the size of the chosenDates list on form submit (creation process)?
RichFaces 4, JSF 2.1 (Mojarra).
I'd advise a cleaner approach with a JSF PhaseListener. The JSF processing will stop skip ahead the other phases if validation fails. Create a PhaseListener that will inspect the size of your list during the validations phase as against during the model update/invoke action phase. Try something like this
Create a phase listener for the validations phase
public class TestPhaseListener implements PhaseListener {
#Override
public void afterPhase(PhaseEvent event) {
throw new UnsupportedOperationException("Not supported yet.");
}
#Override
public void beforePhase(PhaseEvent event) {
if(event.getPhaseId().equals(PhaseId.PROCESS_VALIDATIONS)){
FacesContext ctx = event.getFacesContext();
YourBeanClass theBeanClass = ctx.getApplication().evaluateExpressionGet(ctx, "#{someBean}", YourNeanClass.class); //obtain a reference to the backing bean containing the list
/*
inspect the size of the list here and based on that throw the exception below
*/
throw new ValidatorException(new FacesMessage("Too many dates","Too Many Dates"));
}
}
#Override
public PhaseId getPhaseId() {
throw new UnsupportedOperationException("Not supported yet.");
}
}
Register your new listener in the faces_config.xml file
<lifecycle>
<phase-listener>your.package.structure.TestPhaseListener</phase-listener>
</lifecycle>
EDIT: Based on your comment, as an alternative, you can hook into the component's lifecycle using the <f:event/> tag and the preValidate or postValidate events (depending on your preference)
A listener tag to your component
<rich:dataTable>
<f:event type="preValidate" listener="#{yourBean.listener}"/>
</rich:dataTable>
Define a listener method in your backing bean to run per your defined event. The method signature must take an argument of type ComponentSystemEvent
public void preCheck(ComponentSystemEvent evt){
//You're in your backing bean so you can do pretty much whatever you want. I'd advise you mark the request as validation failed and queue FacesMessages. Obtain a reference to FacesContext and:
facesContext.validationFailed();
}
Do something like:
#{yourBean.chosenDates.size()}
I suppose you have a getter called getChosenDates which returns the chosenDates list.
Regarding your "validation concerns":
You can create a Validate method in your bean and return list of ValidationMessages. A sample is below, one that i used in my code.
public List<ValidationMessage> validate() {
List<ValidationMessage> validations = new ArrayList<ValidationMessage>();
int curSampleSize = sampleTable.getDataModel().getRowCount();
if(getNumberOfSamples() != null) {
size += getNumberOfSamples();
} else {
validations.add(new ValidationMessage("Please enter the no of samples to continue."));
return validations;
}
return validations;
}
Then, on submit you can check if you have any ValidationMessages as follows:
List<ValidationMessage> errs = validate();
if(errs.size()>0) {
FacesValidationUtil.addFacesMessages(errs);
return null;
}
Hope this helps!
I have a tag class which extends UIComponent and UIOutput. In this class I have encodeBegin and encodeEnd which I can use my contextWriter to output any kinda html tag I want too by using writer.startElement("div", myComponent) and so on.
My problem now is that I need to insert for example a instead of using the writer.startElement. I can get this done by doing getChildren().add(HtmlCommandButton button = new HtmlCommandButton()); but when doing it like that I cant seem to output the component where I want them to appear, like I can with write.startElement.
Does anyone have any good solutions in how I can take advantage of richfaces tags, JSF tags and similar in my own taglibrary? In short what I would really want to do is inside my encodeBegin:
writer.startElement("a4j:commandButton", myComponent);
writer.writeAttribite("action", "#{Handler.myAction}", null);
writer.endElement("a4j:commandButton");
Thanks by advance
You cannot use the ResponseWriter as you wish to. Two ways I can think of how to add child controls programmatically are either via the binding attribute (see this answer) or in the place where controls usually get created (in JSPs, that is in the tag class).
There are two ways for JSF components to contain other controls: as children or as named facets. Components always control how they render their facets; if they are to render their children, they must return true for getRendersChildren.
This is untested code, but the sequence goes something like this:
#Override
public boolean getRendersChildren() {
return true;
}
#Override
public void encodeBegin(FacesContext context)
throws IOException {
// should really delegate to a renderer, but this is only demo code
ResponseWriter writer = context.getResponseWriter();
writer.startElement("span", this);
String styleClass = getStyleClass();
writer
.writeAttribute("class", styleClass, "styleClass");
UIComponent headerComponent = getFacet("header");
if (headerComponent != null) {
headerComponent.encodeAll(context);
}
writer.startElement("hr", null);
}
#Override
public void encodeChildren(FacesContext context)
throws IOException {
ResponseWriter writer = context.getResponseWriter();
for (UIComponent kid : getChildren()) {
kid.encodeAll(context);
writer.startElement("br", null);
}
}
#Override
public void encodeEnd(FacesContext context)
throws IOException {
ResponseWriter writer = context.getResponseWriter();
writer.endElement("span");
}
Not really an answer, more of a guess, but maybe you could extend one of the facelets controls?
Alternatively, either use facelets directly - which seems to be exactly what you want really though I've not used it myself. Or you could add UIOutput controls where you want HTML to appear and set the value of each to the HTML you want to appear - this is exactly what f:verbatim does under the hood, or so it seems from looking at the source code :-)