Tabs: solved spinner issues and ajax loading not being stopped when selecting a static page tab, fixes #4109, #3929

This commit is contained in:
Klaus Hartl 2009-02-18 21:44:34 +00:00
parent c748a543c4
commit 07809340c8
7 changed files with 92 additions and 53 deletions

BIN
tests/unit/tabs/spinner.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -24,9 +24,9 @@
<div id="main"> <div id="main">
<div id="tabs1"> <div id="tabs1">
<ul> <ul>
<li><a href="#fragment-1">1</a></li> <li><a href="#fragment-1"><span>1</span></a></li>
<li><a href="#fragment-2">2</a></li> <li><a href="#fragment-2"><span>2</span></a></li>
<li><a href="#fragment-3">3</a></li> <li><a href="#fragment-3"><span>3</span></a></li>
</ul> </ul>
<div id="fragment-1"></div> <div id="fragment-1"></div>
<div id="fragment-2"></div> <div id="fragment-2"></div>
@ -34,9 +34,9 @@
</div> </div>
<div id="tabs2"> <div id="tabs2">
<ul> <ul>
<li><a href="#colon:test">1</a></li> <li><a href="#colon:test"><span>1</span></a></li>
<li><a href="#inline-style">2</a></li> <li><a href="#inline-style"><span>2</span></a></li>
<li><a href="test.html#test">1</a></li> <li><a href="test.html#test"><span>3</span></a></li>
</ul> </ul>
<div id="colon:test"></div> <div id="colon:test"></div>
<div style="height: 300px;" id="inline-style"></div> <div style="height: 300px;" id="inline-style"></div>

View File

@ -7,4 +7,33 @@ var el;
module("tabs: core"); module("tabs: core");
test('ajax', function() {
expect(4);
stop();
el = $('#tabs2');
el.tabs({
selected: 2,
load: function() {
// spinner: default spinner
equals($('li:eq(2) > a > span', el).length, 1, "should restore tab markup after spinner is removed");
equals($('li:eq(2) > a > span', el).html(), '3', "should restore tab label after spinner is removed");
el.tabs('destroy');
el.tabs({
selected: 2,
spinner: '<img src="spinner.gif" alt="">',
load: function() {
// spinner: image
equals($('li:eq(2) > a > span', el).length, 1, "should restore tab markup after spinner is removed");
equals($('li:eq(2) > a > span', el).html(), '3', "should restore tab label after spinner is removed");
start();
}
});
}
});
});
})(jQuery); })(jQuery);

View File

@ -12,7 +12,7 @@ var tabs_defaults = {
fx: null, fx: null,
idPrefix: 'ui-tabs-', idPrefix: 'ui-tabs-',
panelTemplate: '<div></div>', panelTemplate: '<div></div>',
spinner: 'Loading&#8230;', spinner: '<em>Loading&#8230;</em>',
tabTemplate: '<li><a href="#{href}"><span>#{label}</span></a></li>' tabTemplate: '<li><a href="#{href}"><span>#{label}</span></a></li>'
}; };

View File

@ -0,0 +1 @@
<p>&#8230;content loaded via Ajax.</p>

View File

@ -5,7 +5,7 @@
.ui-tabs .ui-tabs-nav li { position: relative; float: left; border-bottom-width: 0 !important; margin: 0 .2em -1px 0; padding: 0; } .ui-tabs .ui-tabs-nav li { position: relative; float: left; border-bottom-width: 0 !important; margin: 0 .2em -1px 0; padding: 0; }
.ui-tabs .ui-tabs-nav li a { float: left; text-decoration: none; padding: .5em 1em; } .ui-tabs .ui-tabs-nav li a { float: left; text-decoration: none; padding: .5em 1em; }
.ui-tabs .ui-tabs-nav li.ui-tabs-selected { padding-bottom: .1em; border-bottom-width: 0; } .ui-tabs .ui-tabs-nav li.ui-tabs-selected { padding-bottom: .1em; border-bottom-width: 0; }
.ui-tabs .ui-tabs-nav li.ui-tabs-selected a { cursor: text; } .ui-tabs .ui-tabs-nav li.ui-tabs-selected a, .ui-tabs .ui-tabs-nav li.ui-state-processing a { cursor: text; }
.ui-tabs .ui-tabs-nav li a, .ui-tabs.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-selected a { cursor: pointer; } /* first selector in group seems obsolete, but required to overcome bug in Opera applying cursor: text overall if defined elsewhere... */ .ui-tabs .ui-tabs-nav li a, .ui-tabs.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-selected a { cursor: pointer; } /* first selector in group seems obsolete, but required to overcome bug in Opera applying cursor: text overall if defined elsewhere... */
.ui-tabs .ui-tabs-panel { padding: 1em 1.4em; display: block; border-width: 0; background: none; } .ui-tabs .ui-tabs-panel { padding: 1em 1.4em; display: block; border-width: 0; background: none; }
.ui-tabs .ui-tabs-hide { display: none !important; } .ui-tabs .ui-tabs-hide { display: none !important; }

