I'm Aza Raskin @azaaza. I make shiny things. I simplify.

I am the cofounder of Massive Health.

 

Undo Made Easy with Ajax (Part 1.5)

This is the second half of the first part of the Undo Made Easy with Ajax series. If you are jumping in late, make sure to check out Part 1.

One of my readers, Alex Botero-Lowry, pointed out a big caveat to the entirely client-side event queue method of implementing Undo: If a user opens a second tab to a page they were viewing, those pages might be out of sync. In the to-do list example, if you delete three to-do items and then open a new tab/window to the same page, those to-do items would still be there. Why? Because the first page knows about the deletes (even though they are uncommitted) and the second does not.

Let’s take the client-side only approach and run with it. There is something nice about not having to mess with the back-end. We can fix the multiple-tab problem by syncing the event queue across all open pages with a cookie.

I’d like to stress that the event queue method is a light-weight solution to Undo. There are other, more robust ways of implementing Undo, like using a server-side based command pattern (a couple of my readers pointed this out, and I’ll probably cover it later in the series). But that’s beside the point. When we, as engineers, try to solve difficult problems, we often over-generalize and over-abstract our back-end solutions to cover all possible cases. When we do that, we miss the simple solutions that can help our users now. My point is that certain forms of Undo are low hanging fruit, so why not pick them? End of digression.

To reiterate, we are resolving what happens when the user deletes some items, opens a new tab to the same page, and those items do not appear to have been deleted. Let’s assume that the event queue holds the ids of the deleted objects, and that every time an item is deleted, or a deletion is undone, we write the queue to a cookie; when the page is closed, we clear the cookie. When the page is loaded, we check the cookie, and if it contains a non-empty list, we client-side “delete” the items and add them to the event queue. Now, any new tab/window opened to the page are guaranteed to be in sync with the old one.

Problem solved.

The Tricky Bit

So, what happens when you make a change on one page and then switch back to the already open page? This could get complicated. We’re saved from having to deal with the full problem because the web is not a “push” technology. Currently, no web-app that I am aware of will, without needing to manually issue a refresh, update a page when it has been modified in a different tab. Users are used to refreshing a page in order to see changes. This means we need to correctly deal with the case that the user deletes or undoes deletes on the second tab, returns to the first tab, and hits refresh.

To solve this problem, we need to make the assumption that the last tab/window that the user was editing contains the most up-to-date content state. It’s a reasonable assumption because the last thing the user worked on is by far the most likely to be what the user thinks of as the latest version.

When the user refreshes a page, the onUnload callback gets called. In this callback we need to check whether this page’s event queue is the same as the last saved-in-a-cookie event queue (remember that the event queue gets saved to a cookie when a user deletes or undoes a delete). If two two event queues are the same, the page is up-to-date and we go ahead with committing the changes to the server. If the two events queues are not the same, then the current page needs to be synced with the saved-in-a-cookie event queue, thus bringing it up-to-date.

Try It Out

Play around with it, then open a new browser window. Now, change something in the new window and then refresh the old window. It all updates correctly. Good times.

The Source

Available here.

Conclusion

A side benefit of the cookie route is that it ameliorates the already rare case that the user loses work due to a browser crash. When the browser crashes, all changes are saved in the cookie, so when users load up the page again, their changes will still be in effect.

The event queue system with a cookie synchronization system is a low-hanging Undo fruit. It won’t work in every situation, but it will work for a surprising number of cases. It’s well worth the relatively small implementation time.

Next week, I’ll write about solving Undo for time-sensitive actions.

RT @azaaza Undo Made Easy with Ajax (Part 1.5) | Follow @azaaza on Twitter | All blog posts

View all 12 comments


I applaud the attention you’re giving to the Undo problem. It seems silly that in this day and age we’ve lost it in our move to the web. I think one of the reasons we’ve lost it is that web apps are usually multi-user. To take the challenge of multiple tabs in the same browser further, what about multiple browser windows? or multiple browsers (IE, FF, Safari, etc.)? or the most common case in many web apps: multiple users?

I think the only real solution is a server side solution. It is a complicated problem, but I worry that client-side only tricks take what was previously a well understood end-user metaphor (like copy/paste), and changes the behavior enough that it no longer behaves like expected. This causes confusion around “what is Undo really supposed to do?”

I’d certainly like to see a portable design pattern (server-side) emerge, regardless of programming language (we’re talking architecture here).

The Undo problem, because it has multi-user implications, is much more complex than the copy/paste problem, which is bound by a single users’ session, using a clipboard which is already “system wide” and works across applications and multiple instances of the browser.

I have some thoughts on a design pattern for truly multi-user Undo, but I plan to test it in the coming months.



Michael Latta

There are times when you can be too clever. While Jef often suggested doing things in the UI that had the illusion of success to make the user think things were completed before they were, he never suggested such limited use cases. A web site that can only operate with a single client editing data at a time is pretty limited. Also, Jef always suggested that undo/redo had to be boundary-less. You should be able to undo back to an empty document and redo back to the newest version at any time. Doing a hard commit when the user happens to navigate to another page is a very arbitrary boundary.

What I would have rather seen was keeping the event queue on the server and using the client side processing to make the resulting operation faster and more secure in the face of server communications failure or delays.


Firstly, the source link seems to be broken.

I’m actually rather intrigued by this implementation. I agree that there are some real advantages to not having to write server-side code, conversely I think that at some point one’s tricks to avoid server-side work get so involved that one ends up doing more work than just adding a deleted bit, and flipping it via the web API. In general though, I think the point that you make, that undo is an important enough feature for us to just do it, is really important. I think that the direction we should go in is the command pattern, but it’s a lot easier for us to grab this “low hanging fruit” and run with it.

As for between-client syncing, I was going to implement that in my example, but i just got kind of bored. It’s usually done with either Comet or polling. Comet being better to my eyes, but for some examples of http, it can be really problematic. A while back I wrote an xmms2 client for the browser that was totally live (time, playlist, etc updated live), and you see it with gmail where you get new messages as they arrive.


A good set of articles, I’ve become interested in exploring undo functionality since reading your article on ALA so it’s nice to find more information here.

I’ve also been exploring using javascript to add undo functionality, but in my case I took the simple case of undoing the reset functionality of a form, you can see my results here.

Thanks a lot for your helpful articles.


This is a very good script.

can you please put the source code of the first example that “Try out the to-do list with the standard warning dialog box solution.”

i have a example on my site:

http://scripts.ajaxflakes.com/category/accordion/



Thomas

Not only the article is a very interesting approach, also Craig’s, Michael’s and Alexander’s comments give a very critical and noteworthy view at the drawbacks.

I think that the user should be informed that the items are NOT deleted in realtime, just marked as “to be deleted”. With that information in mind, users shouldn’t get too confused when opening another tab, window or browser and so on (in case of a second tab, even if cookie-synchronized, you can commit the changes in tab 1, and they are deleted for read, but in tab 2, you still are in believe to be able undoing it).


This is a very good script.

can you please put the source code of the first example that “Try out the to-do list with the standard warning dialog box solution.”


I think the only real solution is a server side solution. It is a complicated problem, but I worry that client-side only tricks take what was previously a well understood end-user metaphor (like copy/paste), and changes the behavior enough that it no longer behaves like expected. This causes confusion around “what is Undo really supposed to do?”



Sex

I think the only real solution is a server side solution. It is a complicated problem, but I worry that client-side only tricks take what was previously a well understood end-user metaphor (like copy/paste), and changes the behavior enough that it no longer behaves like expected. This causes confusion around “what is Undo really supposed to do?”


thnks
goooooooooooood
min:)


Leave a Comment