PageRenderTime 84ms CodeModel.GetById 41ms app.highlight 16ms RepoModel.GetById 24ms app.codeStats 0ms

/ajax/scripts/history.js

http://showslow.googlecode.com/
JavaScript | 220 lines | 150 code | 36 blank | 34 comment | 14 complexity | 8aba413b4c580f466e6edff9d271dfd8 MD5 | raw file
  1/*======================================================================
  2 *  History
  3 *
  4 *  This is a singleton that keeps track of undoable user actions and 
  5 *  performs undos and redos in response to the browser's Back and 
  6 *  Forward buttons.
  7 *
  8 *  Call addAction(action) to register an undoable user action. action
  9 *  must have 4 fields:
 10 *
 11 *      perform: an argument-less function that carries out the action
 12 *      undo:    an argument-less function that undos the action
 13 *      label:   a short, user-friendly string describing the action
 14 *      uiLayer: the UI layer on which the action takes place
 15 *
 16 *  By default, the history keeps track of upto 10 actions. You can 
 17 *  configure this behavior by setting 
 18 *      SimileAjax.History.maxHistoryLength
 19 *  to a different number.
 20 *
 21 *  An iframe is inserted into the document's body element to track 
 22 *  onload events.
 23 *======================================================================
 24 */
 25 
 26SimileAjax.History = {
 27    maxHistoryLength:       10,
 28    historyFile:            "__history__.html",
 29    enabled:               true,
 30    
 31    _initialized:           false,
 32    _listeners:             new SimileAjax.ListenerQueue(),
 33    
 34    _actions:               [],
 35    _baseIndex:             0,
 36    _currentIndex:          0,
 37    
 38    _plainDocumentTitle:    document.title
 39};
 40
 41SimileAjax.History.formatHistoryEntryTitle = function(actionLabel) {
 42    return SimileAjax.History._plainDocumentTitle + " {" + actionLabel + "}";
 43};
 44
 45SimileAjax.History.initialize = function() {
 46    if (SimileAjax.History._initialized) {
 47        return;
 48    }
 49    
 50    if (SimileAjax.History.enabled) {
 51        var iframe = document.createElement("iframe");
 52        iframe.id = "simile-ajax-history";
 53        iframe.style.position = "absolute";
 54        iframe.style.width = "10px";
 55        iframe.style.height = "10px";
 56        iframe.style.top = "0px";
 57        iframe.style.left = "0px";
 58        iframe.style.visibility = "hidden";
 59        iframe.src = SimileAjax.History.historyFile + "?0";
 60        
 61        document.body.appendChild(iframe);
 62        SimileAjax.DOM.registerEvent(iframe, "load", SimileAjax.History._handleIFrameOnLoad);
 63        
 64        SimileAjax.History._iframe = iframe;
 65    }
 66    SimileAjax.History._initialized = true;
 67};
 68
 69SimileAjax.History.addListener = function(listener) {
 70    SimileAjax.History.initialize();
 71    
 72    SimileAjax.History._listeners.add(listener);
 73};
 74
 75SimileAjax.History.removeListener = function(listener) {
 76    SimileAjax.History.initialize();
 77    
 78    SimileAjax.History._listeners.remove(listener);
 79};
 80
 81SimileAjax.History.addAction = function(action) {
 82    SimileAjax.History.initialize();
 83    
 84    SimileAjax.History._listeners.fire("onBeforePerform", [ action ]);
 85    window.setTimeout(function() {
 86        try {
 87            action.perform();
 88            SimileAjax.History._listeners.fire("onAfterPerform", [ action ]);
 89                
 90            if (SimileAjax.History.enabled) {
 91                SimileAjax.History._actions = SimileAjax.History._actions.slice(
 92                    0, SimileAjax.History._currentIndex - SimileAjax.History._baseIndex);
 93                    
 94                SimileAjax.History._actions.push(action);
 95                SimileAjax.History._currentIndex++;
 96                
 97                var diff = SimileAjax.History._actions.length - SimileAjax.History.maxHistoryLength;
 98                if (diff > 0) {
 99                    SimileAjax.History._actions = SimileAjax.History._actions.slice(diff);
100                    SimileAjax.History._baseIndex += diff;
101                }
102                
103                try {
104                    SimileAjax.History._iframe.contentWindow.location.search = 
105                        "?" + SimileAjax.History._currentIndex;
106                } catch (e) {
107                    /*
108                     *  We can't modify location.search most probably because it's a file:// url.
109                     *  We'll just going to modify the document's title.
110                     */
111                    var title = SimileAjax.History.formatHistoryEntryTitle(action.label);
112                    document.title = title;
113                }
114            }
115        } catch (e) {
116            SimileAjax.Debug.exception(e, "Error adding action {" + action.label + "} to history");
117        }
118    }, 0);
119};
120
121SimileAjax.History.addLengthyAction = function(perform, undo, label) {
122    SimileAjax.History.addAction({
123        perform:    perform,
124        undo:       undo,
125        label:      label,
126        uiLayer:    SimileAjax.WindowManager.getBaseLayer(),
127        lengthy:    true
128    });
129};
130
131SimileAjax.History._handleIFrameOnLoad = function() {
132    /*
133     *  This function is invoked when the user herself
134     *  navigates backward or forward. We need to adjust
135     *  the application's state accordingly.
136     */
137    
138    try {
139        var q = SimileAjax.History._iframe.contentWindow.location.search;
140        var c = (q.length == 0) ? 0 : Math.max(0, parseInt(q.substr(1)));
141        
142        var finishUp = function() {
143            var diff = c - SimileAjax.History._currentIndex;
144            SimileAjax.History._currentIndex += diff;
145            SimileAjax.History._baseIndex += diff;
146                
147            SimileAjax.History._iframe.contentWindow.location.search = "?" + c;
148        };
149        
150        if (c < SimileAjax.History._currentIndex) { // need to undo
151            SimileAjax.History._listeners.fire("onBeforeUndoSeveral", []);
152            window.setTimeout(function() {
153                while (SimileAjax.History._currentIndex > c && 
154                       SimileAjax.History._currentIndex > SimileAjax.History._baseIndex) {
155                       
156                    SimileAjax.History._currentIndex--;
157                    
158                    var action = SimileAjax.History._actions[SimileAjax.History._currentIndex - SimileAjax.History._baseIndex];
159                    
160                    try {
161                        action.undo();
162                    } catch (e) {
163                        SimileAjax.Debug.exception(e, "History: Failed to undo action {" + action.label + "}");
164                    }
165                }
166                
167                SimileAjax.History._listeners.fire("onAfterUndoSeveral", []);
168                finishUp();
169            }, 0);
170        } else if (c > SimileAjax.History._currentIndex) { // need to redo
171            SimileAjax.History._listeners.fire("onBeforeRedoSeveral", []);
172            window.setTimeout(function() {
173                while (SimileAjax.History._currentIndex < c && 
174                       SimileAjax.History._currentIndex - SimileAjax.History._baseIndex < SimileAjax.History._actions.length) {
175                       
176                    var action = SimileAjax.History._actions[SimileAjax.History._currentIndex - SimileAjax.History._baseIndex];
177                    
178                    try {
179                        action.perform();
180                    } catch (e) {
181                        SimileAjax.Debug.exception(e, "History: Failed to redo action {" + action.label + "}");
182                    }
183                    
184                    SimileAjax.History._currentIndex++;
185                }
186                
187                SimileAjax.History._listeners.fire("onAfterRedoSeveral", []);
188                finishUp();
189            }, 0);
190        } else {
191            var index = SimileAjax.History._currentIndex - SimileAjax.History._baseIndex - 1;
192            var title = (index >= 0 && index < SimileAjax.History._actions.length) ?
193                SimileAjax.History.formatHistoryEntryTitle(SimileAjax.History._actions[index].label) :
194                SimileAjax.History._plainDocumentTitle;
195                
196            SimileAjax.History._iframe.contentWindow.document.title = title;
197            document.title = title;
198        }
199    } catch (e) {
200        // silent
201    }
202};
203
204SimileAjax.History.getNextUndoAction = function() {
205    try {
206        var index = SimileAjax.History._currentIndex - SimileAjax.History._baseIndex - 1;
207        return SimileAjax.History._actions[index];
208    } catch (e) {
209        return null;
210    }
211};
212
213SimileAjax.History.getNextRedoAction = function() {
214    try {
215        var index = SimileAjax.History._currentIndex - SimileAjax.History._baseIndex;
216        return SimileAjax.History._actions[index];
217    } catch (e) {
218        return null;
219    }
220};