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

Last change on this file since 4918 was 4918, checked in by nikrou, 14 years ago

Feature 1442 : update jquery

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