JSF 2.0 Composite components - ajax render parameter OUTSIDE component definition
Consider a simple composite component which takes an action parameter of some sort - a simple link 'prettifier', for example. I want to 'ajaxify' it.
<composite:interface>
<composite:attribute name="act" method-signature="java.lang.String action()"></composite:attribute>
<composite:attribute name="text" required="true"></composite:attribute>
<composite:clientBehavior name="click" event="action" targets="l"/> </composite:interface>
<composite:implementation>
<h:commandLink id="l" act="#{cc.attrs.action}" immediate="true"> <b>#{cc.attrs.text}</b> </h:commandLink> </composite:implementation>
I expose an event through client 开发者_Go百科behaviour. I use it like this:
<h:panelGroup layout="block" id="outside">
#{mybean.otherdata} <br/>
<mc:mylink text="Click click" action="#{mybean.click}" >
<f:ajax event="click" render="outside"/>"
</mc:mylink><br/>
</h:panelGroup>
You can see what I want to do: I want to do an ajax render outside the composite definition; just setting render to "outside" gives the dreaded <f:ajax> contains an unknown id
error.
Yes, I am aware of naming containers, and I know we can prepend with a colon and specify an absolute path, but that's quite clunky. If I wrap it up in several more layers (which is the whole point) I'd have to chain these references together manually.
Can I make some sort of relative reference like render="../outside"
to skip the reference to the parent container of the component?
I did a jsf 1 app with a4j, and this pattern was used all over the place.
In JSF 2.0, you can use the implicit cc
and component
objects inside EL. In order to fetch the full client ID for any component do:
#{component.clientId}
To retrieve the client ID for a composite component do:
#{cc.clientId}
In the same way, you can also fetch parent with #{cc.parent}
. In this case, this is probably what you want. For a bit longer answer see Acquire full prefix for a component clientId inside naming containers with JSF 2.0.
You can use render="@all"
or render="@form"
to render everything, or the whole from respectively.
Alternatively, you can pass in the absolute id of what you want to update as a parameter into your component. This maintains flexibility, without unnecessarily re-rendering too much of the page.
I've found an even better solution that I'm happy with which makes use of the implicit 'component' mapping in the template. It allows basic relative mapping back from the composite component using ../
filesystem notation. It could be expanded to handle more, but it's all I need. It works for executing the parameter, as well.
I'd like to thank me for figuring it out! Again, am I misunderstanding something? I shouldn't need to be figuring this out.
Usage:
<mc:mylink text="Click me" action="#{blah.myaction}" render="../../pg" />
Implementation:
<composite:implementation>
<h:commandLink id="l" action="#{cc.attrs.action}" immediate="true">
<f:ajax render="#{pathProcessor.pathProcess(3, cc.attrs.render, component.clientId)}" />
#{simon.rand} . <b>#{cc.attrs.text}</b>
</h:commandLink>
.....
pathprocessor method:
public static String pathProcess(int currentNesting, String path, String clid) {
//System.out.println("clientid is "+clid);
//System.out.println("path is "+path);
//System.out.println("nesting is "+currentNesting);
String backTok="../";
String sepchar=":";
StringBuilder src=new StringBuilder(path);
int backs=0;
int inc=backTok.length();
while (src.indexOf(backTok,backs*inc)==backs*inc)
{
backs++;
}
//get rid of the source
String suffix=src.substring(backs*inc);
//add in internal nesting
backs+=(currentNesting-4);
StringTokenizer st=new StringTokenizer(clid,sepchar);
StringBuilder sb=new StringBuilder(sepchar);
for (int x=0;x<st.countTokens()-backs;x++)
{
sb.append(st.nextToken()).append(sepchar);
}
//backtracked path
String p=sb.toString();
//add suffix to the backtracked client path to get full absolute path
String abs=p+suffix;
return abs;
}
errrr - I don't know why I used the StringBuilder
when counting the backs, but you get the idea. It's something like that.
After some tinkering, here's one solution:
Put a listener on the event:
<f:ajax event="click" listener="#{mycomp.listen}" render="#{mycomp.getParId('outside')}"/>"
Implementation:
public void listen(AjaxBehaviorEvent event) {
String clid=event.getComponent().getClientId();
StringTokenizer st=new StringTokenizer(clid,":");
StringBuilder sb=new StringBuilder(":");
for (int x=1;x<st.countTokens();x++)
{
sb.append(st.nextToken()).append(":");
}
parId=sb.toString();
}
public String getParId(String suff) {
//must precheck as id is prevalidated for existence. if not set yet set to form
if (parId==null)
{
return "@form";
}
return parId+suff;
}
Even so, why would you have to do this?
cc.parent.cliendId resolves the parent composite component instead of the immediate parent. Obtaining the clientId of the parent of a JSF2 composite component
<composite:interface>
<cc:attribute name="render" type="java.lang.String" default="@this"/>
<cc:attribute name="action" method-signature="java.lang.String action()">
</composite:interface>
</composite:implementation>
<h:form>
<h:commandLink action="#{cc.attrs.action}" value="Click">
<f:ajax render="{cc.attr.render}" execute="@form"
</h:commandLink>
</h:form>
</composite:implementation>
So you can decide by self which component you going to render.
<h:panelGroup layout="block" id="outside">
#{mybean.otherdata}
<mc:mylink action="#{mybean.click}" render=":outside"/>
</h:panelGroup>
精彩评论