--- jquery.makeCollapsible.js 2012-03-07 20:10:24.000000000 +0000 +++ jquery.makeCollapsible.js 2012-03-08 09:54:52.000000000 +0000 @@ -5,7 +5,7 @@ * Will prevent binding twice to the same element. * Initial state is expanded by default, this can be overriden by adding class * "mw-collapsed" to the "mw-collapsible" element. - * Elements made collapsible have class "mw-made-collapsible". + * Elements made collapsible have the jQuery data "mw-made-collapsible" set to true. * Except for tables and lists, the inner content is wrapped in "mw-collapsible-content". * * @author Krinkle @@ -18,27 +18,6 @@ ( function( $, mw ) { -// Warning: this code employs a number of speed optimizations to avoid excessive runtime on older -// browsers to set up the initial state if there are many collapsible, initially collapsed elements -// on the page. -// A particular problem is that this module uses classes to record the collapsed/expanded state of -// items. Modifying classes may cause reflows of the page rendering, which may make subsequent hide -// operations slow because they need to recalculate the computed styles of elements. Also, simply -// triggering the installed events to actually collapse the elements may cause significant overhead, -// even if event bubbling is bypassed through triggerHandler() instead of trigger(). Finally, when -// setting up the initial collapsed elements, no animations must be used. -// The code below takes care of all this. Initial setup runs in two phases: in a first phase, we only -// collect all the modifications in a "data" object without making any changes to the DOM; then in a -// second phase, we play back all the recorded modifications. During phase one, we do not trigger events -// on togglers but invoke the installed toggle* function directly. -// Once the setup is done, normal event handling will typically invoke events without "data" objects, -// and will then perform DOM modifications right away, which should be OK since any click on a toggler -// will change only one collapsible element, in which case the performance should be still good enough. -// Performance becomes only a problem if we want to toggle several collapsible elements at once. -// Because we know that we only have a "data" object during initial setup, where we at most want to collapse -// some elements, the handling of collapse/expand is slightly asymmetric in the toggle* handlers as we apply -// class changes directly when expanding. - var rspace = /\s+/; // Remove and add some classes in one step. @@ -91,7 +70,7 @@ // Hide all table rows of this table // Slide doesn't work with tables, but fade does as of jQuery 1.1.3 // http://stackoverflow.com/questions/467336#920480 - $containers = $collapsible.children( 'tbody' ).children ( 'tr' ); + $containers = getRows( $collapsible[0] ); if ( $defaultToggle ) { // Exclude table row containing toggle link $containers = $containers.not( $defaultToggle.parent().parent() ); @@ -129,7 +108,7 @@ data = { immediate: [], fade: [], slide: [] }; } if ( $collapsible.is( 'table' ) ) { - $containers = $collapsible.children( 'tbody' ).children ( 'tr' ); + $containers = getRows( $collapsible[0] ); if ( $defaultToggle ) { // Exclude table row containing toggle $containers = $containers.not( $defaultToggle.parent().parent() ); @@ -237,6 +216,25 @@ } } +// Helper function to avoid needlessly complex Sizzle queries. +function getRows (tableOrTbody, onlyFirst) { + if ( typeof (tableOrTbody.rows) !== 'undefined' ) { + if ( tableOrTbody.rows === null || !tableOrTbody.rows.length ) { + return $(); + } + return $( onlyFirst ? tableOrTbody.rows[0] : tableOrTbody.rows ); + } + // Fall back to jQuery standard behavior + var $t = $( tableOrTbody ); + if ( $t.is( 'table' ) ) { + $t = $t.children( 'tbody' ); + if ( !$t.length ) { // Is tbody mandatory nowadays? I seem to dimly remember that is once wasn't... + $t = $( tableOrTbody ); + } + } + return $t.children( onlyFirst ? 'tr:first' : 'tr' ); +} + $.fn.makeCollapsible = function() { var defaultCollapseText = mw.msg( 'collapsible-collapse' ); @@ -244,8 +242,9 @@ var data = { immediate: true, doIt: false, toggles: [], change: [] }; // Record changes; use plain arrays var $result = this - .not( '.mw-made-collapsible' ) // Exclude already handled elements - .addClass ( 'mw-collapsible mw-made-collapsible' ) // case: $( '#myAJAXelement' ).makeCollapsible() + .filter (function( elem ) { + return !$( elem ).data ( 'mw-made-collapsible' ); + } ) .each(function() { var _fn = 'jquery.makeCollapsible> '; @@ -255,6 +254,7 @@ collapsetext = $that.attr( 'data-collapsetext' ) || defaultCollapseText, expandtext = $that.attr( 'data-expandtext' ) || defaultExpandText; + $that.data( 'mw-made-collapsible', true ); function toggleLinkDefault( $that, e, $collapsible, data ) { if (!data) { data = { immediate: false, doIt: true, toggles : []}; @@ -338,7 +338,7 @@ // Elements are treated differently } else if ( $that.is( 'table' ) ) { // The toggle-link will be in one the the cells (td or th) of the first row - var $firstRowCells = $that.children ( 'tbody' ).children( 'tr:first' ).children ('th, td'), + var $firstRowCells = getRows( $that[0], true ).children ('th, td'), $toggle = $firstRowCells.children( '.mw-collapsible-toggle' ); // If theres no toggle link, add it to the last cell @@ -355,7 +355,7 @@ } else if ( $that.is( 'ul' ) || $that.is( 'ol' ) ) { // The toggle-link will be in the first list-item var $firstItem = $that.children( 'li:first' ), - $toggle = $firstItem.children( '.mw-collapsible-toggle' ); + $toggle = $firstItem.children( '.mw-collapsible-toggle' ); // If theres no toggle link, add it if ( !$toggle.length ) { @@ -408,7 +408,8 @@ var oldOff = jQuery.fx.off; jQuery.fx.off = true; // Make sure we don't get any animations hide( data.lists ); jQuery.fx.off = oldOff; - } + } + $result.not ( '.mw-collapsible' ).addClass ( 'mw-collapsible' ); // case: $( '#myAJAXelement' ).makeCollapsible() $( data.toggles ).changeClass( 'mw-collapsible-toggle-expanded', 'mw-collapsible-toggle-collapsed' ); for ( var i = 0; i < data.change.length; i++ ) { var $lks = data.change[i].$item.children( 'a' );