UI Tabs: fixed "colon in id bug", wrote more unit tests and refactored stuff afterwards

This commit is contained in:
Klaus Hartl 2008-10-02 14:20:35 +00:00
parent de7d22a02e
commit c2b0afd3e7
3 changed files with 184 additions and 26 deletions

View File

@ -11,13 +11,14 @@
<script type="text/javascript" src="qunit/testrunner.js"></script> <script type="text/javascript" src="qunit/testrunner.js"></script>
<script type="text/javascript" src="simulate/jquery.simulate.js"></script> <script type="text/javascript" src="simulate/jquery.simulate.js"></script>
<script type="text/javascript" src="plugins/cookie/jquery.cookie.js"></script>
<script type="text/javascript" src="tabs.js"></script> <script type="text/javascript" src="tabs.js"></script>
<style type="text/css"> <style type="text/css">
.ui-tabs-hide {
display: none !important;
}
</style> </style>
@ -32,6 +33,7 @@
<ol id="tests"></ol> <ol id="tests"></ol>
<div id="main" style="position:absolute;top:-20000px"> <div id="main" style="position:absolute;top:-20000px">
<div id="tabs1"> <div id="tabs1">
<ul> <ul>
<li><a href="#fragment-1"><span>One</span></a></li> <li><a href="#fragment-1"><span>One</span></a></li>
@ -51,6 +53,20 @@
Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat.
</div> </div>
</div> </div>
<div id="tabs2">
<ul>
<li><a href="#colon:test"><span>One</span></a></li>
<li><a href="#inline-style"><span>Two</span></a></li>
</ul>
<div id="colon:test">
Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat.
</div>
<div style="height: 300px;" id="inline-style">
Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat.
</div>
</div>
</div> </div>
</body> </body>

View File

@ -8,14 +8,145 @@
// Tabs Tests // Tabs Tests
module("tabs"); module('tabs');
test("init", function() { test('init', function() {
expect(1); expect(4);
el = $("#tabs1").tabs(); var el = $('#tabs1 > ul').tabs();
ok(true, '.tabs() called on element'); ok(true, '.tabs() called on element');
el.tabs('destroy').tabs({ selected: 1 });
equals( el.data('selected.tabs'), 1 );
equals( $('li', el).index( $('li.ui-tabs-selected', el) ), 1, 'second tab active');
equals( $('div', '#tabs1').index( $('div.ui-tabs-hide', '#tabs1') ), 0, 'first panel should be hidden' );
}); });
test('destroy', function() {
expect(0);
});
test("defaults", function() {
var expected = {
unselect: false,
event: 'click',
disabled: [],
cookie: null,
spinner: 'Loading&#8230;',
cache: false,
idPrefix: 'ui-tabs-',
ajaxOptions: null,
fx: null,
tabTemplate: '<li><a href="#{href}"><span>#{label}</span></a></li>',
panelTemplate: '<div></div>',
navClass: 'ui-tabs-nav',
selectedClass: 'ui-tabs-selected',
unselectClass: 'ui-tabs-unselect',
disabledClass: 'ui-tabs-disabled',
panelClass: 'ui-tabs-panel',
hideClass: 'ui-tabs-hide',
loadingClass: 'ui-tabs-loading'
};
var el = $("#tabs1").tabs();
for (var optionName in expected) {
var actual = el.data(optionName + '.tabs'), expects = expected[optionName];
if (optionName == 'disabled')
ok(expects.constructor == Array && !expects.length, optionName);
else
equals(actual, expects, optionName);
}
});
test('add', function() {
expect(0);
});
test('remove', function() {
expect(0);
});
test('enable', function() {
expect(0);
});
test('disable', function() {
expect(0);
});
test('select', function() {
expect(0);
});
test('load', function() {
expect(0);
});
test('url', function() {
expect(0);
});
test('cookie', function() {
expect(5);
var el = $('#tabs1 > ul');
var cookieName = 'ui-tabs-' + $.data(el[0]);
$.cookie(cookieName, null); // blank state
var cookie = function() {
return parseInt($.cookie(cookieName), 10);
};
el.tabs({ cookie: {} });
equals(cookie(), 0, 'initial cookie value, no cookie given');
el.tabs('destroy');
el.tabs({ selected: 1, cookie: {} });
equals(cookie(), 1, 'initial cookie value, given selected');
el.tabs('select', 2);
equals(cookie(), 2, 'cookie value after tabs select');
el.tabs('destroy');
$.cookie(cookieName, 1);
el.tabs({ cookie: {} });
equals(cookie(), 1, 'initial cookie value, from existing cookie');
el.tabs('destroy');
ok($.cookie(cookieName) === null, 'erase cookie after destroy');
});
// #???
test('id containing colon', function() {
expect(4);
var el = $('#tabs2 > ul').tabs();
ok( $('div.ui-tabs-panel:eq(0)', '#tabs2').is(':visible'), 'first panel should be visible' );
ok( $('div.ui-tabs-panel:eq(1)', '#tabs2').is(':hidden'), 'second panel should be hidden' );
el.tabs('select', 1).tabs('select', 0);
ok( $('div.ui-tabs-panel:eq(0)', '#tabs2').is(':visible'), 'first panel should be visible' );
ok( $('div.ui-tabs-panel:eq(1)', '#tabs2').is(':hidden'), 'second panel should be hidden' );
});
// test('', function() {
// expect(0);
//
// });
})(jQuery); })(jQuery);

