|
#1
|
|||
|
|||
|
Hello,
I'd like to configure a global transition for a form-based "back"-button. Is that possible? Best Oliver |
|
#2
|
|||
|
|||
|
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 |
|
#3
|
|||
|
|||
|
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 |
|
#4
|
|||
|
|||
|
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 |
|
#5
|
|||
|
|||
|
Quote:
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 |
|
#6
|
|||
|
|||
|
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 |
|
#7
|
|||
|
|||
|
Quote:
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());
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 |
|
#8
|
|||
|
|||
|
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);
}
}
}
}
WM |
|
#9
|
|||
|
|||
|
Quote:
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);
}
Oliver |
|
#10
|
|||
|
|||
|
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 |
![]() |
| Thread Tools | |
| Display Modes | |
|
|