collapsibleTabs: Clean up and simplify code

* Rename 'rtl' to 'isRTL' and use DOM to compute its value.
* Document code with JSDuck similar to other extensions.
* Remove unused 'prevElement' property. Not used in Vector
  nor anywhere else in Wikimedia Git.
* Make 'boundEvent' property private. Not used anywhere in
  Wikimedia Git outside this file.

* Simplify 'instances' tracking.
  The jQuery object stored in this property wasn't used beyond
  calling each(). Convert to a plain array.
  Preserve and re-use the jQuery object first created
  by collapsibleTabs().

* Simplify calculateTabDistance() by using getElementById() and
  getBoundingClientRect(). Its support includes IE 5.
  The "new" version (since Firefox 3.5 and IE 9) also includes
  'height' and 'width' properties and is supported in all
  browsers supported by the MediaWiki 1.28 startup feature test.

  This helps avoid code in offset() and width(), which is fairly
  expensive in jQuery 1.x.

* moveToCollapsed()
  - Remove redundant jQuery object creation (only caller passes a
    jQuery object already).
  - Remove redundant re-receiving of expContainerSettings inside
    the animate() callback. Already in the main scope.
    Relates to a bunch of patches that work around a problem
    caused by use of remove() instead of detach() in an earlier
    version of the code. Which was only a problem because the
    other settings object was also not used from the main scope.
    (See pre-Gerrit commits 1f93310e and e7900807.)

Change-Id: I48d542580d767df2d17ce4c6668e9e233a0f7902
This commit is contained in:
Timo Tijhof 2016-11-28 15:14:33 -08:00 committed by Krinkle
parent 9da3112abb
commit 1f26153a5d
4 changed files with 74 additions and 57 deletions

7
.gitignore vendored
View File

@ -18,11 +18,10 @@ sublime-*
sftp-config.json
# Building & testing
node_modules/
# Composer
/vendor
/composer.lock
/docs
/node_modules
/vendor
# Operating systems
## Mac OS X

View File

