jquery-ui.tabs-1.6rc2.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588
  1. /*
  2. * jQuery UI Tabs @VERSION
  3. *
  4. * Copyright (c) 2007, 2008 Klaus Hartl (stilbuero.de)
  5. * Dual licensed under the MIT (MIT-LICENSE.txt)
  6. * and GPL (GPL-LICENSE.txt) licenses.
  7. *
  8. * http://docs.jquery.com/UI/Tabs
  9. *
  10. * Depends:
  11. * ui.core.js
  12. */
  13. (function($) {
  14. $.widget("ui.tabs", {
  15. _init: function() {
  16. this.options.event += '.tabs'; // namespace event
  17. // create tabs
  18. this._tabify(true);
  19. },
  20. _setData: function(key, value) {
  21. if ((/^selected/).test(key))
  22. this.select(value);
  23. else {
  24. this.options[key] = value;
  25. this._tabify();
  26. }
  27. },
  28. length: function() {
  29. return this.$tabs.length;
  30. },
  31. _tabId: function(a) {
  32. return a.title && a.title.replace(/\s/g, '_').replace(/[^A-Za-z0-9\-_:\.]/g, '')
  33. || this.options.idPrefix + $.data(a);
  34. },
  35. ui: function(tab, panel) {
  36. return {
  37. options: this.options,
  38. tab: tab,
  39. panel: panel,
  40. index: this.$tabs.index(tab)
  41. };
  42. },
  43. _tabify: function(init) {
  44. this.$lis = $('li:has(a[href])', this.element);
  45. this.$tabs = this.$lis.map(function() { return $('a', this)[0]; });
  46. this.$panels = $([]);
  47. var self = this, o = this.options;
  48. this.$tabs.each(function(i, a) {
  49. // inline tab
  50. if (a.hash && a.hash.replace('#', '')) // Safari 2 reports '#' for an empty hash
  51. self.$panels = self.$panels.add(a.hash);
  52. // remote tab
  53. else if ($(a).attr('href') != '#') { // prevent loading the page itself if href is just "#"
  54. $.data(a, 'href.tabs', a.href); // required for restore on destroy
  55. $.data(a, 'load.tabs', a.href); // mutable
  56. var id = self._tabId(a);
  57. a.href = '#' + id;
  58. var $panel = $('#' + id);
  59. if (!$panel.length) {
  60. $panel = $(o.panelTemplate).attr('id', id).addClass(o.panelClass)
  61. .insertAfter( self.$panels[i - 1] || self.element );
  62. $panel.data('destroy.tabs', true);
  63. }
  64. self.$panels = self.$panels.add( $panel );
  65. }
  66. // invalid tab href
  67. else
  68. o.disabled.push(i + 1);
  69. });
  70. // initialization from scratch
  71. if (init) {
  72. // attach necessary classes for styling if not present
  73. this.element.addClass(o.navClass);
  74. this.$panels.each(function() {
  75. var $this = $(this);
  76. $this.addClass(o.panelClass);
  77. });
  78. // Selected tab
  79. // use "selected" option or try to retrieve:
  80. // 1. from fragment identifier in url
  81. // 2. from cookie
  82. // 3. from selected class attribute on <li>
  83. if (o.selected === undefined) {
  84. if (location.hash) {
  85. this.$tabs.each(function(i, a) {
  86. if (a.hash == location.hash) {
  87. o.selected = i;
  88. // prevent page scroll to fragment
  89. if ($.browser.msie || $.browser.opera) { // && !o.remote
  90. var $toShow = $(location.hash), toShowId = $toShow.attr('id');
  91. $toShow.attr('id', '');
  92. setTimeout(function() {
  93. $toShow.attr('id', toShowId); // restore id
  94. }, 500);
  95. }
  96. scrollTo(0, 0);
  97. return false; // break
  98. }
  99. });
  100. }
  101. else if (o.cookie) {
  102. var index = parseInt($.cookie('ui-tabs-' + $.data(self.element[0])), 10);
  103. if (index && self.$tabs[index])
  104. o.selected = index;
  105. }
  106. else if (self.$lis.filter('.' + o.selectedClass).length)
  107. o.selected = self.$lis.index( self.$lis.filter('.' + o.selectedClass)[0] );
  108. }
  109. o.selected = o.selected === null || o.selected !== undefined ? o.selected : 0; // first tab selected by default
  110. // Take disabling tabs via class attribute from HTML
  111. // into account and update option properly.
  112. // A selected tab cannot become disabled.
  113. o.disabled = $.unique(o.disabled.concat(
  114. $.map(this.$lis.filter('.' + o.disabledClass),
  115. function(n, i) { return self.$lis.index(n); } )
  116. )).sort();
  117. if ($.inArray(o.selected, o.disabled) != -1)
  118. o.disabled.splice($.inArray(o.selected, o.disabled), 1);
  119. // highlight selected tab
  120. this.$panels.addClass(o.hideClass);
  121. this.$lis.removeClass(o.selectedClass);
  122. if (o.selected !== null) {
  123. this.$panels.eq(o.selected).show().removeClass(o.hideClass); // use show and remove class to show in any case no matter how it has been hidden before
  124. this.$lis.eq(o.selected).addClass(o.selectedClass);
  125. // seems to be expected behavior that the show callback is fired
  126. var onShow = function() {
  127. self._trigger('show', null,
  128. self.ui(self.$tabs[o.selected], self.$panels[o.selected]));
  129. };
  130. // load if remote tab
  131. if ($.data(this.$tabs[o.selected], 'load.tabs'))
  132. this.load(o.selected, onShow);
  133. // just trigger show event
  134. else
  135. onShow();
  136. }
  137. // clean up to avoid memory leaks in certain versions of IE 6
  138. $(window).bind('unload', function() {
  139. self.$tabs.unbind('.tabs');
  140. self.$lis = self.$tabs = self.$panels = null;
  141. });
  142. }
  143. // update selected after add/remove
  144. else
  145. o.selected = this.$lis.index( this.$lis.filter('.' + o.selectedClass)[0] );
  146. // set or update cookie after init and add/remove respectively
  147. if (o.cookie)
  148. $.cookie('ui-tabs-' + $.data(self.element[0]), o.selected, o.cookie);
  149. // disable tabs
  150. for (var i = 0, li; li = this.$lis[i]; i++)
  151. $(li)[$.inArray(i, o.disabled) != -1 && !$(li).hasClass(o.selectedClass) ? 'addClass' : 'removeClass'](o.disabledClass);
  152. // reset cache if switching from cached to not cached
  153. if (o.cache === false)
  154. this.$tabs.removeData('cache.tabs');
  155. // set up animations
  156. var hideFx, showFx, baseFx = { 'min-width': 0, duration: 1 }, baseDuration = 'normal';
  157. if (o.fx && o.fx.constructor == Array)
  158. hideFx = o.fx[0] || baseFx, showFx = o.fx[1] || baseFx;
  159. else
  160. hideFx = showFx = o.fx || baseFx;
  161. // reset some styles to maintain print style sheets etc.
  162. var resetCSS = { display: '', overflow: '', height: '' };
  163. if (!$.browser.msie) // not in IE to prevent ClearType font issue
  164. resetCSS.opacity = '';
  165. // Hide a tab, animation prevents browser scrolling to fragment,
  166. // $show is optional.
  167. function hideTab(clicked, $hide, $show) {
  168. $hide.animate(hideFx, hideFx.duration || baseDuration, function() { //
  169. $hide.addClass(o.hideClass).css(resetCSS); // maintain flexible height and accessibility in print etc.
  170. if ($.browser.msie && hideFx.opacity)
  171. $hide[0].style.filter = '';
  172. if ($show)
  173. showTab(clicked, $show, $hide);
  174. });
  175. }
  176. // Show a tab, animation prevents browser scrolling to fragment,
  177. // $hide is optional.
  178. function showTab(clicked, $show, $hide) {
  179. if (showFx === baseFx)
  180. $show.css('display', 'block'); // prevent occasionally occuring flicker in Firefox cause by gap between showing and hiding the tab panels
  181. $show.animate(showFx, showFx.duration || baseDuration, function() {
  182. $show.removeClass(o.hideClass).css(resetCSS); // maintain flexible height and accessibility in print etc.
  183. if ($.browser.msie && showFx.opacity)
  184. $show[0].style.filter = '';
  185. // callback
  186. self._trigger('show', null, self.ui(clicked, $show[0]));
  187. });
  188. }
  189. // switch a tab
  190. function switchTab(clicked, $li, $hide, $show) {
  191. /*if (o.bookmarkable && trueClick) { // add to history only if true click occured, not a triggered click
  192. $.ajaxHistory.update(clicked.hash);
  193. }*/
  194. $li.addClass(o.selectedClass)
  195. .siblings().removeClass(o.selectedClass);
  196. hideTab(clicked, $hide, $show);
  197. }
  198. // attach tab event handler, unbind to avoid duplicates from former tabifying...
  199. this.$tabs.unbind('.tabs').bind(o.event, function() {
  200. //var trueClick = e.clientX; // add to history only if true click occured, not a triggered click
  201. var $li = $(this).parents('li:eq(0)'),
  202. $hide = self.$panels.filter(':visible'),
  203. $show = $(this.hash);
  204. // If tab is already selected and not unselectable or tab disabled or
  205. // or is already loading or click callback returns false stop here.
  206. // Check if click handler returns false last so that it is not executed
  207. // for a disabled or loading tab!
  208. if (($li.hasClass(o.selectedClass) && !o.unselect)
  209. || $li.hasClass(o.disabledClass)
  210. || $(this).hasClass(o.loadingClass)
  211. || self._trigger('select', null, self.ui(this, $show[0])) === false
  212. ) {
  213. this.blur();
  214. return false;
  215. }
  216. self.options.selected = self.$tabs.index(this);
  217. // if tab may be closed
  218. if (o.unselect) {
  219. if ($li.hasClass(o.selectedClass)) {
  220. self.options.selected = null;
  221. $li.removeClass(o.selectedClass);
  222. self.$panels.stop();
  223. hideTab(this, $hide);
  224. this.blur();
  225. return false;
  226. } else if (!$hide.length) {
  227. self.$panels.stop();
  228. var a = this;
  229. self.load(self.$tabs.index(this), function() {
  230. $li.addClass(o.selectedClass).addClass(o.unselectClass);
  231. showTab(a, $show);
  232. });
  233. this.blur();
  234. return false;
  235. }
  236. }
  237. if (o.cookie)
  238. $.cookie('ui-tabs-' + $.data(self.element[0]), self.options.selected, o.cookie);
  239. // stop possibly running animations
  240. self.$panels.stop();
  241. // show new tab
  242. if ($show.length) {
  243. // prevent scrollbar scrolling to 0 and than back in IE7, happens only if bookmarking/history is enabled
  244. /*if ($.browser.msie && o.bookmarkable) {
  245. var showId = this.hash.replace('#', '');
  246. $show.attr('id', '');
  247. setTimeout(function() {
  248. $show.attr('id', showId); // restore id
  249. }, 0);
  250. }*/
  251. var a = this;
  252. self.load(self.$tabs.index(this), $hide.length ?
  253. function() {
  254. switchTab(a, $li, $hide, $show);
  255. } :
  256. function() {
  257. $li.addClass(o.selectedClass);
  258. showTab(a, $show);
  259. }
  260. );
  261. // Set scrollbar to saved position - need to use timeout with 0 to prevent browser scroll to target of hash
  262. /*var scrollX = window.pageXOffset || document.documentElement && document.documentElement.scrollLeft || document.body.scrollLeft || 0;
  263. var scrollY = window.pageYOffset || document.documentElement && document.documentElement.scrollTop || document.body.scrollTop || 0;
  264. setTimeout(function() {
  265. scrollTo(scrollX, scrollY);
  266. }, 0);*/
  267. } else
  268. throw 'jQuery UI Tabs: Mismatching fragment identifier.';
  269. // Prevent IE from keeping other link focussed when using the back button
  270. // and remove dotted border from clicked link. This is controlled in modern
  271. // browsers via CSS, also blur removes focus from address bar in Firefox
  272. // which can become a usability and annoying problem with tabsRotate.
  273. if ($.browser.msie)
  274. this.blur();
  275. //return o.bookmarkable && !!trueClick; // convert trueClick == undefined to Boolean required in IE
  276. return false;
  277. });
  278. // disable click if event is configured to something else
  279. if (!(/^click/).test(o.event))
  280. this.$tabs.bind('click.tabs', function() { return false; });
  281. },
  282. add: function(url, label, index) {
  283. if (index == undefined)
  284. index = this.$tabs.length; // append by default
  285. var o = this.options;
  286. var $li = $(o.tabTemplate.replace(/#\{href\}/g, url).replace(/#\{label\}/g, label));
  287. $li.data('destroy.tabs', true);
  288. var id = url.indexOf('#') == 0 ? url.replace('#', '') : this._tabId( $('a:first-child', $li)[0] );
  289. // try to find an existing element before creating a new one
  290. var $panel = $('#' + id);
  291. if (!$panel.length) {
  292. $panel = $(o.panelTemplate).attr('id', id)
  293. .addClass(o.hideClass)
  294. .data('destroy.tabs', true);
  295. }
  296. $panel.addClass(o.panelClass);
  297. if (index >= this.$lis.length) {
  298. $li.appendTo(this.element);
  299. $panel.appendTo(this.element[0].parentNode);
  300. } else {
  301. $li.insertBefore(this.$lis[index]);
  302. $panel.insertBefore(this.$panels[index]);
  303. }
  304. o.disabled = $.map(o.disabled,
  305. function(n, i) { return n >= index ? ++n : n });
  306. this._tabify();
  307. if (this.$tabs.length == 1) {
  308. $li.addClass(o.selectedClass);
  309. $panel.removeClass(o.hideClass);
  310. var href = $.data(this.$tabs[0], 'load.tabs');
  311. if (href)
  312. this.load(index, href);
  313. }
  314. // callback
  315. this._trigger('add', null, this.ui(this.$tabs[index], this.$panels[index]));
  316. },
  317. remove: function(index) {
  318. var o = this.options, $li = this.$lis.eq(index).remove(),
  319. $panel = this.$panels.eq(index).remove();
  320. // If selected tab was removed focus tab to the right or
  321. // in case the last tab was removed the tab to the left.
  322. if ($li.hasClass(o.selectedClass) && this.$tabs.length > 1)
  323. this.select(index + (index + 1 < this.$tabs.length ? 1 : -1));
  324. o.disabled = $.map($.grep(o.disabled, function(n, i) { return n != index; }),
  325. function(n, i) { return n >= index ? --n : n });
  326. this._tabify();
  327. // callback
  328. this._trigger('remove', null, this.ui($li.find('a')[0], $panel[0]));
  329. },
  330. enable: function(index) {
  331. var o = this.options;
  332. if ($.inArray(index, o.disabled) == -1)
  333. return;
  334. var $li = this.$lis.eq(index).removeClass(o.disabledClass);
  335. if ($.browser.safari) { // fix disappearing tab (that used opacity indicating disabling) after enabling in Safari 2...
  336. $li.css('display', 'inline-block');
  337. setTimeout(function() {
  338. $li.css('display', 'block');
  339. }, 0);
  340. }
  341. o.disabled = $.grep(o.disabled, function(n, i) { return n != index; });
  342. // callback
  343. this._trigger('enable', null, this.ui(this.$tabs[index], this.$panels[index]));
  344. },
  345. disable: function(index) {
  346. var self = this, o = this.options;
  347. if (index != o.selected) { // cannot disable already selected tab
  348. this.$lis.eq(index).addClass(o.disabledClass);
  349. o.disabled.push(index);
  350. o.disabled.sort();
  351. // callback
  352. this._trigger('disable', null, this.ui(this.$tabs[index], this.$panels[index]));
  353. }
  354. },
  355. select: function(index) {
  356. if (typeof index == 'string')
  357. index = this.$tabs.index( this.$tabs.filter('[href$=' + index + ']')[0] );
  358. this.$tabs.eq(index).trigger(this.options.event);
  359. },
  360. load: function(index, callback) { // callback is for internal usage only
  361. var self = this, o = this.options, $a = this.$tabs.eq(index), a = $a[0],
  362. bypassCache = callback == undefined || callback === false, url = $a.data('load.tabs');
  363. callback = callback || function() {};
  364. // no remote or from cache - just finish with callback
  365. if (!url || !bypassCache && $.data(a, 'cache.tabs')) {
  366. callback();
  367. return;
  368. }
  369. // load remote from here on
  370. var inner = function(parent) {
  371. var $parent = $(parent), $inner = $parent.find('*:last');
  372. return $inner.length && $inner.is(':not(img)') && $inner || $parent;
  373. };
  374. var cleanup = function() {
  375. self.$tabs.filter('.' + o.loadingClass).removeClass(o.loadingClass)
  376. .each(function() {
  377. if (o.spinner)
  378. inner(this).parent().html(inner(this).data('label.tabs'));
  379. });
  380. self.xhr = null;
  381. };
  382. if (o.spinner) {
  383. var label = inner(a).html();
  384. inner(a).wrapInner('<em></em>')
  385. .find('em').data('label.tabs', label).html(o.spinner);
  386. }
  387. var ajaxOptions = $.extend({}, o.ajaxOptions, {
  388. url: url,
  389. success: function(r, s) {
  390. $(a.hash).html(r);
  391. cleanup();
  392. if (o.cache)
  393. $.data(a, 'cache.tabs', true); // if loaded once do not load them again
  394. // callbacks
  395. self._trigger('load', null, self.ui(self.$tabs[index], self.$panels[index]));
  396. o.ajaxOptions.success && o.ajaxOptions.success(r, s);
  397. // This callback is required because the switch has to take
  398. // place after loading has completed. Call last in order to
  399. // fire load before show callback...
  400. callback();
  401. }
  402. });
  403. if (this.xhr) {
  404. // terminate pending requests from other tabs and restore tab label
  405. this.xhr.abort();
  406. cleanup();
  407. }
  408. $a.addClass(o.loadingClass);
  409. setTimeout(function() { // timeout is again required in IE, "wait" for id being restored
  410. self.xhr = $.ajax(ajaxOptions);
  411. }, 0);
  412. },
  413. url: function(index, url) {
  414. this.$tabs.eq(index).removeData('cache.tabs').data('load.tabs', url);
  415. },
  416. destroy: function() {
  417. var o = this.options;
  418. this.element.unbind('.tabs')
  419. .removeClass(o.navClass).removeData('tabs');
  420. this.$tabs.each(function() {
  421. var href = $.data(this, 'href.tabs');
  422. if (href)
  423. this.href = href;
  424. var $this = $(this).unbind('.tabs');
  425. $.each(['href', 'load', 'cache'], function(i, prefix) {
  426. $this.removeData(prefix + '.tabs');
  427. });
  428. });
  429. this.$lis.add(this.$panels).each(function() {
  430. if ($.data(this, 'destroy.tabs'))
  431. $(this).remove();
  432. else
  433. $(this).removeClass([o.selectedClass, o.unselectClass,
  434. o.disabledClass, o.panelClass, o.hideClass].join(' '));
  435. });
  436. }
  437. });
  438. $.ui.tabs.defaults = {
  439. // basic setup
  440. unselect: false,
  441. event: 'click',
  442. disabled: [],
  443. cookie: null, // e.g. { expires: 7, path: '/', domain: 'jquery.com', secure: true }
  444. // TODO history: false,
  445. // Ajax
  446. spinner: 'Loading&#8230;',
  447. cache: false,
  448. idPrefix: 'ui-tabs-',
  449. ajaxOptions: {},
  450. // animations
  451. fx: null, // e.g. { height: 'toggle', opacity: 'toggle', duration: 200 }
  452. // templates
  453. tabTemplate: '<li><a href="#{href}"><span>#{label}</span></a></li>',
  454. panelTemplate: '<div></div>',
  455. // CSS classes
  456. navClass: 'ui-tabs-nav',
  457. selectedClass: 'ui-tabs-selected',
  458. unselectClass: 'ui-tabs-unselect',
  459. disabledClass: 'ui-tabs-disabled',
  460. panelClass: 'ui-tabs-panel',
  461. hideClass: 'ui-tabs-hide',
  462. loadingClass: 'ui-tabs-loading'
  463. };
  464. $.ui.tabs.getter = "length";
  465. /*
  466. * Tabs Extensions
  467. */
  468. /*
  469. * Rotate
  470. */
  471. $.extend($.ui.tabs.prototype, {
  472. rotation: null,
  473. rotate: function(ms, continuing) {
  474. continuing = continuing || false;
  475. var self = this, t = this.options.selected;
  476. function start() {
  477. self.rotation = setInterval(function() {
  478. t = ++t < self.$tabs.length ? t : 0;
  479. self.select(t);
  480. }, ms);
  481. }
  482. function stop(e) {
  483. if (!e || e.clientX) { // only in case of a true click
  484. clearInterval(self.rotation);
  485. }
  486. }
  487. // start interval
  488. if (ms) {
  489. start();
  490. if (!continuing)
  491. this.$tabs.bind(this.options.event, stop);
  492. else
  493. this.$tabs.bind(this.options.event, function() {
  494. stop();
  495. t = self.options.selected;
  496. start();
  497. });
  498. }
  499. // stop interval
  500. else {
  501. stop();
  502. this.$tabs.unbind(this.options.event, stop);
  503. }
  504. }
  505. });
  506. })(jQuery);