4983726ebaf55b893d3a2f3f2eab75d0883dc832.svn-base 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454
  1. if (!dojo._hasResource["dojo.back"]) { // _hasResource checks added by build.
  2. // Do not use _hasResource directly in
  3. // your code.
  4. dojo._hasResource["dojo.back"] = true;
  5. dojo.provide("dojo.back");
  6. (function() {
  7. var back = dojo.back;
  8. // everyone deals with encoding the hash slightly differently
  9. function getHash() {
  10. var h = window.location.hash;
  11. if (h.charAt(0) == "#") {
  12. h = h.substring(1);
  13. }
  14. return dojo.isMozilla ? h : decodeURIComponent(h);
  15. }
  16. function setHash(h) {
  17. if (!h) {
  18. h = ""
  19. };
  20. window.location.hash = encodeURIComponent(h);
  21. historyCounter = history.length;
  22. }
  23. // if we're in the test for these methods, expose them on dojo.back.
  24. // ok'd with alex.
  25. if (dojo.exists("tests.back-hash")) {
  26. back.getHash = getHash;
  27. back.setHash = setHash;
  28. }
  29. var initialHref = (typeof(window) !== "undefined")
  30. ? window.location.href
  31. : "";
  32. var initialHash = (typeof(window) !== "undefined") ? getHash() : "";
  33. var initialState = null;
  34. var locationTimer = null;
  35. var bookmarkAnchor = null;
  36. var historyIframe = null;
  37. var forwardStack = [];
  38. var historyStack = [];
  39. var moveForward = false;
  40. var changingUrl = false;
  41. var historyCounter;
  42. function handleBackButton() {
  43. // summary: private method. Do not call this directly.
  44. // The "current" page is always at the top of the history stack.
  45. // console.debug("handlingBackButton");
  46. var current = historyStack.pop();
  47. if (!current) {
  48. return;
  49. }
  50. var last = historyStack[historyStack.length - 1];
  51. if (!last && historyStack.length == 0) {
  52. last = initialState;
  53. }
  54. if (last) {
  55. if (last.kwArgs["back"]) {
  56. last.kwArgs["back"]();
  57. } else if (last.kwArgs["backButton"]) {
  58. last.kwArgs["backButton"]();
  59. } else if (last.kwArgs["handle"]) {
  60. last.kwArgs.handle("back");
  61. }
  62. }
  63. forwardStack.push(current);
  64. // console.debug("done handling back");
  65. }
  66. back.goBack = handleBackButton;
  67. function handleForwardButton() {
  68. // summary: private method. Do not call this directly.
  69. // console.debug("handling forward");
  70. var last = forwardStack.pop();
  71. if (!last) {
  72. return;
  73. }
  74. if (last.kwArgs["forward"]) {
  75. last.kwArgs.forward();
  76. } else if (last.kwArgs["forwardButton"]) {
  77. last.kwArgs.forwardButton();
  78. } else if (last.kwArgs["handle"]) {
  79. last.kwArgs.handle("forward");
  80. }
  81. historyStack.push(last);
  82. // console.debug("done handling forward");
  83. }
  84. back.goForward = handleForwardButton;
  85. function createState(url, args, hash) {
  86. // summary: private method. Do not call this directly.
  87. return {
  88. "url" : url,
  89. "kwArgs" : args,
  90. "urlHash" : hash
  91. }; // Object
  92. }
  93. function getUrlQuery(url) {
  94. // summary: private method. Do not call this directly.
  95. var segments = url.split("?");
  96. if (segments.length < 2) {
  97. return null; // null
  98. } else {
  99. return segments[1]; // String
  100. }
  101. }
  102. function loadIframeHistory() {
  103. // summary: private method. Do not call this directly.
  104. var url = (djConfig["dojoIframeHistoryUrl"] || dojo.moduleUrl(
  105. "dojo", "resources/iframe_history.html"))
  106. + "?" + (allGetServerTime()).getTime();
  107. moveForward = true;
  108. if (historyIframe) {
  109. (dojo.isSafari)
  110. ? historyIframe.location = url
  111. : window.frames[historyIframe.name].location = url;
  112. } else {
  113. // console.warn("dojo.back: Not initialised. You need to call
  114. // dojo.back.init() from a <script> block that lives inside the
  115. // <body> tag.");
  116. }
  117. return url; // String
  118. }
  119. function checkLocation() {
  120. // console.debug("checking url");
  121. if (!changingUrl) {
  122. var hsl = historyStack.length;
  123. var hash = getHash();
  124. if ((hash === initialHash || window.location.href == initialHref)
  125. && (hsl == 1)) {
  126. // FIXME: could this ever be a forward button?
  127. // we can't clear it because we still need to check for
  128. // forwards. Ugg.
  129. // clearInterval(this.locationTimer);
  130. handleBackButton();
  131. return;
  132. }
  133. // first check to see if we could have gone forward. We always
  134. // halt on
  135. // a no-hash item.
  136. if (forwardStack.length > 0) {
  137. if (forwardStack[forwardStack.length - 1].urlHash === hash) {
  138. handleForwardButton();
  139. return;
  140. }
  141. }
  142. // ok, that didn't work, try someplace back in the history stack
  143. if ((hsl >= 2) && (historyStack[hsl - 2])) {
  144. if (historyStack[hsl - 2].urlHash === hash) {
  145. handleBackButton();
  146. return;
  147. }
  148. }
  149. if (dojo.isSafari && dojo.isSafari < 3) {
  150. var hisLen = history.length;
  151. if (hisLen > historyCounter)
  152. handleForwardButton();
  153. else if (hisLen < historyCounter)
  154. handleBackButton();
  155. historyCounter = hisLen;
  156. }
  157. }
  158. // console.debug("done checking");
  159. };
  160. back.init = function() {
  161. // summary: Initializes the undo stack. This must be called from a
  162. // <script>
  163. // block that lives inside the <body> tag to prevent bugs on IE.
  164. if (dojo.byId("dj_history")) {
  165. return;
  166. } // prevent reinit
  167. var src = djConfig["dojoIframeHistoryUrl"]
  168. || dojo.moduleUrl("dojo", "resources/iframe_history.html");
  169. document
  170. .write('<iframe style="border:0;width:1px;height:1px;position:absolute;visibility:hidden;bottom:0;right:0;" name="dj_history" id="dj_history" src="'
  171. + src + '"></iframe>');
  172. };
  173. back.setInitialState = function(/* Object */args) {
  174. // summary:
  175. // Sets the state object and back callback for the very first page
  176. // that is loaded.
  177. // description:
  178. // It is recommended that you call this method as part of an event
  179. // listener that is registered via dojo.addOnLoad().
  180. // args: Object
  181. // See the addToHistory() function for the list of valid args
  182. // properties.
  183. initialState = createState(initialHref, args, initialHash);
  184. };
  185. // FIXME: Make these doc comments not be awful. At least they're not
  186. // wrong.
  187. // FIXME: Would like to support arbitrary back/forward jumps. Have to
  188. // rework iframeLoaded among other things.
  189. // FIXME: is there a slight race condition in moz using change URL with
  190. // the timer check and when
  191. // the hash gets set? I think I have seen a back/forward call in quick
  192. // succession, but not consistent.
  193. /*
  194. * ===== dojo.__backArgs = function(kwArgs){ // back: Function? // A
  195. * function to be called when this state is reached via the user //
  196. * clicking the back button. // forward: Function? // Upon return to
  197. * this state from the "back, forward" combination // of navigation
  198. * steps, this function will be called. Somewhat // analgous to the
  199. * semantic of an "onRedo" event handler. // changeUrl: Boolean?|String? //
  200. * Boolean indicating whether or not to create a unique hash for // this
  201. * state. If a string is passed instead, it is used as the // hash. }
  202. * =====
  203. */
  204. back.addToHistory = function(/* dojo.__backArgs */args) {
  205. // summary:
  206. // adds a state object (args) to the history list.
  207. // description:
  208. // To support getting back button notifications, the object
  209. // argument should implement a function called either "back",
  210. // "backButton", or "handle". The string "back" will be passed as
  211. // the first and only argument to this callback.
  212. //
  213. // To support getting forward button notifications, the object
  214. // argument should implement a function called either "forward",
  215. // "forwardButton", or "handle". The string "forward" will be
  216. // passed as the first and only argument to this callback.
  217. //
  218. // If you want the browser location string to change, define
  219. // "changeUrl" on the object. If the
  220. // value of "changeUrl" is true, then a unique number will be
  221. // appended to the URL as a fragment
  222. // identifier (http://some.domain.com/path#uniquenumber). If it is
  223. // any other value that does
  224. // not evaluate to false, that value will be used as the fragment
  225. // identifier. For example,
  226. // if changeUrl: 'page1', then the URL will look like:
  227. // http://some.domain.com/path#page1
  228. //
  229. // example:
  230. // | dojo.back.addToHistory({
  231. // | back: function(){ console.debug('back pressed'); },
  232. // | forward: function(){ console.debug('forward pressed'); },
  233. // | changeUrl: true
  234. // | });
  235. // BROWSER NOTES:
  236. // Safari 1.2:
  237. // back button "works" fine, however it's not possible to actually
  238. // DETECT that you've moved backwards by inspecting window.location.
  239. // Unless there is some other means of locating.
  240. // FIXME: perhaps we can poll on history.length?
  241. // Safari 2.0.3+ (and probably 1.3.2+):
  242. // works fine, except when changeUrl is used. When changeUrl is
  243. // used,
  244. // Safari jumps all the way back to whatever page was shown before
  245. // the page that uses dojo.undo.browser support.
  246. // IE 5.5 SP2:
  247. // back button behavior is macro. It does not move back to the
  248. // previous hash value, but to the last full page load. This
  249. // suggests
  250. // that the iframe is the correct way to capture the back button in
  251. // these cases.
  252. // Don't test this page using local disk for MSIE. MSIE will not
  253. // create
  254. // a history list for iframe_history.html if served from a file:
  255. // URL.
  256. // The XML served back from the XHR tests will also not be properly
  257. // created if served from local disk. Serve the test pages from a
  258. // web
  259. // server to test in that browser.
  260. // IE 6.0:
  261. // same behavior as IE 5.5 SP2
  262. // Firefox 1.0+:
  263. // the back button will return us to the previous hash on the same
  264. // page, thereby not requiring an iframe hack, although we do then
  265. // need to run a timer to detect inter-page movement.
  266. // If addToHistory is called, then that means we prune the
  267. // forward stack -- the user went back, then wanted to
  268. // start a new forward path.
  269. forwardStack = [];
  270. var hash = null;
  271. var url = null;
  272. if (!historyIframe) {
  273. if (djConfig["useXDomain"] && !djConfig["dojoIframeHistoryUrl"]) {
  274. console
  275. .debug("dojo.back: When using cross-domain Dojo builds,"
  276. + " please save iframe_history.html to your domain and set djConfig.dojoIframeHistoryUrl"
  277. + " to the path on your domain to iframe_history.html");
  278. }
  279. historyIframe = window.frames["dj_history"];
  280. }
  281. if (!bookmarkAnchor) {
  282. bookmarkAnchor = document.createElement("a");
  283. dojo.body().appendChild(bookmarkAnchor);
  284. bookmarkAnchor.style.display = "none";
  285. }
  286. if (args["changeUrl"]) {
  287. hash = ""
  288. + ((args["changeUrl"] !== true)
  289. ? args["changeUrl"]
  290. : (allGetServerTime()).getTime());
  291. // If the current hash matches the new one, just replace the
  292. // history object with
  293. // this new one. It doesn't make sense to track different state
  294. // objects for the same
  295. // logical URL. This matches the browser behavior of only
  296. // putting in one history
  297. // item no matter how many times you click on the same #hash
  298. // link, at least in Firefox
  299. // and Safari, and there is no reliable way in those browsers to
  300. // know if a #hash link
  301. // has been clicked on multiple times. So making this the
  302. // standard behavior in all browsers
  303. // so that dojo.back's behavior is the same in all browsers.
  304. if (historyStack.length == 0 && initialState.urlHash == hash) {
  305. initialState = createState(url, args, hash);
  306. return;
  307. } else if (historyStack.length > 0
  308. && historyStack[historyStack.length - 1].urlHash == hash) {
  309. historyStack[historyStack.length - 1] = createState(url,
  310. args, hash);
  311. return;
  312. }
  313. changingUrl = true;
  314. setTimeout(function() {
  315. setHash(hash);
  316. changingUrl = false;
  317. }, 1);
  318. bookmarkAnchor.href = hash;
  319. if (dojo.isIE) {
  320. url = loadIframeHistory();
  321. var oldCB = args["back"] || args["backButton"]
  322. || args["handle"];
  323. // The function takes handleName as a parameter, in case the
  324. // callback we are overriding was "handle". In that case,
  325. // we will need to pass the handle name to handle.
  326. var tcb = function(handleName) {
  327. if (getHash() != "") {
  328. setTimeout(function() {
  329. setHash(hash);
  330. }, 1);
  331. }
  332. // Use apply to set "this" to args, and to try to avoid
  333. // memory leaks.
  334. oldCB.apply(this, [handleName]);
  335. };
  336. // Set interceptor function in the right place.
  337. if (args["back"]) {
  338. args.back = tcb;
  339. } else if (args["backButton"]) {
  340. args.backButton = tcb;
  341. } else if (args["handle"]) {
  342. args.handle = tcb;
  343. }
  344. var oldFW = args["forward"] || args["forwardButton"]
  345. || args["handle"];
  346. // The function takes handleName as a parameter, in case the
  347. // callback we are overriding was "handle". In that case,
  348. // we will need to pass the handle name to handle.
  349. var tfw = function(handleName) {
  350. if (getHash() != "") {
  351. setHash(hash);
  352. }
  353. if (oldFW) { // we might not actually have one
  354. // Use apply to set "this" to args, and to try to
  355. // avoid memory leaks.
  356. oldFW.apply(this, [handleName]);
  357. }
  358. };
  359. // Set interceptor function in the right place.
  360. if (args["forward"]) {
  361. args.forward = tfw;
  362. } else if (args["forwardButton"]) {
  363. args.forwardButton = tfw;
  364. } else if (args["handle"]) {
  365. args.handle = tfw;
  366. }
  367. } else if (!dojo.isIE) {
  368. // start the timer
  369. if (!locationTimer) {
  370. locationTimer = setInterval(checkLocation, 200);
  371. }
  372. }
  373. } else {
  374. url = loadIframeHistory();
  375. }
  376. historyStack.push(createState(url, args, hash));
  377. };
  378. back._iframeLoaded = function(evt, ifrLoc) {
  379. // summary:
  380. // private method. Do not call this directly.
  381. var query = getUrlQuery(ifrLoc.href);
  382. if (query == null) {
  383. // alert("iframeLoaded");
  384. // we hit the end of the history, so we should go back
  385. if (historyStack.length == 1) {
  386. handleBackButton();
  387. }
  388. return;
  389. }
  390. if (moveForward) {
  391. // we were expecting it, so it's not either a forward or
  392. // backward movement
  393. moveForward = false;
  394. return;
  395. }
  396. // Check the back stack first, since it is more likely.
  397. // Note that only one step back or forward is supported.
  398. if (historyStack.length >= 2
  399. && query == getUrlQuery(historyStack[historyStack.length
  400. - 2].url)) {
  401. handleBackButton();
  402. } else if (forwardStack.length > 0
  403. && query == getUrlQuery(forwardStack[forwardStack.length
  404. - 1].url)) {
  405. handleForwardButton();
  406. }
  407. };
  408. })();
  409. }