2 * Originally based of this code... - refactored for Roo...
3 * https://github.com/aaalsaleh/undo-manager
6 * @author Abdulrahman Alsaleh
7 * @copyright 2015 Abdulrahman Alsaleh
8 * @license MIT License (c)
10 * Hackily modifyed by alan@roojs.com
17 * Documentation to be done....
22 * @class Roo.lib.UndoManager
23 * An undo manager implementation in JavaScript. It follows the W3C UndoManager and DOM Transaction
24 * Draft and the undocumented and disabled Mozilla Firefox's UndoManager implementation.
30 editor.undoManager = new Roo.lib.UndoManager(1000, editor);
34 * For more information see this blog post with examples:
35 * <a href="http://www.cnitblog.com/seeyeah/archive/2011/12/30/38728.html/">DomHelper
36 - Create Elements using DOM, HTML fragments and Templates</a>.
38 * @param {Number} limit how far back to go ... use 1000?
39 * @param {Object} scope usually use document..
42 Roo.lib.UndoManager = function (limit, undoScopeHost)
46 this.scope = undoScopeHost;
47 this.fireEvent = typeof CustomEvent != 'undefined' && undoScopeHost && undoScopeHost.dispatchEvent;
55 Roo.lib.UndoManager.prototype = {
66 * To push and execute a transaction, the method undoManager.transact
67 * must be called by passing a transaction object as the first argument, and a merge
68 * flag as the second argument. A transaction object has the following properties:
72 undoManager.transact({
74 execute: function() { ... },
75 undo: function() { ... },
76 // redo same as execute
77 redo: function() { this.execute(); }
81 undoManager.transact({
83 execute: function() { ... }, // this will be run...
84 undo: function() { ... }, // what to do when undo is run.
85 // redo same as execute
86 redo: function() { this.execute(); }
91 * @param {Object} transaction The transaction to add to the stack.
92 * @return {String} The HTML fragment
96 transact : function (transaction, merge)
98 if (arguments.length < 2) {
99 throw new TypeError('Not enough arguments to UndoManager.transact.');
102 transaction.execute();
104 this.stack.splice(0, this.position);
105 if (merge && this.length) {
106 this.stack[0].push(transaction);
108 this.stack.unshift([transaction]);
113 if (this.limit && this.stack.length > this.limit) {
114 this.length = this.stack.length = this.limit;
116 this.length = this.stack.length;
119 if (this.fireEvent) {
120 this.scope.dispatchEvent(
121 new CustomEvent('DOMTransaction', {
123 transactions: this.stack[0].slice()
131 //Roo.log("transaction: pos:" + this.position + " len: " + this.length + " slen:" + this.stack.length);
138 //Roo.log("undo: pos:" + this.position + " len: " + this.length + " slen:" + this.stack.length);
140 if (this.position < this.length) {
141 for (var i = this.stack[this.position].length - 1; i >= 0; i--) {
142 this.stack[this.position][i].undo();
146 if (this.fireEvent) {
147 this.scope.dispatchEvent(
148 new CustomEvent('undo', {
150 transactions: this.stack[this.position - 1].slice()
162 if (this.position > 0) {
163 for (var i = 0, n = this.stack[this.position - 1].length; i < n; i++) {
164 this.stack[this.position - 1][i].redo();
168 if (this.fireEvent) {
169 this.scope.dispatchEvent(
170 new CustomEvent('redo', {
172 transactions: this.stack[this.position].slice()
182 item : function (index)
184 if (index >= 0 && index < this.length) {
185 return this.stack[index].slice();
190 clearUndo : function () {
191 this.stack.length = this.length = this.position;
194 clearRedo : function () {
195 this.stack.splice(0, this.position);
197 this.length = this.stack.length;
200 * Reset the undo - probaly done on load to clear all history.
207 this.current_html = this.scope.innerHTML;
208 if (this.timer !== false) {
209 clearTimeout(this.timer);
221 // this will handle the undo/redo on the element.?
222 bindEvents : function()
225 el.undoManager = this;
228 this.scope.addEventListener('keydown', function(e) {
229 if ((e.ctrlKey || e.metaKey) && e.keyCode === 90) {
231 el.undoManager.redo(); // Ctrl/Command + Shift + Z
233 el.undoManager.undo(); // Ctrl/Command + Z
240 this.scope.addEventListener('keyup', function(e) {
241 if ((e.ctrlKey || e.metaKey) && e.keyCode === 90) {
250 el.addEventListener('input', function(e) {
251 if(el.innerHTML == t.current_html) {
254 // only record events every second.
255 if (t.timer !== false) {
256 clearTimeout(t.timer);
259 t.timer = setTimeout(function() { t.merge = false; }, 1000);
262 t.merge = true; // ignore changes happening every second..
266 * Manually add an event.
267 * Normall called without arguements - and it will just get added to the stack.
271 addEvent : function(merge)
273 //Roo.log("undomanager +" + (merge ? 'Y':'n'));
274 // not sure if this should clear the timer
275 merge = typeof(merge) == 'undefined' ? false : merge;
277 this.scope.undoManager.transact({
279 oldHTML: this.current_html,
280 newHTML: this.scope.innerHTML,
281 // nothing to execute (content already changed when input is fired)
282 execute: function() { },
284 this.scope.innerHTML = this.current_html = this.oldHTML;
287 this.scope.innerHTML = this.current_html = this.newHTML;
293 this.current_html = this.scope.innerHTML;