Death to confirmation dialogs with jquery.undoable

Confirmation dialogs were designed by masochists intent on making users of the web miserable. At least that’s how I feel when I run into too many of them. And yes, if you take a look at Subtext, you can see I’m a perpetrator.

Well no longer!

I was managing my Netflix queue recently when I accidentally added a movie I did not intend to add (click on the image for a larger view).

netflix-queue Naturally, I clicked on the blue “x” to remove it from the queue and saw this.

netflix-queue-deleted Notice that there’s no confirmation dialog that I’m most likely to ignore questioning my intent requiring me to take yet one more action to remove the movie. No, the movie is removed immediately from my queue just as I requested. I love it when software does what I tell it to do and doesn’t second guess me!

But what’s great about this interface is that it respects that I’m human and am likely to make mistakes, so it offers me an out. The row becomes grayed out, shows me a status message, and provides a link to undo the delete. So if I did make a mistake, I can just click undo and everything is right with the world. Very nicely done!

I started to get curious about how they did it and did not find any existing jQuery plugins for building this sort of undoable interface, so I decided this would be a fun second jQuery plugin for me to write, my first being the live preview plugin.

The Plugin

Much like my jQuery hide/close link, the default usage of the plugin relies heavily on conventions. As you might expect, all the conventions are easily overriden. Here’s the sample HTML for a table of comments you might have in the admin section of a blog.

<table>
    <thead>
        <tr>
            <th>title</th>
            <th>author</th>
            <th>date</th>
            <th></th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>This is an interesting plugin</td>
            <td>Bugs Bunny</td>
            <td>12/31/2009</td>
            <td><a href="#1" class="delete">delete</a></td>
        </tr>
        <tr>
            <td>No, it's a bit derivative. But nice try.</td>
            <td>Derrida</td>
            <td>1/1/2010</td>
            <td><a href="#2" class="delete">delete</a></td>
        </tr>
        <tr>
            <td>Writing sample data is no fun at all.</td>
            <td>Peter Griffin</td>
            <td>1/2/2010</td>
            <td><a href="#3" class="delete">delete</a></td>
        </tr>
    </tbody>
</table>

And the following is the code to enable the behavior.

$(function() {
    $('a.delete').undoable({url: 'http://example.com/delete/'});
});

By convention, when one of the delete links is clicked, the value in href attribute is posted as form encoded data with the key “id” to the specified URL, in this case http://example.com/delete/.

If you have more form data to post, it’s quite easy to override how the form data is put together and send whatever you want. The following examples pulls the id from a hidden input and sends it with the form key “commentId”.

$(function() {
  $('a.delete').undoable({
    url: 'http://example.com/delete/',
    getPostData: function(clickSource, target) {
      return {
        commentId: target.find("input[type='hidden'][name='id']").value(),
        commentType: 'contact'
      };
    }
  });
});

When the data is posted to the server, the server must respond with a JSON object having two properties, subject and predicate. For example, in ASP.NET MVC you might post to an action method which looks like:

public ActionResult Delete(int id) {
  // Delete it
  return Json(new {subject = "The comment", predicate = "was deleted"});
}

The only reason I broke up the response message into two parts is to enable nice formatting like Netflix’s approach.

This of course is is easily overridden. For example, it may be simpler to simply return  I can override the formatStatus method to expect other properties in the response from the server. For example, suppose you simply want the server to respond with one message property, you might do this.

$(function() {
  $('a.delete').undoable({
    url: 'http://example.com/delete/',
    formatMessage: function(response) {
      return response.message;
    }
  });
});

I wrote the plugin with the TABLE scenario in mind as I planned to use it in the comment admin section of Subtext, but it works equally well with other elements such as DIV elements. For example, the user facing comments of a blog are most likely in a list of DIV tags. All you need to do to make this work is make sure the DIV has a class of “target” or override the getTarget method.

If you want to see more examples of how to use the plugin in various scenarios, check out the jquery.undoable demos page.

I need your help!

I really hope some of you out there find this plugin useful. Writing these plugins has been a great learning experience for me. I found the following two resources extremely valuable.

  • jQuery Plugin Authoring Guide This is the beginners guide on the jQuery documentation page. I found it to be very helpful in learning the basics of plugin development.
  • A Plugin Development Pattern by Mike Alsup. Mike outlines a pattern for writing jQuery plugins that has worked well for him based on his extensive experience. I tried to follow this pattern as much as I could.

However, I still feel there’s room to improve. I’m not sure that I fully grasped all the tips and wrote a truly idiomatic usable extensible clean jQuery plugin. So if you have experience writing jQuery plugins, please do enumerate all the ways my plugin sucks.

If you simply use this plugin, please tell me what does and doesn’t work for you and how it could be better. I’m really having fun writing these plugins and would find your constructive feedback very helpful.

The Source

As with my last plugin, the source is hosted on GitHub. Git is another tool I’m learning. I can’t really make a judgment until I use it on a project where I’m collaborating with others *hint* *hint*. :) Please do fork it and provide patches and educate me on writing better plugins. :)

