source: trunk/themes/default/js/ui/jquery.ui.accordion.js @ 12525

Last change on this file since 12525 was 12525, checked in by patdenice, 12 years ago

feature:2487
Update jQuery to 1.6.4 and jQuery UI to 1.8.16

File size: 15.9 KB
Line 
1/*
2 * jQuery UI Accordion 1.8.16
3 *
4 * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
5 * Dual licensed under the MIT or GPL Version 2 licenses.
6 * http://jquery.org/license
7 *
8 * http://docs.jquery.com/UI/Accordion
9 *
10 * Depends:
11 *      jquery.ui.core.js
12 *      jquery.ui.widget.js
13 */
14(function( $, undefined ) {
15
16$.widget( "ui.accordion", {
17        options: {
18                active: 0,
19                animated: "slide",
20                autoHeight: true,
21                clearStyle: false,
22                collapsible: false,
23                event: "click",
24                fillSpace: false,
25                header: "> li > :first-child,> :not(li):even",
26                icons: {
27                        header: "ui-icon-triangle-1-e",
28                        headerSelected: "ui-icon-triangle-1-s"
29                },
30                navigation: false,
31                navigationFilter: function() {
32                        return this.href.toLowerCase() === location.href.toLowerCase();
33                }
34        },
35
36        _create: function() {
37                var self = this,
38                        options = self.options;
39
40                self.running = 0;
41
42                self.element
43                        .addClass( "ui-accordion ui-widget ui-helper-reset" )
44                        // in lack of child-selectors in CSS
45                        // we need to mark top-LIs in a UL-accordion for some IE-fix
46                        .children( "li" )
47                                .addClass( "ui-accordion-li-fix" );
48
49                self.headers = self.element.find( options.header )
50                        .addClass( "ui-accordion-header ui-helper-reset ui-state-default ui-corner-all" )
51                        .bind( "mouseenter.accordion", function() {
52                                if ( options.disabled ) {
53                                        return;
54                                }
55                                $( this ).addClass( "ui-state-hover" );
56                        })
57                        .bind( "mouseleave.accordion", function() {
58                                if ( options.disabled ) {
59                                        return;
60                                }
61                                $( this ).removeClass( "ui-state-hover" );
62                        })
63                        .bind( "focus.accordion", function() {
64                                if ( options.disabled ) {
65                                        return;
66                                }
67                                $( this ).addClass( "ui-state-focus" );
68                        })
69                        .bind( "blur.accordion", function() {
70                                if ( options.disabled ) {
71                                        return;
72                                }
73                                $( this ).removeClass( "ui-state-focus" );
74                        });
75
76                self.headers.next()
77                        .addClass( "ui-accordion-content ui-helper-reset ui-widget-content ui-corner-bottom" );
78
79                if ( options.navigation ) {
80                        var current = self.element.find( "a" ).filter( options.navigationFilter ).eq( 0 );
81                        if ( current.length ) {
82                                var header = current.closest( ".ui-accordion-header" );
83                                if ( header.length ) {
84                                        // anchor within header
85                                        self.active = header;
86                                } else {
87                                        // anchor within content
88                                        self.active = current.closest( ".ui-accordion-content" ).prev();
89                                }
90                        }
91                }
92
93                self.active = self._findActive( self.active || options.active )
94                        .addClass( "ui-state-default ui-state-active" )
95                        .toggleClass( "ui-corner-all" )
96                        .toggleClass( "ui-corner-top" );
97                self.active.next().addClass( "ui-accordion-content-active" );
98
99                self._createIcons();
100                self.resize();
101               
102                // ARIA
103                self.element.attr( "role", "tablist" );
104
105                self.headers
106                        .attr( "role", "tab" )
107                        .bind( "keydown.accordion", function( event ) {
108                                return self._keydown( event );
109                        })
110                        .next()
111                                .attr( "role", "tabpanel" );
112
113                self.headers
114                        .not( self.active || "" )
115                        .attr({
116                                "aria-expanded": "false",
117                                "aria-selected": "false",
118                                tabIndex: -1
119                        })
120                        .next()
121                                .hide();
122
123                // make sure at least one header is in the tab order
124                if ( !self.active.length ) {
125                        self.headers.eq( 0 ).attr( "tabIndex", 0 );
126                } else {
127                        self.active
128                                .attr({
129                                        "aria-expanded": "true",
130                                        "aria-selected": "true",
131                                        tabIndex: 0
132                                });
133                }
134
135                // only need links in tab order for Safari
136                if ( !$.browser.safari ) {
137                        self.headers.find( "a" ).attr( "tabIndex", -1 );
138                }
139
140                if ( options.event ) {
141                        self.headers.bind( options.event.split(" ").join(".accordion ") + ".accordion", function(event) {
142                                self._clickHandler.call( self, event, this );
143                                event.preventDefault();
144                        });
145                }
146        },
147
148        _createIcons: function() {
149                var options = this.options;
150                if ( options.icons ) {
151                        $( "<span></span>" )
152                                .addClass( "ui-icon " + options.icons.header )
153                                .prependTo( this.headers );
154                        this.active.children( ".ui-icon" )
155                                .toggleClass(options.icons.header)
156                                .toggleClass(options.icons.headerSelected);
157                        this.element.addClass( "ui-accordion-icons" );
158                }
159        },
160
161        _destroyIcons: function() {
162                this.headers.children( ".ui-icon" ).remove();
163                this.element.removeClass( "ui-accordion-icons" );
164        },
165
166        destroy: function() {
167                var options = this.options;
168
169                this.element
170                        .removeClass( "ui-accordion ui-widget ui-helper-reset" )
171                        .removeAttr( "role" );
172
173                this.headers
174                        .unbind( ".accordion" )
175                        .removeClass( "ui-accordion-header ui-accordion-disabled ui-helper-reset ui-state-default ui-corner-all ui-state-active ui-state-disabled ui-corner-top" )
176                        .removeAttr( "role" )
177                        .removeAttr( "aria-expanded" )
178                        .removeAttr( "aria-selected" )
179                        .removeAttr( "tabIndex" );
180
181                this.headers.find( "a" ).removeAttr( "tabIndex" );
182                this._destroyIcons();
183                var contents = this.headers.next()
184                        .css( "display", "" )
185                        .removeAttr( "role" )
186                        .removeClass( "ui-helper-reset ui-widget-content ui-corner-bottom ui-accordion-content ui-accordion-content-active ui-accordion-disabled ui-state-disabled" );
187                if ( options.autoHeight || options.fillHeight ) {
188                        contents.css( "height", "" );
189                }
190
191                return $.Widget.prototype.destroy.call( this );
192        },
193
194        _setOption: function( key, value ) {
195                $.Widget.prototype._setOption.apply( this, arguments );
196                       
197                if ( key == "active" ) {
198                        this.activate( value );
199                }
200                if ( key == "icons" ) {
201                        this._destroyIcons();
202                        if ( value ) {
203                                this._createIcons();
204                        }
205                }
206                // #5332 - opacity doesn't cascade to positioned elements in IE
207                // so we need to add the disabled class to the headers and panels
208                if ( key == "disabled" ) {
209                        this.headers.add(this.headers.next())
210                                [ value ? "addClass" : "removeClass" ](
211                                        "ui-accordion-disabled ui-state-disabled" );
212                }
213        },
214
215        _keydown: function( event ) {
216                if ( this.options.disabled || event.altKey || event.ctrlKey ) {
217                        return;
218                }
219
220                var keyCode = $.ui.keyCode,
221                        length = this.headers.length,
222                        currentIndex = this.headers.index( event.target ),
223                        toFocus = false;
224
225                switch ( event.keyCode ) {
226                        case keyCode.RIGHT:
227                        case keyCode.DOWN:
228                                toFocus = this.headers[ ( currentIndex + 1 ) % length ];
229                                break;
230                        case keyCode.LEFT:
231                        case keyCode.UP:
232                                toFocus = this.headers[ ( currentIndex - 1 + length ) % length ];
233                                break;
234                        case keyCode.SPACE:
235                        case keyCode.ENTER:
236                                this._clickHandler( { target: event.target }, event.target );
237                                event.preventDefault();
238                }
239
240                if ( toFocus ) {
241                        $( event.target ).attr( "tabIndex", -1 );
242                        $( toFocus ).attr( "tabIndex", 0 );
243                        toFocus.focus();
244                        return false;
245                }
246
247                return true;
248        },
249
250        resize: function() {
251                var options = this.options,
252                        maxHeight;
253
254                if ( options.fillSpace ) {
255                        if ( $.browser.msie ) {
256                                var defOverflow = this.element.parent().css( "overflow" );
257                                this.element.parent().css( "overflow", "hidden");
258                        }
259                        maxHeight = this.element.parent().height();
260                        if ($.browser.msie) {
261                                this.element.parent().css( "overflow", defOverflow );
262                        }
263
264                        this.headers.each(function() {
265                                maxHeight -= $( this ).outerHeight( true );
266                        });
267
268                        this.headers.next()
269                                .each(function() {
270                                        $( this ).height( Math.max( 0, maxHeight -
271                                                $( this ).innerHeight() + $( this ).height() ) );
272                                })
273                                .css( "overflow", "auto" );
274                } else if ( options.autoHeight ) {
275                        maxHeight = 0;
276                        this.headers.next()
277                                .each(function() {
278                                        maxHeight = Math.max( maxHeight, $( this ).height( "" ).height() );
279                                })
280                                .height( maxHeight );
281                }
282
283                return this;
284        },
285
286        activate: function( index ) {
287                // TODO this gets called on init, changing the option without an explicit call for that
288                this.options.active = index;
289                // call clickHandler with custom event
290                var active = this._findActive( index )[ 0 ];
291                this._clickHandler( { target: active }, active );
292
293                return this;
294        },
295
296        _findActive: function( selector ) {
297                return selector
298                        ? typeof selector === "number"
299                                ? this.headers.filter( ":eq(" + selector + ")" )
300                                : this.headers.not( this.headers.not( selector ) )
301                        : selector === false
302                                ? $( [] )
303                                : this.headers.filter( ":eq(0)" );
304        },
305
306        // TODO isn't event.target enough? why the separate target argument?
307        _clickHandler: function( event, target ) {
308                var options = this.options;
309                if ( options.disabled ) {
310                        return;
311                }
312
313                // called only when using activate(false) to close all parts programmatically
314                if ( !event.target ) {
315                        if ( !options.collapsible ) {
316                                return;
317                        }
318                        this.active
319                                .removeClass( "ui-state-active ui-corner-top" )
320                                .addClass( "ui-state-default ui-corner-all" )
321                                .children( ".ui-icon" )
322                                        .removeClass( options.icons.headerSelected )
323                                        .addClass( options.icons.header );
324                        this.active.next().addClass( "ui-accordion-content-active" );
325                        var toHide = this.active.next(),
326                                data = {
327                                        options: options,
328                                        newHeader: $( [] ),
329                                        oldHeader: options.active,
330                                        newContent: $( [] ),
331                                        oldContent: toHide
332                                },
333                                toShow = ( this.active = $( [] ) );
334                        this._toggle( toShow, toHide, data );
335                        return;
336                }
337
338                // get the click target
339                var clicked = $( event.currentTarget || target ),
340                        clickedIsActive = clicked[0] === this.active[0];
341
342                // TODO the option is changed, is that correct?
343                // TODO if it is correct, shouldn't that happen after determining that the click is valid?
344                options.active = options.collapsible && clickedIsActive ?
345                        false :
346                        this.headers.index( clicked );
347
348                // if animations are still active, or the active header is the target, ignore click
349                if ( this.running || ( !options.collapsible && clickedIsActive ) ) {
350                        return;
351                }
352
353                // find elements to show and hide
354                var active = this.active,
355                        toShow = clicked.next(),
356                        toHide = this.active.next(),
357                        data = {
358                                options: options,
359                                newHeader: clickedIsActive && options.collapsible ? $([]) : clicked,
360                                oldHeader: this.active,
361                                newContent: clickedIsActive && options.collapsible ? $([]) : toShow,
362                                oldContent: toHide
363                        },
364                        down = this.headers.index( this.active[0] ) > this.headers.index( clicked[0] );
365
366                // when the call to ._toggle() comes after the class changes
367                // it causes a very odd bug in IE 8 (see #6720)
368                this.active = clickedIsActive ? $([]) : clicked;
369                this._toggle( toShow, toHide, data, clickedIsActive, down );
370
371                // switch classes
372                active
373                        .removeClass( "ui-state-active ui-corner-top" )
374                        .addClass( "ui-state-default ui-corner-all" )
375                        .children( ".ui-icon" )
376                                .removeClass( options.icons.headerSelected )
377                                .addClass( options.icons.header );
378                if ( !clickedIsActive ) {
379                        clicked
380                                .removeClass( "ui-state-default ui-corner-all" )
381                                .addClass( "ui-state-active ui-corner-top" )
382                                .children( ".ui-icon" )
383                                        .removeClass( options.icons.header )
384                                        .addClass( options.icons.headerSelected );
385                        clicked
386                                .next()
387                                .addClass( "ui-accordion-content-active" );
388                }
389
390                return;
391        },
392
393        _toggle: function( toShow, toHide, data, clickedIsActive, down ) {
394                var self = this,
395                        options = self.options;
396
397                self.toShow = toShow;
398                self.toHide = toHide;
399                self.data = data;
400
401                var complete = function() {
402                        if ( !self ) {
403                                return;
404                        }
405                        return self._completed.apply( self, arguments );
406                };
407
408                // trigger changestart event
409                self._trigger( "changestart", null, self.data );
410
411                // count elements to animate
412                self.running = toHide.size() === 0 ? toShow.size() : toHide.size();
413
414                if ( options.animated ) {
415                        var animOptions = {};
416
417                        if ( options.collapsible && clickedIsActive ) {
418                                animOptions = {
419                                        toShow: $( [] ),
420                                        toHide: toHide,
421                                        complete: complete,
422                                        down: down,
423                                        autoHeight: options.autoHeight || options.fillSpace
424                                };
425                        } else {
426                                animOptions = {
427                                        toShow: toShow,
428                                        toHide: toHide,
429                                        complete: complete,
430                                        down: down,
431                                        autoHeight: options.autoHeight || options.fillSpace
432                                };
433                        }
434
435                        if ( !options.proxied ) {
436                                options.proxied = options.animated;
437                        }
438
439                        if ( !options.proxiedDuration ) {
440                                options.proxiedDuration = options.duration;
441                        }
442
443                        options.animated = $.isFunction( options.proxied ) ?
444                                options.proxied( animOptions ) :
445                                options.proxied;
446
447                        options.duration = $.isFunction( options.proxiedDuration ) ?
448                                options.proxiedDuration( animOptions ) :
449                                options.proxiedDuration;
450
451                        var animations = $.ui.accordion.animations,
452                                duration = options.duration,
453                                easing = options.animated;
454
455                        if ( easing && !animations[ easing ] && !$.easing[ easing ] ) {
456                                easing = "slide";
457                        }
458                        if ( !animations[ easing ] ) {
459                                animations[ easing ] = function( options ) {
460                                        this.slide( options, {
461                                                easing: easing,
462                                                duration: duration || 700
463                                        });
464                                };
465                        }
466
467                        animations[ easing ]( animOptions );
468                } else {
469                        if ( options.collapsible && clickedIsActive ) {
470                                toShow.toggle();
471                        } else {
472                                toHide.hide();
473                                toShow.show();
474                        }
475
476                        complete( true );
477                }
478
479                // TODO assert that the blur and focus triggers are really necessary, remove otherwise
480                toHide.prev()
481                        .attr({
482                                "aria-expanded": "false",
483                                "aria-selected": "false",
484                                tabIndex: -1
485                        })
486                        .blur();
487                toShow.prev()
488                        .attr({
489                                "aria-expanded": "true",
490                                "aria-selected": "true",
491                                tabIndex: 0
492                        })
493                        .focus();
494        },
495
496        _completed: function( cancel ) {
497                this.running = cancel ? 0 : --this.running;
498                if ( this.running ) {
499                        return;
500                }
501
502                if ( this.options.clearStyle ) {
503                        this.toShow.add( this.toHide ).css({
504                                height: "",
505                                overflow: ""
506                        });
507                }
508
509                // other classes are removed before the animation; this one needs to stay until completed
510                this.toHide.removeClass( "ui-accordion-content-active" );
511                // Work around for rendering bug in IE (#5421)
512                if ( this.toHide.length ) {
513                        this.toHide.parent()[0].className = this.toHide.parent()[0].className;
514                }
515
516                this._trigger( "change", null, this.data );
517        }
518});
519
520$.extend( $.ui.accordion, {
521        version: "1.8.16",
522        animations: {
523                slide: function( options, additions ) {
524                        options = $.extend({
525                                easing: "swing",
526                                duration: 300
527                        }, options, additions );
528                        if ( !options.toHide.size() ) {
529                                options.toShow.animate({
530                                        height: "show",
531                                        paddingTop: "show",
532                                        paddingBottom: "show"
533                                }, options );
534                                return;
535                        }
536                        if ( !options.toShow.size() ) {
537                                options.toHide.animate({
538                                        height: "hide",
539                                        paddingTop: "hide",
540                                        paddingBottom: "hide"
541                                }, options );
542                                return;
543                        }
544                        var overflow = options.toShow.css( "overflow" ),
545                                percentDone = 0,
546                                showProps = {},
547                                hideProps = {},
548                                fxAttrs = [ "height", "paddingTop", "paddingBottom" ],
549                                originalWidth;
550                        // fix width before calculating height of hidden element
551                        var s = options.toShow;
552                        originalWidth = s[0].style.width;
553                        s.width( parseInt( s.parent().width(), 10 )
554                                - parseInt( s.css( "paddingLeft" ), 10 )
555                                - parseInt( s.css( "paddingRight" ), 10 )
556                                - ( parseInt( s.css( "borderLeftWidth" ), 10 ) || 0 )
557                                - ( parseInt( s.css( "borderRightWidth" ), 10) || 0 ) );
558
559                        $.each( fxAttrs, function( i, prop ) {
560                                hideProps[ prop ] = "hide";
561
562                                var parts = ( "" + $.css( options.toShow[0], prop ) ).match( /^([\d+-.]+)(.*)$/ );
563                                showProps[ prop ] = {
564                                        value: parts[ 1 ],
565                                        unit: parts[ 2 ] || "px"
566                                };
567                        });
568                        options.toShow.css({ height: 0, overflow: "hidden" }).show();
569                        options.toHide
570                                .filter( ":hidden" )
571                                        .each( options.complete )
572                                .end()
573                                .filter( ":visible" )
574                                .animate( hideProps, {
575                                step: function( now, settings ) {
576                                        // only calculate the percent when animating height
577                                        // IE gets very inconsistent results when animating elements
578                                        // with small values, which is common for padding
579                                        if ( settings.prop == "height" ) {
580                                                percentDone = ( settings.end - settings.start === 0 ) ? 0 :
581                                                        ( settings.now - settings.start ) / ( settings.end - settings.start );
582                                        }
583
584                                        options.toShow[ 0 ].style[ settings.prop ] =
585                                                ( percentDone * showProps[ settings.prop ].value )
586                                                + showProps[ settings.prop ].unit;
587                                },
588                                duration: options.duration,
589                                easing: options.easing,
590                                complete: function() {
591                                        if ( !options.autoHeight ) {
592                                                options.toShow.css( "height", "" );
593                                        }
594                                        options.toShow.css({
595                                                width: originalWidth,
596                                                overflow: overflow
597                                        });
598                                        options.complete();
599                                }
600                        });
601                },
602                bounceslide: function( options ) {
603                        this.slide( options, {
604                                easing: options.down ? "easeOutBounce" : "swing",
605                                duration: options.down ? 1000 : 200
606                        });
607                }
608        }
609});
610
611})( jQuery );
Note: See TracBrowser for help on using the repository browser.