A common idiom in web development is that a form is submitted via POST, and then a redirect is done to a success page. The reason for this is that if we left the result of the submission to return HTML to the browser, we would run into the infamous and confusing message “To display this page, Firefox must send information that will repeat any action (such as a search or order confirmation) that was performed earlier.” (At least the way firefox tells you). This has become none as the POST/REDIRECT/GET pattern.
The problem becomes that, because of the stateless nature of HTTP, this second request has absolutely no connection to the previous request. If we just wanted to display a thank you page, or a success page, this would not be much of a problem. But what if we wanted to display the same form again, and display a success message on top when its submitted?
One way of doing this, which is the way Expression Engine does it, is to pass in a parameter in the query string, like ?success=1. The problem with this is that now if somebody refreshes the page, they’ll see the same message. Not the biggest deal in the world, but can we do better?
The answer to all the statelessness of HTTP is with sessions. There is a pattern that takes advantage of this called a “flash message“. The basic idea is to store a value in a session when the form is submitted, and to echo it back the first time the page is displayed again, after a succesful POST.
I wanted this functionality in some of my Expression Engine SAEF’s and other such forms. I also wanted a way to easily set up flash messages, within the form itself, so that I didn’t have to hack any modules, or look for a hook.
Here is what the API looks like. I will pick on solspaces {exp:user:edit} which I am using to create a edit profile form.
Image the following form:
<h1>My Profile</h1>
{exp:user:edit}
<label for="first_name">First Name</label>
<input name="first_name" type="text" value="{first_name}" />
<label for="last_name">Last Name</label>
<input name="last_name" type="text" value="{last_name}" />
<input type="submit" />
{/exp:user:edit}This creates a basic profile form where the user can change his first and last name (assuming these custom member variables were set up). If we submit this form, it will return right to were it came from, with no indication as to that anything happened.
So we will change it like this:
<h1>My Profile</h1>
{flash_msg}
{exp:user:edit}
{exp:flash_msg msg="Profile edited succesfully!"}
<label for="first_name">First Name</label>
<input name="first_name" type="text" value="{first_name}" />
<label for="last_name">Last Name</label>
<input name="last_name" type="text" value="{last_name}" />
<input type="submit" />
{/exp:user:edit}Now when we submit the form, we are greeted with the message “Profile edited succesfully!”. Refresh the page, and its gone!
And now for the exciting technical part! How is this magic done?
First the general overview: The plugin creates a hidden form element who’s value is a random hash. At the same time this element is created, a session variable is created with this hash and the message, as well as a flag. There is a extension on the session_end hook which looks for the presence of this form element, and the session variable. If the two hashes are the same, the flag in the session variable is set, and the plugin knows it can display the session variable.
Here is the extension:
function sessions_end($SESS) { global $IN; if (REQ == 'ACTION') { $hash = $IN->GBL('flash-hash'); if ($hash) { session_start(); if ($_SESSION['flash_msg']['flash-hash'] == $hash) { $_SESSION['flash_msg']['done'] = true; } } } }
I think that code is pretty self explanatory (or at least I explained it earlier).
Now the plugin:
function Flash_msg() { global $TMPL, $FNS; $hash = $FNS->random('encrypt'); session_start(); if (isset($_SESSION['flash_msg']) && $_SESSION['flash_msg']['done']) { $TMPL->template = $TMPL->swap_var_single('flash_msg', $_SESSION['flash_msg']['msg'], $TMPL->template); } else{ $TMPL->template = $TMPL->swap_var_single('flash_msg', '', $TMPL->template); } $_SESSION['flash_msg'] = array('done' => false, "flash-hash" => $hash, "msg" => $TMPL->fetch_param('msg')); $this->return_data = "<input name="flash-hash" type="hidden" value="$hash" />"; }
Pretty straight forward, I think. The only interesting thing here is the line:
$TMPL->template = $TMPL->swap_var_single('flash_msg', '', $TMPL->template);
This takes advantage of some bad OOP design on Expression Engine’s part, so that my plugin can make changes, like variable substitution, anywhere in the template!
There’s one problem with all of this though. What if we set one of the fields in that form to required, and left it out? We would see the output of the Expression Engine’s error page, and when we went back, the flash message would appear! We need a way at to determine if the form was successful or not. Unfortunately, a successful submit is merely a semantic thing. Its up to the programmer to determine if was successful or not. I though about this problem for a long time, and then came up with an ingenious, almost hackish solution. If only there was a hook in the Output::show_user_error() method!
In absence of that I thought, why don’t I just subclass the Output class, and overwrite that method, and then replace the global $OUT variable with new version!
As simple as this:
class MyOutput extends Output { function show_user_error($type = 'submission', $errors, $heading = '') { $_SESSION['flash_msg']['done'] = false; return parent::show_user_error($type, $errors, $heading); } }
This just simply overwrites the show_user_error method, resets the flag, and shores off back to its parent to do the rest!
I simply include this little class at the bottom of the extension and then we rewrite our extension method as follows:
function sessions_end($SESS) { global $IN, $OUT; if (REQ == 'ACTION') { $hash = $IN->GBL('flash-hash'); if ($hash) { $OUT = new MyOutput(); session_start(); if ($_SESSION['flash_msg']['flash-hash'] == $hash) { $_SESSION['flash_msg']['done'] = true; } } } }
All I’m doing now is replacing the global $OUT variable with my own subclassed Output class!
#1 by Philip Zaengle at May 19th, 2009
Great extension! I was missing :flash from rails, and actually requested this as a feature request. Thanks for the great add on!
#2 by Avi at May 20th, 2009
I’m glad you enjoyed this. I have now officially released this in the EE forum…http://expressionengine.com/forums/viewthread/115057/
#3 by Dan Jasker at January 14th, 2010
So does this only work for sites that don’t have “moderate comments” set? I seem to only get the “Flash Message” when I’m logged in as the admin, but since I like to approve before they show..then this wouldn’t work, right?