You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

597 lines
25 KiB

  1. /*! jQuery Address v${version} | (c) 2009, 2013 Rostislav Hristov | jquery.org/license */
  2. (function ($) {
  3. $.address = (function () {
  4. var _trigger = function(name) {
  5. var e = $.extend($.Event(name), (function() {
  6. var parameters = {},
  7. parameterNames = $.address.parameterNames();
  8. for (var i = 0, l = parameterNames.length; i < l; i++) {
  9. parameters[parameterNames[i]] = $.address.parameter(parameterNames[i]);
  10. }
  11. return {
  12. value: $.address.value(),
  13. path: $.address.path(),
  14. pathNames: $.address.pathNames(),
  15. parameterNames: parameterNames,
  16. parameters: parameters,
  17. queryString: $.address.queryString()
  18. };
  19. }).call($.address));
  20. $($.address).trigger(e);
  21. return e;
  22. },
  23. _array = function(obj) {
  24. return Array.prototype.slice.call(obj);
  25. },
  26. _bind = function(value, data, fn) {
  27. $().bind.apply($($.address), Array.prototype.slice.call(arguments));
  28. return $.address;
  29. },
  30. _unbind = function(value, fn) {
  31. $().unbind.apply($($.address), Array.prototype.slice.call(arguments));
  32. return $.address;
  33. },
  34. _supportsState = function() {
  35. return (_h.pushState && _opts.state !== UNDEFINED);
  36. },
  37. _hrefState = function() {
  38. return ('/' + _l.pathname.replace(new RegExp(_opts.state), '') +
  39. _l.search + (_hrefHash() ? '#' + _hrefHash() : '')).replace(_re, '/');
  40. },
  41. _hrefHash = function() {
  42. var index = _l.href.indexOf('#');
  43. return index != -1 ? _l.href.substr(index + 1) : '';
  44. },
  45. _href = function() {
  46. return _supportsState() ? _hrefState() : _hrefHash();
  47. },
  48. _window = function() {
  49. try {
  50. return top.document !== UNDEFINED && top.document.title !== UNDEFINED ? top : window;
  51. } catch (e) {
  52. return window;
  53. }
  54. },
  55. _js = function() {
  56. return 'javascript';
  57. },
  58. _strict = function(value) {
  59. value = value.toString();
  60. return (_opts.strict && value.substr(0, 1) != '/' ? '/' : '') + value;
  61. },
  62. _cssint = function(el, value) {
  63. return parseInt(el.css(value), 10);
  64. },
  65. _listen = function() {
  66. if (!_silent) {
  67. var hash = _href(),
  68. diff = decodeURI(_value) != decodeURI(hash);
  69. if (diff) {
  70. if (_msie && _version < 7) {
  71. _l.reload();
  72. } else {
  73. if (_msie && !_hashchange && _opts.history) {
  74. _st(_html, 50);
  75. }
  76. _value = hash;
  77. _update(FALSE);
  78. }
  79. }
  80. }
  81. },
  82. _update = function(internal) {
  83. _st(_track, 10);
  84. return _trigger(CHANGE).isDefaultPrevented() ||
  85. _trigger(internal ? INTERNAL_CHANGE : EXTERNAL_CHANGE).isDefaultPrevented();
  86. },
  87. _track = function() {
  88. if (_opts.tracker !== 'null' && _opts.tracker !== NULL) {
  89. var fn = $.isFunction(_opts.tracker) ? _opts.tracker : _t[_opts.tracker],
  90. value = (_l.pathname + _l.search +
  91. ($.address && !_supportsState() ? $.address.value() : ''))
  92. .replace(/\/\//, '/').replace(/^\/$/, '');
  93. if ($.isFunction(fn)) {
  94. fn(value);
  95. } else if ($.isFunction(_t.urchinTracker)) {
  96. _t.urchinTracker(value);
  97. } else if (_t.pageTracker !== UNDEFINED && $.isFunction(_t.pageTracker._trackPageview)) {
  98. _t.pageTracker._trackPageview(value);
  99. } else if (_t._gaq !== UNDEFINED && $.isFunction(_t._gaq.push)) {
  100. _t._gaq.push(['_trackPageview', decodeURI(value)]);
  101. }
  102. }
  103. },
  104. _html = function() {
  105. var src = _js() + ':' + FALSE + ';document.open();document.writeln(\'<html><head><title>' +
  106. _d.title.replace(/\'/g, '\\\'') + '</title><script>var ' + ID + ' = "' + encodeURIComponent(_href()).replace(/\'/g, '\\\'') +
  107. (_d.domain != _l.hostname ? '";document.domain="' + _d.domain : '') +
  108. '";</' + 'script></head></html>\');document.close();';
  109. if (_version < 7) {
  110. _frame.src = src;
  111. } else {
  112. _frame.contentWindow.location.replace(src);
  113. }
  114. },
  115. _options = function() {
  116. if (_url && _qi != -1) {
  117. var i, param, params = _url.substr(_qi + 1).split('&');
  118. for (i = 0; i < params.length; i++) {
  119. param = params[i].split('=');
  120. if (/^(autoUpdate|history|strict|wrap)$/.test(param[0])) {
  121. _opts[param[0]] = (isNaN(param[1]) ? /^(true|yes)$/i.test(param[1]) : (parseInt(param[1], 10) !== 0));
  122. }
  123. if (/^(state|tracker)$/.test(param[0])) {
  124. _opts[param[0]] = param[1];
  125. }
  126. }
  127. _url = NULL;
  128. }
  129. _value = _href();
  130. },
  131. _load = function() {
  132. if (!_loaded) {
  133. _loaded = TRUE;
  134. _options();
  135. $('a[rel*="address:"]').address();
  136. if (_opts.wrap) {
  137. var body = $('body'),
  138. wrap = $('body > *')
  139. .wrapAll('<div style="padding:' +
  140. (_cssint(body, 'marginTop') + _cssint(body, 'paddingTop')) + 'px ' +
  141. (_cssint(body, 'marginRight') + _cssint(body, 'paddingRight')) + 'px ' +
  142. (_cssint(body, 'marginBottom') + _cssint(body, 'paddingBottom')) + 'px ' +
  143. (_cssint(body, 'marginLeft') + _cssint(body, 'paddingLeft')) + 'px;" />')
  144. .parent()
  145. .wrap('<div id="' + ID + '" style="height:100%;overflow:auto;position:relative;' +
  146. (_webkit && !window.statusbar.visible ? 'resize:both;' : '') + '" />');
  147. $('html, body')
  148. .css({
  149. height: '100%',
  150. margin: 0,
  151. padding: 0,
  152. overflow: 'hidden'
  153. });
  154. if (_webkit) {
  155. $('<style type="text/css" />')
  156. .appendTo('head')
  157. .text('#' + ID + '::-webkit-resizer { background-color: #fff; }');
  158. }
  159. }
  160. if (_msie && !_hashchange) {
  161. var frameset = _d.getElementsByTagName('frameset')[0];
  162. _frame = _d.createElement((frameset ? '' : 'i') + 'frame');
  163. _frame.src = _js() + ':' + FALSE;
  164. if (frameset) {
  165. frameset.insertAdjacentElement('beforeEnd', _frame);
  166. frameset[frameset.cols ? 'cols' : 'rows'] += ',0';
  167. _frame.noResize = TRUE;
  168. _frame.frameBorder = _frame.frameSpacing = 0;
  169. } else {
  170. _frame.style.display = 'none';
  171. _frame.style.width = _frame.style.height = 0;
  172. _frame.tabIndex = -1;
  173. _d.body.insertAdjacentElement('afterBegin', _frame);
  174. }
  175. _st(function() {
  176. $(_frame).bind('load', function() {
  177. var win = _frame.contentWindow;
  178. _value = win[ID] !== UNDEFINED ? win[ID] : '';
  179. if (_value != _href()) {
  180. _update(FALSE);
  181. _l.hash = _value;
  182. }
  183. });
  184. if (_frame.contentWindow[ID] === UNDEFINED) {
  185. _html();
  186. }
  187. }, 50);
  188. }
  189. _st(function() {
  190. _trigger('init');
  191. _update(FALSE);
  192. }, 1);
  193. if (!_supportsState()) {
  194. if ((_msie && _version > 7) || (!_msie && _hashchange)) {
  195. if (_t.addEventListener) {
  196. _t.addEventListener(HASH_CHANGE, _listen, FALSE);
  197. } else if (_t.attachEvent) {
  198. _t.attachEvent('on' + HASH_CHANGE, _listen);
  199. }
  200. } else {
  201. _si(_listen, 50);
  202. }
  203. }
  204. if ('state' in window.history) {
  205. $(window).trigger('popstate');
  206. }
  207. }
  208. },
  209. _popstate = function() {
  210. if (decodeURI(_value) != decodeURI(_href())) {
  211. _value = _href();
  212. _update(FALSE);
  213. }
  214. },
  215. _unload = function() {
  216. if (_t.removeEventListener) {
  217. _t.removeEventListener(HASH_CHANGE, _listen, FALSE);
  218. } else if (_t.detachEvent) {
  219. _t.detachEvent('on' + HASH_CHANGE, _listen);
  220. }
  221. },
  222. _uaMatch = function(ua) {
  223. ua = ua.toLowerCase();
  224. var match = /(chrome)[ \/]([\w.]+)/.exec( ua ) ||
  225. /(webkit)[ \/]([\w.]+)/.exec( ua ) ||
  226. /(opera)(?:.*version|)[ \/]([\w.]+)/.exec( ua ) ||
  227. /(msie) ([\w.]+)/.exec( ua ) ||
  228. ua.indexOf('compatible') < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec( ua ) ||
  229. [];
  230. return {
  231. browser: match[ 1 ] || '',
  232. version: match[ 2 ] || '0'
  233. };
  234. },
  235. _detectBrowser = function() {
  236. var browser = {},
  237. matched = _uaMatch(navigator.userAgent);
  238. if (matched.browser) {
  239. browser[matched.browser] = true;
  240. browser.version = matched.version;
  241. }
  242. if (browser.chrome) {
  243. browser.webkit = true;
  244. } else if (browser.webkit) {
  245. browser.safari = true;
  246. }
  247. return browser;
  248. },
  249. UNDEFINED,
  250. NULL = null,
  251. ID = 'jQueryAddress',
  252. STRING = 'string',
  253. HASH_CHANGE = 'hashchange',
  254. INIT = 'init',
  255. CHANGE = 'change',
  256. INTERNAL_CHANGE = 'internalChange',
  257. EXTERNAL_CHANGE = 'externalChange',
  258. TRUE = true,
  259. FALSE = false,
  260. _opts = {
  261. autoUpdate: TRUE,
  262. history: TRUE,
  263. strict: TRUE,
  264. wrap: FALSE
  265. },
  266. _browser = _detectBrowser(),
  267. _version = parseFloat(_browser.version),
  268. _webkit = _browser.webkit || _browser.safari,
  269. _msie = !$.support.opacity,
  270. _t = _window(),
  271. _d = _t.document,
  272. _h = _t.history,
  273. _l = _t.location,
  274. _si = setInterval,
  275. _st = setTimeout,
  276. _re = /\/{2,9}/g,
  277. _agent = navigator.userAgent,
  278. _hashchange = 'on' + HASH_CHANGE in _t,
  279. _frame,
  280. _form,
  281. _url = $('script:last').attr('src'),
  282. _qi = _url ? _url.indexOf('?') : -1,
  283. _title = _d.title,
  284. _silent = FALSE,
  285. _loaded = FALSE,
  286. _juststart = TRUE,
  287. _updating = FALSE,
  288. _listeners = {},
  289. _value = _href();
  290. if (_msie) {
  291. _version = parseFloat(_agent.substr(_agent.indexOf('MSIE') + 4));
  292. if (_d.documentMode && _d.documentMode != _version) {
  293. _version = _d.documentMode != 8 ? 7 : 8;
  294. }
  295. var pc = _d.onpropertychange;
  296. _d.onpropertychange = function() {
  297. if (pc) {
  298. pc.call(_d);
  299. }
  300. if (_d.title != _title && _d.title.indexOf('#' + _href()) != -1) {
  301. _d.title = _title;
  302. }
  303. };
  304. }
  305. if (_h.navigationMode) {
  306. _h.navigationMode = 'compatible';
  307. }
  308. if (document.readyState == 'complete') {
  309. var interval = setInterval(function() {
  310. if ($.address) {
  311. _load();
  312. clearInterval(interval);
  313. }
  314. }, 50);
  315. } else {
  316. _options();
  317. $(_load);
  318. }
  319. $(window).bind('popstate', _popstate).bind('unload', _unload);
  320. return {
  321. bind: function(type, data, fn) {
  322. return _bind.apply(this, _array(arguments));
  323. },
  324. unbind: function(type, fn) {
  325. return _unbind.apply(this, _array(arguments));
  326. },
  327. init: function(data, fn) {
  328. return _bind.apply(this, [INIT].concat(_array(arguments)));
  329. },
  330. change: function(data, fn) {
  331. return _bind.apply(this, [CHANGE].concat(_array(arguments)));
  332. },
  333. internalChange: function(data, fn) {
  334. return _bind.apply(this, [INTERNAL_CHANGE].concat(_array(arguments)));
  335. },
  336. externalChange: function(data, fn) {
  337. return _bind.apply(this, [EXTERNAL_CHANGE].concat(_array(arguments)));
  338. },
  339. baseURL: function() {
  340. var url = _l.href;
  341. if (url.indexOf('#') != -1) {
  342. url = url.substr(0, url.indexOf('#'));
  343. }
  344. if (/\/$/.test(url)) {
  345. url = url.substr(0, url.length - 1);
  346. }
  347. return url;
  348. },
  349. autoUpdate: function(value) {
  350. if (value !== UNDEFINED) {
  351. _opts.autoUpdate = value;
  352. return this;
  353. }
  354. return _opts.autoUpdate;
  355. },
  356. history: function(value) {
  357. if (value !== UNDEFINED) {
  358. _opts.history = value;
  359. return this;
  360. }
  361. return _opts.history;
  362. },
  363. state: function(value) {
  364. if (value !== UNDEFINED) {
  365. _opts.state = value;
  366. var hrefState = _hrefState();
  367. if (_opts.state !== UNDEFINED) {
  368. if (_h.pushState) {
  369. if (hrefState.substr(0, 3) == '/#/') {
  370. _l.replace(_opts.state.replace(/^\/$/, '') + hrefState.substr(2));
  371. }
  372. } else if (hrefState != '/' && hrefState.replace(/^\/#/, '') != _hrefHash()) {
  373. _st(function() {
  374. _l.replace(_opts.state.replace(/^\/$/, '') + '/#' + hrefState);
  375. }, 1);
  376. }
  377. }
  378. return this;
  379. }
  380. return _opts.state;
  381. },
  382. strict: function(value) {
  383. if (value !== UNDEFINED) {
  384. _opts.strict = value;
  385. return this;
  386. }
  387. return _opts.strict;
  388. },
  389. tracker: function(value) {
  390. if (value !== UNDEFINED) {
  391. _opts.tracker = value;
  392. return this;
  393. }
  394. return _opts.tracker;
  395. },
  396. wrap: function(value) {
  397. if (value !== UNDEFINED) {
  398. _opts.wrap = value;
  399. return this;
  400. }
  401. return _opts.wrap;
  402. },
  403. update: function() {
  404. _updating = TRUE;
  405. this.value(_value);
  406. _updating = FALSE;
  407. return this;
  408. },
  409. title: function(value) {
  410. if (value !== UNDEFINED) {
  411. _st(function() {
  412. _title = _d.title = value;
  413. if (_juststart && _frame && _frame.contentWindow && _frame.contentWindow.document) {
  414. _frame.contentWindow.document.title = value;
  415. _juststart = FALSE;
  416. }
  417. }, 50);
  418. return this;
  419. }
  420. return _d.title;
  421. },
  422. value: function(value) {
  423. if (value !== UNDEFINED) {
  424. value = _strict(value);
  425. if (value == '/') {
  426. value = '';
  427. }
  428. if (_value == value && !_updating) {
  429. return;
  430. }
  431. _value = value;
  432. if (_opts.autoUpdate || _updating) {
  433. if (_update(TRUE)) {
  434. return this;
  435. }
  436. if (_supportsState()) {
  437. _h[_opts.history ? 'pushState' : 'replaceState']({}, '',
  438. _opts.state.replace(/\/$/, '') + (_value === '' ? '/' : _value));
  439. } else {
  440. _silent = TRUE;
  441. if (_webkit) {
  442. if (_opts.history) {
  443. _l.hash = '#' + _value;
  444. } else {
  445. _l.replace('#' + _value);
  446. }
  447. } else if (_value != _href()) {
  448. if (_opts.history) {
  449. _l.hash = '#' + _value;
  450. } else {
  451. _l.replace('#' + _value);
  452. }
  453. }
  454. if ((_msie && !_hashchange) && _opts.history) {
  455. _st(_html, 50);
  456. }
  457. if (_webkit) {
  458. _st(function(){ _silent = FALSE; }, 1);
  459. } else {
  460. _silent = FALSE;
  461. }
  462. }
  463. }
  464. return this;
  465. }
  466. return _strict(_value);
  467. },
  468. path: function(value) {
  469. if (value !== UNDEFINED) {
  470. var qs = this.queryString(),
  471. hash = this.hash();
  472. this.value(value + (qs ? '?' + qs : '') + (hash ? '#' + hash : ''));
  473. return this;
  474. }
  475. return _strict(_value).split('#')[0].split('?')[0];
  476. },
  477. pathNames: function() {
  478. var path = this.path(),
  479. names = path.replace(_re, '/').split('/');
  480. if (path.substr(0, 1) == '/' || path.length === 0) {
  481. names.splice(0, 1);
  482. }
  483. if (path.substr(path.length - 1, 1) == '/') {
  484. names.splice(names.length - 1, 1);
  485. }
  486. return names;
  487. },
  488. queryString: function(value) {
  489. if (value !== UNDEFINED) {
  490. var hash = this.hash();
  491. this.value(this.path() + (value ? '?' + value : '') + (hash ? '#' + hash : ''));
  492. return this;
  493. }
  494. var arr = _value.split('?');
  495. return arr.slice(1, arr.length).join('?').split('#')[0];
  496. },
  497. parameter: function(name, value, append) {
  498. var i, params;
  499. if (value !== UNDEFINED) {
  500. var names = this.parameterNames();
  501. params = [];
  502. value = value === UNDEFINED || value === NULL ? '' : value.toString();
  503. for (i = 0; i < names.length; i++) {
  504. var n = names[i],
  505. v = this.parameter(n);
  506. if (typeof v == STRING) {
  507. v = [v];
  508. }
  509. if (n == name) {
  510. v = (value === NULL || value === '') ? [] :
  511. (append ? v.concat([value]) : [value]);
  512. }
  513. for (var j = 0; j < v.length; j++) {
  514. params.push(n + '=' + v[j]);
  515. }
  516. }
  517. if ($.inArray(name, names) == -1 && value !== NULL && value !== '') {
  518. params.push(name + '=' + value);
  519. }
  520. this.queryString(params.join('&'));
  521. return this;
  522. }
  523. value = this.queryString();
  524. if (value) {
  525. var r = [];
  526. params = value.split('&');
  527. for (i = 0; i < params.length; i++) {
  528. var p = params[i].split('=');
  529. if (p[0] == name) {
  530. r.push(p.slice(1).join('='));
  531. }
  532. }
  533. if (r.length !== 0) {
  534. return r.length != 1 ? r : r[0];
  535. }
  536. }
  537. },
  538. parameterNames: function() {
  539. var qs = this.queryString(),
  540. names = [];
  541. if (qs && qs.indexOf('=') != -1) {
  542. var params = qs.split('&');
  543. for (var i = 0; i < params.length; i++) {
  544. var name = params[i].split('=')[0];
  545. if ($.inArray(name, names) == -1) {
  546. names.push(name);
  547. }
  548. }
  549. }
  550. return names;
  551. },
  552. hash: function(value) {
  553. if (value !== UNDEFINED) {
  554. this.value(_value.split('#')[0] + (value ? '#' + value : ''));
  555. return this;
  556. }
  557. var arr = _value.split('#');
  558. return arr.slice(1, arr.length).join('#');
  559. }
  560. };
  561. })();
  562. $.fn.address = function(fn) {
  563. if (!this.data('address')) {
  564. this.on('click', function(e) {
  565. if (e.shiftKey || e.ctrlKey || e.metaKey || e.which == 2) {
  566. return true;
  567. }
  568. var target = e.currentTarget;
  569. if ($(target).is('a')) {
  570. e.preventDefault();
  571. var value = fn ? fn.call(target) :
  572. /address:/.test($(target).attr('rel')) ? $(target).attr('rel').split('address:')[1].split(' ')[0] :
  573. $.address.state() !== undefined && !/^\/?$/.test($.address.state()) ?
  574. $(target).attr('href').replace(new RegExp('^(.*' + $.address.state() + '|\\.)'), '') :
  575. $(target).attr('href').replace(/^(#\!?|\.)/, '');
  576. $.address.value(value);
  577. }
  578. }).on('submit', function(e) {
  579. var target = e.currentTarget;
  580. if ($(target).is('form')) {
  581. e.preventDefault();
  582. var action = $(target).attr('action'),
  583. value = fn ? fn.call(target) : (action.indexOf('?') != -1 ? action.replace(/&$/, '') : action + '?') +
  584. $(target).serialize();
  585. $.address.value(value);
  586. }
  587. }).data('address', true);
  588. }
  589. return this;
  590. };
  591. })(jQuery);