source: trunk/template-common/lib/ui/ui.tabs.js @ 3102

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