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

Last change on this file since 9559 was 9559, checked in by patdenice, 13 years ago

Update jQuery UI to 1.8.10.
Improve jquery ui management in template class.

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