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.
Related
I've replaced the f:ajax tag with an homemade solution that doesn't put inline script. It works wonder for actionButton. However I cannot make it work for a listener on a panelGroup. The reason is that it is specified nowhere what the bean target method resulting from the ajax request should be. In other words with a commandButton I can specify the target bean method in action, but there is no such attribute for panelGroup; as I don't want to use f:ajax listener, I want to replace it.
<h:commandButton data-widget="jsfajax" value="ajax" action="#{someAction}"/>
$(document).ready(function(){
(function(widgets){
document.body.addEventListener("click", function(e) {
var w = e.target.getAttribute("data-widget");
if(w){
e.preventDefault();
widgets[w](e.target);
}
});
})(new Widgets);
});
function Widgets(){
this.jsfajax = function jsfajax(elem){
if(elem.id == ""){
elem.id = elem.name;
}
mojarra.ab(elem,"click",'action','#form',0);
}
}
This works.
But this obviously doesn't (it does but it doesn't invoke anything) :
<h:panelGroup>
<f:passThroughAttribute name="data-widget" value="jsfajax"/>
Click here
</h:panelGroup>
But this does :
<h:panelGroup>
<f:ajax event="click" listener="#{someAction}"/>
Click here
</h:panelGroup>
Both those panelGroup result in the same HTML output, so I assume it's the jsf container which "remembers" the click on that panelGroup is linked to #{someAction}.
What I'd like to do is recreate that link without using f:ajax listener. At the moment I've to use an hidden commandButton which is less elegant.
So maybe a composite component panelGroup which would save the "action link", I've no idea.
What you want to achieve is only possible on UICommand components, not on ClientBehaviorHolder components. One solution would be to create a custom component extending HtmlCommandLink which renders a <div> instead of <a> and use it like so <your:div action="#{bean.action}">.
The most ideal solution would be to replace the standard renderers. E.g. for <h:panelGorup>:
<render-kit>
<renderer>
<component-family>javax.faces.Panel</component-family>
<renderer-type>javax.faces.Group</renderer-type>
<renderer-class>com.example.YourPanelGroupRenderer</renderer-class>
</renderer>
</render-kit>
Basically, those renderers should skip rendering <f:ajax>-related on* attributes and instead render your data-widget attribute (and preferably also other attributes representing existing <f:ajax> attributes such as execute, render, delay, etc). You should also program against the standard API, not the Mojarra-specific API. I.e. use jsf.ajax.request() directly instead of mojarra.ab() shortcut.
This way you can keep your view identical conform the JSF standards. You and future developers would this way not even need to learn/think about a "proprietary" API while writing JSF code. You just continue using <h:panelGroup><f:ajax>. You simply plug in the custom renders and script via a JAR in webapp and you're done. That JAR would even be reusable on all other existing JSF applications. It could even become popular, because inline scripts are indeed considered poor practice.
It's only quite some code and not necessarily trivial for a starter.
A different approach is to replace the standard response writer with a custom one wherein you override writeAttribute() and check if the attribute name starts with on and then handle them accordingly the way you had in mind. E.g. parsing it and writing a different attribute. Here's a kickoff example which also recognizes <h:panelGroup><f:ajax>.
public class NoInlineScriptRenderKitFactory extends RenderKitFactory {
private RenderKitFactory wrapped;
public NoInlineScriptRenderKitFactory(RenderKitFactory wrapped) {
this.wrapped = wrapped;
}
#Override
public void addRenderKit(String renderKitId, RenderKit renderKit) {
wrapped.addRenderKit(renderKitId, renderKit);
}
#Override
public RenderKit getRenderKit(FacesContext context, String renderKitId) {
RenderKit renderKit = wrapped.getRenderKit(context, renderKitId);
return (HTML_BASIC_RENDER_KIT.equals(renderKitId)) ? new NoInlineScriptRenderKit(renderKit) : renderKit;
}
#Override
public Iterator<String> getRenderKitIds() {
return wrapped.getRenderKitIds();
}
}
public class NoInlineScriptRenderKit extends RenderKitWrapper {
private RenderKit wrapped;
public NoInlineScriptRenderKit(RenderKit wrapped) {
this.wrapped = wrapped;
}
#Override
public ResponseWriter createResponseWriter(Writer writer, String contentTypeList, String characterEncoding) {
return new NoInlineScriptResponseWriter(super.createResponseWriter(writer, contentTypeList, characterEncoding));
}
#Override
public RenderKit getWrapped() {
return wrapped;
}
}
public class NoInlineScriptResponseWriter extends ResponseWriterWrapper {
private ResponseWriter wrapped;
public NoInlineScriptResponseWriter(ResponseWriter wrapped) {
this.wrapped = wrapped;
}
#Override
public ResponseWriter cloneWithWriter(Writer writer) {
return new NoInlineScriptResponseWriter(super.cloneWithWriter(writer));
}
#Override
public void writeAttribute(String name, Object value, String property) throws IOException {
if (name.startsWith("on")) {
if (value != null && value.toString().startsWith("mojarra.ab(")) {
super.writeAttribute("data-widget", "jsfajax", property);
}
}
else {
super.writeAttribute(name, value, property);
}
}
#Override
public ResponseWriter getWrapped() {
return wrapped;
}
}
The most important part where you have your freedom is the writeAttribute() method in the last snippet. The above kickoff example just blindly checks if the on* attribute value starts with Mojarra-specific "mojarra.ab(" and then instead writes your data-widget="jsfajax". In other words, every single (naturally used!) <f:ajax> will be rewritten this way. You can continue using <h:commandLink><f:ajax> and <h:panelGroup><f:ajax> the natural way. Don't forget to deal with other <f:ajax> attributes while you're at it.
In order to get it to run, register as below in faces-config.xml:
<factory>
<render-kit-factory>com.example.NoInlineScriptRenderKitFactory</render-kit-factory>
</factory>
You only still need to take into account existing implementation-specific details (fortunately there are only two: Mojarra and MyFaces).
See also:
How do I determine the renderer of a built-in component
Where in the contexts can I find the information for something built with a <ui:define>? I want to access a page title that has been defined with <ui:define name="title">Some title</ui:define> in my bean.
To illustrate my question, I can access a variable defined with
<ui:param name="myVariable" value="This is my variable!"/>
by looking at the variable mapper in the EL context, like this
VariableMapper variableMapper = elContext.getVariableMapper();
String myVariable = variableMapper.resolveVariable("myVariable").getValue(elContext).toString();
This works for <ui:param>, but how is it done for <ui:define>?
This is not possible via standard API. Xtreme Biker has posted a brilliant trick whereby a "default" <ui:param> value is specified inside the <ui:insert> which would be overriden (and thus absent) when a <ui:define> is actually specified as answer on Test if ui:insert has been defined in the template client
A (hacky) alternative would be to create a custom taghandler for the job. The <ui:define>s are by their name collected in Map handlers field of the CompositionHandler taghandler class behind <ui:composition>. This is (unfortunately) implementation specific, Mojarra and MyFaces have their own implementations whereby Mojarra has named the field handlers and MyFaces _handlers.
As the field is just protected, cleanest would be to just extend the CompositionHandler taghandler class and expose at least the keyset in the apply() method as attribute of FaceletContext. However, as the CompositionHandler class itself is declared final, we can't subclass it. Therefore, we can't go around wrapping it as a delegate and use some reflection hackery to grab the field anyway.
Here's a kickoff example based on Mojarra which collects all declared <ui:define> handler names in a Map<String, Boolean> so that you can nicely use them in EL like so #{defined.foo ? '...' : '...'} respectively #{not defined.foo ? '...' : '...'}.
public class DefineAwareCompositionHandler extends TagHandlerImpl implements TemplateClient {
private CompositionHandler delegate;
private Map<String, Boolean> defined;
#SuppressWarnings("unchecked")
public DefineAwareCompositionHandler(TagConfig config) {
super(config);
delegate = new CompositionHandler(config);
try {
Field field = delegate.getClass().getDeclaredField("handlers");
field.setAccessible(true);
Map<String, DefineHandler> handlers = (Map<String, DefineHandler>) field.get(delegate);
if (handlers != null) {
defined = new HashMap<>();
for (String name : handlers.keySet()) {
defined.put(name, true);
}
}
}
catch (Exception e) {
throw new FaceletException(e);
}
}
#Override
public void apply(FaceletContext ctx, UIComponent parent) throws IOException {
ctx.setAttribute("defined", defined);
delegate.apply(ctx, parent);
}
#Override
public boolean apply(FaceletContext ctx, UIComponent parent, String name) throws IOException {
return delegate.apply(ctx, parent, name);
}
}
Register it as follows in your custom my.taglib.xml:
<tag>
<tag-name>composition</tag-name>
<handler-class>com.example.DefineAwareCompositionHandler</handler-class>
</tag>
You could make use of it as below:
<my:composition
xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://xmlns.jcp.org/jsf/core"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
xmlns:my="http://example.com/ui"
>
<ui:insert name="foo">
...
</ui:insert>
<div class="#{defined.foo ? 'style1' : 'style2'}">
...
</div>
</my:composition>
Again, this is hacky (as it's implementation specific), I'd not recommend using it.
See also:
Custom Facelet component in JSF
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 custom component that look as follow
<custom:container>
<custom:checkbox index="0"/>
<custom:checkbox index="1"/>
</custom:container>
so when encodeBegin first call, it will show hit the tag <custom:container>, and it will try to save this component cliend id,
private String containerClientId;
public void encodeBegin(FacesContext context, UIComponent component){
if (component instanceof ManyCheckboxContainer) {
containerClientId = component.getClientId(context);
return;
}
}
so encodeEnd get called when I hit <custom:checkbox index="0"/>, like this
public void encodeEnd(FacesContext context, UIComponent component) throws IOException {
...
if (component instanceof Checkbox) {
renderCheckbox(context, (Checkbox) component);
}
...
}
protected void renderCheckbox(FacesContext facesContext, InforRadio radio) throws IOException {
...
UIComponent uiComponent = radio.findComponent(containerClientId);
if(uiComponent == null){
//throw error
}
...
}
If I DO NOT have this custom component inside composite component then everything work great, but once I put it in a composite component, radio.findComponent(containerClientId); return null. Is this a bug in mojarra? I test this under 2.1.10 and 2.1.11, same behavior.
EDIT
So I take that back, this behavior happen when my custom component is inside two nested NamingContainer, so something like this
<h:form id="myForm">
<f:subView id="myView">
<custom:container id="myCustom">
<custom:checkbox index="0"/>
<custom:checkbox index="1"/>
</custom:container>
</f:subView>
</h:form>
so in this case the client id (that return by component.getClientId(context)) for <custom:container> is myForm:myView:myCustom, but inside Mojarra, the findComponent method has this
public UIComponent findComponent(String expr) {
...
else if (!(base instanceof NamingContainer)) {
// Relative expressions start at the closest NamingContainer or root
while (base.getParent() != null) {
if (base instanceof NamingContainer) {
break;
}
base = base.getParent();
}
...
}
so it looks for the next ancestor NamingContainer, which in my case is the f:subView not the h:form. It then parse the client id, loop through it, each time passing piece of the id to the UIComponent findComponent(UIComponent base, String id, boolean checkId). So the first time in, this method take form3 as id and current UIComponent is f:subView, it search for all its facets and children to see if any component match form3, of course, none will match since form3 is the parent of f:subView in my structure. So null is return. Is this Mojarra bugs or am I doing some wrong. I thought that the client id is relative from the next NamingContainer ancestor instead of all the way to the root of the NamingContainer? Am I wrong on that?
After reading the docs it seems to me that Mojarra got it right.
According to the docs you should place a seperator (a colon) in front of your search expression if you want to do an "absolute" search from the root of your tree.
Otherwise it will do a relative search from the component itself if it is a NamingContainer or the first parent that is a NamingContainer.
BTW: The docs I linked to appear to be the same as the official docs distributed with the specs. Official specs are here.
I know about startElement, endElement, and writeAttribute methods on ResponseWriter. My problem is that I want to for example output a h:commandLink by declaring it like HtmlCommandLink link = new HtmlCommandLink(); .
How can I output other UIComponents like this in my own component? I might want to use some RichFaces ajax stuff in my components aswell so hoping I can avoid making it all by scratch.
Edit: What I'm trying to do is create my own tag library with the following tag <myTags:commentTree>. Every comment have a reply button, when the reply button is clicked I render the reply form beneath the comment. Once that is rendered, I would like to output for example the richfaces <a4j:commandButton> component. This have to be done inside my own java tag file which Ive called for CommentsTreeUI.java.
Normally I output all my elements that display the forms and buttons with writer.startElement("input", myComponent); writer.writeAttribute("type", "button", null); but if I could instead do for example startElement("a4j:commandbutton", myComponent) that would help my ALOT since it has all the built in ajax features etc.
Any clues?
This problem was solved by adding new components by using
HtmlCommandButton button = new HtmlCommandButton();
button.encodeAll(context);
You can do something like this:
HtmlCommandLink link = new HtmlCommandLink();
getChildren().add(link);
It does depend on what you want to do with the child components though i.e. if you want them surrounded with custom HTML (in an HTML list, for example) you will need something a bit more complex.
One approach to making composite controls is to use the binding attribute to associate the tag with your own code:
<f:view>
<h:form>
<h:panelGroup binding="#{compositeControlBean.panelGrid}" />
</h:form>
</f:view>
The bean configuration in faces-config.xml:
<managed-bean>
<managed-bean-name>compositeControlBean</managed-bean-name>
<managed-bean-class>
composite.CompositeControlBean
</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
</managed-bean>
The bean code:
/**
* Configure this bean in request scope as "compositeControlBean".
*/
public class CompositeControlBean {
private transient UIComponent panelGrid;
public UIComponent getPanelGrid() {
if (panelGrid == null) {
panelGrid = createCompositePanel();
}
return panelGrid;
}
public void setPanelGrid(UIComponent panelGrid) {
this.panelGrid = panelGrid;
}
private UIComponent createCompositePanel() {
initContextMemebers();
UIComponent commandLink = createCommandLink();
String id = view.createUniqueId();
UIComponent panelGrid = application
.createComponent("javax.faces.HtmlPanelGroup");
panelGrid.setId(id);
panelGrid.setRendererType("javax.faces.Group");
panelGrid.getChildren().add(commandLink);
return panelGrid;
}
private UIComponent createCommandLink() {
// create control
String id = view.createUniqueId();
UIComponent commandLink = application
.createComponent("javax.faces.HtmlCommandLink");
commandLink.setId(id);
commandLink.setRendererType("javax.faces.Link");
// set attributes (bind to printHello method)
Map<String, Object> attributes = commandLink
.getAttributes();
MethodExpression action = expressionFactory
.createMethodExpression(elContext,
"#{compositeControlBean.printHello}",
String.class, new Class<?>[0]);
attributes.put("value", "print hello");
attributes.put("actionExpression", action);
return commandLink;
}
private transient FacesContext context;
private transient Application application;
private transient ELContext elContext;
private transient ExpressionFactory expressionFactory;
private transient UIViewRoot view;
private void initContextMemebers() {
context = FacesContext.getCurrentInstance();
application = context.getApplication();
elContext = context.getELContext();
expressionFactory = application.getExpressionFactory();
view = context.getViewRoot();
}
public String printHello() {
System.out.println("Hello");
return null;
}
}