What others have said

Requesting Gravatar... sirrocco Jan 01, 2010 10:24 PM
# re: Death to confirmation dialogs with jquery.undoable
One small problem, if I start clicking fast on delete/undo it starts creating rows :).

Probably you should disable the link while the animation is on.

Otherwise .. nice plugin.
Requesting Gravatar... Coden4fun Jan 01, 2010 10:36 PM
# re: Death to confirmation dialogs with jquery.undoable
Very useful, thank you! I can use this in my MVC CMS I'm writing. You know if you want to get real familiar with GIT Rob Conery has some nice vids on using it at his TekPub site.

http://tekpub.com/
Requesting Gravatar... Adam Jan 02, 2010 1:02 AM
# re: Death to confirmation dialogs with jquery.undoable
Looks cool but this means you have to wire the insert up as well when they click undo doesn't it?

And you would have store retrieve all data, even stuff that doesn't get displayed like created_date etc.

Requesting Gravatar... Adriaan Jan 02, 2010 1:41 AM
# re: Death to confirmation dialogs with jquery.undoable
Phil what methods would you use to keep the data for the undo action? Do you pull everything down to the client, and if they undo you just re-insert the data. I can think of another way where you just have a IsDeleted column, but then you sit with data that you don't need.
Requesting Gravatar... Rajesh Batheja Jan 02, 2010 4:33 AM
# re: Death to confirmation dialogs with jquery.undoable
Good one Phil! I will very likely use this. I first noticed Gmail doing something similar when delete an email. There's no confirmation but undo delete exists.
Requesting Gravatar... luis Jan 02, 2010 5:43 AM
# re: Death to confirmation dialogs with jquery.undoable
Very nice, tkx for sharing...

I need samples of code to do the opposite, that is to expand the info of a row to provide full details for a given ID ex: expand the full address for a given name....

tkx again and happy new year
Requesting Gravatar... Kenny Ma Jan 02, 2010 9:32 AM
# re: Death to confirmation dialogs with jquery.undoable
Great plug-in Phil, thanks.

For those wondering how to post or wire up the undo action, here's the code:


$(function() {
$('a.delete').undoable({
url: 'http://example.com/delete/',
undoUrl: 'http://example.com/insert/'
});
});


You can also override how the undo data is generated by overriding the "getUndoPostData" option like this:


$(function() {
$('a.delete').undoable({
url: 'http://example.com/delete/',
undoUrl: 'http://example.com/insert/',
getUndoPostData: function(clicksource, target, options) {
// return your JSON post data here
}
});
});
Requesting Gravatar... Tomas Jan 02, 2010 9:32 AM
# re: Death to confirmation dialogs with jquery.undoable
Hi Phil!

This is great, but for it to be really useful, we need the "Death to confirmation dialogs, part II" blog post where you explain how the "undo" button works.

Keep up the good work!

// Tomas
Requesting Gravatar... Dan Dumitru Jan 02, 2010 11:45 AM
# re: Death to confirmation dialogs with jquery.undoable
@Adriaan:

I can think of another way where you just have a IsDeleted column, but then you sit with data that you don't need.


I think this is the most logical and easy way to do it, with an IsDeleted column.

And then you can keep a date when it was deleted, and run a clean-up task that permanently deletes items from the database one day after they were deleted by the user, or something like that.
Requesting Gravatar... Dan Dumitru Jan 02, 2010 11:47 AM
# re: Death to confirmation dialogs with jquery.undoable
And btw, Phil, putting your live preview plugin here on this comment writing would be a nice touch :)
Requesting Gravatar... haacked Jan 02, 2010 12:26 PM
# re: Death to confirmation dialogs with jquery.undoable
I agree with Dan and that's the way I do it. In the case of comments, I have a status column because a comment could be deleted, marked as spam, needing moderation, or approved.

I then have another interface that allows you to look at deleted comments (kind of like the recycling bin in Windows). You can destroy those items if you really need to free up space.

For the most part, I don't care so much about data hanging around as disk space is cheap and it doesn't hurt anything. But I do clean it out from time to time. The UI for destroying items doesn't have an UNDO. I figure if you've already moved the item to the trash, it's very unlikely you want to keep it.
Requesting Gravatar... haacked Jan 02, 2010 12:30 PM
# re: Death to confirmation dialogs with jquery.undoable

@sirrocco thanks for the bug report. I'll fix it.

UPDATE: I fixed it in the latest checkin.

Requesting Gravatar... Mike Jan 03, 2010 8:26 AM
# re: Death to confirmation dialogs with jquery.undoable
There is a different approach to plugin modeling with an easier way to customize and reduce file size. A good example here github.com/.../jquery.ui.autocomplete.js .
It's a snap to change the behavior of the plugin without the developer having to support all the options since it uses jquery's rich event model.
I have other example of this plugin pattern too if you'd like some.
Requesting Gravatar... Saif Khan Jan 03, 2010 6:45 PM
# re: Death to confirmation dialogs with jquery.undoable
That's the work of Bill Scott...I think.
Requesting Gravatar... Andy Edinborough Jan 03, 2010 9:11 PM
# re: Death to confirmation dialogs with jquery.undoable
What about making the action delayed? When then user clicks delete, it gives them something like 3 seconds before actually sending the ajax post to delete? That way, if they click 'undo', we didn't have to communicate with the server at all?