@ -1,10 +1,31 @@
/**
* Collapsible tabs jQuery Plugin
* Collapsible Tabs for the Vector skin.
*
* @class jQuery.plugin.collapsibleTabs
*/
( function ( $ ) {
var rtl = $( 'html' ).attr( 'dir' ) === 'rtl',
var isRTL = document.documentElement.dir === 'rtl',
boundEvent = false,
rAF = window.requestAnimationFrame || setTimeout;
/**
* @event beforeTabCollapse
*/
/**
* @event afterTabCollapse
*/
/**
* @param {Object} [options]
* @param {string} [options.expandedContainer="#p-views ul"] List of tabs
* @param {string} [options.collapsedContainer="#p-cactions ul"] List of menu items
* @param {string} [options.collapsible="li.collapsible"] Match tabs that are collapsible
* @param {Function} [options.expandCondition]
* @param {Function} [options.collapseCondition]
* @return {jQuery}
* @chainable
*/
$.fn.collapsibleTabs = function ( options ) {
// Merge options into the defaults
var settings = $.extend( {}, $.collapsibleTabs.defaults, options );
@ -17,7 +38,7 @@
this.each( function () {
var $el = $( this );
// add the element to our array of collapsible managers
$.collapsibleTabs.instances = $.collapsibleTabs.instances.add( $el );
$.collapsibleTabs.instances.push( $el );
// attach the settings to the elements
$el.data( 'collapsibleTabsSettings', settings );
// attach data to our collapsible elements
@ -27,11 +48,11 @@
} );
// if we haven't already bound our resize handler, bind it now
if ( !$.collapsibleTabs.boundEvent ) {
if ( !boundEvent ) {
boundEvent = true;
$( window ).on( 'resize', $.debounce( 100, function () {
rAF( $.collapsibleTabs.handleResize );
} ) );
$.collapsibleTabs.boundEvent = true;
}
// call our resize handler to setup the page
@ -39,8 +60,7 @@
return this;
};
$.collapsibleTabs = {
instances: $( [] ),
boundEvent: null,
instances: [],
defaults: {
expandedContainer: '#p-views ul',
collapsedContainer: '#p-cactions ul',
@ -62,8 +82,7 @@
$collapsible.data( 'collapsibleTabsSettings', {
expandedContainer: settings.expandedContainer,
collapsedContainer: settings.collapsedContainer,
expandedWidth: $collapsible.width(),
prevElement: $collapsible.prev()
expandedWidth: $collapsible.width()
} );
}
},
@ -76,17 +95,14 @@
return settings;
},
handleResize: function () {
$.collapsibleTabs.instances.each( function () {
var $el = $( this ),
data = $.collapsibleTabs.getSettings( $el );
$.each( $.collapsibleTabs.instances, function ( i, $el ) {
var data = $.collapsibleTabs.getSettings( $el );
if ( data.shifting ) {
return;
}
// if the two navigations are colliding
if ( $el.children( data.collapsible ).length > 0 && data.collapseCondition() ) {
if ( $el.children( data.collapsible ).length && data.collapseCondition() ) {
$el.trigger( 'beforeTabCollapse' );
// move the element to the dropdown menu
$.collapsibleTabs.moveToCollapsed( $el.children( data.collapsible + ':last' ) );
@ -94,7 +110,7 @@
// 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 &&
if ( $( data.collapsedContainer + ' ' + data.collapsible ).length &&
data.expandCondition( $.collapsibleTabs.getSettings( $( data.collapsedContainer ).children(
data.collapsible + ':first' ) ).expandedWidth ) ) {
// move the element from the dropdown to the tab
@ -104,9 +120,8 @@
}
} );
},
moveToCollapsed: function ( ele ) {
var outerData, expContainerSettings, target,
$moving = $( ele );
moveToCollapsed: function ( $moving ) {
var outerData, expContainerSettings, target;
outerData = $.collapsibleTabs.getSettings( $moving );
if ( !outerData ) {
@ -121,22 +136,15 @@
// Remove the element from where it's at and put it in the dropdown menu
target = outerData.collapsedContainer;
$moving.css( 'position', 'relative' )
.css( ( rtl ? 'left' : 'right' ), 0 )
.css( ( isRTL ? '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 );
$( this ).detach().prependTo( target ).data( 'collapsibleTabsSettings', outerData );
$( this ).attr( 'style', 'display: list-item;' );
data = $.collapsibleTabs.getSettings( $( ele ) );
if ( data ) {
expContainerSettings = $.collapsibleTabs.getSettings( $( data.expandedContainer ) );
if ( expContainerSettings ) {
expContainerSettings.shifting = false;
rAF( $.collapsibleTabs.handleResize );
}
}
expContainerSettings.shifting = false;
rAF( $.collapsibleTabs.handleResize );
} );
},
moveToExpanded: function ( ele ) {
@ -156,54 +164,52 @@
// 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' );
$moving.css( 'position', 'relative' ).css( ( isRTL ? 'right' : 'left' ), 0 ).css( 'width', '1px' );
$target.replaceWith(
$moving
.detach()
.css( 'width', '1px' )
.data( 'collapsibleTabsSettings', data )
.animate( { width: expandedWidth + 'px' }, 'normal', function () {
var data, expContainerSettings;
$( this ).attr( 'style', 'display: block;' );
data = $.collapsibleTabs.getSettings( $( this ) );
if ( data ) {
expContainerSettings = $.collapsibleTabs.getSettings( $( data.expandedContainer ) );
if ( expContainerSettings ) {
expContainerSettings.shifting = false;
rAF( $.collapsibleTabs.handleResize );
}
}
expContainerSettings.shifting = false;
rAF( $.collapsibleTabs.handleResize );
} )
);
},
/**
* Returns the amount of horizontal distance between the two tabs groups
* (#left-navigation and #right-navigation), in pixels. If negative, this
* Get the amount of horizontal distance between the two tabs groups in pixels.
*
* Uses `#left-navigation` and `#right-navigation`. If negative, this
* means that the tabs overlap, and the value is the width of overlapping
* parts.
*
* Used in default expandCondition and collapseCondition.
* Used in default `expandCondition` and `collapseCondition` options.
*
* @return {Numeric} distance/overlap in pixels
* @return {number} distance/overlap in pixels
*/
calculateTabDistance: function () {
var $leftTab, $rightTab, leftEnd, rightStart;
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' );
if ( !isRTL ) {
leftTab = document.getElementById( 'left-navigation' );
rightTab = document.getElementById( 'right-navigation' );
} else {
$leftTab = $( '#right-navigation' );
$rightTab = $( '#left-navigation' );
leftTab = document.getElementById( 'right-navigation' );
rightTab = document.getElementById( 'left-navigation' );
}
leftEnd = $leftTab.offset().left + $leftTab.width();
rightStart = $rightTab.offset().left;
leftEnd = leftTab.getBoundingClientRect().right;
rightStart = rightTab.getBoundingClientRect().left;
return rightStart - leftEnd;
}
};
/**
* @class jQuery
* @mixins jQuery.plugin.collapsibleTabs
*/
}( jQuery ) );

10
jsduck.json Normal file
View File

@ -0,0 +1,10 @@
{
"--title": "Vector skin - Documentation",
"--output": "docs",
"--processes": "0",
"--warnings-exit-nonzero": true,
"--warnings": ["-nodoc(class,public)"],
"--": [
"collapsibleTabs.js"
]
}

View File

@ -1,6 +1,8 @@
{
"private": true,
"scripts": {
"test": "grunt test"
"test": "grunt test",
"doc": "jsduck"
},
"devDependencies": {
"grunt": "0.4.5",