Community   SpringSource   Projects    Downloads    Documentation    Forums    Training   Exchange   Blogs

Go Back   Spring Community Forums > Core Spring Projects > Spring Web Flow

Reply
 
Thread Tools Display Modes
  #1  
Old Oct 30th, 2006, 08:06 AM
ojs ojs is offline
Senior Member
 
Join Date: Jul 2005
Location: Munich, Germany
Posts: 153
Default How to configure a global transition for a back button?

Hello,

I'd like to configure a global transition for a form-based "back"-button. Is that possible?


Best

Oliver
Reply With Quote
  #2  
Old Oct 30th, 2006, 09:56 AM
klr8 klr8 is offline
Senior Member
Spring Team
 
Join Date: Sep 2004
Location: Leuven, Belgium
Posts: 1,844
Default

It all depends on what 'Back' is supposed to do. Should back go to a fixed url or to the 'previous view state'? The latter is easy, just include a global transition to go where you need to go when the back event is signaled. The former is a bit harder. You'll have to track the view states you hit in your flow, e.g. using a FlowExecutionListener, and store the 'history' in flow scope. You could then have a global transition that looks something like this:

<transition on="back" to="${flowScope.history.previousViewStateId}"/>

You'll need to do some custom coding (the FlowExecutionListener and the history object to store in flow scope) to make this possible.

Erwin
Reply With Quote
  #3  
Old Oct 30th, 2006, 12:14 PM
ojs ojs is offline
Senior Member
 
Join Date: Jul 2005
Location: Munich, Germany
Posts: 153
Default

Yes, I want the 'back' button to go to the 'previous view state'. Wouldn't that (an automatically defined '${flowScope.history.previousViewStateId}') be another cool feature for a new SWF version? I think the need for a 'back' button is pretty common in form wizards, isn't it?


Best

Oliver
Reply With Quote
  #4  
Old Oct 30th, 2006, 02:27 PM
klr8 klr8 is offline
Senior Member
Spring Team
 
Join Date: Sep 2004
Location: Leuven, Belgium
Posts: 1,844
Default

A universal definition for 'back' is not that common. What is the 'previous view state' really? Is it the last view state entered in the flow or the previous 'step' in the flow or something else still? How does this play with subflows?

Questions like this have so far prevented us from implementing something generic enough to be in SWF itself.

Erwin
Reply With Quote
  #5  
Old Oct 31st, 2006, 10:27 AM
ojs ojs is offline
Senior Member
 
Join Date: Jul 2005
Location: Munich, Germany
Posts: 153
Default

Quote:
Originally Posted by klr8 View Post
A universal definition for 'back' is not that common. What is the 'previous view state' really? Is it the last view state entered in the flow or the previous 'step' in the flow or something else still?
In my opinion it is the last view state entered in the flow. But there might be other cases I currently don't know of.

Quote:
Originally Posted by klr8 View Post
How does this play with subflows?
I don't know ;-) - I haven't used subflows so far.

For all other SWF users that need a simple 'global back to last view FlowExecutionListener' - here it is:

Code:
import java.util.LinkedList;

import org.springframework.webflow.definition.StateDefinition;
import org.springframework.webflow.engine.ViewState;
import org.springframework.webflow.execution.FlowExecutionListenerAdapter;
import org.springframework.webflow.execution.FlowSession;
import org.springframework.webflow.execution.RequestContext;

public class BackToLastViewStateFlowExecutionListener
    extends FlowExecutionListenerAdapter
{

    private String viewStatesName = "GLOBAL_BACK_LISTENER_VIEW_STATES";
    private String backEventId = "back";

    public void setViewStatesName(String viewStatesName)
    {
        this.viewStatesName = viewStatesName;
    }

    public void setBackEventId(String backEventId)
    {
        this.backEventId = backEventId;
    }

    @Override
    public void sessionStarted(final RequestContext context,
                               final FlowSession session)
    {
        session.getScope().put(viewStatesName, new LinkedList<String>());
    }

    @Override
    public void stateEntered(final RequestContext context,
                             final StateDefinition previousState,
                             final StateDefinition state)
    {
        if (!(previousState instanceof ViewState))
            return;

        @SuppressWarnings("unchecked")
        final LinkedList<String> viewStates =
            (LinkedList<String>)context.getFlowScope().get(viewStatesName);

        if (viewStates == null)
            throw new IllegalStateException("viewStates is null");

        final String previousStateId;

        if (context.getLastEvent().getId().equals(backEventId))
        {
            viewStates.removeLast();
            previousStateId = viewStates.getLast();
        }
        else
        {
            previousStateId = previousState.getId();
            viewStates.add(previousStateId);
        }

        context.getFlowScope().put("previousViewStateId", previousStateId);
    }

}

BTW: Any tips for improvement?


Best

Oliver
Reply With Quote
  #6  
Old Jan 10th, 2007, 04:35 PM
wee_malky wee_malky is offline
Junior Member
 
Join Date: Sep 2006
Posts: 4
Default Bugfix

The last if then else should read

if (context.getLastEvent().getId().equals(backEventId ))
{
previousStateId = viewStates.getLast();
viewStates.removeLast();
}
else
{
previousStateId = previousState.getId();
viewStates.add(previousStateId);
}

In the first compound statement if you remove the last state and then attempt to access is (as was happening in the previous version) you will get an exception.

wm
Reply With Quote
  #7  
Old Jan 11th, 2007, 02:24 AM
ojs ojs is offline
Senior Member
 
Join Date: Jul 2005
Location: Munich, Germany
Posts: 153
Default

