The first few iterations of Scatterand used the browser’s built-in editing behavior pretty much as-is. There are definitely some advantages to that — it’s consistent with other web sites that provide editing capabilities, and it is (or at least should be) better tested than anything that I could write from scratch in JavaScript. But as Scatterand evolved, the browser’s built-in behavior kept being not good enough. There’s no consistent way to get back to paragraph text after editing a heading. There’s no way to create a multi-level list. WebKit lacks a way to add table rows. And those are just the obvious examples.
And so every version of Scatterand past the initial experiments provides a system for inserting “core” scripts into every document that capture key presses and manipulate the document in lieu of the browser’s default behavior.
Of course, if you’re going to implement any editing behavior in JavaScript, one of the first things you need to do is roll your own undo system. See, the browser has a built-in undo, but it only applies to the browser’s built-in editing commands. As soon as scripts start modifying the document, the browser’s undo system breaks — and breaks badly. It starts undoing some of your changes and not others and generally mangles your work. Clearly, this was not going to work.
As a stopgap measure I stuck in a script that just (*gulp*) stored a complete deep copy of the <body> element after every change. That worked, for a certain definition of “worked”. It was incredibly memory-hungry and kept breaking the other scripts, but it allowed me to put off writing a real, delta-based undo system.
Or at least put it off for a while — as it turns out, there are some things that you just can’t do if you can’t count on the identity of your elements to stay the same over the life of the document. I got tired of having to getElementById() over and over on the same elements because you never know when the user might have hit the undo button and broken the identity of every element in the document. I got tired of never being able to set script properties on DOM nodes because the undo system “lost” them. I got tired of event handlers being completely unreliable because the undo system would periodically lose all of them.
And so I’m happy to announce that as of r48,the old, buggy, memory-hungry undo system has been replaced with a generic delta-based. It makes a single deep copy of the document when the document is opened, and when a change is made, it updates the reference tree to match the document tree, recording the insertions, deletions, text and attribute changes that it made to reconcile the two.
The nice thing about the new undo system is that it’s completely generic. Other scripts that modify the document don’t have to worry about making their changes “undoable” because the undo system will catch the changes automatically. DOM nodes retain their identity across undos (and even across deletions — if you delete a node and then undo the change, you get the same node back).
All of this not only takes a huge bite out of Scatterand’s memory consumption, and makes it easier to write scripts — it fixes an entire category of bugs (“X not working after undo”) in one swoop.
You can get the code of the undo system (which should be able to stand alone), or the entire Scatterand project. You can also get an easy to install deb package for Ubuntu systems.