View File

@ -14,8 +14,6 @@
$.widget("ui.tabs", { $.widget("ui.tabs", {
_init: function() { _init: function() {
this.options.event += '.tabs'; // namespace event
// create tabs // create tabs
this._tabify(true); this._tabify(true);
}, },
@ -42,6 +40,13 @@ $.widget("ui.tabs", {
index: this.$tabs.index(tab) index: this.$tabs.index(tab)
}; };
}, },
_sanitizeSelector: function(hash) {
return hash.replace(/:/g, '\\:'); // we need this because an id may contain a ":"
},
_cookie: function() {
var cookie = this.cookie || (this.cookie = 'ui-tabs-' + $.data(this.element[0]));
return $.cookie.apply(null, [cookie].concat($.makeArray(arguments)));
},
_tabify: function(init) { _tabify: function(init) {
this.$lis = $('li:has(a[href])', this.element); this.$lis = $('li:has(a[href])', this.element);
@ -53,7 +58,7 @@ $.widget("ui.tabs", {
this.$tabs.each(function(i, a) { this.$tabs.each(function(i, a) {
// inline tab // inline tab
if (a.hash && a.hash.replace('#', '')) // Safari 2 reports '#' for an empty hash if (a.hash && a.hash.replace('#', '')) // Safari 2 reports '#' for an empty hash
self.$panels = self.$panels.add(a.hash); self.$panels = self.$panels.add(self._sanitizeSelector(a.hash));
// remote tab // remote tab
else if ($(a).attr('href') != '#') { // prevent loading the page itself if href is just "#" else if ($(a).attr('href') != '#') { // prevent loading the page itself if href is just "#"
$.data(a, 'href.tabs', a.href); // required for restore on destroy $.data(a, 'href.tabs', a.href); // required for restore on destroy
@ -94,8 +99,8 @@ $.widget("ui.tabs", {
if (a.hash == location.hash) { if (a.hash == location.hash) {
o.selected = i; o.selected = i;
// prevent page scroll to fragment // prevent page scroll to fragment
if ($.browser.msie || $.browser.opera) { // && !o.remote if ($.browser.msie || $.browser.opera) {
var $toShow = $(location.hash), toShowId = $toShow.attr('id'); var $toShow = $(this._sanitizeSelector(location.hash)), toShowId = $toShow.attr('id');
$toShow.attr('id', ''); $toShow.attr('id', '');
setTimeout(function() { setTimeout(function() {
$toShow.attr('id', toShowId); // restore id $toShow.attr('id', toShowId); // restore id
@ -107,7 +112,7 @@ $.widget("ui.tabs", {
}); });
} }
else if (o.cookie) { else if (o.cookie) {
var index = parseInt($.cookie('ui-tabs-' + $.data(self.element[0])), 10); var index = parseInt(self._cookie(), 10);
if (index && self.$tabs[index]) if (index && self.$tabs[index])
o.selected = index; o.selected = index;
} }
@ -160,7 +165,7 @@ $.widget("ui.tabs", {
// set or update cookie after init and add/remove respectively // set or update cookie after init and add/remove respectively
if (o.cookie) if (o.cookie)
$.cookie('ui-tabs-' + $.data(self.element[0]), o.selected, o.cookie); this._cookie(o.selected, o.cookie);
// disable tabs // disable tabs
for (var i = 0, li; li = this.$lis[i]; i++) for (var i = 0, li; li = this.$lis[i]; i++)
@ -220,12 +225,12 @@ $.widget("ui.tabs", {
} }
// attach tab event handler, unbind to avoid duplicates from former tabifying... // attach tab event handler, unbind to avoid duplicates from former tabifying...
this.$tabs.unbind('.tabs').bind(o.event, function() { this.$tabs.unbind('.tabs').bind(o.event + '.tabs', function() {
//var trueClick = e.clientX; // add to history only if true click occured, not a triggered click //var trueClick = e.clientX; // add to history only if true click occured, not a triggered click
var $li = $(this).parents('li:eq(0)'), var $li = $(this).parents('li:eq(0)'),
$hide = self.$panels.filter(':visible'), $hide = self.$panels.filter(':visible'),
$show = $(this.hash); $show = $(self._sanitizeSelector(this.hash));
// If tab is already selected and not unselectable or tab disabled or // If tab is already selected and not unselectable or tab disabled or
// or is already loading or click callback returns false stop here. // or is already loading or click callback returns false stop here.
@ -240,7 +245,7 @@ $.widget("ui.tabs", {
return false; return false;
} }
self.options.selected = self.$tabs.index(this); o.selected = self.$tabs.index(this);
// if tab may be closed // if tab may be closed
if (o.unselect) { if (o.unselect) {
@ -264,7 +269,7 @@ $.widget("ui.tabs", {
} }
if (o.cookie) if (o.cookie)
$.cookie('ui-tabs-' + $.data(self.element[0]), self.options.selected, o.cookie); self._cookie(o.selected, o.cookie);
// stop possibly running animations // stop possibly running animations
self.$panels.stop(); self.$panels.stop();
@ -315,7 +320,7 @@ $.widget("ui.tabs", {
}); });
// disable click if event is configured to something else // disable click if event is configured to something else
if (!(/^click/).test(o.event)) if (o.event != 'click')
this.$tabs.bind('click.tabs', function() { return false; }); this.$tabs.bind('click.tabs', function() { return false; });
}, },
@ -409,9 +414,10 @@ $.widget("ui.tabs", {
} }
}, },
select: function(index) { select: function(index) {
// TODO make null as argument work
if (typeof index == 'string') if (typeof index == 'string')
index = this.$tabs.index( this.$tabs.filter('[href$=' + index + ']')[0] ); index = this.$tabs.index( this.$tabs.filter('[href$=' + index + ']')[0] );
this.$tabs.eq(index).trigger(this.options.event); 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
@ -450,7 +456,7 @@ $.widget("ui.tabs", {
var ajaxOptions = $.extend({}, o.ajaxOptions, { var ajaxOptions = $.extend({}, o.ajaxOptions, {
url: url, url: url,
success: function(r, s) { success: function(r, s) {
$(a.hash).html(r); $(self._sanitizeSelector(a.hash)).html(r);
cleanup(); cleanup();
if (o.cache) if (o.cache)
@ -458,7 +464,10 @@ $.widget("ui.tabs", {
// 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]));
o.ajaxOptions.success && o.ajaxOptions.success(r, s); try {
o.ajaxOptions.success(r, s);
}
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
@ -500,6 +509,8 @@ $.widget("ui.tabs", {
$(this).removeClass([o.selectedClass, o.unselectClass, $(this).removeClass([o.selectedClass, o.unselectClass,
o.disabledClass, o.panelClass, o.hideClass].join(' ')); o.disabledClass, o.panelClass, o.hideClass].join(' '));
}); });
if (o.cookie)
this._cookie(null, o.cookie);
} }
}); });
@ -515,7 +526,7 @@ $.ui.tabs.defaults = {
spinner: 'Loading&#8230;', spinner: 'Loading&#8230;',
cache: false, cache: false,
idPrefix: 'ui-tabs-', idPrefix: 'ui-tabs-',
ajaxOptions: {}, ajaxOptions: null,
// animations // animations
fx: null, // e.g. { height: 'toggle', opacity: 'toggle', duration: 200 } fx: null, // e.g. { height: 'toggle', opacity: 'toggle', duration: 200 }
@ -568,9 +579,9 @@ $.extend($.ui.tabs.prototype, {
if (ms) { if (ms) {
start(); start();
if (!continuing) if (!continuing)
this.$tabs.bind(this.options.event, stop); this.$tabs.bind(this.options.event + '.tabs', stop);
else else
this.$tabs.bind(this.options.event, function() { this.$tabs.bind(this.options.event + '.tabs', function() {
stop(); stop();
t = self.options.selected; t = self.options.selected;
start(); start();
@ -579,7 +590,7 @@ $.extend($.ui.tabs.prototype, {
// stop interval // stop interval
else { else {
stop(); stop();
this.$tabs.unbind(this.options.event, stop); this.$tabs.unbind(this.options.event + '.tabs', stop);
} }
} }
}); });