source: main/trunk/greenstone3/web/interfaces/oran/js/jquery-ui-1.8rc1/ui/jquery.ui.tabs.js@ 24245

Last change on this file since 24245 was 24245, checked in by sjb48, 13 years ago

Oran code for supporting format changes to document.

  • Property svn:executable set to *
File size: 19.5 KB
Line 
1/*
2 * jQuery UI Tabs 1.8rc1
3 *
4 * Copyright (c) 2010 AUTHORS.txt (http://jqueryui.com/about)
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 * jquery.ui.core.js
12 * jquery.ui.widget.js
13 */
14(function($) {
15
16var tabId = 0;
17
18$.widget("ui.tabs", {
19 options: {
20 add: null,
21 ajaxOptions: null,
22 cache: false,
23 cookie: null, // e.g. { expires: 7, path: '/', domain: 'jquery.com', secure: true }
24 collapsible: false,
25 disable: null,
26 disabled: [],
27 enable: null,
28 event: 'click',
29 fx: null, // e.g. { height: 'toggle', opacity: 'toggle', duration: 200 }
30 idPrefix: 'ui-tabs-',
31 load: null,
32 panelTemplate: '<div></div>',
33 remove: null,
34 select: null,
35 show: null,
36 spinner: '<em>Loading&#8230;</em>',
37 tabTemplate: '<li><a href="#{href}"><span>#{label}</span></a></li>'
38 },
39 _create: function() {
40 this._tabify(true);
41 },
42
43 _setOption: function(key, value) {
44 if (key == 'selected') {
45 if (this.options.collapsible && value == this.options.selected) {
46 return;
47 }
48 this.select(value);
49 }
50 else {
51 this.options[key] = value;
52 this._tabify();
53 }
54 },
55
56 _tabId: function(a) {
57 return a.title && a.title.replace(/\s/g, '_').replace(/[^A-Za-z0-9\-_:\.]/g, '') ||
58 this.options.idPrefix + (++tabId);
59 },
60
61 _sanitizeSelector: function(hash) {
62 return hash.replace(/:/g, '\\:'); // we need this because an id may contain a ":"
63 },
64
65 _cookie: function() {
66 var cookie = this.cookie || (this.cookie = this.options.cookie.name || 'ui-tabs-' + $.data(this.list[0]));
67 return $.cookie.apply(null, [cookie].concat($.makeArray(arguments)));
68 },
69
70 _ui: function(tab, panel) {
71 return {
72 tab: tab,
73 panel: panel,
74 index: this.anchors.index(tab)
75 };
76 },
77
78 _cleanup: function() {
79 // restore all former loading tabs labels
80 this.lis.filter('.ui-state-processing').removeClass('ui-state-processing')
81 .find('span:data(label.tabs)')
82 .each(function() {
83 var el = $(this);
84 el.html(el.data('label.tabs')).removeData('label.tabs');
85 });
86 },
87
88 _tabify: function(init) {
89
90 this.list = this.element.find('ol,ul').eq(0);
91 this.lis = $('li:has(a[href])', this.list);
92 this.anchors = this.lis.map(function() { return $('a', this)[0]; });
93 this.panels = $([]);
94
95 var self = this, o = this.options;
96
97 var fragmentId = /^#.+/; // Safari 2 reports '#' for an empty hash
98 this.anchors.each(function(i, a) {
99 var href = $(a).attr('href');
100
101 // For dynamically created HTML that contains a hash as href IE < 8 expands
102 // such href to the full page url with hash and then misinterprets tab as ajax.
103 // Same consideration applies for an added tab with a fragment identifier
104 // since a[href=#fragment-identifier] does unexpectedly not match.
105 // Thus normalize href attribute...
106 var hrefBase = href.split('#')[0], baseEl;
107 if (hrefBase && (hrefBase === location.toString().split('#')[0] ||
108 (baseEl = $('base')[0]) && hrefBase === baseEl.href)) {
109 href = a.hash;
110 a.href = href;
111 }
112
113 // inline tab
114 if (fragmentId.test(href)) {
115 self.panels = self.panels.add(self._sanitizeSelector(href));
116 }
117
118 // remote tab
119 else if (href != '#') { // prevent loading the page itself if href is just "#"
120 $.data(a, 'href.tabs', href); // required for restore on destroy
121
122 // TODO until #3808 is fixed strip fragment identifier from url
123 // (IE fails to load from such url)
124 $.data(a, 'load.tabs', href.replace(/#.*$/, '')); // mutable data
125
126 var id = self._tabId(a);
127 a.href = '#' + id;
128 var $panel = $('#' + id);
129 if (!$panel.length) {
130 $panel = $(o.panelTemplate).attr('id', id).addClass('ui-tabs-panel ui-widget-content ui-corner-bottom')
131 .insertAfter(self.panels[i - 1] || self.list);
132 $panel.data('destroy.tabs', true);
133 }
134 self.panels = self.panels.add($panel);
135 }
136
137 // invalid tab href
138 else {
139 o.disabled.push(i);
140 }
141 });
142
143 // initialization from scratch
144 if (init) {
145
146 // attach necessary classes for styling
147 this.element.addClass('ui-tabs ui-widget ui-widget-content ui-corner-all');
148 this.list.addClass('ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all');
149 this.lis.addClass('ui-state-default ui-corner-top');
150 this.panels.addClass('ui-tabs-panel ui-widget-content ui-corner-bottom');
151
152 // Selected tab
153 // use "selected" option or try to retrieve:
154 // 1. from fragment identifier in url
155 // 2. from cookie
156 // 3. from selected class attribute on <li>
157 if (o.selected === undefined) {
158 if (location.hash) {
159 this.anchors.each(function(i, a) {
160 if (a.hash == location.hash) {
161 o.selected = i;
162 return false; // break
163 }
164 });
165 }
166 if (typeof o.selected != 'number' && o.cookie) {
167 o.selected = parseInt(self._cookie(), 10);
168 }
169 if (typeof o.selected != 'number' && this.lis.filter('.ui-tabs-selected').length) {
170 o.selected = this.lis.index(this.lis.filter('.ui-tabs-selected'));
171 }
172 o.selected = o.selected || this.lis.length ? 0 : -1;
173 }
174 else if (o.selected === null) { // usage of null is deprecated, TODO remove in next release
175 o.selected = -1;
176 }
177
178 // sanity check - default to first tab...
179 o.selected = ((o.selected >= 0 && this.anchors[o.selected]) || o.selected < 0) ? o.selected : 0;
180
181 // Take disabling tabs via class attribute from HTML
182 // into account and update option properly.
183 // A selected tab cannot become disabled.
184 o.disabled = $.unique(o.disabled.concat(
185 $.map(this.lis.filter('.ui-state-disabled'),
186 function(n, i) { return self.lis.index(n); } )
187 )).sort();
188
189 if ($.inArray(o.selected, o.disabled) != -1) {
190 o.disabled.splice($.inArray(o.selected, o.disabled), 1);
191 }
192
193 // highlight selected tab
194 this.panels.addClass('ui-tabs-hide');
195 this.lis.removeClass('ui-tabs-selected ui-state-active');
196 if (o.selected >= 0 && this.anchors.length) { // check for length avoids error when initializing empty list
197 this.panels.eq(o.selected).removeClass('ui-tabs-hide');
198 this.lis.eq(o.selected).addClass('ui-tabs-selected ui-state-active');
199
200 // seems to be expected behavior that the show callback is fired
201 self.element.queue("tabs", function() {
202 self._trigger('show', null, self._ui(self.anchors[o.selected], self.panels[o.selected]));
203 });
204
205 this.load(o.selected);
206 }
207
208 // clean up to avoid memory leaks in certain versions of IE 6
209 $(window).bind('unload', function() {
210 self.lis.add(self.anchors).unbind('.tabs');
211 self.lis = self.anchors = self.panels = null;
212 });
213
214 }
215 // update selected after add/remove
216 else {
217 o.selected = this.lis.index(this.lis.filter('.ui-tabs-selected'));
218 }
219
220 // update collapsible
221 this.element[o.collapsible ? 'addClass' : 'removeClass']('ui-tabs-collapsible');
222
223 // set or update cookie after init and add/remove respectively
224 if (o.cookie) {
225 this._cookie(o.selected, o.cookie);
226 }
227
228 // disable tabs
229 for (var i = 0, li; (li = this.lis[i]); i++) {
230 $(li)[$.inArray(i, o.disabled) != -1 &&
231 !$(li).hasClass('ui-tabs-selected') ? 'addClass' : 'removeClass']('ui-state-disabled');
232 }
233
234 // reset cache if switching from cached to not cached
235 if (o.cache === false) {
236 this.anchors.removeData('cache.tabs');
237 }
238
239 // remove all handlers before, tabify may run on existing tabs after add or option change
240 this.lis.add(this.anchors).unbind('.tabs');
241
242 if (o.event != 'mouseover') {
243 var addState = function(state, el) {
244 if (el.is(':not(.ui-state-disabled)')) {
245 el.addClass('ui-state-' + state);
246 }
247 };
248 var removeState = function(state, el) {
249 el.removeClass('ui-state-' + state);
250 };
251 this.lis.bind('mouseover.tabs', function() {
252 addState('hover', $(this));
253 });
254 this.lis.bind('mouseout.tabs', function() {
255 removeState('hover', $(this));
256 });
257 this.anchors.bind('focus.tabs', function() {
258 addState('focus', $(this).closest('li'));
259 });
260 this.anchors.bind('blur.tabs', function() {
261 removeState('focus', $(this).closest('li'));
262 });
263 }
264
265 // set up animations
266 var hideFx, showFx;
267 if (o.fx) {
268 if ($.isArray(o.fx)) {
269 hideFx = o.fx[0];
270 showFx = o.fx[1];
271 }
272 else {
273 hideFx = showFx = o.fx;
274 }
275 }
276
277 // Reset certain styles left over from animation
278 // and prevent IE's ClearType bug...
279 function resetStyle($el, fx) {
280 $el.css({ display: '' });
281 if (!$.support.opacity && fx.opacity) {
282 $el[0].style.removeAttribute('filter');
283 }
284 }
285
286 // Show a tab...
287 var showTab = showFx ?
288 function(clicked, $show) {
289 $(clicked).closest('li').addClass('ui-tabs-selected ui-state-active');
290 $show.hide().removeClass('ui-tabs-hide') // avoid flicker that way
291 .animate(showFx, showFx.duration || 'normal', function() {
292 resetStyle($show, showFx);
293 self._trigger('show', null, self._ui(clicked, $show[0]));
294 });
295 } :
296 function(clicked, $show) {
297 $(clicked).closest('li').addClass('ui-tabs-selected ui-state-active');
298 $show.removeClass('ui-tabs-hide');
299 self._trigger('show', null, self._ui(clicked, $show[0]));
300 };
301
302 // Hide a tab, $show is optional...
303 var hideTab = hideFx ?
304 function(clicked, $hide) {
305 $hide.animate(hideFx, hideFx.duration || 'normal', function() {
306 self.lis.removeClass('ui-tabs-selected ui-state-active');
307 $hide.addClass('ui-tabs-hide');
308 resetStyle($hide, hideFx);
309 self.element.dequeue("tabs");
310 });
311 } :
312 function(clicked, $hide, $show) {
313 self.lis.removeClass('ui-tabs-selected ui-state-active');
314 $hide.addClass('ui-tabs-hide');
315 self.element.dequeue("tabs");
316 };
317
318 // attach tab event handler, unbind to avoid duplicates from former tabifying...
319 this.anchors.bind(o.event + '.tabs', function() {
320 var el = this, $li = $(this).closest('li'), $hide = self.panels.filter(':not(.ui-tabs-hide)'),
321 $show = $(self._sanitizeSelector(this.hash));
322
323 // If tab is already selected and not collapsible or tab disabled or
324 // or is already loading or click callback returns false stop here.
325 // Check if click handler returns false last so that it is not executed
326 // for a disabled or loading tab!
327 if (($li.hasClass('ui-tabs-selected') && !o.collapsible) ||
328 $li.hasClass('ui-state-disabled') ||
329 $li.hasClass('ui-state-processing') ||
330 self._trigger('select', null, self._ui(this, $show[0])) === false) {
331 this.blur();
332 return false;
333 }
334
335 o.selected = self.anchors.index(this);
336
337 self.abort();
338
339 // if tab may be closed
340 if (o.collapsible) {
341 if ($li.hasClass('ui-tabs-selected')) {
342 o.selected = -1;
343
344 if (o.cookie) {
345 self._cookie(o.selected, o.cookie);
346 }
347
348 self.element.queue("tabs", function() {
349 hideTab(el, $hide);
350 }).dequeue("tabs");
351
352 this.blur();
353 return false;
354 }
355 else if (!$hide.length) {
356 if (o.cookie) {
357 self._cookie(o.selected, o.cookie);
358 }
359
360 self.element.queue("tabs", function() {
361 showTab(el, $show);
362 });
363
364 self.load(self.anchors.index(this)); // TODO make passing in node possible, see also http://dev.jqueryui.com/ticket/3171
365
366 this.blur();
367 return false;
368 }
369 }
370
371 if (o.cookie) {
372 self._cookie(o.selected, o.cookie);
373 }
374
375 // show new tab
376 if ($show.length) {
377 if ($hide.length) {
378 self.element.queue("tabs", function() {
379 hideTab(el, $hide);
380 });
381 }
382 self.element.queue("tabs", function() {
383 showTab(el, $show);
384 });
385
386 self.load(self.anchors.index(this));
387 }
388 else {
389 throw 'jQuery UI Tabs: Mismatching fragment identifier.';
390 }
391
392 // Prevent IE from keeping other link focussed when using the back button
393 // and remove dotted border from clicked link. This is controlled via CSS
394 // in modern browsers; blur() removes focus from address bar in Firefox
395 // which can become a usability and annoying problem with tabs('rotate').
396 if ($.browser.msie) {
397 this.blur();
398 }
399
400 });
401
402 // disable click in any case
403 this.anchors.bind('click.tabs', function(){return false;});
404
405 },
406
407 destroy: function() {
408 var o = this.options;
409
410 this.abort();
411
412 this.element.unbind('.tabs')
413 .removeClass('ui-tabs ui-widget ui-widget-content ui-corner-all ui-tabs-collapsible')
414 .removeData('tabs');
415
416 this.list.removeClass('ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all');
417
418 this.anchors.each(function() {
419 var href = $.data(this, 'href.tabs');
420 if (href) {
421 this.href = href;
422 }
423 var $this = $(this).unbind('.tabs');
424 $.each(['href', 'load', 'cache'], function(i, prefix) {
425 $this.removeData(prefix + '.tabs');
426 });
427 });
428
429 this.lis.unbind('.tabs').add(this.panels).each(function() {
430 if ($.data(this, 'destroy.tabs')) {
431 $(this).remove();
432 }
433 else {
434 $(this).removeClass([
435 'ui-state-default',
436 'ui-corner-top',
437 'ui-tabs-selected',
438 'ui-state-active',
439 'ui-state-hover',
440 'ui-state-focus',
441 'ui-state-disabled',
442 'ui-tabs-panel',
443 'ui-widget-content',
444 'ui-corner-bottom',
445 'ui-tabs-hide'
446 ].join(' '));
447 }
448 });
449
450 if (o.cookie) {
451 this._cookie(null, o.cookie);
452 }
453
454 return this;
455 },
456
457 add: function(url, label, index) {
458 if (index === undefined) {
459 index = this.anchors.length; // append by default
460 }
461
462 var self = this, o = this.options,
463 $li = $(o.tabTemplate.replace(/#\{href\}/g, url).replace(/#\{label\}/g, label)),
464 id = !url.indexOf('#') ? url.replace('#', '') : this._tabId($('a', $li)[0]);
465
466 $li.addClass('ui-state-default ui-corner-top').data('destroy.tabs', true);
467
468 // try to find an existing element before creating a new one
469 var $panel = $('#' + id);
470 if (!$panel.length) {
471 $panel = $(o.panelTemplate).attr('id', id).data('destroy.tabs', true);
472 }
473 $panel.addClass('ui-tabs-panel ui-widget-content ui-corner-bottom ui-tabs-hide');
474
475 if (index >= this.lis.length) {
476 $li.appendTo(this.list);
477 $panel.appendTo(this.list[0].parentNode);
478 }
479 else {
480 $li.insertBefore(this.lis[index]);
481 $panel.insertBefore(this.panels[index]);
482 }
483
484 o.disabled = $.map(o.disabled,
485 function(n, i) { return n >= index ? ++n : n; });
486
487 this._tabify();
488
489 if (this.anchors.length == 1) { // after tabify
490 o.selected = 0;
491 $li.addClass('ui-tabs-selected ui-state-active');
492 $panel.removeClass('ui-tabs-hide');
493 this.element.queue("tabs", function() {
494 self._trigger('show', null, self._ui(self.anchors[0], self.panels[0]));
495 });
496
497 this.load(0);
498 }
499
500 // callback
501 this._trigger('add', null, this._ui(this.anchors[index], this.panels[index]));
502 return this;
503 },
504
505 remove: function(index) {
506 var o = this.options, $li = this.lis.eq(index).remove(),
507 $panel = this.panels.eq(index).remove();
508
509 // If selected tab was removed focus tab to the right or
510 // in case the last tab was removed the tab to the left.
511 if ($li.hasClass('ui-tabs-selected') && this.anchors.length > 1) {
512 this.select(index + (index + 1 < this.anchors.length ? 1 : -1));
513 }
514
515 o.disabled = $.map($.grep(o.disabled, function(n, i) { return n != index; }),
516 function(n, i) { return n >= index ? --n : n; });
517
518 this._tabify();
519
520 // callback
521 this._trigger('remove', null, this._ui($li.find('a')[0], $panel[0]));
522 return this;
523 },
524
525 enable: function(index) {
526 var o = this.options;
527 if ($.inArray(index, o.disabled) == -1) {
528 return;
529 }
530
531 this.lis.eq(index).removeClass('ui-state-disabled');
532 o.disabled = $.grep(o.disabled, function(n, i) { return n != index; });
533
534 // callback
535 this._trigger('enable', null, this._ui(this.anchors[index], this.panels[index]));
536 return this;
537 },
538
539 disable: function(index) {
540 var self = this, o = this.options;
541 if (index != o.selected) { // cannot disable already selected tab
542 this.lis.eq(index).addClass('ui-state-disabled');
543
544 o.disabled.push(index);
545 o.disabled.sort();
546
547 // callback
548 this._trigger('disable', null, this._ui(this.anchors[index], this.panels[index]));
549 }
550
551 return this;
552 },
553
554 select: function(index) {
555 if (typeof index == 'string') {
556 index = this.anchors.index(this.anchors.filter('[href$=' + index + ']'));
557 }
558 else if (index === null) { // usage of null is deprecated, TODO remove in next release
559 index = -1;
560 }
561 if (index == -1 && this.options.collapsible) {
562 index = this.options.selected;
563 }
564
565 this.anchors.eq(index).trigger(this.options.event + '.tabs');
566 return this;
567 },
568
569 load: function(index) {
570 var self = this, o = this.options, a = this.anchors.eq(index)[0], url = $.data(a, 'load.tabs');
571
572 this.abort();
573
574 // not remote or from cache
575 if (!url || this.element.queue("tabs").length !== 0 && $.data(a, 'cache.tabs')) {
576 this.element.dequeue("tabs");
577 return;
578 }
579
580 // load remote from here on
581 this.lis.eq(index).addClass('ui-state-processing');
582
583 if (o.spinner) {
584 var span = $('span', a);
585 span.data('label.tabs', span.html()).html(o.spinner);
586 }
587
588 this.xhr = $.ajax($.extend({}, o.ajaxOptions, {
589 url: url,
590 success: function(r, s) {
591 $(self._sanitizeSelector(a.hash)).html(r);
592
593 // take care of tab labels
594 self._cleanup();
595
596 if (o.cache) {
597 $.data(a, 'cache.tabs', true); // if loaded once do not load them again
598 }
599
600 // callbacks
601 self._trigger('load', null, self._ui(self.anchors[index], self.panels[index]));
602 try {
603 o.ajaxOptions.success(r, s);
604 }
605 catch (e) {}
606
607 // last, so that load event is fired before show...
608 self.element.dequeue("tabs");
609 }
610 }));
611
612 return this;
613 },
614
615 abort: function() {
616 // stop possibly running animations
617 this.element.queue([]);
618 this.panels.stop(false, true);
619
620 // "tabs" queue must not contain more than two elements,
621 // which are the callbacks for the latest clicked tab...
622 this.element.queue("tabs", this.element.queue("tabs").splice(-2, 2));
623
624 // terminate pending requests from other tabs
625 if (this.xhr) {
626 this.xhr.abort();
627 delete this.xhr;
628 }
629
630 // take care of tab labels
631 this._cleanup();
632 return this;
633 },
634
635 url: function(index, url) {
636 this.anchors.eq(index).removeData('cache.tabs').data('load.tabs', url);
637 return this;
638 },
639
640 length: function() {
641 return this.anchors.length;
642 }
643
644});
645
646$.extend($.ui.tabs, {
647 version: '1.8rc1'
648});
649
650/*
651 * Tabs Extensions
652 */
653
654/*
655 * Rotate
656 */
657$.extend($.ui.tabs.prototype, {
658 rotation: null,
659 rotate: function(ms, continuing) {
660
661 var self = this, o = this.options;
662
663 var rotate = self._rotate || (self._rotate = function(e) {
664 clearTimeout(self.rotation);
665 self.rotation = setTimeout(function() {
666 var t = o.selected;
667 self.select( ++t < self.anchors.length ? t : 0 );
668 }, ms);
669
670 if (e) {
671 e.stopPropagation();
672 }
673 });
674
675 var stop = self._unrotate || (self._unrotate = !continuing ?
676 function(e) {
677 if (e.clientX) { // in case of a true click
678 self.rotate(null);
679 }
680 } :
681 function(e) {
682 t = o.selected;
683 rotate();
684 });
685
686 // start rotation
687 if (ms) {
688 this.element.bind('tabsshow', rotate);
689 this.anchors.bind(o.event + '.tabs', stop);
690 rotate();
691 }
692 // stop rotation
693 else {
694 clearTimeout(self.rotation);
695 this.element.unbind('tabsshow', rotate);
696 this.anchors.unbind(o.event + '.tabs', stop);
697 delete this._rotate;
698 delete this._unrotate;
699 }
700
701 return this;
702 }
703});
704
705})(jQuery);
Note: See TracBrowser for help on using the repository browser.