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.

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