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.