Merge "Whitelist the <wbr> element."
[mediawiki.git] / skins / vector / collapsibleTabs.js
blobeb84325a5fe1d7c851f45d83b0c934070321bf38
1 /**
2  * Collapsible tabs jQuery Plugin
3  */
4 ( function ( $ ) {
5         var rtl = $( 'html' ).attr( 'dir' ) === 'rtl';
6         $.fn.collapsibleTabs = function ( options ) {
7                 // return if the function is called on an empty jquery object
8                 if ( !this.length ) {
9                         return this;
10                 }
11                 // Merge options into the defaults
12                 var $settings = $.extend( {}, $.collapsibleTabs.defaults, options );
14                 this.each( function () {
15                         var $el = $( this );
16                         // add the element to our array of collapsible managers
17                         $.collapsibleTabs.instances = ( $.collapsibleTabs.instances.length === 0 ?
18                                 $el : $.collapsibleTabs.instances.add( $el ) );
19                         // attach the settings to the elements
20                         $el.data( 'collapsibleTabsSettings', $settings );
21                         // attach data to our collapsible elements
22                         $el.children( $settings.collapsible ).each( function () {
23                                 $.collapsibleTabs.addData( $( this ) );
24                         } );
25                 } );
27                 // if we haven't already bound our resize hanlder, bind it now
28                 if ( !$.collapsibleTabs.boundEvent ) {
29                         $( window )
30                                 .delayedBind( '500', 'resize', function ( ) {
31                                         $.collapsibleTabs.handleResize();
32                                 } );
33                 }
34                 // call our resize handler to setup the page
35                 $.collapsibleTabs.handleResize();
36                 return this;
37         };
38         /**
39          * Returns the amount of horizontal distance between the two tabs groups
40          * (#left-navigation and #right-navigation), in pixels. If negative, this
41          * means that the tabs overlap, and the value is the width of overlapping
42          * parts.
43          *
44          * Used in default expandCondition and collapseCondition.
45          *
46          * @return {Numeric} distance/overlap in pixels
47          */
48         function calculateTabDistance() {
49                 var $leftTab, $rightTab, leftEnd, rightStart;
51                 // In RTL, #right-navigation is actually on the left and vice versa.
52                 // Hooray for descriptive naming.
53                 if ( !rtl ) {
54                         $leftTab = $( '#left-navigation' );
55                         $rightTab = $( '#right-navigation' );
56                 } else {
57                         $leftTab = $( '#right-navigation' );
58                         $rightTab = $( '#left-navigation' );
59                 }
61                 leftEnd = $leftTab.offset().left + $leftTab.width();
62                 rightStart = $rightTab.offset().left;
64                 return rightStart - leftEnd;
65         }
66         $.collapsibleTabs = {
67                 instances: [],
68                 boundEvent: null,
69                 defaults: {
70                         expandedContainer: '#p-views ul',
71                         collapsedContainer: '#p-cactions ul',
72                         collapsible: 'li.collapsible',
73                         shifting: false,
74                         expandCondition: function ( eleWidth ) {
75                                 // If there's at least eleWidth pixels free space, expand.
76                                 return calculateTabDistance() >= eleWidth;
77                         },
78                         collapseCondition: function () {
79                                 // If there's an overlap, collapse.
80                                 return calculateTabDistance() < 0;
81                         }
82                 },
83                 addData: function ( $collapsible ) {
84                         var $settings = $collapsible.parent().data( 'collapsibleTabsSettings' );
85                         if ( $settings !== null ) {
86                                 $collapsible.data( 'collapsibleTabsSettings', {
87                                         expandedContainer: $settings.expandedContainer,
88                                         collapsedContainer: $settings.collapsedContainer,
89                                         expandedWidth: $collapsible.width(),
90                                         prevElement: $collapsible.prev()
91                                 } );
92                         }
93                 },
94                 getSettings: function ( $collapsible ) {
95                         var $settings = $collapsible.data( 'collapsibleTabsSettings' );
96                         if ( $settings === undefined ) {
97                                 $.collapsibleTabs.addData( $collapsible );
98                                 $settings = $collapsible.data( 'collapsibleTabsSettings' );
99                         }
100                         return $settings;
101                 },
102                 /**
103                  * @param {jQuery.Event} e
104                  */
105                 handleResize: function () {
106                         $.collapsibleTabs.instances.each( function () {
107                                 var $el = $( this ),
108                                         data = $.collapsibleTabs.getSettings( $el );
110                                 if ( data.shifting ) {
111                                         return;
112                                 }
114                                 // if the two navigations are colliding
115                                 if ( $el.children( data.collapsible ).length > 0 && data.collapseCondition() ) {
117                                         $el.trigger( 'beforeTabCollapse' );
118                                         // move the element to the dropdown menu
119                                         $.collapsibleTabs.moveToCollapsed( $el.children( data.collapsible + ':last' ) );
120                                 }
122                                 // if there are still moveable items in the dropdown menu,
123                                 // and there is sufficient space to place them in the tab container
124                                 if ( $( data.collapsedContainer + ' ' + data.collapsible ).length > 0 &&
125                                                 data.expandCondition( $.collapsibleTabs.getSettings( $( data.collapsedContainer ).children(
126                                                                 data.collapsible + ':first' ) ).expandedWidth ) ) {
127                                         //move the element from the dropdown to the tab
128                                         $el.trigger( 'beforeTabExpand' );
129                                         $.collapsibleTabs
130                                                 .moveToExpanded( data.collapsedContainer + ' ' + data.collapsible + ':first' );
131                                 }
132                         });
133                 },
134                 moveToCollapsed: function ( ele ) {
135                         var data, expContainerSettings, target,
136                                 $moving = $( ele );
138                         data = $.collapsibleTabs.getSettings( $moving );
139                         if ( !data ) {
140                                 return;
141                         }
142                         expContainerSettings = $.collapsibleTabs.getSettings( $( data.expandedContainer ) );
143                         if ( !expContainerSettings ) {
144                                 return;
145                         }
146                         expContainerSettings.shifting = true;
148                         // Remove the element from where it's at and put it in the dropdown menu
149                         target = data.collapsedContainer;
150                         $moving.css( 'position', 'relative' )
151                                 .css( ( rtl ? 'left' : 'right' ), 0 )
152                                 .animate( { width: '1px' }, 'normal', function () {
153                                         var data, expContainerSettings;
154                                         $( this ).hide();
155                                         // add the placeholder
156                                         $( '<span class="placeholder" style="display: none;"></span>' ).insertAfter( this );
157                                         // XXX: 'data' is undefined here, should the 'data' from the outer scope have
158                                         // a different name?
159                                         $( this ).detach().prependTo( target ).data( 'collapsibleTabsSettings', data );
160                                         $( this ).attr( 'style', 'display: list-item;' );
161                                         data = $.collapsibleTabs.getSettings( $( ele ) );
162                                         if ( data ) {
163                                                 expContainerSettings = $.collapsibleTabs.getSettings( $( data.expandedContainer ) );
164                                                 if ( expContainerSettings ) {
165                                                         expContainerSettings.shifting = false;
166                                                         $.collapsibleTabs.handleResize();
167                                                 }
168                                         }
169                                 } );
170                 },
171                 moveToExpanded: function ( ele ) {
172                         var data, expContainerSettings, $target, expandedWidth,
173                                 $moving = $( ele );
175                         data = $.collapsibleTabs.getSettings( $moving );
176                         if ( !data ) {
177                                 return;
178                         }
179                         expContainerSettings = $.collapsibleTabs.getSettings( $( data.expandedContainer ) );
180                         if ( !expContainerSettings ) {
181                                 return;
182                         }
183                         expContainerSettings.shifting = true;
185                         // grab the next appearing placeholder so we can use it for replacing
186                         $target = $( data.expandedContainer ).find( 'span.placeholder:first' );
187                         expandedWidth = data.expandedWidth;
188                         $moving.css( 'position', 'relative' ).css( ( rtl ? 'right' : 'left' ), 0 ).css( 'width', '1px' );
189                         $target.replaceWith(
190                                 $moving
191                                 .detach()
192                                 .css( 'width', '1px' )
193                                 .data( 'collapsibleTabsSettings', data )
194                                 .animate( { width: expandedWidth + 'px' }, 'normal', function () {
195                                         $( this ).attr( 'style', 'display: block;' );
196                                         var data, expContainerSettings;
197                                         data = $.collapsibleTabs.getSettings( $( this ) );
198                                         if ( data ) {
199                                                 expContainerSettings = $.collapsibleTabs.getSettings( $( data.expandedContainer ) );
200                                                 if ( expContainerSettings ) {
201                                                         expContainerSettings.shifting = false;
202                                                         $.collapsibleTabs.handleResize();
203                                                 }
204                                         }
205                                 } )
206                         );
207                 }
208         };
210 }( jQuery ) );