Ian Bicking: the old part of his blog

Web Application Patterns: Status Notification

(Were you looking for PyLogo?)

Here's a little pattern I use quite often in web applications. Like a lot of patterns, it's not tied to any particular environment, or even to any specific application. There's a lot of these kinds of patterns out there that are specific to web applications (in addition to the standard patterns), but they don't seem to be well collected, defined, or named...

Frequently you will need to present out-of-context information to the user. For instance, in a webmail application when sending an email. A page saying "email successfully sent" isn't very interesting. Simply redirecting the user to the email index isn't very friendly either: it's important to give some feedback when a user performs a state-altering action. You want to predict what the user is likely to be most interested in, and direct them there, but you also want to put some notification about what just happened.

In whatever framework I am using (Webware, Zope, PHP), I'll usually add something like this to the template header (Webware here):

class SitePage(Page):
....
def writeMessage(self):
if self.session().value('messages'):
self.write('<div class="alert">%s</div>' %
'<br>\n'.join(session.value('messages')))
self.session.setValue('messages', [])
def sendMessage(self, msg):
self.session().setValue('messages',
self.session().value('messages', []) + [msg])
... and calling writeMessage somewhere in the SitePage header. In Zope (somewhere in standard_template.pt):
<div tal:condition="request/SESSION/message | nothing"
style="border: thin black solid; background-color: #ddddff">
<tal:block repeat="msg request/SESSION/message">
<span tal:replace="msg" /><br>
</tal:block>
<tal:block replace="python: request.SESSION.set('message', [])" />
</div>
It's important to reset the messages session variable only when you actually display it, as the user may get redirected before the template is rendered (especially if you use redirect after POST -- another pattern -- which works well with this technique).

If you haven't seen tal:block before, any tag prefixed with tal: gets removed from the final ZPT output, and any attributes of that tag are treated as TAL attributes. It saves a bit of typing, and anyone using ZPT can use all the help they can get to save typing.

People sometimes implement this in different ways. For instance, they simply invoke the template passing in a message variable. So in a webmail application, this might look like:

# send mail action:
invalid_message = validate_email(request) # return None if valid
if invalid_message:
return sendmail_form(message=invalid_message)
sendmail(user.email, request.to, request.subject, request.body)
return mail_index(message='Email to %s sent successfully' %
request.to)
Anyone who likes to use the back button knows what a pain this sort of application is to use. A common navigation would be to arrive at the index page after sending, view an email, and then you want to go back to the index (using your back button). But you can't, because that index was the result of a POST, and probably isn't cachable. To reload it would resend the email. (In contrast: for the invalid case passing in a variable works well)

A solution to this is to redirect the user after sending their email. There's a few gotchas here relating to exactly what result code you use (there's a whole set of 3xx result codes), but that's another topic.

People sometimes start passing the messages in GET variables, e.g. redirect('/index?message=Email+sent+successfully'). This is terribly awkard, and it's not worth it to go down this path. Just use sessions, which are perfectly suited for this task. The GET variable is also flawed because that message is forever tied to that page, even after a reload, and even after the message is no longer timely.

Once you integrate this into your templates, you'll find yourself using these messages all the time. You can use it to add quick error messages, where before you might have been too lazy, or just have used some sort of generic "There was an error, hit the back button on your browser and correct it" page. You can notify the user of all sorts of little events which you might otherwise simply imply (e.g., "item deleted from cart", "edits saved", and so on). These updates give the user a clear idea of what they've done, what they need to do next (because they usually are in the middle of some longer set of tasks only they understand), and generally that their efforts are successful.

Created 18 Nov '03
Modified 25 Jan '05

Comments:

I very much liked the article.

I've started to use 300 codes in my applications so that I can preserve the Back button, and so that I can use one set of URLs for handling code, before redirecting to the correct page for fetching the results.

I'm sure that there are many other patterns for web development, one that comes to mind is a page which starts a long running search, then reloads itself until it completes (like most travel sites).

Aaron
# Aaron Brady

You have to be careful in Zope when redirecting and using sessions.

If you raise the string 'Redirect' (capital R), it will cause Zope to abort the transaction and thus, abort the session write. There are cases when this is what you want - going along, coming across a problem after some data has already been written, and wanting to alert the user to this fact.

When you use the redirect method on Zope's RESPONSE object, the transaction will commit and the session data will be retained.

In cases like this, I've started using my own API calls such as 'returnWithMessage' which takes, among other parameters, a 'do_raise' argument. If 'do_raise' is true, the Redirect is raised as an error. If false, a regular redirect occurs. I've just added smarts into one of those calls to use a new 'page_messenger' service I wrote, inspired by your post (it's a bit cleaner than calling request.SESSION.set in a template, and has let me retain expections from current code that passes the message in via the request or a template call option).
# Jeffrey Shell

it does not seems to be 'transactions-aware'. I mean if somebody will request very fast 2 pages, where 1st returning a error, then both of the pages will display same error. may be I am wrong, but it seems to be so.

personally I prefer to drive template from a script in this case, passing variables directly to template. its possible in php, webware, and Zope. Last year I am working with Zope, here is example in script:

errors=[]
if username=='':
errors.append('Please enter your username')
if password=='':
errors.append('Please enter your password')

if len(errors)>0:
request.set('errors', eroors)
return getattr(context,'login.html)(container,request)

so, in the page template I loop over the error list same way as described in the Ian example above.

its also more secure, since 3rd party do not see any particular data into URL GET requests, so both Session, and this example should be good.

# Alex V. Koval