Vector: Add the collapsibleTabs script from the Vector extension
authorlfs <schenonef@gmail.com>
Sun, 24 Mar 2013 23:10:35 +0000 (20:10 -0300)
committerTimo Tijhof <krinklemail@gmail.com>
Wed, 19 Jun 2013 02:09:28 +0000 (19:09 -0700)
* Move jquery.collapsibleTabs from the Vector extension
  to the Vector skin (see 097f387362)
* Register the module and its dependencies
* Merge the init script into the existing vector.js

Bug: 46513
Change-Id: I7bd77e6e4c384c9e2033f9d05ffcae86b0bb7a69

RELEASE-NOTES-1.22
resources/Resources.php
skins/vector/collapsibleTabs.js [new file with mode: 0644]
skins/vector/vector.js

index e98ed8e..bd7066c 100644 (file)
@@ -111,6 +111,7 @@ production.
 ** editmyuserjs controls whether a user may edit their own JS subpages.
 * Add new hook AbortTalkPageEmailNotification, this will be used to determine
   whether to send the regular talk page email notification
+* (bug 46513) Vector: Add the collapsibleTabs script from the Vector extension.
 
 === Bug fixes in 1.22 ===
 * Disable Special:PasswordReset when $wgEnableEmail is false. Previously one
index 347884a..4e85c1e 100644 (file)
@@ -99,7 +99,12 @@ return array(
                'localBasePath' => $GLOBALS['wgStyleDirectory'],
        ),
        'skins.vector.js' => array(
-               'scripts' => 'vector/vector.js',
+               'scripts' => array(
+                       'vector/vector.js',
+                       'vector/collapsibleTabs.js',
+               ),
+               'position' => 'top',
+               'dependencies' => 'jquery.delayedBind',
                'remoteBasePath' => $GLOBALS['wgStylePath'],
                'localBasePath' => $GLOBALS['wgStyleDirectory'],
        ),