Quote:
Originally Posted by wee_malky View Post
In the first compound statement if you remove the last state and then attempt to access is (as was happening in the previous version) you will get an exception.
No, that's not right. Example:

Code:
LinkedList<String> l = new LinkedList<String>();

l.add("A");
l.add("B");
l.add("C");

System.out.println(l.removeLast());
System.out.println(l.getLast());
That will print C and then B because LinkedList's internal iterator will be adjusted when removeLast() is called.

My code will only throw an exception if you have a back button on the first page because after the removeLast() there's no element left. If you change the order of the removeLast() and getLast() calls (as you did), then code shouldn't work anymore, does it?

Anyway, thanks for the review!


Best

Oliver
Reply With Quote
  #8  
Old Jan 11th, 2007, 10:38 AM
wee_malky wee_malky is offline
Junior Member
 
Join Date: Sep 2006
Posts: 4
Default Fixed version

Oliver,

Quite right. Some wooly thinking on my behalf. As you move back you remove the last 'previous' entry so that the remaining one is the new valid value.

I have another refinement for you. When you are on a form and you enter invalid values the bindAndValidate will take you back to the same page with the error messages displayed. This transition will of course get added into the viewStates list. Oh dear. Pressing the back button on current form-with-errors will take you back to the form-without-errors.

To fix this we must catch the transition to form-with-errors page and remove the incorrect previous state (which would have taken us back to form-without-errors). No better way to explain than to produce some code, voila!

Code:
    @Override
    public void stateEntered(final RequestContext context,
                             final StateDefinition previousState,
                             final StateDefinition state)
    {
        @SuppressWarnings("unchecked")
        final LinkedList<String> viewStates = (LinkedList<String>)context.getFlowScope().get(viewStatesName);
        
        String previousStateId = null;
        
        if (previousState instanceof ViewState)
        {
            // Back button pressed
            if (context.getLastEvent().getId().equals(backEventId))
            {
                if (!viewStates.isEmpty())
                { 
                    viewStates.removeLast();
                    if (!viewStates.isEmpty()) previousStateId = viewStates.getLast();
                }
            }
            else
            {
                previousStateId = previousState.getId();
                viewStates.add(previousStateId);
            }
            
            log.info("previousStateId = " + previousStateId);
            if (previousStateId!=null) context.getFlowScope().put("previousViewStateId", previousStateId);
        }
        else 
            if (state instanceof ViewState && viewStates!=null)
            {
                if (!viewStates.isEmpty())
                {
                    // If we an re-entering a state which is already stored then roll back 
                    if (state.getId().equals(viewStates.getLast()))
                    {
                        viewStates.removeLast();
                        if (!viewStates.isEmpty()) previousStateId = viewStates.getLast();
                        
                        log.info("previousStateId = " + previousStateId);
                        if (previousStateId!=null) context.getFlowScope().put("previousViewStateId", previousStateId);
                    }
                }
            }
    }
I _think_ this is last piece in puzzle of how to implement a proper wizard flow.

WM
Reply With Quote
  #9  
Old Jan 12th, 2007, 04:27 AM
ojs ojs is offline
Senior Member
 
Join Date: Jul 2005
Location: Munich, Germany
Posts: 153
Default

Quote:
Originally Posted by wee_malky View Post
I have another refinement for you. When you are on a form and you enter invalid values the bindAndValidate will take you back to the same page with the error messages displayed. This transition will of course get added into the viewStates list. Oh dear. Pressing the back button on current form-with-errors will take you back to the form-without-errors.
Oh, thanks for pointing that out.

What do you think about this (somewhat less complex) code to solve this. Have I overlooked something?

Code:
    private LinkedList<String> getViewStates(final RequestContext context)
    {
        @SuppressWarnings("unchecked")
        final LinkedList<String> viewStates =
            (LinkedList<String>)context.getFlowScope().get(viewStatesName);

        if (viewStates == null)
            throw new IllegalStateException("viewStates is null");

        return viewStates;
    }

    @Override
    public void stateEntered(final RequestContext context,
                             final StateDefinition previousState,
                             final StateDefinition state)
    {
        // If there's no previous ViewState, there's nothing we can do...
        if (!(previousState instanceof ViewState))
            return;

        // If we're entering the same state (due to a reload or
        // binding error), ignore that...
        if (previousState.getId().equals(state.getId()))
            return;

        final LinkedList<String> viewStates = getViewStates(context);

        final String previousStateId;

        if (context.getLastEvent().getId().equals(backEventId))
        {
            viewStates.removeLast();
            previousStateId = viewStates.isEmpty() ? "" : viewStates.getLast();
        }
        else
        {
            previousStateId = previousState.getId();
            viewStates.add(previousStateId);
        }

        context.getFlowScope().put("previousViewStateId", previousStateId);
    }
Best

Oliver
Reply With Quote
  #10  
Old Jan 15th, 2007, 06:33 AM
klr8 klr8 is offline
Senior Member
Spring Team
 
Join Date: Sep 2004
Location: Leuven, Belgium
Posts: 1,844
Default

Good stuff guys!
Might be a good idea to attach this to a JIRA issue so that it's available for everybody and for possible inclusion in a future version of SWF!

Erwin
Reply With Quote
Reply

Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off

Forum Jump


All times are GMT -5. The time now is 02:47 AM.


Contegix provides first-class managed hosting and partial sponsorship of these forums.

Powered by vBulletin® Version 3.8.4
Copyright ©2000 - 2010, Jelsoft Enterprises Ltd.