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 :-)
Related
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() {
// ...
}
I am testing JSF component but I am getting NullPointerException :( The problem code is :
#FacesComponent(value="mycomponent0")
public class MyComponent extends HtmlPanelGroup{
MyComponent(){
String a0=this.getAttributes().get("attr0");
}
}
the taglib.xml's tag contains the attr0 attribute and the tags usage is :
<abc:mycomponent attr0="helloworld"></abc:mycomponent>
So my question is what causes the issue and how to handle it?
Thanks
I guess I could figure out how to workaround the NPE issue...
I don't use constructor's body to get attributes but get attribute in overriden encodeBegin method;
#Override
public void encodeBegin(FacesContext context) throws IOException {
String id=this.getAttributes().get("id").toString();
System.out.println("debug: attribute id="+id);
String color=this.getAttributes().get("attr0").toString();
System.out.println("debug: attribute attr0="+attr0);
HtmlOutputLabel label0=new HtmlOutputLabel();
label0.setValue("attribute attr0 value="+attr0);
this.getChildren().add(label0);
super.encodeBegin(context);
}
So it's working and I think I am OK with this solution; I am not sure is it the most optimal way so please do share some snippets...
I've created a custom component which extends the UIComponentBase abstract class, so when I use this component: <test:mycomponent /> It works as espected.
I'm creating another custom component and I want to use the previously created component in this one, so I tried:
#Override
public void encodeBegin(FacesContext context) throws IOException {
ResponseWriter writer = context.getResponseWriter();
writer.startElement("mycomponent", this);
writer.endElement("mycomponent");
}
I new this was a long shot, since startElement just creates a tag with the given component name i.e mycomponent, so I searched around and found:
UIComponentBase mycomponent =
(UIComponentBase)context.getApplication().createComponent("mycomponent");
If this is correct, how does one add the component ? I'm using JSF 2.2
A link to where I can find more on this would be greatly apreciated also.
I need to add a component (UIParameter) to a HtmlCommandLink component dinamically through a Phase Listener.
What I want to achieve is that every element <h:link outcome="out"> renders as <a href="out_url_parsed + ?param=paramvalue">.Where "param" is my component.
I've tried using this
private void addElement(final PhaseEvent event, final Class clazz, final UIComponent component) {
final FacesContext fcontext = event.getFacesContext();
UIViewRoot root = fcontext.getViewRoot();
if (root == null) {
return;
}
root.visitTree(new FullVisitContext(fcontext), new VisitCallback() {
#Override
public VisitResult visit(VisitContext context, UIComponent target) {
if (clazz.isInstance(target)) {
LOGGER.info("Element Found");
UIParameter parameter = new UIParameter();
parameter.setValue("willberonadom");
parameter.setId("sessiontoken");
target.getChildren().add(parameter);
}
return VisitResult.ACCEPT;
}
});
}
But it's not working. The element is actually found on the tree but the UIParameter does not render.
I've found that the UIViewRoot only has child elements after RENDER_RESPONSE phase. So i think this is why my added element is not rendered at the end of the process.
I'm sure I can add this param editing the views but I don't want to do that since it must be present on all h:link in the application and must be present on any other new added too. So I consider this as a better approach to avoid missing tags
On a similar case I've managed to add input hidden elements to every form on view with this code...
HtmlInputHidden hiddenToken = new HtmlInputHidden();
hiddenToken.setId("sessiontoken");
hiddenToken.setValue("willberandom");
hiddenToken.setRendered(true);
root.addComponentResource(event.getFacesContext(), hiddenToken,"form");
But it doesn't work on anchor tags
There are several mistakes:
You want to add a parameter to a HtmlCommandLink component which represents <h:commandLink>, but you're giving an example with <h:link>, which is represented by HtmlOutcomeTargetLink. What exactly do you want?
A PhaseListener on beforePhase() of RENDER_RESPONSE may be too late on GET requests which would only build the view for the first time during render response. At the moment your PhaseListener runs, the UIViewRoot would have no children at all. You'd better hook on view build time instead. For that, a SystemEventListener on PostAddToViewEvent is the best suitable.
You're setting the parameter name as an id instead of name. Use UIParameter#setName() instead of UIParameter#setId().
Provided that you actually meant to add them to <h:link> components, then here's a kickoff example how you can achieve that with a SystemEventListener.
public class YourSystemEventListener implements SystemEventListener {
#Override
public boolean isListenerForSource(Object source) {
return source instanceof HtmlOutcomeTargetLink;
}
#Override
public void processEvent(SystemEvent event) throws AbortProcessingException {
UIParameter parameter = new UIParameter();
parameter.setName("sessiontoken");
parameter.setValue("willberonadom");
((UIComponent) event.getSource()).getChildren().add(parameter);
}
}
(if you actually want to apply them on <h:commandLink> as well, just extend the isListenerForSource() check with a || source instanceof HtmlCommandLink)
In order to get it to run, register it as follows in faces-config.xml:
<application>
<system-event-listener>
<system-event-listener-class>com.example.YourSystemEventListener</system-event-listener-class>
<system-event-class>javax.faces.event.PostAddToViewEvent</system-event-class>
</system-event-listener>
</application>
I have a problem when dynamically instantiating a PF 3.4.2 AutoComplete component.
The component intially renders ok, its value is refreshed on partial processing
but the suggestions are never displayed.
I am instantiating this control the following way :
AutoComplete ac = (AutoComplete) context.getApplication().createComponent(AutoComplete.COMPONENT_TYPE);
final String varName = "p";
ValueExpression ve = JSFUtils.createValueExpression("#{minContext.selected.sen}"), Sen.Type);
ac.setValueExpression("value", ve);
ac.setForceSelection(true);
ac.setVar(varName);
ValueExpression itemLabel = JSFUtils.createValueExpression("#{sc:senLibelle(p)}"), String.class);
ac.setValueExpression("itemLabel", itemLabel);
ValueExpression itemValue = JSFUtils.createValueExpression("#{" + varName + "}");
ac.setValueExpression("itemValue", itemValue);
MethodExpression completeMethod = JSFUtils.createMethodExpression("#{senUtils.completeAllSens}", List.class,new Class[]{String.class});
ac.setCompleteMethod(completeMethod);
then adding it to parent control using
getChildrens().add(ac);
The parent component is a derivation of PF PanelGrid. I use this approach successfully to generate various edition panels and it works like a charm. But I can not figure why it does not with autoComplete.
The parent control looks like :
#FacesComponent(SenatDataTableEntryDetail.SENAT_COMPONENT_TYPE)
public class SenatDataTableEntryDetail extends PanelGrid {
/** Leaving renderer unchanged, so that PF renderer for PanelGrid is used.
*/
public static final String SENAT_COMPONENT_FAMILY = "fr.senat.faces.components";
public static final String SENAT_COMPONENT_TYPE = SENAT_COMPONENT_FAMILY + ".SenatDataTableEntryDetail";
private enum PropertyKeys { mapper, bean; }
#Override
public void encodeBegin(FacesContext context) throws IOException {
super.encodeBegin(context);
addDynamicChildren(context);
}
#Override
public boolean getRendersChildren()
{
return true;
}
...
private Boolean isInitialized() {
return (Boolean)getStateHelper().eval(SENAT_INITIALIZED,false);
}
private void setInitialized(Boolean param) {
getStateHelper().put(SENAT_INITIALIZED, param);
}
private void addDynamicChildren(FacesContext context) throws IOException {
if(isInitialized()) {
return;
}
setInitialized(true);
/* components are instiated and added as children only once */
}
}
It just adds children to the panel grid.
The other aspects of custom component declaration (in taglib and so on) are ok.
The problem doest not seem to be in EL expressions, completeMethod definition, etc. If I include in my test xhtml page an instanciation of the p:autoComplete with the very same parameters, it just works as expected :
<p:autoComplete value="#{minContext.selected.sen}" forceSelection="true"
var="p" itemLabel="#{sc:senLibelle(p)}" itemValue="#{p}"
completeMethod="#{senUtils.completeAllSens}"/>
I noticed that the PF AutoComplete component is a bit special as it renders differently
when a query is detected. See AutoCompleteRenderer source code in http://primefaces.googlecode.com/files/primefaces-3.4.2.zip .
In the "dynamically instantiated" case, the decode method of this component is not called. I failed to find why those last days, but did not succeed.
I look forward for your suggestions on what to check to correct this annoying "bug".
So, the problem was in id generation (see the two comments).
The beginning of component instantiation becomes :
AutoComplete ac = (AutoComplete) context.getApplication().createComponent(AutoComplete.COMPONENT_TYPE);
ac.setParent(this);
ac.setId(...some application specific unique id generation...);
final String varName = "p";
This way, the naming container is properly taken in account on client id generation.