You are viewing a plain text version of this content. The canonical link for it is here.
Posted to user@struts.apache.org by "Nelson, Laird" <La...@FMR.COM> on 2003/04/16 21:38:32 UTC

RE: Page Context Stack - a general solution to the problem of re turning to your start page

> -----Original Message-----
> From: Jeff Smith [mailto:jeffs@centralscheduling.net]
> The problem is, I still don't know if I've re-invented a 
> wheel here. Would
> this be useful to anybody else?

Very much so.

Cheers,
Laird

---------------------------------------------------------------------
To unsubscribe, e-mail: struts-user-unsubscribe@jakarta.apache.org
For additional commands, e-mail: struts-user-help@jakarta.apache.org


Re: [Long] Re: Page Context Stack - a general solution to the problem of returning to your start page

Posted by Cody Sherr <cs...@covalent.net>.
I've implemented something similar. However, I took a different approach
for item 1). Instead of generating a return path on all the pages that
might be the origin, I set it in my controllers. Each controller has the
responsibility of generating a return url that represents it's current
state and saving it in the session. If a user enters a workflow, that
returnPath is taken from the session and is pushed onto the workflow as
it's origin.

-Cody Sherr



On Wed, 2003-04-16 at 14:51, Jeff Smith wrote:
> Jefficus said: Would that be useful to anybody?
> Laird said: Very much so.
> 
> So here ya go.
> 
> There are three steps to the problem:
> 1) Determining what page you came from.
> 2) Storing that page info when you start an action sequence
> 3) Returning to that page when you complete (or abort) an action sequence.
> 
> 1) At first I was going to use request.getHeader("referer"). But then I read
> a couple of postings about how it is not reliably provided by some client
> browsers. So I decided to do it differently. At the top of every JSP in my
> app, I have placed the following code:
> <c:set var="waiURL">
>   <c:out  value="${pageContext.request.requestURL}" escapeXml="false"/>
>     <c:if test="${pageContext.request.queryString != null}">
>       ?<c:out  value="${pageContext.request.queryString}"
> escapeXml="false"/>
>     </c:if>
> </c:set>
> <bean:define id="PageStackCurrentLocation" toScope="session">
>   <c:out value="${waiURL}" escapeXml="false"/>
> </bean:define>
> 
> (Improvements to this gobbledy-gook would be most welcome. :-)
> 
> There are probably more elegant ways to accomplish this - I am no JSP taglib
> genius. But essentially this fragment constructs the full URL path of the
> page that is being rendered and places that URL (including request params)
> into a session bean called PageStackCurrentLocation.
> 
> If you have a bunch of disjoint JSP files, you'll probably want to put this
> fragment in a separate file and include it into each of your JSPs.
> Fortunately for me, I use a single Tiles template to control the layout of
> all my pages, so I just had to stick it in the template.
> 
> 
> 2) Now my user invokes a "floating action chain", say the Edit User Profile
> sequence which in my app can be accessed from every single page. The link
> forwards to the first action in the sequence. In my app, most sequences
> (even simple editors) have a prepAction that I go to first so that I can do
> some logging and any pre-display processing. (This prepAction is required
> for now, but down below I'll explain how I've removed this requirement.)
> 
> When I get to my prepAction, the first thing I do is call
> PageStack.pushPage( ). Since I have not yet forwarded to the JSP for this
> action, my session PageStackCurrentLocation bean is still pointing to the
> JSP I just left. (Neat, huh?) I simply grab this from the session scope and
> put it into another session bean called PageStackMemory.
> 
> In my world, I implemented PageStackMemory as a simple String array of size
> 8. I'm never going to nest 8 action chains. But you can be as fancy as you
> like with dynamic array lists and whatever turns your crank. I just place my
> URL in the first empty cell of the array and move on. Now the prepAction can
> do its other processing and jump to its associated JSP or go wherever it
> likes.
> 
> 
> 3) Sooner or later, the action sequence will either terminate or the user
> will abort. In either case, I now want to go back to my starting point. So
> instead of looking for an action forward (I'll explain that part later) I
> call PageStack.popPage( ) which goes to my PageStackMemory array in the
> session and grabs the first non-empty cell (starting at the end of the
> array) and set that cell to empty. PageStack.popPage( ) then calls
> response.Redirect(myURL) and I jump back to where I started.
> 
> At any point between step 2 and 3,  I can allow the user to start another
> floating sequence, pushing the URL of the current page and then popping back
> to it when that sub-sequence is  done. In my case, I can only allow 8 of
> these nested sequences, but you get the idea.
> 
> 
> 
> Eliminate Requirement for PrepAction
> The next trick is to eliminate the requirement for a prepAction at the
> beginning of each action chain. The class I referred to above, PageStack, is
> actually an Action class. (More specifically, its a DispatchAction, but who
> wants to quibble?)
> 
> Suppose I want the user to be able to logon from any page he might be on in
> the app, and then return to that page after logon. If you're a normal
> person, you don't have a prepAction for your logon sequence. So how do you
> grab the current page before displaying your logon form?
> 
> Here's how it works - create a global forward that looks like this:
> <forward name="startLogon"
> path="/PageStack.do?operation=pushPage&NextAction=logon/>
> 
> And then set your logon link like this: <html:link forward="startLogon"/>
> 
> When the user clicks on the logon link, it will forward to the
> PageStack.pushPage method, which, as you may recall, is where I grab the
> current page and put it into the PageStackMemory bean. After doing so, it
> looks in the request params for the name of the NextAction and forwards
> there.  In our example, that would be your traditional "logon" action.
> Voila!. You've reached your form page and you've saved the URL of the start
> location along the way.
> 
> 
> 
> Eliminate Direct call to PageStack.popPage
> When I was in there setting up the global forward for startLogon, I also set
> one up called popPage, like this:
> <forward name="popPage" path="/PageStack.do?operation=popPage/>.
> 
> Now any action chain that wants to return to its starting place can just
> forward to "popPage" and it will work, as long as it went through pushPage
> at the outset.
> 
> 
> Support More Complex First Actions
> There were two special cases that I encountered while setting this up, that
> we still haven't covered.
> 
> A) What if the first page of my sequence takes request params to control its
> function?
> 
> Simple.  Just create another global forward like this:
> <forward name="paramInfestedActionStep"
> path="/MyAction.jsp?someparam=foo&otherparam=bar"/>
> Now your startLogon declaration can set NextAction=paramInfestedActionStep
> and everything will be peachy.
> 
> B) Even worse, my first step not only takes params, but they are determined
> dynamically, so I can't create a statically declared global action to get me
> there.
> 
> Again, no problem. My implementation of pushPage is smart - it takes all of
> its incoming params and relays them to whoever it forwards to. (except the
> operation and NextAction params which are consumed locally). So if your call
> to "startLogon" includes params, they will be passed to
> paramInfestedActionStep and the world should proceed apace. (Unless you are
> unlucky enough to have params called NextAction or operation. But I'll leave
> fixing that as an exercise for the reader.)
> 
> So now we have a page stack system that allows recursive action sequences to
> declare both their start and end using the struts-config action forwards,
> but the definitions are not rooted to specific URLs. It's a little
> complicated when you first look at it, but once you understand it, its
> pretty simple to work with. (Sounds like most of struts. :-) And best of
> all, it doesn't require changes to the struts code itself, so it should work
> on (most? many? some?) struts revs out there.
> 
> Hope some of you find that useful,
> Jefficus
> 
> 
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: struts-user-unsubscribe@jakarta.apache.org
> For additional commands, e-mail: struts-user-help@jakarta.apache.org
> 
> 


