Navigation not possible until page is completely loaded -- Severe usability
My deployed app works OK but I ran into a snag in the real internet world and am hoping someone can help me solve it before I have to go and write a bunch of weird filters and stuff :(
My app has a page fragment header containing a tabset component and a bunch of tabs (minus group panels) whose immediate property is set true (virtual forms no good here.) Basically, as I recall, it's functionally identical to the tutorial example on tabsets.
So,when a user clicks a link on a partially rendered page, either on the tabset or on the normal buttons on the page,page navigation does not occur unless the page is fully transfered from the server. I've not yet determined if this behaviour is an artificat of the faces servlet or something in the rendered html in the browser (I need to write a "slow down" filter first to test this)
Anyway, this is a big issue in a real world app and hard to detect until you go live with your app. Also creator is not the most frugal generator of html I've ever seen so this compounds the problem. Please anyone with knowledge / suggestions / advice -- bring it on!
Regards
Y
=========================
Update:
Did some research on this an obviously validation won't work on a partially downloaded page... So my guess is that the facesservlet is attempting to see if the user has a complete copy of the page before allowing navigation. Looks like this tag: <input id="form1_hidden" name="form1_hidden" value="form1_hidden" type="hidden" />
might be responsible.
Sun Guys: can you file a request for enhancement for me? I'd like the facesservlet to allow navigation if the component clicked has an immediate property set "true". Also similar behavior if a virtual form has all components already loaded.
For now I'm going to try a workaround by writing a filter that injects / removes the hidden field from the raw html. Will post my results here.
Message was edited by:
yossarian
[2128 byte] By [
yossarian] at [2007-11-26 11:13:51]

# 2
The tag above is the culprit. It appears the facesservlet will only return the current page if it does not receive the hidden field.
I've written a filter as a work around (well more like copied someone else's filter). In it's current form my filter sucks because it blocks the response until the whole page has been produced by the faces servlet. I'm working on a filter that will allow me to simulate latency throughput and reliability so I can test this stuff without messing with my live installation. The filter code follows. Workaround is to insert a hidden field in your page at the point where you want the faces servlet to accept a submit request and set the hidden field's text property to "enableSubmit". the hidden field's id must be "hiddenField1" and the form "form1" (both are defaults). If someone feels like telling me how to make it non blocking (I've got to override the write method of something to do this) please feel free to let know how...
Regards
Y
import java.io.CharArrayWriter;
import java.io.PrintWriter;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
/* This class 'borrowed' from Sun's example:
*See http://java.sun.com/products/servlet/Filters.html
*/
public class CharResponseWrapper extends
HttpServletResponseWrapper {
private CharArrayWriter output;
public String toString() {
return output.toString();
}
public CharResponseWrapper(HttpServletResponse response){
super(response);
output = new CharArrayWriter();
}
public PrintWriter getWriter(){
return new PrintWriter(output);
}
}
/*
* HiddenFieldFilter.java
*
* Created on 03 November 2006, 12:34
*/
import java.io.*;
import java.net.*;
import java.util.*;
import java.text.*;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
/**
*
* @author yossarian
* @version
*/
public class HiddenFieldFilter implements Filter {
// The filter configuration object we are associated with. If
// this value is null, this filter instance is not currently
// configured.
private FilterConfig filterConfig = null;
public HiddenFieldFilter() {
}
private PrintWriter out;
private CharResponseWrapper wrapper;
private void doBeforeProcessing(ServletRequest request, ServletResponse response)
throws IOException, ServletException {
if (debug) log("NewSimpleFilter:DoBeforeProcessing");
//
// Write code here to process the request and/or response before
// the rest of the filter chain is invoked.
//
/*This code adapted from Suns example
*see http://java.sun.com/products/servlet/Filters.html
*/
out = response.getWriter();
wrapper = new CharResponseWrapper((HttpServletResponse)response);
}
String remove="<input id=\"form1_hidden\" name=\"form1_hidden\" value=\"form1_hidden\" type=\"hidden\" />";
String replace="<input type=\"hidden\" id=\"form1:hiddenField1\" name=\"form1:hiddenField1\" value=\"enableSubmit\" />";
private void doAfterProcessing(ServletRequest request, ServletResponse response)
throws IOException, ServletException {
if (debug) log("NewSimpleFilter:DoAfterProcessing");
//
// Write code here to process the request and/or response after
// the rest of the filter chain is invoked.
//
/* Removes the string inquestion from the stream and then injects it
*into the stream after the <form> element
*TODO see if having two fields is a problem
*TODO allow replacement based on some key field in the orginal page
*eg replace a hidden field "bananna" with the magic hidden field
*/
//<input id=\"form1:hiddenField1\" name=\"form1:hiddenField1\" type=\"hidden\" value=\"enableSubmit\" />";
if(wrapper.getContentType().indexOf("text/html")!=-1) {
//CharArrayWriter caw = new CharArrayWriter();
//// Remove hidden field
//if (debug){log("removing magic field at pos "+new Integer(res.indexOf(remove)).toString());}
//res=res.replace((CharSequence)remove, (CharSequence)"");
//if (debug){log("magic field now at pos "+new Integer(res.indexOf(remove)).toString());}
//Insert hidden field in place of our user one
// if (debug){log("exchanging user field at pos "+new Integer(res.indexOf(replace)).toString());}
String res=wrapper.toString().replace((CharSequence)replace, (CharSequence)remove);
// if (debug){log("magic field now at pos "+new Integer(res.indexOf(remove)).toString());}
//caw.write(res);
response.setContentLength(res.length());
out.write(res);
} else
out.write(wrapper.toString());
out.close();
}
/**
*
* @param request The servlet request we are processing
* @param result The servlet response we are creating
* @param chain The filter chain we are processing
*
* @exception IOException if an input/output error occurs
* @exception ServletException if a servlet error occurs
*/
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain)
throws IOException, ServletException {
long begin = new Date().getTime();
if (debug) log("NewSimpleFilter:doFilter()");
doBeforeProcessing(request, response);
Throwable problem = null;
try {
chain.doFilter(request, wrapper);
} catch(Throwable t) {
//
// If an exception is thrown somewhere down the filter chain,
// we still want to execute our after processing, and then
// rethrow the problem after that.
//
problem = t;
t.printStackTrace();
}
doAfterProcessing(request, response);
if (debug){log("HiddenFieldFilter processing time "+ new Long(new Date().getTime()-begin).toString());}
//
// If there was a problem, we want to rethrow it if it is
// a known type, otherwise log it.
//
if (problem != null) {
if (problem instanceof ServletException) throw (ServletException)problem;
if (problem instanceof IOException) throw (IOException)problem;
sendProcessingError(problem, response);
}
}
/**
* Return the filter configuration object for this filter.
*/
public FilterConfig getFilterConfig() {
return (this.filterConfig);
}
/**
* Set the filter configuration object for this filter.
*
* @param filterConfig The filter configuration object
*/
public void setFilterConfig(FilterConfig filterConfig) {
this.filterConfig = filterConfig;
}
/**
* Destroy method for this filter
*
*/
public void destroy() {
}
/**
* Init method for this filter
*
*/
public void init(FilterConfig filterConfig) {
this.filterConfig = filterConfig;
if (filterConfig != null) {
if (debug) {
log("NewSimpleFilter:Initializing filter");
}
}
}
/**
* Return a String representation of this object.
*/
public String toString() {
if (filterConfig == null) return ("NewSimpleFilter()");
StringBuffer sb = new StringBuffer("NewSimpleFilter(");
sb.append(filterConfig);
sb.append(")");
return (sb.toString());
}
private void sendProcessingError(Throwable t, ServletResponse response) {
String stackTrace = getStackTrace(t);
if(stackTrace != null && !stackTrace.equals("")) {
try {
response.setContentType("text/html");
PrintStream ps = new PrintStream(response.getOutputStream());
PrintWriter pw = new PrintWriter(ps);
pw.print("<html>\n<head>\n<title>Error</title>\n</head>\n<body>\n"); //NOI18N
// PENDING! Localize this for next official release
pw.print("<h1>The resource did not process correctly</h1>\n<pre>\n");
pw.print(stackTrace);
pw.print("</pre></body>\n</html>"); //NOI18N
pw.close();
ps.close();
response.getOutputStream().close();;
}
catch(Exception ex){ }
} else {
try {
PrintStream ps = new PrintStream(response.getOutputStream());
t.printStackTrace(ps);
ps.close();
response.getOutputStream().close();;
} catch(Exception ex){ }
}
}
public static String getStackTrace(Throwable t) {
String stackTrace = null;
try {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
t.printStackTrace(pw);
pw.close();
sw.close();
stackTrace = sw.getBuffer().toString();
} catch(Exception ex) {}
return stackTrace;
}
public void log(String msg) {
filterConfig.getServletContext().log(msg);
}
private static final boolean debug = false;
}
Sorry -- it's a bit messy with stuff commented out etc...