mirror of
https://github.com/jquery/jquery-ui.git
synced 2024-11-21 11:04:24 +00:00
Tabs: solved spinner issues and ajax loading not being stopped when selecting a static page tab, fixes #4109, #3929
This commit is contained in:
parent
c748a543c4
commit
07809340c8
BIN
tests/unit/tabs/spinner.gif
Normal file
BIN
tests/unit/tabs/spinner.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.5 KiB |
@ -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>
|
||||||
|
@ -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);
|
||||||
|
@ -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…',
|
spinner: '<em>Loading…</em>',
|
||||||
tabTemplate: '<li><a href="#{href}"><span>#{label}</span></a></li>'
|
tabTemplate: '<li><a href="#{href}"><span>#{label}</span></a></li>'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
1
tests/unit/tabs/test.html
Normal file
1
tests/unit/tabs/test.html
Normal file
@ -0,0 +1 @@
|
|||||||
|
<p>…content loaded via Ajax.</p>
|
@ -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; }
|
||||||
|
@ -24,14 +24,14 @@ $.widget("ui.tabs", {
|
|||||||
if (key == 'selected') {
|
if (key == 'selected') {
|
||||||
if (this.options.collapsible
|
if (this.options.collapsible
|
||||||
&& value == this.options.selected) return;
|
&& value == this.options.selected) return;
|
||||||
|
|
||||||
this.select(value);
|
this.select(value);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
this.options[key] = value;
|
this.options[key] = value;
|
||||||
if (key == 'deselectable')
|
if (key == 'deselectable')
|
||||||
this.options.collapsible = value;
|
this.options.collapsible = value;
|
||||||
|
|
||||||
this._tabify();
|
this._tabify();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -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);
|
||||||
@ -70,7 +80,7 @@ $.widget("ui.tabs", {
|
|||||||
var fragmentId = /^#.+/; // Safari 2 reports '#' for an empty hash
|
var fragmentId = /^#.+/; // Safari 2 reports '#' for an empty hash
|
||||||
this.$tabs.each(function(i, a) {
|
this.$tabs.each(function(i, a) {
|
||||||
var href = $(a).attr('href');
|
var href = $(a).attr('href');
|
||||||
|
|
||||||
// For dynamically created HTML that contains a hash as href IE expands
|
// For dynamically created HTML that contains a hash as href IE expands
|
||||||
// such href to the full page url with hash and then misinterprets tab as ajax...
|
// such href to the full page url with hash and then misinterprets tab as ajax...
|
||||||
if (href.split('#')[0] == location.toString().split('#')[0]) href = a.hash;
|
if (href.split('#')[0] == location.toString().split('#')[0]) href = a.hash;
|
||||||
@ -183,10 +193,10 @@ $.widget("ui.tabs", {
|
|||||||
else {
|
else {
|
||||||
o.selected = this.$lis.index(this.$lis.filter('.ui-tabs-selected'));
|
o.selected = this.$lis.index(this.$lis.filter('.ui-tabs-selected'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// update collapsible
|
// update collapsible
|
||||||
this.element[o.collapsible ? 'addClass' : 'removeClass']('ui-tabs-collapsible');
|
this.element[o.collapsible ? 'addClass' : 'removeClass']('ui-tabs-collapsible');
|
||||||
|
|
||||||
// set or update cookie after init and add/remove respectively
|
// set or update cookie after init and add/remove respectively
|
||||||
if (o.cookie) this._cookie(o.selected, o.cookie);
|
if (o.cookie) this._cookie(o.selected, o.cookie);
|
||||||
|
|
||||||
@ -199,7 +209,7 @@ $.widget("ui.tabs", {
|
|||||||
|
|
||||||
// remove all handlers before, tabify may run on existing tabs after add or option change
|
// remove all handlers before, tabify may run on existing tabs after add or option change
|
||||||
this.$lis.add(this.$tabs).unbind('.tabs');
|
this.$lis.add(this.$tabs).unbind('.tabs');
|
||||||
|
|
||||||
if (o.event != 'mouseover') {
|
if (o.event != 'mouseover') {
|
||||||
var handleState = function(state, el) {
|
var handleState = function(state, el) {
|
||||||
if (el.is(':not(.ui-state-disabled)')) el.toggleClass('ui-state-' + state);
|
if (el.is(':not(.ui-state-disabled)')) el.toggleClass('ui-state-' + state);
|
||||||
@ -211,7 +221,7 @@ $.widget("ui.tabs", {
|
|||||||
handleState('focus', $(this).closest('li'));
|
handleState('focus', $(this).closest('li'));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// set up animations
|
// set up animations
|
||||||
var hideFx, showFx;
|
var hideFx, showFx;
|
||||||
if (o.fx) {
|
if (o.fx) {
|
||||||
@ -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
|
||||||
@ -474,21 +483,23 @@ $.widget("ui.tabs", {
|
|||||||
select: function(index) {
|
select: function(index) {
|
||||||
if (typeof index == 'string')
|
if (typeof index == 'string')
|
||||||
index = this.$tabs.index(this.$tabs.filter('[href$=' + index + ']'));
|
index = this.$tabs.index(this.$tabs.filter('[href$=' + index + ']'));
|
||||||
|
|
||||||
else if (index === null)
|
else if (index === null)
|
||||||
index = -1;
|
index = -1;
|
||||||
|
|
||||||
if (index == -1 && this.options.collapsible)
|
if (index == -1 && this.options.collapsible)
|
||||||
index = this.options.selected;
|
index = this.options.selected;
|
||||||
|
|
||||||
this.$tabs.eq(index).trigger(this.options.event + '.tabs');
|
this.$tabs.eq(index).trigger(this.options.event + '.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…',
|
spinner: '<em>Loading…</em>',
|
||||||
tabTemplate: '<li><a href="#{href}"><span>#{label}</span></a></li>'
|
tabTemplate: '<li><a href="#{href}"><span>#{label}</span></a></li>'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user