Because we all make mistakes
I designed co.mments, so it’s hard to accidentally delete conversations. I did what other Web developers do and added a confirmation popup. It looked good on paper.
Most times, I really do mean to delete stuff, and confirmation popups just get in the way. When I’m scattered, usually early morning before the coffee kicks in, I end up clicking (and confirming) the wrong stuff. If I’m not paying attention to what I delete, I can’t find it again. Especially when I’m deleting something I added a few days back.
Software that forces you to think twice before you do anything is software you can’t trust. And software you can’t trust isn’t fun to use.
So I got rid of the confirmation popup and added an undo button.

How it works
It’s surprisingly easy to do. Now I wonder why I didn’t do it before. I’d like to see that in all my favorite apps, so I’m going to share the recipe and code with you. If you’re building the next best Web app, please add this. Or get your favorite geek to add it to their site.
Each action you do has a URL with all the parameters. I created an undo stack that stores these URLs in the user’s session. Each time you perform an action, the server pushes another undo action URL to the stack. If you just created a new post, the server pushes a ‘delete that post’ URL to the stack.
An undo button at the top of the page uses the most recent undo action URL. Click on the button and it will perform the ‘delete that post’ action. It also removes that action from the stack, so you can undo the previous action. For co.mments, I set the undo stack to five actions, so you can undo more than one change, but it doesn’t take over the database.
Since you can perform an action directly or by undoing a previous button, the undo URL includes the parameter undo=true.
Example using Rails
Here’s the Rails controller with a create action that creates a new record, a delete action that will undo it, and a before_filter that removes undo actions from the stack:
# Remove undo action from stack. before_filter do |controller| controller.undo.pop(controller.params) if controller.params[:undo] end # The create action creates a new record, the undo action deletes it. def create() # Do something useful here record = Record.create(@params) # If this is not an undo, add an undo action to delete the record. unless @params[:undo] undo.push (”Delete newly created record”, :action=>”delete”, :id=>record.id) end # Render page (see below). . . . end def delete() . . . end
A third method renders the undo form and button, using the last undo action from the stack. The view creates a wrapper element and renders an action:
<div id="undo"><%= undo.render %></div>
For AJAX requests, I use RJS to instruct the browser to update the undo button without reloading:
render :update do |page| page["undo"].replace_html undo.render end
Get the code
I wrapped the code as a Rails plugin. To install the plugin into your Rails app:
./script/plugin source http://labnotes.org/svn/public/ruby/rails_plugins/ ./script/plugin install undo_helper
You can find the source code here. You can also try it out on the co.mments server.
Update: I forgot to mention that GMail has had undo for a while, although only for the last action. Hans posted a screen shot of undo on Technorati.

Anonymous
May 24th, 2006 at 1:16 am
hans.gerwitz
May 25th, 2006 at 7:40 am
Luke
May 26th, 2006 at 3:09 am
Assaf
May 26th, 2006 at 7:56 am
undo_helper: Add ‘Undo’ to your Rails application
June 2nd, 2006 at 7:01 am
johan
August 10th, 2006 at 12:35 am
Assaf
August 10th, 2006 at 1:46 am
Terry Darnell
October 16th, 2006 at 1:38 pm
Andy Stewart
July 18th, 2007 at 1:03 am
Assaf
July 18th, 2007 at 11:34 am
Labnotes » Rounded Corners - 128 (lolcathost)
July 20th, 2007 at 12:32 am
Contrast | The Blog | 37signals’ biggest flaw
February 8th, 2008 at 6:55 am