[prev in list] [next in list] [prev in thread] [next in thread] 

List:       tapestry-dev
Subject:    Back button and call transitions (1)
From:       "Mindbridge" <mindbridgeweb () yahoo ! com>
Date:       2003-09-22 2:38:18
[Download RAW message or body]


Hi guys,

Thank you for the messages. I am sorry for changing the topic a little, but before \
continuing with the rest of the call transitions story, I'd like to make a small \
detour. Handling of the back button is critical for the correct operation of call \
transitions and it does influence some design decisions. Sorry if much of this is \
obvious, I just want to be complete.


BACK BUTTON AND REFRESH

The Back button is dangerous because it causes desynchronization between the visible \
state and the session state.

The web application has an 'idea' of what the user sees and what he can do, and acts \
on his clicks and renders future pages accordingly. That 'idea' (or state in other \
words) is kept as session data, i.e. in the Visit. When the back button is used, \
however, this 'idea' is no longer correct -- what the user sees in his browser (an \
old page, typically) reflects one state, but it is different from the state kept in \
the user's session on the server, i.e. what the server thinks the user is seeing. As \
a result, invoking a server-side action in the older page (by clicking on a link) \
will cause the action to be executed in the context of the session state, and that \
may be quite different from what the user expects to happen based on what he is \
currently seeing (i.e. the visible state). 

(An interesting corollary of the above is that an application that is stateless or \
only uses its Visit (session) as a cache will not have Back button issues since \
desynchronization is impossible in its case. One more reason to write stateless apps \
if possible :)

A few quick observations:

- Refresh (F5) is functionally identical to pressing the back button once and then \
clicking on the link selected before again. The request generated is exactly the same \
and from the view point of the server there is no difference. In a sense Refresh can \
be looked at as a special case of using the Back button.  (Konstantin Sharenkov \
offered in the user list an ingenious way to improve the Refresh handling by \
separating the action and render phases through an intermediate client-side request. \
It is neat, but unfortunately it has a number of disadvantages -- it makes things \
much slower as perceived by the user; client-side redirect does not seem to be \
universally supported, either deliberately or due to bugs, etc.)

- Opening a second browser with the same app using ctrl-click (or Ctrl-N) and then \
using both windows has the same side-effects as using the Back button -- by the \
virtue that the windows are different but share the same session, at least one of \
them will be with an apparent 'state' that is different from the one that the server \
has. (and things get even worse and more confusing to the server from then on as both \
windows keep getting used)


The quick conclusion one can draw here is that for proper operation of the program, \
NO state that influences the actions performed by the links in the program should be \
kept in the session. 

For example, suppose that you have a page that lists a selection of products and you \
have an "add to basket" link at the bottom. In order to make the behaviour of the \
link stable, it (or the request it generates) should contain the IDs of the items \
listed, rather than keep that list in the session and act on it when clicked. The \
former is immune to desynchronization, the latter is not.


That said, is it always possible to use this approach?

The answer, unfortunately, is no. Here are a few examples:

    - multi-page selections, i.e. selecting items from a list spanning several pages. \
(e.g. emails listed in a web interface). One could drag the selected list from \
request to request, but this is simply impractical if the list is large. Rather than \
hog the network traffic and slow things down, it will probably be better to store and \
update the list in the session.

    - presenting data for approval by the user, i.e. showing a list of people who \
will be emailed. It is not possible to get the list from the database when the "send \
emails" button is clicked, since it may have changed between the time the list was \
shown and the button was pressed. Hence the list must be contained either in the \
request or in the session. If it is large, again, keeping it in the request is not \
practical and the session must be used. (this is an example directly lifted from some \
of our apps)


In other words, we cannot always circumvent using the session, so we might just bite \
the bullet and see what we can do about it. Here are some thoughts:

Detecting desynchronization is easy. We can associate a UID with our server state, \
modify it with every change (or each new request -- an easier solution), encode it in \
the links and the forms, and upon request verify that the UID submitted is identical \
to the UID of the state. If it is different, then desynchronization has (probably) \
occurred. 

This catches things like pressing F5 after a form has been submitted (thus reissuing \
the form submission), any use of the Back button, etc. Should we throw StaleLink \
exception every time we detect desynchronization though? Obviously not. In most cases \
the program can easily 'recover', especially if the information needed to execute the \
action is contained only in the request. (as described above)

The problem is that the framework has NO WAY of knowing by itself whether the \
desynchronization is dangerous or not. This depends entirely on the application's \
implementation, and should something be done, it should be done by the application. \
Nevertheless, in the cases where it is dangerous, it will be excellent for the \
application to know that is the case and act accordingly, e.g. by letting the user \
know that this is a stale link and it cannot execute his request.

This could be implemented by making the framework use the method above to detect \
desynchronizations and act on them if a binding similar to 'stateful' in Form is set. \
(another alternative is to have a listener that is always invoked but does nothing by \
default -- if something is wrong, the program can take action; it seems to me that a \
binding idea is probably easier to use though). Personally, I believe that having \
such a mechanism would be very useful in a number of cases if the application needs \
to be robust.


(to be continued)



[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic