12th
Lazy Function Definitions and “Undo” in Javascript
Implementing Undo/Redo in Javascript
I’ve been working on a Javascript project for class that includes “undo” functionality. ”Undo” is a feature I take for granted in many different applications, but it’s a really interesting problem and has been really fun to implement.
My approach uses what I call an “Agent,” an object that wraps some piece of data.
Agents provide undo(), redo(), and apply(). apply() takes an object with two function properties as its argument:
- .apply(): A function taking one argument, the data, and returning the changed data. Stored on the “redo” stack inside of the Agent, and run immediately.
- .unapply(): A function taking one argument, the data, and returning the changed data. Stored on the “undo” stack inside of the Agent.
So, to change the data inside the Agent you supply the Agent with a function to change it, and a function to change it back. By storing these two functions on stacks internally, undoing or redoing is as simple as popping previously stored functions from their respective stacks and executing them.
My whole approach here was influenced heavily by Rails migrations, which accomplish basically the same thing. More generally, what I have going is a memento pattern.
Lazy Function Definitions
Because the state of the application might have changed since .apply() and .unapply() were passed to the Agent for storage, it was important that they contained enough information about the application’s state when they were first run to successfully run again later.
Luckily, there’s a way in Javascript to describe a function that can “cache” values. The first time these kinds of functions are run, they do something and store it. The next time they’re run, they use the cached value. The pattern is called Lazy Function Definitions, and I didn’t invent it. But here’s an example, courtesy Peter Michaux:
var foo = function() {
var t = new Date();
foo = function() {
return t;
};
return foo();
};
“When foo is called the first time, we instantiate a new Date object and reassign foo to a new function which has that Date object in it’s closure. Then before the end of the first call to foo the new function value of foo is called and supplies the return value.”
Lazy Undo/Redo
Instead of creating a new Date(), my application reaches into the DOM API to fetch some user inputted value from a text box. Because the contents of this particular text box will change over the application’s lifetime, it’s important I only fetch it once.
Here’s what calling Agent.apply() looks like, using a lazy function definition:
agent.apply({
"apply" : function(node) {
var cachedValue = document.getElementById('newText').value;
this.apply = function(node) {
return node.appendChild(makeElem(cachedValue)).parentNode;
}
return this.apply(node);
},
"unapply" : function(node) {
return node.removeChild(node.childNodes[node.childNodes.length-1]);
}
});
Javascript is Cool
Javascript is a really fun language. To see this all in action, check out my undo/redo demo.