---------------------------------------------------------------------
To unsubscribe, e-mail: struts-user-unsubscribe@jakarta.apache.org
For additional commands, e-mail: struts-user-help@jakarta.apache.org


[Long] Re: Page Context Stack - a general solution to the problem of returning to your start page

Posted by Jeff Smith <je...@centralscheduling.net>.
Jefficus said: Would that be useful to anybody?
Laird said: Very much so.

So here ya go.

There are three steps to the problem:
1) Determining what page you came from.
2) Storing that page info when you start an action sequence
3) Returning to that page when you complete (or abort) an action sequence.

1) At first I was going to use request.getHeader("referer"). But then I read
a couple of postings about how it is not reliably provided by some client
browsers. So I decided to do it differently. At the top of every JSP in my
app, I have placed the following code:
<c:set var="waiURL">
  <c:out  value="${pageContext.request.requestURL}" escapeXml="false"/>
    <c:if test="${pageContext.request.queryString != null}">
      ?<c:out  value="${pageContext.request.queryString}"
escapeXml="false"/>
    </c:if>
</c:set>
<bean:define id="PageStackCurrentLocation" toScope="session">
  <c:out value="${waiURL}" escapeXml="false"/>
</bean:define>

(Improvements to this gobbledy-gook would be most welcome. :-)

There are probably more elegant ways to accomplish this - I am no JSP taglib
genius. But essentially this fragment constructs the full URL path of the
page that is being rendered and places that URL (including request params)
into a session bean called PageStackCurrentLocation.

If you have a bunch of disjoint JSP files, you'll probably want to put this
fragment in a separate file and include it into each of your JSPs.
Fortunately for me, I use a single Tiles template to control the layout of
all my pages, so I just had to stick it in the template.


2) Now my user invokes a "floating action chain", say the Edit User Profile
sequence which in my app can be accessed from every single page. The link
forwards to the first action in the sequence. In my app, most sequences
(even simple editors) have a prepAction that I go to first so that I can do
some logging and any pre-display processing. (This prepAction is required
for now, but down below I'll explain how I've removed this requirement.)

When I get to my prepAction, the first thing I do is call
PageStack.pushPage( ). Since I have not yet forwarded to the JSP for this
action, my session PageStackCurrentLocation bean is still pointing to the
JSP I just left. (Neat, huh?) I simply grab this from the session scope and
put it into another session bean called PageStackMemory.

In my world, I implemented PageStackMemory as a simple String array of size
8. I'm never going to nest 8 action chains. But you can be as fancy as you
like with dynamic array lists and whatever turns your crank. I just place my
URL in the first empty cell of the array and move on. Now the prepAction can
do its other processing and jump to its associated JSP or go wherever it
likes.


3) Sooner or later, the action sequence will either terminate or the user
will abort. In either case, I now want to go back to my starting point. So
instead of looking for an action forward (I'll explain that part later) I
call PageStack.popPage( ) which goes to my PageStackMemory array in the
session and grabs the first non-empty cell (starting at the end of the
array) and set that cell to empty. PageStack.popPage( ) then calls
response.Redirect(myURL) and I jump back to where I started.

At any point between step 2 and 3,  I can allow the user to start another
floating sequence, pushing the URL of the current page and then popping back
to it when that sub-sequence is  done. In my case, I can only allow 8 of
these nested sequences, but you get the idea.



Eliminate Requirement for PrepAction
The next trick is to eliminate the requirement for a prepAction at the
beginning of each action chain. The class I referred to above, PageStack, is
actually an Action class. (More specifically, its a DispatchAction, but who
wants to quibble?)

Suppose I want the user to be able to logon from any page he might be on in
the app, and then return to that page after logon. If you're a normal
person, you don't have a prepAction for your logon sequence. So how do you
grab the current page before displaying your logon form?

Here's how it works - create a global forward that looks like this:
<forward name="startLogon"
path="/PageStack.do?operation=pushPage&NextAction=logon/>

And then set your logon link like this: <html:link forward="startLogon"/>

When the user clicks on the logon link, it will forward to the
PageStack.pushPage method, which, as you may recall, is where I grab the
current page and put it into the PageStackMemory bean. After doing so, it
looks in the request params for the name of the NextAction and forwards
there.  In our example, that would be your traditional "logon" action.
Voila!. You've reached your form page and you've saved the URL of the start
location along the way.



Eliminate Direct call to PageStack.popPage
When I was in there setting up the global forward for startLogon, I also set
one up called popPage, like this:
<forward name="popPage" path="/PageStack.do?operation=popPage/>.

Now any action chain that wants to return to its starting place can just
forward to "popPage" and it will work, as long as it went through pushPage
at the outset.


Support More Complex First Actions
There were two special cases that I encountered while setting this up, that
we still haven't covered.

A) What if the first page of my sequence takes request params to control its
function?

Simple.  Just create another global forward like this:
<forward name="paramInfestedActionStep"
path="/MyAction.jsp?someparam=foo&otherparam=bar"/>
Now your startLogon declaration can set NextAction=paramInfestedActionStep
and everything will be peachy.

B) Even worse, my first step not only takes params, but they are determined
dynamically, so I can't create a statically declared global action to get me
there.

Again, no problem. My implementation of pushPage is smart - it takes all of
its incoming params and relays them to whoever it forwards to. (except the
operation and NextAction params which are consumed locally). So if your call
to "startLogon" includes params, they will be passed to
paramInfestedActionStep and the world should proceed apace. (Unless you are
unlucky enough to have params called NextAction or operation. But I'll leave
fixing that as an exercise for the reader.)

So now we have a page stack system that allows recursive action sequences to
declare both their start and end using the struts-config action forwards,
but the definitions are not rooted to specific URLs. It's a little
complicated when you first look at it, but once you understand it, its
pretty simple to work with. (Sounds like most of struts. :-) And best of
all, it doesn't require changes to the struts code itself, so it should work
on (most? many? some?) struts revs out there.

Hope some of you find that useful,
Jefficus


---------------------------------------------------------------------
To unsubscribe, e-mail: struts-user-unsubscribe@jakarta.apache.org
For additional commands, e-mail: struts-user-help@jakarta.apache.org