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: