In my app, user should be able to switch the locale (the language used to render text on pages). Tons of tutorials are using FacesContext.getCurrentInstance().getViewRoot().setLocale(). For example: http://www.mkyong.com/jsf2/jsf-2-internationalization-example/. But, that simply doesn't work in JSF 2.0 (it did work in 1.2). The language never switches. No errors or anything. The same code worked fine in JSF 1.2.
What is the correct and definitive approach? I have cobbled together a solution, but not sure if this is the correct one. This works fine. The language switches after user clicks on English or French. Here is code snippet to give you some idea.
#ManagedBean(name = "switcher")
#SessionScoped
public class LanguageSwitcher {
Locale locale = FacesContext.getCurrentInstance().getViewRoot().getLocale();
public String switchLocale(String lang) {
locale = new Locale(lang);
return FacesContext.getCurrentInstance().getViewRoot().getViewId() +
"?faces-redirect=true";
}
//getLocale() etc. omitted for brevity
}
The XHTML:
<f:view locale="#{switcher.locale}">
<h:outputText value="#{msg.greeting}" />
<h:commandLink value="English" action="#{switcher.switchLocale('en')}" />
<h:commandLink value="French" action="#{switcher.switchLocale('fr')}" />
</f:view>
Just to give you more info, here is the config file.
<application>
<locale-config>
<supported-locale>en</supported-locale>
<supported-locale>fr</supported-locale>
</locale-config>
<resource-bundle>
<base-name>com.resources.Messages</base-name>
<var>msg</var>
</resource-bundle>
</application>
Once again, this works. But, I haven't changed the locale of JSF itself by calling any API in any way. This gives me somewhat of a creepy feeling. Is this the correct way to change user's locale?
OK, at the risk of answering my own question, I will like to summarize all the different approaches that I have found.
The basic approach is what I am already doing. That is, have a managed bean in session scope that returns the Locale of the user. This locale needs to be used in every XHTML using <f:view locale="...">. I learned this technique from a post by BalusC, so thanks are due there.
Now, the concern is the use of the f:view element. This needs to be repeated in every page, a potential source of defect if omitted by mistake. I have found a couple of ways of solving this problem.
Approach #1: Create a Facelet template and add the f:view element there. Individual template user pages don't have to worry about adding this element.
Approach #2 uses a phase listener. #meriton has posted the solution here. Thank you for that.
Approach #3 uses a custom view handler that extends MultiViewHandler and returns user's locale from the calculateLocale() method. This is described in the book Beginning JSF 2 APIs and JBoss Seam By: Kent Ka Iok Tong. Here is a slightly altered example from the book:
public class MyViewHandler extends MultiViewHandler {
public Locale calculateLocale(FacesContext context) {
HttpSession session = (HttpSession) context.getExternalContext()
.getSession(false);
if (session != null) {
//Return the locale saved by the managed bean earlier
Locale locale = (Locale) session.getAttribute("locale");
if (locale != null) {
return locale;
}
}
return super.calculateLocale(context);
}
}
Then register it in faces config.
<application>
<view-handler>com.package.MyViewHandler</view-handler>
<!-- Other stuff ... -->
</application>
This is somewhat more elegant than the phase listener. Unfortunately, MultiViewHandler is an internal non-API class from the com.sun.faces.application.view package. That incurs some risk going forward.
With either approach #2 or #3, there is no need for the f:view element in the pages.
One can use custom view handler that extends javax.faces.application.ViewHandlerWrapper and returns user's locale from the calculateLocale() method.
This is definitely better than extending MultiViewHandler from the proprietary SUN package com.sun.faces.application.view, no matter what is described in the book Beginning JSF 2 APIs mentioned in your suggestion. Apart from that, your original approach is absolutely OK:
public class MyViewHandler extends ViewHandlerWrapper {
public Locale calculateLocale(FacesContext context) {
HttpSession session = (HttpSession) context.getExternalContext()
.getSession(false);
if (session != null) {
//Return the locale saved by the managed bean earlier
Locale locale = (Locale) session.getAttribute("locale");
if (locale != null) {
return locale;
}
}
return super.calculateLocale(context);
}
}
Then register it in faces config.
<application>
<view-handler>com.package.MyViewHandler</view-handler>
<!-- Other stuff ... -->
</application>
I had a related problem recently. In my case, the JSF implementation forgot the view locale set by UIViewRoot.setLocale() after navigating to a different view. I rather consider this a bug in the JSF impl, but I didn't have time to make sure.
I didn't particularly like the <f:view> approach, as that tag has been obsoleted by facelets - except for keeping the locale, it seems. This made my leary of including it in a Facelets template. I therefore wrote the following PhaseListener:
/**
* PhaseListener that keeps the current view locale in the session while no request is being processed, to work around
* bugs where JSF forgets the changed locale.
*/
public class SaveViewLocaleToSessionPhaseListener implements PhaseListener {
private static final String key = "locale";
#Override
public PhaseId getPhaseId() {
return PhaseId.ANY_PHASE;
}
#Override
public void beforePhase(PhaseEvent event) {
// do nothing
}
#Override
public void afterPhase(PhaseEvent event) {
PhaseId currentPhase = event.getPhaseId();
if (currentPhase == PhaseId.RESTORE_VIEW) {
viewRoot().setLocale((Locale) sessionMap().get(key));
} else if (currentPhase == PhaseId.RENDER_RESPONSE) {
sessionMap().put(key, viewRoot().getLocale());
}
}
private Map<String, Object> sessionMap() {
return FacesContext.getCurrentInstance().getExternalContext().getSessionMap();
}
private UIViewRoot viewRoot() {
return FacesContext.getCurrentInstance().getViewRoot();
}
}
However, I can not offer any solid evidence that this is really better than simply using <f:view>.
But, I haven't changed the locale of JSF itself in any way.
Sure you did: The <f:view> tag reads the locale from the value expression, and passes it to UIViewRoot.setLocale().
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
I am using a custom bean validation constraint. When bean validation constraint fails, isValid returns false. I would then expect, that the action phase will be skipped, which does not seem to be the case. The operation doSomething is still executed. What could be the cause of this? Thanks a lot in advance and let me know if you need more information.
Using MyFaces 2.0.15
Constraint Validator:
public class MyValidatorImpl extends ConstraintValidator<MyValidator, MyView>
{
#Override
public boolean isValid(...){
return false;
}
}
Model:
#MyValidator
public class MyViewImpl {
public void doSomething(){
System.out.println("Does something although Validation failed");
}
}
View:
<h:commandButton value="Test"
action="#{myViewImpl.doSomething}" />
Update: In a previous software version we used the same myfaces implementation and this version worked fine. I've compared the xhtmls / java classes / validators, they are pretty much all the same.
Any suggestions what I could look for?
Simple question from a beginner at JSF:
I have very simple JSF form:
<h:form>
<p>#{messages.loginTextfieldUsername}</p>
<h:inputText value="#{userServiceImpl.user.name}" />
<p>#{messages.loginTextfieldPassword}</p>
<h:inputSecret value="#{userServiceImpl.user.password}" />
<h:commandButton value="#{messages.loginButtonLogin}" action="#{userServiceImpl.authenticateUser}" />
</h:form>
The userServiceImpl class is:
#Named
#RequestScoped
public class UserServiceImpl implements UserService {
private UserSession userSession;
private User user;
#Inject
public UserServiceImpl(UserSession userSession) {
this.userSession = userSession;
}
#PostConstruct
public void prepareService() {
user = new User();
}
#Override
public View authenticateUser() {
userSession.setLoggedUser(user);
return View.MAIN;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}
My goal is pretty simple: when the user hits the login button, I want to authenticate the user.
The problem is:
When the authenticate method is called, the User attributes are null. I debugged the application and the getUser method is called and the values are properly set, but at some point (which I did not find [yet]) before the authenticateUser is called the User attributes are set to null...
I'm aware that this is a pretty basic question... but are you able to point out where my mistake is?
Thanks.
Based on your previous question, you seem to have experimented with <managed-bean-scope> of none in faces-config.xml for some reason. The problem symptoms matches exactly when using #ManagedBean #NoneScoped. You seem to have configured this bean in faces-config.xml as well on a none scope which totally explains this problem. With the none scope, a brand new bean instance is been created everytime when #{userServiceImpl} is been evaluated in EL. Your form submit has thus effectively created 3 beans: one where the user name is set, another one where user password is set and another one where action is invoked.
You need to remove the managed bean configuration from faces-config.xml. You should not use it when you intend to use #Inject (or #ManagedBean). The faces-config.xml way of configuring beans is a leftover from old JSF 1.x ages when annotations weren't available. As of JSF 2.x it would only override any bean management annotations.
I want to embed a link in a JSF message, is this possible?
When I try it, the rendered html of the h:messages tag escapes the html characters. I tried setting the escape attribute of the h:messages tag to false, but that didn't help.
Unfortunately, this is not possible in the standard JSF implementation. The component and the renderer doesn't officially support this attribute. You can however homegrow a renderer which handles this.
Since this is a pretty common requirement/wish, I thought to take a look what's all possible.
First some background information: JSF by default uses ResponseWriter#writeText() to write the tag body, which escapes HTML by default. We'd like to let it use ResponseWriter#write() instead like as with <h:outputText escape="false" />. We'd like to extend the MessagesRenderer of the standard JSF implementation and override the encodeEnd() method accordingly. But since the MessagesRenderer#encodeEnd() contains pretty a lot of code (~180 lines) which we prefer not to copypaste to just change one or two lines after all, I found it better to replace the ResponseWriter with a custom implementation with help of ResponseWriterWrapper wherein the writeText() is been overriden to handle the escaping.
So, I ended up with this:
package com.example;
import java.io.IOException;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.context.ResponseWriterWrapper;
import javax.faces.render.FacesRenderer;
import com.sun.faces.renderkit.html_basic.MessagesRenderer;
#FacesRenderer(componentFamily="javax.faces.Messages", rendererType="javax.faces.Messages")
public class EscapableMessagesRenderer extends MessagesRenderer {
#Override
public void encodeEnd(FacesContext context, UIComponent component) throws IOException {
final ResponseWriter originalResponseWriter = context.getResponseWriter();
try {
context.setResponseWriter(new ResponseWriterWrapper() {
#Override
public ResponseWriter getWrapped() {
return originalResponseWriter;
}
#Override
public void writeText(Object text, UIComponent component, String property) throws IOException {
String string = String.valueOf(text);
String escape = (String) component.getAttributes().get("escape");
if (escape != null && !Boolean.valueOf(escape)) {
super.write(string);
} else {
super.writeText(string, component, property);
}
}
});
super.encodeEnd(context, component); // Now, render it!
} finally {
context.setResponseWriter(originalResponseWriter); // Restore original writer.
}
}
}
In spite of the #FacesRenderer annotation, it get overriden by the default MessagesRenderer implementation. I suspect here a bug, so I reported issue 1748. To get it to work anyway, we have to fall back to the faces-config.xml:
<render-kit>
<renderer>
<component-family>javax.faces.Messages</component-family>
<renderer-type>javax.faces.Messages</renderer-type>
<renderer-class>com.example.EscapableMessagesRenderer</renderer-class>
</renderer>
</render-kit>
Then, to trigger it, just do:
<h:messages escape="false" />
And it works! :)
Note: the above affects <h:messages> only. To do the same for <h:message>, just do the same, but replace anywhere "Messages" by "Message" (component family, renderer type and classnames).
The escape="false" attributed you need is provided by the OmniFaces <o:messages> component. The OmniFaces utility library is available for JSF 2.
I posted this solution mentioned by #BalusC's comment as an answer since this is the most straightforward solution.
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.