[whatwg] Fixing undo on the Web - UndoManager and Transaction

Jonas Sicking jonas at sicking.cc
Fri Aug 5 13:59:11 PDT 2011

>> Why is there a need for a 'reapply' action? How is it different from
>> the 'apply' action?
> In the case of collaborative editing apps, reapply is different from apply because the backend server may have a tree of transaction history and may need to consult on demand in order to determine exactly what mutations have to happen.

Looking at your examples other emails in this thread I do understand
the idea better I think. However, it seems to me that apply is the
same as reapply plus some initialization. This leads me to two

1. Would it be better make the split be init/apply/unapply rather than
apply/unapply/reapply. The latter seems to cause a lot of code
duplication between apply and reapply as can be seen in your examples.
IMHO the former is also more understandable, but that might be a
matter of taste.

2. If we do go with the init/apply/unapply approach, would it make
sense to skip init and just ask people to do it manually before
creating the transaction? Or at least make init optional?

>> What is the purpose of the UndoManager.replace function and it's
>> replaceGroup argument? In general I'm not sure I fully understand
>> "transaction groups". Are they different from simply a set of
>> transactions which have been merged together such that they are
>> done/undone together?
> Yeah, sorry about that.  I should clarify that part of the design.
> UndoManager.replace
> The key observation is that ManagedTransaction is constrained to work only with a "proper sequence of managed transactions".  Whenever an app inserts a manual transaction or directly mutates the DOM under the same editing host, the UA won't be able to unapply/reapply the existing ManagedTransaction created by user editing actions; i.e. inserting a manual transaction breaks the existing managed transactions.

Follow you so far...

> Without this constraint, however, the UA is required to keep track of all DOM mutations happening in the undo scope :(

...but now you lost me :)

For the record, my vision was that the editor will keep track of the
mutations *it* does to the DOM and inserts managed transactions for
those into the UndoManager.

Any other mutations to the DOM the editor completely ignores. If the
page makes other mutations to the DOM, it should use the UndoManager
and create a managed transaction for it. If it doesn't, the page is on
its own. If the UndoManager is asked to undo or redo a managed
transaction and the DOM doesn't look the same as it had expected, then
it should do the best it can. In some cases this means that it won't
produce useful mutations, but this only happens if the page has done
BadThings (tm).

> Given this constraint, the apps that do use managed transactions such as collaborative editing apps need to replace the managed transactions inserted by the UA by the corresponding manual transaction when user types text.

The vision I had for how this is done is that the editor code should
fire events before it makes any modifications to the DOM. The website
should be able to cancel this event and instead perform it's own
modifications to the DOM.

Until the various editor implementations are there, the website can
always undo the editor transaction and then create its own manual
transaction and insert in the UndoManager.

> Transaction Group
> When a user types "hello", he isn't really typing all of "hello" at once but rather inserting character "h", "e", "l", "l", and "o", letting the UA insert each letter as a separate managed transaction in the order.  However, when the user undoes or redoes this typing command, the entire "hello" needs to removed.  This is accomplished as grouping 5 managed transactions for inserting "h", "e", "l", "l", and "o" as a transaction group.

Cool, that matches my understanding. Is there a reason that you're not
allowing manual transactions to be part of a transaction group though?

>> Why expose explicit Transaction objects. What value does that provide?
> I admit this is more of stylistic issue rather than the technical limitation but given a transaction already has label and apply "properties", it seemed more natural for it to be an object.  That'll allow us to add more properties in the future.
> In particular, I really wanted to provide a mutation list as discussed on the mutation events replacement thread on public-webapps.  This allows apps to determine which transaction did what without having to listening to each mutation event, and makes it easier to replace managed transactions by manual transactions as described above.

Hmm.. this is indeed an interesting idea. Though I would imagine that
it's even more interesting for the page to know the "semantics" of the
modification rather than knowing what exact DOM modifications are
performed. I.e. understand that a transaction means "break current
text into two paragraphs" rather than "insert new element with name
'p'. Remove text from other element. Add text to newly inserted

This in order to be able to reapply the transaction even if the page
looks slightly differently due to other collaborators editing it.

My idea is that the event that editor fires before modifying the DOM
would essentially describe "inserting paragraph break at cursor".

/ Jonas

More information about the whatwg mailing list