PageRenderTime 42ms CodeModel.GetById 11ms RepoModel.GetById 1ms app.codeStats 0ms

/files/history.js/1.8.0b2/dev/history.html4.js

https://gitlab.com/Mirros/jsdelivr
JavaScript | 685 lines | 278 code | 127 blank | 280 comment | 48 complexity | 23f50bc829e1b4496014af0e61e24793 MD5 | raw file
  1. /**
  2. * History.js HTML4 Support
  3. * Depends on the HTML5 Support
  4. * @author Benjamin Arthur Lupton <contact@balupton.com>
  5. * @copyright 2010-2011 Benjamin Arthur Lupton <contact@balupton.com>
  6. * @license New BSD License <http://creativecommons.org/licenses/BSD/>
  7. */
  8. (function(window,undefined){
  9. "use strict";
  10. // ========================================================================
  11. // Initialise
  12. // Localise Globals
  13. var
  14. document = window.document, // Make sure we are using the correct document
  15. setTimeout = window.setTimeout||setTimeout,
  16. clearTimeout = window.clearTimeout||clearTimeout,
  17. setInterval = window.setInterval||setInterval,
  18. History = window.History = window.History||{}; // Public History Object
  19. // Check Existence
  20. if ( typeof History.initHtml4 !== 'undefined' ) {
  21. throw new Error('History.js HTML4 Support has already been loaded...');
  22. }
  23. // ========================================================================
  24. // Initialise HTML4 Support
  25. // Initialise HTML4 Support
  26. History.initHtml4 = function(){
  27. // Initialise
  28. if ( typeof History.initHtml4.initialized !== 'undefined' ) {
  29. // Already Loaded
  30. return false;
  31. }
  32. else {
  33. History.initHtml4.initialized = true;
  34. }
  35. // ====================================================================
  36. // Properties
  37. /**
  38. * History.enabled
  39. * Is History enabled?
  40. */
  41. History.enabled = true;
  42. // ====================================================================
  43. // Hash Storage
  44. /**
  45. * History.savedHashes
  46. * Store the hashes in an array
  47. */
  48. History.savedHashes = [];
  49. /**
  50. * History.isLastHash(newHash)
  51. * Checks if the hash is the last hash
  52. * @param {string} newHash
  53. * @return {boolean} true
  54. */
  55. History.isLastHash = function(newHash){
  56. // Prepare
  57. var oldHash = History.getHashByIndex(),
  58. isLast;
  59. // Check
  60. isLast = newHash === oldHash;
  61. // Return isLast
  62. return isLast;
  63. };
  64. /**
  65. * History.isHashEqual(newHash, oldHash)
  66. * Checks to see if two hashes are functionally equal
  67. * @param {string} newHash
  68. * @param {string} oldHash
  69. * @return {boolean} true
  70. */
  71. History.isHashEqual = function(newHash, oldHash){
  72. newHash = encodeURIComponent(newHash).replace(/%25/g, "%");
  73. oldHash = encodeURIComponent(oldHash).replace(/%25/g, "%");
  74. return newHash === oldHash;
  75. };
  76. /**
  77. * History.saveHash(newHash)
  78. * Push a Hash
  79. * @param {string} newHash
  80. * @return {boolean} true
  81. */
  82. History.saveHash = function(newHash){
  83. // Check Hash
  84. if ( History.isLastHash(newHash) ) {
  85. return false;
  86. }
  87. // Push the Hash
  88. History.savedHashes.push(newHash);
  89. // Return true
  90. return true;
  91. };
  92. /**
  93. * History.getHashByIndex()
  94. * Gets a hash by the index
  95. * @param {integer} index
  96. * @return {string}
  97. */
  98. History.getHashByIndex = function(index){
  99. // Prepare
  100. var hash = null;
  101. // Handle
  102. if ( typeof index === 'undefined' ) {
  103. // Get the last inserted
  104. hash = History.savedHashes[History.savedHashes.length-1];
  105. }
  106. else if ( index < 0 ) {
  107. // Get from the end
  108. hash = History.savedHashes[History.savedHashes.length+index];
  109. }
  110. else {
  111. // Get from the beginning
  112. hash = History.savedHashes[index];
  113. }
  114. // Return hash
  115. return hash;
  116. };
  117. // ====================================================================
  118. // Discarded States
  119. /**
  120. * History.discardedHashes
  121. * A hashed array of discarded hashes
  122. */
  123. History.discardedHashes = {};
  124. /**
  125. * History.discardedStates
  126. * A hashed array of discarded states
  127. */
  128. History.discardedStates = {};
  129. /**
  130. * History.discardState(State)
  131. * Discards the state by ignoring it through History
  132. * @param {object} State
  133. * @return {true}
  134. */
  135. History.discardState = function(discardedState,forwardState,backState){
  136. //History.debug('History.discardState', arguments);
  137. // Prepare
  138. var discardedStateHash = History.getHashByState(discardedState),
  139. discardObject;
  140. // Create Discard Object
  141. discardObject = {
  142. 'discardedState': discardedState,
  143. 'backState': backState,
  144. 'forwardState': forwardState
  145. };
  146. // Add to DiscardedStates
  147. History.discardedStates[discardedStateHash] = discardObject;
  148. // Return true
  149. return true;
  150. };
  151. /**
  152. * History.discardHash(hash)
  153. * Discards the hash by ignoring it through History
  154. * @param {string} hash
  155. * @return {true}
  156. */
  157. History.discardHash = function(discardedHash,forwardState,backState){
  158. //History.debug('History.discardState', arguments);
  159. // Create Discard Object
  160. var discardObject = {
  161. 'discardedHash': discardedHash,
  162. 'backState': backState,
  163. 'forwardState': forwardState
  164. };
  165. // Add to discardedHash
  166. History.discardedHashes[discardedHash] = discardObject;
  167. // Return true
  168. return true;
  169. };
  170. /**
  171. * History.discardedState(State)
  172. * Checks to see if the state is discarded
  173. * @param {object} State
  174. * @return {bool}
  175. */
  176. History.discardedState = function(State){
  177. // Prepare
  178. var StateHash = History.getHashByState(State),
  179. discarded;
  180. // Check
  181. discarded = History.discardedStates[StateHash]||false;
  182. // Return true
  183. return discarded;
  184. };
  185. /**
  186. * History.discardedHash(hash)
  187. * Checks to see if the state is discarded
  188. * @param {string} State
  189. * @return {bool}
  190. */
  191. History.discardedHash = function(hash){
  192. // Check
  193. var discarded = History.discardedHashes[hash]||false;
  194. // Return true
  195. return discarded;
  196. };
  197. /**
  198. * History.recycleState(State)
  199. * Allows a discarded state to be used again
  200. * @param {object} data
  201. * @param {string} title
  202. * @param {string} url
  203. * @return {true}
  204. */
  205. History.recycleState = function(State){
  206. //History.debug('History.recycleState', arguments);
  207. // Prepare
  208. var StateHash = History.getHashByState(State);
  209. // Remove from DiscardedStates
  210. if ( History.discardedState(State) ) {
  211. delete History.discardedStates[StateHash];
  212. }
  213. // Return true
  214. return true;
  215. };
  216. // ====================================================================
  217. // HTML4 HashChange Support
  218. if ( History.emulated.hashChange ) {
  219. /*
  220. * We must emulate the HTML4 HashChange Support by manually checking for hash changes
  221. */
  222. /**
  223. * History.hashChangeInit()
  224. * Init the HashChange Emulation
  225. */
  226. History.hashChangeInit = function(){
  227. // Define our Checker Function
  228. History.checkerFunction = null;
  229. // Define some variables that will help in our checker function
  230. var lastDocumentHash = '',
  231. iframeId, iframe,
  232. lastIframeHash, checkerRunning,
  233. startedWithHash = Boolean(History.getHash());
  234. // Handle depending on the browser
  235. if ( History.isInternetExplorer() ) {
  236. // IE6 and IE7
  237. // We need to use an iframe to emulate the back and forward buttons
  238. // Create iFrame
  239. iframeId = 'historyjs-iframe';
  240. iframe = document.createElement('iframe');
  241. // Adjust iFarme
  242. // IE 6 requires iframe to have a src on HTTPS pages, otherwise it will throw a
  243. // "This page contains both secure and nonsecure items" warning.
  244. iframe.setAttribute('id', iframeId);
  245. iframe.setAttribute('src', '#');
  246. iframe.style.display = 'none';
  247. // Append iFrame
  248. document.body.appendChild(iframe);
  249. // Create initial history entry
  250. iframe.contentWindow.document.open();
  251. iframe.contentWindow.document.close();
  252. // Define some variables that will help in our checker function
  253. lastIframeHash = '';
  254. checkerRunning = false;
  255. // Define the checker function
  256. History.checkerFunction = function(){
  257. // Check Running
  258. if ( checkerRunning ) {
  259. return false;
  260. }
  261. // Update Running
  262. checkerRunning = true;
  263. // Fetch
  264. var
  265. documentHash = History.getHash(),
  266. iframeHash = History.getHash(iframe.contentWindow.document);
  267. // The Document Hash has changed (application caused)
  268. if ( documentHash !== lastDocumentHash ) {
  269. // Equalise
  270. lastDocumentHash = documentHash;
  271. // Create a history entry in the iframe
  272. if ( iframeHash !== documentHash ) {
  273. //History.debug('hashchange.checker: iframe hash change', 'documentHash (new):', documentHash, 'iframeHash (old):', iframeHash);
  274. // Equalise
  275. lastIframeHash = iframeHash = documentHash;
  276. // Create History Entry
  277. iframe.contentWindow.document.open();
  278. iframe.contentWindow.document.close();
  279. // Update the iframe's hash
  280. iframe.contentWindow.document.location.hash = History.escapeHash(documentHash);
  281. }
  282. // Trigger Hashchange Event
  283. History.Adapter.trigger(window,'hashchange');
  284. }
  285. // The iFrame Hash has changed (back button caused)
  286. else if ( iframeHash !== lastIframeHash ) {
  287. //History.debug('hashchange.checker: iframe hash out of sync', 'iframeHash (new):', iframeHash, 'documentHash (old):', documentHash);
  288. // Equalise
  289. lastIframeHash = iframeHash;
  290. // If there is no iframe hash that means we're at the original
  291. // iframe state.
  292. // And if there was a hash on the original request, the original
  293. // iframe state was replaced instantly, so skip this state and take
  294. // the user back to where they came from.
  295. if (startedWithHash && iframeHash === '') {
  296. History.back();
  297. }
  298. else {
  299. // Update the Hash
  300. History.setHash(iframeHash,false);
  301. }
  302. }
  303. // Reset Running
  304. checkerRunning = false;
  305. // Return true
  306. return true;
  307. };
  308. }
  309. else {
  310. // We are not IE
  311. // Firefox 1 or 2, Opera
  312. // Define the checker function
  313. History.checkerFunction = function(){
  314. // Prepare
  315. var documentHash = History.getHash()||'';
  316. // The Document Hash has changed (application caused)
  317. if ( documentHash !== lastDocumentHash ) {
  318. // Equalise
  319. lastDocumentHash = documentHash;
  320. // Trigger Hashchange Event
  321. History.Adapter.trigger(window,'hashchange');
  322. }
  323. // Return true
  324. return true;
  325. };
  326. }
  327. // Apply the checker function
  328. History.intervalList.push(setInterval(History.checkerFunction, History.options.hashChangeInterval));
  329. // Done
  330. return true;
  331. }; // History.hashChangeInit
  332. // Bind hashChangeInit
  333. History.Adapter.onDomLoad(History.hashChangeInit);
  334. } // History.emulated.hashChange
  335. // ====================================================================
  336. // HTML5 State Support
  337. // Non-Native pushState Implementation
  338. if ( History.emulated.pushState ) {
  339. /*
  340. * We must emulate the HTML5 State Management by using HTML4 HashChange
  341. */
  342. /**
  343. * History.onHashChange(event)
  344. * Trigger HTML5's window.onpopstate via HTML4 HashChange Support
  345. */
  346. History.onHashChange = function(event){
  347. //History.debug('History.onHashChange', arguments);
  348. // Prepare
  349. var currentUrl = ((event && event.newURL) || History.getLocationHref()),
  350. currentHash = History.getHashByUrl(currentUrl),
  351. currentState = null,
  352. currentStateHash = null,
  353. currentStateHashExits = null,
  354. discardObject;
  355. // Check if we are the same state
  356. if ( History.isLastHash(currentHash) ) {
  357. // There has been no change (just the page's hash has finally propagated)
  358. //History.debug('History.onHashChange: no change');
  359. History.busy(false);
  360. return false;
  361. }
  362. // Reset the double check
  363. History.doubleCheckComplete();
  364. // Store our location for use in detecting back/forward direction
  365. History.saveHash(currentHash);
  366. // Expand Hash
  367. if ( currentHash && History.isTraditionalAnchor(currentHash) ) {
  368. //History.debug('History.onHashChange: traditional anchor', currentHash);
  369. // Traditional Anchor Hash
  370. History.Adapter.trigger(window,'anchorchange');
  371. History.busy(false);
  372. return false;
  373. }
  374. // Create State
  375. currentState = History.extractState(History.getFullUrl(currentHash||History.getLocationHref()),true);
  376. // Check if we are the same state
  377. if ( History.isLastSavedState(currentState) ) {
  378. //History.debug('History.onHashChange: no change');
  379. // There has been no change (just the page's hash has finally propagated)
  380. History.busy(false);
  381. return false;
  382. }
  383. // Create the state Hash
  384. currentStateHash = History.getHashByState(currentState);
  385. // Check if we are DiscardedState
  386. discardObject = History.discardedState(currentState);
  387. if ( discardObject ) {
  388. // Ignore this state as it has been discarded and go back to the state before it
  389. if ( History.getHashByIndex(-2) === History.getHashByState(discardObject.forwardState) ) {
  390. // We are going backwards
  391. //History.debug('History.onHashChange: go backwards');
  392. History.back(false);
  393. } else {
  394. // We are going forwards
  395. //History.debug('History.onHashChange: go forwards');
  396. History.forward(false);
  397. }
  398. return false;
  399. }
  400. // Push the new HTML5 State
  401. //History.debug('History.onHashChange: success hashchange');
  402. History.pushState(currentState.data,currentState.title,encodeURI(currentState.url),false);
  403. // End onHashChange closure
  404. return true;
  405. };
  406. History.Adapter.bind(window,'hashchange',History.onHashChange);
  407. /**
  408. * History.pushState(data,title,url)
  409. * Add a new State to the history object, become it, and trigger onpopstate
  410. * We have to trigger for HTML4 compatibility
  411. * @param {object} data
  412. * @param {string} title
  413. * @param {string} url
  414. * @return {true}
  415. */
  416. History.pushState = function(data,title,url,queue){
  417. //History.debug('History.pushState: called', arguments);
  418. // We assume that the URL passed in is URI-encoded, but this makes
  419. // sure that it's fully URI encoded; any '%'s that are encoded are
  420. // converted back into '%'s
  421. url = encodeURI(url).replace(/%25/g, "%");
  422. // Check the State
  423. if ( History.getHashByUrl(url) ) {
  424. throw new Error('History.js does not support states with fragment-identifiers (hashes/anchors).');
  425. }
  426. // Handle Queueing
  427. if ( queue !== false && History.busy() ) {
  428. // Wait + Push to Queue
  429. //History.debug('History.pushState: we must wait', arguments);
  430. History.pushQueue({
  431. scope: History,
  432. callback: History.pushState,
  433. args: arguments,
  434. queue: queue
  435. });
  436. return false;
  437. }
  438. // Make Busy
  439. History.busy(true);
  440. // Fetch the State Object
  441. var newState = History.createStateObject(data,title,url),
  442. newStateHash = History.getHashByState(newState),
  443. oldState = History.getState(false),
  444. oldStateHash = History.getHashByState(oldState),
  445. html4Hash = History.getHash(),
  446. wasExpected = History.expectedStateId == newState.id;
  447. // Store the newState
  448. History.storeState(newState);
  449. History.expectedStateId = newState.id;
  450. // Recycle the State
  451. History.recycleState(newState);
  452. // Force update of the title
  453. History.setTitle(newState);
  454. // Check if we are the same State
  455. if ( newStateHash === oldStateHash ) {
  456. //History.debug('History.pushState: no change', newStateHash);
  457. History.busy(false);
  458. return false;
  459. }
  460. // Update HTML5 State
  461. History.saveState(newState);
  462. // Fire HTML5 Event
  463. if(!wasExpected)
  464. History.Adapter.trigger(window,'statechange');
  465. // Update HTML4 Hash
  466. if ( !History.isHashEqual(newStateHash, html4Hash) && !History.isHashEqual(newStateHash, History.getShortUrl(History.getLocationHref())) ) {
  467. History.setHash(newStateHash,false);
  468. }
  469. History.busy(false);
  470. // End pushState closure
  471. return true;
  472. };
  473. /**
  474. * History.replaceState(data,title,url)
  475. * Replace the State and trigger onpopstate
  476. * We have to trigger for HTML4 compatibility
  477. * @param {object} data
  478. * @param {string} title
  479. * @param {string} url
  480. * @return {true}
  481. */
  482. History.replaceState = function(data,title,url,queue){
  483. //History.debug('History.replaceState: called', arguments);
  484. // We assume that the URL passed in is URI-encoded, but this makes
  485. // sure that it's fully URI encoded; any '%'s that are encoded are
  486. // converted back into '%'s
  487. url = encodeURI(url).replace(/%25/g, "%");
  488. // Check the State
  489. if ( History.getHashByUrl(url) ) {
  490. throw new Error('History.js does not support states with fragment-identifiers (hashes/anchors).');
  491. }
  492. // Handle Queueing
  493. if ( queue !== false && History.busy() ) {
  494. // Wait + Push to Queue
  495. //History.debug('History.replaceState: we must wait', arguments);
  496. History.pushQueue({
  497. scope: History,
  498. callback: History.replaceState,
  499. args: arguments,
  500. queue: queue
  501. });
  502. return false;
  503. }
  504. // Make Busy
  505. History.busy(true);
  506. // Fetch the State Objects
  507. var newState = History.createStateObject(data,title,url),
  508. newStateHash = History.getHashByState(newState),
  509. oldState = History.getState(false),
  510. oldStateHash = History.getHashByState(oldState),
  511. previousState = History.getStateByIndex(-2);
  512. // Discard Old State
  513. History.discardState(oldState,newState,previousState);
  514. // If the url hasn't changed, just store and save the state
  515. // and fire a statechange event to be consistent with the
  516. // html 5 api
  517. if ( newStateHash === oldStateHash ) {
  518. // Store the newState
  519. History.storeState(newState);
  520. History.expectedStateId = newState.id;
  521. // Recycle the State
  522. History.recycleState(newState);
  523. // Force update of the title
  524. History.setTitle(newState);
  525. // Update HTML5 State
  526. History.saveState(newState);
  527. // Fire HTML5 Event
  528. //History.debug('History.pushState: trigger popstate');
  529. History.Adapter.trigger(window,'statechange');
  530. History.busy(false);
  531. }
  532. else {
  533. // Alias to PushState
  534. History.pushState(newState.data,newState.title,newState.url,false);
  535. }
  536. // End replaceState closure
  537. return true;
  538. };
  539. } // History.emulated.pushState
  540. // ====================================================================
  541. // Initialise
  542. // Non-Native pushState Implementation
  543. if ( History.emulated.pushState ) {
  544. /**
  545. * Ensure initial state is handled correctly
  546. */
  547. if ( History.getHash() && !History.emulated.hashChange ) {
  548. History.Adapter.onDomLoad(function(){
  549. History.Adapter.trigger(window,'hashchange');
  550. });
  551. }
  552. } // History.emulated.pushState
  553. }; // History.initHtml4
  554. // Try to Initialise History
  555. if ( typeof History.init !== 'undefined' ) {
  556. History.init();
  557. }
  558. })(window);