Permalink
Browse files

Initial Undo.js.

  • Loading branch information...
0 parents commit a8df277c16983f01244194cb06278f9b2d46e055 @jzaefferer committed Feb 27, 2011
Showing with 10,258 additions and 0 deletions.
  1. +5 −0 .gitignore
  2. +17 −0 README
  3. +99 −0 demos/index.html
  4. +66 −0 tests/core.js
  5. +18 −0 tests/index.html
  6. +120 −0 undo.js
  7. +8,316 −0 vendor/jquery-1.5.1.js
  8. +205 −0 vendor/qunit.css
  9. +1,412 −0 vendor/qunit.js
@@ -0,0 +1,5 @@
+.project
+*~
+*.diff
+*.patch
+.DS_Store
@@ -0,0 +1,17 @@
+Undo.js provides an abstraction for undoing and redoing any task. It can run both in the browser and on the server (targetted at node.js).
+It can be used to add undo and redo features to a custom application, e.g. undo and redo changing the priority of a task in a TODO list.
+It can also be used to undo and redo native browser features: Clicking a checkbox, editing a textbox and eventually, editing a contenteditable element.
+
+The base abstraction will stay independent of any framework, while plugins, like a command editing a sortable list, will likely depend on libraries such as jQuery.
+
+Roadmap for demos to add:
+- Extend sortable list demo with jQuery UI sortable
+- form: track all form changes: text input, checkbox change, radio change, select change
+- add undo/redo to Backbone TODO demo
+- contenteditable with bold command
+
+//For Docs, License, Tests, and pre-packed downloads, see:
+//http://jzaefferer.github.com/undo/
+
+To suggest a feature, report a bug, or general discussion:
+http://github.com/jzaefferer/undo/issues/
@@ -0,0 +1,99 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <script src="../undo.js"></script>
+ <script src="../vendor/jquery-1.5.1.js"></script>
+ <title>Undo.js demo</title>
+ <style>
+ body { font:62.5% Verdana,Arial,sans-serif; }
+ </style>
+ <script>
+ $(function() {
+ var stack = window.stack = new Undo.Stack(),
+ UpCommand = Undo.Command.extend({
+ constructor: function(li) {
+ this.li = li;
+ },
+ execute: function() {
+ this.li.insertBefore(this.li.prev());
+ },
+ undo: function() {
+ this.li.insertAfter(this.li.next());
+ }
+ }),
+ DownCommand = UpCommand.extend({
+ execute: UpCommand.prototype.undo,
+ undo: UpCommand.prototype.execute,
+ });
+ stack.changed = function() {
+ stackUI();
+ listUI();
+ };
+
+ var undo = $(".undo"),
+ redo = $(".redo"),
+ dirty = $(".dirty");
+ function stackUI() {
+ undo.attr("disabled", !stack.canUndo());
+ redo.attr("disabled", !stack.canRedo());
+ dirty.toggle(stack.dirty());
+ }
+ function listUI() {
+ $("ul li button").attr("disabled", false);
+ $("ul li:first .up").attr("disabled", true);
+ $("ul li:last .down").attr("disabled", true);
+ }
+ stackUI();
+ listUI();
+
+ $(document.body).delegate(".undo, .redo, .save", "click", function() {
+ var what = $(this).attr("class");
+ stack[what]();
+ return false;
+ })
+ $(document.body).delegate(".up, .down", "click", function() {
+ var what = $(this).attr("class");
+ if (what == "up") {
+ stack.execute(new UpCommand($(this).parent()));
+ } else {
+ stack.execute(new DownCommand($(this).parent()));
+ }
+ return false;
+ })
+ });
+ </script>
+</head>
+<body>
+ <button class="undo" href="#">Undo</button>
+ <button class="redo" href="#">Redo</button>
+ <button class="save" href="#">Save<span class="dirty">*</span></button>
+ <ul>
+ <li>
+ <span>1. item</span>
+ <button class="up" href="#">Up</button>
+ <button class="down" href="#">Down</button>
+ </li>
+ <li>
+ <span>2. item</span>
+ <button class="up" href="#">Up</button>
+ <button class="down" href="#">Down</button>
+ </li>
+ <li>
+ <span>3. item</span>
+ <button class="up" href="#">Up</button>
+ <button class="down" href="#">Down</button>
+ </li>
+ <li>
+ <span>4. item</span>
+ <button class="up" href="#">Up</button>
+ <button class="down" href="#">Down</button>
+ </li>
+ <li>
+ <span>5. item</span>
+ <button class="up" href="#">Up</button>
+ <button class="down" href="#">Down</button>
+ </li>
+ </ul>
+</body>
+</html>
@@ -0,0 +1,66 @@
+module("core");
+
+var NameChange = Undo.Command.extend({
+ constructor: function(object, newName) {
+ NameChange.__super__.constructor("Name change to " + newName);
+ this.object = object;
+ this.oldName = object.name;
+ this.newName = newName;
+ },
+ execute: function() {
+ this.object.name = this.newName;
+ },
+ undo: function() {
+ this.object.name = this.oldName;
+ }
+});
+
+test("Command.extend", function() {
+ var object = {
+ name: "Peter"
+ }
+ var command = new NameChange(object, "Pan");
+ equal(command.name, "Name change to Pan");
+ equal(object.name, "Peter");
+
+ command.execute();
+ equal(object.name, "Pan");
+
+ command.undo();
+ equal(object.name, "Peter");
+
+ command.redo();
+ equal(object.name, "Pan");
+});
+
+test("Stack", function() {
+ var object = {
+ name: "Peter"
+ }
+ var command = new NameChange(object, "Pawn");
+ var stack = new Undo.Stack();
+
+
+ stack.execute(command);
+ equal(object.name, "Pawn");
+ ok( stack.dirty() );
+ ok( stack.canUndo() );
+ ok( !stack.canRedo() );
+
+ stack.save();
+ ok( !stack.dirty() );
+ ok( stack.canUndo() );
+ ok( !stack.canRedo() );
+
+ stack.undo();
+ equal(object.name, "Peter");
+ ok( stack.dirty() );
+ ok( !stack.canUndo() );
+ ok( stack.canRedo() );
+
+ stack.redo();
+ equal(object.name, "Pawn");
+ ok( !stack.dirty() );
+ ok( stack.canUndo() );
+ ok( !stack.canRedo() );
+});
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Undo.js Test Suite</title>
+ <link rel="stylesheet" href="../vendor/qunit.css" type="text/css" media="screen">
+ <script type="text/javascript" src="../vendor/qunit.js"></script>
+ <script type="text/javascript" src="../undo.js"></script>
+ <script type="text/javascript" src="core.js"></script>
+</head>
+<body>
+ <h1 id="qunit-header">Undo.js Test Suite</h1>
+ <h2 id="qunit-banner"></h2>
+ <div id="qunit-testrunner-toolbar"></div>
+ <h2 id="qunit-userAgent"></h2>
+ <ol id="qunit-tests"></ol>
+ <div id="qunit-fixture">test markup</div>
+</body>
+</html>
@@ -0,0 +1,120 @@
+/*
+ * Undo.js - A undo/redo framework for JavaScript
+ *
+ * http://jzaefferer.github.com/undo
+ *
+ * Copyright (c) 2011 Jörn Zaefferer
+ * MIT licensed.
+ */
+(function() {
+
+// based on Backbone.js' inherits
+var ctor = function(){};
+var inherits = function(parent, protoProps) {
+ var child;
+
+ if (protoProps && protoProps.hasOwnProperty('constructor')) {
+ child = protoProps.constructor;
+ } else {
+ child = function(){ return parent.apply(this, arguments); };
+ }
+
+ ctor.prototype = parent.prototype;
+ child.prototype = new ctor();
+
+ if (protoProps) extend(child.prototype, protoProps);
+
+ child.prototype.constructor = child;
+ child.__super__ = parent.prototype;
+ return child;
+};
+
+function extend(target, ref) {
+ for ( name in ref ) {
+ var value = ref[name];
+ if (value !== undefined) {
+ target[ name ] = value;
+ }
+ }
+ return target;
+};
+
+var Undo;
+if (typeof exports !== 'undefined') {
+ Undo = exports;
+} else {
+ Undo = this.Undo = {};
+}
+
+Undo.Stack = function() {
+ this.commands = [];
+ this.stackPosition = -1;
+ this.savePosition = -1;
+};
+
+extend(Undo.Stack.prototype, {
+ execute: function(command) {
+ this._clearRedo();
+ command.execute();
+ this.commands.push(command);
+ this.stackPosition++;
+ this.changed();
+ },
+ undo: function() {
+ this.commands[this.stackPosition].undo();
+ this.stackPosition--;
+ this.changed();
+ },
+ canUndo: function() {
+ return this.stackPosition >= 0;
+ },
+ redo: function() {
+ this.stackPosition++;
+ this.commands[this.stackPosition].execute();
+ this.changed();
+ },
+ canRedo: function() {
+ return this.stackPosition < this.commands.length - 1;
+ },
+ save: function() {
+ this.savePosition = this.stackPosition;
+ this.changed();
+ },
+ dirty: function() {
+ return this.stackPosition != this.savePosition;
+ },
+ _clearRedo: function() {
+ // TODO there's probably a more efficient way for this
+ // FIXME
+ //this.commands = this.commands.slice(0, this.stackPosition);
+ },
+ changed: function() {
+ // do nothing, override
+ }
+});
+
+Undo.Command = function(name) {
+ this.name = name;
+}
+
+var up = new Error("override me!");
+
+extend(Undo.Command.prototype, {
+ execute: function() {
+ throw up;
+ },
+ undo: function() {
+ throw up;
+ },
+ redo: function() {
+ this.execute();
+ }
+});
+
+Undo.Command.extend = function(protoProps) {
+ var child = inherits(this, protoProps);
+ child.extend = Undo.Command.extend;
+ return child;
+};
+
+}).call(this);
Oops, something went wrong.

0 comments on commit a8df277

Please sign in to comment.