View File

@ -58,6 +58,16 @@ $.widget("ui.tabs", {
}; };
}, },
_cleanup: function() {
// restore all former loading tabs labels
this.$lis.filter('.ui-state-processing').removeClass('ui-state-processing')
.find('span:data(label.tabs)')
.each(function() {
var el = $(this);
el.html(el.data('label.tabs'));
});
},
_tabify: function(init) { _tabify: function(init) {
this.list = this.element.children('ul:first, ol:first').eq(0); this.list = this.element.children('ul:first, ol:first').eq(0);
@ -276,7 +286,7 @@ $.widget("ui.tabs", {
// for a disabled or loading tab! // for a disabled or loading tab!
if (($li.hasClass('ui-tabs-selected') && !o.collapsible) if (($li.hasClass('ui-tabs-selected') && !o.collapsible)
|| $li.hasClass('ui-state-disabled') || $li.hasClass('ui-state-disabled')
|| $(this).hasClass('ui-tabs-loading') || $li.hasClass('ui-state-processing')
|| self._trigger('select', null, self._ui(this, $show[0])) === false || self._trigger('select', null, self._ui(this, $show[0])) === false
) { ) {
this.blur(); this.blur();
@ -285,6 +295,8 @@ $.widget("ui.tabs", {
o.selected = self.$tabs.index(this); o.selected = self.$tabs.index(this);
self.abort();
// if tab may be closed TODO avoid redundant code in this block // if tab may be closed TODO avoid redundant code in this block
if (o.collapsible) { if (o.collapsible) {
if ($li.hasClass('ui-tabs-selected')) { if ($li.hasClass('ui-tabs-selected')) {
@ -292,13 +304,11 @@ $.widget("ui.tabs", {
if (o.cookie) self._cookie(o.selected, o.cookie); if (o.cookie) self._cookie(o.selected, o.cookie);
$li.removeClass('ui-tabs-selected ui-state-active') $li.removeClass('ui-tabs-selected ui-state-active')
.addClass('ui-state-default'); .addClass('ui-state-default');
self.$panels.stop();
hideTab(this, $hide); hideTab(this, $hide);
this.blur(); this.blur();
return false; return false;
} else if (!$hide.length) { } else if (!$hide.length) {
if (o.cookie) self._cookie(o.selected, o.cookie); if (o.cookie) self._cookie(o.selected, o.cookie);
self.$panels.stop();
var a = this; var a = this;
self.load(self.$tabs.index(this), function() { self.load(self.$tabs.index(this), function() {
$li.addClass('ui-tabs-selected ui-state-active') $li.addClass('ui-tabs-selected ui-state-active')
@ -312,9 +322,6 @@ $.widget("ui.tabs", {
if (o.cookie) self._cookie(o.selected, o.cookie); if (o.cookie) self._cookie(o.selected, o.cookie);
// stop possibly running animations
self.$panels.stop(false, true);
// show new tab // show new tab
if ($show.length) { if ($show.length) {
var a = this; var a = this;
@ -327,8 +334,10 @@ $.widget("ui.tabs", {
showTab(a, $show); showTab(a, $show);
} }
); );
} else }
else {
throw 'jQuery UI Tabs: Mismatching fragment identifier.'; throw 'jQuery UI Tabs: Mismatching fragment identifier.';
}
// Prevent IE from keeping other link focussed when using the back button // Prevent IE from keeping other link focussed when using the back button
// and remove dotted border from clicked link. This is controlled via CSS // and remove dotted border from clicked link. This is controlled via CSS
@ -487,8 +496,10 @@ $.widget("ui.tabs", {
load: function(index, callback) { // callback is for internal usage only load: function(index, callback) { // callback is for internal usage only
callback = callback || function() {}; callback = callback || function() {};
var self = this, o = this.options, $a = this.$tabs.eq(index), a = $a[0], var self = this, o = this.options, a = this.$tabs.eq(index)[0],
bypassCache = callback == undefined, url = $a.data('load.tabs'); bypassCache = callback == undefined, url = $.data(a, 'load.tabs');
this.abort();
// not remote or from cache - just finish with callback // not remote or from cache - just finish with callback
if (!url || !bypassCache && $.data(a, 'cache.tabs')) { if (!url || !bypassCache && $.data(a, 'cache.tabs')) {
@ -497,55 +508,53 @@ $.widget("ui.tabs", {
} }
// load remote from here on // load remote from here on
this.$lis.eq(index).addClass('ui-state-processing');
var inner = function(parent) {
var $parent = $(parent), $inner = $parent.find('*:last');
return $inner.length && $inner.is(':not(img)') && $inner || $parent;
};
var cleanup = function() {
self.$tabs.filter('.ui-tabs-loading').removeClass('ui-tabs-loading')
.each(function() {
if (o.spinner)
inner(this).parent().html(inner(this).data('label.tabs'));
});
self.xhr = null;
};
if (o.spinner) { if (o.spinner) {
var label = inner(a).html(); var span = $('span', a);
inner(a).wrapInner('<em></em>') span.data('label.tabs', span.html()).html(o.spinner);
.find('em').data('label.tabs', label).html(o.spinner);
} }
var ajaxOptions = $.extend({}, o.ajaxOptions, { this.xhr = $.ajax($.extend({}, o.ajaxOptions, {
url: url, url: url,
success: function(r, s) { success: function(r, s) {
$(self._sanitizeSelector(a.hash)).html(r); $(self._sanitizeSelector(a.hash)).html(r);
cleanup();
if (o.cache) // take care of tab labels
self._cleanup();
if (o.cache) {
$.data(a, 'cache.tabs', true); // if loaded once do not load them again $.data(a, 'cache.tabs', true); // if loaded once do not load them again
}
// callbacks // callbacks
self._trigger('load', null, self._ui(self.$tabs[index], self.$panels[index])); self._trigger('load', null, self._ui(self.$tabs[index], self.$panels[index]));
try { try {
o.ajaxOptions.success(r, s); o.ajaxOptions.success(r, s);
} }
catch (er) {} catch (e) {}
// This callback is required because the switch has to take // This callback is required because the switch has to take
// place after loading has completed. Call last in order to // place after loading has completed. Call last in order to
// fire load before show callback... // fire load before show callback...
callback(); callback();
} }
}); }));
},
abort: function() {
// stop possibly running animations
this.$panels.stop(false, true);
// terminate pending requests from other tabs
if (this.xhr) { if (this.xhr) {
// terminate pending requests from other tabs and restore tab label
this.xhr.abort(); this.xhr.abort();
cleanup(); delete this.xhr;
} }
$a.addClass('ui-tabs-loading');
self.xhr = $.ajax(ajaxOptions); // take care of tab labels
this._cleanup();
}, },
url: function(index, url) { url: function(index, url) {
@ -571,7 +580,7 @@ $.extend($.ui.tabs, {
fx: null, // e.g. { height: 'toggle', opacity: 'toggle', duration: 200 } fx: null, // e.g. { height: 'toggle', opacity: 'toggle', duration: 200 }
idPrefix: 'ui-tabs-', idPrefix: 'ui-tabs-',
panelTemplate: '<div></div>', panelTemplate: '<div></div>',
spinner: 'Loading&#8230;', spinner: '<em>Loading&#8230;</em>',
tabTemplate: '<li><a href="#{href}"><span>#{label}</span></a></li>' tabTemplate: '<li><a href="#{href}"><span>#{label}</span></a></li>'
} }
}); });