/ajax/scripts/history.js
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};