﻿/*
* Accordion 1.3 - jQuery menu widget
*
* Copyright (c) 2006 Jörn Zaefferer, Frank Marcia
*
* Dual licensed under the MIT and GPL licenses:
*   http://www.opensource.org/licenses/mit-license.php
*   http://www.gnu.org/licenses/gpl.html
*
* Revision: $Id: jquery.accordion.js 1524 2007-03-13 20:09:19Z joern $
*
*/

/**
* Make the selected elements Accordion widgets.
*
* Semantic requirements:
*
* If the structure of your container is flat with unique
* tags for header and content elements, eg. a definition list
* (dl > dt + dd), you don't have to specify any options at
* all.
*
* If your structure uses the same elements for header and
* content or uses some kind of nested structure, you have to
* specify the header elements, eg. via class, see the second example.
*
* Use activate(Number) to change the active content programmatically.
*
* A change event is triggered everytime the accordion changes. Apart from
* the event object, all arguments are jQuery objects.
* Arguments: event, newHeader, oldHeader, newContent, oldContent
*
* @example jQuery('#nav').Accordion();
* @before <dl id="nav">
*   <dt>Header 1</dt>
*   <dd>Content 1</dd>
*   <dt>Header 2</dt>
*   <dd>Content 2</dd>
* </dl>
* @desc Creates an Accordion from the given definition list
*
* @example jQuery('#nav').Accordion({
*   header: 'div.title'
* });
* @before <div id="nav">
*  <div>
*    <div class="title">Header 1</div>
*    <div>Content 1</div>
*  </div>
*  <div>
*    <div class="title">Header 2</div>
*    <div>Content 2</div>
*  </div>
* </div>
* @desc Creates an Accordion from the given div structure
*
* @example jQuery('#nav').Accordion({
*   header: 'a.head'
* });
* @before <ul id="nav">
*   <li>
*     <a class="head">Header 1</a>
*     <ul>
*       <li><a href="#">Link 1</a></li>
*       <li><a href="#">Link 2></a></li>
*     </ul>
*   </li>
*   <li>
*     <a class="head">Header 2</a>
*     <ul>
*       <li><a href="#">Link 3</a></li>
*       <li><a href="#">Link 4></a></li>
*     </ul>
*   </li>
* </ul>
* @desc Creates an Accordion from the given navigation list
*
* @example jQuery('#accordion').Accordion().change(function(event, newHeader, oldHeader, newContent, oldContent) {
*   jQuery('#status').html(newHeader.text());
* });
* @desc Updates the element with id status with the text of the selected header every time the accordion changes
*
* @param Map options key/value pairs of optional settings.
* @option String|Element|jQuery|Boolean active Selector for the active element, default is the first child, set to false to display none at start
* @option String|Element|jQuery header Selector for the header element, eg. div.title, a.head, default is the first child's tagname
* @option String|Number showSpeed Speed for the slideIn, default is 'slow' (for numbers: smaller = faster)
* @option String|Number hideSpeed Speed for the slideOut, default is 'fast' (for numbers: smaller = faster)
* @option String selectedClass Class for active header elements, default is 'selected'
* @option Boolean alwaysOpen Whether there must be one content element open, default is true.
* @option Boolean animated Set to false to disable animations. Default: true
* @option String event The event on which to trigger the accordion, eg. "mouseover". Default: "click"
*
* @type jQuery
* @see activate(Number)
* @name Accordion
* @cat Plugins/Accordion
*/

/**
* Activate a content part of the Accordion programmatically at the given zero-based index.
*
* If the index is not specified, it defaults to zero, if it is an invalid index, eg. a string,
* nothing happens.
*
* @example jQuery('#accordion').activate(1);
* @desc Activate the second content of the Accordion contained in <div id="accordion">.
*
* @example jQuery('#nav').activate();
* @desc Activate the first content of the Accordion contained in <ul id="nav">.
*
* @param Number index (optional) An Integer specifying the zero-based index of the content to be
*				 activated. Default: 0
*
* @type jQuery
* @name activate
* @cat Plugins/Accordion
*/

