Guest User

jQuery Accordion

a guest
Oct 10th, 2010
286
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. /*
  2.  * jQuery UI Accordion 1.8.5
  3.  *
  4.  * Copyright 2010, 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.  
  17. $.widget( "ui.accordion", {
  18.     options: {
  19.         active: 0,
  20.         animated: "slide",
  21.         autoHeight: true,
  22.         clearStyle: false,
  23.         collapsible: false,
  24.         event: "click",
  25.         fillSpace: false,
  26.         header: "> li > :first-child,> :not(li):even",
  27.         icons: {
  28.             header: "ui-icon-triangle-1-e",
  29.             headerSelected: "ui-icon-triangle-1-s"
  30.         },
  31.         navigation: false,
  32.         navigationFilter: function() {
  33.             return this.href.toLowerCase() === location.href.toLowerCase();
  34.         }
  35.     },
  36.  
  37.     _create: function() {
  38.         var self = this,
  39.             options = self.options;
  40.  
  41.         self.running = 0;
  42.  
  43.         self.element
  44.             .addClass( "ui-accordion ui-widget ui-helper-reset" )
  45.             // in lack of child-selectors in CSS
  46.             // we need to mark top-LIs in a UL-accordion for some IE-fix
  47.             .children( "li" )
  48.                 .addClass( "ui-accordion-li-fix" );
  49.  
  50.         self.headers = self.element.find( options.header )
  51.             .addClass( "ui-accordion-header ui-helper-reset ui-state-default ui-corner-all" )
  52.             .bind( "mouseenter.accordion", function() {
  53.                 if ( options.disabled ) {
  54.                     return;
  55.                 }
  56.                 $( this ).addClass( "ui-state-hover" );
  57.             })
  58.             .bind( "mouseleave.accordion", function() {
  59.                 if ( options.disabled ) {
  60.                     return;
  61.                 }
  62.                 $( this ).removeClass( "ui-state-hover" );
  63.             })
  64.             .bind( "focus.accordion", function() {
  65.                 if ( options.disabled ) {
  66.                     return;
  67.                 }
  68.                 $( this ).addClass( "ui-state-focus" );
  69.             })
  70.             .bind( "blur.accordion", function() {
  71.                 if ( options.disabled ) {
  72.                     return;
  73.                 }
  74.                 $( this ).removeClass( "ui-state-focus" );
  75.             });
  76.  
  77.         self.headers.next()
  78.             .addClass( "ui-accordion-content ui-helper-reset ui-widget-content ui-corner-bottom" );
  79.  
  80.         if ( options.navigation ) {
  81.             var current = self.element.find( "a" ).filter( options.navigationFilter ).eq( 0 );
  82.             if ( current.length ) {
  83.                 var header = current.closest( ".ui-accordion-header" );
  84.                 if ( header.length ) {
  85.                     // anchor within header
  86.                     self.active = header;
  87.                 } else {
  88.                     // anchor within content
  89.                     self.active = current.closest( ".ui-accordion-content" ).prev();
  90.                 }
  91.             }
  92.         }
  93.  
  94.         self.active = self._findActive( self.active || options.active )
  95.             .addClass( "ui-state-default ui-state-active" )
  96.             .toggleClass( "ui-corner-all 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.         $("#accordion").accordion({ active: false });
  335.  
  336.         // get the click target
  337.         var clicked = $( event.currentTarget || target ),
  338.             clickedIsActive = clicked[0] === this.active[0];
  339.  
  340.         // TODO the option is changed, is that correct?
  341.         // TODO if it is correct, shouldn't that happen after determining that the click is valid?
  342.         options.active = options.collapsible && clickedIsActive ?
  343.             false :
  344.             this.headers.index( clicked );
  345.  
  346.         // if animations are still active, or the active header is the target, ignore click
  347.         if ( this.running || ( !options.collapsible && clickedIsActive ) ) {
  348.             return;
  349.         }
  350.  
  351.         // switch classes
  352.         this.active
  353.             .removeClass( "ui-state-active ui-corner-top" )
  354.             .addClass( "ui-state-default ui-corner-all" )
  355.             .children( ".ui-icon" )
  356.                 .removeClass( options.icons.headerSelected )
  357.                 .addClass( options.icons.header );
  358.         if ( !clickedIsActive ) {
  359.             clicked
  360.                 .removeClass( "ui-state-default ui-corner-all" )
  361.                 .addClass( "ui-state-active ui-corner-top" )
  362.                 .children( ".ui-icon" )
  363.                     .removeClass( options.icons.header )
  364.                     .addClass( options.icons.headerSelected );
  365.             clicked
  366.                 .next()
  367.                 .addClass( "ui-accordion-content-active" );
  368.         }
  369.  
  370.         // find elements to show and hide
  371.         var toShow = clicked.next(),
  372.             toHide = this.active.next(),
  373.             data = {
  374.                 options: options,
  375.                 newHeader: clickedIsActive && options.collapsible ? $([]) : clicked,
  376.                 oldHeader: this.active,
  377.                 newContent: clickedIsActive && options.collapsible ? $([]) : toShow,
  378.                 oldContent: toHide
  379.             },
  380.             down = this.headers.index( this.active[0] ) > this.headers.index( clicked[0] );
  381.  
  382.         this.active = clickedIsActive ? $([]) : clicked;
  383.         this._toggle( toShow, toHide, data, clickedIsActive, down );
  384.  
  385.         return;
  386.     },
  387.  
  388.     _toggle: function( toShow, toHide, data, clickedIsActive, down ) {
  389.         var self = this,
  390.             options = self.options;
  391.  
  392.         self.toShow = toShow;
  393.         self.toHide = toHide;
  394.         self.data = data;
  395.  
  396.         var complete = function() {
  397.             if ( !self ) {
  398.                 return;
  399.             }
  400.             return self._completed.apply( self, arguments );
  401.         };
  402.  
  403.         // trigger changestart event
  404.         self._trigger( "changestart", null, self.data );
  405.  
  406.         // count elements to animate
  407.         self.running = toHide.size() === 0 ? toShow.size() : toHide.size();
  408.  
  409.         if ( options.animated ) {
  410.             var animOptions = {};
  411.  
  412.             if ( options.collapsible && clickedIsActive ) {
  413.                 animOptions = {
  414.                     toShow: $( [] ),
  415.                     toHide: toHide,
  416.                     complete: complete,
  417.                     down: down,
  418.                     autoHeight: options.autoHeight || options.fillSpace
  419.                 };
  420.             } else {
  421.                 animOptions = {
  422.                     toShow: toShow,
  423.                     toHide: toHide,
  424.                     complete: complete,
  425.                     down: down,
  426.                     autoHeight: options.autoHeight || options.fillSpace
  427.                 };
  428.             }
  429.  
  430.             if ( !options.proxied ) {
  431.                 options.proxied = options.animated;
  432.             }
  433.  
  434.             if ( !options.proxiedDuration ) {
  435.                 options.proxiedDuration = options.duration;
  436.             }
  437.  
  438.             options.animated = $.isFunction( options.proxied ) ?
  439.                 options.proxied( animOptions ) :
  440.                 options.proxied;
  441.  
  442.             options.duration = $.isFunction( options.proxiedDuration ) ?
  443.                 options.proxiedDuration( animOptions ) :
  444.                 options.proxiedDuration;
  445.  
  446.             var animations = $.ui.accordion.animations,
  447.                 duration = options.duration,
  448.                 easing = options.animated;
  449.  
  450.             if ( easing && !animations[ easing ] && !$.easing[ easing ] ) {
  451.                 easing = "slide";
  452.             }
  453.             if ( !animations[ easing ] ) {
  454.                 animations[ easing ] = function( options ) {
  455.                     this.slide( options, {
  456.                         easing: easing,
  457.                         duration: duration || 700
  458.                     });
  459.                 };
  460.             }
  461.  
  462.             animations[ easing ]( animOptions );
  463.         } else {
  464.             if ( options.collapsible && clickedIsActive ) {
  465.                 toShow.toggle();
  466.             } else {
  467.                 toHide.hide();
  468.                 toShow.show();
  469.             }
  470.  
  471.             complete( true );
  472.         }
  473.  
  474.         // TODO assert that the blur and focus triggers are really necessary, remove otherwise
  475.         toHide.prev()
  476.             .attr({
  477.                 "aria-expanded": "false",
  478.                 tabIndex: -1
  479.             })
  480.             .blur();
  481.         toShow.prev()
  482.             .attr({
  483.                 "aria-expanded": "true",
  484.                 tabIndex: 0
  485.             })
  486.             .focus();
  487.     },
  488.  
  489.     _completed: function( cancel ) {
  490.         this.running = cancel ? 0 : --this.running;
  491.         if ( this.running ) {
  492.             return;
  493.         }
  494.  
  495.         if ( this.options.clearStyle ) {
  496.             this.toShow.add( this.toHide ).css({
  497.                 height: "",
  498.                 overflow: ""
  499.             });
  500.         }
  501.  
  502.         // other classes are removed before the animation; this one needs to stay until completed
  503.         this.toHide.removeClass( "ui-accordion-content-active" );
  504.  
  505.         this._trigger( "change", null, this.data );
  506.     }
  507. });
  508.  
  509. $.extend( $.ui.accordion, {
  510.     version: "1.8.5",
  511.     animations: {
  512.         slide: function( options, additions ) {
  513.             options = $.extend({
  514.                 easing: "swing",
  515.                 duration: 300
  516.             }, options, additions );
  517.             if ( !options.toHide.size() ) {
  518.                 options.toShow.animate({
  519.                     height: "show",
  520.                     paddingTop: "show",
  521.                     paddingBottom: "show"
  522.                 }, options );
  523.                 return;
  524.             }
  525.             if ( !options.toShow.size() ) {
  526.                 options.toHide.animate({
  527.                     height: "hide",
  528.                     paddingTop: "hide",
  529.                     paddingBottom: "hide"
  530.                 }, options );
  531.                 return;
  532.             }
  533.             var overflow = options.toShow.css( "overflow" ),
  534.                 percentDone = 0,
  535.                 showProps = {},
  536.                 hideProps = {},
  537.                 fxAttrs = [ "height", "paddingTop", "paddingBottom" ],
  538.                 originalWidth;
  539.             // fix width before calculating height of hidden element
  540.             var s = options.toShow;
  541.             originalWidth = s[0].style.width;
  542.             s.width( parseInt( s.parent().width(), 10 )
  543.                 - parseInt( s.css( "paddingLeft" ), 10 )
  544.                 - parseInt( s.css( "paddingRight" ), 10 )
  545.                 - ( parseInt( s.css( "borderLeftWidth" ), 10 ) || 0 )
  546.                 - ( parseInt( s.css( "borderRightWidth" ), 10) || 0 ) );
  547.  
  548.             $.each( fxAttrs, function( i, prop ) {
  549.                 hideProps[ prop ] = "hide";
  550.  
  551.                 var parts = ( "" + $.css( options.toShow[0], prop ) ).match( /^([\d+-.]+)(.*)$/ );
  552.                 showProps[ prop ] = {
  553.                     value: parts[ 1 ],
  554.                     unit: parts[ 2 ] || "px"
  555.                 };
  556.             });
  557.             options.toShow.css({ height: 0, overflow: "hidden" }).show();
  558.             options.toHide
  559.                 .filter( ":hidden" )
  560.                     .each( options.complete )
  561.                 .end()
  562.                 .filter( ":visible" )
  563.                 .animate( hideProps, {
  564.                 step: function( now, settings ) {
  565.                     // only calculate the percent when animating height
  566.                     // IE gets very inconsistent results when animating elements
  567.                     // with small values, which is common for padding
  568.                     if ( settings.prop == "height" ) {
  569.                         percentDone = ( settings.end - settings.start === 0 ) ? 0 :
  570.                             ( settings.now - settings.start ) / ( settings.end - settings.start );
  571.                     }
  572.  
  573.                     options.toShow[ 0 ].style[ settings.prop ] =
  574.                         ( percentDone * showProps[ settings.prop ].value )
  575.                         + showProps[ settings.prop ].unit;
  576.                 },
  577.                 duration: options.duration,
  578.                 easing: options.easing,
  579.                 complete: function() {
  580.                     if ( !options.autoHeight ) {
  581.                         options.toShow.css( "height", "" );
  582.                     }
  583.                     options.toShow.css({
  584.                         width: originalWidth,
  585.                         overflow: overflow
  586.                     });
  587.                     options.complete();
  588.                 }
  589.             });
  590.         },
  591.         bounceslide: function( options ) {
  592.             this.slide( options, {
  593.                 easing: options.down ? "easeOutBounce" : "swing",
  594.                 duration: options.down ? 1000 : 200
  595.             });
  596.         }
  597.     }
  598. });
  599.  
  600. })( jQuery );
Advertisement
Add Comment
Please, Sign In to add comment