I have a web application that uses optional modules. The modules are implemented as Web Fragment projects, their jars may or may not be deployed with the war depending on the build profile.
A module can contain it's own module.taglib.xml with a http://company.com/module namespace and some tags.
The war xhtml templates use module tags like this:
<ui:composition ... xmlns:mod="http://company.com/module">
<c:if test="#{moduleDeployed}">
<mod:someTag />
</c:if>
Problems.
When the module is not deployed, the war pages work fine, but in ProjectStage.Development I get FacesMessage warnings:
Warning: This page calls for XML namespace
http://company.com/module declared with prefix mod but no
taglibrary exists for that namespace.
As far as I can see, JSF specification doesn't define what happens, when a template uses a nonexistent tag library. So with my current approach war pages can stop working after an upgrade or a switch to a different JSF implementation.
Questions.
Is there a (not very ugly) way to disable this specific warning?
Is there a better approach to using optional facelet tag libraries?
As of now I plan to disable the warning anyway I can: e.g. override Messages renderer and check message string if I have to. If the problem 2 manifests, make the build supply placeholder taglib.xml files for not deployed modules.
Even though placeholder taglibs seemed like a pretty good solution, they also seemed harder to implement and maintain.
So in the end I went with filtering the messages. This is likely Mojarra specific: the message text, the fact that the iterator allows removal (this isn't forbidden by the spec, but it's not required either). It's known to work with Mojarra 2.2.8 to 2.2.13.
public class SuppressNoTaglibraryExistsFacesMessage implements SystemEventListener {
private static final Pattern PTTRN_NO_TAGLIBRARY_EXISTS_FOR_NAMESPACE =
Pattern.compile("Warning: This page calls for XML namespace \\S+ declared with "
+ "prefix \\S+ but no taglibrary exists for that namespace.");
#Override
public void processEvent(SystemEvent event) {
Iterator<FacesMessage> messages = FacesContext.getCurrentInstance().getMessages();
while (messages.hasNext()) {
String messageSummary = messages.next().getSummary();
if (PTTRN_NO_TAGLIBRARY_EXISTS_FOR_NAMESPACE.matcher(messageSummary).matches()) {
messages.remove();
}
}
}
#Override
public boolean isListenerForSource(Object source) {
return true;
}
}
Bind the listener only in Development project stage.
public class SubscribeListenersAfterApplicationPostConstructListener
implements SystemEventListener {
#Override
public void processEvent(SystemEvent event) throws AbortProcessingException {
Application application = (Application) event.getSource();
if (ProjectStage.Development.equals(application.getProjectStage())) {
application.subscribeToEvent(PostAddToViewEvent.class, UIViewRoot.class,
new SuppressNoTaglibraryExistsFacesMessage());
}
}
#Override
public boolean isListenerForSource(Object source) {
return source instanceof Application;
}
}
And in faces-config.xml:
<system-event-listener>
<system-event-listener-class><packages>.SubscribeListenersAfterApplicationPostConstructListener</system-event-listener-class>
<system-event-class>javax.faces.event.PostConstructApplicationEvent</system-event-class>
</system-event-listener>
Related
I'm dynamically injecting some JS into all my pages, and this works fine in Mojarra, but I've found out it fails in myfaces.
My event listener is configured as:
<application>
<system-event-listener>
<system-event-listener-class>a.b.HeadResourceListener</system-event-listener-class>
<system-event-class>javax.faces.event.PostAddToViewEvent</system-event-class>
<source-class>javax.faces.component.UIOutput</source-class>
</system-event-listener>
</application>
With code looking something like:
public class HeadResourceListener implements SystemEventListener {
#Override
public boolean isListenerForSource(Object source) {
return "javax.faces.Head".equals(((UIComponent) source).getRendererType());
}
#Override
public void processEvent(SystemEvent event) {
UIComponent outputScript = new UIOutput();
outputScript.setRendererType("javax.faces.resource.Script");
UIOutput content = new UIOutput();
content.setValue("var abc='';");
outputScript.getChildren().add(content);
context.getViewRoot().addComponentResource(context, outputScript, "head");
}
}
Unfortunately, with myfaces, the rendererType of the source is never javax.faces.Head (I only found occurrences of javax.faces.resources.Script and javax.faces.resources.Stylesheet)
Is there any specific reason why the behaviour differs here?
Any suggestions for another solution maybe?
EDIT
As suggested, when linking this listener to source-class , it is triggered in myfaces. However, on postback, I get duplicate id errors...
Caused by: org.apache.myfaces.view.facelets.compiler.DuplicateIdException: Component with duplicate id "j_id__v_7" found. The first component is {Component- Path : [Class: javax.faces.component.UIViewRoot,ViewId: /user/login.xhtml][Class: org.apache.myfaces.component.ComponentResourceContainer,Id: javax_faces_location_head][Class: javax.faces.component.UIOutput,Id: j_id__v_7]}
at org.apache.myfaces.view.facelets.compiler.CheckDuplicateIdFaceletUtils.createAndQueueException(CheckDuplicateIdFaceletUtils.java:148)
at [internal classes]
at javax.faces.application.ViewHandlerWrapper.renderView(ViewHandlerWrapper.java:73)
at org.apache.myfaces.tomahawk.application.ResourceViewHandlerWrapper.renderView(ResourceViewHandlerWrapper.java:169)
at javax.faces.application.ViewHandlerWrapper.renderView(ViewHandlerWrapper.java:73)
It's a bug in MyFaces.
JSF 2.3 specification says the following in table 9.2:
TABLE 9-2 Standard HTML RenderKit Tag Library
getComponentType() getRendererType()
javax.faces.Output javax.faces.Head
As per chapter 4.1.10.1 of the same specification, javax.faces.Output maps to javax.faces.component.UIOutput.
4.1.10.1 Component Type
The standard component type for UIOutput components is “javax.faces.Output”.
So, the <h:head> must be an instance of UIOutput.
If we look back at table 9.2, the javax.faces.Output can have multiple renderers, so you can indeed only listen on <source-class> of javax.faces.component.UIOutput and you'd have to manually inspect its renderer type to be javax.faces.Head. Your HeadResourceListener is correct.
See also:
How to load a JavaScript file on all pages programmatically
I have a jsf jar library, and I want to load a specific JavaScript file (exist in the jar) in all pages of the JSF 2.2 project that use it, and without any additional configuration.
I want to load a specific JavaScript file exist in my library without use of
h:outputScript tag in the page( Neither the template nor the page )
Is this possible in jsf web application?
You can use UIViewRoot#addComponentResource() to programmatically add JSF resources. You can use a SystemEventListener on PostAddToViewEvent of <h:head> for this.
public class DynamicHeadResourceListener implements SystemEventListener {
#Override
public boolean isListenerForSource(Object source) {
return "javax.faces.Head".equals(((UIComponent) source).getRendererType());
}
#Override
public void processEvent(SystemEvent event) {
String library = "yourLibraryName";
String name = "yourScript.js"; // Can be dynamic here.
addHeadResource(FacesContext.getCurrentInstance(), library, name);
}
private void addHeadResource(FacesContext context, String library, String name) {
UIComponent resource = new UIOutput();
resource.getAttributes().put("library", library);
resource.getAttributes().put("name", name);
resource.setRendererType(context.getApplication().getResourceHandler().getRendererTypeForResourceName(name));
context.getViewRoot().addComponentResource(context, resource, "head");
}
}
In order to get it to run, register it in faces-config.xml of your module project as below:
<system-event-listener>
<system-event-listener-class>com.example.DynamicHeadResourceListener</system-event-listener-class>
<system-event-class>javax.faces.event.PostAddToViewEvent</system-event-class>
<source-class>javax.faces.component.UIOutput</source-class>
</system-event-listener>
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
I am trying to have a custom SystemEventListener that registers for all instances of type UIInput and reacts to their postValidate-Events. Based on an example I found on the web I managed to get one running for HtmlInputText by registering it in the faces-config.xml as follows:
<system-event-listener>
<source-class>javax.faces.component.html.HtmlInputText</source-class>
<system-event-class>javax.faces.event.PostValidateEvent</system-event-class>
<system-event-listener-class>com.ourcompany.ourproduct.validators.inputPostValidationListener</system-event-listener-class>
</system-event-listener>
I then tried to 1) broaden this to work for UIInputs in general and to 2) use the #ListenerForannotation, but I just can't seem to get either of it to work.
for 1) I couldn't really find any examples or documentation, so i just tried around by a) defining multiple source-class-tags or by b) using javax.faces.component.UIInput as source-class. Neither worked.
for 2) I tried
#ListenerFor(systemEventClass = PostValidateEvent.class, sourceClass = UIInput.class)
which worked neither for UIInput nor for html.HtmlInputText.
Now, when I duplicate the same XML-configuration for all the other types of HTML-Inputs, this does the trick, but it just clutters up the xml and altogether seems quite annoying to me.
So the question is: Am I generally doing something wrong with the #ListenerFor annotation? Is there a restriction on which source-classes are possible, i.e. why can't I use the more generic UIInput? Is there a more efficient way to register the listener for all those different inputs than repeating the XML? And finally: I'd rather like to implement ComponentSystemEventListener. Assuming that the above problem was resolved, I'd just change the implements-Statement and implement the abstract processEvent accordingly, right? Would that work just the same or is the registration/xml-config different in this case (e.g. maybe <component-system-event-listener> instead of just <system-event-listener>?
(and as an after-note: is it just me or is it kind of hard to find any non-trivial examples for this kind of stuff on the web?)
The #ListenerFor is supposed to be set on an UIComponent or Renderer implementation, not on a standalone SystemEventListener implementation. See also the javadoc (emphasis mine):
The default implementation must support attaching this annotation to UIComponent or Renderer classes. In both cases, the annotation processing described herein must commence during the implementation of any variant of Application.createComponent() and must complete before the UIComponent instance is returned from createComponent(). The annotation processing must proceed according to an algorithm semantically equivalent to the following.
...
In order to have a global listener, not specific to an UIComponent or Renderer, your best bet is creating and registering a PhaseListener which subscribes the listener to the view root.
public class PostValidateListener implements PhaseListener {
#Override
public PhaseId getPhaseId() {
return PhaseId.PROCESS_VALIDATIONS;
}
#Override
public void beforePhase(PhaseEvent event) {
event.getFacesContext().getViewRoot()
.subscribeToViewEvent(PostValidateEvent.class, new InputPostValidationListener()); // Capitalize class name?
}
#Override
public void afterPhase(PhaseEvent event) {
// NOOP.
}
}
To get it to run, register it as follows in faces-config.xml:
<lifecycle>
<phase-listener>com.example.PostValidateListener</phase-listener>
</lifecycle>
You can even make your InputPostValidationListener itself a PhaseListener.
public class InputPostValidationListener implements PhaseListener, SystemEventListener {
#Override
public void beforePhase(PhaseEvent event) {
event.getFacesContext().getViewRoot().subscribeToViewEvent(PostValidateEvent.class, this);
}
// ...
}
I know of two ways of creating custom JSF components:
1. Native JSF way: creating JSF component class, tag, etc.
2. Facelets way: defining component in a xhtml file and then creating appropriate decrption in facelets taglib.
Currently I work on a project in which introducing facelets is unfortunately out of the question. On the other hand, creating custom components the standard JSF way seems like a pain in the ass.
Is there maybe a third party library that allows creating custom components in the way similar to facelets but doesn't entail the need of using non-standard renderer?
You can do a limited amount of templating using (for example) jsp:include and f:subview.
Alternatively, you can extend a UIComponent overriding selected methods and then provide it via an existing tag and a managed bean using the binding attribute. This still requires a reasonably detailed understanding of component development (and the consequences of this choice), but could cut down the number of files/volume of code significantly.
This approach is a bit of a hack, but might be OK for short-term stuff. You wouldn't do it for component libraries you want to distribute or components requiring long term maintenance.
The new component:
public class QuickComponent extends HtmlOutputText {
#Override public void encodeAll(FacesContext context) throws IOException {
ResponseWriter writer = context.getResponseWriter();
writer.writeText("I'm not really a HtmlOutputText", null);
for (UIComponent kid : getChildren()) {
if (kid instanceof UIParameter) {
UIParameter param = (UIParameter) kid;
writer.startElement("br", this);
writer.endElement("br");
writer.writeText(param.getName() + "=" + param.getValue(), null);
}
}
}
}
The bean providing an instance:
/**Request-scope managed bean defined in faces-config.xml*/
public class QuickComponentProviderBean {
private QuickComponent quick;
public void setQuick(QuickComponent quick) {
this.quick = quick;
}
public QuickComponent getQuick() {
if (quick == null) {
quick = new QuickComponent();
}
return quick;
}
}
Note: don't reuse a single bean property for multiple tags in your views, or they'll reference the same object instance.
Adding the new component to the view:
<h:outputText binding="#{quickComponentProviderBean.quick}">
<f:param name="Hello" value="World" />
</h:outputText>
Note: the attributes that can be defined have not changed. They're fixed by the TLD.