/**
* Override the default settings of the Accordion. Affects only following plugin calls.
*
* @example jQuery.Accordion.setDefaults({
* 	showSpeed: 1000,
* 	hideSpeed: 150
* });
*
* @param Map options key/value pairs of optional settings, see Accordion() for details
*
* @type jQuery
* @name setDefaults
* @cat Plugins/Accordion
*/

jQuery.fn.extend({
	// nextUntil is necessary, would be nice to have this in jQuery core
	nextUntil: function(expr) {
		var match = [];

		// We need to figure out which elements to push onto the array
		this.each(function() {
			// Traverse through the sibling nodes
			for (var i = this.nextSibling; i; i = i.nextSibling) {
				// Make sure that we're only dealing with elements
				if (i.nodeType != 1) continue;

				// If we find a match then we need to stop
				if (jQuery.filter(expr, [i]).r.length) break;

				// Otherwise, add it on to the stack
				match.push(i);
			}
		});

		return this.pushStack(match);
	},
	// the plugin method itself
	Accordion: function(settings) {
		// setup configuration
		settings = jQuery.extend({}, jQuery.Accordion.defaults, {
			// define context defaults
			header: jQuery(':first-child', this)[0].tagName // take first childs tagName as header
		}, settings);

		// calculate active if not specified, using the first header
		var container = this,
			active = settings.active
				? jQuery(settings.active, this)
				: settings.active === false
					? jQuery("<div>")
					: jQuery(settings.header, this).eq(0),
			running = 0;

		container.find(settings.header)
			.not(active || "")
			.nextUntil(settings.header)
			.hide();
		active.addClass(settings.selectedClass);

		function clickHandler(event) {
			// get the click target
			var clicked = jQuery(event.target);

			// due to the event delegation model, we have to check if one
			// of the parent elements is our actual header, and find that
			if (clicked.parents(settings.header).length) {
				while (!clicked.is(settings.header)) {
					clicked = clicked.parent();
				}
			}

			var clickedActive = clicked[0] == active[0];

			// if animations are still active, or the active header is the target, ignore click
			if (running || (settings.alwaysOpen && clickedActive) || !clicked.is(settings.header))
				return;

			// switch classes
			active.toggleClass(settings.selectedClass);
			if (!clickedActive) {
				clicked.addClass(settings.selectedClass);
			}

			// find elements to show and hide
			var toShow = clicked.nextUntil(settings.header),
				toHide = active.nextUntil(settings.header),
				data = [clicked, active, toShow, toHide];
			active = clickedActive ? jQuery([]) : clicked;
			// count elements to animate
			running = toHide.size() + toShow.size();
			var finished = function(cancel) {
				running = cancel ? 0 : --running;
				if (running)
					return;

				// trigger custom change event
				container.trigger("change", data);
			};
			// TODO if hideSpeed is set to zero, animations are crappy
			// workaround: use hide instead
			// solution: animate should check for speed of 0 and do something about it
			if (settings.animated) {
				if (!settings.alwaysOpen && clickedActive) {
					toShow.slideToggle(settings.showSpeed);
					finished(true);
				} else {
					toHide.filter(":hidden").each(finished).end().filter(":visible").slideUp(settings.hideSpeed, finished);
					toShow.slideDown(settings.showSpeed, finished);
				}
			} else {
				if (!settings.alwaysOpen && clickedActive) {
					toShow.toggle();
				} else {
					toHide.hide();
					toShow.show();
				}
				finished(true);
			}

			return false;
		};
		function activateHandlder(event, index) {
			// call clickHandler with custom event
			clickHandler({
				target: jQuery(settings.header, this)[index]
			});
		};

		return container
			.bind(settings.event, clickHandler)
			.bind("activate", activateHandlder);
	},
	// programmatic triggering
	activate: function(index) {
		return this.trigger('activate', [index || 0]);
	}
});

jQuery.Accordion = {};
jQuery.extend(jQuery.Accordion, {
	defaults: {
		selectedClass: "selected",
		showSpeed: 'slow',
		hideSpeed: 'fast',
		alwaysOpen: true,
		animated: true,
		event: "click"
	},
	setDefaults: function(settings) {
		jQuery.extend(jQuery.Accordion.defaults, settings);
	}
});
