Localisation updates from http://translatewiki.net.
[mediawiki.git] / resources / jquery / jquery.makeCollapsible.js
blobace4a55e5d686e074372d2c165125cd188de6b59
1 /**
2  * jQuery makeCollapsible
3  *
4  * This will enable collapsible-functionality on all passed elements.
5  * - Will prevent binding twice to the same element.
6  * - Initial state is expanded by default, this can be overriden by adding class
7  *   "mw-collapsed" to the "mw-collapsible" element.
8  * - Elements made collapsible have jQuery data "mw-made-collapsible" set to true.
9  * - The inner content is wrapped in a "div.mw-collapsible-content" (except for tables and lists).
10  *
11  * @author Krinkle, 2011-2012
12  *
13  * Dual license:
14  * @license CC-BY 3.0 <http://creativecommons.org/licenses/by/3.0>
15  * @license GPL2 <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>
16  */
17 ( function ( $, mw ) {
19 $.fn.makeCollapsible = function () {
21         return this.each(function () {
23                 // Define reused variables and functions
24                 var lpx = 'jquery.makeCollapsible> ',
25                         collapsible = this,
26                         // Ensure class "mw-collapsible" is present in case .makeCollapsible()
27                         // is called on element(s) that don't have it yet.
28                         $collapsible = $(collapsible).addClass( 'mw-collapsible' ),
29                         collapsetext = $collapsible.attr( 'data-collapsetext' ),
30                         expandtext = $collapsible.attr( 'data-expandtext' ),
31                         $toggle,
32                         $toggleLink,
33                         $firstItem,
34                         collapsibleId,
35                         $customTogglers,
36                         firstval,
37                         /**
38                          * @param {jQuery} $collapsible
39                          * @param {string} action The action this function will take ('expand' or 'collapse').
40                          * @param {jQuery|null} [optional] $defaultToggle
41                          * @param {Object|undefined} options
42                          */
43                         toggleElement = function ( $collapsible, action, $defaultToggle, options ) {
44                                 var $collapsibleContent, $containers;
45                                 options = options || {};
47                                 // Validate parameters
49                                 // $collapsible must be an instance of jQuery
50                                 if ( !$collapsible.jquery ) {
51                                         return;
52                                 }
53                                 if ( action !== 'expand' && action !== 'collapse' ) {
54                                         // action must be string with 'expand' or 'collapse'
55                                         return;
56                                 }
57                                 if ( $defaultToggle === undefined ) {
58                                         $defaultToggle = null;
59                                 }
60                                 if ( $defaultToggle !== null && !$defaultToggle.jquery ) {
61                                         // is optional (may be undefined), but if defined it must be an instance of jQuery.
62                                         // If it's not, abort right away.
63                                         // After this $defaultToggle is either null or a valid jQuery instance.
64                                         return;
65                                 }
67                                 if ( action === 'collapse' ) {
69                                         // Collapse the element
70                                         if ( $collapsible.is( 'table' ) ) {
71                                                 // Hide all table rows of this table
72                                                 // Slide doens't work with tables, but fade does as of jQuery 1.1.3
73                                                 // http://stackoverflow.com/questions/467336#920480
74                                                 $containers = $collapsible.find( '> tbody > tr' );
75                                                 if ( $defaultToggle ) {
76                                                         // Exclude tablerow containing togglelink
77                                                         $containers = $containers.not( $defaultToggle.closest( 'tr' ) );
78                                                 }
80                                                 if ( options.instantHide ) {
81                                                         $containers.hide();
82                                                 } else {
83                                                         $containers.stop( true, true ).fadeOut();
84                                                 }
86                                         } else if ( $collapsible.is( 'ul' ) || $collapsible.is( 'ol' ) ) {
87                                                 $containers = $collapsible.find( '> li' );
88                                                 if ( $defaultToggle ) {
89                                                         // Exclude list-item containing togglelink
90                                                         $containers = $containers.not( $defaultToggle.parent() );
91                                                 }
93                                                 if ( options.instantHide ) {
94                                                         $containers.hide();
95                                                 } else {
96                                                         $containers.stop( true, true ).slideUp();
97                                                 }
99                                         } else {
100                                                 // <div>, <p> etc.
101                                                 $collapsibleContent = $collapsible.find( '> .mw-collapsible-content' );
103                                                 // If a collapsible-content is defined, collapse it
104                                                 if ( $collapsibleContent.length ) {
105                                                         if ( options.instantHide ) {
106                                                                 $collapsibleContent.hide();
107                                                         } else {
108                                                                 $collapsibleContent.slideUp();
109                                                         }
111                                                 // Otherwise assume this is a customcollapse with a remote toggle
112                                                 // .. and there is no collapsible-content because the entire element should be toggled
113                                                 } else {
114                                                         if ( options.instantHide ) {
115                                                                 $collapsible.hide();
116                                                         } else {
117                                                                 if ( $collapsible.is( 'tr' ) || $collapsible.is( 'td' ) || $collapsible.is( 'th' ) ) {
118                                                                         $collapsible.fadeOut();
119                                                                 } else {
120                                                                         $collapsible.slideUp();
121                                                                 }
122                                                         }
123                                                 }
124                                         }
126                                 } else {
128                                         // Expand the element
129                                         if ( $collapsible.is( 'table' ) ) {
130                                                 $containers = $collapsible.find( '>tbody>tr' );
131                                                 if ( $defaultToggle ) {
132                                                         // Exclude tablerow containing togglelink
133                                                         $containers.not( $defaultToggle.parent().parent() ).stop(true, true).fadeIn();
134                                                 } else {
135                                                         $containers.stop( true, true ).fadeIn();
136                                                 }
138                                         } else if ( $collapsible.is( 'ul' ) || $collapsible.is( 'ol' ) ) {
139                                                 $containers = $collapsible.find( '> li' );
140                                                 if ( $defaultToggle ) {
141                                                         // Exclude list-item containing togglelink
142                                                         $containers.not( $defaultToggle.parent() ).stop( true, true ).slideDown();
143                                                 } else {
144                                                         $containers.stop( true, true ).slideDown();
145                                                 }
147                                         } else {
148                                                 // <div>, <p> etc.
149                                                 $collapsibleContent = $collapsible.find( '> .mw-collapsible-content' );
151                                                 // If a collapsible-content is defined, collapse it
152                                                 if ( $collapsibleContent.length ) {
153                                                         $collapsibleContent.slideDown();
155                                                 // Otherwise assume this is a customcollapse with a remote toggle
156                                                 // .. and there is no collapsible-content because the entire element should be toggled
157                                                 } else {
158                                                         if ( $collapsible.is( 'tr' ) || $collapsible.is( 'td' ) || $collapsible.is( 'th' ) ) {
159                                                                 $collapsible.fadeIn();
160                                                         } else {
161                                                                 $collapsible.slideDown();
162                                                         }
163                                                 }
164                                         }
165                                 }
166                         },
167                         /**
168                          * Toggles collapsible and togglelink class and updates text label.
169                          *
170                          * @param {jQuery} $that
171                          * @param {jQuery.Event} e
172                          * @param {Object|undefined} options
173                          */
174                         toggleLinkDefault = function ( $that, e, options ) {
175                                 var $collapsible = $that.closest( '.mw-collapsible' ).toggleClass( 'mw-collapsed' );
176                                 e.preventDefault();
177                                 e.stopPropagation();
179                                 // It's expanded right now
180                                 if ( !$that.hasClass( 'mw-collapsible-toggle-collapsed' ) ) {
181                                         // Change link to "Show"
182                                         $that.removeClass( 'mw-collapsible-toggle-expanded' ).addClass( 'mw-collapsible-toggle-collapsed' );
183                                         if ( $that.find( '> a' ).length ) {
184                                                 $that.find( '> a' ).text( expandtext );
185                                         } else {
186                                                 $that.text( expandtext );
187                                         }
188                                         // Collapse element
189                                         toggleElement( $collapsible, 'collapse', $that, options );
191                                 // It's collapsed right now
192                                 } else {
193                                         // Change link to "Hide"
194                                         $that.removeClass( 'mw-collapsible-toggle-collapsed' ).addClass( 'mw-collapsible-toggle-expanded' );
195                                         if ( $that.find( '> a' ).length ) {
196                                                 $that.find( '> a' ).text( collapsetext );
197                                         } else {
198                                                 $that.text( collapsetext );
199                                         }
200                                         // Expand element
201                                         toggleElement( $collapsible, 'expand', $that, options );
202                                 }
203                                 return;
204                         },
205                         /**
206                          * Toggles collapsible and togglelink class.
207                          *
208                          * @param {jQuery} $that
209                          * @param {jQuery.Event} e
210                          * @param {Object|undefined} options
211                          */
212                         toggleLinkPremade = function ( $that, e, options ) {
213                                 var $collapsible = $that.eq( 0 ).closest( '.mw-collapsible' ).toggleClass( 'mw-collapsed' );
214                                 if ( $.nodeName( e.target, 'a' ) ) {
215                                         return true;
216                                 }
217                                 e.preventDefault();
218                                 e.stopPropagation();
220                                 // It's expanded right now
221                                 if ( !$that.hasClass( 'mw-collapsible-toggle-collapsed' ) ) {
222                                         // Change toggle to collapsed
223                                         $that.removeClass( 'mw-collapsible-toggle-expanded' ).addClass( 'mw-collapsible-toggle-collapsed' );
224                                         // Collapse element
225                                         toggleElement( $collapsible, 'collapse', $that, options );
227                                 // It's collapsed right now
228                                 } else {
229                                         // Change toggle to expanded
230                                         $that.removeClass( 'mw-collapsible-toggle-collapsed' ).addClass( 'mw-collapsible-toggle-expanded' );
231                                         // Expand element
232                                         toggleElement( $collapsible, 'expand', $that, options );
233                                 }
234                                 return;
235                         },
236                         /**
237                          * Toggles customcollapsible.
238                          *
239                          * @param {jQuery} $that
240                          * @param {jQuery.Event} e
241                          * @param {Object|undefined} options
242                          * @param {jQuery} $collapsible
243                          */
244                         toggleLinkCustom = function ( $that, e, options, $collapsible ) {
245                                 // For the initial state call of customtogglers there is no event passed
246                                 if ( e ) {
247                                         e.preventDefault();
248                                         e.stopPropagation();
249                                 }
250                                 // Get current state and toggle to the opposite
251                                 var action = $collapsible.hasClass( 'mw-collapsed' ) ? 'expand' : 'collapse';
252                                 $collapsible.toggleClass( 'mw-collapsed' );
253                                 toggleElement( $collapsible, action, $that, options );
255                         };
257                 // Return if it has been enabled already.
258                 if ( $collapsible.data( 'mw-made-collapsible' ) ) {
259                         return;
260                 } else {
261                         $collapsible.data( 'mw-made-collapsible', true );
262                 }
264                 // Use custom text or default ?
265                 if ( !collapsetext ) {
266                         collapsetext = mw.msg( 'collapsible-collapse' );
267                 }
268                 if ( !expandtext ) {
269                         expandtext = mw.msg( 'collapsible-expand' );
270                 }
272                 // Create toggle link with a space around the brackets (&nbsp;[text]&nbsp;)
273                 $toggleLink =
274                         $( '<a href="#"></a>' )
275                                 .text( collapsetext )
276                                 .wrap( '<span class="mw-collapsible-toggle"></span>' )
277                                         .parent()
278                                         .prepend( '&nbsp;[' )
279                                         .append( ']&nbsp;' )
280                                         .on( 'click.mw-collapse', function ( e, options ) {
281                                                 toggleLinkDefault( $(this), e, options );
282                                         } );
284                 // Check if this element has a custom position for the toggle link
285                 // (ie. outside the container or deeper inside the tree)
286                 // Then: Locate the custom toggle link(s) and bind them
287                 if ( ( $collapsible.attr( 'id' ) || '' ).indexOf( 'mw-customcollapsible-' ) === 0 ) {
289                         collapsibleId = $collapsible.attr( 'id' );
290                         $customTogglers = $( '.' + collapsibleId.replace( 'mw-customcollapsible', 'mw-customtoggle' ) );
291                         mw.log( lpx + 'Found custom collapsible: #' + collapsibleId );
293                         // Double check that there is actually a customtoggle link
294                         if ( $customTogglers.length ) {
295                                 $customTogglers.on( 'click.mw-collapse', function ( e, options ) {
296                                         toggleLinkCustom( $(this), e, options, $collapsible );
297                                 } );
298                         } else {
299                                 mw.log( lpx + '#' + collapsibleId + ': Missing toggler!' );
300                         }
302                         // Initial state
303                         if ( $collapsible.hasClass( 'mw-collapsed' ) ) {
304                                 // Remove here so that the toggler goes in the right direction,
305                                 // It re-adds the class.
306                                 $collapsible.removeClass( 'mw-collapsed' );
307                                 toggleLinkCustom( $customTogglers, null, { instantHide: true }, $collapsible );
308                         }
310                 // If this is not a custom case, do the default:
311                 // Wrap the contents add the toggle link
312                 } else {
314                         // Elements are treated differently
315                         if ( $collapsible.is( 'table' ) ) {
316                                 // The toggle-link will be in one the the cells (td or th) of the first row
317                                 $firstItem = $collapsible.find( 'tr:first th, tr:first td' );
318                                 $toggle = $firstItem.find( '> .mw-collapsible-toggle' );
320                                 // If theres no toggle link, add it to the last cell
321                                 if ( !$toggle.length ) {
322                                         $firstItem.eq(-1).prepend( $toggleLink );
323                                 } else {
324                                         $toggleLink = $toggle.off( 'click.mw-collapse' ).on( 'click.mw-collapse', function ( e, options ) {
325                                                 toggleLinkPremade( $toggle, e, options );
326                                         } );
327                                 }
329                         } else if ( $collapsible.is( 'ul' ) || $collapsible.is( 'ol' ) ) {
330                                 // The toggle-link will be in the first list-item
331                                 $firstItem = $collapsible.find( 'li:first' );
332                                 $toggle = $firstItem.find( '> .mw-collapsible-toggle' );
334                                 // If theres no toggle link, add it
335                                 if ( !$toggle.length ) {
336                                         // Make sure the numeral order doesn't get messed up, force the first (soon to be second) item
337                                         // to be "1". Except if the value-attribute is already used.
338                                         // If no value was set WebKit returns "", Mozilla returns '-1', others return null or undefined.
339                                         firstval = $firstItem.attr( 'value' );
340                                         if ( firstval === undefined || !firstval || firstval === '-1' || firstval === -1 ) {
341                                                 $firstItem.attr( 'value', '1' );
342                                         }
343                                         $collapsible.prepend( $toggleLink.wrap( '<li class="mw-collapsible-toggle-li"></li>' ).parent() );
344                                 } else {
345                                         $toggleLink = $toggle.off( 'click.mw-collapse' ).on( 'click.mw-collapse', function ( e, options ) {
346                                                 toggleLinkPremade( $toggle, e, options );
347                                         } );
348                                 }
350                         } else { // <div>, <p> etc.
352                                 // The toggle-link will be the first child of the element
353                                 $toggle = $collapsible.find( '> .mw-collapsible-toggle' );
355                                 // If a direct child .content-wrapper does not exists, create it
356                                 if ( !$collapsible.find( '> .mw-collapsible-content' ).length ) {
357                                         $collapsible.wrapInner( '<div class="mw-collapsible-content"></div>' );
358                                 }
360                                 // If theres no toggle link, add it
361                                 if ( !$toggle.length ) {
362                                         $collapsible.prepend( $toggleLink );
363                                 } else {
364                                         $toggleLink = $toggle.off( 'click.mw-collapse' ).on( 'click.mw-collapse', function ( e, options ) {
365                                                 toggleLinkPremade( $toggle, e, options );
366                                         } );
367                                 }
368                         }
369                 }
371                 // Initial state (only for those that are not custom,
372                 // because the initial state of those has been taken care of already).
373                 if ( $collapsible.hasClass( 'mw-collapsed' ) && ( $collapsible.attr( 'id' ) || '').indexOf( 'mw-customcollapsible-' ) !== 0 ) {
374                         $collapsible.removeClass( 'mw-collapsed' );
375                         // The collapsible element could have multiple togglers
376                         // To toggle the initial state only click one of them (ie. the first one, eq(0) )
377                         // Else it would go like: hide,show,hide,show for each toggle link.
378                         // This is just like it would be in reality (only one toggle is clicked at a time).
379                         $toggleLink.eq( 0 ).trigger( 'click', [ { instantHide: true } ] );
380                 }
381         } );
384 }( jQuery, mediaWiki ) );