JSF: DataTable and CommandLink
Posted by Dominik Wed, 07 Feb 2007 13:39:00 GMT
Like a lot of other programmers who are forced to work with JSF, I fell into THE TRAP!
If you are using ManagedBeans in request scope, you get problems with CommandLinks inside DataTables.
DataTables are one thing I really like about JSF, and CommandLinks often come in handy as well. But when you put a CommandLink inside a DataTable, e. g., to select the entry of the row in which the CommandLink is, you get bitten. That is, if you want ManagedBeans with request scope. The action which should be triggered by the CommandLink is never triggered, the page is simply rendered again.
The reason for this behaviour is that the DataTable modifies the id of the CommandLink during renderering, but the CommandLink does not know that it was rendererd with a different id. During the decoding of the request which was triggered by clicking the CommandLink, the ComandLinkRenderer looks at a hidden form parameter. If the value of that form parameter equals the id of the CommandLink, an action is queued. If not, nothing is done.
Since the DataTable changes the ids, the value of the hidden form parameter does not match the id of the CommandLink.
So I wrote my own CommandLinkRenderer, overwrote the decode method and patched it so that the id match up again. When I want a CommandLink in a DataTable, I then specify that it should use my CommandLinkRenderer.
Here is the source of the decode method:
@Override
public void decode(FacesContext context, UIComponent component)
{
if (context == null || component == null)
{
throw new NullPointerException(Util
.getExceptionMessageString(Util.NULL_PARAMETERS_ERROR_MESSAGE_ID));
}
if (log.isTraceEnabled())
{
log.trace("Begin decoding component " + component.getId());
}
UICommand command = (UICommand) component;
// If the component is disabled, do not change the value of the
// component, since its state cannot be changed.
if (Util.componentIsDisabledOnReadonly(component))
{
if (log.isTraceEnabled())
{
log.trace("No decoding necessary since the component "
+ component.getId() + " is disabled");
}
return;
}
String clientId = command.getClientId(context), paramName = getHiddenFieldName(
context, command);
Map requestParameterMap = context.getExternalContext()
.getRequestParameterMap();
String value = (String) requestParameterMap.get(paramName);
clientId = clientId.substring(0, clientId.lastIndexOf(":"));
value = value.substring(0, value.lastIndexOf(":"));
if (value == null || value.equals("") || !clientId.equals(value))
{
return;
}
ActionEvent actionEvent = new ActionEvent(component);
component.queueEvent(actionEvent);
if (log.isDebugEnabled())
{
log.debug("This command resulted in form submission "
+ " ActionEvent queued " + actionEvent);
}
if (log.isTraceEnabled())
{
log.trace("End decoding component " + component.getId());
}
return;
}
The diff to the original method is:
diff CommandLinkRenderer.java PatchedCommandLinkRenderer.java
124a125,126
> clientId = clientId.substring(0, clientId.lastIndexOf(":"));
> value = value.substring(0, value.lastIndexOf(":"));
It works form me, maybe others get some benefit from it as well.
(P.S. this is crossposted to http://forum.java.sun.com/thread.jspa?threadID=5116147)


Can you be a bit more specific on the environment you are working in, espacially concerning the JSF implementation (myfaces, RI)? I never encountered the described “trap”, but quiet frankly this may be because everytime I use dataTables I work a) with statefull managed beans (session scope or Seam/EJB3 conversational Beans or SFSB) or b) use a request-parameter to submit the identifier of the row.
you’re not “Valid XHTML 1.0”, click on the link to test your page :-)
good one……
Hi Frank,
to be more specific:
I’m currently using:
I know that this doesn’t happen with statefull managed beans, but currently we try to get along stateless. Passing the request parameter of the row (or the identifier of the object in the row) works equally well, it is just that I needed a post request, because the action will remove an item from a list. I tried passing a parameter directly, but the invoke application phase is not run when no action was detected. So the workaround was to simply make CommandLink work inside a DataTable with request scoped managed beans.
evilmonkey:
you got me there. It’s true that this page is not valid xhtml :-[ I’ll fix it, that’s a promise. It’s just one “onClick” attribute.
Excuse me, but i don’t see the implemention of getHiddenFieldName() methode, because i tested this code and it make an compile error on this method. help please.
Hi Réda,
getHiddenFieldName() is a protected method on CommandLinkRenderer. If you extend CommandLinkRenderer it should be visible. The workaround is for the reference implementation of Sun, maybe other render kits don’t provide that method.
hi all, in my application the commandlink, actionlistener and datatable work together in a request-scope bean when i use the attribute
preserveDataModel=”true”
of the datatable
good luck!
mbnmbn,b,b
Thanks
It is wonderful
It is Samson
hi i have an where i am using tiles frame work,jsf ..i was displaying some links in menu , when i try to click on the any link it has to do some action …. here i used comandlinks where as that to be tiles frame work
How can i overwrite CommandLinkRender decode method (jsf sun ri 1.1_02)??? Is there any method without building all the jsf ri sources????
i am trapped in this problem and i found no solution to that the method you suggested where it may be placed
You can use the tag (from tomahawk) , in the same page where the table is. It works for me.
the tag is saveState