I really like this idea, and agree that confirmation dialogs are evil, but I don't like the delete-first-then-re-insert-later-if-it-was-a-mistake model.
Requesting Gravatar... Bob Armour Jan 04, 2010 8:46 AM
# re: Death to confirmation dialogs with jquery.undoable
Phil,

While I fully agree with the sentiment of this post, I don't really see how the jquery plugin has anything to do with 'undo' - it just seems like a wrapper around the AJAX calls to the server side actions that actually handle the delete/undo operations. Th implementations of the operations still need to be implemented by the user.

Or am I missing something?
Requesting Gravatar... Sean Grimes Jan 04, 2010 11:56 AM
# re: Death to confirmation dialogs with jquery.undoable
@Andy

The problem with your approach is what happens if the user ends up closing out the browser or browser tab before the event gets a chance to fire. You then have a disconnect between what the user expected to happen when the user clicked the delete button and what the software actually did. I generally consider that kind of situation to be a bug, even if the software is working exactly as intended.
Requesting Gravatar... Andre Sanches Jan 04, 2010 12:11 PM
# re: Death to confirmation dialogs with jquery.undoable
I'm starting to write some JQuery plugins and found your articles and code very useful. Do you know of any Visual Studio add-ins or whatnots that would enable intellisense and highlight opening->closing parenthesis when writing javascript code? That would be awesome.
Requesting Gravatar... haacked Jan 04, 2010 2:23 PM
# re: Death to confirmation dialogs with jquery.undoable
@Andy I don't think you should delete and re-insert. I think you should do a soft-delete in this case. For example, in Subtext, we have the ability to flag something as spam. The record isn't deleted, it gets moved to a "spam folder". But now, when you flag something as spam, you can easily undo it without having to navigate to the spam folder.

@Bob the point of my plug-in is to make building the UI around undoable operations much easier. I couldn't find anything existing that provided the same effect. You are right, you still need to implement the underlying server action yourself. That'd be out of scope for a jQuery plugin.
Requesting Gravatar... Tobin Titus Jan 05, 2010 8:45 AM
# re: Death to confirmation dialogs with jquery.undoable
Remember the milk does somethign similar too. I've always loved this convention over the others.
Requesting Gravatar... Glen Jan 06, 2010 11:22 AM
# re: Death to confirmation dialogs with jquery.undoable
It would be nice if it didn't cause the headers/cells to shift around. If you look at your sample, and click the first item to delete, stuff moves to the right. Not much movement on the third item. Not sure if you have any cotrol over that, but the shifting is noticeable(in IE 7 anyways).

Good job otherwise, I also like not having a confirmation dialog.
Requesting Gravatar... haacked Jan 06, 2010 4:18 PM
# re: Death to confirmation dialogs with jquery.undoable
@Glen Yeah. What I probably need to do is get the size of the target and explicitly set the width and height of the new row to be that of the original. I'm not sure how to do this yet as I didn't have time to look into it.
Requesting Gravatar... Visual C# Kicks Jan 07, 2010 12:43 PM
# re: Death to confirmation dialogs with jquery.undoable
This is very cool, never thought about it. Question though, what would be better approach: delete an entry, and if the user hits undo the data is reinserted OR delete simply sets the task to delete within a certain amount of time or something and undo cancels the task. I'm just curious
Requesting Gravatar... Andrei Ignat Jan 08, 2010 4:35 AM
# re: Death to confirmation dialogs with jquery.undoable
I tend to agree with IsDeleted column. However, for more advanced scenarios, the CSLA framework from Lhotka is a must have
Requesting Gravatar... Brian Jan 12, 2010 5:17 PM
# re: Death to confirmation dialogs with jquery.undoable
A similar point to the one I made about your hide/show div script. If you don't intend to support the delete functionality with script off it should be written to add the delete links dynamically. i.e. the plugin could inject delete links into td elements with a class of options or something like that.
Requesting Gravatar... Christian Toivola Jan 14, 2010 7:26 AM
# re: Death to confirmation dialogs with jquery.undoable
What a great UX pattern - much more intuitive than the alternative.
Requesting Gravatar... sadomovalex May 19, 2010 2:42 PM
# re: Death to confirmation dialogs with jquery.undoable
Phil, thank you for your plugin
but I've found that localization of "undo" link text is not very convenient. Without modification of plugin's code I was needed to copy-past whole showUndo function and replace hardcoded "undo" literal by value from resource file: http://tinyurl.com/36a4kch. Think that it can be improved in future versions. Nevertheless current flexibility allowed me to find workaround for this. So thank you very much again

What do you have to say?

(will show your gravatar)
Please add 3 and 2 and type the answer here: