e3c2c51966d71c44c6aa5d66e3717daf917c647c
[lhc/web/wiklou.git] / skins / vector / collapsibleTabs.js
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 );
13
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 } );
26
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;
50
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 }
60
61 leftEnd = $leftTab.offset().left + $leftTab.width();
62 rightStart = $rightTab.offset().left;
63
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 );
109
110 if ( data.shifting ) {
111 return;
112 }
113
114 // if the two navigations are colliding
115 if ( $el.children( data.collapsible ).length > 0 && data.collapseCondition() ) {
116
117 $el.trigger( 'beforeTabCollapse' );
118 // move the element to the dropdown menu
119 $.collapsibleTabs.moveToCollapsed( $el.children( data.collapsible + ':last' ) );
120 }
121
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 );
137
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;
147
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 );
174
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;
184
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 };
209
210 }( jQuery ) );