diff --git a/skins/vector/collapsibleTabs.js b/skins/vector/collapsibleTabs.js
new file mode 100644 (file)
index 0000000..ad77c36
--- /dev/null
@@ -0,0 +1,210 @@
+/**
+ * Collapsible tabs jQuery Plugin
+ */
+( function ( $ ) {
+       var rtl = $( 'body' ).is( '.rtl' );
+       $.fn.collapsibleTabs = function ( options ) {
+               // return if the function is called on an empty jquery object
+               if ( !this.length ) {
+                       return this;
+               }
+               // Merge options into the defaults
+               var $settings = $.extend( {}, $.collapsibleTabs.defaults, options );
+
+               this.each( function () {
+                       var $el = $( this );
+                       // add the element to our array of collapsible managers
+                       $.collapsibleTabs.instances = ( $.collapsibleTabs.instances.length === 0 ?
+                               $el : $.collapsibleTabs.instances.add( $el ) );
+                       // attach the settings to the elements
+                       $el.data( 'collapsibleTabsSettings', $settings );
+                       // attach data to our collapsible elements
+                       $el.children( $settings.collapsible ).each( function () {
+                               $.collapsibleTabs.addData( $( this ) );
+                       } );
+               } );
+
+               // if we haven't already bound our resize hanlder, bind it now
+               if ( !$.collapsibleTabs.boundEvent ) {
+                       $( window )
+                               .delayedBind( '500', 'resize', function ( ) {
+                                       $.collapsibleTabs.handleResize();
+                               } );
+               }
+               // call our resize handler to setup the page
+               $.collapsibleTabs.handleResize();
+               return this;
+       };
+       /**
+        * Returns the amount of horizontal distance between the two tabs groups
+        * (#left-navigation and #right-navigation), in pixels. If negative, this
+        * means that the tabs overlap, and the value is the width of overlapping
+        * parts.
+        *
+        * Used in default expandCondition and collapseCondition.
+        *
+        * @return {Numeric} distance/overlap in pixels
+        */
+       function calculateTabDistance() {
+               var $leftTab, $rightTab, leftEnd, rightStart;
+
+               // In RTL, #right-navigation is actually on the left and vice versa.
+               // Hooray for descriptive naming.
+               if ( !rtl ) {
+                       $leftTab = $( '#left-navigation' );
+                       $rightTab = $( '#right-navigation' );
+               } else {
+                       $leftTab = $( '#right-navigation' );
+                       $rightTab = $( '#left-navigation' );
+               }
+
+               leftEnd = $leftTab.offset().left + $leftTab.width();
+               rightStart = $rightTab.offset().left;
+
+               return rightStart - leftEnd;
+       }
+       $.collapsibleTabs = {
+               instances: [],
+               boundEvent: null,
+               defaults: {
+                       expandedContainer: '#p-views ul',
+                       collapsedContainer: '#p-cactions ul',
+                       collapsible: 'li.collapsible',
+                       shifting: false,
+                       expandCondition: function ( eleWidth ) {
+                               // If there's at least eleWidth pixels free space, expand.
+                               return calculateTabDistance() >= eleWidth;
+                       },
+                       collapseCondition: function () {
+                               // If there's an overlap, collapse.
+                               return calculateTabDistance() < 0;
+                       }
+               },
+               addData: function ( $collapsible ) {
+                       var $settings = $collapsible.parent().data( 'collapsibleTabsSettings' );
+                       if ( $settings !== null ) {
+                               $collapsible.data( 'collapsibleTabsSettings', {
+                                       expandedContainer: $settings.expandedContainer,
+                                       collapsedContainer: $settings.collapsedContainer,
+                                       expandedWidth: $collapsible.width(),
+                                       prevElement: $collapsible.prev()
+                               } );
+                       }
+               },
+               getSettings: function ( $collapsible ) {
+                       var $settings = $collapsible.data( 'collapsibleTabsSettings' );
+                       if ( $settings === undefined ) {
+                               $.collapsibleTabs.addData( $collapsible );
+                               $settings = $collapsible.data( 'collapsibleTabsSettings' );
+                       }
+                       return $settings;
+               },
+               /**
+                * @param {jQuery.Event} e
+                */
+               handleResize: function () {
+                       $.collapsibleTabs.instances.each( function () {
+                               var $el = $( this ),
+                                       data = $.collapsibleTabs.getSettings( $el );
+
+                               if ( data.shifting ) {
+                                       return;
+                               }
+
+                               // if the two navigations are colliding
+                               if ( $el.children( data.collapsible ).length > 0 && data.collapseCondition() ) {
+
+                                       $el.trigger( 'beforeTabCollapse' );
+                                       // move the element to the dropdown menu
+                                       $.collapsibleTabs.moveToCollapsed( $el.children( data.collapsible + ':last' ) );
+                               }
+
+                               // if there are still moveable items in the dropdown menu,
+                               // and there is sufficient space to place them in the tab container
+                               if ( $( data.collapsedContainer + ' ' + data.collapsible ).length > 0 &&
+                                               data.expandCondition( $.collapsibleTabs.getSettings( $( data.collapsedContainer ).children(
+                                                               data.collapsible + ':first' ) ).expandedWidth ) ) {
+                                       //move the element from the dropdown to the tab
+                                       $el.trigger( 'beforeTabExpand' );
+                                       $.collapsibleTabs
+                                               .moveToExpanded( data.collapsedContainer + ' ' + data.collapsible + ':first' );
+                               }
+                       });
+               },
+               moveToCollapsed: function ( ele ) {
+                       var data, expContainerSettings, target,
+                               $moving = $( ele );
+
+                       data = $.collapsibleTabs.getSettings( $moving );
+                       if ( !data ) {
+                               return;
+                       }
+                       expContainerSettings = $.collapsibleTabs.getSettings( $( data.expandedContainer ) );
+                       if ( !expContainerSettings ) {
+                               return;
+                       }
+                       expContainerSettings.shifting = true;
+
+                       // Remove the element from where it's at and put it in the dropdown menu
+                       target = data.collapsedContainer;
+                       $moving.css( 'position', 'relative' )
+                               .css( ( rtl ? 'left' : 'right' ), 0 )
+                               .animate( { width: '1px' }, 'normal', function () {
+                                       var data, expContainerSettings;
+                                       $( this ).hide();
+                                       // add the placeholder
+                                       $( '<span class="placeholder" style="display: none;"></span>' ).insertAfter( this );
+                                       // XXX: 'data' is undefined here, should the 'data' from the outer scope have
+                                       // a different name?
+                                       $( this ).detach().prependTo( target ).data( 'collapsibleTabsSettings', data );
+                                       $( this ).attr( 'style', 'display: list-item;' );
+                                       data = $.collapsibleTabs.getSettings( $( ele ) );
+                                       if ( data ) {
+                                               expContainerSettings = $.collapsibleTabs.getSettings( $( data.expandedContainer ) );
+                                               if ( expContainerSettings ) {
+                                                       expContainerSettings.shifting = false;
+                                                       $.collapsibleTabs.handleResize();
+                                               }
+                                       }
+                               } );
+               },
+               moveToExpanded: function ( ele ) {
+                       var data, expContainerSettings, $target, expandedWidth,
+                               $moving = $( ele );
+
+                       data = $.collapsibleTabs.getSettings( $moving );
+                       if ( !data ) {
+                               return;
+                       }
+                       expContainerSettings = $.collapsibleTabs.getSettings( $( data.expandedContainer ) );
+                       if ( !expContainerSettings ) {
+                               return;
+                       }
+                       expContainerSettings.shifting = true;
+
+                       // grab the next appearing placeholder so we can use it for replacing
+                       $target = $( data.expandedContainer ).find( 'span.placeholder:first' );
+                       expandedWidth = data.expandedWidth;
+                       $moving.css( 'position', 'relative' ).css( ( rtl ? 'right' : 'left' ), 0 ).css( 'width', '1px' );
+                       $target.replaceWith(
+                               $moving
+                               .detach()
+                               .css( 'width', '1px' )
+                               .data( 'collapsibleTabsSettings', data )
+                               .animate( { width: expandedWidth + 'px' }, 'normal', function () {
+                                       $( this ).attr( 'style', 'display: block;' );
+                                       var data, expContainerSettings;
+                                       data = $.collapsibleTabs.getSettings( $( this ) );
+                                       if ( data ) {
+                                               expContainerSettings = $.collapsibleTabs.getSettings( $( data.expandedContainer ) );
+                                               if ( expContainerSettings ) {
+                                                       expContainerSettings.shifting = false;
+                                                       $.collapsibleTabs.handleResize();
+                                               }
+                                       }
+                               } )
+                       );
+               }
+       };
+
+}( jQuery ) );
index 4427d9a..fb1f212 100644 (file)
@@ -18,4 +18,32 @@ jQuery( function ( $ ) {
                                $el.removeClass( 'vectorMenuFocus' );
                        } );
        } );
+
+       /**
+        * Collapsible tabs for Vector
+        */
+       var $cactions = $( '#p-cactions' );
+
+       // Bind callback functions to animate our drop down menu in and out
+       // and then call the collapsibleTabs function on the menu
+       $( '#p-views ul' )
+               .bind( 'beforeTabCollapse', function () {
+                       // If the dropdown was hidden, show it
+                       if ( $cactions.hasClass( 'emptyPortlet' ) ) {
+                               $cactions
+                                       .removeClass( 'emptyPortlet' )
+                                       .find( 'h3, h5' )
+                                               .css( 'width', '1px' ).animate( { 'width': '24px' }, 390 );
+                       }
+               } )
+               .bind( 'beforeTabExpand', function () {
+                       // If we're removing the last child node right now, hide the dropdown
+                       if ( $cactions.find( 'li' ).length === 1 ) {
+                               $cactions.find( 'h3, h5' ).animate( { 'width': '1px' }, 390, function () {
+                                       $( this ).attr( 'style', '' )
+                                               .parent().addClass( 'emptyPortlet' );
+                               });
+                       }
+               } )
+               .collapsibleTabs();
 } );