My application (JSF 2, Java 6, JBoss 7.1) has to offer two operating modes: accessibility mode and non-accessibility mode.
In accessibility mode, some (not all) pages have a particular design to be better read by a screen reader. The difference between the two modes is purely visual, the managed beans are precisely the same. Ideally, no Java code has to be changed.
Most of the work is done:
With all that, it works almost perfectly, but it seems there is some kind of view cache that breaks my solution. Consider the scenario below:
In the last step we can understand that, even in accessibility mode and with the resource path translation happening (I have logs to proof), the pages are generated as they were in the default, non-accessibility mode.
So, is really there a pages cache in JSF? How can I clear it, so the pages will indeed be rendered again?
A network monitoring showed me that the request is indeed issued to the application, so no browser cache is playing here.
After some time I could finally find a solution, which means, a coding strategy that satisfies all the requirements I had. Maybe it's not a technically good solution, but it is a functinal solution in the sense it produces the experience I needed to provide, saving me to touch all already existing my Java code. There it goes!
The managed bean below is responsible to turn the accessibility on and off in response to the users click on a certain command link. The idea is to add an attribute to the session when the accessibility mode is turned on and remove it when the accessibility mode is turned off.
Those actions return a redirection the current page so the user immedially sees the interface changing from one mode to the other.
When the accessibility is turned on, the view path is changed to append a /ac
, because all my accessible views have the same file name that the non-accessibility ones, but in a subdirectory named ac
.
@ManagedBean
@RequestScoped
public class AccessibilityMB {
private boolean accessibilityOn = false;
@PostConstruct
public void init() {
FacesContext context = FacesContext.getCurrentInstance();
HttpSession session = (HttpSession)context.getExternalContext().getSession(false);
if(session!=null) {
Boolean accessibilityMode = (Boolean)session.getAttribute("AccessibilityMode");
accessibilityOn = accessibilityMode!=null && accessibilityMode;
}
}
public String turnAccessibilityOff() {
FacesContext context = FacesContext.getCurrentInstance();
HttpSession session = (HttpSession)context.getExternalContext().getSession(false);
if(session!=null) {
session.setAttribute("AccessibilityMode", accessibilityOn = true);
}
String viewId = context.getViewRoot().getViewId();
return viewId+"?faces-redirect=true";
}
public String turnAccessibilityOn() {
FacesContext context = FacesContext.getCurrentInstance();
HttpSession session = (HttpSession)context.getExternalContext().getSession(false);
if(session!=null) {
accessibilityOn = false;
session.removeAttribute("AccessibilityMode");
}
String viewId = context.getViewRoot().getViewId();
int index = viewId.lastIndexOf("/ac/");
if(index>-1)
viewId = viewId.substring(0, index)+viewId.substring(index+3);
return viewId+"?faces-redirect=true";
}
public boolean getAccessibilityOn() {
return accessibilityOn;
}
}
The PhaseListener just checks for the accessibility mode and, in such a circunstance, rewrites the path the view to look for in the ac
subdirectoty. If the desided view exists there, the current component tree is discarded and rebuilt from the accessible version of the same view.
Here the solution is not so good, because JSF already worked to build a component tree and I'm simply discarding it to reworking it from another file.
public class AccessibilityPhaseListener implements PhaseListener{
private static final long serialVersionUID = 1L;
@Override
public void beforePhase(PhaseEvent event) {
FacesContext context = FacesContext.getCurrentInstance();
HttpSession session = (HttpSession)context.getExternalContext().getSession(false);
if(session==null) {
return;
}
Boolean acessibilityMode = (Boolean)session.getAttribute("AcessibilityMode");
if(acessibilityMode==null || !acessibilityMode)
return;
String viewId = context.getViewRoot().getViewId();
if(acessibilityMode) {
int index = viewId.lastIndexOf("/");
viewId = viewId.substring(0, index+1)+"ac/"+viewId.substring(index+1);
} else {
int index = viewId.lastIndexOf("/");
if(viewId.substring(index-3, index).equals("/ac"))
viewId = viewId.substring(0, index-3)+viewId.substring(index);
}
URL url = null;
try {
url = context.getExternalContext().getResource(viewId);
} catch (MalformedURLException e) {
}
if(url==null)
return;
ViewHandler handler = context.getApplication().getViewHandler();
UIViewRoot root = handler.createView(context, viewId);
root.setViewId(viewId);
context.setViewRoot(root);
}
@Override
public PhaseId getPhaseId() {
return PhaseId.RENDER_RESPONSE;
}
}
I could satisfy all my requirements:
I undestand that this solution works with any kind of application modes, not only accessibility. Any time someone needs to select a certain view instead of the other based on an application or session parameter, it will work. For instance, a multiculture application where the culture customization goes futher than color and language, requiring a complete redesign of views can take advantage of this model.
The downside of all this is the fact that, when the accessibility mode is on and there is an accessible version of a certain view, JSF will work twice, one time to build the original view and a second time to build the accessible version.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With