PageRenderTime 27ms CodeModel.GetById 25ms RepoModel.GetById 0ms app.codeStats 0ms

/ajax/libs/hasher/1.1.4/hasher.js

https://gitlab.com/Mirros/cdnjs
JavaScript | 419 lines | 202 code | 60 blank | 157 comment | 41 complexity | 659657ce332a5b8acbca78f0359c50e8 MD5 | raw file
  1. /*!!
  2. * Hasher <http://github.com/millermedeiros/hasher>
  3. * @author Miller Medeiros
  4. * @version 1.1.4 (2013/04/03 08:59 AM)
  5. * Released under the MIT License
  6. */
  7. ;(function () {
  8. var factory = function(signals){
  9. /*jshint white:false*/
  10. /*global signals:false, window:false*/
  11. /**
  12. * Hasher
  13. * @namespace History Manager for rich-media applications.
  14. * @name hasher
  15. */
  16. var hasher = (function(window){
  17. //--------------------------------------------------------------------------------------
  18. // Private Vars
  19. //--------------------------------------------------------------------------------------
  20. var
  21. // frequency that it will check hash value on IE 6-7 since it doesn't
  22. // support the hashchange event
  23. POOL_INTERVAL = 25,
  24. // local storage for brevity and better compression --------------------------------
  25. document = window.document,
  26. history = window.history,
  27. Signal = signals.Signal,
  28. // local vars ----------------------------------------------------------------------
  29. hasher,
  30. _hash,
  31. _checkInterval,
  32. _isActive,
  33. _frame, //iframe used for legacy IE (6-7)
  34. _checkHistory,
  35. _hashValRegexp = /#(.*)$/,
  36. _baseUrlRegexp = /(\?.*)|(\#.*)/,
  37. _hashRegexp = /^\#/,
  38. // sniffing/feature detection -------------------------------------------------------
  39. //hack based on this: http://webreflection.blogspot.com/2009/01/32-bytes-to-know-if-your-browser-is-ie.html
  40. _isIE = (!+"\v1"),
  41. // hashchange is supported by FF3.6+, IE8+, Chrome 5+, Safari 5+ but
  42. // feature detection fails on IE compatibility mode, so we need to
  43. // check documentMode
  44. _isHashChangeSupported = ('onhashchange' in window) && document.documentMode !== 7,
  45. //check if is IE6-7 since hash change is only supported on IE8+ and
  46. //changing hash value on IE6-7 doesn't generate history record.
  47. _isLegacyIE = _isIE && !_isHashChangeSupported,
  48. _isLocal = (location.protocol === 'file:');
  49. //--------------------------------------------------------------------------------------
  50. // Private Methods
  51. //--------------------------------------------------------------------------------------
  52. function _escapeRegExp(str){
  53. return String(str || '').replace(/[\\.+*?\^$\[\](){}\/'#]/g, "\\$&");
  54. }
  55. function _trimHash(hash){
  56. if (!hash) return '';
  57. var regexp = new RegExp('^' + _escapeRegExp(hasher.prependHash) + '|' + _escapeRegExp(hasher.appendHash) + '$', 'g');
  58. return hash.replace(regexp, '');
  59. }
  60. function _getWindowHash(){
  61. //parsed full URL instead of getting window.location.hash because Firefox decode hash value (and all the other browsers don't)
  62. //also because of IE8 bug with hash query in local file [issue #6]
  63. var result = _hashValRegexp.exec( hasher.getURL() );
  64. return (result && result[1])? decodeURIComponent(result[1]) : '';
  65. }
  66. function _getFrameHash(){
  67. return (_frame)? _frame.contentWindow.frameHash : null;
  68. }
  69. function _createFrame(){
  70. _frame = document.createElement('iframe');
  71. _frame.src = 'about:blank';
  72. _frame.style.display = 'none';
  73. document.body.appendChild(_frame);
  74. }
  75. function _updateFrame(){
  76. if(_frame && _hash !== _getFrameHash()){
  77. var frameDoc = _frame.contentWindow.document;
  78. frameDoc.open();
  79. //update iframe content to force new history record.
  80. //based on Really Simple History, SWFAddress and YUI.history.
  81. frameDoc.write('<html><head><title>' + document.title + '</title><script type="text/javascript">var frameHash="' + _hash + '";</script></head><body>&nbsp;</body></html>');
  82. frameDoc.close();
  83. }
  84. }
  85. function _registerChange(newHash, isReplace){
  86. if(_hash !== newHash){
  87. var oldHash = _hash;
  88. _hash = newHash; //should come before event dispatch to make sure user can get proper value inside event handler
  89. if(_isLegacyIE){
  90. if(!isReplace){
  91. _updateFrame();
  92. } else {
  93. _frame.contentWindow.frameHash = newHash;
  94. }
  95. }
  96. hasher.changed.dispatch(_trimHash(newHash), _trimHash(oldHash));
  97. }
  98. }
  99. if (_isLegacyIE) {
  100. /**
  101. * @private
  102. */
  103. _checkHistory = function(){
  104. var windowHash = _getWindowHash(),
  105. frameHash = _getFrameHash();
  106. if(frameHash !== _hash && frameHash !== windowHash){
  107. //detect changes made pressing browser history buttons.
  108. //Workaround since history.back() and history.forward() doesn't
  109. //update hash value on IE6/7 but updates content of the iframe.
  110. //needs to trim hash since value stored already have
  111. //prependHash + appendHash for fast check.
  112. hasher.setHash(_trimHash(frameHash));
  113. } else if (windowHash !== _hash){
  114. //detect if hash changed (manually or using setHash)
  115. _registerChange(windowHash);
  116. }
  117. };
  118. } else {
  119. /**
  120. * @private
  121. */
  122. _checkHistory = function(){
  123. var windowHash = _getWindowHash();
  124. if(windowHash !== _hash){
  125. _registerChange(windowHash);
  126. }
  127. };
  128. }
  129. function _addListener(elm, eType, fn){
  130. if(elm.addEventListener){
  131. elm.addEventListener(eType, fn, false);
  132. } else if (elm.attachEvent){
  133. elm.attachEvent('on' + eType, fn);
  134. }
  135. }
  136. function _removeListener(elm, eType, fn){
  137. if(elm.removeEventListener){
  138. elm.removeEventListener(eType, fn, false);
  139. } else if (elm.detachEvent){
  140. elm.detachEvent('on' + eType, fn);
  141. }
  142. }
  143. function _makePath(paths){
  144. paths = Array.prototype.slice.call(arguments);
  145. var path = paths.join(hasher.separator);
  146. path = path? hasher.prependHash + path.replace(_hashRegexp, '') + hasher.appendHash : path;
  147. return path;
  148. }
  149. function _encodePath(path){
  150. //used encodeURI instead of encodeURIComponent to preserve '?', '/',
  151. //'#'. Fixes Safari bug [issue #8]
  152. path = encodeURI(path);
  153. if(_isIE && _isLocal){
  154. //fix IE8 local file bug [issue #6]
  155. path = path.replace(/\?/, '%3F');
  156. }
  157. return path;
  158. }
  159. //--------------------------------------------------------------------------------------
  160. // Public (API)
  161. //--------------------------------------------------------------------------------------
  162. hasher = /** @lends hasher */ {
  163. /**
  164. * hasher Version Number
  165. * @type string
  166. * @constant
  167. */
  168. VERSION : '1.1.4',
  169. /**
  170. * String that should always be added to the end of Hash value.
  171. * <ul>
  172. * <li>default value: '';</li>
  173. * <li>will be automatically removed from `hasher.getHash()`</li>
  174. * <li>avoid conflicts with elements that contain ID equal to hash value;</li>
  175. * </ul>
  176. * @type string
  177. */
  178. appendHash : '',
  179. /**
  180. * String that should always be added to the beginning of Hash value.
  181. * <ul>
  182. * <li>default value: '/';</li>
  183. * <li>will be automatically removed from `hasher.getHash()`</li>
  184. * <li>avoid conflicts with elements that contain ID equal to hash value;</li>
  185. * </ul>
  186. * @type string
  187. */
  188. prependHash : '/',
  189. /**
  190. * String used to split hash paths; used by `hasher.getHashAsArray()` to split paths.
  191. * <ul>
  192. * <li>default value: '/';</li>
  193. * </ul>
  194. * @type string
  195. */
  196. separator : '/',
  197. /**
  198. * Signal dispatched when hash value changes.
  199. * - pass current hash as 1st parameter to listeners and previous hash value as 2nd parameter.
  200. * @type signals.Signal
  201. */
  202. changed : new Signal(),
  203. /**
  204. * Signal dispatched when hasher is stopped.
  205. * - pass current hash as first parameter to listeners
  206. * @type signals.Signal
  207. */
  208. stopped : new Signal(),
  209. /**
  210. * Signal dispatched when hasher is initialized.
  211. * - pass current hash as first parameter to listeners.
  212. * @type signals.Signal
  213. */
  214. initialized : new Signal(),
  215. /**
  216. * Start listening/dispatching changes in the hash/history.
  217. * <ul>
  218. * <li>hasher won't dispatch CHANGE events by manually typing a new value or pressing the back/forward buttons before calling this method.</li>
  219. * </ul>
  220. */
  221. init : function(){
  222. if(_isActive) return;
  223. _hash = _getWindowHash();
  224. //thought about branching/overloading hasher.init() to avoid checking multiple times but
  225. //don't think worth doing it since it probably won't be called multiple times.
  226. if(_isHashChangeSupported){
  227. _addListener(window, 'hashchange', _checkHistory);
  228. }else {
  229. if(_isLegacyIE){
  230. if(! _frame){
  231. _createFrame();
  232. }
  233. _updateFrame();
  234. }
  235. _checkInterval = setInterval(_checkHistory, POOL_INTERVAL);
  236. }
  237. _isActive = true;
  238. hasher.initialized.dispatch(_trimHash(_hash));
  239. },
  240. /**
  241. * Stop listening/dispatching changes in the hash/history.
  242. * <ul>
  243. * <li>hasher won't dispatch CHANGE events by manually typing a new value or pressing the back/forward buttons after calling this method, unless you call hasher.init() again.</li>
  244. * <li>hasher will still dispatch changes made programatically by calling hasher.setHash();</li>
  245. * </ul>
  246. */
  247. stop : function(){
  248. if(! _isActive) return;
  249. if(_isHashChangeSupported){
  250. _removeListener(window, 'hashchange', _checkHistory);
  251. }else{
  252. clearInterval(_checkInterval);
  253. _checkInterval = null;
  254. }
  255. _isActive = false;
  256. hasher.stopped.dispatch(_trimHash(_hash));
  257. },
  258. /**
  259. * @return {boolean} If hasher is listening to changes on the browser history and/or hash value.
  260. */
  261. isActive : function(){
  262. return _isActive;
  263. },
  264. /**
  265. * @return {string} Full URL.
  266. */
  267. getURL : function(){
  268. return window.location.href;
  269. },
  270. /**
  271. * @return {string} Retrieve URL without query string and hash.
  272. */
  273. getBaseURL : function(){
  274. return hasher.getURL().replace(_baseUrlRegexp, ''); //removes everything after '?' and/or '#'
  275. },
  276. /**
  277. * Set Hash value, generating a new history record.
  278. * @param {...string} path Hash value without '#'. Hasher will join
  279. * path segments using `hasher.separator` and prepend/append hash value
  280. * with `hasher.appendHash` and `hasher.prependHash`
  281. * @example hasher.setHash('lorem', 'ipsum', 'dolor') -> '#/lorem/ipsum/dolor'
  282. */
  283. setHash : function(path){
  284. path = _makePath.apply(null, arguments);
  285. if(path !== _hash){
  286. // we should store raw value
  287. _registerChange(path);
  288. if (path === _hash) {
  289. // we check if path is still === _hash to avoid error in
  290. // case of multiple consecutive redirects [issue #39]
  291. window.location.hash = '#' + _encodePath(path);
  292. }
  293. }
  294. },
  295. /**
  296. * Set Hash value without keeping previous hash on the history record.
  297. * Similar to calling `window.location.replace("#/hash")` but will also work on IE6-7.
  298. * @param {...string} path Hash value without '#'. Hasher will join
  299. * path segments using `hasher.separator` and prepend/append hash value
  300. * with `hasher.appendHash` and `hasher.prependHash`
  301. * @example hasher.replaceHash('lorem', 'ipsum', 'dolor') -> '#/lorem/ipsum/dolor'
  302. */
  303. replaceHash : function(path){
  304. path = _makePath.apply(null, arguments);
  305. if(path !== _hash){
  306. // we should store raw value
  307. _registerChange(path, true);
  308. if (path === _hash) {
  309. // we check if path is still === _hash to avoid error in
  310. // case of multiple consecutive redirects [issue #39]
  311. window.location.replace('#' + _encodePath(path));
  312. }
  313. }
  314. },
  315. /**
  316. * @return {string} Hash value without '#', `hasher.appendHash` and `hasher.prependHash`.
  317. */
  318. getHash : function(){
  319. //didn't used actual value of the `window.location.hash` to avoid breaking the application in case `window.location.hash` isn't available and also because value should always be synched.
  320. return _trimHash(_hash);
  321. },
  322. /**
  323. * @return {Array.<string>} Hash value split into an Array.
  324. */
  325. getHashAsArray : function(){
  326. return hasher.getHash().split(hasher.separator);
  327. },
  328. /**
  329. * Removes all event listeners, stops hasher and destroy hasher object.
  330. * - IMPORTANT: hasher won't work after calling this method, hasher Object will be deleted.
  331. */
  332. dispose : function(){
  333. hasher.stop();
  334. hasher.initialized.dispose();
  335. hasher.stopped.dispose();
  336. hasher.changed.dispose();
  337. _frame = hasher = window.hasher = null;
  338. },
  339. /**
  340. * @return {string} A string representation of the object.
  341. */
  342. toString : function(){
  343. return '[hasher version="'+ hasher.VERSION +'" hash="'+ hasher.getHash() +'"]';
  344. }
  345. };
  346. hasher.initialized.memorize = true; //see #33
  347. return hasher;
  348. }(window));
  349. return hasher;
  350. };
  351. if (typeof define === 'function' && define.amd) {
  352. define(['signals'], factory);
  353. } else if (typeof exports === 'object') {
  354. module.exports = factory(require('signals'));
  355. } else {
  356. /*jshint sub:true */
  357. window['hasher'] = factory(window['signals']);
  358. }
  359. }());