Announcement Announcement Module
Collapse
No announcement yet.
Displaytag Sorting in WebFlow Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Displaytag Sorting in WebFlow

    I have problems getting displaytag sorting to work under webflow. I have already searched the forums. Here is what happens:

    I have a displaytag declaration like this:

    Code:
    <display:table 
    	name="subList" 
    	sort="list" 
    	id="row" 
    	defaultsort="1" 
    	defaultorder="descending" 
    	style="background-color:#727171;" 
    	cellpadding="3" 
    	decorator="com.ibnads.submission.decorator.SubTableDecorator"
    	requestURI="flow.htm?_eventId=sort&_flowExecutionKey=${flowExecutionKey}">
    The section of my flow xml relevant here looks like this:

    Code:
    <view-state id="displaytagview" view="...">
    	<render-actions>
            ...
    	</render-actions>
    	
    	<!-- Transition used by sorting mechanism of displaytag headers -->
    	<transition on="sort" to="displaytagview" >
    		<action bean="..." />
    	</transition>
            ...
    </view-state>
    The sorting links look like this:

    Code:
    http://localhost:8084/.../flow.htm?d-16544-p=1&d-16544-s=2&_flowExecutionKey=_cA5EEC42D-5DD8-123A-0815-EA798884BD4E_k1A451150-D18E-5132-9566-4AF7FAA1FC2D&_eventId=sort&d-16544-o=2
    But the resulting page isn't sorted and the url looks like this:

    Code:
    http://localhost:8084/.../flow.htm?_flowExecutionKey=_cA5EEC42D-5DD8-123A-0815-EA798884BD4E_kAE421956-87B0-93DB-40E2-6A2C3854D58F
    So, in other words, the request parameters are not being passed along in my flow. Any ideas what I can do to make sure the displaytag parameters are not thrown away in the transition?

  • #2
    Because of the alwaysRedirectOnPause (feature) of webflows, you need to add something like this to your spring.xml (where you have your flowExecutor defined)

    Code:
    <bean id="listener" class="com.yourcompany.web.spring.RequestParameterFlowExecutionListener"  />
    Code:
    /**
     * This class puts all request params in flash scope so that they're available
     * after webflow's standard redirect (alwaysRedirectOnPause) behavior
     * This is a attached as a filter in webflow.xml
     * 
     * 
     */
    public class RequestParameterFlowExecutionListener extends FlowExecutionListenerAdapter {
    
        @SuppressWarnings("unchecked")
        @Override
        public void requestProcessed(RequestContext context) {
    
            if (context.getFlowExecutionContext().isActive()) {
                HttpServletRequest request = ((ServletExternalContext) context.getExternalContext()).getRequest();
    
                Map params = request.getParameterMap();
    
                for (Map.Entry param : (Set<Map.Entry>) params.entrySet()) {
    
                    Object[] values = (Object[]) param.getValue();
                    if (values != null) {
                        if (values.length == 1) {
                            context.getFlashScope().put((String) param.getKey(), values[0]);
                        } else {
                            context.getFlashScope().put((String) param.getKey(), values);
                        }
                    }
                }
            }
            super.requestProcessed(context);
        }
    }

    Comment


    • #3
      Thanks Sam. I was just about to ask the exact same question as the OP!

      By the way Lobo, an alternative (much less desirable than Sam's solution) is to simply turn alwaysRedirectOnPause off in your spring.xml (where you have your flowExecutor defined):

      <flow:executor id="flowExecutor" registry-ref="flowRegistry" repository-type="continuation">
      <flow:execution-attributes>
      <flow:alwaysRedirectOnPause value="false"/>
      </flow:execution-attributes>
      </flow:executor>

      The reason it is undesirable (usually) is that you no longer get the POST/Redirect/GET functionality that prevents double posts.

      Comment


      • #4
        Sam (or anyone),
        I used the code provided but couldn't get it to work. The problem seems to be that the code saves off the query string parameters into flashscope attributes, but displaytag is only looking for query string parameters. What is the secret to having the listener recreate the query string rather than save them as attributes?

        TIA

        Comment


        • #5
          I actually just figured this out. Apparently, DisplayTag only sees url parameters. It's not enough to just put it in the request/flash scope. So, I changed my flow:executor in my flow.xml like this:

          Code:
          <flow:executor id="flowExecutor" registry-ref="flowRegistry">
          		<flow:execution-listeners>	
          			<flow:listener ref="displayTagListener" criteria="myflow" />
          		</flow:execution-listeners>
          	</flow:executor>
          
          <bean id="displayTagListener" class="com.yourcompany.web.spring.RequestParameterFlowExecutionListener"  />
          Then, I overwrote the paused method in my RequestParameterFlowExecutionListener.

          Code:
          @SuppressWarnings("unchecked")
          @Override
          public void paused( RequestContext context, ViewSelection selectedView )
          {
          	boolean hasDTagParams = false;
          	if (context.getFlowExecutionContext().isActive()) {
          		if( selectedView.toString().contains( "[myview]" ) )
          		{
          			HttpServletRequest request = ((ServletExternalContext) context.getExternalContext()).getRequest();
          			HttpServletResponse response = ((ServletExternalContext) context.getExternalContext()).getResponse();
          				
          			String requestURL = request.getRequestURL().toString();
          			String queryString = "?" + request.getQueryString();
          			Map params = context.getFlashScope().asMap();
          			for( Map.Entry param : (Set<Map.Entry>) params.entrySet() ) 
          			{
          				String key = (String) param.getKey();
          				if( key.startsWith( "d-" ) )
          				{
          					hasDTagParams = true;
          				}
          			}
          				
          			if( !queryString.contains( "&d-" ) && hasDTagParams == true )
          			{
          				for( Map.Entry param : (Set<Map.Entry>) params.entrySet() ) 
          				{
          					String key = (String) param.getKey();
          					if( key.startsWith( "d-" ) )
          					{
          						Object value = param.getValue();
          						if( value != null )
          						{
          							queryString += "&"+key+"="+value.toString();
          						}
          					}
          				}
          				try{
          					response.sendRedirect( requestURL + queryString );
          				}
          				catch( IOException e )
          				{
          					e.printStackTrace();
          				}
          			}
          		}
                 }
                 super.paused(context, selectedView );
          }
          Where "[myview]" is the name of your view where you use displayTag. Any improvements to this are welcome, of course. But, it works.

          Comment


          • #6
            Thanks Lobo,
            My attempt, derived from your version of the code, fails because no _flowExecutionKey is included in the "response.sendRedirect( requestURL + queryString );" statement. This results in a page not found error.

            The URL produced is: http://127.0.0.1/member/memberstatus...=1&d-49653-s=2

            Did you have this same problem?

            Comment


            • #7
              oh yeah. I forgot. You need to add these two attributes to your display:table tag.

              Code:
              requestURI="flow.htm?_eventId=[sort]&_flowExecutionKey=${flowExecutionKey}"
              excludedParams="*"
              replacing "flow.htm" with whatever your mapping is and "[sort]" with whatever your event id is.

              Comment


              • #8
                OK, I think I found the solution that seems to fit best into how Spring "intends" to be extended...


                Code:
                public class DisplayTagReqParmFlowExecutorArgumentHandler extends
                		RequestParameterFlowExecutorArgumentHandler {
                
                	private static Logger logger = Logger.getLogger( DisplayTagReqParmFlowExecutorArgumentHandler.class );
                	private final String DISPLAY_TAG_PARM = "d-\\d+-[o|p|s]";
                
                	@Override
                	public String createFlowExecutionUrl( String flowExecutionKey , FlowExecutionContext flowExecution ,
                			ExternalContext context ) {
                		StringBuffer url = new StringBuffer();
                		appendFlowExecutorPath( url , context );
                		url.append( '?' );
                		// Create a hash with the original desired query parm ( flow execution key ) and then add in
                		// DisplayTag specific parms.
                		Hashtable< String , String > queryParmsHash = new Hashtable< String , String >();
                		queryParmsHash.put( getFlowExecutionKeyArgumentName(), flowExecutionKey );
                		queryParmsHash.putAll( appendDisplayTagQueryString( context.getRequestParameterMap() ) );
                		appendQueryParameters( url , queryParmsHash );
                
                		return url.toString();
                	}
                
                
                
                    /**
                     * @param parameters - A ParameterMap contain the request's query strings
                     * @return Map< String , String > containing any DisplayTag query string name/value pairs.
                     *
                     * Loop through the query strings looking for any DisplayTag parameters.  Their names are autogenerated,
                     * so their actual value isn't known, but they all follow the pattern d-99999-x (where 9 is any numeric value
                     * and x is either o, p, or s).
                     *
                     * Any matches are added to the return map.
                     *
                     */
                    private Map< String , String > appendDisplayTagQueryString( ParameterMap parameters ) {
                
                    	Hashtable< String , String > hashtableOfDisplayTagParms = new Hashtable< String , String >();
                
                    	if ( ! parameters.isEmpty() ) {
                	    	Map< String , Object > requestParametersMap = ( Map< String , Object> ) parameters.asMap();
                
                	    	for ( String key : requestParametersMap.keySet() ) {
                	    		if ( key.matches( DISPLAY_TAG_PARM ) ) {
                	    			hashtableOfDisplayTagParms.put( key , ( String ) requestParametersMap.get( key ) );
                	    	        if ( logger.isDebugEnabled() ) {
                	    	        	logger.debug( "Adding DisplayTag query string: " + key + " - " + requestParametersMap.get( key ) );
                	    	        }
                	    		}
                	    	}
                    	}
                
                    	return hashtableOfDisplayTagParms;
                    }
                and
                Code:
                    <bean id="flowEx" class="org.springframework.webflow.executor.mvc.FlowController">
                        <property name="flowExecutor" ref="flowExecutor"/>
                		<property name="argumentHandler" ref="customArgumentHandler"/>
                		<property name="defaultFlowId" value="membersearch-flow"/>
                    </bean>
                
                    <bean id="customArgumentHandler" class="eyemed.web.spring.executor.support.DisplayTagReqParmFlowExecutorArgumentHandler" />

                Comment


                • #9
                  I have a similar problem. Please take a look

                  http://forum.springframework.org/showthread.php?t=59665

                  Comment


                  • #10
                    Thanks for posting the code prickett. It was very useful to make DisplayTag work. Here is a similar solution for WebFlow version 2 in case anyone has a similar need (things were moved around a little, but it's basically the same):

                    Code:
                    import org.springframework.webflow.context.servlet.DefaultFlowUrlHandler;
                    
                    public class CustomFlowUrlHandler extends DefaultFlowUrlHandler {
                    	private static final String DEFAULT_URL_ENCODING_SCHEME = "UTF-8";
                    	private final String DISPLAY_TAG_PARM = "d-\\d+-[o|p|s]";
                    	
                    	protected final Log logger = LogFactory.getLog(getClass());	
                    	private String urlEncodingScheme = DEFAULT_URL_ENCODING_SCHEME;
                    
                    	@Override
                    	@SuppressWarnings("unchecked")
                    	public String createFlowExecutionUrl(String flowId,
                    			String flowExecutionKey, HttpServletRequest request) {
                    		
                    		// get url from super class (it should already have a partial query string appended to it)
                    		StringBuffer url = new StringBuffer(super.createFlowExecutionUrl(flowId, flowExecutionKey, request));
                    
                    		// iterate through parameters and add back on the parameter needed by DisplayTag
                    		Map<String,String[]> parameters = request.getParameterMap();
                    		if (parameters != null) {
                    			for(String key: parameters.keySet()) {
                    				if (key.matches(DISPLAY_TAG_PARM)) {
                    					url.append('&');
                    					String value = parameters.get(key)[0];
                    					appendQueryParameter(url, key, value);
                    					logger.debug("Added parameter to url: " + key + "=" + value);
                    				}
                    			}
                    		}
                    		return url.toString();
                    	}
                    	
                    	/* copied from super as they were declared as private and therefore not visible to sub-classses */
                    	private void appendQueryParameter(StringBuffer url, Object key, Object value) {
                    		String encodedKey = encode(key);
                    		String encodedValue = encode(value);
                    		url.append(encodedKey).append('=').append(encodedValue);
                    	}
                    
                    	private String encode(Object value) {
                    		return value != null ? urlEncode(String.valueOf(value)) : "";
                    	}
                    
                    	private String urlEncode(String value) {
                    		try {
                    			return URLEncoder.encode(String.valueOf(value), urlEncodingScheme);
                    		} catch (UnsupportedEncodingException e) {
                    			throw new IllegalArgumentException("Cannot url encode " + value);
                    		}
                    	}
                    
                    }
                    Code:
                    	<bean id="flowController" class="org.springframework.webflow.mvc.servlet.FlowController">
                    		<property name="flowExecutor" ref="flowExecutor"/>
                    		<property name="flowUrlHandler">
                    			<bean class="com.test.CustomFlowUrlHandler" />
                    		</property>
                    	</bean>
                    Last edited by splashout; Nov 3rd, 2008, 11:50 AM. Reason: changed regex -- wasn't matching all the time

                    Comment

                    Working...
                    X