Bug 20489 Configure illegal file characters https://bugzilla.wikimedia.org/show_bug...
[mediawiki.git] / js2 / mwEmbed / jquery / jquery.ui / ui / ui.tabs.js
bloba73dbc9a454d1bb2012e3704faaefadd3f075c5e
1 /*
2  * jQuery UI Tabs 1.7.1
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($) {
15 $.widget("ui.tabs", {
17         _init: function() {
18                 if (this.options.deselectable !== undefined) {
19                         this.options.collapsible = this.options.deselectable;
20                 }
21                 this._tabify(true);
22         },
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         },
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         },
45         _sanitizeSelector: function(hash) {
46                 return hash.replace(/:/g, '\\:'); // we need this because an id may contain a ":"
47         },
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         },
54         _ui: function(tab, panel) {
55                 return {
56                         tab: tab,
57                         panel: panel,
58                         index: this.anchors.index(tab)
59                 };
60         },
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         },
72         _tabify: function(init) {
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 = $([]);
79                 var self = this, o = this.options;
81                 var fragmentId = /^#.+/; // Safari 2 reports '#' for an empty hash
82                 this.anchors.each(function(i, a) {
83                         var href = $(a).attr('href');
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                         }
97                         // inline tab
98                         if (fragmentId.test(href)) {
99                                 self.panels = self.panels.add(self._sanitizeSelector(href));
100                         }
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
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
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                         }
121                         // invalid tab href
122                         else {
123                                 o.disabled.push(i);
124                         }
125                 });
127                 // initialization from scratch
128                 if (init) {
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');
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                         }
162                         // sanity check - default to first tab...
163                         o.selected = ((o.selected >= 0 && this.anchors[o.selected]) || o.selected < 0) ? o.selected : 0;
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();
173                         if ($.inArray(o.selected, o.disabled) != -1) {
174                                 o.disabled.splice($.inArray(o.selected, o.disabled), 1);
175                         }
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');
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                         }
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                         });
198                 }
199                 // update selected after add/remove
200                 else {
201                         o.selected = this.lis.index(this.lis.filter('.ui-tabs-selected'));
202                 }
204                 // update collapsible
205                 this.element[o.collapsible ? 'addClass' : 'removeClass']('ui-tabs-collapsible');
207                 // set or update cookie after init and add/remove respectively
208                 if (o.cookie) {
209                         this._cookie(o.selected, o.cookie);
210                 }
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                 }
218                 // reset cache if switching from cached to not cached
219                 if (o.cache === false) {
220                         this.anchors.removeData('cache.tabs');
221                 }
223                 // remove all handlers before, tabify may run on existing tabs after add or option change
224                 this.lis.add(this.anchors).unbind('.tabs');
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                 }
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                 }
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                 }
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                         };
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                         };
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));
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                         }
319                         o.selected = self.anchors.index(this);
321                         self.abort();
323                         // if tab may be closed
324                         if (o.collapsible) {
325                                 if ($li.hasClass('ui-tabs-selected')) {
326                                         o.selected = -1;
328                                         if (o.cookie) {
329                                                 self._cookie(o.selected, o.cookie);
330                                         }
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                                         });
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                         }
355                         if (o.cookie) {
356                                 self._cookie(o.selected, o.cookie);
357                         }
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                         }
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                         }
384                 });
386                 // disable click in any case
387                 this.anchors.bind('click.tabs', function(){return false;});
389         },
391         destroy: function() {
392                 var o = this.options;
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');
400                 this.list.removeClass('ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all');
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                 });
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                 });
434                 if (o.cookie) {
435                         this._cookie(null, o.cookie);
436                 }
437         },
439         add: function(url, label, index) {
440                 if (index === undefined) {
441                         index = this.anchors.length; // append by default
442                 }
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]);
448                 $li.addClass('ui-state-default ui-corner-top').data('destroy.tabs', true);
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');
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                 }
466                 o.disabled = $.map(o.disabled,
467                         function(n, i) { return n >= index ? ++n : n; });
469                 this._tabify();
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                 }
481                 // callback
482                 this._trigger('add', null, this._ui(this.anchors[index], this.panels[index]));
483         },
485         remove: function(index) {
486                 var o = this.options, $li = this.lis.eq(index).remove(),
487                         $panel = this.panels.eq(index).remove();
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                 }
495                 o.disabled = $.map($.grep(o.disabled, function(n, i) { return n != index; }),
496                         function(n, i) { return n >= index ? --n : n; });
498                 this._tabify();
500                 // callback
501                 this._trigger('remove', null, this._ui($li.find('a')[0], $panel[0]));
502         },
504         enable: function(index) {
505                 var o = this.options;
506                 if ($.inArray(index, o.disabled) == -1) {
507                         return;
508                 }
510                 this.lis.eq(index).removeClass('ui-state-disabled');
511                 o.disabled = $.grep(o.disabled, function(n, i) { return n != index; });
513                 // callback
514                 this._trigger('enable', null, this._ui(this.anchors[index], this.panels[index]));
515         },
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');
522                         o.disabled.push(index);
523                         o.disabled.sort();
525                         // callback
526                         this._trigger('disable', null, this._ui(this.anchors[index], this.panels[index]));
527                 }
528         },
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                 }
541                 this.anchors.eq(index).trigger(this.options.event + '.tabs');
542         },
544         load: function(index) {
545                 var self = this, o = this.options, a = this.anchors.eq(index)[0], url = $.data(a, 'load.tabs');
547                 this.abort();
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                 }
555                 // load remote from here on
556                 this.lis.eq(index).addClass('ui-state-processing');
558                 if (o.spinner) {
559                         var span = $('span', a);
560                         span.data('label.tabs', span.html()).html(o.spinner);
561                 }
563                 this.xhr = $.ajax($.extend({}, o.ajaxOptions, {
564                         url: url,
565                         success: function(r, s) {
566                                 $(self._sanitizeSelector(a.hash)).html(r);
568                                 // take care of tab labels
569                                 self._cleanup();
571                                 if (o.cache) {
572                                         $.data(a, 'cache.tabs', true); // if loaded once do not load them again
573                                 }
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) {}
582                                 // last, so that load event is fired before show...
583                                 self.element.dequeue("tabs");
584                         }
585                 }));
586         },
588         abort: function() {
589                 // stop possibly running animations
590                 this.element.queue([]);
591                 this.panels.stop(false, true);
593                 // terminate pending requests from other tabs
594                 if (this.xhr) {
595                         this.xhr.abort();
596                         delete this.xhr;
597                 }
599                 // take care of tab labels
600                 this._cleanup();
602         },
604         url: function(index, url) {
605                 this.anchors.eq(index).removeData('cache.tabs').data('load.tabs', url);
606         },
608         length: function() {
609                 return this.anchors.length;
610         }
614 $.extend($.ui.tabs, {
615         version: '1.7.1',
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         }
633  * Tabs Extensions
634  */
637  * Rotate
638  */
639 $.extend($.ui.tabs.prototype, {
640         rotation: null,
641         rotate: function(ms, continuing) {
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                         });
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         }
685 })(jQuery);